summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 17:54:43 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 17:54:43 +0000
commite4283f6d48b98e764b988b43bbc86b9d52e6ec94 (patch)
treec8f7f7a6c2f5faa2942d27cefc6fd46cca492656 /src
parentInitial commit. (diff)
downloadgnome-shell-upstream.tar.xz
gnome-shell-upstream.zip
Adding upstream version 43.9.upstream/43.9upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r--src/calendar-server/README1
-rw-r--r--src/calendar-server/calendar-debug.h50
-rw-r--r--src/calendar-server/calendar-sources.c506
-rw-r--r--src/calendar-server/calendar-sources.h64
-rw-r--r--src/calendar-server/evolution-calendar.desktop.in8
-rw-r--r--src/calendar-server/gnome-shell-calendar-server.c1131
-rw-r--r--src/calendar-server/meson.build37
-rw-r--r--src/calendar-server/org.gnome.Shell.CalendarServer.service.in3
-rwxr-xr-xsrc/data-to-c.pl37
-rwxr-xr-xsrc/gnome-shell-extension-prefs31
-rwxr-xr-xsrc/gnome-shell-extension-tool.in59
-rwxr-xr-xsrc/gnome-shell-perf-tool.in326
-rw-r--r--src/gnome-shell-plugin.c394
-rw-r--r--src/gnome-shell-portal-helper.c52
-rw-r--r--src/gtkactionmuxer.c945
-rw-r--r--src/gtkactionmuxer.h64
-rw-r--r--src/gtkactionobservable.c78
-rw-r--r--src/gtkactionobservable.h60
-rw-r--r--src/gtkactionobserver.c189
-rw-r--r--src/gtkactionobserver.h91
-rw-r--r--src/hotplug-sniffer/hotplug-mimetypes.h141
-rw-r--r--src/hotplug-sniffer/hotplug-sniffer.c298
-rw-r--r--src/hotplug-sniffer/meson.build22
-rw-r--r--src/hotplug-sniffer/org.gnome.Shell.HotplugSniffer.service.in3
-rw-r--r--src/hotplug-sniffer/shell-mime-sniffer.c590
-rw-r--r--src/hotplug-sniffer/shell-mime-sniffer.h46
-rw-r--r--src/main.c599
-rw-r--r--src/meson.build276
-rw-r--r--src/org.gtk.Application.xml19
-rw-r--r--src/run-js-test.c118
-rw-r--r--src/shell-action-modes.h35
-rw-r--r--src/shell-app-cache-private.h19
-rw-r--r--src/shell-app-cache.c404
-rw-r--r--src/shell-app-private.h24
-rw-r--r--src/shell-app-system-private.h9
-rw-r--r--src/shell-app-system.c586
-rw-r--r--src/shell-app-system.h32
-rw-r--r--src/shell-app-usage.c774
-rw-r--r--src/shell-app-usage.h23
-rw-r--r--src/shell-app.c1756
-rw-r--r--src/shell-app.h83
-rw-r--r--src/shell-blur-effect.c907
-rw-r--r--src/shell-blur-effect.h57
-rw-r--r--src/shell-embedded-window-private.h20
-rw-r--r--src/shell-embedded-window.c247
-rw-r--r--src/shell-embedded-window.h19
-rw-r--r--src/shell-global-private.h23
-rw-r--r--src/shell-global.c1860
-rw-r--r--src/shell-global.h94
-rw-r--r--src/shell-glsl-effect.c205
-rw-r--r--src/shell-glsl-effect.h62
-rw-r--r--src/shell-gtk-embed.c364
-rw-r--r--src/shell-gtk-embed.h20
-rw-r--r--src/shell-invert-lightness-effect.c152
-rw-r--r--src/shell-invert-lightness-effect.h41
-rw-r--r--src/shell-keyring-prompt.c832
-rw-r--r--src/shell-keyring-prompt.h57
-rw-r--r--src/shell-mount-operation.c189
-rw-r--r--src/shell-mount-operation.h41
-rw-r--r--src/shell-network-agent.c899
-rw-r--r--src/shell-network-agent.h73
-rw-r--r--src/shell-perf-helper.c376
-rw-r--r--src/shell-perf-log.c959
-rw-r--r--src/shell-perf-log.h75
-rw-r--r--src/shell-polkit-authentication-agent.c429
-rw-r--r--src/shell-polkit-authentication-agent.h35
-rw-r--r--src/shell-screenshot.c1217
-rw-r--r--src/shell-screenshot.h90
-rw-r--r--src/shell-secure-text-buffer.c191
-rw-r--r--src/shell-secure-text-buffer.h39
-rw-r--r--src/shell-square-bin.c43
-rw-r--r--src/shell-square-bin.h13
-rw-r--r--src/shell-stack.c196
-rw-r--r--src/shell-stack.h11
-rw-r--r--src/shell-tray-icon.c294
-rw-r--r--src/shell-tray-icon.h16
-rw-r--r--src/shell-tray-manager.c374
-rw-r--r--src/shell-tray-manager.h22
-rw-r--r--src/shell-util.c854
-rw-r--r--src/shell-util.h93
-rw-r--r--src/shell-window-preview-layout.c495
-rw-r--r--src/shell-window-preview-layout.h33
-rw-r--r--src/shell-window-preview.c177
-rw-r--r--src/shell-window-preview.h14
-rw-r--r--src/shell-window-tracker-private.h11
-rw-r--r--src/shell-window-tracker.c811
-rw-r--r--src/shell-window-tracker.h29
-rw-r--r--src/shell-wm-private.h63
-rw-r--r--src/shell-wm.c462
-rw-r--r--src/shell-wm.h32
-rw-r--r--src/shell-workspace-background.c226
-rw-r--r--src/shell-workspace-background.h14
-rw-r--r--src/st/croco/cr-additional-sel.c498
-rw-r--r--src/st/croco/cr-additional-sel.h98
-rw-r--r--src/st/croco/cr-attr-sel.c234
-rw-r--r--src/st/croco/cr-attr-sel.h74
-rw-r--r--src/st/croco/cr-cascade.c215
-rw-r--r--src/st/croco/cr-cascade.h74
-rw-r--r--src/st/croco/cr-declaration.c798
-rw-r--r--src/st/croco/cr-declaration.h136
-rw-r--r--src/st/croco/cr-doc-handler.c276
-rw-r--r--src/st/croco/cr-doc-handler.h298
-rw-r--r--src/st/croco/cr-enc-handler.c184
-rw-r--r--src/st/croco/cr-enc-handler.h94
-rw-r--r--src/st/croco/cr-fonts.c948
-rw-r--r--src/st/croco/cr-fonts.h315
-rw-r--r--src/st/croco/cr-input.c1191
-rw-r--r--src/st/croco/cr-input.h174
-rw-r--r--src/st/croco/cr-num.c313
-rw-r--r--src/st/croco/cr-num.h127
-rw-r--r--src/st/croco/cr-om-parser.c1142
-rw-r--r--src/st/croco/cr-om-parser.h98
-rw-r--r--src/st/croco/cr-parser.c4539
-rw-r--r--src/st/croco/cr-parser.h128
-rw-r--r--src/st/croco/cr-parsing-location.c171
-rw-r--r--src/st/croco/cr-parsing-location.h70
-rw-r--r--src/st/croco/cr-prop-list.c404
-rw-r--r--src/st/croco/cr-prop-list.h80
-rw-r--r--src/st/croco/cr-pseudo.c166
-rw-r--r--src/st/croco/cr-pseudo.h64
-rw-r--r--src/st/croco/cr-rgb.c604
-rw-r--r--src/st/croco/cr-rgb.h84
-rw-r--r--src/st/croco/cr-selector.c305
-rw-r--r--src/st/croco/cr-selector.h95
-rw-r--r--src/st/croco/cr-simple-sel.c323
-rw-r--r--src/st/croco/cr-simple-sel.h130
-rw-r--r--src/st/croco/cr-statement.c2784
-rw-r--r--src/st/croco/cr-statement.h440
-rw-r--r--src/st/croco/cr-string.c168
-rw-r--r--src/st/croco/cr-string.h76
-rw-r--r--src/st/croco/cr-stylesheet.c177
-rw-r--r--src/st/croco/cr-stylesheet.h102
-rw-r--r--src/st/croco/cr-term.c786
-rw-r--r--src/st/croco/cr-term.h190
-rw-r--r--src/st/croco/cr-tknzr.c2762
-rw-r--r--src/st/croco/cr-tknzr.h115
-rw-r--r--src/st/croco/cr-token.c636
-rw-r--r--src/st/croco/cr-token.h212
-rw-r--r--src/st/croco/cr-utils.c1330
-rw-r--r--src/st/croco/cr-utils.h246
-rw-r--r--src/st/croco/libcroco-config.h13
-rw-r--r--src/st/croco/libcroco.h42
-rw-r--r--src/st/meson.build220
-rw-r--r--src/st/st-adjustment.c1013
-rw-r--r--src/st/st-adjustment.h92
-rw-r--r--src/st/st-bin.c404
-rw-r--r--src/st/st-bin.h53
-rw-r--r--src/st/st-border-image.c171
-rw-r--r--src/st/st-border-image.h54
-rw-r--r--src/st/st-box-layout.c307
-rw-r--r--src/st/st-box-layout.h65
-rw-r--r--src/st/st-button.c1043
-rw-r--r--src/st/st-button.h85
-rw-r--r--src/st/st-clipboard.c348
-rw-r--r--src/st/st-clipboard.h105
-rw-r--r--src/st/st-drawing-area.c242
-rw-r--r--src/st/st-drawing-area.h44
-rw-r--r--src/st/st-entry.c1626
-rw-r--r--src/st/st-entry.h79
-rw-r--r--src/st/st-focus-manager.c256
-rw-r--r--src/st/st-focus-manager.h65
-rw-r--r--src/st/st-generic-accessible.c246
-rw-r--r--src/st/st-generic-accessible.h62
-rw-r--r--src/st/st-icon-colors.c133
-rw-r--r--src/st/st-icon-colors.h43
-rw-r--r--src/st/st-icon.c833
-rw-r--r--src/st/st-icon.h82
-rw-r--r--src/st/st-image-content.c346
-rw-r--r--src/st/st-image-content.h33
-rw-r--r--src/st/st-label.c549
-rw-r--r--src/st/st-label.h58
-rw-r--r--src/st/st-password-entry.c363
-rw-r--r--src/st/st-password-entry.h46
-rw-r--r--src/st/st-private.c804
-rw-r--r--src/st/st-private.h75
-rw-r--r--src/st/st-scroll-bar.c1014
-rw-r--r--src/st/st-scroll-bar.h53
-rw-r--r--src/st/st-scroll-view-fade.c461
-rw-r--r--src/st/st-scroll-view-fade.glsl77
-rw-r--r--src/st/st-scroll-view-fade.h36
-rw-r--r--src/st/st-scroll-view.c1327
-rw-r--r--src/st/st-scroll-view.h90
-rw-r--r--src/st/st-scrollable.c196
-rw-r--r--src/st/st-scrollable.h59
-rw-r--r--src/st/st-settings.c451
-rw-r--r--src/st/st-settings.h42
-rw-r--r--src/st/st-shadow.c307
-rw-r--r--src/st/st-shadow.h95
-rw-r--r--src/st/st-texture-cache.c1688
-rw-r--r--src/st/st-texture-cache.h120
-rw-r--r--src/st/st-theme-context.c492
-rw-r--r--src/st/st-theme-context.h65
-rw-r--r--src/st/st-theme-node-drawing.c2864
-rw-r--r--src/st/st-theme-node-private.h131
-rw-r--r--src/st/st-theme-node-transition.c470
-rw-r--r--src/st/st-theme-node-transition.h58
-rw-r--r--src/st/st-theme-node.c4325
-rw-r--r--src/st/st-theme-node.h368
-rw-r--r--src/st/st-theme-private.h41
-rw-r--r--src/st/st-theme.c1085
-rw-r--r--src/st/st-theme.h51
-rw-r--r--src/st/st-types.h52
-rw-r--r--src/st/st-viewport.c600
-rw-r--r--src/st/st-viewport.h40
-rw-r--r--src/st/st-widget-accessible.h76
-rw-r--r--src/st/st-widget.c3049
-rw-r--r--src/st/st-widget.h167
-rw-r--r--src/st/st.h.in3
-rw-r--r--src/st/test-theme.c637
-rw-r--r--src/st/test-theme.css107
-rw-r--r--src/tray/meson.build12
-rw-r--r--src/tray/na-tray-child.c504
-rw-r--r--src/tray/na-tray-child.h66
-rw-r--r--src/tray/na-tray-manager.c888
-rw-r--r--src/tray/na-tray-manager.h106
215 files changed, 80586 insertions, 0 deletions
diff --git a/src/calendar-server/README b/src/calendar-server/README
new file mode 100644
index 0000000..ad9b5e3
--- /dev/null
+++ b/src/calendar-server/README
@@ -0,0 +1 @@
+Please keep in sync with gnome-panel.
diff --git a/src/calendar-server/calendar-debug.h b/src/calendar-server/calendar-debug.h
new file mode 100644
index 0000000..39befd7
--- /dev/null
+++ b/src/calendar-server/calendar-debug.h
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2004 Free Software Foundation, Inc.
+ *
+ * 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, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors:
+ * Mark McLoughlin <mark@skynet.ie>
+ */
+
+#ifndef __CALENDAR_DEBUG_H__
+#define __CALENDAR_DEBUG_H__
+
+#include <glib.h>
+
+G_BEGIN_DECLS
+
+#ifdef CALENDAR_ENABLE_DEBUG
+
+#include <stdio.h>
+
+#ifdef G_HAVE_ISO_VARARGS
+# define dprintf(...) fprintf (stderr, __VA_ARGS__);
+#elif defined(G_HAVE_GNUC_VARARGS)
+# define dprintf(args...) fprintf (stderr, args);
+#endif
+
+#else /* if !defined (CALENDAR_DEBUG) */
+
+#ifdef G_HAVE_ISO_VARARGS
+# define dprintf(...)
+#elif defined(G_HAVE_GNUC_VARARGS)
+# define dprintf(args...)
+#endif
+
+#endif /* CALENDAR_ENABLE_DEBUG */
+
+G_END_DECLS
+
+#endif /* __CALENDAR_DEBUG_H__ */
diff --git a/src/calendar-server/calendar-sources.c b/src/calendar-server/calendar-sources.c
new file mode 100644
index 0000000..9c25f4e
--- /dev/null
+++ b/src/calendar-server/calendar-sources.c
@@ -0,0 +1,506 @@
+/*
+ * Copyright (C) 2004 Free Software Foundation, Inc.
+ *
+ * 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, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors:
+ * Mark McLoughlin <mark@skynet.ie>
+ * William Jon McCann <mccann@jhu.edu>
+ * Martin Grimme <martin@pycage.de>
+ * Christian Kellner <gicmo@xatom.net>
+ */
+
+#include <config.h>
+
+#include "calendar-sources.h"
+
+#include <libintl.h>
+#include <string.h>
+#define HANDLE_LIBICAL_MEMORY
+#define EDS_DISABLE_DEPRECATED
+G_GNUC_BEGIN_IGNORE_DEPRECATIONS
+#include <libecal/libecal.h>
+G_GNUC_END_IGNORE_DEPRECATIONS
+
+#undef CALENDAR_ENABLE_DEBUG
+#include "calendar-debug.h"
+
+typedef struct _ClientData ClientData;
+typedef struct _CalendarSourceData CalendarSourceData;
+
+struct _ClientData
+{
+ ECalClient *client;
+ gulong backend_died_id;
+};
+
+typedef struct _CalendarSourcesPrivate CalendarSourcesPrivate;
+
+struct _CalendarSources
+{
+ GObject parent;
+
+ ESourceRegistryWatcher *registry_watcher;
+ gulong filter_id;
+ gulong appeared_id;
+ gulong disappeared_id;
+
+ GMutex clients_lock;
+ GHashTable *clients; /* ESource -> ClientData */
+};
+
+G_DEFINE_TYPE (CalendarSources, calendar_sources, G_TYPE_OBJECT)
+
+enum
+{
+ CLIENT_APPEARED,
+ CLIENT_DISAPPEARED,
+ LAST_SIGNAL
+};
+static guint signals [LAST_SIGNAL] = { 0, };
+
+static void
+calendar_sources_client_connected_cb (GObject *source_object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ CalendarSources *sources = CALENDAR_SOURCES (source_object);
+ ESource *source = user_data;
+ EClient *client;
+ g_autoptr (GError) error = NULL;
+
+ /* The calendar_sources_connect_client_sync() already stored the 'client'
+ * into the sources->clients */
+ client = calendar_sources_connect_client_finish (sources, result, &error);
+ if (error)
+ {
+ g_warning ("Could not load source '%s': %s",
+ e_source_get_uid (source),
+ error->message);
+ }
+ else
+ {
+ g_signal_emit (sources, signals[CLIENT_APPEARED], 0, client, NULL);
+ }
+
+ g_clear_object (&client);
+ g_clear_object (&source);
+}
+
+static gboolean
+registry_watcher_filter_cb (ESourceRegistryWatcher *watcher,
+ ESource *source,
+ CalendarSources *sources)
+{
+ return e_source_has_extension (source, E_SOURCE_EXTENSION_CALENDAR) &&
+ e_source_selectable_get_selected (e_source_get_extension (source, E_SOURCE_EXTENSION_CALENDAR));
+}
+
+static void
+registry_watcher_source_appeared_cb (ESourceRegistryWatcher *watcher,
+ ESource *source,
+ CalendarSources *sources)
+{
+ ECalClientSourceType source_type;
+
+ if (e_source_has_extension (source, E_SOURCE_EXTENSION_CALENDAR))
+ source_type = E_CAL_CLIENT_SOURCE_TYPE_EVENTS;
+ else if (e_source_has_extension (source, E_SOURCE_EXTENSION_MEMO_LIST))
+ source_type = E_CAL_CLIENT_SOURCE_TYPE_MEMOS;
+ else if (e_source_has_extension (source, E_SOURCE_EXTENSION_TASK_LIST))
+ source_type = E_CAL_CLIENT_SOURCE_TYPE_TASKS;
+ else
+ g_return_if_reached ();
+
+ calendar_sources_connect_client (sources, source, source_type, 30, NULL, calendar_sources_client_connected_cb, g_object_ref (source));
+}
+
+static void
+registry_watcher_source_disappeared_cb (ESourceRegistryWatcher *watcher,
+ ESource *source,
+ CalendarSources *sources)
+{
+ gboolean emit;
+
+ g_mutex_lock (&sources->clients_lock);
+
+ emit = g_hash_table_remove (sources->clients, source);
+
+ g_mutex_unlock (&sources->clients_lock);
+
+ if (emit)
+ g_signal_emit (sources, signals[CLIENT_DISAPPEARED], 0, e_source_get_uid (source), NULL);
+}
+
+static void
+client_data_free (ClientData *data)
+{
+ g_signal_handler_disconnect (data->client, data->backend_died_id);
+ g_object_unref (data->client);
+ g_free (data);
+}
+
+static void
+calendar_sources_constructed (GObject *object)
+{
+ CalendarSources *sources = CALENDAR_SOURCES (object);
+ ESourceRegistry *registry = NULL;
+ GError *error = NULL;
+
+ G_OBJECT_CLASS (calendar_sources_parent_class)->constructed (object);
+
+ registry = e_source_registry_new_sync (NULL, &error);
+ if (error != NULL)
+ {
+ /* Any error is fatal, but we don't want to crash gnome-shell-calendar-server
+ because of e-d-s problems. So just exit here.
+ */
+ g_warning ("Failed to start evolution-source-registry: %s", error->message);
+ exit (EXIT_FAILURE);
+ }
+
+ g_return_if_fail (registry != NULL);
+
+ sources->registry_watcher = e_source_registry_watcher_new (registry, NULL);
+
+ g_clear_object (&registry);
+
+ sources->clients = g_hash_table_new_full ((GHashFunc) e_source_hash,
+ (GEqualFunc) e_source_equal,
+ (GDestroyNotify) g_object_unref,
+ (GDestroyNotify) client_data_free);
+ sources->filter_id = g_signal_connect (sources->registry_watcher,
+ "filter",
+ G_CALLBACK (registry_watcher_filter_cb),
+ sources);
+ sources->appeared_id = g_signal_connect (sources->registry_watcher,
+ "appeared",
+ G_CALLBACK (registry_watcher_source_appeared_cb),
+ sources);
+ sources->disappeared_id = g_signal_connect (sources->registry_watcher,
+ "disappeared",
+ G_CALLBACK (registry_watcher_source_disappeared_cb),
+ sources);
+
+ e_source_registry_watcher_reclaim (sources->registry_watcher);
+}
+
+static void
+calendar_sources_finalize (GObject *object)
+{
+ CalendarSources *sources = CALENDAR_SOURCES (object);
+
+ g_clear_pointer (&sources->clients, g_hash_table_destroy);
+
+ if (sources->registry_watcher)
+ {
+ g_signal_handler_disconnect (sources->registry_watcher,
+ sources->filter_id);
+ g_signal_handler_disconnect (sources->registry_watcher,
+ sources->appeared_id);
+ g_signal_handler_disconnect (sources->registry_watcher,
+ sources->disappeared_id);
+ g_clear_object (&sources->registry_watcher);
+ }
+
+ g_mutex_clear (&sources->clients_lock);
+
+ G_OBJECT_CLASS (calendar_sources_parent_class)->finalize (object);
+}
+
+static void
+calendar_sources_class_init (CalendarSourcesClass *klass)
+{
+ GObjectClass *gobject_class = (GObjectClass *) klass;
+
+ gobject_class->constructed = calendar_sources_constructed;
+ gobject_class->finalize = calendar_sources_finalize;
+
+ signals [CLIENT_APPEARED] =
+ g_signal_new ("client-appeared",
+ G_TYPE_FROM_CLASS (gobject_class),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL,
+ NULL,
+ NULL,
+ G_TYPE_NONE,
+ 1,
+ E_TYPE_CAL_CLIENT);
+
+ signals [CLIENT_DISAPPEARED] =
+ g_signal_new ("client-disappeared",
+ G_TYPE_FROM_CLASS (gobject_class),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL,
+ NULL,
+ NULL,
+ G_TYPE_NONE,
+ 1,
+ G_TYPE_STRING); /* ESource::uid of the disappeared client */
+}
+
+static void
+calendar_sources_init (CalendarSources *sources)
+{
+ g_mutex_init (&sources->clients_lock);
+}
+
+CalendarSources *
+calendar_sources_get (void)
+{
+ static CalendarSources *calendar_sources_singleton = NULL;
+ gpointer singleton_location = &calendar_sources_singleton;
+
+ if (calendar_sources_singleton)
+ return g_object_ref (calendar_sources_singleton);
+
+ calendar_sources_singleton = g_object_new (CALENDAR_TYPE_SOURCES, NULL);
+ g_object_add_weak_pointer (G_OBJECT (calendar_sources_singleton),
+ singleton_location);
+
+ return calendar_sources_singleton;
+}
+
+ESourceRegistry *
+calendar_sources_get_registry (CalendarSources *sources)
+{
+ return e_source_registry_watcher_get_registry (sources->registry_watcher);
+}
+
+static void
+gather_event_clients_cb (gpointer key,
+ gpointer value,
+ gpointer user_data)
+{
+ GSList **plist = user_data;
+ ClientData *cd = value;
+
+ if (cd)
+ *plist = g_slist_prepend (*plist, g_object_ref (cd->client));
+}
+
+GSList *
+calendar_sources_ref_clients (CalendarSources *sources)
+{
+ GSList *list = NULL;
+
+ g_return_val_if_fail (CALENDAR_IS_SOURCES (sources), NULL);
+
+ g_mutex_lock (&sources->clients_lock);
+ g_hash_table_foreach (sources->clients, gather_event_clients_cb, &list);
+ g_mutex_unlock (&sources->clients_lock);
+
+ return list;
+}
+
+gboolean
+calendar_sources_has_clients (CalendarSources *sources)
+{
+ GHashTableIter iter;
+ gpointer value;
+ gboolean has = FALSE;
+
+ g_return_val_if_fail (CALENDAR_IS_SOURCES (sources), FALSE);
+
+ g_mutex_lock (&sources->clients_lock);
+
+ g_hash_table_iter_init (&iter, sources->clients);
+ while (!has && g_hash_table_iter_next (&iter, NULL, &value))
+ {
+ ClientData *cd = value;
+
+ has = cd != NULL;
+ }
+
+ g_mutex_unlock (&sources->clients_lock);
+
+ return has;
+}
+
+static void
+backend_died_cb (EClient *client,
+ CalendarSources *sources)
+{
+ ESource *source;
+ const char *display_name;
+
+ source = e_client_get_source (client);
+ display_name = e_source_get_display_name (source);
+ g_warning ("The calendar backend for '%s' has crashed.", display_name);
+ g_mutex_lock (&sources->clients_lock);
+ g_hash_table_remove (sources->clients, source);
+ g_mutex_unlock (&sources->clients_lock);
+}
+
+static EClient *
+calendar_sources_connect_client_sync (CalendarSources *sources,
+ ESource *source,
+ ECalClientSourceType source_type,
+ guint32 wait_for_connected_seconds,
+ GCancellable *cancellable,
+ GError **error)
+{
+ EClient *client = NULL;
+ ClientData *client_data;
+
+ g_mutex_lock (&sources->clients_lock);
+ client_data = g_hash_table_lookup (sources->clients, source);
+ if (client_data)
+ client = E_CLIENT (g_object_ref (client_data->client));
+ g_mutex_unlock (&sources->clients_lock);
+
+ if (client)
+ return client;
+
+ client = e_cal_client_connect_sync (source, source_type, wait_for_connected_seconds, cancellable, error);
+ if (!client)
+ return NULL;
+
+ g_mutex_lock (&sources->clients_lock);
+ client_data = g_hash_table_lookup (sources->clients, source);
+ if (client_data)
+ {
+ g_clear_object (&client);
+ client = E_CLIENT (g_object_ref (client_data->client));
+ }
+ else
+ {
+ client_data = g_new0 (ClientData, 1);
+ client_data->client = E_CAL_CLIENT (g_object_ref (client));
+ client_data->backend_died_id = g_signal_connect (client,
+ "backend-died",
+ G_CALLBACK (backend_died_cb),
+ sources);
+
+ g_hash_table_insert (sources->clients, g_object_ref (source), client_data);
+ }
+ g_mutex_unlock (&sources->clients_lock);
+
+ return client;
+}
+
+typedef struct _AsyncContext {
+ ESource *source;
+ ECalClientSourceType source_type;
+ guint32 wait_for_connected_seconds;
+} AsyncContext;
+
+static void
+async_context_free (gpointer ptr)
+{
+ AsyncContext *ctx = ptr;
+
+ if (ctx)
+ {
+ g_clear_object (&ctx->source);
+ g_free (ctx);
+ }
+}
+
+static void
+calendar_sources_connect_client_thread (GTask *task,
+ gpointer source_object,
+ gpointer task_data,
+ GCancellable *cancellable)
+{
+ CalendarSources *sources = source_object;
+ AsyncContext *ctx = task_data;
+ EClient *client;
+ GError *local_error = NULL;
+
+ client = calendar_sources_connect_client_sync (sources, ctx->source, ctx->source_type,
+ ctx->wait_for_connected_seconds, cancellable, &local_error);
+ if (!client)
+ {
+ if (local_error)
+ g_task_return_error (task, local_error);
+ else
+ g_task_return_pointer (task, NULL, NULL);
+ } else {
+ g_task_return_pointer (task, client, g_object_unref);
+ }
+}
+
+void
+calendar_sources_connect_client (CalendarSources *sources,
+ ESource *source,
+ ECalClientSourceType source_type,
+ guint32 wait_for_connected_seconds,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ AsyncContext *ctx;
+ g_autoptr (GTask) task = NULL;
+
+ ctx = g_new0 (AsyncContext, 1);
+ ctx->source = g_object_ref (source);
+ ctx->source_type = source_type;
+ ctx->wait_for_connected_seconds = wait_for_connected_seconds;
+
+ task = g_task_new (sources, cancellable, callback, user_data);
+ g_task_set_source_tag (task, calendar_sources_connect_client);
+ g_task_set_task_data (task, ctx, async_context_free);
+
+ g_task_run_in_thread (task, calendar_sources_connect_client_thread);
+}
+
+EClient *
+calendar_sources_connect_client_finish (CalendarSources *sources,
+ GAsyncResult *result,
+ GError **error)
+{
+ g_return_val_if_fail (g_task_is_valid (result, sources), NULL);
+ g_return_val_if_fail (g_async_result_is_tagged (result, calendar_sources_connect_client), NULL);
+
+ return g_task_propagate_pointer (G_TASK (result), error);
+}
+
+
+void
+print_debug (const gchar *format,
+ ...)
+{
+ g_autofree char *s = NULL;
+ g_autofree char *timestamp = NULL;
+ va_list ap;
+ g_autoptr (GDateTime) now = NULL;
+ static size_t once_init_value = 0;
+ static gboolean show_debug = FALSE;
+ static guint pid = 0;
+
+ if (g_once_init_enter (&once_init_value))
+ {
+ show_debug = (g_getenv ("CALENDAR_SERVER_DEBUG") != NULL);
+ pid = getpid ();
+ g_once_init_leave (&once_init_value, 1);
+ }
+
+ if (!show_debug)
+ goto out;
+
+ now = g_date_time_new_now_local ();
+ timestamp = g_date_time_format (now, "%H:%M:%S");
+
+ va_start (ap, format);
+ s = g_strdup_vprintf (format, ap);
+ va_end (ap);
+
+ g_print ("gnome-shell-calendar-server[%d]: %s.%03d: %s\n",
+ pid, timestamp, g_date_time_get_microsecond (now), s);
+ out:
+ ;
+}
diff --git a/src/calendar-server/calendar-sources.h b/src/calendar-server/calendar-sources.h
new file mode 100644
index 0000000..1ffc8ad
--- /dev/null
+++ b/src/calendar-server/calendar-sources.h
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2004 Free Software Foundation, Inc.
+ *
+ * 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, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors:
+ * Mark McLoughlin <mark@skynet.ie>
+ * William Jon McCann <mccann@jhu.edu>
+ * Martin Grimme <martin@pycage.de>
+ * Christian Kellner <gicmo@xatom.net>
+ */
+
+#ifndef __CALENDAR_SOURCES_H__
+#define __CALENDAR_SOURCES_H__
+
+#include <glib-object.h>
+
+#define EDS_DISABLE_DEPRECATED
+G_GNUC_BEGIN_IGNORE_DEPRECATIONS
+#include <libedataserver/libedataserver.h>
+#include <libecal/libecal.h>
+G_GNUC_END_IGNORE_DEPRECATIONS
+
+G_BEGIN_DECLS
+
+#define CALENDAR_TYPE_SOURCES (calendar_sources_get_type ())
+G_DECLARE_FINAL_TYPE (CalendarSources, calendar_sources,
+ CALENDAR, SOURCES, GObject)
+
+CalendarSources *calendar_sources_get (void);
+ESourceRegistry *calendar_sources_get_registry (CalendarSources *sources);
+GSList *calendar_sources_ref_clients (CalendarSources *sources);
+gboolean calendar_sources_has_clients (CalendarSources *sources);
+
+void calendar_sources_connect_client (CalendarSources *sources,
+ ESource *source,
+ ECalClientSourceType source_type,
+ guint32 wait_for_connected_seconds,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+EClient *calendar_sources_connect_client_finish
+ (CalendarSources *sources,
+ GAsyncResult *result,
+ GError **error);
+
+/* Set the environment variable CALENDAR_SERVER_DEBUG to show debug */
+void print_debug (const gchar *str,
+ ...) G_GNUC_PRINTF (1, 2);
+
+G_END_DECLS
+
+#endif /* __CALENDAR_SOURCES_H__ */
diff --git a/src/calendar-server/evolution-calendar.desktop.in b/src/calendar-server/evolution-calendar.desktop.in
new file mode 100644
index 0000000..1e34997
--- /dev/null
+++ b/src/calendar-server/evolution-calendar.desktop.in
@@ -0,0 +1,8 @@
+[Desktop Entry]
+Name=Evolution Calendar
+Exec=evolution -c calendar
+# Translators: Do NOT translate or transliterate this text (this is an icon file name)!
+Icon=evolution
+NoDisplay=true
+Type=Application
+StartupNotify=true
diff --git a/src/calendar-server/gnome-shell-calendar-server.c b/src/calendar-server/gnome-shell-calendar-server.c
new file mode 100644
index 0000000..4cd28d1
--- /dev/null
+++ b/src/calendar-server/gnome-shell-calendar-server.c
@@ -0,0 +1,1131 @@
+/*
+ * Copyright (C) 2011 Red Hat, Inc.
+ *
+ * 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, see <http://www.gnu.org/licenses/>.
+ *
+ * Author: David Zeuthen <davidz@redhat.com>
+ *
+ * Based on code from gnome-panel's clock-applet, file calendar-client.c, with Authors:
+ *
+ * Mark McLoughlin <mark@skynet.ie>
+ * William Jon McCann <mccann@jhu.edu>
+ * Martin Grimme <martin@pycage.de>
+ * Christian Kellner <gicmo@xatom.net>
+ *
+ */
+
+#include "config.h"
+
+#include <string.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <gio/gio.h>
+
+#define HANDLE_LIBICAL_MEMORY
+#define EDS_DISABLE_DEPRECATED
+G_GNUC_BEGIN_IGNORE_DEPRECATIONS
+#include <libecal/libecal.h>
+G_GNUC_END_IGNORE_DEPRECATIONS
+
+#include "calendar-sources.h"
+
+#define BUS_NAME "org.gnome.Shell.CalendarServer"
+
+static const gchar introspection_xml[] =
+ "<node>"
+ " <interface name='org.gnome.Shell.CalendarServer'>"
+ " <method name='SetTimeRange'>"
+ " <arg type='x' name='since' direction='in'/>"
+ " <arg type='x' name='until' direction='in'/>"
+ " <arg type='b' name='force_reload' direction='in'/>"
+ " </method>"
+ " <signal name='EventsAddedOrUpdated'>"
+ " <arg type='a(ssxxa{sv})' name='events' direction='out'/>"
+ " </signal>"
+ " <signal name='EventsRemoved'>"
+ " <arg type='as' name='ids' direction='out'/>"
+ " </signal>"
+ " <signal name='ClientDisappeared'>"
+ " <arg type='s' name='source_uid' direction='out'/>"
+ " </signal>"
+ " <property name='Since' type='x' access='read'/>"
+ " <property name='Until' type='x' access='read'/>"
+ " <property name='HasCalendars' type='b' access='read'/>"
+ " </interface>"
+ "</node>";
+static GDBusNodeInfo *introspection_data = NULL;
+
+struct _App;
+typedef struct _App App;
+
+static gboolean opt_replace = FALSE;
+static GOptionEntry opt_entries[] = {
+ {"replace", 0, 0, G_OPTION_ARG_NONE, &opt_replace, "Replace existing daemon", NULL},
+ {NULL }
+};
+static App *_global_app = NULL;
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/* While the UID is usually enough to identify an event,
+ * only the triple of (source,UID,RID) is fully unambiguous;
+ * neither may contain '\n', so we can safely use it to
+ * create a unique ID from the triple
+ */
+static gchar *
+create_event_id (const gchar *source_uid,
+ const gchar *comp_uid,
+ const gchar *comp_rid)
+{
+ return g_strconcat (
+ source_uid ? source_uid : "",
+ "\n",
+ comp_uid ? comp_uid : "",
+ "\n",
+ comp_rid ? comp_rid : "",
+ NULL);
+}
+
+typedef struct
+{
+ ECalClient *client;
+ GSList **pappointments; /* CalendarAppointment * */
+} CollectAppointmentsData;
+
+typedef struct
+{
+ gchar *id;
+ gchar *summary;
+ time_t start_time;
+ time_t end_time;
+} CalendarAppointment;
+
+static gboolean
+get_time_from_property (ECalClient *cal,
+ ICalComponent *icomp,
+ ICalPropertyKind prop_kind,
+ ICalTime * (* get_prop_func) (ICalProperty *prop),
+ ICalTimezone *default_zone,
+ ICalTime **out_itt,
+ ICalTimezone **out_timezone)
+{
+ ICalProperty *prop;
+ ICalTime *itt;
+ ICalTimezone *timezone = NULL;
+
+ prop = i_cal_component_get_first_property (icomp, prop_kind);
+ if (!prop)
+ return FALSE;
+
+ itt = get_prop_func (prop);
+
+ if (i_cal_time_is_utc (itt))
+ timezone = i_cal_timezone_get_utc_timezone ();
+ else
+ {
+ ICalParameter *param;
+
+ param = i_cal_property_get_first_parameter (prop, I_CAL_TZID_PARAMETER);
+ if (param && !e_cal_client_get_timezone_sync (cal, i_cal_parameter_get_tzid (param), &timezone, NULL, NULL))
+ print_debug ("Failed to get timezone '%s'\n", i_cal_parameter_get_tzid (param));
+
+ g_clear_object (&param);
+ }
+
+ if (timezone == NULL)
+ timezone = default_zone;
+
+ i_cal_time_set_timezone (itt, timezone);
+
+ g_clear_object (&prop);
+
+ *out_itt = itt;
+ *out_timezone = timezone;
+
+ return TRUE;
+}
+
+static inline time_t
+get_ical_start_time (ECalClient *cal,
+ ICalComponent *icomp,
+ ICalTimezone *default_zone)
+{
+ ICalTime *itt;
+ ICalTimezone *timezone;
+ time_t retval;
+
+ if (!get_time_from_property (cal,
+ icomp,
+ I_CAL_DTSTART_PROPERTY,
+ i_cal_property_get_dtstart,
+ default_zone,
+ &itt,
+ &timezone))
+ {
+ return 0;
+ }
+
+ retval = i_cal_time_as_timet_with_zone (itt, timezone);
+
+ g_clear_object (&itt);
+
+ return retval;
+}
+
+static inline time_t
+get_ical_end_time (ECalClient *cal,
+ ICalComponent *icomp,
+ ICalTimezone *default_zone)
+{
+ ICalTime *itt;
+ ICalTimezone *timezone;
+ time_t retval;
+
+ if (!get_time_from_property (cal,
+ icomp,
+ I_CAL_DTEND_PROPERTY,
+ i_cal_property_get_dtend,
+ default_zone,
+ &itt,
+ &timezone))
+ {
+ if (!get_time_from_property (cal,
+ icomp,
+ I_CAL_DTSTART_PROPERTY,
+ i_cal_property_get_dtstart,
+ default_zone,
+ &itt,
+ &timezone))
+ {
+ return 0;
+ }
+
+ if (i_cal_time_is_date (itt))
+ i_cal_time_adjust (itt, 1, 0, 0, 0);
+ }
+
+ retval = i_cal_time_as_timet_with_zone (itt, timezone);
+
+ g_clear_object (&itt);
+
+ return retval;
+}
+
+static CalendarAppointment *
+calendar_appointment_new (ECalClient *cal,
+ ECalComponent *comp)
+{
+ CalendarAppointment *appt;
+ ICalTimezone *default_zone;
+ ICalComponent *ical;
+ ECalComponentId *id;
+
+ default_zone = e_cal_client_get_default_timezone (cal);
+ ical = e_cal_component_get_icalcomponent (comp);
+ id = e_cal_component_get_id (comp);
+
+ appt = g_new0 (CalendarAppointment, 1);
+
+ appt->id = create_event_id (e_source_get_uid (e_client_get_source (E_CLIENT (cal))),
+ id ? e_cal_component_id_get_uid (id) : NULL,
+ id ? e_cal_component_id_get_rid (id) : NULL);
+ appt->summary = g_strdup (i_cal_component_get_summary (ical));
+ appt->start_time = get_ical_start_time (cal, ical, default_zone);
+ appt->end_time = get_ical_end_time (cal, ical, default_zone);
+
+ e_cal_component_id_free (id);
+
+ return appt;
+}
+
+static void
+calendar_appointment_free (gpointer ptr)
+{
+ CalendarAppointment *appt = ptr;
+
+ if (appt)
+ {
+ g_free (appt->id);
+ g_free (appt->summary);
+ g_free (appt);
+ }
+}
+
+static time_t
+timet_from_ical_time (ICalTime *time,
+ ICalTimezone *default_zone)
+{
+ ICalTimezone *timezone = NULL;
+
+ timezone = i_cal_time_get_timezone (time);
+ if (timezone == NULL)
+ timezone = default_zone;
+ return i_cal_time_as_timet_with_zone (time, timezone);
+}
+
+static gboolean
+generate_instances_cb (ICalComponent *icomp,
+ ICalTime *instance_start,
+ ICalTime *instance_end,
+ gpointer user_data,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CollectAppointmentsData *data = user_data;
+ CalendarAppointment *appointment;
+ ECalComponent *comp;
+ ICalTimezone *default_zone;
+
+ default_zone = e_cal_client_get_default_timezone (data->client);
+ comp = e_cal_component_new_from_icalcomponent (i_cal_component_clone (icomp));
+
+ appointment = calendar_appointment_new (data->client, comp);
+ appointment->start_time = timet_from_ical_time (instance_start, default_zone);
+ appointment->end_time = timet_from_ical_time (instance_end, default_zone);
+
+ *(data->pappointments) = g_slist_prepend (*(data->pappointments), appointment);
+
+ g_clear_object (&comp);
+
+ return TRUE;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+struct _App
+{
+ GDBusConnection *connection;
+
+ time_t since;
+ time_t until;
+
+ ICalTimezone *zone;
+
+ CalendarSources *sources;
+ gulong client_appeared_signal_id;
+ gulong client_disappeared_signal_id;
+
+ gchar *timezone_location;
+
+ GSList *notify_appointments; /* CalendarAppointment *, for EventsAdded */
+ GSList *notify_ids; /* gchar *, for EventsRemoved */
+
+ GSList *live_views;
+};
+
+static void
+app_update_timezone (App *app)
+{
+ g_autofree char *location = NULL;
+
+ location = e_cal_system_timezone_get_location ();
+ if (g_strcmp0 (location, app->timezone_location) != 0)
+ {
+ if (location == NULL)
+ app->zone = i_cal_timezone_get_utc_timezone ();
+ else
+ app->zone = i_cal_timezone_get_builtin_timezone (location);
+ g_free (app->timezone_location);
+ app->timezone_location = g_steal_pointer (&location);
+ print_debug ("Using timezone %s", app->timezone_location);
+ }
+}
+
+static void
+app_notify_events_added (App *app)
+{
+ GVariantBuilder builder, extras_builder;
+ GSList *events, *link;
+
+ events = g_slist_reverse (app->notify_appointments);
+ app->notify_appointments = NULL;
+
+ print_debug ("Emitting EventsAddedOrUpdated with %d events", g_slist_length (events));
+
+ if (!events)
+ return;
+
+ /* The a{sv} is used as an escape hatch in case we want to provide more
+ * information in the future without breaking ABI
+ */
+ g_variant_builder_init (&builder, G_VARIANT_TYPE ("a(ssxxa{sv})"));
+ for (link = events; link; link = g_slist_next (link))
+ {
+ CalendarAppointment *appt = link->data;
+ time_t start_time = appt->start_time;
+ time_t end_time = appt->end_time;
+
+ if ((start_time >= app->since &&
+ start_time < app->until) ||
+ (start_time <= app->since &&
+ (end_time - 1) > app->since))
+ {
+ g_variant_builder_init (&extras_builder, G_VARIANT_TYPE ("a{sv}"));
+ g_variant_builder_add (&builder,
+ "(ssxxa{sv})",
+ appt->id,
+ appt->summary != NULL ? appt->summary : "",
+ (gint64) start_time,
+ (gint64) end_time,
+ &extras_builder);
+ }
+ }
+
+ g_dbus_connection_emit_signal (app->connection,
+ NULL, /* destination_bus_name */
+ "/org/gnome/Shell/CalendarServer",
+ "org.gnome.Shell.CalendarServer",
+ "EventsAddedOrUpdated",
+ g_variant_new ("(a(ssxxa{sv}))", &builder),
+ NULL);
+
+ g_variant_builder_clear (&builder);
+
+ g_slist_free_full (events, calendar_appointment_free);
+}
+
+static void
+app_notify_events_removed (App *app)
+{
+ GVariantBuilder builder;
+ GSList *ids, *link;
+
+ ids = app->notify_ids;
+ app->notify_ids = NULL;
+
+ print_debug ("Emitting EventsRemoved with %d ids", g_slist_length (ids));
+
+ if (!ids)
+ return;
+
+ g_variant_builder_init (&builder, G_VARIANT_TYPE ("as"));
+ for (link = ids; link; link = g_slist_next (link))
+ {
+ const gchar *id = link->data;
+
+ g_variant_builder_add (&builder, "s", id);
+ }
+
+ g_dbus_connection_emit_signal (app->connection,
+ NULL, /* destination_bus_name */
+ "/org/gnome/Shell/CalendarServer",
+ "org.gnome.Shell.CalendarServer",
+ "EventsRemoved",
+ g_variant_new ("(as)", &builder),
+ NULL);
+ g_variant_builder_clear (&builder);
+
+ g_slist_free_full (ids, g_free);
+
+ return;
+}
+
+static void
+app_process_added_modified_objects (App *app,
+ ECalClientView *view,
+ GSList *objects) /* ICalComponent * */
+{
+ ECalClient *cal_client;
+ g_autoptr(GHashTable) covered_uids = NULL;
+ GSList *link;
+ gboolean expand_recurrences;
+
+ cal_client = e_cal_client_view_ref_client (view);
+ covered_uids = g_hash_table_new (g_str_hash, g_str_equal);
+ expand_recurrences = e_cal_client_get_source_type (cal_client) == E_CAL_CLIENT_SOURCE_TYPE_EVENTS;
+
+ for (link = objects; link; link = g_slist_next (link))
+ {
+ ECalComponent *comp;
+ ICalComponent *icomp = link->data;
+ const gchar *uid;
+ gboolean fallback = FALSE;
+
+ if (!icomp)
+ continue;
+
+ uid = i_cal_component_get_uid (icomp);
+ if (!uid || g_hash_table_contains (covered_uids, uid))
+ continue;
+
+ g_hash_table_add (covered_uids, (gpointer) uid);
+
+ if (expand_recurrences &&
+ !e_cal_util_component_is_instance (icomp) &&
+ e_cal_util_component_has_recurrences (icomp))
+ {
+ CollectAppointmentsData data;
+
+ data.client = cal_client;
+ data.pappointments = &app->notify_appointments;
+
+ e_cal_client_generate_instances_for_object_sync (cal_client, icomp, app->since, app->until, NULL,
+ generate_instances_cb, &data);
+ }
+ else if (expand_recurrences &&
+ e_cal_util_component_is_instance (icomp))
+ {
+ ICalComponent *main_comp = NULL;
+
+ /* Always pass whole series of the recurring events, because
+ * the calendar removes events with the same UID first. */
+ if (e_cal_client_get_object_sync (cal_client, uid, NULL, &main_comp, NULL, NULL))
+ {
+ CollectAppointmentsData data;
+
+ data.client = cal_client;
+ data.pappointments = &app->notify_appointments;
+
+ e_cal_client_generate_instances_for_object_sync (cal_client, main_comp, app->since, app->until, NULL,
+ generate_instances_cb, &data);
+
+ g_clear_object (&main_comp);
+ }
+ else
+ {
+ fallback = TRUE;
+ }
+ }
+ else
+ {
+ fallback = TRUE;
+ }
+
+ if (fallback)
+ {
+ comp = e_cal_component_new_from_icalcomponent (i_cal_component_clone (icomp));
+ if (!comp)
+ continue;
+
+ app->notify_appointments = g_slist_prepend (app->notify_appointments,
+ calendar_appointment_new (cal_client, comp));
+ g_object_unref (comp);
+ }
+ }
+
+ g_clear_object (&cal_client);
+
+ if (app->notify_appointments)
+ app_notify_events_added (app);
+}
+
+static void
+on_objects_added (ECalClientView *view,
+ GSList *objects,
+ gpointer user_data)
+{
+ App *app = user_data;
+ ECalClient *client;
+
+ client = e_cal_client_view_ref_client (view);
+ print_debug ("%s (%d) for calendar '%s'", G_STRFUNC, g_slist_length (objects), e_source_get_uid (e_client_get_source (E_CLIENT (client))));
+ g_clear_object (&client);
+
+ app_process_added_modified_objects (app, view, objects);
+}
+
+static void
+on_objects_modified (ECalClientView *view,
+ GSList *objects,
+ gpointer user_data)
+{
+ App *app = user_data;
+ ECalClient *client;
+
+ client = e_cal_client_view_ref_client (view);
+ print_debug ("%s (%d) for calendar '%s'", G_STRFUNC, g_slist_length (objects), e_source_get_uid (e_client_get_source (E_CLIENT (client))));
+ g_clear_object (&client);
+
+ app_process_added_modified_objects (app, view, objects);
+}
+
+static void
+on_objects_removed (ECalClientView *view,
+ GSList *uids,
+ gpointer user_data)
+{
+ App *app = user_data;
+ ECalClient *client;
+ GSList *link;
+ const gchar *source_uid;
+
+ client = e_cal_client_view_ref_client (view);
+ source_uid = e_source_get_uid (e_client_get_source (E_CLIENT (client)));
+
+ print_debug ("%s (%d) for calendar '%s'", G_STRFUNC, g_slist_length (uids), source_uid);
+
+ for (link = uids; link; link = g_slist_next (link))
+ {
+ ECalComponentId *id = link->data;
+
+ if (!id)
+ continue;
+
+ app->notify_ids = g_slist_prepend (app->notify_ids,
+ create_event_id (source_uid,
+ e_cal_component_id_get_uid (id),
+ e_cal_component_id_get_rid (id)));
+ }
+
+ g_clear_object (&client);
+
+ if (app->notify_ids)
+ app_notify_events_removed (app);
+}
+
+static gboolean
+app_has_calendars (App *app)
+{
+ return app->live_views != NULL;
+}
+
+static ECalClientView *
+app_start_view (App *app,
+ ECalClient *cal_client)
+{
+ g_autofree char *since_iso8601 = NULL;
+ g_autofree char *until_iso8601 = NULL;
+ g_autofree char *query = NULL;
+ const gchar *tz_location;
+ ECalClientView *view = NULL;
+ g_autoptr (GError) error = NULL;
+
+ if (app->since <= 0 || app->since >= app->until)
+ return NULL;
+
+ if (!app->since || !app->until)
+ {
+ print_debug ("Skipping load of events, no time interval set yet");
+ return NULL;
+ }
+
+ /* timezone could have changed */
+ app_update_timezone (app);
+
+ since_iso8601 = isodate_from_time_t (app->since);
+ until_iso8601 = isodate_from_time_t (app->until);
+ tz_location = i_cal_timezone_get_location (app->zone);
+
+ print_debug ("Loading events since %s until %s for calendar '%s'",
+ since_iso8601,
+ until_iso8601,
+ e_source_get_uid (e_client_get_source (E_CLIENT (cal_client))));
+
+ query = g_strdup_printf ("occur-in-time-range? (make-time \"%s\") "
+ "(make-time \"%s\") \"%s\"",
+ since_iso8601,
+ until_iso8601,
+ tz_location);
+
+ e_cal_client_set_default_timezone (cal_client, app->zone);
+
+ if (!e_cal_client_get_view_sync (cal_client, query, &view, NULL /* cancellable */, &error))
+ {
+ g_warning ("Error setting up live-query '%s' on calendar: %s\n", query, error ? error->message : "Unknown error");
+ view = NULL;
+ }
+ else
+ {
+ g_signal_connect (view,
+ "objects-added",
+ G_CALLBACK (on_objects_added),
+ app);
+ g_signal_connect (view,
+ "objects-modified",
+ G_CALLBACK (on_objects_modified),
+ app);
+ g_signal_connect (view,
+ "objects-removed",
+ G_CALLBACK (on_objects_removed),
+ app);
+ e_cal_client_view_start (view, NULL);
+ }
+
+ return view;
+}
+
+static void
+app_stop_view (App *app,
+ ECalClientView *view)
+{
+ e_cal_client_view_stop (view, NULL);
+
+ g_signal_handlers_disconnect_by_func (view, on_objects_added, app);
+ g_signal_handlers_disconnect_by_func (view, on_objects_modified, app);
+ g_signal_handlers_disconnect_by_func (view, on_objects_removed, app);
+}
+
+static void
+app_notify_has_calendars (App *app)
+{
+ GVariantBuilder dict_builder;
+
+ g_variant_builder_init (&dict_builder, G_VARIANT_TYPE ("a{sv}"));
+ g_variant_builder_add (&dict_builder, "{sv}", "HasCalendars",
+ g_variant_new_boolean (app_has_calendars (app)));
+
+ g_dbus_connection_emit_signal (app->connection,
+ NULL,
+ "/org/gnome/Shell/CalendarServer",
+ "org.freedesktop.DBus.Properties",
+ "PropertiesChanged",
+ g_variant_new ("(sa{sv}as)",
+ "org.gnome.Shell.CalendarServer",
+ &dict_builder,
+ NULL),
+ NULL);
+ g_variant_builder_clear (&dict_builder);
+}
+
+static void
+app_update_views (App *app)
+{
+ GSList *link, *clients;
+ gboolean had_views, has_views;
+
+ had_views = app->live_views != NULL;
+
+ for (link = app->live_views; link; link = g_slist_next (link))
+ {
+ app_stop_view (app, link->data);
+ }
+
+ g_slist_free_full (app->live_views, g_object_unref);
+ app->live_views = NULL;
+
+ clients = calendar_sources_ref_clients (app->sources);
+
+ for (link = clients; link; link = g_slist_next (link))
+ {
+ ECalClient *cal_client = link->data;
+ ECalClientView *view;
+
+ if (!cal_client)
+ continue;
+
+ view = app_start_view (app, cal_client);
+ if (view)
+ app->live_views = g_slist_prepend (app->live_views, view);
+ }
+
+ has_views = app->live_views != NULL;
+
+ if (has_views != had_views)
+ app_notify_has_calendars (app);
+
+ g_slist_free_full (clients, g_object_unref);
+}
+
+static void
+on_client_appeared_cb (CalendarSources *sources,
+ ECalClient *client,
+ gpointer user_data)
+{
+ App *app = user_data;
+ ECalClientView *view;
+ GSList *link;
+ const gchar *source_uid;
+
+ source_uid = e_source_get_uid (e_client_get_source (E_CLIENT (client)));
+
+ print_debug ("Client appeared '%s'", source_uid);
+
+ for (link = app->live_views; link; link = g_slist_next (link))
+ {
+ ECalClientView *view = link->data;
+ ECalClient *cal_client;
+ ESource *source;
+
+ cal_client = e_cal_client_view_ref_client (view);
+ source = e_client_get_source (E_CLIENT (cal_client));
+
+ if (g_strcmp0 (source_uid, e_source_get_uid (source)) == 0)
+ {
+ g_clear_object (&cal_client);
+ return;
+ }
+
+ g_clear_object (&cal_client);
+ }
+
+ view = app_start_view (app, client);
+
+ if (view)
+ {
+ app->live_views = g_slist_prepend (app->live_views, view);
+
+ /* It's the first view, notify that it has calendars now */
+ if (!g_slist_next (app->live_views))
+ app_notify_has_calendars (app);
+ }
+}
+
+static void
+on_client_disappeared_cb (CalendarSources *sources,
+ const gchar *source_uid,
+ gpointer user_data)
+{
+ App *app = user_data;
+ GSList *link;
+
+ print_debug ("Client disappeared '%s'", source_uid);
+
+ for (link = app->live_views; link; link = g_slist_next (link))
+ {
+ ECalClientView *view = link->data;
+ ECalClient *cal_client;
+ ESource *source;
+
+ cal_client = e_cal_client_view_ref_client (view);
+ source = e_client_get_source (E_CLIENT (cal_client));
+
+ if (g_strcmp0 (source_uid, e_source_get_uid (source)) == 0)
+ {
+ g_clear_object (&cal_client);
+ app_stop_view (app, view);
+ app->live_views = g_slist_remove (app->live_views, view);
+ g_object_unref (view);
+
+ print_debug ("Emitting ClientDisappeared for '%s'", source_uid);
+
+ g_dbus_connection_emit_signal (app->connection,
+ NULL, /* destination_bus_name */
+ "/org/gnome/Shell/CalendarServer",
+ "org.gnome.Shell.CalendarServer",
+ "ClientDisappeared",
+ g_variant_new ("(s)", source_uid),
+ NULL);
+
+ /* It was the last view, notify that it doesn't have calendars now */
+ if (!app->live_views)
+ app_notify_has_calendars (app);
+
+ break;
+ }
+
+ g_clear_object (&cal_client);
+ }
+}
+
+static App *
+app_new (GDBusConnection *connection)
+{
+ App *app;
+
+ app = g_new0 (App, 1);
+ app->connection = g_object_ref (connection);
+ app->sources = calendar_sources_get ();
+ app->client_appeared_signal_id = g_signal_connect (app->sources,
+ "client-appeared",
+ G_CALLBACK (on_client_appeared_cb),
+ app);
+ app->client_disappeared_signal_id = g_signal_connect (app->sources,
+ "client-disappeared",
+ G_CALLBACK (on_client_disappeared_cb),
+ app);
+
+ app_update_timezone (app);
+
+ return app;
+}
+
+static void
+app_free (App *app)
+{
+ GSList *ll;
+
+ for (ll = app->live_views; ll != NULL; ll = g_slist_next (ll))
+ {
+ ECalClientView *view = E_CAL_CLIENT_VIEW (ll->data);
+
+ app_stop_view (app, view);
+ }
+
+ g_signal_handler_disconnect (app->sources,
+ app->client_appeared_signal_id);
+ g_signal_handler_disconnect (app->sources,
+ app->client_disappeared_signal_id);
+
+ g_free (app->timezone_location);
+
+ g_slist_free_full (app->live_views, g_object_unref);
+ g_slist_free_full (app->notify_appointments, calendar_appointment_free);
+ g_slist_free_full (app->notify_ids, g_free);
+
+ g_object_unref (app->connection);
+ g_object_unref (app->sources);
+
+ g_free (app);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+handle_method_call (GDBusConnection *connection,
+ const gchar *sender,
+ const gchar *object_path,
+ const gchar *interface_name,
+ const gchar *method_name,
+ GVariant *parameters,
+ GDBusMethodInvocation *invocation,
+ gpointer user_data)
+{
+ App *app = user_data;
+
+ if (g_strcmp0 (method_name, "SetTimeRange") == 0)
+ {
+ gint64 since;
+ gint64 until;
+ gboolean force_reload = FALSE;
+ gboolean window_changed = FALSE;
+
+ g_variant_get (parameters,
+ "(xxb)",
+ &since,
+ &until,
+ &force_reload);
+
+ if (until < since)
+ {
+ g_dbus_method_invocation_return_dbus_error (invocation,
+ "org.gnome.Shell.CalendarServer.Error.Failed",
+ "until cannot be before since");
+ goto out;
+ }
+
+ print_debug ("Handling SetTimeRange (since=%" G_GINT64_FORMAT ", until=%" G_GINT64_FORMAT ", force_reload=%s)",
+ since,
+ until,
+ force_reload ? "true" : "false");
+
+ if (app->until != until || app->since != since)
+ {
+ GVariantBuilder *builder;
+ GVariantBuilder *invalidated_builder;
+
+ app->until = until;
+ app->since = since;
+ window_changed = TRUE;
+
+ builder = g_variant_builder_new (G_VARIANT_TYPE ("a{sv}"));
+ invalidated_builder = g_variant_builder_new (G_VARIANT_TYPE ("as"));
+ g_variant_builder_add (builder, "{sv}",
+ "Until", g_variant_new_int64 (app->until));
+ g_variant_builder_add (builder, "{sv}",
+ "Since", g_variant_new_int64 (app->since));
+ g_dbus_connection_emit_signal (app->connection,
+ NULL, /* destination_bus_name */
+ "/org/gnome/Shell/CalendarServer",
+ "org.freedesktop.DBus.Properties",
+ "PropertiesChanged",
+ g_variant_new ("(sa{sv}as)",
+ "org.gnome.Shell.CalendarServer",
+ builder,
+ invalidated_builder),
+ NULL); /* GError** */
+
+ g_variant_builder_unref (builder);
+ g_variant_builder_unref (invalidated_builder);
+ }
+
+ g_dbus_method_invocation_return_value (invocation, NULL);
+
+ if (window_changed || force_reload)
+ app_update_views (app);
+ }
+ else
+ {
+ g_assert_not_reached ();
+ }
+
+ out:
+ ;
+}
+
+static GVariant *
+handle_get_property (GDBusConnection *connection,
+ const gchar *sender,
+ const gchar *object_path,
+ const gchar *interface_name,
+ const gchar *property_name,
+ GError **error,
+ gpointer user_data)
+{
+ App *app = user_data;
+ GVariant *ret;
+
+ ret = NULL;
+ if (g_strcmp0 (property_name, "Since") == 0)
+ {
+ ret = g_variant_new_int64 (app->since);
+ }
+ else if (g_strcmp0 (property_name, "Until") == 0)
+ {
+ ret = g_variant_new_int64 (app->until);
+ }
+ else if (g_strcmp0 (property_name, "HasCalendars") == 0)
+ {
+ ret = g_variant_new_boolean (app_has_calendars (app));
+ }
+ else
+ {
+ g_assert_not_reached ();
+ }
+ return ret;
+}
+
+static const GDBusInterfaceVTable interface_vtable =
+{
+ handle_method_call,
+ handle_get_property,
+ NULL /* handle_set_property */
+};
+
+static void
+on_bus_acquired (GDBusConnection *connection,
+ const gchar *name,
+ gpointer user_data)
+{
+ GMainLoop *main_loop = user_data;
+ guint registration_id;
+ g_autoptr (GError) error = NULL;
+
+ _global_app = app_new (connection);
+
+ registration_id = g_dbus_connection_register_object (connection,
+ "/org/gnome/Shell/CalendarServer",
+ introspection_data->interfaces[0],
+ &interface_vtable,
+ _global_app,
+ NULL, /* user_data_free_func */
+ &error);
+ if (registration_id == 0)
+ {
+ g_printerr ("Error exporting object: %s (%s %d)\n",
+ error->message,
+ g_quark_to_string (error->domain),
+ error->code);
+ g_main_loop_quit (main_loop);
+ return;
+ }
+
+ print_debug ("Connected to the session bus");
+}
+
+static void
+on_name_lost (GDBusConnection *connection,
+ const gchar *name,
+ gpointer user_data)
+{
+ GMainLoop *main_loop = user_data;
+
+ g_print ("gnome-shell-calendar-server[%d]: Lost (or failed to acquire) the name " BUS_NAME " - exiting\n",
+ (gint) getpid ());
+ g_main_loop_quit (main_loop);
+}
+
+static void
+on_name_acquired (GDBusConnection *connection,
+ const gchar *name,
+ gpointer user_data)
+{
+ print_debug ("Acquired the name " BUS_NAME);
+}
+
+static gboolean
+stdin_channel_io_func (GIOChannel *source,
+ GIOCondition condition,
+ gpointer data)
+{
+ GMainLoop *main_loop = data;
+
+ if (condition & G_IO_HUP)
+ {
+ g_debug ("gnome-shell-calendar-server[%d]: Got HUP on stdin - exiting\n",
+ (gint) getpid ());
+ g_main_loop_quit (main_loop);
+ }
+ else
+ {
+ g_warning ("Unhandled condition %d on GIOChannel for stdin", condition);
+ }
+ return FALSE; /* remove source */
+}
+
+int
+main (int argc,
+ char **argv)
+{
+ g_autoptr (GError) error = NULL;
+ GOptionContext *opt_context;
+ GMainLoop *main_loop;
+ gint ret;
+ guint name_owner_id;
+ GIOChannel *stdin_channel;
+
+ ret = 1;
+ opt_context = NULL;
+ name_owner_id = 0;
+ stdin_channel = NULL;
+
+ introspection_data = g_dbus_node_info_new_for_xml (introspection_xml, NULL);
+ g_assert (introspection_data != NULL);
+
+ opt_context = g_option_context_new ("gnome-shell calendar server");
+ g_option_context_add_main_entries (opt_context, opt_entries, NULL);
+ if (!g_option_context_parse (opt_context, &argc, &argv, &error))
+ {
+ g_printerr ("Error parsing options: %s\n", error->message);
+ goto out;
+ }
+
+ main_loop = g_main_loop_new (NULL, FALSE);
+
+ stdin_channel = g_io_channel_unix_new (STDIN_FILENO);
+ g_io_add_watch_full (stdin_channel,
+ G_PRIORITY_DEFAULT,
+ G_IO_HUP,
+ stdin_channel_io_func,
+ g_main_loop_ref (main_loop),
+ (GDestroyNotify) g_main_loop_unref);
+
+ name_owner_id = g_bus_own_name (G_BUS_TYPE_SESSION,
+ BUS_NAME,
+ G_BUS_NAME_OWNER_FLAGS_ALLOW_REPLACEMENT |
+ (opt_replace ? G_BUS_NAME_OWNER_FLAGS_REPLACE : 0),
+ on_bus_acquired,
+ on_name_acquired,
+ on_name_lost,
+ g_main_loop_ref (main_loop),
+ (GDestroyNotify) g_main_loop_unref);
+
+ g_main_loop_run (main_loop);
+
+ g_main_loop_unref (main_loop);
+
+ ret = 0;
+
+ out:
+ if (stdin_channel != NULL)
+ g_io_channel_unref (stdin_channel);
+ if (_global_app != NULL)
+ app_free (_global_app);
+ if (name_owner_id != 0)
+ g_bus_unown_name (name_owner_id);
+ if (opt_context != NULL)
+ g_option_context_free (opt_context);
+
+ return ret;
+}
diff --git a/src/calendar-server/meson.build b/src/calendar-server/meson.build
new file mode 100644
index 0000000..8b4ef41
--- /dev/null
+++ b/src/calendar-server/meson.build
@@ -0,0 +1,37 @@
+calendar_sources = [
+ 'gnome-shell-calendar-server.c',
+ 'calendar-debug.h',
+ 'calendar-sources.c',
+ 'calendar-sources.h'
+]
+
+calendar_server = executable('gnome-shell-calendar-server', calendar_sources,
+ dependencies: [ecal_dep, eds_dep, gio_dep],
+ include_directories: include_directories('..', '../..'),
+ c_args: [
+ '-DPREFIX="@0@"'.format(prefix),
+ '-DLIBDIR="@0@"'.format(libdir),
+ '-DDATADIR="@0@"'.format(datadir),
+ '-DG_LOG_DOMAIN="ShellCalendarServer"'
+ ],
+ install_dir: libexecdir,
+ install: true
+)
+
+service_file = 'org.gnome.Shell.CalendarServer.service'
+
+configure_file(
+ input: service_file + '.in',
+ output: service_file,
+ configuration: service_data,
+ install_dir: servicedir
+)
+
+i18n.merge_file(
+ input: 'evolution-calendar.desktop.in',
+ output: 'evolution-calendar.desktop',
+ po_dir: po_dir,
+ install: true,
+ install_dir: desktopdir,
+ type: 'desktop'
+)
diff --git a/src/calendar-server/org.gnome.Shell.CalendarServer.service.in b/src/calendar-server/org.gnome.Shell.CalendarServer.service.in
new file mode 100644
index 0000000..5addce6
--- /dev/null
+++ b/src/calendar-server/org.gnome.Shell.CalendarServer.service.in
@@ -0,0 +1,3 @@
+[D-BUS Service]
+Name=org.gnome.Shell.CalendarServer
+Exec=@libexecdir@/gnome-shell-calendar-server
diff --git a/src/data-to-c.pl b/src/data-to-c.pl
new file mode 100755
index 0000000..69f7436
--- /dev/null
+++ b/src/data-to-c.pl
@@ -0,0 +1,37 @@
+#!/usr/bin/env perl
+
+# Copyright © 2011 Red Hat, Inc
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2 of the licence, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, see <http://www.gnu.org/licenses/>.
+#
+# Author: Kalev Lember <kalevlember@gmail.com>
+
+
+if (@ARGV != 2) {
+ die "Usage: data-to-c.pl <filename> <variable>\n";
+}
+
+$file = $ARGV[0];
+
+open (FILE, $file) || die "Cannot open $file: $!\n";
+
+printf ("const char %s[] = \"", $ARGV[1]);
+while (my $line = <FILE>) {
+ foreach my $c (split //, $line) {
+ printf ("\\x%02x", ord ($c));
+ }
+}
+print "\";\n";
+
+close (FILE);
diff --git a/src/gnome-shell-extension-prefs b/src/gnome-shell-extension-prefs
new file mode 100755
index 0000000..303b196
--- /dev/null
+++ b/src/gnome-shell-extension-prefs
@@ -0,0 +1,31 @@
+#!/bin/sh
+
+openPrefs() {
+ if [ "$(which gnome-extensions)" ]
+ then
+ gnome-extensions prefs $1
+ else
+ gdbus call --session \
+ --dest=org.gnome.Shell.Extensions \
+ --object-path=/org/gnome/Shell/Extensions \
+ --method=org.gnome.Shell.Extensions.OpenExtensionPrefs $1 '' '{}'
+ fi
+}
+
+cat >&2 <<EOT
+gnome-shell-extension-prefs is deprecated
+
+Install https://flathub.org/apps/details/org.gnome.Extensions for extension
+management, or use the gnome-extensions command line tool.
+
+Extensions can use the ExtensionUtils.openPrefs() method.
+EOT
+
+UUID=$1
+
+if [ "$UUID" ]
+then
+ openPrefs $UUID
+else
+ gapplication launch org.gnome.Extensions
+fi
diff --git a/src/gnome-shell-extension-tool.in b/src/gnome-shell-extension-tool.in
new file mode 100755
index 0000000..fb3d0d8
--- /dev/null
+++ b/src/gnome-shell-extension-tool.in
@@ -0,0 +1,59 @@
+#!@PYTHON@
+# -*- mode: Python; indent-tabs-mode: nil; -*-
+
+import subprocess
+import sys
+import optparse
+
+def extension_command(args):
+ print("gnome-shell-extension-tool is deprecated, use gnome-extensions instead",
+ file=sys.stderr)
+ subprocess.run(["@bindir@/gnome-extensions"] + args)
+
+def create_extension():
+ extension_command(["create", "--interactive"])
+
+def enable_extension(uuid):
+ extension_command(["enable", uuid])
+
+def disable_extension(uuid):
+ extension_command(["disable", uuid])
+
+def reload_extension(uuid):
+ print("Reloading extensions does not work correctly and is no longer supported",
+ file=sys.stderr)
+
+def main():
+ parser = optparse.OptionParser()
+ parser.add_option("-d", "--disable-extension", dest="disable",
+ help="Disable a GNOME Shell extension")
+ parser.add_option("-e", "--enable-extension", dest="enable",
+ help="Enable a GNOME Shell extension")
+ parser.add_option("-c", "--create-extension", dest="create", action="store_true",
+ help="Create a new GNOME Shell extension")
+ parser.add_option("-r", "--reload-extension", dest="reload",
+ help="Reload a GNOME Shell extension")
+ options, args = parser.parse_args()
+
+ if args:
+ parser.print_usage()
+ sys.exit(1)
+
+ if options.disable:
+ disable_extension(options.disable)
+
+ elif options.enable:
+ enable_extension(options.enable)
+
+ elif options.create:
+ create_extension()
+
+ elif options.reload:
+ reload_extension(options.reload)
+
+ else:
+ parser.print_usage()
+ sys.exit(1)
+
+if __name__ == "__main__":
+ main()
diff --git a/src/gnome-shell-perf-tool.in b/src/gnome-shell-perf-tool.in
new file mode 100755
index 0000000..a1b5d59
--- /dev/null
+++ b/src/gnome-shell-perf-tool.in
@@ -0,0 +1,326 @@
+#!@PYTHON@
+# -*- mode: Python; indent-tabs-mode: nil; -*-
+
+import datetime
+from gi.repository import GLib, GObject, Gio
+try:
+ import json
+except ImportError:
+ import simplejson as json
+import optparse
+import os
+import re
+import subprocess
+import sys
+import tempfile
+import base64
+from configparser import RawConfigParser
+import hashlib
+import hmac
+from http import client
+from urllib import parse
+
+def show_version(option, opt_str, value, parser):
+ print("GNOME Shell Performance Test @VERSION@")
+ sys.exit()
+
+def start_shell(perf_output=None):
+ # Set up environment
+ env = dict(os.environ)
+ env['SHELL_PERF_MODULE'] = options.perf
+
+ filters = ['Gnome-shell-perf-helper'] + options.extra_filter
+ env['MUTTER_WM_CLASS_FILTER'] = ','.join(filters)
+
+ if perf_output is not None:
+ env['SHELL_PERF_OUTPUT'] = perf_output
+
+ # A fixed background image
+ env['SHELL_BACKGROUND_IMAGE'] = '@pkgdatadir@/perf-background.xml'
+
+ self_dir = os.path.dirname(os.path.abspath(sys.argv[0]))
+ args = []
+ args.append(os.path.join(self_dir, 'gnome-shell'))
+
+ if options.replace:
+ args.append('--replace')
+
+ if options.wayland or options.nested:
+ args.append('--wayland')
+ if options.nested:
+ args.append('--nested')
+ else:
+ args.append('--display-server')
+ elif options.x11:
+ args.append('--x11')
+
+ return subprocess.Popen(args, env=env)
+
+def run_shell(perf_output=None):
+ # we do no additional supervision of gnome-shell,
+ # beyond that of wait
+ # in particular, we don't kill the shell upon
+ # receiving a KeyboardInterrupt, as we expect to be
+ # in the same process group
+ shell = start_shell(perf_output=perf_output)
+ shell.wait()
+ return shell.returncode == 0
+
+def restore_shell():
+ pid = os.fork()
+ if (pid == 0):
+ os.execlp("gnome-shell", "gnome-shell", "--replace")
+ else:
+ sys.exit(0)
+
+def upload_performance_report(report_text):
+ try:
+ config_home = os.environ['XDG_CONFIG_HOME']
+ except KeyError:
+ config_home = None
+
+ if not config_home:
+ config_home = os.path.expanduser("~/.config")
+
+ config_file = os.path.join(config_home, "gnome-shell/perf.ini")
+
+ try:
+ config = RawConfigParser()
+ f = open(config_file)
+ config.readfp(f)
+ f.close()
+
+ base_url = config.get('upload', 'url')
+ system_name = config.get('upload', 'name')
+ secret_key = config.get('upload', 'key')
+ except Exception as e:
+ print("Can't read upload configuration from %s: %s" % (config_file, str(e)))
+ sys.exit(1)
+
+ # Determine host, port and upload URL from provided data, we're
+ # a bit extra-careful about normalization since the URL is part
+ # of the signature.
+
+ split = parse.urlsplit(base_url)
+ scheme = split[0].lower()
+ netloc = split[1]
+ base_path = split[2]
+
+ m = re.match(r'^(.*?)(?::(\d+))?$', netloc)
+ if m.group(2):
+ host, port = m.group(1), int(m.group(2))
+ else:
+ host, port = m.group(1), None
+
+ if scheme != "http":
+ print("'%s' is not a HTTP URL" % base_url)
+ sys.exit(1)
+
+ if port is None:
+ port = 80
+
+ if base_path.endswith('/'):
+ base_path = base_path[:-1]
+
+ if port == 80:
+ normalized_base = "%s://%s%s" % (scheme, host, base_path)
+ else:
+ normalized_base = "%s://%s:%d%s" % (scheme, host, port, base_path)
+
+ upload_url = normalized_base + '/system/%s/upload' % system_name
+ upload_path = parse.urlsplit(upload_url)[2] # path portion
+
+ # Create signature based on upload URL and the report data
+
+ signature_data = 'POST&' + upload_url + "&&"
+ h = hmac.new(secret_key, digestmod=hashlib.sha1)
+ h.update(signature_data)
+ h.update(report_text)
+ signature = parse.quote(base64.b64encode(h.digest()), "~")
+
+ headers = {
+ 'User-Agent': 'gnome-shell-performance-tool/@VERSION@',
+ 'Content-Type': 'application/json',
+ 'X-Shell-Signature': 'HMAC-SHA1 ' + signature
+ };
+
+ connection = client.HTTPConnection(host, port)
+ connection.request('POST', upload_path, report_text, headers)
+ response = connection.getresponse()
+
+ if response.status == 200:
+ print("Performance report upload succeeded")
+ else:
+ print("Performance report upload failed with status %d" % response.status)
+ print(response.read())
+
+def gnome_hwtest_log(*args):
+ command = ['gnome-hwtest-log', '-t', 'gnome-shell-perf-tool']
+ command.extend(args)
+ subprocess.check_call(command)
+
+def run_performance_test():
+ iters = options.perf_iters
+ if options.perf_warmup:
+ iters += 1
+
+ logs = []
+ metric_summaries = {}
+
+ for i in range(0, iters):
+ # We create an empty temporary file that the shell will overwrite
+ # with the contents.
+ handle, output_file = tempfile.mkstemp(".json", "gnome-shell-perf.")
+ os.close(handle)
+
+ # Run the performance test and collect the output as JSON
+ normal_exit = False
+ try:
+ normal_exit = run_shell(perf_output=output_file)
+ except:
+ raise
+ finally:
+ if not normal_exit:
+ os.remove(output_file)
+
+ if not normal_exit:
+ return False
+
+ try:
+ f = open(output_file)
+ output = json.load(f)
+ f.close()
+ except:
+ raise
+ finally:
+ os.remove(output_file)
+
+ # Grab the event definitions and monitor layout the first time around
+ if i == 0:
+ events = output['events']
+ monitors = output['monitors']
+
+ if options.perf_warmup and i == 0:
+ continue
+
+ for metric in output['metrics']:
+ name = metric['name']
+ if not name in metric_summaries:
+ summary = {}
+ summary['description'] = metric['description']
+ summary['units'] = metric['units']
+ summary['values'] = []
+ metric_summaries[name] = summary
+ else:
+ summary = metric_summaries[name]
+
+ summary['values'].append(metric['value'])
+
+ logs.append(output['log'])
+
+ if options.perf_output or options.perf_upload:
+ # Write a complete report, formatted as JSON. The Javascript/C code that
+ # generates the individual reports we are summarizing here is very careful
+ # to format them nicely, but we just dump out a compressed no-whitespace
+ # version here for simplicity. Using json.dump(indent=0) doesn't real
+ # improve the readability of the output much.
+ report = {
+ 'date': datetime.datetime.utcnow().isoformat() + 'Z',
+ 'events': events,
+ 'monitors': monitors,
+ 'metrics': metric_summaries,
+ 'logs': logs
+ }
+
+ # Add the Git revision if available
+ self_dir = os.path.dirname(os.path.abspath(sys.argv[0]))
+ if os.path.exists(os.path.join(self_dir, 'gnome-shell-jhbuild.in')):
+ top_dir = os.path.dirname(self_dir)
+ git_dir = os.path.join(top_dir, '.git')
+ if os.path.exists(git_dir):
+ env = dict(os.environ)
+ env['GIT_DIR'] = git_dir
+ revision = subprocess.Popen(['git', 'rev-parse', 'HEAD'],
+ env=env,
+ stdout=subprocess.PIPE).communicate()[0].strip()
+ report['revision'] = revision
+
+ if options.perf_output:
+ f = open(options.perf_output, 'w')
+ json.dump(report, f)
+ f.close()
+
+ if options.perf_upload:
+ upload_performance_report(json.dumps(report))
+ elif options.hwtest:
+ # Log to systemd journal
+ for metric in sorted(metric_summaries.keys()):
+ summary = metric_summaries[metric]
+ gnome_hwtest_log('--metric=' + metric + '=' + str(summary['values'][0]) + summary['units'],
+ '--metric-description=' + summary['description'])
+ gnome_hwtest_log('--finished')
+ else:
+ # Write a human readable summary
+ print('------------------------------------------------------------')
+ for metric in sorted(metric_summaries.keys()):
+ summary = metric_summaries[metric]
+ print("#", summary['description'])
+ print(metric, ", ".join((str(x) for x in summary['values'])))
+ print('------------------------------------------------------------')
+
+ return True
+
+# Main program
+
+parser = optparse.OptionParser()
+parser.add_option("", "--perf", metavar="PERF_MODULE",
+ help="Specify the name of a performance module to run")
+parser.add_option("", "--perf-iters", type="int", metavar="ITERS",
+ help="Numbers of iterations of performance module to run",
+ default=1)
+parser.add_option("", "--perf-warmup", action="store_true",
+ help="Run a dry run before performance tests")
+parser.add_option("", "--perf-output", metavar="OUTPUT_FILE",
+ help="Output file to write performance report")
+parser.add_option("", "--perf-upload", action="store_true",
+ help="Upload performance report to server")
+parser.add_option("", "--extra-filter", action="append",
+ help="add an extra window class that should be allowed")
+parser.add_option("", "--hwtest", action="store_true",
+ help="Log results appropriately for GNOME Hardware Testing")
+parser.add_option("", "--version", action="callback", callback=show_version,
+ help="Display version and exit")
+
+parser.add_option("-r", "--replace", action="store_true",
+ help="Replace the running window manager")
+parser.add_option("-w", "--wayland", action="store_true",
+ help="Run as a Wayland compositor")
+parser.add_option("-n", "--nested", action="store_true",
+ help="Run as a Wayland nested compositor")
+parser.add_option("-x", "--x11", action="store_true",
+ help="Run as an X11 compositor")
+
+options, args = parser.parse_args()
+
+if options.perf == None:
+ if options.hwtest:
+ options.perf = 'hwtest'
+ else:
+ options.perf = 'core'
+
+if options.extra_filter is None:
+ options.extra_filter = []
+
+if options.perf == 'hwtest':
+ options.extra_filter.append('Gedit')
+
+if args:
+ parser.print_usage()
+ sys.exit(1)
+
+normal_exit = run_performance_test()
+if normal_exit:
+ if not options.hwtest:
+ restore_shell()
+else:
+ sys.exit(1)
diff --git a/src/gnome-shell-plugin.c b/src/gnome-shell-plugin.c
new file mode 100644
index 0000000..5364f04
--- /dev/null
+++ b/src/gnome-shell-plugin.c
@@ -0,0 +1,394 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+/*
+ * Copyright (c) 2008 Red Hat, Inc.
+ * Copyright (c) 2008 Intel Corp.
+ *
+ * Based on plugin skeleton by:
+ * Author: Tomas Frydrych <tf@linux.intel.com>
+ *
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+/*
+ * GnomeShellPlugin is the entry point for for GNOME Shell into and out of
+ * Mutter. By registering itself into Mutter using
+ * meta_plugin_manager_set_plugin_type(), Mutter will call the vfuncs of the
+ * plugin at the appropriate time.
+ *
+ * The functions in in GnomeShellPlugin are all just stubs, which just call the
+ * similar methods in GnomeShellWm.
+ */
+
+#include "config.h"
+
+#include <stdlib.h>
+#include <string.h>
+
+#include <clutter/clutter.h>
+#include <gjs/gjs.h>
+#include <meta/display.h>
+#include <meta/meta-plugin.h>
+#include <meta/meta-x11-display.h>
+#include <meta/util.h>
+
+#include "shell-global-private.h"
+#include "shell-perf-log.h"
+#include "shell-wm-private.h"
+
+#define GNOME_TYPE_SHELL_PLUGIN (gnome_shell_plugin_get_type ())
+G_DECLARE_FINAL_TYPE (GnomeShellPlugin, gnome_shell_plugin,
+ GNOME, SHELL_PLUGIN,
+ MetaPlugin)
+
+struct _GnomeShellPlugin
+{
+ MetaPlugin parent;
+
+ int glx_error_base;
+ int glx_event_base;
+ guint have_swap_event : 1;
+ CoglContext *cogl_context;
+
+ ShellGlobal *global;
+};
+
+G_DEFINE_TYPE (GnomeShellPlugin, gnome_shell_plugin, META_TYPE_PLUGIN)
+
+static gboolean
+gnome_shell_plugin_has_swap_event (GnomeShellPlugin *shell_plugin)
+{
+ CoglDisplay *cogl_display =
+ cogl_context_get_display (shell_plugin->cogl_context);
+ CoglRenderer *renderer = cogl_display_get_renderer (cogl_display);
+ const char * (* query_extensions_string) (Display *dpy, int screen);
+ Bool (* query_extension) (Display *dpy, int *error, int *event);
+ MetaDisplay *display = meta_plugin_get_display (META_PLUGIN (shell_plugin));
+ MetaX11Display *x11_display = meta_display_get_x11_display (display);
+ Display *xdisplay;
+ int screen_number;
+ const char *glx_extensions;
+
+ /* We will only get swap events if Cogl is using GLX */
+ if (cogl_renderer_get_winsys_id (renderer) != COGL_WINSYS_ID_GLX)
+ return FALSE;
+
+ xdisplay = meta_x11_display_get_xdisplay (x11_display);
+
+ query_extensions_string =
+ (void *) cogl_get_proc_address ("glXQueryExtensionsString");
+ query_extension =
+ (void *) cogl_get_proc_address ("glXQueryExtension");
+
+ query_extension (xdisplay,
+ &shell_plugin->glx_error_base,
+ &shell_plugin->glx_event_base);
+
+ screen_number = XDefaultScreen (xdisplay);
+ glx_extensions = query_extensions_string (xdisplay, screen_number);
+
+ return strstr (glx_extensions, "GLX_INTEL_swap_event") != NULL;
+}
+
+static void
+gnome_shell_plugin_start (MetaPlugin *plugin)
+{
+ GnomeShellPlugin *shell_plugin = GNOME_SHELL_PLUGIN (plugin);
+ GError *error = NULL;
+ uint8_t status;
+ GjsContext *gjs_context;
+ ClutterBackend *backend;
+
+ backend = clutter_get_default_backend ();
+ shell_plugin->cogl_context = clutter_backend_get_cogl_context (backend);
+
+ shell_plugin->have_swap_event =
+ gnome_shell_plugin_has_swap_event (shell_plugin);
+
+ shell_perf_log_define_event (shell_perf_log_get_default (),
+ "glx.swapComplete",
+ "GL buffer swap complete event received (with timestamp of completion)",
+ "x");
+
+ shell_plugin->global = shell_global_get ();
+ _shell_global_set_plugin (shell_plugin->global, META_PLUGIN (shell_plugin));
+
+ gjs_context = _shell_global_get_gjs_context (shell_plugin->global);
+
+ if (!gjs_context_eval_module_file (gjs_context,
+ "resource:///org/gnome/shell/ui/init.js",
+ &status,
+ &error))
+ {
+ g_message ("Execution of main.js threw exception: %s", error->message);
+ g_error_free (error);
+ /* We just exit() here, since in a development environment you'll get the
+ * error in your shell output, and it's way better than a busted WM,
+ * which typically manifests as a white screen.
+ *
+ * In production, we shouldn't crash =) But if we do, we should get
+ * restarted by the session infrastructure, which is likely going
+ * to be better than some undefined state.
+ *
+ * If there was a generic "hook into bug-buddy for non-C crashes"
+ * infrastructure, here would be the place to put it.
+ */
+ g_object_unref (gjs_context);
+ exit (1);
+ }
+}
+
+static ShellWM *
+get_shell_wm (void)
+{
+ ShellWM *wm;
+
+ g_object_get (shell_global_get (),
+ "window-manager", &wm,
+ NULL);
+ /* drop extra ref added by g_object_get */
+ g_object_unref (wm);
+
+ return wm;
+}
+
+static void
+gnome_shell_plugin_minimize (MetaPlugin *plugin,
+ MetaWindowActor *actor)
+{
+ _shell_wm_minimize (get_shell_wm (),
+ actor);
+
+}
+
+static void
+gnome_shell_plugin_unminimize (MetaPlugin *plugin,
+ MetaWindowActor *actor)
+{
+ _shell_wm_unminimize (get_shell_wm (),
+ actor);
+
+}
+
+static void
+gnome_shell_plugin_size_changed (MetaPlugin *plugin,
+ MetaWindowActor *actor)
+{
+ _shell_wm_size_changed (get_shell_wm (), actor);
+}
+
+static void
+gnome_shell_plugin_size_change (MetaPlugin *plugin,
+ MetaWindowActor *actor,
+ MetaSizeChange which_change,
+ MetaRectangle *old_frame_rect,
+ MetaRectangle *old_buffer_rect)
+{
+ _shell_wm_size_change (get_shell_wm (), actor, which_change, old_frame_rect, old_buffer_rect);
+}
+
+static void
+gnome_shell_plugin_map (MetaPlugin *plugin,
+ MetaWindowActor *actor)
+{
+ _shell_wm_map (get_shell_wm (),
+ actor);
+}
+
+static void
+gnome_shell_plugin_destroy (MetaPlugin *plugin,
+ MetaWindowActor *actor)
+{
+ _shell_wm_destroy (get_shell_wm (),
+ actor);
+}
+
+static void
+gnome_shell_plugin_switch_workspace (MetaPlugin *plugin,
+ gint from,
+ gint to,
+ MetaMotionDirection direction)
+{
+ _shell_wm_switch_workspace (get_shell_wm(), from, to, direction);
+}
+
+static void
+gnome_shell_plugin_kill_window_effects (MetaPlugin *plugin,
+ MetaWindowActor *actor)
+{
+ _shell_wm_kill_window_effects (get_shell_wm(), actor);
+}
+
+static void
+gnome_shell_plugin_kill_switch_workspace (MetaPlugin *plugin)
+{
+ _shell_wm_kill_switch_workspace (get_shell_wm());
+}
+
+static void
+gnome_shell_plugin_show_tile_preview (MetaPlugin *plugin,
+ MetaWindow *window,
+ MetaRectangle *tile_rect,
+ int tile_monitor)
+{
+ _shell_wm_show_tile_preview (get_shell_wm (), window, tile_rect, tile_monitor);
+}
+
+static void
+gnome_shell_plugin_hide_tile_preview (MetaPlugin *plugin)
+{
+ _shell_wm_hide_tile_preview (get_shell_wm ());
+}
+
+static void
+gnome_shell_plugin_show_window_menu (MetaPlugin *plugin,
+ MetaWindow *window,
+ MetaWindowMenuType menu,
+ int x,
+ int y)
+{
+ _shell_wm_show_window_menu (get_shell_wm (), window, menu, x, y);
+}
+
+static void
+gnome_shell_plugin_show_window_menu_for_rect (MetaPlugin *plugin,
+ MetaWindow *window,
+ MetaWindowMenuType menu,
+ MetaRectangle *rect)
+{
+ _shell_wm_show_window_menu_for_rect (get_shell_wm (), window, menu, rect);
+}
+
+static gboolean
+gnome_shell_plugin_xevent_filter (MetaPlugin *plugin,
+ XEvent *xev)
+{
+#ifdef GLX_INTEL_swap_event
+ GnomeShellPlugin *shell_plugin = GNOME_SHELL_PLUGIN (plugin);
+
+ if (shell_plugin->have_swap_event &&
+ xev->type == (shell_plugin->glx_event_base + GLX_BufferSwapComplete))
+ {
+ GLXBufferSwapComplete *swap_complete_event;
+ swap_complete_event = (GLXBufferSwapComplete *)xev;
+
+ /* Buggy early versions of the INTEL_swap_event implementation in Mesa
+ * can send this with a ust of 0. Simplify life for consumers
+ * by ignoring such events */
+ if (swap_complete_event->ust != 0)
+ {
+ gboolean frame_timestamps;
+ g_object_get (shell_plugin->global,
+ "frame-timestamps", &frame_timestamps,
+ NULL);
+
+ if (frame_timestamps)
+ shell_perf_log_event_x (shell_perf_log_get_default (),
+ "glx.swapComplete",
+ swap_complete_event->ust);
+ }
+ }
+#endif
+
+ return FALSE;
+}
+
+static gboolean
+gnome_shell_plugin_keybinding_filter (MetaPlugin *plugin,
+ MetaKeyBinding *binding)
+{
+ return _shell_wm_filter_keybinding (get_shell_wm (), binding);
+}
+
+static void
+gnome_shell_plugin_confirm_display_change (MetaPlugin *plugin)
+{
+ _shell_wm_confirm_display_change (get_shell_wm ());
+}
+
+static const MetaPluginInfo *
+gnome_shell_plugin_plugin_info (MetaPlugin *plugin)
+{
+ static const MetaPluginInfo info = {
+ .name = "GNOME Shell",
+ .version = "0.1",
+ .author = "Various",
+ .license = "GPLv2+",
+ .description = "Provides GNOME Shell core functionality"
+ };
+
+ return &info;
+}
+
+static MetaCloseDialog *
+gnome_shell_plugin_create_close_dialog (MetaPlugin *plugin,
+ MetaWindow *window)
+{
+ return _shell_wm_create_close_dialog (get_shell_wm (), window);
+}
+
+static MetaInhibitShortcutsDialog *
+gnome_shell_plugin_create_inhibit_shortcuts_dialog (MetaPlugin *plugin,
+ MetaWindow *window)
+{
+ return _shell_wm_create_inhibit_shortcuts_dialog (get_shell_wm (), window);
+}
+
+static void
+gnome_shell_plugin_locate_pointer (MetaPlugin *plugin)
+{
+ GnomeShellPlugin *shell_plugin = GNOME_SHELL_PLUGIN (plugin);
+ _shell_global_locate_pointer (shell_plugin->global);
+}
+
+static void
+gnome_shell_plugin_class_init (GnomeShellPluginClass *klass)
+{
+ MetaPluginClass *plugin_class = META_PLUGIN_CLASS (klass);
+
+ plugin_class->start = gnome_shell_plugin_start;
+ plugin_class->map = gnome_shell_plugin_map;
+ plugin_class->minimize = gnome_shell_plugin_minimize;
+ plugin_class->unminimize = gnome_shell_plugin_unminimize;
+ plugin_class->size_changed = gnome_shell_plugin_size_changed;
+ plugin_class->size_change = gnome_shell_plugin_size_change;
+ plugin_class->destroy = gnome_shell_plugin_destroy;
+
+ plugin_class->switch_workspace = gnome_shell_plugin_switch_workspace;
+
+ plugin_class->kill_window_effects = gnome_shell_plugin_kill_window_effects;
+ plugin_class->kill_switch_workspace = gnome_shell_plugin_kill_switch_workspace;
+
+ plugin_class->show_tile_preview = gnome_shell_plugin_show_tile_preview;
+ plugin_class->hide_tile_preview = gnome_shell_plugin_hide_tile_preview;
+ plugin_class->show_window_menu = gnome_shell_plugin_show_window_menu;
+ plugin_class->show_window_menu_for_rect = gnome_shell_plugin_show_window_menu_for_rect;
+
+ plugin_class->xevent_filter = gnome_shell_plugin_xevent_filter;
+ plugin_class->keybinding_filter = gnome_shell_plugin_keybinding_filter;
+
+ plugin_class->confirm_display_change = gnome_shell_plugin_confirm_display_change;
+
+ plugin_class->plugin_info = gnome_shell_plugin_plugin_info;
+
+ plugin_class->create_close_dialog = gnome_shell_plugin_create_close_dialog;
+ plugin_class->create_inhibit_shortcuts_dialog = gnome_shell_plugin_create_inhibit_shortcuts_dialog;
+
+ plugin_class->locate_pointer = gnome_shell_plugin_locate_pointer;
+}
+
+static void
+gnome_shell_plugin_init (GnomeShellPlugin *shell_plugin)
+{
+}
diff --git a/src/gnome-shell-portal-helper.c b/src/gnome-shell-portal-helper.c
new file mode 100644
index 0000000..a0bebb2
--- /dev/null
+++ b/src/gnome-shell-portal-helper.c
@@ -0,0 +1,52 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+#include "config.h"
+
+#include <gjs/gjs.h>
+#include <glib/gi18n.h>
+
+int
+main (int argc, char *argv[])
+{
+ const char *search_path[] = { "resource:///org/gnome/shell", NULL };
+ GError *error = NULL;
+ GjsContext *context;
+ int status;
+
+ bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR);
+ bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
+ textdomain (GETTEXT_PACKAGE);
+
+ context = g_object_new (GJS_TYPE_CONTEXT,
+ "search-path", search_path,
+ NULL);
+
+ if (!gjs_context_define_string_array(context, "ARGV",
+ argc, (const char**)argv,
+ &error))
+ {
+ g_message("Failed to define ARGV: %s", error->message);
+ g_error_free (error);
+ g_object_unref (context);
+
+ return 1;
+ }
+
+
+ if (!gjs_context_eval (context,
+ "const Main = imports.portalHelper.main; Main.main(ARGV);",
+ -1,
+ "<main>",
+ &status,
+ &error))
+ {
+ g_message ("Execution of main.js threw exception: %s", error->message);
+ g_error_free (error);
+ g_object_unref (context);
+
+ return status;
+ }
+
+ g_object_unref (context);
+ return 0;
+}
diff --git a/src/gtkactionmuxer.c b/src/gtkactionmuxer.c
new file mode 100644
index 0000000..7e3e86e
--- /dev/null
+++ b/src/gtkactionmuxer.c
@@ -0,0 +1,945 @@
+/*
+ * Copyright © 2011 Canonical Limited
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the licence, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Author: Ryan Lortie <desrt@desrt.ca>
+ */
+
+#include "config.h"
+
+#include "gtkactionmuxer.h"
+
+#include "gtkactionobservable.h"
+#include "gtkactionobserver.h"
+
+#include <clutter/clutter.h>
+
+#include <string.h>
+
+/**
+ * SECTION:gtkactionmuxer
+ * @short_description: Aggregate and monitor several action groups
+ *
+ * #GtkActionMuxer is a #GActionGroup and #GtkActionObservable that is
+ * capable of containing other #GActionGroup instances.
+ *
+ * The typical use is aggregating all of the actions applicable to a
+ * particular context into a single action group, with namespacing.
+ *
+ * Consider the case of two action groups -- one containing actions
+ * applicable to an entire application (such as 'quit') and one
+ * containing actions applicable to a particular window in the
+ * application (such as 'fullscreen').
+ *
+ * In this case, each of these action groups could be added to a
+ * #GtkActionMuxer with the prefixes "app" and "win", respectively. This
+ * would expose the actions as "app.quit" and "win.fullscreen" on the
+ * #GActionGroup interface presented by the #GtkActionMuxer.
+ *
+ * Activations and state change requests on the #GtkActionMuxer are wired
+ * through to the underlying action group in the expected way.
+ *
+ * This class is typically only used at the site of "consumption" of
+ * actions (eg: when displaying a menu that contains many actions on
+ * different objects).
+ */
+
+static void gtk_action_muxer_group_iface_init (GActionGroupInterface *iface);
+static void gtk_action_muxer_observable_iface_init (GtkActionObservableInterface *iface);
+
+typedef GObjectClass GtkActionMuxerClass;
+
+struct _GtkActionMuxer
+{
+ GObject parent_instance;
+
+ GHashTable *observed_actions;
+ GHashTable *groups;
+ GHashTable *primary_accels;
+ GtkActionMuxer *parent;
+};
+
+G_DEFINE_TYPE_WITH_CODE (GtkActionMuxer, gtk_action_muxer, G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (G_TYPE_ACTION_GROUP, gtk_action_muxer_group_iface_init)
+ G_IMPLEMENT_INTERFACE (GTK_TYPE_ACTION_OBSERVABLE, gtk_action_muxer_observable_iface_init))
+
+enum
+{
+ PROP_0,
+ PROP_PARENT,
+ NUM_PROPERTIES
+};
+
+static GParamSpec *properties[NUM_PROPERTIES];
+
+guint accel_signal;
+
+typedef struct
+{
+ GtkActionMuxer *muxer;
+ GSList *watchers;
+ gchar *fullname;
+} Action;
+
+typedef struct
+{
+ GtkActionMuxer *muxer;
+ GActionGroup *group;
+ gchar *prefix;
+ gulong handler_ids[4];
+} Group;
+
+static void
+gtk_action_muxer_append_group_actions (gpointer key,
+ gpointer value,
+ gpointer user_data)
+{
+ const gchar *prefix = key;
+ Group *group = value;
+ GArray *actions = user_data;
+ gchar **group_actions;
+ gchar **action;
+
+ group_actions = g_action_group_list_actions (group->group);
+ for (action = group_actions; *action; action++)
+ {
+ gchar *fullname;
+
+ fullname = g_strconcat (prefix, ".", *action, NULL);
+ g_array_append_val (actions, fullname);
+ }
+
+ g_strfreev (group_actions);
+}
+
+static gchar **
+gtk_action_muxer_list_actions (GActionGroup *action_group)
+{
+ GtkActionMuxer *muxer = GTK_ACTION_MUXER (action_group);
+ GArray *actions;
+
+ actions = g_array_new (TRUE, FALSE, sizeof (gchar *));
+
+ for ( ; muxer != NULL; muxer = muxer->parent)
+ {
+ g_hash_table_foreach (muxer->groups,
+ gtk_action_muxer_append_group_actions,
+ actions);
+ }
+
+ return (gchar **)(void *) g_array_free (actions, FALSE);
+}
+
+static Group *
+gtk_action_muxer_find_group (GtkActionMuxer *muxer,
+ const gchar *full_name,
+ const gchar **action_name)
+{
+ const gchar *dot;
+ gchar *prefix;
+ Group *group;
+
+ dot = strchr (full_name, '.');
+
+ if (!dot)
+ return NULL;
+
+ prefix = g_strndup (full_name, dot - full_name);
+ group = g_hash_table_lookup (muxer->groups, prefix);
+ g_free (prefix);
+
+ if (action_name)
+ *action_name = dot + 1;
+
+ return group;
+}
+
+static void
+gtk_action_muxer_action_enabled_changed (GtkActionMuxer *muxer,
+ const gchar *action_name,
+ gboolean enabled)
+{
+ Action *action;
+ GSList *node;
+
+ action = g_hash_table_lookup (muxer->observed_actions, action_name);
+ for (node = action ? action->watchers : NULL; node; node = node->next)
+ gtk_action_observer_action_enabled_changed (node->data, GTK_ACTION_OBSERVABLE (muxer), action_name, enabled);
+ g_action_group_action_enabled_changed (G_ACTION_GROUP (muxer), action_name, enabled);
+}
+
+static void
+gtk_action_muxer_group_action_enabled_changed (GActionGroup *action_group,
+ const gchar *action_name,
+ gboolean enabled,
+ gpointer user_data)
+{
+ Group *group = user_data;
+ gchar *fullname;
+
+ fullname = g_strconcat (group->prefix, ".", action_name, NULL);
+ gtk_action_muxer_action_enabled_changed (group->muxer, fullname, enabled);
+
+ g_free (fullname);
+}
+
+static void
+gtk_action_muxer_parent_action_enabled_changed (GActionGroup *action_group,
+ const gchar *action_name,
+ gboolean enabled,
+ gpointer user_data)
+{
+ GtkActionMuxer *muxer = user_data;
+
+ gtk_action_muxer_action_enabled_changed (muxer, action_name, enabled);
+}
+
+static void
+gtk_action_muxer_action_state_changed (GtkActionMuxer *muxer,
+ const gchar *action_name,
+ GVariant *state)
+{
+ Action *action;
+ GSList *node;
+
+ action = g_hash_table_lookup (muxer->observed_actions, action_name);
+ for (node = action ? action->watchers : NULL; node; node = node->next)
+ gtk_action_observer_action_state_changed (node->data, GTK_ACTION_OBSERVABLE (muxer), action_name, state);
+ g_action_group_action_state_changed (G_ACTION_GROUP (muxer), action_name, state);
+}
+
+static void
+gtk_action_muxer_group_action_state_changed (GActionGroup *action_group,
+ const gchar *action_name,
+ GVariant *state,
+ gpointer user_data)
+{
+ Group *group = user_data;
+ gchar *fullname;
+
+ fullname = g_strconcat (group->prefix, ".", action_name, NULL);
+ gtk_action_muxer_action_state_changed (group->muxer, fullname, state);
+
+ g_free (fullname);
+}
+
+static void
+gtk_action_muxer_parent_action_state_changed (GActionGroup *action_group,
+ const gchar *action_name,
+ GVariant *state,
+ gpointer user_data)
+{
+ GtkActionMuxer *muxer = user_data;
+
+ gtk_action_muxer_action_state_changed (muxer, action_name, state);
+}
+
+static void
+gtk_action_muxer_action_added (GtkActionMuxer *muxer,
+ const gchar *action_name,
+ GActionGroup *original_group,
+ const gchar *orignal_action_name)
+{
+ const GVariantType *parameter_type;
+ gboolean enabled;
+ GVariant *state;
+ Action *action;
+
+ action = g_hash_table_lookup (muxer->observed_actions, action_name);
+
+ if (action && action->watchers &&
+ g_action_group_query_action (original_group, orignal_action_name,
+ &enabled, &parameter_type, NULL, NULL, &state))
+ {
+ GSList *node;
+
+ for (node = action->watchers; node; node = node->next)
+ gtk_action_observer_action_added (node->data,
+ GTK_ACTION_OBSERVABLE (muxer),
+ action_name, parameter_type, enabled, state);
+
+ if (state)
+ g_variant_unref (state);
+ }
+
+ g_action_group_action_added (G_ACTION_GROUP (muxer), action_name);
+}
+
+static void
+gtk_action_muxer_action_added_to_group (GActionGroup *action_group,
+ const gchar *action_name,
+ gpointer user_data)
+{
+ Group *group = user_data;
+ gchar *fullname;
+
+ fullname = g_strconcat (group->prefix, ".", action_name, NULL);
+ gtk_action_muxer_action_added (group->muxer, fullname, action_group, action_name);
+
+ g_free (fullname);
+}
+
+static void
+gtk_action_muxer_action_added_to_parent (GActionGroup *action_group,
+ const gchar *action_name,
+ gpointer user_data)
+{
+ GtkActionMuxer *muxer = user_data;
+
+ gtk_action_muxer_action_added (muxer, action_name, action_group, action_name);
+}
+
+static void
+gtk_action_muxer_action_removed (GtkActionMuxer *muxer,
+ const gchar *action_name)
+{
+ Action *action;
+ GSList *node;
+
+ action = g_hash_table_lookup (muxer->observed_actions, action_name);
+ for (node = action ? action->watchers : NULL; node; node = node->next)
+ gtk_action_observer_action_removed (node->data, GTK_ACTION_OBSERVABLE (muxer), action_name);
+ g_action_group_action_removed (G_ACTION_GROUP (muxer), action_name);
+}
+
+static void
+gtk_action_muxer_action_removed_from_group (GActionGroup *action_group,
+ const gchar *action_name,
+ gpointer user_data)
+{
+ Group *group = user_data;
+ gchar *fullname;
+
+ fullname = g_strconcat (group->prefix, ".", action_name, NULL);
+ gtk_action_muxer_action_removed (group->muxer, fullname);
+
+ g_free (fullname);
+}
+
+static void
+gtk_action_muxer_action_removed_from_parent (GActionGroup *action_group,
+ const gchar *action_name,
+ gpointer user_data)
+{
+ GtkActionMuxer *muxer = user_data;
+
+ gtk_action_muxer_action_removed (muxer, action_name);
+}
+
+static void
+gtk_action_muxer_primary_accel_changed (GtkActionMuxer *muxer,
+ const gchar *action_name,
+ const gchar *action_and_target)
+{
+ Action *action;
+ GSList *node;
+
+ if (!action_name)
+ action_name = strrchr (action_and_target, '|') + 1;
+
+ action = g_hash_table_lookup (muxer->observed_actions, action_name);
+ for (node = action ? action->watchers : NULL; node; node = node->next)
+ gtk_action_observer_primary_accel_changed (node->data, GTK_ACTION_OBSERVABLE (muxer),
+ action_name, action_and_target);
+ g_signal_emit (muxer, accel_signal, 0, action_name, action_and_target);
+}
+
+static void
+gtk_action_muxer_parent_primary_accel_changed (GtkActionMuxer *parent,
+ const gchar *action_name,
+ const gchar *action_and_target,
+ gpointer user_data)
+{
+ GtkActionMuxer *muxer = user_data;
+
+ /* If it's in our table then don't let the parent one filter through */
+ if (muxer->primary_accels && g_hash_table_lookup (muxer->primary_accels, action_and_target))
+ return;
+
+ gtk_action_muxer_primary_accel_changed (muxer, action_name, action_and_target);
+}
+
+static gboolean
+gtk_action_muxer_query_action (GActionGroup *action_group,
+ const gchar *action_name,
+ gboolean *enabled,
+ const GVariantType **parameter_type,
+ const GVariantType **state_type,
+ GVariant **state_hint,
+ GVariant **state)
+{
+ GtkActionMuxer *muxer = GTK_ACTION_MUXER (action_group);
+ Group *group;
+ const gchar *unprefixed_name;
+
+ group = gtk_action_muxer_find_group (muxer, action_name, &unprefixed_name);
+
+ if (group)
+ return g_action_group_query_action (group->group, unprefixed_name, enabled,
+ parameter_type, state_type, state_hint, state);
+
+ if (muxer->parent)
+ return g_action_group_query_action (G_ACTION_GROUP (muxer->parent), action_name,
+ enabled, parameter_type,
+ state_type, state_hint, state);
+
+ return FALSE;
+}
+
+static GVariant *
+get_platform_data (void)
+{
+ gchar time[32];
+ GVariantBuilder *builder;
+ GVariant *result;
+
+ g_snprintf (time, 32, "_TIME%d", clutter_get_current_event_time ());
+
+ builder = g_variant_builder_new (G_VARIANT_TYPE ("a{sv}"));
+
+ g_variant_builder_add (builder, "{sv}", "desktop-startup-id",
+ g_variant_new_string (time));
+
+ result = g_variant_builder_end (builder);
+ g_variant_builder_unref (builder);
+
+ return result;
+}
+
+static void
+gtk_action_muxer_activate_action (GActionGroup *action_group,
+ const gchar *action_name,
+ GVariant *parameter)
+{
+ GtkActionMuxer *muxer = GTK_ACTION_MUXER (action_group);
+ Group *group;
+ const gchar *unprefixed_name;
+
+ group = gtk_action_muxer_find_group (muxer, action_name, &unprefixed_name);
+
+ if (group)
+ {
+ if (G_IS_REMOTE_ACTION_GROUP (group->group))
+ g_remote_action_group_activate_action_full (G_REMOTE_ACTION_GROUP (group->group),
+ unprefixed_name, parameter,
+ get_platform_data ());
+ else
+ g_action_group_activate_action (group->group, unprefixed_name, parameter);
+ }
+ else if (muxer->parent)
+ g_action_group_activate_action (G_ACTION_GROUP (muxer->parent), action_name, parameter);
+}
+
+static void
+gtk_action_muxer_change_action_state (GActionGroup *action_group,
+ const gchar *action_name,
+ GVariant *state)
+{
+ GtkActionMuxer *muxer = GTK_ACTION_MUXER (action_group);
+ Group *group;
+ const gchar *unprefixed_name;
+
+ group = gtk_action_muxer_find_group (muxer, action_name, &unprefixed_name);
+
+ if (group)
+ {
+ if (G_IS_REMOTE_ACTION_GROUP (group->group))
+ g_remote_action_group_change_action_state_full (G_REMOTE_ACTION_GROUP (group->group),
+ unprefixed_name,
+ state,
+ get_platform_data ());
+ else
+ g_action_group_change_action_state (group->group, unprefixed_name, state);
+ }
+ else if (muxer->parent)
+ g_action_group_change_action_state (G_ACTION_GROUP (muxer->parent), action_name, state);
+}
+
+static void
+gtk_action_muxer_unregister_internal (Action *action,
+ gpointer observer)
+{
+ GtkActionMuxer *muxer = action->muxer;
+ GSList **ptr;
+
+ for (ptr = &action->watchers; *ptr; ptr = &(*ptr)->next)
+ if ((*ptr)->data == observer)
+ {
+ *ptr = g_slist_remove (*ptr, observer);
+
+ if (action->watchers == NULL)
+ g_hash_table_remove (muxer->observed_actions, action->fullname);
+
+ break;
+ }
+}
+
+static void
+gtk_action_muxer_weak_notify (gpointer data,
+ GObject *where_the_object_was)
+{
+ Action *action = data;
+
+ gtk_action_muxer_unregister_internal (action, where_the_object_was);
+}
+
+static void
+gtk_action_muxer_register_observer (GtkActionObservable *observable,
+ const gchar *name,
+ GtkActionObserver *observer)
+{
+ GtkActionMuxer *muxer = GTK_ACTION_MUXER (observable);
+ Action *action;
+
+ action = g_hash_table_lookup (muxer->observed_actions, name);
+
+ if (action == NULL)
+ {
+ action = g_new (Action, 1);
+ action->muxer = muxer;
+ action->fullname = g_strdup (name);
+ action->watchers = NULL;
+
+ g_hash_table_insert (muxer->observed_actions, action->fullname, action);
+ }
+
+ action->watchers = g_slist_prepend (action->watchers, observer);
+ g_object_weak_ref (G_OBJECT (observer), gtk_action_muxer_weak_notify, action);
+}
+
+static void
+gtk_action_muxer_unregister_observer (GtkActionObservable *observable,
+ const gchar *name,
+ GtkActionObserver *observer)
+{
+ GtkActionMuxer *muxer = GTK_ACTION_MUXER (observable);
+ Action *action;
+
+ action = g_hash_table_lookup (muxer->observed_actions, name);
+ g_object_weak_unref (G_OBJECT (observer), gtk_action_muxer_weak_notify, action);
+ gtk_action_muxer_unregister_internal (action, observer);
+}
+
+static void
+gtk_action_muxer_free_group (gpointer data)
+{
+ Group *group = data;
+ gint i;
+
+ /* 'for loop' or 'four loop'? */
+ for (i = 0; i < 4; i++)
+ g_clear_signal_handler (&group->handler_ids[i], group->group);
+
+ g_object_unref (group->group);
+ g_free (group->prefix);
+
+ g_free (group);
+}
+
+static void
+gtk_action_muxer_free_action (gpointer data)
+{
+ Action *action = data;
+ GSList *it;
+
+ for (it = action->watchers; it; it = it->next)
+ g_object_weak_unref (G_OBJECT (it->data), gtk_action_muxer_weak_notify, action);
+
+ g_slist_free (action->watchers);
+ g_free (action->fullname);
+
+ g_free (action);
+}
+
+static void
+gtk_action_muxer_finalize (GObject *object)
+{
+ GtkActionMuxer *muxer = GTK_ACTION_MUXER (object);
+
+ g_assert_cmpint (g_hash_table_size (muxer->observed_actions), ==, 0);
+ g_hash_table_unref (muxer->observed_actions);
+ g_hash_table_unref (muxer->groups);
+
+ G_OBJECT_CLASS (gtk_action_muxer_parent_class)
+ ->finalize (object);
+}
+
+static void
+gtk_action_muxer_dispose (GObject *object)
+{
+ GtkActionMuxer *muxer = GTK_ACTION_MUXER (object);
+
+ if (muxer->parent)
+ {
+ g_signal_handlers_disconnect_by_func (muxer->parent, gtk_action_muxer_action_added_to_parent, muxer);
+ g_signal_handlers_disconnect_by_func (muxer->parent, gtk_action_muxer_action_removed_from_parent, muxer);
+ g_signal_handlers_disconnect_by_func (muxer->parent, gtk_action_muxer_parent_action_enabled_changed, muxer);
+ g_signal_handlers_disconnect_by_func (muxer->parent, gtk_action_muxer_parent_action_state_changed, muxer);
+ g_signal_handlers_disconnect_by_func (muxer->parent, gtk_action_muxer_parent_primary_accel_changed, muxer);
+
+ g_clear_object (&muxer->parent);
+ }
+
+ g_hash_table_remove_all (muxer->observed_actions);
+
+ G_OBJECT_CLASS (gtk_action_muxer_parent_class)
+ ->dispose (object);
+}
+
+static void
+gtk_action_muxer_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GtkActionMuxer *muxer = GTK_ACTION_MUXER (object);
+
+ switch (property_id)
+ {
+ case PROP_PARENT:
+ g_value_set_object (value, gtk_action_muxer_get_parent (muxer));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ }
+}
+
+static void
+gtk_action_muxer_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GtkActionMuxer *muxer = GTK_ACTION_MUXER (object);
+
+ switch (property_id)
+ {
+ case PROP_PARENT:
+ gtk_action_muxer_set_parent (muxer, g_value_get_object (value));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ }
+}
+
+static void
+gtk_action_muxer_init (GtkActionMuxer *muxer)
+{
+ muxer->observed_actions = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, gtk_action_muxer_free_action);
+ muxer->groups = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, gtk_action_muxer_free_group);
+}
+
+static void
+gtk_action_muxer_observable_iface_init (GtkActionObservableInterface *iface)
+{
+ iface->register_observer = gtk_action_muxer_register_observer;
+ iface->unregister_observer = gtk_action_muxer_unregister_observer;
+}
+
+static void
+gtk_action_muxer_group_iface_init (GActionGroupInterface *iface)
+{
+ iface->list_actions = gtk_action_muxer_list_actions;
+ iface->query_action = gtk_action_muxer_query_action;
+ iface->activate_action = gtk_action_muxer_activate_action;
+ iface->change_action_state = gtk_action_muxer_change_action_state;
+}
+
+static void
+gtk_action_muxer_class_init (GObjectClass *class)
+{
+ class->get_property = gtk_action_muxer_get_property;
+ class->set_property = gtk_action_muxer_set_property;
+ class->finalize = gtk_action_muxer_finalize;
+ class->dispose = gtk_action_muxer_dispose;
+
+ accel_signal = g_signal_new ("primary-accel-changed", GTK_TYPE_ACTION_MUXER, G_SIGNAL_RUN_LAST,
+ 0, NULL, NULL, NULL, G_TYPE_NONE, 2, G_TYPE_STRING, G_TYPE_STRING);
+
+ properties[PROP_PARENT] = g_param_spec_object ("parent", "Parent",
+ "The parent muxer",
+ GTK_TYPE_ACTION_MUXER,
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS);
+
+ g_object_class_install_properties (class, NUM_PROPERTIES, properties);
+}
+
+/**
+ * gtk_action_muxer_insert:
+ * @muxer: a #GtkActionMuxer
+ * @prefix: the prefix string for the action group
+ * @action_group: a #GActionGroup
+ *
+ * Adds the actions in @action_group to the list of actions provided by
+ * @muxer. @prefix is prefixed to each action name, such that for each
+ * action <varname>x</varname> in @action_group, there is an equivalent
+ * action @prefix<literal>.</literal><varname>x</varname> in @muxer.
+ *
+ * For example, if @prefix is "<literal>app</literal>" and @action_group
+ * contains an action called "<literal>quit</literal>", then @muxer will
+ * now contain an action called "<literal>app.quit</literal>".
+ *
+ * If any #GtkActionObservers are registered for actions in the group,
+ * "action_added" notifications will be emitted, as appropriate.
+ *
+ * @prefix must not contain a dot ('.').
+ */
+void
+gtk_action_muxer_insert (GtkActionMuxer *muxer,
+ const gchar *prefix,
+ GActionGroup *action_group)
+{
+ gchar **actions;
+ Group *group;
+ gint i;
+
+ /* TODO: diff instead of ripout and replace */
+ gtk_action_muxer_remove (muxer, prefix);
+
+ group = g_new (Group, 1);
+ group->muxer = muxer;
+ group->group = g_object_ref (action_group);
+ group->prefix = g_strdup (prefix);
+
+ g_hash_table_insert (muxer->groups, group->prefix, group);
+
+ actions = g_action_group_list_actions (group->group);
+ for (i = 0; actions[i]; i++)
+ gtk_action_muxer_action_added_to_group (group->group, actions[i], group);
+ g_strfreev (actions);
+
+ group->handler_ids[0] = g_signal_connect (group->group, "action-added",
+ G_CALLBACK (gtk_action_muxer_action_added_to_group), group);
+ group->handler_ids[1] = g_signal_connect (group->group, "action-removed",
+ G_CALLBACK (gtk_action_muxer_action_removed_from_group), group);
+ group->handler_ids[2] = g_signal_connect (group->group, "action-enabled-changed",
+ G_CALLBACK (gtk_action_muxer_group_action_enabled_changed), group);
+ group->handler_ids[3] = g_signal_connect (group->group, "action-state-changed",
+ G_CALLBACK (gtk_action_muxer_group_action_state_changed), group);
+}
+
+/**
+ * gtk_action_muxer_remove:
+ * @muxer: a #GtkActionMuxer
+ * @prefix: the prefix of the action group to remove
+ *
+ * Removes a #GActionGroup from the #GtkActionMuxer.
+ *
+ * If any #GtkActionObservers are registered for actions in the group,
+ * "action_removed" notifications will be emitted, as appropriate.
+ */
+void
+gtk_action_muxer_remove (GtkActionMuxer *muxer,
+ const gchar *prefix)
+{
+ Group *group;
+
+ group = g_hash_table_lookup (muxer->groups, prefix);
+
+ if (group != NULL)
+ {
+ gchar **actions;
+ gint i;
+
+ g_hash_table_steal (muxer->groups, prefix);
+
+ actions = g_action_group_list_actions (group->group);
+ for (i = 0; actions[i]; i++)
+ gtk_action_muxer_action_removed_from_group (group->group, actions[i], group);
+ g_strfreev (actions);
+
+ gtk_action_muxer_free_group (group);
+ }
+}
+
+/**
+ * gtk_action_muxer_new:
+ *
+ * Creates a new #GtkActionMuxer.
+ */
+GtkActionMuxer *
+gtk_action_muxer_new (void)
+{
+ return g_object_new (GTK_TYPE_ACTION_MUXER, NULL);
+}
+
+/**
+ * gtk_action_muxer_get_parent:
+ * @muxer: a #GtkActionMuxer
+ *
+ * Returns: (transfer none): the parent of @muxer, or NULL.
+ */
+GtkActionMuxer *
+gtk_action_muxer_get_parent (GtkActionMuxer *muxer)
+{
+ g_return_val_if_fail (GTK_IS_ACTION_MUXER (muxer), NULL);
+
+ return muxer->parent;
+}
+
+static void
+emit_changed_accels (GtkActionMuxer *muxer,
+ GtkActionMuxer *parent)
+{
+ while (parent)
+ {
+ if (parent->primary_accels)
+ {
+ GHashTableIter iter;
+ gpointer key;
+
+ g_hash_table_iter_init (&iter, parent->primary_accels);
+ while (g_hash_table_iter_next (&iter, &key, NULL))
+ gtk_action_muxer_primary_accel_changed (muxer, NULL, key);
+ }
+
+ parent = parent->parent;
+ }
+}
+
+/**
+ * gtk_action_muxer_set_parent:
+ * @muxer: a #GtkActionMuxer
+ * @parent: (nullable): the new parent #GtkActionMuxer
+ *
+ * Sets the parent of @muxer to @parent.
+ */
+void
+gtk_action_muxer_set_parent (GtkActionMuxer *muxer,
+ GtkActionMuxer *parent)
+{
+ g_return_if_fail (GTK_IS_ACTION_MUXER (muxer));
+ g_return_if_fail (parent == NULL || GTK_IS_ACTION_MUXER (parent));
+
+ if (muxer->parent == parent)
+ return;
+
+ if (muxer->parent != NULL)
+ {
+ gchar **actions;
+ gchar **it;
+
+ actions = g_action_group_list_actions (G_ACTION_GROUP (muxer->parent));
+ for (it = actions; *it; it++)
+ gtk_action_muxer_action_removed (muxer, *it);
+ g_strfreev (actions);
+
+ emit_changed_accels (muxer, muxer->parent);
+
+ g_signal_handlers_disconnect_by_func (muxer->parent, gtk_action_muxer_action_added_to_parent, muxer);
+ g_signal_handlers_disconnect_by_func (muxer->parent, gtk_action_muxer_action_removed_from_parent, muxer);
+ g_signal_handlers_disconnect_by_func (muxer->parent, gtk_action_muxer_parent_action_enabled_changed, muxer);
+ g_signal_handlers_disconnect_by_func (muxer->parent, gtk_action_muxer_parent_action_state_changed, muxer);
+ g_signal_handlers_disconnect_by_func (muxer->parent, gtk_action_muxer_parent_primary_accel_changed, muxer);
+
+ g_object_unref (muxer->parent);
+ }
+
+ muxer->parent = parent;
+
+ if (muxer->parent != NULL)
+ {
+ gchar **actions;
+ gchar **it;
+
+ g_object_ref (muxer->parent);
+
+ actions = g_action_group_list_actions (G_ACTION_GROUP (muxer->parent));
+ for (it = actions; *it; it++)
+ gtk_action_muxer_action_added (muxer, *it, G_ACTION_GROUP (muxer->parent), *it);
+ g_strfreev (actions);
+
+ emit_changed_accels (muxer, muxer->parent);
+
+ g_signal_connect (muxer->parent, "action-added",
+ G_CALLBACK (gtk_action_muxer_action_added_to_parent), muxer);
+ g_signal_connect (muxer->parent, "action-removed",
+ G_CALLBACK (gtk_action_muxer_action_removed_from_parent), muxer);
+ g_signal_connect (muxer->parent, "action-enabled-changed",
+ G_CALLBACK (gtk_action_muxer_parent_action_enabled_changed), muxer);
+ g_signal_connect (muxer->parent, "action-state-changed",
+ G_CALLBACK (gtk_action_muxer_parent_action_state_changed), muxer);
+ g_signal_connect (muxer->parent, "primary-accel-changed",
+ G_CALLBACK (gtk_action_muxer_parent_primary_accel_changed), muxer);
+ }
+
+ g_object_notify_by_pspec (G_OBJECT (muxer), properties[PROP_PARENT]);
+}
+
+void
+gtk_action_muxer_set_primary_accel (GtkActionMuxer *muxer,
+ const gchar *action_and_target,
+ const gchar *primary_accel)
+{
+ if (!muxer->primary_accels)
+ muxer->primary_accels = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
+
+ if (primary_accel)
+ g_hash_table_insert (muxer->primary_accels, g_strdup (action_and_target), g_strdup (primary_accel));
+ else
+ g_hash_table_remove (muxer->primary_accels, action_and_target);
+
+ gtk_action_muxer_primary_accel_changed (muxer, NULL, action_and_target);
+}
+
+const gchar *
+gtk_action_muxer_get_primary_accel (GtkActionMuxer *muxer,
+ const gchar *action_and_target)
+{
+ if (muxer->primary_accels)
+ {
+ const gchar *primary_accel;
+
+ primary_accel = g_hash_table_lookup (muxer->primary_accels, action_and_target);
+
+ if (primary_accel)
+ return primary_accel;
+ }
+
+ if (!muxer->parent)
+ return NULL;
+
+ return gtk_action_muxer_get_primary_accel (muxer->parent, action_and_target);
+}
+
+gchar *
+gtk_print_action_and_target (const gchar *action_namespace,
+ const gchar *action_name,
+ GVariant *target)
+{
+ GString *result;
+
+ g_return_val_if_fail (strchr (action_name, '|') == NULL, NULL);
+ g_return_val_if_fail (action_namespace == NULL || strchr (action_namespace, '|') == NULL, NULL);
+
+ result = g_string_new (NULL);
+
+ if (target)
+ g_variant_print_string (target, result, TRUE);
+ g_string_append_c (result, '|');
+
+ if (action_namespace)
+ {
+ g_string_append (result, action_namespace);
+ g_string_append_c (result, '.');
+ }
+
+ g_string_append (result, action_name);
+
+ return g_string_free (result, FALSE);
+}
diff --git a/src/gtkactionmuxer.h b/src/gtkactionmuxer.h
new file mode 100644
index 0000000..d71abf4
--- /dev/null
+++ b/src/gtkactionmuxer.h
@@ -0,0 +1,64 @@
+/*
+ * Copyright © 2011 Canonical Limited
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the licence, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Author: Ryan Lortie <desrt@desrt.ca>
+ */
+
+#ifndef __GTK_ACTION_MUXER_H__
+#define __GTK_ACTION_MUXER_H__
+
+#include <gio/gio.h>
+
+G_BEGIN_DECLS
+
+#define GTK_TYPE_ACTION_MUXER (gtk_action_muxer_get_type ())
+#define GTK_ACTION_MUXER(inst) (G_TYPE_CHECK_INSTANCE_CAST ((inst), \
+ GTK_TYPE_ACTION_MUXER, GtkActionMuxer))
+#define GTK_IS_ACTION_MUXER(inst) (G_TYPE_CHECK_INSTANCE_TYPE ((inst), \
+ GTK_TYPE_ACTION_MUXER))
+
+typedef struct _GtkActionMuxer GtkActionMuxer;
+
+GType gtk_action_muxer_get_type (void);
+GtkActionMuxer * gtk_action_muxer_new (void);
+
+void gtk_action_muxer_insert (GtkActionMuxer *muxer,
+ const gchar *prefix,
+ GActionGroup *action_group);
+
+void gtk_action_muxer_remove (GtkActionMuxer *muxer,
+ const gchar *prefix);
+
+GtkActionMuxer * gtk_action_muxer_get_parent (GtkActionMuxer *muxer);
+
+void gtk_action_muxer_set_parent (GtkActionMuxer *muxer,
+ GtkActionMuxer *parent);
+
+void gtk_action_muxer_set_primary_accel (GtkActionMuxer *muxer,
+ const gchar *action_and_target,
+ const gchar *primary_accel);
+
+const gchar * gtk_action_muxer_get_primary_accel (GtkActionMuxer *muxer,
+ const gchar *action_and_target);
+
+/* No better place for this... */
+gchar * gtk_print_action_and_target (const gchar *action_namespace,
+ const gchar *action_name,
+ GVariant *target);
+
+G_END_DECLS
+
+#endif /* __GTK_ACTION_MUXER_H__ */
diff --git a/src/gtkactionobservable.c b/src/gtkactionobservable.c
new file mode 100644
index 0000000..ab90df2
--- /dev/null
+++ b/src/gtkactionobservable.c
@@ -0,0 +1,78 @@
+/*
+ * Copyright © 2011 Canonical Limited
+ *
+ * This library is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * licence or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Ryan Lortie <desrt@desrt.ca>
+ */
+
+#include "config.h"
+
+#include "gtkactionobservable.h"
+
+G_DEFINE_INTERFACE (GtkActionObservable, gtk_action_observable, G_TYPE_OBJECT)
+
+/*
+ * SECTION:gtkactionobserable
+ * @short_description: an interface implemented by objects that report
+ * changes to actions
+ */
+
+void
+gtk_action_observable_default_init (GtkActionObservableInterface *iface)
+{
+}
+
+/**
+ * gtk_action_observable_register_observer:
+ * @observable: a #GtkActionObservable
+ * @action_name: the name of the action
+ * @observer: the #GtkActionObserver to which the events will be reported
+ *
+ * Registers @observer as being interested in changes to @action_name on
+ * @observable.
+ */
+void
+gtk_action_observable_register_observer (GtkActionObservable *observable,
+ const gchar *action_name,
+ GtkActionObserver *observer)
+{
+ g_return_if_fail (GTK_IS_ACTION_OBSERVABLE (observable));
+
+ GTK_ACTION_OBSERVABLE_GET_IFACE (observable)
+ ->register_observer (observable, action_name, observer);
+}
+
+/**
+ * gtk_action_observable_unregister_observer:
+ * @observable: a #GtkActionObservable
+ * @action_name: the name of the action
+ * @observer: the #GtkActionObserver to which the events will be reported
+ *
+ * Removes the registration of @observer as being interested in changes
+ * to @action_name on @observable.
+ *
+ * If the observer was registered multiple times, it must be
+ * unregistered an equal number of times.
+ */
+void
+gtk_action_observable_unregister_observer (GtkActionObservable *observable,
+ const gchar *action_name,
+ GtkActionObserver *observer)
+{
+ g_return_if_fail (GTK_IS_ACTION_OBSERVABLE (observable));
+
+ GTK_ACTION_OBSERVABLE_GET_IFACE (observable)
+ ->unregister_observer (observable, action_name, observer);
+}
diff --git a/src/gtkactionobservable.h b/src/gtkactionobservable.h
new file mode 100644
index 0000000..aa1514b
--- /dev/null
+++ b/src/gtkactionobservable.h
@@ -0,0 +1,60 @@
+/*
+ * Copyright © 2011 Canonical Limited
+ *
+ * This library is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * licence or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Ryan Lortie <desrt@desrt.ca>
+ */
+
+#ifndef __GTK_ACTION_OBSERVABLE_H__
+#define __GTK_ACTION_OBSERVABLE_H__
+
+#include "gtkactionobserver.h"
+
+G_BEGIN_DECLS
+
+#define GTK_TYPE_ACTION_OBSERVABLE (gtk_action_observable_get_type ())
+#define GTK_ACTION_OBSERVABLE(inst) (G_TYPE_CHECK_INSTANCE_CAST ((inst), \
+ GTK_TYPE_ACTION_OBSERVABLE, GtkActionObservable))
+#define GTK_IS_ACTION_OBSERVABLE(inst) (G_TYPE_CHECK_INSTANCE_TYPE ((inst), \
+ GTK_TYPE_ACTION_OBSERVABLE))
+#define GTK_ACTION_OBSERVABLE_GET_IFACE(inst) (G_TYPE_INSTANCE_GET_INTERFACE ((inst), \
+ GTK_TYPE_ACTION_OBSERVABLE, \
+ GtkActionObservableInterface))
+
+typedef struct _GtkActionObservableInterface GtkActionObservableInterface;
+
+struct _GtkActionObservableInterface
+{
+ GTypeInterface g_iface;
+
+ void (* register_observer) (GtkActionObservable *observable,
+ const gchar *action_name,
+ GtkActionObserver *observer);
+ void (* unregister_observer) (GtkActionObservable *observable,
+ const gchar *action_name,
+ GtkActionObserver *observer);
+};
+
+GType gtk_action_observable_get_type (void);
+void gtk_action_observable_register_observer (GtkActionObservable *observable,
+ const gchar *action_name,
+ GtkActionObserver *observer);
+void gtk_action_observable_unregister_observer (GtkActionObservable *observable,
+ const gchar *action_name,
+ GtkActionObserver *observer);
+
+G_END_DECLS
+
+#endif /* __GTK_ACTION_OBSERVABLE_H__ */
diff --git a/src/gtkactionobserver.c b/src/gtkactionobserver.c
new file mode 100644
index 0000000..3287106
--- /dev/null
+++ b/src/gtkactionobserver.c
@@ -0,0 +1,189 @@
+/*
+ * Copyright © 2011 Canonical Limited
+ *
+ * This library is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * licence or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Ryan Lortie <desrt@desrt.ca>
+ */
+
+#include "config.h"
+
+#include "gtkactionobserver.h"
+
+G_DEFINE_INTERFACE (GtkActionObserver, gtk_action_observer, G_TYPE_OBJECT)
+
+/**
+ * SECTION:gtkactionobserver
+ * @short_description: an interface implemented by objects that are
+ * interested in monitoring actions for changes
+ *
+ * GtkActionObserver is a simple interface allowing objects that wish to
+ * be notified of changes to actions to be notified of those changes.
+ *
+ * It is also possible to monitor changes to action groups using
+ * #GObject signals, but there are a number of reasons that this
+ * approach could become problematic:
+ *
+ * - there are four separate signals that must be manually connected
+ * and disconnected
+ *
+ * - when a large number of different observers wish to monitor a
+ * (usually disjoint) set of actions within the same action group,
+ * there is only one way to avoid having all notifications delivered
+ * to all observers: signal detail. In order to use signal detail,
+ * each action name must be quarked, which is not always practical.
+ *
+ * - even if quarking is acceptable, #GObject signal details are
+ * implemented by scanning a linked list, so there is no real
+ * decrease in complexity
+ */
+
+void
+gtk_action_observer_default_init (GtkActionObserverInterface *class)
+{
+}
+
+/**
+ * gtk_action_observer_action_added:
+ * @observer: a #GtkActionObserver
+ * @observable: the source of the event
+ * @action_name: the name of the action
+ * @enabled: %TRUE if the action is now enabled
+ * @parameter_type: the parameter type for action invocations, or %NULL
+ * if no parameter is required
+ * @state: the current state of the action, or %NULL if the action is
+ * stateless
+ *
+ * This function is called when an action that the observer is
+ * registered to receive events for is added.
+ *
+ * This function should only be called by objects with which the
+ * observer has explicitly registered itself to receive events.
+ */
+void
+gtk_action_observer_action_added (GtkActionObserver *observer,
+ GtkActionObservable *observable,
+ const gchar *action_name,
+ const GVariantType *parameter_type,
+ gboolean enabled,
+ GVariant *state)
+{
+ g_return_if_fail (GTK_IS_ACTION_OBSERVER (observer));
+
+ GTK_ACTION_OBSERVER_GET_IFACE (observer)
+ ->action_added (observer, observable, action_name, parameter_type, enabled, state);
+}
+
+/**
+ * gtk_action_observer_action_enabled_changed:
+ * @observer: a #GtkActionObserver
+ * @observable: the source of the event
+ * @action_name: the name of the action
+ * @enabled: %TRUE if the action is now enabled
+ *
+ * This function is called when an action that the observer is
+ * registered to receive events for becomes enabled or disabled.
+ *
+ * This function should only be called by objects with which the
+ * observer has explicitly registered itself to receive events.
+ */
+void
+gtk_action_observer_action_enabled_changed (GtkActionObserver *observer,
+ GtkActionObservable *observable,
+ const gchar *action_name,
+ gboolean enabled)
+{
+ g_return_if_fail (GTK_IS_ACTION_OBSERVER (observer));
+
+ GTK_ACTION_OBSERVER_GET_IFACE (observer)
+ ->action_enabled_changed (observer, observable, action_name, enabled);
+}
+
+/**
+ * gtk_action_observer_action_state_changed:
+ * @observer: a #GtkActionObserver
+ * @observable: the source of the event
+ * @action_name: the name of the action
+ * @state: the new state of the action
+ *
+ * This function is called when an action that the observer is
+ * registered to receive events for changes to its state.
+ *
+ * This function should only be called by objects with which the
+ * observer has explicitly registered itself to receive events.
+ */
+void
+gtk_action_observer_action_state_changed (GtkActionObserver *observer,
+ GtkActionObservable *observable,
+ const gchar *action_name,
+ GVariant *state)
+{
+ g_return_if_fail (GTK_IS_ACTION_OBSERVER (observer));
+
+ GTK_ACTION_OBSERVER_GET_IFACE (observer)
+ ->action_state_changed (observer, observable, action_name, state);
+}
+
+/**
+ * gtk_action_observer_action_removed:
+ * @observer: a #GtkActionObserver
+ * @observable: the source of the event
+ * @action_name: the name of the action
+ *
+ * This function is called when an action that the observer is
+ * registered to receive events for is removed.
+ *
+ * This function should only be called by objects with which the
+ * observer has explicitly registered itself to receive events.
+ */
+void
+gtk_action_observer_action_removed (GtkActionObserver *observer,
+ GtkActionObservable *observable,
+ const gchar *action_name)
+{
+ g_return_if_fail (GTK_IS_ACTION_OBSERVER (observer));
+
+ GTK_ACTION_OBSERVER_GET_IFACE (observer)
+ ->action_removed (observer, observable, action_name);
+}
+
+/**
+ * gtk_action_observer_primary_accel_changed:
+ * @observer: a #GtkActionObserver
+ * @observable: the source of the event
+ * @action_name: the name of the action
+ * @action_and_target: detailed action of the changed accel, in "action and target" format
+ *
+ * This function is called when an action that the observer is
+ * registered to receive events for has one of its accelerators changed.
+ *
+ * Accelerator changes are reported for all targets associated with the
+ * action. The @action_and_target string should be used to check if the
+ * reported target is the one that the observer is interested in.
+ */
+void
+gtk_action_observer_primary_accel_changed (GtkActionObserver *observer,
+ GtkActionObservable *observable,
+ const gchar *action_name,
+ const gchar *action_and_target)
+{
+ GtkActionObserverInterface *iface;
+
+ g_return_if_fail (GTK_IS_ACTION_OBSERVER (observer));
+
+ iface = GTK_ACTION_OBSERVER_GET_IFACE (observer);
+
+ if (iface->primary_accel_changed)
+ iface->primary_accel_changed (observer, observable, action_name, action_and_target);
+}
diff --git a/src/gtkactionobserver.h b/src/gtkactionobserver.h
new file mode 100644
index 0000000..a4e9659
--- /dev/null
+++ b/src/gtkactionobserver.h
@@ -0,0 +1,91 @@
+/*
+ * Copyright © 2011 Canonical Limited
+ *
+ * This library is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * licence or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Ryan Lortie <desrt@desrt.ca>
+ */
+
+#ifndef __GTK_ACTION_OBSERVER_H__
+#define __GTK_ACTION_OBSERVER_H__
+
+#include <gio/gio.h>
+
+G_BEGIN_DECLS
+
+#define GTK_TYPE_ACTION_OBSERVER (gtk_action_observer_get_type ())
+#define GTK_ACTION_OBSERVER(inst) (G_TYPE_CHECK_INSTANCE_CAST ((inst), \
+ GTK_TYPE_ACTION_OBSERVER, GtkActionObserver))
+#define GTK_IS_ACTION_OBSERVER(inst) (G_TYPE_CHECK_INSTANCE_TYPE ((inst), \
+ GTK_TYPE_ACTION_OBSERVER))
+#define GTK_ACTION_OBSERVER_GET_IFACE(inst) (G_TYPE_INSTANCE_GET_INTERFACE ((inst), \
+ GTK_TYPE_ACTION_OBSERVER, GtkActionObserverInterface))
+
+typedef struct _GtkActionObserverInterface GtkActionObserverInterface;
+typedef struct _GtkActionObservable GtkActionObservable;
+typedef struct _GtkActionObserver GtkActionObserver;
+
+struct _GtkActionObserverInterface
+{
+ GTypeInterface g_iface;
+
+ void (* action_added) (GtkActionObserver *observer,
+ GtkActionObservable *observable,
+ const gchar *action_name,
+ const GVariantType *parameter_type,
+ gboolean enabled,
+ GVariant *state);
+ void (* action_enabled_changed) (GtkActionObserver *observer,
+ GtkActionObservable *observable,
+ const gchar *action_name,
+ gboolean enabled);
+ void (* action_state_changed) (GtkActionObserver *observer,
+ GtkActionObservable *observable,
+ const gchar *action_name,
+ GVariant *state);
+ void (* action_removed) (GtkActionObserver *observer,
+ GtkActionObservable *observable,
+ const gchar *action_name);
+ void (* primary_accel_changed) (GtkActionObserver *observer,
+ GtkActionObservable *observable,
+ const gchar *action_name,
+ const gchar *action_and_target);
+};
+
+GType gtk_action_observer_get_type (void);
+void gtk_action_observer_action_added (GtkActionObserver *observer,
+ GtkActionObservable *observable,
+ const gchar *action_name,
+ const GVariantType *parameter_type,
+ gboolean enabled,
+ GVariant *state);
+void gtk_action_observer_action_enabled_changed (GtkActionObserver *observer,
+ GtkActionObservable *observable,
+ const gchar *action_name,
+ gboolean enabled);
+void gtk_action_observer_action_state_changed (GtkActionObserver *observer,
+ GtkActionObservable *observable,
+ const gchar *action_name,
+ GVariant *state);
+void gtk_action_observer_action_removed (GtkActionObserver *observer,
+ GtkActionObservable *observable,
+ const gchar *action_name);
+void gtk_action_observer_primary_accel_changed (GtkActionObserver *observer,
+ GtkActionObservable *observable,
+ const gchar *action_name,
+ const gchar *action_and_target);
+
+G_END_DECLS
+
+#endif /* __GTK_ACTION_OBSERVER_H__ */
diff --git a/src/hotplug-sniffer/hotplug-mimetypes.h b/src/hotplug-sniffer/hotplug-mimetypes.h
new file mode 100644
index 0000000..b034020
--- /dev/null
+++ b/src/hotplug-sniffer/hotplug-mimetypes.h
@@ -0,0 +1,141 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+#ifndef __HOTPLUG_MIMETYPES_H__
+#define __HOTPLUG_MIMETYPES_H__
+
+#include <glib.h>
+
+G_GNUC_UNUSED static const gchar *docs_mimetypes[] = {
+ "application/vnd.oasis.opendocument.text",
+ "application/vnd.oasis.opendocument.presentation",
+ "application/vnd.oasis.opendocument.spreadsheet",
+ "application/msword",
+ "application/vnd.ms-excel",
+ "application/vnd.ms-powerpoint",
+ "application/rtf",
+ "application/pdf",
+ "application/x-bzpdf",
+ "application/x-gzpdf",
+ "application/x-xzpdf",
+ "application/postscript",
+ "application/x-bzpostscript",
+ "application/x-gzpostscript",
+ "image/x-eps",
+ "image/x-bzeps",
+ "image/x-gzeps",
+ "application/x-dvi",
+ "application/x-bzdvi",
+ "application/x-gzdvi",
+ "image/vnd.djvu",
+ "application/x-cbr",
+ "application/x-cbz",
+ "application/x-cb7",
+ "application/x-cbt",
+ NULL
+};
+
+G_GNUC_UNUSED static const gchar *video_mimetypes[] = {
+ "application/mxf",
+ "application/ogg",
+ "application/ram",
+ "application/sdp",
+ "application/vnd.ms-wpl",
+ "application/vnd.rn-realmedia",
+ "application/x-extension-m4a",
+ "application/x-extension-mp4",
+ "application/x-flash-video",
+ "application/x-matroska",
+ "application/x-netshow-channel",
+ "application/x-ogg",
+ "application/x-quicktimeplayer",
+ "application/x-shorten",
+ "image/vnd.rn-realpix",
+ "image/x-pict",
+ "misc/ultravox",
+ "text/x-google-video-pointer",
+ "video/3gpp",
+ "video/dv",
+ "video/fli",
+ "video/flv",
+ "video/mp2t",
+ "video/mp4",
+ "video/mp4v-es",
+ "video/mpeg",
+ "video/msvideo",
+ "video/ogg",
+ "video/quicktime",
+ "video/vivo",
+ "video/vnd.divx",
+ "video/vnd.rn-realvideo",
+ "video/vnd.vivo",
+ "video/webm",
+ "video/x-anim",
+ "video/x-avi",
+ "video/x-flc",
+ "video/x-fli",
+ "video/x-flic",
+ "video/x-flv",
+ "video/x-m4v",
+ "video/x-matroska",
+ "video/x-mpeg",
+ "video/x-ms-asf",
+ "video/x-ms-asx",
+ "video/x-msvideo",
+ "video/x-ms-wm",
+ "video/x-ms-wmv",
+ "video/x-ms-wmx",
+ "video/x-ms-wvx",
+ "video/x-nsv",
+ "video/x-ogm+ogg",
+ "video/x-theora+ogg",
+ "video/x-totem-stream",
+ NULL
+};
+
+G_GNUC_UNUSED static const gchar *audio_mimetypes[] = {
+ "audio/3gpp",
+ "audio/ac3",
+ "audio/AMR",
+ "audio/AMR-WB",
+ "audio/basic",
+ "audio/flac",
+ "audio/midi",
+ "audio/mp2",
+ "audio/mp4",
+ "audio/mpeg",
+ "audio/ogg",
+ "audio/prs.sid",
+ "audio/vnd.rn-realaudio",
+ "audio/x-aiff",
+ "audio/x-ape",
+ "audio/x-flac",
+ "audio/x-gsm",
+ "audio/x-it",
+ "audio/x-m4a",
+ "audio/x-matroska",
+ "audio/x-mod",
+ "audio/x-mp3",
+ "audio/x-mpeg",
+ "audio/x-ms-asf",
+ "audio/x-ms-asx",
+ "audio/x-ms-wax",
+ "audio/x-ms-wma",
+ "audio/x-musepack",
+ "audio/x-pn-aiff",
+ "audio/x-pn-au",
+ "audio/x-pn-wav",
+ "audio/x-pn-windows-acm",
+ "audio/x-realaudio",
+ "audio/x-real-audio",
+ "audio/x-sbc",
+ "audio/x-speex",
+ "audio/x-tta",
+ "audio/x-wav",
+ "audio/x-wavpack",
+ "audio/x-vorbis",
+ "audio/x-vorbis+ogg",
+ "audio/x-xm",
+ NULL
+};
+
+#endif /* __HOTPLUG_MIMETYPES_H__ */
diff --git a/src/hotplug-sniffer/hotplug-sniffer.c b/src/hotplug-sniffer/hotplug-sniffer.c
new file mode 100644
index 0000000..4b70ec7
--- /dev/null
+++ b/src/hotplug-sniffer/hotplug-sniffer.c
@@ -0,0 +1,298 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * Copyright (C) 2011 Red Hat, Inc.
+ *
+ * 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, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: David Zeuthen <davidz@redhat.com>
+ * Cosimo Cecchi <cosimoc@redhat.com>
+ *
+ */
+
+#include "shell-mime-sniffer.h"
+#include "hotplug-mimetypes.h"
+
+/* Set the environment variable HOTPLUG_SNIFFER_DEBUG to show debug */
+static void print_debug (const gchar *str, ...);
+
+#define BUS_NAME "org.gnome.Shell.HotplugSniffer"
+#define AUTOQUIT_TIMEOUT 5
+
+static const gchar introspection_xml[] =
+ "<node>"
+ " <interface name='org.gnome.Shell.HotplugSniffer'>"
+ " <method name='SniffURI'>"
+ " <arg type='s' name='uri' direction='in'/>"
+ " <arg type='as' name='content_types' direction='out'/>"
+ " </method>"
+ " </interface>"
+ "</node>";
+
+static GDBusNodeInfo *introspection_data = NULL;
+static GMainLoop *loop = NULL;
+static guint autoquit_id = 0;
+
+static gboolean
+autoquit_timeout_cb (gpointer _unused)
+{
+ print_debug ("Timeout reached, quitting...");
+
+ autoquit_id = 0;
+ g_main_loop_quit (loop);
+
+ return FALSE;
+}
+
+static void
+ensure_autoquit_off (void)
+{
+ if (g_getenv ("HOTPLUG_SNIFFER_PERSIST") != NULL)
+ return;
+
+ g_clear_handle_id (&autoquit_id, g_source_remove);
+}
+
+static void
+ensure_autoquit_on (void)
+{
+ if (g_getenv ("HOTPLUG_SNIFFER_PERSIST") != NULL)
+ return;
+
+ autoquit_id =
+ g_timeout_add_seconds (AUTOQUIT_TIMEOUT,
+ autoquit_timeout_cb, NULL);
+ g_source_set_name_by_id (autoquit_id, "[gnome-shell] autoquit_timeout_cb");
+}
+
+typedef struct {
+ GVariant *parameters;
+ GDBusMethodInvocation *invocation;
+} InvocationData;
+
+static InvocationData *
+invocation_data_new (GVariant *params,
+ GDBusMethodInvocation *invocation)
+{
+ InvocationData *ret;
+
+ ret = g_new0 (InvocationData, 1);
+ ret->parameters = g_variant_ref (params);
+ ret->invocation = g_object_ref (invocation);
+
+ return ret;
+}
+
+static void
+invocation_data_free (InvocationData *data)
+{
+ g_variant_unref (data->parameters);
+ g_clear_object (&data->invocation);
+
+ g_free (data);
+}
+
+static void
+sniff_async_ready_cb (GObject *source,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ InvocationData *data = user_data;
+ gchar **types;
+ GError *error = NULL;
+
+ types = shell_mime_sniffer_sniff_finish (SHELL_MIME_SNIFFER (source),
+ res, &error);
+
+ if (error != NULL)
+ {
+ g_dbus_method_invocation_return_gerror (data->invocation, error);
+ g_error_free (error);
+ goto out;
+ }
+
+ g_dbus_method_invocation_return_value (data->invocation,
+ g_variant_new ("(^as)", types));
+ g_strfreev (types);
+
+ out:
+ invocation_data_free (data);
+ ensure_autoquit_on ();
+}
+
+static void
+handle_sniff_uri (InvocationData *data)
+{
+ ShellMimeSniffer *sniffer;
+ const gchar *uri;
+ GFile *file;
+
+ ensure_autoquit_off ();
+
+ g_variant_get (data->parameters,
+ "(&s)", &uri,
+ NULL);
+ file = g_file_new_for_uri (uri);
+
+ print_debug ("Initiating sniff for uri %s", uri);
+
+ sniffer = shell_mime_sniffer_new (file);
+ shell_mime_sniffer_sniff_async (sniffer,
+ sniff_async_ready_cb,
+ data);
+
+ g_object_unref (sniffer);
+ g_object_unref (file);
+}
+
+static void
+handle_method_call (GDBusConnection *connection,
+ const gchar *sender,
+ const gchar *object_path,
+ const gchar *interface_name,
+ const gchar *method_name,
+ GVariant *parameters,
+ GDBusMethodInvocation *invocation,
+ gpointer user_data)
+{
+ InvocationData *data;
+
+ data = invocation_data_new (parameters, invocation);
+
+ if (g_strcmp0 (method_name, "SniffURI") == 0)
+ handle_sniff_uri (data);
+ else
+ g_assert_not_reached ();
+}
+
+static const GDBusInterfaceVTable interface_vtable =
+{
+ handle_method_call,
+ NULL, /* get_property */
+ NULL, /* set_property */
+};
+
+static void
+on_bus_acquired (GDBusConnection *connection,
+ const gchar *name,
+ gpointer user_data)
+{
+ GError *error = NULL;
+
+ print_debug ("Connected to the session bus: %s", name);
+
+ g_dbus_connection_register_object (connection,
+ "/org/gnome/Shell/HotplugSniffer",
+ introspection_data->interfaces[0],
+ &interface_vtable,
+ NULL,
+ NULL,
+ &error);
+
+ if (error != NULL)
+ {
+ g_printerr ("Error exporting object on the session bus: %s",
+ error->message);
+ g_error_free (error);
+
+ _exit(1);
+ }
+
+ print_debug ("Object exported on the session bus");
+}
+
+static void
+on_name_lost (GDBusConnection *connection,
+ const gchar *name,
+ gpointer user_data)
+{
+ print_debug ("Lost bus name: %s, exiting", name);
+
+ g_main_loop_quit (loop);
+}
+
+static void
+on_name_acquired (GDBusConnection *connection,
+ const gchar *name,
+ gpointer user_data)
+{
+ print_debug ("Acquired bus name: %s", name);
+}
+
+int
+main (int argc,
+ char **argv)
+{
+ guint name_owner_id;
+
+ introspection_data = g_dbus_node_info_new_for_xml (introspection_xml, NULL);
+ g_assert (introspection_data != NULL);
+
+ ensure_autoquit_on ();
+ loop = g_main_loop_new (NULL, FALSE);
+
+ name_owner_id = g_bus_own_name (G_BUS_TYPE_SESSION,
+ BUS_NAME, 0,
+ on_bus_acquired,
+ on_name_acquired,
+ on_name_lost,
+ NULL,
+ NULL);
+
+ g_main_loop_run (loop);
+
+ if (name_owner_id != 0)
+ g_bus_unown_name (name_owner_id);
+
+ if (loop != NULL)
+ g_main_loop_unref (loop);
+
+ return 0;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void __attribute__((format(printf, 1, 0)))
+print_debug (const gchar *format, ...)
+{
+ g_autofree char *s = NULL;
+ g_autofree char *timestamp = NULL;
+ va_list ap;
+ g_autoptr (GDateTime) now = NULL;
+ static size_t once_init_value = 0;
+ static gboolean show_debug = FALSE;
+ static guint pid = 0;
+
+ if (g_once_init_enter (&once_init_value))
+ {
+ show_debug = (g_getenv ("HOTPLUG_SNIFFER_DEBUG") != NULL);
+ pid = getpid ();
+ g_once_init_leave (&once_init_value, 1);
+ }
+
+ if (!show_debug)
+ goto out;
+
+ now = g_date_time_new_now_local ();
+ timestamp = g_date_time_format (now, "%H:%M:%S");
+
+ va_start (ap, format);
+ s = g_strdup_vprintf (format, ap);
+ va_end (ap);
+
+ g_print ("gnome-shell-hotplug-sniffer[%d]: %s.%03d: %s\n",
+ pid, timestamp, g_date_time_get_microsecond (now), s);
+ out:
+ ;
+}
+
diff --git a/src/hotplug-sniffer/meson.build b/src/hotplug-sniffer/meson.build
new file mode 100644
index 0000000..4a777e5
--- /dev/null
+++ b/src/hotplug-sniffer/meson.build
@@ -0,0 +1,22 @@
+hotplug_sources = [
+ 'hotplug-mimetypes.h',
+ 'shell-mime-sniffer.h',
+ 'shell-mime-sniffer.c',
+ 'hotplug-sniffer.c'
+]
+
+executable('gnome-shell-hotplug-sniffer', hotplug_sources,
+ dependencies: [gio_dep, gdk_pixbuf_dep],
+ include_directories: include_directories('../..'),
+ install_dir: libexecdir,
+ install: true
+)
+
+service_file = 'org.gnome.Shell.HotplugSniffer.service'
+
+configure_file(
+ input: service_file + '.in',
+ output: service_file,
+ configuration: service_data,
+ install_dir: servicedir
+)
diff --git a/src/hotplug-sniffer/org.gnome.Shell.HotplugSniffer.service.in b/src/hotplug-sniffer/org.gnome.Shell.HotplugSniffer.service.in
new file mode 100644
index 0000000..b14cea9
--- /dev/null
+++ b/src/hotplug-sniffer/org.gnome.Shell.HotplugSniffer.service.in
@@ -0,0 +1,3 @@
+[D-BUS Service]
+Name=org.gnome.Shell.HotplugSniffer
+Exec=@libexecdir@/gnome-shell-hotplug-sniffer
diff --git a/src/hotplug-sniffer/shell-mime-sniffer.c b/src/hotplug-sniffer/shell-mime-sniffer.c
new file mode 100644
index 0000000..7a1c1fe
--- /dev/null
+++ b/src/hotplug-sniffer/shell-mime-sniffer.c
@@ -0,0 +1,590 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * Copyright (C) 1999, 2000, 2001 Eazel, Inc.
+ * Copyright (C) 2011 Red Hat, Inc.
+ *
+ * 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, see <http://www.gnu.org/licenses/>.
+ *
+ * Author: Cosimo Cecchi <cosimoc@redhat.com>
+ *
+ * The code for crawling the directory hierarchy is based on
+ * nautilus/libnautilus-private/nautilus-directory-async.c, with
+ * the following copyright and author:
+ *
+ * Copyright (C) 1999, 2000, 2001 Eazel, Inc.
+ * Author: Darin Adler <darin@bentspoon.com>
+ *
+ */
+
+#include "shell-mime-sniffer.h"
+#include "hotplug-mimetypes.h"
+
+#include <glib/gi18n.h>
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+
+#define LOADER_ATTRS \
+ G_FILE_ATTRIBUTE_STANDARD_TYPE "," \
+ G_FILE_ATTRIBUTE_STANDARD_NAME "," \
+ G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE ","
+
+#define WATCHDOG_TIMEOUT 1500
+#define DIRECTORY_LOAD_ITEMS_PER_CALLBACK 100
+#define HIGH_SCORE_RATIO 0.10
+
+enum {
+ PROP_FILE = 1,
+ NUM_PROPERTIES
+};
+
+static GHashTable *image_type_table = NULL;
+static GHashTable *audio_type_table = NULL;
+static GHashTable *video_type_table = NULL;
+static GHashTable *docs_type_table = NULL;
+
+static GParamSpec *properties[NUM_PROPERTIES] = { NULL, };
+
+typedef struct {
+ ShellMimeSniffer *self;
+
+ GFile *file;
+ GFileEnumerator *enumerator;
+ GList *deep_count_subdirectories;
+
+ gint audio_count;
+ gint image_count;
+ gint document_count;
+ gint video_count;
+
+ gint total_items;
+} DeepCountState;
+
+typedef struct _ShellMimeSnifferPrivate ShellMimeSnifferPrivate;
+
+struct _ShellMimeSniffer
+{
+ GObject parent_instance;
+
+ ShellMimeSnifferPrivate *priv;
+};
+
+struct _ShellMimeSnifferPrivate {
+ GFile *file;
+
+ GCancellable *cancellable;
+ guint watchdog_id;
+
+ GTask *task;
+};
+
+G_DEFINE_TYPE_WITH_PRIVATE (ShellMimeSniffer, shell_mime_sniffer, G_TYPE_OBJECT);
+
+static void deep_count_load (DeepCountState *state,
+ GFile *file);
+
+static void
+init_mimetypes (void)
+{
+ static gsize once_init = 0;
+
+ if (g_once_init_enter (&once_init))
+ {
+ GSList *formats, *l;
+ GdkPixbufFormat *format;
+ gchar **types;
+ gint idx;
+
+ image_type_table = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
+ video_type_table = g_hash_table_new (g_str_hash, g_str_equal);
+ audio_type_table = g_hash_table_new (g_str_hash, g_str_equal);
+ docs_type_table = g_hash_table_new (g_str_hash, g_str_equal);
+
+ formats = gdk_pixbuf_get_formats ();
+
+ for (l = formats; l != NULL; l = l->next)
+ {
+ format = l->data;
+ types = gdk_pixbuf_format_get_mime_types (format);
+
+ for (idx = 0; types[idx] != NULL; idx++)
+ g_hash_table_insert (image_type_table, g_strdup (types[idx]), GINT_TO_POINTER (1));
+
+ g_strfreev (types);
+ }
+
+ g_slist_free (formats);
+
+ for (idx = 0; audio_mimetypes[idx] != NULL; idx++)
+ g_hash_table_insert (audio_type_table, (gpointer) audio_mimetypes[idx], GINT_TO_POINTER (1));
+
+ for (idx = 0; video_mimetypes[idx] != NULL; idx++)
+ g_hash_table_insert (video_type_table, (gpointer) video_mimetypes[idx], GINT_TO_POINTER (1));
+
+ for (idx = 0; docs_mimetypes[idx] != NULL; idx++)
+ g_hash_table_insert (docs_type_table, (gpointer) docs_mimetypes[idx], GINT_TO_POINTER (1));
+
+ g_once_init_leave (&once_init, 1);
+ }
+}
+
+static void
+add_content_type_to_cache (DeepCountState *state,
+ const gchar *content_type)
+{
+ gboolean matched = TRUE;
+
+ if (g_hash_table_lookup (image_type_table, content_type))
+ state->image_count++;
+ else if (g_hash_table_lookup (video_type_table, content_type))
+ state->video_count++;
+ else if (g_hash_table_lookup (docs_type_table, content_type))
+ state->document_count++;
+ else if (g_hash_table_lookup (audio_type_table, content_type))
+ state->audio_count++;
+ else
+ matched = FALSE;
+
+ if (matched)
+ state->total_items++;
+}
+
+typedef struct {
+ const gchar *type;
+ gdouble ratio;
+} SniffedResult;
+
+static gint
+results_cmp_func (gconstpointer a,
+ gconstpointer b)
+{
+ const SniffedResult *sniffed_a = a;
+ const SniffedResult *sniffed_b = b;
+
+ if (sniffed_a->ratio < sniffed_b->ratio)
+ return 1;
+
+ if (sniffed_a->ratio > sniffed_b->ratio)
+ return -1;
+
+ return 0;
+}
+
+static void
+prepare_async_result (DeepCountState *state)
+{
+ ShellMimeSniffer *self = state->self;
+ GArray *results;
+ GPtrArray *sniffed_mime;
+ SniffedResult result;
+ char **mimes;
+
+ sniffed_mime = g_ptr_array_new ();
+ results = g_array_new (TRUE, TRUE, sizeof (SniffedResult));
+
+ if (state->total_items == 0)
+ goto out;
+
+ result.type = "x-content/video";
+ result.ratio = (gdouble) state->video_count / (gdouble) state->total_items;
+ g_array_append_val (results, result);
+
+ result.type = "x-content/audio";
+ result.ratio = (gdouble) state->audio_count / (gdouble) state->total_items;
+ g_array_append_val (results, result);
+
+ result.type = "x-content/pictures";
+ result.ratio = (gdouble) state->image_count / (gdouble) state->total_items;
+ g_array_append_val (results, result);
+
+ result.type = "x-content/documents";
+ result.ratio = (gdouble) state->document_count / (gdouble) state->total_items;
+ g_array_append_val (results, result);
+
+ g_array_sort (results, results_cmp_func);
+
+ result = g_array_index (results, SniffedResult, 0);
+ g_ptr_array_add (sniffed_mime, g_strdup (result.type));
+
+ /* if other types score high in ratio, add them, up to three */
+ result = g_array_index (results, SniffedResult, 1);
+ if (result.ratio < HIGH_SCORE_RATIO)
+ goto out;
+ g_ptr_array_add (sniffed_mime, g_strdup (result.type));
+
+ result = g_array_index (results, SniffedResult, 2);
+ if (result.ratio < HIGH_SCORE_RATIO)
+ goto out;
+ g_ptr_array_add (sniffed_mime, g_strdup (result.type));
+
+ out:
+ g_ptr_array_add (sniffed_mime, NULL);
+ mimes = (gchar **) g_ptr_array_free (sniffed_mime, FALSE);
+
+ g_array_free (results, TRUE);
+ g_task_return_pointer (self->priv->task, mimes, (GDestroyNotify)g_strfreev);
+}
+
+/* adapted from nautilus/libnautilus-private/nautilus-directory-async.c */
+static void
+deep_count_one (DeepCountState *state,
+ GFileInfo *info)
+{
+ GFile *subdir;
+ const char *content_type;
+
+ if (g_file_info_get_file_type (info) == G_FILE_TYPE_DIRECTORY)
+ {
+ /* record the fact that we have to descend into this directory */
+ subdir = g_file_get_child (state->file, g_file_info_get_name (info));
+ state->deep_count_subdirectories =
+ g_list_append (state->deep_count_subdirectories, subdir);
+ }
+ else
+ {
+ content_type = g_file_info_get_content_type (info);
+
+ if (content_type)
+ add_content_type_to_cache (state, content_type);
+ }
+}
+
+static void
+deep_count_finish (DeepCountState *state)
+{
+ prepare_async_result (state);
+
+ if (state->enumerator)
+ {
+ if (!g_file_enumerator_is_closed (state->enumerator))
+ g_file_enumerator_close_async (state->enumerator,
+ 0, NULL, NULL, NULL);
+
+ g_object_unref (state->enumerator);
+ }
+
+ g_cancellable_reset (state->self->priv->cancellable);
+ g_clear_object (&state->file);
+
+ g_list_free_full (state->deep_count_subdirectories, g_object_unref);
+
+ g_free (state);
+}
+
+static void
+deep_count_next_dir (DeepCountState *state)
+{
+ GFile *new_file;
+
+ g_clear_object (&state->file);
+
+ if (state->deep_count_subdirectories != NULL)
+ {
+ /* Work on a new directory. */
+ new_file = state->deep_count_subdirectories->data;
+ state->deep_count_subdirectories =
+ g_list_remove (state->deep_count_subdirectories, new_file);
+
+ deep_count_load (state, new_file);
+ g_object_unref (new_file);
+ }
+ else
+ {
+ deep_count_finish (state);
+ }
+}
+
+static void
+deep_count_more_files_callback (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ DeepCountState *state;
+ GList *files, *l;
+ GFileInfo *info;
+
+ state = user_data;
+
+ if (g_cancellable_is_cancelled (state->self->priv->cancellable))
+ {
+ deep_count_finish (state);
+ return;
+ }
+
+ files = g_file_enumerator_next_files_finish (state->enumerator,
+ res, NULL);
+
+ for (l = files; l != NULL; l = l->next)
+ {
+ info = l->data;
+ deep_count_one (state, info);
+ g_object_unref (info);
+ }
+
+ if (files == NULL)
+ {
+ g_file_enumerator_close_async (state->enumerator, 0, NULL, NULL, NULL);
+ g_object_unref (state->enumerator);
+ state->enumerator = NULL;
+
+ deep_count_next_dir (state);
+ }
+ else
+ {
+ g_file_enumerator_next_files_async (state->enumerator,
+ DIRECTORY_LOAD_ITEMS_PER_CALLBACK,
+ G_PRIORITY_LOW,
+ state->self->priv->cancellable,
+ deep_count_more_files_callback,
+ state);
+ }
+
+ g_list_free (files);
+}
+
+static void
+deep_count_callback (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ DeepCountState *state;
+ GFileEnumerator *enumerator;
+
+ state = user_data;
+
+ if (g_cancellable_is_cancelled (state->self->priv->cancellable))
+ {
+ deep_count_finish (state);
+ return;
+ }
+
+ enumerator = g_file_enumerate_children_finish (G_FILE (source_object),
+ res, NULL);
+
+ if (enumerator == NULL)
+ {
+ deep_count_next_dir (state);
+ }
+ else
+ {
+ state->enumerator = enumerator;
+ g_file_enumerator_next_files_async (state->enumerator,
+ DIRECTORY_LOAD_ITEMS_PER_CALLBACK,
+ G_PRIORITY_LOW,
+ state->self->priv->cancellable,
+ deep_count_more_files_callback,
+ state);
+ }
+}
+
+static void
+deep_count_load (DeepCountState *state,
+ GFile *file)
+{
+ state->file = g_object_ref (file);
+
+ g_file_enumerate_children_async (state->file,
+ LOADER_ATTRS,
+ G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, /* flags */
+ G_PRIORITY_LOW, /* prio */
+ state->self->priv->cancellable,
+ deep_count_callback,
+ state);
+}
+
+static void
+deep_count_start (ShellMimeSniffer *self)
+{
+ DeepCountState *state;
+
+ state = g_new0 (DeepCountState, 1);
+ state->self = self;
+
+ deep_count_load (state, self->priv->file);
+}
+
+static void
+query_info_async_ready_cb (GObject *source,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ GFileInfo *info;
+ GError *error = NULL;
+ ShellMimeSniffer *self = user_data;
+
+ info = g_file_query_info_finish (G_FILE (source),
+ res, &error);
+
+ if (error != NULL)
+ {
+ g_task_return_error (self->priv->task, error);
+
+ return;
+ }
+
+ if (g_file_info_get_file_type (info) != G_FILE_TYPE_DIRECTORY)
+ {
+ g_task_return_new_error (self->priv->task,
+ G_IO_ERROR,
+ G_IO_ERROR_NOT_DIRECTORY,
+ "Not a directory");
+
+ return;
+ }
+
+ deep_count_start (self);
+}
+
+static gboolean
+watchdog_timeout_reached_cb (gpointer user_data)
+{
+ ShellMimeSniffer *self = user_data;
+
+ self->priv->watchdog_id = 0;
+ g_cancellable_cancel (self->priv->cancellable);
+
+ return FALSE;
+}
+
+static void
+start_loading_file (ShellMimeSniffer *self)
+{
+ g_file_query_info_async (self->priv->file,
+ LOADER_ATTRS,
+ G_FILE_QUERY_INFO_NONE,
+ G_PRIORITY_DEFAULT,
+ self->priv->cancellable,
+ query_info_async_ready_cb,
+ self);
+}
+
+static void
+shell_mime_sniffer_set_file (ShellMimeSniffer *self,
+ GFile *file)
+{
+ g_clear_object (&self->priv->file);
+ self->priv->file = g_object_ref (file);
+}
+
+static void
+shell_mime_sniffer_dispose (GObject *object)
+{
+ ShellMimeSniffer *self = SHELL_MIME_SNIFFER (object);
+
+ g_clear_object (&self->priv->file);
+ g_clear_object (&self->priv->cancellable);
+ g_clear_object (&self->priv->task);
+
+ g_clear_handle_id (&self->priv->watchdog_id, g_source_remove);
+
+ G_OBJECT_CLASS (shell_mime_sniffer_parent_class)->dispose (object);
+}
+
+static void
+shell_mime_sniffer_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ ShellMimeSniffer *self = SHELL_MIME_SNIFFER (object);
+
+ switch (prop_id) {
+ case PROP_FILE:
+ g_value_set_object (value, self->priv->file);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+shell_mime_sniffer_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ ShellMimeSniffer *self = SHELL_MIME_SNIFFER (object);
+
+ switch (prop_id) {
+ case PROP_FILE:
+ shell_mime_sniffer_set_file (self, g_value_get_object (value));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+shell_mime_sniffer_class_init (ShellMimeSnifferClass *klass)
+{
+ GObjectClass *oclass;
+
+ oclass = G_OBJECT_CLASS (klass);
+ oclass->dispose = shell_mime_sniffer_dispose;
+ oclass->get_property = shell_mime_sniffer_get_property;
+ oclass->set_property = shell_mime_sniffer_set_property;
+
+ properties[PROP_FILE] =
+ g_param_spec_object ("file",
+ "File",
+ "The loaded file",
+ G_TYPE_FILE,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+
+ g_object_class_install_properties (oclass, NUM_PROPERTIES, properties);
+}
+
+static void
+shell_mime_sniffer_init (ShellMimeSniffer *self)
+{
+ self->priv = shell_mime_sniffer_get_instance_private (self);
+ init_mimetypes ();
+}
+
+ShellMimeSniffer *
+shell_mime_sniffer_new (GFile *file)
+{
+ return g_object_new (SHELL_TYPE_MIME_SNIFFER,
+ "file", file,
+ NULL);
+}
+
+void
+shell_mime_sniffer_sniff_async (ShellMimeSniffer *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ g_assert (self->priv->watchdog_id == 0);
+ g_assert (self->priv->task == NULL);
+
+ self->priv->cancellable = g_cancellable_new ();
+ self->priv->task = g_task_new (self, self->priv->cancellable,
+ callback, user_data);
+
+ self->priv->watchdog_id =
+ g_timeout_add (WATCHDOG_TIMEOUT,
+ watchdog_timeout_reached_cb, self);
+ g_source_set_name_by_id (self->priv->watchdog_id, "[gnome-shell] watchdog_timeout_reached_cb");
+
+ start_loading_file (self);
+}
+
+gchar **
+shell_mime_sniffer_sniff_finish (ShellMimeSniffer *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_pointer (self->priv->task, error);
+}
diff --git a/src/hotplug-sniffer/shell-mime-sniffer.h b/src/hotplug-sniffer/shell-mime-sniffer.h
new file mode 100644
index 0000000..3936eef
--- /dev/null
+++ b/src/hotplug-sniffer/shell-mime-sniffer.h
@@ -0,0 +1,46 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * Copyright (C) 2011 Red Hat, Inc.
+ *
+ * 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, see <http://www.gnu.org/licenses/>.
+ *
+ * Author: Cosimo Cecchi <cosimoc@redhat.com>
+ *
+ */
+
+#ifndef __SHELL_MIME_SNIFFER_H__
+#define __SHELL_MIME_SNIFFER_H__
+
+#include <glib-object.h>
+#include <gio/gio.h>
+
+G_BEGIN_DECLS
+
+#define SHELL_TYPE_MIME_SNIFFER (shell_mime_sniffer_get_type ())
+G_DECLARE_FINAL_TYPE (ShellMimeSniffer, shell_mime_sniffer,
+ SHELL, MIME_SNIFFER, GObject)
+
+ShellMimeSniffer *shell_mime_sniffer_new (GFile *file);
+
+void shell_mime_sniffer_sniff_async (ShellMimeSniffer *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+
+gchar ** shell_mime_sniffer_sniff_finish (ShellMimeSniffer *self,
+ GAsyncResult *res,
+ GError **error);
+
+G_END_DECLS
+
+#endif /* __SHELL_MIME_SNIFFER_H__ */
diff --git a/src/main.c b/src/main.c
new file mode 100644
index 0000000..2311a74
--- /dev/null
+++ b/src/main.c
@@ -0,0 +1,599 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+#include "config.h"
+
+#if defined (HAVE_MALLINFO) || defined (HAVE_MALLINFO2)
+#include <malloc.h>
+#endif
+#include <stdlib.h>
+#include <string.h>
+
+#include <cogl-pango/cogl-pango.h>
+#include <clutter/clutter.h>
+#include <gtk/gtk.h>
+#include <glib-unix.h>
+#include <glib/gi18n-lib.h>
+#include <girepository.h>
+#include <meta/meta-context.h>
+#include <meta/meta-plugin.h>
+#include <meta/prefs.h>
+#include <atk-bridge.h>
+
+#include "shell-global.h"
+#include "shell-global-private.h"
+#include "shell-perf-log.h"
+#include "st.h"
+
+extern GType gnome_shell_plugin_get_type (void);
+
+#define SHELL_DBUS_SERVICE "org.gnome.Shell"
+
+#define WM_NAME "GNOME Shell"
+#define GNOME_WM_KEYBINDINGS "Mutter,GNOME Shell"
+
+static gboolean is_gdm_mode = FALSE;
+static char *session_mode = NULL;
+static int caught_signal = 0;
+
+#define DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER 1
+#define DBUS_REQUEST_NAME_REPLY_ALREADY_OWNER 4
+
+enum {
+ SHELL_DEBUG_BACKTRACE_WARNINGS = 1,
+ SHELL_DEBUG_BACKTRACE_SEGFAULTS = 2,
+};
+static int _shell_debug;
+static gboolean _tracked_signals[NSIG] = { 0 };
+
+static void
+shell_dbus_acquire_name (GDBusProxy *bus,
+ guint32 request_name_flags,
+ guint32 *request_name_result,
+ const gchar *name,
+ gboolean fatal)
+{
+ GError *error = NULL;
+ GVariant *request_name_variant;
+
+ if (!(request_name_variant = g_dbus_proxy_call_sync (bus,
+ "RequestName",
+ g_variant_new ("(su)", name, request_name_flags),
+ 0, /* call flags */
+ -1, /* timeout */
+ NULL, /* cancellable */
+ &error)))
+ {
+ g_printerr ("failed to acquire %s: %s\n", name, error->message);
+ g_clear_error (&error);
+ if (!fatal)
+ return;
+ exit (1);
+ }
+ g_variant_get (request_name_variant, "(u)", request_name_result);
+ g_variant_unref (request_name_variant);
+}
+
+static void
+shell_dbus_init (gboolean replace)
+{
+ GDBusConnection *session;
+ GDBusProxy *bus;
+ GError *error = NULL;
+ guint32 request_name_flags;
+ guint32 request_name_result;
+
+ session = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error);
+
+ if (error) {
+ g_printerr ("Failed to connect to session bus: %s", error->message);
+ exit (1);
+ }
+
+ bus = g_dbus_proxy_new_sync (session,
+ G_DBUS_PROXY_FLAGS_NONE,
+ NULL, /* interface info */
+ "org.freedesktop.DBus",
+ "/org/freedesktop/DBus",
+ "org.freedesktop.DBus",
+ NULL, /* cancellable */
+ &error);
+
+ if (!bus)
+ {
+ g_printerr ("Failed to get a session bus proxy: %s", error->message);
+ exit (1);
+ }
+
+ request_name_flags = G_BUS_NAME_OWNER_FLAGS_ALLOW_REPLACEMENT;
+ if (replace)
+ request_name_flags |= G_BUS_NAME_OWNER_FLAGS_REPLACE;
+
+ shell_dbus_acquire_name (bus,
+ request_name_flags,
+ &request_name_result,
+ SHELL_DBUS_SERVICE, TRUE);
+ if (!(request_name_result == DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER
+ || request_name_result == DBUS_REQUEST_NAME_REPLY_ALREADY_OWNER))
+ {
+ g_printerr (SHELL_DBUS_SERVICE " already exists on bus and --replace not specified\n");
+ exit (1);
+ }
+
+ g_object_unref (bus);
+ g_object_unref (session);
+}
+
+static void
+shell_introspection_init (void)
+{
+
+ g_irepository_prepend_search_path (MUTTER_TYPELIB_DIR);
+ g_irepository_prepend_search_path (GNOME_SHELL_PKGLIBDIR);
+
+ /* We need to explicitly add the directories where the private libraries are
+ * installed to the GIR's library path, so that they can be found at runtime
+ * when linking using DT_RUNPATH (instead of DT_RPATH), which is the default
+ * for some linkers (e.g. gold) and in some distros (e.g. Debian).
+ */
+ g_irepository_prepend_library_path (MUTTER_TYPELIB_DIR);
+ g_irepository_prepend_library_path (GNOME_SHELL_PKGLIBDIR);
+}
+
+static void
+shell_fonts_init (void)
+{
+ CoglPangoFontMap *fontmap;
+
+ /* Disable text mipmapping; it causes problems on pre-GEM Intel
+ * drivers and we should just be rendering text at the right
+ * size rather than scaling it. If we do effects where we dynamically
+ * zoom labels, then we might want to reconsider.
+ */
+ fontmap = COGL_PANGO_FONT_MAP (clutter_get_font_map ());
+ cogl_pango_font_map_set_use_mipmapping (fontmap, FALSE);
+}
+
+static void
+shell_profiler_init (void)
+{
+ ShellGlobal *global;
+ GjsProfiler *profiler;
+ GjsContext *context;
+ const char *enabled;
+ const char *fd_str;
+ int fd = -1;
+
+ /* Sysprof uses the "GJS_TRACE_FD=N" environment variable to connect GJS
+ * profiler data to the combined Sysprof capture. Since we are in control of
+ * the GjsContext, we need to proxy this FD across to the GJS profiler.
+ */
+
+ fd_str = g_getenv ("GJS_TRACE_FD");
+ enabled = g_getenv ("GJS_ENABLE_PROFILER");
+ if (fd_str == NULL || enabled == NULL)
+ return;
+
+ global = shell_global_get ();
+ g_return_if_fail (global);
+
+ context = _shell_global_get_gjs_context (global);
+ g_return_if_fail (context);
+
+ profiler = gjs_context_get_profiler (context);
+ g_return_if_fail (profiler);
+
+ if (fd_str)
+ {
+ fd = atoi (fd_str);
+
+ if (fd > 2)
+ {
+ gjs_profiler_set_fd (profiler, fd);
+ gjs_profiler_start (profiler);
+ }
+ }
+}
+
+static void
+shell_profiler_shutdown (void)
+{
+ ShellGlobal *global;
+ GjsProfiler *profiler;
+ GjsContext *context;
+
+ global = shell_global_get ();
+ context = _shell_global_get_gjs_context (global);
+ profiler = gjs_context_get_profiler (context);
+
+ if (profiler)
+ gjs_profiler_stop (profiler);
+}
+
+static void
+malloc_statistics_callback (ShellPerfLog *perf_log,
+ gpointer data)
+{
+#if defined (HAVE_MALLINFO) || defined (HAVE_MALLINFO2)
+#ifdef HAVE_MALLINFO2
+ struct mallinfo2 info = mallinfo2 ();
+#else
+ struct mallinfo info = mallinfo ();
+#endif
+
+ shell_perf_log_update_statistic_i (perf_log,
+ "malloc.arenaSize",
+ info.arena);
+ shell_perf_log_update_statistic_i (perf_log,
+ "malloc.mmapSize",
+ info.hblkhd);
+ shell_perf_log_update_statistic_i (perf_log,
+ "malloc.usedSize",
+ info.uordblks);
+#endif /* defined (HAVE_MALLINFO) || defined (HAVE_MALLINFO2) */
+}
+
+static void
+shell_perf_log_init (void)
+{
+ ShellPerfLog *perf_log = shell_perf_log_get_default ();
+
+ /* For probably historical reasons, mallinfo() defines the returned values,
+ * even those in bytes as int, not size_t. We're determined not to use
+ * more than 2G of malloc'ed memory, so are OK with that.
+ */
+ shell_perf_log_define_statistic (perf_log,
+ "malloc.arenaSize",
+ "Amount of memory allocated by malloc() with brk(), in bytes",
+ "i");
+ shell_perf_log_define_statistic (perf_log,
+ "malloc.mmapSize",
+ "Amount of memory allocated by malloc() with mmap(), in bytes",
+ "i");
+ shell_perf_log_define_statistic (perf_log,
+ "malloc.usedSize",
+ "Amount of malloc'ed memory currently in use",
+ "i");
+
+ shell_perf_log_add_statistics_callback (perf_log,
+ malloc_statistics_callback,
+ NULL, NULL);
+}
+
+static void
+shell_a11y_init (void)
+{
+ cally_accessibility_init ();
+
+ if (clutter_get_accessibility_enabled () == FALSE)
+ {
+ g_warning ("Accessibility: clutter has no accessibility enabled"
+ " skipping the atk-bridge load");
+ }
+ else
+ {
+ atk_bridge_adaptor_init (NULL, NULL);
+ }
+}
+
+static void
+shell_init_debug (const char *debug_env)
+{
+ static const GDebugKey keys[] = {
+ { "backtrace-warnings", SHELL_DEBUG_BACKTRACE_WARNINGS },
+ { "backtrace-segfaults", SHELL_DEBUG_BACKTRACE_SEGFAULTS },
+ };
+
+ _shell_debug = g_parse_debug_string (debug_env, keys,
+ G_N_ELEMENTS (keys));
+}
+
+static GLogWriterOutput
+default_log_writer (GLogLevelFlags log_level,
+ const GLogField *fields,
+ gsize n_fields,
+ gpointer user_data)
+{
+ GLogWriterOutput output;
+ int i;
+
+ output = g_log_writer_default (log_level, fields, n_fields, user_data);
+
+ if ((_shell_debug & SHELL_DEBUG_BACKTRACE_WARNINGS) &&
+ ((log_level & G_LOG_LEVEL_CRITICAL) ||
+ (log_level & G_LOG_LEVEL_WARNING)))
+ {
+ const char *log_domain = NULL;
+
+ for (i = 0; i < n_fields; i++)
+ {
+ if (g_strcmp0 (fields[i].key, "GLIB_DOMAIN") == 0)
+ {
+ log_domain = fields[i].value;
+ break;
+ }
+ }
+
+ /* Filter out Gjs logs, those already have the stack */
+ if (g_strcmp0 (log_domain, "Gjs") != 0)
+ gjs_dumpstack ();
+ }
+
+ return output;
+}
+
+static GLogWriterOutput
+shut_up (GLogLevelFlags log_level,
+ const GLogField *fields,
+ gsize n_fields,
+ gpointer user_data)
+{
+ return (GLogWriterOutput) {0};
+}
+
+static void
+dump_gjs_stack_alarm_sigaction (int signo)
+{
+ g_log_set_writer_func (g_log_writer_default, NULL, NULL);
+ g_warning ("Failed to dump Javascript stack, got stuck");
+ g_log_set_writer_func (default_log_writer, NULL, NULL);
+
+ raise (caught_signal);
+}
+
+static void
+dump_gjs_stack_on_signal_handler (int signo)
+{
+ struct sigaction sa = { .sa_handler = dump_gjs_stack_alarm_sigaction };
+ gsize i;
+
+ /* Ignore all the signals starting this point, a part the one we'll raise
+ * (which is implicitly ignored here through SA_RESETHAND), this is needed
+ * not to get this handler being called by other signals that we were
+ * tracking and that might be emitted by code called starting from now.
+ */
+ for (i = 0; i < G_N_ELEMENTS (_tracked_signals); ++i)
+ {
+ if (_tracked_signals[i] && i != signo)
+ signal (i, SIG_IGN);
+ }
+
+ /* Waiting at least 5 seconds for the dumpstack, if it fails, we raise the error */
+ caught_signal = signo;
+ sigemptyset (&sa.sa_mask);
+ sigaction (SIGALRM, &sa, NULL);
+
+ alarm (5);
+ gjs_dumpstack ();
+ alarm (0);
+
+ raise (signo);
+}
+
+static void
+dump_gjs_stack_on_signal (int signo)
+{
+ struct sigaction sa = {
+ .sa_flags = SA_RESETHAND | SA_NODEFER,
+ .sa_handler = dump_gjs_stack_on_signal_handler,
+ };
+
+ sigemptyset (&sa.sa_mask);
+
+ sigaction (signo, &sa, NULL);
+ _tracked_signals[signo] = TRUE;
+}
+
+static gboolean
+list_modes (const char *option_name,
+ const char *value,
+ gpointer data,
+ GError **error)
+{
+ ShellGlobal *global;
+ GjsContext *context;
+ const char *script;
+ int status;
+
+ /* Many of our imports require global to be set, so rather than
+ * tayloring our imports carefully here to avoid that dependency,
+ * we just set it.
+ * ShellGlobal has some GTK+ dependencies, so initialize GTK+; we
+ * don't really care if it fails though (e.g. when running from a tty),
+ * so we mute all warnings */
+ g_log_set_writer_func (shut_up, NULL, NULL);
+ gtk_init_check (NULL, NULL);
+
+ _shell_global_init (NULL);
+ global = shell_global_get ();
+ context = _shell_global_get_gjs_context (global);
+
+ shell_introspection_init ();
+
+ script = "imports.ui.environment.init();"
+ "imports.ui.sessionMode.listModes();";
+ if (!gjs_context_eval (context, script, -1, "<main>", &status, NULL))
+ g_message ("Retrieving list of available modes failed.");
+
+ g_object_unref (context);
+ exit (status);
+}
+
+static gboolean
+print_version (const gchar *option_name,
+ const gchar *value,
+ gpointer data,
+ GError **error)
+{
+ g_print ("GNOME Shell %s\n", VERSION);
+ exit (0);
+}
+
+GOptionEntry gnome_shell_options[] = {
+ {
+ "version", 0, G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK,
+ print_version,
+ N_("Print version"),
+ NULL
+ },
+ {
+ "gdm-mode", 0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_NONE,
+ &is_gdm_mode,
+ N_("Mode used by GDM for login screen"),
+ NULL
+ },
+ {
+ "mode", 0, 0, G_OPTION_ARG_STRING,
+ &session_mode,
+ N_("Use a specific mode, e.g. “gdm” for login screen"),
+ "MODE"
+ },
+ {
+ "list-modes", 0, G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK,
+ list_modes,
+ N_("List possible modes"),
+ NULL
+ },
+ { NULL }
+};
+
+static gboolean
+on_sigterm (gpointer user_data)
+{
+ MetaContext *context = META_CONTEXT (user_data);
+
+ meta_context_terminate (context);
+
+ return G_SOURCE_REMOVE;
+}
+
+static void
+init_signal_handlers (MetaContext *context)
+{
+ struct sigaction act = { 0 };
+ sigset_t empty_mask;
+
+ sigemptyset (&empty_mask);
+ act.sa_handler = SIG_IGN;
+ act.sa_mask = empty_mask;
+ act.sa_flags = 0;
+ if (sigaction (SIGPIPE, &act, NULL) < 0)
+ g_warning ("Failed to register SIGPIPE handler: %s", g_strerror (errno));
+#ifdef SIGXFSZ
+ if (sigaction (SIGXFSZ, &act, NULL) < 0)
+ g_warning ("Failed to register SIGXFSZ handler: %s", g_strerror (errno));
+#endif
+
+ g_unix_signal_add (SIGTERM, on_sigterm, context);
+}
+
+static void
+change_to_home_directory (void)
+{
+ const char *home_dir;
+
+ home_dir = g_get_home_dir ();
+ if (!home_dir)
+ return;
+
+ if (chdir (home_dir) < 0)
+ g_warning ("Could not change to home directory %s", home_dir);
+}
+
+int
+main (int argc, char **argv)
+{
+ g_autoptr (MetaContext) context = NULL;
+ GError *error = NULL;
+ int ecode = EXIT_SUCCESS;
+
+ bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR);
+ bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
+ textdomain (GETTEXT_PACKAGE);
+
+ context = meta_create_context (WM_NAME);
+ meta_context_add_option_entries (context, gnome_shell_options,
+ GETTEXT_PACKAGE);
+ meta_context_add_option_group (context, g_irepository_get_option_group ());
+
+ session_mode = (char *) g_getenv ("GNOME_SHELL_SESSION_MODE");
+
+ if (!meta_context_configure (context, &argc, &argv, &error))
+ {
+ g_printerr ("Failed to configure: %s", error->message);
+ return EXIT_FAILURE;
+ }
+
+ meta_context_set_plugin_gtype (context, gnome_shell_plugin_get_type ());
+ meta_context_set_gnome_wm_keybindings (context, GNOME_WM_KEYBINDINGS);
+
+ init_signal_handlers (context);
+ change_to_home_directory ();
+
+ if (!meta_context_setup (context, &error))
+ {
+ g_printerr ("Failed to setup: %s", error->message);
+ return EXIT_FAILURE;
+ }
+
+ /* FIXME: Add gjs API to set this stuff and don't depend on the
+ * environment. These propagate to child processes.
+ */
+ g_setenv ("GJS_DEBUG_OUTPUT", "stderr", TRUE);
+ g_setenv ("GJS_DEBUG_TOPICS", "JS ERROR;JS LOG", TRUE);
+
+ shell_init_debug (g_getenv ("SHELL_DEBUG"));
+
+ shell_dbus_init (meta_context_is_replacing (context));
+ shell_a11y_init ();
+ shell_perf_log_init ();
+ shell_introspection_init ();
+ shell_fonts_init ();
+
+ g_log_set_writer_func (default_log_writer, NULL, NULL);
+
+ /* Initialize the global object */
+ if (session_mode == NULL)
+ session_mode = is_gdm_mode ? (char *)"gdm" : (char *)"user";
+
+ _shell_global_init ("session-mode", session_mode, NULL);
+
+ dump_gjs_stack_on_signal (SIGABRT);
+ dump_gjs_stack_on_signal (SIGFPE);
+ dump_gjs_stack_on_signal (SIGIOT);
+ dump_gjs_stack_on_signal (SIGTRAP);
+
+ if ((_shell_debug & SHELL_DEBUG_BACKTRACE_SEGFAULTS))
+ {
+ dump_gjs_stack_on_signal (SIGBUS);
+ dump_gjs_stack_on_signal (SIGSEGV);
+ }
+
+ shell_profiler_init ();
+
+ if (meta_context_get_compositor_type (context) == META_COMPOSITOR_TYPE_WAYLAND)
+ meta_context_raise_rlimit_nofile (context, NULL);
+
+ if (!meta_context_start (context, &error))
+ {
+ g_printerr ("GNOME Shell failed to start: %s", error->message);
+ return EXIT_FAILURE;
+ }
+
+ if (!meta_context_run_main_loop (context, &error))
+ {
+ g_printerr ("GNOME Shell terminated with an error: %s", error->message);
+ ecode = EXIT_FAILURE;
+ }
+
+ meta_context_destroy (g_steal_pointer (&context));
+
+ shell_profiler_shutdown ();
+
+#if 0
+ g_debug ("Doing final cleanup");
+ _shell_global_destroy_gjs_context (shell_global_get ());
+ g_object_unref (shell_global_get ());
+#endif
+
+ return ecode;
+}
diff --git a/src/meson.build b/src/meson.build
new file mode 100644
index 0000000..fc7f8bf
--- /dev/null
+++ b/src/meson.build
@@ -0,0 +1,276 @@
+service_data = configuration_data()
+service_data.set('libexecdir', libexecdir)
+
+subdir('calendar-server')
+subdir('hotplug-sniffer')
+subdir('st')
+subdir('tray')
+
+script_data = configuration_data()
+script_data.set('bindir', bindir)
+script_data.set('datadir', datadir)
+script_data.set('libdir', libdir)
+script_data.set('libexecdir', libexecdir)
+script_data.set('pkgdatadir', pkgdatadir)
+script_data.set('pkglibdir', pkglibdir)
+script_data.set('PYTHON', python.full_path())
+script_data.set('VERSION', meson.project_version())
+
+script_tools = ['gnome-shell-perf-tool']
+
+if get_option('extensions_tool')
+ script_tools += 'gnome-shell-extension-tool'
+endif
+
+foreach tool : script_tools
+ configure_file(
+ input: tool + '.in',
+ output: tool,
+ configuration: script_data,
+ install_dir: bindir
+ )
+endforeach
+
+install_data('gnome-shell-extension-prefs',
+ install_dir: bindir
+)
+
+gnome_shell_cflags = [
+ '-DCLUTTER_ENABLE_EXPERIMENTAL_API',
+ '-DCOGL_ENABLE_EXPERIMENTAL_API',
+ '-DVERSION="@0@"'.format(meson.project_version()),
+ '-DLOCALEDIR="@0@"'.format(localedir),
+ '-DDATADIR="@0@"'.format(datadir),
+ '-DGNOME_SHELL_LIBEXECDIR="@0@"'.format(libexecdir),
+ '-DGNOME_SHELL_DATADIR="@0@"'.format(pkgdatadir),
+ '-DGNOME_SHELL_PKGLIBDIR="@0@"'.format(pkglibdir)
+]
+
+install_rpath = ':'.join([mutter_typelibdir, pkglibdir])
+
+gnome_shell_deps = [
+ gio_unix_dep,
+ libxml_dep,
+ gtk_dep,
+ atk_bridge_dep,
+ gjs_dep,
+ gdk_x11_dep,
+ clutter_dep,
+ cogl_pango_dep,
+ startup_dep,
+ gi_dep,
+ polkit_dep,
+ gcr_dep,
+ libsystemd_dep
+]
+
+gnome_shell_deps += nm_deps
+
+tools_cflags = '-DLOCALEDIR="@0@"'.format(localedir)
+tools_deps = [gio_dep, gjs_dep]
+
+libshell_menu_sources = [
+ 'gtkactionmuxer.h',
+ 'gtkactionmuxer.c',
+ 'gtkactionobservable.h',
+ 'gtkactionobservable.c',
+ 'gtkactionobserver.h',
+ 'gtkactionobserver.c'
+]
+
+libshell_menu = library('gnome-shell-menu',
+ sources: libshell_menu_sources,
+ dependencies: [gio_dep, clutter_dep],
+ include_directories: conf_inc,
+ build_rpath: mutter_typelibdir,
+ install_rpath: mutter_typelibdir,
+ install_dir: pkglibdir,
+ install: true
+)
+
+libshell_menu_dep = declare_dependency(link_with: libshell_menu)
+
+libshell_public_headers = [
+ 'shell-app.h',
+ 'shell-app-system.h',
+ 'shell-app-usage.h',
+ 'shell-blur-effect.h',
+ 'shell-embedded-window.h',
+ 'shell-glsl-effect.h',
+ 'shell-gtk-embed.h',
+ 'shell-global.h',
+ 'shell-invert-lightness-effect.h',
+ 'shell-action-modes.h',
+ 'shell-mount-operation.h',
+ 'shell-perf-log.h',
+ 'shell-screenshot.h',
+ 'shell-square-bin.h',
+ 'shell-stack.h',
+ 'shell-tray-icon.h',
+ 'shell-tray-manager.h',
+ 'shell-util.h',
+ 'shell-window-preview.h',
+ 'shell-window-preview-layout.h',
+ 'shell-window-tracker.h',
+ 'shell-wm.h',
+ 'shell-workspace-background.h'
+]
+
+if have_networkmanager
+ libshell_public_headers += 'shell-network-agent.h'
+endif
+
+libshell_private_headers = [
+ 'shell-app-private.h',
+ 'shell-app-cache-private.h',
+ 'shell-app-system-private.h',
+ 'shell-global-private.h',
+ 'shell-window-tracker-private.h',
+ 'shell-wm-private.h'
+]
+
+libshell_sources = [
+ 'gnome-shell-plugin.c',
+ 'shell-app.c',
+ 'shell-app-system.c',
+ 'shell-app-usage.c',
+ 'shell-blur-effect.c',
+ 'shell-embedded-window.c',
+ 'shell-embedded-window-private.h',
+ 'shell-global.c',
+ 'shell-glsl-effect.c',
+ 'shell-gtk-embed.c',
+ 'shell-invert-lightness-effect.c',
+ 'shell-keyring-prompt.c',
+ 'shell-keyring-prompt.h',
+ 'shell-mount-operation.c',
+ 'shell-perf-log.c',
+ 'shell-polkit-authentication-agent.c',
+ 'shell-polkit-authentication-agent.h',
+ 'shell-screenshot.c',
+ 'shell-secure-text-buffer.c',
+ 'shell-secure-text-buffer.h',
+ 'shell-square-bin.c',
+ 'shell-stack.c',
+ 'shell-tray-icon.c',
+ 'shell-tray-manager.c',
+ 'shell-util.c',
+ 'shell-window-preview.c',
+ 'shell-window-preview-layout.c',
+ 'shell-window-tracker.c',
+ 'shell-wm.c',
+ 'shell-workspace-background.c'
+]
+
+if have_networkmanager
+ libshell_sources += 'shell-network-agent.c'
+endif
+
+libshell_private_sources = [
+ 'shell-app-cache.c',
+]
+
+libshell_enums = gnome.mkenums_simple('shell-enum-types',
+ sources: libshell_public_headers
+)
+
+libshell_gir_sources = [
+ libshell_enums,
+ libshell_public_headers,
+ libshell_sources
+]
+
+libshell_no_gir_sources = [
+ js_resources,
+ libshell_private_headers,
+ libshell_private_sources
+]
+
+dbus_generated = gnome.gdbus_codegen('org-gtk-application',
+ 'org.gtk.Application.xml',
+ namespace: 'Shell'
+)
+
+dbus_generated += gnome.gdbus_codegen('switcheroo-control',
+ '../data/dbus-interfaces/net.hadess.SwitcherooControl.xml',
+ namespace: 'Shell'
+)
+
+libshell_no_gir_sources += dbus_generated
+
+libshell = library('gnome-shell',
+ sources: libshell_gir_sources + libshell_no_gir_sources,
+ dependencies: gnome_shell_deps + [libshell_menu_dep, libst_dep, mutter_dep, gnome_desktop_dep, m_dep],
+ include_directories: [conf_inc, st_inc, include_directories('tray')],
+ c_args: gnome_shell_cflags,
+ link_with: [libtray],
+ build_rpath: mutter_typelibdir,
+ install_rpath: install_rpath,
+ install_dir: pkglibdir,
+ install: true
+)
+
+libshell_dep = declare_dependency(link_with: libshell)
+
+libshell_gir_includes = [
+ 'Clutter-@0@'.format(mutter_api_version),
+ 'Meta-@0@'.format(mutter_api_version),
+ 'Gcr-4',
+ 'PolkitAgent-1.0'
+]
+
+if have_networkmanager
+ libshell_gir_includes += ['NM-1.0']
+endif
+
+libshell_gir_includes += [
+ libgvc_gir[0],
+ libst_gir[0]
+]
+
+gnome.generate_gir(libshell,
+ sources: libshell_gir_sources,
+ nsversion: '0.1',
+ namespace: 'Shell',
+ includes: libshell_gir_includes,
+ extra_args: ['--quiet'],
+ install_dir_gir: pkgdatadir,
+ install_dir_typelib: pkglibdir,
+ install: true
+)
+
+executable('gnome-shell', 'main.c',
+ c_args: gnome_shell_cflags + [
+ '-DMUTTER_TYPELIB_DIR="@0@"'.format(mutter_typelibdir)
+ ],
+ dependencies: gnome_shell_deps + [libshell_dep, libst_dep, mutter_dep],
+ include_directories: [conf_inc, st_inc, include_directories('tray')],
+ build_rpath: mutter_typelibdir,
+ install_rpath: install_rpath,
+ install: true
+)
+
+if have_networkmanager
+ executable('gnome-shell-portal-helper',
+ 'gnome-shell-portal-helper.c', portal_resources,
+ c_args: tools_cflags,
+ dependencies: tools_deps,
+ include_directories: [conf_inc],
+ install_dir: libexecdir,
+ install: true
+ )
+endif
+
+executable('gnome-shell-perf-helper', 'shell-perf-helper.c',
+ dependencies: [gtk_dep, gio_dep, m_dep],
+ include_directories: [conf_inc],
+ install_dir: libexecdir,
+ install: true
+)
+
+executable('run-js-test', 'run-js-test.c',
+ dependencies: [mutter_dep, gio_dep, gi_dep, gjs_dep],
+ include_directories: [conf_inc],
+ link_with: libshell,
+ build_rpath: mutter_typelibdir,
+)
diff --git a/src/org.gtk.Application.xml b/src/org.gtk.Application.xml
new file mode 100644
index 0000000..161aa1d
--- /dev/null
+++ b/src/org.gtk.Application.xml
@@ -0,0 +1,19 @@
+<node>
+ <interface name='org.gtk.Application'>
+ <method name='Activate'>
+ <arg type='a{sv}' name='platform_data' direction='in'/>
+ </method>
+ <method name='Open'>
+ <arg type='as' name='uris' direction='in'/>
+ <arg type='s' name='hint' direction='in'/>
+ <arg type='a{sv}' name='platform_data' direction='in'/>
+ </method>
+ <method name='CommandLine'>
+ <arg type='o' name='path' direction='in'/>
+ <arg type='aay' name='arguments' direction='in'/>
+ <arg type='a{sv}' name='platform_data' direction='in'/>
+ <arg type='i' name='exit_status' direction='out'/>
+ </method>
+ <property name='Busy' type='b' access='read'/>
+ </interface>
+</node>
diff --git a/src/run-js-test.c b/src/run-js-test.c
new file mode 100644
index 0000000..ba5e875
--- /dev/null
+++ b/src/run-js-test.c
@@ -0,0 +1,118 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * Based on gjs/console.c from GJS
+ *
+ * Copyright (c) 2008 litl, LLC
+ * Copyright (c) 2010 Red Hat, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+#include "config.h"
+
+#include <locale.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <girepository.h>
+#include <gjs/gjs.h>
+
+#include "shell-global.h"
+#include "shell-global-private.h"
+
+static char *command = NULL;
+
+static GOptionEntry entries[] = {
+ { "command", 'c', 0, G_OPTION_ARG_STRING, &command, "Program passed in as a string", "COMMAND" },
+ { NULL }
+};
+
+int
+main(int argc, char **argv)
+{
+ GOptionContext *context;
+ GError *error = NULL;
+ ShellGlobal *global;
+ GjsContext *js_context;
+ char *script;
+ const char *filename;
+ char *title;
+ gsize len;
+ int code;
+
+ context = g_option_context_new (NULL);
+
+ /* pass unknown through to the JS script */
+ g_option_context_set_ignore_unknown_options (context, TRUE);
+
+ g_option_context_add_main_entries (context, entries, NULL);
+ if (!g_option_context_parse (context, &argc, &argv, &error))
+ g_error ("option parsing failed: %s", error->message);
+
+ setlocale (LC_ALL, "");
+
+ _shell_global_init (NULL);
+ global = shell_global_get ();
+ js_context = _shell_global_get_gjs_context (global);
+
+ /* prepare command line arguments */
+ if (!gjs_context_define_string_array (js_context, "ARGV",
+ argc - 2, (const char**)argv + 2,
+ &error)) {
+ g_printerr ("Failed to defined ARGV: %s", error->message);
+ exit (1);
+ }
+
+ if (command != NULL) {
+ script = command;
+ len = strlen (script);
+ filename = "<command line>";
+ } else if (argc <= 1) {
+ script = g_strdup ("const Console = imports.console; Console.interact();");
+ len = strlen (script);
+ filename = "<stdin>";
+ } else /*if (argc >= 2)*/ {
+ error = NULL;
+ if (!g_file_get_contents (argv[1], &script, &len, &error)) {
+ g_printerr ("%s\n", error->message);
+ exit (1);
+ }
+ filename = argv[1];
+ }
+
+ title = g_filename_display_basename (filename);
+ g_set_prgname (title);
+ g_free (title);
+
+ /* evaluate the script */
+ error = NULL;
+ if (!gjs_context_eval (js_context, script, len,
+ filename, &code, &error)) {
+ g_free (script);
+ g_printerr ("%s\n", error->message);
+ exit (1);
+ }
+
+ gjs_context_gc (js_context);
+ gjs_context_gc (js_context);
+
+ g_object_unref (js_context);
+ g_free (script);
+ exit (code);
+}
diff --git a/src/shell-action-modes.h b/src/shell-action-modes.h
new file mode 100644
index 0000000..edbdd16
--- /dev/null
+++ b/src/shell-action-modes.h
@@ -0,0 +1,35 @@
+/**
+ * ShellActionMode:
+ * @SHELL_ACTION_MODE_NONE: block action
+ * @SHELL_ACTION_MODE_NORMAL: allow action when in window mode,
+ * e.g. when the focus is in an application window
+ * @SHELL_ACTION_MODE_OVERVIEW: allow action while the overview
+ * is active
+ * @SHELL_ACTION_MODE_LOCK_SCREEN: allow action when the screen
+ * is locked, e.g. when the screen shield is shown
+ * @SHELL_ACTION_MODE_UNLOCK_SCREEN: allow action in the unlock
+ * dialog
+ * @SHELL_ACTION_MODE_LOGIN_SCREEN: allow action in the login screen
+ * @SHELL_ACTION_MODE_SYSTEM_MODAL: allow action when a system modal
+ * dialog (e.g. authentication or session dialogs) is open
+ * @SHELL_ACTION_MODE_LOOKING_GLASS: allow action in looking glass
+ * @SHELL_ACTION_MODE_POPUP: allow action while a shell menu is open
+ * @SHELL_ACTION_MODE_ALL: always allow action
+ *
+ * Controls in which GNOME Shell states an action (like keybindings and gestures)
+ * should be handled.
+*/
+typedef enum {
+ SHELL_ACTION_MODE_NONE = 0,
+ SHELL_ACTION_MODE_NORMAL = 1 << 0,
+ SHELL_ACTION_MODE_OVERVIEW = 1 << 1,
+ SHELL_ACTION_MODE_LOCK_SCREEN = 1 << 2,
+ SHELL_ACTION_MODE_UNLOCK_SCREEN = 1 << 3,
+ SHELL_ACTION_MODE_LOGIN_SCREEN = 1 << 4,
+ SHELL_ACTION_MODE_SYSTEM_MODAL = 1 << 5,
+ SHELL_ACTION_MODE_LOOKING_GLASS = 1 << 6,
+ SHELL_ACTION_MODE_POPUP = 1 << 7,
+
+ SHELL_ACTION_MODE_ALL = ~0,
+} ShellActionMode;
+
diff --git a/src/shell-app-cache-private.h b/src/shell-app-cache-private.h
new file mode 100644
index 0000000..b73094a
--- /dev/null
+++ b/src/shell-app-cache-private.h
@@ -0,0 +1,19 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+#ifndef __SHELL_APP_CACHE_PRIVATE_H__
+#define __SHELL_APP_CACHE_PRIVATE_H__
+
+#include <gio/gio.h>
+#include <gio/gdesktopappinfo.h>
+
+#define SHELL_TYPE_APP_CACHE (shell_app_cache_get_type())
+
+G_DECLARE_FINAL_TYPE (ShellAppCache, shell_app_cache, SHELL, APP_CACHE, GObject)
+
+ShellAppCache *shell_app_cache_get_default (void);
+GList *shell_app_cache_get_all (ShellAppCache *cache);
+GDesktopAppInfo *shell_app_cache_get_info (ShellAppCache *cache,
+ const char *id);
+char *shell_app_cache_translate_folder (ShellAppCache *cache,
+ const char *name);
+
+#endif /* __SHELL_APP_CACHE_PRIVATE_H__ */
diff --git a/src/shell-app-cache.c b/src/shell-app-cache.c
new file mode 100644
index 0000000..44fc8b0
--- /dev/null
+++ b/src/shell-app-cache.c
@@ -0,0 +1,404 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+#include "config.h"
+
+#include "shell-app-cache-private.h"
+
+/**
+ * SECTION:shell-app-cache
+ * @title: ShellAppCache
+ * @short_description: application information cache
+ *
+ * The #ShellAppCache is responsible for caching information about #GAppInfo
+ * to ensure that the compositor thread never needs to perform disk reads to
+ * access them. All of the work is done off-thread. When the new data has
+ * been loaded, a #ShellAppCache::changed signal is emitted.
+ *
+ * Additionally, the #ShellAppCache caches information about translations for
+ * directories. This allows translation provided in [Desktop Entry] GKeyFiles
+ * to be available when building StLabel and other elements without performing
+ * costly disk reads.
+ *
+ * Various monitors are used to keep this information up to date while the
+ * Shell is running.
+ */
+
+#define DEFAULT_TIMEOUT_SECONDS 5
+
+struct _ShellAppCache
+{
+ GObject parent_instance;
+
+ GAppInfoMonitor *monitor;
+ GPtrArray *dir_monitors;
+ GHashTable *folders;
+ GCancellable *cancellable;
+ GList *app_infos;
+
+ guint queued_update;
+};
+
+typedef struct
+{
+ GList *app_infos;
+ GHashTable *folders;
+} CacheState;
+
+G_DEFINE_TYPE (ShellAppCache, shell_app_cache, G_TYPE_OBJECT)
+
+enum {
+ CHANGED,
+ N_SIGNALS
+};
+
+static guint signals [N_SIGNALS];
+
+static void
+cache_state_free (CacheState *state)
+{
+ g_clear_pointer (&state->folders, g_hash_table_unref);
+ g_list_free_full (state->app_infos, g_object_unref);
+ g_free (state);
+}
+
+static CacheState *
+cache_state_new (void)
+{
+ CacheState *state;
+
+ state = g_new0 (CacheState, 1);
+ state->folders = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
+
+ return g_steal_pointer (&state);
+}
+
+/**
+ * shell_app_cache_get_default:
+ *
+ * Gets the default #ShellAppCache.
+ *
+ * Returns: (transfer none): a #ShellAppCache
+ */
+ShellAppCache *
+shell_app_cache_get_default (void)
+{
+ static ShellAppCache *instance;
+
+ if (instance == NULL)
+ {
+ instance = g_object_new (SHELL_TYPE_APP_CACHE, NULL);
+ g_object_add_weak_pointer (G_OBJECT (instance), (gpointer *)&instance);
+ }
+
+ return instance;
+}
+
+static void
+load_folder (GHashTable *folders,
+ const char *path)
+{
+ g_autoptr(GDir) dir = NULL;
+ const char *name;
+
+ g_assert (folders != NULL);
+ g_assert (path != NULL);
+
+ dir = g_dir_open (path, 0, NULL);
+ if (dir == NULL)
+ return;
+
+ while ((name = g_dir_read_name (dir)))
+ {
+ g_autofree gchar *filename = NULL;
+ g_autoptr(GKeyFile) keyfile = NULL;
+
+ /* First added wins */
+ if (g_hash_table_contains (folders, name))
+ continue;
+
+ filename = g_build_filename (path, name, NULL);
+ keyfile = g_key_file_new ();
+
+ if (g_key_file_load_from_file (keyfile, filename, G_KEY_FILE_NONE, NULL))
+ {
+ gchar *translated;
+
+ translated = g_key_file_get_locale_string (keyfile,
+ "Desktop Entry", "Name",
+ NULL, NULL);
+
+ if (translated != NULL)
+ g_hash_table_insert (folders, g_strdup (name), translated);
+ }
+ }
+}
+
+static void
+load_folders (GHashTable *folders)
+{
+ const char * const *dirs;
+ g_autofree gchar *userdir = NULL;
+ guint i;
+
+ g_assert (folders != NULL);
+
+ userdir = g_build_filename (g_get_user_data_dir (), "desktop-directories", NULL);
+ load_folder (folders, userdir);
+
+ dirs = g_get_system_data_dirs ();
+ for (i = 0; dirs[i] != NULL; i++)
+ {
+ g_autofree gchar *sysdir = g_build_filename (dirs[i], "desktop-directories", NULL);
+ load_folder (folders, sysdir);
+ }
+}
+
+static void
+shell_app_cache_worker (GTask *task,
+ gpointer source_object,
+ gpointer task_data,
+ GCancellable *cancellable)
+{
+ CacheState *state;
+
+ g_assert (G_IS_TASK (task));
+ g_assert (SHELL_IS_APP_CACHE (source_object));
+
+ state = cache_state_new ();
+ state->app_infos = g_app_info_get_all ();
+ load_folders (state->folders);
+
+ g_task_return_pointer (task, state, (GDestroyNotify) cache_state_free);
+}
+
+static void
+apply_update_cb (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ ShellAppCache *cache = (ShellAppCache *)object;
+ g_autoptr(GError) error = NULL;
+ CacheState *state;
+
+ g_assert (SHELL_IS_APP_CACHE (cache));
+ g_assert (G_IS_TASK (result));
+ g_assert (user_data == NULL);
+
+ state = g_task_propagate_pointer (G_TASK (result), &error);
+
+ if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ return;
+
+ g_list_free_full (cache->app_infos, g_object_unref);
+ cache->app_infos = g_steal_pointer (&state->app_infos);
+
+ g_clear_pointer (&cache->folders, g_hash_table_unref);
+ cache->folders = g_steal_pointer (&state->folders);
+
+ g_signal_emit (cache, signals[CHANGED], 0);
+
+ cache_state_free (state);
+}
+
+static gboolean
+shell_app_cache_do_update (gpointer user_data)
+{
+ ShellAppCache *cache = user_data;
+ g_autoptr(GTask) task = NULL;
+
+ cache->queued_update = 0;
+
+ /* Reset the cancellable state so we don't race with
+ * two updates coming back overlapped and applying the
+ * information in the wrong order.
+ */
+ g_cancellable_cancel (cache->cancellable);
+ g_clear_object (&cache->cancellable);
+ cache->cancellable = g_cancellable_new ();
+
+ task = g_task_new (cache, cache->cancellable, apply_update_cb, NULL);
+ g_task_set_source_tag (task, shell_app_cache_do_update);
+ g_task_run_in_thread (task, shell_app_cache_worker);
+
+ return G_SOURCE_REMOVE;
+}
+
+static void
+shell_app_cache_queue_update (ShellAppCache *self)
+{
+ g_assert (SHELL_IS_APP_CACHE (self));
+
+ if (self->queued_update != 0)
+ g_source_remove (self->queued_update);
+
+ self->queued_update = g_timeout_add_seconds (DEFAULT_TIMEOUT_SECONDS,
+ shell_app_cache_do_update,
+ self);
+}
+
+static void
+monitor_desktop_directories_for_data_dir (ShellAppCache *self,
+ const gchar *directory)
+{
+ g_autofree gchar *subdir = NULL;
+ g_autoptr(GFile) file = NULL;
+ g_autoptr(GFileMonitor) monitor = NULL;
+
+ g_assert (SHELL_IS_APP_CACHE (self));
+
+ if (directory == NULL)
+ return;
+
+ subdir = g_build_filename (directory, "desktop-directories", NULL);
+ file = g_file_new_for_path (subdir);
+ monitor = g_file_monitor_directory (file, G_FILE_MONITOR_NONE, NULL, NULL);
+
+ if (monitor != NULL)
+ {
+ g_file_monitor_set_rate_limit (monitor, DEFAULT_TIMEOUT_SECONDS * 1000);
+ g_signal_connect_object (monitor,
+ "changed",
+ G_CALLBACK (shell_app_cache_queue_update),
+ self,
+ G_CONNECT_SWAPPED);
+ g_ptr_array_add (self->dir_monitors, g_steal_pointer (&monitor));
+ }
+}
+
+static void
+shell_app_cache_finalize (GObject *object)
+{
+ ShellAppCache *self = (ShellAppCache *)object;
+
+ g_clear_object (&self->monitor);
+
+ if (self->queued_update)
+ {
+ g_source_remove (self->queued_update);
+ self->queued_update = 0;
+ }
+
+ g_clear_pointer (&self->dir_monitors, g_ptr_array_unref);
+ g_clear_pointer (&self->folders, g_hash_table_unref);
+ g_list_free_full (self->app_infos, g_object_unref);
+
+ G_OBJECT_CLASS (shell_app_cache_parent_class)->finalize (object);
+}
+
+static void
+shell_app_cache_class_init (ShellAppCacheClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->finalize = shell_app_cache_finalize;
+
+ /**
+ * ShellAppCache::changed:
+ *
+ * The "changed" signal is emitted when the cache has updated
+ * information about installed applications.
+ */
+ signals [CHANGED] =
+ g_signal_new ("changed",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ 0, NULL, NULL, NULL,
+ G_TYPE_NONE, 0);
+}
+
+static void
+shell_app_cache_init (ShellAppCache *self)
+{
+ const gchar * const *sysdirs;
+ guint i;
+
+ /* Monitor directories for translation changes */
+ self->dir_monitors = g_ptr_array_new_with_free_func (g_object_unref);
+ monitor_desktop_directories_for_data_dir (self, g_get_user_data_dir ());
+ sysdirs = g_get_system_data_dirs ();
+ for (i = 0; sysdirs[i] != NULL; i++)
+ monitor_desktop_directories_for_data_dir (self, sysdirs[i]);
+
+ /* Load translated directory names immediately */
+ self->folders = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
+ load_folders (self->folders);
+
+ /* Setup AppMonitor to track changes */
+ self->monitor = g_app_info_monitor_get ();
+ g_signal_connect_object (self->monitor,
+ "changed",
+ G_CALLBACK (shell_app_cache_queue_update),
+ self,
+ G_CONNECT_SWAPPED);
+ self->app_infos = g_app_info_get_all ();
+}
+
+/**
+ * shell_app_cache_get_all:
+ * @cache: (nullable): a #ShellAppCache or %NULL
+ *
+ * Like g_app_info_get_all() but always returns a
+ * cached set of application info so the caller can be
+ * sure that I/O will not happen on the current thread.
+ *
+ * Returns: (transfer none) (element-type GAppInfo):
+ * a #GList of references to #GAppInfo.
+ */
+GList *
+shell_app_cache_get_all (ShellAppCache *cache)
+{
+ g_return_val_if_fail (SHELL_IS_APP_CACHE (cache), NULL);
+
+ return cache->app_infos;
+}
+
+/**
+ * shell_app_cache_get_info:
+ * @cache: (nullable): a #ShellAppCache or %NULL
+ * @id: the application id
+ *
+ * A replacement for g_desktop_app_info_new() that will lookup the
+ * information from the cache instead of (re)loading from disk.
+ *
+ * Returns: (nullable) (transfer none): a #GDesktopAppInfo or %NULL
+ */
+GDesktopAppInfo *
+shell_app_cache_get_info (ShellAppCache *cache,
+ const char *id)
+{
+ const GList *iter;
+
+ g_return_val_if_fail (SHELL_IS_APP_CACHE (cache), NULL);
+
+ for (iter = cache->app_infos; iter != NULL; iter = iter->next)
+ {
+ GAppInfo *info = iter->data;
+
+ if (g_strcmp0 (id, g_app_info_get_id (info)) == 0)
+ return G_DESKTOP_APP_INFO (info);
+ }
+
+ return NULL;
+}
+
+/**
+ * shell_app_cache_translate_folder:
+ * @cache: (nullable): a #ShellAppCache or %NULL
+ * @name: the folder name
+ *
+ * Gets the translated folder name for @name if any exists.
+ *
+ * Returns: (nullable): the translated string or %NULL if there is no
+ * translation.
+ */
+char *
+shell_app_cache_translate_folder (ShellAppCache *cache,
+ const char *name)
+{
+ g_return_val_if_fail (SHELL_IS_APP_CACHE (cache), NULL);
+
+ if (name == NULL)
+ return NULL;
+
+ return g_strdup (g_hash_table_lookup (cache->folders, name));
+}
diff --git a/src/shell-app-private.h b/src/shell-app-private.h
new file mode 100644
index 0000000..b1786b3
--- /dev/null
+++ b/src/shell-app-private.h
@@ -0,0 +1,24 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+#ifndef __SHELL_APP_PRIVATE_H__
+#define __SHELL_APP_PRIVATE_H__
+
+#include "shell-app.h"
+#include "shell-app-system.h"
+
+G_BEGIN_DECLS
+
+ShellApp* _shell_app_new_for_window (MetaWindow *window);
+
+ShellApp* _shell_app_new (GDesktopAppInfo *info);
+
+void _shell_app_set_app_info (ShellApp *app, GDesktopAppInfo *info);
+
+void _shell_app_handle_startup_sequence (ShellApp *app, MetaStartupSequence *sequence);
+
+void _shell_app_add_window (ShellApp *app, MetaWindow *window);
+
+void _shell_app_remove_window (ShellApp *app, MetaWindow *window);
+
+G_END_DECLS
+
+#endif /* __SHELL_APP_PRIVATE_H__ */
diff --git a/src/shell-app-system-private.h b/src/shell-app-system-private.h
new file mode 100644
index 0000000..975d563
--- /dev/null
+++ b/src/shell-app-system-private.h
@@ -0,0 +1,9 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+#ifndef __SHELL_APP_SYSTEM_PRIVATE_H__
+#define __SHELL_APP_SYSTEM_PRIVATE_H__
+
+#include "shell-app-system.h"
+
+void _shell_app_system_notify_app_state_changed (ShellAppSystem *self, ShellApp *app);
+
+#endif
diff --git a/src/shell-app-system.c b/src/shell-app-system.c
new file mode 100644
index 0000000..2899544
--- /dev/null
+++ b/src/shell-app-system.c
@@ -0,0 +1,586 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+#include "config.h"
+
+#include "shell-app-system.h"
+#include "shell-app-usage.h"
+#include <string.h>
+
+#include <gio/gio.h>
+#include <glib/gi18n.h>
+
+#include "shell-app-cache-private.h"
+#include "shell-app-private.h"
+#include "shell-window-tracker-private.h"
+#include "shell-app-system-private.h"
+#include "shell-global.h"
+#include "shell-util.h"
+#include "st.h"
+
+/* Rescan for at most RESCAN_TIMEOUT_MS * MAX_RESCAN_RETRIES. That
+ * should be plenty of time for even a slow spinning drive to update
+ * the icon cache.
+ */
+#define RESCAN_TIMEOUT_MS 2500
+#define MAX_RESCAN_RETRIES 6
+
+/* Vendor prefixes are something that can be preprended to a .desktop
+ * file name. Undo this.
+ */
+static const char*const vendor_prefixes[] = { "gnome-",
+ "fedora-",
+ "mozilla-",
+ "debian-",
+ NULL };
+
+enum {
+ PROP_0,
+
+};
+
+enum {
+ APP_STATE_CHANGED,
+ INSTALLED_CHANGED,
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = { 0 };
+
+typedef struct _ShellAppSystemPrivate ShellAppSystemPrivate;
+
+struct _ShellAppSystem
+{
+ GObject parent;
+
+ ShellAppSystemPrivate *priv;
+};
+
+struct _ShellAppSystemPrivate {
+ GHashTable *running_apps;
+ GHashTable *id_to_app;
+ GHashTable *startup_wm_class_to_id;
+ GList *installed_apps;
+
+ guint rescan_icons_timeout_id;
+ guint n_rescan_retries;
+};
+
+static void shell_app_system_finalize (GObject *object);
+
+G_DEFINE_TYPE_WITH_PRIVATE (ShellAppSystem, shell_app_system, G_TYPE_OBJECT);
+
+static void shell_app_system_class_init(ShellAppSystemClass *klass)
+{
+ GObjectClass *gobject_class = (GObjectClass *)klass;
+
+ gobject_class->finalize = shell_app_system_finalize;
+
+ signals[APP_STATE_CHANGED] = g_signal_new ("app-state-changed",
+ SHELL_TYPE_APP_SYSTEM,
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL, NULL,
+ G_TYPE_NONE, 1,
+ SHELL_TYPE_APP);
+ signals[INSTALLED_CHANGED] =
+ g_signal_new ("installed-changed",
+ SHELL_TYPE_APP_SYSTEM,
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL, NULL,
+ G_TYPE_NONE, 0);
+}
+
+/*
+ * Check whether @wm_class matches @id exactly when ignoring the .desktop suffix
+ */
+static gboolean
+startup_wm_class_is_exact_match (const char *id,
+ const char *wm_class)
+{
+ size_t wm_class_len;
+
+ if (!g_str_has_prefix (id, wm_class))
+ return FALSE;
+
+ wm_class_len = strlen (wm_class);
+ if (id[wm_class_len] == '\0')
+ return TRUE;
+
+ return g_str_equal (id + wm_class_len, ".desktop");
+}
+
+static void
+scan_startup_wm_class_to_id (ShellAppSystem *self)
+{
+ ShellAppSystemPrivate *priv = self->priv;
+ g_autoptr(GPtrArray) no_show_ids = NULL;
+ const GList *l;
+ GList *all;
+
+ g_hash_table_remove_all (priv->startup_wm_class_to_id);
+
+ all = shell_app_cache_get_all (shell_app_cache_get_default ());
+ no_show_ids = g_ptr_array_new ();
+
+ for (l = all; l != NULL; l = l->next)
+ {
+ GAppInfo *info = l->data;
+ const char *startup_wm_class, *id, *old_id;
+ gboolean should_show;
+
+ id = g_app_info_get_id (info);
+ startup_wm_class = g_desktop_app_info_get_startup_wm_class (G_DESKTOP_APP_INFO (info));
+
+ if (startup_wm_class == NULL)
+ continue;
+
+ should_show = g_app_info_should_show (info);
+ if (!should_show)
+ g_ptr_array_add (no_show_ids, (char *) id);
+
+ /* In case multiple .desktop files set the same StartupWMClass, prefer
+ * the one where ID and StartupWMClass match */
+ old_id = g_hash_table_lookup (priv->startup_wm_class_to_id, startup_wm_class);
+
+ if (old_id && startup_wm_class_is_exact_match (id, startup_wm_class))
+ old_id = NULL;
+
+ /* Give priority to the desktop files that should be shown */
+ if (old_id && should_show &&
+ g_ptr_array_find_with_equal_func (no_show_ids, old_id, g_str_equal, NULL))
+ old_id = NULL;
+
+ if (!old_id)
+ g_hash_table_insert (priv->startup_wm_class_to_id,
+ g_strdup (startup_wm_class), g_strdup (id));
+ }
+}
+
+static gboolean
+app_is_stale (ShellApp *app)
+{
+ GDesktopAppInfo *info, *old;
+ GAppInfo *old_info, *new_info;
+ gboolean is_unchanged;
+
+ if (shell_app_is_window_backed (app))
+ return FALSE;
+
+ info = shell_app_cache_get_info (shell_app_cache_get_default (),
+ shell_app_get_id (app));
+ if (!info)
+ return TRUE;
+
+ old = shell_app_get_app_info (app);
+ old_info = G_APP_INFO (old);
+ new_info = G_APP_INFO (info);
+
+ is_unchanged =
+ g_app_info_should_show (old_info) == g_app_info_should_show (new_info) &&
+ strcmp (g_desktop_app_info_get_filename (old),
+ g_desktop_app_info_get_filename (info)) == 0 &&
+ g_strcmp0 (g_app_info_get_executable (old_info),
+ g_app_info_get_executable (new_info)) == 0 &&
+ g_strcmp0 (g_app_info_get_commandline (old_info),
+ g_app_info_get_commandline (new_info)) == 0 &&
+ strcmp (g_app_info_get_name (old_info),
+ g_app_info_get_name (new_info)) == 0 &&
+ g_strcmp0 (g_app_info_get_description (old_info),
+ g_app_info_get_description (new_info)) == 0 &&
+ strcmp (g_app_info_get_display_name (old_info),
+ g_app_info_get_display_name (new_info)) == 0 &&
+ g_icon_equal (g_app_info_get_icon (old_info),
+ g_app_info_get_icon (new_info));
+
+ return !is_unchanged;
+}
+
+static gboolean
+stale_app_remove_func (gpointer key,
+ gpointer value,
+ gpointer user_data)
+{
+ return app_is_stale (value);
+}
+
+static void
+collect_stale_windows (gpointer key,
+ gpointer value,
+ gpointer user_data)
+{
+ ShellApp *app = key;
+ GDesktopAppInfo *info;
+ GPtrArray *windows = user_data;
+
+ info = shell_app_cache_get_info (shell_app_cache_get_default (),
+ shell_app_get_id (app));
+
+ /* No info either means that the app became stale, or that it is
+ * window-backed. Re-tracking the app's windows allows us to reflect
+ * changes in either direction, i.e. from stale app to window-backed,
+ * or from window-backed to app-backed (if the app was launched right
+ * between installing the app and updating the app cache).
+ */
+ if (info == NULL)
+ {
+ GSList *l;
+
+ for (l = shell_app_get_windows (app); l; l = l->next)
+ g_ptr_array_add (windows, l->data);
+ }
+}
+
+static void
+retrack_window (gpointer data,
+ gpointer user_data)
+{
+ GObject *window = data;
+
+ /* Make ShellWindowTracker retrack the window */
+ g_object_notify (window, "wm-class");
+}
+
+static gboolean
+rescan_icon_theme_cb (gpointer user_data)
+{
+ ShellAppSystemPrivate *priv;
+ ShellAppSystem *self;
+ StTextureCache *texture_cache;
+ gboolean rescanned;
+
+ self = (ShellAppSystem *) user_data;
+ priv = self->priv;
+
+ texture_cache = st_texture_cache_get_default ();
+ rescanned = st_texture_cache_rescan_icon_theme (texture_cache);
+
+ priv->n_rescan_retries++;
+
+ if (rescanned || priv->n_rescan_retries >= MAX_RESCAN_RETRIES)
+ {
+ priv->n_rescan_retries = 0;
+ priv->rescan_icons_timeout_id = 0;
+ return G_SOURCE_REMOVE;
+ }
+
+ return G_SOURCE_CONTINUE;
+}
+
+static void
+rescan_icon_theme (ShellAppSystem *self)
+{
+ ShellAppSystemPrivate *priv = self->priv;
+
+ priv->n_rescan_retries = 0;
+
+ if (priv->rescan_icons_timeout_id > 0)
+ return;
+
+ priv->rescan_icons_timeout_id = g_timeout_add (RESCAN_TIMEOUT_MS,
+ rescan_icon_theme_cb,
+ self);
+}
+
+static void
+installed_changed (ShellAppCache *cache,
+ ShellAppSystem *self)
+{
+ GPtrArray *windows = g_ptr_array_new ();
+
+ rescan_icon_theme (self);
+ scan_startup_wm_class_to_id (self);
+
+ g_hash_table_foreach_remove (self->priv->id_to_app, stale_app_remove_func, NULL);
+ g_hash_table_foreach (self->priv->running_apps, collect_stale_windows, windows);
+
+ g_ptr_array_foreach (windows, retrack_window, NULL);
+ g_ptr_array_free (windows, TRUE);
+
+ g_signal_emit (self, signals[INSTALLED_CHANGED], 0, NULL);
+}
+
+static void
+shell_app_system_init (ShellAppSystem *self)
+{
+ ShellAppSystemPrivate *priv;
+ ShellAppCache *cache;
+
+ self->priv = priv = shell_app_system_get_instance_private (self);
+
+ priv->running_apps = g_hash_table_new_full (NULL, NULL, (GDestroyNotify) g_object_unref, NULL);
+ priv->id_to_app = g_hash_table_new_full (g_str_hash, g_str_equal,
+ NULL,
+ (GDestroyNotify)g_object_unref);
+
+ priv->startup_wm_class_to_id = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
+
+ cache = shell_app_cache_get_default ();
+ g_signal_connect (cache, "changed", G_CALLBACK (installed_changed), self);
+ installed_changed (cache, self);
+}
+
+static void
+shell_app_system_finalize (GObject *object)
+{
+ ShellAppSystem *self = SHELL_APP_SYSTEM (object);
+ ShellAppSystemPrivate *priv = self->priv;
+
+ g_hash_table_destroy (priv->running_apps);
+ g_hash_table_destroy (priv->id_to_app);
+ g_hash_table_destroy (priv->startup_wm_class_to_id);
+ g_list_free_full (priv->installed_apps, g_object_unref);
+ g_clear_handle_id (&priv->rescan_icons_timeout_id, g_source_remove);
+
+ G_OBJECT_CLASS (shell_app_system_parent_class)->finalize (object);
+}
+
+/**
+ * shell_app_system_get_default:
+ *
+ * Return Value: (transfer none): The global #ShellAppSystem singleton
+ */
+ShellAppSystem *
+shell_app_system_get_default (void)
+{
+ static ShellAppSystem *instance = NULL;
+
+ if (instance == NULL)
+ instance = g_object_new (SHELL_TYPE_APP_SYSTEM, NULL);
+
+ return instance;
+}
+
+/**
+ * shell_app_system_lookup_app:
+ *
+ * Find a #ShellApp corresponding to an id.
+ *
+ * Return value: (transfer none): The #ShellApp for id, or %NULL if none
+ */
+ShellApp *
+shell_app_system_lookup_app (ShellAppSystem *self,
+ const char *id)
+{
+ ShellAppSystemPrivate *priv = self->priv;
+ ShellApp *app;
+ GDesktopAppInfo *info;
+
+ app = g_hash_table_lookup (priv->id_to_app, id);
+ if (app)
+ return app;
+
+ info = shell_app_cache_get_info (shell_app_cache_get_default (), id);
+ if (!info)
+ return NULL;
+
+ app = _shell_app_new (info);
+ g_hash_table_insert (priv->id_to_app, (char *) shell_app_get_id (app), app);
+ return app;
+}
+
+/**
+ * shell_app_system_lookup_heuristic_basename:
+ * @system: a #ShellAppSystem
+ * @id: Probable application identifier
+ *
+ * Find a valid application corresponding to a given
+ * heuristically determined application identifier
+ * string, or %NULL if none.
+ *
+ * Returns: (transfer none): A #ShellApp for @name
+ */
+ShellApp *
+shell_app_system_lookup_heuristic_basename (ShellAppSystem *system,
+ const char *name)
+{
+ ShellApp *result;
+ const char *const *prefix;
+
+ result = shell_app_system_lookup_app (system, name);
+ if (result != NULL)
+ return result;
+
+ for (prefix = vendor_prefixes; *prefix != NULL; prefix++)
+ {
+ char *tmpid = g_strconcat (*prefix, name, NULL);
+ result = shell_app_system_lookup_app (system, tmpid);
+ g_free (tmpid);
+ if (result != NULL)
+ return result;
+ }
+
+ return NULL;
+}
+
+/**
+ * shell_app_system_lookup_desktop_wmclass:
+ * @system: a #ShellAppSystem
+ * @wmclass: (nullable): A WM_CLASS value
+ *
+ * Find a valid application whose .desktop file, without the extension
+ * and properly canonicalized, matches @wmclass.
+ *
+ * Returns: (transfer none): A #ShellApp for @wmclass
+ */
+ShellApp *
+shell_app_system_lookup_desktop_wmclass (ShellAppSystem *system,
+ const char *wmclass)
+{
+ char *canonicalized;
+ char *desktop_file;
+ ShellApp *app;
+
+ if (wmclass == NULL)
+ return NULL;
+
+ /* First try without changing the case (this handles
+ org.example.Foo.Bar.desktop applications)
+
+ Note that is slightly wrong in that Gtk+ would set
+ the WM_CLASS to Org.example.Foo.Bar, but it also
+ sets the instance part to org.example.Foo.Bar, so we're ok
+ */
+ desktop_file = g_strconcat (wmclass, ".desktop", NULL);
+ app = shell_app_system_lookup_heuristic_basename (system, desktop_file);
+ g_free (desktop_file);
+
+ if (app)
+ return app;
+
+ canonicalized = g_ascii_strdown (wmclass, -1);
+
+ /* This handles "Fedora Eclipse", probably others.
+ * Note g_strdelimit is modify-in-place. */
+ g_strdelimit (canonicalized, " ", '-');
+
+ desktop_file = g_strconcat (canonicalized, ".desktop", NULL);
+
+ app = shell_app_system_lookup_heuristic_basename (system, desktop_file);
+
+ g_free (canonicalized);
+ g_free (desktop_file);
+
+ return app;
+}
+
+/**
+ * shell_app_system_lookup_startup_wmclass:
+ * @system: a #ShellAppSystem
+ * @wmclass: (nullable): A WM_CLASS value
+ *
+ * Find a valid application whose .desktop file contains a
+ * StartupWMClass entry matching @wmclass.
+ *
+ * Returns: (transfer none): A #ShellApp for @wmclass
+ */
+ShellApp *
+shell_app_system_lookup_startup_wmclass (ShellAppSystem *system,
+ const char *wmclass)
+{
+ const char *id;
+
+ if (wmclass == NULL)
+ return NULL;
+
+ id = g_hash_table_lookup (system->priv->startup_wm_class_to_id, wmclass);
+ if (id == NULL)
+ return NULL;
+
+ return shell_app_system_lookup_app (system, id);
+}
+
+void
+_shell_app_system_notify_app_state_changed (ShellAppSystem *self,
+ ShellApp *app)
+{
+ ShellAppState state = shell_app_get_state (app);
+
+ switch (state)
+ {
+ case SHELL_APP_STATE_RUNNING:
+ g_hash_table_insert (self->priv->running_apps, g_object_ref (app), NULL);
+ break;
+ case SHELL_APP_STATE_STARTING:
+ break;
+ case SHELL_APP_STATE_STOPPED:
+ g_hash_table_remove (self->priv->running_apps, app);
+ break;
+ default:
+ g_warn_if_reached();
+ break;
+ }
+ g_signal_emit (self, signals[APP_STATE_CHANGED], 0, app);
+}
+
+/**
+ * shell_app_system_get_running:
+ * @self: A #ShellAppSystem
+ *
+ * Returns the set of applications which currently have at least one
+ * open window. The returned list will be sorted by shell_app_compare().
+ *
+ * Returns: (element-type ShellApp) (transfer container): Active applications
+ */
+GSList *
+shell_app_system_get_running (ShellAppSystem *self)
+{
+ gpointer key, value;
+ GSList *ret;
+ GHashTableIter iter;
+
+ g_hash_table_iter_init (&iter, self->priv->running_apps);
+
+ ret = NULL;
+ while (g_hash_table_iter_next (&iter, &key, &value))
+ {
+ ShellApp *app = key;
+
+ ret = g_slist_prepend (ret, app);
+ }
+
+ ret = g_slist_sort (ret, (GCompareFunc)shell_app_compare);
+
+ return ret;
+}
+
+/**
+ * shell_app_system_search:
+ * @search_string: the search string to use
+ *
+ * Wrapper around g_desktop_app_info_search() that replaces results that
+ * don't validate as UTF-8 with the empty string.
+ *
+ * Returns: (array zero-terminated=1) (element-type GStrv) (transfer full): a
+ * list of strvs. Free each item with g_strfreev() and free the outer
+ * list with g_free().
+ */
+char ***
+shell_app_system_search (const char *search_string)
+{
+ char ***results = g_desktop_app_info_search (search_string);
+ char ***groups, **ids;
+
+ for (groups = results; *groups; groups++)
+ for (ids = *groups; *ids; ids++)
+ if (!g_utf8_validate (*ids, -1, NULL))
+ **ids = '\0';
+
+ return results;
+}
+
+/**
+ * shell_app_system_get_installed:
+ * @self: the #ShellAppSystem
+ *
+ * Returns all installed apps, as a list of #GAppInfo
+ *
+ * Returns: (transfer none) (element-type GAppInfo): a list of #GAppInfo
+ * describing all known applications. This memory is owned by the
+ * #ShellAppSystem and should not be freed.
+ **/
+GList *
+shell_app_system_get_installed (ShellAppSystem *self)
+{
+ return shell_app_cache_get_all (shell_app_cache_get_default ());
+}
diff --git a/src/shell-app-system.h b/src/shell-app-system.h
new file mode 100644
index 0000000..8719dbc
--- /dev/null
+++ b/src/shell-app-system.h
@@ -0,0 +1,32 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+#ifndef __SHELL_APP_SYSTEM_H__
+#define __SHELL_APP_SYSTEM_H__
+
+#include <gio/gio.h>
+#include <clutter/clutter.h>
+#include <meta/window.h>
+
+#include "shell-app.h"
+
+#define SHELL_TYPE_APP_SYSTEM (shell_app_system_get_type ())
+G_DECLARE_FINAL_TYPE (ShellAppSystem, shell_app_system,
+ SHELL, APP_SYSTEM, GObject)
+
+ShellAppSystem *shell_app_system_get_default (void);
+
+ShellApp *shell_app_system_lookup_app (ShellAppSystem *system,
+ const char *id);
+ShellApp *shell_app_system_lookup_heuristic_basename (ShellAppSystem *system,
+ const char *id);
+
+ShellApp *shell_app_system_lookup_startup_wmclass (ShellAppSystem *system,
+ const char *wmclass);
+ShellApp *shell_app_system_lookup_desktop_wmclass (ShellAppSystem *system,
+ const char *wmclass);
+
+GSList *shell_app_system_get_running (ShellAppSystem *self);
+char ***shell_app_system_search (const char *search_string);
+
+GList *shell_app_system_get_installed (ShellAppSystem *self);
+
+#endif /* __SHELL_APP_SYSTEM_H__ */
diff --git a/src/shell-app-usage.c b/src/shell-app-usage.c
new file mode 100644
index 0000000..0dfb209
--- /dev/null
+++ b/src/shell-app-usage.c
@@ -0,0 +1,774 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+#include "config.h"
+
+#include <string.h>
+#include <stdlib.h>
+
+#include <X11/Xlib.h>
+#include <X11/Xatom.h>
+#include <gdk/gdk.h>
+#include <gdk/gdkx.h>
+#include <glib.h>
+#include <gio/gio.h>
+#include <meta/display.h>
+#include <meta/group.h>
+#include <meta/window.h>
+
+#include "shell-app-usage.h"
+#include "shell-window-tracker.h"
+#include "shell-global.h"
+
+/* This file includes modified code from
+ * desktop-data-engine/engine-dbus/hippo-application-monitor.c
+ * in the functions collecting application usage data.
+ * Written by Owen Taylor, originally licensed under LGPL 2.1.
+ * Copyright Red Hat, Inc. 2006-2008
+ */
+
+/**
+ * SECTION:shell-app-usage
+ * @short_description: Track application usage/state data
+ *
+ * This class maintains some usage and state statistics for
+ * applications by keeping track of the approximate time an application's
+ * windows are focused, as well as the last workspace it was seen on.
+ * This time tracking is implemented by watching for focus notifications,
+ * and computing a time delta between them. Also we watch the
+ * GNOME Session "StatusChanged" signal which by default is emitted after 5
+ * minutes to signify idle.
+ */
+
+#define PRIVACY_SCHEMA "org.gnome.desktop.privacy"
+#define ENABLE_MONITORING_KEY "remember-app-usage"
+
+#define FOCUS_TIME_MIN_SECONDS 7 /* Need 7 continuous seconds of focus */
+
+#define USAGE_CLEAN_DAYS 7 /* If after 7 days we haven't seen an app, purge it */
+
+/* Data is saved to file SHELL_CONFIG_DIR/DATA_FILENAME */
+#define DATA_FILENAME "application_state"
+
+#define IDLE_TIME_TRANSITION_SECONDS 30 /* If we transition to idle, only count
+ * this many seconds of usage */
+
+/* The ranking algorithm we use is: every time an app score reaches SCORE_MAX,
+ * divide all scores by 2. Scores are raised by 1 unit every SAVE_APPS_TIMEOUT
+ * seconds. This mechanism allows the list to update relatively fast when
+ * a new app is used intensively.
+ * To keep the list clean, and avoid being Big Brother, apps that have not been
+ * seen for a week and whose score is below SCORE_MIN are removed.
+ */
+
+/* How often we save internally app data, in seconds */
+#define SAVE_APPS_TIMEOUT_SECONDS (5 * 60)
+
+/* With this value, an app goes from bottom to top of the
+ * usage list in 50 hours of use */
+#define SCORE_MAX (3600 * 50 / FOCUS_TIME_MIN_SECONDS)
+
+/* If an app's score in lower than this and the app has not been used in a week,
+ * remove it */
+#define SCORE_MIN (SCORE_MAX >> 3)
+
+/* http://www.gnome.org/~mccann/gnome-session/docs/gnome-session.html#org.gnome.SessionManager.Presence */
+#define GNOME_SESSION_STATUS_IDLE 3
+
+typedef struct UsageData UsageData;
+
+struct _ShellAppUsage
+{
+ GObject parent;
+
+ GFile *configfile;
+ GDBusProxy *session_proxy;
+ GSettings *privacy_settings;
+ guint idle_focus_change_id;
+ guint save_id;
+ gboolean currently_idle;
+ gboolean enable_monitoring;
+
+ long watch_start_time;
+ ShellApp *watched_app;
+
+ /* <char *appid, UsageData *usage> */
+ GHashTable *app_usages;
+};
+
+G_DEFINE_TYPE (ShellAppUsage, shell_app_usage, G_TYPE_OBJECT);
+
+/* Represents an application record for a given context */
+struct UsageData
+{
+ gdouble score; /* Based on the number of times we'e seen the app and normalized */
+ long last_seen; /* Used to clear old apps we've only seen a few times */
+};
+
+static void shell_app_usage_finalize (GObject *object);
+
+static void on_session_status_changed (GDBusProxy *proxy, guint status, ShellAppUsage *self);
+static void on_focus_app_changed (ShellWindowTracker *tracker, GParamSpec *spec, ShellAppUsage *self);
+static void ensure_queued_save (ShellAppUsage *self);
+
+static gboolean idle_save_application_usage (gpointer data);
+
+static void restore_from_file (ShellAppUsage *self);
+
+static void update_enable_monitoring (ShellAppUsage *self);
+
+static void on_enable_monitoring_key_changed (GSettings *settings,
+ const gchar *key,
+ ShellAppUsage *self);
+
+static long
+get_time (void)
+{
+ return g_get_real_time () / G_TIME_SPAN_SECOND;
+}
+
+static void
+shell_app_usage_class_init (ShellAppUsageClass *klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+ gobject_class->finalize = shell_app_usage_finalize;
+}
+
+static UsageData *
+get_usage_for_app (ShellAppUsage *self,
+ ShellApp *app)
+{
+ UsageData *usage;
+ const char *appid = shell_app_get_id (app);
+
+ usage = g_hash_table_lookup (self->app_usages, appid);
+ if (usage)
+ return usage;
+
+ usage = g_new0 (UsageData, 1);
+ g_hash_table_insert (self->app_usages, g_strdup (appid), usage);
+
+ return usage;
+}
+
+/* Limit the score to a certain level so that most used apps can change */
+static void
+normalize_usage (ShellAppUsage *self)
+{
+ GHashTableIter iter;
+ UsageData *usage;
+
+ g_hash_table_iter_init (&iter, self->app_usages);
+
+ while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &usage))
+ usage->score /= 2;
+}
+
+static void
+increment_usage_for_app_at_time (ShellAppUsage *self,
+ ShellApp *app,
+ long time)
+{
+ UsageData *usage;
+ guint elapsed;
+ guint usage_count;
+
+ usage = get_usage_for_app (self, app);
+
+ usage->last_seen = time;
+
+ elapsed = time - self->watch_start_time;
+ usage_count = elapsed / FOCUS_TIME_MIN_SECONDS;
+ if (usage_count > 0)
+ {
+ usage->score += usage_count;
+ if (usage->score > SCORE_MAX)
+ normalize_usage (self);
+ ensure_queued_save (self);
+ }
+}
+
+static void
+increment_usage_for_app (ShellAppUsage *self,
+ ShellApp *app)
+{
+ long curtime = get_time ();
+ increment_usage_for_app_at_time (self, app, curtime);
+}
+
+static void
+on_app_state_changed (ShellAppSystem *app_system,
+ ShellApp *app,
+ gpointer user_data)
+{
+ ShellAppUsage *self = SHELL_APP_USAGE (user_data);
+ UsageData *usage;
+ gboolean running;
+
+ if (shell_app_is_window_backed (app))
+ return;
+
+ usage = get_usage_for_app (self, app);
+
+ running = shell_app_get_state (app) == SHELL_APP_STATE_RUNNING;
+
+ if (running)
+ usage->last_seen = get_time ();
+}
+
+static void
+on_focus_app_changed (ShellWindowTracker *tracker,
+ GParamSpec *spec,
+ ShellAppUsage *self)
+{
+ if (self->watched_app != NULL)
+ increment_usage_for_app (self, self->watched_app);
+
+ if (self->watched_app)
+ g_object_unref (self->watched_app);
+
+ g_object_get (tracker, "focus-app", &(self->watched_app), NULL);
+ self->watch_start_time = get_time ();
+}
+
+static void
+on_session_status_changed (GDBusProxy *proxy,
+ guint status,
+ ShellAppUsage *self)
+{
+ gboolean idle;
+
+ idle = (status >= GNOME_SESSION_STATUS_IDLE);
+ if (self->currently_idle == idle)
+ return;
+
+ self->currently_idle = idle;
+ if (idle)
+ {
+ long end_time;
+
+ /* The GNOME Session signal we watch is 5 minutes, but that's a long
+ * time for this purpose. Instead, just add a base 30 seconds.
+ */
+ if (self->watched_app)
+ {
+ end_time = self->watch_start_time + IDLE_TIME_TRANSITION_SECONDS;
+ increment_usage_for_app_at_time (self, self->watched_app, end_time);
+ }
+ }
+ else
+ {
+ /* Transitioning to !idle, reset the start time */
+ self->watch_start_time = get_time ();
+ }
+}
+
+static void
+session_proxy_signal (GDBusProxy *proxy, gchar *sender_name, gchar *signal_name, GVariant *parameters, gpointer user_data)
+{
+ if (g_str_equal (signal_name, "StatusChanged"))
+ {
+ guint status;
+ g_variant_get (parameters, "(u)", &status);
+ on_session_status_changed (proxy, status, SHELL_APP_USAGE (user_data));
+ }
+}
+
+static void
+shell_app_usage_init (ShellAppUsage *self)
+{
+ ShellGlobal *global;
+ char *shell_userdata_dir, *path;
+ GDBusConnection *session_bus;
+ ShellWindowTracker *tracker;
+ ShellAppSystem *app_system;
+
+ global = shell_global_get ();
+
+ self->app_usages = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
+
+ tracker = shell_window_tracker_get_default ();
+ g_signal_connect (tracker, "notify::focus-app", G_CALLBACK (on_focus_app_changed), self);
+
+ app_system = shell_app_system_get_default ();
+ g_signal_connect (app_system, "app-state-changed", G_CALLBACK (on_app_state_changed), self);
+
+ session_bus = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL);
+ self->session_proxy = g_dbus_proxy_new_sync (session_bus,
+ G_DBUS_PROXY_FLAGS_NONE,
+ NULL, /* interface info */
+ "org.gnome.SessionManager",
+ "/org/gnome/SessionManager/Presence",
+ "org.gnome.SessionManager",
+ NULL, /* cancellable */
+ NULL /* error */);
+ g_signal_connect (self->session_proxy, "g-signal", G_CALLBACK (session_proxy_signal), self);
+ g_object_unref (session_bus);
+
+ self->currently_idle = FALSE;
+ self->enable_monitoring = FALSE;
+
+ g_object_get (global, "userdatadir", &shell_userdata_dir, NULL),
+ path = g_build_filename (shell_userdata_dir, DATA_FILENAME, NULL);
+ g_free (shell_userdata_dir);
+ self->configfile = g_file_new_for_path (path);
+ g_free (path);
+ restore_from_file (self);
+
+ self->privacy_settings = g_settings_new(PRIVACY_SCHEMA);
+ g_signal_connect (self->privacy_settings,
+ "changed::" ENABLE_MONITORING_KEY,
+ G_CALLBACK (on_enable_monitoring_key_changed),
+ self);
+ update_enable_monitoring (self);
+}
+
+static void
+shell_app_usage_finalize (GObject *object)
+{
+ ShellAppUsage *self = SHELL_APP_USAGE (object);
+
+ g_clear_handle_id (&self->save_id, g_source_remove);
+
+ g_object_unref (self->privacy_settings);
+
+ g_object_unref (self->configfile);
+
+ g_object_unref (self->session_proxy);
+
+ G_OBJECT_CLASS (shell_app_usage_parent_class)->finalize(object);
+}
+
+static int
+sort_apps_by_usage (gconstpointer a,
+ gconstpointer b,
+ gpointer datap)
+{
+ ShellAppUsage *self = datap;
+ ShellApp *app_a, *app_b;
+ UsageData *usage_a, *usage_b;
+
+ app_a = (ShellApp*)a;
+ app_b = (ShellApp*)b;
+
+ usage_a = g_hash_table_lookup (self->app_usages, shell_app_get_id (app_a));
+ usage_b = g_hash_table_lookup (self->app_usages, shell_app_get_id (app_b));
+
+ return usage_b->score - usage_a->score;
+}
+
+/**
+ * shell_app_usage_get_most_used:
+ * @usage: the usage instance to request
+ *
+ * Returns: (element-type ShellApp) (transfer full): List of applications
+ */
+GSList *
+shell_app_usage_get_most_used (ShellAppUsage *self)
+{
+ GSList *apps;
+ char *appid;
+ ShellAppSystem *appsys;
+ GHashTableIter iter;
+
+ appsys = shell_app_system_get_default ();
+
+ g_hash_table_iter_init (&iter, self->app_usages);
+ apps = NULL;
+ while (g_hash_table_iter_next (&iter, (gpointer *) &appid, NULL))
+ {
+ ShellApp *app;
+
+ app = shell_app_system_lookup_app (appsys, appid);
+ if (!app)
+ continue;
+
+ apps = g_slist_prepend (apps, g_object_ref (app));
+ }
+
+ apps = g_slist_sort_with_data (apps, sort_apps_by_usage, self);
+
+ return apps;
+}
+
+
+/**
+ * shell_app_usage_compare:
+ * @self: the usage instance to request
+ * @id_a: ID of first app
+ * @id_b: ID of second app
+ *
+ * Compare @id_a and @id_b based on frequency of use.
+ *
+ * Returns: -1 if @id_a ranks higher than @id_b, 1 if @id_b ranks higher
+ * than @id_a, and 0 if both rank equally.
+ */
+int
+shell_app_usage_compare (ShellAppUsage *self,
+ const char *id_a,
+ const char *id_b)
+{
+ UsageData *usage_a, *usage_b;
+
+ usage_a = g_hash_table_lookup (self->app_usages, id_a);
+ usage_b = g_hash_table_lookup (self->app_usages, id_b);
+
+ if (usage_a == NULL && usage_b == NULL)
+ return 0;
+ else if (usage_a == NULL)
+ return 1;
+ else if (usage_b == NULL)
+ return -1;
+
+ return usage_b->score - usage_a->score;
+}
+
+static void
+ensure_queued_save (ShellAppUsage *self)
+{
+ if (self->save_id != 0)
+ return;
+ self->save_id = g_timeout_add_seconds (SAVE_APPS_TIMEOUT_SECONDS, idle_save_application_usage, self);
+ g_source_set_name_by_id (self->save_id, "[gnome-shell] idle_save_application_usage");
+}
+
+/* Clean up apps we see rarely.
+ * The logic behind this is that if an app was seen less than SCORE_MIN times
+ * and not seen for a week, it can probably be forgotten about.
+ * This should much reduce the size of the list and avoid 'pollution'. */
+static gboolean
+idle_clean_usage (ShellAppUsage *self)
+{
+ GHashTableIter iter;
+ UsageData *usage;
+ long current_time;
+ long week_ago;
+
+ current_time = get_time ();
+ week_ago = current_time - (7 * 24 * 60 * 60);
+
+ g_hash_table_iter_init (&iter, self->app_usages);
+
+ while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &usage))
+ {
+ if ((usage->score < SCORE_MIN) &&
+ (usage->last_seen < week_ago))
+ g_hash_table_iter_remove (&iter);
+ }
+
+ return FALSE;
+}
+
+static gboolean
+write_escaped (GDataOutputStream *stream,
+ const char *str,
+ GError **error)
+{
+ gboolean ret;
+ char *quoted = g_markup_escape_text (str, -1);
+ ret = g_data_output_stream_put_string (stream, quoted, NULL, error);
+ g_free (quoted);
+ return ret;
+}
+
+static gboolean
+write_attribute_string (GDataOutputStream *stream,
+ const char *elt_name,
+ const char *str,
+ GError **error)
+{
+ gboolean ret = FALSE;
+ char *elt;
+
+ elt = g_strdup_printf (" %s=\"", elt_name);
+ ret = g_data_output_stream_put_string (stream, elt, NULL, error);
+ g_free (elt);
+ if (!ret)
+ goto out;
+
+ ret = write_escaped (stream, str, error);
+ if (!ret)
+ goto out;
+
+ ret = g_data_output_stream_put_string (stream, "\"", NULL, error);
+
+out:
+ return ret;
+}
+
+static gboolean
+write_attribute_uint (GDataOutputStream *stream,
+ const char *elt_name,
+ guint value,
+ GError **error)
+{
+ gboolean ret;
+ char *buf;
+
+ buf = g_strdup_printf ("%u", value);
+ ret = write_attribute_string (stream, elt_name, buf, error);
+ g_free (buf);
+
+ return ret;
+}
+
+static gboolean
+write_attribute_double (GDataOutputStream *stream,
+ const char *elt_name,
+ double value,
+ GError **error)
+{
+ gchar buf[G_ASCII_DTOSTR_BUF_SIZE];
+ gboolean ret;
+
+ g_ascii_dtostr (buf, sizeof (buf), value);
+ ret = write_attribute_string (stream, elt_name, buf, error);
+
+ return ret;
+}
+
+/* Save app data lists to file */
+static gboolean
+idle_save_application_usage (gpointer data)
+{
+ ShellAppUsage *self = SHELL_APP_USAGE (data);
+ char *id;
+ GHashTableIter iter;
+ UsageData *usage;
+ GFileOutputStream *output;
+ GOutputStream *buffered_output;
+ GDataOutputStream *data_output;
+ GError *error = NULL;
+
+ self->save_id = 0;
+
+ /* Parent directory is already created by shell-global */
+ output = g_file_replace (self->configfile, NULL, FALSE, G_FILE_CREATE_NONE, NULL, &error);
+ if (!output)
+ {
+ g_debug ("Could not save applications usage data: %s", error->message);
+ g_error_free (error);
+ return FALSE;
+ }
+ buffered_output = g_buffered_output_stream_new (G_OUTPUT_STREAM (output));
+ g_object_unref (output);
+ data_output = g_data_output_stream_new (G_OUTPUT_STREAM (buffered_output));
+ g_object_unref (buffered_output);
+
+ if (!g_data_output_stream_put_string (data_output, "<?xml version=\"1.0\"?>\n<application-state>\n", NULL, &error))
+ goto out;
+ if (!g_data_output_stream_put_string (data_output, " <context id=\"\">\n", NULL, &error))
+ goto out;
+
+ g_hash_table_iter_init (&iter, self->app_usages);
+
+ while (g_hash_table_iter_next (&iter, (gpointer *) &id, (gpointer *) &usage))
+ {
+ ShellApp *app;
+
+ app = shell_app_system_lookup_app (shell_app_system_get_default(), id);
+
+ if (!app)
+ continue;
+
+ if (!g_data_output_stream_put_string (data_output, " <application", NULL, &error))
+ goto out;
+ if (!write_attribute_string (data_output, "id", id, &error))
+ goto out;
+ if (!write_attribute_double (data_output, "score", usage->score, &error))
+ goto out;
+ if (!write_attribute_uint (data_output, "last-seen", usage->last_seen, &error))
+ goto out;
+ if (!g_data_output_stream_put_string (data_output, "/>\n", NULL, &error))
+ goto out;
+ }
+ if (!g_data_output_stream_put_string (data_output, " </context>\n", NULL, &error))
+ goto out;
+ if (!g_data_output_stream_put_string (data_output, "</application-state>\n", NULL, &error))
+ goto out;
+
+out:
+ if (!error)
+ g_output_stream_close_async (G_OUTPUT_STREAM (data_output), 0, NULL, NULL, NULL);
+ g_object_unref (data_output);
+ if (error)
+ {
+ g_debug ("Could not save applications usage data: %s", error->message);
+ g_error_free (error);
+ }
+ return FALSE;
+}
+
+static void
+shell_app_usage_start_element_handler (GMarkupParseContext *context,
+ const gchar *element_name,
+ const gchar **attribute_names,
+ const gchar **attribute_values,
+ gpointer user_data,
+ GError **error)
+{
+ ShellAppUsage *self = user_data;
+
+ if (strcmp (element_name, "application-state") == 0)
+ {
+ }
+ else if (strcmp (element_name, "context") == 0)
+ {
+ }
+ else if (strcmp (element_name, "application") == 0)
+ {
+ const char **attribute;
+ const char **value;
+ UsageData *usage;
+ char *appid = NULL;
+
+ for (attribute = attribute_names, value = attribute_values; *attribute; attribute++, value++)
+ {
+ if (strcmp (*attribute, "id") == 0)
+ {
+ appid = g_strdup (*value);
+ break;
+ }
+ }
+
+ if (!appid)
+ {
+ g_set_error (error,
+ G_MARKUP_ERROR,
+ G_MARKUP_ERROR_PARSE,
+ "Missing attribute id on <%s> element",
+ element_name);
+ return;
+ }
+
+ usage = g_new0 (UsageData, 1);
+ g_hash_table_insert (self->app_usages, appid, usage);
+
+ for (attribute = attribute_names, value = attribute_values; *attribute; attribute++, value++)
+ {
+ if (strcmp (*attribute, "score") == 0)
+ {
+ usage->score = g_ascii_strtod (*value, NULL);
+ }
+ else if (strcmp (*attribute, "last-seen") == 0)
+ {
+ usage->last_seen = (guint) g_ascii_strtoull (*value, NULL, 10);
+ }
+ }
+ }
+ else
+ {
+ g_set_error (error,
+ G_MARKUP_ERROR,
+ G_MARKUP_ERROR_PARSE,
+ "Unknown element <%s>",
+ element_name);
+ }
+}
+
+static GMarkupParser app_state_parse_funcs =
+{
+ shell_app_usage_start_element_handler,
+ NULL,
+ NULL,
+ NULL,
+ NULL
+};
+
+/* Load data about apps usage from file */
+static void
+restore_from_file (ShellAppUsage *self)
+{
+ GFileInputStream *input;
+ GMarkupParseContext *parse_context;
+ GError *error = NULL;
+ char buf[1024];
+
+ input = g_file_read (self->configfile, NULL, &error);
+ if (error)
+ {
+ if (error->code != G_IO_ERROR_NOT_FOUND)
+ g_warning ("Could not load applications usage data: %s", error->message);
+
+ g_error_free (error);
+ return;
+ }
+
+ parse_context = g_markup_parse_context_new (&app_state_parse_funcs, 0, self, NULL);
+
+ while (TRUE)
+ {
+ gssize count = g_input_stream_read ((GInputStream*) input, buf, sizeof(buf), NULL, &error);
+ if (count <= 0)
+ goto out;
+ if (!g_markup_parse_context_parse (parse_context, buf, count, &error))
+ goto out;
+ }
+
+out:
+ g_markup_parse_context_free (parse_context);
+ g_input_stream_close ((GInputStream*)input, NULL, NULL);
+ g_object_unref (input);
+
+ idle_clean_usage (self);
+
+ if (error)
+ {
+ g_warning ("Could not load applications usage data: %s", error->message);
+ g_error_free (error);
+ }
+}
+
+/* Enable or disable the timers, depending on the value of ENABLE_MONITORING_KEY
+ * and taking care of the previous state. If selfing is disabled, we still
+ * report apps usage based on (possibly) saved data, but don't collect data.
+ */
+static void
+update_enable_monitoring (ShellAppUsage *self)
+{
+ gboolean enable;
+
+ enable = g_settings_get_boolean (self->privacy_settings,
+ ENABLE_MONITORING_KEY);
+
+ /* Be sure not to start the timers if they were already set */
+ if (enable && !self->enable_monitoring)
+ {
+ on_focus_app_changed (shell_window_tracker_get_default (), NULL, self);
+ }
+ /* ...and don't try to stop them if they were not running */
+ else if (!enable && self->enable_monitoring)
+ {
+ if (self->watched_app)
+ g_object_unref (self->watched_app);
+ self->watched_app = NULL;
+ g_clear_handle_id (&self->save_id, g_source_remove);
+ }
+
+ self->enable_monitoring = enable;
+}
+
+/* Called when the ENABLE_MONITORING_KEY boolean has changed */
+static void
+on_enable_monitoring_key_changed (GSettings *settings,
+ const gchar *key,
+ ShellAppUsage *self)
+{
+ update_enable_monitoring (self);
+}
+
+/**
+ * shell_app_usage_get_default:
+ *
+ * Return Value: (transfer none): The global #ShellAppUsage instance
+ */
+ShellAppUsage *
+shell_app_usage_get_default (void)
+{
+ static ShellAppUsage *instance;
+
+ if (instance == NULL)
+ instance = g_object_new (SHELL_TYPE_APP_USAGE, NULL);
+
+ return instance;
+}
diff --git a/src/shell-app-usage.h b/src/shell-app-usage.h
new file mode 100644
index 0000000..4b0e169
--- /dev/null
+++ b/src/shell-app-usage.h
@@ -0,0 +1,23 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+#ifndef __SHELL_APP_USAGE_H__
+#define __SHELL_APP_USAGE_H__
+
+#include "shell-app.h"
+#include "shell-window-tracker.h"
+
+G_BEGIN_DECLS
+
+#define SHELL_TYPE_APP_USAGE (shell_app_usage_get_type ())
+G_DECLARE_FINAL_TYPE (ShellAppUsage, shell_app_usage,
+ SHELL, APP_USAGE, GObject)
+
+ShellAppUsage* shell_app_usage_get_default(void);
+
+GSList *shell_app_usage_get_most_used (ShellAppUsage *usage);
+int shell_app_usage_compare (ShellAppUsage *self,
+ const char *id_a,
+ const char *id_b);
+
+G_END_DECLS
+
+#endif /* __SHELL_APP_USAGE_H__ */
diff --git a/src/shell-app.c b/src/shell-app.c
new file mode 100644
index 0000000..5c38a9c
--- /dev/null
+++ b/src/shell-app.c
@@ -0,0 +1,1756 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <glib/gi18n-lib.h>
+
+#include <meta/display.h>
+#include <meta/meta-context.h>
+#include <meta/meta-workspace-manager.h>
+#include <meta/meta-x11-display.h>
+
+#include "shell-app-private.h"
+#include "shell-enum-types.h"
+#include "shell-global.h"
+#include "shell-util.h"
+#include "shell-app-system-private.h"
+#include "shell-window-tracker-private.h"
+#include "st.h"
+#include "gtkactionmuxer.h"
+#include "org-gtk-application.h"
+#include "switcheroo-control.h"
+
+#ifdef HAVE_SYSTEMD
+#include <systemd/sd-journal.h>
+#include <errno.h>
+#include <unistd.h>
+#endif
+
+/* This is mainly a memory usage optimization - the user is going to
+ * be running far fewer of the applications at one time than they have
+ * installed. But it also just helps keep the code more logically
+ * separated.
+ */
+typedef struct {
+ guint refcount;
+
+ /* Signal connection to dirty window sort list on workspace changes */
+ gulong workspace_switch_id;
+
+ GSList *windows;
+
+ guint interesting_windows;
+
+ /* Whether or not we need to resort the windows; this is done on demand */
+ guint window_sort_stale : 1;
+
+ /* See GApplication documentation */
+ GtkActionMuxer *muxer;
+ char *unique_bus_name;
+ GDBusConnection *session;
+
+ /* GDBus Proxy for getting application busy state */
+ ShellOrgGtkApplication *application_proxy;
+ GCancellable *cancellable;
+
+} ShellAppRunningState;
+
+/**
+ * SECTION:shell-app
+ * @short_description: Object representing an application
+ *
+ * This object wraps a #GDesktopAppInfo, providing methods and signals
+ * primarily useful for running applications.
+ */
+struct _ShellApp
+{
+ GObject parent;
+
+ int started_on_workspace;
+
+ ShellAppState state;
+
+ GDesktopAppInfo *info; /* If NULL, this app is backed by one or more
+ * MetaWindow. For purposes of app title
+ * etc., we use the first window added,
+ * because it's most likely to be what we
+ * want (e.g. it will be of TYPE_NORMAL from
+ * the way shell-window-tracker.c works).
+ */
+ GIcon *fallback_icon;
+ MetaWindow *fallback_icon_window;
+
+ ShellAppRunningState *running_state;
+
+ char *window_id_string;
+ char *name_collation_key;
+};
+
+enum {
+ PROP_0,
+
+ PROP_STATE,
+ PROP_BUSY,
+ PROP_ID,
+ PROP_ACTION_GROUP,
+ PROP_ICON,
+ PROP_APP_INFO,
+
+ N_PROPS
+};
+
+static GParamSpec *props[N_PROPS] = { NULL, };
+
+enum {
+ WINDOWS_CHANGED,
+ LAST_SIGNAL
+};
+
+static guint shell_app_signals[LAST_SIGNAL] = { 0 };
+
+static void create_running_state (ShellApp *app);
+static void unref_running_state (ShellAppRunningState *state);
+
+G_DEFINE_TYPE (ShellApp, shell_app, G_TYPE_OBJECT)
+
+static void
+shell_app_get_property (GObject *gobject,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ ShellApp *app = SHELL_APP (gobject);
+
+ switch (prop_id)
+ {
+ case PROP_STATE:
+ g_value_set_enum (value, app->state);
+ break;
+ case PROP_BUSY:
+ g_value_set_boolean (value, shell_app_get_busy (app));
+ break;
+ case PROP_ID:
+ g_value_set_string (value, shell_app_get_id (app));
+ break;
+ case PROP_ICON:
+ g_value_set_object (value, shell_app_get_icon (app));
+ break;
+ case PROP_ACTION_GROUP:
+ if (app->running_state)
+ g_value_set_object (value, app->running_state->muxer);
+ break;
+ case PROP_APP_INFO:
+ if (app->info)
+ g_value_set_object (value, app->info);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+shell_app_set_property (GObject *gobject,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ ShellApp *app = SHELL_APP (gobject);
+
+ switch (prop_id)
+ {
+ case PROP_APP_INFO:
+ _shell_app_set_app_info (app, g_value_get_object (value));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
+ break;
+ }
+}
+
+const char *
+shell_app_get_id (ShellApp *app)
+{
+ if (app->info)
+ return g_app_info_get_id (G_APP_INFO (app->info));
+ return app->window_id_string;
+}
+
+static MetaWindow *
+window_backed_app_get_window (ShellApp *app)
+{
+ g_assert (app->info == NULL);
+ if (app->running_state)
+ {
+ g_assert (app->running_state->windows);
+ return app->running_state->windows->data;
+ }
+ else
+ return NULL;
+}
+
+static GIcon *
+x11_window_create_fallback_gicon (MetaWindow *window)
+{
+ StTextureCache *texture_cache;
+ cairo_surface_t *surface;
+
+ g_object_get (window, "icon", &surface, NULL);
+
+ texture_cache = st_texture_cache_get_default ();
+ return st_texture_cache_load_cairo_surface_to_gicon (texture_cache, surface);
+}
+
+static void
+on_window_icon_changed (GObject *object,
+ const GParamSpec *pspec,
+ gpointer user_data)
+{
+ MetaWindow *window = META_WINDOW (object);
+ ShellApp *app = user_data;
+
+ g_clear_object (&app->fallback_icon);
+ app->fallback_icon = x11_window_create_fallback_gicon (window);
+
+ if (!app->fallback_icon)
+ app->fallback_icon = g_themed_icon_new ("application-x-executable");
+
+ g_object_notify_by_pspec (G_OBJECT (app), props[PROP_ICON]);
+}
+
+/**
+ * shell_app_get_icon:
+ *
+ * Look up the icon for this application
+ *
+ * Return value: (transfer none): A #GIcon
+ */
+GIcon *
+shell_app_get_icon (ShellApp *app)
+{
+ MetaWindow *window = NULL;
+
+ g_return_val_if_fail (SHELL_IS_APP (app), NULL);
+
+ if (app->info)
+ return g_app_info_get_icon (G_APP_INFO (app->info));
+
+ if (app->fallback_icon)
+ return app->fallback_icon;
+
+ /* During a state transition from running to not-running for
+ * window-backend apps, it's possible we get a request for the icon.
+ * Avoid asserting here and just return a fallback icon
+ */
+ if (app->running_state != NULL)
+ window = window_backed_app_get_window (app);
+
+ if (window &&
+ meta_window_get_client_type (window) == META_WINDOW_CLIENT_TYPE_X11)
+ {
+ app->fallback_icon_window = window;
+ app->fallback_icon = x11_window_create_fallback_gicon (window);
+ g_signal_connect (G_OBJECT (window),
+ "notify::icon", G_CALLBACK (on_window_icon_changed), app);
+ }
+ else
+ {
+ app->fallback_icon = g_themed_icon_new ("application-x-executable");
+ }
+
+ return app->fallback_icon;
+}
+
+/**
+ * shell_app_create_icon_texture:
+ *
+ * Look up the icon for this application, and create a #ClutterActor
+ * for it at the given size.
+ *
+ * Return value: (transfer none): A floating #ClutterActor
+ */
+ClutterActor *
+shell_app_create_icon_texture (ShellApp *app,
+ int size)
+{
+ ClutterActor *ret;
+
+ ret = st_icon_new ();
+ st_icon_set_icon_size (ST_ICON (ret), size);
+ st_icon_set_fallback_icon_name (ST_ICON (ret), "application-x-executable");
+
+ g_object_bind_property (app, "icon", ret, "gicon", G_BINDING_SYNC_CREATE);
+
+ if (shell_app_is_window_backed (app))
+ st_widget_add_style_class_name (ST_WIDGET (ret), "fallback-app-icon");
+
+ return ret;
+}
+
+const char *
+shell_app_get_name (ShellApp *app)
+{
+ if (app->info)
+ return g_app_info_get_name (G_APP_INFO (app->info));
+ else
+ {
+ MetaWindow *window = window_backed_app_get_window (app);
+ const char *name = NULL;
+
+ if (window)
+ name = meta_window_get_wm_class (window);
+ if (!name)
+ name = C_("program", "Unknown");
+ return name;
+ }
+}
+
+const char *
+shell_app_get_description (ShellApp *app)
+{
+ if (app->info)
+ return g_app_info_get_description (G_APP_INFO (app->info));
+ else
+ return NULL;
+}
+
+/**
+ * shell_app_is_window_backed:
+ *
+ * A window backed application is one which represents just an open
+ * window, i.e. there's no .desktop file association, so we don't know
+ * how to launch it again.
+ */
+gboolean
+shell_app_is_window_backed (ShellApp *app)
+{
+ return app->info == NULL;
+}
+
+typedef struct {
+ MetaWorkspace *workspace;
+ GSList **transients;
+} CollectTransientsData;
+
+static gboolean
+collect_transients_on_workspace (MetaWindow *window,
+ gpointer datap)
+{
+ CollectTransientsData *data = datap;
+
+ if (data->workspace && meta_window_get_workspace (window) != data->workspace)
+ return TRUE;
+
+ *data->transients = g_slist_prepend (*data->transients, window);
+ return TRUE;
+}
+
+/* The basic idea here is that when we're targeting a window,
+ * if it has transients we want to pick the most recent one
+ * the user interacted with.
+ * This function makes raising GEdit with the file chooser
+ * open work correctly.
+ */
+static MetaWindow *
+find_most_recent_transient_on_same_workspace (MetaDisplay *display,
+ MetaWindow *reference)
+{
+ GSList *transients, *transients_sorted, *iter;
+ MetaWindow *result;
+ CollectTransientsData data;
+
+ transients = NULL;
+ data.workspace = meta_window_get_workspace (reference);
+ data.transients = &transients;
+
+ meta_window_foreach_transient (reference, collect_transients_on_workspace, &data);
+
+ transients_sorted = meta_display_sort_windows_by_stacking (display, transients);
+ /* Reverse this so we're top-to-bottom (yes, we should probably change the order
+ * returned from the sort_windows_by_stacking function)
+ */
+ transients_sorted = g_slist_reverse (transients_sorted);
+ g_slist_free (transients);
+ transients = NULL;
+
+ result = NULL;
+ for (iter = transients_sorted; iter; iter = iter->next)
+ {
+ MetaWindow *window = iter->data;
+ MetaWindowType wintype = meta_window_get_window_type (window);
+
+ /* Don't want to focus UTILITY types, like the Gimp toolbars */
+ if (wintype == META_WINDOW_NORMAL ||
+ wintype == META_WINDOW_DIALOG)
+ {
+ result = window;
+ break;
+ }
+ }
+ g_slist_free (transients_sorted);
+ return result;
+}
+
+static MetaWorkspace *
+get_active_workspace (void)
+{
+ ShellGlobal *global = shell_global_get ();
+ MetaDisplay *display = shell_global_get_display (global);
+ MetaWorkspaceManager *workspace_manager =
+ meta_display_get_workspace_manager (display);
+
+ return meta_workspace_manager_get_active_workspace (workspace_manager);
+}
+
+/**
+ * shell_app_activate_window:
+ * @app: a #ShellApp
+ * @window: (nullable): Window to be focused
+ * @timestamp: Event timestamp
+ *
+ * Bring all windows for the given app to the foreground,
+ * but ensure that @window is on top. If @window is %NULL,
+ * the window with the most recent user time for the app
+ * will be used.
+ *
+ * This function has no effect if @app is not currently running.
+ */
+void
+shell_app_activate_window (ShellApp *app,
+ MetaWindow *window,
+ guint32 timestamp)
+{
+ g_autoptr (GSList) windows = NULL;
+
+ if (shell_app_get_state (app) != SHELL_APP_STATE_RUNNING)
+ return;
+
+ windows = shell_app_get_windows (app);
+ if (window == NULL && windows)
+ window = windows->data;
+
+ if (!g_slist_find (windows, window))
+ return;
+ else
+ {
+ GSList *windows_reversed, *iter;
+ ShellGlobal *global = shell_global_get ();
+ MetaDisplay *display = shell_global_get_display (global);
+ MetaWorkspace *active = get_active_workspace ();
+ MetaWorkspace *workspace = meta_window_get_workspace (window);
+ guint32 last_user_timestamp = meta_display_get_last_user_time (display);
+ MetaWindow *most_recent_transient;
+
+ if (meta_display_xserver_time_is_before (display, timestamp, last_user_timestamp))
+ {
+ meta_window_set_demands_attention (window);
+ return;
+ }
+
+ /* Now raise all the other windows for the app that are on
+ * the same workspace, in reverse order to preserve the stacking.
+ */
+ windows_reversed = g_slist_copy (windows);
+ windows_reversed = g_slist_reverse (windows_reversed);
+ for (iter = windows_reversed; iter; iter = iter->next)
+ {
+ MetaWindow *other_window = iter->data;
+
+ if (other_window != window && meta_window_get_workspace (other_window) == workspace)
+ meta_window_raise (other_window);
+ }
+ g_slist_free (windows_reversed);
+
+ /* If we have a transient that the user's interacted with more recently than
+ * the window, pick that.
+ */
+ most_recent_transient = find_most_recent_transient_on_same_workspace (display, window);
+ if (most_recent_transient
+ && meta_display_xserver_time_is_before (display,
+ meta_window_get_user_time (window),
+ meta_window_get_user_time (most_recent_transient)))
+ window = most_recent_transient;
+
+
+ if (active != workspace)
+ meta_workspace_activate_with_focus (workspace, window, timestamp);
+ else
+ meta_window_activate (window, timestamp);
+ }
+}
+
+
+void
+shell_app_update_window_actions (ShellApp *app, MetaWindow *window)
+{
+ const char *object_path;
+
+ object_path = meta_window_get_gtk_window_object_path (window);
+ if (object_path != NULL)
+ {
+ GActionGroup *actions;
+
+ actions = g_object_get_data (G_OBJECT (window), "actions");
+ if (actions == NULL)
+ {
+ actions = G_ACTION_GROUP (g_dbus_action_group_get (app->running_state->session,
+ meta_window_get_gtk_unique_bus_name (window),
+ object_path));
+ g_object_set_data_full (G_OBJECT (window), "actions", actions, g_object_unref);
+ }
+
+ g_assert (app->running_state->muxer);
+ gtk_action_muxer_insert (app->running_state->muxer, "win", actions);
+ g_object_notify_by_pspec (G_OBJECT (app), props[PROP_ACTION_GROUP]);
+ }
+}
+
+/**
+ * shell_app_activate:
+ * @app: a #ShellApp
+ *
+ * Like shell_app_activate_full(), but using the default workspace and
+ * event timestamp.
+ */
+void
+shell_app_activate (ShellApp *app)
+{
+ return shell_app_activate_full (app, -1, 0);
+}
+
+/**
+ * shell_app_activate_full:
+ * @app: a #ShellApp
+ * @workspace: launch on this workspace, or -1 for default. Ignored if
+ * activating an existing window
+ * @timestamp: Event timestamp
+ *
+ * Perform an appropriate default action for operating on this application,
+ * dependent on its current state. For example, if the application is not
+ * currently running, launch it. If it is running, activate the most
+ * recently used NORMAL window (or if that window has a transient, the most
+ * recently used transient for that window).
+ */
+void
+shell_app_activate_full (ShellApp *app,
+ int workspace,
+ guint32 timestamp)
+{
+ ShellGlobal *global;
+
+ global = shell_global_get ();
+
+ if (timestamp == 0)
+ timestamp = shell_global_get_current_time (global);
+
+ switch (app->state)
+ {
+ case SHELL_APP_STATE_STOPPED:
+ {
+ GError *error = NULL;
+ if (!shell_app_launch (app, timestamp, workspace, SHELL_APP_LAUNCH_GPU_APP_PREF, &error))
+ {
+ char *msg;
+ msg = g_strdup_printf (_("Failed to launch “%s”"), shell_app_get_name (app));
+ shell_global_notify_error (global,
+ msg,
+ error->message);
+ g_free (msg);
+ g_clear_error (&error);
+ }
+ }
+ break;
+ case SHELL_APP_STATE_STARTING:
+ break;
+ case SHELL_APP_STATE_RUNNING:
+ shell_app_activate_window (app, NULL, timestamp);
+ break;
+ default:
+ g_assert_not_reached();
+ break;
+ }
+}
+
+/**
+ * shell_app_open_new_window:
+ * @app: a #ShellApp
+ * @workspace: open on this workspace, or -1 for default
+ *
+ * Request that the application create a new window.
+ */
+void
+shell_app_open_new_window (ShellApp *app,
+ int workspace)
+{
+ GActionGroup *group = NULL;
+ const char * const *actions;
+
+ g_return_if_fail (app->info != NULL);
+
+ /* First check whether the application provides a "new-window" desktop
+ * action - it is a safe bet that it will open a new window, and activating
+ * it will trigger startup notification if necessary
+ */
+ actions = g_desktop_app_info_list_actions (G_DESKTOP_APP_INFO (app->info));
+
+ if (g_strv_contains (actions, "new-window"))
+ {
+ shell_app_launch_action (app, "new-window", 0, workspace);
+ return;
+ }
+
+ /* Next, check whether the app exports an explicit "new-window" action
+ * that we can activate on the bus - the muxer will add startup notification
+ * information to the platform data, so this should work just as well as
+ * desktop actions.
+ */
+ group = app->running_state ? G_ACTION_GROUP (app->running_state->muxer)
+ : NULL;
+
+ if (group &&
+ g_action_group_has_action (group, "app.new-window") &&
+ g_action_group_get_action_parameter_type (group, "app.new-window") == NULL)
+ {
+ g_action_group_activate_action (group, "app.new-window", NULL);
+
+ return;
+ }
+
+ /* Lastly, just always launch the application again, even if we know
+ * it was already running. For most applications this
+ * should have the effect of creating a new window, whether that's
+ * a second process (in the case of Calculator) or IPC to existing
+ * instance (Firefox). There are a few less-sensical cases such
+ * as say Pidgin.
+ */
+ shell_app_launch (app, 0, workspace, SHELL_APP_LAUNCH_GPU_APP_PREF, NULL);
+}
+
+/**
+ * shell_app_can_open_new_window:
+ * @app: a #ShellApp
+ *
+ * Returns %TRUE if the app supports opening a new window through
+ * shell_app_open_new_window() (ie, if calling that function will
+ * result in actually opening a new window and not something else,
+ * like presenting the most recently active one)
+ */
+gboolean
+shell_app_can_open_new_window (ShellApp *app)
+{
+ ShellAppRunningState *state;
+ MetaWindow *window;
+ GDesktopAppInfo *desktop_info;
+ const char * const *desktop_actions;
+
+ /* Apps that are stopped can always open new windows, because
+ * activating them would open the first one; if they are starting,
+ * we cannot tell whether they can open additional windows until
+ * they are running */
+ if (app->state != SHELL_APP_STATE_RUNNING)
+ return app->state == SHELL_APP_STATE_STOPPED;
+
+ state = app->running_state;
+
+ /* If the app has an explicit new-window action, then it can
+ (or it should be able to) ...
+ */
+ if (g_action_group_has_action (G_ACTION_GROUP (state->muxer), "app.new-window"))
+ return TRUE;
+
+ /* If the app doesn't have a desktop file, then nothing is possible */
+ if (!app->info)
+ return FALSE;
+
+ desktop_info = G_DESKTOP_APP_INFO (app->info);
+
+ /* If the app is explicitly telling us via its desktop file, then we know
+ * for sure
+ */
+ if (g_desktop_app_info_has_key (desktop_info, "SingleMainWindow"))
+ return !g_desktop_app_info_get_boolean (desktop_info,
+ "SingleMainWindow");
+
+ /* GNOME-specific key, for backwards compatibility with apps that haven't
+ * started using the XDG "SingleMainWindow" key yet
+ */
+ if (g_desktop_app_info_has_key (desktop_info, "X-GNOME-SingleWindow"))
+ return !g_desktop_app_info_get_boolean (desktop_info,
+ "X-GNOME-SingleWindow");
+
+ /* If it has a new-window desktop action, it should be able to */
+ desktop_actions = g_desktop_app_info_list_actions (desktop_info);
+ if (desktop_actions && g_strv_contains (desktop_actions, "new-window"))
+ return TRUE;
+
+ /* If this is a unique GtkApplication, and we don't have a new-window, then
+ probably we can't
+
+ We don't consider non-unique GtkApplications here to handle cases like
+ evince, which don't export a new-window action because each window is in
+ a different process. In any case, in a non-unique GtkApplication each
+ Activate() knows nothing about the other instances, so it will show a
+ new window.
+ */
+
+ window = state->windows->data;
+
+ if (state->unique_bus_name != NULL &&
+ meta_window_get_gtk_application_object_path (window) != NULL)
+ {
+ if (meta_window_get_gtk_application_id (window) != NULL)
+ return FALSE;
+ else
+ return TRUE;
+ }
+
+ /* In all other cases, we don't have a reliable source of information
+ or a decent heuristic, so we err on the compatibility side and say
+ yes.
+ */
+ return TRUE;
+}
+
+/**
+ * shell_app_get_state:
+ * @app: a #ShellApp
+ *
+ * Returns: State of the application
+ */
+ShellAppState
+shell_app_get_state (ShellApp *app)
+{
+ return app->state;
+}
+
+typedef struct {
+ ShellApp *app;
+ MetaWorkspace *active_workspace;
+} CompareWindowsData;
+
+static int
+shell_app_compare_windows (gconstpointer a,
+ gconstpointer b,
+ gpointer datap)
+{
+ MetaWindow *win_a = (gpointer)a;
+ MetaWindow *win_b = (gpointer)b;
+ CompareWindowsData *data = datap;
+ gboolean ws_a, ws_b;
+ gboolean vis_a, vis_b;
+
+ ws_a = meta_window_get_workspace (win_a) == data->active_workspace;
+ ws_b = meta_window_get_workspace (win_b) == data->active_workspace;
+
+ if (ws_a && !ws_b)
+ return -1;
+ else if (!ws_a && ws_b)
+ return 1;
+
+ vis_a = meta_window_showing_on_its_workspace (win_a);
+ vis_b = meta_window_showing_on_its_workspace (win_b);
+
+ if (vis_a && !vis_b)
+ return -1;
+ else if (!vis_a && vis_b)
+ return 1;
+
+ return meta_window_get_user_time (win_b) - meta_window_get_user_time (win_a);
+}
+
+/**
+ * shell_app_get_windows:
+ * @app:
+ *
+ * Get the windows which are associated with this application. The
+ * returned list will be sorted first by whether they're on the
+ * active workspace, then by whether they're visible, and finally
+ * by the time the user last interacted with them.
+ *
+ * Returns: (transfer container) (element-type MetaWindow): List of windows
+ */
+GSList *
+shell_app_get_windows (ShellApp *app)
+{
+ GSList *windows = NULL;
+ GSList *l;
+
+ if (app->running_state == NULL)
+ return NULL;
+
+ if (app->running_state->window_sort_stale)
+ {
+ CompareWindowsData data;
+ data.app = app;
+ data.active_workspace = get_active_workspace ();
+ app->running_state->windows = g_slist_sort_with_data (app->running_state->windows, shell_app_compare_windows, &data);
+ app->running_state->window_sort_stale = FALSE;
+ }
+
+ for (l = app->running_state->windows; l; l = l->next)
+ if (!meta_window_is_override_redirect (META_WINDOW (l->data)))
+ windows = g_slist_prepend (windows, l->data);
+
+ return g_slist_reverse (windows);
+}
+
+guint
+shell_app_get_n_windows (ShellApp *app)
+{
+ if (app->running_state == NULL)
+ return 0;
+ return g_slist_length (app->running_state->windows);
+}
+
+gboolean
+shell_app_is_on_workspace (ShellApp *app,
+ MetaWorkspace *workspace)
+{
+ GSList *iter;
+
+ if (shell_app_get_state (app) == SHELL_APP_STATE_STARTING)
+ {
+ if (app->started_on_workspace == -1 ||
+ meta_workspace_index (workspace) == app->started_on_workspace)
+ return TRUE;
+ else
+ return FALSE;
+ }
+
+ if (app->running_state == NULL)
+ return FALSE;
+
+ for (iter = app->running_state->windows; iter; iter = iter->next)
+ {
+ if (meta_window_get_workspace (iter->data) == workspace)
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static int
+shell_app_get_last_user_time (ShellApp *app)
+{
+ GSList *iter;
+ guint32 last_user_time;
+
+ last_user_time = 0;
+
+ if (app->running_state != NULL)
+ {
+ for (iter = app->running_state->windows; iter; iter = iter->next)
+ last_user_time = MAX (last_user_time, meta_window_get_user_time (iter->data));
+ }
+
+ return (int)last_user_time;
+}
+
+static gboolean
+shell_app_is_minimized (ShellApp *app)
+{
+ GSList *iter;
+
+ if (app->running_state == NULL)
+ return FALSE;
+
+ for (iter = app->running_state->windows; iter; iter = iter->next)
+ {
+ if (meta_window_showing_on_its_workspace (iter->data))
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+/**
+ * shell_app_compare:
+ * @app:
+ * @other: A #ShellApp
+ *
+ * Compare one #ShellApp instance to another, in the following way:
+ * - Running applications sort before not-running applications.
+ * - If one of them has non-minimized windows and the other does not,
+ * the one with visible windows is first.
+ * - Finally, the application which the user interacted with most recently
+ * compares earlier.
+ */
+int
+shell_app_compare (ShellApp *app,
+ ShellApp *other)
+{
+ gboolean min_app, min_other;
+
+ if (app->state != other->state)
+ {
+ if (app->state == SHELL_APP_STATE_RUNNING)
+ return -1;
+ return 1;
+ }
+
+ min_app = shell_app_is_minimized (app);
+ min_other = shell_app_is_minimized (other);
+
+ if (min_app != min_other)
+ {
+ if (min_other)
+ return -1;
+ return 1;
+ }
+
+ if (app->state == SHELL_APP_STATE_RUNNING)
+ {
+ if (app->running_state->windows && !other->running_state->windows)
+ return -1;
+ else if (!app->running_state->windows && other->running_state->windows)
+ return 1;
+
+ return shell_app_get_last_user_time (other) - shell_app_get_last_user_time (app);
+ }
+
+ return 0;
+}
+
+ShellApp *
+_shell_app_new_for_window (MetaWindow *window)
+{
+ ShellApp *app;
+
+ app = g_object_new (SHELL_TYPE_APP, NULL);
+
+ app->window_id_string = g_strdup_printf ("window:%d", meta_window_get_stable_sequence (window));
+
+ _shell_app_add_window (app, window);
+
+ return app;
+}
+
+ShellApp *
+_shell_app_new (GDesktopAppInfo *info)
+{
+ ShellApp *app;
+
+ app = g_object_new (SHELL_TYPE_APP,
+ "app-info", info,
+ NULL);
+
+ return app;
+}
+
+void
+_shell_app_set_app_info (ShellApp *app,
+ GDesktopAppInfo *info)
+{
+ g_set_object (&app->info, info);
+
+ g_clear_pointer (&app->name_collation_key, g_free);
+ if (app->info)
+ app->name_collation_key = g_utf8_collate_key (shell_app_get_name (app), -1);
+}
+
+static void
+shell_app_state_transition (ShellApp *app,
+ ShellAppState state)
+{
+ if (app->state == state)
+ return;
+ g_return_if_fail (!(app->state == SHELL_APP_STATE_RUNNING &&
+ state == SHELL_APP_STATE_STARTING));
+ app->state = state;
+
+ _shell_app_system_notify_app_state_changed (shell_app_system_get_default (), app);
+
+ g_object_notify_by_pspec (G_OBJECT (app), props[PROP_STATE]);
+}
+
+static void
+shell_app_on_user_time_changed (MetaWindow *window,
+ GParamSpec *pspec,
+ ShellApp *app)
+{
+ g_assert (app->running_state != NULL);
+
+ /* Ideally we don't want to emit windows-changed if the sort order
+ * isn't actually changing. This check catches most of those.
+ */
+ if (window != app->running_state->windows->data)
+ {
+ app->running_state->window_sort_stale = TRUE;
+ g_signal_emit (app, shell_app_signals[WINDOWS_CHANGED], 0);
+ }
+}
+
+static void
+shell_app_sync_running_state (ShellApp *app)
+{
+ g_return_if_fail (app->running_state != NULL);
+
+ if (app->state != SHELL_APP_STATE_STARTING)
+ {
+ if (app->running_state->interesting_windows == 0)
+ shell_app_state_transition (app, SHELL_APP_STATE_STOPPED);
+ else
+ shell_app_state_transition (app, SHELL_APP_STATE_RUNNING);
+ }
+}
+
+
+static void
+shell_app_on_skip_taskbar_changed (MetaWindow *window,
+ GParamSpec *pspec,
+ ShellApp *app)
+{
+ g_assert (app->running_state != NULL);
+
+ /* we rely on MetaWindow:skip-taskbar only being notified
+ * when it actually changes; when that assumption breaks,
+ * we'll have to track the "interesting" windows themselves
+ */
+ if (meta_window_is_skip_taskbar (window))
+ app->running_state->interesting_windows--;
+ else
+ app->running_state->interesting_windows++;
+
+ shell_app_sync_running_state (app);
+}
+
+static void
+shell_app_on_ws_switch (MetaWorkspaceManager *workspace_manager,
+ int from,
+ int to,
+ MetaMotionDirection direction,
+ gpointer data)
+{
+ ShellApp *app = SHELL_APP (data);
+
+ g_assert (app->running_state != NULL);
+
+ app->running_state->window_sort_stale = TRUE;
+
+ g_signal_emit (app, shell_app_signals[WINDOWS_CHANGED], 0);
+}
+
+gboolean
+shell_app_get_busy (ShellApp *app)
+{
+ if (app->running_state != NULL &&
+ app->running_state->application_proxy != NULL &&
+ shell_org_gtk_application_get_busy (app->running_state->application_proxy))
+ return TRUE;
+
+ return FALSE;
+}
+
+static void
+busy_changed_cb (GObject *object,
+ GParamSpec *pspec,
+ gpointer user_data)
+{
+ ShellApp *app = user_data;
+
+ g_assert (SHELL_IS_APP (app));
+
+ g_object_notify_by_pspec (G_OBJECT (app), props[PROP_BUSY]);
+}
+
+static void
+get_application_proxy (GObject *source,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ ShellApp *app = user_data;
+ ShellOrgGtkApplication *proxy;
+ g_autoptr (GError) error = NULL;
+
+ g_assert (SHELL_IS_APP (app));
+
+ proxy = shell_org_gtk_application_proxy_new_finish (result, &error);
+ if (proxy != NULL)
+ {
+ app->running_state->application_proxy = proxy;
+ g_signal_connect (proxy,
+ "notify::busy",
+ G_CALLBACK (busy_changed_cb),
+ app);
+ if (shell_org_gtk_application_get_busy (proxy))
+ g_object_notify_by_pspec (G_OBJECT (app), props[PROP_BUSY]);
+ }
+
+ if (app->running_state != NULL &&
+ !g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ g_clear_object (&app->running_state->cancellable);
+
+ g_object_unref (app);
+}
+
+static void
+shell_app_ensure_busy_watch (ShellApp *app)
+{
+ ShellAppRunningState *running_state = app->running_state;
+ MetaWindow *window;
+ const gchar *object_path;
+
+ if (running_state->application_proxy != NULL ||
+ running_state->cancellable != NULL)
+ return;
+
+ if (running_state->unique_bus_name == NULL)
+ return;
+
+ window = g_slist_nth_data (running_state->windows, 0);
+ object_path = meta_window_get_gtk_application_object_path (window);
+
+ if (object_path == NULL)
+ return;
+
+ running_state->cancellable = g_cancellable_new();
+ /* Take a reference to app to make sure it isn't finalized before
+ get_application_proxy runs */
+ shell_org_gtk_application_proxy_new (running_state->session,
+ G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START,
+ running_state->unique_bus_name,
+ object_path,
+ running_state->cancellable,
+ get_application_proxy,
+ g_object_ref (app));
+}
+
+void
+_shell_app_add_window (ShellApp *app,
+ MetaWindow *window)
+{
+ if (app->running_state && g_slist_find (app->running_state->windows, window))
+ return;
+
+ g_object_freeze_notify (G_OBJECT (app));
+
+ if (!app->running_state)
+ create_running_state (app);
+
+ app->running_state->window_sort_stale = TRUE;
+ app->running_state->windows = g_slist_prepend (app->running_state->windows, g_object_ref (window));
+ g_signal_connect_object (window, "notify::user-time", G_CALLBACK(shell_app_on_user_time_changed), app, 0);
+ g_signal_connect_object (window, "notify::skip-taskbar", G_CALLBACK(shell_app_on_skip_taskbar_changed), app, 0);
+
+ shell_app_update_app_actions (app, window);
+ shell_app_ensure_busy_watch (app);
+
+ if (!meta_window_is_skip_taskbar (window))
+ app->running_state->interesting_windows++;
+ shell_app_sync_running_state (app);
+
+ if (app->started_on_workspace >= 0 && !meta_window_is_on_all_workspaces (window))
+ meta_window_change_workspace_by_index (window, app->started_on_workspace, FALSE);
+ app->started_on_workspace = -1;
+
+ g_object_thaw_notify (G_OBJECT (app));
+
+ g_signal_emit (app, shell_app_signals[WINDOWS_CHANGED], 0);
+}
+
+void
+_shell_app_remove_window (ShellApp *app,
+ MetaWindow *window)
+{
+ g_assert (app->running_state != NULL);
+
+ if (!g_slist_find (app->running_state->windows, window))
+ return;
+
+ app->running_state->windows = g_slist_remove (app->running_state->windows, window);
+
+ if (!meta_window_is_skip_taskbar (window))
+ app->running_state->interesting_windows--;
+ shell_app_sync_running_state (app);
+
+ if (app->running_state->windows == NULL)
+ g_clear_pointer (&app->running_state, unref_running_state);
+
+ g_signal_handlers_disconnect_by_func (window, G_CALLBACK(shell_app_on_user_time_changed), app);
+ g_signal_handlers_disconnect_by_func (window, G_CALLBACK(shell_app_on_skip_taskbar_changed), app);
+ if (window == app->fallback_icon_window)
+ {
+ g_signal_handlers_disconnect_by_func (window, G_CALLBACK(on_window_icon_changed), app);
+ app->fallback_icon_window = NULL;
+
+ /* Select a new icon from a different window. */
+ g_clear_object (&app->fallback_icon);
+ g_object_notify_by_pspec (G_OBJECT (app), props[PROP_ICON]);
+ }
+
+ g_object_unref (window);
+
+ g_signal_emit (app, shell_app_signals[WINDOWS_CHANGED], 0);
+}
+
+/**
+ * shell_app_get_pids:
+ * @app: a #ShellApp
+ *
+ * Returns: (transfer container) (element-type int): An unordered list of process identifiers associated with this application.
+ */
+GSList *
+shell_app_get_pids (ShellApp *app)
+{
+ GSList *result;
+ g_autoptr (GSList) windows = NULL;
+ GSList *iter;
+
+ result = NULL;
+ windows = shell_app_get_windows (app);
+ for (iter = windows; iter; iter = iter->next)
+ {
+ MetaWindow *window = iter->data;
+ pid_t pid = meta_window_get_pid (window);
+
+ if (pid < 1)
+ continue;
+
+ /* Note in the (by far) common case, app will only have one pid, so
+ * we'll hit the first element, so don't worry about O(N^2) here.
+ */
+ if (!g_slist_find (result, GINT_TO_POINTER (pid)))
+ result = g_slist_prepend (result, GINT_TO_POINTER (pid));
+ }
+ return result;
+}
+
+void
+_shell_app_handle_startup_sequence (ShellApp *app,
+ MetaStartupSequence *sequence)
+{
+ gboolean starting = !meta_startup_sequence_get_completed (sequence);
+
+ /* The Shell design calls for on application launch, the app title
+ * appears at top, and no X window is focused. So when we get
+ * a startup-notification for this app, transition it to STARTING
+ * if it's currently stopped, set it as our application focus,
+ * but focus the no_focus window.
+ */
+ if (starting && shell_app_get_state (app) == SHELL_APP_STATE_STOPPED)
+ {
+ MetaDisplay *display = shell_global_get_display (shell_global_get ());
+
+ shell_app_state_transition (app, SHELL_APP_STATE_STARTING);
+ meta_display_unset_input_focus (display,
+ meta_startup_sequence_get_timestamp (sequence));
+ }
+
+ if (starting)
+ app->started_on_workspace = meta_startup_sequence_get_workspace (sequence);
+ else if (app->running_state && app->running_state->windows)
+ shell_app_state_transition (app, SHELL_APP_STATE_RUNNING);
+ else /* application have > 1 .desktop file */
+ shell_app_state_transition (app, SHELL_APP_STATE_STOPPED);
+}
+
+/**
+ * shell_app_request_quit:
+ * @app: A #ShellApp
+ *
+ * Initiate an asynchronous request to quit this application.
+ * The application may interact with the user, and the user
+ * might cancel the quit request from the application UI.
+ *
+ * This operation may not be supported for all applications.
+ *
+ * Returns: %TRUE if a quit request is supported for this application
+ */
+gboolean
+shell_app_request_quit (ShellApp *app)
+{
+ GActionGroup *group = NULL;
+ GSList *iter;
+
+ if (shell_app_get_state (app) != SHELL_APP_STATE_RUNNING)
+ return FALSE;
+
+ /* First, check whether the app exports an explicit "quit" action
+ * that we can activate on the bus
+ */
+ group = G_ACTION_GROUP (app->running_state->muxer);
+
+ if (g_action_group_has_action (group, "app.quit") &&
+ g_action_group_get_action_parameter_type (group, "app.quit") == NULL)
+ {
+ g_action_group_activate_action (group, "app.quit", NULL);
+
+ return TRUE;
+ }
+
+ /* Otherwise, fall back to closing all the app's windows */
+ for (iter = app->running_state->windows; iter; iter = iter->next)
+ {
+ MetaWindow *win = iter->data;
+
+ if (!meta_window_can_close (win))
+ continue;
+
+ meta_window_delete (win, shell_global_get_current_time (shell_global_get ()));
+ }
+ return TRUE;
+}
+
+static void
+child_context_setup (gpointer user_data)
+{
+ ShellGlobal *shell_global = user_data;
+ MetaContext *meta_context;
+
+ g_object_get (shell_global, "context", &meta_context, NULL);
+ meta_context_restore_rlimit_nofile (meta_context, NULL);
+}
+
+#if !defined(HAVE_GIO_DESKTOP_LAUNCH_URIS_WITH_FDS) && defined(HAVE_SYSTEMD)
+/* This sets up the launched application to log to the journal
+ * using its own identifier, instead of just "gnome-session".
+ */
+static void
+app_child_setup (gpointer user_data)
+{
+ const char *appid = user_data;
+ int res;
+ int journalfd = sd_journal_stream_fd (appid, LOG_INFO, FALSE);
+ ShellGlobal *shell_global = shell_global_get ();
+
+ if (journalfd >= 0)
+ {
+ do
+ res = dup2 (journalfd, 1);
+ while (G_UNLIKELY (res == -1 && errno == EINTR));
+ do
+ res = dup2 (journalfd, 2);
+ while (G_UNLIKELY (res == -1 && errno == EINTR));
+ (void) close (journalfd);
+ }
+
+ child_context_setup (shell_global);
+}
+#endif
+
+static void
+wait_pid (GDesktopAppInfo *appinfo,
+ GPid pid,
+ gpointer user_data)
+{
+ g_child_watch_add (pid, (GChildWatchFunc) g_spawn_close_pid, NULL);
+}
+
+static void
+apply_discrete_gpu_env (GAppLaunchContext *context,
+ ShellGlobal *global)
+{
+ GDBusProxy *proxy;
+ GVariant* variant;
+ guint num_children, i;
+
+ proxy = shell_global_get_switcheroo_control (global);
+ if (!proxy)
+ {
+ g_warning ("Could not apply discrete GPU environment, switcheroo-control not available");
+ return;
+ }
+
+ variant = shell_net_hadess_switcheroo_control_get_gpus (SHELL_NET_HADESS_SWITCHEROO_CONTROL (proxy));
+ if (!variant)
+ {
+ g_warning ("Could not apply discrete GPU environment, no GPUs in list");
+ return;
+ }
+
+ num_children = g_variant_n_children (variant);
+ for (i = 0; i < num_children; i++)
+ {
+ g_autoptr(GVariant) gpu = NULL;
+ g_autoptr(GVariant) env = NULL;
+ g_autoptr(GVariant) default_variant = NULL;
+ g_autofree const char **env_s = NULL;
+ guint j;
+
+ gpu = g_variant_get_child_value (variant, i);
+ if (!gpu ||
+ !g_variant_is_of_type (gpu, G_VARIANT_TYPE ("a{s*}")))
+ continue;
+
+ /* Skip over the default GPU */
+ default_variant = g_variant_lookup_value (gpu, "Default", NULL);
+ if (!default_variant || g_variant_get_boolean (default_variant))
+ continue;
+
+ env = g_variant_lookup_value (gpu, "Environment", NULL);
+ if (!env)
+ continue;
+
+ env_s = g_variant_get_strv (env, NULL);
+ for (j = 0; env_s[j] != NULL; j = j + 2)
+ g_app_launch_context_setenv (context, env_s[j], env_s[j+1]);
+ return;
+ }
+
+ g_debug ("Could not find discrete GPU in switcheroo-control, not applying environment");
+}
+
+/**
+ * shell_app_launch:
+ * @timestamp: Event timestamp, or 0 for current event timestamp
+ * @workspace: Start on this workspace, or -1 for default
+ * @gpu_pref: the GPU to prefer launching on
+ * @error: A #GError
+ */
+gboolean
+shell_app_launch (ShellApp *app,
+ guint timestamp,
+ int workspace,
+ ShellAppLaunchGpu gpu_pref,
+ GError **error)
+{
+ ShellGlobal *global;
+ GAppLaunchContext *context;
+ gboolean ret;
+ GSpawnFlags flags;
+ gboolean discrete_gpu = FALSE;
+ ShellGlobal *shell_global = shell_global_get ();
+
+ if (app->info == NULL)
+ {
+ MetaWindow *window = window_backed_app_get_window (app);
+ /* We don't use an error return if there no longer any windows, because the
+ * user attempting to activate a stale window backed app isn't something
+ * we would expect the caller to meaningfully handle or display an error
+ * message to the user.
+ */
+ if (window)
+ meta_window_activate (window, timestamp);
+ return TRUE;
+ }
+
+ global = shell_global_get ();
+ context = shell_global_create_app_launch_context (global, timestamp, workspace);
+ if (gpu_pref == SHELL_APP_LAUNCH_GPU_APP_PREF)
+ discrete_gpu = g_desktop_app_info_get_boolean (app->info, "PrefersNonDefaultGPU");
+ else
+ discrete_gpu = (gpu_pref == SHELL_APP_LAUNCH_GPU_DISCRETE);
+
+ if (discrete_gpu)
+ apply_discrete_gpu_env (context, global);
+
+ /* Set LEAVE_DESCRIPTORS_OPEN in order to use an optimized gspawn
+ * codepath. The shell's open file descriptors should be marked CLOEXEC
+ * so that they are automatically closed even with this flag set.
+ */
+ flags = G_SPAWN_SEARCH_PATH | G_SPAWN_DO_NOT_REAP_CHILD |
+ G_SPAWN_LEAVE_DESCRIPTORS_OPEN;
+
+#ifdef HAVE_GIO_DESKTOP_LAUNCH_URIS_WITH_FDS
+ /* Optimized spawn path, avoiding a child_setup function */
+ {
+ int journalfd = -1;
+
+#ifdef HAVE_SYSTEMD
+ journalfd = sd_journal_stream_fd (shell_app_get_id (app), LOG_INFO, FALSE);
+#endif /* HAVE_SYSTEMD */
+
+ ret = g_desktop_app_info_launch_uris_as_manager_with_fds (app->info, NULL,
+ context,
+ flags,
+ child_context_setup, shell_global,
+ wait_pid, NULL,
+ -1,
+ journalfd,
+ journalfd,
+ error);
+
+ if (journalfd >= 0)
+ (void) close (journalfd);
+ }
+#else /* !HAVE_GIO_DESKTOP_LAUNCH_URIS_WITH_FDS */
+ ret = g_desktop_app_info_launch_uris_as_manager (app->info, NULL,
+ context,
+ flags,
+#ifdef HAVE_SYSTEMD
+ app_child_setup, (gpointer)shell_app_get_id (app),
+#else
+ child_context_setup, shell_global,
+#endif
+ wait_pid, NULL,
+ error);
+#endif /* HAVE_GIO_DESKTOP_LAUNCH_URIS_WITH_FDS */
+ g_object_unref (context);
+
+ return ret;
+}
+
+/**
+ * shell_app_launch_action:
+ * @app: the #ShellApp
+ * @action_name: the name of the action to launch (as obtained by
+ * g_desktop_app_info_list_actions())
+ * @timestamp: Event timestamp, or 0 for current event timestamp
+ * @workspace: Start on this workspace, or -1 for default
+ */
+void
+shell_app_launch_action (ShellApp *app,
+ const char *action_name,
+ guint timestamp,
+ int workspace)
+{
+ ShellGlobal *global;
+ GAppLaunchContext *context;
+
+ global = shell_global_get ();
+ context = shell_global_create_app_launch_context (global, timestamp, workspace);
+
+ g_desktop_app_info_launch_action (G_DESKTOP_APP_INFO (app->info),
+ action_name, context);
+
+ g_object_unref (context);
+}
+
+/**
+ * shell_app_get_app_info:
+ * @app: a #ShellApp
+ *
+ * Returns: (transfer none): The #GDesktopAppInfo for this app, or %NULL if backed by a window
+ */
+GDesktopAppInfo *
+shell_app_get_app_info (ShellApp *app)
+{
+ return app->info;
+}
+
+static void
+create_running_state (ShellApp *app)
+{
+ MetaDisplay *display = shell_global_get_display (shell_global_get ());
+ MetaWorkspaceManager *workspace_manager =
+ meta_display_get_workspace_manager (display);
+
+ g_assert (app->running_state == NULL);
+
+ app->running_state = g_new0 (ShellAppRunningState, 1);
+ app->running_state->refcount = 1;
+ app->running_state->workspace_switch_id =
+ g_signal_connect (workspace_manager, "workspace-switched",
+ G_CALLBACK (shell_app_on_ws_switch), app);
+
+ app->running_state->session = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL);
+ g_assert (app->running_state->session != NULL);
+ app->running_state->muxer = gtk_action_muxer_new ();
+}
+
+void
+shell_app_update_app_actions (ShellApp *app,
+ MetaWindow *window)
+{
+ const gchar *unique_bus_name;
+
+ /* We assume that 'gtk-application-object-path' and
+ * 'gtk-app-menu-object-path' are the same for all windows which
+ * have it set.
+ *
+ * It could be possible, however, that the first window we see
+ * belonging to the app didn't have them set. For this reason, we
+ * take the values from the first window that has them set and ignore
+ * all the rest (until the app is stopped and restarted).
+ */
+
+ unique_bus_name = meta_window_get_gtk_unique_bus_name (window);
+
+ if (g_strcmp0 (app->running_state->unique_bus_name, unique_bus_name) != 0)
+ {
+ const gchar *application_object_path;
+ GDBusActionGroup *actions;
+
+ application_object_path = meta_window_get_gtk_application_object_path (window);
+
+ if (application_object_path == NULL || unique_bus_name == NULL)
+ return;
+
+ g_clear_pointer (&app->running_state->unique_bus_name, g_free);
+ app->running_state->unique_bus_name = g_strdup (unique_bus_name);
+ actions = g_dbus_action_group_get (app->running_state->session, unique_bus_name, application_object_path);
+ gtk_action_muxer_insert (app->running_state->muxer, "app", G_ACTION_GROUP (actions));
+ g_object_unref (actions);
+ }
+}
+
+static void
+unref_running_state (ShellAppRunningState *state)
+{
+ MetaDisplay *display = shell_global_get_display (shell_global_get ());
+ MetaWorkspaceManager *workspace_manager =
+ meta_display_get_workspace_manager (display);
+
+ g_assert (state->refcount > 0);
+
+ state->refcount--;
+ if (state->refcount > 0)
+ return;
+
+ g_clear_signal_handler (&state->workspace_switch_id, workspace_manager);
+
+ g_clear_object (&state->application_proxy);
+
+ if (state->cancellable != NULL)
+ {
+ g_cancellable_cancel (state->cancellable);
+ g_clear_object (&state->cancellable);
+ }
+
+ g_clear_object (&state->muxer);
+ g_clear_object (&state->session);
+ g_clear_pointer (&state->unique_bus_name, g_free);
+
+ g_free (state);
+}
+
+/**
+ * shell_app_compare_by_name:
+ * @app: One app
+ * @other: The other app
+ *
+ * Order two applications by name.
+ *
+ * Returns: -1, 0, or 1; suitable for use as a comparison function
+ * for e.g. g_slist_sort()
+ */
+int
+shell_app_compare_by_name (ShellApp *app, ShellApp *other)
+{
+ return strcmp (app->name_collation_key, other->name_collation_key);
+}
+
+static void
+shell_app_init (ShellApp *self)
+{
+ self->state = SHELL_APP_STATE_STOPPED;
+ self->started_on_workspace = -1;
+}
+
+static void
+shell_app_dispose (GObject *object)
+{
+ ShellApp *app = SHELL_APP (object);
+
+ g_clear_object (&app->info);
+ g_clear_object (&app->fallback_icon);
+
+ while (app->running_state)
+ _shell_app_remove_window (app, app->running_state->windows->data);
+
+ /* We should have been transitioned when we removed all of our windows */
+ g_assert (app->state == SHELL_APP_STATE_STOPPED);
+ g_assert (app->running_state == NULL);
+
+ G_OBJECT_CLASS(shell_app_parent_class)->dispose (object);
+}
+
+static void
+shell_app_finalize (GObject *object)
+{
+ ShellApp *app = SHELL_APP (object);
+
+ g_free (app->window_id_string);
+
+ g_free (app->name_collation_key);
+
+ G_OBJECT_CLASS(shell_app_parent_class)->finalize (object);
+}
+
+static void
+shell_app_class_init(ShellAppClass *klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+ gobject_class->get_property = shell_app_get_property;
+ gobject_class->set_property = shell_app_set_property;
+ gobject_class->dispose = shell_app_dispose;
+ gobject_class->finalize = shell_app_finalize;
+
+ shell_app_signals[WINDOWS_CHANGED] = g_signal_new ("windows-changed",
+ SHELL_TYPE_APP,
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL, NULL,
+ G_TYPE_NONE, 0);
+
+ /**
+ * ShellApp:state:
+ *
+ * The high-level state of the application, effectively whether it's
+ * running or not, or transitioning between those states.
+ */
+ props[PROP_STATE] =
+ g_param_spec_enum ("state",
+ "State",
+ "Application state",
+ SHELL_TYPE_APP_STATE,
+ SHELL_APP_STATE_STOPPED,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+ /**
+ * ShellApp:busy:
+ *
+ * Whether the application has marked itself as busy.
+ */
+ props[PROP_BUSY] =
+ g_param_spec_boolean ("busy",
+ "Busy",
+ "Busy state",
+ FALSE,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+ /**
+ * ShellApp:id:
+ *
+ * The id of this application (a desktop filename, or a special string
+ * like window:0xabcd1234)
+ */
+ props[PROP_ID] =
+ g_param_spec_string ("id",
+ "Application id",
+ "The desktop file id of this ShellApp",
+ NULL,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+ /**
+ * ShellApp:icon:
+ *
+ * The #GIcon representing this ShellApp
+ */
+ props[PROP_ICON] =
+ g_param_spec_object ("icon",
+ "GIcon",
+ "The GIcon representing this app",
+ G_TYPE_ICON,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+ /**
+ * ShellApp:action-group:
+ *
+ * The #GDBusActionGroup associated with this ShellApp, if any. See the
+ * documentation of #GApplication and #GActionGroup for details.
+ */
+ props[PROP_ACTION_GROUP] =
+ g_param_spec_object ("action-group",
+ "Application Action Group",
+ "The action group exported by the remote application",
+ G_TYPE_ACTION_GROUP,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+ /**
+ * ShellApp:app-info:
+ *
+ * The #GDesktopAppInfo associated with this ShellApp, if any.
+ */
+ props[PROP_APP_INFO] =
+ g_param_spec_object ("app-info",
+ "DesktopAppInfo",
+ "The DesktopAppInfo associated with this app",
+ G_TYPE_DESKTOP_APP_INFO,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
+
+ g_object_class_install_properties (gobject_class, N_PROPS, props);
+}
diff --git a/src/shell-app.h b/src/shell-app.h
new file mode 100644
index 0000000..bf6f45e
--- /dev/null
+++ b/src/shell-app.h
@@ -0,0 +1,83 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+#ifndef __SHELL_APP_H__
+#define __SHELL_APP_H__
+
+#include <clutter/clutter.h>
+#include <gio/gio.h>
+#include <gio/gdesktopappinfo.h>
+#include <meta/window.h>
+
+G_BEGIN_DECLS
+
+#define SHELL_TYPE_APP (shell_app_get_type ())
+G_DECLARE_FINAL_TYPE (ShellApp, shell_app, SHELL, APP, GObject)
+
+typedef enum {
+ SHELL_APP_STATE_STOPPED,
+ SHELL_APP_STATE_STARTING,
+ SHELL_APP_STATE_RUNNING
+} ShellAppState;
+
+typedef enum {
+ SHELL_APP_LAUNCH_GPU_APP_PREF = 0,
+ SHELL_APP_LAUNCH_GPU_DISCRETE,
+ SHELL_APP_LAUNCH_GPU_DEFAULT
+} ShellAppLaunchGpu;
+
+const char *shell_app_get_id (ShellApp *app);
+
+GDesktopAppInfo *shell_app_get_app_info (ShellApp *app);
+
+ClutterActor *shell_app_create_icon_texture (ShellApp *app, int size);
+GIcon *shell_app_get_icon (ShellApp *app);
+const char *shell_app_get_name (ShellApp *app);
+const char *shell_app_get_description (ShellApp *app);
+gboolean shell_app_is_window_backed (ShellApp *app);
+
+void shell_app_activate_window (ShellApp *app, MetaWindow *window, guint32 timestamp);
+
+void shell_app_activate (ShellApp *app);
+
+void shell_app_activate_full (ShellApp *app,
+ int workspace,
+ guint32 timestamp);
+
+void shell_app_open_new_window (ShellApp *app,
+ int workspace);
+gboolean shell_app_can_open_new_window (ShellApp *app);
+
+ShellAppState shell_app_get_state (ShellApp *app);
+
+gboolean shell_app_request_quit (ShellApp *app);
+
+guint shell_app_get_n_windows (ShellApp *app);
+
+GSList *shell_app_get_windows (ShellApp *app);
+
+GSList *shell_app_get_pids (ShellApp *app);
+
+gboolean shell_app_is_on_workspace (ShellApp *app, MetaWorkspace *workspace);
+
+gboolean shell_app_launch (ShellApp *app,
+ guint timestamp,
+ int workspace,
+ ShellAppLaunchGpu gpu_pref,
+ GError **error);
+
+void shell_app_launch_action (ShellApp *app,
+ const char *action_name,
+ guint timestamp,
+ int workspace);
+
+int shell_app_compare_by_name (ShellApp *app, ShellApp *other);
+
+int shell_app_compare (ShellApp *app, ShellApp *other);
+
+void shell_app_update_window_actions (ShellApp *app, MetaWindow *window);
+void shell_app_update_app_actions (ShellApp *app, MetaWindow *window);
+
+gboolean shell_app_get_busy (ShellApp *app);
+
+G_END_DECLS
+
+#endif /* __SHELL_APP_H__ */
diff --git a/src/shell-blur-effect.c b/src/shell-blur-effect.c
new file mode 100644
index 0000000..0d4bb45
--- /dev/null
+++ b/src/shell-blur-effect.c
@@ -0,0 +1,907 @@
+/* shell-blur-effect.c
+ *
+ * Copyright 2019 Georges Basile Stavracas Neto <georges.stavracas@gmail.com>
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#include "shell-blur-effect.h"
+
+#include "shell-enum-types.h"
+
+/**
+ * SECTION:shell-blur-effect
+ * @short_description: Blur effect for actors
+ *
+ * #ShellBlurEffect is a blur implementation based on Clutter. It also has
+ * an optional brightness property.
+ *
+ * # Modes
+ *
+ * #ShellBlurEffect can work in @SHELL_BLUR_MODE_BACKGROUND and @SHELL_BLUR_MODE_ACTOR
+ * modes. The actor mode blurs the actor itself, and all of its children. The
+ * background mode blurs the pixels beneath the actor, but not the actor itself.
+ *
+ * @SHELL_BLUR_MODE_BACKGROUND can be computationally expensive, since the contents
+ * beneath the actor cannot be cached, so beware of the performance implications
+ * of using this blur mode.
+ */
+
+static const gchar *brightness_glsl_declarations =
+"uniform float brightness; \n";
+
+static const gchar *brightness_glsl =
+" cogl_color_out.rgb *= brightness; \n";
+
+#define MIN_DOWNSCALE_SIZE 256.f
+#define MAX_SIGMA 6.f
+
+typedef enum
+{
+ ACTOR_PAINTED = 1 << 0,
+ BLUR_APPLIED = 1 << 1,
+} CacheFlags;
+
+typedef struct
+{
+ CoglFramebuffer *framebuffer;
+ CoglPipeline *pipeline;
+ CoglTexture *texture;
+} FramebufferData;
+
+struct _ShellBlurEffect
+{
+ ClutterEffect parent_instance;
+
+ ClutterActor *actor;
+
+ unsigned int tex_width;
+ unsigned int tex_height;
+
+ /* The cached contents */
+ FramebufferData actor_fb;
+ CacheFlags cache_flags;
+
+ FramebufferData background_fb;
+ FramebufferData brightness_fb;
+ int brightness_uniform;
+
+ ShellBlurMode mode;
+ float downscale_factor;
+ float brightness;
+ int sigma;
+};
+
+G_DEFINE_TYPE (ShellBlurEffect, shell_blur_effect, CLUTTER_TYPE_EFFECT)
+
+enum {
+ PROP_0,
+ PROP_SIGMA,
+ PROP_BRIGHTNESS,
+ PROP_MODE,
+ N_PROPS
+};
+
+static GParamSpec *properties [N_PROPS] = { NULL, };
+
+static CoglPipeline*
+create_base_pipeline (void)
+{
+ static CoglPipeline *base_pipeline = NULL;
+
+ if (G_UNLIKELY (base_pipeline == NULL))
+ {
+ CoglContext *ctx =
+ clutter_backend_get_cogl_context (clutter_get_default_backend ());
+
+ base_pipeline = cogl_pipeline_new (ctx);
+ cogl_pipeline_set_layer_null_texture (base_pipeline, 0);
+ cogl_pipeline_set_layer_filters (base_pipeline,
+ 0,
+ COGL_PIPELINE_FILTER_LINEAR,
+ COGL_PIPELINE_FILTER_LINEAR);
+ cogl_pipeline_set_layer_wrap_mode (base_pipeline,
+ 0,
+ COGL_PIPELINE_WRAP_MODE_CLAMP_TO_EDGE);
+ }
+
+ return cogl_pipeline_copy (base_pipeline);
+}
+
+static CoglPipeline*
+create_brightness_pipeline (void)
+{
+ static CoglPipeline *brightness_pipeline = NULL;
+
+ if (G_UNLIKELY (brightness_pipeline == NULL))
+ {
+ CoglSnippet *snippet;
+
+ brightness_pipeline = create_base_pipeline ();
+
+ snippet = cogl_snippet_new (COGL_SNIPPET_HOOK_FRAGMENT,
+ brightness_glsl_declarations,
+ brightness_glsl);
+ cogl_pipeline_add_snippet (brightness_pipeline, snippet);
+ cogl_object_unref (snippet);
+ }
+
+ return cogl_pipeline_copy (brightness_pipeline);
+}
+
+
+static void
+update_brightness (ShellBlurEffect *self,
+ uint8_t paint_opacity)
+{
+ cogl_pipeline_set_color4ub (self->brightness_fb.pipeline,
+ paint_opacity,
+ paint_opacity,
+ paint_opacity,
+ paint_opacity);
+
+ if (self->brightness_uniform > -1)
+ {
+ cogl_pipeline_set_uniform_1f (self->brightness_fb.pipeline,
+ self->brightness_uniform,
+ self->brightness);
+ }
+}
+
+static void
+setup_projection_matrix (CoglFramebuffer *framebuffer,
+ float width,
+ float height)
+{
+ graphene_matrix_t projection;
+
+ graphene_matrix_init_translate (&projection,
+ &GRAPHENE_POINT3D_INIT (-width / 2.0,
+ -height / 2.0,
+ 0.f));
+ graphene_matrix_scale (&projection, 2.0 / width, -2.0 / height, 1.f);
+
+ cogl_framebuffer_set_projection_matrix (framebuffer, &projection);
+}
+
+static gboolean
+update_fbo (FramebufferData *data,
+ unsigned int width,
+ unsigned int height,
+ float downscale_factor)
+{
+ CoglContext *ctx =
+ clutter_backend_get_cogl_context (clutter_get_default_backend ());
+
+ g_clear_pointer (&data->texture, cogl_object_unref);
+ g_clear_object (&data->framebuffer);
+
+ float new_width = floorf (width / downscale_factor);
+ float new_height = floorf (height / downscale_factor);
+
+ data->texture = cogl_texture_2d_new_with_size (ctx, new_width, new_height);
+ if (!data->texture)
+ return FALSE;
+
+ cogl_pipeline_set_layer_texture (data->pipeline, 0, data->texture);
+
+ data->framebuffer =
+ COGL_FRAMEBUFFER (cogl_offscreen_new_with_texture (data->texture));
+ if (!data->framebuffer)
+ {
+ g_warning ("%s: Unable to create an Offscreen buffer", G_STRLOC);
+ return FALSE;
+ }
+
+ setup_projection_matrix (data->framebuffer, new_width, new_height);
+
+ return TRUE;
+}
+
+static gboolean
+update_actor_fbo (ShellBlurEffect *self,
+ unsigned int width,
+ unsigned int height,
+ float downscale_factor)
+{
+ if (self->tex_width == width &&
+ self->tex_height == height &&
+ self->downscale_factor == downscale_factor &&
+ self->actor_fb.framebuffer)
+ {
+ return TRUE;
+ }
+
+ self->cache_flags &= ~ACTOR_PAINTED;
+
+ return update_fbo (&self->actor_fb, width, height, downscale_factor);
+}
+
+static gboolean
+update_brightness_fbo (ShellBlurEffect *self,
+ unsigned int width,
+ unsigned int height,
+ float downscale_factor)
+{
+ if (self->tex_width == width &&
+ self->tex_height == height &&
+ self->downscale_factor == downscale_factor &&
+ self->brightness_fb.framebuffer)
+ {
+ return TRUE;
+ }
+
+ return update_fbo (&self->brightness_fb,
+ width, height,
+ downscale_factor);
+}
+
+static gboolean
+update_background_fbo (ShellBlurEffect *self,
+ unsigned int width,
+ unsigned int height)
+{
+ if (self->tex_width == width &&
+ self->tex_height == height &&
+ self->background_fb.framebuffer)
+ {
+ return TRUE;
+ }
+
+ return update_fbo (&self->background_fb, width, height, 1.0);
+}
+
+static void
+clear_framebuffer_data (FramebufferData *fb_data)
+{
+ g_clear_pointer (&fb_data->texture, cogl_object_unref);
+ g_clear_object (&fb_data->framebuffer);
+}
+
+static float
+calculate_downscale_factor (float width,
+ float height,
+ float sigma)
+{
+ float downscale_factor = 1.0;
+ float scaled_width = width;
+ float scaled_height = height;
+ float scaled_sigma = sigma;
+
+ /* This is the algorithm used by Firefox; keep downscaling until either the
+ * blur radius is lower than the threshold, or the downscaled texture is too
+ * small.
+ */
+ while (scaled_sigma > MAX_SIGMA &&
+ scaled_width > MIN_DOWNSCALE_SIZE &&
+ scaled_height > MIN_DOWNSCALE_SIZE)
+ {
+ downscale_factor *= 2.f;
+
+ scaled_width = width / downscale_factor;
+ scaled_height = height / downscale_factor;
+ scaled_sigma = sigma / downscale_factor;
+ }
+
+ return downscale_factor;
+}
+
+static void
+shell_blur_effect_set_actor (ClutterActorMeta *meta,
+ ClutterActor *actor)
+{
+ ShellBlurEffect *self = SHELL_BLUR_EFFECT (meta);
+ ClutterActorMetaClass *meta_class;
+
+ meta_class = CLUTTER_ACTOR_META_CLASS (shell_blur_effect_parent_class);
+ meta_class->set_actor (meta, actor);
+
+ /* clear out the previous state */
+ clear_framebuffer_data (&self->actor_fb);
+ clear_framebuffer_data (&self->background_fb);
+ clear_framebuffer_data (&self->brightness_fb);
+
+ /* we keep a back pointer here, to avoid going through the ActorMeta */
+ self->actor = clutter_actor_meta_get_actor (meta);
+}
+
+static void
+update_actor_box (ShellBlurEffect *self,
+ ClutterPaintContext *paint_context,
+ ClutterActorBox *source_actor_box)
+{
+ ClutterStageView *stage_view;
+ float box_scale_factor = 1.0f;
+ float origin_x, origin_y;
+ float width, height;
+
+ switch (self->mode)
+ {
+ case SHELL_BLUR_MODE_ACTOR:
+ clutter_actor_get_allocation_box (self->actor, source_actor_box);
+ break;
+
+ case SHELL_BLUR_MODE_BACKGROUND:
+ stage_view = clutter_paint_context_get_stage_view (paint_context);
+
+ clutter_actor_get_transformed_position (self->actor, &origin_x, &origin_y);
+ clutter_actor_get_transformed_size (self->actor, &width, &height);
+
+ if (stage_view)
+ {
+ cairo_rectangle_int_t stage_view_layout;
+
+ box_scale_factor = clutter_stage_view_get_scale (stage_view);
+ clutter_stage_view_get_layout (stage_view, &stage_view_layout);
+
+ origin_x -= stage_view_layout.x;
+ origin_y -= stage_view_layout.y;
+ }
+ else
+ {
+ /* If we're drawing off stage, just assume scale = 1, this won't work
+ * with stage-view scaling though.
+ */
+ }
+
+ clutter_actor_box_set_origin (source_actor_box, origin_x, origin_y);
+ clutter_actor_box_set_size (source_actor_box, width, height);
+
+ clutter_actor_box_scale (source_actor_box, box_scale_factor);
+ break;
+ }
+
+ clutter_actor_box_clamp_to_pixel (source_actor_box);
+}
+
+static void
+add_blurred_pipeline (ShellBlurEffect *self,
+ ClutterPaintNode *node,
+ uint8_t paint_opacity)
+{
+ g_autoptr (ClutterPaintNode) pipeline_node = NULL;
+ float width, height;
+
+ /* Use the untransformed actor size here, since the framebuffer itself already
+ * has the actor transform matrix applied.
+ */
+ clutter_actor_get_size (self->actor, &width, &height);
+
+ update_brightness (self, paint_opacity);
+
+ pipeline_node = clutter_pipeline_node_new (self->brightness_fb.pipeline);
+ clutter_paint_node_set_static_name (pipeline_node, "ShellBlurEffect (final)");
+ clutter_paint_node_add_child (node, pipeline_node);
+
+ clutter_paint_node_add_rectangle (pipeline_node,
+ &(ClutterActorBox) {
+ 0.f, 0.f,
+ width,
+ height,
+ });
+}
+
+static ClutterPaintNode *
+create_blur_nodes (ShellBlurEffect *self,
+ ClutterPaintNode *node,
+ uint8_t paint_opacity)
+{
+ g_autoptr (ClutterPaintNode) brightness_node = NULL;
+ g_autoptr (ClutterPaintNode) blur_node = NULL;
+ float width;
+ float height;
+
+ clutter_actor_get_size (self->actor, &width, &height);
+
+ update_brightness (self, paint_opacity);
+ brightness_node = clutter_layer_node_new_to_framebuffer (self->brightness_fb.framebuffer,
+ self->brightness_fb.pipeline);
+ clutter_paint_node_set_static_name (brightness_node, "ShellBlurEffect (brightness)");
+ clutter_paint_node_add_child (node, brightness_node);
+ clutter_paint_node_add_rectangle (brightness_node,
+ &(ClutterActorBox) {
+ 0.f, 0.f,
+ width, height,
+ });
+
+ blur_node = clutter_blur_node_new (self->tex_width / self->downscale_factor,
+ self->tex_height / self->downscale_factor,
+ self->sigma / self->downscale_factor);
+ clutter_paint_node_set_static_name (blur_node, "ShellBlurEffect (blur)");
+ clutter_paint_node_add_child (brightness_node, blur_node);
+ clutter_paint_node_add_rectangle (blur_node,
+ &(ClutterActorBox) {
+ 0.f, 0.f,
+ cogl_texture_get_width (self->brightness_fb.texture),
+ cogl_texture_get_height (self->brightness_fb.texture),
+ });
+
+ self->cache_flags |= BLUR_APPLIED;
+
+ return g_steal_pointer (&blur_node);
+}
+
+static void
+paint_background (ShellBlurEffect *self,
+ ClutterPaintNode *node,
+ ClutterPaintContext *paint_context,
+ ClutterActorBox *source_actor_box)
+{
+ g_autoptr (ClutterPaintNode) background_node = NULL;
+ g_autoptr (ClutterPaintNode) blit_node = NULL;
+ CoglFramebuffer *src;
+ float transformed_x;
+ float transformed_y;
+ float transformed_width;
+ float transformed_height;
+
+ clutter_actor_box_get_origin (source_actor_box,
+ &transformed_x,
+ &transformed_y);
+ clutter_actor_box_get_size (source_actor_box,
+ &transformed_width,
+ &transformed_height);
+
+ /* Background layer node */
+ background_node =
+ clutter_layer_node_new_to_framebuffer (self->background_fb.framebuffer,
+ self->background_fb.pipeline);
+ clutter_paint_node_set_static_name (background_node, "ShellBlurEffect (background)");
+ clutter_paint_node_add_child (node, background_node);
+ clutter_paint_node_add_rectangle (background_node,
+ &(ClutterActorBox) {
+ 0.f, 0.f,
+ self->tex_width / self->downscale_factor,
+ self->tex_height / self->downscale_factor,
+ });
+
+ /* Blit node */
+ src = clutter_paint_context_get_framebuffer (paint_context);
+ blit_node = clutter_blit_node_new (src);
+ clutter_paint_node_set_static_name (blit_node, "ShellBlurEffect (blit)");
+ clutter_paint_node_add_child (background_node, blit_node);
+ clutter_blit_node_add_blit_rectangle (CLUTTER_BLIT_NODE (blit_node),
+ transformed_x,
+ transformed_y,
+ 0, 0,
+ transformed_width,
+ transformed_height);
+}
+
+static gboolean
+update_framebuffers (ShellBlurEffect *self,
+ ClutterPaintContext *paint_context,
+ ClutterActorBox *source_actor_box)
+{
+ gboolean updated = FALSE;
+ float downscale_factor;
+ float height = -1;
+ float width = -1;
+
+ clutter_actor_box_get_size (source_actor_box, &width, &height);
+
+ downscale_factor = calculate_downscale_factor (width, height, self->sigma);
+
+ updated = update_actor_fbo (self, width, height, downscale_factor) &&
+ update_brightness_fbo (self, width, height, downscale_factor);
+
+ if (self->mode == SHELL_BLUR_MODE_BACKGROUND)
+ updated = updated && update_background_fbo (self, width, height);
+
+ self->tex_width = width;
+ self->tex_height = height;
+ self->downscale_factor = downscale_factor;
+
+ return updated;
+}
+
+static void
+add_actor_node (ShellBlurEffect *self,
+ ClutterPaintNode *node,
+ int opacity)
+{
+ g_autoptr (ClutterPaintNode) actor_node = NULL;
+
+ actor_node = clutter_actor_node_new (self->actor, opacity);
+ clutter_paint_node_add_child (node, actor_node);
+}
+
+static void
+paint_actor_offscreen (ShellBlurEffect *self,
+ ClutterPaintNode *node,
+ ClutterEffectPaintFlags flags)
+{
+ gboolean actor_dirty;
+
+ actor_dirty = (flags & CLUTTER_EFFECT_PAINT_ACTOR_DIRTY) != 0;
+
+ /* The actor offscreen framebuffer is updated already */
+ if (actor_dirty || !(self->cache_flags & ACTOR_PAINTED))
+ {
+ g_autoptr (ClutterPaintNode) transform_node = NULL;
+ g_autoptr (ClutterPaintNode) layer_node = NULL;
+ graphene_matrix_t transform;
+
+ /* Layer node */
+ layer_node = clutter_layer_node_new_to_framebuffer (self->actor_fb.framebuffer,
+ self->actor_fb.pipeline);
+ clutter_paint_node_set_static_name (layer_node, "ShellBlurEffect (actor offscreen)");
+ clutter_paint_node_add_child (node, layer_node);
+ clutter_paint_node_add_rectangle (layer_node,
+ &(ClutterActorBox) {
+ 0.f, 0.f,
+ self->tex_width / self->downscale_factor,
+ self->tex_height / self->downscale_factor,
+ });
+
+ /* Transform node */
+ graphene_matrix_init_scale (&transform,
+ 1.f / self->downscale_factor,
+ 1.f / self->downscale_factor,
+ 1.f);
+ transform_node = clutter_transform_node_new (&transform);
+ clutter_paint_node_set_static_name (transform_node, "ShellBlurEffect (downscale)");
+ clutter_paint_node_add_child (layer_node, transform_node);
+
+ /* Actor node */
+ add_actor_node (self, transform_node, 255);
+
+ self->cache_flags |= ACTOR_PAINTED;
+ }
+ else
+ {
+ g_autoptr (ClutterPaintNode) pipeline_node = NULL;
+
+ pipeline_node = clutter_pipeline_node_new (self->actor_fb.pipeline);
+ clutter_paint_node_set_static_name (pipeline_node,
+ "ShellBlurEffect (actor texture)");
+ clutter_paint_node_add_child (node, pipeline_node);
+ clutter_paint_node_add_rectangle (pipeline_node,
+ &(ClutterActorBox) {
+ 0.f, 0.f,
+ self->tex_width / self->downscale_factor,
+ self->tex_height / self->downscale_factor,
+ });
+ }
+}
+
+static gboolean
+needs_repaint (ShellBlurEffect *self,
+ ClutterEffectPaintFlags flags)
+{
+ gboolean actor_cached;
+ gboolean blur_cached;
+ gboolean actor_dirty;
+
+ actor_dirty = (flags & CLUTTER_EFFECT_PAINT_ACTOR_DIRTY) != 0;
+ blur_cached = (self->cache_flags & BLUR_APPLIED) != 0;
+ actor_cached = (self->cache_flags & ACTOR_PAINTED) != 0;
+
+ switch (self->mode)
+ {
+ case SHELL_BLUR_MODE_ACTOR:
+ return actor_dirty || !blur_cached || !actor_cached;
+
+ case SHELL_BLUR_MODE_BACKGROUND:
+ return TRUE;
+ }
+
+ return TRUE;
+}
+
+static void
+shell_blur_effect_paint_node (ClutterEffect *effect,
+ ClutterPaintNode *node,
+ ClutterPaintContext *paint_context,
+ ClutterEffectPaintFlags flags)
+{
+ ShellBlurEffect *self = SHELL_BLUR_EFFECT (effect);
+ uint8_t paint_opacity;
+
+ g_assert (self->actor != NULL);
+
+ if (self->sigma > 0)
+ {
+ g_autoptr (ClutterPaintNode) blur_node = NULL;
+
+ switch (self->mode)
+ {
+ case SHELL_BLUR_MODE_ACTOR:
+ paint_opacity = clutter_actor_get_paint_opacity (self->actor);
+ break;
+
+ case SHELL_BLUR_MODE_BACKGROUND:
+ paint_opacity = 255;
+ break;
+
+ default:
+ g_assert_not_reached();
+ break;
+ }
+
+ if (needs_repaint (self, flags))
+ {
+ ClutterActorBox source_actor_box;
+
+ update_actor_box (self, paint_context, &source_actor_box);
+
+ /* Failing to create or update the offscreen framebuffers prevents
+ * the entire effect to be applied.
+ */
+ if (!update_framebuffers (self, paint_context, &source_actor_box))
+ goto fail;
+
+ blur_node = create_blur_nodes (self, node, paint_opacity);
+
+ switch (self->mode)
+ {
+ case SHELL_BLUR_MODE_ACTOR:
+ paint_actor_offscreen (self, blur_node, flags);
+ break;
+
+ case SHELL_BLUR_MODE_BACKGROUND:
+ paint_background (self, blur_node, paint_context, &source_actor_box);
+ break;
+ }
+ }
+ else
+ {
+ /* Use the cached pipeline if no repaint is needed */
+ add_blurred_pipeline (self, node, paint_opacity);
+ }
+
+ /* Background blur needs to paint the actor after painting the blurred
+ * background.
+ */
+ switch (self->mode)
+ {
+ case SHELL_BLUR_MODE_ACTOR:
+ break;
+
+ case SHELL_BLUR_MODE_BACKGROUND:
+ add_actor_node (self, node, -1);
+ break;
+ }
+
+ return;
+ }
+
+fail:
+ /* When no blur is applied, or the offscreen framebuffers
+ * couldn't be created, fallback to simply painting the actor.
+ */
+ add_actor_node (self, node, -1);
+}
+
+static void
+shell_blur_effect_finalize (GObject *object)
+{
+ ShellBlurEffect *self = (ShellBlurEffect *)object;
+
+ clear_framebuffer_data (&self->actor_fb);
+ clear_framebuffer_data (&self->background_fb);
+ clear_framebuffer_data (&self->brightness_fb);
+
+ g_clear_pointer (&self->actor_fb.pipeline, cogl_object_unref);
+ g_clear_pointer (&self->background_fb.pipeline, cogl_object_unref);
+ g_clear_pointer (&self->brightness_fb.pipeline, cogl_object_unref);
+
+ G_OBJECT_CLASS (shell_blur_effect_parent_class)->finalize (object);
+}
+
+static void
+shell_blur_effect_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ ShellBlurEffect *self = SHELL_BLUR_EFFECT (object);
+
+ switch (prop_id)
+ {
+ case PROP_SIGMA:
+ g_value_set_int (value, self->sigma);
+ break;
+
+ case PROP_BRIGHTNESS:
+ g_value_set_float (value, self->brightness);
+ break;
+
+ case PROP_MODE:
+ g_value_set_enum (value, self->mode);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+shell_blur_effect_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ ShellBlurEffect *self = SHELL_BLUR_EFFECT (object);
+
+ switch (prop_id)
+ {
+ case PROP_SIGMA:
+ shell_blur_effect_set_sigma (self, g_value_get_int (value));
+ break;
+
+ case PROP_BRIGHTNESS:
+ shell_blur_effect_set_brightness (self, g_value_get_float (value));
+ break;
+
+ case PROP_MODE:
+ shell_blur_effect_set_mode (self, g_value_get_enum (value));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+shell_blur_effect_class_init (ShellBlurEffectClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ ClutterActorMetaClass *meta_class = CLUTTER_ACTOR_META_CLASS (klass);
+ ClutterEffectClass *effect_class = CLUTTER_EFFECT_CLASS (klass);
+
+ object_class->finalize = shell_blur_effect_finalize;
+ object_class->get_property = shell_blur_effect_get_property;
+ object_class->set_property = shell_blur_effect_set_property;
+
+ meta_class->set_actor = shell_blur_effect_set_actor;
+
+ effect_class->paint_node = shell_blur_effect_paint_node;
+
+ properties[PROP_SIGMA] =
+ g_param_spec_int ("sigma",
+ "Sigma",
+ "Sigma",
+ 0, G_MAXINT, 0,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY);
+
+ properties[PROP_BRIGHTNESS] =
+ g_param_spec_float ("brightness",
+ "Brightness",
+ "Brightness",
+ 0.f, 1.f, 1.f,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY);
+
+ properties[PROP_MODE] =
+ g_param_spec_enum ("mode",
+ "Blur mode",
+ "Blur mode",
+ SHELL_TYPE_BLUR_MODE,
+ SHELL_BLUR_MODE_ACTOR,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY);
+
+ g_object_class_install_properties (object_class, N_PROPS, properties);
+}
+
+static void
+shell_blur_effect_init (ShellBlurEffect *self)
+{
+ self->mode = SHELL_BLUR_MODE_ACTOR;
+ self->sigma = 0;
+ self->brightness = 1.f;
+
+ self->actor_fb.pipeline = create_base_pipeline ();
+ self->background_fb.pipeline = create_base_pipeline ();
+ self->brightness_fb.pipeline = create_brightness_pipeline ();
+ self->brightness_uniform =
+ cogl_pipeline_get_uniform_location (self->brightness_fb.pipeline, "brightness");
+}
+
+ShellBlurEffect *
+shell_blur_effect_new (void)
+{
+ return g_object_new (SHELL_TYPE_BLUR_EFFECT, NULL);
+}
+
+int
+shell_blur_effect_get_sigma (ShellBlurEffect *self)
+{
+ g_return_val_if_fail (SHELL_IS_BLUR_EFFECT (self), -1);
+
+ return self->sigma;
+}
+
+void
+shell_blur_effect_set_sigma (ShellBlurEffect *self,
+ int sigma)
+{
+ g_return_if_fail (SHELL_IS_BLUR_EFFECT (self));
+
+ if (self->sigma == sigma)
+ return;
+
+ self->sigma = sigma;
+ self->cache_flags &= ~BLUR_APPLIED;
+
+ if (self->actor)
+ clutter_effect_queue_repaint (CLUTTER_EFFECT (self));
+
+ g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SIGMA]);
+}
+
+float
+shell_blur_effect_get_brightness (ShellBlurEffect *self)
+{
+ g_return_val_if_fail (SHELL_IS_BLUR_EFFECT (self), FALSE);
+
+ return self->brightness;
+}
+
+void
+shell_blur_effect_set_brightness (ShellBlurEffect *self,
+ float brightness)
+{
+ g_return_if_fail (SHELL_IS_BLUR_EFFECT (self));
+
+ if (self->brightness == brightness)
+ return;
+
+ self->brightness = brightness;
+ self->cache_flags &= ~BLUR_APPLIED;
+
+ if (self->actor)
+ clutter_effect_queue_repaint (CLUTTER_EFFECT (self));
+
+ g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_BRIGHTNESS]);
+}
+
+ShellBlurMode
+shell_blur_effect_get_mode (ShellBlurEffect *self)
+{
+ g_return_val_if_fail (SHELL_IS_BLUR_EFFECT (self), -1);
+
+ return self->mode;
+}
+
+void
+shell_blur_effect_set_mode (ShellBlurEffect *self,
+ ShellBlurMode mode)
+{
+ g_return_if_fail (SHELL_IS_BLUR_EFFECT (self));
+
+ if (self->mode == mode)
+ return;
+
+ self->mode = mode;
+ self->cache_flags &= ~BLUR_APPLIED;
+
+ switch (mode)
+ {
+ case SHELL_BLUR_MODE_ACTOR:
+ clear_framebuffer_data (&self->background_fb);
+ break;
+
+ case SHELL_BLUR_MODE_BACKGROUND:
+ default:
+ /* Do nothing */
+ break;
+ }
+
+ if (self->actor)
+ clutter_effect_queue_repaint (CLUTTER_EFFECT (self));
+
+ g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_MODE]);
+}
diff --git a/src/shell-blur-effect.h b/src/shell-blur-effect.h
new file mode 100644
index 0000000..a7486cc
--- /dev/null
+++ b/src/shell-blur-effect.h
@@ -0,0 +1,57 @@
+/* shell-blur-effect.h
+ *
+ * Copyright 2019 Georges Basile Stavracas Neto <georges.stavracas@gmail.com>
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#pragma once
+
+#include <clutter/clutter.h>
+
+G_BEGIN_DECLS
+
+/**
+ * ShellBlurMode:
+ * @SHELL_BLUR_MODE_ACTOR: blur the actor contents, and its children
+ * @SHELL_BLUR_MODE_BACKGROUND: blur what's beneath the actor
+ *
+ * The mode of blurring of the effect.
+ */
+typedef enum
+{
+ SHELL_BLUR_MODE_ACTOR,
+ SHELL_BLUR_MODE_BACKGROUND,
+} ShellBlurMode;
+
+#define SHELL_TYPE_BLUR_EFFECT (shell_blur_effect_get_type())
+G_DECLARE_FINAL_TYPE (ShellBlurEffect, shell_blur_effect, SHELL, BLUR_EFFECT, ClutterEffect)
+
+ShellBlurEffect *shell_blur_effect_new (void);
+
+int shell_blur_effect_get_sigma (ShellBlurEffect *self);
+void shell_blur_effect_set_sigma (ShellBlurEffect *self,
+ int sigma);
+
+float shell_blur_effect_get_brightness (ShellBlurEffect *self);
+void shell_blur_effect_set_brightness (ShellBlurEffect *self,
+ float brightness);
+
+ShellBlurMode shell_blur_effect_get_mode (ShellBlurEffect *self);
+void shell_blur_effect_set_mode (ShellBlurEffect *self,
+ ShellBlurMode mode);
+
+G_END_DECLS
diff --git a/src/shell-embedded-window-private.h b/src/shell-embedded-window-private.h
new file mode 100644
index 0000000..5714af9
--- /dev/null
+++ b/src/shell-embedded-window-private.h
@@ -0,0 +1,20 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+#ifndef __SHELL_EMBEDDED_WINDOW_PRIVATE_H__
+#define __SHELL_EMBEDDED_WINDOW_PRIVATE_H__
+
+#include "shell-embedded-window.h"
+#include "shell-gtk-embed.h"
+
+void _shell_embedded_window_set_actor (ShellEmbeddedWindow *window,
+ ShellGtkEmbed *embed);
+
+void _shell_embedded_window_allocate (ShellEmbeddedWindow *window,
+ int x,
+ int y,
+ int width,
+ int height);
+
+void _shell_embedded_window_map (ShellEmbeddedWindow *window);
+void _shell_embedded_window_unmap (ShellEmbeddedWindow *window);
+
+#endif /* __SHELL_EMBEDDED_WINDOW_PRIVATE_H__ */
diff --git a/src/shell-embedded-window.c b/src/shell-embedded-window.c
new file mode 100644
index 0000000..8fd6112
--- /dev/null
+++ b/src/shell-embedded-window.c
@@ -0,0 +1,247 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+#include "config.h"
+
+#include <gdk/gdkx.h>
+
+#include "shell-embedded-window-private.h"
+
+/* This type is a subclass of GtkWindow that ties the window to a
+ * ShellGtkEmbed; the resizing logic is bound to the clutter logic.
+ *
+ * The typical usage we might expect is
+ *
+ * - ShellEmbeddedWindow is created and filled with content
+ * - ShellEmbeddedWindow is shown with gtk_widget_show_all()
+ * - ShellGtkEmbed is created for the ShellEmbeddedWindow
+ * - actor is added to a stage
+ *
+ * The way it works is that the GtkWindow is mapped if and only if both:
+ *
+ * - gtk_widget_visible (window) [widget has been shown]
+ * - Actor is mapped [actor and all parents visible, actor in stage]
+ */
+
+enum {
+ PROP_0
+};
+
+typedef struct _ShellEmbeddedWindowPrivate ShellEmbeddedWindowPrivate;
+
+struct _ShellEmbeddedWindowPrivate {
+ ShellGtkEmbed *actor;
+
+ GdkRectangle position;
+};
+
+G_DEFINE_TYPE_WITH_PRIVATE (ShellEmbeddedWindow,
+ shell_embedded_window,
+ GTK_TYPE_WINDOW);
+
+/*
+ * The normal gtk_window_show() starts all of the complicated asynchronous
+ * window resizing code running; we don't want or need any of that.
+ * Bypassing the normal code does mean that the extra geometry management
+ * available on GtkWindow: gridding, maximum sizes, etc, is ignored; we
+ * don't really want that anyways - we just want a way of embedding a
+ * GtkWidget into a Clutter stage.
+ */
+static void
+shell_embedded_window_show (GtkWidget *widget)
+{
+ ShellEmbeddedWindow *window = SHELL_EMBEDDED_WINDOW (widget);
+ ShellEmbeddedWindowPrivate *priv;
+ GtkWidgetClass *widget_class;
+
+ priv = shell_embedded_window_get_instance_private (window);
+
+ /* Skip GtkWindow, but run the default GtkWidget handling which
+ * marks the widget visible */
+ widget_class = g_type_class_peek (GTK_TYPE_WIDGET);
+ widget_class->show (widget);
+
+ if (priv->actor)
+ {
+ /* Size is 0x0 if the GtkWindow is not shown */
+ clutter_actor_queue_relayout (CLUTTER_ACTOR (priv->actor));
+
+ if (clutter_actor_is_realized (CLUTTER_ACTOR (priv->actor)))
+ gtk_widget_map (widget);
+ }
+}
+
+static void
+shell_embedded_window_hide (GtkWidget *widget)
+{
+ ShellEmbeddedWindow *window = SHELL_EMBEDDED_WINDOW (widget);
+ ShellEmbeddedWindowPrivate *priv;
+
+ priv = shell_embedded_window_get_instance_private (window);
+
+ if (priv->actor)
+ clutter_actor_queue_relayout (CLUTTER_ACTOR (priv->actor));
+
+ GTK_WIDGET_CLASS (shell_embedded_window_parent_class)->hide (widget);
+}
+
+static gboolean
+shell_embedded_window_configure_event (GtkWidget *widget,
+ GdkEventConfigure *event)
+{
+ /* Normally a configure event coming back from X triggers the
+ * resizing logic inside GtkWindow; we just ignore them
+ * since we are handling the resizing logic separately.
+ */
+ return FALSE;
+}
+
+static void
+shell_embedded_window_check_resize (GtkContainer *container)
+{
+ ShellEmbeddedWindow *window = SHELL_EMBEDDED_WINDOW (container);
+ ShellEmbeddedWindowPrivate *priv;
+
+ priv = shell_embedded_window_get_instance_private (window);
+
+ /* Check resize is called when a resize is queued on something
+ * inside the GtkWindow; we need to make sure that in response
+ * to this gtk_widget_size_request() and then
+ * gtk_widget_size_allocate() are called; we defer to the Clutter
+ * logic and assume it will do the right thing.
+ */
+ if (priv->actor)
+ clutter_actor_queue_relayout (CLUTTER_ACTOR (priv->actor));
+}
+
+static GObject *
+shell_embedded_window_constructor (GType gtype,
+ guint n_properties,
+ GObjectConstructParam *properties)
+{
+ GObject *object;
+ GObjectClass *parent_class;
+
+ parent_class = G_OBJECT_CLASS (shell_embedded_window_parent_class);
+ object = parent_class->constructor (gtype, n_properties, properties);
+
+ /* Setting the resize mode to immediate means that calling queue_resize()
+ * on a widget within the window will immediately call check_resize()
+ * to be called, instead of having it queued to an idle. From our perspective,
+ * this is ideal since we just are going to queue a resize to Clutter's
+ * idle resize anyways.
+ */
+ g_object_set (object,
+ "app-paintable", TRUE,
+ "resize-mode", GTK_RESIZE_IMMEDIATE,
+ "type", GTK_WINDOW_POPUP,
+ NULL);
+
+ return object;
+}
+
+static void
+shell_embedded_window_class_init (ShellEmbeddedWindowClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+ GtkContainerClass *container_class = GTK_CONTAINER_CLASS (klass);
+
+ object_class->constructor = shell_embedded_window_constructor;
+
+ widget_class->show = shell_embedded_window_show;
+ widget_class->hide = shell_embedded_window_hide;
+ widget_class->configure_event = shell_embedded_window_configure_event;
+
+ container_class->check_resize = shell_embedded_window_check_resize;
+}
+
+static void
+shell_embedded_window_init (ShellEmbeddedWindow *window)
+{
+}
+
+/*
+ * Private routines called by ShellGtkEmbed
+ */
+
+void
+_shell_embedded_window_set_actor (ShellEmbeddedWindow *window,
+ ShellGtkEmbed *actor)
+
+{
+ ShellEmbeddedWindowPrivate *priv;
+
+ g_return_if_fail (SHELL_IS_EMBEDDED_WINDOW (window));
+
+ priv = shell_embedded_window_get_instance_private (window);
+ priv->actor = actor;
+
+ if (actor &&
+ clutter_actor_is_mapped (CLUTTER_ACTOR (actor)) &&
+ gtk_widget_get_visible (GTK_WIDGET (window)))
+ gtk_widget_map (GTK_WIDGET (window));
+}
+
+void
+_shell_embedded_window_allocate (ShellEmbeddedWindow *window,
+ int x,
+ int y,
+ int width,
+ int height)
+{
+ ShellEmbeddedWindowPrivate *priv;
+ GtkAllocation allocation;
+
+ g_return_if_fail (SHELL_IS_EMBEDDED_WINDOW (window));
+
+ priv = shell_embedded_window_get_instance_private (window);
+
+ if (priv->position.x == x &&
+ priv->position.y == y &&
+ priv->position.width == width &&
+ priv->position.height == height)
+ return;
+
+ priv->position.x = x;
+ priv->position.y = y;
+ priv->position.width = width;
+ priv->position.height = height;
+
+ if (gtk_widget_get_realized (GTK_WIDGET (window)))
+ gdk_window_move_resize (gtk_widget_get_window (GTK_WIDGET (window)),
+ x, y, width, height);
+
+ allocation.x = 0;
+ allocation.y = 0;
+ allocation.width = width;
+ allocation.height = height;
+
+ gtk_widget_size_allocate (GTK_WIDGET (window), &allocation);
+}
+
+void
+_shell_embedded_window_map (ShellEmbeddedWindow *window)
+{
+ g_return_if_fail (SHELL_IS_EMBEDDED_WINDOW (window));
+
+ if (gtk_widget_get_visible (GTK_WIDGET (window)))
+ gtk_widget_map (GTK_WIDGET (window));
+}
+
+void
+_shell_embedded_window_unmap (ShellEmbeddedWindow *window)
+{
+ g_return_if_fail (SHELL_IS_EMBEDDED_WINDOW (window));
+
+ gtk_widget_unmap (GTK_WIDGET (window));
+}
+
+/*
+ * Public API
+ */
+GtkWidget *
+shell_embedded_window_new (void)
+{
+ return g_object_new (SHELL_TYPE_EMBEDDED_WINDOW,
+ NULL);
+}
diff --git a/src/shell-embedded-window.h b/src/shell-embedded-window.h
new file mode 100644
index 0000000..835165b
--- /dev/null
+++ b/src/shell-embedded-window.h
@@ -0,0 +1,19 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+#ifndef __SHELL_EMBEDDED_WINDOW_H__
+#define __SHELL_EMBEDDED_WINDOW_H__
+
+#include <gtk/gtk.h>
+#include <clutter/clutter.h>
+
+#define SHELL_TYPE_EMBEDDED_WINDOW (shell_embedded_window_get_type ())
+G_DECLARE_DERIVABLE_TYPE (ShellEmbeddedWindow, shell_embedded_window,
+ SHELL, EMBEDDED_WINDOW, GtkWindow)
+
+struct _ShellEmbeddedWindowClass
+{
+ GtkWindowClass parent_class;
+};
+
+GtkWidget *shell_embedded_window_new (void);
+
+#endif /* __SHELL_EMBEDDED_WINDOW_H__ */
diff --git a/src/shell-global-private.h b/src/shell-global-private.h
new file mode 100644
index 0000000..9969691
--- /dev/null
+++ b/src/shell-global-private.h
@@ -0,0 +1,23 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+#ifndef __SHELL_GLOBAL_PRIVATE_H__
+#define __SHELL_GLOBAL_PRIVATE_H__
+
+#include "shell-global.h"
+
+#include <gjs/gjs.h>
+
+void _shell_global_init (const char *first_property_name,
+ ...);
+void _shell_global_set_plugin (ShellGlobal *global,
+ MetaPlugin *plugin);
+
+void _shell_global_destroy_gjs_context (ShellGlobal *global);
+
+GjsContext *_shell_global_get_gjs_context (ShellGlobal *global);
+
+gboolean _shell_global_check_xdnd_event (ShellGlobal *global,
+ XEvent *xev);
+
+void _shell_global_locate_pointer (ShellGlobal *global);
+
+#endif /* __SHELL_GLOBAL_PRIVATE_H__ */
diff --git a/src/shell-global.c b/src/shell-global.c
new file mode 100644
index 0000000..0ccdb10
--- /dev/null
+++ b/src/shell-global.c
@@ -0,0 +1,1860 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+#include "config.h"
+
+#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <math.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#ifdef HAVE_SYS_RESOURCE_H
+#include <sys/resource.h>
+#endif
+#include <locale.h>
+
+#include <X11/extensions/Xfixes.h>
+#include <gdk/gdkx.h>
+#include <gio/gio.h>
+#include <girepository.h>
+#include <meta/meta-backend.h>
+#include <meta/meta-context.h>
+#include <meta/display.h>
+#include <meta/util.h>
+#include <meta/meta-shaped-texture.h>
+#include <meta/meta-cursor-tracker.h>
+#include <meta/meta-settings.h>
+#include <meta/meta-workspace-manager.h>
+#include <meta/meta-x11-display.h>
+
+#define GNOME_DESKTOP_USE_UNSTABLE_API
+#include <libgnome-desktop/gnome-systemd.h>
+
+#if defined __OpenBSD__ || defined __FreeBSD__
+#include <sys/sysctl.h>
+#endif
+
+#include "shell-enum-types.h"
+#include "shell-global-private.h"
+#include "shell-perf-log.h"
+#include "shell-window-tracker.h"
+#include "shell-wm.h"
+#include "shell-util.h"
+#include "st.h"
+#include "switcheroo-control.h"
+
+static ShellGlobal *the_object = NULL;
+
+struct _ShellGlobal {
+ GObject parent;
+
+ ClutterStage *stage;
+
+ MetaBackend *backend;
+ MetaContext *meta_context;
+ MetaDisplay *meta_display;
+ MetaWorkspaceManager *workspace_manager;
+ Display *xdisplay;
+
+ char *session_mode;
+
+ XserverRegion input_region;
+
+ GjsContext *js_context;
+ MetaPlugin *plugin;
+ ShellWM *wm;
+ GSettings *settings;
+ const char *datadir;
+ char *imagedir;
+ char *userdatadir;
+ GFile *userdatadir_path;
+ GFile *runtime_state_path;
+
+ StFocusManager *focus_manager;
+
+ guint work_count;
+ GSList *leisure_closures;
+ guint leisure_function_id;
+
+ GHashTable *save_ops;
+
+ gboolean frame_timestamps;
+ gboolean frame_finish_timestamp;
+
+ GDBusProxy *switcheroo_control;
+ GCancellable *switcheroo_cancellable;
+};
+
+enum {
+ PROP_0,
+
+ PROP_SESSION_MODE,
+ PROP_BACKEND,
+ PROP_CONTEXT,
+ PROP_DISPLAY,
+ PROP_WORKSPACE_MANAGER,
+ PROP_SCREEN_WIDTH,
+ PROP_SCREEN_HEIGHT,
+ PROP_STAGE,
+ PROP_WINDOW_GROUP,
+ PROP_TOP_WINDOW_GROUP,
+ PROP_WINDOW_MANAGER,
+ PROP_SETTINGS,
+ PROP_DATADIR,
+ PROP_IMAGEDIR,
+ PROP_USERDATADIR,
+ PROP_FOCUS_MANAGER,
+ PROP_FRAME_TIMESTAMPS,
+ PROP_FRAME_FINISH_TIMESTAMP,
+ PROP_SWITCHEROO_CONTROL,
+
+ N_PROPS
+};
+
+static GParamSpec *props[N_PROPS] = { NULL, };
+
+/* Signals */
+enum
+{
+ NOTIFY_ERROR,
+ LOCATE_POINTER,
+ LAST_SIGNAL
+};
+
+G_DEFINE_TYPE(ShellGlobal, shell_global, G_TYPE_OBJECT);
+
+static guint shell_global_signals [LAST_SIGNAL] = { 0 };
+
+static void
+got_switcheroo_control_gpus_property_cb (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ ShellGlobal *global;
+ GError *error = NULL;
+ GVariant *gpus;
+
+ gpus = g_dbus_connection_call_finish (G_DBUS_CONNECTION (source_object),
+ res, &error);
+ if (!gpus)
+ {
+ if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ g_debug ("Could not get GPUs property from switcheroo-control: %s", error->message);
+ g_clear_error (&error);
+ return;
+ }
+
+ global = user_data;
+ g_dbus_proxy_set_cached_property (global->switcheroo_control, "GPUs", gpus);
+ g_object_notify_by_pspec (G_OBJECT (global), props[PROP_SWITCHEROO_CONTROL]);
+}
+
+static void
+switcheroo_control_ready_cb (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ ShellGlobal *global;
+ GError *error = NULL;
+ ShellNetHadessSwitcherooControl *control;
+ g_auto(GStrv) cached_props = NULL;
+
+ control = shell_net_hadess_switcheroo_control_proxy_new_for_bus_finish (res, &error);
+ if (!control)
+ {
+ if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ g_debug ("Could not get switcheroo-control GDBusProxy: %s", error->message);
+ g_clear_error (&error);
+ return;
+ }
+
+ global = user_data;
+ global->switcheroo_control = G_DBUS_PROXY (control);
+ g_debug ("Got switcheroo-control proxy successfully");
+
+ cached_props = g_dbus_proxy_get_cached_property_names (global->switcheroo_control);
+ if (cached_props != NULL && g_strv_contains ((const gchar * const *) cached_props, "GPUs"))
+ {
+ g_object_notify_by_pspec (G_OBJECT (global), props[PROP_SWITCHEROO_CONTROL]);
+ return;
+ }
+ /* Delay property notification until we have all the properties gathered */
+
+ g_dbus_connection_call (g_dbus_proxy_get_connection (global->switcheroo_control),
+ g_dbus_proxy_get_name (global->switcheroo_control),
+ g_dbus_proxy_get_object_path (global->switcheroo_control),
+ "org.freedesktop.DBus.Properties",
+ "Get",
+ g_variant_new ("(ss)",
+ g_dbus_proxy_get_interface_name (global->switcheroo_control),
+ "GPUs"),
+ NULL,
+ G_DBUS_CALL_FLAGS_NONE,
+ -1,
+ global->switcheroo_cancellable,
+ got_switcheroo_control_gpus_property_cb,
+ user_data);
+}
+
+static void
+shell_global_set_property(GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ ShellGlobal *global = SHELL_GLOBAL (object);
+
+ switch (prop_id)
+ {
+ case PROP_SESSION_MODE:
+ g_clear_pointer (&global->session_mode, g_free);
+ global->session_mode = g_ascii_strdown (g_value_get_string (value), -1);
+ break;
+ case PROP_FRAME_TIMESTAMPS:
+ {
+ gboolean enable = g_value_get_boolean (value);
+
+ if (global->frame_timestamps != enable)
+ {
+ global->frame_timestamps = enable;
+ g_object_notify_by_pspec (object, props[PROP_FRAME_TIMESTAMPS]);
+ }
+ }
+ break;
+ case PROP_FRAME_FINISH_TIMESTAMP:
+ {
+ gboolean enable = g_value_get_boolean (value);
+
+ if (global->frame_finish_timestamp != enable)
+ {
+ global->frame_finish_timestamp = enable;
+ g_object_notify_by_pspec (object, props[PROP_FRAME_FINISH_TIMESTAMP]);
+ }
+ }
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+shell_global_get_property(GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ ShellGlobal *global = SHELL_GLOBAL (object);
+
+ switch (prop_id)
+ {
+ case PROP_SESSION_MODE:
+ g_value_set_string (value, shell_global_get_session_mode (global));
+ break;
+ case PROP_BACKEND:
+ g_value_set_object (value, global->backend);
+ break;
+ case PROP_CONTEXT:
+ g_value_set_object (value, global->meta_context);
+ break;
+ case PROP_DISPLAY:
+ g_value_set_object (value, global->meta_display);
+ break;
+ case PROP_WORKSPACE_MANAGER:
+ g_value_set_object (value, global->workspace_manager);
+ break;
+ case PROP_SCREEN_WIDTH:
+ {
+ int width, height;
+
+ meta_display_get_size (global->meta_display, &width, &height);
+ g_value_set_int (value, width);
+ }
+ break;
+ case PROP_SCREEN_HEIGHT:
+ {
+ int width, height;
+
+ meta_display_get_size (global->meta_display, &width, &height);
+ g_value_set_int (value, height);
+ }
+ break;
+ case PROP_STAGE:
+ g_value_set_object (value, global->stage);
+ break;
+ case PROP_WINDOW_GROUP:
+ g_value_set_object (value, meta_get_window_group_for_display (global->meta_display));
+ break;
+ case PROP_TOP_WINDOW_GROUP:
+ g_value_set_object (value, meta_get_top_window_group_for_display (global->meta_display));
+ break;
+ case PROP_WINDOW_MANAGER:
+ g_value_set_object (value, global->wm);
+ break;
+ case PROP_SETTINGS:
+ g_value_set_object (value, global->settings);
+ break;
+ case PROP_DATADIR:
+ g_value_set_string (value, global->datadir);
+ break;
+ case PROP_IMAGEDIR:
+ g_value_set_string (value, global->imagedir);
+ break;
+ case PROP_USERDATADIR:
+ g_value_set_string (value, global->userdatadir);
+ break;
+ case PROP_FOCUS_MANAGER:
+ g_value_set_object (value, global->focus_manager);
+ break;
+ case PROP_FRAME_TIMESTAMPS:
+ g_value_set_boolean (value, global->frame_timestamps);
+ break;
+ case PROP_FRAME_FINISH_TIMESTAMP:
+ g_value_set_boolean (value, global->frame_finish_timestamp);
+ break;
+ case PROP_SWITCHEROO_CONTROL:
+ g_value_set_object (value, global->switcheroo_control);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+switcheroo_appeared_cb (GDBusConnection *connection,
+ const char *name,
+ const char *name_owner,
+ gpointer user_data)
+{
+ ShellGlobal *global = user_data;
+
+ g_debug ("switcheroo-control appeared");
+ shell_net_hadess_switcheroo_control_proxy_new_for_bus (G_BUS_TYPE_SYSTEM,
+ G_DBUS_PROXY_FLAGS_NONE,
+ "net.hadess.SwitcherooControl",
+ "/net/hadess/SwitcherooControl",
+ global->switcheroo_cancellable,
+ switcheroo_control_ready_cb,
+ global);
+}
+
+static void
+switcheroo_vanished_cb (GDBusConnection *connection,
+ const char *name,
+ gpointer user_data)
+{
+ ShellGlobal *global = user_data;
+
+ g_debug ("switcheroo-control vanished");
+ g_clear_object (&global->switcheroo_control);
+ g_object_notify_by_pspec (G_OBJECT (global), props[PROP_SWITCHEROO_CONTROL]);
+}
+
+static void
+shell_global_init (ShellGlobal *global)
+{
+ const char *datadir = g_getenv ("GNOME_SHELL_DATADIR");
+ const char *shell_js = g_getenv("GNOME_SHELL_JS");
+ char *imagedir, **search_path;
+ char *path;
+ const char *byteorder_string;
+
+ if (!datadir)
+ datadir = GNOME_SHELL_DATADIR;
+ global->datadir = datadir;
+
+ /* We make sure imagedir ends with a '/', since the JS won't have
+ * access to g_build_filename() and so will end up just
+ * concatenating global.imagedir to a filename.
+ */
+ imagedir = g_build_filename (datadir, "images/", NULL);
+ if (g_file_test (imagedir, G_FILE_TEST_IS_DIR))
+ global->imagedir = imagedir;
+ else
+ {
+ g_free (imagedir);
+ global->imagedir = g_strdup_printf ("%s/", datadir);
+ }
+
+ /* Ensure config dir exists for later use */
+ global->userdatadir = g_build_filename (g_get_user_data_dir (), "gnome-shell", NULL);
+ g_mkdir_with_parents (global->userdatadir, 0700);
+ global->userdatadir_path = g_file_new_for_path (global->userdatadir);
+
+#if G_BYTE_ORDER == G_LITTLE_ENDIAN
+ byteorder_string = "LE";
+#else
+ byteorder_string = "BE";
+#endif
+
+ /* And the runtime state */
+ path = g_strdup_printf ("%s/gnome-shell/runtime-state-%s.%s",
+ g_get_user_runtime_dir (),
+ byteorder_string,
+ XDisplayName (NULL));
+ (void) g_mkdir_with_parents (path, 0700);
+ global->runtime_state_path = g_file_new_for_path (path);
+ g_free (path);
+
+ global->settings = g_settings_new ("org.gnome.shell");
+
+ if (shell_js)
+ {
+ int i, j;
+ search_path = g_strsplit (shell_js, ":", -1);
+
+ /* The naive g_strsplit above will split 'resource:///foo/bar' into 'resource',
+ * '///foo/bar'. Combine these back together by looking for a literal 'resource'
+ * in the array. */
+ for (i = 0, j = 0; search_path[i];)
+ {
+ char *out;
+
+ if (strcmp (search_path[i], "resource") == 0 && search_path[i + 1] != NULL)
+ {
+ out = g_strconcat (search_path[i], ":", search_path[i + 1], NULL);
+ g_free (search_path[i]);
+ g_free (search_path[i + 1]);
+ i += 2;
+ }
+ else
+ {
+ out = search_path[i];
+ i += 1;
+ }
+
+ search_path[j++] = out;
+ }
+
+ search_path[j] = NULL; /* NULL-terminate the now possibly shorter array */
+ }
+ else
+ {
+ search_path = g_malloc0 (2 * sizeof (char *));
+ search_path[0] = g_strdup ("resource:///org/gnome/shell");
+ }
+
+ global->js_context = g_object_new (GJS_TYPE_CONTEXT,
+ "search-path", search_path,
+ NULL);
+
+ g_strfreev (search_path);
+
+ global->save_ops = g_hash_table_new_full (g_file_hash,
+ (GEqualFunc) g_file_equal,
+ g_object_unref, g_object_unref);
+
+ global->switcheroo_cancellable = g_cancellable_new ();
+ g_bus_watch_name (G_BUS_TYPE_SYSTEM,
+ "net.hadess.SwitcherooControl",
+ G_BUS_NAME_WATCHER_FLAGS_NONE,
+ switcheroo_appeared_cb,
+ switcheroo_vanished_cb,
+ global,
+ NULL);
+}
+
+static void
+shell_global_finalize (GObject *object)
+{
+ ShellGlobal *global = SHELL_GLOBAL (object);
+
+ g_clear_object (&global->js_context);
+ g_object_unref (global->settings);
+
+ the_object = NULL;
+
+ g_cancellable_cancel (global->switcheroo_cancellable);
+ g_clear_object (&global->switcheroo_cancellable);
+
+ g_clear_object (&global->userdatadir_path);
+ g_clear_object (&global->runtime_state_path);
+
+ g_free (global->session_mode);
+ g_free (global->imagedir);
+ g_free (global->userdatadir);
+
+ g_hash_table_unref (global->save_ops);
+
+ G_OBJECT_CLASS(shell_global_parent_class)->finalize (object);
+}
+
+static void
+shell_global_class_init (ShellGlobalClass *klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+ gobject_class->get_property = shell_global_get_property;
+ gobject_class->set_property = shell_global_set_property;
+ gobject_class->finalize = shell_global_finalize;
+
+ shell_global_signals[NOTIFY_ERROR] =
+ g_signal_new ("notify-error",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL, NULL,
+ G_TYPE_NONE, 2,
+ G_TYPE_STRING,
+ G_TYPE_STRING);
+ shell_global_signals[LOCATE_POINTER] =
+ g_signal_new ("locate-pointer",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL, NULL,
+ G_TYPE_NONE, 0);
+
+ props[PROP_SESSION_MODE] =
+ g_param_spec_string ("session-mode",
+ "Session Mode",
+ "The session mode to use",
+ "user",
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
+
+ props[PROP_SCREEN_WIDTH] =
+ g_param_spec_int ("screen-width",
+ "Screen Width",
+ "Screen width, in pixels",
+ 0, G_MAXINT, 1,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+ props[PROP_SCREEN_HEIGHT] =
+ g_param_spec_int ("screen-height",
+ "Screen Height",
+ "Screen height, in pixels",
+ 0, G_MAXINT, 1,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+ props[PROP_BACKEND] =
+ g_param_spec_object ("backend",
+ "Backend",
+ "MetaBackend object",
+ META_TYPE_BACKEND,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+ props[PROP_CONTEXT] =
+ g_param_spec_object ("context",
+ "Context",
+ "MetaContext object",
+ META_TYPE_CONTEXT,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+ props[PROP_DISPLAY] =
+ g_param_spec_object ("display",
+ "Display",
+ "Metacity display object for the shell",
+ META_TYPE_DISPLAY,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+ props[PROP_WORKSPACE_MANAGER] =
+ g_param_spec_object ("workspace-manager",
+ "Workspace manager",
+ "Workspace manager",
+ META_TYPE_WORKSPACE_MANAGER,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+ props[PROP_STAGE] =
+ g_param_spec_object ("stage",
+ "Stage",
+ "Stage holding the desktop scene graph",
+ CLUTTER_TYPE_ACTOR,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+ props[PROP_WINDOW_GROUP] =
+ g_param_spec_object ("window-group",
+ "Window Group",
+ "Actor holding window actors",
+ CLUTTER_TYPE_ACTOR,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+ props[PROP_TOP_WINDOW_GROUP] =
+ g_param_spec_object ("top-window-group",
+ "Top Window Group",
+ "Actor holding override-redirect windows",
+ CLUTTER_TYPE_ACTOR,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+ props[PROP_WINDOW_MANAGER] =
+ g_param_spec_object ("window-manager",
+ "Window Manager",
+ "Window management interface",
+ SHELL_TYPE_WM,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+ props[PROP_SETTINGS] =
+ g_param_spec_object ("settings",
+ "Settings",
+ "GSettings instance for gnome-shell configuration",
+ G_TYPE_SETTINGS,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+ props[PROP_DATADIR] =
+ g_param_spec_string ("datadir",
+ "Data directory",
+ "Directory containing gnome-shell data files",
+ NULL,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+ props[PROP_IMAGEDIR] =
+ g_param_spec_string ("imagedir",
+ "Image directory",
+ "Directory containing gnome-shell image files",
+ NULL,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+ props[PROP_USERDATADIR] =
+ g_param_spec_string ("userdatadir",
+ "User data directory",
+ "Directory containing gnome-shell user data",
+ NULL,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+ props[PROP_FOCUS_MANAGER] =
+ g_param_spec_object ("focus-manager",
+ "Focus manager",
+ "The shell's StFocusManager",
+ ST_TYPE_FOCUS_MANAGER,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+ props[PROP_FRAME_TIMESTAMPS] =
+ g_param_spec_boolean ("frame-timestamps",
+ "Frame Timestamps",
+ "Whether to log frame timestamps in the performance log",
+ FALSE,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY);
+
+ props[PROP_FRAME_FINISH_TIMESTAMP] =
+ g_param_spec_boolean ("frame-finish-timestamp",
+ "Frame Finish Timestamps",
+ "Whether at the end of a frame to call glFinish and log paintCompletedTimestamp",
+ FALSE,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY);
+
+ props[PROP_SWITCHEROO_CONTROL] =
+ g_param_spec_object ("switcheroo-control",
+ "switcheroo-control",
+ "D-Bus Proxy for switcheroo-control daemon",
+ G_TYPE_DBUS_PROXY,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+ g_object_class_install_properties (gobject_class, N_PROPS, props);
+}
+
+/*
+ * _shell_global_init: (skip)
+ * @first_property_name: the name of the first property
+ * @...: the value of the first property, followed optionally by more
+ * name/value pairs, followed by %NULL
+ *
+ * Initializes the shell global singleton with the construction-time
+ * properties.
+ *
+ * There are currently no such properties, so @first_property_name should
+ * always be %NULL.
+ *
+ * This call must be called before shell_global_get() and shouldn't be called
+ * more than once.
+ */
+void
+_shell_global_init (const char *first_property_name,
+ ...)
+{
+ va_list argument_list;
+
+ g_return_if_fail (the_object == NULL);
+
+ va_start (argument_list, first_property_name);
+ the_object = SHELL_GLOBAL (g_object_new_valist (SHELL_TYPE_GLOBAL,
+ first_property_name,
+ argument_list));
+ va_end (argument_list);
+
+}
+
+/**
+ * shell_global_get:
+ *
+ * Gets the singleton global object that represents the desktop.
+ *
+ * Return value: (transfer none): the singleton global object
+ */
+ShellGlobal *
+shell_global_get (void)
+{
+ return the_object;
+}
+
+/**
+ * _shell_global_destroy_gjs_context: (skip)
+ * @self: global object
+ *
+ * Destroys the GjsContext held by ShellGlobal, in order to break reference
+ * counting cycles. (The GjsContext holds a reference to ShellGlobal because
+ * it's available as window.global inside JS.)
+ */
+void
+_shell_global_destroy_gjs_context (ShellGlobal *self)
+{
+ g_clear_object (&self->js_context);
+}
+
+static guint32
+get_current_time_maybe_roundtrip (ShellGlobal *global)
+{
+ guint32 time;
+
+ time = shell_global_get_current_time (global);
+ if (time != CurrentTime)
+ return time;
+
+ return meta_display_get_current_time_roundtrip (global->meta_display);
+}
+
+static void
+focus_window_changed (MetaDisplay *display,
+ GParamSpec *param,
+ gpointer user_data)
+{
+ ShellGlobal *global = user_data;
+
+ /* If the stage window became unfocused, drop the key focus
+ * on Clutter's side. */
+ if (!meta_stage_is_focused (global->meta_display))
+ clutter_stage_set_key_focus (global->stage, NULL);
+}
+
+static ClutterActor *
+get_key_focused_actor (ShellGlobal *global)
+{
+ ClutterActor *actor;
+
+ actor = clutter_stage_get_key_focus (global->stage);
+
+ /* If there's no explicit key focus, clutter_stage_get_key_focus()
+ * returns the stage. This is a terrible API. */
+ if (actor == CLUTTER_ACTOR (global->stage))
+ actor = NULL;
+
+ return actor;
+}
+
+static void
+sync_stage_window_focus (ShellGlobal *global)
+{
+ ClutterActor *actor;
+
+ actor = get_key_focused_actor (global);
+
+ /* An actor got key focus and the stage needs to be focused. */
+ if (actor != NULL && !meta_stage_is_focused (global->meta_display))
+ meta_focus_stage_window (global->meta_display,
+ get_current_time_maybe_roundtrip (global));
+
+ /* An actor dropped key focus. Focus the default window. */
+ else if (actor == NULL && meta_stage_is_focused (global->meta_display))
+ meta_display_focus_default_window (global->meta_display,
+ get_current_time_maybe_roundtrip (global));
+}
+
+static void
+focus_actor_changed (ClutterStage *stage,
+ GParamSpec *param,
+ gpointer user_data)
+{
+ ShellGlobal *global = user_data;
+ sync_stage_window_focus (global);
+}
+
+static void
+sync_input_region (ShellGlobal *global)
+{
+ MetaDisplay *display = global->meta_display;
+ MetaX11Display *x11_display = meta_display_get_x11_display (display);
+
+ meta_x11_display_set_stage_input_region (x11_display, global->input_region);
+}
+
+/**
+ * shell_global_set_stage_input_region:
+ * @global: the #ShellGlobal
+ * @rectangles: (element-type Meta.Rectangle): a list of #MetaRectangle
+ * describing the input region.
+ *
+ * Sets the area of the stage that is responsive to mouse clicks when
+ * we don't have a modal or grab.
+ */
+void
+shell_global_set_stage_input_region (ShellGlobal *global,
+ GSList *rectangles)
+{
+ MetaRectangle *rect;
+ XRectangle *rects;
+ int nrects, i;
+ GSList *r;
+
+ g_return_if_fail (SHELL_IS_GLOBAL (global));
+
+ if (meta_is_wayland_compositor ())
+ return;
+
+ nrects = g_slist_length (rectangles);
+ rects = g_new (XRectangle, nrects);
+ for (r = rectangles, i = 0; r; r = r->next, i++)
+ {
+ rect = (MetaRectangle *)r->data;
+ rects[i].x = rect->x;
+ rects[i].y = rect->y;
+ rects[i].width = rect->width;
+ rects[i].height = rect->height;
+ }
+
+ if (global->input_region)
+ XFixesDestroyRegion (global->xdisplay, global->input_region);
+
+ global->input_region = XFixesCreateRegion (global->xdisplay, rects, nrects);
+ g_free (rects);
+
+ sync_input_region (global);
+}
+
+/**
+ * shell_global_get_stage:
+ *
+ * Return value: (transfer none): The default #ClutterStage
+ */
+ClutterStage *
+shell_global_get_stage (ShellGlobal *global)
+{
+ return global->stage;
+}
+
+/**
+ * shell_global_get_display:
+ *
+ * Return value: (transfer none): The default #MetaDisplay
+ */
+MetaDisplay *
+shell_global_get_display (ShellGlobal *global)
+{
+ return global->meta_display;
+}
+
+/**
+ * shell_global_get_workspace_manager:
+ *
+ * Return value: (transfer none): The default #MetaWorkspaceManager
+ */
+MetaWorkspaceManager *
+shell_global_get_workspace_manager (ShellGlobal *global)
+{
+ return global->workspace_manager;
+}
+
+/**
+ * shell_global_get_window_actors:
+ *
+ * Gets the list of #MetaWindowActor for the plugin's screen
+ *
+ * Return value: (element-type Meta.WindowActor) (transfer container): the list of windows
+ */
+GList *
+shell_global_get_window_actors (ShellGlobal *global)
+{
+ GList *filtered = NULL;
+ GList *l;
+
+ g_return_val_if_fail (SHELL_IS_GLOBAL (global), NULL);
+
+ for (l = meta_get_window_actors (global->meta_display); l; l = l->next)
+ if (!meta_window_actor_is_destroyed (l->data))
+ filtered = g_list_prepend (filtered, l->data);
+
+ return g_list_reverse (filtered);
+}
+
+static void
+global_stage_notify_width (GObject *gobject,
+ GParamSpec *pspec,
+ gpointer data)
+{
+ ShellGlobal *global = SHELL_GLOBAL (data);
+
+ g_object_notify_by_pspec (G_OBJECT (global), props[PROP_SCREEN_WIDTH]);
+}
+
+static void
+global_stage_notify_height (GObject *gobject,
+ GParamSpec *pspec,
+ gpointer data)
+{
+ ShellGlobal *global = SHELL_GLOBAL (data);
+
+ g_object_notify_by_pspec (G_OBJECT (global), props[PROP_SCREEN_HEIGHT]);
+}
+
+static gboolean
+global_stage_before_paint (gpointer data)
+{
+ ShellGlobal *global = SHELL_GLOBAL (data);
+
+ if (global->frame_timestamps)
+ shell_perf_log_event (shell_perf_log_get_default (),
+ "clutter.stagePaintStart");
+
+ return TRUE;
+}
+
+static gboolean
+load_gl_symbol (const char *name,
+ void **func)
+{
+ *func = cogl_get_proc_address (name);
+ if (!*func)
+ {
+ g_warning ("failed to resolve required GL symbol \"%s\"\n", name);
+ return FALSE;
+ }
+ return TRUE;
+}
+
+static void
+global_stage_after_paint (ClutterStage *stage,
+ ClutterStageView *stage_view,
+ ShellGlobal *global)
+{
+ /* At this point, we've finished all layout and painting, but haven't
+ * actually flushed or swapped */
+
+ if (global->frame_timestamps && global->frame_finish_timestamp)
+ {
+ /* It's interesting to find out when the paint actually finishes
+ * on the GPU. We could wait for this asynchronously with
+ * ARB_timer_query (see https://bugzilla.gnome.org/show_bug.cgi?id=732350
+ * for an implementation of this), but what we actually would
+ * find out then is the latency for drawing a frame, not how much
+ * GPU work was needed, since frames can overlap. Calling glFinish()
+ * is a fairly reliable way to separate out adjacent frames
+ * and measure the amount of GPU work. This is turned on with a
+ * separate property from ::frame-timestamps, since it should not
+ * be turned on if we're trying to actual measure latency or frame
+ * rate.
+ */
+ static void (*finish) (void);
+
+ if (!finish)
+ load_gl_symbol ("glFinish", (void **)&finish);
+
+ cogl_flush ();
+ finish ();
+
+ shell_perf_log_event (shell_perf_log_get_default (),
+ "clutter.paintCompletedTimestamp");
+ }
+}
+
+static gboolean
+global_stage_after_swap (gpointer data)
+{
+ /* Everything is done, we're ready for a new frame */
+
+ ShellGlobal *global = SHELL_GLOBAL (data);
+
+ if (global->frame_timestamps)
+ shell_perf_log_event (shell_perf_log_get_default (),
+ "clutter.stagePaintDone");
+
+ return TRUE;
+}
+
+static void
+update_scaling_factor (ShellGlobal *global,
+ MetaSettings *settings)
+{
+ ClutterStage *stage = CLUTTER_STAGE (global->stage);
+ StThemeContext *context = st_theme_context_get_for_stage (stage);
+ int scaling_factor;
+
+ scaling_factor = meta_settings_get_ui_scaling_factor (settings);
+ g_object_set (context, "scale-factor", scaling_factor, NULL);
+}
+
+static void
+ui_scaling_factor_changed (MetaSettings *settings,
+ ShellGlobal *global)
+{
+ update_scaling_factor (global, settings);
+}
+
+static void
+entry_cursor_func (StEntry *entry,
+ gboolean use_ibeam,
+ gpointer user_data)
+{
+ ShellGlobal *global = user_data;
+
+ meta_display_set_cursor (global->meta_display,
+ use_ibeam ? META_CURSOR_IBEAM : META_CURSOR_DEFAULT);
+}
+
+static void
+on_x11_display_closed (MetaDisplay *display,
+ ShellGlobal *global)
+{
+ g_signal_handlers_disconnect_by_data (global->stage, global);
+}
+
+void
+_shell_global_set_plugin (ShellGlobal *global,
+ MetaPlugin *plugin)
+{
+ MetaDisplay *display;
+ MetaBackend *backend;
+ MetaSettings *settings;
+
+ g_return_if_fail (SHELL_IS_GLOBAL (global));
+ g_return_if_fail (global->plugin == NULL);
+
+ global->backend = meta_get_backend ();
+ global->plugin = plugin;
+ global->wm = shell_wm_new (plugin);
+
+ display = meta_plugin_get_display (plugin);
+ global->meta_display = display;
+ global->meta_context = meta_display_get_context (display);
+ global->workspace_manager = meta_display_get_workspace_manager (display);
+
+ global->stage = CLUTTER_STAGE (meta_get_stage_for_display (display));
+
+ if (!meta_is_wayland_compositor ())
+ {
+ MetaX11Display *x11_display = meta_display_get_x11_display (display);
+ global->xdisplay = meta_x11_display_get_xdisplay (x11_display);
+ }
+
+ st_entry_set_cursor_func (entry_cursor_func, global);
+ st_clipboard_set_selection (meta_display_get_selection (display));
+
+ g_signal_connect (global->stage, "notify::width",
+ G_CALLBACK (global_stage_notify_width), global);
+ g_signal_connect (global->stage, "notify::height",
+ G_CALLBACK (global_stage_notify_height), global);
+
+ clutter_threads_add_repaint_func_full (CLUTTER_REPAINT_FLAGS_PRE_PAINT,
+ global_stage_before_paint,
+ global, NULL);
+
+ g_signal_connect (global->stage, "after-paint",
+ G_CALLBACK (global_stage_after_paint), global);
+
+ clutter_threads_add_repaint_func_full (CLUTTER_REPAINT_FLAGS_POST_PAINT,
+ global_stage_after_swap,
+ global, NULL);
+
+ shell_perf_log_define_event (shell_perf_log_get_default(),
+ "clutter.stagePaintStart",
+ "Start of stage page repaint",
+ "");
+ shell_perf_log_define_event (shell_perf_log_get_default(),
+ "clutter.paintCompletedTimestamp",
+ "Paint completion on GPU",
+ "");
+ shell_perf_log_define_event (shell_perf_log_get_default(),
+ "clutter.stagePaintDone",
+ "End of frame, possibly including swap time",
+ "");
+
+ g_signal_connect (global->stage, "notify::key-focus",
+ G_CALLBACK (focus_actor_changed), global);
+ g_signal_connect (global->meta_display, "notify::focus-window",
+ G_CALLBACK (focus_window_changed), global);
+
+ if (global->xdisplay)
+ g_signal_connect_object (global->meta_display, "x11-display-closing",
+ G_CALLBACK (on_x11_display_closed), global, 0);
+
+ backend = meta_get_backend ();
+ settings = meta_backend_get_settings (backend);
+ g_signal_connect (settings, "ui-scaling-factor-changed",
+ G_CALLBACK (ui_scaling_factor_changed), global);
+
+ global->focus_manager = st_focus_manager_get_for_stage (global->stage);
+
+ update_scaling_factor (global, settings);
+}
+
+GjsContext *
+_shell_global_get_gjs_context (ShellGlobal *global)
+{
+ return global->js_context;
+}
+
+/* Code to close all file descriptors before we exec; copied from gspawn.c in GLib.
+ *
+ * Authors: Padraig O'Briain, Matthias Clasen, Lennart Poettering
+ *
+ * http://bugzilla.gnome.org/show_bug.cgi?id=469231
+ * http://bugzilla.gnome.org/show_bug.cgi?id=357585
+ */
+
+static int
+set_cloexec (void *data, gint fd)
+{
+ if (fd >= GPOINTER_TO_INT (data))
+ fcntl (fd, F_SETFD, FD_CLOEXEC);
+
+ return 0;
+}
+
+#ifndef HAVE_FDWALK
+static int
+fdwalk (int (*cb)(void *data, int fd), void *data)
+{
+ gint open_max;
+ gint fd;
+ gint res = 0;
+
+#ifdef HAVE_SYS_RESOURCE_H
+ struct rlimit rl;
+#endif
+
+#ifdef __linux__
+ DIR *d;
+
+ if ((d = opendir("/proc/self/fd"))) {
+ struct dirent *de;
+
+ while ((de = readdir(d))) {
+ glong l;
+ gchar *e = NULL;
+
+ if (de->d_name[0] == '.')
+ continue;
+
+ errno = 0;
+ l = strtol(de->d_name, &e, 10);
+ if (errno != 0 || !e || *e)
+ continue;
+
+ fd = (gint) l;
+
+ if ((glong) fd != l)
+ continue;
+
+ if (fd == dirfd(d))
+ continue;
+
+ if ((res = cb (data, fd)) != 0)
+ break;
+ }
+
+ closedir(d);
+ return res;
+ }
+
+ /* If /proc is not mounted or not accessible we fall back to the old
+ * rlimit trick */
+
+#endif
+
+#ifdef HAVE_SYS_RESOURCE_H
+ if (getrlimit(RLIMIT_NOFILE, &rl) == 0 && rl.rlim_max != RLIM_INFINITY)
+ open_max = rl.rlim_max;
+ else
+#endif
+ open_max = sysconf (_SC_OPEN_MAX);
+
+ for (fd = 0; fd < open_max; fd++)
+ if ((res = cb (data, fd)) != 0)
+ break;
+
+ return res;
+}
+#endif
+
+static void
+pre_exec_close_fds(void)
+{
+ fdwalk (set_cloexec, GINT_TO_POINTER(3));
+}
+
+/**
+ * shell_global_reexec_self:
+ * @global: A #ShellGlobal
+ *
+ * Restart the current process. Only intended for development purposes.
+ */
+void
+shell_global_reexec_self (ShellGlobal *global)
+{
+ GPtrArray *arr;
+ gsize len;
+ MetaContext *meta_context;
+
+#if defined __linux__ || defined __sun
+ char *buf;
+ char *buf_p;
+ char *buf_end;
+ g_autoptr (GError) error = NULL;
+
+ if (!g_file_get_contents ("/proc/self/cmdline", &buf, &len, &error))
+ {
+ g_warning ("failed to get /proc/self/cmdline: %s", error->message);
+ return;
+ }
+
+ buf_end = buf+len;
+ arr = g_ptr_array_new ();
+ /* The cmdline file is NUL-separated */
+ for (buf_p = buf; buf_p < buf_end; buf_p = buf_p + strlen (buf_p) + 1)
+ g_ptr_array_add (arr, buf_p);
+
+ g_ptr_array_add (arr, NULL);
+#elif defined __OpenBSD__
+ gchar **args, **args_p;
+ gint mib[] = { CTL_KERN, KERN_PROC_ARGS, getpid(), KERN_PROC_ARGV };
+
+ if (sysctl (mib, G_N_ELEMENTS (mib), NULL, &len, NULL, 0) == -1)
+ return;
+
+ args = g_malloc0 (len);
+
+ if (sysctl (mib, G_N_ELEMENTS (mib), args, &len, NULL, 0) == -1) {
+ g_warning ("failed to get command line args: %d", errno);
+ g_free (args);
+ return;
+ }
+
+ arr = g_ptr_array_new ();
+ for (args_p = args; *args_p != NULL; args_p++) {
+ g_ptr_array_add (arr, *args_p);
+ }
+
+ g_ptr_array_add (arr, NULL);
+#elif defined __FreeBSD__
+ char *buf;
+ char *buf_p;
+ char *buf_end;
+ gint mib[] = { CTL_KERN, KERN_PROC, KERN_PROC_ARGS, getpid() };
+
+ if (sysctl (mib, G_N_ELEMENTS (mib), NULL, &len, NULL, 0) == -1)
+ return;
+
+ buf = g_malloc0 (len);
+
+ if (sysctl (mib, G_N_ELEMENTS (mib), buf, &len, NULL, 0) == -1) {
+ g_warning ("failed to get command line args: %d", errno);
+ g_free (buf);
+ return;
+ }
+
+ buf_end = buf+len;
+ arr = g_ptr_array_new ();
+ /* The value returned by sysctl is NUL-separated */
+ for (buf_p = buf; buf_p < buf_end; buf_p = buf_p + strlen (buf_p) + 1)
+ g_ptr_array_add (arr, buf_p);
+
+ g_ptr_array_add (arr, NULL);
+#else
+ return;
+#endif
+
+ /* Close all file descriptors other than stdin/stdout/stderr, otherwise
+ * they will leak and stay open after the exec. In particular, this is
+ * important for file descriptors that represent mapped graphics buffer
+ * objects.
+ */
+ pre_exec_close_fds ();
+
+ g_object_get (global, "context", &meta_context, NULL);
+ meta_context_restore_rlimit_nofile (meta_context, NULL);
+
+ meta_display_close (shell_global_get_display (global),
+ shell_global_get_current_time (global));
+
+ execvp (arr->pdata[0], (char**)arr->pdata);
+ g_warning ("failed to reexec: %s", g_strerror (errno));
+ g_ptr_array_free (arr, TRUE);
+#if defined __linux__ || defined __FreeBSD__
+ g_free (buf);
+#elif defined __OpenBSD__
+ g_free (args);
+#endif
+}
+
+/**
+ * shell_global_notify_error:
+ * @global: a #ShellGlobal
+ * @msg: Error message
+ * @details: Error details
+ *
+ * Show a system error notification. Use this function
+ * when a user-initiated action results in a non-fatal problem
+ * from causes that may not be under system control. For
+ * example, an application crash.
+ */
+void
+shell_global_notify_error (ShellGlobal *global,
+ const char *msg,
+ const char *details)
+{
+ g_signal_emit_by_name (global, "notify-error", msg, details);
+}
+
+/**
+ * shell_global_get_pointer:
+ * @global: the #ShellGlobal
+ * @x: (out): the X coordinate of the pointer, in global coordinates
+ * @y: (out): the Y coordinate of the pointer, in global coordinates
+ * @mods: (out): the current set of modifier keys that are pressed down
+ *
+ * Gets the pointer coordinates and current modifier key state.
+ */
+void
+shell_global_get_pointer (ShellGlobal *global,
+ int *x,
+ int *y,
+ ClutterModifierType *mods)
+{
+ ClutterModifierType raw_mods;
+ MetaCursorTracker *tracker;
+ graphene_point_t point;
+
+ tracker = meta_cursor_tracker_get_for_display (global->meta_display);
+ meta_cursor_tracker_get_pointer (tracker, &point, &raw_mods);
+
+ if (x)
+ *x = point.x;
+ if (y)
+ *y = point.y;
+
+ *mods = raw_mods & CLUTTER_MODIFIER_MASK;
+}
+
+/**
+ * shell_global_get_switcheroo_control:
+ * @global: A #ShellGlobal
+ *
+ * Get the global #GDBusProxy instance for the switcheroo-control
+ * daemon.
+ *
+ * Return value: (transfer none): the #GDBusProxy for the daemon,
+ * or %NULL on error.
+ */
+GDBusProxy *
+shell_global_get_switcheroo_control (ShellGlobal *global)
+{
+ return global->switcheroo_control;
+}
+
+/**
+ * shell_global_get_settings:
+ * @global: A #ShellGlobal
+ *
+ * Get the global GSettings instance.
+ *
+ * Return value: (transfer none): The GSettings object
+ */
+GSettings *
+shell_global_get_settings (ShellGlobal *global)
+{
+ return global->settings;
+}
+
+/**
+ * shell_global_get_current_time:
+ * @global: A #ShellGlobal
+ *
+ * Returns: the current X server time from the current Clutter, Gdk, or X
+ * event. If called from outside an event handler, this may return
+ * %Clutter.CURRENT_TIME (aka 0), or it may return a slightly
+ * out-of-date timestamp.
+ */
+guint32
+shell_global_get_current_time (ShellGlobal *global)
+{
+ guint32 time;
+
+ /* meta_display_get_current_time() will return the correct time
+ when handling an X or Gdk event, but will return CurrentTime
+ from some Clutter event callbacks.
+
+ clutter_get_current_event_time() will return the correct time
+ from a Clutter event callback, but may return CLUTTER_CURRENT_TIME
+ timestamp if called at other times.
+
+ So we try meta_display_get_current_time() first, since we
+ can recognize a "wrong" answer from that, and then fall back
+ to clutter_get_current_event_time().
+ */
+
+ time = meta_display_get_current_time (global->meta_display);
+ if (time != CLUTTER_CURRENT_TIME)
+ return time;
+
+ return clutter_get_current_event_time ();
+}
+
+static void
+shell_global_app_launched_cb (GAppLaunchContext *context,
+ GAppInfo *info,
+ GVariant *platform_data,
+ gpointer user_data)
+{
+ gint32 pid;
+ const gchar *app_name;
+
+ if (!g_variant_lookup (platform_data, "pid", "i", &pid))
+ return;
+
+ /* If pid == 0 the application was launched through D-Bus
+ * activation, therefore it's already in its own unit */
+ if (pid == 0)
+ return;
+
+ app_name = g_app_info_get_id (info);
+ if (app_name == NULL)
+ app_name = g_app_info_get_executable (info);
+
+ /* Start async request; we don't care about the result */
+ gnome_start_systemd_scope (app_name,
+ pid,
+ NULL,
+ NULL,
+ NULL, NULL, NULL);
+}
+
+/**
+ * shell_global_create_app_launch_context:
+ * @global: A #ShellGlobal
+ * @timestamp: the timestamp for the launch (or 0 for current time)
+ * @workspace: a workspace index, or -1 to indicate the current one
+ *
+ * Create a #GAppLaunchContext set up with the correct timestamp, and
+ * targeted to activate on the current workspace.
+ *
+ * Return value: (transfer full): A new #GAppLaunchContext
+ */
+GAppLaunchContext *
+shell_global_create_app_launch_context (ShellGlobal *global,
+ guint32 timestamp,
+ int workspace)
+{
+ MetaWorkspaceManager *workspace_manager = global->workspace_manager;
+ MetaStartupNotification *sn;
+ MetaLaunchContext *context;
+ MetaWorkspace *ws = NULL;
+
+ sn = meta_display_get_startup_notification (global->meta_display);
+ context = meta_startup_notification_create_launcher (sn);
+
+ if (timestamp == 0)
+ timestamp = shell_global_get_current_time (global);
+ meta_launch_context_set_timestamp (context, timestamp);
+
+ if (workspace < 0)
+ ws = meta_workspace_manager_get_active_workspace (workspace_manager);
+ else
+ ws = meta_workspace_manager_get_workspace_by_index (workspace_manager, workspace);
+
+ meta_launch_context_set_workspace (context, ws);
+
+ g_signal_connect (context,
+ "launched",
+ G_CALLBACK (shell_global_app_launched_cb),
+ NULL);
+
+ return (GAppLaunchContext *) context;
+}
+
+typedef struct
+{
+ ShellLeisureFunction func;
+ gpointer user_data;
+ GDestroyNotify notify;
+} LeisureClosure;
+
+static gboolean
+run_leisure_functions (gpointer data)
+{
+ ShellGlobal *global = data;
+ GSList *closures;
+ GSList *iter;
+
+ global->leisure_function_id = 0;
+
+ /* We started more work since we scheduled the idle */
+ if (global->work_count > 0)
+ return FALSE;
+
+ /* No leisure closures, so we are done */
+ if (global->leisure_closures == NULL)
+ return FALSE;
+
+ closures = global->leisure_closures;
+ global->leisure_closures = NULL;
+
+ for (iter = closures; iter; iter = iter->next)
+ {
+ LeisureClosure *closure = closures->data;
+ closure->func (closure->user_data);
+
+ if (closure->notify)
+ closure->notify (closure->user_data);
+
+ g_free (closure);
+ }
+
+ g_slist_free (closures);
+
+ return FALSE;
+}
+
+static void
+schedule_leisure_functions (ShellGlobal *global)
+{
+ /* This is called when we think we are ready to run leisure functions
+ * by our own accounting. We try to handle other types of business
+ * (like ClutterAnimation) by adding a low priority idle function.
+ *
+ * This won't work properly if the mainloop goes idle waiting for
+ * the vertical blanking interval or waiting for work being done
+ * in another thread.
+ */
+ if (!global->leisure_function_id)
+ {
+ global->leisure_function_id = g_idle_add_full (G_PRIORITY_LOW,
+ run_leisure_functions,
+ global, NULL);
+ g_source_set_name_by_id (global->leisure_function_id, "[gnome-shell] run_leisure_functions");
+ }
+}
+
+/**
+ * shell_global_begin_work:
+ * @global: the #ShellGlobal
+ *
+ * Marks that we are currently doing work. This is used to to track
+ * whether we are busy for the purposes of shell_global_run_at_leisure().
+ * A count is kept and shell_global_end_work() must be called exactly
+ * as many times as shell_global_begin_work().
+ */
+void
+shell_global_begin_work (ShellGlobal *global)
+{
+ global->work_count++;
+}
+
+/**
+ * shell_global_end_work:
+ * @global: the #ShellGlobal
+ *
+ * Marks the end of work that we started with shell_global_begin_work().
+ * If no other work is ongoing and functions have been added with
+ * shell_global_run_at_leisure(), they will be run at the next
+ * opportunity.
+ */
+void
+shell_global_end_work (ShellGlobal *global)
+{
+ g_return_if_fail (global->work_count > 0);
+
+ global->work_count--;
+ if (global->work_count == 0)
+ schedule_leisure_functions (global);
+
+}
+
+/**
+ * shell_global_run_at_leisure:
+ * @global: the #ShellGlobal
+ * @func: function to call at leisure
+ * @user_data: data to pass to @func
+ * @notify: function to call to free @user_data
+ *
+ * Schedules a function to be called the next time the shell is idle.
+ * Idle means here no animations, no redrawing, and no ongoing background
+ * work. Since there is currently no way to hook into the Clutter master
+ * clock and know when is running, the implementation here is somewhat
+ * approximation. Animations may be detected as terminating early if they
+ * can be drawn fast enough so that the event loop goes idle between frames.
+ *
+ * The intent of this function is for performance measurement runs
+ * where a number of actions should be run serially and each action is
+ * timed individually. Using this function for other purposes will
+ * interfere with the ability to use it for performance measurement so
+ * should be avoided.
+ */
+void
+shell_global_run_at_leisure (ShellGlobal *global,
+ ShellLeisureFunction func,
+ gpointer user_data,
+ GDestroyNotify notify)
+{
+ LeisureClosure *closure = g_new (LeisureClosure, 1);
+ closure->func = func;
+ closure->user_data = user_data;
+ closure->notify = notify;
+
+ global->leisure_closures = g_slist_append (global->leisure_closures,
+ closure);
+
+ if (global->work_count == 0)
+ schedule_leisure_functions (global);
+}
+
+const char *
+shell_global_get_session_mode (ShellGlobal *global)
+{
+ g_return_val_if_fail (SHELL_IS_GLOBAL (global), "user");
+
+ return global->session_mode;
+}
+
+static void
+delete_variant_cb (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ ShellGlobal *global = user_data;
+ GError *error = NULL;
+
+ if (!g_file_delete_finish (G_FILE (object), result, &error))
+ {
+ if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED) &&
+ !g_error_matches (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))
+ {
+ g_warning ("Could not delete runtime/persistent state file: %s\n",
+ error->message);
+ }
+
+ g_error_free (error);
+ }
+
+ g_hash_table_remove (global->save_ops, object);
+}
+
+static void
+replace_contents_worker (GTask *task,
+ gpointer source_object,
+ gpointer task_data,
+ GCancellable *cancellable)
+{
+ GFile *file = source_object;
+ GBytes *bytes = task_data;
+ GError *error = NULL;
+ const gchar *data;
+ gsize len;
+
+ data = g_bytes_get_data (bytes, &len);
+
+ if (!g_file_replace_contents (file, data, len, NULL, FALSE,
+ G_FILE_CREATE_REPLACE_DESTINATION,
+ NULL, cancellable, &error))
+ g_task_return_error (task, g_steal_pointer (&error));
+ else
+ g_task_return_boolean (task, TRUE);
+}
+
+static void
+replace_contents_async (GFile *path,
+ GBytes *bytes,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ g_autoptr(GTask) task = NULL;
+
+ g_assert (G_IS_FILE (path));
+ g_assert (bytes != NULL);
+ g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+ task = g_task_new (path, cancellable, callback, user_data);
+ g_task_set_source_tag (task, replace_contents_async);
+ g_task_set_task_data (task, g_bytes_ref (bytes), (GDestroyNotify)g_bytes_unref);
+ g_task_run_in_thread (task, replace_contents_worker);
+}
+
+static gboolean
+replace_contents_finish (GFile *file,
+ GAsyncResult *result,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (result), error);
+}
+
+static void
+replace_variant_cb (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ ShellGlobal *global = user_data;
+ GError *error = NULL;
+
+ if (!replace_contents_finish (G_FILE (object), result, &error))
+ {
+ if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ {
+ g_warning ("Could not replace runtime/persistent state file: %s\n",
+ error->message);
+ }
+
+ g_error_free (error);
+ }
+
+ g_hash_table_remove (global->save_ops, object);
+}
+
+static void
+save_variant (ShellGlobal *global,
+ GFile *dir,
+ const char *property_name,
+ GVariant *variant)
+{
+ GFile *path = g_file_get_child (dir, property_name);
+ GCancellable *cancellable;
+
+ cancellable = g_hash_table_lookup (global->save_ops, path);
+ g_cancellable_cancel (cancellable);
+
+ cancellable = g_cancellable_new ();
+ g_hash_table_insert (global->save_ops, g_object_ref (path), cancellable);
+
+ if (variant == NULL || g_variant_get_data (variant) == NULL)
+ {
+ g_file_delete_async (path, G_PRIORITY_DEFAULT, cancellable,
+ delete_variant_cb, global);
+ }
+ else
+ {
+ g_autoptr(GBytes) bytes = NULL;
+
+ bytes = g_bytes_new_with_free_func (g_variant_get_data (variant),
+ g_variant_get_size (variant),
+ (GDestroyNotify)g_variant_unref,
+ g_variant_ref (variant));
+ /* g_file_replace_contents_async() can potentially fsync() from the
+ * calling thread when completing the asynchronous task. Instead, we
+ * want to force that fsync() to a thread to avoid blocking the
+ * compositor main loop. Using our own replace_contents_async()
+ * simply executes the operation synchronously from a thread.
+ */
+ replace_contents_async (path, bytes, cancellable, replace_variant_cb, global);
+ }
+
+ g_object_unref (path);
+}
+
+static GVariant *
+load_variant (GFile *dir,
+ const char *property_type,
+ const char *property_name)
+{
+ GVariant *res = NULL;
+ GMappedFile *mfile;
+ GFile *path = g_file_get_child (dir, property_name);
+ char *pathstr;
+ GError *local_error = NULL;
+
+ pathstr = g_file_get_path (path);
+ mfile = g_mapped_file_new (pathstr, FALSE, &local_error);
+ if (!mfile)
+ {
+ if (!g_error_matches (local_error, G_FILE_ERROR, G_FILE_ERROR_NOENT))
+ {
+ g_warning ("Failed to open runtime state: %s", local_error->message);
+ }
+ g_clear_error (&local_error);
+ }
+ else
+ {
+ GBytes *bytes = g_mapped_file_get_bytes (mfile);
+ res = g_variant_new_from_bytes (G_VARIANT_TYPE (property_type), bytes, FALSE);
+ g_bytes_unref (bytes);
+ g_mapped_file_unref (mfile);
+ }
+
+ g_object_unref (path);
+ g_free (pathstr);
+
+ return res;
+}
+
+/**
+ * shell_global_set_runtime_state:
+ * @global: a #ShellGlobal
+ * @property_name: Name of the property
+ * @variant: (nullable): A #GVariant, or %NULL to unset
+ *
+ * Change the value of serialized runtime state.
+ */
+void
+shell_global_set_runtime_state (ShellGlobal *global,
+ const char *property_name,
+ GVariant *variant)
+{
+ save_variant (global, global->runtime_state_path, property_name, variant);
+}
+
+/**
+ * shell_global_get_runtime_state:
+ * @global: a #ShellGlobal
+ * @property_type: Expected data type
+ * @property_name: Name of the property
+ *
+ * The shell maintains "runtime" state which does not persist across
+ * logout or reboot.
+ *
+ * Returns: (transfer floating): The value of a serialized property, or %NULL if none stored
+ */
+GVariant *
+shell_global_get_runtime_state (ShellGlobal *global,
+ const char *property_type,
+ const char *property_name)
+{
+ return load_variant (global->runtime_state_path, property_type, property_name);
+}
+
+/**
+ * shell_global_set_persistent_state:
+ * @global: a #ShellGlobal
+ * @property_name: Name of the property
+ * @variant: (nullable): A #GVariant, or %NULL to unset
+ *
+ * Change the value of serialized persistent state.
+ */
+void
+shell_global_set_persistent_state (ShellGlobal *global,
+ const char *property_name,
+ GVariant *variant)
+{
+ save_variant (global, global->userdatadir_path, property_name, variant);
+}
+
+/**
+ * shell_global_get_persistent_state:
+ * @global: a #ShellGlobal
+ * @property_type: Expected data type
+ * @property_name: Name of the property
+ *
+ * The shell maintains "persistent" state which will persist after
+ * logout or reboot.
+ *
+ * Returns: (transfer none): The value of a serialized property, or %NULL if none stored
+ */
+GVariant *
+shell_global_get_persistent_state (ShellGlobal *global,
+ const char *property_type,
+ const char *property_name)
+{
+ return load_variant (global->userdatadir_path, property_type, property_name);
+}
+
+void
+_shell_global_locate_pointer (ShellGlobal *global)
+{
+ g_signal_emit (global, shell_global_signals[LOCATE_POINTER], 0);
+}
diff --git a/src/shell-global.h b/src/shell-global.h
new file mode 100644
index 0000000..8d8238c
--- /dev/null
+++ b/src/shell-global.h
@@ -0,0 +1,94 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+#ifndef __SHELL_GLOBAL_H__
+#define __SHELL_GLOBAL_H__
+
+#include <clutter/clutter.h>
+#include <glib-object.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gtk/gtk.h>
+#include <meta/meta-plugin.h>
+
+G_BEGIN_DECLS
+
+#define SHELL_TYPE_GLOBAL (shell_global_get_type ())
+G_DECLARE_FINAL_TYPE (ShellGlobal, shell_global, SHELL, GLOBAL, GObject)
+
+ShellGlobal *shell_global_get (void);
+
+ClutterStage *shell_global_get_stage (ShellGlobal *global);
+MetaDisplay *shell_global_get_display (ShellGlobal *global);
+GList *shell_global_get_window_actors (ShellGlobal *global);
+GSettings *shell_global_get_settings (ShellGlobal *global);
+guint32 shell_global_get_current_time (ShellGlobal *global);
+MetaWorkspaceManager *shell_global_get_workspace_manager (ShellGlobal *global);
+
+
+/* Input/event handling */
+void shell_global_set_stage_input_region (ShellGlobal *global,
+ GSList *rectangles);
+
+void shell_global_get_pointer (ShellGlobal *global,
+ int *x,
+ int *y,
+ ClutterModifierType *mods);
+
+typedef struct {
+ guint glibc_uordblks;
+
+ guint js_bytes;
+
+ guint gjs_boxed;
+ guint gjs_gobject;
+ guint gjs_function;
+ guint gjs_closure;
+
+ /* 32 bit to avoid js conversion problems with 64 bit */
+ guint last_gc_seconds_ago;
+} ShellMemoryInfo;
+
+/* Run-at-leisure API */
+void shell_global_begin_work (ShellGlobal *global);
+void shell_global_end_work (ShellGlobal *global);
+
+typedef void (*ShellLeisureFunction) (gpointer data);
+
+void shell_global_run_at_leisure (ShellGlobal *global,
+ ShellLeisureFunction func,
+ gpointer user_data,
+ GDestroyNotify notify);
+
+
+/* Misc utilities / Shell API */
+GDBusProxy *
+ shell_global_get_switcheroo_control (ShellGlobal *global);
+
+GAppLaunchContext *
+ shell_global_create_app_launch_context (ShellGlobal *global,
+ guint32 timestamp,
+ int workspace);
+
+void shell_global_notify_error (ShellGlobal *global,
+ const char *msg,
+ const char *details);
+
+void shell_global_reexec_self (ShellGlobal *global);
+
+const char * shell_global_get_session_mode (ShellGlobal *global);
+
+void shell_global_set_runtime_state (ShellGlobal *global,
+ const char *property_name,
+ GVariant *variant);
+GVariant * shell_global_get_runtime_state (ShellGlobal *global,
+ const char *property_type,
+ const char *property_name);
+
+void shell_global_set_persistent_state (ShellGlobal *global,
+ const char *property_name,
+ GVariant *variant);
+GVariant * shell_global_get_persistent_state (ShellGlobal *global,
+ const char *property_type,
+ const char *property_name);
+
+G_END_DECLS
+
+#endif /* __SHELL_GLOBAL_H__ */
diff --git a/src/shell-glsl-effect.c b/src/shell-glsl-effect.c
new file mode 100644
index 0000000..3051e0a
--- /dev/null
+++ b/src/shell-glsl-effect.c
@@ -0,0 +1,205 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+/**
+ * SECTION:shell-glsl-effect
+ * @short_description: An offscreen effect using GLSL
+ *
+ * A #ShellGLSLEffect is a #ClutterOffscreenEffect that allows
+ * running custom GLSL to the vertex and fragment stages of the
+ * graphic pipeline.
+ */
+
+#include "config.h"
+
+#include <cogl/cogl.h>
+#include "shell-glsl-effect.h"
+
+typedef struct _ShellGLSLEffectPrivate ShellGLSLEffectPrivate;
+struct _ShellGLSLEffectPrivate
+{
+ CoglPipeline *pipeline;
+};
+
+G_DEFINE_TYPE_WITH_PRIVATE (ShellGLSLEffect, shell_glsl_effect, CLUTTER_TYPE_OFFSCREEN_EFFECT);
+
+static CoglPipeline *
+shell_glsl_effect_create_pipeline (ClutterOffscreenEffect *effect,
+ CoglTexture *texture)
+{
+ ShellGLSLEffect *self = SHELL_GLSL_EFFECT (effect);
+ ShellGLSLEffectPrivate *priv = shell_glsl_effect_get_instance_private (self);
+
+ cogl_pipeline_set_layer_texture (priv->pipeline, 0, texture);
+
+ return cogl_object_ref (priv->pipeline);
+}
+
+/**
+ * shell_glsl_effect_add_glsl_snippet:
+ * @effect: a #ShellGLSLEffect
+ * @hook: where to insert the code
+ * @declarations: GLSL declarations
+ * @code: GLSL code
+ * @is_replace: whether Cogl code should be replaced by the custom shader
+ *
+ * Adds a GLSL snippet to the pipeline used for drawing the effect texture.
+ * See #CoglSnippet for details.
+ *
+ * This is only valid inside the a call to the build_pipeline() virtual
+ * function.
+ */
+void
+shell_glsl_effect_add_glsl_snippet (ShellGLSLEffect *effect,
+ ShellSnippetHook hook,
+ const char *declarations,
+ const char *code,
+ gboolean is_replace)
+{
+ ShellGLSLEffectClass *klass = SHELL_GLSL_EFFECT_GET_CLASS (effect);
+ CoglSnippet *snippet;
+
+ g_return_if_fail (klass->base_pipeline != NULL);
+
+ if (is_replace)
+ {
+ snippet = cogl_snippet_new ((CoglSnippetHook)hook, declarations, NULL);
+ cogl_snippet_set_replace (snippet, code);
+ }
+ else
+ {
+ snippet = cogl_snippet_new ((CoglSnippetHook)hook, declarations, code);
+ }
+
+ if (hook == SHELL_SNIPPET_HOOK_VERTEX ||
+ hook == SHELL_SNIPPET_HOOK_FRAGMENT)
+ cogl_pipeline_add_snippet (klass->base_pipeline, snippet);
+ else
+ cogl_pipeline_add_layer_snippet (klass->base_pipeline, 0, snippet);
+
+ cogl_object_unref (snippet);
+}
+
+static void
+shell_glsl_effect_dispose (GObject *gobject)
+{
+ ShellGLSLEffect *self = SHELL_GLSL_EFFECT (gobject);
+ ShellGLSLEffectPrivate *priv;
+
+ priv = shell_glsl_effect_get_instance_private (self);
+
+ g_clear_pointer (&priv->pipeline, cogl_object_unref);
+
+ G_OBJECT_CLASS (shell_glsl_effect_parent_class)->dispose (gobject);
+}
+
+static void
+shell_glsl_effect_init (ShellGLSLEffect *effect)
+{
+}
+
+static void
+shell_glsl_effect_constructed (GObject *object)
+{
+ ShellGLSLEffect *self;
+ ShellGLSLEffectClass *klass;
+ ShellGLSLEffectPrivate *priv;
+ CoglContext *ctx =
+ clutter_backend_get_cogl_context (clutter_get_default_backend ());
+
+ G_OBJECT_CLASS (shell_glsl_effect_parent_class)->constructed (object);
+
+ /* Note that, differently from ClutterBlurEffect, we are calling
+ this inside constructed, not init, so klass points to the most-derived
+ GTypeClass, not ShellGLSLEffectClass.
+ */
+ klass = SHELL_GLSL_EFFECT_GET_CLASS (object);
+ self = SHELL_GLSL_EFFECT (object);
+ priv = shell_glsl_effect_get_instance_private (self);
+
+ if (G_UNLIKELY (klass->base_pipeline == NULL))
+ {
+ klass->base_pipeline = cogl_pipeline_new (ctx);
+ cogl_pipeline_set_blend (klass->base_pipeline, "RGBA = ADD (SRC_COLOR * (SRC_COLOR[A]), DST_COLOR * (1-SRC_COLOR[A]))", NULL);
+
+ if (klass->build_pipeline != NULL)
+ klass->build_pipeline (self);
+ }
+
+ priv->pipeline = cogl_pipeline_copy (klass->base_pipeline);
+
+ cogl_pipeline_set_layer_null_texture (klass->base_pipeline, 0);
+}
+
+static void
+shell_glsl_effect_class_init (ShellGLSLEffectClass *klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+ ClutterOffscreenEffectClass *offscreen_class;
+
+ offscreen_class = CLUTTER_OFFSCREEN_EFFECT_CLASS (klass);
+ offscreen_class->create_pipeline = shell_glsl_effect_create_pipeline;
+
+ gobject_class->constructed = shell_glsl_effect_constructed;
+ gobject_class->dispose = shell_glsl_effect_dispose;
+}
+
+/**
+ * shell_glsl_effect_get_uniform_location:
+ * @effect: a #ShellGLSLEffect
+ * @name: the uniform name
+ *
+ * Returns: the location of the uniform named @name, that can be
+ * passed to shell_glsl_effect_set_uniform_float().
+ */
+int
+shell_glsl_effect_get_uniform_location (ShellGLSLEffect *effect,
+ const char *name)
+{
+ ShellGLSLEffectPrivate *priv = shell_glsl_effect_get_instance_private (effect);
+ return cogl_pipeline_get_uniform_location (priv->pipeline, name);
+}
+
+/**
+ * shell_glsl_effect_set_uniform_float:
+ * @effect: a #ShellGLSLEffect
+ * @uniform: the uniform location (as returned by shell_glsl_effect_get_uniform_location())
+ * @n_components: the number of components in the uniform (eg. 3 for a vec3)
+ * @total_count: the total number of floats in @value
+ * @value: (array length=total_count): the array of floats to set @uniform
+ */
+void
+shell_glsl_effect_set_uniform_float (ShellGLSLEffect *effect,
+ int uniform,
+ int n_components,
+ int total_count,
+ const float *value)
+{
+ ShellGLSLEffectPrivate *priv = shell_glsl_effect_get_instance_private (effect);
+ cogl_pipeline_set_uniform_float (priv->pipeline, uniform,
+ n_components, total_count / n_components,
+ value);
+}
+
+/**
+ * shell_glsl_effect_set_uniform_matrix:
+ * @effect: a #ShellGLSLEffect
+ * @uniform: the uniform location (as returned by shell_glsl_effect_get_uniform_location())
+ * @transpose: Whether to transpose the matrix
+ * @dimensions: the number of components in the uniform (eg. 3 for a vec3)
+ * @total_count: the total number of floats in @value
+ * @value: (array length=total_count): the array of floats to set @uniform
+ */
+void
+shell_glsl_effect_set_uniform_matrix (ShellGLSLEffect *effect,
+ int uniform,
+ gboolean transpose,
+ int dimensions,
+ int total_count,
+ const float *value)
+{
+ ShellGLSLEffectPrivate *priv = shell_glsl_effect_get_instance_private (effect);
+ cogl_pipeline_set_uniform_matrix (priv->pipeline, uniform,
+ dimensions,
+ total_count / (dimensions * dimensions),
+ transpose, value);
+}
diff --git a/src/shell-glsl-effect.h b/src/shell-glsl-effect.h
new file mode 100644
index 0000000..3759e71
--- /dev/null
+++ b/src/shell-glsl-effect.h
@@ -0,0 +1,62 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+#ifndef __SHELL_GLSL_EFFECT_H__
+#define __SHELL_GLSL_EFFECT_H__
+
+#include "st.h"
+#include <gtk/gtk.h>
+
+/**
+ * ShellSnippetHook:
+ * Temporary hack to work around Cogl not exporting CoglSnippetHook in
+ * the 1.0 API. Don't use.
+ */
+typedef enum {
+ /* Per pipeline vertex hooks */
+ SHELL_SNIPPET_HOOK_VERTEX = 0,
+ SHELL_SNIPPET_HOOK_VERTEX_TRANSFORM,
+
+ /* Per pipeline fragment hooks */
+ SHELL_SNIPPET_HOOK_FRAGMENT = 2048,
+
+ /* Per layer vertex hooks */
+ SHELL_SNIPPET_HOOK_TEXTURE_COORD_TRANSFORM = 4096,
+
+ /* Per layer fragment hooks */
+ SHELL_SNIPPET_HOOK_LAYER_FRAGMENT = 6144,
+ SHELL_SNIPPET_HOOK_TEXTURE_LOOKUP
+} ShellSnippetHook;
+
+#define SHELL_TYPE_GLSL_EFFECT (shell_glsl_effect_get_type ())
+G_DECLARE_DERIVABLE_TYPE (ShellGLSLEffect, shell_glsl_effect,
+ SHELL, GLSL_EFFECT, ClutterOffscreenEffect)
+
+struct _ShellGLSLEffectClass
+{
+ ClutterOffscreenEffectClass parent_class;
+
+ CoglPipeline *base_pipeline;
+
+ void (*build_pipeline) (ShellGLSLEffect *effect);
+};
+
+void shell_glsl_effect_add_glsl_snippet (ShellGLSLEffect *effect,
+ ShellSnippetHook hook,
+ const char *declarations,
+ const char *code,
+ gboolean is_replace);
+
+int shell_glsl_effect_get_uniform_location (ShellGLSLEffect *effect,
+ const char *name);
+void shell_glsl_effect_set_uniform_float (ShellGLSLEffect *effect,
+ int uniform,
+ int n_components,
+ int total_count,
+ const float *value);
+void shell_glsl_effect_set_uniform_matrix (ShellGLSLEffect *effect,
+ int uniform,
+ gboolean transpose,
+ int dimensions,
+ int total_count,
+ const float *value);
+
+#endif /* __SHELL_GLSL_EFFECT_H__ */
diff --git a/src/shell-gtk-embed.c b/src/shell-gtk-embed.c
new file mode 100644
index 0000000..2ad18a1
--- /dev/null
+++ b/src/shell-gtk-embed.c
@@ -0,0 +1,364 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+#include "config.h"
+
+#include "shell-embedded-window-private.h"
+#include "shell-global.h"
+#include "shell-util.h"
+
+#include <gdk/gdkx.h>
+#include <meta/display.h>
+#include <meta/window.h>
+
+enum {
+ PROP_0,
+
+ PROP_WINDOW
+};
+
+typedef struct _ShellGtkEmbedPrivate ShellGtkEmbedPrivate;
+
+struct _ShellGtkEmbedPrivate
+{
+ ShellEmbeddedWindow *window;
+
+ ClutterActor *window_actor;
+ gulong window_actor_destroyed_handler;
+
+ gulong window_created_handler;
+};
+
+G_DEFINE_TYPE_WITH_PRIVATE (ShellGtkEmbed, shell_gtk_embed, CLUTTER_TYPE_CLONE);
+
+static void shell_gtk_embed_set_window (ShellGtkEmbed *embed,
+ ShellEmbeddedWindow *window);
+
+static void
+shell_gtk_embed_on_window_destroy (GtkWidget *object,
+ ShellGtkEmbed *embed)
+{
+ shell_gtk_embed_set_window (embed, NULL);
+}
+
+static void
+shell_gtk_embed_remove_window_actor (ShellGtkEmbed *embed)
+{
+ ShellGtkEmbedPrivate *priv = shell_gtk_embed_get_instance_private (embed);
+
+ if (priv->window_actor)
+ {
+ g_clear_signal_handler (&priv->window_actor_destroyed_handler,
+ priv->window_actor);
+
+ g_object_unref (priv->window_actor);
+ priv->window_actor = NULL;
+ }
+
+ clutter_clone_set_source (CLUTTER_CLONE (embed), NULL);
+}
+
+static void
+shell_gtk_embed_window_created_cb (MetaDisplay *display,
+ MetaWindow *window,
+ ShellGtkEmbed *embed)
+{
+ ShellGtkEmbedPrivate *priv = shell_gtk_embed_get_instance_private (embed);
+ Window xwindow = meta_window_get_xwindow (window);
+ GdkWindow *gdk_window = gtk_widget_get_window (GTK_WIDGET (priv->window));
+
+ if (gdk_window && xwindow == gdk_x11_window_get_xid (gdk_window))
+ {
+ ClutterActor *window_actor =
+ CLUTTER_ACTOR (meta_window_get_compositor_private (window));
+ GCallback remove_cb = G_CALLBACK (shell_gtk_embed_remove_window_actor);
+ cairo_region_t *empty_region;
+
+ clutter_clone_set_source (CLUTTER_CLONE (embed), window_actor);
+
+ /* We want to explicitly clear the clone source when the window
+ actor is destroyed because otherwise we might end up keeping
+ it alive after it has been disposed. Otherwise this can cause
+ a crash if there is a paint after mutter notices that the top
+ level window has been destroyed, which causes it to dispose
+ the window, and before the tray manager notices that the
+ window is gone which would otherwise reset the window and
+ unref the clone */
+ priv->window_actor = g_object_ref (window_actor);
+ priv->window_actor_destroyed_handler =
+ g_signal_connect_swapped (window_actor,
+ "destroy",
+ remove_cb,
+ embed);
+
+ /* Hide the original actor otherwise it will appear in the scene
+ as a normal window */
+ clutter_actor_set_opacity (window_actor, 0);
+
+ /* Also make sure it (or any of its children) doesn't block
+ events on wayland */
+ shell_util_set_hidden_from_pick (window_actor, TRUE);
+
+ /* Set an empty input shape on the window so that it can't get
+ any input. This probably isn't the ideal way to achieve this.
+ It would probably be better to force the window to go behind
+ Mutter's guard window, but this is quite difficult to do as
+ Mutter doesn't manage the stacking for override redirect
+ windows and the guard window is repeatedly lowered to the
+ bottom of the stack. */
+ empty_region = cairo_region_create ();
+ gdk_window_input_shape_combine_region (gdk_window,
+ empty_region,
+ 0, 0 /* offset x/y */);
+ cairo_region_destroy (empty_region);
+
+ gdk_window_lower (gdk_window);
+
+ /* Now that we've found the window we don't need to listen for
+ new windows anymore */
+ g_clear_signal_handler (&priv->window_created_handler,
+ display);
+ }
+}
+
+static void
+shell_gtk_embed_on_window_mapped (GtkWidget *object,
+ ShellGtkEmbed *embed)
+{
+ ShellGtkEmbedPrivate *priv = shell_gtk_embed_get_instance_private (embed);
+ MetaDisplay *display = shell_global_get_display (shell_global_get ());
+
+ if (priv->window_created_handler == 0 && priv->window_actor == NULL)
+ /* Listen for new windows so we can detect when Mutter has
+ created a MutterWindow for this window */
+ priv->window_created_handler =
+ g_signal_connect (display,
+ "window-created",
+ G_CALLBACK (shell_gtk_embed_window_created_cb),
+ embed);
+}
+
+static void
+shell_gtk_embed_set_window (ShellGtkEmbed *embed,
+ ShellEmbeddedWindow *window)
+{
+ ShellGtkEmbedPrivate *priv = shell_gtk_embed_get_instance_private (embed);
+ MetaDisplay *display = shell_global_get_display (shell_global_get ());
+
+ if (priv->window)
+ {
+ g_clear_signal_handler (&priv->window_created_handler, display);
+
+ shell_gtk_embed_remove_window_actor (embed);
+
+ _shell_embedded_window_set_actor (priv->window, NULL);
+
+ g_object_unref (priv->window);
+
+ g_signal_handlers_disconnect_by_func (priv->window,
+ (gpointer)shell_gtk_embed_on_window_destroy,
+ embed);
+
+ g_signal_handlers_disconnect_by_func (priv->window,
+ (gpointer)shell_gtk_embed_on_window_mapped,
+ embed);
+ }
+
+ priv->window = window;
+
+ if (priv->window)
+ {
+ g_object_ref (priv->window);
+
+ _shell_embedded_window_set_actor (priv->window, embed);
+
+ g_signal_connect (priv->window, "destroy",
+ G_CALLBACK (shell_gtk_embed_on_window_destroy), embed);
+
+ g_signal_connect (priv->window, "map",
+ G_CALLBACK (shell_gtk_embed_on_window_mapped), embed);
+ }
+
+ clutter_actor_queue_relayout (CLUTTER_ACTOR (embed));
+}
+
+static void
+shell_gtk_embed_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ ShellGtkEmbed *embed = SHELL_GTK_EMBED (object);
+
+ switch (prop_id)
+ {
+ case PROP_WINDOW:
+ shell_gtk_embed_set_window (embed, (ShellEmbeddedWindow *)g_value_get_object (value));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+shell_gtk_embed_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ ShellGtkEmbed *embed = SHELL_GTK_EMBED (object);
+ ShellGtkEmbedPrivate *priv = shell_gtk_embed_get_instance_private (embed);
+
+ switch (prop_id)
+ {
+ case PROP_WINDOW:
+ g_value_set_object (value, priv->window);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+shell_gtk_embed_get_preferred_width (ClutterActor *actor,
+ float for_height,
+ float *min_width_p,
+ float *natural_width_p)
+{
+ ShellGtkEmbed *embed = SHELL_GTK_EMBED (actor);
+ ShellGtkEmbedPrivate *priv = shell_gtk_embed_get_instance_private (embed);
+
+ if (priv->window
+ && gtk_widget_get_visible (GTK_WIDGET (priv->window)))
+ {
+ GtkRequisition min_req, natural_req;
+ gtk_widget_get_preferred_size (GTK_WIDGET (priv->window), &min_req, &natural_req);
+
+ *min_width_p = min_req.width;
+ *natural_width_p = natural_req.width;
+ }
+ else
+ *min_width_p = *natural_width_p = 0;
+}
+
+static void
+shell_gtk_embed_get_preferred_height (ClutterActor *actor,
+ float for_width,
+ float *min_height_p,
+ float *natural_height_p)
+{
+ ShellGtkEmbed *embed = SHELL_GTK_EMBED (actor);
+ ShellGtkEmbedPrivate *priv = shell_gtk_embed_get_instance_private (embed);
+
+ if (priv->window
+ && gtk_widget_get_visible (GTK_WIDGET (priv->window)))
+ {
+ GtkRequisition min_req, natural_req;
+ gtk_widget_get_preferred_size (GTK_WIDGET (priv->window), &min_req, &natural_req);
+
+ *min_height_p = min_req.height;
+ *natural_height_p = natural_req.height;
+ }
+ else
+ *min_height_p = *natural_height_p = 0;
+}
+
+static void
+shell_gtk_embed_allocate (ClutterActor *actor,
+ const ClutterActorBox *box)
+{
+ ShellGtkEmbed *embed = SHELL_GTK_EMBED (actor);
+ ShellGtkEmbedPrivate *priv = shell_gtk_embed_get_instance_private (embed);
+ float wx, wy;
+
+ CLUTTER_ACTOR_CLASS (shell_gtk_embed_parent_class)->
+ allocate (actor, box);
+
+ /* Find the actor's new coordinates in terms of the stage (which is
+ * priv->window's parent window.
+ */
+ clutter_actor_get_transformed_position (actor, &wx, &wy);
+
+ _shell_embedded_window_allocate (priv->window,
+ (int)(0.5 + wx), (int)(0.5 + wy),
+ box->x2 - box->x1,
+ box->y2 - box->y1);
+}
+
+static void
+shell_gtk_embed_map (ClutterActor *actor)
+{
+ ShellGtkEmbed *embed = SHELL_GTK_EMBED (actor);
+ ShellGtkEmbedPrivate *priv = shell_gtk_embed_get_instance_private (embed);
+
+ _shell_embedded_window_map (priv->window);
+
+ CLUTTER_ACTOR_CLASS (shell_gtk_embed_parent_class)->map (actor);
+}
+
+static void
+shell_gtk_embed_unmap (ClutterActor *actor)
+{
+ ShellGtkEmbed *embed = SHELL_GTK_EMBED (actor);
+ ShellGtkEmbedPrivate *priv = shell_gtk_embed_get_instance_private (embed);
+
+ _shell_embedded_window_unmap (priv->window);
+
+ CLUTTER_ACTOR_CLASS (shell_gtk_embed_parent_class)->unmap (actor);
+}
+
+static void
+shell_gtk_embed_dispose (GObject *object)
+{
+ ShellGtkEmbed *embed = SHELL_GTK_EMBED (object);
+
+ G_OBJECT_CLASS (shell_gtk_embed_parent_class)->dispose (object);
+
+ shell_gtk_embed_set_window (embed, NULL);
+}
+
+static void
+shell_gtk_embed_class_init (ShellGtkEmbedClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ ClutterActorClass *actor_class = CLUTTER_ACTOR_CLASS (klass);
+
+ object_class->get_property = shell_gtk_embed_get_property;
+ object_class->set_property = shell_gtk_embed_set_property;
+ object_class->dispose = shell_gtk_embed_dispose;
+
+ actor_class->get_preferred_width = shell_gtk_embed_get_preferred_width;
+ actor_class->get_preferred_height = shell_gtk_embed_get_preferred_height;
+ actor_class->allocate = shell_gtk_embed_allocate;
+ actor_class->map = shell_gtk_embed_map;
+ actor_class->unmap = shell_gtk_embed_unmap;
+
+ g_object_class_install_property (object_class,
+ PROP_WINDOW,
+ g_param_spec_object ("window",
+ "Window",
+ "ShellEmbeddedWindow to embed",
+ SHELL_TYPE_EMBEDDED_WINDOW,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+}
+
+static void
+shell_gtk_embed_init (ShellGtkEmbed *embed)
+{
+}
+
+/*
+ * Public API
+ */
+ClutterActor *
+shell_gtk_embed_new (ShellEmbeddedWindow *window)
+{
+ g_return_val_if_fail (SHELL_IS_EMBEDDED_WINDOW (window), NULL);
+
+ return g_object_new (SHELL_TYPE_GTK_EMBED,
+ "window", window,
+ NULL);
+}
diff --git a/src/shell-gtk-embed.h b/src/shell-gtk-embed.h
new file mode 100644
index 0000000..4cfc489
--- /dev/null
+++ b/src/shell-gtk-embed.h
@@ -0,0 +1,20 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+#ifndef __SHELL_GTK_EMBED_H__
+#define __SHELL_GTK_EMBED_H__
+
+#include <clutter/clutter.h>
+
+#include "shell-embedded-window.h"
+
+#define SHELL_TYPE_GTK_EMBED (shell_gtk_embed_get_type ())
+G_DECLARE_DERIVABLE_TYPE (ShellGtkEmbed, shell_gtk_embed,
+ SHELL, GTK_EMBED, ClutterClone)
+
+struct _ShellGtkEmbedClass
+{
+ ClutterCloneClass parent_class;
+};
+
+ClutterActor *shell_gtk_embed_new (ShellEmbeddedWindow *window);
+
+#endif /* __SHELL_GTK_EMBED_H__ */
diff --git a/src/shell-invert-lightness-effect.c b/src/shell-invert-lightness-effect.c
new file mode 100644
index 0000000..f856ede
--- /dev/null
+++ b/src/shell-invert-lightness-effect.c
@@ -0,0 +1,152 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * Copyright (C) 2010-2012 Inclusive Design Research Centre, OCAD University.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Author:
+ * Joseph Scheuhammer <clown@alum.mit.edu>
+ */
+
+/**
+ * SECTION:shell-invert-lightness-effect
+ * @short_description: A colorization effect where lightness is inverted but
+ * color is not.
+ * @see_also: #ClutterEffect, #ClutterOffscreenEffect
+ *
+ * #ShellInvertLightnessEffect is a sub-class of #ClutterEffect that enhances
+ * the appearance of a clutter actor. Specifically it inverts the lightness
+ * of a #ClutterActor (e.g., darker colors become lighter, white becomes black,
+ * and white, black).
+ */
+
+#define SHELL_INVERT_LIGHTNESS_EFFECT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), SHELL_TYPE_INVERT_LIGHTNESS_EFFECT, ShellInvertLightnessEffectClass))
+#define SHELL_IS_INVERT_EFFECT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), SHELL_TYPE_INVERT_LIGHTNESS_EFFECT))
+#define SHELL_INVERT_LIGHTNESS_EFFECT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), SHELL_TYPE_INVERT_LIGHTNESS_EFFEC, ShellInvertLightnessEffectClass))
+
+#include "shell-invert-lightness-effect.h"
+
+#include <cogl/cogl.h>
+
+struct _ShellInvertLightnessEffect
+{
+ ClutterOffscreenEffect parent_instance;
+
+ CoglPipeline *pipeline;
+};
+
+struct _ShellInvertLightnessEffectClass
+{
+ ClutterOffscreenEffectClass parent_class;
+
+ CoglPipeline *base_pipeline;
+};
+
+/* Lightness inversion in GLSL.
+ */
+static const gchar *invert_lightness_source =
+ "cogl_texel = texture2D (cogl_sampler, cogl_tex_coord.st);\n"
+ "vec3 effect = vec3 (cogl_texel);\n"
+ "\n"
+ "float maxColor = max (cogl_texel.r, max (cogl_texel.g, cogl_texel.b));\n"
+ "float minColor = min (cogl_texel.r, min (cogl_texel.g, cogl_texel.b));\n"
+ "float lightness = (maxColor + minColor) / 2.0;\n"
+ "\n"
+ "float delta = (1.0 - lightness) - lightness;\n"
+ "effect.rgb = (effect.rgb + delta);\n"
+ "\n"
+ "cogl_texel = vec4 (effect, cogl_texel.a);\n";
+
+G_DEFINE_TYPE (ShellInvertLightnessEffect,
+ shell_invert_lightness_effect,
+ CLUTTER_TYPE_OFFSCREEN_EFFECT);
+
+static CoglPipeline *
+shell_glsl_effect_create_pipeline (ClutterOffscreenEffect *effect,
+ CoglTexture *texture)
+{
+ ShellInvertLightnessEffect *self = SHELL_INVERT_LIGHTNESS_EFFECT (effect);
+
+ cogl_pipeline_set_layer_texture (self->pipeline, 0, texture);
+
+ return cogl_object_ref (self->pipeline);
+}
+
+static void
+shell_invert_lightness_effect_dispose (GObject *gobject)
+{
+ ShellInvertLightnessEffect *self = SHELL_INVERT_LIGHTNESS_EFFECT (gobject);
+
+ if (self->pipeline != NULL)
+ {
+ cogl_object_unref (self->pipeline);
+ self->pipeline = NULL;
+ }
+
+ G_OBJECT_CLASS (shell_invert_lightness_effect_parent_class)->dispose (gobject);
+}
+
+static void
+shell_invert_lightness_effect_class_init (ShellInvertLightnessEffectClass *klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+ ClutterOffscreenEffectClass *offscreen_class;
+
+ offscreen_class = CLUTTER_OFFSCREEN_EFFECT_CLASS (klass);
+ offscreen_class->create_pipeline = shell_glsl_effect_create_pipeline;
+
+ gobject_class->dispose = shell_invert_lightness_effect_dispose;
+}
+
+static void
+shell_invert_lightness_effect_init (ShellInvertLightnessEffect *self)
+{
+ ShellInvertLightnessEffectClass *klass;
+ klass = SHELL_INVERT_LIGHTNESS_EFFECT_GET_CLASS (self);
+
+ if (G_UNLIKELY (klass->base_pipeline == NULL))
+ {
+ CoglSnippet *snippet;
+ CoglContext *ctx =
+ clutter_backend_get_cogl_context (clutter_get_default_backend ());
+
+ klass->base_pipeline = cogl_pipeline_new (ctx);
+
+ snippet = cogl_snippet_new (COGL_SNIPPET_HOOK_TEXTURE_LOOKUP,
+ NULL,
+ NULL);
+ cogl_snippet_set_replace (snippet, invert_lightness_source);
+ cogl_pipeline_add_layer_snippet (klass->base_pipeline, 0, snippet);
+ cogl_object_unref (snippet);
+
+ cogl_pipeline_set_layer_null_texture (klass->base_pipeline, 0);
+ }
+
+ self->pipeline = cogl_pipeline_copy (klass->base_pipeline);
+}
+
+/**
+ * shell_invert_lightness_effect_new:
+ *
+ * Creates a new #ShellInvertLightnessEffect to be used with
+ * clutter_actor_add_effect()
+ *
+ * Return value: (transfer full): the newly created
+ * #ShellInvertLightnessEffect or %NULL. Use g_object_unref() when done.
+ */
+ClutterEffect *
+shell_invert_lightness_effect_new (void)
+{
+ return g_object_new (SHELL_TYPE_INVERT_LIGHTNESS_EFFECT, NULL);
+}
diff --git a/src/shell-invert-lightness-effect.h b/src/shell-invert-lightness-effect.h
new file mode 100644
index 0000000..3d7cf3a
--- /dev/null
+++ b/src/shell-invert-lightness-effect.h
@@ -0,0 +1,41 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * Copyright © 2010-2012 Inclusive Design Research Centre, OCAD University.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Author:
+ * Joseph Scheuhammer <clown@alum.mit.edu>
+ */
+#ifndef __SHELL_INVERT_LIGHTNESS_EFFECT_H__
+#define __SHELL_INVERT_LIGHTNESS_EFFECT_H__
+
+#include <clutter/clutter.h>
+
+G_BEGIN_DECLS
+
+#define SHELL_TYPE_INVERT_LIGHTNESS_EFFECT (shell_invert_lightness_effect_get_type ())
+#define SHELL_INVERT_LIGHTNESS_EFFECT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), SHELL_TYPE_INVERT_LIGHTNESS_EFFECT, ShellInvertLightnessEffect))
+#define SHELL_IS_INVERT_LIGHTNESS_EFFECT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SHELL_TYPE_INVERT_LIGHTNESS_EFFECT))
+
+typedef struct _ShellInvertLightnessEffect ShellInvertLightnessEffect;
+typedef struct _ShellInvertLightnessEffectClass ShellInvertLightnessEffectClass;
+
+GType shell_invert_lightness_effect_get_type (void) G_GNUC_CONST;
+
+ClutterEffect *shell_invert_lightness_effect_new (void);
+
+G_END_DECLS
+
+#endif /* __SHELL_INVERT_LIGHTNESS_EFFECT_H__ */
diff --git a/src/shell-keyring-prompt.c b/src/shell-keyring-prompt.c
new file mode 100644
index 0000000..bb03279
--- /dev/null
+++ b/src/shell-keyring-prompt.c
@@ -0,0 +1,832 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * Copyright 2012 Red Hat, Inc.
+ * 2012 Stef Walter <stefw@gnome.org>
+ *
+ * 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.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Author: Stef Walter <stefw@gnome.org>
+ */
+
+#include "config.h"
+
+#include "shell-keyring-prompt.h"
+#include "shell-secure-text-buffer.h"
+
+#define GCR_API_SUBJECT_TO_CHANGE
+#include <gcr/gcr.h>
+
+#include <glib/gi18n.h>
+
+#include <string.h>
+
+typedef struct _ShellPasswordPromptPrivate ShellPasswordPromptPrivate;
+
+typedef enum
+{
+ PROMPTING_NONE,
+ PROMPTING_FOR_CONFIRM,
+ PROMPTING_FOR_PASSWORD
+} PromptingMode;
+
+struct _ShellKeyringPrompt
+{
+ GObject parent;
+
+ gchar *title;
+ gchar *message;
+ gchar *description;
+ gchar *warning;
+ gchar *choice_label;
+ gboolean choice_chosen;
+ gboolean password_new;
+ guint password_strength;
+ gchar *continue_label;
+ gchar *cancel_label;
+
+ GTask *task;
+ ClutterText *password_actor;
+ ClutterText *confirm_actor;
+ PromptingMode mode;
+ gboolean shown;
+};
+
+enum {
+ PROP_0,
+
+ PROP_PASSWORD_VISIBLE,
+ PROP_CONFIRM_VISIBLE,
+ PROP_WARNING_VISIBLE,
+ PROP_CHOICE_VISIBLE,
+ PROP_PASSWORD_ACTOR,
+ PROP_CONFIRM_ACTOR,
+
+ N_PROPS,
+
+ /* GcrPrompt */
+ PROP_TITLE,
+ PROP_MESSAGE,
+ PROP_DESCRIPTION,
+ PROP_WARNING,
+ PROP_CHOICE_LABEL,
+ PROP_CHOICE_CHOSEN,
+ PROP_PASSWORD_NEW,
+ PROP_PASSWORD_STRENGTH,
+ PROP_CALLER_WINDOW,
+ PROP_CONTINUE_LABEL,
+ PROP_CANCEL_LABEL
+};
+
+static GParamSpec *props[N_PROPS] = { NULL, };
+
+static void shell_keyring_prompt_iface (GcrPromptInterface *iface);
+
+G_DEFINE_TYPE_WITH_CODE (ShellKeyringPrompt, shell_keyring_prompt, G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (GCR_TYPE_PROMPT, shell_keyring_prompt_iface);
+);
+
+enum {
+ SIGNAL_SHOW_PASSWORD,
+ SIGNAL_SHOW_CONFIRM,
+ SIGNAL_LAST
+};
+
+static gint signals[SIGNAL_LAST];
+
+static void
+shell_keyring_prompt_init (ShellKeyringPrompt *self)
+{
+
+}
+
+static gchar *
+remove_mnemonics (const GValue *value)
+{
+ const gchar mnemonic = '_';
+ gchar *stripped_label, *temp;
+ const gchar *label;
+
+ g_return_val_if_fail (value != NULL, NULL);
+ g_return_val_if_fail (G_VALUE_HOLDS_STRING (value), NULL);
+
+ label = g_value_get_string (value);
+ if (!label)
+ return NULL;
+
+ /* Stripped label will have the original label length at most */
+ stripped_label = temp = g_new (gchar, strlen(label) + 1);
+ g_assert (stripped_label != NULL);
+
+ while (*label != '\0')
+ {
+ if (*label == mnemonic)
+ label++;
+ *(temp++) = *(label++);
+ }
+ *temp = '\0';
+
+ return stripped_label;
+}
+
+static void
+shell_keyring_prompt_set_property (GObject *obj,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ ShellKeyringPrompt *self = SHELL_KEYRING_PROMPT (obj);
+
+ switch (prop_id) {
+ case PROP_TITLE:
+ g_free (self->title);
+ self->title = g_value_dup_string (value);
+ g_object_notify (obj, "title");
+ break;
+ case PROP_MESSAGE:
+ g_free (self->message);
+ self->message = g_value_dup_string (value);
+ g_object_notify (obj, "message");
+ break;
+ case PROP_DESCRIPTION:
+ g_free (self->description);
+ self->description = g_value_dup_string (value);
+ g_object_notify (obj, "description");
+ break;
+ case PROP_WARNING:
+ g_free (self->warning);
+ self->warning = g_value_dup_string (value);
+ if (!self->warning)
+ self->warning = g_strdup ("");
+ g_object_notify (obj, "warning");
+ g_object_notify_by_pspec (obj, props[PROP_WARNING_VISIBLE]);
+ break;
+ case PROP_CHOICE_LABEL:
+ g_free (self->choice_label);
+ self->choice_label = remove_mnemonics (value);
+ if (!self->choice_label)
+ self->choice_label = g_strdup ("");
+ g_object_notify (obj, "choice-label");
+ g_object_notify_by_pspec (obj, props[PROP_CHOICE_VISIBLE]);
+ break;
+ case PROP_CHOICE_CHOSEN:
+ self->choice_chosen = g_value_get_boolean (value);
+ g_object_notify (obj, "choice-chosen");
+ break;
+ case PROP_PASSWORD_NEW:
+ self->password_new = g_value_get_boolean (value);
+ g_object_notify (obj, "password-new");
+ g_object_notify_by_pspec (obj, props[PROP_CONFIRM_VISIBLE]);
+ break;
+ case PROP_CALLER_WINDOW:
+ /* ignored */
+ break;
+ case PROP_CONTINUE_LABEL:
+ g_free (self->continue_label);
+ self->continue_label = remove_mnemonics (value);
+ g_object_notify (obj, "continue-label");
+ break;
+ case PROP_CANCEL_LABEL:
+ g_free (self->cancel_label);
+ self->cancel_label = remove_mnemonics (value);
+ g_object_notify (obj, "cancel-label");
+ break;
+ case PROP_PASSWORD_ACTOR:
+ shell_keyring_prompt_set_password_actor (self, g_value_get_object (value));
+ break;
+ case PROP_CONFIRM_ACTOR:
+ shell_keyring_prompt_set_confirm_actor (self, g_value_get_object (value));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+shell_keyring_prompt_get_property (GObject *obj,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ ShellKeyringPrompt *self = SHELL_KEYRING_PROMPT (obj);
+
+ switch (prop_id) {
+ case PROP_TITLE:
+ g_value_set_string (value, self->title ? self->title : "");
+ break;
+ case PROP_MESSAGE:
+ g_value_set_string (value, self->message ? self->message : "");
+ break;
+ case PROP_DESCRIPTION:
+ g_value_set_string (value, self->description ? self->description : "");
+ break;
+ case PROP_WARNING:
+ g_value_set_string (value, self->warning ? self->warning : "");
+ break;
+ case PROP_CHOICE_LABEL:
+ g_value_set_string (value, self->choice_label ? self->choice_label : "");
+ break;
+ case PROP_CHOICE_CHOSEN:
+ g_value_set_boolean (value, self->choice_chosen);
+ break;
+ case PROP_PASSWORD_NEW:
+ g_value_set_boolean (value, self->password_new);
+ break;
+ case PROP_PASSWORD_STRENGTH:
+ g_value_set_int (value, self->password_strength);
+ break;
+ case PROP_CALLER_WINDOW:
+ g_value_set_string (value, "");
+ break;
+ case PROP_CONTINUE_LABEL:
+ g_value_set_string (value, self->continue_label);
+ break;
+ case PROP_CANCEL_LABEL:
+ g_value_set_string (value, self->cancel_label);
+ break;
+ case PROP_PASSWORD_VISIBLE:
+ g_value_set_boolean (value, self->mode == PROMPTING_FOR_PASSWORD);
+ break;
+ case PROP_CONFIRM_VISIBLE:
+ g_value_set_boolean (value, self->password_new &&
+ self->mode == PROMPTING_FOR_PASSWORD);
+ break;
+ case PROP_WARNING_VISIBLE:
+ g_value_set_boolean (value, self->warning && self->warning[0]);
+ break;
+ case PROP_CHOICE_VISIBLE:
+ g_value_set_boolean (value, self->choice_label && self->choice_label[0]);
+ break;
+ case PROP_PASSWORD_ACTOR:
+ g_value_set_object (value, shell_keyring_prompt_get_password_actor (self));
+ break;
+ case PROP_CONFIRM_ACTOR:
+ g_value_set_object (value, shell_keyring_prompt_get_confirm_actor (self));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+shell_keyring_prompt_dispose (GObject *obj)
+{
+ ShellKeyringPrompt *self = SHELL_KEYRING_PROMPT (obj);
+
+ if (self->shown)
+ gcr_prompt_close (GCR_PROMPT (self));
+
+ if (self->task)
+ shell_keyring_prompt_cancel (self);
+ g_assert (self->task == NULL);
+
+ shell_keyring_prompt_set_password_actor (self, NULL);
+ shell_keyring_prompt_set_confirm_actor (self, NULL);
+
+ G_OBJECT_CLASS (shell_keyring_prompt_parent_class)->dispose (obj);
+}
+
+static void
+shell_keyring_prompt_finalize (GObject *obj)
+{
+ ShellKeyringPrompt *self = SHELL_KEYRING_PROMPT (obj);
+
+ g_free (self->title);
+ g_free (self->message);
+ g_free (self->description);
+ g_free (self->warning);
+ g_free (self->choice_label);
+ g_free (self->continue_label);
+ g_free (self->cancel_label);
+
+ G_OBJECT_CLASS (shell_keyring_prompt_parent_class)->finalize (obj);
+}
+
+static void
+shell_keyring_prompt_class_init (ShellKeyringPromptClass *klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+ gobject_class->get_property = shell_keyring_prompt_get_property;
+ gobject_class->set_property = shell_keyring_prompt_set_property;
+ gobject_class->dispose = shell_keyring_prompt_dispose;
+ gobject_class->finalize = shell_keyring_prompt_finalize;
+
+ g_object_class_override_property (gobject_class, PROP_TITLE, "title");
+
+ g_object_class_override_property (gobject_class, PROP_MESSAGE, "message");
+
+ g_object_class_override_property (gobject_class, PROP_DESCRIPTION, "description");
+
+ g_object_class_override_property (gobject_class, PROP_WARNING, "warning");
+
+ g_object_class_override_property (gobject_class, PROP_PASSWORD_NEW, "password-new");
+
+ g_object_class_override_property (gobject_class, PROP_PASSWORD_STRENGTH, "password-strength");
+
+ g_object_class_override_property (gobject_class, PROP_CHOICE_LABEL, "choice-label");
+
+ g_object_class_override_property (gobject_class, PROP_CHOICE_CHOSEN, "choice-chosen");
+
+ g_object_class_override_property (gobject_class, PROP_CALLER_WINDOW, "caller-window");
+
+ g_object_class_override_property (gobject_class, PROP_CONTINUE_LABEL, "continue-label");
+
+ g_object_class_override_property (gobject_class, PROP_CANCEL_LABEL, "cancel-label");
+
+ /**
+ * ShellKeyringPrompt:password-visible:
+ *
+ * Whether the password entry is visible or not.
+ */
+ props[PROP_PASSWORD_VISIBLE] =
+ g_param_spec_boolean ("password-visible",
+ "Password visible",
+ "Password field is visible",
+ FALSE,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+ /**
+ * ShellKeyringPrompt:confirm-visible:
+ *
+ * Whether the password confirm entry is visible or not.
+ */
+ props[PROP_CONFIRM_VISIBLE] =
+ g_param_spec_boolean ("confirm-visible",
+ "Confirm visible",
+ "Confirm field is visible",
+ FALSE,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+ /**
+ * ShellKeyringPrompt:warning-visible:
+ *
+ * Whether the warning label is visible or not.
+ */
+ props[PROP_WARNING_VISIBLE] =
+ g_param_spec_boolean ("warning-visible",
+ "Warning visible",
+ "Warning is visible",
+ FALSE,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+ /**
+ * ShellKeyringPrompt:choice-visible:
+ *
+ * Whether the choice check box is visible or not.
+ */
+ props[PROP_CHOICE_VISIBLE] =
+ g_param_spec_boolean ("choice-visible",
+ "Choice visible",
+ "Choice is visible",
+ FALSE,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+ /**
+ * ShellKeyringPrompt:password-actor:
+ *
+ * Text field for password
+ */
+ props[PROP_PASSWORD_ACTOR] =
+ g_param_spec_object ("password-actor",
+ "Password actor",
+ "Text field for password",
+ CLUTTER_TYPE_TEXT,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY);
+
+ /**
+ * ShellKeyringPrompt:confirm-actor:
+ *
+ * Text field for confirmation password
+ */
+ props[PROP_CONFIRM_ACTOR] =
+ g_param_spec_object ("confirm-actor",
+ "Confirm actor",
+ "Text field for confirming password",
+ CLUTTER_TYPE_TEXT,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY);
+
+ g_object_class_install_properties (gobject_class, N_PROPS, props);
+
+ signals[SIGNAL_SHOW_PASSWORD] = g_signal_new ("show-password", G_TYPE_FROM_CLASS (klass),
+ 0, 0, NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ signals[SIGNAL_SHOW_CONFIRM] = g_signal_new ("show-confirm", G_TYPE_FROM_CLASS (klass),
+ 0, 0, NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+}
+
+static void
+shell_keyring_prompt_password_async (GcrPrompt *prompt,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ ShellKeyringPrompt *self = SHELL_KEYRING_PROMPT (prompt);
+ GObject *obj;
+
+ if (self->task != NULL) {
+ g_warning ("this prompt can only show one prompt at a time");
+ return;
+ }
+
+ self->mode = PROMPTING_FOR_PASSWORD;
+ self->task = g_task_new (self, NULL, callback, user_data);
+ g_task_set_source_tag (self->task, shell_keyring_prompt_password_async);
+
+ obj = G_OBJECT (self);
+ g_object_notify (obj, "password-visible");
+ g_object_notify (obj, "confirm-visible");
+ g_object_notify (obj, "warning-visible");
+ g_object_notify (obj, "choice-visible");
+
+ self->shown = TRUE;
+ g_signal_emit (self, signals[SIGNAL_SHOW_PASSWORD], 0);
+}
+
+static const gchar *
+shell_keyring_prompt_password_finish (GcrPrompt *prompt,
+ GAsyncResult *result,
+ GError **error)
+{
+ g_return_val_if_fail (g_task_get_source_object (G_TASK (result)) == prompt, NULL);
+ g_return_val_if_fail (g_async_result_is_tagged (result,
+ shell_keyring_prompt_password_async), NULL);
+
+ return g_task_propagate_pointer (G_TASK (result), error);
+}
+
+static void
+shell_keyring_prompt_confirm_async (GcrPrompt *prompt,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ ShellKeyringPrompt *self = SHELL_KEYRING_PROMPT (prompt);
+ GObject *obj;
+
+ if (self->task != NULL) {
+ g_warning ("this prompt is already prompting");
+ return;
+ }
+
+ self->mode = PROMPTING_FOR_CONFIRM;
+ self->task = g_task_new (self, NULL, callback, user_data);
+ g_task_set_source_tag (self->task, shell_keyring_prompt_confirm_async);
+
+ obj = G_OBJECT (self);
+ g_object_notify (obj, "password-visible");
+ g_object_notify (obj, "confirm-visible");
+ g_object_notify (obj, "warning-visible");
+ g_object_notify (obj, "choice-visible");
+
+ self->shown = TRUE;
+ g_signal_emit (self, signals[SIGNAL_SHOW_CONFIRM], 0);
+}
+
+static GcrPromptReply
+shell_keyring_prompt_confirm_finish (GcrPrompt *prompt,
+ GAsyncResult *result,
+ GError **error)
+{
+ GTask *task = G_TASK (result);
+ gssize res;
+
+ g_return_val_if_fail (g_task_get_source_object (task) == prompt,
+ GCR_PROMPT_REPLY_CANCEL);
+ g_return_val_if_fail (g_async_result_is_tagged (result,
+ shell_keyring_prompt_confirm_async), GCR_PROMPT_REPLY_CANCEL);
+
+ res = g_task_propagate_int (task, error);
+ return res == -1 ? GCR_PROMPT_REPLY_CANCEL : (GcrPromptReply)res;
+}
+
+static void
+shell_keyring_prompt_close (GcrPrompt *prompt)
+{
+ ShellKeyringPrompt *self = SHELL_KEYRING_PROMPT (prompt);
+
+ /*
+ * We expect keyring.js to connect to this signal and do the
+ * actual work of closing the prompt.
+ */
+
+ self->shown = FALSE;
+}
+
+static void
+shell_keyring_prompt_iface (GcrPromptInterface *iface)
+{
+ iface->prompt_password_async = shell_keyring_prompt_password_async;
+ iface->prompt_password_finish = shell_keyring_prompt_password_finish;
+ iface->prompt_confirm_async = shell_keyring_prompt_confirm_async;
+ iface->prompt_confirm_finish = shell_keyring_prompt_confirm_finish;
+ iface->prompt_close = shell_keyring_prompt_close;
+}
+
+/**
+ * shell_keyring_prompt_new:
+ *
+ * Create new internal prompt base
+ *
+ * Returns: (transfer full): new internal prompt
+ */
+ShellKeyringPrompt *
+shell_keyring_prompt_new (void)
+{
+ return g_object_new (SHELL_TYPE_KEYRING_PROMPT, NULL);
+}
+
+/**
+ * shell_keyring_prompt_get_password_actor:
+ * @self: the internal prompt
+ *
+ * Get the prompt password text actor
+ *
+ * Returns: (transfer none) (nullable): the password actor
+ */
+ClutterText *
+shell_keyring_prompt_get_password_actor (ShellKeyringPrompt *self)
+{
+ g_return_val_if_fail (SHELL_IS_KEYRING_PROMPT (self), NULL);
+ return self->password_actor;
+}
+
+/**
+ * shell_keyring_prompt_get_confirm_actor:
+ * @self: the internal prompt
+ *
+ * Get the prompt password text actor
+ *
+ * Returns: (transfer none) (nullable): the password actor
+ */
+ClutterText *
+shell_keyring_prompt_get_confirm_actor (ShellKeyringPrompt *self)
+{
+ g_return_val_if_fail (SHELL_IS_KEYRING_PROMPT (self), NULL);
+ return self->confirm_actor;
+}
+
+static guint
+calculate_password_strength (const gchar *password)
+{
+ int upper, lower, digit, misc;
+ gdouble pwstrength;
+ int length, i;
+
+ /*
+ * This code is based on the Master Password dialog in Firefox
+ * (pref-masterpass.js)
+ * Original code triple-licensed under the MPL, GPL, and LGPL
+ * so is license-compatible with this file
+ */
+
+ length = strlen (password);
+
+ /* Always return 0 for empty passwords */
+ if (length == 0)
+ return 0;
+
+ upper = 0;
+ lower = 0;
+ digit = 0;
+ misc = 0;
+
+ for (i = 0; i < length ; i++)
+ {
+ if (g_ascii_isdigit (password[i]))
+ digit++;
+ else if (g_ascii_islower (password[i]))
+ lower++;
+ else if (g_ascii_isupper (password[i]))
+ upper++;
+ else
+ misc++;
+ }
+
+ if (length > 5)
+ length = 5;
+ if (digit > 3)
+ digit = 3;
+ if (upper > 3)
+ upper = 3;
+ if (misc > 3)
+ misc = 3;
+
+ pwstrength = ((length * 1) - 2) +
+ (digit * 1) +
+ (misc * 1.5) +
+ (upper * 1);
+
+ /* Always return 1+ for non-empty passwords */
+ if (pwstrength < 1.0)
+ pwstrength = 1.0;
+ if (pwstrength > 10.0)
+ pwstrength = 10.0;
+
+ return (guint)pwstrength;
+}
+
+static void
+on_password_changed (ClutterText *text,
+ gpointer user_data)
+{
+ ShellKeyringPrompt *self = SHELL_KEYRING_PROMPT (user_data);
+ const gchar *password;
+
+ password = clutter_text_get_text (self->password_actor);
+
+ self->password_strength = calculate_password_strength (password);
+ g_object_notify (G_OBJECT (self), "password-strength");
+}
+
+/**
+ * shell_keyring_prompt_set_password_actor:
+ * @self: the internal prompt
+ * @password_actor: (nullable): the password actor
+ *
+ * Set the prompt password text actor
+ */
+void
+shell_keyring_prompt_set_password_actor (ShellKeyringPrompt *self,
+ ClutterText *password_actor)
+{
+ ClutterTextBuffer *buffer;
+
+ g_return_if_fail (SHELL_IS_KEYRING_PROMPT (self));
+ g_return_if_fail (password_actor == NULL || CLUTTER_IS_TEXT (password_actor));
+
+ if (self->password_actor == password_actor)
+ return;
+
+ if (password_actor)
+ {
+ buffer = shell_secure_text_buffer_new ();
+ clutter_text_set_buffer (password_actor, buffer);
+ g_object_unref (buffer);
+
+ g_signal_connect (password_actor, "text-changed", G_CALLBACK (on_password_changed), self);
+ g_object_ref (password_actor);
+ }
+ if (self->password_actor)
+ {
+ g_signal_handlers_disconnect_by_func (self->password_actor, on_password_changed, self);
+ g_object_unref (self->password_actor);
+ }
+
+ self->password_actor = password_actor;
+ g_object_notify_by_pspec (G_OBJECT (self), props[PROP_PASSWORD_ACTOR]);
+}
+
+/**
+ * shell_keyring_prompt_set_confirm_actor:
+ * @self: the internal prompt
+ * @confirm_actor: (nullable): the confirm password actor
+ *
+ * Set the prompt password confirmation text actor
+ */
+void
+shell_keyring_prompt_set_confirm_actor (ShellKeyringPrompt *self,
+ ClutterText *confirm_actor)
+{
+ ClutterTextBuffer *buffer;
+
+ g_return_if_fail (SHELL_IS_KEYRING_PROMPT (self));
+ g_return_if_fail (confirm_actor == NULL || CLUTTER_IS_TEXT (confirm_actor));
+
+ if (self->confirm_actor == confirm_actor)
+ return;
+
+ if (confirm_actor)
+ {
+ buffer = shell_secure_text_buffer_new ();
+ clutter_text_set_buffer (confirm_actor, buffer);
+ g_object_unref (buffer);
+
+ g_object_ref (confirm_actor);
+ }
+ if (self->confirm_actor)
+ g_object_unref (self->confirm_actor);
+ self->confirm_actor = confirm_actor;
+ g_object_notify_by_pspec (G_OBJECT (self), props[PROP_CONFIRM_ACTOR]);
+}
+
+/**
+ * shell_keyring_prompt_complete:
+ * @self: the internal prompt
+ *
+ * Called by the implementation when the prompt completes. There are various
+ * checks done. %TRUE is returned if the prompt actually should complete.
+ *
+ * Returns: whether the prompt completed
+ */
+gboolean
+shell_keyring_prompt_complete (ShellKeyringPrompt *self)
+{
+ GTask *res;
+ PromptingMode mode;
+ const gchar *password;
+ const gchar *confirm;
+ const gchar *env;
+
+ g_return_val_if_fail (SHELL_IS_KEYRING_PROMPT (self), FALSE);
+ g_return_val_if_fail (self->mode != PROMPTING_NONE, FALSE);
+ g_return_val_if_fail (self->task != NULL, FALSE);
+
+ password = clutter_text_get_text (self->password_actor);
+
+ if (self->mode == PROMPTING_FOR_PASSWORD)
+ {
+ /* Is it a new password? */
+ if (self->password_new)
+ {
+ confirm = clutter_text_get_text (self->confirm_actor);
+
+ /* Do the passwords match? */
+ if (!g_str_equal (password, confirm))
+ {
+ gcr_prompt_set_warning (GCR_PROMPT (self), _("Passwords do not match."));
+ return FALSE;
+ }
+
+ /* Don't allow blank passwords if in paranoid mode */
+ env = g_getenv ("GNOME_KEYRING_PARANOID");
+ if (env && *env)
+ {
+ gcr_prompt_set_warning (GCR_PROMPT (self), _("Password cannot be blank"));
+ return FALSE;
+ }
+ }
+
+ self->password_strength = calculate_password_strength (password);
+ g_object_notify (G_OBJECT (self), "password-strength");
+ }
+
+ res = self->task;
+ mode = self->mode;
+ self->task = NULL;
+ self->mode = PROMPTING_NONE;
+
+ if (mode == PROMPTING_FOR_CONFIRM)
+ g_task_return_int (res, (gssize)GCR_PROMPT_REPLY_CONTINUE);
+ else
+ g_task_return_pointer (res, (gpointer)password, NULL);
+ g_object_unref (res);
+
+ return TRUE;
+}
+
+/**
+ * shell_keyring_prompt_cancel:
+ * @self: the internal prompt
+ *
+ * Called by implementation when the prompt is cancelled.
+ */
+void
+shell_keyring_prompt_cancel (ShellKeyringPrompt *self)
+{
+ GTask *res;
+ PromptingMode mode;
+
+ g_return_if_fail (SHELL_IS_KEYRING_PROMPT (self));
+
+ /*
+ * If cancelled while not prompting, we should just close the prompt,
+ * the user wants it to go away.
+ */
+ if (self->mode == PROMPTING_NONE)
+ {
+ if (self->shown)
+ gcr_prompt_close (GCR_PROMPT (self));
+ return;
+ }
+
+ g_return_if_fail (self->task != NULL);
+
+ res = self->task;
+ mode = self->mode;
+ self->task = NULL;
+ self->mode = PROMPTING_NONE;
+
+ if (mode == PROMPTING_FOR_CONFIRM)
+ g_task_return_int (res, (gssize) GCR_PROMPT_REPLY_CANCEL);
+ else
+ g_task_return_pointer (res, NULL, NULL);
+ g_object_unref (res);
+}
diff --git a/src/shell-keyring-prompt.h b/src/shell-keyring-prompt.h
new file mode 100644
index 0000000..fcacf4c
--- /dev/null
+++ b/src/shell-keyring-prompt.h
@@ -0,0 +1,57 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/* shell-keyring-prompt.c - prompt handler for gnome-keyring-daemon
+
+ Copyright (C) 2011 Stefan Walter
+
+ 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., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ Author: Stef Walter <stef@thewalter.net>
+*/
+
+#ifndef __SHELL_KEYRING_PROMPT_H__
+#define __SHELL_KEYRING_PROMPT_H__
+
+#include <glib-object.h>
+#include <glib.h>
+
+#include <clutter/clutter.h>
+
+G_BEGIN_DECLS
+
+typedef struct _ShellKeyringPrompt ShellKeyringPrompt;
+
+#define SHELL_TYPE_KEYRING_PROMPT (shell_keyring_prompt_get_type ())
+G_DECLARE_FINAL_TYPE (ShellKeyringPrompt, shell_keyring_prompt,
+ SHELL, KEYRING_PROMPT, GObject)
+
+ShellKeyringPrompt * shell_keyring_prompt_new (void);
+
+ClutterText * shell_keyring_prompt_get_password_actor (ShellKeyringPrompt *self);
+
+void shell_keyring_prompt_set_password_actor (ShellKeyringPrompt *self,
+ ClutterText *password_actor);
+
+ClutterText * shell_keyring_prompt_get_confirm_actor (ShellKeyringPrompt *self);
+
+void shell_keyring_prompt_set_confirm_actor (ShellKeyringPrompt *self,
+ ClutterText *confirm_actor);
+
+gboolean shell_keyring_prompt_complete (ShellKeyringPrompt *self);
+
+void shell_keyring_prompt_cancel (ShellKeyringPrompt *self);
+
+G_END_DECLS
+
+#endif /* __SHELL_KEYRING_PROMPT_H__ */
diff --git a/src/shell-mount-operation.c b/src/shell-mount-operation.c
new file mode 100644
index 0000000..8d43368
--- /dev/null
+++ b/src/shell-mount-operation.c
@@ -0,0 +1,189 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * Copyright (C) 2011 Red Hat, Inc.
+ *
+ * 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, see <http://www.gnu.org/licenses/>.
+ *
+ * Author: Cosimo Cecchi <cosimoc@redhat.com>
+ *
+ */
+
+#include "shell-mount-operation.h"
+
+/* This is a dummy class; we would like to be able to subclass the
+ * object from JS but we can't yet; the default GMountOperation impl
+ * automatically calls g_mount_operation_reply(UNHANDLED) after an idle,
+ * in interactive methods. We want to handle the reply ourselves
+ * instead, so we just override the default methods with empty ones,
+ * except for ask-password, as we don't want to handle that.
+ *
+ * Also, we need to workaround the fact that gjs doesn't support type
+ * annotations for signals yet (so we can't effectively forward e.g.
+ * the GPid array to JS).
+ * See https://bugzilla.gnome.org/show_bug.cgi?id=645978
+ */
+
+enum {
+ SHOW_PROCESSES_2,
+ NUM_SIGNALS
+};
+
+static guint signals[NUM_SIGNALS] = { 0, };
+
+typedef struct _ShellMountOperationPrivate ShellMountOperationPrivate;
+
+struct _ShellMountOperation
+{
+ GMountOperation parent_instance;
+
+ ShellMountOperationPrivate *priv;
+};
+
+struct _ShellMountOperationPrivate {
+ GArray *pids;
+ gchar **choices;
+ gchar *message;
+};
+
+G_DEFINE_TYPE_WITH_PRIVATE (ShellMountOperation, shell_mount_operation, G_TYPE_MOUNT_OPERATION);
+
+static void
+shell_mount_operation_init (ShellMountOperation *self)
+{
+ self->priv = shell_mount_operation_get_instance_private (self);
+}
+
+static void
+shell_mount_operation_ask_password (GMountOperation *op,
+ const char *message,
+ const char *default_user,
+ const char *default_domain,
+ GAskPasswordFlags flags)
+{
+ /* do nothing */
+}
+
+static void
+shell_mount_operation_ask_question (GMountOperation *op,
+ const char *message,
+ const char *choices[])
+{
+ /* do nothing */
+}
+
+static void
+shell_mount_operation_show_processes (GMountOperation *operation,
+ const gchar *message,
+ GArray *processes,
+ const gchar *choices[])
+{
+ ShellMountOperation *self = SHELL_MOUNT_OPERATION (operation);
+
+ if (self->priv->pids != NULL)
+ {
+ g_array_unref (self->priv->pids);
+ self->priv->pids = NULL;
+ }
+
+ g_free (self->priv->message);
+ g_strfreev (self->priv->choices);
+
+ /* save the parameters */
+ self->priv->pids = g_array_ref (processes);
+ self->priv->choices = g_strdupv ((gchar **) choices);
+ self->priv->message = g_strdup (message);
+
+ g_signal_emit (self, signals[SHOW_PROCESSES_2], 0);
+}
+
+static void
+shell_mount_operation_finalize (GObject *obj)
+{
+ ShellMountOperation *self = SHELL_MOUNT_OPERATION (obj);
+
+ g_strfreev (self->priv->choices);
+ g_free (self->priv->message);
+
+ if (self->priv->pids != NULL)
+ {
+ g_array_unref (self->priv->pids);
+ self->priv->pids = NULL;
+ }
+
+ G_OBJECT_CLASS (shell_mount_operation_parent_class)->finalize (obj);
+}
+
+static void
+shell_mount_operation_class_init (ShellMountOperationClass *klass)
+{
+ GMountOperationClass *mclass;
+ GObjectClass *oclass;
+
+ mclass = G_MOUNT_OPERATION_CLASS (klass);
+ mclass->show_processes = shell_mount_operation_show_processes;
+ mclass->ask_question = shell_mount_operation_ask_question;
+ mclass->ask_password = shell_mount_operation_ask_password;
+
+ oclass = G_OBJECT_CLASS (klass);
+ oclass->finalize = shell_mount_operation_finalize;
+
+ signals[SHOW_PROCESSES_2] =
+ g_signal_new ("show-processes-2",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ 0, NULL, NULL, NULL,
+ G_TYPE_NONE, 0);
+}
+
+GMountOperation *
+shell_mount_operation_new (void)
+{
+ return g_object_new (SHELL_TYPE_MOUNT_OPERATION, NULL);
+}
+
+/**
+ * shell_mount_operation_get_show_processes_pids:
+ * @self: a #ShellMountOperation
+ *
+ * Returns: (transfer full) (element-type GPid): a #GArray
+ */
+GArray *
+shell_mount_operation_get_show_processes_pids (ShellMountOperation *self)
+{
+ return g_array_ref (self->priv->pids);
+}
+
+/**
+ * shell_mount_operation_get_show_processes_choices:
+ * @self: a #ShellMountOperation
+ *
+ * Returns: (transfer full):
+ */
+gchar **
+shell_mount_operation_get_show_processes_choices (ShellMountOperation *self)
+{
+ return g_strdupv (self->priv->choices);
+}
+
+/**
+ * shell_mount_operation_get_show_processes_message:
+ * @self: a #ShellMountOperation
+ *
+ * Returns: (transfer full):
+ */
+gchar *
+shell_mount_operation_get_show_processes_message (ShellMountOperation *self)
+{
+ return g_strdup (self->priv->message);
+}
diff --git a/src/shell-mount-operation.h b/src/shell-mount-operation.h
new file mode 100644
index 0000000..c4019a5
--- /dev/null
+++ b/src/shell-mount-operation.h
@@ -0,0 +1,41 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * Copyright (C) 2011 Red Hat, Inc.
+ *
+ * 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, see <http://www.gnu.org/licenses/>.
+ *
+ * Author: Cosimo Cecchi <cosimoc@redhat.com>
+ *
+ */
+
+#ifndef __SHELL_MOUNT_OPERATION_H__
+#define __SHELL_MOUNT_OPERATION_H__
+
+#include <gio/gio.h>
+
+G_BEGIN_DECLS
+
+#define SHELL_TYPE_MOUNT_OPERATION (shell_mount_operation_get_type ())
+G_DECLARE_FINAL_TYPE (ShellMountOperation, shell_mount_operation,
+ SHELL, MOUNT_OPERATION, GMountOperation)
+
+GMountOperation *shell_mount_operation_new (void);
+
+GArray * shell_mount_operation_get_show_processes_pids (ShellMountOperation *self);
+gchar ** shell_mount_operation_get_show_processes_choices (ShellMountOperation *self);
+gchar * shell_mount_operation_get_show_processes_message (ShellMountOperation *self);
+
+G_END_DECLS
+
+#endif /* __SHELL_MOUNT_OPERATION_H__ */
diff --git a/src/shell-network-agent.c b/src/shell-network-agent.c
new file mode 100644
index 0000000..474394f
--- /dev/null
+++ b/src/shell-network-agent.c
@@ -0,0 +1,899 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * Copyright 2011 Red Hat, Inc.
+ * 2011 Giovanni Campagna <scampa.giovanni@gmail.com>
+ * 2017 Lubomir Rintel <lkundrak@v3.sk>
+ *
+ * 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.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#include "config.h"
+#include <string.h>
+
+#include <libsecret/secret.h>
+
+#include "shell-network-agent.h"
+
+enum {
+ SIGNAL_NEW_REQUEST,
+ SIGNAL_CANCEL_REQUEST,
+ SIGNAL_LAST
+};
+
+static gint signals[SIGNAL_LAST];
+
+typedef struct {
+ GCancellable * cancellable;
+ ShellNetworkAgent *self;
+
+ gchar *request_id;
+ NMConnection *connection;
+ gchar *setting_name;
+ gchar **hints;
+ NMSecretAgentGetSecretsFlags flags;
+ NMSecretAgentOldGetSecretsFunc callback;
+ gpointer callback_data;
+
+ GVariantDict *entries;
+ GVariantBuilder builder_vpn;
+} ShellAgentRequest;
+
+struct _ShellNetworkAgentPrivate {
+ /* <gchar *request_id, ShellAgentRequest *request> */
+ GHashTable *requests;
+};
+
+G_DEFINE_TYPE_WITH_PRIVATE (ShellNetworkAgent, shell_network_agent, NM_TYPE_SECRET_AGENT_OLD)
+
+static const SecretSchema network_agent_schema = {
+ "org.freedesktop.NetworkManager.Connection",
+ SECRET_SCHEMA_DONT_MATCH_NAME,
+ {
+ { SHELL_KEYRING_UUID_TAG, SECRET_SCHEMA_ATTRIBUTE_STRING },
+ { SHELL_KEYRING_SN_TAG, SECRET_SCHEMA_ATTRIBUTE_STRING },
+ { SHELL_KEYRING_SK_TAG, SECRET_SCHEMA_ATTRIBUTE_STRING },
+ { NULL, 0 },
+ }
+};
+
+static void
+shell_agent_request_free (gpointer data)
+{
+ ShellAgentRequest *request = data;
+
+ g_cancellable_cancel (request->cancellable);
+ g_object_unref (request->cancellable);
+ g_object_unref (request->self);
+ g_object_unref (request->connection);
+ g_free (request->setting_name);
+ g_strfreev (request->hints);
+ g_clear_pointer (&request->entries, g_variant_dict_unref);
+ g_variant_builder_clear (&request->builder_vpn);
+
+ g_free (request);
+}
+
+static void
+shell_agent_request_cancel (ShellAgentRequest *request)
+{
+ GError *error;
+ ShellNetworkAgent *self;
+
+ self = request->self;
+
+ error = g_error_new (NM_SECRET_AGENT_ERROR,
+ NM_SECRET_AGENT_ERROR_AGENT_CANCELED,
+ "Canceled by NetworkManager");
+ request->callback (NM_SECRET_AGENT_OLD (self), request->connection,
+ NULL, error, request->callback_data);
+
+ g_signal_emit (self, signals[SIGNAL_CANCEL_REQUEST], 0, request->request_id);
+
+ g_hash_table_remove (self->priv->requests, request->request_id);
+ g_error_free (error);
+}
+
+static void
+shell_network_agent_init (ShellNetworkAgent *agent)
+{
+ ShellNetworkAgentPrivate *priv;
+
+ priv = agent->priv = shell_network_agent_get_instance_private (agent);
+ priv->requests = g_hash_table_new_full (g_str_hash, g_str_equal,
+ g_free, shell_agent_request_free);
+}
+
+static void
+shell_network_agent_finalize (GObject *object)
+{
+ ShellNetworkAgentPrivate *priv = SHELL_NETWORK_AGENT (object)->priv;
+ GError *error;
+ GHashTableIter iter;
+ gpointer key;
+ gpointer value;
+
+ error = g_error_new (NM_SECRET_AGENT_ERROR,
+ NM_SECRET_AGENT_ERROR_AGENT_CANCELED,
+ "The secret agent is going away");
+
+ g_hash_table_iter_init (&iter, priv->requests);
+ while (g_hash_table_iter_next (&iter, &key, &value))
+ {
+ ShellAgentRequest *request = value;
+
+ request->callback (NM_SECRET_AGENT_OLD (object),
+ request->connection,
+ NULL, error,
+ request->callback_data);
+ }
+
+ g_hash_table_destroy (priv->requests);
+ g_error_free (error);
+
+ G_OBJECT_CLASS (shell_network_agent_parent_class)->finalize (object);
+}
+
+static void
+request_secrets_from_ui (ShellAgentRequest *request)
+{
+ g_signal_emit (request->self, signals[SIGNAL_NEW_REQUEST], 0,
+ request->request_id,
+ request->connection,
+ request->setting_name,
+ request->hints,
+ (int)request->flags);
+}
+
+static void
+check_always_ask_cb (NMSetting *setting,
+ const gchar *key,
+ const GValue *value,
+ GParamFlags flags,
+ gpointer user_data)
+{
+ gboolean *always_ask = user_data;
+ NMSettingSecretFlags secret_flags = NM_SETTING_SECRET_FLAG_NONE;
+
+ if (flags & NM_SETTING_PARAM_SECRET)
+ {
+ if (nm_setting_get_secret_flags (setting, key, &secret_flags, NULL))
+ {
+ if (secret_flags & NM_SETTING_SECRET_FLAG_NOT_SAVED)
+ *always_ask = TRUE;
+ }
+ }
+}
+
+static gboolean
+has_always_ask (NMSetting *setting)
+{
+ gboolean always_ask = FALSE;
+
+ nm_setting_enumerate_values (setting, check_always_ask_cb, &always_ask);
+ return always_ask;
+}
+
+static gboolean
+is_connection_always_ask (NMConnection *connection)
+{
+ NMSettingConnection *s_con;
+ const gchar *ctype;
+ NMSetting *setting;
+
+ /* For the given connection type, check if the secrets for that connection
+ * are always-ask or not.
+ */
+ s_con = (NMSettingConnection *) nm_connection_get_setting (connection, NM_TYPE_SETTING_CONNECTION);
+ g_assert (s_con);
+ ctype = nm_setting_connection_get_connection_type (s_con);
+
+ setting = nm_connection_get_setting_by_name (connection, ctype);
+ g_return_val_if_fail (setting != NULL, FALSE);
+
+ if (has_always_ask (setting))
+ return TRUE;
+
+ /* Try type-specific settings too; be a bit paranoid and only consider
+ * secrets from settings relevant to the connection type.
+ */
+ if (NM_IS_SETTING_WIRELESS (setting))
+ {
+ setting = nm_connection_get_setting (connection, NM_TYPE_SETTING_WIRELESS_SECURITY);
+ if (setting && has_always_ask (setting))
+ return TRUE;
+ setting = nm_connection_get_setting (connection, NM_TYPE_SETTING_802_1X);
+ if (setting && has_always_ask (setting))
+ return TRUE;
+ }
+ else if (NM_IS_SETTING_WIRED (setting))
+ {
+ setting = nm_connection_get_setting (connection, NM_TYPE_SETTING_PPPOE);
+ if (setting && has_always_ask (setting))
+ return TRUE;
+ setting = nm_connection_get_setting (connection, NM_TYPE_SETTING_802_1X);
+ if (setting && has_always_ask (setting))
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static void
+get_secrets_keyring_cb (GObject *source,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ ShellAgentRequest *closure;
+ ShellNetworkAgent *self;
+ ShellNetworkAgentPrivate *priv;
+ GError *secret_error = NULL;
+ GError *error = NULL;
+ GList *items;
+ GList *l;
+ gboolean secrets_found = FALSE;
+ GVariantBuilder builder_setting, builder_connection;
+ g_autoptr (GVariant) setting = NULL;
+
+ items = secret_service_search_finish (NULL, result, &secret_error);
+
+ if (g_error_matches (secret_error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ {
+ g_error_free (secret_error);
+ return;
+ }
+
+ closure = user_data;
+ self = closure->self;
+ priv = self->priv;
+
+ if (secret_error != NULL)
+ {
+ g_set_error (&error,
+ NM_SECRET_AGENT_ERROR,
+ NM_SECRET_AGENT_ERROR_FAILED,
+ "Internal error while retrieving secrets from the keyring (%s)", secret_error->message);
+ g_error_free (secret_error);
+ closure->callback (NM_SECRET_AGENT_OLD (closure->self), closure->connection, NULL, error, closure->callback_data);
+
+ goto out;
+ }
+
+ g_variant_builder_init (&builder_setting, NM_VARIANT_TYPE_SETTING);
+
+ for (l = items; l; l = g_list_next (l))
+ {
+ SecretItem *item = l->data;
+ GHashTable *attributes;
+ GHashTableIter iter;
+ const gchar *name, *attribute;
+ SecretValue *secret = secret_item_get_secret (item);
+
+ /* This can happen if the user denied a request to unlock */
+ if (secret == NULL)
+ continue;
+
+ attributes = secret_item_get_attributes (item);
+ g_hash_table_iter_init (&iter, attributes);
+ while (g_hash_table_iter_next (&iter, (gpointer *)&name, (gpointer *)&attribute))
+ {
+ if (g_strcmp0 (name, SHELL_KEYRING_SK_TAG) == 0)
+ {
+ g_variant_builder_add (&builder_setting, "{sv}", attribute,
+ g_variant_new_string (secret_value_get (secret, NULL)));
+
+ secrets_found = TRUE;
+
+ break;
+ }
+ }
+
+ g_hash_table_unref (attributes);
+ secret_value_unref (secret);
+ }
+
+ g_list_free_full (items, g_object_unref);
+ setting = g_variant_ref_sink (g_variant_builder_end (&builder_setting));
+
+ /* All VPN requests get sent to the VPN's auth dialog, since it knows better
+ * than the agent about what secrets are required. Otherwise, if no secrets
+ * were found and interaction is allowed the ask for some secrets, because
+ * NetworkManager will fail the connection if not secrets are returned
+ * instead of asking again with REQUEST_NEW.
+ */
+ if (strcmp(closure->setting_name, NM_SETTING_VPN_SETTING_NAME) == 0 ||
+ (!secrets_found && (closure->flags & NM_SECRET_AGENT_GET_SECRETS_FLAG_ALLOW_INTERACTION)))
+ {
+ nm_connection_update_secrets (closure->connection, closure->setting_name,
+ setting, NULL);
+
+ closure->entries = g_variant_dict_new (setting);
+ request_secrets_from_ui (closure);
+ return;
+ }
+
+ g_variant_builder_init (&builder_connection, NM_VARIANT_TYPE_CONNECTION);
+ g_variant_builder_add (&builder_connection, "{s@a{sv}}",
+ closure->setting_name, setting);
+
+ closure->callback (NM_SECRET_AGENT_OLD (closure->self), closure->connection,
+ g_variant_builder_end (&builder_connection), NULL,
+ closure->callback_data);
+
+ out:
+ g_hash_table_remove (priv->requests, closure->request_id);
+ g_clear_error (&error);
+}
+
+static void
+shell_network_agent_get_secrets (NMSecretAgentOld *agent,
+ NMConnection *connection,
+ const gchar *connection_path,
+ const gchar *setting_name,
+ const gchar **hints,
+ NMSecretAgentGetSecretsFlags flags,
+ NMSecretAgentOldGetSecretsFunc callback,
+ gpointer callback_data)
+{
+ ShellNetworkAgent *self = SHELL_NETWORK_AGENT (agent);
+ ShellAgentRequest *request;
+ GHashTable *attributes;
+ char *request_id;
+
+ request_id = g_strdup_printf ("%s/%s", connection_path, setting_name);
+ if ((request = g_hash_table_lookup (self->priv->requests, request_id)) != NULL)
+ {
+ /* We already have a request pending for this (connection, setting)
+ * Cancel it before starting the new one.
+ * This will also free the request structure and associated resources.
+ */
+ shell_agent_request_cancel (request);
+ }
+
+ request = g_new0 (ShellAgentRequest, 1);
+ request->self = g_object_ref (self);
+ request->cancellable = g_cancellable_new ();
+ request->connection = g_object_ref (connection);
+ request->setting_name = g_strdup (setting_name);
+ request->hints = g_strdupv ((gchar **)hints);
+ request->flags = flags;
+ request->callback = callback;
+ request->callback_data = callback_data;
+
+ request->request_id = request_id;
+ g_hash_table_replace (self->priv->requests, request->request_id, request);
+
+ g_variant_builder_init (&request->builder_vpn, G_VARIANT_TYPE ("a{ss}"));
+
+ if ((flags & NM_SECRET_AGENT_GET_SECRETS_FLAG_REQUEST_NEW) ||
+ ((flags & NM_SECRET_AGENT_GET_SECRETS_FLAG_ALLOW_INTERACTION)
+ && is_connection_always_ask (request->connection)))
+ {
+ request->entries = g_variant_dict_new (NULL);
+ request_secrets_from_ui (request);
+ return;
+ }
+
+ attributes = secret_attributes_build (&network_agent_schema,
+ SHELL_KEYRING_UUID_TAG, nm_connection_get_uuid (connection),
+ SHELL_KEYRING_SN_TAG, setting_name,
+ NULL);
+
+ secret_service_search (NULL, &network_agent_schema, attributes,
+ SECRET_SEARCH_ALL | SECRET_SEARCH_UNLOCK | SECRET_SEARCH_LOAD_SECRETS,
+ request->cancellable, get_secrets_keyring_cb, request);
+
+ g_hash_table_unref (attributes);
+}
+
+void
+shell_network_agent_add_vpn_secret (ShellNetworkAgent *self,
+ gchar *request_id,
+ gchar *setting_key,
+ gchar *setting_value)
+{
+ ShellNetworkAgentPrivate *priv;
+ ShellAgentRequest *request;
+
+ g_return_if_fail (SHELL_IS_NETWORK_AGENT (self));
+
+ priv = self->priv;
+ request = g_hash_table_lookup (priv->requests, request_id);
+ g_return_if_fail (request != NULL);
+
+ g_variant_builder_add (&request->builder_vpn, "{ss}", setting_key, setting_value);
+}
+
+void
+shell_network_agent_set_password (ShellNetworkAgent *self,
+ gchar *request_id,
+ gchar *setting_key,
+ gchar *setting_value)
+{
+ ShellNetworkAgentPrivate *priv;
+ ShellAgentRequest *request;
+
+ g_return_if_fail (SHELL_IS_NETWORK_AGENT (self));
+
+ priv = self->priv;
+ request = g_hash_table_lookup (priv->requests, request_id);
+ g_return_if_fail (request != NULL);
+
+ g_variant_dict_insert (request->entries, setting_key, "s", setting_value);
+}
+
+void
+shell_network_agent_respond (ShellNetworkAgent *self,
+ gchar *request_id,
+ ShellNetworkAgentResponse response)
+{
+ ShellNetworkAgentPrivate *priv;
+ ShellAgentRequest *request;
+ GVariantBuilder builder_connection;
+ GVariant *vpn_secrets, *setting;
+
+ g_return_if_fail (SHELL_IS_NETWORK_AGENT (self));
+
+ priv = self->priv;
+ request = g_hash_table_lookup (priv->requests, request_id);
+ g_return_if_fail (request != NULL);
+
+ if (response == SHELL_NETWORK_AGENT_USER_CANCELED)
+ {
+ GError *error = g_error_new (NM_SECRET_AGENT_ERROR,
+ NM_SECRET_AGENT_ERROR_USER_CANCELED,
+ "Network dialog was canceled by the user");
+
+ request->callback (NM_SECRET_AGENT_OLD (self), request->connection, NULL, error, request->callback_data);
+ g_error_free (error);
+ g_hash_table_remove (priv->requests, request_id);
+ return;
+ }
+
+ if (response == SHELL_NETWORK_AGENT_INTERNAL_ERROR)
+ {
+ GError *error = g_error_new (NM_SECRET_AGENT_ERROR,
+ NM_SECRET_AGENT_ERROR_FAILED,
+ "An internal error occurred while processing the request.");
+
+ request->callback (NM_SECRET_AGENT_OLD (self), request->connection, NULL, error, request->callback_data);
+ g_error_free (error);
+ g_hash_table_remove (priv->requests, request_id);
+ return;
+ }
+
+ /* response == SHELL_NETWORK_AGENT_CONFIRMED */
+
+ /* VPN secrets are stored as a hash of secrets in a single setting */
+ vpn_secrets = g_variant_builder_end (&request->builder_vpn);
+ if (g_variant_n_children (vpn_secrets))
+ g_variant_dict_insert_value (request->entries, NM_SETTING_VPN_SECRETS, vpn_secrets);
+ else
+ g_variant_unref (vpn_secrets);
+
+ setting = g_variant_dict_end (request->entries);
+
+ /* Save any updated secrets */
+ if ((request->flags & NM_SECRET_AGENT_GET_SECRETS_FLAG_ALLOW_INTERACTION) ||
+ (request->flags & NM_SECRET_AGENT_GET_SECRETS_FLAG_REQUEST_NEW))
+ {
+ NMConnection *dup = nm_simple_connection_new_clone (request->connection);
+
+ nm_connection_update_secrets (dup, request->setting_name, setting, NULL);
+ nm_secret_agent_old_save_secrets (NM_SECRET_AGENT_OLD (self), dup, NULL, NULL);
+ g_object_unref (dup);
+ }
+
+ g_variant_builder_init (&builder_connection, NM_VARIANT_TYPE_CONNECTION);
+ g_variant_builder_add (&builder_connection, "{s@a{sv}}",
+ request->setting_name, setting);
+
+ request->callback (NM_SECRET_AGENT_OLD (self), request->connection,
+ g_variant_builder_end (&builder_connection), NULL,
+ request->callback_data);
+
+ g_hash_table_remove (priv->requests, request_id);
+}
+
+static void
+search_vpn_plugin (GTask *task,
+ gpointer object,
+ gpointer task_data,
+ GCancellable *cancellable)
+{
+ NMVpnPluginInfo *info = NULL;
+ char *service = task_data;
+
+ info = nm_vpn_plugin_info_new_search_file (NULL, service);
+
+ if (info)
+ {
+ g_task_return_pointer (task, info, g_object_unref);
+ }
+ else
+ {
+ g_task_return_new_error (task,
+ G_IO_ERROR, G_IO_ERROR_NOT_FOUND,
+ "No plugin for %s", service);
+ }
+}
+
+void
+shell_network_agent_search_vpn_plugin (ShellNetworkAgent *self,
+ const char *service,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ g_autoptr(GTask) task = NULL;
+
+ g_return_if_fail (SHELL_IS_NETWORK_AGENT (self));
+ g_return_if_fail (service != NULL);
+
+ task = g_task_new (self, NULL, callback, user_data);
+ g_task_set_source_tag (task, shell_network_agent_search_vpn_plugin);
+ g_task_set_task_data (task, g_strdup (service), g_free);
+
+ g_task_run_in_thread (task, search_vpn_plugin);
+}
+
+/**
+ * shell_network_agent_search_vpn_plugin_finish:
+ *
+ * Returns: (nullable) (transfer full): The found plugin or %NULL
+ */
+NMVpnPluginInfo *
+shell_network_agent_search_vpn_plugin_finish (ShellNetworkAgent *self,
+ GAsyncResult *result,
+ GError **error)
+{
+ g_return_val_if_fail (SHELL_IS_NETWORK_AGENT (self), NULL);
+ g_return_val_if_fail (G_IS_TASK (result), NULL);
+
+ return g_task_propagate_pointer (G_TASK (result), error);
+}
+
+static void
+shell_network_agent_cancel_get_secrets (NMSecretAgentOld *agent,
+ const gchar *connection_path,
+ const gchar *setting_name)
+{
+ ShellNetworkAgent *self = SHELL_NETWORK_AGENT (agent);
+ ShellNetworkAgentPrivate *priv = self->priv;
+ gchar *request_id;
+ ShellAgentRequest *request;
+
+ request_id = g_strdup_printf ("%s/%s", connection_path, setting_name);
+ request = g_hash_table_lookup (priv->requests, request_id);
+ g_free (request_id);
+
+ if (!request)
+ {
+ /* We've already sent the result, but the caller cancelled the
+ * operation before receiving that result.
+ */
+ return;
+ }
+
+ shell_agent_request_cancel (request);
+}
+
+/************************* saving of secrets ****************************************/
+
+static GHashTable *
+create_keyring_add_attr_list (NMConnection *connection,
+ const gchar *connection_uuid,
+ const gchar *connection_id,
+ const gchar *setting_name,
+ const gchar *setting_key,
+ gchar **out_display_name)
+{
+ NMSettingConnection *s_con;
+
+ if (connection)
+ {
+ s_con = (NMSettingConnection *) nm_connection_get_setting (connection, NM_TYPE_SETTING_CONNECTION);
+ g_return_val_if_fail (s_con != NULL, NULL);
+ connection_uuid = nm_setting_connection_get_uuid (s_con);
+ connection_id = nm_setting_connection_get_id (s_con);
+ }
+
+ g_return_val_if_fail (connection_uuid != NULL, NULL);
+ g_return_val_if_fail (connection_id != NULL, NULL);
+ g_return_val_if_fail (setting_name != NULL, NULL);
+ g_return_val_if_fail (setting_key != NULL, NULL);
+
+ if (out_display_name)
+ {
+ *out_display_name = g_strdup_printf ("Network secret for %s/%s/%s",
+ connection_id,
+ setting_name,
+ setting_key);
+ }
+
+ return secret_attributes_build (&network_agent_schema,
+ SHELL_KEYRING_UUID_TAG, connection_uuid,
+ SHELL_KEYRING_SN_TAG, setting_name,
+ SHELL_KEYRING_SK_TAG, setting_key,
+ NULL);
+}
+
+typedef struct
+{
+ /* Sort of ref count, indicates the number of secrets we still need to save */
+ gint n_secrets;
+
+ NMSecretAgentOld *self;
+ NMConnection *connection;
+ gpointer callback;
+ gpointer callback_data;
+} KeyringRequest;
+
+static void
+keyring_request_free (KeyringRequest *r)
+{
+ g_object_unref (r->self);
+ g_object_unref (r->connection);
+
+ g_free (r);
+}
+
+static void
+save_secret_cb (GObject *source,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ KeyringRequest *call = user_data;
+ NMSecretAgentOldSaveSecretsFunc callback = call->callback;
+
+ call->n_secrets--;
+
+ if (call->n_secrets == 0)
+ {
+ if (callback)
+ callback (call->self, call->connection, NULL, call->callback_data);
+ keyring_request_free (call);
+ }
+}
+
+static void
+save_one_secret (KeyringRequest *r,
+ NMSetting *setting,
+ const gchar *key,
+ const gchar *secret,
+ const gchar *display_name)
+{
+ GHashTable *attrs;
+ gchar *alt_display_name = NULL;
+ const gchar *setting_name;
+ NMSettingSecretFlags secret_flags = NM_SETTING_SECRET_FLAG_NONE;
+
+ /* Only save agent-owned secrets (not system-owned or always-ask) */
+ nm_setting_get_secret_flags (setting, key, &secret_flags, NULL);
+ if (secret_flags != NM_SETTING_SECRET_FLAG_AGENT_OWNED)
+ return;
+
+ setting_name = nm_setting_get_name (setting);
+ g_assert (setting_name);
+
+ attrs = create_keyring_add_attr_list (r->connection, NULL, NULL,
+ setting_name,
+ key,
+ display_name ? NULL : &alt_display_name);
+ g_assert (attrs);
+ r->n_secrets++;
+ secret_password_storev (&network_agent_schema, attrs, SECRET_COLLECTION_DEFAULT,
+ display_name ? display_name : alt_display_name,
+ secret, NULL, save_secret_cb, r);
+
+ g_hash_table_unref (attrs);
+ g_free (alt_display_name);
+}
+
+static void
+vpn_secret_iter_cb (const gchar *key,
+ const gchar *secret,
+ gpointer user_data)
+{
+ KeyringRequest *r = user_data;
+ NMSetting *setting;
+ const gchar *service_name, *id;
+ gchar *display_name;
+
+ if (secret && strlen (secret))
+ {
+ setting = nm_connection_get_setting (r->connection, NM_TYPE_SETTING_VPN);
+ g_assert (setting);
+ service_name = nm_setting_vpn_get_service_type (NM_SETTING_VPN (setting));
+ g_assert (service_name);
+ id = nm_connection_get_id (r->connection);
+ g_assert (id);
+
+ display_name = g_strdup_printf ("VPN %s secret for %s/%s/" NM_SETTING_VPN_SETTING_NAME,
+ key,
+ id,
+ service_name);
+ save_one_secret (r, setting, key, secret, display_name);
+ g_free (display_name);
+ }
+}
+
+static void
+write_one_secret_to_keyring (NMSetting *setting,
+ const gchar *key,
+ const GValue *value,
+ GParamFlags flags,
+ gpointer user_data)
+{
+ KeyringRequest *r = user_data;
+ const gchar *secret;
+
+ /* Non-secrets obviously don't get saved in the keyring */
+ if (!(flags & NM_SETTING_PARAM_SECRET))
+ return;
+
+ if (NM_IS_SETTING_VPN (setting) && (g_strcmp0 (key, NM_SETTING_VPN_SECRETS) == 0))
+ {
+ /* Process VPN secrets specially since it's a hash of secrets, not just one */
+ nm_setting_vpn_foreach_secret (NM_SETTING_VPN (setting),
+ vpn_secret_iter_cb,
+ r);
+ }
+ else
+ {
+ if (!G_VALUE_HOLDS_STRING (value))
+ return;
+
+ secret = g_value_get_string (value);
+ if (secret && strlen (secret))
+ save_one_secret (r, setting, key, secret, NULL);
+ }
+}
+
+static void
+save_delete_cb (NMSecretAgentOld *agent,
+ NMConnection *connection,
+ GError *error,
+ gpointer user_data)
+{
+ KeyringRequest *r = user_data;
+
+ /* Ignore errors; now save all new secrets */
+ nm_connection_for_each_setting_value (connection, write_one_secret_to_keyring, r);
+
+ /* If no secrets actually got saved there may be nothing to do so
+ * try to complete the request here. If there were secrets to save the
+ * request will get completed when those keyring calls return (at the next
+ * mainloop iteration).
+ */
+ if (r->n_secrets == 0)
+ {
+ if (r->callback)
+ ((NMSecretAgentOldSaveSecretsFunc)r->callback) (agent, connection, NULL, r->callback_data);
+ keyring_request_free (r);
+ }
+}
+
+static void
+shell_network_agent_save_secrets (NMSecretAgentOld *agent,
+ NMConnection *connection,
+ const gchar *connection_path,
+ NMSecretAgentOldSaveSecretsFunc callback,
+ gpointer callback_data)
+{
+ KeyringRequest *r;
+
+ r = g_new (KeyringRequest, 1);
+ r->n_secrets = 0;
+ r->self = g_object_ref (agent);
+ r->connection = g_object_ref (connection);
+ r->callback = callback;
+ r->callback_data = callback_data;
+
+ /* First delete any existing items in the keyring */
+ nm_secret_agent_old_delete_secrets (agent, connection, save_delete_cb, r);
+}
+
+static void
+delete_items_cb (GObject *source,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ KeyringRequest *r = user_data;
+ GError *secret_error = NULL;
+ GError *error = NULL;
+ NMSecretAgentOldDeleteSecretsFunc callback = r->callback;
+
+ secret_password_clear_finish (result, &secret_error);
+ if (secret_error != NULL)
+ {
+ error = g_error_new (NM_SECRET_AGENT_ERROR,
+ NM_SECRET_AGENT_ERROR_FAILED,
+ "The request could not be completed. Keyring result: %s",
+ secret_error->message);
+ g_error_free (secret_error);
+ }
+
+ callback (r->self, r->connection, error, r->callback_data);
+ g_clear_error (&error);
+ keyring_request_free (r);
+}
+
+static void
+shell_network_agent_delete_secrets (NMSecretAgentOld *agent,
+ NMConnection *connection,
+ const gchar *connection_path,
+ NMSecretAgentOldDeleteSecretsFunc callback,
+ gpointer callback_data)
+{
+ KeyringRequest *r;
+ NMSettingConnection *s_con;
+ const gchar *uuid;
+
+ r = g_new (KeyringRequest, 1);
+ r->n_secrets = 0; /* ignored by delete secrets calls */
+ r->self = g_object_ref (agent);
+ r->connection = g_object_ref (connection);
+ r->callback = callback;
+ r->callback_data = callback_data;
+
+ s_con = (NMSettingConnection *) nm_connection_get_setting (connection, NM_TYPE_SETTING_CONNECTION);
+ g_assert (s_con);
+ uuid = nm_setting_connection_get_uuid (s_con);
+ g_assert (uuid);
+
+ secret_password_clear (&network_agent_schema, NULL, delete_items_cb, r,
+ SHELL_KEYRING_UUID_TAG, uuid,
+ NULL);
+}
+
+void
+shell_network_agent_class_init (ShellNetworkAgentClass *klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+ NMSecretAgentOldClass *agent_class = NM_SECRET_AGENT_OLD_CLASS (klass);
+
+ gobject_class->finalize = shell_network_agent_finalize;
+
+ agent_class->get_secrets = shell_network_agent_get_secrets;
+ agent_class->cancel_get_secrets = shell_network_agent_cancel_get_secrets;
+ agent_class->save_secrets = shell_network_agent_save_secrets;
+ agent_class->delete_secrets = shell_network_agent_delete_secrets;
+
+ signals[SIGNAL_NEW_REQUEST] = g_signal_new ("new-request",
+ G_TYPE_FROM_CLASS (klass),
+ 0, /* flags */
+ 0, /* class offset */
+ NULL, /* accumulator */
+ NULL, /* accu_data */
+ NULL, /* marshaller */
+ G_TYPE_NONE, /* return */
+ 5, /* n_params */
+ G_TYPE_STRING,
+ NM_TYPE_CONNECTION,
+ G_TYPE_STRING,
+ G_TYPE_STRV,
+ G_TYPE_INT);
+
+ signals[SIGNAL_CANCEL_REQUEST] = g_signal_new ("cancel-request",
+ G_TYPE_FROM_CLASS (klass),
+ 0, /* flags */
+ 0, /* class offset */
+ NULL, /* accumulator */
+ NULL, /* accu_data */
+ NULL, /* marshaller */
+ G_TYPE_NONE,
+ 1, /* n_params */
+ G_TYPE_STRING);
+}
diff --git a/src/shell-network-agent.h b/src/shell-network-agent.h
new file mode 100644
index 0000000..0ffde02
--- /dev/null
+++ b/src/shell-network-agent.h
@@ -0,0 +1,73 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+#ifndef __SHELL_NETWORK_AGENT_H__
+#define __SHELL_NETWORK_AGENT_H__
+
+#include <glib-object.h>
+#include <glib.h>
+#include <NetworkManager.h>
+#include <nm-secret-agent-old.h>
+
+G_BEGIN_DECLS
+
+typedef enum {
+ SHELL_NETWORK_AGENT_CONFIRMED,
+ SHELL_NETWORK_AGENT_USER_CANCELED,
+ SHELL_NETWORK_AGENT_INTERNAL_ERROR
+} ShellNetworkAgentResponse;
+
+typedef struct _ShellNetworkAgent ShellNetworkAgent;
+typedef struct _ShellNetworkAgentClass ShellNetworkAgentClass;
+typedef struct _ShellNetworkAgentPrivate ShellNetworkAgentPrivate;
+
+#define SHELL_TYPE_NETWORK_AGENT (shell_network_agent_get_type ())
+#define SHELL_NETWORK_AGENT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), SHELL_TYPE_NETWORK_AGENT, ShellNetworkAgent))
+#define SHELL_IS_NETWORK_AGENT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SHELL_TYPE_NETWORK_AGENT))
+#define SHELL_NETWORK_AGENT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), SHELL_TYPE_NETWORK_AGENT, ShellNetworkAgentClass))
+#define SHELL_IS_NETWORK_AGENT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), SHELL_TYPE_NETWORK_AGENT))
+#define SHELL_NETWORK_AGENT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), SHELL_TYPE_NETWORK_AGENT, ShellNetworkAgentClass))
+
+struct _ShellNetworkAgent
+{
+ /*< private >*/
+ NMSecretAgentOld parent_instance;
+
+ ShellNetworkAgentPrivate *priv;
+};
+
+struct _ShellNetworkAgentClass
+{
+ /*< private >*/
+ NMSecretAgentOldClass parent_class;
+};
+
+/* used by SHELL_TYPE_NETWORK_AGENT */
+GType shell_network_agent_get_type (void);
+
+void shell_network_agent_add_vpn_secret (ShellNetworkAgent *self,
+ gchar *request_id,
+ gchar *setting_key,
+ gchar *setting_value);
+void shell_network_agent_set_password (ShellNetworkAgent *self,
+ gchar *request_id,
+ gchar *setting_key,
+ gchar *setting_value);
+void shell_network_agent_respond (ShellNetworkAgent *self,
+ gchar *request_id,
+ ShellNetworkAgentResponse response);
+
+void shell_network_agent_search_vpn_plugin (ShellNetworkAgent *self,
+ const char *service,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+NMVpnPluginInfo *shell_network_agent_search_vpn_plugin_finish (ShellNetworkAgent *self,
+ GAsyncResult *result,
+ GError **error);
+
+/* If these are kept in sync with nm-applet, secrets will be shared */
+#define SHELL_KEYRING_UUID_TAG "connection-uuid"
+#define SHELL_KEYRING_SN_TAG "setting-name"
+#define SHELL_KEYRING_SK_TAG "setting-key"
+
+G_END_DECLS
+
+#endif /* __SHELL_NETWORK_AGENT_H__ */
diff --git a/src/shell-perf-helper.c b/src/shell-perf-helper.c
new file mode 100644
index 0000000..a50376e
--- /dev/null
+++ b/src/shell-perf-helper.c
@@ -0,0 +1,376 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+/* gnome-shell-perf-helper: a program to create windows for performance tests
+ *
+ * Running performance tests with whatever windows a user has open results
+ * in unreliable results, so instead we hide all other windows and talk
+ * to this program over D-Bus to create just the windows we want.
+ */
+
+#include "config.h"
+
+#include <math.h>
+
+#include <gtk/gtk.h>
+
+#define BUS_NAME "org.gnome.Shell.PerfHelper"
+
+static void destroy_windows (void);
+static void finish_wait_windows (void);
+static void check_finish_wait_windows (void);
+
+static const gchar introspection_xml[] =
+ "<node>"
+ " <interface name='org.gnome.Shell.PerfHelper'>"
+ " <method name='Exit'/>"
+ " <method name='CreateWindow'>"
+ " <arg type='i' name='width' direction='in'/>"
+ " <arg type='i' name='height' direction='in'/>"
+ " <arg type='b' name='alpha' direction='in'/>"
+ " <arg type='b' name='maximized' direction='in'/>"
+ " <arg type='b' name='redraws' direction='in'/>"
+ " </method>"
+ " <method name='WaitWindows'/>"
+ " <method name='DestroyWindows'/>"
+ " </interface>"
+ "</node>";
+
+typedef struct {
+ GtkWidget *window;
+ int width;
+ int height;
+
+ guint alpha : 1;
+ guint maximized : 1;
+ guint redraws : 1;
+ guint mapped : 1;
+ guint exposed : 1;
+ guint pending : 1;
+
+ gint64 start_time;
+ gint64 time;
+} WindowInfo;
+
+static int opt_idle_timeout = 30;
+
+static GOptionEntry opt_entries[] =
+ {
+ { "idle-timeout", 'r', 0, G_OPTION_ARG_INT, &opt_idle_timeout, "Exit after N seconds", "N" },
+ { NULL }
+ };
+
+static guint timeout_id;
+static GList *our_windows;
+static GList *wait_windows_invocations;
+
+static gboolean
+on_timeout (gpointer data)
+{
+ timeout_id = 0;
+
+ destroy_windows ();
+ gtk_main_quit ();
+
+ return FALSE;
+}
+
+static void
+establish_timeout (void)
+{
+ g_clear_handle_id (&timeout_id, g_source_remove);
+
+ timeout_id = g_timeout_add (opt_idle_timeout * 1000, on_timeout, NULL);
+ g_source_set_name_by_id (timeout_id, "[gnome-shell] on_timeout");
+}
+
+static void
+destroy_windows (void)
+{
+ GList *l;
+
+ for (l = our_windows; l; l = l->next)
+ {
+ WindowInfo *info = l->data;
+ gtk_widget_destroy (info->window);
+ g_free (info);
+ }
+
+ g_list_free (our_windows);
+ our_windows = NULL;
+
+ check_finish_wait_windows ();
+}
+
+static gboolean
+on_window_map_event (GtkWidget *window,
+ GdkEventAny *event,
+ WindowInfo *info)
+{
+ info->mapped = TRUE;
+
+ return FALSE;
+}
+
+static gboolean
+on_child_draw (GtkWidget *window,
+ cairo_t *cr,
+ WindowInfo *info)
+{
+ cairo_rectangle_int_t allocation;
+ double x_offset, y_offset;
+
+ gtk_widget_get_allocation (window, &allocation);
+
+ /* We draw an arbitrary pattern of red lines near the border of the
+ * window to make it more clear than empty windows if something
+ * is drastrically wrong.
+ */
+
+ cairo_save (cr);
+ cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
+
+ if (info->alpha)
+ cairo_set_source_rgba (cr, 1, 1, 1, 0.5);
+ else
+ cairo_set_source_rgb (cr, 1, 1, 1);
+
+ cairo_paint (cr);
+ cairo_restore (cr);
+
+ if (info->redraws)
+ {
+ double position = (info->time - info->start_time) / 1000000.;
+ x_offset = 20 * cos (2 * M_PI * position);
+ y_offset = 20 * sin (2 * M_PI * position);
+ }
+ else
+ {
+ x_offset = y_offset = 0;
+ }
+
+ cairo_set_source_rgb (cr, 1, 0, 0);
+ cairo_set_line_width (cr, 10);
+ cairo_move_to (cr, 0, 40 + y_offset);
+ cairo_line_to (cr, allocation.width, 40 + y_offset);
+ cairo_move_to (cr, 0, allocation.height - 40 + y_offset);
+ cairo_line_to (cr, allocation.width, allocation.height - 40 + y_offset);
+ cairo_move_to (cr, 40 + x_offset, 0);
+ cairo_line_to (cr, 40 + x_offset, allocation.height);
+ cairo_move_to (cr, allocation.width - 40 + x_offset, 0);
+ cairo_line_to (cr, allocation.width - 40 + x_offset, allocation.height);
+ cairo_stroke (cr);
+
+ info->exposed = TRUE;
+
+ if (info->exposed && info->mapped && info->pending)
+ {
+ info->pending = FALSE;
+ check_finish_wait_windows ();
+ }
+
+ return FALSE;
+}
+
+static gboolean
+tick_callback (GtkWidget *widget,
+ GdkFrameClock *frame_clock,
+ gpointer user_data)
+{
+ WindowInfo *info = user_data;
+
+ if (info->start_time < 0)
+ info->start_time = info->time = gdk_frame_clock_get_frame_time (frame_clock);
+ else
+ info->time = gdk_frame_clock_get_frame_time (frame_clock);
+
+ gtk_widget_queue_draw (widget);
+
+ return TRUE;
+}
+
+static void
+create_window (int width,
+ int height,
+ gboolean alpha,
+ gboolean maximized,
+ gboolean redraws)
+{
+ WindowInfo *info;
+ GtkWidget *child;
+
+ info = g_new0 (WindowInfo, 1);
+ info->width = width;
+ info->height = height;
+ info->alpha = alpha;
+ info->maximized = maximized;
+ info->redraws = redraws;
+ info->window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
+ if (alpha)
+ gtk_widget_set_visual (info->window, gdk_screen_get_rgba_visual (gdk_screen_get_default ()));
+ if (maximized)
+ gtk_window_maximize (GTK_WINDOW (info->window));
+ info->pending = TRUE;
+ info->start_time = -1;
+
+ child = g_object_new (GTK_TYPE_BOX, "visible", TRUE, "app-paintable", TRUE, NULL);
+ gtk_container_add (GTK_CONTAINER (info->window), child);
+
+ gtk_widget_set_size_request (info->window, width, height);
+ gtk_widget_set_app_paintable (info->window, TRUE);
+ g_signal_connect (info->window, "map-event", G_CALLBACK (on_window_map_event), info);
+ g_signal_connect (child, "draw", G_CALLBACK (on_child_draw), info);
+ gtk_widget_show (info->window);
+
+ if (info->redraws)
+ gtk_widget_add_tick_callback (info->window, tick_callback,
+ info, NULL);
+
+ our_windows = g_list_prepend (our_windows, info);
+}
+
+static void
+finish_wait_windows (void)
+{
+ GList *l;
+
+ for (l = wait_windows_invocations; l; l = l->next)
+ g_dbus_method_invocation_return_value (l->data, NULL);
+
+ g_list_free (wait_windows_invocations);
+ wait_windows_invocations = NULL;
+}
+
+static void
+check_finish_wait_windows (void)
+{
+ GList *l;
+ gboolean have_pending = FALSE;
+
+ for (l = our_windows; l; l = l->next)
+ {
+ WindowInfo *info = l->data;
+ if (info->pending)
+ have_pending = TRUE;
+ }
+
+ if (!have_pending)
+ finish_wait_windows ();
+}
+
+static void
+handle_method_call (GDBusConnection *connection,
+ const gchar *sender,
+ const gchar *object_path,
+ const gchar *interface_name,
+ const gchar *method_name,
+ GVariant *parameters,
+ GDBusMethodInvocation *invocation,
+ gpointer user_data)
+{
+ /* Push off the idle timeout */
+ establish_timeout ();
+
+ if (g_strcmp0 (method_name, "Exit") == 0)
+ {
+ destroy_windows ();
+
+ g_dbus_method_invocation_return_value (invocation, NULL);
+ g_dbus_connection_flush_sync (connection, NULL, NULL);
+
+ gtk_main_quit ();
+ }
+ else if (g_strcmp0 (method_name, "CreateWindow") == 0)
+ {
+ int width, height;
+ gboolean alpha, maximized, redraws;
+
+ g_variant_get (parameters, "(iibbb)", &width, &height, &alpha, &maximized, &redraws);
+
+ create_window (width, height, alpha, maximized, redraws);
+ g_dbus_method_invocation_return_value (invocation, NULL);
+ }
+ else if (g_strcmp0 (method_name, "WaitWindows") == 0)
+ {
+ wait_windows_invocations = g_list_prepend (wait_windows_invocations, invocation);
+ check_finish_wait_windows ();
+ }
+ else if (g_strcmp0 (method_name, "DestroyWindows") == 0)
+ {
+ destroy_windows ();
+ g_dbus_method_invocation_return_value (invocation, NULL);
+ }
+}
+
+static const GDBusInterfaceVTable interface_vtable =
+{
+ handle_method_call,
+ NULL,
+ NULL
+};
+
+static void
+on_bus_acquired (GDBusConnection *connection,
+ const gchar *name,
+ gpointer user_data)
+{
+ GDBusNodeInfo *introspection_data = g_dbus_node_info_new_for_xml (introspection_xml, NULL);
+
+ g_dbus_connection_register_object (connection,
+ "/org/gnome/Shell/PerfHelper",
+ introspection_data->interfaces[0],
+ &interface_vtable,
+ NULL, /* user_data */
+ NULL, /* user_data_free_func */
+ NULL); /* GError** */
+}
+
+static void
+on_name_acquired (GDBusConnection *connection,
+ const gchar *name,
+ gpointer user_data)
+{
+}
+
+static void
+on_name_lost (GDBusConnection *connection,
+ const gchar *name,
+ gpointer user_data)
+{
+ destroy_windows ();
+ gtk_main_quit ();
+}
+
+int
+main (int argc, char **argv)
+{
+ GOptionContext *context;
+ GError *error = NULL;
+
+ /* Since we depend on this, avoid the possibility of lt-gnome-shell-perf-helper */
+ g_set_prgname ("gnome-shell-perf-helper");
+
+ context = g_option_context_new (" - server to create windows for performance testing");
+ g_option_context_add_main_entries (context, opt_entries, NULL);
+ g_option_context_add_group (context, gtk_get_option_group (TRUE));
+ if (!g_option_context_parse (context, &argc, &argv, &error))
+ {
+ g_print ("option parsing failed: %s\n", error->message);
+ return 1;
+ }
+
+ g_bus_own_name (G_BUS_TYPE_SESSION,
+ BUS_NAME,
+ G_BUS_NAME_OWNER_FLAGS_ALLOW_REPLACEMENT |
+ G_BUS_NAME_OWNER_FLAGS_REPLACE,
+ on_bus_acquired,
+ on_name_acquired,
+ on_name_lost,
+ NULL,
+ NULL);
+
+ establish_timeout ();
+
+ gtk_main ();
+
+ return 0;
+}
diff --git a/src/shell-perf-log.c b/src/shell-perf-log.c
new file mode 100644
index 0000000..3bd5228
--- /dev/null
+++ b/src/shell-perf-log.c
@@ -0,0 +1,959 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+#include "config.h"
+
+#include <string.h>
+
+#include "shell-perf-log.h"
+
+typedef struct _ShellPerfEvent ShellPerfEvent;
+typedef struct _ShellPerfStatistic ShellPerfStatistic;
+typedef struct _ShellPerfStatisticsClosure ShellPerfStatisticsClosure;
+typedef union _ShellPerfStatisticValue ShellPerfStatisticValue;
+typedef struct _ShellPerfBlock ShellPerfBlock;
+
+/**
+ * SECTION:shell-perf-log
+ * @short_description: Event recorder for performance measurement
+ *
+ * ShellPerfLog provides a way for different parts of the code to
+ * record information for subsequent analysis and interactive
+ * exploration. Events exist of a timestamp, an event ID, and
+ * arguments to the event.
+ *
+ * Emphasis is placed on storing recorded events in a compact
+ * fashion so log recording disturbs the execution of the program
+ * as little as possible, however events should not be recorded
+ * at too fine a granularity - an event that is recorded once
+ * per frame or once per user action is appropriate, an event that
+ * occurs many times per frame is not.
+ *
+ * Arguments are identified by a D-Bus style signature; at the moment
+ * only a limited number of event signatures are supported to
+ * simplify the code.
+ */
+struct _ShellPerfLog
+{
+ GObject parent;
+
+ GPtrArray *events;
+ GHashTable *events_by_name;
+ GPtrArray *statistics;
+ GHashTable *statistics_by_name;
+
+ GPtrArray *statistics_closures;
+
+ GQueue *blocks;
+
+ gint64 start_time;
+ gint64 last_time;
+
+ guint statistics_timeout_id;
+
+ guint enabled : 1;
+};
+
+struct _ShellPerfEvent
+{
+ guint16 id;
+ char *name;
+ char *description;
+ char *signature;
+};
+
+union _ShellPerfStatisticValue
+{
+ int i;
+ gint64 x;
+};
+
+struct _ShellPerfStatistic
+{
+ ShellPerfEvent *event;
+
+ ShellPerfStatisticValue current_value;
+ ShellPerfStatisticValue last_value;
+
+ guint initialized : 1;
+ guint recorded : 1;
+};
+
+struct _ShellPerfStatisticsClosure
+{
+ ShellPerfStatisticsCallback callback;
+ gpointer user_data;
+ GDestroyNotify notify;
+};
+
+/* The events in the log are stored in a linked list of fixed size
+ * blocks.
+ *
+ * Note that the power-of-two nature of BLOCK_SIZE here is superficial
+ * since the allocated block has the 'bytes' field and malloc
+ * overhead. The current value is well below the size that will
+ * typically be independently mmapped by the malloc implementation so
+ * it doesn't matter. If we switched to mmapping blocks manually
+ * (perhaps to avoid polluting malloc statistics), we'd want to use a
+ * different value of BLOCK_SIZE.
+ */
+#define BLOCK_SIZE 8192
+
+struct _ShellPerfBlock
+{
+ guint32 bytes;
+ guchar buffer[BLOCK_SIZE];
+};
+
+/* Number of milliseconds between periodic statistics collection when
+ * events are enabled. Statistics collection can also be explicitly
+ * triggered.
+ */
+#define STATISTIC_COLLECTION_INTERVAL_MS 5000
+
+/* Builtin events */
+enum {
+ EVENT_SET_TIME,
+ EVENT_STATISTICS_COLLECTED
+};
+
+G_DEFINE_TYPE(ShellPerfLog, shell_perf_log, G_TYPE_OBJECT);
+
+static gint64
+get_time (void)
+{
+ return g_get_monotonic_time ();
+}
+
+static void
+shell_perf_log_init (ShellPerfLog *perf_log)
+{
+ perf_log->events = g_ptr_array_new ();
+ perf_log->events_by_name = g_hash_table_new (g_str_hash, g_str_equal);
+ perf_log->statistics = g_ptr_array_new ();
+ perf_log->statistics_by_name = g_hash_table_new (g_str_hash, g_str_equal);
+ perf_log->statistics_closures = g_ptr_array_new ();
+ perf_log->blocks = g_queue_new ();
+
+ /* This event is used when timestamp deltas are greater than
+ * fits in a gint32. 0xffffffff microseconds is about 70 minutes, so this
+ * is not going to happen in normal usage. It might happen if performance
+ * logging is enabled some time after starting the shell */
+ shell_perf_log_define_event (perf_log, "perf.setTime", "", "x");
+ g_assert (perf_log->events->len == EVENT_SET_TIME + 1);
+
+ /* The purpose of this event is to allow us to optimize out storing
+ * statistics that haven't changed. We want to mark every time we
+ * collect statistics even if we don't record any individual
+ * statistics so that we can distinguish sudden changes from gradual changes.
+ *
+ * The argument is the number of microseconds that statistics collection
+ * took; we record that since statistics collection could start taking
+ * significant time if we do things like grub around in /proc/
+ */
+ shell_perf_log_define_event (perf_log, "perf.statisticsCollected",
+ "Finished collecting statistics",
+ "x");
+ g_assert (perf_log->events->len == EVENT_STATISTICS_COLLECTED + 1);
+
+ perf_log->start_time = perf_log->last_time = get_time();
+}
+
+static void
+shell_perf_log_class_init (ShellPerfLogClass *class)
+{
+}
+
+/**
+ * shell_perf_log_get_default:
+ *
+ * Gets the global singleton performance log. This is initially disabled
+ * and must be explicitly enabled with shell_perf_log_set_enabled().
+ *
+ * Return value: (transfer none): the global singleton performance log
+ */
+ShellPerfLog *
+shell_perf_log_get_default (void)
+{
+ static ShellPerfLog *perf_log;
+
+ if (perf_log == NULL)
+ perf_log = g_object_new (SHELL_TYPE_PERF_LOG, NULL);
+
+ return perf_log;
+}
+
+static gboolean
+statistics_timeout (gpointer data)
+{
+ ShellPerfLog *perf_log = data;
+
+ shell_perf_log_collect_statistics (perf_log);
+
+ return TRUE;
+}
+
+/**
+ * shell_perf_log_set_enabled:
+ * @perf_log: a #ShellPerfLog
+ * @enabled: whether to record events
+ *
+ * Sets whether events are currently being recorded.
+ */
+void
+shell_perf_log_set_enabled (ShellPerfLog *perf_log,
+ gboolean enabled)
+{
+ enabled = enabled != FALSE;
+
+ if (enabled != perf_log->enabled)
+ {
+ perf_log->enabled = enabled;
+
+ if (enabled)
+ {
+ perf_log->statistics_timeout_id = g_timeout_add (STATISTIC_COLLECTION_INTERVAL_MS,
+ statistics_timeout,
+ perf_log);
+ g_source_set_name_by_id (perf_log->statistics_timeout_id, "[gnome-shell] statistics_timeout");
+ }
+ else
+ {
+ g_clear_handle_id (&perf_log->statistics_timeout_id, g_source_remove);
+ }
+ }
+}
+
+static ShellPerfEvent *
+define_event (ShellPerfLog *perf_log,
+ const char *name,
+ const char *description,
+ const char *signature)
+{
+ ShellPerfEvent *event;
+
+ if (strcmp (signature, "") != 0 &&
+ strcmp (signature, "s") != 0 &&
+ strcmp (signature, "i") != 0 &&
+ strcmp (signature, "x") != 0)
+ {
+ g_warning ("Only supported event signatures are '', 's', 'i', and 'x'\n");
+ return NULL;
+ }
+
+ if (perf_log->events->len == 65536)
+ {
+ g_warning ("Maximum number of events defined\n");
+ return NULL;
+ }
+
+ /* We could do stricter validation, but this will break our JSON dumps */
+ if (strchr (name, '"') != NULL)
+ {
+ g_warning ("Event names can't include '\"'");
+ return NULL;
+ }
+
+ if (g_hash_table_lookup (perf_log->events_by_name, name) != NULL)
+ {
+ g_warning ("Duplicate event event for '%s'\n", name);
+ return NULL;
+ }
+
+ event = g_new (ShellPerfEvent, 1);
+
+ event->id = perf_log->events->len;
+ event->name = g_strdup (name);
+ event->signature = g_strdup (signature);
+ event->description = g_strdup (description);
+
+ g_ptr_array_add (perf_log->events, event);
+ g_hash_table_insert (perf_log->events_by_name, event->name, event);
+
+ return event;
+}
+
+/**
+ * shell_perf_log_define_event:
+ * @perf_log: a #ShellPerfLog
+ * @name: name of the event. This should of the form
+ * '<namespace>.<specific eventf'>, for example
+ * 'clutter.stagePaintDone'.
+ * @description: human readable description of the event.
+ * @signature: signature defining the arguments that event takes.
+ * This is a string of type characters, using the same characters
+ * as D-Bus or GVariant. Only a very limited number of signatures
+ * are supported: , '', 's', 'i', and 'x'. This mean respectively:
+ * no arguments, one string, one 32-bit integer, and one 64-bit
+ * integer.
+ *
+ * Defines a performance event for later recording.
+ */
+void
+shell_perf_log_define_event (ShellPerfLog *perf_log,
+ const char *name,
+ const char *description,
+ const char *signature)
+{
+ define_event (perf_log, name, description, signature);
+}
+
+static ShellPerfEvent *
+lookup_event (ShellPerfLog *perf_log,
+ const char *name,
+ const char *signature)
+{
+ ShellPerfEvent *event = g_hash_table_lookup (perf_log->events_by_name, name);
+
+ if (G_UNLIKELY (event == NULL))
+ {
+ g_warning ("Discarding unknown event '%s'\n", name);
+ return NULL;
+ }
+
+ if (G_UNLIKELY (strcmp (event->signature, signature) != 0))
+ {
+ g_warning ("Event '%s'; defined with signature '%s', used with '%s'\n",
+ name, event->signature, signature);
+ return NULL;
+ }
+
+ return event;
+}
+
+static void
+record_event (ShellPerfLog *perf_log,
+ gint64 event_time,
+ ShellPerfEvent *event,
+ const guchar *bytes,
+ size_t bytes_len)
+{
+ ShellPerfBlock *block;
+ size_t total_bytes;
+ guint32 time_delta;
+ guint32 pos;
+
+ if (!perf_log->enabled)
+ return;
+
+ total_bytes = sizeof (gint32) + sizeof (gint16) + bytes_len;
+ if (G_UNLIKELY (bytes_len > BLOCK_SIZE || total_bytes > BLOCK_SIZE))
+ {
+ g_warning ("Discarding oversize event '%s'\n", event->name);
+ return;
+ }
+
+ if (event_time > perf_log->last_time + G_GINT64_CONSTANT(0xffffffff))
+ {
+ perf_log->last_time = event_time;
+ record_event (perf_log, event_time,
+ lookup_event (perf_log, "perf.setTime", "x"),
+ (const guchar *)&event_time, sizeof(gint64));
+ time_delta = 0;
+ }
+ else if (event_time < perf_log->last_time)
+ time_delta = 0;
+ else
+ time_delta = (guint32)(event_time - perf_log->last_time);
+
+ perf_log->last_time = event_time;
+
+ if (perf_log->blocks->tail == NULL ||
+ total_bytes + ((ShellPerfBlock *)perf_log->blocks->tail->data)->bytes > BLOCK_SIZE)
+ {
+ block = g_new (ShellPerfBlock, 1);
+ block->bytes = 0;
+ g_queue_push_tail (perf_log->blocks, block);
+ }
+ else
+ {
+ block = (ShellPerfBlock *)perf_log->blocks->tail->data;
+ }
+
+ pos = block->bytes;
+
+ memcpy (block->buffer + pos, &time_delta, sizeof (guint32));
+ pos += sizeof (guint32);
+ memcpy (block->buffer + pos, &event->id, sizeof (guint16));
+ pos += sizeof (guint16);
+ memcpy (block->buffer + pos, bytes, bytes_len);
+ pos += bytes_len;
+
+ block->bytes = pos;
+}
+
+/**
+ * shell_perf_log_event:
+ * @perf_log: a #ShellPerfLog
+ * @name: name of the event
+ *
+ * Records a performance event with no arguments.
+ */
+void
+shell_perf_log_event (ShellPerfLog *perf_log,
+ const char *name)
+{
+ ShellPerfEvent *event = lookup_event (perf_log, name, "");
+ if (G_UNLIKELY (event == NULL))
+ return;
+
+ record_event (perf_log, get_time(), event, NULL, 0);
+}
+
+/**
+ * shell_perf_log_event_i:
+ * @perf_log: a #ShellPerfLog
+ * @name: name of the event
+ * @arg: the argument
+ *
+ * Records a performance event with one 32-bit integer argument.
+ */
+void
+shell_perf_log_event_i (ShellPerfLog *perf_log,
+ const char *name,
+ gint32 arg)
+{
+ ShellPerfEvent *event = lookup_event (perf_log, name, "i");
+ if (G_UNLIKELY (event == NULL))
+ return;
+
+ record_event (perf_log, get_time(), event,
+ (const guchar *)&arg, sizeof (arg));
+}
+
+/**
+ * shell_perf_log_event_x:
+ * @perf_log: a #ShellPerfLog
+ * @name: name of the event
+ * @arg: the argument
+ *
+ * Records a performance event with one 64-bit integer argument.
+ */
+void
+shell_perf_log_event_x (ShellPerfLog *perf_log,
+ const char *name,
+ gint64 arg)
+{
+ ShellPerfEvent *event = lookup_event (perf_log, name, "x");
+ if (G_UNLIKELY (event == NULL))
+ return;
+
+ record_event (perf_log, get_time(), event,
+ (const guchar *)&arg, sizeof (arg));
+}
+
+/**
+ * shell_perf_log_event_s:
+ * @perf_log: a #ShellPerfLog
+ * @name: name of the event
+ * @arg: the argument
+ *
+ * Records a performance event with one string argument.
+ */
+void
+shell_perf_log_event_s (ShellPerfLog *perf_log,
+ const char *name,
+ const char *arg)
+{
+ ShellPerfEvent *event = lookup_event (perf_log, name, "s");
+ if (G_UNLIKELY (event == NULL))
+ return;
+
+ record_event (perf_log, get_time(), event,
+ (const guchar *)arg, strlen (arg) + 1);
+}
+
+/**
+ * shell_perf_log_define_statistic:
+ * @name: name of the statistic and of the corresponding event.
+ * This should follow the same guidelines as for shell_perf_log_define_event()
+ * @description: human readable description of the statistic.
+ * @signature: The type of the data stored for statistic. Must
+ * currently be 'i' or 'x'.
+ *
+ * Defines a statistic. A statistic is a numeric value that is stored
+ * by the performance log and recorded periodically or when
+ * shell_perf_log_collect_statistics() is called explicitly.
+ *
+ * Code that defines a statistic should update it by calling
+ * the update function for the particular data type of the statistic,
+ * such as shell_perf_log_update_statistic_i(). This can be done
+ * at any time, but would normally done inside a function registered
+ * with shell_perf_log_add_statistics_callback(). These functions
+ * are called immediately before statistics are recorded.
+ */
+void
+shell_perf_log_define_statistic (ShellPerfLog *perf_log,
+ const char *name,
+ const char *description,
+ const char *signature)
+{
+ ShellPerfEvent *event;
+ ShellPerfStatistic *statistic;
+
+ if (strcmp (signature, "i") != 0 &&
+ strcmp (signature, "x") != 0)
+ {
+ g_warning ("Only supported statistic signatures are 'i' and 'x'\n");
+ return;
+ }
+
+ event = define_event (perf_log, name, description, signature);
+ if (event == NULL)
+ return;
+
+ statistic = g_new (ShellPerfStatistic, 1);
+ statistic->event = event;
+
+ statistic->initialized = FALSE;
+ statistic->recorded = FALSE;
+
+ g_ptr_array_add (perf_log->statistics, statistic);
+ g_hash_table_insert (perf_log->statistics_by_name, event->name, statistic);
+}
+
+static ShellPerfStatistic *
+lookup_statistic (ShellPerfLog *perf_log,
+ const char *name,
+ const char *signature)
+{
+ ShellPerfStatistic *statistic = g_hash_table_lookup (perf_log->statistics_by_name, name);
+
+ if (G_UNLIKELY (statistic == NULL))
+ {
+ g_warning ("Unknown statistic '%s'\n", name);
+ return NULL;
+ }
+
+ if (G_UNLIKELY (strcmp (statistic->event->signature, signature) != 0))
+ {
+ g_warning ("Statistic '%s'; defined with signature '%s', used with '%s'\n",
+ name, statistic->event->signature, signature);
+ return NULL;
+ }
+
+ return statistic;
+}
+
+/**
+ * shell_perf_log_update_statistic_i:
+ * @perf_log: a #ShellPerfLog
+ * @name: name of the statistic
+ * @value: new value for the statistic
+ *
+ * Updates the current value of an 32-bit integer statistic.
+ */
+void
+shell_perf_log_update_statistic_i (ShellPerfLog *perf_log,
+ const char *name,
+ gint32 value)
+{
+ ShellPerfStatistic *statistic;
+
+ statistic = lookup_statistic (perf_log, name, "i");
+ if (G_UNLIKELY (statistic == NULL))
+ return;
+
+ statistic->current_value.i = value;
+ statistic->initialized = TRUE;
+}
+
+/**
+ * shell_perf_log_update_statistic_x:
+ * @perf_log: a #ShellPerfLog
+ * @name: name of the statistic
+ * @value: new value for the statistic
+ *
+ * Updates the current value of an 64-bit integer statistic.
+ */
+void
+shell_perf_log_update_statistic_x (ShellPerfLog *perf_log,
+ const char *name,
+ gint64 value)
+{
+ ShellPerfStatistic *statistic;
+
+ statistic = lookup_statistic (perf_log, name, "x");
+ if (G_UNLIKELY (statistic == NULL))
+ return;
+
+ statistic->current_value.x = value;
+ statistic->initialized = TRUE;
+}
+
+/**
+ * shell_perf_log_add_statistics_callback:
+ * @perf_log: a #ShellPerfLog
+ * @callback: function to call before recording statistics
+ * @user_data: data to pass to @callback
+ * @notify: function to call when @user_data is no longer needed
+ *
+ * Adds a function that will be called before statistics are recorded.
+ * The function would typically compute one or more statistics values
+ * and call a function such as shell_perf_log_update_statistic_i()
+ * to update the value that will be recorded.
+ */
+void
+shell_perf_log_add_statistics_callback (ShellPerfLog *perf_log,
+ ShellPerfStatisticsCallback callback,
+ gpointer user_data,
+ GDestroyNotify notify)
+{
+ ShellPerfStatisticsClosure *closure = g_new (ShellPerfStatisticsClosure, 1);
+
+ closure->callback = callback;
+ closure->user_data = user_data;
+ closure->notify = notify;
+
+ g_ptr_array_add (perf_log->statistics_closures, closure);
+}
+
+/**
+ * shell_perf_log_collect_statistics:
+ * @perf_log: a #ShellPerfLog
+ *
+ * Calls all the update functions added with
+ * shell_perf_log_add_statistics_callback() and then records events
+ * for all statistics, followed by a perf.statisticsCollected event.
+ */
+void
+shell_perf_log_collect_statistics (ShellPerfLog *perf_log)
+{
+ gint64 event_time = get_time ();
+ gint64 collection_time;
+ guint i;
+
+ if (!perf_log->enabled)
+ return;
+
+ for (i = 0; i < perf_log->statistics_closures->len; i++)
+ {
+ ShellPerfStatisticsClosure *closure;
+
+ closure = g_ptr_array_index (perf_log->statistics_closures, i);
+ closure->callback (perf_log, closure->user_data);
+ }
+
+ collection_time = get_time() - event_time;
+
+ for (i = 0; i < perf_log->statistics->len; i++)
+ {
+ ShellPerfStatistic *statistic = g_ptr_array_index (perf_log->statistics, i);
+
+ if (!statistic->initialized)
+ continue;
+
+ switch (statistic->event->signature[0])
+ {
+ case 'i':
+ if (!statistic->recorded ||
+ statistic->current_value.i != statistic->last_value.i)
+ {
+ record_event (perf_log, event_time, statistic->event,
+ (const guchar *)&statistic->current_value.i,
+ sizeof (gint32));
+ statistic->last_value.i = statistic->current_value.i;
+ statistic->recorded = TRUE;
+ }
+ break;
+ case 'x':
+ if (!statistic->recorded ||
+ statistic->current_value.x != statistic->last_value.x)
+ {
+ record_event (perf_log, event_time, statistic->event,
+ (const guchar *)&statistic->current_value.x,
+ sizeof (gint64));
+ statistic->last_value.x = statistic->current_value.x;
+ statistic->recorded = TRUE;
+ }
+ break;
+ default:
+ g_warning ("Unsupported signature in event");
+ break;
+ }
+ }
+
+ record_event (perf_log, event_time,
+ g_ptr_array_index (perf_log->events, EVENT_STATISTICS_COLLECTED),
+ (const guchar *)&collection_time, sizeof (gint64));
+}
+
+/**
+ * shell_perf_log_replay:
+ * @perf_log: a #ShellPerfLog
+ * @replay_function: (scope call): function to call for each event in the log
+ * @user_data: data to pass to @replay_function
+ *
+ * Replays the log by calling the given function for each event
+ * in the log.
+ */
+void
+shell_perf_log_replay (ShellPerfLog *perf_log,
+ ShellPerfReplayFunction replay_function,
+ gpointer user_data)
+{
+ gint64 event_time = perf_log->start_time;
+ GList *iter;
+
+ for (iter = perf_log->blocks->head; iter; iter = iter->next)
+ {
+ ShellPerfBlock *block = iter->data;
+ guint32 pos = 0;
+
+ while (pos < block->bytes)
+ {
+ ShellPerfEvent *event;
+ guint16 id;
+ guint32 time_delta;
+ GValue arg = { 0, };
+
+ memcpy (&time_delta, block->buffer + pos, sizeof (guint32));
+ pos += sizeof (guint32);
+ memcpy (&id, block->buffer + pos, sizeof (guint16));
+ pos += sizeof (guint16);
+
+ if (id == EVENT_SET_TIME)
+ {
+ /* Internal, we don't include in the replay */
+ memcpy (&event_time, block->buffer + pos, sizeof (gint64));
+ pos += sizeof (gint64);
+ continue;
+ }
+ else
+ {
+ event_time += time_delta;
+ }
+
+ event = g_ptr_array_index (perf_log->events, id);
+
+ if (strcmp (event->signature, "") == 0)
+ {
+ /* We need to pass something, so pass an empty string */
+ g_value_init (&arg, G_TYPE_STRING);
+ }
+ else if (strcmp (event->signature, "i") == 0)
+ {
+ gint32 l;
+
+ memcpy (&l, block->buffer + pos, sizeof (gint32));
+ pos += sizeof (gint32);
+
+ g_value_init (&arg, G_TYPE_INT);
+ g_value_set_int (&arg, l);
+ }
+ else if (strcmp (event->signature, "x") == 0)
+ {
+ gint64 l;
+
+ memcpy (&l, block->buffer + pos, sizeof (gint64));
+ pos += sizeof (gint64);
+
+ g_value_init (&arg, G_TYPE_INT64);
+ g_value_set_int64 (&arg, l);
+ }
+ else if (strcmp (event->signature, "s") == 0)
+ {
+ g_value_init (&arg, G_TYPE_STRING);
+ g_value_set_string (&arg, (char *)block->buffer + pos);
+ pos += strlen ((char *)(block->buffer + pos)) + 1;
+ }
+
+ replay_function (event_time, event->name, event->signature, &arg, user_data);
+ g_value_unset (&arg);
+ }
+ }
+}
+
+static char *
+escape_quotes (const char *input)
+{
+ GString *result;
+ const char *p;
+
+ if (strchr (input, '"') == NULL)
+ return (char *)input;
+
+ result = g_string_new (NULL);
+ for (p = input; *p; p++)
+ {
+ if (*p == '"')
+ g_string_append (result, "\\\"");
+ else
+ g_string_append_c (result, *p);
+ }
+
+ return g_string_free (result, FALSE);
+}
+
+static gboolean
+write_string (GOutputStream *out,
+ const char *str,
+ GError **error)
+{
+ return g_output_stream_write_all (out, str, strlen (str),
+ NULL, NULL,
+ error);
+}
+
+/**
+ * shell_perf_log_dump_events:
+ * @perf_log: a #ShellPerfLog
+ * @out: output stream into which to write the event definitions
+ * @error: location to store #GError, or %NULL
+ *
+ * Dump the definition of currently defined events and statistics, formatted
+ * as JSON, to the specified output stream. The JSON output is an array,
+ * with each element being a dictionary of the form:
+ *
+ * { name: <name of event>,
+ * description: <description of string,
+ * statistic: true } (only for statistics)
+ *
+ * Return value: %TRUE if the dump succeeded. %FALSE if an IO error occurred
+ */
+gboolean
+shell_perf_log_dump_events (ShellPerfLog *perf_log,
+ GOutputStream *out,
+ GError **error)
+{
+ GString *output;
+ guint i;
+
+ output = g_string_new (NULL);
+ g_string_append (output, "[ ");
+
+ for (i = 0; i < perf_log->events->len; i++)
+ {
+ ShellPerfEvent *event = g_ptr_array_index (perf_log->events, i);
+ char *escaped_description = escape_quotes (event->description);
+ gboolean is_statistic = g_hash_table_lookup (perf_log->statistics_by_name, event->name) != NULL;
+
+ if (i != 0)
+ g_string_append (output, ",\n ");
+
+ g_string_append_printf (output,
+ "{ \"name\": \"%s\",\n"
+ " \"description\": \"%s\"",
+ event->name, escaped_description);
+ if (is_statistic)
+ g_string_append (output, ",\n \"statistic\": true");
+
+ g_string_append (output, " }");
+
+ if (escaped_description != event->description)
+ g_free (escaped_description);
+ }
+
+ g_string_append (output, " ]");
+
+ return write_string (out, g_string_free (output, FALSE), error);
+}
+
+typedef struct {
+ GOutputStream *out;
+ GError *error;
+ gboolean first;
+} ReplayToJsonClosure;
+
+static void
+replay_to_json (gint64 time,
+ const char *name,
+ const char *signature,
+ GValue *arg,
+ gpointer user_data)
+{
+ ReplayToJsonClosure *closure = user_data;
+ g_autofree char *event_str = NULL;
+
+ if (closure->error != NULL)
+ return;
+
+ if (!closure->first)
+ {
+ if (!write_string (closure->out, ",\n ", &closure->error))
+ return;
+ }
+
+ closure->first = FALSE;
+
+ if (strcmp (signature, "") == 0)
+ {
+ event_str = g_strdup_printf ("[%" G_GINT64_FORMAT ", \"%s\"]", time, name);
+ }
+ else if (strcmp (signature, "i") == 0)
+ {
+ event_str = g_strdup_printf ("[%" G_GINT64_FORMAT ", \"%s\", %i]",
+ time,
+ name,
+ g_value_get_int (arg));
+ }
+ else if (strcmp (signature, "x") == 0)
+ {
+ event_str = g_strdup_printf ("[%" G_GINT64_FORMAT ", \"%s\", %"G_GINT64_FORMAT "]",
+ time,
+ name,
+ g_value_get_int64 (arg));
+ }
+ else if (strcmp (signature, "s") == 0)
+ {
+ const char *arg_str = g_value_get_string (arg);
+ char *escaped = escape_quotes (arg_str);
+
+ event_str = g_strdup_printf ("[%" G_GINT64_FORMAT ", \"%s\", \"%s\"]",
+ time,
+ name,
+ g_value_get_string (arg));
+
+ if (escaped != arg_str)
+ g_free (escaped);
+ }
+ else
+ {
+ g_assert_not_reached ();
+ }
+
+ if (!write_string (closure->out, event_str, &closure->error))
+ return;
+}
+
+/**
+ * shell_perf_log_dump_log:
+ * @perf_log: a #ShellPerfLog
+ * @out: output stream into which to write the event log
+ * @error: location to store #GError, or %NULL
+ *
+ * Writes the performance event log, formatted as JSON, to the specified
+ * output stream. For performance reasons, the output stream passed
+ * in should generally be a buffered (or memory) output stream, since
+ * it will be written to in small pieces. The JSON output is an array
+ * with the elements of the array also being arrays, of the form
+ * '[' <time>, <event name> [, <event_arg>... ] ']'.
+ *
+ * Return value: %TRUE if the dump succeeded. %FALSE if an IO error occurred
+ */
+gboolean
+shell_perf_log_dump_log (ShellPerfLog *perf_log,
+ GOutputStream *out,
+ GError **error)
+{
+ ReplayToJsonClosure closure;
+
+ closure.out = out;
+ closure.error = NULL;
+ closure.first = TRUE;
+
+ if (!write_string (out, "[ ", &closure.error))
+ return FALSE;
+
+ shell_perf_log_replay (perf_log, replay_to_json, &closure);
+
+ if (closure.error != NULL)
+ {
+ g_propagate_error (error, closure.error);
+ return FALSE;
+ }
+
+ if (!write_string (out, " ]", &closure.error))
+ return FALSE;
+
+ return TRUE;
+}
diff --git a/src/shell-perf-log.h b/src/shell-perf-log.h
new file mode 100644
index 0000000..45d07a5
--- /dev/null
+++ b/src/shell-perf-log.h
@@ -0,0 +1,75 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+#ifndef __SHELL_PERF_LOG_H__
+#define __SHELL_PERF_LOG_H__
+
+#include <glib-object.h>
+#include <gio/gio.h>
+
+G_BEGIN_DECLS
+
+#define SHELL_TYPE_PERF_LOG (shell_perf_log_get_type ())
+G_DECLARE_FINAL_TYPE (ShellPerfLog, shell_perf_log, SHELL, PERF_LOG, GObject)
+
+ShellPerfLog *shell_perf_log_get_default (void);
+
+void shell_perf_log_set_enabled (ShellPerfLog *perf_log,
+ gboolean enabled);
+
+void shell_perf_log_define_event (ShellPerfLog *perf_log,
+ const char *name,
+ const char *description,
+ const char *signature);
+void shell_perf_log_event (ShellPerfLog *perf_log,
+ const char *name);
+void shell_perf_log_event_i (ShellPerfLog *perf_log,
+ const char *name,
+ gint32 arg);
+void shell_perf_log_event_x (ShellPerfLog *perf_log,
+ const char *name,
+ gint64 arg);
+void shell_perf_log_event_s (ShellPerfLog *perf_log,
+ const char *name,
+ const char *arg);
+
+void shell_perf_log_define_statistic (ShellPerfLog *perf_log,
+ const char *name,
+ const char *description,
+ const char *signature);
+
+void shell_perf_log_update_statistic_i (ShellPerfLog *perf_log,
+ const char *name,
+ int value);
+void shell_perf_log_update_statistic_x (ShellPerfLog *perf_log,
+ const char *name,
+ gint64 value);
+
+typedef void (*ShellPerfStatisticsCallback) (ShellPerfLog *perf_log,
+ gpointer data);
+
+void shell_perf_log_add_statistics_callback (ShellPerfLog *perf_log,
+ ShellPerfStatisticsCallback callback,
+ gpointer user_data,
+ GDestroyNotify notify);
+
+void shell_perf_log_collect_statistics (ShellPerfLog *perf_log);
+
+typedef void (*ShellPerfReplayFunction) (gint64 time,
+ const char *name,
+ const char *signature,
+ GValue *arg,
+ gpointer user_data);
+
+void shell_perf_log_replay (ShellPerfLog *perf_log,
+ ShellPerfReplayFunction replay_function,
+ gpointer user_data);
+
+gboolean shell_perf_log_dump_events (ShellPerfLog *perf_log,
+ GOutputStream *out,
+ GError **error);
+gboolean shell_perf_log_dump_log (ShellPerfLog *perf_log,
+ GOutputStream *out,
+ GError **error);
+
+G_END_DECLS
+
+#endif /* __SHELL_PERF_LOG_H__ */
diff --git a/src/shell-polkit-authentication-agent.c b/src/shell-polkit-authentication-agent.c
new file mode 100644
index 0000000..6815f01
--- /dev/null
+++ b/src/shell-polkit-authentication-agent.c
@@ -0,0 +1,429 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+/*
+ * Copyright (C) 20011 Red Hat, Inc.
+ *
+ * Author: David Zeuthen <davidz@redhat.com>
+ */
+
+#include "config.h"
+
+#include <pwd.h>
+
+#include "shell-polkit-authentication-agent.h"
+
+#include <glib/gi18n.h>
+
+/* uncomment for useful debug output */
+/* #define SHOW_DEBUG */
+
+#ifdef SHOW_DEBUG
+static void
+print_debug (const gchar *format, ...)
+{
+ g_autofree char *s = NULL;
+ g_autofree char *timestamp = NULL;
+ g_autoptr (GDateTime) now = NULL;
+ va_list ap;
+
+ now = g_date_time_new_now_local ();
+ timestamp = g_date_time_format (now, "%H:%M:%S");
+
+ va_start (ap, format);
+ s = g_strdup_vprintf (format, ap);
+ va_end (ap);
+
+ g_print ("ShellPolkitAuthenticationAgent: %s.%03d: %s\n",
+ timestamp, g_date_time_get_microsecond (now), s);
+}
+#else
+static void
+print_debug (const gchar *str, ...)
+{
+}
+#endif
+
+struct _AuthRequest;
+typedef struct _AuthRequest AuthRequest;
+
+struct _ShellPolkitAuthenticationAgent {
+ PolkitAgentListener parent_instance;
+
+ GList *scheduled_requests;
+ AuthRequest *current_request;
+
+ gpointer handle;
+};
+
+/* Signals */
+enum
+{
+ INITIATE_SIGNAL,
+ CANCEL_SIGNAL,
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = { 0 };
+
+G_DEFINE_TYPE (ShellPolkitAuthenticationAgent, shell_polkit_authentication_agent, POLKIT_AGENT_TYPE_LISTENER);
+
+static void initiate_authentication (PolkitAgentListener *listener,
+ const gchar *action_id,
+ const gchar *message,
+ const gchar *icon_name,
+ PolkitDetails *details,
+ const gchar *cookie,
+ GList *identities,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+
+static gboolean initiate_authentication_finish (PolkitAgentListener *listener,
+ GAsyncResult *res,
+ GError **error);
+
+void
+shell_polkit_authentication_agent_init (ShellPolkitAuthenticationAgent *agent)
+{
+}
+
+void
+shell_polkit_authentication_agent_register (ShellPolkitAuthenticationAgent *agent,
+ GError **error_out)
+{
+ GError *error = NULL;
+ PolkitSubject *subject;
+
+ subject = polkit_unix_session_new_for_process_sync (getpid (),
+ NULL, /* GCancellable* */
+ &error);
+ if (subject == NULL)
+ {
+ if (error == NULL) /* polkit version 104 and older don't properly set error on failure */
+ error = g_error_new (POLKIT_ERROR, POLKIT_ERROR_FAILED,
+ "PolKit failed to properly get our session");
+ goto out;
+ }
+
+ agent->handle = polkit_agent_listener_register (POLKIT_AGENT_LISTENER (agent),
+ POLKIT_AGENT_REGISTER_FLAGS_NONE,
+ subject,
+ NULL, /* use default object path */
+ NULL, /* GCancellable */
+ &error);
+
+ out:
+ if (error != NULL)
+ g_propagate_error (error_out, error);
+
+ if (subject != NULL)
+ g_object_unref (subject);
+}
+
+static void
+shell_polkit_authentication_agent_finalize (GObject *object)
+{
+ ShellPolkitAuthenticationAgent *agent = SHELL_POLKIT_AUTHENTICATION_AGENT (object);
+ shell_polkit_authentication_agent_unregister (agent);
+ G_OBJECT_CLASS (shell_polkit_authentication_agent_parent_class)->finalize (object);
+}
+
+static void
+shell_polkit_authentication_agent_class_init (ShellPolkitAuthenticationAgentClass *klass)
+{
+ GObjectClass *gobject_class;
+ PolkitAgentListenerClass *listener_class;
+
+ gobject_class = G_OBJECT_CLASS (klass);
+ gobject_class->finalize = shell_polkit_authentication_agent_finalize;
+
+ listener_class = POLKIT_AGENT_LISTENER_CLASS (klass);
+ listener_class->initiate_authentication = initiate_authentication;
+ listener_class->initiate_authentication_finish = initiate_authentication_finish;
+
+ signals[INITIATE_SIGNAL] =
+ g_signal_new ("initiate",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ 0, /* class_offset */
+ NULL, /* accumulator */
+ NULL, /* accumulator data */
+ NULL, /* marshaller */
+ G_TYPE_NONE,
+ 5,
+ G_TYPE_STRING,
+ G_TYPE_STRING,
+ G_TYPE_STRING,
+ G_TYPE_STRING,
+ G_TYPE_STRV);
+
+ signals[CANCEL_SIGNAL] =
+ g_signal_new ("cancel",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ 0, /* class_offset */
+ NULL, /* accumulator */
+ NULL, /* accumulator data */
+ NULL, /* marshaller */
+ G_TYPE_NONE,
+ 0);
+}
+
+ShellPolkitAuthenticationAgent *
+shell_polkit_authentication_agent_new (void)
+{
+ return SHELL_POLKIT_AUTHENTICATION_AGENT (g_object_new (SHELL_TYPE_POLKIT_AUTHENTICATION_AGENT, NULL));
+}
+
+struct _AuthRequest {
+ /* not holding ref */
+ ShellPolkitAuthenticationAgent *agent;
+ GCancellable *cancellable;
+ gulong handler_id;
+
+ /* copies */
+ gchar *action_id;
+ gchar *message;
+ gchar *icon_name;
+ PolkitDetails *details;
+ gchar *cookie;
+ GList *identities;
+
+ GTask *simple;
+};
+
+static void
+auth_request_free (AuthRequest *request)
+{
+ g_free (request->action_id);
+ g_free (request->message);
+ g_free (request->icon_name);
+ g_object_unref (request->details);
+ g_free (request->cookie);
+ g_list_foreach (request->identities, (GFunc) g_object_unref, NULL);
+ g_list_free (request->identities);
+ g_object_unref (request->simple);
+ g_free (request);
+}
+
+static void
+auth_request_initiate (AuthRequest *request)
+{
+ gchar **user_names;
+ GPtrArray *p;
+ GList *l;
+
+ p = g_ptr_array_new ();
+ for (l = request->identities; l != NULL; l = l->next)
+ {
+ if (POLKIT_IS_UNIX_USER (l->data))
+ {
+ PolkitUnixUser *user = POLKIT_UNIX_USER (l->data);
+ gint uid;
+ gchar buf[4096];
+ struct passwd pwd;
+ struct passwd *ppwd;
+
+ uid = polkit_unix_user_get_uid (user);
+ if (getpwuid_r (uid, &pwd, buf, sizeof (buf), &ppwd) == 0)
+ {
+ if (!g_utf8_validate (pwd.pw_name, -1, NULL))
+ {
+ g_warning ("Invalid UTF-8 in username for uid %d. Skipping", uid);
+ }
+ else
+ {
+ g_ptr_array_add (p, g_strdup (pwd.pw_name));
+ }
+ }
+ else
+ {
+ g_warning ("Error looking up user name for uid %d", uid);
+ }
+ }
+ else
+ {
+ g_warning ("Unsupporting identity of GType %s", g_type_name (G_TYPE_FROM_INSTANCE (l->data)));
+ }
+ }
+ g_ptr_array_add (p, NULL);
+ user_names = (gchar **) g_ptr_array_free (p, FALSE);
+ g_signal_emit (request->agent,
+ signals[INITIATE_SIGNAL],
+ 0, /* detail */
+ request->action_id,
+ request->message,
+ request->icon_name,
+ request->cookie,
+ user_names);
+ g_strfreev (user_names);
+}
+
+static void auth_request_complete (AuthRequest *request,
+ gboolean dismissed);
+
+static gboolean
+handle_cancelled_in_idle (gpointer user_data)
+{
+ AuthRequest *request = user_data;
+
+ print_debug ("CANCELLED %s cookie %s", request->action_id, request->cookie);
+ if (request == request->agent->current_request)
+ {
+ g_signal_emit (request->agent,
+ signals[CANCEL_SIGNAL],
+ 0); /* detail */
+ }
+ else
+ {
+ auth_request_complete (request, FALSE);
+ }
+
+ return FALSE;
+}
+
+static void
+on_request_cancelled (GCancellable *cancellable,
+ gpointer user_data)
+{
+ AuthRequest *request = user_data;
+ guint id;
+
+ /* post-pone to idle to handle GCancellable deadlock in
+ *
+ * https://bugzilla.gnome.org/show_bug.cgi?id=642968
+ */
+ id = g_idle_add (handle_cancelled_in_idle, request);
+ g_source_set_name_by_id (id, "[gnome-shell] handle_cancelled_in_idle");
+}
+
+static void
+auth_request_dismiss (AuthRequest *request)
+{
+ auth_request_complete (request, TRUE);
+}
+
+void
+shell_polkit_authentication_agent_unregister (ShellPolkitAuthenticationAgent *agent)
+{
+ if (agent->scheduled_requests != NULL)
+ {
+ g_list_foreach (agent->scheduled_requests, (GFunc)auth_request_dismiss, NULL);
+ agent->scheduled_requests = NULL;
+ }
+ if (agent->current_request != NULL)
+ auth_request_dismiss (agent->current_request);
+
+ if (agent->handle)
+ {
+ polkit_agent_listener_unregister (agent->handle);
+ agent->handle = NULL;
+ }
+}
+
+static void maybe_process_next_request (ShellPolkitAuthenticationAgent *agent);
+
+static void
+auth_request_complete (AuthRequest *request,
+ gboolean dismissed)
+{
+ ShellPolkitAuthenticationAgent *agent = request->agent;
+ gboolean is_current = agent->current_request == request;
+
+ print_debug ("COMPLETING %s %s cookie %s", is_current ? "CURRENT" : "SCHEDULED",
+ request->action_id, request->cookie);
+
+ if (!is_current)
+ agent->scheduled_requests = g_list_remove (agent->scheduled_requests, request);
+ g_cancellable_disconnect (request->cancellable, request->handler_id);
+
+ if (dismissed)
+ g_task_return_new_error (request->simple,
+ POLKIT_ERROR,
+ POLKIT_ERROR_CANCELLED,
+ _("Authentication dialog was dismissed by the user"));
+ else
+ g_task_return_boolean (request->simple, TRUE);
+
+ auth_request_free (request);
+
+ if (is_current)
+ {
+ agent->current_request = NULL;
+ maybe_process_next_request (agent);
+ }
+}
+
+static void
+maybe_process_next_request (ShellPolkitAuthenticationAgent *agent)
+{
+ print_debug ("MAYBE_PROCESS cur=%p len(scheduled)=%d", agent->current_request, g_list_length (agent->scheduled_requests));
+
+ if (agent->current_request == NULL && agent->scheduled_requests != NULL)
+ {
+ AuthRequest *request;
+
+ request = agent->scheduled_requests->data;
+
+ agent->current_request = request;
+ agent->scheduled_requests = g_list_remove (agent->scheduled_requests, request);
+
+ print_debug ("INITIATING %s cookie %s", request->action_id, request->cookie);
+ auth_request_initiate (request);
+ }
+}
+
+static void
+initiate_authentication (PolkitAgentListener *listener,
+ const gchar *action_id,
+ const gchar *message,
+ const gchar *icon_name,
+ PolkitDetails *details,
+ const gchar *cookie,
+ GList *identities,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ ShellPolkitAuthenticationAgent *agent = SHELL_POLKIT_AUTHENTICATION_AGENT (listener);
+ AuthRequest *request;
+
+ request = g_new0 (AuthRequest, 1);
+ request->agent = agent;
+ request->action_id = g_strdup (action_id);
+ request->message = g_strdup (message);
+ request->icon_name = g_strdup (icon_name);
+ request->details = g_object_ref (details);
+ request->cookie = g_strdup (cookie);
+ request->identities = g_list_copy (identities);
+ g_list_foreach (request->identities, (GFunc) g_object_ref, NULL);
+ request->simple = g_task_new (listener, NULL, callback, user_data);
+ request->cancellable = cancellable;
+ request->handler_id = g_cancellable_connect (request->cancellable,
+ G_CALLBACK (on_request_cancelled),
+ request,
+ NULL); /* GDestroyNotify for request */
+
+ print_debug ("SCHEDULING %s cookie %s", request->action_id, request->cookie);
+ agent->scheduled_requests = g_list_append (agent->scheduled_requests, request);
+
+ maybe_process_next_request (agent);
+}
+
+static gboolean
+initiate_authentication_finish (PolkitAgentListener *listener,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+void
+shell_polkit_authentication_agent_complete (ShellPolkitAuthenticationAgent *agent,
+ gboolean dismissed)
+{
+ g_return_if_fail (SHELL_IS_POLKIT_AUTHENTICATION_AGENT (agent));
+ g_return_if_fail (agent->current_request != NULL);
+
+ auth_request_complete (agent->current_request, dismissed);
+}
diff --git a/src/shell-polkit-authentication-agent.h b/src/shell-polkit-authentication-agent.h
new file mode 100644
index 0000000..4f14749
--- /dev/null
+++ b/src/shell-polkit-authentication-agent.h
@@ -0,0 +1,35 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+/*
+ * Copyright (C) 20011 Red Hat, Inc.
+ *
+ * Author: David Zeuthen <davidz@redhat.com>
+ */
+
+#pragma once
+
+#define POLKIT_AGENT_I_KNOW_API_IS_SUBJECT_TO_CHANGE
+#include <polkitagent/polkitagent.h>
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#ifndef HAVE_POLKIT_AUTOCLEANUP
+/* Polkit doesn't have g_autoptr support, thus we have to manually set the autoptr function here */
+G_DEFINE_AUTOPTR_CLEANUP_FUNC (PolkitAgentListener, g_object_unref)
+#endif
+
+#define SHELL_TYPE_POLKIT_AUTHENTICATION_AGENT (shell_polkit_authentication_agent_get_type())
+
+G_DECLARE_FINAL_TYPE (ShellPolkitAuthenticationAgent, shell_polkit_authentication_agent, SHELL, POLKIT_AUTHENTICATION_AGENT, PolkitAgentListener)
+
+ShellPolkitAuthenticationAgent *shell_polkit_authentication_agent_new (void);
+
+void shell_polkit_authentication_agent_complete (ShellPolkitAuthenticationAgent *agent,
+ gboolean dismissed);
+void shell_polkit_authentication_agent_register (ShellPolkitAuthenticationAgent *agent,
+ GError **error_out);
+void shell_polkit_authentication_agent_unregister (ShellPolkitAuthenticationAgent *agent);
+
+G_END_DECLS
+
diff --git a/src/shell-screenshot.c b/src/shell-screenshot.c
new file mode 100644
index 0000000..f7132ee
--- /dev/null
+++ b/src/shell-screenshot.c
@@ -0,0 +1,1217 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+#include <clutter/clutter.h>
+#include <cogl/cogl.h>
+#include <meta/display.h>
+#include <meta/util.h>
+#include <meta/meta-plugin.h>
+#include <meta/meta-cursor-tracker.h>
+#include <st/st.h>
+
+#include "shell-global.h"
+#include "shell-screenshot.h"
+#include "shell-util.h"
+
+typedef enum _ShellScreenshotFlag
+{
+ SHELL_SCREENSHOT_FLAG_NONE,
+ SHELL_SCREENSHOT_FLAG_INCLUDE_CURSOR,
+} ShellScreenshotFlag;
+
+typedef enum _ShellScreenshotMode
+{
+ SHELL_SCREENSHOT_SCREEN,
+ SHELL_SCREENSHOT_WINDOW,
+ SHELL_SCREENSHOT_AREA,
+} ShellScreenshotMode;
+
+enum
+{
+ SCREENSHOT_TAKEN,
+
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = { 0, };
+
+typedef struct _ShellScreenshotPrivate ShellScreenshotPrivate;
+
+struct _ShellScreenshot
+{
+ GObject parent_instance;
+
+ ShellScreenshotPrivate *priv;
+};
+
+struct _ShellScreenshotPrivate
+{
+ ShellGlobal *global;
+
+ GOutputStream *stream;
+ ShellScreenshotFlag flags;
+ ShellScreenshotMode mode;
+
+ GDateTime *datetime;
+
+ cairo_surface_t *image;
+ cairo_rectangle_int_t screenshot_area;
+
+ gboolean include_frame;
+
+ float scale;
+ ClutterContent *cursor_content;
+ graphene_point_t cursor_point;
+ float cursor_scale;
+};
+
+G_DEFINE_TYPE_WITH_PRIVATE (ShellScreenshot, shell_screenshot, G_TYPE_OBJECT);
+
+static void
+shell_screenshot_class_init (ShellScreenshotClass *screenshot_class)
+{
+ signals[SCREENSHOT_TAKEN] =
+ g_signal_new ("screenshot-taken",
+ G_TYPE_FROM_CLASS(screenshot_class),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL, NULL,
+ G_TYPE_NONE,
+ 1,
+ META_TYPE_RECTANGLE);
+}
+
+static void
+shell_screenshot_init (ShellScreenshot *screenshot)
+{
+ screenshot->priv = shell_screenshot_get_instance_private (screenshot);
+ screenshot->priv->global = shell_global_get ();
+}
+
+static void
+on_screenshot_written (GObject *source,
+ GAsyncResult *task,
+ gpointer user_data)
+{
+ ShellScreenshot *screenshot = SHELL_SCREENSHOT (source);
+ ShellScreenshotPrivate *priv = screenshot->priv;
+ GTask *result = user_data;
+
+ g_task_return_boolean (result, g_task_propagate_boolean (G_TASK (task), NULL));
+ g_object_unref (result);
+
+ g_clear_pointer (&priv->image, cairo_surface_destroy);
+ g_clear_object (&priv->stream);
+ g_clear_pointer (&priv->datetime, g_date_time_unref);
+}
+
+static void
+write_screenshot_thread (GTask *result,
+ gpointer object,
+ gpointer task_data,
+ GCancellable *cancellable)
+{
+ ShellScreenshot *screenshot = SHELL_SCREENSHOT (object);
+ ShellScreenshotPrivate *priv;
+ g_autoptr (GOutputStream) stream = NULL;
+ g_autoptr(GdkPixbuf) pixbuf = NULL;
+ g_autofree char *creation_time = NULL;
+ GError *error = NULL;
+
+ g_assert (screenshot != NULL);
+
+ priv = screenshot->priv;
+
+ stream = g_object_ref (priv->stream);
+
+ pixbuf = gdk_pixbuf_get_from_surface (priv->image,
+ 0, 0,
+ cairo_image_surface_get_width (priv->image),
+ cairo_image_surface_get_height (priv->image));
+ creation_time = g_date_time_format (priv->datetime, "%c");
+
+ if (!creation_time)
+ creation_time = g_date_time_format (priv->datetime, "%FT%T%z");
+
+ gdk_pixbuf_save_to_stream (pixbuf, stream, "png", NULL, &error,
+ "tEXt::Software", "gnome-screenshot",
+ "tEXt::Creation Time", creation_time,
+ NULL);
+
+ if (error)
+ g_task_return_error (result, error);
+ else
+ g_task_return_boolean (result, TRUE);
+}
+
+static void
+do_grab_screenshot (ShellScreenshot *screenshot,
+ int x,
+ int y,
+ int width,
+ int height,
+ ShellScreenshotFlag flags)
+{
+ ShellScreenshotPrivate *priv = screenshot->priv;
+ ClutterStage *stage = shell_global_get_stage (priv->global);
+ cairo_rectangle_int_t screenshot_rect = { x, y, width, height };
+ int image_width;
+ int image_height;
+ float scale;
+ cairo_surface_t *image;
+ ClutterPaintFlag paint_flags = CLUTTER_PAINT_FLAG_NONE;
+ g_autoptr (GError) error = NULL;
+
+ clutter_stage_get_capture_final_size (stage, &screenshot_rect,
+ &image_width,
+ &image_height,
+ &scale);
+ image = cairo_image_surface_create (CAIRO_FORMAT_ARGB32,
+ image_width, image_height);
+
+ if (flags & SHELL_SCREENSHOT_FLAG_INCLUDE_CURSOR)
+ paint_flags |= CLUTTER_PAINT_FLAG_FORCE_CURSORS;
+ else
+ paint_flags |= CLUTTER_PAINT_FLAG_NO_CURSORS;
+ if (!clutter_stage_paint_to_buffer (stage, &screenshot_rect, scale,
+ cairo_image_surface_get_data (image),
+ cairo_image_surface_get_stride (image),
+ CLUTTER_CAIRO_FORMAT_ARGB32,
+ paint_flags,
+ &error))
+ {
+ cairo_surface_destroy (image);
+ g_warning ("Failed to take screenshot: %s", error->message);
+ return;
+ }
+
+ priv->image = image;
+
+ priv->datetime = g_date_time_new_now_local ();
+}
+
+static void
+draw_cursor_image (cairo_surface_t *surface,
+ cairo_rectangle_int_t area)
+{
+ CoglTexture *texture;
+ int width, height;
+ int stride;
+ guint8 *data;
+ MetaDisplay *display;
+ MetaCursorTracker *tracker;
+ cairo_surface_t *cursor_surface;
+ cairo_region_t *screenshot_region;
+ cairo_t *cr;
+ int x, y;
+ int xhot, yhot;
+ double xscale, yscale;
+ graphene_point_t point;
+
+ display = shell_global_get_display (shell_global_get ());
+ tracker = meta_cursor_tracker_get_for_display (display);
+ texture = meta_cursor_tracker_get_sprite (tracker);
+
+ if (!texture)
+ return;
+
+ screenshot_region = cairo_region_create_rectangle (&area);
+ meta_cursor_tracker_get_pointer (tracker, &point, NULL);
+ x = point.x;
+ y = point.y;
+
+ if (!cairo_region_contains_point (screenshot_region, point.x, point.y))
+ {
+ cairo_region_destroy (screenshot_region);
+ return;
+ }
+
+ meta_cursor_tracker_get_hot (tracker, &xhot, &yhot);
+ width = cogl_texture_get_width (texture);
+ height = cogl_texture_get_height (texture);
+ stride = 4 * width;
+ data = g_new (guint8, stride * height);
+ cogl_texture_get_data (texture, CLUTTER_CAIRO_FORMAT_ARGB32, stride, data);
+
+ /* FIXME: cairo-gl? */
+ cursor_surface = cairo_image_surface_create_for_data (data,
+ CAIRO_FORMAT_ARGB32,
+ width, height,
+ stride);
+
+ cairo_surface_get_device_scale (surface, &xscale, &yscale);
+
+ if (xscale != 1.0 || yscale != 1.0)
+ {
+ int monitor;
+ float monitor_scale;
+ MetaRectangle cursor_rect = {
+ .x = x, .y = y, .width = width, .height = height
+ };
+
+ monitor = meta_display_get_monitor_index_for_rect (display, &cursor_rect);
+ monitor_scale = meta_display_get_monitor_scale (display, monitor);
+
+ cairo_surface_set_device_scale (cursor_surface, monitor_scale, monitor_scale);
+ }
+
+ cr = cairo_create (surface);
+ cairo_set_source_surface (cr,
+ cursor_surface,
+ x - xhot - area.x,
+ y - yhot - area.y);
+ cairo_paint (cr);
+
+ cairo_destroy (cr);
+ cairo_surface_destroy (cursor_surface);
+ cairo_region_destroy (screenshot_region);
+ g_free (data);
+}
+
+static void
+grab_screenshot (ShellScreenshot *screenshot,
+ ShellScreenshotFlag flags,
+ GTask *result)
+{
+ ShellScreenshotPrivate *priv = screenshot->priv;
+ MetaDisplay *display;
+ int width, height;
+ GTask *task;
+
+ display = shell_global_get_display (priv->global);
+ meta_display_get_size (display, &width, &height);
+
+ do_grab_screenshot (screenshot,
+ 0, 0, width, height,
+ flags);
+
+ priv->screenshot_area.x = 0;
+ priv->screenshot_area.y = 0;
+ priv->screenshot_area.width = width;
+ priv->screenshot_area.height = height;
+
+ task = g_task_new (screenshot, NULL, on_screenshot_written, result);
+ g_task_run_in_thread (task, write_screenshot_thread);
+ g_object_unref (task);
+}
+
+static void
+grab_screenshot_content (ShellScreenshot *screenshot,
+ GTask *result)
+{
+ ShellScreenshotPrivate *priv = screenshot->priv;
+ MetaDisplay *display;
+ int width, height;
+ cairo_rectangle_int_t screenshot_rect;
+ ClutterStage *stage;
+ int image_width;
+ int image_height;
+ float scale;
+ g_autoptr (GError) error = NULL;
+ g_autoptr (ClutterContent) content = NULL;
+ g_autoptr (GTask) task = result;
+ MetaCursorTracker *tracker;
+ CoglTexture *cursor_texture;
+ int cursor_hot_x, cursor_hot_y;
+
+ display = shell_global_get_display (priv->global);
+ meta_display_get_size (display, &width, &height);
+ screenshot_rect = (cairo_rectangle_int_t) {
+ .x = 0,
+ .y = 0,
+ .width = width,
+ .height = height,
+ };
+
+ stage = shell_global_get_stage (priv->global);
+
+ clutter_stage_get_capture_final_size (stage, &screenshot_rect,
+ &image_width,
+ &image_height,
+ &scale);
+
+ priv->scale = scale;
+
+ content = clutter_stage_paint_to_content (stage, &screenshot_rect, scale,
+ CLUTTER_PAINT_FLAG_NO_CURSORS,
+ &error);
+ if (!content)
+ {
+ g_task_return_error (result, g_steal_pointer (&error));
+ return;
+ }
+
+ tracker = meta_cursor_tracker_get_for_display (display);
+ cursor_texture = meta_cursor_tracker_get_sprite (tracker);
+
+ // If the cursor is invisible, the texture is NULL.
+ if (cursor_texture)
+ {
+ unsigned int width, height;
+ CoglContext *ctx;
+ CoglPipeline *pipeline;
+ CoglTexture2D *texture;
+ CoglOffscreen *offscreen;
+ ClutterStageView *view;
+
+ // Copy the texture to prevent it from changing shortly after.
+ width = cogl_texture_get_width (cursor_texture);
+ height = cogl_texture_get_height (cursor_texture);
+
+ ctx = clutter_backend_get_cogl_context (clutter_get_default_backend ());
+
+ texture = cogl_texture_2d_new_with_size (ctx, width, height);
+ offscreen = cogl_offscreen_new_with_texture (texture);
+ cogl_framebuffer_clear4f (COGL_FRAMEBUFFER (offscreen),
+ COGL_BUFFER_BIT_COLOR,
+ 0, 0, 0, 0);
+
+ pipeline = cogl_pipeline_new (ctx);
+ cogl_pipeline_set_layer_texture (pipeline, 0, cursor_texture);
+
+ cogl_framebuffer_draw_textured_rectangle (COGL_FRAMEBUFFER (offscreen),
+ pipeline,
+ -1, 1, 1, -1,
+ 0, 0, 1, 1);
+ cogl_object_unref (pipeline);
+ g_object_unref (offscreen);
+
+ priv->cursor_content =
+ clutter_texture_content_new_from_texture (texture, NULL);
+ cogl_object_unref (texture);
+
+ priv->cursor_scale = meta_cursor_tracker_get_scale (tracker);
+
+ meta_cursor_tracker_get_pointer (tracker, &priv->cursor_point, NULL);
+
+ view = clutter_stage_get_view_at (stage,
+ priv->cursor_point.x,
+ priv->cursor_point.y);
+
+ meta_cursor_tracker_get_hot (tracker, &cursor_hot_x, &cursor_hot_y);
+ priv->cursor_point.x -= cursor_hot_x * priv->cursor_scale;
+ priv->cursor_point.y -= cursor_hot_y * priv->cursor_scale;
+
+ // Align the coordinates to the pixel grid the same way it's done in
+ // MetaCursorRenderer.
+ if (view)
+ {
+ cairo_rectangle_int_t view_layout;
+ float view_scale;
+
+ clutter_stage_view_get_layout (view, &view_layout);
+ view_scale = clutter_stage_view_get_scale (view);
+
+ priv->cursor_point.x -= view_layout.x;
+ priv->cursor_point.y -= view_layout.y;
+
+ priv->cursor_point.x =
+ floorf (priv->cursor_point.x * view_scale) / view_scale;
+ priv->cursor_point.y =
+ floorf (priv->cursor_point.y * view_scale) / view_scale;
+
+ priv->cursor_point.x += view_layout.x;
+ priv->cursor_point.y += view_layout.y;
+ }
+ }
+
+ g_task_return_pointer (result, g_steal_pointer (&content), g_object_unref);
+}
+
+static void
+grab_window_screenshot (ShellScreenshot *screenshot,
+ ShellScreenshotFlag flags,
+ GTask *result)
+{
+ ShellScreenshotPrivate *priv = screenshot->priv;
+ GTask *task;
+ MetaDisplay *display = shell_global_get_display (priv->global);
+ MetaWindow *window = meta_display_get_focus_window (display);
+ ClutterActor *window_actor;
+ gfloat actor_x, actor_y;
+ MetaRectangle rect;
+
+ window_actor = CLUTTER_ACTOR (meta_window_get_compositor_private (window));
+ clutter_actor_get_position (window_actor, &actor_x, &actor_y);
+
+ meta_window_get_frame_rect (window, &rect);
+
+ if (!priv->include_frame)
+ meta_window_frame_rect_to_client_rect (window, &rect, &rect);
+
+ priv->screenshot_area = rect;
+
+ priv->image = meta_window_actor_get_image (META_WINDOW_ACTOR (window_actor),
+ NULL);
+
+ if (!priv->image)
+ {
+ g_task_report_new_error (screenshot, on_screenshot_written, result, NULL,
+ G_IO_ERROR, G_IO_ERROR_FAILED,
+ "Capturing window failed");
+ return;
+ }
+
+ priv->datetime = g_date_time_new_now_local ();
+
+ if (flags & SHELL_SCREENSHOT_FLAG_INCLUDE_CURSOR)
+ {
+ if (meta_window_get_client_type (window) == META_WINDOW_CLIENT_TYPE_WAYLAND)
+ {
+ float resource_scale;
+ resource_scale = clutter_actor_get_resource_scale (window_actor);
+
+ cairo_surface_set_device_scale (priv->image, resource_scale, resource_scale);
+ }
+
+ draw_cursor_image (priv->image, priv->screenshot_area);
+ }
+
+ g_signal_emit (screenshot, signals[SCREENSHOT_TAKEN], 0, &rect);
+
+ task = g_task_new (screenshot, NULL, on_screenshot_written, result);
+ g_task_run_in_thread (task, write_screenshot_thread);
+ g_object_unref (task);
+}
+
+static gboolean
+finish_screenshot (ShellScreenshot *screenshot,
+ GAsyncResult *result,
+ cairo_rectangle_int_t **area,
+ GError **error)
+{
+ ShellScreenshotPrivate *priv = screenshot->priv;
+
+ if (!g_task_propagate_boolean (G_TASK (result), error))
+ return FALSE;
+
+ if (area)
+ *area = &priv->screenshot_area;
+
+ return TRUE;
+}
+
+static void
+on_after_paint (ClutterStage *stage,
+ ClutterStageView *view,
+ GTask *result)
+{
+ ShellScreenshot *screenshot = g_task_get_task_data (result);
+ ShellScreenshotPrivate *priv = screenshot->priv;
+ MetaDisplay *display = shell_global_get_display (priv->global);
+ GTask *task;
+
+ g_signal_handlers_disconnect_by_func (stage, on_after_paint, result);
+
+ if (priv->mode == SHELL_SCREENSHOT_AREA)
+ {
+ do_grab_screenshot (screenshot,
+ priv->screenshot_area.x,
+ priv->screenshot_area.y,
+ priv->screenshot_area.width,
+ priv->screenshot_area.height,
+ priv->flags);
+
+ task = g_task_new (screenshot, NULL, on_screenshot_written, result);
+ g_task_run_in_thread (task, write_screenshot_thread);
+ }
+ else
+ {
+ grab_screenshot (screenshot, priv->flags, result);
+ }
+
+ g_signal_emit (screenshot, signals[SCREENSHOT_TAKEN], 0,
+ (cairo_rectangle_int_t *) &priv->screenshot_area);
+
+ meta_enable_unredirect_for_display (display);
+}
+
+/**
+ * shell_screenshot_screenshot:
+ * @screenshot: the #ShellScreenshot
+ * @include_cursor: Whether to include the cursor or not
+ * @stream: The stream for the screenshot
+ * @callback: (scope async): function to call returning success or failure
+ * of the async grabbing
+ * @user_data: the data to pass to callback function
+ *
+ * Takes a screenshot of the whole screen
+ * in @stream as png image.
+ *
+ */
+void
+shell_screenshot_screenshot (ShellScreenshot *screenshot,
+ gboolean include_cursor,
+ GOutputStream *stream,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ ShellScreenshotPrivate *priv;
+ GTask *result;
+ ShellScreenshotFlag flags;
+
+ g_return_if_fail (SHELL_IS_SCREENSHOT (screenshot));
+ g_return_if_fail (G_IS_OUTPUT_STREAM (stream));
+
+ priv = screenshot->priv;
+
+ if (priv->stream != NULL) {
+ if (callback)
+ g_task_report_new_error (screenshot,
+ callback,
+ user_data,
+ shell_screenshot_screenshot,
+ G_IO_ERROR,
+ G_IO_ERROR_PENDING,
+ "Only one screenshot operation at a time "
+ "is permitted");
+ return;
+ }
+
+ result = g_task_new (screenshot, NULL, callback, user_data);
+ g_task_set_source_tag (result, shell_screenshot_screenshot);
+ g_task_set_task_data (result, screenshot, NULL);
+
+ priv->stream = g_object_ref (stream);
+
+ flags = SHELL_SCREENSHOT_FLAG_NONE;
+ if (include_cursor)
+ flags |= SHELL_SCREENSHOT_FLAG_INCLUDE_CURSOR;
+
+ if (meta_is_wayland_compositor ())
+ {
+ grab_screenshot (screenshot, flags, result);
+
+ g_signal_emit (screenshot, signals[SCREENSHOT_TAKEN], 0,
+ (cairo_rectangle_int_t *) &priv->screenshot_area);
+ }
+ else
+ {
+ MetaDisplay *display = shell_global_get_display (priv->global);
+ ClutterStage *stage = shell_global_get_stage (priv->global);
+
+ meta_disable_unredirect_for_display (display);
+ clutter_actor_queue_redraw (CLUTTER_ACTOR (stage));
+ priv->flags = flags;
+ priv->mode = SHELL_SCREENSHOT_SCREEN;
+ g_signal_connect (stage, "after-paint",
+ G_CALLBACK (on_after_paint), result);
+ }
+}
+
+/**
+ * shell_screenshot_screenshot_finish:
+ * @screenshot: the #ShellScreenshot
+ * @result: the #GAsyncResult that was provided to the callback
+ * @area: (out) (transfer none): the area that was grabbed in screen coordinates
+ * @error: #GError for error reporting
+ *
+ * Finish the asynchronous operation started by shell_screenshot_screenshot()
+ * and obtain its result.
+ *
+ * Returns: whether the operation was successful
+ *
+ */
+gboolean
+shell_screenshot_screenshot_finish (ShellScreenshot *screenshot,
+ GAsyncResult *result,
+ cairo_rectangle_int_t **area,
+ GError **error)
+{
+ g_return_val_if_fail (SHELL_IS_SCREENSHOT (screenshot), FALSE);
+ g_return_val_if_fail (G_IS_TASK (result), FALSE);
+ g_return_val_if_fail (g_async_result_is_tagged (result,
+ shell_screenshot_screenshot),
+ FALSE);
+ return finish_screenshot (screenshot, result, area, error);
+}
+
+static void
+screenshot_stage_to_content_on_after_paint (ClutterStage *stage,
+ ClutterStageView *view,
+ GTask *result)
+{
+ ShellScreenshot *screenshot = g_task_get_task_data (result);
+ ShellScreenshotPrivate *priv = screenshot->priv;
+ MetaDisplay *display = shell_global_get_display (priv->global);
+
+ g_signal_handlers_disconnect_by_func (stage,
+ screenshot_stage_to_content_on_after_paint,
+ result);
+
+ meta_enable_unredirect_for_display (display);
+
+ grab_screenshot_content (screenshot, result);
+}
+
+/**
+ * shell_screenshot_screenshot_stage_to_content:
+ * @screenshot: the #ShellScreenshot
+ * @callback: (scope async): function to call returning success or failure
+ * of the async grabbing
+ * @user_data: the data to pass to callback function
+ *
+ * Takes a screenshot of the whole screen as #ClutterContent.
+ *
+ */
+void
+shell_screenshot_screenshot_stage_to_content (ShellScreenshot *screenshot,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ ShellScreenshotPrivate *priv;
+ GTask *result;
+
+ g_return_if_fail (SHELL_IS_SCREENSHOT (screenshot));
+
+ result = g_task_new (screenshot, NULL, callback, user_data);
+ g_task_set_source_tag (result, shell_screenshot_screenshot_stage_to_content);
+ g_task_set_task_data (result, screenshot, NULL);
+
+ if (meta_is_wayland_compositor ())
+ {
+ grab_screenshot_content (screenshot, result);
+ }
+ else
+ {
+ priv = screenshot->priv;
+
+ MetaDisplay *display = shell_global_get_display (priv->global);
+ ClutterStage *stage = shell_global_get_stage (priv->global);
+
+ meta_disable_unredirect_for_display (display);
+ clutter_actor_queue_redraw (CLUTTER_ACTOR (stage));
+ g_signal_connect (stage, "after-paint",
+ G_CALLBACK (screenshot_stage_to_content_on_after_paint),
+ result);
+ }
+}
+
+/**
+ * shell_screenshot_screenshot_stage_to_content_finish:
+ * @screenshot: the #ShellScreenshot
+ * @result: the #GAsyncResult that was provided to the callback
+ * @scale: (out) (optional): location to store the content scale
+ * @cursor_content: (out) (optional): location to store the cursor content
+ * @cursor_point: (out) (optional): location to store the point at which to
+ * draw the cursor content
+ * @cursor_scale: (out) (optional): location to store the cursor scale
+ * @error: #GError for error reporting
+ *
+ * Finish the asynchronous operation started by
+ * shell_screenshot_screenshot_stage_to_content() and obtain its result.
+ *
+ * Returns: (transfer full): the #ClutterContent, or NULL
+ *
+ */
+ClutterContent *
+shell_screenshot_screenshot_stage_to_content_finish (ShellScreenshot *screenshot,
+ GAsyncResult *result,
+ float *scale,
+ ClutterContent **cursor_content,
+ graphene_point_t *cursor_point,
+ float *cursor_scale,
+ GError **error)
+{
+ ShellScreenshotPrivate *priv = screenshot->priv;
+ ClutterContent *content;
+
+ g_return_val_if_fail (SHELL_IS_SCREENSHOT (screenshot), FALSE);
+ g_return_val_if_fail (G_IS_TASK (result), FALSE);
+ g_return_val_if_fail (g_async_result_is_tagged (result,
+ shell_screenshot_screenshot_stage_to_content),
+ FALSE);
+
+ content = g_task_propagate_pointer (G_TASK (result), error);
+ if (!content)
+ return NULL;
+
+ if (scale)
+ *scale = priv->scale;
+
+ if (cursor_content)
+ *cursor_content = g_steal_pointer (&priv->cursor_content);
+ else
+ g_clear_pointer (&priv->cursor_content, g_object_unref);
+
+ if (cursor_point)
+ *cursor_point = priv->cursor_point;
+
+ if (cursor_scale)
+ *cursor_scale = priv->cursor_scale;
+
+ return content;
+}
+
+/**
+ * shell_screenshot_screenshot_area:
+ * @screenshot: the #ShellScreenshot
+ * @x: The X coordinate of the area
+ * @y: The Y coordinate of the area
+ * @width: The width of the area
+ * @height: The height of the area
+ * @stream: The stream for the screenshot
+ * @callback: (scope async): function to call returning success or failure
+ * of the async grabbing
+ * @user_data: the data to pass to callback function
+ *
+ * Takes a screenshot of the passed in area and saves it
+ * in @stream as png image.
+ *
+ */
+void
+shell_screenshot_screenshot_area (ShellScreenshot *screenshot,
+ int x,
+ int y,
+ int width,
+ int height,
+ GOutputStream *stream,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ ShellScreenshotPrivate *priv;
+ GTask *result;
+ g_autoptr (GTask) task = NULL;
+
+ g_return_if_fail (SHELL_IS_SCREENSHOT (screenshot));
+ g_return_if_fail (G_IS_OUTPUT_STREAM (stream));
+
+ priv = screenshot->priv;
+
+ if (priv->stream != NULL) {
+ if (callback)
+ g_task_report_new_error (screenshot,
+ callback,
+ NULL,
+ shell_screenshot_screenshot_area,
+ G_IO_ERROR,
+ G_IO_ERROR_PENDING,
+ "Only one screenshot operation at a time "
+ "is permitted");
+ return;
+ }
+
+ result = g_task_new (screenshot, NULL, callback, user_data);
+ g_task_set_source_tag (result, shell_screenshot_screenshot_area);
+ g_task_set_task_data (result, screenshot, NULL);
+
+ priv->stream = g_object_ref (stream);
+ priv->screenshot_area.x = x;
+ priv->screenshot_area.y = y;
+ priv->screenshot_area.width = width;
+ priv->screenshot_area.height = height;
+
+
+ if (meta_is_wayland_compositor ())
+ {
+ do_grab_screenshot (screenshot,
+ priv->screenshot_area.x,
+ priv->screenshot_area.y,
+ priv->screenshot_area.width,
+ priv->screenshot_area.height,
+ SHELL_SCREENSHOT_FLAG_NONE);
+
+ g_signal_emit (screenshot, signals[SCREENSHOT_TAKEN], 0,
+ (cairo_rectangle_int_t *) &priv->screenshot_area);
+
+ task = g_task_new (screenshot, NULL, on_screenshot_written, result);
+ g_task_run_in_thread (task, write_screenshot_thread);
+ }
+ else
+ {
+ MetaDisplay *display = shell_global_get_display (priv->global);
+ ClutterStage *stage = shell_global_get_stage (priv->global);
+
+ meta_disable_unredirect_for_display (display);
+ clutter_actor_queue_redraw (CLUTTER_ACTOR (stage));
+ priv->flags = SHELL_SCREENSHOT_FLAG_NONE;
+ priv->mode = SHELL_SCREENSHOT_AREA;
+ g_signal_connect (stage, "after-paint",
+ G_CALLBACK (on_after_paint), result);
+ }
+}
+
+/**
+ * shell_screenshot_screenshot_area_finish:
+ * @screenshot: the #ShellScreenshot
+ * @result: the #GAsyncResult that was provided to the callback
+ * @area: (out) (transfer none): the area that was grabbed in screen coordinates
+ * @error: #GError for error reporting
+ *
+ * Finish the asynchronous operation started by shell_screenshot_screenshot_area()
+ * and obtain its result.
+ *
+ * Returns: whether the operation was successful
+ *
+ */
+gboolean
+shell_screenshot_screenshot_area_finish (ShellScreenshot *screenshot,
+ GAsyncResult *result,
+ cairo_rectangle_int_t **area,
+ GError **error)
+{
+ g_return_val_if_fail (SHELL_IS_SCREENSHOT (screenshot), FALSE);
+ g_return_val_if_fail (G_IS_TASK (result), FALSE);
+ g_return_val_if_fail (g_async_result_is_tagged (result,
+ shell_screenshot_screenshot_area),
+ FALSE);
+ return finish_screenshot (screenshot, result, area, error);
+}
+
+/**
+ * shell_screenshot_screenshot_window:
+ * @screenshot: the #ShellScreenshot
+ * @include_frame: Whether to include the frame or not
+ * @include_cursor: Whether to include the cursor or not
+ * @stream: The stream for the screenshot
+ * @callback: (scope async): function to call returning success or failure
+ * of the async grabbing
+ * @user_data: the data to pass to callback function
+ *
+ * Takes a screenshot of the focused window (optionally omitting the frame)
+ * in @stream as png image.
+ *
+ */
+void
+shell_screenshot_screenshot_window (ShellScreenshot *screenshot,
+ gboolean include_frame,
+ gboolean include_cursor,
+ GOutputStream *stream,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ ShellScreenshotPrivate *priv;
+ MetaDisplay *display;
+ MetaWindow *window;
+ GTask *result;
+
+ g_return_if_fail (SHELL_IS_SCREENSHOT (screenshot));
+ g_return_if_fail (G_IS_OUTPUT_STREAM (stream));
+
+ priv = screenshot->priv;
+ display = shell_global_get_display (priv->global);
+ window = meta_display_get_focus_window (display);
+
+ if (priv->stream != NULL || !window) {
+ if (callback)
+ g_task_report_new_error (screenshot,
+ callback,
+ NULL,
+ shell_screenshot_screenshot_window,
+ G_IO_ERROR,
+ G_IO_ERROR_PENDING,
+ "Only one screenshot operation at a time "
+ "is permitted");
+ return;
+ }
+
+ result = g_task_new (screenshot, NULL, callback, user_data);
+ g_task_set_source_tag (result, shell_screenshot_screenshot_window);
+
+ priv->stream = g_object_ref (stream);
+ priv->include_frame = include_frame;
+
+ grab_window_screenshot (screenshot, include_cursor, result);
+}
+
+/**
+ * shell_screenshot_screenshot_window_finish:
+ * @screenshot: the #ShellScreenshot
+ * @result: the #GAsyncResult that was provided to the callback
+ * @area: (out) (transfer none): the area that was grabbed in screen coordinates
+ * @error: #GError for error reporting
+ *
+ * Finish the asynchronous operation started by shell_screenshot_screenshot_window()
+ * and obtain its result.
+ *
+ * Returns: whether the operation was successful
+ *
+ */
+gboolean
+shell_screenshot_screenshot_window_finish (ShellScreenshot *screenshot,
+ GAsyncResult *result,
+ cairo_rectangle_int_t **area,
+ GError **error)
+{
+ g_return_val_if_fail (SHELL_IS_SCREENSHOT (screenshot), FALSE);
+ g_return_val_if_fail (G_IS_TASK (result), FALSE);
+ g_return_val_if_fail (g_async_result_is_tagged (result,
+ shell_screenshot_screenshot_window),
+ FALSE);
+ return finish_screenshot (screenshot, result, area, error);
+}
+
+/**
+ * shell_screenshot_pick_color:
+ * @screenshot: the #ShellScreenshot
+ * @x: The X coordinate to pick
+ * @y: The Y coordinate to pick
+ * @callback: (scope async): function to call returning success or failure
+ * of the async grabbing
+ *
+ * Picks the pixel at @x, @y and returns its color as #ClutterColor.
+ *
+ */
+void
+shell_screenshot_pick_color (ShellScreenshot *screenshot,
+ int x,
+ int y,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ ShellScreenshotPrivate *priv;
+ g_autoptr (GTask) result = NULL;
+
+ g_return_if_fail (SHELL_IS_SCREENSHOT (screenshot));
+
+ result = g_task_new (screenshot, NULL, callback, user_data);
+ g_task_set_source_tag (result, shell_screenshot_pick_color);
+
+ priv = screenshot->priv;
+
+ priv->screenshot_area.x = x;
+ priv->screenshot_area.y = y;
+ priv->screenshot_area.width = 1;
+ priv->screenshot_area.height = 1;
+
+ do_grab_screenshot (screenshot,
+ priv->screenshot_area.x,
+ priv->screenshot_area.y,
+ 1,
+ 1,
+ SHELL_SCREENSHOT_FLAG_NONE);
+
+ g_task_return_boolean (result, TRUE);
+}
+
+#if G_BYTE_ORDER == G_LITTLE_ENDIAN
+#define INDEX_A 3
+#define INDEX_R 2
+#define INDEX_G 1
+#define INDEX_B 0
+#else
+#define INDEX_A 0
+#define INDEX_R 1
+#define INDEX_G 2
+#define INDEX_B 3
+#endif
+
+/**
+ * shell_screenshot_pick_color_finish:
+ * @screenshot: the #ShellScreenshot
+ * @result: the #GAsyncResult that was provided to the callback
+ * @color: (out caller-allocates): the picked color
+ * @error: #GError for error reporting
+ *
+ * Finish the asynchronous operation started by shell_screenshot_pick_color()
+ * and obtain its result.
+ *
+ * Returns: whether the operation was successful
+ *
+ */
+gboolean
+shell_screenshot_pick_color_finish (ShellScreenshot *screenshot,
+ GAsyncResult *result,
+ ClutterColor *color,
+ GError **error)
+{
+ ShellScreenshotPrivate *priv;
+
+ g_return_val_if_fail (SHELL_IS_SCREENSHOT (screenshot), FALSE);
+ g_return_val_if_fail (G_IS_TASK (result), FALSE);
+ g_return_val_if_fail (color != NULL, FALSE);
+ g_return_val_if_fail (g_async_result_is_tagged (result,
+ shell_screenshot_pick_color),
+ FALSE);
+
+ if (!g_task_propagate_boolean (G_TASK (result), error))
+ return FALSE;
+
+ priv = screenshot->priv;
+
+ /* protect against mutter changing the format used for stage captures */
+ g_assert (cairo_image_surface_get_format (priv->image) == CAIRO_FORMAT_ARGB32);
+
+ if (color)
+ {
+ uint8_t *data = cairo_image_surface_get_data (priv->image);
+
+ color->alpha = data[INDEX_A];
+ color->red = data[INDEX_R];
+ color->green = data[INDEX_G];
+ color->blue = data[INDEX_B];
+ }
+
+ return TRUE;
+}
+
+#undef INDEX_A
+#undef INDEX_R
+#undef INDEX_G
+#undef INDEX_B
+
+static void
+composite_to_stream_on_png_saved (GObject *pixbuf,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ GTask *task = G_TASK (user_data);
+ GError *error = NULL;
+
+ if (!gdk_pixbuf_save_to_stream_finish (result, &error))
+ g_task_return_error (task, error);
+ else
+ g_task_return_pointer (task, g_object_ref (pixbuf), g_object_unref);
+
+ g_object_unref (task);
+}
+
+/**
+ * shell_screenshot_composite_to_stream:
+ * @texture: the source texture
+ * @x: x coordinate of the rectangle
+ * @y: y coordinate of the rectangle
+ * @width: width of the rectangle, or -1 to use the full texture
+ * @height: height of the rectangle, or -1 to use the full texture
+ * @scale: scale of the source texture
+ * @cursor: (nullable): the cursor texture
+ * @cursor_x: x coordinate to put the cursor texture at, relative to the full
+ * source texture
+ * @cursor_y: y coordinate to put the cursor texture at, relative to the full
+ * source texture
+ * @cursor_scale: scale of the cursor texture
+ * @stream: the stream to write the PNG image into
+ * @callback: (scope async): function to call returning success or failure
+ * @user_data: the data to pass to callback function
+ *
+ * Composite a rectangle defined by x, y, width, height from the texture to a
+ * pixbuf and write it as a PNG image into the stream.
+ *
+ */
+void
+shell_screenshot_composite_to_stream (CoglTexture *texture,
+ int x,
+ int y,
+ int width,
+ int height,
+ float scale,
+ CoglTexture *cursor,
+ int cursor_x,
+ int cursor_y,
+ float cursor_scale,
+ GOutputStream *stream,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ CoglContext *ctx;
+ CoglTexture *sub_texture;
+ cairo_surface_t *surface;
+ cairo_surface_t *cursor_surface;
+ cairo_t *cr;
+ g_autoptr (GTask) task = NULL;
+ g_autoptr (GdkPixbuf) pixbuf = NULL;
+ g_autofree char *creation_time = NULL;
+ g_autoptr (GDateTime) date_time = NULL;
+
+ task = g_task_new (NULL, NULL, callback, user_data);
+ g_task_set_source_tag (task, shell_screenshot_composite_to_stream);
+
+ if (width == -1 || height == -1)
+ {
+ x = 0;
+ y = 0;
+ width = cogl_texture_get_width (texture);
+ height = cogl_texture_get_height (texture);
+ }
+
+ ctx = clutter_backend_get_cogl_context (clutter_get_default_backend ());
+ sub_texture = cogl_sub_texture_new (ctx, texture, x, y, width, height);
+
+ surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32,
+ cogl_texture_get_width (sub_texture),
+ cogl_texture_get_height (sub_texture));
+
+ cogl_texture_get_data (sub_texture, CLUTTER_CAIRO_FORMAT_ARGB32,
+ cairo_image_surface_get_stride (surface),
+ cairo_image_surface_get_data (surface));
+ cairo_surface_mark_dirty (surface);
+
+ cogl_object_unref (sub_texture);
+
+ cairo_surface_set_device_scale (surface, scale, scale);
+
+ if (cursor != NULL)
+ {
+ // Paint the cursor on top.
+ cursor_surface =
+ cairo_image_surface_create (CAIRO_FORMAT_ARGB32,
+ cogl_texture_get_width (cursor),
+ cogl_texture_get_height (cursor));
+ cogl_texture_get_data (cursor, CLUTTER_CAIRO_FORMAT_ARGB32,
+ cairo_image_surface_get_stride (cursor_surface),
+ cairo_image_surface_get_data (cursor_surface));
+ cairo_surface_mark_dirty (cursor_surface);
+
+ cairo_surface_set_device_scale (cursor_surface,
+ 1 / cursor_scale,
+ 1 / cursor_scale);
+
+ cr = cairo_create (surface);
+ cairo_set_source_surface (cr, cursor_surface,
+ (cursor_x - x) / scale,
+ (cursor_y - y) / scale);
+ cairo_paint (cr);
+ cairo_destroy (cr);
+
+ cairo_surface_destroy (cursor_surface);
+ }
+
+ // Save to an image.
+ pixbuf = gdk_pixbuf_get_from_surface (surface,
+ 0, 0,
+ cairo_image_surface_get_width (surface),
+ cairo_image_surface_get_height (surface));
+ cairo_surface_destroy (surface);
+
+ date_time = g_date_time_new_now_local ();
+ creation_time = g_date_time_format (date_time, "%c");
+
+ if (!creation_time)
+ creation_time = g_date_time_format (date_time, "%FT%T%z");
+
+ gdk_pixbuf_save_to_stream_async (pixbuf, stream, "png", NULL,
+ composite_to_stream_on_png_saved,
+ g_steal_pointer (&task),
+ "tEXt::Software", "gnome-screenshot",
+ "tEXt::Creation Time", creation_time,
+ NULL);
+}
+
+/**
+ * shell_screenshot_composite_to_stream_finish:
+ * @result: the #GAsyncResult that was provided to the callback
+ * @error: #GError for error reporting
+ *
+ * Finish the asynchronous operation started by
+ * shell_screenshot_composite_to_stream () and obtain its result.
+ *
+ * Returns: (transfer full) (nullable): a GdkPixbuf with the final image if the
+ * operation was successful, or NULL on error.
+ *
+ */
+GdkPixbuf *
+shell_screenshot_composite_to_stream_finish (GAsyncResult *result,
+ GError **error)
+{
+ g_return_val_if_fail (G_IS_TASK (result), FALSE);
+ g_return_val_if_fail (g_async_result_is_tagged (result,
+ shell_screenshot_composite_to_stream),
+ FALSE);
+
+ return g_task_propagate_pointer (G_TASK (result), error);
+}
+
+ShellScreenshot *
+shell_screenshot_new (void)
+{
+ return g_object_new (SHELL_TYPE_SCREENSHOT, NULL);
+}
diff --git a/src/shell-screenshot.h b/src/shell-screenshot.h
new file mode 100644
index 0000000..441410d
--- /dev/null
+++ b/src/shell-screenshot.h
@@ -0,0 +1,90 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+#ifndef __SHELL_SCREENSHOT_H__
+#define __SHELL_SCREENSHOT_H__
+
+/**
+ * SECTION:shell-screenshot
+ * @short_description: Grabs screenshots of areas and/or windows
+ *
+ * The #ShellScreenshot object is used to take screenshots of screen
+ * areas or windows and write them out as png files.
+ *
+ */
+#define SHELL_TYPE_SCREENSHOT (shell_screenshot_get_type ())
+G_DECLARE_FINAL_TYPE (ShellScreenshot, shell_screenshot,
+ SHELL, SCREENSHOT, GObject)
+
+ShellScreenshot *shell_screenshot_new (void);
+
+void shell_screenshot_screenshot_area (ShellScreenshot *screenshot,
+ int x,
+ int y,
+ int width,
+ int height,
+ GOutputStream *stream,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean shell_screenshot_screenshot_area_finish (ShellScreenshot *screenshot,
+ GAsyncResult *result,
+ cairo_rectangle_int_t **area,
+ GError **error);
+
+void shell_screenshot_screenshot_window (ShellScreenshot *screenshot,
+ gboolean include_frame,
+ gboolean include_cursor,
+ GOutputStream *stream,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean shell_screenshot_screenshot_window_finish (ShellScreenshot *screenshot,
+ GAsyncResult *result,
+ cairo_rectangle_int_t **area,
+ GError **error);
+
+void shell_screenshot_screenshot (ShellScreenshot *screenshot,
+ gboolean include_cursor,
+ GOutputStream *stream,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean shell_screenshot_screenshot_finish (ShellScreenshot *screenshot,
+ GAsyncResult *result,
+ cairo_rectangle_int_t **area,
+ GError **error);
+
+void shell_screenshot_screenshot_stage_to_content (ShellScreenshot *screenshot,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+ClutterContent *shell_screenshot_screenshot_stage_to_content_finish (ShellScreenshot *screenshot,
+ GAsyncResult *result,
+ float *scale,
+ ClutterContent **cursor_content,
+ graphene_point_t *cursor_point,
+ float *cursor_scale,
+ GError **error);
+
+void shell_screenshot_pick_color (ShellScreenshot *screenshot,
+ int x,
+ int y,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean shell_screenshot_pick_color_finish (ShellScreenshot *screenshot,
+ GAsyncResult *result,
+ ClutterColor *color,
+ GError **error);
+
+void shell_screenshot_composite_to_stream (CoglTexture *texture,
+ int x,
+ int y,
+ int width,
+ int height,
+ float scale,
+ CoglTexture *cursor,
+ int cursor_x,
+ int cursor_y,
+ float cursor_scale,
+ GOutputStream *stream,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+GdkPixbuf *shell_screenshot_composite_to_stream_finish (GAsyncResult *result,
+ GError **error);
+
+#endif /* ___SHELL_SCREENSHOT_H__ */
diff --git a/src/shell-secure-text-buffer.c b/src/shell-secure-text-buffer.c
new file mode 100644
index 0000000..8271410
--- /dev/null
+++ b/src/shell-secure-text-buffer.c
@@ -0,0 +1,191 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/* shell-secure-text-buffer.c - secure memory clutter text buffer
+
+ Copyright (C) 2009 Stefan Walter
+ Copyright (C) 2012 Red Hat Inc.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public License as
+ published by the Free Software Foundation; either version 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
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public
+ License along with the Gnome Library; see the file COPYING.LIB. If not,
+ see <http://www.gnu.org/licenses/>.
+
+ Author: Stef Walter <stefw@gnome.org>
+*/
+
+#include "config.h"
+
+#include "shell-secure-text-buffer.h"
+
+#define GCR_API_SUBJECT_TO_CHANGE
+#include <gcr/gcr.h>
+
+#include <string.h>
+
+struct _ShellSecureTextBuffer {
+ ClutterTextBuffer parent;
+ gchar *text;
+ gsize text_size;
+ gsize text_bytes;
+ guint text_chars;
+};
+
+/* Initial size of buffer, in bytes */
+#define MIN_SIZE 16
+
+G_DEFINE_TYPE (ShellSecureTextBuffer, shell_secure_text_buffer, CLUTTER_TYPE_TEXT_BUFFER);
+
+static const gchar *
+shell_secure_text_buffer_real_get_text (ClutterTextBuffer *buffer,
+ gsize *n_bytes)
+{
+ ShellSecureTextBuffer *self = SHELL_SECURE_TEXT_BUFFER (buffer);
+ if (n_bytes)
+ *n_bytes = self->text_bytes;
+ if (!self->text)
+ return "";
+ return self->text;
+}
+
+static guint
+shell_secure_text_buffer_real_get_length (ClutterTextBuffer *buffer)
+{
+ ShellSecureTextBuffer *self = SHELL_SECURE_TEXT_BUFFER (buffer);
+ return self->text_chars;
+}
+
+static guint
+shell_secure_text_buffer_real_insert_text (ClutterTextBuffer *buffer,
+ guint position,
+ const gchar *chars,
+ guint n_chars)
+{
+ ShellSecureTextBuffer *self = SHELL_SECURE_TEXT_BUFFER (buffer);
+ gsize n_bytes;
+ gsize at;
+
+ n_bytes = g_utf8_offset_to_pointer (chars, n_chars) - chars;
+
+ /* Need more memory */
+ if (n_bytes + self->text_bytes + 1 > self->text_size)
+ {
+ /* Calculate our new buffer size */
+ while (n_bytes + self->text_bytes + 1 > self->text_size)
+ {
+ if (self->text_size == 0)
+ {
+ self->text_size = MIN_SIZE;
+ }
+ else
+ {
+ if (2 * self->text_size < CLUTTER_TEXT_BUFFER_MAX_SIZE)
+ {
+ self->text_size *= 2;
+ }
+ else
+ {
+ self->text_size = CLUTTER_TEXT_BUFFER_MAX_SIZE;
+ if (n_bytes > self->text_size - self->text_bytes - 1)
+ {
+ n_bytes = self->text_size - self->text_bytes - 1;
+ n_bytes = g_utf8_find_prev_char (chars, chars + n_bytes + 1) - chars;
+ n_chars = g_utf8_strlen (chars, n_bytes);
+ }
+ break;
+ }
+ }
+ }
+ self->text = gcr_secure_memory_realloc (self->text, self->text_size);
+ }
+
+ /* Actual text insertion */
+ at = g_utf8_offset_to_pointer (self->text, position) - self->text;
+ memmove (self->text + at + n_bytes, self->text + at, self->text_bytes - at);
+ memcpy (self->text + at, chars, n_bytes);
+
+ /* Book keeping */
+ self->text_bytes += n_bytes;
+ self->text_chars += n_chars;
+ self->text[self->text_bytes] = '\0';
+
+ clutter_text_buffer_emit_inserted_text (buffer, position, chars, n_chars);
+ return n_chars;
+}
+
+static guint
+shell_secure_text_buffer_real_delete_text (ClutterTextBuffer *buffer,
+ guint position,
+ guint n_chars)
+{
+ ShellSecureTextBuffer *self = SHELL_SECURE_TEXT_BUFFER (buffer);
+ gsize start, end;
+
+ if (position > self->text_chars)
+ position = self->text_chars;
+ if (position + n_chars > self->text_chars)
+ n_chars = self->text_chars - position;
+
+ if (n_chars > 0)
+ {
+ start = g_utf8_offset_to_pointer (self->text, position) - self->text;
+ end = g_utf8_offset_to_pointer (self->text, position + n_chars) - self->text;
+
+ memmove (self->text + start, self->text + end, self->text_bytes + 1 - end);
+ self->text_chars -= n_chars;
+ self->text_bytes -= (end - start);
+
+ clutter_text_buffer_emit_deleted_text (buffer, position, n_chars);
+ }
+
+ return n_chars;
+}
+
+static void
+shell_secure_text_buffer_init (ShellSecureTextBuffer *self)
+{
+
+}
+
+static void
+shell_secure_text_buffer_finalize (GObject *obj)
+{
+ ShellSecureTextBuffer *self = SHELL_SECURE_TEXT_BUFFER (obj);
+
+ if (self->text)
+ {
+ gcr_secure_memory_strfree (self->text);
+ self->text = NULL;
+ self->text_bytes = self->text_size = 0;
+ self->text_chars = 0;
+ }
+
+ G_OBJECT_CLASS (shell_secure_text_buffer_parent_class)->finalize (obj);
+}
+
+static void
+shell_secure_text_buffer_class_init (ShellSecureTextBufferClass *klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+ ClutterTextBufferClass *buffer_class = CLUTTER_TEXT_BUFFER_CLASS (klass);
+
+ gobject_class->finalize = shell_secure_text_buffer_finalize;
+
+ buffer_class->get_text = shell_secure_text_buffer_real_get_text;
+ buffer_class->get_length = shell_secure_text_buffer_real_get_length;
+ buffer_class->insert_text = shell_secure_text_buffer_real_insert_text;
+ buffer_class->delete_text = shell_secure_text_buffer_real_delete_text;
+}
+
+ClutterTextBuffer *
+shell_secure_text_buffer_new (void)
+{
+ return g_object_new (SHELL_TYPE_SECURE_TEXT_BUFFER, NULL);
+}
diff --git a/src/shell-secure-text-buffer.h b/src/shell-secure-text-buffer.h
new file mode 100644
index 0000000..6685b72
--- /dev/null
+++ b/src/shell-secure-text-buffer.h
@@ -0,0 +1,39 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/* shell-secure-text-buffer.h - secure memory clutter text buffer
+
+ Copyright (C) 2009 Stefan Walter
+ Copyright (C) 2012 Red Hat Inc.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public License as
+ published by the Free Software Foundation; either version 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
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public
+ License along with the Gnome Library; see the file COPYING.LIB. If not,
+ see <http://www.gnu.org/licenses/>.
+
+ Author: Stef Walter <stefw@gnome.org>
+*/
+
+#ifndef __SHELL_SECURE_TEXT_BUFFER_H__
+#define __SHELL_SECURE_TEXT_BUFFER_H__
+
+#include <clutter/clutter.h>
+
+G_BEGIN_DECLS
+
+#define SHELL_TYPE_SECURE_TEXT_BUFFER (shell_secure_text_buffer_get_type ())
+G_DECLARE_FINAL_TYPE (ShellSecureTextBuffer, shell_secure_text_buffer,
+ SHELL, SECURE_TEXT_BUFFER, ClutterTextBuffer)
+
+ClutterTextBuffer * shell_secure_text_buffer_new (void);
+
+G_END_DECLS
+
+#endif /* __SHELL_SECURE_TEXT_BUFFER_H__ */
diff --git a/src/shell-square-bin.c b/src/shell-square-bin.c
new file mode 100644
index 0000000..06587fb
--- /dev/null
+++ b/src/shell-square-bin.c
@@ -0,0 +1,43 @@
+#include "config.h"
+
+#include "shell-square-bin.h"
+
+struct _ShellSquareBin
+{
+ /*< private >*/
+ StBin parent_instance;
+};
+
+G_DEFINE_TYPE (ShellSquareBin, shell_square_bin, ST_TYPE_BIN);
+
+static void
+shell_square_bin_get_preferred_width (ClutterActor *actor,
+ float for_height,
+ float *min_width_p,
+ float *natural_width_p)
+{
+ float min_width, nat_width;
+
+ /* Return the actual height to keep the squared aspect */
+ clutter_actor_get_preferred_height (actor, -1,
+ &min_width, &nat_width);
+
+ if (min_width_p)
+ *min_width_p = min_width;
+
+ if (natural_width_p)
+ *natural_width_p = nat_width;
+}
+
+static void
+shell_square_bin_init (ShellSquareBin *self)
+{
+}
+
+static void
+shell_square_bin_class_init (ShellSquareBinClass *klass)
+{
+ ClutterActorClass *actor_class = CLUTTER_ACTOR_CLASS (klass);
+
+ actor_class->get_preferred_width = shell_square_bin_get_preferred_width;
+}
diff --git a/src/shell-square-bin.h b/src/shell-square-bin.h
new file mode 100644
index 0000000..2b7d4b2
--- /dev/null
+++ b/src/shell-square-bin.h
@@ -0,0 +1,13 @@
+#ifndef __SHELL_SQUARE_BIN_H__
+#define __SHELL_SQUARE_BIN_H__
+
+#include <st/st.h>
+
+G_BEGIN_DECLS
+
+#define SHELL_TYPE_SQUARE_BIN (shell_square_bin_get_type ())
+G_DECLARE_FINAL_TYPE (ShellSquareBin, shell_square_bin, SHELL, SquareBin, StBin)
+
+G_END_DECLS
+
+#endif /* __SHELL_SQUARE_BIN_H__ */
diff --git a/src/shell-stack.c b/src/shell-stack.c
new file mode 100644
index 0000000..79841e0
--- /dev/null
+++ b/src/shell-stack.c
@@ -0,0 +1,196 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+/**
+ * SECTION:shell-stack
+ * @short_description: Pure "Z-axis" container class
+ *
+ * A #ShellStack draws its children on top of each other,
+ * aligned to the top left. It will be sized in width/height
+ * according to the largest such dimension of its children, and
+ * all children will be allocated that size. This differs
+ * from #ClutterGroup which allocates its children their natural
+ * size, even if that would overflow the size allocated to the stack.
+ */
+
+#include "config.h"
+
+#include "shell-stack.h"
+
+struct _ShellStack
+{
+ StWidget parent;
+};
+
+G_DEFINE_TYPE (ShellStack, shell_stack, ST_TYPE_WIDGET);
+
+static void
+shell_stack_allocate (ClutterActor *self,
+ const ClutterActorBox *box)
+{
+ StThemeNode *theme_node = st_widget_get_theme_node (ST_WIDGET (self));
+ ClutterActorBox content_box;
+ ClutterActor *child;
+
+ clutter_actor_set_allocation (self, box);
+
+ st_theme_node_get_content_box (theme_node, box, &content_box);
+
+ for (child = clutter_actor_get_first_child (self);
+ child != NULL;
+ child = clutter_actor_get_next_sibling (child))
+ {
+ ClutterActorBox child_box = content_box;
+ clutter_actor_allocate (child, &child_box);
+ }
+}
+
+static void
+shell_stack_get_preferred_height (ClutterActor *actor,
+ gfloat for_width,
+ gfloat *min_height_p,
+ gfloat *natural_height_p)
+{
+ StThemeNode *theme_node = st_widget_get_theme_node (ST_WIDGET (actor));
+ gboolean first = TRUE;
+ float min = 0, natural = 0;
+ ClutterActor *child;
+
+ st_theme_node_adjust_for_width (theme_node, &for_width);
+
+ for (child = clutter_actor_get_first_child (actor);
+ child != NULL;
+ child = clutter_actor_get_next_sibling (child))
+ {
+ float child_min, child_natural;
+
+ clutter_actor_get_preferred_height (child,
+ for_width,
+ &child_min,
+ &child_natural);
+
+ if (first)
+ {
+ first = FALSE;
+ min = child_min;
+ natural = child_natural;
+ }
+ else
+ {
+ if (child_min > min)
+ min = child_min;
+
+ if (child_natural > natural)
+ natural = child_natural;
+ }
+ }
+
+ if (min_height_p)
+ *min_height_p = min;
+
+ if (natural_height_p)
+ *natural_height_p = natural;
+
+ st_theme_node_adjust_preferred_height (theme_node, min_height_p, natural_height_p);
+}
+
+static void
+shell_stack_get_preferred_width (ClutterActor *actor,
+ gfloat for_height,
+ gfloat *min_width_p,
+ gfloat *natural_width_p)
+{
+ StThemeNode *theme_node = st_widget_get_theme_node (ST_WIDGET (actor));
+ gboolean first = TRUE;
+ float min = 0, natural = 0;
+ ClutterActor *child;
+
+ st_theme_node_adjust_for_height (theme_node, &for_height);
+
+ for (child = clutter_actor_get_first_child (actor);
+ child != NULL;
+ child = clutter_actor_get_next_sibling (child))
+ {
+ float child_min, child_natural;
+
+ clutter_actor_get_preferred_width (child,
+ for_height,
+ &child_min,
+ &child_natural);
+
+ if (first)
+ {
+ first = FALSE;
+ min = child_min;
+ natural = child_natural;
+ }
+ else
+ {
+ if (child_min > min)
+ min = child_min;
+
+ if (child_natural > natural)
+ natural = child_natural;
+ }
+ }
+
+ if (min_width_p)
+ *min_width_p = min;
+
+ if (natural_width_p)
+ *natural_width_p = natural;
+
+ st_theme_node_adjust_preferred_width (theme_node, min_width_p, natural_width_p);
+}
+
+static gboolean
+shell_stack_navigate_focus (StWidget *widget,
+ ClutterActor *from,
+ StDirectionType direction)
+{
+ ClutterActor *top_actor;
+
+ /* If the stack is itself focusable, then focus into or out of
+ * it, as appropriate.
+ */
+ if (st_widget_get_can_focus (widget))
+ {
+ if (from && clutter_actor_contains (CLUTTER_ACTOR (widget), from))
+ return FALSE;
+
+ if (clutter_actor_is_mapped (CLUTTER_ACTOR (widget)))
+ {
+ clutter_actor_grab_key_focus (CLUTTER_ACTOR (widget));
+ return TRUE;
+ }
+ else
+ {
+ return FALSE;
+ }
+ }
+
+ top_actor = clutter_actor_get_last_child (CLUTTER_ACTOR (widget));
+ while (top_actor && !clutter_actor_is_visible (top_actor))
+ top_actor = clutter_actor_get_previous_sibling (top_actor);
+ if (ST_IS_WIDGET (top_actor))
+ return st_widget_navigate_focus (ST_WIDGET (top_actor), from, direction, FALSE);
+ else
+ return FALSE;
+}
+
+static void
+shell_stack_class_init (ShellStackClass *klass)
+{
+ ClutterActorClass *actor_class = CLUTTER_ACTOR_CLASS (klass);
+ StWidgetClass *widget_class = ST_WIDGET_CLASS (klass);
+
+ actor_class->get_preferred_width = shell_stack_get_preferred_width;
+ actor_class->get_preferred_height = shell_stack_get_preferred_height;
+ actor_class->allocate = shell_stack_allocate;
+
+ widget_class->navigate_focus = shell_stack_navigate_focus;
+}
+
+static void
+shell_stack_init (ShellStack *actor)
+{
+}
diff --git a/src/shell-stack.h b/src/shell-stack.h
new file mode 100644
index 0000000..e54160d
--- /dev/null
+++ b/src/shell-stack.h
@@ -0,0 +1,11 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+#ifndef __SHELL_STACK_H__
+#define __SHELL_STACK_H__
+
+#include "st.h"
+#include <gtk/gtk.h>
+
+#define SHELL_TYPE_STACK (shell_stack_get_type ())
+G_DECLARE_FINAL_TYPE (ShellStack, shell_stack, SHELL, STACK, StWidget)
+
+#endif /* __SHELL_STACK_H__ */
diff --git a/src/shell-tray-icon.c b/src/shell-tray-icon.c
new file mode 100644
index 0000000..14b2191
--- /dev/null
+++ b/src/shell-tray-icon.c
@@ -0,0 +1,294 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+#include "config.h"
+
+#include "shell-tray-icon.h"
+#include "shell-gtk-embed.h"
+#include "tray/na-tray-child.h"
+#include <gdk/gdkx.h>
+#include <X11/Xatom.h>
+#include "st.h"
+
+enum {
+ PROP_0,
+
+ PROP_PID,
+ PROP_TITLE,
+ PROP_WM_CLASS
+};
+
+typedef struct _ShellTrayIconPrivate ShellTrayIconPrivate;
+
+struct _ShellTrayIcon
+{
+ ShellGtkEmbed parent;
+
+ ShellTrayIconPrivate *priv;
+};
+
+struct _ShellTrayIconPrivate
+{
+ NaTrayChild *socket;
+
+ pid_t pid;
+ char *title, *wm_class;
+};
+
+G_DEFINE_TYPE_WITH_PRIVATE (ShellTrayIcon, shell_tray_icon, SHELL_TYPE_GTK_EMBED);
+
+static void
+shell_tray_icon_finalize (GObject *object)
+{
+ ShellTrayIcon *icon = SHELL_TRAY_ICON (object);
+
+ g_free (icon->priv->title);
+ g_free (icon->priv->wm_class);
+
+ G_OBJECT_CLASS (shell_tray_icon_parent_class)->finalize (object);
+}
+
+static void
+shell_tray_icon_constructed (GObject *object)
+{
+ GdkWindow *icon_app_window;
+ ShellTrayIcon *icon = SHELL_TRAY_ICON (object);
+ ShellEmbeddedWindow *window;
+ GdkDisplay *display;
+ Window plug_xid;
+ Atom _NET_WM_PID, type;
+ int result, format;
+ gulong nitems, bytes_after, *val = NULL;
+
+ /* We do all this now rather than computing it on the fly later,
+ * because the shell may want to see their values from a
+ * tray-icon-removed signal handler, at which point the plug has
+ * already been removed from the socket.
+ */
+
+ g_object_get (object, "window", &window, NULL);
+ g_return_if_fail (window != NULL);
+ icon->priv->socket = NA_TRAY_CHILD (gtk_bin_get_child (GTK_BIN (window)));
+ g_object_unref (window);
+
+ icon->priv->title = na_tray_child_get_title (icon->priv->socket);
+ na_tray_child_get_wm_class (icon->priv->socket, NULL, &icon->priv->wm_class);
+
+ icon_app_window = gtk_socket_get_plug_window (GTK_SOCKET (icon->priv->socket));
+ plug_xid = GDK_WINDOW_XID (icon_app_window);
+
+ display = gtk_widget_get_display (GTK_WIDGET (icon->priv->socket));
+ gdk_x11_display_error_trap_push (display);
+ _NET_WM_PID = gdk_x11_get_xatom_by_name_for_display (display, "_NET_WM_PID");
+ result = XGetWindowProperty (GDK_DISPLAY_XDISPLAY (display), plug_xid,
+ _NET_WM_PID, 0, G_MAXLONG, False, XA_CARDINAL,
+ &type, &format, &nitems,
+ &bytes_after, (guchar **)&val);
+ if (!gdk_x11_display_error_trap_pop (display) &&
+ result == Success &&
+ type == XA_CARDINAL &&
+ nitems == 1)
+ icon->priv->pid = *val;
+
+ if (val)
+ XFree (val);
+}
+
+static void
+shell_tray_icon_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ ShellTrayIcon *icon = SHELL_TRAY_ICON (object);
+
+ switch (prop_id)
+ {
+ case PROP_PID:
+ g_value_set_uint (value, icon->priv->pid);
+ break;
+
+ case PROP_TITLE:
+ g_value_set_string (value, icon->priv->title);
+ break;
+
+ case PROP_WM_CLASS:
+ g_value_set_string (value, icon->priv->wm_class);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+shell_tray_icon_class_init (ShellTrayIconClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->get_property = shell_tray_icon_get_property;
+ object_class->constructed = shell_tray_icon_constructed;
+ object_class->finalize = shell_tray_icon_finalize;
+
+ g_object_class_install_property (object_class,
+ PROP_PID,
+ g_param_spec_uint ("pid",
+ "PID",
+ "The PID of the icon's application",
+ 0, G_MAXUINT, 0,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+ g_object_class_install_property (object_class,
+ PROP_TITLE,
+ g_param_spec_string ("title",
+ "Title",
+ "The icon's window title",
+ NULL,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+ g_object_class_install_property (object_class,
+ PROP_WM_CLASS,
+ g_param_spec_string ("wm-class",
+ "WM Class",
+ "The icon's window WM_CLASS",
+ NULL,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+}
+
+static void
+shell_tray_icon_init (ShellTrayIcon *icon)
+{
+ icon->priv = shell_tray_icon_get_instance_private (icon);
+}
+
+/*
+ * Public API
+ */
+ClutterActor *
+shell_tray_icon_new (ShellEmbeddedWindow *window)
+{
+ g_return_val_if_fail (SHELL_IS_EMBEDDED_WINDOW (window), NULL);
+
+ return g_object_new (SHELL_TYPE_TRAY_ICON,
+ "window", window,
+ NULL);
+}
+
+/**
+ * shell_tray_icon_click:
+ * @icon: a #ShellTrayIcon
+ * @event: the #ClutterEvent triggering the fake click
+ *
+ * Fakes a press and release on @icon. @event must be a
+ * %CLUTTER_BUTTON_RELEASE, %CLUTTER_KEY_PRESS or %CLUTTER_KEY_RELEASE event.
+ * Its relevant details will be passed on to the icon, but its
+ * coordinates will be ignored; the click is
+ * always made on the center of @icon.
+ */
+void
+shell_tray_icon_click (ShellTrayIcon *icon,
+ ClutterEvent *event)
+{
+ XKeyEvent xkevent;
+ XButtonEvent xbevent;
+ XCrossingEvent xcevent;
+ GdkDisplay *display;
+ GdkWindow *remote_window;
+ GdkScreen *screen;
+ int x_root, y_root;
+ Display *xdisplay;
+ Window xwindow, xrootwindow;
+ ClutterEventType event_type = clutter_event_type (event);
+
+ g_return_if_fail (event_type == CLUTTER_BUTTON_RELEASE ||
+ event_type == CLUTTER_KEY_PRESS ||
+ event_type == CLUTTER_KEY_RELEASE);
+
+ remote_window = gtk_socket_get_plug_window (GTK_SOCKET (icon->priv->socket));
+ if (remote_window == NULL)
+ {
+ g_warning ("shell tray: plug window is gone");
+ return;
+ }
+ xdisplay = GDK_WINDOW_XDISPLAY (remote_window);
+
+ display = gdk_x11_lookup_xdisplay (xdisplay);
+ gdk_x11_display_error_trap_push (display);
+
+ xwindow = GDK_WINDOW_XID (remote_window);
+ screen = gdk_window_get_screen (remote_window);
+ xrootwindow = GDK_WINDOW_XID (gdk_screen_get_root_window (screen));
+ gdk_window_get_origin (remote_window, &x_root, &y_root);
+
+
+ /* First make the icon believe the pointer is inside it */
+ xcevent.type = EnterNotify;
+ xcevent.window = xwindow;
+ xcevent.root = xrootwindow;
+ xcevent.subwindow = None;
+ xcevent.time = clutter_event_get_time (event);
+ xcevent.x = gdk_window_get_width (remote_window) / 2;
+ xcevent.y = gdk_window_get_height (remote_window) / 2;
+ xcevent.x_root = x_root + xcevent.x;
+ xcevent.y_root = y_root + xcevent.y;
+ xcevent.mode = NotifyNormal;
+ xcevent.detail = NotifyNonlinear;
+ xcevent.same_screen = True;
+ XSendEvent (xdisplay, xwindow, False, 0, (XEvent *)&xcevent);
+
+ /* Now do the click */
+ if (event_type == CLUTTER_BUTTON_RELEASE)
+ {
+ xbevent.window = xwindow;
+ xbevent.root = xrootwindow;
+ xbevent.subwindow = None;
+ xbevent.time = xcevent.time;
+ xbevent.x = xcevent.x;
+ xbevent.y = xcevent.y;
+ xbevent.x_root = xcevent.x_root;
+ xbevent.y_root = xcevent.y_root;
+ xbevent.state = clutter_event_get_state (event);
+ xbevent.same_screen = True;
+ xbevent.type = ButtonPress;
+ xbevent.button = clutter_event_get_button (event);
+ XSendEvent (xdisplay, xwindow, False, 0, (XEvent *)&xbevent);
+
+ xbevent.type = ButtonRelease;
+ XSendEvent (xdisplay, xwindow, False, 0, (XEvent *)&xbevent);
+ }
+ else
+ {
+ xkevent.window = xwindow;
+ xkevent.root = xrootwindow;
+ xkevent.subwindow = None;
+ xkevent.time = xcevent.time;
+ xkevent.x = xcevent.x;
+ xkevent.y = xcevent.y;
+ xkevent.x_root = xcevent.x_root;
+ xkevent.y_root = xcevent.y_root;
+ xkevent.state = clutter_event_get_state (event);
+ xkevent.same_screen = True;
+ xkevent.keycode = clutter_event_get_key_code (event);
+
+ xkevent.type = KeyPress;
+ XSendEvent (xdisplay, xwindow, False, 0, (XEvent *)&xkevent);
+
+ if (event_type == CLUTTER_KEY_RELEASE)
+ {
+ /* If the application takes a grab on KeyPress, we don't
+ * want to send it a KeyRelease. There's no good way of
+ * knowing whether a tray icon will take a grab, so just
+ * assume it does, and don't send the KeyRelease. That might
+ * make the tracking for key events messed up if it doesn't take
+ * a grab, but the tray icon won't get key focus in normal cases,
+ * so let's hope this isn't too damaging...
+ */
+ xkevent.type = KeyRelease;
+ XSendEvent (xdisplay, xwindow, False, 0, (XEvent *)&xkevent);
+ }
+ }
+
+ /* And move the pointer back out */
+ xcevent.type = LeaveNotify;
+ XSendEvent (xdisplay, xwindow, False, 0, (XEvent *)&xcevent);
+
+ gdk_x11_display_error_trap_pop_ignored (display);
+}
diff --git a/src/shell-tray-icon.h b/src/shell-tray-icon.h
new file mode 100644
index 0000000..f0e1191
--- /dev/null
+++ b/src/shell-tray-icon.h
@@ -0,0 +1,16 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+#ifndef __SHELL_TRAY_ICON_H__
+#define __SHELL_TRAY_ICON_H__
+
+#include "shell-gtk-embed.h"
+
+#define SHELL_TYPE_TRAY_ICON (shell_tray_icon_get_type ())
+G_DECLARE_FINAL_TYPE (ShellTrayIcon, shell_tray_icon,
+ SHELL, TRAY_ICON, ShellGtkEmbed)
+
+ClutterActor *shell_tray_icon_new (ShellEmbeddedWindow *window);
+
+void shell_tray_icon_click (ShellTrayIcon *icon,
+ ClutterEvent *event);
+
+#endif /* __SHELL_TRAY_ICON_H__ */
diff --git a/src/shell-tray-manager.c b/src/shell-tray-manager.c
new file mode 100644
index 0000000..c8e3259
--- /dev/null
+++ b/src/shell-tray-manager.c
@@ -0,0 +1,374 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+#include "config.h"
+
+#include <clutter/clutter.h>
+#include <girepository.h>
+#include <gtk/gtk.h>
+#include <meta/display.h>
+
+#include "shell-tray-manager.h"
+#include "na-tray-manager.h"
+
+#include "shell-tray-icon.h"
+#include "shell-embedded-window.h"
+#include "shell-global.h"
+
+typedef struct _ShellTrayManagerPrivate ShellTrayManagerPrivate;
+
+struct _ShellTrayManager
+{
+ GObject parent_instance;
+
+ ShellTrayManagerPrivate *priv;
+};
+
+struct _ShellTrayManagerPrivate {
+ NaTrayManager *na_manager;
+ ClutterColor bg_color;
+
+ GHashTable *icons;
+ StWidget *theme_widget;
+};
+
+typedef struct {
+ ShellTrayManager *manager;
+ GtkWidget *socket;
+ GtkWidget *window;
+ ClutterActor *actor;
+} ShellTrayManagerChild;
+
+enum {
+ PROP_0,
+
+ PROP_BG_COLOR
+};
+
+/* Signals */
+enum
+{
+ TRAY_ICON_ADDED,
+ TRAY_ICON_REMOVED,
+ LAST_SIGNAL
+};
+
+G_DEFINE_TYPE_WITH_PRIVATE (ShellTrayManager, shell_tray_manager, G_TYPE_OBJECT);
+
+static guint shell_tray_manager_signals [LAST_SIGNAL] = { 0 };
+
+static const ClutterColor default_color = { 0x00, 0x00, 0x00, 0xff };
+
+static void shell_tray_manager_release_resources (ShellTrayManager *manager);
+
+static void na_tray_icon_added (NaTrayManager *na_manager, GtkWidget *child, gpointer manager);
+static void na_tray_icon_removed (NaTrayManager *na_manager, GtkWidget *child, gpointer manager);
+
+static void
+free_tray_icon (gpointer data)
+{
+ ShellTrayManagerChild *child = data;
+
+ gtk_widget_destroy (child->window);
+ if (child->actor)
+ {
+ g_signal_handlers_disconnect_matched (child->actor, G_SIGNAL_MATCH_DATA,
+ 0, 0, NULL, NULL, child);
+ g_object_unref (child->actor);
+ }
+ g_free (child);
+}
+
+static void
+shell_tray_manager_set_property(GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ ShellTrayManager *manager = SHELL_TRAY_MANAGER (object);
+
+ switch (prop_id)
+ {
+ case PROP_BG_COLOR:
+ {
+ ClutterColor *color = g_value_get_boxed (value);
+ if (color)
+ manager->priv->bg_color = *color;
+ else
+ manager->priv->bg_color = default_color;
+ }
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+shell_tray_manager_get_property(GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ ShellTrayManager *manager = SHELL_TRAY_MANAGER (object);
+
+ switch (prop_id)
+ {
+ case PROP_BG_COLOR:
+ g_value_set_boxed (value, &manager->priv->bg_color);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+shell_tray_manager_init (ShellTrayManager *manager)
+{
+ manager->priv = shell_tray_manager_get_instance_private (manager);
+
+ manager->priv->bg_color = default_color;
+}
+
+static void
+shell_tray_manager_finalize (GObject *object)
+{
+ ShellTrayManager *manager = SHELL_TRAY_MANAGER (object);
+
+ shell_tray_manager_release_resources (manager);
+
+ G_OBJECT_CLASS (shell_tray_manager_parent_class)->finalize (object);
+}
+
+static void
+shell_tray_manager_class_init (ShellTrayManagerClass *klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+ gobject_class->finalize = shell_tray_manager_finalize;
+ gobject_class->set_property = shell_tray_manager_set_property;
+ gobject_class->get_property = shell_tray_manager_get_property;
+
+ shell_tray_manager_signals[TRAY_ICON_ADDED] =
+ g_signal_new ("tray-icon-added",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL, NULL,
+ G_TYPE_NONE, 1,
+ CLUTTER_TYPE_ACTOR);
+ shell_tray_manager_signals[TRAY_ICON_REMOVED] =
+ g_signal_new ("tray-icon-removed",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL, NULL,
+ G_TYPE_NONE, 1,
+ CLUTTER_TYPE_ACTOR);
+
+ /* Lifting the CONSTRUCT_ONLY here isn't hard; you just need to
+ * iterate through the icons, reset the background pixmap, and
+ * call na_tray_child_force_redraw()
+ */
+ g_object_class_install_property (gobject_class,
+ PROP_BG_COLOR,
+ g_param_spec_boxed ("bg-color",
+ "BG Color",
+ "Background color (only if we don't have transparency)",
+ CLUTTER_TYPE_COLOR,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+}
+
+ShellTrayManager *
+shell_tray_manager_new (void)
+{
+ return g_object_new (SHELL_TYPE_TRAY_MANAGER, NULL);
+}
+
+static void
+shell_tray_manager_ensure_resources (ShellTrayManager *manager)
+{
+ if (manager->priv->na_manager != NULL)
+ return;
+
+ manager->priv->icons = g_hash_table_new_full (NULL, NULL,
+ NULL, free_tray_icon);
+
+ manager->priv->na_manager = na_tray_manager_new ();
+
+ g_signal_connect (manager->priv->na_manager, "tray-icon-added",
+ G_CALLBACK (na_tray_icon_added), manager);
+ g_signal_connect (manager->priv->na_manager, "tray-icon-removed",
+ G_CALLBACK (na_tray_icon_removed), manager);
+}
+
+static void
+shell_tray_manager_release_resources (ShellTrayManager *manager)
+{
+ g_clear_object (&manager->priv->na_manager);
+ g_clear_pointer (&manager->priv->icons, g_hash_table_destroy);
+}
+
+static void
+shell_tray_manager_style_changed (StWidget *theme_widget,
+ gpointer user_data)
+{
+ ShellTrayManager *manager = user_data;
+ StThemeNode *theme_node;
+ StIconColors *icon_colors;
+
+ if (manager->priv->na_manager == NULL)
+ return;
+
+ theme_node = st_widget_get_theme_node (theme_widget);
+ icon_colors = st_theme_node_get_icon_colors (theme_node);
+ na_tray_manager_set_colors (manager->priv->na_manager,
+ &icon_colors->foreground, &icon_colors->warning,
+ &icon_colors->error, &icon_colors->success);
+}
+
+static void
+shell_tray_manager_manage_screen_internal (ShellTrayManager *manager)
+{
+ shell_tray_manager_ensure_resources (manager);
+ na_tray_manager_manage_screen (manager->priv->na_manager);
+}
+
+void
+shell_tray_manager_manage_screen (ShellTrayManager *manager,
+ StWidget *theme_widget)
+{
+ MetaDisplay *display = shell_global_get_display (shell_global_get ());
+
+ g_set_weak_pointer (&manager->priv->theme_widget, theme_widget);
+
+ if (meta_display_get_x11_display (display) != NULL)
+ shell_tray_manager_manage_screen_internal (manager);
+
+ g_signal_connect_object (display, "x11-display-setup",
+ G_CALLBACK (shell_tray_manager_manage_screen_internal),
+ manager, G_CONNECT_SWAPPED);
+ g_signal_connect_object (display, "x11-display-closing",
+ G_CALLBACK (shell_tray_manager_release_resources),
+ manager, G_CONNECT_SWAPPED);
+
+ g_signal_connect_object (theme_widget, "style-changed",
+ G_CALLBACK (shell_tray_manager_style_changed),
+ manager, 0);
+ shell_tray_manager_style_changed (theme_widget, manager);
+}
+
+void
+shell_tray_manager_unmanage_screen (ShellTrayManager *manager)
+{
+ MetaDisplay *display = shell_global_get_display (shell_global_get ());
+
+ g_signal_handlers_disconnect_by_data (display, manager);
+
+ if (manager->priv->theme_widget != NULL)
+ {
+ g_signal_handlers_disconnect_by_func (manager->priv->theme_widget,
+ G_CALLBACK (shell_tray_manager_style_changed),
+ manager);
+ }
+ g_set_weak_pointer (&manager->priv->theme_widget, NULL);
+
+ shell_tray_manager_release_resources (manager);
+}
+
+static void
+shell_tray_manager_child_on_realize (GtkWidget *widget,
+ ShellTrayManagerChild *child)
+{
+ /* If the tray child is using an RGBA colormap (and so we have real
+ * transparency), we don't need to worry about the background. If
+ * not, we obey the bg-color property by creating a cairo pattern of
+ * that color and setting it as our background. Then "parent-relative"
+ * background on the socket and the plug within that will cause
+ * the icons contents to appear on top of our background color.
+ */
+ if (!na_tray_child_has_alpha (NA_TRAY_CHILD (child->socket)))
+ {
+ ClutterColor color = child->manager->priv->bg_color;
+ cairo_pattern_t *bg_pattern;
+
+ bg_pattern = cairo_pattern_create_rgb (color.red / 255.,
+ color.green / 255.,
+ color.blue / 255.);
+G_GNUC_BEGIN_IGNORE_DEPRECATIONS
+ gdk_window_set_background_pattern (gtk_widget_get_window (widget),
+ bg_pattern);
+G_GNUC_END_IGNORE_DEPRECATIONS
+
+ cairo_pattern_destroy (bg_pattern);
+ }
+}
+
+static void
+on_plug_added (GtkSocket *socket,
+ ShellTrayManager *manager)
+{
+ ShellTrayManagerChild *child;
+
+ g_signal_handlers_disconnect_by_func (socket, on_plug_added, manager);
+
+ child = g_hash_table_lookup (manager->priv->icons, socket);
+
+ child->actor = shell_tray_icon_new (SHELL_EMBEDDED_WINDOW (child->window));
+ g_object_ref_sink (child->actor);
+
+ g_signal_emit (manager, shell_tray_manager_signals[TRAY_ICON_ADDED], 0,
+ child->actor);
+}
+
+static void
+na_tray_icon_added (NaTrayManager *na_manager, GtkWidget *socket,
+ gpointer user_data)
+{
+ ShellTrayManager *manager = user_data;
+ GtkWidget *win;
+ ShellTrayManagerChild *child;
+
+ win = shell_embedded_window_new ();
+ gtk_container_add (GTK_CONTAINER (win), socket);
+
+ /* The visual of the socket matches that of its contents; make
+ * the window we put it in match that as well */
+ gtk_widget_set_visual (win, gtk_widget_get_visual (socket));
+
+ child = g_new0 (ShellTrayManagerChild, 1);
+ child->manager = manager;
+ child->window = win;
+ child->socket = socket;
+
+ g_signal_connect (win, "realize",
+ G_CALLBACK (shell_tray_manager_child_on_realize), child);
+
+ gtk_widget_show_all (win);
+
+ g_hash_table_insert (manager->priv->icons, socket, child);
+
+ g_signal_connect (socket, "plug-added", G_CALLBACK (on_plug_added), manager);
+}
+
+static void
+na_tray_icon_removed (NaTrayManager *na_manager, GtkWidget *socket,
+ gpointer user_data)
+{
+ ShellTrayManager *manager = user_data;
+ ShellTrayManagerChild *child;
+
+ child = g_hash_table_lookup (manager->priv->icons, socket);
+ g_return_if_fail (child != NULL);
+
+ if (child->actor != NULL)
+ {
+ /* Only emit signal if a corresponding tray-icon-added signal was emitted,
+ that is, if embedding did not fail and we got a plug-added
+ */
+ g_signal_emit (manager,
+ shell_tray_manager_signals[TRAY_ICON_REMOVED], 0,
+ child->actor);
+ }
+ g_hash_table_remove (manager->priv->icons, socket);
+}
diff --git a/src/shell-tray-manager.h b/src/shell-tray-manager.h
new file mode 100644
index 0000000..d6279d4
--- /dev/null
+++ b/src/shell-tray-manager.h
@@ -0,0 +1,22 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+#ifndef __SHELL_TRAY_MANAGER_H__
+#define __SHELL_TRAY_MANAGER_H__
+
+#include <clutter/clutter.h>
+#include "st.h"
+
+G_BEGIN_DECLS
+
+#define SHELL_TYPE_TRAY_MANAGER (shell_tray_manager_get_type ())
+G_DECLARE_FINAL_TYPE (ShellTrayManager, shell_tray_manager,
+ SHELL, TRAY_MANAGER, GObject)
+
+ShellTrayManager *shell_tray_manager_new (void);
+void shell_tray_manager_manage_screen (ShellTrayManager *manager,
+ StWidget *theme_widget);
+void shell_tray_manager_unmanage_screen (ShellTrayManager *manager);
+
+G_END_DECLS
+
+#endif /* __SHELL_TRAY_MANAGER_H__ */
diff --git a/src/shell-util.c b/src/shell-util.c
new file mode 100644
index 0000000..aeb81a1
--- /dev/null
+++ b/src/shell-util.c
@@ -0,0 +1,854 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+#include "config.h"
+
+#include <errno.h>
+#include <math.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#ifdef HAVE_SYS_RESOURCE_H
+#include <sys/resource.h>
+#endif
+
+#include <GL/gl.h>
+#include <cogl/cogl.h>
+
+#include "shell-app-cache-private.h"
+#include "shell-util.h"
+#include <glib/gi18n-lib.h>
+#include <gtk/gtk.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <meta/display.h>
+#include <meta/meta-x11-display.h>
+
+#include <locale.h>
+#ifdef HAVE__NL_TIME_FIRST_WEEKDAY
+#include <langinfo.h>
+#endif
+
+#ifdef HAVE_SYSTEMD
+#include <systemd/sd-daemon.h>
+#include <systemd/sd-login.h>
+#else
+/* So we don't need to add ifdef's everywhere */
+#define sd_notify(u, m) do {} while (0)
+#define sd_notifyf(u, m, ...) do {} while (0)
+#endif
+
+static void
+stop_pick (ClutterActor *actor)
+{
+ g_signal_stop_emission_by_name (actor, "pick");
+}
+
+/**
+ * shell_util_set_hidden_from_pick:
+ * @actor: A #ClutterActor
+ * @hidden: Whether @actor should be hidden from pick
+ *
+ * If @hidden is %TRUE, hide @actor from pick even with a mode of
+ * %CLUTTER_PICK_ALL; if @hidden is %FALSE, unhide @actor.
+ */
+void
+shell_util_set_hidden_from_pick (ClutterActor *actor,
+ gboolean hidden)
+{
+ gpointer existing_handler_data;
+
+ existing_handler_data = g_object_get_data (G_OBJECT (actor),
+ "shell-stop-pick");
+ if (hidden)
+ {
+ if (existing_handler_data != NULL)
+ return;
+ g_signal_connect (actor, "pick", G_CALLBACK (stop_pick), NULL);
+ g_object_set_data (G_OBJECT (actor),
+ "shell-stop-pick", GUINT_TO_POINTER (1));
+ }
+ else
+ {
+ if (existing_handler_data == NULL)
+ return;
+ g_signal_handlers_disconnect_by_func (actor, stop_pick, NULL);
+ g_object_set_data (G_OBJECT (actor), "shell-stop-pick", NULL);
+ }
+}
+
+/**
+ * shell_util_get_week_start:
+ *
+ * Gets the first week day for the current locale, expressed as a
+ * number in the range 0..6, representing week days from Sunday to
+ * Saturday.
+ *
+ * Returns: A number representing the first week day for the current
+ * locale
+ */
+/* Copied from gtkcalendar.c */
+int
+shell_util_get_week_start (void)
+{
+ int week_start;
+#ifdef HAVE__NL_TIME_FIRST_WEEKDAY
+ union { unsigned int word; char *string; } langinfo;
+ int week_1stday = 0;
+ int first_weekday = 1;
+ guint week_origin;
+#else
+ char *gtk_week_start;
+#endif
+
+#ifdef HAVE__NL_TIME_FIRST_WEEKDAY
+ langinfo.string = nl_langinfo (_NL_TIME_FIRST_WEEKDAY);
+ first_weekday = langinfo.string[0];
+ langinfo.string = nl_langinfo (_NL_TIME_WEEK_1STDAY);
+ week_origin = langinfo.word;
+ if (week_origin == 19971130) /* Sunday */
+ week_1stday = 0;
+ else if (week_origin == 19971201) /* Monday */
+ week_1stday = 1;
+ else
+ g_warning ("Unknown value of _NL_TIME_WEEK_1STDAY.\n");
+
+ week_start = (week_1stday + first_weekday - 1) % 7;
+#else
+ /* Use a define to hide the string from xgettext */
+# define GTK_WEEK_START "calendar:week_start:0"
+ gtk_week_start = dgettext ("gtk30", GTK_WEEK_START);
+
+ if (strncmp (gtk_week_start, "calendar:week_start:", 20) == 0)
+ week_start = *(gtk_week_start + 20) - '0';
+ else
+ week_start = -1;
+
+ if (week_start < 0 || week_start > 6)
+ {
+ g_warning ("Whoever translated calendar:week_start:0 for GTK+ "
+ "did so wrongly.\n");
+ return 0;
+ }
+#endif
+
+ return week_start;
+}
+
+/**
+ * shell_util_translate_time_string:
+ * @str: String to translate
+ *
+ * Translate @str according to the locale defined by LC_TIME; unlike
+ * dcgettext(), the translations is still taken from the LC_MESSAGES
+ * catalogue and not the LC_TIME one.
+ *
+ * Returns: the translated string
+ */
+const char *
+shell_util_translate_time_string (const char *str)
+{
+ const char *locale = g_getenv ("LC_TIME");
+ const char *res;
+ char *sep;
+ locale_t old_loc;
+ locale_t loc = (locale_t) 0;
+
+ if (locale)
+ loc = newlocale (LC_MESSAGES_MASK, locale, (locale_t) 0);
+
+ old_loc = uselocale (loc);
+
+ sep = strchr (str, '\004');
+ res = g_dpgettext (NULL, str, sep ? sep - str + 1 : 0);
+
+ uselocale (old_loc);
+
+ if (loc != (locale_t) 0)
+ freelocale (loc);
+
+ return res;
+}
+
+/**
+ * shell_util_regex_escape:
+ * @str: a UTF-8 string to escape
+ *
+ * A wrapper around g_regex_escape_string() that takes its argument as
+ * \0-terminated string rather than a byte-array that confuses gjs.
+ *
+ * Returns: @str with all regex-special characters escaped
+ */
+char *
+shell_util_regex_escape (const char *str)
+{
+ return g_regex_escape_string (str, -1);
+}
+
+/**
+ * shell_write_string_to_stream:
+ * @stream: a #GOutputStream
+ * @str: a UTF-8 string to write to @stream
+ * @error: location to store GError
+ *
+ * Write a string to a GOutputStream as UTF-8. This is a workaround
+ * for not having binary buffers in GJS.
+ *
+ * Return value: %TRUE if write succeeded
+ */
+gboolean
+shell_write_string_to_stream (GOutputStream *stream,
+ const char *str,
+ GError **error)
+{
+ return g_output_stream_write_all (stream, str, strlen (str),
+ NULL, NULL, error);
+}
+
+/**
+ * shell_get_file_contents_utf8_sync:
+ * @path: UTF-8 encoded filename path
+ * @error: a #GError
+ *
+ * Synchronously load the contents of a file as a NUL terminated
+ * string, validating it as UTF-8. Embedded NUL characters count as
+ * invalid content.
+ *
+ * Returns: (transfer full): File contents
+ */
+char *
+shell_get_file_contents_utf8_sync (const char *path,
+ GError **error)
+{
+ char *contents;
+ gsize len;
+ if (!g_file_get_contents (path, &contents, &len, error))
+ return NULL;
+ if (!g_utf8_validate (contents, len, NULL))
+ {
+ g_free (contents);
+ g_set_error (error,
+ G_IO_ERROR,
+ G_IO_ERROR_FAILED,
+ "File %s contains invalid UTF-8",
+ path);
+ return NULL;
+ }
+ return contents;
+}
+
+static void
+touch_file (GTask *task,
+ gpointer object,
+ gpointer task_data,
+ GCancellable *cancellable)
+{
+ GFile *file = object;
+ g_autoptr (GFile) parent = NULL;
+ g_autoptr (GFileOutputStream) stream = NULL;
+ GError *error = NULL;
+
+ parent = g_file_get_parent (file);
+ g_file_make_directory_with_parents (parent, cancellable, &error);
+
+ if (error && !g_error_matches (error, G_IO_ERROR, G_IO_ERROR_EXISTS))
+ {
+ g_task_return_error (task, error);
+ return;
+ }
+ g_clear_error (&error);
+
+ stream = g_file_create (file, G_FILE_CREATE_NONE, cancellable, &error);
+
+ if (error && !g_error_matches (error, G_IO_ERROR, G_IO_ERROR_EXISTS))
+ {
+ g_task_return_error (task, error);
+ return;
+ }
+ g_clear_error (&error);
+
+ if (stream)
+ g_output_stream_close (G_OUTPUT_STREAM (stream), NULL, NULL);
+
+ g_task_return_boolean (task, stream != NULL);
+}
+
+void
+shell_util_touch_file_async (GFile *file,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ g_autoptr (GTask) task = NULL;
+
+ g_return_if_fail (G_IS_FILE (file));
+
+ task = g_task_new (file, NULL, callback, user_data);
+ g_task_set_source_tag (task, shell_util_touch_file_async);
+
+ g_task_run_in_thread (task, touch_file);
+}
+
+gboolean
+shell_util_touch_file_finish (GFile *file,
+ GAsyncResult *res,
+ GError **error)
+{
+ g_return_val_if_fail (G_IS_FILE (file), FALSE);
+ g_return_val_if_fail (G_IS_TASK (res), FALSE);
+
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+/**
+ * shell_util_wifexited:
+ * @status: the status returned by wait() or waitpid()
+ * @exit: (out): the actual exit status of the process
+ *
+ * Implements libc standard WIFEXITED, that cannot be used JS
+ * code.
+ * Returns: TRUE if the process exited normally, FALSE otherwise
+ */
+gboolean
+shell_util_wifexited (int status,
+ int *exit)
+{
+ gboolean ret;
+
+ ret = WIFEXITED(status);
+
+ if (ret)
+ *exit = WEXITSTATUS(status);
+
+ return ret;
+}
+
+/**
+ * shell_util_create_pixbuf_from_data:
+ * @data: (array length=len) (element-type guint8) (transfer full):
+ * @len:
+ * @colorspace:
+ * @has_alpha:
+ * @bits_per_sample:
+ * @width:
+ * @height:
+ * @rowstride:
+ *
+ * Workaround for non-introspectability of gdk_pixbuf_from_data().
+ *
+ * Returns: (transfer full):
+ */
+GdkPixbuf *
+shell_util_create_pixbuf_from_data (const guchar *data,
+ gsize len,
+ GdkColorspace colorspace,
+ gboolean has_alpha,
+ int bits_per_sample,
+ int width,
+ int height,
+ int rowstride)
+{
+ return gdk_pixbuf_new_from_data (data, colorspace, has_alpha,
+ bits_per_sample, width, height, rowstride,
+ (GdkPixbufDestroyNotify) g_free, NULL);
+}
+
+typedef const gchar *(*ShellGLGetString) (GLenum);
+
+cairo_surface_t *
+shell_util_composite_capture_images (ClutterCapture *captures,
+ int n_captures,
+ int x,
+ int y,
+ int target_width,
+ int target_height,
+ float target_scale)
+{
+ int i;
+ cairo_format_t format;
+ cairo_surface_t *image;
+ cairo_t *cr;
+
+ g_assert (n_captures > 0);
+ g_assert (target_scale > 0.0f);
+
+ format = cairo_image_surface_get_format (captures[0].image);
+ image = cairo_image_surface_create (format, target_width, target_height);
+ cairo_surface_set_device_scale (image, target_scale, target_scale);
+
+ cr = cairo_create (image);
+
+ for (i = 0; i < n_captures; i++)
+ {
+ ClutterCapture *capture = &captures[i];
+
+ cairo_save (cr);
+
+ cairo_translate (cr,
+ capture->rect.x - x,
+ capture->rect.y - y);
+ cairo_set_source_surface (cr, capture->image, 0, 0);
+ cairo_paint (cr);
+
+ cairo_restore (cr);
+ }
+ cairo_destroy (cr);
+
+ return image;
+}
+
+#ifndef HAVE_FDWALK
+static int
+fdwalk (int (*cb)(void *data, int fd),
+ void *data)
+{
+ gint open_max;
+ gint fd;
+ gint res = 0;
+
+#ifdef HAVE_SYS_RESOURCE_H
+ struct rlimit rl;
+#endif
+
+#ifdef __linux__
+ DIR *d;
+
+ if ((d = opendir("/proc/self/fd")))
+ {
+ struct dirent *de;
+
+ while ((de = readdir(d)))
+ {
+ glong l;
+ gchar *e = NULL;
+
+ if (de->d_name[0] == '.')
+ continue;
+
+ errno = 0;
+ l = strtol(de->d_name, &e, 10);
+ if (errno != 0 || !e || *e)
+ continue;
+
+ fd = (gint) l;
+
+ if ((glong) fd != l)
+ continue;
+
+ if (fd == dirfd(d))
+ continue;
+
+ if ((res = cb (data, fd)) != 0)
+ break;
+ }
+
+ closedir(d);
+ return res;
+ }
+
+ /* If /proc is not mounted or not accessible we fall back to the old
+ * rlimit trick */
+
+#endif
+
+#ifdef HAVE_SYS_RESOURCE_H
+ if (getrlimit (RLIMIT_NOFILE, &rl) == 0 && rl.rlim_max != RLIM_INFINITY)
+ open_max = rl.rlim_max;
+ else
+#endif
+ open_max = sysconf (_SC_OPEN_MAX);
+
+ for (fd = 0; fd < open_max; fd++)
+ if ((res = cb (data, fd)) != 0)
+ break;
+
+ return res;
+}
+#endif
+
+static int
+check_cloexec (void *data,
+ gint fd)
+{
+ int r;
+
+ if (fd < 3)
+ return 0;
+
+ r = fcntl (fd, F_GETFD);
+ if (r < 0)
+ return 0;
+
+ if (!(r & FD_CLOEXEC))
+ g_warning ("fd %d is not CLOEXEC", fd);
+
+ return 0;
+}
+
+/**
+ * shell_util_check_cloexec_fds:
+ *
+ * Walk over all open file descriptors. Check them for the FD_CLOEXEC flag.
+ * If this flag is not set, log the offending file descriptor number.
+ *
+ * It is important that gnome-shell's file descriptors are all marked CLOEXEC,
+ * so that the shell's open file descriptors are not passed to child processes
+ * that we launch.
+ */
+void
+shell_util_check_cloexec_fds (void)
+{
+ fdwalk (check_cloexec, NULL);
+ g_info ("Open fd CLOEXEC check complete");
+}
+
+/**
+ * shell_util_get_uid:
+ *
+ * A wrapper around getuid() so that it can be used from JavaScript. This
+ * function will always succeed.
+ *
+ * Returns: the real user ID of the calling process
+ */
+gint
+shell_util_get_uid (void)
+{
+ return getuid ();
+}
+
+typedef enum {
+ SYSTEMD_CALL_FLAGS_NONE = 0,
+ SYSTEMD_CALL_FLAGS_WATCH_JOB = 1 << 0,
+} SystemdFlags;
+
+#ifdef HAVE_SYSTEMD
+typedef struct {
+ GDBusConnection *connection;
+ gchar *command;
+ SystemdFlags flags;
+
+ GCancellable *cancellable;
+ gulong cancel_id;
+
+ guint job_watch;
+ gchar *job;
+} SystemdCall;
+
+static void
+shell_util_systemd_call_data_free (SystemdCall *data)
+{
+ if (data->job_watch)
+ {
+ g_dbus_connection_signal_unsubscribe (data->connection, data->job_watch);
+ data->job_watch = 0;
+ }
+
+ if (data->cancellable)
+ {
+ g_cancellable_disconnect (data->cancellable, data->cancel_id);
+ g_clear_object (&data->cancellable);
+ data->cancel_id = 0;
+ }
+
+ g_clear_object (&data->connection);
+ g_clear_pointer (&data->job, g_free);
+ g_clear_pointer (&data->command, g_free);
+ g_free (data);
+}
+
+static void
+shell_util_systemd_call_cancelled_cb (GCancellable *cancellable,
+ GTask *task)
+{
+ SystemdCall *data = g_task_get_task_data (task);
+
+ /* Task has returned, but data is not yet free'ed, ignore signal. */
+ if (g_task_get_completed (task))
+ return;
+
+ /* We are still in the DBus call; it will return the error. */
+ if (data->job == NULL)
+ return;
+
+ g_task_return_error_if_cancelled (task);
+ g_object_unref (task);
+}
+
+static void
+on_systemd_call_cb (GObject *source,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ g_autoptr (GVariant) reply = NULL;
+ g_autoptr (GError) error = NULL;
+ GTask *task = G_TASK (user_data);
+ SystemdCall *data;
+
+ reply = g_dbus_connection_call_finish (G_DBUS_CONNECTION (source),
+ res, &error);
+
+ data = g_task_get_task_data (task);
+
+ if (error) {
+ g_warning ("Could not issue '%s' systemd call", data->command);
+ g_task_return_error (task, g_steal_pointer (&error));
+ g_object_unref (task);
+
+ return;
+ }
+
+ g_assert (data->job == NULL);
+ g_variant_get (reply, "(o)", &data->job);
+
+ /* we should either wait for the JobRemoved notification, or
+ * notify here */
+ if ((data->flags & SYSTEMD_CALL_FLAGS_WATCH_JOB) == 0)
+ g_task_return_boolean (task, TRUE);
+}
+
+static void
+on_systemd_job_removed_cb (GDBusConnection *connection,
+ const gchar *sender_name,
+ const gchar *object_path,
+ const gchar *interface_name,
+ const gchar *signal_name,
+ GVariant *parameters,
+ gpointer user_data)
+{
+ GTask *task = G_TASK (user_data);
+ SystemdCall *data;
+ guint32 id;
+ const char *path, *unit, *result;
+
+ /* Task has returned, but data is not yet free'ed, ignore signal. */
+ if (g_task_get_completed (task))
+ return;
+
+ data = g_task_get_task_data (task);
+
+ /* No job information yet, ignore. */
+ if (data->job == NULL)
+ return;
+
+ g_variant_get (parameters, "(u&o&s&s)", &id, &path, &unit, &result);
+
+ /* Is it the job we are waiting for? */
+ if (g_strcmp0 (path, data->job) != 0)
+ return;
+
+ /* Task has completed; return the result of the job */
+ if (g_strcmp0 (result, "done") == 0)
+ g_task_return_boolean (task, TRUE);
+ else
+ g_task_return_new_error (task,
+ G_IO_ERROR,
+ G_IO_ERROR_FAILED,
+ "Systemd job completed with status \"%s\"",
+ result);
+
+ g_object_unref (task);
+}
+#endif /* HAVE_SYSTEMD */
+
+static void
+shell_util_systemd_call (const char *command,
+ GVariant *params,
+ SystemdFlags flags,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ g_autoptr (GTask) task = g_task_new (NULL, cancellable, callback, user_data);
+
+#ifdef HAVE_SYSTEMD
+ g_autoptr (GDBusConnection) connection = NULL;
+ GError *error = NULL;
+ SystemdCall *data;
+ g_autofree char *self_unit = NULL;
+ int res;
+
+ connection = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error);
+
+ if (connection == NULL) {
+ g_task_return_error (task, error);
+ return;
+ }
+
+ /* We look up the systemd unit that our own process is running in here.
+ * This way we determine whether the session is managed using systemd.
+ */
+ res = sd_pid_get_user_unit (getpid (), &self_unit);
+
+ if (res == -ENODATA)
+ {
+ g_task_return_new_error (task,
+ G_IO_ERROR,
+ G_IO_ERROR_NOT_SUPPORTED,
+ "Not systemd managed");
+ return;
+ }
+ else if (res < 0)
+ {
+ g_task_return_new_error (task,
+ G_IO_ERROR,
+ g_io_error_from_errno (-res),
+ "Error fetching own systemd unit: %s",
+ g_strerror (-res));
+ return;
+ }
+
+ data = g_new0 (SystemdCall, 1);
+ data->command = g_strdup (command);
+ data->connection = g_object_ref (connection);
+ data->flags = flags;
+
+ if ((data->flags & SYSTEMD_CALL_FLAGS_WATCH_JOB) != 0)
+ {
+ data->job_watch = g_dbus_connection_signal_subscribe (connection,
+ "org.freedesktop.systemd1",
+ "org.freedesktop.systemd1.Manager",
+ "JobRemoved",
+ "/org/freedesktop/systemd1",
+ NULL,
+ G_DBUS_SIGNAL_FLAGS_NONE,
+ on_systemd_job_removed_cb,
+ task,
+ NULL);
+ }
+
+ g_task_set_task_data (task,
+ data,
+ (GDestroyNotify) shell_util_systemd_call_data_free);
+
+ if (cancellable)
+ {
+ data->cancellable = g_object_ref (cancellable);
+ data->cancel_id = g_cancellable_connect (cancellable,
+ G_CALLBACK (shell_util_systemd_call_cancelled_cb),
+ task,
+ NULL);
+ }
+
+ g_dbus_connection_call (connection,
+ "org.freedesktop.systemd1",
+ "/org/freedesktop/systemd1",
+ "org.freedesktop.systemd1.Manager",
+ command,
+ params,
+ G_VARIANT_TYPE ("(o)"),
+ G_DBUS_CALL_FLAGS_NONE,
+ -1, cancellable,
+ on_systemd_call_cb,
+ g_steal_pointer (&task));
+#else /* HAVE_SYSTEMD */
+ g_task_return_new_error (task,
+ G_IO_ERROR,
+ G_IO_ERROR_NOT_SUPPORTED,
+ "systemd not supported by gnome-shell");
+#endif /* !HAVE_SYSTEMD */
+}
+
+void
+shell_util_start_systemd_unit (const char *unit,
+ const char *mode,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ shell_util_systemd_call ("StartUnit",
+ g_variant_new ("(ss)", unit, mode),
+ SYSTEMD_CALL_FLAGS_WATCH_JOB,
+ cancellable, callback, user_data);
+}
+
+gboolean
+shell_util_start_systemd_unit_finish (GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+void
+shell_util_stop_systemd_unit (const char *unit,
+ const char *mode,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ shell_util_systemd_call ("StopUnit",
+ g_variant_new ("(ss)", unit, mode),
+ SYSTEMD_CALL_FLAGS_WATCH_JOB,
+ cancellable, callback, user_data);
+}
+
+gboolean
+shell_util_stop_systemd_unit_finish (GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+void
+shell_util_systemd_unit_exists (const gchar *unit,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ shell_util_systemd_call ("GetUnit",
+ g_variant_new ("(s)", unit),
+ SYSTEMD_CALL_FLAGS_NONE,
+ cancellable, callback, user_data);
+}
+
+gboolean
+shell_util_systemd_unit_exists_finish (GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+void
+shell_util_sd_notify (void)
+{
+ /* We only use NOTIFY_SOCKET exactly once; unset it so it doesn't remain in
+ * our environment. */
+ sd_notify (1, "READY=1");
+}
+
+/**
+ * shell_util_has_x11_display_extension:
+ * @display: A #MetaDisplay
+ * @extension: An X11 extension
+ *
+ * If the corresponding X11 display provides the passed extension, return %TRUE,
+ * otherwise %FALSE. If there is no X11 display, %FALSE is passed.
+ */
+gboolean
+shell_util_has_x11_display_extension (MetaDisplay *display,
+ const char *extension)
+{
+ MetaX11Display *x11_display;
+ Display *xdisplay;
+ int op, event, error;
+
+ x11_display = meta_display_get_x11_display (display);
+ if (!x11_display)
+ return FALSE;
+
+ xdisplay = meta_x11_display_get_xdisplay (x11_display);
+ return XQueryExtension (xdisplay, extension, &op, &event, &error);
+}
+
+/**
+ * shell_util_get_translated_folder_name:
+ * @name: the untranslated folder name
+ *
+ * Attempts to translate the folder @name using translations provided
+ * by .directory files.
+ *
+ * Returns: (nullable): a translated string or %NULL
+ */
+char *
+shell_util_get_translated_folder_name (const char *name)
+{
+ return shell_app_cache_translate_folder (shell_app_cache_get_default (), name);
+}
diff --git a/src/shell-util.h b/src/shell-util.h
new file mode 100644
index 0000000..7e1f7d8
--- /dev/null
+++ b/src/shell-util.h
@@ -0,0 +1,93 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+#ifndef __SHELL_UTIL_H__
+#define __SHELL_UTIL_H__
+
+#include <gio/gio.h>
+#include <clutter/clutter.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <meta/meta-cursor-tracker.h>
+#include <meta/meta-window-actor.h>
+
+G_BEGIN_DECLS
+
+void shell_util_set_hidden_from_pick (ClutterActor *actor,
+ gboolean hidden);
+
+int shell_util_get_week_start (void);
+
+const char *shell_util_translate_time_string (const char *str);
+
+char *shell_util_regex_escape (const char *str);
+
+gboolean shell_write_string_to_stream (GOutputStream *stream,
+ const char *str,
+ GError **error);
+
+char *shell_get_file_contents_utf8_sync (const char *path,
+ GError **error);
+
+void shell_util_touch_file_async (GFile *file,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean shell_util_touch_file_finish (GFile *file,
+ GAsyncResult *res,
+ GError **error);
+
+gboolean shell_util_wifexited (int status,
+ int *exit);
+
+GdkPixbuf *shell_util_create_pixbuf_from_data (const guchar *data,
+ gsize len,
+ GdkColorspace colorspace,
+ gboolean has_alpha,
+ int bits_per_sample,
+ int width,
+ int height,
+ int rowstride);
+
+cairo_surface_t * shell_util_composite_capture_images (ClutterCapture *captures,
+ int n_captures,
+ int x,
+ int y,
+ int target_width,
+ int target_height,
+ float target_scale);
+
+void shell_util_check_cloexec_fds (void);
+
+void shell_util_start_systemd_unit (const char *unit,
+ const char *mode,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean shell_util_start_systemd_unit_finish (GAsyncResult *res,
+ GError **error);
+
+void shell_util_stop_systemd_unit (const char *unit,
+ const char *mode,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean shell_util_stop_systemd_unit_finish (GAsyncResult *res,
+ GError **error);
+
+void shell_util_systemd_unit_exists (const gchar *unit,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean shell_util_systemd_unit_exists_finish (GAsyncResult *res,
+ GError **error);
+
+void shell_util_sd_notify (void);
+
+gboolean shell_util_has_x11_display_extension (MetaDisplay *display,
+ const char *extension);
+
+char *shell_util_get_translated_folder_name (const char *name);
+
+gint shell_util_get_uid (void);
+
+G_END_DECLS
+
+#endif /* __SHELL_UTIL_H__ */
diff --git a/src/shell-window-preview-layout.c b/src/shell-window-preview-layout.c
new file mode 100644
index 0000000..fa3cc1f
--- /dev/null
+++ b/src/shell-window-preview-layout.c
@@ -0,0 +1,495 @@
+#include "config.h"
+
+#include <clutter/clutter.h>
+#include <meta/window.h>
+#include "shell-window-preview-layout.h"
+
+typedef struct _ShellWindowPreviewLayoutPrivate ShellWindowPreviewLayoutPrivate;
+struct _ShellWindowPreviewLayoutPrivate
+{
+ ClutterActor *container;
+ GHashTable *windows;
+
+ ClutterActorBox bounding_box;
+};
+
+enum
+{
+ PROP_0,
+
+ PROP_BOUNDING_BOX,
+
+ PROP_LAST
+};
+
+static GParamSpec *obj_props[PROP_LAST] = { NULL, };
+
+G_DEFINE_TYPE_WITH_PRIVATE (ShellWindowPreviewLayout, shell_window_preview_layout,
+ CLUTTER_TYPE_LAYOUT_MANAGER);
+
+typedef struct _WindowInfo
+{
+ MetaWindow *window;
+ ClutterActor *window_actor;
+
+ gulong size_changed_id;
+ gulong position_changed_id;
+ gulong window_actor_destroy_id;
+ gulong destroy_id;
+} WindowInfo;
+
+static void
+shell_window_preview_layout_get_property (GObject *object,
+ unsigned int property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ ShellWindowPreviewLayout *self = SHELL_WINDOW_PREVIEW_LAYOUT (object);
+ ShellWindowPreviewLayoutPrivate *priv;
+
+ priv = shell_window_preview_layout_get_instance_private (self);
+
+ switch (property_id)
+ {
+ case PROP_BOUNDING_BOX:
+ g_value_set_boxed (value, &priv->bounding_box);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ }
+}
+
+static void
+shell_window_preview_layout_set_container (ClutterLayoutManager *layout,
+ ClutterContainer *container)
+{
+ ShellWindowPreviewLayout *self = SHELL_WINDOW_PREVIEW_LAYOUT (layout);
+ ShellWindowPreviewLayoutPrivate *priv;
+ ClutterLayoutManagerClass *parent_class;
+
+ priv = shell_window_preview_layout_get_instance_private (self);
+
+ priv->container = CLUTTER_ACTOR (container);
+
+ parent_class = CLUTTER_LAYOUT_MANAGER_CLASS (shell_window_preview_layout_parent_class);
+ parent_class->set_container (layout, container);
+}
+
+static void
+shell_window_preview_layout_get_preferred_width (ClutterLayoutManager *layout,
+ ClutterContainer *container,
+ float for_height,
+ float *min_width_p,
+ float *natural_width_p)
+{
+ ShellWindowPreviewLayout *self = SHELL_WINDOW_PREVIEW_LAYOUT (layout);
+ ShellWindowPreviewLayoutPrivate *priv;
+
+ priv = shell_window_preview_layout_get_instance_private (self);
+
+ if (min_width_p)
+ *min_width_p = 0;
+
+ if (natural_width_p)
+ *natural_width_p = clutter_actor_box_get_width (&priv->bounding_box);
+}
+
+static void
+shell_window_preview_layout_get_preferred_height (ClutterLayoutManager *layout,
+ ClutterContainer *container,
+ float for_width,
+ float *min_height_p,
+ float *natural_height_p)
+{
+ ShellWindowPreviewLayout *self = SHELL_WINDOW_PREVIEW_LAYOUT (layout);
+ ShellWindowPreviewLayoutPrivate *priv;
+
+ priv = shell_window_preview_layout_get_instance_private (self);
+
+ if (min_height_p)
+ *min_height_p = 0;
+
+ if (natural_height_p)
+ *natural_height_p = clutter_actor_box_get_height (&priv->bounding_box);
+}
+
+
+static void
+shell_window_preview_layout_allocate (ClutterLayoutManager *layout,
+ ClutterContainer *container,
+ const ClutterActorBox *box)
+{
+ ShellWindowPreviewLayout *self = SHELL_WINDOW_PREVIEW_LAYOUT (layout);
+ ShellWindowPreviewLayoutPrivate *priv;
+ float scale_x, scale_y;
+ float bounding_box_width, bounding_box_height;
+ ClutterActorIter iter;
+ ClutterActor *child;
+
+ priv = shell_window_preview_layout_get_instance_private (self);
+
+ bounding_box_width = clutter_actor_box_get_width (&priv->bounding_box);
+ bounding_box_height = clutter_actor_box_get_height (&priv->bounding_box);
+
+ if (bounding_box_width == 0)
+ scale_x = 1.f;
+ else
+ scale_x = clutter_actor_box_get_width (box) / bounding_box_width;
+
+ if (bounding_box_height == 0)
+ scale_y = 1.f;
+ else
+ scale_y = clutter_actor_box_get_height (box) / bounding_box_height;
+
+ clutter_actor_iter_init (&iter, CLUTTER_ACTOR (container));
+ while (clutter_actor_iter_next (&iter, &child))
+ {
+ ClutterActorBox child_box = { 0, };
+ WindowInfo *window_info;
+
+ if (!clutter_actor_is_visible (child))
+ continue;
+
+ window_info = g_hash_table_lookup (priv->windows, child);
+
+ if (window_info)
+ {
+ MetaRectangle buffer_rect;
+ float child_nat_width, child_nat_height;
+
+ meta_window_get_buffer_rect (window_info->window, &buffer_rect);
+
+ clutter_actor_box_set_origin (&child_box,
+ buffer_rect.x - priv->bounding_box.x1,
+ buffer_rect.y - priv->bounding_box.y1);
+
+ clutter_actor_get_preferred_size (child, NULL, NULL,
+ &child_nat_width, &child_nat_height);
+
+ clutter_actor_box_set_size (&child_box, child_nat_width, child_nat_height);
+
+ child_box.x1 *= scale_x;
+ child_box.x2 *= scale_x;
+ child_box.y1 *= scale_y;
+ child_box.y2 *= scale_y;
+
+ clutter_actor_allocate (child, &child_box);
+ }
+ else
+ {
+ float x, y;
+
+ clutter_actor_get_fixed_position (child, &x, &y);
+ clutter_actor_allocate_preferred_size (child, x, y);
+ }
+ }
+}
+
+static void
+on_layout_changed (ShellWindowPreviewLayout *self)
+{
+ ShellWindowPreviewLayoutPrivate *priv;
+ GHashTableIter iter;
+ gpointer value;
+ gboolean first_rect = TRUE;
+ MetaRectangle bounding_rect = { 0, };
+ ClutterActorBox old_bounding_box;
+
+ priv = shell_window_preview_layout_get_instance_private (self);
+
+ old_bounding_box =
+ (ClutterActorBox) CLUTTER_ACTOR_BOX_INIT (priv->bounding_box.x1,
+ priv->bounding_box.y1,
+ priv->bounding_box.x2,
+ priv->bounding_box.y2);
+
+ g_hash_table_iter_init (&iter, priv->windows);
+ while (g_hash_table_iter_next (&iter, NULL, &value))
+ {
+ WindowInfo *window_info = value;
+ MetaRectangle frame_rect;
+
+ meta_window_get_frame_rect (window_info->window, &frame_rect);
+
+ if (first_rect)
+ {
+ bounding_rect = frame_rect;
+ first_rect = FALSE;
+ continue;
+ }
+
+ meta_rectangle_union (&frame_rect, &bounding_rect, &bounding_rect);
+ }
+
+ clutter_actor_box_set_origin (&priv->bounding_box,
+ (float) bounding_rect.x,
+ (float) bounding_rect.y);
+ clutter_actor_box_set_size (&priv->bounding_box,
+ (float) bounding_rect.width,
+ (float) bounding_rect.height);
+
+ if (!clutter_actor_box_equal (&priv->bounding_box, &old_bounding_box))
+ g_object_notify_by_pspec (G_OBJECT (self), obj_props[PROP_BOUNDING_BOX]);
+
+ clutter_layout_manager_layout_changed (CLUTTER_LAYOUT_MANAGER (self));
+}
+
+static void
+on_window_size_position_changed (MetaWindow *window,
+ ShellWindowPreviewLayout *self)
+{
+ on_layout_changed (self);
+}
+
+static void
+on_window_destroyed (ClutterActor *actor)
+{
+ clutter_actor_destroy (actor);
+}
+
+static void
+on_actor_destroyed (ClutterActor *actor,
+ ShellWindowPreviewLayout *self)
+{
+ ShellWindowPreviewLayoutPrivate *priv;
+ WindowInfo *window_info;
+
+ priv = shell_window_preview_layout_get_instance_private (self);
+
+ window_info = g_hash_table_lookup (priv->windows, actor);
+ g_assert (window_info != NULL);
+
+ shell_window_preview_layout_remove_window (self, window_info->window);
+}
+
+static void
+shell_window_preview_layout_dispose (GObject *gobject)
+{
+ ShellWindowPreviewLayout *self = SHELL_WINDOW_PREVIEW_LAYOUT (gobject);
+ ShellWindowPreviewLayoutPrivate *priv;
+ GHashTableIter iter;
+ gpointer key, value;
+
+ priv = shell_window_preview_layout_get_instance_private (self);
+
+ g_hash_table_iter_init (&iter, priv->windows);
+ while (g_hash_table_iter_next (&iter, &key, &value))
+ {
+ ClutterActor *actor = key;
+ WindowInfo *info = value;
+
+ g_clear_signal_handler (&info->size_changed_id, info->window);
+ g_clear_signal_handler (&info->position_changed_id, info->window);
+ g_clear_signal_handler (&info->window_actor_destroy_id, info->window_actor);
+ g_clear_signal_handler (&info->destroy_id, actor);
+
+ clutter_actor_remove_child (priv->container, actor);
+ }
+
+ g_hash_table_remove_all (priv->windows);
+
+ G_OBJECT_CLASS (shell_window_preview_layout_parent_class)->dispose (gobject);
+}
+
+static void
+shell_window_preview_layout_finalize (GObject *gobject)
+{
+ ShellWindowPreviewLayout *self = SHELL_WINDOW_PREVIEW_LAYOUT (gobject);
+ ShellWindowPreviewLayoutPrivate *priv;
+
+ priv = shell_window_preview_layout_get_instance_private (self);
+
+ g_hash_table_destroy (priv->windows);
+
+ G_OBJECT_CLASS (shell_window_preview_layout_parent_class)->finalize (gobject);
+}
+
+static void
+shell_window_preview_layout_init (ShellWindowPreviewLayout *self)
+{
+ ShellWindowPreviewLayoutPrivate *priv;
+
+ priv = shell_window_preview_layout_get_instance_private (self);
+
+ priv->windows = g_hash_table_new_full (NULL, NULL, NULL,
+ (GDestroyNotify) g_free);
+}
+
+static void
+shell_window_preview_layout_class_init (ShellWindowPreviewLayoutClass *klass)
+{
+ ClutterLayoutManagerClass *layout_class = CLUTTER_LAYOUT_MANAGER_CLASS (klass);
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+ layout_class->get_preferred_width = shell_window_preview_layout_get_preferred_width;
+ layout_class->get_preferred_height = shell_window_preview_layout_get_preferred_height;
+ layout_class->allocate = shell_window_preview_layout_allocate;
+ layout_class->set_container = shell_window_preview_layout_set_container;
+
+ gobject_class->dispose = shell_window_preview_layout_dispose;
+ gobject_class->finalize = shell_window_preview_layout_finalize;
+ gobject_class->get_property = shell_window_preview_layout_get_property;
+
+ /**
+ * ShellWindowPreviewLayout:bounding-box:
+ */
+ obj_props[PROP_BOUNDING_BOX] =
+ g_param_spec_boxed ("bounding-box",
+ "Bounding Box",
+ "Bounding Box",
+ CLUTTER_TYPE_ACTOR_BOX,
+ G_PARAM_READABLE |
+ G_PARAM_STATIC_STRINGS);
+
+ g_object_class_install_properties (gobject_class, PROP_LAST, obj_props);
+}
+
+/**
+ * shell_window_preview_layout_add_window:
+ * @self: a #ShellWindowPreviewLayout
+ * @window: the #MetaWindow
+ *
+ * Creates a ClutterActor drawing the texture of @window and adds it
+ * to the container. If @window is already part of the preview, this
+ * function will do nothing.
+ *
+ * Returns: (nullable) (transfer none): The newly created actor drawing @window
+ */
+ClutterActor *
+shell_window_preview_layout_add_window (ShellWindowPreviewLayout *self,
+ MetaWindow *window)
+{
+ ShellWindowPreviewLayoutPrivate *priv;
+ ClutterActor *window_actor, *actor;
+ WindowInfo *window_info;
+ GHashTableIter iter;
+ gpointer value;
+
+ g_return_val_if_fail (SHELL_IS_WINDOW_PREVIEW_LAYOUT (self), NULL);
+ g_return_val_if_fail (META_IS_WINDOW (window), NULL);
+
+ priv = shell_window_preview_layout_get_instance_private (self);
+
+ g_hash_table_iter_init (&iter, priv->windows);
+ while (g_hash_table_iter_next (&iter, NULL, &value))
+ {
+ WindowInfo *info = value;
+
+ if (info->window == window)
+ return NULL;
+ }
+
+ window_actor = CLUTTER_ACTOR (meta_window_get_compositor_private (window));
+ actor = clutter_clone_new (window_actor);
+
+ window_info = g_new0 (WindowInfo, 1);
+
+ window_info->window = window;
+ window_info->window_actor = window_actor;
+ window_info->size_changed_id =
+ g_signal_connect (window, "size-changed",
+ G_CALLBACK (on_window_size_position_changed), self);
+ window_info->position_changed_id =
+ g_signal_connect (window, "position-changed",
+ G_CALLBACK (on_window_size_position_changed), self);
+ window_info->window_actor_destroy_id =
+ g_signal_connect_swapped (window_actor, "destroy",
+ G_CALLBACK (on_window_destroyed), actor);
+ window_info->destroy_id =
+ g_signal_connect (actor, "destroy",
+ G_CALLBACK (on_actor_destroyed), self);
+
+ g_hash_table_insert (priv->windows, actor, window_info);
+
+ clutter_actor_add_child (priv->container, actor);
+
+ on_layout_changed (self);
+
+ return actor;
+}
+
+/**
+ * shell_window_preview_layout_remove_window:
+ * @self: a #ShellWindowPreviewLayout
+ * @window: the #MetaWindow
+ *
+ * Removes a MetaWindow @window from the preview which has been added
+ * previously using shell_window_preview_layout_add_window().
+ * If @window is not part of preview, this function will do nothing.
+ */
+void
+shell_window_preview_layout_remove_window (ShellWindowPreviewLayout *self,
+ MetaWindow *window)
+{
+ ShellWindowPreviewLayoutPrivate *priv;
+ ClutterActor *actor;
+ WindowInfo *window_info = NULL;
+ GHashTableIter iter;
+ gpointer key, value;
+
+ g_return_if_fail (SHELL_IS_WINDOW_PREVIEW_LAYOUT (self));
+ g_return_if_fail (META_IS_WINDOW (window));
+
+ priv = shell_window_preview_layout_get_instance_private (self);
+
+ g_hash_table_iter_init (&iter, priv->windows);
+ while (g_hash_table_iter_next (&iter, &key, &value))
+ {
+ WindowInfo *info = value;
+
+ if (info->window == window)
+ {
+ actor = CLUTTER_ACTOR (key);
+ window_info = info;
+ break;
+ }
+ }
+
+ if (window_info == NULL)
+ return;
+
+ g_clear_signal_handler (&window_info->size_changed_id, window);
+ g_clear_signal_handler (&window_info->position_changed_id, window);
+ g_clear_signal_handler (&window_info->window_actor_destroy_id, window_info->window_actor);
+ g_clear_signal_handler (&window_info->destroy_id, actor);
+
+ g_hash_table_remove (priv->windows, actor);
+
+ clutter_actor_remove_child (priv->container, actor);
+
+ on_layout_changed (self);
+}
+
+/**
+ * shell_window_preview_layout_get_windows:
+ * @self: a #ShellWindowPreviewLayout
+ *
+ * Gets an array of all MetaWindows that were added to the layout
+ * using shell_window_preview_layout_add_window(), ordered by the
+ * insertion order.
+ *
+ * Returns: (transfer container) (element-type Meta.Window): The list of windows
+ */
+GList *
+shell_window_preview_layout_get_windows (ShellWindowPreviewLayout *self)
+{
+ ShellWindowPreviewLayoutPrivate *priv;
+ GList *windows = NULL;
+ GHashTableIter iter;
+ gpointer value;
+
+ g_return_val_if_fail (SHELL_IS_WINDOW_PREVIEW_LAYOUT (self), NULL);
+
+ priv = shell_window_preview_layout_get_instance_private (self);
+
+ g_hash_table_iter_init (&iter, priv->windows);
+ while (g_hash_table_iter_next (&iter, NULL, &value))
+ {
+ WindowInfo *window_info = value;
+
+ windows = g_list_prepend (windows, window_info->window);
+ }
+
+ return windows;
+}
diff --git a/src/shell-window-preview-layout.h b/src/shell-window-preview-layout.h
new file mode 100644
index 0000000..9376b0d
--- /dev/null
+++ b/src/shell-window-preview-layout.h
@@ -0,0 +1,33 @@
+#ifndef __SHELL_WINDOW_PREVIEW_LAYOUT_H__
+#define __SHELL_WINDOW_PREVIEW_LAYOUT_H__
+
+G_BEGIN_DECLS
+
+#include <clutter/clutter.h>
+
+#define SHELL_TYPE_WINDOW_PREVIEW_LAYOUT (shell_window_preview_layout_get_type ())
+G_DECLARE_FINAL_TYPE (ShellWindowPreviewLayout, shell_window_preview_layout,
+ SHELL, WINDOW_PREVIEW_LAYOUT, ClutterLayoutManager)
+
+typedef struct _ShellWindowPreviewLayout ShellWindowPreviewLayout;
+typedef struct _ShellWindowPreviewLayoutPrivate ShellWindowPreviewLayoutPrivate;
+
+struct _ShellWindowPreviewLayout
+{
+ /*< private >*/
+ ClutterLayoutManager parent;
+
+ ShellWindowPreviewLayoutPrivate *priv;
+};
+
+ClutterActor * shell_window_preview_layout_add_window (ShellWindowPreviewLayout *self,
+ MetaWindow *window);
+
+void shell_window_preview_layout_remove_window (ShellWindowPreviewLayout *self,
+ MetaWindow *window);
+
+GList * shell_window_preview_layout_get_windows (ShellWindowPreviewLayout *self);
+
+G_END_DECLS
+
+#endif /* __SHELL_WINDOW_PREVIEW_LAYOUT_H__ */
diff --git a/src/shell-window-preview.c b/src/shell-window-preview.c
new file mode 100644
index 0000000..47a11c5
--- /dev/null
+++ b/src/shell-window-preview.c
@@ -0,0 +1,177 @@
+#include "config.h"
+
+#include "shell-window-preview.h"
+
+enum
+{
+ PROP_0,
+
+ PROP_WINDOW_CONTAINER,
+
+ PROP_LAST
+};
+
+static GParamSpec *obj_props[PROP_LAST] = { NULL, };
+
+struct _ShellWindowPreview
+{
+ /*< private >*/
+ StWidget parent_instance;
+
+ ClutterActor *window_container;
+};
+
+G_DEFINE_TYPE (ShellWindowPreview, shell_window_preview, ST_TYPE_WIDGET);
+
+static void
+shell_window_preview_get_property (GObject *gobject,
+ unsigned int property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ ShellWindowPreview *self = SHELL_WINDOW_PREVIEW (gobject);
+
+ switch (property_id)
+ {
+ case PROP_WINDOW_CONTAINER:
+ g_value_set_object (value, self->window_container);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, property_id, pspec);
+ }
+}
+
+static void
+shell_window_preview_set_property (GObject *gobject,
+ unsigned int property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ ShellWindowPreview *self = SHELL_WINDOW_PREVIEW (gobject);
+
+ switch (property_id)
+ {
+ case PROP_WINDOW_CONTAINER:
+ if (g_set_object (&self->window_container, g_value_get_object (value)))
+ g_object_notify_by_pspec (gobject, obj_props[PROP_WINDOW_CONTAINER]);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, property_id, pspec);
+ }
+}
+
+static void
+shell_window_preview_get_preferred_width (ClutterActor *actor,
+ float for_height,
+ float *min_width_p,
+ float *natural_width_p)
+{
+ ShellWindowPreview *self = SHELL_WINDOW_PREVIEW (actor);
+ StThemeNode *theme_node = st_widget_get_theme_node (ST_WIDGET (self));
+ float min_width, nat_width;
+
+ st_theme_node_adjust_for_height (theme_node, &for_height);
+
+ clutter_actor_get_preferred_width (self->window_container, for_height,
+ &min_width, &nat_width);
+
+ st_theme_node_adjust_preferred_width (theme_node, &min_width, &nat_width);
+
+ if (min_width_p)
+ *min_width_p = min_width;
+
+ if (natural_width_p)
+ *natural_width_p = nat_width;
+}
+
+static void
+shell_window_preview_get_preferred_height (ClutterActor *actor,
+ float for_width,
+ float *min_height_p,
+ float *natural_height_p)
+{
+ ShellWindowPreview *self = SHELL_WINDOW_PREVIEW (actor);
+ StThemeNode *theme_node = st_widget_get_theme_node (ST_WIDGET (self));
+ float min_height, nat_height;
+
+ st_theme_node_adjust_for_width (theme_node, &for_width);
+
+ clutter_actor_get_preferred_height (self->window_container, for_width,
+ &min_height, &nat_height);
+
+ st_theme_node_adjust_preferred_height (theme_node, &min_height, &nat_height);
+
+ if (min_height_p)
+ *min_height_p = min_height;
+
+ if (natural_height_p)
+ *natural_height_p = nat_height;
+}
+
+static void
+shell_window_preview_allocate (ClutterActor *actor,
+ const ClutterActorBox *box)
+{
+ StThemeNode *theme_node = st_widget_get_theme_node (ST_WIDGET (actor));
+ ClutterActorBox content_box;
+ float x, y, max_width, max_height;
+ ClutterActorIter iter;
+ ClutterActor *child;
+
+ clutter_actor_set_allocation (actor, box);
+
+ st_theme_node_get_content_box (theme_node, box, &content_box);
+
+ clutter_actor_box_get_origin (&content_box, &x, &y);
+ clutter_actor_box_get_size (&content_box, &max_width, &max_height);
+
+ clutter_actor_iter_init (&iter, actor);
+ while (clutter_actor_iter_next (&iter, &child))
+ clutter_actor_allocate_available_size (child, x, y, max_width, max_height);
+}
+
+static void
+shell_window_preview_dispose (GObject *gobject)
+{
+ ShellWindowPreview *self = SHELL_WINDOW_PREVIEW (gobject);
+
+ g_clear_object (&self->window_container);
+
+ G_OBJECT_CLASS (shell_window_preview_parent_class)->dispose (gobject);
+}
+
+static void
+shell_window_preview_init (ShellWindowPreview *self)
+{
+}
+
+static void
+shell_window_preview_class_init (ShellWindowPreviewClass *klass)
+{
+ ClutterActorClass *actor_class = CLUTTER_ACTOR_CLASS (klass);
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+ actor_class->get_preferred_width = shell_window_preview_get_preferred_width;
+ actor_class->get_preferred_height = shell_window_preview_get_preferred_height;
+ actor_class->allocate = shell_window_preview_allocate;
+
+ gobject_class->dispose = shell_window_preview_dispose;
+ gobject_class->get_property = shell_window_preview_get_property;
+ gobject_class->set_property = shell_window_preview_set_property;
+
+ /**
+ * ShellWindowPreview:window-container:
+ */
+ obj_props[PROP_WINDOW_CONTAINER] =
+ g_param_spec_object ("window-container",
+ "window-container",
+ "window-container",
+ CLUTTER_TYPE_ACTOR,
+ G_PARAM_READWRITE |
+ G_PARAM_EXPLICIT_NOTIFY |
+ G_PARAM_STATIC_STRINGS);
+
+ g_object_class_install_properties (gobject_class, PROP_LAST, obj_props);
+}
diff --git a/src/shell-window-preview.h b/src/shell-window-preview.h
new file mode 100644
index 0000000..e503800
--- /dev/null
+++ b/src/shell-window-preview.h
@@ -0,0 +1,14 @@
+#ifndef __SHELL_WINDOW_PREVIEW_H__
+#define __SHELL_WINDOW_PREVIEW_H__
+
+#include <st/st.h>
+
+G_BEGIN_DECLS
+
+#define SHELL_TYPE_WINDOW_PREVIEW (shell_window_preview_get_type ())
+G_DECLARE_FINAL_TYPE (ShellWindowPreview, shell_window_preview,
+ SHELL, WINDOW_PREVIEW, StWidget)
+
+G_END_DECLS
+
+#endif /* __SHELL_WINDOW_PREVIEW_H__ */
diff --git a/src/shell-window-tracker-private.h b/src/shell-window-tracker-private.h
new file mode 100644
index 0000000..4307d15
--- /dev/null
+++ b/src/shell-window-tracker-private.h
@@ -0,0 +1,11 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+#ifndef __SHELL_WINDOW_TRACKER_PRIVATE_H__
+#define __SHELL_WINDOW_TRACKER_PRIVATE_H__
+
+#include "shell-window-tracker.h"
+
+void _shell_window_tracker_add_child_process_app (ShellWindowTracker *tracker,
+ GPid pid,
+ ShellApp *app);
+
+#endif
diff --git a/src/shell-window-tracker.c b/src/shell-window-tracker.c
new file mode 100644
index 0000000..dda9e2b
--- /dev/null
+++ b/src/shell-window-tracker.c
@@ -0,0 +1,811 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+#include "config.h"
+
+#include <string.h>
+#include <stdlib.h>
+
+#include <meta/display.h>
+#include <meta/group.h>
+#include <meta/util.h>
+#include <meta/window.h>
+#include <meta/meta-workspace-manager.h>
+#include <meta/meta-startup-notification.h>
+
+#include "shell-window-tracker-private.h"
+#include "shell-app-private.h"
+#include "shell-global.h"
+#include "st.h"
+
+/* This file includes modified code from
+ * desktop-data-engine/engine-dbus/hippo-application-monitor.c
+ * in the functions collecting application usage data.
+ * Written by Owen Taylor, originally licensed under LGPL 2.1.
+ * Copyright Red Hat, Inc. 2006-2008
+ */
+
+/**
+ * SECTION:shell-window-tracker
+ * @short_description: Associate windows with applications
+ *
+ * Maintains a mapping from windows to applications (.desktop file ids).
+ * It currently implements this with some heuristics on the WM_CLASS X11
+ * property (and some static override regexps); in the future, we want to
+ * have it also track through startup-notification.
+ */
+
+struct _ShellWindowTracker
+{
+ GObject parent;
+
+ ShellApp *focus_app;
+
+ /* <MetaWindow * window, ShellApp *app> */
+ GHashTable *window_to_app;
+};
+
+G_DEFINE_TYPE (ShellWindowTracker, shell_window_tracker, G_TYPE_OBJECT);
+
+enum {
+ PROP_0,
+
+ PROP_FOCUS_APP,
+
+ N_PROPS
+};
+
+static GParamSpec *props[N_PROPS] = { NULL, };
+
+enum {
+ STARTUP_SEQUENCE_CHANGED,
+ TRACKED_WINDOWS_CHANGED,
+
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = { 0 };
+
+static void shell_window_tracker_finalize (GObject *object);
+static void set_focus_app (ShellWindowTracker *tracker,
+ ShellApp *new_focus_app);
+static void on_focus_window_changed (MetaDisplay *display, GParamSpec *spec, ShellWindowTracker *tracker);
+
+static void track_window (ShellWindowTracker *tracker, MetaWindow *window);
+static void disassociate_window (ShellWindowTracker *tracker, MetaWindow *window);
+
+static ShellApp * shell_startup_sequence_get_app (MetaStartupSequence *sequence);
+
+static void
+shell_window_tracker_get_property (GObject *gobject,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ ShellWindowTracker *tracker = SHELL_WINDOW_TRACKER (gobject);
+
+ switch (prop_id)
+ {
+ case PROP_FOCUS_APP:
+ g_value_set_object (value, tracker->focus_app);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+shell_window_tracker_class_init (ShellWindowTrackerClass *klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+ gobject_class->get_property = shell_window_tracker_get_property;
+ gobject_class->finalize = shell_window_tracker_finalize;
+
+ props[PROP_FOCUS_APP] =
+ g_param_spec_object ("focus-app",
+ "Focus App",
+ "Focused application",
+ SHELL_TYPE_APP,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+ g_object_class_install_properties (gobject_class, N_PROPS, props);
+
+ signals[STARTUP_SEQUENCE_CHANGED] = g_signal_new ("startup-sequence-changed",
+ SHELL_TYPE_WINDOW_TRACKER,
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL, NULL,
+ G_TYPE_NONE, 1, META_TYPE_STARTUP_SEQUENCE);
+ signals[TRACKED_WINDOWS_CHANGED] = g_signal_new ("tracked-windows-changed",
+ SHELL_TYPE_WINDOW_TRACKER,
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL, NULL,
+ G_TYPE_NONE, 0);
+}
+
+static gboolean
+check_app_id_prefix (ShellApp *app,
+ const char *prefix)
+{
+ if (prefix == NULL)
+ return TRUE;
+
+ return g_str_has_prefix (shell_app_get_id (app), prefix);
+}
+
+/*
+ * get_app_from_window_wmclass:
+ *
+ * Looks only at the given window, and attempts to determine
+ * an application based on WM_CLASS. If one can't be determined,
+ * return %NULL.
+ *
+ * Return value: (transfer full): A newly-referenced #ShellApp, or %NULL
+ */
+static ShellApp *
+get_app_from_window_wmclass (MetaWindow *window)
+{
+ ShellApp *app;
+ ShellAppSystem *appsys;
+ const char *wm_class;
+ const char *wm_instance;
+ const char *sandbox_id;
+ g_autofree char *app_prefix = NULL;
+
+ appsys = shell_app_system_get_default ();
+
+ sandbox_id = meta_window_get_sandboxed_app_id (window);
+ if (sandbox_id)
+ app_prefix = g_strdup_printf ("%s.", sandbox_id);
+
+ /* Notes on the heuristics used here:
+ much of the complexity here comes from the desire to support
+ Chrome apps.
+
+ From https://bugzilla.gnome.org/show_bug.cgi?id=673657#c13
+
+ Currently chrome sets WM_CLASS as follows (the first string is the 'instance',
+ the second one is the 'class':
+
+ For the normal browser:
+ WM_CLASS(STRING) = "chromium", "Chromium"
+
+ For a bookmarked page (through 'Tools -> Create application shortcuts')
+ WM_CLASS(STRING) = "wiki.gnome.org__GnomeShell_ApplicationBased", "Chromium"
+
+ For an application from the chrome store (with a .desktop file created through
+ right click, "Create shortcuts" from Chrome's apps overview)
+ WM_CLASS(STRING) = "crx_blpcfgokakmgnkcojhhkbfbldkacnbeo", "Chromium"
+
+ The .desktop file has a matching StartupWMClass, but the name differs, e.g. for
+ the store app (youtube) there is
+
+ .local/share/applications/chrome-blpcfgokakmgnkcojhhkbfbldkacnbeo-Default.desktop
+
+ with
+
+ StartupWMClass=crx_blpcfgokakmgnkcojhhkbfbldkacnbeo
+
+ Note that chromium (but not google-chrome!) includes a StartupWMClass=chromium
+ in their .desktop file, so we must match the instance first.
+
+ Also note that in the good case (regular gtk+ app without hacks), instance and
+ class are the same except for case and there is no StartupWMClass at all.
+ */
+
+ /* first try a match from WM_CLASS (instance part) to StartupWMClass */
+ wm_instance = meta_window_get_wm_class_instance (window);
+ app = shell_app_system_lookup_startup_wmclass (appsys, wm_instance);
+ if (app != NULL && check_app_id_prefix (app, app_prefix))
+ return g_object_ref (app);
+
+ /* then try a match from WM_CLASS to StartupWMClass */
+ wm_class = meta_window_get_wm_class (window);
+ app = shell_app_system_lookup_startup_wmclass (appsys, wm_class);
+ if (app != NULL && check_app_id_prefix (app, app_prefix))
+ return g_object_ref (app);
+
+ /* then try a match from WM_CLASS (instance part) to .desktop */
+ app = shell_app_system_lookup_desktop_wmclass (appsys, wm_instance);
+ if (app != NULL && check_app_id_prefix (app, app_prefix))
+ return g_object_ref (app);
+
+ /* finally, try a match from WM_CLASS to .desktop */
+ app = shell_app_system_lookup_desktop_wmclass (appsys, wm_class);
+ if (app != NULL && check_app_id_prefix (app, app_prefix))
+ return g_object_ref (app);
+
+ return NULL;
+}
+
+/*
+ * get_app_from_id:
+ * @window: a #MetaWindow
+ *
+ * Looks only at the given window, and attempts to determine
+ * an application based on %id. If one can't be determined,
+ * return %NULL.
+ *
+ * Return value: (transfer full): A newly-referenced #ShellApp, or %NULL
+ */
+static ShellApp *
+get_app_from_id (MetaWindow *window,
+ const char *id)
+{
+ ShellApp *app;
+ ShellAppSystem *appsys;
+ g_autofree char *desktop_file = NULL;
+
+ g_return_val_if_fail (id != NULL, NULL);
+
+ appsys = shell_app_system_get_default ();
+
+ desktop_file = g_strconcat (id, ".desktop", NULL);
+ app = shell_app_system_lookup_app (appsys, desktop_file);
+ if (app)
+ return g_object_ref (app);
+
+ return NULL;
+}
+
+/*
+ * get_app_from_gapplication_id:
+ * @window: a #MetaWindow
+ *
+ * Looks only at the given window, and attempts to determine
+ * an application based on _GTK_APPLICATION_ID. If one can't be determined,
+ * return %NULL.
+ *
+ * Return value: (transfer full): A newly-referenced #ShellApp, or %NULL
+ */
+static ShellApp *
+get_app_from_gapplication_id (MetaWindow *window)
+{
+ const char *id;
+
+ id = meta_window_get_gtk_application_id (window);
+ if (!id)
+ return NULL;
+
+ return get_app_from_id (window, id);
+}
+
+/*
+ * get_app_from_sandboxed_app_id:
+ * @window: a #MetaWindow
+ *
+ * Looks only at the given window, and attempts to determine
+ * an application based on its Flatpak or Snap ID. If one can't be determined,
+ * return %NULL.
+ *
+ * Return value: (transfer full): A newly-referenced #ShellApp, or %NULL
+ */
+static ShellApp *
+get_app_from_sandboxed_app_id (MetaWindow *window)
+{
+ const char *id;
+
+ id = meta_window_get_sandboxed_app_id (window);
+ if (!id)
+ return NULL;
+
+ return get_app_from_id (window, id);
+}
+
+/*
+ * get_app_from_window_group:
+ * @monitor: a #ShellWindowTracker
+ * @window: a #MetaWindow
+ *
+ * Check other windows in the group for @window to see if we have
+ * an application for one of them.
+ *
+ * Return value: (transfer full): A newly-referenced #ShellApp, or %NULL
+ */
+static ShellApp*
+get_app_from_window_group (ShellWindowTracker *tracker,
+ MetaWindow *window)
+{
+ ShellApp *result;
+ GSList *group_windows;
+ MetaGroup *group;
+ GSList *iter;
+
+ group = meta_window_get_group (window);
+ if (group == NULL)
+ return NULL;
+
+ group_windows = meta_group_list_windows (group);
+
+ result = NULL;
+ /* Try finding a window in the group of type NORMAL; if we
+ * succeed, use that as our source. */
+ for (iter = group_windows; iter; iter = iter->next)
+ {
+ MetaWindow *group_window = iter->data;
+
+ if (meta_window_get_window_type (group_window) != META_WINDOW_NORMAL)
+ continue;
+
+ result = g_hash_table_lookup (tracker->window_to_app, group_window);
+ if (result)
+ break;
+ }
+
+ g_slist_free (group_windows);
+
+ if (result)
+ g_object_ref (result);
+
+ return result;
+}
+
+/*
+ * get_app_from_window_pid:
+ * @tracker: a #ShellWindowTracker
+ * @window: a #MetaWindow
+ *
+ * Check if the pid associated with @window corresponds to an
+ * application.
+ *
+ * Return value: (transfer full): A newly-referenced #ShellApp, or %NULL
+ */
+static ShellApp *
+get_app_from_window_pid (ShellWindowTracker *tracker,
+ MetaWindow *window)
+{
+ ShellApp *result;
+ pid_t pid;
+
+ if (meta_window_is_remote (window))
+ return NULL;
+
+ pid = meta_window_get_pid (window);
+
+ if (pid < 1)
+ return NULL;
+
+ result = shell_window_tracker_get_app_from_pid (tracker, pid);
+ if (result != NULL)
+ g_object_ref (result);
+
+ return result;
+}
+
+/**
+ * get_app_for_window:
+ *
+ * Determines the application associated with a window, using
+ * all available information such as the window's MetaGroup,
+ * and what we know about other windows.
+ *
+ * Returns: (transfer full): a #ShellApp, or NULL if none is found
+ */
+static ShellApp *
+get_app_for_window (ShellWindowTracker *tracker,
+ MetaWindow *window)
+{
+ ShellApp *result = NULL;
+ MetaWindow *transient_for;
+ const char *startup_id;
+
+ transient_for = meta_window_get_transient_for (window);
+ if (transient_for != NULL)
+ return get_app_for_window (tracker, transient_for);
+
+ /* First, we check whether we already know about this window,
+ * if so, just return that.
+ */
+ if (meta_window_get_window_type (window) == META_WINDOW_NORMAL
+ || meta_window_is_remote (window))
+ {
+ result = g_hash_table_lookup (tracker->window_to_app, window);
+ if (result != NULL)
+ {
+ g_object_ref (result);
+ return result;
+ }
+ }
+
+ if (meta_window_is_remote (window))
+ return _shell_app_new_for_window (window);
+
+ /* Check if the app's WM_CLASS specifies an app; this is
+ * canonical if it does.
+ */
+ result = get_app_from_window_wmclass (window);
+ if (result != NULL)
+ return result;
+
+ /* Check if the window was opened from within a sandbox; if this
+ * is the case, a corresponding .desktop file is guaranteed to match;
+ */
+ result = get_app_from_sandboxed_app_id (window);
+ if (result != NULL)
+ return result;
+
+ /* Check if the window has a GApplication ID attached; this is
+ * canonical if it does
+ */
+ result = get_app_from_gapplication_id (window);
+ if (result != NULL)
+ return result;
+
+ result = get_app_from_window_pid (tracker, window);
+ if (result != NULL)
+ return result;
+
+ /* Now we check whether we have a match through startup-notification */
+ startup_id = meta_window_get_startup_id (window);
+ if (startup_id)
+ {
+ GSList *iter, *sequences;
+
+ sequences = shell_window_tracker_get_startup_sequences (tracker);
+ for (iter = sequences; iter; iter = iter->next)
+ {
+ MetaStartupSequence *sequence = iter->data;
+ const char *id = meta_startup_sequence_get_id (sequence);
+ if (strcmp (id, startup_id) != 0)
+ continue;
+
+ result = shell_startup_sequence_get_app (sequence);
+ if (result)
+ {
+ result = g_object_ref (result);
+ break;
+ }
+ }
+ }
+
+ /* If we didn't get a startup-notification match, see if we matched
+ * any other windows in the group.
+ */
+ if (result == NULL)
+ result = get_app_from_window_group (tracker, window);
+
+ /* Our last resort - we create a fake app from the window */
+ if (result == NULL)
+ result = _shell_app_new_for_window (window);
+
+ return result;
+}
+
+static void
+update_focus_app (ShellWindowTracker *self)
+{
+ MetaWindow *new_focus_win;
+ ShellApp *new_focus_app;
+
+ new_focus_win = meta_display_get_focus_window (shell_global_get_display (shell_global_get ()));
+
+ /* we only consider an app focused if the focus window can be clearly
+ * associated with a running app; this is the case if the focus window
+ * or one of its parents is visible in the taskbar, e.g.
+ * - 'nautilus' should appear focused when its about dialog has focus
+ * - 'nautilus' should not appear focused when the DESKTOP has focus
+ */
+ while (new_focus_win && meta_window_is_skip_taskbar (new_focus_win))
+ new_focus_win = meta_window_get_transient_for (new_focus_win);
+
+ new_focus_app = new_focus_win ? shell_window_tracker_get_window_app (self, new_focus_win) : NULL;
+
+ if (new_focus_app)
+ {
+ shell_app_update_window_actions (new_focus_app, new_focus_win);
+ shell_app_update_app_actions (new_focus_app, new_focus_win);
+ }
+
+ set_focus_app (self, new_focus_app);
+
+ g_clear_object (&new_focus_app);
+}
+
+static void
+tracked_window_changed (ShellWindowTracker *self,
+ MetaWindow *window)
+{
+ /* It's simplest to just treat this as a remove + add. */
+ disassociate_window (self, window);
+ track_window (self, window);
+ /* also just recalculate the focused app, in case it was the focused
+ window that changed */
+ update_focus_app (self);
+}
+
+static void
+on_wm_class_changed (MetaWindow *window,
+ GParamSpec *pspec,
+ gpointer user_data)
+{
+ ShellWindowTracker *self = SHELL_WINDOW_TRACKER (user_data);
+ tracked_window_changed (self, window);
+}
+
+static void
+on_title_changed (MetaWindow *window,
+ GParamSpec *pspec,
+ gpointer user_data)
+{
+ ShellWindowTracker *self = SHELL_WINDOW_TRACKER (user_data);
+ g_signal_emit (self, signals[TRACKED_WINDOWS_CHANGED], 0);
+}
+
+static void
+on_gtk_application_id_changed (MetaWindow *window,
+ GParamSpec *pspec,
+ gpointer user_data)
+{
+ ShellWindowTracker *self = SHELL_WINDOW_TRACKER (user_data);
+ tracked_window_changed (self, window);
+}
+
+static void
+on_window_unmanaged (MetaWindow *window,
+ gpointer user_data)
+{
+ disassociate_window (SHELL_WINDOW_TRACKER (user_data), window);
+}
+
+static void
+track_window (ShellWindowTracker *self,
+ MetaWindow *window)
+{
+ ShellApp *app;
+
+ app = get_app_for_window (self, window);
+ if (!app)
+ return;
+
+ /* At this point we've stored the association from window -> application */
+ g_hash_table_insert (self->window_to_app, window, app);
+
+ g_signal_connect (window, "notify::wm-class", G_CALLBACK (on_wm_class_changed), self);
+ g_signal_connect (window, "notify::title", G_CALLBACK (on_title_changed), self);
+ g_signal_connect (window, "notify::gtk-application-id", G_CALLBACK (on_gtk_application_id_changed), self);
+ g_signal_connect (window, "unmanaged", G_CALLBACK (on_window_unmanaged), self);
+
+ _shell_app_add_window (app, window);
+
+ g_signal_emit (self, signals[TRACKED_WINDOWS_CHANGED], 0);
+}
+
+static void
+on_window_created (MetaDisplay *display,
+ MetaWindow *window,
+ gpointer user_data)
+{
+ track_window (SHELL_WINDOW_TRACKER (user_data), window);
+}
+
+static void
+disassociate_window (ShellWindowTracker *self,
+ MetaWindow *window)
+{
+ ShellApp *app;
+
+ app = g_hash_table_lookup (self->window_to_app, window);
+ if (!app)
+ return;
+
+ g_object_ref (app);
+
+ g_hash_table_remove (self->window_to_app, window);
+
+ _shell_app_remove_window (app, window);
+ g_signal_handlers_disconnect_by_func (window, G_CALLBACK (on_wm_class_changed), self);
+ g_signal_handlers_disconnect_by_func (window, G_CALLBACK (on_title_changed), self);
+ g_signal_handlers_disconnect_by_func (window, G_CALLBACK (on_gtk_application_id_changed), self);
+ g_signal_handlers_disconnect_by_func (window, G_CALLBACK (on_window_unmanaged), self);
+
+ g_signal_emit (self, signals[TRACKED_WINDOWS_CHANGED], 0);
+
+ g_object_unref (app);
+}
+
+static void
+load_initial_windows (ShellWindowTracker *tracker)
+{
+ MetaDisplay *display = shell_global_get_display (shell_global_get ());
+ g_autoptr (GList) windows = NULL;
+ GList *l;
+
+ windows = meta_display_list_all_windows (display);
+ for (l = windows; l; l = l->next)
+ track_window (tracker, l->data);
+}
+
+static void
+init_window_tracking (ShellWindowTracker *self)
+{
+ MetaDisplay *display = shell_global_get_display (shell_global_get ());
+
+ g_signal_connect (display, "notify::focus-window",
+ G_CALLBACK (on_focus_window_changed), self);
+ g_signal_connect(display, "window-created",
+ G_CALLBACK (on_window_created), self);
+}
+
+static void
+on_startup_sequence_changed (MetaStartupNotification *sn,
+ MetaStartupSequence *sequence,
+ ShellWindowTracker *self)
+{
+ ShellApp *app;
+
+ app = shell_startup_sequence_get_app (sequence);
+ if (app)
+ _shell_app_handle_startup_sequence (app, sequence);
+
+ g_signal_emit (G_OBJECT (self), signals[STARTUP_SEQUENCE_CHANGED], 0, sequence);
+}
+
+static void
+shell_window_tracker_init (ShellWindowTracker *self)
+{
+ MetaDisplay *display = shell_global_get_display (shell_global_get ());
+ MetaStartupNotification *sn = meta_display_get_startup_notification (display);
+
+ self->window_to_app = g_hash_table_new_full (g_direct_hash, g_direct_equal,
+ NULL, (GDestroyNotify) g_object_unref);
+
+
+ g_signal_connect (sn, "changed",
+ G_CALLBACK (on_startup_sequence_changed), self);
+
+ load_initial_windows (self);
+ init_window_tracking (self);
+}
+
+static void
+shell_window_tracker_finalize (GObject *object)
+{
+ ShellWindowTracker *self = SHELL_WINDOW_TRACKER (object);
+
+ g_hash_table_destroy (self->window_to_app);
+
+ G_OBJECT_CLASS (shell_window_tracker_parent_class)->finalize(object);
+}
+
+/**
+ * shell_window_tracker_get_window_app:
+ * @tracker: An app monitor instance
+ * @metawin: A #MetaWindow
+ *
+ * Returns: (transfer full): Application associated with window
+ */
+ShellApp *
+shell_window_tracker_get_window_app (ShellWindowTracker *tracker,
+ MetaWindow *metawin)
+{
+ ShellApp *app;
+
+ app = g_hash_table_lookup (tracker->window_to_app, metawin);
+ if (app)
+ g_object_ref (app);
+
+ return app;
+}
+
+
+/**
+ * shell_window_tracker_get_app_from_pid:
+ * @tracker: A #ShellAppSystem
+ * @pid: A Unix process identifier
+ *
+ * Look up the application corresponding to a process.
+ *
+ * Returns: (transfer none): A #ShellApp, or %NULL if none
+ */
+ShellApp *
+shell_window_tracker_get_app_from_pid (ShellWindowTracker *tracker,
+ int pid)
+{
+ GSList *running = shell_app_system_get_running (shell_app_system_get_default());
+ GSList *iter;
+ ShellApp *result = NULL;
+
+ for (iter = running; iter; iter = iter->next)
+ {
+ ShellApp *app = iter->data;
+ GSList *pids = shell_app_get_pids (app);
+ GSList *pids_iter;
+
+ for (pids_iter = pids; pids_iter; pids_iter = pids_iter->next)
+ {
+ int app_pid = GPOINTER_TO_INT (pids_iter->data);
+ if (app_pid == pid)
+ {
+ result = app;
+ break;
+ }
+ }
+ g_slist_free (pids);
+
+ if (result != NULL)
+ break;
+ }
+
+ g_slist_free (running);
+
+ return result;
+}
+
+static void
+set_focus_app (ShellWindowTracker *tracker,
+ ShellApp *new_focus_app)
+{
+ if (new_focus_app == tracker->focus_app)
+ return;
+
+ if (tracker->focus_app != NULL)
+ g_object_unref (tracker->focus_app);
+
+ tracker->focus_app = new_focus_app;
+
+ if (tracker->focus_app != NULL)
+ g_object_ref (tracker->focus_app);
+
+ g_object_notify_by_pspec (G_OBJECT (tracker), props[PROP_FOCUS_APP]);
+}
+
+static void
+on_focus_window_changed (MetaDisplay *display,
+ GParamSpec *spec,
+ ShellWindowTracker *tracker)
+{
+ update_focus_app (tracker);
+}
+
+/**
+ * shell_window_tracker_get_startup_sequences:
+ * @tracker:
+ *
+ * Returns: (transfer none) (element-type MetaStartupSequence): Currently active startup sequences
+ */
+GSList *
+shell_window_tracker_get_startup_sequences (ShellWindowTracker *self)
+{
+ ShellGlobal *global = shell_global_get ();
+ MetaDisplay *display = shell_global_get_display (global);
+ MetaStartupNotification *sn = meta_display_get_startup_notification (display);
+
+ return meta_startup_notification_get_sequences (sn);
+}
+
+static ShellApp *
+shell_startup_sequence_get_app (MetaStartupSequence *sequence)
+{
+ const char *appid;
+ char *basename;
+ ShellAppSystem *appsys;
+ ShellApp *app;
+
+ appid = meta_startup_sequence_get_application_id (sequence);
+ if (!appid)
+ return NULL;
+
+ basename = g_path_get_basename (appid);
+ appsys = shell_app_system_get_default ();
+ app = shell_app_system_lookup_app (appsys, basename);
+ g_free (basename);
+ return app;
+}
+
+/**
+ * shell_window_tracker_get_default:
+ *
+ * Return Value: (transfer none): The global #ShellWindowTracker instance
+ */
+ShellWindowTracker *
+shell_window_tracker_get_default (void)
+{
+ static ShellWindowTracker *instance;
+
+ if (instance == NULL)
+ instance = g_object_new (SHELL_TYPE_WINDOW_TRACKER, NULL);
+
+ return instance;
+}
diff --git a/src/shell-window-tracker.h b/src/shell-window-tracker.h
new file mode 100644
index 0000000..87c38f8
--- /dev/null
+++ b/src/shell-window-tracker.h
@@ -0,0 +1,29 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+#ifndef __SHELL_WINDOW_TRACKER_H__
+#define __SHELL_WINDOW_TRACKER_H__
+
+#include <glib-object.h>
+#include <glib.h>
+#include <meta/window.h>
+#include <meta/meta-startup-notification.h>
+
+#include "shell-app.h"
+#include "shell-app-system.h"
+
+G_BEGIN_DECLS
+
+#define SHELL_TYPE_WINDOW_TRACKER (shell_window_tracker_get_type ())
+G_DECLARE_FINAL_TYPE (ShellWindowTracker, shell_window_tracker,
+ SHELL, WINDOW_TRACKER, GObject)
+
+ShellWindowTracker* shell_window_tracker_get_default(void);
+
+ShellApp *shell_window_tracker_get_window_app (ShellWindowTracker *tracker, MetaWindow *metawin);
+
+ShellApp *shell_window_tracker_get_app_from_pid (ShellWindowTracker *tracker, int pid);
+
+GSList *shell_window_tracker_get_startup_sequences (ShellWindowTracker *tracker);
+
+G_END_DECLS
+
+#endif /* __SHELL_WINDOW_TRACKER_H__ */
diff --git a/src/shell-wm-private.h b/src/shell-wm-private.h
new file mode 100644
index 0000000..1363087
--- /dev/null
+++ b/src/shell-wm-private.h
@@ -0,0 +1,63 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+#ifndef __SHELL_WM_PRIVATE_H__
+#define __SHELL_WM_PRIVATE_H__
+
+#include "shell-wm.h"
+
+G_BEGIN_DECLS
+
+/* These forward along the different effects from GnomeShellPlugin */
+
+void _shell_wm_minimize (ShellWM *wm,
+ MetaWindowActor *actor);
+void _shell_wm_unminimize (ShellWM *wm,
+ MetaWindowActor *actor);
+void _shell_wm_size_changed(ShellWM *wm,
+ MetaWindowActor *actor);
+void _shell_wm_size_change(ShellWM *wm,
+ MetaWindowActor *actor,
+ MetaSizeChange which_change,
+ MetaRectangle *old_frame_rect,
+ MetaRectangle *old_buffer_rect);
+void _shell_wm_map (ShellWM *wm,
+ MetaWindowActor *actor);
+void _shell_wm_destroy (ShellWM *wm,
+ MetaWindowActor *actor);
+
+void _shell_wm_switch_workspace (ShellWM *wm,
+ gint from,
+ gint to,
+ MetaMotionDirection direction);
+void _shell_wm_kill_window_effects (ShellWM *wm,
+ MetaWindowActor *actor);
+void _shell_wm_kill_switch_workspace (ShellWM *wm);
+
+void _shell_wm_show_tile_preview (ShellWM *wm,
+ MetaWindow *window,
+ MetaRectangle *tile_rect,
+ int tile_monitor);
+void _shell_wm_hide_tile_preview (ShellWM *wm);
+void _shell_wm_show_window_menu (ShellWM *wm,
+ MetaWindow *window,
+ MetaWindowMenuType menu,
+ int x,
+ int y);
+void _shell_wm_show_window_menu_for_rect (ShellWM *wm,
+ MetaWindow *window,
+ MetaWindowMenuType menu,
+ MetaRectangle *rect);
+
+gboolean _shell_wm_filter_keybinding (ShellWM *wm,
+ MetaKeyBinding *binding);
+
+void _shell_wm_confirm_display_change (ShellWM *wm);
+
+MetaCloseDialog * _shell_wm_create_close_dialog (ShellWM *wm,
+ MetaWindow *window);
+
+MetaInhibitShortcutsDialog * _shell_wm_create_inhibit_shortcuts_dialog (ShellWM *wm,
+ MetaWindow *window);
+
+G_END_DECLS
+
+#endif /* __SHELL_WM_PRIVATE_H__ */
diff --git a/src/shell-wm.c b/src/shell-wm.c
new file mode 100644
index 0000000..a11e419
--- /dev/null
+++ b/src/shell-wm.c
@@ -0,0 +1,462 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <meta/meta-enum-types.h>
+#include <meta/keybindings.h>
+
+#include "shell-wm-private.h"
+#include "shell-global.h"
+
+struct _ShellWM {
+ GObject parent;
+
+ MetaPlugin *plugin;
+};
+
+/* Signals */
+enum
+{
+ MINIMIZE,
+ UNMINIMIZE,
+ SIZE_CHANGED,
+ SIZE_CHANGE,
+ MAP,
+ DESTROY,
+ SWITCH_WORKSPACE,
+ KILL_SWITCH_WORKSPACE,
+ KILL_WINDOW_EFFECTS,
+ SHOW_TILE_PREVIEW,
+ HIDE_TILE_PREVIEW,
+ SHOW_WINDOW_MENU,
+ FILTER_KEYBINDING,
+ CONFIRM_DISPLAY_CHANGE,
+ CREATE_CLOSE_DIALOG,
+ CREATE_INHIBIT_SHORTCUTS_DIALOG,
+
+ LAST_SIGNAL
+};
+
+G_DEFINE_TYPE(ShellWM, shell_wm, G_TYPE_OBJECT);
+
+static guint shell_wm_signals [LAST_SIGNAL] = { 0 };
+
+static void
+shell_wm_init (ShellWM *wm)
+{
+}
+
+static void
+shell_wm_finalize (GObject *object)
+{
+ G_OBJECT_CLASS (shell_wm_parent_class)->finalize (object);
+}
+
+static void
+shell_wm_class_init (ShellWMClass *klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+ gobject_class->finalize = shell_wm_finalize;
+
+ shell_wm_signals[MINIMIZE] =
+ g_signal_new ("minimize",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL, NULL,
+ G_TYPE_NONE, 1,
+ META_TYPE_WINDOW_ACTOR);
+ shell_wm_signals[UNMINIMIZE] =
+ g_signal_new ("unminimize",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL, NULL,
+ G_TYPE_NONE, 1,
+ META_TYPE_WINDOW_ACTOR);
+ shell_wm_signals[SIZE_CHANGED] =
+ g_signal_new ("size-changed",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL, NULL,
+ G_TYPE_NONE, 1,
+ META_TYPE_WINDOW_ACTOR);
+ shell_wm_signals[SIZE_CHANGE] =
+ g_signal_new ("size-change",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL, NULL,
+ G_TYPE_NONE, 4,
+ META_TYPE_WINDOW_ACTOR, META_TYPE_SIZE_CHANGE, META_TYPE_RECTANGLE, META_TYPE_RECTANGLE);
+ shell_wm_signals[MAP] =
+ g_signal_new ("map",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL, NULL,
+ G_TYPE_NONE, 1,
+ META_TYPE_WINDOW_ACTOR);
+ shell_wm_signals[DESTROY] =
+ g_signal_new ("destroy",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL, NULL,
+ G_TYPE_NONE, 1,
+ META_TYPE_WINDOW_ACTOR);
+ shell_wm_signals[SWITCH_WORKSPACE] =
+ g_signal_new ("switch-workspace",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL, NULL,
+ G_TYPE_NONE, 3,
+ G_TYPE_INT, G_TYPE_INT, G_TYPE_INT);
+ shell_wm_signals[KILL_SWITCH_WORKSPACE] =
+ g_signal_new ("kill-switch-workspace",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL, NULL,
+ G_TYPE_NONE, 0);
+ shell_wm_signals[KILL_WINDOW_EFFECTS] =
+ g_signal_new ("kill-window-effects",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL, NULL,
+ G_TYPE_NONE, 1,
+ META_TYPE_WINDOW_ACTOR);
+ shell_wm_signals[SHOW_TILE_PREVIEW] =
+ g_signal_new ("show-tile-preview",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ 0, NULL, NULL, NULL,
+ G_TYPE_NONE, 3,
+ META_TYPE_WINDOW,
+ META_TYPE_RECTANGLE,
+ G_TYPE_INT);
+ shell_wm_signals[HIDE_TILE_PREVIEW] =
+ g_signal_new ("hide-tile-preview",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL, NULL,
+ G_TYPE_NONE, 0);
+ shell_wm_signals[SHOW_WINDOW_MENU] =
+ g_signal_new ("show-window-menu",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ 0, NULL, NULL, NULL,
+ G_TYPE_NONE, 3,
+ META_TYPE_WINDOW, G_TYPE_INT, META_TYPE_RECTANGLE);
+ shell_wm_signals[FILTER_KEYBINDING] =
+ g_signal_new ("filter-keybinding",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ g_signal_accumulator_true_handled, NULL, NULL,
+ G_TYPE_BOOLEAN, 1,
+ META_TYPE_KEY_BINDING);
+ shell_wm_signals[CONFIRM_DISPLAY_CHANGE] =
+ g_signal_new ("confirm-display-change",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL, NULL,
+ G_TYPE_NONE, 0);
+ /**
+ * ShellWM::create-close-dialog:
+ * @wm: The WM
+ * @window: The window to create the dialog for
+ *
+ * Creates a close dialog for the given window.
+ *
+ * Returns: (transfer full): The close dialog instance.
+ */
+ shell_wm_signals[CREATE_CLOSE_DIALOG] =
+ g_signal_new ("create-close-dialog",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL, NULL,
+ META_TYPE_CLOSE_DIALOG, 1, META_TYPE_WINDOW);
+ /**
+ * ShellWM::create-inhibit-shortcuts-dialog:
+ * @wm: The WM
+ * @window: The window to create the dialog for
+ *
+ * Creates an inhibit shortcuts dialog for the given window.
+ *
+ * Returns: (transfer full): The inhibit shortcuts dialog instance.
+ */
+ shell_wm_signals[CREATE_INHIBIT_SHORTCUTS_DIALOG] =
+ g_signal_new ("create-inhibit-shortcuts-dialog",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL, NULL,
+ META_TYPE_INHIBIT_SHORTCUTS_DIALOG, 1, META_TYPE_WINDOW);
+}
+
+void
+_shell_wm_switch_workspace (ShellWM *wm,
+ gint from,
+ gint to,
+ MetaMotionDirection direction)
+{
+ g_signal_emit (wm, shell_wm_signals[SWITCH_WORKSPACE], 0,
+ from, to, direction);
+}
+
+/**
+ * shell_wm_completed_switch_workspace:
+ * @wm: the ShellWM
+ *
+ * The plugin must call this when it has finished switching the
+ * workspace.
+ **/
+void
+shell_wm_completed_switch_workspace (ShellWM *wm)
+{
+ meta_plugin_switch_workspace_completed (wm->plugin);
+}
+
+/**
+ * shell_wm_completed_minimize:
+ * @wm: the ShellWM
+ * @actor: the MetaWindowActor actor
+ *
+ * The plugin must call this when it has completed a window minimize effect.
+ **/
+void
+shell_wm_completed_minimize (ShellWM *wm,
+ MetaWindowActor *actor)
+{
+ meta_plugin_minimize_completed (wm->plugin, actor);
+}
+
+/**
+ * shell_wm_completed_unminimize:
+ * @wm: the ShellWM
+ * @actor: the MetaWindowActor actor
+ *
+ * The plugin must call this when it has completed a window unminimize effect.
+ **/
+void
+shell_wm_completed_unminimize (ShellWM *wm,
+ MetaWindowActor *actor)
+{
+ meta_plugin_unminimize_completed (wm->plugin, actor);
+}
+
+void
+shell_wm_completed_size_change (ShellWM *wm,
+ MetaWindowActor *actor)
+{
+ meta_plugin_size_change_completed (wm->plugin, actor);
+}
+
+/**
+ * shell_wm_completed_map:
+ * @wm: the ShellWM
+ * @actor: the MetaWindowActor actor
+ *
+ * The plugin must call this when it has completed a window map effect.
+ **/
+void
+shell_wm_completed_map (ShellWM *wm,
+ MetaWindowActor *actor)
+{
+ meta_plugin_map_completed (wm->plugin, actor);
+}
+
+/**
+ * shell_wm_completed_destroy:
+ * @wm: the ShellWM
+ * @actor: the MetaWindowActor actor
+ *
+ * The plugin must call this when it has completed a window destroy effect.
+ **/
+void
+shell_wm_completed_destroy (ShellWM *wm,
+ MetaWindowActor *actor)
+{
+ meta_plugin_destroy_completed (wm->plugin, actor);
+}
+
+/**
+ * shell_wm_complete_display_change:
+ * @wm: the ShellWM
+ * @ok: if the new configuration was OK
+ *
+ * The plugin must call this after the user responded to the confirmation dialog.
+ */
+void
+shell_wm_complete_display_change (ShellWM *wm,
+ gboolean ok)
+{
+ meta_plugin_complete_display_change (wm->plugin, ok);
+}
+
+void
+_shell_wm_kill_switch_workspace (ShellWM *wm)
+{
+ g_signal_emit (wm, shell_wm_signals[KILL_SWITCH_WORKSPACE], 0);
+}
+
+void
+_shell_wm_kill_window_effects (ShellWM *wm,
+ MetaWindowActor *actor)
+{
+ g_signal_emit (wm, shell_wm_signals[KILL_WINDOW_EFFECTS], 0, actor);
+}
+
+void
+_shell_wm_show_tile_preview (ShellWM *wm,
+ MetaWindow *window,
+ MetaRectangle *tile_rect,
+ int tile_monitor)
+{
+ g_signal_emit (wm, shell_wm_signals[SHOW_TILE_PREVIEW], 0,
+ window, tile_rect, tile_monitor);
+}
+
+void
+_shell_wm_hide_tile_preview (ShellWM *wm)
+{
+ g_signal_emit (wm, shell_wm_signals[HIDE_TILE_PREVIEW], 0);
+}
+
+void
+_shell_wm_show_window_menu (ShellWM *wm,
+ MetaWindow *window,
+ MetaWindowMenuType menu,
+ int x,
+ int y)
+{
+ MetaRectangle rect;
+
+ rect.x = x;
+ rect.y = y;
+ rect.width = rect.height = 0;
+
+ _shell_wm_show_window_menu_for_rect (wm, window, menu, &rect);
+}
+
+void
+_shell_wm_show_window_menu_for_rect (ShellWM *wm,
+ MetaWindow *window,
+ MetaWindowMenuType menu,
+ MetaRectangle *rect)
+{
+ g_signal_emit (wm, shell_wm_signals[SHOW_WINDOW_MENU], 0, window, menu, rect);
+}
+
+void
+_shell_wm_minimize (ShellWM *wm,
+ MetaWindowActor *actor)
+{
+ g_signal_emit (wm, shell_wm_signals[MINIMIZE], 0, actor);
+}
+
+void
+_shell_wm_unminimize (ShellWM *wm,
+ MetaWindowActor *actor)
+{
+ g_signal_emit (wm, shell_wm_signals[UNMINIMIZE], 0, actor);
+}
+
+void
+_shell_wm_size_changed (ShellWM *wm,
+ MetaWindowActor *actor)
+{
+ g_signal_emit (wm, shell_wm_signals[SIZE_CHANGED], 0, actor);
+}
+
+void
+_shell_wm_size_change (ShellWM *wm,
+ MetaWindowActor *actor,
+ MetaSizeChange which_change,
+ MetaRectangle *old_frame_rect,
+ MetaRectangle *old_buffer_rect)
+{
+ g_signal_emit (wm, shell_wm_signals[SIZE_CHANGE], 0, actor, which_change, old_frame_rect, old_buffer_rect);
+}
+
+void
+_shell_wm_map (ShellWM *wm,
+ MetaWindowActor *actor)
+{
+ g_signal_emit (wm, shell_wm_signals[MAP], 0, actor);
+}
+
+void
+_shell_wm_destroy (ShellWM *wm,
+ MetaWindowActor *actor)
+{
+ g_signal_emit (wm, shell_wm_signals[DESTROY], 0, actor);
+}
+
+gboolean
+_shell_wm_filter_keybinding (ShellWM *wm,
+ MetaKeyBinding *binding)
+{
+ gboolean rv;
+
+ g_signal_emit (wm, shell_wm_signals[FILTER_KEYBINDING], 0, binding, &rv);
+
+ return rv;
+}
+
+void
+_shell_wm_confirm_display_change (ShellWM *wm)
+{
+ g_signal_emit (wm, shell_wm_signals[CONFIRM_DISPLAY_CHANGE], 0);
+}
+
+MetaCloseDialog *
+_shell_wm_create_close_dialog (ShellWM *wm,
+ MetaWindow *window)
+{
+ MetaCloseDialog *dialog;
+
+ g_signal_emit (wm, shell_wm_signals[CREATE_CLOSE_DIALOG], 0, window, &dialog);
+
+ return dialog;
+}
+
+MetaInhibitShortcutsDialog *
+_shell_wm_create_inhibit_shortcuts_dialog (ShellWM *wm,
+ MetaWindow *window)
+{
+ MetaInhibitShortcutsDialog *dialog;
+
+ g_signal_emit (wm, shell_wm_signals[CREATE_INHIBIT_SHORTCUTS_DIALOG], 0, window, &dialog);
+
+ return dialog;
+}
+
+/**
+ * shell_wm_new:
+ * @plugin: the #MetaPlugin
+ *
+ * Creates a new window management interface by hooking into @plugin.
+ *
+ * Return value: the new window-management interface
+ **/
+ShellWM *
+shell_wm_new (MetaPlugin *plugin)
+{
+ ShellWM *wm;
+
+ wm = g_object_new (SHELL_TYPE_WM, NULL);
+ wm->plugin = plugin;
+
+ return wm;
+}
diff --git a/src/shell-wm.h b/src/shell-wm.h
new file mode 100644
index 0000000..ddfe095
--- /dev/null
+++ b/src/shell-wm.h
@@ -0,0 +1,32 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+#ifndef __SHELL_WM_H__
+#define __SHELL_WM_H__
+
+#include <glib-object.h>
+#include <meta/meta-plugin.h>
+
+G_BEGIN_DECLS
+
+#define SHELL_TYPE_WM (shell_wm_get_type ())
+G_DECLARE_FINAL_TYPE (ShellWM, shell_wm, SHELL, WM, GObject)
+
+ShellWM *shell_wm_new (MetaPlugin *plugin);
+
+void shell_wm_completed_minimize (ShellWM *wm,
+ MetaWindowActor *actor);
+void shell_wm_completed_unminimize (ShellWM *wm,
+ MetaWindowActor *actor);
+void shell_wm_completed_size_change (ShellWM *wm,
+ MetaWindowActor *actor);
+void shell_wm_completed_map (ShellWM *wm,
+ MetaWindowActor *actor);
+void shell_wm_completed_destroy (ShellWM *wm,
+ MetaWindowActor *actor);
+void shell_wm_completed_switch_workspace (ShellWM *wm);
+
+void shell_wm_complete_display_change (ShellWM *wm,
+ gboolean ok);
+
+G_END_DECLS
+
+#endif /* __SHELL_WM_H__ */
diff --git a/src/shell-workspace-background.c b/src/shell-workspace-background.c
new file mode 100644
index 0000000..1c0bbc3
--- /dev/null
+++ b/src/shell-workspace-background.c
@@ -0,0 +1,226 @@
+#include "config.h"
+
+#include "shell-workspace-background.h"
+
+#include "shell-global.h"
+#include <meta/meta-workspace-manager.h>
+
+#define BACKGROUND_MARGIN 12
+
+enum
+{
+ PROP_0,
+
+ PROP_MONITOR_INDEX,
+ PROP_STATE_ADJUSTMENT_VALUE,
+
+ PROP_LAST
+};
+
+static GParamSpec *obj_props[PROP_LAST] = { NULL, };
+
+struct _ShellWorkspaceBackground
+{
+ /*< private >*/
+ StWidget parent_instance;
+
+ int monitor_index;
+ double state_adjustment_value;
+
+ MetaRectangle work_area;
+ MetaRectangle monitor_geometry;
+};
+
+G_DEFINE_TYPE (ShellWorkspaceBackground, shell_workspace_background, ST_TYPE_WIDGET);
+
+static void
+on_workareas_changed (ShellWorkspaceBackground *self)
+{
+ ShellGlobal *global = shell_global_get ();
+ MetaDisplay *display = shell_global_get_display (global);
+ MetaWorkspaceManager *workspace_manager = shell_global_get_workspace_manager (global);
+ MetaWorkspace *workspace =
+ meta_workspace_manager_get_workspace_by_index (workspace_manager, 0);
+
+ meta_workspace_get_work_area_for_monitor (workspace,
+ self->monitor_index,
+ &self->work_area);
+
+ meta_display_get_monitor_geometry (display,
+ self->monitor_index,
+ &self->monitor_geometry);
+}
+
+static void
+shell_workspace_background_allocate (ClutterActor *actor,
+ const ClutterActorBox *box)
+{
+ ShellWorkspaceBackground *self = SHELL_WORKSPACE_BACKGROUND (actor);
+ ShellGlobal *global = shell_global_get ();
+ ClutterStage *stage = shell_global_get_stage (global);
+ StThemeContext *context = st_theme_context_get_for_stage (stage);
+ StThemeNode *theme_node = st_widget_get_theme_node (ST_WIDGET (actor));
+ ClutterActorBox scaled_box, my_box, content_box;
+ ClutterActor *child;
+ float content_width, content_height;
+ float scaled_width, scaled_height;
+ float x_scale, y_scale;
+ float width, height;
+ int left_offset, right_offset;
+ int top_offset, bottom_offset;
+ int scale_factor;
+
+ scale_factor = st_theme_context_get_scale_factor (context);
+
+ clutter_actor_box_get_size (box, &width, &height);
+ scaled_height = height - BACKGROUND_MARGIN * 2 * scale_factor;
+ scaled_width = (scaled_height / height) * width;
+
+ scaled_box.x1 = box->x1 + (width - scaled_width) / 2;
+ scaled_box.y1 = box->y1 + (height - scaled_height) / 2;
+ clutter_actor_box_set_size (&scaled_box, scaled_width, scaled_height);
+
+ clutter_actor_box_interpolate(box, &scaled_box,
+ self->state_adjustment_value, &my_box);
+
+ clutter_actor_set_allocation (actor, &my_box);
+
+ st_theme_node_get_content_box (theme_node, &my_box, &content_box);
+
+ child = clutter_actor_get_first_child (actor);
+ clutter_actor_allocate (child, &content_box);
+
+ clutter_actor_box_get_size (&content_box, &content_width, &content_height);
+ x_scale = content_width / self->work_area.width;
+ y_scale = content_height / self->work_area.height;
+
+ left_offset = self->work_area.x - self->monitor_geometry.x;
+ top_offset = self->work_area.y - self->monitor_geometry.y;
+ right_offset = self->monitor_geometry.width - self->work_area.width - left_offset;
+ bottom_offset = self->monitor_geometry.height - self->work_area.height - top_offset;
+
+ clutter_actor_box_set_origin (&content_box,
+ -left_offset * x_scale,
+ -top_offset * y_scale);
+ clutter_actor_box_set_size (&content_box,
+ content_width + (left_offset + right_offset) * x_scale,
+ content_height + (top_offset + bottom_offset) * y_scale);
+
+ child = clutter_actor_get_first_child (child);
+ clutter_actor_allocate (child, &content_box);
+}
+
+static void
+shell_workspace_background_constructed (GObject *object)
+{
+ G_OBJECT_CLASS (shell_workspace_background_parent_class)->constructed (object);
+
+ on_workareas_changed (SHELL_WORKSPACE_BACKGROUND (object));
+}
+
+static void
+shell_workspace_background_get_property (GObject *gobject,
+ unsigned int property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ ShellWorkspaceBackground *self = SHELL_WORKSPACE_BACKGROUND (gobject);
+
+ switch (property_id)
+ {
+ case PROP_MONITOR_INDEX:
+ g_value_set_int (value, self->monitor_index);
+ break;
+
+ case PROP_STATE_ADJUSTMENT_VALUE:
+ g_value_set_double (value, self->state_adjustment_value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, property_id, pspec);
+ }
+}
+
+static void
+shell_workspace_background_set_property (GObject *gobject,
+ unsigned int property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ ShellWorkspaceBackground *self = SHELL_WORKSPACE_BACKGROUND (gobject);
+
+ switch (property_id)
+ {
+ case PROP_MONITOR_INDEX:
+ {
+ int new_value = g_value_get_int (value);
+ if (self->monitor_index != new_value)
+ {
+ self->monitor_index = new_value;
+ g_object_notify_by_pspec (gobject, obj_props[PROP_MONITOR_INDEX]);
+ }
+ }
+ break;
+
+ case PROP_STATE_ADJUSTMENT_VALUE:
+ {
+ double new_value = g_value_get_double (value);
+ if (self->state_adjustment_value != new_value)
+ {
+ self->state_adjustment_value = new_value;
+ g_object_notify_by_pspec (gobject, obj_props[PROP_STATE_ADJUSTMENT_VALUE]);
+ }
+ }
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, property_id, pspec);
+ }
+}
+
+static void
+shell_workspace_background_class_init (ShellWorkspaceBackgroundClass *klass)
+{
+ ClutterActorClass *actor_class = CLUTTER_ACTOR_CLASS (klass);
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+ actor_class->allocate = shell_workspace_background_allocate;
+
+ gobject_class->constructed = shell_workspace_background_constructed;
+ gobject_class->get_property = shell_workspace_background_get_property;
+ gobject_class->set_property = shell_workspace_background_set_property;
+
+ /**
+ * ShellWorkspaceBackground:monitor-index:
+ */
+ obj_props[PROP_MONITOR_INDEX] =
+ g_param_spec_int ("monitor-index", "", "",
+ 0, G_MAXINT, 0,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_STRINGS |
+ G_PARAM_EXPLICIT_NOTIFY);
+
+ /**
+ * ShellWorkspaceBackground:state-adjustment-value:
+ */
+ obj_props[PROP_STATE_ADJUSTMENT_VALUE] =
+ g_param_spec_double ("state-adjustment-value", "", "",
+ -G_MAXDOUBLE, G_MAXDOUBLE, 0.0,
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS |
+ G_PARAM_EXPLICIT_NOTIFY);
+
+ g_object_class_install_properties (gobject_class, PROP_LAST, obj_props);
+}
+
+static void
+shell_workspace_background_init (ShellWorkspaceBackground *self)
+{
+ ShellGlobal *global = shell_global_get ();
+ MetaDisplay *display = shell_global_get_display (global);
+
+ g_signal_connect_object (display, "workareas-changed",
+ G_CALLBACK (on_workareas_changed),
+ self, G_CONNECT_SWAPPED);
+}
diff --git a/src/shell-workspace-background.h b/src/shell-workspace-background.h
new file mode 100644
index 0000000..48b2bd6
--- /dev/null
+++ b/src/shell-workspace-background.h
@@ -0,0 +1,14 @@
+#ifndef __SHELL_WORKSPACE_BACKGROUND_H__
+#define __SHELL_WORKSPACE_BACKGROUND_H__
+
+#include <st/st.h>
+
+G_BEGIN_DECLS
+
+#define SHELL_TYPE_WORKSPACE_BACKGROUND (shell_workspace_background_get_type ())
+G_DECLARE_FINAL_TYPE (ShellWorkspaceBackground, shell_workspace_background,
+ SHELL, WORKSPACE_BACKGROUND, StWidget)
+
+G_END_DECLS
+
+#endif /* __SHELL_WORKSPACE_BACKGROUND_H__ */
diff --git a/src/st/croco/cr-additional-sel.c b/src/st/croco/cr-additional-sel.c
new file mode 100644
index 0000000..9bd8d6a
--- /dev/null
+++ b/src/st/croco/cr-additional-sel.c
@@ -0,0 +1,498 @@
+/* -*- Mode: C; indent-tabs-mode:nil; c-basic-offset: 8-*- */
+
+/*
+ * This file is part of The Croco Library
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2.1 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * 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 Lesser General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ *
+ * Author: Dodji Seketeli
+ * See COPYRIGHTS file for copyright information.
+ *
+ */
+
+#include "cr-additional-sel.h"
+#include "string.h"
+
+/**
+ * CRAdditionalSel:
+ *
+ * #CRAdditionalSel abstracts an additional selector.
+ * An additional selector is the selector part
+ * that comes after the combination of type selectors.
+ * It can be either "a class selector (the .class part),
+ * a pseudo class selector, an attribute selector
+ * or an id selector.
+ */
+
+/**
+ * cr_additional_sel_new:
+ *
+ * Default constructor of #CRAdditionalSel.
+ * Returns the newly build instance of #CRAdditionalSel.
+ */
+CRAdditionalSel *
+cr_additional_sel_new (void)
+{
+ CRAdditionalSel *result = NULL;
+
+ result = g_try_malloc (sizeof (CRAdditionalSel));
+
+ if (result == NULL) {
+ cr_utils_trace_debug ("Out of memory");
+ return NULL;
+ }
+
+ memset (result, 0, sizeof (CRAdditionalSel));
+
+ return result;
+}
+
+/**
+ * cr_additional_sel_new_with_type:
+ * @a_sel_type: the type of the newly built instance
+ * of #CRAdditionalSel.
+ *
+ * Constructor of #CRAdditionalSel.
+ * Returns the newly built instance of #CRAdditionalSel.
+ */
+CRAdditionalSel *
+cr_additional_sel_new_with_type (enum AddSelectorType a_sel_type)
+{
+ CRAdditionalSel *result = NULL;
+
+ result = cr_additional_sel_new ();
+
+ g_return_val_if_fail (result, NULL);
+
+ result->type = a_sel_type;
+
+ return result;
+}
+
+/**
+ * cr_additional_sel_set_class_name:
+ * @a_this: the "this pointer" of the current instance
+ * of #CRAdditionalSel .
+ * @a_class_name: the new class name to set.
+ *
+ * Sets a new class name to a
+ * CLASS additional selector.
+ */
+void
+cr_additional_sel_set_class_name (CRAdditionalSel * a_this,
+ CRString * a_class_name)
+{
+ g_return_if_fail (a_this && a_this->type == CLASS_ADD_SELECTOR);
+
+ if (a_this->content.class_name) {
+ cr_string_destroy (a_this->content.class_name);
+ }
+
+ a_this->content.class_name = a_class_name;
+}
+
+/**
+ * cr_additional_sel_set_id_name:
+ * @a_this: the "this pointer" of the current instance
+ * of #CRAdditionalSel .
+ * @a_id: the new id to set.
+ *
+ * Sets a new id name to an
+ * ID additional selector.
+ */
+void
+cr_additional_sel_set_id_name (CRAdditionalSel * a_this, CRString * a_id)
+{
+ g_return_if_fail (a_this && a_this->type == ID_ADD_SELECTOR);
+
+ if (a_this->content.id_name) {
+ cr_string_destroy (a_this->content.id_name);
+ }
+
+ a_this->content.id_name = a_id;
+}
+
+/**
+ * cr_additional_sel_set_pseudo:
+ * @a_this: the "this pointer" of the current instance
+ * of #CRAdditionalSel .
+ * @a_pseudo: the new pseudo to set.
+ *
+ * Sets a new pseudo to a
+ * PSEUDO additional selector.
+ */
+void
+cr_additional_sel_set_pseudo (CRAdditionalSel * a_this, CRPseudo * a_pseudo)
+{
+ g_return_if_fail (a_this
+ && a_this->type == PSEUDO_CLASS_ADD_SELECTOR);
+
+ if (a_this->content.pseudo) {
+ cr_pseudo_destroy (a_this->content.pseudo);
+ }
+
+ a_this->content.pseudo = a_pseudo;
+}
+
+/**
+ * cr_additional_sel_set_attr_sel:
+ * @a_this: the "this pointer" of the current instance
+ * of #CRAdditionalSel .
+ * @a_sel: the new instance of #CRAttrSel to set.
+ *
+ * Sets a new instance of #CRAttrSel to
+ * a ATTRIBUTE additional selector.
+ */
+void
+cr_additional_sel_set_attr_sel (CRAdditionalSel * a_this, CRAttrSel * a_sel)
+{
+ g_return_if_fail (a_this && a_this->type == ATTRIBUTE_ADD_SELECTOR);
+
+ if (a_this->content.attr_sel) {
+ cr_attr_sel_destroy (a_this->content.attr_sel);
+ }
+
+ a_this->content.attr_sel = a_sel;
+}
+
+/**
+ * cr_additional_sel_append:
+ * @a_this: the "this pointer" of the current instance
+ * of #CRAdditionalSel .
+ * @a_sel: the new instance to #CRAdditional to append.
+ *
+ * Appends a new instance of #CRAdditional to the
+ * current list of #CRAdditional.
+ *
+ * Returns the new list of CRAdditionalSel or NULL if an error arises.
+ */
+CRAdditionalSel *
+cr_additional_sel_append (CRAdditionalSel * a_this, CRAdditionalSel * a_sel)
+{
+ CRAdditionalSel *cur_sel = NULL;
+
+ g_return_val_if_fail (a_sel, NULL);
+
+ if (a_this == NULL) {
+ return a_sel;
+ }
+
+ if (a_sel == NULL)
+ return NULL;
+
+ for (cur_sel = a_this;
+ cur_sel && cur_sel->next; cur_sel = cur_sel->next) ;
+
+ g_return_val_if_fail (cur_sel != NULL, NULL);
+
+ cur_sel->next = a_sel;
+ a_sel->prev = cur_sel;
+
+ return a_this;
+}
+
+/**
+ * cr_additional_sel_prepend:
+ * @a_this: the "this pointer" of the current instance
+ * of #CRAdditionalSel .
+ * @a_sel: the new instance to #CRAdditional to preappend.
+ *
+ * Preppends a new instance of #CRAdditional to the
+ * current list of #CRAdditional.
+ *
+ * Returns the new list of CRAdditionalSel or NULL if an error arises.
+ */
+CRAdditionalSel *
+cr_additional_sel_prepend (CRAdditionalSel * a_this, CRAdditionalSel * a_sel)
+{
+ g_return_val_if_fail (a_sel, NULL);
+
+ if (a_this == NULL) {
+ return a_sel;
+ }
+
+ a_sel->next = a_this;
+ a_this->prev = a_sel;
+
+ return a_sel;
+}
+
+guchar *
+cr_additional_sel_to_string (CRAdditionalSel const * a_this)
+{
+ guchar *result = NULL;
+ GString *str_buf = NULL;
+ CRAdditionalSel const *cur = NULL;
+
+ g_return_val_if_fail (a_this, NULL);
+
+ str_buf = g_string_new (NULL);
+
+ for (cur = a_this; cur; cur = cur->next) {
+ switch (cur->type) {
+ case CLASS_ADD_SELECTOR:
+ {
+ guchar *name = NULL;
+
+ if (cur->content.class_name) {
+ name = (guchar *) g_strndup
+ (cur->content.class_name->stryng->str,
+ cur->content.class_name->stryng->len);
+
+ if (name) {
+ g_string_append_printf
+ (str_buf, ".%s",
+ name);
+ g_free (name);
+ name = NULL;
+ }
+ }
+ }
+ break;
+
+ case ID_ADD_SELECTOR:
+ {
+ guchar *name = NULL;
+
+ if (cur->content.id_name) {
+ name = (guchar *) g_strndup
+ (cur->content.id_name->stryng->str,
+ cur->content.id_name->stryng->len);
+
+ if (name) {
+ g_string_append_printf
+ (str_buf, "#%s",
+ name);
+ g_free (name);
+ name = NULL;
+ }
+ }
+ }
+
+ break;
+
+ case PSEUDO_CLASS_ADD_SELECTOR:
+ {
+ if (cur->content.pseudo) {
+ guchar *tmp_str = NULL;
+
+ tmp_str = cr_pseudo_to_string
+ (cur->content.pseudo);
+ if (tmp_str) {
+ g_string_append_printf
+ (str_buf, ":%s",
+ tmp_str);
+ g_free (tmp_str);
+ tmp_str = NULL;
+ }
+ }
+ }
+ break;
+
+ case ATTRIBUTE_ADD_SELECTOR:
+ if (cur->content.attr_sel) {
+ guchar *tmp_str = NULL;
+
+ g_string_append_c (str_buf, '[');
+ tmp_str = cr_attr_sel_to_string
+ (cur->content.attr_sel);
+ if (tmp_str) {
+ g_string_append_printf
+ (str_buf, "%s]", tmp_str);
+ g_free (tmp_str);
+ tmp_str = NULL;
+ }
+ }
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ if (str_buf) {
+ result = (guchar *) g_string_free (str_buf, FALSE);
+ str_buf = NULL;
+ }
+
+ return result;
+}
+
+guchar *
+cr_additional_sel_one_to_string (CRAdditionalSel const *a_this)
+{
+ guchar *result = NULL;
+ GString *str_buf = NULL;
+
+ g_return_val_if_fail (a_this, NULL) ;
+
+ str_buf = g_string_new (NULL) ;
+
+ switch (a_this->type) {
+ case CLASS_ADD_SELECTOR:
+ {
+ guchar *name = NULL;
+
+ if (a_this->content.class_name) {
+ name = (guchar *) g_strndup
+ (a_this->content.class_name->stryng->str,
+ a_this->content.class_name->stryng->len);
+
+ if (name) {
+ g_string_append_printf
+ (str_buf, ".%s",
+ name);
+ g_free (name);
+ name = NULL;
+ }
+ }
+ }
+ break;
+
+ case ID_ADD_SELECTOR:
+ {
+ guchar *name = NULL;
+
+ if (a_this->content.id_name) {
+ name = (guchar *) g_strndup
+ (a_this->content.id_name->stryng->str,
+ a_this->content.id_name->stryng->len);
+
+ if (name) {
+ g_string_append_printf
+ (str_buf, "#%s",
+ name);
+ g_free (name);
+ name = NULL;
+ }
+ }
+ }
+
+ break;
+
+ case PSEUDO_CLASS_ADD_SELECTOR:
+ {
+ if (a_this->content.pseudo) {
+ guchar *tmp_str = NULL;
+
+ tmp_str = cr_pseudo_to_string
+ (a_this->content.pseudo);
+ if (tmp_str) {
+ g_string_append_printf
+ (str_buf, ":%s",
+ tmp_str);
+ g_free (tmp_str);
+ tmp_str = NULL;
+ }
+ }
+ }
+ break;
+
+ case ATTRIBUTE_ADD_SELECTOR:
+ if (a_this->content.attr_sel) {
+ guchar *tmp_str = NULL;
+
+ g_string_append_printf (str_buf, "[");
+ tmp_str = cr_attr_sel_to_string
+ (a_this->content.attr_sel);
+ if (tmp_str) {
+ g_string_append_printf
+ (str_buf, "%s]", tmp_str);
+ g_free (tmp_str);
+ tmp_str = NULL;
+ }
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ if (str_buf) {
+ result = (guchar *) g_string_free (str_buf, FALSE);
+ str_buf = NULL;
+ }
+
+ return result;
+}
+
+/**
+ * cr_additional_sel_dump:
+ * @a_this: the "this pointer" of the current instance of
+ * #CRAdditionalSel.
+ * @a_fp: the destination file.
+ *
+ * Dumps the current instance of #CRAdditionalSel to a file
+ */
+void
+cr_additional_sel_dump (CRAdditionalSel const * a_this, FILE * a_fp)
+{
+ guchar *tmp_str = NULL;
+
+ g_return_if_fail (a_fp);
+
+ if (a_this) {
+ tmp_str = cr_additional_sel_to_string (a_this);
+ if (tmp_str) {
+ fprintf (a_fp, "%s", tmp_str);
+ g_free (tmp_str);
+ tmp_str = NULL;
+ }
+ }
+}
+
+/**
+ * cr_additional_sel_destroy:
+ * @a_this: the "this pointer" of the current instance
+ * of #CRAdditionalSel .
+ *
+ * Destroys an instance of #CRAdditional.
+ */
+void
+cr_additional_sel_destroy (CRAdditionalSel * a_this)
+{
+ g_return_if_fail (a_this);
+
+ switch (a_this->type) {
+ case CLASS_ADD_SELECTOR:
+ cr_string_destroy (a_this->content.class_name);
+ a_this->content.class_name = NULL;
+ break;
+
+ case PSEUDO_CLASS_ADD_SELECTOR:
+ cr_pseudo_destroy (a_this->content.pseudo);
+ a_this->content.pseudo = NULL;
+ break;
+
+ case ID_ADD_SELECTOR:
+ cr_string_destroy (a_this->content.id_name);
+ a_this->content.id_name = NULL;
+ break;
+
+ case ATTRIBUTE_ADD_SELECTOR:
+ cr_attr_sel_destroy (a_this->content.attr_sel);
+ a_this->content.attr_sel = NULL;
+ break;
+
+ default:
+ break;
+ }
+
+ if (a_this->next) {
+ cr_additional_sel_destroy (a_this->next);
+ }
+
+ g_free (a_this);
+}
diff --git a/src/st/croco/cr-additional-sel.h b/src/st/croco/cr-additional-sel.h
new file mode 100644
index 0000000..7ca3e07
--- /dev/null
+++ b/src/st/croco/cr-additional-sel.h
@@ -0,0 +1,98 @@
+/* -*- Mode: C; indent-tabs-mode:nil; c-basic-offset: 8-*- */
+
+/*
+ * This file is part of The Croco Library
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2.1 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * 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 Lesser General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ *
+ * Author: Dodji Seketeli
+ * See the COPYRIGHTS file for copyright information.
+ */
+
+
+#ifndef __CR_ADD_SEL_H__
+#define __CR_ADD_SEL_H__
+
+#include <stdio.h>
+#include <glib.h>
+#include "cr-utils.h"
+#include "cr-attr-sel.h"
+#include "cr-pseudo.h"
+#include "cr-additional-sel.h"
+
+G_BEGIN_DECLS
+
+enum AddSelectorType
+{
+ NO_ADD_SELECTOR = 0 ,
+ CLASS_ADD_SELECTOR = 1 ,
+ PSEUDO_CLASS_ADD_SELECTOR = 1 << 1,
+ ID_ADD_SELECTOR = 1 << 3,
+ ATTRIBUTE_ADD_SELECTOR = 1 << 4
+} ;
+
+union CRAdditionalSelectorContent
+{
+ CRString *class_name ;
+ CRString *id_name ;
+ CRPseudo *pseudo ;
+ CRAttrSel *attr_sel ;
+} ;
+
+typedef struct _CRAdditionalSel CRAdditionalSel ;
+
+struct _CRAdditionalSel
+{
+ enum AddSelectorType type ;
+ union CRAdditionalSelectorContent content ;
+
+ CRAdditionalSel * next ;
+ CRAdditionalSel * prev ;
+ CRParsingLocation location ;
+} ;
+
+CRAdditionalSel * cr_additional_sel_new (void) ;
+
+CRAdditionalSel * cr_additional_sel_new_with_type (enum AddSelectorType a_sel_type) ;
+
+CRAdditionalSel * cr_additional_sel_append (CRAdditionalSel *a_this,
+ CRAdditionalSel *a_sel) ;
+
+void cr_additional_sel_set_class_name (CRAdditionalSel *a_this,
+ CRString *a_class_name) ;
+
+void cr_additional_sel_set_id_name (CRAdditionalSel *a_this,
+ CRString *a_id) ;
+
+void cr_additional_sel_set_pseudo (CRAdditionalSel *a_this,
+ CRPseudo *a_pseudo) ;
+
+void cr_additional_sel_set_attr_sel (CRAdditionalSel *a_this,
+ CRAttrSel *a_sel) ;
+
+CRAdditionalSel * cr_additional_sel_prepend (CRAdditionalSel *a_this,
+ CRAdditionalSel *a_sel) ;
+
+guchar * cr_additional_sel_to_string (CRAdditionalSel const *a_this) ;
+
+guchar * cr_additional_sel_one_to_string (CRAdditionalSel const *a_this) ;
+
+void cr_additional_sel_dump (CRAdditionalSel const *a_this, FILE *a_fp) ;
+
+void cr_additional_sel_destroy (CRAdditionalSel *a_this) ;
+
+G_END_DECLS
+
+#endif /*__CR_ADD_SEL_H*/
diff --git a/src/st/croco/cr-attr-sel.c b/src/st/croco/cr-attr-sel.c
new file mode 100644
index 0000000..fc8e6ef
--- /dev/null
+++ b/src/st/croco/cr-attr-sel.c
@@ -0,0 +1,234 @@
+/* -*- Mode: C; indent-tabs-mode:nil; c-basic-offset: 8-*- */
+
+/*
+ * This file is part of The Croco Library
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2.1 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * 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 Lesser General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ *
+ * See COPYRIGHTS file for copyrights information.
+ */
+
+#include <stdio.h>
+#include "cr-attr-sel.h"
+
+/**
+ * CRAttrSel:
+ *
+ * #CRAdditionalSel abstracts an attribute selector.
+ * Attributes selectors are described in the css2 spec [5.8].
+ * There are more generally used in the css2 selectors described in
+ * css2 spec [5] .
+ */
+
+/**
+ * cr_attr_sel_new:
+ * The constructor of #CRAttrSel.
+ * Returns the newly allocated instance
+ * of #CRAttrSel.
+ */
+CRAttrSel *
+cr_attr_sel_new (void)
+{
+ CRAttrSel *result = NULL;
+
+ result = g_malloc0 (sizeof (CRAttrSel));
+
+ return result;
+}
+
+/**
+ * cr_attr_sel_append_attr_sel:
+ * @a_this: the this pointer of the current instance of #CRAttrSel.
+ * @a_attr_sel: selector to append.
+ *
+ * Appends an attribute selector to the current list of
+ * attribute selectors represented by a_this.
+ * Returns CR_OK upon successful completion, an error code otherwise.
+ */
+enum CRStatus
+cr_attr_sel_append_attr_sel (CRAttrSel * a_this, CRAttrSel * a_attr_sel)
+{
+ CRAttrSel *cur_sel = NULL;
+
+ g_return_val_if_fail (a_this && a_attr_sel,
+ CR_BAD_PARAM_ERROR);
+
+ for (cur_sel = a_this;
+ cur_sel->next;
+ cur_sel = cur_sel->next) ;
+
+ cur_sel->next = a_attr_sel;
+ a_attr_sel->prev = cur_sel;
+
+ return CR_OK;
+}
+
+/**
+ * cr_attr_sel_prepend_attr_sel:
+ *@a_this: the "this pointer" of the current instance *of #CRAttrSel.
+ *@a_attr_sel: the attribute selector to append.
+ *
+ *Prepends an attribute selector to the list of
+ *attributes selector represented by a_this.
+ *Returns CR_OK upon successful completion, an error code otherwise.
+ */
+enum CRStatus
+cr_attr_sel_prepend_attr_sel (CRAttrSel * a_this,
+ CRAttrSel * a_attr_sel)
+{
+ g_return_val_if_fail (a_this && a_attr_sel,
+ CR_BAD_PARAM_ERROR);
+
+ a_attr_sel->next = a_this;
+ a_this->prev = a_attr_sel;
+
+ return CR_OK;
+}
+
+/**
+ * cr_attr_sel_to_string:
+ * @a_this: the current instance of #CRAttrSel.
+ *
+ * Serializes an attribute selector into a string
+ * Returns the serialized attribute selector.
+ */
+guchar *
+cr_attr_sel_to_string (CRAttrSel const * a_this)
+{
+ CRAttrSel const *cur = NULL;
+ guchar *result = NULL;
+ GString *str_buf = NULL;
+
+ g_return_val_if_fail (a_this, NULL);
+
+ str_buf = g_string_new (NULL);
+
+ for (cur = a_this; cur; cur = cur->next) {
+ if (cur->prev) {
+ g_string_append_c (str_buf, ' ');
+ }
+
+ if (cur->name) {
+ guchar *name = NULL;
+
+ name = (guchar *) g_strndup (cur->name->stryng->str,
+ cur->name->stryng->len);
+ if (name) {
+ g_string_append (str_buf, (const gchar *) name);
+ g_free (name);
+ name = NULL;
+ }
+ }
+
+ if (cur->value) {
+ guchar *value = NULL;
+
+ value = (guchar *) g_strndup (cur->value->stryng->str,
+ cur->value->stryng->len);
+ if (value) {
+ switch (cur->match_way) {
+ case SET:
+ break;
+
+ case EQUALS:
+ g_string_append_c (str_buf, '=');
+ break;
+
+ case INCLUDES:
+ g_string_append (str_buf, "~=");
+ break;
+
+ case DASHMATCH:
+ g_string_append (str_buf, "|=");
+ break;
+
+ default:
+ break;
+ }
+
+ g_string_append_printf
+ (str_buf, "\"%s\"", value);
+
+ g_free (value);
+ value = NULL;
+ }
+ }
+ }
+
+ if (str_buf) {
+ result = (guchar *) g_string_free (str_buf, FALSE);
+ }
+
+ return result;
+}
+
+/**
+ * cr_attr_sel_dump:
+ * @a_this: the "this pointer" of the current instance of
+ * #CRAttrSel.
+ * @a_fp: the destination file.
+ *
+ * Dumps the current instance of #CRAttrSel to a file.
+ */
+void
+cr_attr_sel_dump (CRAttrSel const * a_this, FILE * a_fp)
+{
+ guchar *tmp_str = NULL;
+
+ g_return_if_fail (a_this);
+
+ tmp_str = cr_attr_sel_to_string (a_this);
+
+ if (tmp_str) {
+ fprintf (a_fp, "%s", tmp_str);
+ g_free (tmp_str);
+ tmp_str = NULL;
+ }
+}
+
+/**
+ *cr_attr_sel_destroy:
+ *@a_this: the "this pointer" of the current
+ *instance of #CRAttrSel.
+ *
+ *Destroys the current instance of #CRAttrSel.
+ *Frees all the fields if they are non null.
+ */
+void
+cr_attr_sel_destroy (CRAttrSel * a_this)
+{
+ g_return_if_fail (a_this);
+
+ if (a_this->name) {
+ cr_string_destroy (a_this->name);
+ a_this->name = NULL;
+ }
+
+ if (a_this->value) {
+ cr_string_destroy (a_this->value);
+ a_this->value = NULL;
+ }
+
+ if (a_this->next) {
+ cr_attr_sel_destroy (a_this->next);
+ a_this->next = NULL;
+ }
+
+ if (a_this) {
+ g_free (a_this);
+ a_this = NULL;
+ }
+}
+
diff --git a/src/st/croco/cr-attr-sel.h b/src/st/croco/cr-attr-sel.h
new file mode 100644
index 0000000..82d5a87
--- /dev/null
+++ b/src/st/croco/cr-attr-sel.h
@@ -0,0 +1,74 @@
+/* -*- Mode: C; indent-tabs-mode:nil; c-basic-offset: 8-*- */
+
+/*
+ * This file is part of The Croco Library
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2.1 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * 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 Lesser General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ *
+ * Author: Dodji Seketeli
+ * See COPYRIGHTS file for copyright information.
+ */
+
+#ifndef __CR_ATTR_SEL_H__
+#define __CR_ATTR_SEL_H__
+
+#include <stdio.h>
+#include <glib.h>
+#include "cr-utils.h"
+#include "cr-parsing-location.h"
+#include "cr-string.h"
+
+G_BEGIN_DECLS
+
+
+struct _CRAttrSel ;
+typedef struct _CRAttrSel CRAttrSel ;
+
+enum AttrMatchWay
+{
+ NO_MATCH = 0,
+ SET,
+ EQUALS,
+ INCLUDES,
+ DASHMATCH
+} ;
+
+struct _CRAttrSel
+{
+ CRString *name ;
+ CRString *value ;
+ enum AttrMatchWay match_way ;
+ CRAttrSel *next ;
+ CRAttrSel *prev ;
+ CRParsingLocation location ;
+} ;
+
+CRAttrSel * cr_attr_sel_new (void) ;
+
+enum CRStatus cr_attr_sel_append_attr_sel (CRAttrSel * a_this,
+ CRAttrSel *a_attr_sel) ;
+
+enum CRStatus cr_attr_sel_prepend_attr_sel (CRAttrSel *a_this,
+ CRAttrSel *a_attr_sel) ;
+
+guchar * cr_attr_sel_to_string (CRAttrSel const *a_this) ;
+
+void cr_attr_sel_dump (CRAttrSel const *a_this, FILE *a_fp) ;
+
+void cr_attr_sel_destroy (CRAttrSel *a_this) ;
+
+G_END_DECLS
+
+#endif /*__CR_ATTR_SEL_H__*/
diff --git a/src/st/croco/cr-cascade.c b/src/st/croco/cr-cascade.c
new file mode 100644
index 0000000..68f59bb
--- /dev/null
+++ b/src/st/croco/cr-cascade.c
@@ -0,0 +1,215 @@
+/* -*- Mode: C; indent-tabs-mode:nil; c-basic-offset: 8-*- */
+
+/*
+ * This file is part of The Croco Library
+ *
+ * Copyright (C) 2002-2003 Dodji Seketeli <dodji@seketeli.org>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2.1 of the
+ * GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * 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 Lesser General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ */
+
+/*
+ *$Id$
+ */
+
+#include <string.h>
+#include "cr-cascade.h"
+
+#define PRIVATE(a_this) ((a_this)->priv)
+
+struct _CRCascadePriv {
+ /**
+ *the 3 style sheets of the cascade:
+ *author, user, and useragent sheet.
+ *Intended to be addressed by
+ *sheets[ORIGIN_AUTHOR] or sheets[ORIGIN_USER]
+ *of sheets[ORIGIN_UA] ;
+ */
+ CRStyleSheet *sheets[3];
+ guint ref_count;
+};
+
+/**
+ * cr_cascade_new:
+ *@a_author_sheet: the author origin style sheet. May be NULL.
+ *@a_user_sheet: the user origin style sheet. May be NULL.
+ *@a_ua_sheet: the user agent origin style sheet. May be NULL.
+ *
+ *Constructor of the #CRCascade class.
+ *Note that all three parameters of this
+ *method are ref counted and their refcount is increased.
+ *Their refcount will be decreased at the destruction of
+ *the instance of #CRCascade.
+ *So the caller should not call their destructor. The caller
+ *should call their ref/unref method instead if it wants
+ *
+ *Returns the newly built instance of CRCascade or NULL if
+ *an error arose during construction.
+ */
+CRCascade *
+cr_cascade_new (CRStyleSheet * a_author_sheet,
+ CRStyleSheet * a_user_sheet, CRStyleSheet * a_ua_sheet)
+{
+ CRCascade *result = NULL;
+
+ result = g_try_malloc (sizeof (CRCascade));
+ if (!result) {
+ cr_utils_trace_info ("Out of memory");
+ return NULL;
+ }
+ memset (result, 0, sizeof (CRCascade));
+
+ PRIVATE (result) = g_try_malloc (sizeof (CRCascadePriv));
+ if (!PRIVATE (result)) {
+ cr_utils_trace_info ("Out of memory");
+ g_free (result);
+ return NULL;
+ }
+ memset (PRIVATE (result), 0, sizeof (CRCascadePriv));
+
+ if (a_author_sheet) {
+ cr_cascade_set_sheet (result, a_author_sheet, ORIGIN_AUTHOR);
+ }
+ if (a_user_sheet) {
+ cr_cascade_set_sheet (result, a_user_sheet, ORIGIN_USER);
+ }
+ if (a_ua_sheet) {
+ cr_cascade_set_sheet (result, a_ua_sheet, ORIGIN_UA);
+ }
+
+ return result;
+}
+
+/**
+ * cr_cascade_get_sheet:
+ *@a_this: the current instance of #CRCascade.
+ *@a_origin: the origin of the style sheet as
+ *defined in the css2 spec in chapter 6.4.
+ *Gets a given origin sheet.
+ *
+ *Gets a sheet, part of the cascade.
+ *Note that the returned stylesheet
+ *is refcounted so if the caller wants
+ *to manage it's lifecycle, it must use
+ *cr_stylesheet_ref()/cr_stylesheet_unref() instead
+ *of the cr_stylesheet_destroy() method.
+ *Returns the style sheet, or NULL if it does not
+ *exist.
+ */
+CRStyleSheet *
+cr_cascade_get_sheet (CRCascade * a_this, enum CRStyleOrigin a_origin)
+{
+ g_return_val_if_fail (a_this
+ && a_origin >= ORIGIN_UA
+ && a_origin < NB_ORIGINS, NULL);
+
+ return PRIVATE (a_this)->sheets[a_origin];
+}
+
+/**
+ * cr_cascade_set_sheet:
+ *@a_this: the current instance of #CRCascade.
+ *@a_sheet: the stylesheet to set.
+ *@a_origin: the origin of the stylesheet.
+ *
+ *Sets a stylesheet in the cascade
+ *
+ *Returns CR_OK upon successful completion, an error
+ *code otherwise.
+ */
+enum CRStatus
+cr_cascade_set_sheet (CRCascade * a_this,
+ CRStyleSheet * a_sheet, enum CRStyleOrigin a_origin)
+{
+ g_return_val_if_fail (a_this
+ && a_sheet
+ && a_origin >= ORIGIN_UA
+ && a_origin < NB_ORIGINS, CR_BAD_PARAM_ERROR);
+
+ if (PRIVATE (a_this)->sheets[a_origin])
+ cr_stylesheet_unref (PRIVATE (a_this)->sheets[a_origin]);
+ PRIVATE (a_this)->sheets[a_origin] = a_sheet;
+ cr_stylesheet_ref (a_sheet);
+ a_sheet->origin = a_origin;
+ return CR_OK;
+}
+
+/**
+ *cr_cascade_ref:
+ *@a_this: the current instance of #CRCascade
+ *
+ *Increases the reference counter of the current instance
+ *of #CRCascade.
+ */
+void
+cr_cascade_ref (CRCascade * a_this)
+{
+ g_return_if_fail (a_this && PRIVATE (a_this));
+
+ PRIVATE (a_this)->ref_count++;
+}
+
+/**
+ * cr_cascade_unref:
+ *@a_this: the current instance of
+ *#CRCascade.
+ *
+ *Decrements the reference counter associated
+ *to this instance of #CRCascade. If the reference
+ *counter reaches zero, the instance is destroyed
+ *using cr_cascade_destroy()
+ */
+void
+cr_cascade_unref (CRCascade * a_this)
+{
+ g_return_if_fail (a_this && PRIVATE (a_this));
+
+ if (PRIVATE (a_this)->ref_count)
+ PRIVATE (a_this)->ref_count--;
+ if (!PRIVATE (a_this)->ref_count) {
+ cr_cascade_destroy (a_this);
+ }
+}
+
+/**
+ * cr_cascade_destroy:
+ * @a_this: the current instance of #CRCascade
+ *
+ * Destructor of #CRCascade.
+ */
+void
+cr_cascade_destroy (CRCascade * a_this)
+{
+ g_return_if_fail (a_this);
+
+ if (PRIVATE (a_this)) {
+ gulong i = 0;
+
+ for (i = 0; i < NB_ORIGINS; i++) {
+ if (PRIVATE (a_this)->sheets[i]) {
+ if (cr_stylesheet_unref
+ (PRIVATE (a_this)->sheets[i])
+ == TRUE) {
+ PRIVATE (a_this)->sheets[i] = NULL;
+ }
+ }
+ }
+ g_free (PRIVATE (a_this));
+ PRIVATE (a_this) = NULL;
+ }
+ g_free (a_this);
+}
diff --git a/src/st/croco/cr-cascade.h b/src/st/croco/cr-cascade.h
new file mode 100644
index 0000000..3119ae8
--- /dev/null
+++ b/src/st/croco/cr-cascade.h
@@ -0,0 +1,74 @@
+/* -*- Mode: C; indent-tabs-mode:nil; c-basic-offset: 8-*- */
+
+/*
+ * This file is part of The Croco Library
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2.1 of the
+ * GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * 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 Lesser General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ *
+ */
+
+/*
+ *$Id$
+ */
+
+#ifndef __CR_CASCADE_H__
+#define __CR_CASCADE_H__
+
+#include "cr-stylesheet.h"
+
+/**
+ *@file
+ *the declaration of the #CRCascade class.
+ */
+
+G_BEGIN_DECLS
+
+
+typedef struct _CRCascadePriv CRCascadePriv ;
+
+/**
+ *An abstraction of the "Cascade" defined
+ *in the css2 spec, chapter 6.4.
+ */
+typedef struct _CRCascade CRCascade ;
+
+struct _CRCascade
+{
+ CRCascadePriv *priv ;
+};
+
+
+CRCascade * cr_cascade_new (CRStyleSheet *a_author_sheet,
+ CRStyleSheet *a_user_sheet,
+ CRStyleSheet *a_ua_sheet) ;
+
+CRStyleSheet * cr_cascade_get_sheet (CRCascade *a_this,
+ enum CRStyleOrigin a_origin) ;
+
+enum CRStatus cr_cascade_set_sheet (CRCascade *a_this,
+ CRStyleSheet *a_sheet,
+ enum CRStyleOrigin a_origin) ;
+
+void cr_cascade_ref (CRCascade *a_this) ;
+
+void cr_cascade_unref (CRCascade *a_this) ;
+
+void cr_cascade_destroy (CRCascade *a_this) ;
+
+G_END_DECLS
+
+#endif /*__CR_CASCADE_H__*/
diff --git a/src/st/croco/cr-declaration.c b/src/st/croco/cr-declaration.c
new file mode 100644
index 0000000..6c70128
--- /dev/null
+++ b/src/st/croco/cr-declaration.c
@@ -0,0 +1,798 @@
+/* -*- Mode: C; indent-tabs-mode: nil; c-basic-offset: 8 -*- */
+
+/*
+ * This file is part of The Croco Library
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2.1 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * 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 Lesser General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ *
+ * Author: Dodji Seketeli.
+ * See COPYRIGHTS file for copyright information.
+ */
+
+
+#include <string.h>
+#include "cr-declaration.h"
+#include "cr-statement.h"
+#include "cr-parser.h"
+
+/**
+ *@CRDeclaration:
+ *
+ *The definition of the #CRDeclaration class.
+ */
+
+/**
+ * dump:
+ *@a_this: the current instance of #CRDeclaration.
+ *@a_fp: the destination file pointer.
+ *@a_indent: the number of indentation white char.
+ *
+ *Dumps (serializes) one css declaration to a file.
+ */
+static void
+dump (CRDeclaration const * a_this, FILE * a_fp, glong a_indent)
+{
+ guchar *str = NULL;
+
+ g_return_if_fail (a_this);
+
+ str = (guchar *) cr_declaration_to_string (a_this, a_indent);
+ if (str) {
+ fprintf (a_fp, "%s", str);
+ g_free (str);
+ str = NULL;
+ }
+}
+
+/**
+ * cr_declaration_new:
+ * @a_statement: the statement this declaration belongs to. can be NULL.
+ *@a_property: the property string of the declaration
+ *@a_value: the value expression of the declaration.
+ *Constructor of #CRDeclaration.
+ *
+ *Returns the newly built instance of #CRDeclaration, or NULL in
+ *case of error.
+ *
+ *The returned CRDeclaration takes ownership of @a_property and @a_value.
+ *(E.g. cr_declaration_destroy on this CRDeclaration will also free
+ *@a_property and @a_value.)
+ */
+CRDeclaration *
+cr_declaration_new (CRStatement * a_statement,
+ CRString * a_property, CRTerm * a_value)
+{
+ CRDeclaration *result = NULL;
+
+ g_return_val_if_fail (a_property, NULL);
+
+ if (a_statement)
+ g_return_val_if_fail (a_statement
+ && ((a_statement->type == RULESET_STMT)
+ || (a_statement->type
+ == AT_FONT_FACE_RULE_STMT)
+ || (a_statement->type
+ == AT_PAGE_RULE_STMT)), NULL);
+
+ result = g_try_malloc (sizeof (CRDeclaration));
+ if (!result) {
+ cr_utils_trace_info ("Out of memory");
+ return NULL;
+ }
+ memset (result, 0, sizeof (CRDeclaration));
+ result->property = a_property;
+ result->value = a_value;
+
+ if (a_value) {
+ cr_term_ref (a_value);
+ }
+ result->parent_statement = a_statement;
+ return result;
+}
+
+/**
+ * cr_declaration_parse_from_buf:
+ *@a_statement: the parent css2 statement of this
+ *this declaration. Must be non NULL and of type
+ *RULESET_STMT (must be a ruleset).
+ *@a_str: the string that contains the statement.
+ *@a_enc: the encoding of a_str.
+ *
+ *Parses a text buffer that contains
+ *a css declaration.
+ *Returns the parsed declaration, or NULL in case of error.
+ */
+CRDeclaration *
+cr_declaration_parse_from_buf (CRStatement * a_statement,
+ const guchar * a_str, enum CREncoding a_enc)
+{
+ enum CRStatus status = CR_OK;
+ CRTerm *value = NULL;
+ CRString *property = NULL;
+ CRDeclaration *result = NULL;
+ CRParser *parser = NULL;
+ gboolean important = FALSE;
+
+ g_return_val_if_fail (a_str, NULL);
+ if (a_statement)
+ g_return_val_if_fail (a_statement->type == RULESET_STMT,
+ NULL);
+
+ parser = cr_parser_new_from_buf ((guchar*)a_str, strlen ((const char *) a_str), a_enc, FALSE);
+ g_return_val_if_fail (parser, NULL);
+
+ status = cr_parser_try_to_skip_spaces_and_comments (parser);
+ if (status != CR_OK)
+ goto cleanup;
+
+ status = cr_parser_parse_declaration (parser, &property,
+ &value, &important);
+ if (status != CR_OK || !property)
+ goto cleanup;
+
+ result = cr_declaration_new (a_statement, property, value);
+ if (result) {
+ property = NULL;
+ value = NULL;
+ result->important = important;
+ }
+
+ cleanup:
+
+ if (parser) {
+ cr_parser_destroy (parser);
+ parser = NULL;
+ }
+
+ if (property) {
+ cr_string_destroy (property);
+ property = NULL;
+ }
+
+ if (value) {
+ cr_term_destroy (value);
+ value = NULL;
+ }
+
+ return result;
+}
+
+/**
+ * cr_declaration_parse_list_from_buf:
+ *@a_str: the input buffer that contains the list of declaration to
+ *parse.
+ *@a_enc: the encoding of a_str
+ *
+ *Parses a ';' separated list of properties declaration.
+ *Returns the parsed list of declaration, NULL if parsing failed.
+ */
+CRDeclaration *
+cr_declaration_parse_list_from_buf (const guchar * a_str,
+ enum CREncoding a_enc)
+{
+
+ enum CRStatus status = CR_OK;
+ CRTerm *value = NULL;
+ CRString *property = NULL;
+ CRDeclaration *result = NULL,
+ *cur_decl = NULL;
+ CRParser *parser = NULL;
+ CRTknzr *tokenizer = NULL;
+ gboolean important = FALSE;
+
+ g_return_val_if_fail (a_str, NULL);
+
+ parser = cr_parser_new_from_buf ((guchar*)a_str, strlen ((const char *) a_str), a_enc, FALSE);
+ g_return_val_if_fail (parser, NULL);
+ status = cr_parser_get_tknzr (parser, &tokenizer);
+ if (status != CR_OK || !tokenizer) {
+ if (status == CR_OK)
+ status = CR_ERROR;
+ goto cleanup;
+ }
+ status = cr_parser_try_to_skip_spaces_and_comments (parser);
+ if (status != CR_OK)
+ goto cleanup;
+
+ status = cr_parser_parse_declaration (parser, &property,
+ &value, &important);
+ if (status != CR_OK || !property) {
+ if (status != CR_OK)
+ status = CR_ERROR;
+ goto cleanup;
+ }
+ result = cr_declaration_new (NULL, property, value);
+ if (result) {
+ property = NULL;
+ value = NULL;
+ result->important = important;
+ }
+ /*now, go parse the other declarations */
+ for (;;) {
+ guint32 c = 0;
+
+ cr_parser_try_to_skip_spaces_and_comments (parser);
+ status = cr_tknzr_peek_char (tokenizer, &c);
+ if (status != CR_OK) {
+ if (status == CR_END_OF_INPUT_ERROR)
+ status = CR_OK;
+ goto cleanup;
+ }
+ if (c == ';') {
+ status = cr_tknzr_read_char (tokenizer, &c);
+ } else {
+ break;
+ }
+ important = FALSE;
+ cr_parser_try_to_skip_spaces_and_comments (parser);
+ status = cr_parser_parse_declaration (parser, &property,
+ &value, &important);
+ if (status != CR_OK || !property) {
+ if (status == CR_END_OF_INPUT_ERROR) {
+ status = CR_OK;
+ }
+ break;
+ }
+ cur_decl = cr_declaration_new (NULL, property, value);
+ if (cur_decl) {
+ cur_decl->important = important;
+ result = cr_declaration_append (result, cur_decl);
+ property = NULL;
+ value = NULL;
+ cur_decl = NULL;
+ } else {
+ break;
+ }
+ }
+
+ cleanup:
+
+ if (parser) {
+ cr_parser_destroy (parser);
+ parser = NULL;
+ }
+
+ if (property) {
+ cr_string_destroy (property);
+ property = NULL;
+ }
+
+ if (value) {
+ cr_term_destroy (value);
+ value = NULL;
+ }
+
+ if (status != CR_OK && result) {
+ cr_declaration_destroy (result);
+ result = NULL;
+ }
+ return result;
+}
+
+/**
+ * cr_declaration_append:
+ *@a_this: the current declaration list.
+ *@a_new: the declaration to append.
+ *
+ *Appends a new declaration to the current declarations list.
+ *Returns the declaration list with a_new appended to it, or NULL
+ *in case of error.
+ */
+CRDeclaration *
+cr_declaration_append (CRDeclaration * a_this, CRDeclaration * a_new)
+{
+ CRDeclaration *cur = NULL;
+
+ g_return_val_if_fail (a_new, NULL);
+
+ if (!a_this)
+ return a_new;
+
+ for (cur = a_this; cur && cur->next; cur = cur->next) ;
+
+ cur->next = a_new;
+ a_new->prev = cur;
+
+ return a_this;
+}
+
+/**
+ * cr_declaration_unlink:
+ *@a_decls: the declaration to unlink.
+ *
+ *Unlinks the declaration from the declaration list.
+ *case of a successful completion, NULL otherwise.
+ *
+ *Returns a pointer to the unlinked declaration in
+ */
+CRDeclaration *
+cr_declaration_unlink (CRDeclaration * a_decl)
+{
+ CRDeclaration *result = a_decl;
+
+ g_return_val_if_fail (result, NULL);
+
+ /*
+ *some sanity checks first
+ */
+ if (a_decl->prev) {
+ g_return_val_if_fail (a_decl->prev->next == a_decl, NULL);
+
+ }
+ if (a_decl->next) {
+ g_return_val_if_fail (a_decl->next->prev == a_decl, NULL);
+ }
+
+ /*
+ *now, the real unlinking job.
+ */
+ if (a_decl->prev) {
+ a_decl->prev->next = a_decl->next;
+ }
+ if (a_decl->next) {
+ a_decl->next->prev = a_decl->prev;
+ }
+ if (a_decl->parent_statement) {
+ CRDeclaration **children_decl_ptr = NULL;
+
+ switch (a_decl->parent_statement->type) {
+ case RULESET_STMT:
+ if (a_decl->parent_statement->kind.ruleset) {
+ children_decl_ptr =
+ &a_decl->parent_statement->
+ kind.ruleset->decl_list;
+ }
+
+ break;
+
+ case AT_FONT_FACE_RULE_STMT:
+ if (a_decl->parent_statement->kind.font_face_rule) {
+ children_decl_ptr =
+ &a_decl->parent_statement->
+ kind.font_face_rule->decl_list;
+ }
+ break;
+ case AT_PAGE_RULE_STMT:
+ if (a_decl->parent_statement->kind.page_rule) {
+ children_decl_ptr =
+ &a_decl->parent_statement->
+ kind.page_rule->decl_list;
+ }
+
+ default:
+ break;
+ }
+ if (children_decl_ptr
+ && *children_decl_ptr && *children_decl_ptr == a_decl)
+ *children_decl_ptr = (*children_decl_ptr)->next;
+ }
+
+ a_decl->next = NULL;
+ a_decl->prev = NULL;
+ a_decl->parent_statement = NULL;
+
+ return result;
+}
+
+/**
+ * cr_declaration_prepend:
+ * @a_this: the current declaration list.
+ * @a_new: the declaration to prepend.
+ *
+ * prepends a declaration to the current declaration list.
+ *
+ * Returns the list with a_new prepended or NULL in case of error.
+ */
+CRDeclaration *
+cr_declaration_prepend (CRDeclaration * a_this, CRDeclaration * a_new)
+{
+ CRDeclaration *cur = NULL;
+
+ g_return_val_if_fail (a_new, NULL);
+
+ if (!a_this)
+ return a_new;
+
+ a_this->prev = a_new;
+ a_new->next = a_this;
+
+ for (cur = a_new; cur && cur->prev; cur = cur->prev) ;
+
+ return cur;
+}
+
+/**
+ * cr_declaration_append2:
+ *@a_this: the current declaration list.
+ *@a_prop: the property string of the declaration to append.
+ *@a_value: the value of the declaration to append.
+ *
+ *Appends a declaration to the current declaration list.
+ *Returns the list with the new property appended to it, or NULL in
+ *case of an error.
+ */
+CRDeclaration *
+cr_declaration_append2 (CRDeclaration * a_this,
+ CRString * a_prop, CRTerm * a_value)
+{
+ CRDeclaration *new_elem = NULL;
+
+ if (a_this) {
+ new_elem = cr_declaration_new (a_this->parent_statement,
+ a_prop, a_value);
+ } else {
+ new_elem = cr_declaration_new (NULL, a_prop, a_value);
+ }
+
+ g_return_val_if_fail (new_elem, NULL);
+
+ return cr_declaration_append (a_this, new_elem);
+}
+
+/**
+ * cr_declaration_dump:
+ *@a_this: the current instance of #CRDeclaration.
+ *@a_fp: the destination file.
+ *@a_indent: the number of indentation white char.
+ *@a_one_per_line: whether to put one declaration per line of not .
+ *
+ *
+ *Dumps a declaration list to a file.
+ */
+void
+cr_declaration_dump (CRDeclaration const * a_this, FILE * a_fp, glong a_indent,
+ gboolean a_one_per_line)
+{
+ CRDeclaration const *cur = NULL;
+
+ g_return_if_fail (a_this);
+
+ for (cur = a_this; cur; cur = cur->next) {
+ if (cur->prev) {
+ if (a_one_per_line == TRUE)
+ fprintf (a_fp, ";\n");
+ else
+ fprintf (a_fp, "; ");
+ }
+ dump (cur, a_fp, a_indent);
+ }
+}
+
+/**
+ * cr_declaration_dump_one:
+ *@a_this: the current instance of #CRDeclaration.
+ *@a_fp: the destination file.
+ *@a_indent: the number of indentation white char.
+ *
+ *Dumps the first declaration of the declaration list to a file.
+ */
+void
+cr_declaration_dump_one (CRDeclaration const * a_this, FILE * a_fp, glong a_indent)
+{
+ g_return_if_fail (a_this);
+
+ dump (a_this, a_fp, a_indent);
+}
+
+/**
+ * cr_declaration_to_string:
+ *@a_this: the current instance of #CRDeclaration.
+ *@a_indent: the number of indentation white char
+ *to put before the actual serialisation.
+ *
+ *Serializes the declaration into a string
+ *Returns the serialized form the declaration. The caller must
+ *free the string using g_free().
+ */
+gchar *
+cr_declaration_to_string (CRDeclaration const * a_this, gulong a_indent)
+{
+ GString *stringue = NULL;
+
+ gchar *str = NULL,
+ *result = NULL;
+
+ g_return_val_if_fail (a_this, NULL);
+
+ stringue = g_string_new (NULL);
+
+ if (a_this->property
+ && a_this->property->stryng
+ && a_this->property->stryng->str) {
+ str = g_strndup (a_this->property->stryng->str,
+ a_this->property->stryng->len);
+ if (str) {
+ cr_utils_dump_n_chars2 (' ', stringue,
+ a_indent);
+ g_string_append (stringue, str);
+ g_free (str);
+ str = NULL;
+ } else
+ goto error;
+
+ if (a_this->value) {
+ guchar *value_str = NULL;
+
+ value_str = cr_term_to_string (a_this->value);
+ if (value_str) {
+ g_string_append_printf (stringue, " : %s",
+ value_str);
+ g_free (value_str);
+ } else
+ goto error;
+ }
+ if (a_this->important == TRUE) {
+ g_string_append_printf (stringue, " %s",
+ "!important");
+ }
+ }
+ if (stringue && stringue->str) {
+ result = g_string_free (stringue, FALSE);
+ }
+ return result;
+
+ error:
+ if (stringue) {
+ g_string_free (stringue, TRUE);
+ stringue = NULL;
+ }
+ if (str) {
+ g_free (str);
+ str = NULL;
+ }
+
+ return result;
+}
+
+/**
+ * cr_declaration_list_to_string:
+ *@a_this: the current instance of #CRDeclaration.
+ *@a_indent: the number of indentation white char
+ *to put before the actual serialisation.
+ *
+ *Serializes the declaration list into a string
+ */
+guchar *
+cr_declaration_list_to_string (CRDeclaration const * a_this, gulong a_indent)
+{
+ CRDeclaration const *cur = NULL;
+ GString *stringue = NULL;
+ guchar *str = NULL,
+ *result = NULL;
+
+ g_return_val_if_fail (a_this, NULL);
+
+ stringue = g_string_new (NULL);
+
+ for (cur = a_this; cur; cur = cur->next) {
+ str = (guchar *) cr_declaration_to_string (cur, a_indent);
+ if (str) {
+ g_string_append_printf (stringue, "%s;", str);
+ g_free (str);
+ } else
+ break;
+ }
+ if (stringue && stringue->str) {
+ result = (guchar *) g_string_free (stringue, FALSE);
+ }
+
+ return result;
+}
+
+/**
+ * cr_declaration_list_to_string2:
+ *@a_this: the current instance of #CRDeclaration.
+ *@a_indent: the number of indentation white char
+ *@a_one_decl_per_line: whether to output one doc per line or not.
+ *to put before the actual serialisation.
+ *
+ *Serializes the declaration list into a string
+ *Returns the serialized form the declararation.
+ */
+guchar *
+cr_declaration_list_to_string2 (CRDeclaration const * a_this,
+ gulong a_indent, gboolean a_one_decl_per_line)
+{
+ CRDeclaration const *cur = NULL;
+ GString *stringue = NULL;
+ guchar *str = NULL,
+ *result = NULL;
+
+ g_return_val_if_fail (a_this, NULL);
+
+ stringue = g_string_new (NULL);
+
+ for (cur = a_this; cur; cur = cur->next) {
+ str = (guchar *) cr_declaration_to_string (cur, a_indent);
+ if (str) {
+ if (a_one_decl_per_line == TRUE) {
+ if (cur->next)
+ g_string_append_printf (stringue,
+ "%s;\n", str);
+ else
+ g_string_append (stringue,
+ (const gchar *) str);
+ } else {
+ if (cur->next)
+ g_string_append_printf (stringue,
+ "%s;", str);
+ else
+ g_string_append (stringue,
+ (const gchar *) str);
+ }
+ g_free (str);
+ } else
+ break;
+ }
+ if (stringue && stringue->str) {
+ result = (guchar *) g_string_free (stringue, FALSE);
+ }
+
+ return result;
+}
+
+/**
+ * cr_declaration_nr_props:
+ *@a_this: the current instance of #CRDeclaration.
+ *Return the number of properties in the declaration
+ */
+gint
+cr_declaration_nr_props (CRDeclaration const * a_this)
+{
+ CRDeclaration const *cur = NULL;
+ int nr = 0;
+
+ g_return_val_if_fail (a_this, -1);
+
+ for (cur = a_this; cur; cur = cur->next)
+ nr++;
+ return nr;
+}
+
+/**
+ * cr_declaration_get_from_list:
+ *@a_this: the current instance of #CRDeclaration.
+ *@itemnr: the index into the declaration list.
+ *
+ *Use an index to get a CRDeclaration from the declaration list.
+ *
+ *Returns #CRDeclaration at position itemnr,
+ *if itemnr > number of declarations - 1,
+ *it will return NULL.
+ */
+CRDeclaration *
+cr_declaration_get_from_list (CRDeclaration * a_this, int itemnr)
+{
+ CRDeclaration *cur = NULL;
+ int nr = 0;
+
+ g_return_val_if_fail (a_this, NULL);
+
+ for (cur = a_this; cur; cur = cur->next)
+ if (nr++ == itemnr)
+ return cur;
+ return NULL;
+}
+
+/**
+ * cr_declaration_get_by_prop_name:
+ *@a_this: the current instance of #CRDeclaration.
+ *@a_prop: the property name to search for.
+ *
+ *Use property name to get a CRDeclaration from the declaration list.
+ *Returns #CRDeclaration with property name a_prop, or NULL if not found.
+ */
+CRDeclaration *
+cr_declaration_get_by_prop_name (CRDeclaration * a_this,
+ const guchar * a_prop)
+{
+ CRDeclaration *cur = NULL;
+
+ g_return_val_if_fail (a_this, NULL);
+ g_return_val_if_fail (a_prop, NULL);
+
+ for (cur = a_this; cur; cur = cur->next) {
+ if (cur->property
+ && cur->property->stryng
+ && cur->property->stryng->str) {
+ if (!strcmp (cur->property->stryng->str,
+ (const char *) a_prop)) {
+ return cur;
+ }
+ }
+ }
+ return NULL;
+}
+
+/**
+ * cr_declaration_ref:
+ *@a_this: the current instance of #CRDeclaration.
+ *
+ *Increases the ref count of the current instance of #CRDeclaration.
+ */
+void
+cr_declaration_ref (CRDeclaration * a_this)
+{
+ g_return_if_fail (a_this);
+
+ a_this->ref_count++;
+}
+
+/**
+ * cr_declaration_unref:
+ *@a_this: the current instance of #CRDeclaration.
+ *
+ *Decrements the ref count of the current instance of #CRDeclaration.
+ *If the ref count reaches zero, the current instance of #CRDeclaration
+ *if destroyed.
+ *Returns TRUE if @a_this was destroyed (ref count reached zero),
+ *FALSE otherwise.
+ */
+gboolean
+cr_declaration_unref (CRDeclaration * a_this)
+{
+ g_return_val_if_fail (a_this, FALSE);
+
+ if (a_this->ref_count) {
+ a_this->ref_count--;
+ }
+
+ if (a_this->ref_count == 0) {
+ cr_declaration_destroy (a_this);
+ return TRUE;
+ }
+ return FALSE;
+}
+
+/**
+ * cr_declaration_destroy:
+ *@a_this: the current instance of #CRDeclaration.
+ *
+ *Destructor of the declaration list.
+ */
+void
+cr_declaration_destroy (CRDeclaration * a_this)
+{
+ CRDeclaration *cur = NULL;
+
+ g_return_if_fail (a_this);
+
+ /*
+ * Go to the last element of the list.
+ */
+ for (cur = a_this; cur->next; cur = cur->next)
+ g_assert (cur->next->prev == cur);
+
+ /*
+ * Walk backward the list and free each "next" element.
+ * Meanwhile, free each property/value pair contained in the list.
+ */
+ for (; cur; cur = cur->prev) {
+ g_free (cur->next);
+ cur->next = NULL;
+
+ if (cur->property) {
+ cr_string_destroy (cur->property);
+ cur->property = NULL;
+ }
+
+ if (cur->value) {
+ cr_term_destroy (cur->value);
+ cur->value = NULL;
+ }
+ }
+
+ g_free (a_this);
+}
diff --git a/src/st/croco/cr-declaration.h b/src/st/croco/cr-declaration.h
new file mode 100644
index 0000000..eee8be3
--- /dev/null
+++ b/src/st/croco/cr-declaration.h
@@ -0,0 +1,136 @@
+/* -*- Mode: C; indent-tabs-mode: nil; c-basic-offset: 8 -*- */
+
+/*
+ * This file is part of The Croco Library
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2.1 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * 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 Lesser General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ *
+ * See the COPYRIGHTS file for copyright information.
+ */
+
+#ifndef __CR_DECLARATION_H__
+#define __CR_DECLARATION_H__
+
+#include <stdio.h>
+#include "cr-utils.h"
+#include "cr-term.h"
+#include "cr-parsing-location.h"
+
+G_BEGIN_DECLS
+
+/**
+ *@file
+ *The declaration of the #CRDeclaration class.
+ */
+
+/*forward declaration of what is defined in cr-statement.h*/
+typedef struct _CRStatement CRStatement ;
+
+/**
+ *The abstraction of a css declaration defined by the
+ *css2 spec in chapter 4.
+ *It is actually a chained list of property/value pairs.
+ */
+typedef struct _CRDeclaration CRDeclaration ;
+struct _CRDeclaration
+{
+ /**The property.*/
+ CRString *property ;
+
+ /**The value of the property.*/
+ CRTerm *value ;
+
+ /*the ruleset that contains this declaration*/
+ CRStatement *parent_statement ;
+
+ /*the next declaration*/
+ CRDeclaration *next ;
+
+ /*the previous one declaration*/
+ CRDeclaration *prev ;
+
+ /*does the declaration have the important keyword ?*/
+ gboolean important ;
+
+ glong ref_count ;
+
+ CRParsingLocation location ;
+ /*reserved for future usage*/
+ gpointer rfu0 ;
+ gpointer rfu1 ;
+ gpointer rfu2 ;
+ gpointer rfu3 ;
+} ;
+
+
+CRDeclaration * cr_declaration_new (CRStatement *a_statement,
+ CRString *a_property,
+ CRTerm *a_value) ;
+
+
+CRDeclaration * cr_declaration_parse_from_buf (CRStatement *a_statement,
+ const guchar *a_str,
+ enum CREncoding a_enc) ;
+
+CRDeclaration * cr_declaration_parse_list_from_buf (const guchar *a_str,
+ enum CREncoding a_enc) ;
+
+CRDeclaration * cr_declaration_append (CRDeclaration *a_this,
+ CRDeclaration *a_new) ;
+
+CRDeclaration * cr_declaration_append2 (CRDeclaration *a_this,
+ CRString *a_prop,
+ CRTerm *a_value) ;
+
+CRDeclaration * cr_declaration_prepend (CRDeclaration *a_this,
+ CRDeclaration *a_new) ;
+
+CRDeclaration * cr_declaration_unlink (CRDeclaration * a_decl) ;
+
+void
+cr_declaration_dump (CRDeclaration const *a_this,
+ FILE *a_fp, glong a_indent,
+ gboolean a_one_per_line) ;
+
+void cr_declaration_dump_one (CRDeclaration const *a_this,
+ FILE *a_fp, glong a_indent) ;
+
+gint cr_declaration_nr_props (CRDeclaration const *a_this) ;
+
+CRDeclaration * cr_declaration_get_from_list (CRDeclaration *a_this,
+ int itemnr) ;
+
+CRDeclaration * cr_declaration_get_by_prop_name (CRDeclaration *a_this,
+ const guchar *a_str) ;
+
+gchar * cr_declaration_to_string (CRDeclaration const *a_this,
+ gulong a_indent) ;
+
+guchar * cr_declaration_list_to_string (CRDeclaration const *a_this,
+ gulong a_indent) ;
+
+guchar * cr_declaration_list_to_string2 (CRDeclaration const *a_this,
+ gulong a_indent,
+ gboolean a_one_decl_per_line) ;
+
+void cr_declaration_ref (CRDeclaration *a_this) ;
+
+gboolean cr_declaration_unref (CRDeclaration *a_this) ;
+
+void cr_declaration_destroy (CRDeclaration *a_this) ;
+
+G_END_DECLS
+
+#endif /*__CR_DECLARATION_H__*/
diff --git a/src/st/croco/cr-doc-handler.c b/src/st/croco/cr-doc-handler.c
new file mode 100644
index 0000000..b0ef13c
--- /dev/null
+++ b/src/st/croco/cr-doc-handler.c
@@ -0,0 +1,276 @@
+/* -*- Mode: C; indent-tabs-mode: nil; c-basic-offset: 8 -*- */
+
+/*
+ * This file is part of The Croco Library
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2.1 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * 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 Lesser General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ *
+ * See COPRYRIGHTS file for copyright information.
+ */
+
+#include <string.h>
+#include "cr-doc-handler.h"
+#include "cr-parser.h"
+
+/**
+ *@CRDocHandler:
+ *
+ *The definition of the CRDocHandler class.
+ *Contains methods to instantiate, destroy,
+ *and initialize instances of #CRDocHandler
+ *to custom values.
+ */
+
+#define PRIVATE(obj) (obj)->priv
+
+struct _CRDocHandlerPriv {
+ /**
+ *This pointer is to hold an application parsing context.
+ *For example, it used by the Object Model parser to
+ *store it parsing context. #CRParser does not touch it, but
+ *#CROMParser does. #CROMParser allocates this pointer at
+ *the beginning of the css document, and frees it at the end
+ *of the document.
+ */
+ gpointer context;
+
+ /**
+ *The place where #CROMParser puts the result of its parsing, if
+ *any.
+ */
+ gpointer result;
+ /**
+ *a pointer to the parser used to parse
+ *the current document.
+ */
+ CRParser *parser ;
+};
+
+/**
+ * cr_doc_handler_new:
+ *Constructor of #CRDocHandler.
+ *
+ *Returns the newly built instance of
+ *#CRDocHandler
+ *
+ */
+CRDocHandler *
+cr_doc_handler_new (void)
+{
+ CRDocHandler *result = NULL;
+
+ result = g_try_malloc (sizeof (CRDocHandler));
+
+ g_return_val_if_fail (result, NULL);
+
+ memset (result, 0, sizeof (CRDocHandler));
+ result->ref_count++;
+
+ result->priv = g_try_malloc (sizeof (CRDocHandlerPriv));
+ if (!result->priv) {
+ cr_utils_trace_info ("Out of memory exception");
+ g_free (result);
+ return NULL;
+ }
+
+ cr_doc_handler_set_default_sac_handler (result);
+
+ return result;
+}
+
+/**
+ * cr_doc_handler_get_ctxt:
+ *@a_this: the current instance of #CRDocHandler.
+ *@a_ctxt: out parameter. The new parsing context.
+ *
+ *Gets the private parsing context associated to the document handler
+ *The private parsing context is used by libcroco only.
+ *
+ *Returns CR_OK upon successful completion, an error code otherwise.
+ */
+enum CRStatus
+cr_doc_handler_get_ctxt (CRDocHandler const * a_this, gpointer * a_ctxt)
+{
+ g_return_val_if_fail (a_this && a_this->priv, CR_BAD_PARAM_ERROR);
+
+ *a_ctxt = a_this->priv->context;
+
+ return CR_OK;
+}
+
+/**
+ * cr_doc_handler_set_ctxt:
+ *@a_this: the current instance of #CRDocHandler
+ *@a_ctxt: a pointer to the parsing context.
+ *
+ *Sets the private parsing context.
+ *This is used by libcroco only.
+ *Returns CR_OK upon successful completion, an error code otherwise.
+ */
+enum CRStatus
+cr_doc_handler_set_ctxt (CRDocHandler * a_this, gpointer a_ctxt)
+{
+ g_return_val_if_fail (a_this && a_this->priv, CR_BAD_PARAM_ERROR);
+ a_this->priv->context = a_ctxt;
+ return CR_OK;
+}
+
+/**
+ * cr_doc_handler_get_result:
+ *@a_this: the current instance of #CRDocHandler
+ *@a_result: out parameter. The returned result.
+ *
+ *Gets the private parsing result.
+ *The private parsing result is used by libcroco only.
+ *
+ *Returns CR_OK upon successful completion, an error code otherwise.
+ */
+enum CRStatus
+cr_doc_handler_get_result (CRDocHandler const * a_this, gpointer * a_result)
+{
+ g_return_val_if_fail (a_this && a_this->priv, CR_BAD_PARAM_ERROR);
+
+ *a_result = a_this->priv->result;
+
+ return CR_OK;
+}
+
+/**
+ * cr_doc_handler_set_result:
+ *@a_this: the current instance of #CRDocHandler
+ *@a_result: the new result.
+ *
+ *Sets the private parsing context.
+ *This is used by libcroco only.
+ *
+ *Returns CR_OK upon successful completion, an error code otherwise.
+ */
+enum CRStatus
+cr_doc_handler_set_result (CRDocHandler * a_this, gpointer a_result)
+{
+ g_return_val_if_fail (a_this && a_this->priv, CR_BAD_PARAM_ERROR);
+ a_this->priv->result = a_result;
+ return CR_OK;
+}
+
+/**
+ *cr_doc_handler_set_default_sac_handler:
+ *@a_this: a pointer to the current instance of #CRDocHandler.
+ *
+ *Sets the sac handlers contained in the current
+ *instance of DocHandler to the default handlers.
+ *For the time being the default handlers are
+ *test handlers. This is expected to change in a
+ *near future, when the libcroco gets a bit debugged.
+ *
+ *Returns CR_OK upon successful completion, an error code otherwise.
+ */
+enum CRStatus
+cr_doc_handler_set_default_sac_handler (CRDocHandler * a_this)
+{
+ g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR);
+
+ a_this->start_document = NULL;
+ a_this->end_document = NULL;
+ a_this->import_style = NULL;
+ a_this->namespace_declaration = NULL;
+ a_this->comment = NULL;
+ a_this->start_selector = NULL;
+ a_this->end_selector = NULL;
+ a_this->property = NULL;
+ a_this->start_font_face = NULL;
+ a_this->end_font_face = NULL;
+ a_this->start_media = NULL;
+ a_this->end_media = NULL;
+ a_this->start_page = NULL;
+ a_this->end_page = NULL;
+ a_this->ignorable_at_rule = NULL;
+ a_this->error = NULL;
+ a_this->unrecoverable_error = NULL;
+ return CR_OK;
+}
+
+/**
+ * cr_doc_handler_ref:
+ *@a_this: the current instance of #CRDocHandler.
+ */
+void
+cr_doc_handler_ref (CRDocHandler * a_this)
+{
+ g_return_if_fail (a_this);
+
+ a_this->ref_count++;
+}
+
+/**
+ * cr_doc_handler_unref:
+ *@a_this: the current instance of #CRDocHandler.
+ *
+ *Decreases the ref count of the current instance of #CRDocHandler.
+ *If the ref count reaches '0' then, destroys the instance.
+ *
+ *Returns TRUE if the instance as been destroyed, FALSE otherwise.
+ */
+gboolean
+cr_doc_handler_unref (CRDocHandler * a_this)
+{
+ g_return_val_if_fail (a_this, FALSE);
+
+ if (a_this->ref_count > 0) {
+ a_this->ref_count--;
+ }
+
+ if (a_this->ref_count == 0) {
+ cr_doc_handler_destroy (a_this);
+ return TRUE;
+ }
+ return FALSE ;
+}
+
+/**
+ * cr_doc_handler_destroy:
+ *@a_this: the instance of #CRDocHandler to
+ *destroy.
+ *
+ *The destructor of the #CRDocHandler class.
+ */
+void
+cr_doc_handler_destroy (CRDocHandler * a_this)
+{
+ g_return_if_fail (a_this);
+
+ if (a_this->priv) {
+ g_free (a_this->priv);
+ a_this->priv = NULL;
+ }
+ g_free (a_this);
+}
+
+/**
+ * cr_doc_handler_associate_a_parser:
+ *Associates a parser to the current document handler
+ *
+ *@a_this: the current instance of document handler.
+ *@a_parser: the parser to associate.
+ */
+void
+cr_doc_handler_associate_a_parser (CRDocHandler *a_this,
+ gpointer a_parser)
+{
+ g_return_if_fail (a_this && PRIVATE (a_this)
+ && a_parser) ;
+
+ PRIVATE (a_this)->parser = a_parser ;
+}
diff --git a/src/st/croco/cr-doc-handler.h b/src/st/croco/cr-doc-handler.h
new file mode 100644
index 0000000..d12673f
--- /dev/null
+++ b/src/st/croco/cr-doc-handler.h
@@ -0,0 +1,298 @@
+/* -*- Mode: C; indent-tabs-mode: nil; c-basic-offset: 8 -*- */
+
+/*
+ * This file is part of The Croco Library
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2.1 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * 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 Lesser General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ *
+ * See the COPYRIGHTS file for copyright information.
+ */
+
+#ifndef __CR_DOC_HANDLER_H__
+#define __CR_DOC_HANDLER_H__
+
+/**
+ *@file
+ *The declaration of the #CRDocumentHandler class.
+ *This class is actually the parsing events handler.
+ */
+
+#include <glib.h>
+#include "cr-utils.h"
+#include "cr-input.h"
+#include "cr-stylesheet.h"
+
+G_BEGIN_DECLS
+
+
+typedef struct _CRDocHandler CRDocHandler ;
+
+struct _CRDocHandlerPriv ;
+typedef struct _CRDocHandlerPriv CRDocHandlerPriv ;
+
+
+/**
+ *The SAC document handler.
+ *An instance of this class is to
+ *be passed to a parser. Then, during the parsing
+ *the parser calls the convenient function pointer
+ *whenever a particular event (a css construction) occurs.
+ */
+struct _CRDocHandler
+{
+ CRDocHandlerPriv *priv ;
+
+ /**
+ *This pointer is to be used by the application for
+ *it custom needs. It is there to extend the doc handler.
+ */
+ gpointer app_data ;
+
+ /**
+ *Is called at the beginning of the parsing of the document.
+ *@param a_this a pointer to the current instance of
+ *#CRDocHandler.
+ */
+ void (*start_document) (CRDocHandler *a_this) ;
+
+ /**
+ *Is called to notify the end of the parsing of the document.
+ *@param a_this a pointer to the current instance of
+ *#CRDocHandler.
+ */
+ void (*end_document) (CRDocHandler *a_this) ;
+
+ /**
+ *Is called to notify an at charset rule.
+ *@param a_this the document handler.
+ *@param a_charset the declared charset.
+ */
+ void (*charset) (CRDocHandler *a_this,
+ CRString *a_charset,
+ CRParsingLocation *a_charset_sym_location) ;
+
+ /**
+ *Is called to notify an import statement in
+ *the stylesheet.
+ *@param a_this the current instance of #CRDocHandler.
+ *@param a_media_list a doubly linked list of GString objects.
+ *Each GString object contains a string which is the
+ *destination media for style information.
+ *@param a_uri the uri of the imported style sheet.
+ *@param a_uri_default_ns the default namespace of URI
+ *@param a_location the parsing location of the '\@import'
+ *keyword.
+ *of the imported style sheet.
+ */
+ void (*import_style) (CRDocHandler *a_this,
+ GList *a_media_list,
+ CRString *a_uri,
+ CRString *a_uri_default_ns,
+ CRParsingLocation *a_location) ;
+
+ void (*import_style_result) (CRDocHandler *a_this,
+ GList *a_media_list,
+ CRString *a_uri,
+ CRString *a_uri_default_ns,
+ CRStyleSheet *a_sheet) ;
+
+ /**
+ *Is called to notify a namespace declaration.
+ *Not used yet.
+ *@param a_this the current instance of #CRDocHandler.
+ *@param a_prefix the prefix of the namespace.
+ *@param a_uri the uri of the namespace.
+ *@param a_location the location of the "@namespace" keyword.
+ */
+ void (*namespace_declaration) (CRDocHandler *a_this,
+ CRString *a_prefix,
+ CRString *a_uri,
+ CRParsingLocation *a_location) ;
+
+ /**
+ *Is called to notify a comment.
+ *@param a_this a pointer to the current instance
+ *of #CRDocHandler.
+ *@param a_comment the comment.
+ */
+ void (*comment) (CRDocHandler *a_this,
+ CRString *a_comment) ;
+
+ /**
+ *Is called to notify the beginning of a rule
+ *statement.
+ *@param a_this the current instance of #CRDocHandler.
+ *@param a_selector_list the list of selectors that precedes
+ *the rule declarations.
+ */
+ void (*start_selector) (CRDocHandler * a_this,
+ CRSelector *a_selector_list) ;
+
+ /**
+ *Is called to notify the end of a rule statement.
+ *@param a_this the current instance of #CRDocHandler.
+ *@param a_selector_list the list of selectors that precedes
+ *the rule declarations. This pointer is the same as
+ *the one passed to start_selector() ;
+ */
+ void (*end_selector) (CRDocHandler *a_this,
+ CRSelector *a_selector_list) ;
+
+
+ /**
+ *Is called to notify a declaration.
+ *@param a_this a pointer to the current instance
+ *of #CRDocHandler.
+ *@param a_name the name of the parsed property.
+ *@param a_expression a css expression that represents
+ *the value of the property. A css expression is
+ *actually a linked list of 'terms'. Each term can
+ *be linked to other using operators.
+ *
+ */
+ void (*property) (CRDocHandler *a_this,
+ CRString *a_name,
+ CRTerm *a_expression,
+ gboolean a_is_important) ;
+ /**
+ *Is called to notify the start of a font face statement.
+ *The parser invokes this method at the beginning of every
+ *font face statement in the style sheet. There will
+ *be a corresponding end_font_face () event for every
+ *start_font_face () event.
+ *
+ *@param a_this a pointer to the current instance of
+ *#CRDocHandler.
+ *@param a_location the parsing location of the "\@font-face"
+ *keyword.
+ */
+ void (*start_font_face) (CRDocHandler *a_this,
+ CRParsingLocation *a_location) ;
+
+ /**
+ *Is called to notify the end of a font face statement.
+ *@param a_this a pointer to the current instance of
+ *#CRDocHandler.
+ */
+ void (*end_font_face) (CRDocHandler *a_this) ;
+
+
+ /**
+ *Is called to notify the beginning of a media statement.
+ *The parser will invoke this method at the beginning of
+ *every media statement in the style sheet. There will be
+ *a corresponding end_media() event for every start_media()
+ *event.
+ *@param a_this a pointer to the current instance of
+ *#CRDocHandler.
+ *@param a_media_list a double linked list of
+ #CRString * objects.
+ *Each CRString objects is actually a destination media for
+ *the style information.
+ */
+ void (*start_media) (CRDocHandler *a_this,
+ GList *a_media_list,
+ CRParsingLocation *a_location) ;
+
+ /**
+ *Is called to notify the end of a media statement.
+ *@param a_this a pointer to the current instance
+ *of #CRDocHandler.
+ *@param a_media_list a double linked list of GString * objects.
+ *Each GString objects is actually a destination media for
+ *the style information.
+ */
+ void (*end_media) (CRDocHandler *a_this,
+ GList *a_media_list) ;
+
+ /**
+ *Is called to notify the beginning of a page statement.
+ *The parser invokes this function at the beginning of
+ *every page statement in the style sheet. There will be
+ *a corresponding end_page() event for every single
+ *start_page() event.
+ *@param a_this a pointer to the current instance of
+ *#CRDocHandler.
+ *@param a_name the name of the page (if any, null otherwise).
+ *@param a_pseudo_page the pseudo page (if any, null otherwise).
+ *@param a_location the parsing location of the "\@page" keyword.
+ */
+ void (*start_page) (CRDocHandler *a_this,
+ CRString *a_name,
+ CRString *a_pseudo_page,
+ CRParsingLocation *a_location) ;
+
+ /**
+ *Is called to notify the end of a page statement.
+ *@param a_this a pointer to the current instance of
+ *#CRDocHandler.
+ *@param a_name the name of the page (if any, null otherwise).
+ *@param a_pseudo_page the pseudo page (if any, null otherwise).
+ */
+ void (*end_page) (CRDocHandler *a_this,
+ CRString *a_name,
+ CRString *pseudo_page) ;
+
+ /**
+ *Is Called to notify an unknown at-rule not supported
+ *by this parser.
+ */
+ void (*ignorable_at_rule) (CRDocHandler *a_this,
+ CRString *a_name) ;
+
+ /**
+ *Is called to notify a parsing error. After this error
+ *the application must ignore the rule being parsed, if
+ *any. After completion of this callback,
+ *the parser will then try to resume the parsing,
+ *ignoring the current error.
+ */
+ void (*error) (CRDocHandler *a_this) ;
+
+ /**
+ *Is called to notify an unrecoverable parsing error.
+ *This is the place to put emergency routines that free allocated
+ *resources.
+ */
+ void (*unrecoverable_error) (CRDocHandler *a_this) ;
+
+ gboolean resolve_import ;
+ gulong ref_count ;
+} ;
+
+CRDocHandler * cr_doc_handler_new (void) ;
+
+enum CRStatus cr_doc_handler_set_result (CRDocHandler *a_this, gpointer a_result) ;
+
+enum CRStatus cr_doc_handler_get_result (CRDocHandler const *a_this, gpointer * a_result) ;
+
+enum CRStatus cr_doc_handler_set_ctxt (CRDocHandler *a_this, gpointer a_ctxt) ;
+
+enum CRStatus cr_doc_handler_get_ctxt (CRDocHandler const *a_this, gpointer * a_ctxt) ;
+
+enum CRStatus cr_doc_handler_set_default_sac_handler (CRDocHandler *a_this) ;
+
+void cr_doc_handler_associate_a_parser (CRDocHandler *a_this,
+ gpointer a_parser) ;
+
+void cr_doc_handler_ref (CRDocHandler *a_this) ;
+
+gboolean cr_doc_handler_unref (CRDocHandler *a_this) ;
+
+void cr_doc_handler_destroy (CRDocHandler *a_this) ;
+
+G_END_DECLS
+
+#endif /*__CR_DOC_HANDLER_H__*/
diff --git a/src/st/croco/cr-enc-handler.c b/src/st/croco/cr-enc-handler.c
new file mode 100644
index 0000000..65adc7a
--- /dev/null
+++ b/src/st/croco/cr-enc-handler.c
@@ -0,0 +1,184 @@
+/* -*- Mode: C; indent-tabs-mode: nil; c-basic-offset: 8 -*- */
+
+/*
+ * This file is part of The Croco Library
+ *
+ * Copyright (C) 2002-2003 Dodji Seketeli <dodji@seketeli.org>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2.1 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * 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 Lesser General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ */
+
+/*
+ *$Id$
+ */
+
+/**
+ *@file
+ *The definition of the #CREncHandler class.
+ */
+
+#include "cr-enc-handler.h"
+#include "cr-utils.h"
+
+#include <string.h>
+
+struct CREncAlias {
+ const gchar *name;
+ enum CREncoding encoding;
+};
+
+static struct CREncAlias gv_default_aliases[] = {
+ {"UTF-8", CR_UTF_8},
+ {"UTF_8", CR_UTF_8},
+ {"UTF8", CR_UTF_8},
+ {"UTF-16", CR_UTF_16},
+ {"UTF_16", CR_UTF_16},
+ {"UTF16", CR_UTF_16},
+ {"UCS1", CR_UCS_1},
+ {"UCS-1", CR_UCS_1},
+ {"UCS_1", CR_UCS_1},
+ {"ISO-8859-1", CR_UCS_1},
+ {"ISO_8859-1", CR_UCS_1},
+ {"UCS-1", CR_UCS_1},
+ {"UCS_1", CR_UCS_1},
+ {"UCS4", CR_UCS_4},
+ {"UCS-4", CR_UCS_4},
+ {"UCS_4", CR_UCS_4},
+ {"ASCII", CR_ASCII},
+ {0, 0}
+};
+
+static CREncHandler gv_default_enc_handlers[] = {
+ {CR_UCS_1, cr_utils_ucs1_to_utf8, cr_utils_utf8_to_ucs1,
+ cr_utils_ucs1_str_len_as_utf8, cr_utils_utf8_str_len_as_ucs1},
+
+ {CR_ISO_8859_1, cr_utils_ucs1_to_utf8, cr_utils_utf8_to_ucs1,
+ cr_utils_ucs1_str_len_as_utf8, cr_utils_utf8_str_len_as_ucs1},
+
+ {CR_ASCII, cr_utils_ucs1_to_utf8, cr_utils_utf8_to_ucs1,
+ cr_utils_ucs1_str_len_as_utf8, cr_utils_utf8_str_len_as_ucs1},
+
+ {0, NULL, NULL, NULL, NULL}
+};
+
+/**
+ * cr_enc_handler_get_instance:
+ *@a_enc: the encoding of the Handler.
+ *
+ *Gets the instance of encoding handler.
+ *This function implements a singleton pattern.
+ *
+ *Returns the instance of #CREncHandler.
+ */
+CREncHandler *
+cr_enc_handler_get_instance (enum CREncoding a_enc)
+{
+ gulong i = 0;
+
+ for (i = 0; gv_default_enc_handlers[i].encoding; i++) {
+ if (gv_default_enc_handlers[i].encoding == a_enc) {
+ return (CREncHandler *) & gv_default_enc_handlers[i];
+ }
+ }
+
+ return NULL;
+}
+
+/**
+ * cr_enc_handler_resolve_enc_alias:
+ *@a_alias_name: the encoding name.
+ *@a_enc: output param. The returned encoding type
+ *or 0 if the alias is not supported.
+ *
+ *Given an encoding name (called an alias name)
+ *the function returns the matching encoding type.
+ *
+ *Returns CR_OK upon successful completion, an error code otherwise.
+ */
+enum CRStatus
+cr_enc_handler_resolve_enc_alias (const guchar * a_alias_name,
+ enum CREncoding *a_enc)
+{
+ gulong i = 0;
+ guchar *alias_name_up = NULL;
+ enum CRStatus status = CR_ENCODING_NOT_FOUND_ERROR;
+
+ g_return_val_if_fail (a_alias_name != NULL, CR_BAD_PARAM_ERROR);
+
+ alias_name_up = (guchar *) g_ascii_strup ((const gchar *) a_alias_name, -1);
+
+ for (i = 0; gv_default_aliases[i].name; i++) {
+ if (!strcmp (gv_default_aliases[i].name, (const gchar *) alias_name_up)) {
+ *a_enc = gv_default_aliases[i].encoding;
+ status = CR_OK;
+ break;
+ }
+ }
+
+ return status;
+}
+
+/**
+ * cr_enc_handler_convert_input:
+ *@a_this: the current instance of #CREncHandler.
+ *@a_in: the input buffer to convert.
+ *@a_in_len: in/out parameter. The len of the input
+ *buffer to convert. After return, contains the number of
+ *bytes actually consumed.
+ *@a_out: output parameter. The converted output buffer.
+ *Must be freed by the buffer.
+ *@a_out_len: output parameter. The length of the output buffer.
+ *
+ *Converts a raw input buffer into an utf8 buffer.
+ *
+ *Returns CR_OK upon successful completion, an error code otherwise.
+ */
+enum CRStatus
+cr_enc_handler_convert_input (CREncHandler * a_this,
+ const guchar * a_in,
+ gulong * a_in_len,
+ guchar ** a_out, gulong * a_out_len)
+{
+ enum CRStatus status = CR_OK;
+
+ g_return_val_if_fail (a_this && a_in && a_in_len && a_out,
+ CR_BAD_PARAM_ERROR);
+
+ if (a_this->decode_input == NULL)
+ return CR_OK;
+
+ if (a_this->enc_str_len_as_utf8) {
+ status = a_this->enc_str_len_as_utf8 (a_in,
+ &a_in[*a_in_len - 1],
+ a_out_len);
+
+ g_return_val_if_fail (status == CR_OK, status);
+ } else {
+ *a_out_len = *a_in_len;
+ }
+
+ *a_out = g_malloc0 (*a_out_len);
+
+ status = a_this->decode_input (a_in, a_in_len, *a_out, a_out_len);
+
+ if (status != CR_OK) {
+ g_free (*a_out);
+ *a_out = NULL;
+ }
+
+ g_return_val_if_fail (status == CR_OK, status);
+
+ return CR_OK;
+}
diff --git a/src/st/croco/cr-enc-handler.h b/src/st/croco/cr-enc-handler.h
new file mode 100644
index 0000000..0727764
--- /dev/null
+++ b/src/st/croco/cr-enc-handler.h
@@ -0,0 +1,94 @@
+/* -*- Mode: C; indent-tabs-mode: nil; c-basic-offset: 8 -*- */
+
+/*
+ * This file is part of The Croco Library
+ *
+ * Copyright (C) 2002-2003 Dodji Seketeli <dodji@seketeli.org>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2.1 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * 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 Lesser General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ */
+
+/*
+ *$Id$
+ */
+
+/**
+ *@file:
+ *The declaration of the #CREncHandler class.
+ *
+ */
+
+#ifndef __CR_ENC_HANDLER_H__
+#define __CR_ENC_HANDLER_H__
+
+#include "cr-utils.h"
+
+G_BEGIN_DECLS
+
+
+typedef struct _CREncHandler CREncHandler ;
+
+typedef enum CRStatus (*CREncInputFunc) (const guchar * a_in,
+ gulong *a_in_len,
+ guchar *a_out,
+ gulong *a_out_len) ;
+
+typedef enum CRStatus (*CREncOutputFunc) (const guchar * a_in,
+ gulong *a_in_len,
+ guchar *a_out,
+ gulong *a_out_len) ;
+
+typedef enum CRStatus (*CREncInputStrLenAsUtf8Func)
+(const guchar *a_in_start,
+ const guchar *a_in_end,
+ gulong *a_in_size);
+
+typedef enum CRStatus (*CREncUtf8StrLenAsOutputFunc)
+(const guchar *a_in_start,
+ const guchar *a_in_end,
+ gulong *a_in_size) ;
+
+/**
+ *This class is responsible of the
+ *the encoding conversions stuffs in
+ *libcroco.
+ */
+
+struct _CREncHandler
+{
+ enum CREncoding encoding ;
+ CREncInputFunc decode_input ;
+ CREncInputFunc encode_output ;
+ CREncInputStrLenAsUtf8Func enc_str_len_as_utf8 ;
+ CREncUtf8StrLenAsOutputFunc utf8_str_len_as_enc ;
+} ;
+
+CREncHandler *
+cr_enc_handler_get_instance (enum CREncoding a_enc) ;
+
+enum CRStatus
+cr_enc_handler_resolve_enc_alias (const guchar *a_alias_name,
+ enum CREncoding *a_enc) ;
+
+enum CRStatus
+cr_enc_handler_convert_input (CREncHandler *a_this,
+ const guchar *a_in,
+ gulong *a_in_len,
+ guchar **a_out,
+ gulong *a_out_len) ;
+
+G_END_DECLS
+
+#endif /*__CR_ENC_HANDLER_H__*/
diff --git a/src/st/croco/cr-fonts.c b/src/st/croco/cr-fonts.c
new file mode 100644
index 0000000..a64ffc0
--- /dev/null
+++ b/src/st/croco/cr-fonts.c
@@ -0,0 +1,948 @@
+/* -*- Mode: C; indent-tabs-mode:nil; c-basic-offset: 8-*- */
+
+/*
+ * This file is part of The Croco Library
+ *
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2.1 of
+ * the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * 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 Lesser General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ *
+ *See COPYRIGHTS file for copyright information
+ */
+
+#include "cr-fonts.h"
+#include <string.h>
+
+static enum CRStatus
+cr_font_family_to_string_real (CRFontFamily const * a_this,
+ gboolean a_walk_list, GString ** a_string)
+{
+ guchar const *name = NULL;
+ enum CRStatus result = CR_OK;
+
+ if (!*a_string) {
+ *a_string = g_string_new (NULL);
+ g_return_val_if_fail (*a_string,
+ CR_INSTANCIATION_FAILED_ERROR);
+ }
+
+ if (!a_this) {
+ g_string_append (*a_string, "NULL");
+ return CR_OK;
+ }
+
+ switch (a_this->type) {
+ case FONT_FAMILY_SANS_SERIF:
+ name = (guchar const *) "sans-serif";
+ break;
+
+ case FONT_FAMILY_SERIF:
+ name = (guchar const *) "sans-serif";
+ break;
+
+ case FONT_FAMILY_CURSIVE:
+ name = (guchar const *) "cursive";
+ break;
+
+ case FONT_FAMILY_FANTASY:
+ name = (guchar const *) "fantasy";
+ break;
+
+ case FONT_FAMILY_MONOSPACE:
+ name = (guchar const *) "monospace";
+ break;
+
+ case FONT_FAMILY_NON_GENERIC:
+ name = (guchar const *) a_this->name;
+ break;
+
+ default:
+ name = NULL;
+ break;
+ }
+
+ if (name) {
+ if (a_this->prev) {
+ g_string_append_printf (*a_string, ", %s", name);
+ } else {
+ g_string_append (*a_string, (const gchar *) name);
+ }
+ }
+ if (a_walk_list == TRUE && a_this->next) {
+ result = cr_font_family_to_string_real (a_this->next,
+ TRUE, a_string);
+ }
+ return result;
+}
+
+static const gchar *
+cr_predefined_absolute_font_size_to_string (enum CRPredefinedAbsoluteFontSize
+ a_code)
+{
+ gchar const *str = NULL;
+
+ switch (a_code) {
+ case FONT_SIZE_XX_SMALL:
+ str = "xx-small";
+ break;
+ case FONT_SIZE_X_SMALL:
+ str = "x-small";
+ break;
+ case FONT_SIZE_SMALL:
+ str = "small";
+ break;
+ case FONT_SIZE_MEDIUM:
+ str = "medium";
+ break;
+ case FONT_SIZE_LARGE:
+ str = "large";
+ break;
+ case FONT_SIZE_X_LARGE:
+ str = "x-large";
+ break;
+ case FONT_SIZE_XX_LARGE:
+ str = "xx-large";
+ break;
+ default:
+ str = "unknown absolute font size value";
+ }
+ return str;
+}
+
+static const gchar *
+cr_relative_font_size_to_string (enum CRRelativeFontSize a_code)
+{
+ gchar const *str = NULL;
+
+ switch (a_code) {
+ case FONT_SIZE_LARGER:
+ str = "larger";
+ break;
+ case FONT_SIZE_SMALLER:
+ str = "smaller";
+ break;
+ default:
+ str = "unknown relative font size value";
+ break;
+ }
+ return str;
+}
+
+/**
+ * cr_font_family_new:
+ * @a_type: the type of font family to create.
+ * @a_name: the name of the font family.
+ *
+ * create a font family.
+ *
+ * Returns the newly built font family.
+ */
+CRFontFamily *
+cr_font_family_new (enum CRFontFamilyType a_type, guchar * a_name)
+{
+ CRFontFamily *result = NULL;
+
+ result = g_try_malloc (sizeof (CRFontFamily));
+
+ if (!result) {
+ cr_utils_trace_info ("Out of memory");
+ return NULL;
+ }
+
+ memset (result, 0, sizeof (CRFontFamily));
+ result->type = a_type;
+
+ cr_font_family_set_name (result, a_name);
+
+ return result;
+}
+
+/**
+ * cr_font_family_to_string:
+ * @a_this: the current instance of #CRFontFamily.
+ * @a_walk_font_family_list: whether the serialize the entire list.
+ *
+ * Returns the seriliazed font family. The caller has to free it using
+ * g_free().
+ */
+guchar *
+cr_font_family_to_string (CRFontFamily const * a_this,
+ gboolean a_walk_font_family_list)
+{
+ enum CRStatus status = CR_OK;
+ guchar *result = NULL;
+ GString *stringue = NULL;
+
+ if (!a_this) {
+ result = (guchar *) g_strdup ("NULL");
+ g_return_val_if_fail (result, NULL);
+ return result;
+ }
+ status = cr_font_family_to_string_real (a_this,
+ a_walk_font_family_list,
+ &stringue);
+
+ if (status == CR_OK && stringue) {
+ result = (guchar *) g_string_free (stringue, FALSE);
+ stringue = NULL;
+
+ } else {
+ if (stringue) {
+ g_string_free (stringue, TRUE);
+ stringue = NULL;
+ }
+ }
+
+ return result;
+}
+
+/**
+ * cr_font_family_set_name:
+ * @a_this: the current instance of #CRFontFamily.
+ * @a_name: the new name
+ *
+ * Returns CR_OK upon successful completion, an error code otherwise.
+ */
+enum CRStatus
+cr_font_family_set_name (CRFontFamily * a_this, guchar * a_name)
+{
+ g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR);
+
+ /*
+ *only non generic font families can have a name
+ */
+
+ if (a_this->type != FONT_FAMILY_NON_GENERIC) {
+ return CR_BAD_PARAM_ERROR;
+ }
+
+ if (a_this->name) {
+ g_free (a_this->name);
+ a_this->name = NULL;
+ }
+
+ a_this->name = a_name;
+ return CR_OK;
+}
+
+/**
+ * cr_font_family_append:
+ * @a_this: the current instance of #CRFontFamily.
+ * @a_family_to_append: the font family to append to the list
+ *
+ * Returns the new font family list.
+ */
+CRFontFamily *
+cr_font_family_append (CRFontFamily * a_this,
+ CRFontFamily * a_family_to_append)
+{
+ CRFontFamily *cur_ff = NULL;
+
+ g_return_val_if_fail (a_family_to_append, NULL);
+
+ if (!a_this)
+ return a_family_to_append;
+
+ for (cur_ff = a_this; cur_ff && cur_ff->next; cur_ff = cur_ff->next) ;
+
+ cur_ff->next = a_family_to_append;
+ a_family_to_append->prev = cur_ff;
+
+ return a_this;
+
+}
+
+/**
+ * cr_font_family_prepend:
+ * @a_this: the current instance #CRFontFamily.
+ * @a_family_to_prepend: the font family to prepend to the list.
+ *
+ * Returns the font family list.
+ */
+CRFontFamily *
+cr_font_family_prepend (CRFontFamily * a_this,
+ CRFontFamily * a_family_to_prepend)
+{
+ g_return_val_if_fail (a_this && a_family_to_prepend, NULL);
+
+ if (!a_this)
+ return a_family_to_prepend;
+
+ a_family_to_prepend->next = a_this;
+ a_this->prev = a_family_to_prepend;
+
+ return a_family_to_prepend;
+}
+
+/**
+ * cr_font_family_destroy:
+ * @a_this: the current instance of #CRFontFamily.
+ *
+ * Returns CR_OK upon successful completion, an error code otherwise.
+ */
+enum CRStatus
+cr_font_family_destroy (CRFontFamily * a_this)
+{
+ CRFontFamily *cur_ff = NULL;
+
+ g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR);
+
+ for (cur_ff = a_this; cur_ff && cur_ff->next; cur_ff = cur_ff->next) ;
+
+ for (; cur_ff; cur_ff = cur_ff->prev) {
+ if (a_this->name) {
+ g_free (a_this->name);
+ a_this->name = NULL;
+ }
+
+ if (cur_ff->next) {
+ g_free (cur_ff->next);
+
+ }
+
+ if (cur_ff->prev == NULL) {
+ g_free (a_this);
+ }
+ }
+
+ return CR_OK;
+}
+
+/***************************************************
+ *'font-size' manipulation functions definitions
+ ***************************************************/
+
+/**
+ * cr_font_size_new:
+ *
+ * Returns the newly created font size.
+ */
+CRFontSize *
+cr_font_size_new (void)
+{
+ CRFontSize *result = NULL;
+
+ result = g_try_malloc (sizeof (CRFontSize));
+ if (!result) {
+ cr_utils_trace_info ("Out of memory");
+ return NULL;
+ }
+ memset (result, 0, sizeof (CRFontSize));
+
+ return result;
+}
+
+/**
+ * cr_font_size_clear:
+ * @a_this: the current instance of #CRFontSize
+ *
+ * Returns CR_OK upon successful completion, an error code otherwise.
+ */
+enum CRStatus
+cr_font_size_clear (CRFontSize * a_this)
+{
+ g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR);
+
+ switch (a_this->type) {
+ case PREDEFINED_ABSOLUTE_FONT_SIZE:
+ case RELATIVE_FONT_SIZE:
+ case INHERITED_FONT_SIZE:
+ memset (a_this, 0, sizeof (CRFontSize));
+ break;
+
+ case ABSOLUTE_FONT_SIZE:
+ memset (a_this, 0, sizeof (CRFontSize));
+ break;
+
+ default:
+ return CR_UNKNOWN_TYPE_ERROR;
+ }
+
+ return CR_OK;
+}
+
+/**
+ * cr_font_size_copy:
+ * @a_dst: the destination #CRFontSize (where to copy to).
+ * @a_src: the source #CRFontSize (where to copy from).
+ *
+ * Returns CR_OK upon successful completion, an error code otherwise.
+ */
+enum CRStatus
+cr_font_size_copy (CRFontSize * a_dst, CRFontSize const * a_src)
+{
+ g_return_val_if_fail (a_dst && a_src, CR_BAD_PARAM_ERROR);
+
+ switch (a_src->type) {
+ case PREDEFINED_ABSOLUTE_FONT_SIZE:
+ case RELATIVE_FONT_SIZE:
+ case INHERITED_FONT_SIZE:
+ cr_font_size_clear (a_dst);
+ memcpy (a_dst, a_src, sizeof (CRFontSize));
+ break;
+
+ case ABSOLUTE_FONT_SIZE:
+ cr_font_size_clear (a_dst);
+ cr_num_copy (&a_dst->value.absolute,
+ &a_src->value.absolute);
+ a_dst->type = a_src->type;
+ break;
+
+ default:
+ return CR_UNKNOWN_TYPE_ERROR;
+ }
+ return CR_OK;
+}
+
+/**
+ * cr_font_size_set_predefined_absolute_font_size:
+ * @a_this: the current instance of #CRFontSize.
+ * @a_predefined: what to set.
+ *
+ * Returns CR_OK upon successful completion, an error code otherwise.
+ */
+enum CRStatus
+cr_font_size_set_predefined_absolute_font_size (CRFontSize *a_this,
+ enum CRPredefinedAbsoluteFontSize a_predefined)
+{
+ g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR) ;
+ g_return_val_if_fail (a_predefined >= FONT_SIZE_XX_SMALL
+ && a_predefined < NB_PREDEFINED_ABSOLUTE_FONT_SIZES,
+ CR_BAD_PARAM_ERROR) ;
+
+ a_this->type = PREDEFINED_ABSOLUTE_FONT_SIZE ;
+ a_this->value.predefined = a_predefined ;
+
+ return CR_OK ;
+}
+
+/**
+ * cr_font_size_set_relative_font_size:
+ * @a_this: the current instance of #CRFontSize
+ * @a_relative: the new relative font size
+ *
+ * Returns CR_OK upon successful completion, an error code otherwise.
+ */
+enum CRStatus
+cr_font_size_set_relative_font_size (CRFontSize *a_this,
+ enum CRRelativeFontSize a_relative)
+{
+ g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR) ;
+ g_return_val_if_fail (a_relative >= FONT_SIZE_LARGER
+ && a_relative < NB_RELATIVE_FONT_SIZE,
+ CR_BAD_PARAM_ERROR) ;
+
+ a_this->type = RELATIVE_FONT_SIZE ;
+ a_this->value.relative = a_relative ;
+ return CR_OK ;
+}
+
+/**
+ * cr_font_size_set_absolute_font_size:
+ * @a_this: the current instance of #CRFontSize
+ * @a_num_type: the type of number to set.
+ * @a_value: the actual value to set.
+ *
+ * Returns CR_OK upon successful completion, an error code otherwise.
+ */
+enum CRStatus
+cr_font_size_set_absolute_font_size (CRFontSize *a_this,
+ enum CRNumType a_num_type,
+ gdouble a_value)
+{
+ g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR) ;
+ g_return_val_if_fail (a_num_type >= NUM_AUTO
+ && a_num_type < NB_NUM_TYPE,
+ CR_BAD_PARAM_ERROR) ;
+
+ a_this->type = ABSOLUTE_FONT_SIZE ;
+ cr_num_set (&a_this->value.absolute,
+ a_value, a_num_type) ;
+ return CR_OK ;
+}
+
+/**
+ * cr_font_size_set_to_inherit:
+ * @a_this: the current instance of #CRFontSize
+ *
+ * Returns CR_OK upon successful completion, an error code otherwise.
+ */
+enum CRStatus
+cr_font_size_set_to_inherit (CRFontSize *a_this)
+{
+ g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR) ;
+
+ cr_font_size_clear (a_this) ;
+ a_this->type = INHERITED_FONT_SIZE ;
+
+ return CR_OK ;
+}
+
+/**
+ * cr_font_size_is_set_to_inherit:
+ * @a_this: the current instance of #CRFontSize.
+ *
+ * Returns TRUE if the current instance is set to 'inherit'.
+ */
+gboolean
+cr_font_size_is_set_to_inherit (CRFontSize const *a_this)
+{
+ g_return_val_if_fail (a_this, FALSE) ;
+
+ return a_this->type == INHERITED_FONT_SIZE ;
+}
+
+/**
+ * cr_font_size_to_string:
+ * @a_this: the current instance of #CRFontSize
+ *
+ * Returns the serialized form of #CRFontSize. The returned string
+ * has to bee freed using g_free().
+ */
+gchar *
+cr_font_size_to_string (CRFontSize const * a_this)
+{
+ gchar *str = NULL;
+
+ if (!a_this) {
+ str = g_strdup ("NULL");
+ g_return_val_if_fail (str, NULL);
+ return str;
+ }
+ switch (a_this->type) {
+ case PREDEFINED_ABSOLUTE_FONT_SIZE:
+ str = g_strdup (cr_predefined_absolute_font_size_to_string
+ (a_this->value.predefined));
+ break;
+ case ABSOLUTE_FONT_SIZE:
+ str = (gchar *) cr_num_to_string (&a_this->value.absolute);
+ break;
+ case RELATIVE_FONT_SIZE:
+ str = g_strdup (cr_relative_font_size_to_string
+ (a_this->value.relative));
+ break;
+ case INHERITED_FONT_SIZE:
+ str = g_strdup ("inherit");
+ break;
+ default:
+ break;
+ }
+ return str;
+}
+
+/**
+ * cr_font_size_get_smaller_predefined:
+ * @a_font_size: the font size to consider.
+ * @a_smaller_size: out parameter. The a smaller value than @a_font_size.
+ */
+void
+cr_font_size_get_smaller_predefined_font_size
+ (enum CRPredefinedAbsoluteFontSize a_font_size,
+ enum CRPredefinedAbsoluteFontSize *a_smaller_size)
+{
+ enum CRPredefinedAbsoluteFontSize result = FONT_SIZE_MEDIUM ;
+
+ g_return_if_fail (a_smaller_size) ;
+ g_return_if_fail (a_font_size < NB_PREDEFINED_ABSOLUTE_FONT_SIZES
+ && a_font_size >= FONT_SIZE_XX_SMALL) ;
+
+ switch (a_font_size) {
+ case FONT_SIZE_XX_SMALL:
+ result = FONT_SIZE_XX_SMALL ;
+ break ;
+ case FONT_SIZE_X_SMALL:
+ result = FONT_SIZE_XX_SMALL ;
+ break ;
+ case FONT_SIZE_SMALL:
+ result = FONT_SIZE_X_SMALL;
+ break ;
+ case FONT_SIZE_MEDIUM:
+ result = FONT_SIZE_SMALL;
+ break ;
+ case FONT_SIZE_LARGE:
+ result = FONT_SIZE_MEDIUM;
+ break ;
+ case FONT_SIZE_X_LARGE:
+ result = FONT_SIZE_LARGE;
+ break ;
+ case FONT_SIZE_XX_LARGE:
+ result = FONT_SIZE_XX_LARGE;
+ break ;
+ case FONT_SIZE_INHERIT:
+ cr_utils_trace_info ("can't return a smaller size for FONT_SIZE_INHERIT") ;
+ result = FONT_SIZE_MEDIUM ;
+ break ;
+ default:
+ cr_utils_trace_info ("Unknown FONT_SIZE") ;
+ result = FONT_SIZE_MEDIUM ;
+ break ;
+ }
+ *a_smaller_size = result ;
+}
+
+
+/**
+ * cr_font_size_get_larger_predefined_font_size:
+ * @a_font_size: the font size to consider.
+ * @a_larger_size: out parameter. the font size considered larger than
+ * @a_font_size.
+ *
+ */
+void
+cr_font_size_get_larger_predefined_font_size
+ (enum CRPredefinedAbsoluteFontSize a_font_size,
+ enum CRPredefinedAbsoluteFontSize *a_larger_size)
+{
+ enum CRPredefinedAbsoluteFontSize result = FONT_SIZE_MEDIUM ;
+
+ g_return_if_fail (a_larger_size) ;
+ g_return_if_fail (a_font_size >= FONT_SIZE_XX_SMALL
+ && a_font_size < NB_PREDEFINED_ABSOLUTE_FONT_SIZES) ;
+
+ switch (a_font_size) {
+ case FONT_SIZE_XX_SMALL:
+ result = FONT_SIZE_X_SMALL ;
+ break ;
+ case FONT_SIZE_X_SMALL:
+ result = FONT_SIZE_SMALL ;
+ break ;
+ case FONT_SIZE_SMALL:
+ result = FONT_SIZE_MEDIUM;
+ break ;
+ case FONT_SIZE_MEDIUM:
+ result = FONT_SIZE_LARGE;
+ break ;
+ case FONT_SIZE_LARGE:
+ result = FONT_SIZE_X_LARGE;
+ break ;
+ case FONT_SIZE_X_LARGE:
+ result = FONT_SIZE_XX_LARGE ;
+ break ;
+ case FONT_SIZE_XX_LARGE:
+ result = FONT_SIZE_XX_LARGE;
+ break ;
+ case FONT_SIZE_INHERIT:
+ cr_utils_trace_info ("can't return a bigger size for FONT_SIZE_INHERIT") ;
+ result = FONT_SIZE_MEDIUM ;
+ break ;
+ default:
+ cr_utils_trace_info ("Unknown FONT_SIZE") ;
+ result = FONT_SIZE_MEDIUM ;
+ break ;
+ }
+ *a_larger_size = result ;
+}
+
+/**
+ * cr_font_size_is_predefined_absolute_font_size:
+ * @a_font_size: the font size to consider.
+ *
+ * Returns TRUE if the instance is an predefined absolute font size, FALSE
+ * otherwise.
+ */
+gboolean
+cr_font_size_is_predefined_absolute_font_size
+ (enum CRPredefinedAbsoluteFontSize a_font_size)
+{
+ if (a_font_size >= FONT_SIZE_XX_SMALL
+ && a_font_size < NB_PREDEFINED_ABSOLUTE_FONT_SIZES) {
+ return TRUE ;
+ } else {
+ return FALSE ;
+ }
+}
+
+/**
+ * cr_font_size_adjust_to_string:
+ * @a_this: the instance of #CRFontSizeAdjust.
+ *
+ * Returns the serialized form of #CRFontSizeAdjust
+ */
+gchar *
+cr_font_size_adjust_to_string (CRFontSizeAdjust const * a_this)
+{
+ gchar *str = NULL;
+
+ if (!a_this) {
+ str = g_strdup ("NULL");
+ g_return_val_if_fail (str, NULL);
+ return str;
+ }
+
+ switch (a_this->type) {
+ case FONT_SIZE_ADJUST_NONE:
+ str = g_strdup ("none");
+ break;
+ case FONT_SIZE_ADJUST_NUMBER:
+ if (a_this->num)
+ str = (gchar *) cr_num_to_string (a_this->num);
+ else
+ str = g_strdup ("unknown font-size-adjust property value"); /* Should raise an error no?*/
+ break;
+ case FONT_SIZE_ADJUST_INHERIT:
+ str = g_strdup ("inherit");
+ }
+ return str;
+}
+
+/**
+ * cr_font_style_to_string:
+ * @a_code: the current instance of #CRFontStyle .
+ *
+ * Returns the serialized #CRFontStyle. The caller must free the returned
+ * string using g_free().
+ */
+const gchar *
+cr_font_style_to_string (enum CRFontStyle a_code)
+{
+ gchar *str = NULL;
+
+ switch (a_code) {
+ case FONT_STYLE_NORMAL:
+ str = (gchar *) "normal";
+ break;
+ case FONT_STYLE_ITALIC:
+ str = (gchar *) "italic";
+ break;
+ case FONT_STYLE_OBLIQUE:
+ str = (gchar *) "oblique";
+ break;
+ case FONT_STYLE_INHERIT:
+ str = (gchar *) "inherit";
+ break;
+ default:
+ str = (gchar *) "unknown font style value";
+ break;
+ }
+ return str;
+}
+
+/**
+ * cr_font_variant_to_string:
+ * @a_code: the current instance of #CRFontVariant.
+ *
+ * Returns the serialized form of #CRFontVariant. The caller has
+ * to free the returned string using g_free().
+ */
+const gchar *
+cr_font_variant_to_string (enum CRFontVariant a_code)
+{
+ gchar *str = NULL;
+
+ switch (a_code) {
+ case FONT_VARIANT_NORMAL:
+ str = (gchar *) "normal";
+ break;
+ case FONT_VARIANT_SMALL_CAPS:
+ str = (gchar *) "small-caps";
+ break;
+ case FONT_VARIANT_INHERIT:
+ str = (gchar *) "inherit";
+ break;
+ }
+ return str;
+}
+
+/**
+ * cr_font_weight_get_bolder:
+ * @a_weight: the #CRFontWeight to consider.
+ *
+ * Returns a font weight bolder than @a_weight
+ */
+enum CRFontWeight
+cr_font_weight_get_bolder (enum CRFontWeight a_weight)
+{
+ if (a_weight == FONT_WEIGHT_INHERIT) {
+ cr_utils_trace_info ("can't return a bolder weight for FONT_WEIGHT_INHERIT") ;
+ return a_weight;
+ } else if (a_weight >= FONT_WEIGHT_900) {
+ return FONT_WEIGHT_900 ;
+ } else if (a_weight < FONT_WEIGHT_NORMAL) {
+ return FONT_WEIGHT_NORMAL ;
+ } else if (a_weight == FONT_WEIGHT_BOLDER
+ || a_weight == FONT_WEIGHT_LIGHTER) {
+ cr_utils_trace_info ("FONT_WEIGHT_BOLDER or FONT_WEIGHT_LIGHTER should not appear here") ;
+ return FONT_WEIGHT_NORMAL ;
+ } else {
+ return a_weight << 1 ;
+ }
+}
+
+/**
+ * cr_font_weight_to_string:
+ * @a_code: the font weight to consider.
+ *
+ * Returns the serialized form of #CRFontWeight.
+ */
+const gchar *
+cr_font_weight_to_string (enum CRFontWeight a_code)
+{
+ gchar *str = NULL;
+
+ switch (a_code) {
+ case FONT_WEIGHT_NORMAL:
+ str = (gchar *) "normal";
+ break;
+ case FONT_WEIGHT_BOLD:
+ str = (gchar *) "bold";
+ break;
+ case FONT_WEIGHT_BOLDER:
+ str = (gchar *) "bolder";
+ break;
+ case FONT_WEIGHT_LIGHTER:
+ str = (gchar *) "lighter";
+ break;
+ case FONT_WEIGHT_100:
+ str = (gchar *) "100";
+ break;
+ case FONT_WEIGHT_200:
+ str = (gchar *) "200";
+ break;
+ case FONT_WEIGHT_300:
+ str = (gchar *) "300";
+ break;
+ case FONT_WEIGHT_400:
+ str = (gchar *) "400";
+ break;
+ case FONT_WEIGHT_500:
+ str = (gchar *) "500";
+ break;
+ case FONT_WEIGHT_600:
+ str = (gchar *) "600";
+ break;
+ case FONT_WEIGHT_700:
+ str = (gchar *) "700";
+ break;
+ case FONT_WEIGHT_800:
+ str = (gchar *) "800";
+ break;
+ case FONT_WEIGHT_900:
+ str = (gchar *) "900";
+ break;
+ case FONT_WEIGHT_INHERIT:
+ str = (gchar *) "inherit";
+ break;
+ default:
+ str = (gchar *) "unknown font-weight property value";
+ break;
+ }
+ return str;
+}
+
+/**
+ * cr_font_stretch_to_string:
+ * @a_code: the instance of #CRFontStretch to consider.
+ *
+ * Returns the serialized form of #CRFontStretch.
+ */
+const gchar *
+cr_font_stretch_to_string (enum CRFontStretch a_code)
+{
+ gchar *str = NULL;
+
+ switch (a_code) {
+ case FONT_STRETCH_NORMAL:
+ str = (gchar *) "normal";
+ break;
+ case FONT_STRETCH_WIDER:
+ str = (gchar *) "wider";
+ break;
+ case FONT_STRETCH_NARROWER:
+ str = (gchar *) "narrower";
+ break;
+ case FONT_STRETCH_ULTRA_CONDENSED:
+ str = (gchar *) "ultra-condensed";
+ break;
+ case FONT_STRETCH_EXTRA_CONDENSED:
+ str = (gchar *) "extra-condensed";
+ break;
+ case FONT_STRETCH_CONDENSED:
+ str = (gchar *) "condensed";
+ break;
+ case FONT_STRETCH_SEMI_CONDENSED:
+ str = (gchar *) "semi-condensed";
+ break;
+ case FONT_STRETCH_SEMI_EXPANDED:
+ str = (gchar *) "semi-expanded";
+ break;
+ case FONT_STRETCH_EXPANDED:
+ str = (gchar *) "expanded";
+ break;
+ case FONT_STRETCH_EXTRA_EXPANDED:
+ str = (gchar *) "extra-expaned";
+ break;
+ case FONT_STRETCH_ULTRA_EXPANDED:
+ str = (gchar *) "ultra-expanded";
+ break;
+ case FONT_STRETCH_INHERIT:
+ str = (gchar *) "inherit";
+ break;
+ }
+ return str;
+}
+
+/**
+ * cr_font_size_destroy:
+ * @a_font_size: the font size to destroy
+ *
+ */
+void
+cr_font_size_destroy (CRFontSize * a_font_size)
+{
+ g_return_if_fail (a_font_size);
+
+ g_free (a_font_size) ;
+}
+
+/*******************************************************
+ *'font-size-adjust' manipulation function definition
+ *******************************************************/
+
+/**
+ * cr_font_size_adjust_new:
+ *
+ * Returns a newly built instance of #CRFontSizeAdjust
+ */
+CRFontSizeAdjust *
+cr_font_size_adjust_new (void)
+{
+ CRFontSizeAdjust *result = NULL;
+
+ result = g_try_malloc (sizeof (CRFontSizeAdjust));
+ if (!result) {
+ cr_utils_trace_info ("Out of memory");
+ return NULL;
+ }
+ memset (result, 0, sizeof (CRFontSizeAdjust));
+
+ return result;
+}
+
+/**
+ * cr_font_size_adjust_destroy:
+ * @a_this: the current instance of #CRFontSizeAdjust.
+ *
+ */
+void
+cr_font_size_adjust_destroy (CRFontSizeAdjust * a_this)
+{
+ g_return_if_fail (a_this);
+
+ if (a_this->type == FONT_SIZE_ADJUST_NUMBER && a_this->num) {
+ cr_num_destroy (a_this->num);
+ a_this->num = NULL;
+ }
+}
diff --git a/src/st/croco/cr-fonts.h b/src/st/croco/cr-fonts.h
new file mode 100644
index 0000000..9eaeeeb
--- /dev/null
+++ b/src/st/croco/cr-fonts.h
@@ -0,0 +1,315 @@
+/* -*- Mode: C; indent-tabs-mode:nil; c-basic-offset: 8-*- */
+
+/*
+ * This file is part of The Croco Library
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2.1 of
+ * the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * 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 Lesser General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ *
+ * Author: Dodji Seketeli
+ * See COPYRIGHTS file for copyright information.
+ */
+
+#ifndef __CR_FONTS_H__
+#define __CR_FONTS_H__
+
+#include "cr-utils.h"
+#include "cr-num.h"
+
+/**
+ *@file
+ *Various type declarations about font selection related
+ *properties.
+ */
+G_BEGIN_DECLS
+
+
+enum CRFontFamilyType
+{
+ FONT_FAMILY_SANS_SERIF,
+ FONT_FAMILY_SERIF,
+ FONT_FAMILY_CURSIVE,
+ FONT_FAMILY_FANTASY,
+ FONT_FAMILY_MONOSPACE,
+ FONT_FAMILY_NON_GENERIC,
+ FONT_FAMILY_INHERIT,
+ /**/
+ NB_FONT_FAMILIE_TYPES
+} ;
+
+typedef struct _CRFontFamily CRFontFamily ;
+
+struct _CRFontFamily
+{
+ enum CRFontFamilyType type ;
+
+ /*
+ *The name of the font family, in case
+ *it is non generic.
+ *Is set only if the type is FONT_FAMILY_NON_GENERIC.
+ */
+ guchar *name ;
+
+ CRFontFamily *next ;
+ CRFontFamily *prev ;
+} ;
+
+
+/**
+ *The different types
+ *of absolute font size.
+ *This is used by the 'font-size'
+ *property defined in css2 spec
+ *in chapter 15.2.4 .
+ *These values a indexes of
+ *table of size so please, do not
+ *change their definition order unless
+ *you know what you are doing.
+ */
+enum CRPredefinedAbsoluteFontSize
+{
+ FONT_SIZE_XX_SMALL=0,
+ FONT_SIZE_X_SMALL,
+ FONT_SIZE_SMALL,
+ FONT_SIZE_MEDIUM,
+ FONT_SIZE_LARGE,
+ FONT_SIZE_X_LARGE,
+ FONT_SIZE_XX_LARGE,
+ FONT_SIZE_INHERIT,
+ NB_PREDEFINED_ABSOLUTE_FONT_SIZES
+} ;
+
+/**
+ *The different types
+ *of relative font size.
+ *This is used by the 'font-size'
+ *property defined in css2 spec
+ *in chapter 15.2.4 .
+ *These values a indexes of
+ *table of size so please, do not
+ *change their definition order unless
+ *you know what you are doing.
+ */
+enum CRRelativeFontSize
+{
+ FONT_SIZE_LARGER,
+ FONT_SIZE_SMALLER,
+ NB_RELATIVE_FONT_SIZE
+} ;
+
+/**
+ *The type of font-size property.
+ *Used to define the type of #CRFontSize .
+ *See css2 spec chapter 15.2.4 to understand.
+ */
+enum CRFontSizeType {
+ /**
+ *If the type of #CRFontSize is
+ *PREDEFINED_ABSOLUTE_FONT_SIZE,
+ *the CRFontSize::value.predefined_absolute
+ *field will be defined.
+ */
+ PREDEFINED_ABSOLUTE_FONT_SIZE,
+
+ /**
+ *If the type of #CRFontSize is
+ *ABSOLUTE_FONT_SIZE,
+ *the CRFontSize::value.absolute
+ *field will be defined.
+ */
+ ABSOLUTE_FONT_SIZE,
+
+ /**
+ *If the type of #CRFontSize is
+ *RELATIVE_FONT_SIZE,
+ *the CRFontSize::value.relative
+ *field will be defined.
+ */
+ RELATIVE_FONT_SIZE,
+
+ /**
+ *If the type of #CRFontSize is
+ *INHERITED_FONT_SIZE,
+ *the None of the field of the CRFontSize::value enum
+ *will be defined.
+ */
+ INHERITED_FONT_SIZE,
+
+ NB_FONT_SIZE_TYPE
+} ;
+
+typedef struct _CRFontSize CRFontSize ;
+struct _CRFontSize {
+ enum CRFontSizeType type ;
+ union {
+ enum CRPredefinedAbsoluteFontSize predefined ;
+ enum CRRelativeFontSize relative ;
+ CRNum absolute ;
+ } value;
+} ;
+
+enum CRFontSizeAdjustType
+{
+ FONT_SIZE_ADJUST_NONE = 0,
+ FONT_SIZE_ADJUST_NUMBER,
+ FONT_SIZE_ADJUST_INHERIT
+} ;
+typedef struct _CRFontSizeAdjust CRFontSizeAdjust ;
+struct _CRFontSizeAdjust
+{
+ enum CRFontSizeAdjustType type ;
+ CRNum *num ;
+} ;
+
+enum CRFontStyle
+{
+ FONT_STYLE_NORMAL=0,
+ FONT_STYLE_ITALIC,
+ FONT_STYLE_OBLIQUE,
+ FONT_STYLE_INHERIT
+} ;
+
+enum CRFontVariant
+{
+ FONT_VARIANT_NORMAL=0,
+ FONT_VARIANT_SMALL_CAPS,
+ FONT_VARIANT_INHERIT
+} ;
+
+enum CRFontWeight
+{
+ FONT_WEIGHT_NORMAL = 1,
+ FONT_WEIGHT_BOLD = 1<<1,
+ FONT_WEIGHT_BOLDER = 1<<2,
+ FONT_WEIGHT_LIGHTER = 1<<3,
+ FONT_WEIGHT_100 = 1<<4,
+ FONT_WEIGHT_200 = 1<<5,
+ FONT_WEIGHT_300 = 1<<6,
+ FONT_WEIGHT_400 = 1<<7,
+ FONT_WEIGHT_500 = 1<<8,
+ FONT_WEIGHT_600 = 1<<9,
+ FONT_WEIGHT_700 = 1<<10,
+ FONT_WEIGHT_800 = 1<<11,
+ FONT_WEIGHT_900 = 1<<12,
+ FONT_WEIGHT_INHERIT = 1<<13,
+ NB_FONT_WEIGHTS
+} ;
+
+enum CRFontStretch
+{
+ FONT_STRETCH_NORMAL=0,
+ FONT_STRETCH_WIDER,
+ FONT_STRETCH_NARROWER,
+ FONT_STRETCH_ULTRA_CONDENSED,
+ FONT_STRETCH_EXTRA_CONDENSED,
+ FONT_STRETCH_CONDENSED,
+ FONT_STRETCH_SEMI_CONDENSED,
+ FONT_STRETCH_SEMI_EXPANDED,
+ FONT_STRETCH_EXPANDED,
+ FONT_STRETCH_EXTRA_EXPANDED,
+ FONT_STRETCH_ULTRA_EXPANDED,
+ FONT_STRETCH_INHERIT
+} ;
+
+/**************************************
+ *'font-family' manipulation functions
+ ***************************************/
+CRFontFamily *
+cr_font_family_new (enum CRFontFamilyType a_type, guchar *a_name) ;
+
+CRFontFamily *
+cr_font_family_append (CRFontFamily *a_this,
+ CRFontFamily *a_family_to_append) ;
+
+guchar *
+cr_font_family_to_string (CRFontFamily const *a_this,
+ gboolean a_walk_font_family_list) ;
+
+CRFontFamily *
+cr_font_family_prepend (CRFontFamily *a_this,
+ CRFontFamily *a_family_to_prepend);
+
+enum CRStatus
+cr_font_family_destroy (CRFontFamily *a_this) ;
+
+enum CRStatus
+cr_font_family_set_name (CRFontFamily *a_this, guchar *a_name) ;
+
+
+/************************************
+ *'font-size' manipulation functions
+ ***********************************/
+
+CRFontSize * cr_font_size_new (void) ;
+
+enum CRStatus cr_font_size_clear (CRFontSize *a_this) ;
+
+enum CRStatus cr_font_size_copy (CRFontSize *a_dst,
+ CRFontSize const *a_src) ;
+enum CRStatus cr_font_size_set_predefined_absolute_font_size (CRFontSize *a_this,
+ enum CRPredefinedAbsoluteFontSize a_predefined) ;
+enum CRStatus cr_font_size_set_relative_font_size (CRFontSize *a_this,
+ enum CRRelativeFontSize a_relative) ;
+
+enum CRStatus cr_font_size_set_absolute_font_size (CRFontSize *a_this,
+ enum CRNumType a_num_type,
+ gdouble a_value) ;
+
+enum CRStatus cr_font_size_set_to_inherit (CRFontSize *a_this) ;
+
+gboolean cr_font_size_is_set_to_inherit (CRFontSize const *a_this) ;
+
+gchar* cr_font_size_to_string (CRFontSize const *a_this) ;
+
+void cr_font_size_destroy (CRFontSize *a_font_size) ;
+
+/*******************************************************
+ *'font-size-adjust' manipulation function declarations
+ *******************************************************/
+
+CRFontSizeAdjust * cr_font_size_adjust_new (void) ;
+
+gchar * cr_font_size_adjust_to_string (CRFontSizeAdjust const *a_this) ;
+
+void cr_font_size_adjust_destroy (CRFontSizeAdjust *a_this) ;
+
+void
+cr_font_size_get_smaller_predefined_font_size (enum CRPredefinedAbsoluteFontSize a_font_size,
+ enum CRPredefinedAbsoluteFontSize *a_smaller_size) ;
+void
+cr_font_size_get_larger_predefined_font_size (enum CRPredefinedAbsoluteFontSize a_font_size,
+ enum CRPredefinedAbsoluteFontSize *a_larger_size) ;
+
+gboolean
+cr_font_size_is_predefined_absolute_font_size (enum CRPredefinedAbsoluteFontSize a_font_size) ;
+
+/***********************************
+ *various other font related functions
+ ***********************************/
+const gchar * cr_font_style_to_string (enum CRFontStyle a_code) ;
+
+const gchar * cr_font_weight_to_string (enum CRFontWeight a_code) ;
+
+enum CRFontWeight
+cr_font_weight_get_bolder (enum CRFontWeight a_weight) ;
+
+const gchar * cr_font_variant_to_string (enum CRFontVariant a_code) ;
+
+const gchar * cr_font_stretch_to_string (enum CRFontStretch a_code) ;
+
+G_END_DECLS
+
+#endif
diff --git a/src/st/croco/cr-input.c b/src/st/croco/cr-input.c
new file mode 100644
index 0000000..430e75e
--- /dev/null
+++ b/src/st/croco/cr-input.c
@@ -0,0 +1,1191 @@
+/* -*- Mode: C; indent-tabs-mode: nil; c-basic-offset: 8-*- */
+
+/*
+ * This file is part of The Croco Library
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2.1 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * 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 Lesser General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ *
+ * Author: Dodji Seketeli
+ * See COPYRIGHTS file for copyright information.
+ */
+
+#include "stdio.h"
+#include <string.h>
+#include "cr-input.h"
+#include "cr-enc-handler.h"
+
+/**
+ *@CRInput:
+ *
+ *The definition of the #CRInput class.
+ */
+
+/*******************
+ *Private type defs
+ *******************/
+
+/**
+ *The private attributes of
+ *the #CRInputPriv class.
+ */
+struct _CRInputPriv {
+ /*
+ *The input buffer
+ */
+ guchar *in_buf;
+ gulong in_buf_size;
+
+ gulong nb_bytes;
+
+ /*
+ *The index of the next byte
+ *to be read.
+ */
+ gulong next_byte_index;
+
+ /*
+ *The current line number
+ */
+ gulong line;
+
+ /*
+ *The current col number
+ */
+ gulong col;
+
+ gboolean end_of_line;
+ gboolean end_of_input;
+
+ /*
+ *the reference count of this
+ *instance.
+ */
+ guint ref_count;
+ gboolean free_in_buf;
+};
+
+#define PRIVATE(object) (object)->priv
+
+/***************************
+ *private constants
+ **************************/
+#define CR_INPUT_MEM_CHUNK_SIZE 1024 * 4
+
+static CRInput *cr_input_new_real (void);
+
+static CRInput *
+cr_input_new_real (void)
+{
+ CRInput *result = NULL;
+
+ result = g_try_malloc (sizeof (CRInput));
+ if (!result) {
+ cr_utils_trace_info ("Out of memory");
+ return NULL;
+ }
+ memset (result, 0, sizeof (CRInput));
+
+ PRIVATE (result) = g_try_malloc (sizeof (CRInputPriv));
+ if (!PRIVATE (result)) {
+ cr_utils_trace_info ("Out of memory");
+ g_free (result);
+ return NULL;
+ }
+ memset (PRIVATE (result), 0, sizeof (CRInputPriv));
+ PRIVATE (result)->free_in_buf = TRUE;
+ return result;
+}
+
+/****************
+ *Public methods
+ ***************/
+
+/**
+ * cr_input_new_from_buf:
+ *@a_buf: the memory buffer to create the input stream from.
+ *The #CRInput keeps this pointer so user should not free it !.
+ *@a_len: the size of the input buffer.
+ *@a_enc: the buffer's encoding.
+ *@a_free_buf: if set to TRUE, this a_buf will be freed
+ *at the destruction of this instance. If set to false, it is up
+ *to the caller to free it.
+ *
+ *Creates a new input stream from a memory buffer.
+ *Returns the newly built instance of #CRInput.
+ */
+CRInput *
+cr_input_new_from_buf (guchar * a_buf,
+ gulong a_len,
+ enum CREncoding a_enc,
+ gboolean a_free_buf)
+{
+ CRInput *result = NULL;
+ enum CRStatus status = CR_OK;
+ CREncHandler *enc_handler = NULL;
+ gulong len = a_len;
+
+ g_return_val_if_fail (a_buf, NULL);
+
+ result = cr_input_new_real ();
+ g_return_val_if_fail (result, NULL);
+
+ /*transform the encoding in utf8 */
+ if (a_enc != CR_UTF_8) {
+ enc_handler = cr_enc_handler_get_instance (a_enc);
+ if (!enc_handler) {
+ goto error;
+ }
+
+ status = cr_enc_handler_convert_input
+ (enc_handler, a_buf, &len,
+ &PRIVATE (result)->in_buf,
+ &PRIVATE (result)->in_buf_size);
+ if (status != CR_OK)
+ goto error;
+ PRIVATE (result)->free_in_buf = TRUE;
+ if (a_free_buf == TRUE && a_buf) {
+ g_free (a_buf) ;
+ a_buf = NULL ;
+ }
+ PRIVATE (result)->nb_bytes = PRIVATE (result)->in_buf_size;
+ } else {
+ PRIVATE (result)->in_buf = (guchar *) a_buf;
+ PRIVATE (result)->in_buf_size = a_len;
+ PRIVATE (result)->nb_bytes = a_len;
+ PRIVATE (result)->free_in_buf = a_free_buf;
+ }
+ PRIVATE (result)->line = 1;
+ PRIVATE (result)->col = 0;
+ return result;
+
+ error:
+ if (result) {
+ cr_input_destroy (result);
+ result = NULL;
+ }
+
+ return NULL;
+}
+
+/**
+ * cr_input_new_from_uri:
+ *@a_file_uri: the file to create *the input stream from.
+ *@a_enc: the encoding of the file *to create the input from.
+ *
+ *Creates a new input stream from
+ *a file.
+ *
+ *Returns the newly created input stream if
+ *this method could read the file and create it,
+ *NULL otherwise.
+ */
+
+CRInput *
+cr_input_new_from_uri (const gchar * a_file_uri, enum CREncoding a_enc)
+{
+ CRInput *result = NULL;
+ enum CRStatus status = CR_OK;
+ FILE *file_ptr = NULL;
+ guchar tmp_buf[CR_INPUT_MEM_CHUNK_SIZE] = { 0 };
+ gulong nb_read = 0,
+ len = 0,
+ buf_size = 0;
+ gboolean loop = TRUE;
+ guchar *buf = NULL;
+
+ g_return_val_if_fail (a_file_uri, NULL);
+
+ file_ptr = fopen (a_file_uri, "r");
+
+ if (file_ptr == NULL) {
+
+#ifdef CR_DEBUG
+ cr_utils_trace_debug ("could not open file");
+#endif
+ g_warning ("Could not open file %s\n", a_file_uri);
+
+ return NULL;
+ }
+
+ /*load the file */
+ while (loop) {
+ nb_read = fread (tmp_buf, 1 /*read bytes */ ,
+ CR_INPUT_MEM_CHUNK_SIZE /*nb of bytes */ ,
+ file_ptr);
+
+ if (nb_read != CR_INPUT_MEM_CHUNK_SIZE) {
+ /*we read less chars than we wanted */
+ if (feof (file_ptr)) {
+ /*we reached eof */
+ loop = FALSE;
+ } else {
+ /*a pb occurred !! */
+ cr_utils_trace_debug ("an io error occurred");
+ status = CR_ERROR;
+ goto cleanup;
+ }
+ }
+
+ if (status == CR_OK) {
+ /*read went well */
+ buf = g_realloc (buf, len + CR_INPUT_MEM_CHUNK_SIZE);
+ memcpy (buf + len, tmp_buf, nb_read);
+ len += nb_read;
+ buf_size += CR_INPUT_MEM_CHUNK_SIZE;
+ }
+ }
+
+ if (status == CR_OK) {
+ result = cr_input_new_from_buf (buf, len, a_enc, TRUE);
+ if (!result) {
+ goto cleanup;
+ }
+ /*
+ *we should free buf here because it's own by CRInput.
+ *(see the last parameter of cr_input_new_from_buf().
+ */
+ buf = NULL;
+ }
+
+ cleanup:
+ if (file_ptr) {
+ fclose (file_ptr);
+ file_ptr = NULL;
+ }
+
+ if (buf) {
+ g_free (buf);
+ buf = NULL;
+ }
+
+ return result;
+}
+
+/**
+ * cr_input_destroy:
+ *@a_this: the current instance of #CRInput.
+ *
+ *The destructor of the #CRInput class.
+ */
+void
+cr_input_destroy (CRInput * a_this)
+{
+ if (a_this == NULL)
+ return;
+
+ if (PRIVATE (a_this)) {
+ if (PRIVATE (a_this)->in_buf && PRIVATE (a_this)->free_in_buf) {
+ g_free (PRIVATE (a_this)->in_buf);
+ PRIVATE (a_this)->in_buf = NULL;
+ }
+
+ g_free (PRIVATE (a_this));
+ PRIVATE (a_this) = NULL;
+ }
+
+ g_free (a_this);
+}
+
+/**
+ * cr_input_ref:
+ *@a_this: the current instance of #CRInput.
+ *
+ *Increments the reference count of the current
+ *instance of #CRInput.
+ */
+void
+cr_input_ref (CRInput * a_this)
+{
+ g_return_if_fail (a_this && PRIVATE (a_this));
+
+ PRIVATE (a_this)->ref_count++;
+}
+
+/**
+ * cr_input_unref:
+ *@a_this: the current instance of #CRInput.
+ *
+ *Decrements the reference count of this instance
+ *of #CRInput. If the reference count goes down to
+ *zero, this instance is destroyed.
+ *
+ * Returns TRUE if the instance of #CRInput got destroyed, false otherwise.
+ */
+gboolean
+cr_input_unref (CRInput * a_this)
+{
+ g_return_val_if_fail (a_this && PRIVATE (a_this), FALSE);
+
+ if (PRIVATE (a_this)->ref_count) {
+ PRIVATE (a_this)->ref_count--;
+ }
+
+ if (PRIVATE (a_this)->ref_count == 0) {
+ cr_input_destroy (a_this);
+ return TRUE;
+ }
+ return FALSE;
+}
+
+/**
+ * cr_input_end_of_input:
+ *@a_this: the current instance of #CRInput.
+ *@a_end_of_input: out parameter. Is set to TRUE if
+ *the current instance has reached the end of its input buffer,
+ *FALSE otherwise.
+ *
+ *Tests whether the current instance of
+ *#CRInput has reached its input buffer.
+ *
+ * Returns CR_OK upon successful completion, an error code otherwise.
+ * Note that all the out parameters of this method are valid if
+ * and only if this method returns CR_OK.
+ */
+enum CRStatus
+cr_input_end_of_input (CRInput const * a_this, gboolean * a_end_of_input)
+{
+ g_return_val_if_fail (a_this && PRIVATE (a_this)
+ && a_end_of_input, CR_BAD_PARAM_ERROR);
+
+ *a_end_of_input = (PRIVATE (a_this)->next_byte_index
+ >= PRIVATE (a_this)->in_buf_size) ? TRUE : FALSE;
+
+ return CR_OK;
+}
+
+/**
+ * cr_input_get_nb_bytes_left:
+ *@a_this: the current instance of #CRInput.
+ *
+ *Returns the number of bytes left in the input stream
+ *before the end, -1 in case of error.
+ */
+glong
+cr_input_get_nb_bytes_left (CRInput const * a_this)
+{
+ g_return_val_if_fail (a_this && PRIVATE (a_this), -1);
+ g_return_val_if_fail (PRIVATE (a_this)->nb_bytes
+ <= PRIVATE (a_this)->in_buf_size, -1);
+ g_return_val_if_fail (PRIVATE (a_this)->next_byte_index
+ <= PRIVATE (a_this)->nb_bytes, -1);
+
+ if (PRIVATE (a_this)->end_of_input)
+ return 0;
+
+ return PRIVATE (a_this)->nb_bytes - PRIVATE (a_this)->next_byte_index;
+}
+
+/**
+ * cr_input_read_byte:
+ *@a_this: the current instance of #CRInput.
+ *@a_byte: out parameter the returned byte.
+ *
+ *Gets the next byte of the input.
+ *Updates the state of the input so that
+ *the next invocation of this method returns
+ *the next coming byte.
+ *
+ *Returns CR_OK upon successful completion, an error code
+ *otherwise. All the out parameters of this method are valid if
+ *and only if this method returns CR_OK.
+ */
+enum CRStatus
+cr_input_read_byte (CRInput * a_this, guchar * a_byte)
+{
+ gulong nb_bytes_left = 0;
+
+ g_return_val_if_fail (a_this && PRIVATE (a_this)
+ && a_byte, CR_BAD_PARAM_ERROR);
+
+ g_return_val_if_fail (PRIVATE (a_this)->next_byte_index <=
+ PRIVATE (a_this)->nb_bytes, CR_BAD_PARAM_ERROR);
+
+ if (PRIVATE (a_this)->end_of_input == TRUE)
+ return CR_END_OF_INPUT_ERROR;
+
+ nb_bytes_left = cr_input_get_nb_bytes_left (a_this);
+
+ if (nb_bytes_left < 1) {
+ return CR_END_OF_INPUT_ERROR;
+ }
+
+ *a_byte = PRIVATE (a_this)->in_buf[PRIVATE (a_this)->next_byte_index];
+
+ if (PRIVATE (a_this)->nb_bytes -
+ PRIVATE (a_this)->next_byte_index < 2) {
+ PRIVATE (a_this)->end_of_input = TRUE;
+ } else {
+ PRIVATE (a_this)->next_byte_index++;
+ }
+
+ return CR_OK;
+}
+
+/**
+ * cr_input_read_char:
+ *@a_this: the current instance of CRInput.
+ *@a_char: out parameter. The read character.
+ *
+ *Reads an unicode character from the current instance of
+ *#CRInput.
+ *
+ *Returns CR_OK upon successful completion, an error code
+ *otherwise.
+ */
+enum CRStatus
+cr_input_read_char (CRInput * a_this, guint32 * a_char)
+{
+ enum CRStatus status = CR_OK;
+ gulong consumed = 0,
+ nb_bytes_left = 0;
+
+ g_return_val_if_fail (a_this && PRIVATE (a_this) && a_char,
+ CR_BAD_PARAM_ERROR);
+
+ if (PRIVATE (a_this)->end_of_input == TRUE)
+ return CR_END_OF_INPUT_ERROR;
+
+ nb_bytes_left = cr_input_get_nb_bytes_left (a_this);
+
+ if (nb_bytes_left < 1) {
+ return CR_END_OF_INPUT_ERROR;
+ }
+
+ status = cr_utils_read_char_from_utf8_buf
+ (PRIVATE (a_this)->in_buf
+ +
+ PRIVATE (a_this)->next_byte_index,
+ nb_bytes_left, a_char, &consumed);
+
+ if (status == CR_OK) {
+ /*update next byte index */
+ PRIVATE (a_this)->next_byte_index += consumed;
+
+ /*update line and column number */
+ if (PRIVATE (a_this)->end_of_line == TRUE) {
+ PRIVATE (a_this)->col = 1;
+ PRIVATE (a_this)->line++;
+ PRIVATE (a_this)->end_of_line = FALSE;
+ } else if (*a_char != '\n') {
+ PRIVATE (a_this)->col++;
+ }
+
+ if (*a_char == '\n') {
+ PRIVATE (a_this)->end_of_line = TRUE;
+ }
+ }
+
+ return status;
+}
+
+/**
+ * cr_input_set_line_num:
+ *@a_this: the "this pointer" of the current instance of #CRInput.
+ *@a_line_num: the new line number.
+ *
+ *Setter of the current line number.
+ *
+ *Return CR_OK upon successful completion, an error code otherwise.
+ */
+enum CRStatus
+cr_input_set_line_num (CRInput * a_this, glong a_line_num)
+{
+ g_return_val_if_fail (a_this && PRIVATE (a_this), CR_BAD_PARAM_ERROR);
+
+ PRIVATE (a_this)->line = a_line_num;
+
+ return CR_OK;
+}
+
+/**
+ * cr_input_get_line_num:
+ *@a_this: the "this pointer" of the current instance of #CRInput.
+ *@a_line_num: the returned line number.
+ *
+ *Getter of the current line number.
+ *
+ *Returns CR_OK upon successful completion, an error code otherwise.
+ */
+enum CRStatus
+cr_input_get_line_num (CRInput const * a_this, glong * a_line_num)
+{
+ g_return_val_if_fail (a_this && PRIVATE (a_this)
+ && a_line_num, CR_BAD_PARAM_ERROR);
+
+ *a_line_num = PRIVATE (a_this)->line;
+
+ return CR_OK;
+}
+
+/**
+ * cr_input_set_column_num:
+ *@a_this: the "this pointer" of the current instance of #CRInput.
+ *@a_col: the new column number.
+ *
+ *Setter of the current column number.
+ *
+ *Returns CR_OK upon successful completion, an error code otherwise.
+ */
+enum CRStatus
+cr_input_set_column_num (CRInput * a_this, glong a_col)
+{
+ g_return_val_if_fail (a_this && PRIVATE (a_this), CR_BAD_PARAM_ERROR);
+
+ PRIVATE (a_this)->col = a_col;
+
+ return CR_OK;
+}
+
+/**
+ * cr_input_get_column_num:
+ *@a_this: the "this pointer" of the current instance of #CRInput.
+ *@a_col: out parameter
+ *
+ *Getter of the current column number.
+ *
+ *Returns CR_OK upon successful completion, an error code otherwise.
+ */
+enum CRStatus
+cr_input_get_column_num (CRInput const * a_this, glong * a_col)
+{
+ g_return_val_if_fail (a_this && PRIVATE (a_this) && a_col,
+ CR_BAD_PARAM_ERROR);
+
+ *a_col = PRIVATE (a_this)->col;
+
+ return CR_OK;
+}
+
+/**
+ * cr_input_increment_line_num:
+ *@a_this: the "this pointer" of the current instance of #CRInput.
+ *@a_increment: the increment to add to the line number.
+ *
+ *Increments the current line number.
+ *
+ *Returns CR_OK upon successful completion, an error code otherwise.
+ */
+enum CRStatus
+cr_input_increment_line_num (CRInput * a_this, glong a_increment)
+{
+ g_return_val_if_fail (a_this && PRIVATE (a_this), CR_BAD_PARAM_ERROR);
+
+ PRIVATE (a_this)->line += a_increment;
+
+ return CR_OK;
+}
+
+/**
+ * cr_input_increment_col_num:
+ *@a_this: the "this pointer" of the current instance of #CRInput.
+ *@a_increment: the increment to add to the column number.
+ *
+ *Increments the current column number.
+ *
+ *Returns CR_OK upon successful completion, an error code otherwise.
+ */
+enum CRStatus
+cr_input_increment_col_num (CRInput * a_this, glong a_increment)
+{
+ g_return_val_if_fail (a_this && PRIVATE (a_this), CR_BAD_PARAM_ERROR);
+
+ PRIVATE (a_this)->col += a_increment;
+
+ return CR_OK;
+}
+
+/**
+ * cr_input_consume_char:
+ *@a_this: the this pointer.
+ *@a_char: the character to consume. If set to zero,
+ *consumes any character.
+ *
+ *Consumes the next character of the input stream if
+ *and only if that character equals a_char.
+ *
+ *Returns CR_OK upon successful completion, CR_PARSING_ERROR if
+ *next char is different from a_char, an other error code otherwise
+ */
+enum CRStatus
+cr_input_consume_char (CRInput * a_this, guint32 a_char)
+{
+ guint32 c;
+ enum CRStatus status;
+
+ g_return_val_if_fail (a_this && PRIVATE (a_this), CR_BAD_PARAM_ERROR);
+
+ if ((status = cr_input_peek_char (a_this, &c)) != CR_OK) {
+ return status;
+ }
+
+ if (c == a_char || a_char == 0) {
+ status = cr_input_read_char (a_this, &c);
+ } else {
+ return CR_PARSING_ERROR;
+ }
+
+ return status;
+}
+
+/**
+ * cr_input_consume_chars:
+ *@a_this: the this pointer of the current instance of #CRInput.
+ *@a_char: the character to consume.
+ *@a_nb_char: in/out parameter. The number of characters to consume.
+ *If set to a negative value, the function will consume all the occurrences
+ *of a_char found.
+ *After return, if the return value equals CR_OK, this variable contains
+ *the number of characters actually consumed.
+ *
+ *Consumes up to a_nb_char occurrences of the next contiguous characters
+ *which equal a_char. Note that the next character of the input stream
+ **MUST* equal a_char to trigger the consumption, or else, the error
+ *code CR_PARSING_ERROR is returned.
+ *If the number of contiguous characters that equals a_char is less than
+ *a_nb_char, then this function consumes all the characters it can consume.
+ *
+ *Returns CR_OK if at least one character has been consumed, an error code
+ *otherwise.
+ */
+enum CRStatus
+cr_input_consume_chars (CRInput * a_this, guint32 a_char, gulong * a_nb_char)
+{
+ enum CRStatus status = CR_OK;
+ gulong nb_consumed = 0;
+
+ g_return_val_if_fail (a_this && PRIVATE (a_this) && a_nb_char,
+ CR_BAD_PARAM_ERROR);
+
+ g_return_val_if_fail (a_char != 0 || a_nb_char != NULL,
+ CR_BAD_PARAM_ERROR);
+
+ for (nb_consumed = 0; ((status == CR_OK)
+ && (*a_nb_char > 0
+ && nb_consumed < *a_nb_char));
+ nb_consumed++) {
+ status = cr_input_consume_char (a_this, a_char);
+ }
+
+ *a_nb_char = nb_consumed;
+
+ if ((nb_consumed > 0)
+ && ((status == CR_PARSING_ERROR)
+ || (status == CR_END_OF_INPUT_ERROR))) {
+ status = CR_OK;
+ }
+
+ return status;
+}
+
+/**
+ * cr_input_consume_white_spaces:
+ *@a_this: the "this pointer" of the current instance of #CRInput.
+ *@a_nb_chars: in/out parameter. The number of white spaces to
+ *consume. After return, holds the number of white spaces actually consumed.
+ *
+ *Same as cr_input_consume_chars() but this one consumes white
+ *spaces.
+ *
+ *Returns CR_OK upon successful completion, an error code otherwise.
+ */
+enum CRStatus
+cr_input_consume_white_spaces (CRInput * a_this, gulong * a_nb_chars)
+{
+ enum CRStatus status = CR_OK;
+ guint32 cur_char = 0,
+ nb_consumed = 0;
+
+ g_return_val_if_fail (a_this && PRIVATE (a_this) && a_nb_chars,
+ CR_BAD_PARAM_ERROR);
+
+ for (nb_consumed = 0;
+ ((*a_nb_chars > 0) && (nb_consumed < *a_nb_chars));
+ nb_consumed++) {
+ status = cr_input_peek_char (a_this, &cur_char);
+ if (status != CR_OK)
+ break;
+
+ /*if the next char is a white space, consume it ! */
+ if (cr_utils_is_white_space (cur_char) == TRUE) {
+ status = cr_input_read_char (a_this, &cur_char);
+ if (status != CR_OK)
+ break;
+ continue;
+ }
+
+ break;
+
+ }
+
+ *a_nb_chars = (gulong) nb_consumed;
+
+ if (nb_consumed && status == CR_END_OF_INPUT_ERROR) {
+ status = CR_OK;
+ }
+
+ return status;
+}
+
+/**
+ * cr_input_peek_char:
+ *@a_this: the current instance of #CRInput.
+ *@a_char: out parameter. The returned character.
+ *
+ *Same as cr_input_read_char() but does not update the
+ *internal state of the input stream. The next call
+ *to cr_input_peek_char() or cr_input_read_char() will thus
+ *return the same character as the current one.
+ *
+ *Returns CR_OK upon successful completion, an error code
+ *otherwise.
+ */
+enum CRStatus
+cr_input_peek_char (CRInput const * a_this, guint32 * a_char)
+{
+ enum CRStatus status = CR_OK;
+ gulong consumed = 0,
+ nb_bytes_left = 0;
+
+ g_return_val_if_fail (a_this && PRIVATE (a_this)
+ && a_char, CR_BAD_PARAM_ERROR);
+
+ if (PRIVATE (a_this)->next_byte_index >=
+ PRIVATE (a_this)->in_buf_size) {
+ return CR_END_OF_INPUT_ERROR;
+ }
+
+ nb_bytes_left = cr_input_get_nb_bytes_left (a_this);
+
+ if (nb_bytes_left < 1) {
+ return CR_END_OF_INPUT_ERROR;
+ }
+
+ status = cr_utils_read_char_from_utf8_buf
+ (PRIVATE (a_this)->in_buf +
+ PRIVATE (a_this)->next_byte_index,
+ nb_bytes_left, a_char, &consumed);
+
+ return status;
+}
+
+/**
+ * cr_input_peek_byte:
+ *@a_this: the current instance of #CRInput.
+ *@a_origin: the origin to consider in the calculation
+ *of the position of the byte to peek.
+ *@a_offset: the offset of the byte to peek, starting from
+ *the origin specified by a_origin.
+ *@a_byte: out parameter the peeked byte.
+ *
+ *Gets a byte from the input stream,
+ *starting from the current position in the input stream.
+ *Unlike cr_input_peek_next_byte() this method
+ *does not update the state of the current input stream.
+ *Subsequent calls to cr_input_peek_byte with the same arguments
+ *will return the same byte.
+ *
+ *Returns CR_OK upon successful completion or,
+ *CR_BAD_PARAM_ERROR if at least one of the parameters is invalid;
+ *CR_OUT_OF_BOUNDS_ERROR if the indexed byte is out of bounds.
+ */
+enum CRStatus
+cr_input_peek_byte (CRInput const * a_this, enum CRSeekPos a_origin,
+ gulong a_offset, guchar * a_byte)
+{
+ gulong abs_offset = 0;
+
+ g_return_val_if_fail (a_this && PRIVATE (a_this)
+ && a_byte, CR_BAD_PARAM_ERROR);
+
+ switch (a_origin) {
+
+ case CR_SEEK_CUR:
+ abs_offset = PRIVATE (a_this)->next_byte_index - 1 + a_offset;
+ break;
+
+ case CR_SEEK_BEGIN:
+ abs_offset = a_offset;
+ break;
+
+ case CR_SEEK_END:
+ abs_offset = PRIVATE (a_this)->in_buf_size - 1 - a_offset;
+ break;
+
+ default:
+ return CR_BAD_PARAM_ERROR;
+ }
+
+ if (abs_offset < PRIVATE (a_this)->in_buf_size) {
+
+ *a_byte = PRIVATE (a_this)->in_buf[abs_offset];
+
+ return CR_OK;
+
+ } else {
+ return CR_END_OF_INPUT_ERROR;
+ }
+}
+
+/**
+ * cr_input_peek_byte2:
+ *@a_this: the current byte input stream.
+ *@a_offset: the offset of the byte to peek, starting
+ *from the current input position pointer.
+ *@a_eof: out parameter. Is set to true is we reach end of
+ *stream. If set to NULL by the caller, this parameter is not taken
+ *in account.
+ *
+ *Same as cr_input_peek_byte() but with a simplified
+ *interface.
+ *
+ *Returns the read byte or 0 if something bad happened.
+ */
+guchar
+cr_input_peek_byte2 (CRInput const * a_this, gulong a_offset, gboolean * a_eof)
+{
+ guchar result = 0;
+ enum CRStatus status = CR_ERROR;
+
+ g_return_val_if_fail (a_this && PRIVATE (a_this), 0);
+
+ if (a_eof)
+ *a_eof = FALSE;
+
+ status = cr_input_peek_byte (a_this, CR_SEEK_CUR, a_offset, &result);
+
+ if ((status == CR_END_OF_INPUT_ERROR)
+ && a_eof)
+ *a_eof = TRUE;
+
+ return result;
+}
+
+/**
+ * cr_input_get_byte_addr:
+ *@a_this: the current instance of #CRInput.
+ *@a_offset: the offset of the byte in the input stream starting
+ *from the beginning of the stream.
+ *
+ *Gets the memory address of the byte located at a given offset
+ *in the input stream.
+ *
+ *Returns the address, otherwise NULL if an error occurred.
+ */
+guchar *
+cr_input_get_byte_addr (CRInput * a_this, gulong a_offset)
+{
+ g_return_val_if_fail (a_this && PRIVATE (a_this), NULL);
+
+ if (a_offset >= PRIVATE (a_this)->nb_bytes) {
+ return NULL;
+ }
+
+ return &PRIVATE (a_this)->in_buf[a_offset];
+}
+
+/**
+ * cr_input_get_cur_byte_addr:
+ *@a_this: the current input stream
+ *@a_offset: out parameter. The returned address.
+ *
+ *Gets the address of the current character pointer.
+ *
+ *Returns CR_OK upon successful completion, an error code otherwise.
+ */
+enum CRStatus
+cr_input_get_cur_byte_addr (CRInput * a_this, guchar ** a_offset)
+{
+ g_return_val_if_fail (a_this && PRIVATE (a_this) && a_offset,
+ CR_BAD_PARAM_ERROR);
+
+ if (!PRIVATE (a_this)->next_byte_index) {
+ return CR_START_OF_INPUT_ERROR;
+ }
+
+ *a_offset = cr_input_get_byte_addr
+ (a_this, PRIVATE (a_this)->next_byte_index - 1);
+
+ return CR_OK;
+}
+
+/**
+ * cr_input_seek_index:
+ *@a_this: the current instance of #CRInput.
+ *@a_origin: the origin to consider during the calculation
+ *of the absolute position of the new "current byte index".
+ *@a_pos: the relative offset of the new "current byte index."
+ *This offset is relative to the origin a_origin.
+ *
+ *Sets the "current byte index" of the current instance
+ *of #CRInput. Next call to cr_input_get_byte() will return
+ *the byte next after the new "current byte index".
+ *
+ *Returns CR_OK upon successful completion otherwise returns
+ *CR_BAD_PARAM_ERROR if at least one of the parameters is not valid
+ *or CR_OUT_BOUNDS_ERROR in case of error.
+ */
+enum CRStatus
+cr_input_seek_index (CRInput * a_this, enum CRSeekPos a_origin, gint a_pos)
+{
+
+ glong abs_offset = 0;
+
+ g_return_val_if_fail (a_this && PRIVATE (a_this), CR_BAD_PARAM_ERROR);
+
+ switch (a_origin) {
+
+ case CR_SEEK_CUR:
+ abs_offset = PRIVATE (a_this)->next_byte_index - 1 + a_pos;
+ break;
+
+ case CR_SEEK_BEGIN:
+ abs_offset = a_pos;
+ break;
+
+ case CR_SEEK_END:
+ abs_offset = PRIVATE (a_this)->in_buf_size - 1 - a_pos;
+ break;
+
+ default:
+ return CR_BAD_PARAM_ERROR;
+ }
+
+ if ((abs_offset > 0)
+ && (gulong) abs_offset < PRIVATE (a_this)->nb_bytes) {
+
+ /*update the input stream's internal state */
+ PRIVATE (a_this)->next_byte_index = abs_offset + 1;
+
+ return CR_OK;
+ }
+
+ return CR_OUT_OF_BOUNDS_ERROR;
+}
+
+/**
+ * cr_input_get_cur_pos:
+ *@a_this: the current instance of #CRInput.
+ *@a_pos: out parameter. The returned position.
+ *
+ *Gets the position of the "current byte index" which
+ *is basically the position of the last returned byte in the
+ *input stream.
+ *
+ *Returns CR_OK upon successful completion. Otherwise,
+ *CR_BAD_PARAMETER_ERROR if at least one of the arguments is invalid.
+ *CR_START_OF_INPUT if no call to either cr_input_read_byte()
+ *or cr_input_seek_index() have been issued before calling
+ *cr_input_get_cur_pos()
+ *Note that the out parameters of this function are valid if and only if this
+ *function returns CR_OK.
+ */
+enum CRStatus
+cr_input_get_cur_pos (CRInput const * a_this, CRInputPos * a_pos)
+{
+ g_return_val_if_fail (a_this && PRIVATE (a_this) && a_pos,
+ CR_BAD_PARAM_ERROR);
+
+ a_pos->next_byte_index = PRIVATE (a_this)->next_byte_index;
+ a_pos->line = PRIVATE (a_this)->line;
+ a_pos->col = PRIVATE (a_this)->col;
+ a_pos->end_of_line = PRIVATE (a_this)->end_of_line;
+ a_pos->end_of_file = PRIVATE (a_this)->end_of_input;
+
+ return CR_OK;
+}
+
+/**
+ * cr_input_get_parsing_location:
+ *@a_this: the current instance of #CRInput
+ *@a_loc: the set parsing location.
+ *
+ *Gets the current parsing location.
+ *The Parsing location is a public datastructure that
+ *represents the current line/column/byte offset/ in the input
+ *stream.
+ *
+ *Returns CR_OK upon successful completion, an error
+ *code otherwise.
+ */
+enum CRStatus
+cr_input_get_parsing_location (CRInput const *a_this,
+ CRParsingLocation *a_loc)
+{
+ g_return_val_if_fail (a_this
+ && PRIVATE (a_this)
+ && a_loc,
+ CR_BAD_PARAM_ERROR) ;
+
+ a_loc->line = PRIVATE (a_this)->line ;
+ a_loc->column = PRIVATE (a_this)->col ;
+ if (PRIVATE (a_this)->next_byte_index) {
+ a_loc->byte_offset = PRIVATE (a_this)->next_byte_index - 1 ;
+ } else {
+ a_loc->byte_offset = PRIVATE (a_this)->next_byte_index ;
+ }
+ return CR_OK ;
+}
+
+/**
+ * cr_input_get_cur_index:
+ *@a_this: the "this pointer" of the current instance of
+ *#CRInput
+ *@a_index: out parameter. The returned index.
+ *
+ *Getter of the next byte index.
+ *It actually returns the index of the
+ *next byte to be read.
+ *
+ *Returns CR_OK upon successful completion, an error code
+ *otherwise.
+ */
+enum CRStatus
+cr_input_get_cur_index (CRInput const * a_this, glong * a_index)
+{
+ g_return_val_if_fail (a_this && PRIVATE (a_this)
+ && a_index, CR_BAD_PARAM_ERROR);
+
+ *a_index = PRIVATE (a_this)->next_byte_index;
+
+ return CR_OK;
+}
+
+/**
+ * cr_input_set_cur_index:
+ *@a_this: the "this pointer" of the current instance
+ *of #CRInput .
+ *@a_index: the new index to set.
+ *
+ *Setter of the next byte index.
+ *It sets the index of the next byte to be read.
+ *
+ *Returns CR_OK upon successful completion, an error code otherwise.
+ */
+enum CRStatus
+cr_input_set_cur_index (CRInput * a_this, glong a_index)
+{
+ g_return_val_if_fail (a_this && PRIVATE (a_this), CR_BAD_PARAM_ERROR);
+
+ PRIVATE (a_this)->next_byte_index = a_index;
+
+ return CR_OK;
+}
+
+/**
+ * cr_input_set_end_of_file:
+ *@a_this: the current instance of #CRInput.
+ *@a_eof: the new end of file flag.
+ *
+ *Sets the end of file flag.
+ *
+ *Returns CR_OK upon successful completion, an error code otherwise.
+ */
+enum CRStatus
+cr_input_set_end_of_file (CRInput * a_this, gboolean a_eof)
+{
+ g_return_val_if_fail (a_this && PRIVATE (a_this), CR_BAD_PARAM_ERROR);
+
+ PRIVATE (a_this)->end_of_input = a_eof;
+
+ return CR_OK;
+}
+
+/**
+ * cr_input_get_end_of_file:
+ *@a_this: the current instance of #CRInput.
+ *@a_eof: out parameter the place to put the end of
+ *file flag.
+ *
+ *Gets the end of file flag.
+ *
+ *Returns CR_OK upon successful completion, an error code otherwise.
+ */
+enum CRStatus
+cr_input_get_end_of_file (CRInput const * a_this, gboolean * a_eof)
+{
+ g_return_val_if_fail (a_this && PRIVATE (a_this)
+ && a_eof, CR_BAD_PARAM_ERROR);
+
+ *a_eof = PRIVATE (a_this)->end_of_input;
+
+ return CR_OK;
+}
+
+/**
+ * cr_input_set_end_of_line:
+ *@a_this: the current instance of #CRInput.
+ *@a_eol: the new end of line flag.
+ *
+ *Sets the end of line flag.
+ *
+ *Returns CR_OK upon successful completion, an error code
+ *otherwise.
+ */
+enum CRStatus
+cr_input_set_end_of_line (CRInput * a_this, gboolean a_eol)
+{
+ g_return_val_if_fail (a_this && PRIVATE (a_this), CR_BAD_PARAM_ERROR);
+
+ PRIVATE (a_this)->end_of_line = a_eol;
+
+ return CR_OK;
+}
+
+/**
+ * cr_input_get_end_of_line:
+ *@a_this: the current instance of #CRInput
+ *@a_eol: out parameter. The place to put
+ *the returned flag
+ *
+ *Gets the end of line flag of the current input.
+ *
+ *Returns CR_OK upon successful completion, an error code
+ *otherwise.
+ */
+enum CRStatus
+cr_input_get_end_of_line (CRInput const * a_this, gboolean * a_eol)
+{
+ g_return_val_if_fail (a_this && PRIVATE (a_this)
+ && a_eol, CR_BAD_PARAM_ERROR);
+
+ *a_eol = PRIVATE (a_this)->end_of_line;
+
+ return CR_OK;
+}
+
+/**
+ * cr_input_set_cur_pos:
+ *@a_this: the "this pointer" of the current instance of
+ *#CRInput.
+ *@a_pos: the new position.
+ *
+ *Sets the current position in the input stream.
+ *
+ * Returns CR_OK upon successful completion, an error code otherwise.
+ */
+enum CRStatus
+cr_input_set_cur_pos (CRInput * a_this, CRInputPos const * a_pos)
+{
+ g_return_val_if_fail (a_this && PRIVATE (a_this) && a_pos,
+ CR_BAD_PARAM_ERROR);
+
+ cr_input_set_column_num (a_this, a_pos->col);
+ cr_input_set_line_num (a_this, a_pos->line);
+ cr_input_set_cur_index (a_this, a_pos->next_byte_index);
+ cr_input_set_end_of_line (a_this, a_pos->end_of_line);
+ cr_input_set_end_of_file (a_this, a_pos->end_of_file);
+
+ return CR_OK;
+}
diff --git a/src/st/croco/cr-input.h b/src/st/croco/cr-input.h
new file mode 100644
index 0000000..9eb402a
--- /dev/null
+++ b/src/st/croco/cr-input.h
@@ -0,0 +1,174 @@
+/* -*- Mode: C; indent-tabs-mode:nil; c-basic-offset:8 -*- */
+
+/*
+ * This file is part of The Croco Library
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2.1 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * 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 Lesser General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ *
+ * Author: Dodji Seketeli
+ * See the COPYRIGHTS file for copyrights information.
+ */
+
+#ifndef __CR_INPUT_SRC_H__
+#define __CR_INPUT_SRC_H__
+
+
+#include <glib.h>
+#include "cr-utils.h"
+#include "cr-parsing-location.h"
+
+G_BEGIN_DECLS
+
+/**
+ *@file
+ *The libcroco basic input stream class
+ *declaration file.
+ */
+
+typedef struct _CRInput CRInput ;
+typedef struct _CRInputPriv CRInputPriv ;
+
+/**
+ *The #CRInput class provides the abstraction of
+ *an utf8-encoded character stream.
+ */
+struct _CRInput
+{
+ CRInputPriv *priv ;
+} ;
+
+typedef struct _CRInputPos CRInputPos ;
+
+struct _CRInputPos
+{
+ glong line ;
+ glong col ;
+ gboolean end_of_file ;
+ gboolean end_of_line ;
+ glong next_byte_index ;
+} ;
+
+CRInput *
+cr_input_new_from_buf (guchar *a_buf, gulong a_len,
+ enum CREncoding a_enc, gboolean a_free_buf) ;
+CRInput *
+cr_input_new_from_uri (const gchar *a_file_uri,
+ enum CREncoding a_enc) ;
+
+void
+cr_input_destroy (CRInput *a_this) ;
+
+void
+cr_input_ref (CRInput *a_this) ;
+
+gboolean
+cr_input_unref (CRInput *a_this) ;
+
+enum CRStatus
+cr_input_read_byte (CRInput *a_this, guchar *a_byte) ;
+
+enum CRStatus
+cr_input_read_char (CRInput *a_this, guint32 *a_char) ;
+
+enum CRStatus
+cr_input_consume_chars (CRInput *a_this, guint32 a_char,
+ gulong *a_nb_char) ;
+
+enum CRStatus
+cr_input_consume_char (CRInput *a_this, guint32 a_char) ;
+
+enum CRStatus
+cr_input_consume_white_spaces (CRInput *a_this, gulong *a_nb_chars) ;
+
+enum CRStatus
+cr_input_peek_byte (CRInput const *a_this, enum CRSeekPos a_origin,
+ gulong a_offset, guchar *a_byte) ;
+
+guchar
+cr_input_peek_byte2 (CRInput const *a_this, gulong a_offset,
+ gboolean *a_eof) ;
+
+enum CRStatus
+cr_input_peek_char (CRInput const *a_this, guint32 *a_char) ;
+
+guchar *
+cr_input_get_byte_addr (CRInput *a_this,
+ gulong a_offset) ;
+
+enum CRStatus
+cr_input_get_cur_byte_addr (CRInput *a_this, guchar ** a_offset) ;
+
+enum CRStatus
+cr_input_seek_index (CRInput *a_this,
+ enum CRSeekPos a_origin, gint a_pos) ;
+
+enum CRStatus
+cr_input_get_cur_index (CRInput const *a_this, glong *a_index) ;
+
+enum CRStatus
+cr_input_set_cur_index (CRInput *a_this, glong a_index) ;
+
+enum CRStatus
+cr_input_get_cur_pos (CRInput const *a_this, CRInputPos * a_pos) ;
+
+enum CRStatus
+cr_input_set_cur_pos (CRInput *a_this, CRInputPos const *a_pos) ;
+
+enum CRStatus
+cr_input_get_parsing_location (CRInput const *a_this,
+ CRParsingLocation *a_loc) ;
+
+enum CRStatus
+cr_input_get_end_of_line (CRInput const *a_this, gboolean *a_eol) ;
+
+enum CRStatus
+cr_input_set_end_of_line (CRInput *a_this, gboolean a_eol) ;
+
+enum CRStatus
+cr_input_get_end_of_file (CRInput const *a_this, gboolean *a_eof) ;
+
+enum CRStatus
+cr_input_set_end_of_file (CRInput *a_this, gboolean a_eof) ;
+
+enum CRStatus
+cr_input_set_line_num (CRInput *a_this, glong a_line_num) ;
+
+enum CRStatus
+cr_input_get_line_num (CRInput const *a_this, glong *a_line_num) ;
+
+enum CRStatus
+cr_input_set_column_num (CRInput *a_this, glong a_col) ;
+
+enum CRStatus
+cr_input_get_column_num (CRInput const *a_this, glong *a_col) ;
+
+enum CRStatus
+cr_input_increment_line_num (CRInput *a_this,
+ glong a_increment) ;
+
+enum CRStatus
+cr_input_increment_col_num (CRInput *a_this,
+ glong a_increment) ;
+
+glong
+cr_input_get_nb_bytes_left (CRInput const *a_this) ;
+
+enum CRStatus
+cr_input_end_of_input (CRInput const *a_this, gboolean *a_end_of_input) ;
+
+G_END_DECLS
+
+#endif /*__CR_INPUT_SRC_H__*/
+
diff --git a/src/st/croco/cr-num.c b/src/st/croco/cr-num.c
new file mode 100644
index 0000000..ba17285
--- /dev/null
+++ b/src/st/croco/cr-num.c
@@ -0,0 +1,313 @@
+/* -*- Mode: C; indent-tabs-mode:nil; c-basic-offset: 8-*- */
+
+/*
+ * This file is part of The Croco Library
+ *
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2.1 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * 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 Lesser General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ *
+ * Author: Dodji Seketeli
+ * See COPYRIGHTS file for copyrights information.
+ */
+
+/**
+ *@CRNum:
+ *
+ *The definition
+ *of the #CRNum class.
+ */
+
+#include "cr-num.h"
+#include "string.h"
+
+/**
+ * cr_num_new:
+ *
+ *#CRNum.
+ *
+ *Returns the newly built instance of
+ *#CRNum.
+ */
+CRNum *
+cr_num_new (void)
+{
+ CRNum *result = NULL;
+
+ result = g_try_malloc (sizeof (CRNum));
+
+ if (result == NULL) {
+ cr_utils_trace_info ("Out of memory");
+ return NULL;
+ }
+
+ memset (result, 0, sizeof (CRNum));
+
+ return result;
+}
+
+/**
+ * cr_num_new_with_val:
+ * @a_val: the numerical value of the number.
+ * @a_type: the type of number.
+ *
+ * A constructor of #CRNum.
+ *
+ * Returns the newly built instance of #CRNum or
+ * NULL if an error arises.
+ */
+CRNum *
+cr_num_new_with_val (gdouble a_val, enum CRNumType a_type)
+{
+ CRNum *result = NULL;
+
+ result = cr_num_new ();
+
+ g_return_val_if_fail (result, NULL);
+
+ result->val = a_val;
+ result->type = a_type;
+
+ return result;
+}
+
+/**
+ * cr_num_to_string:
+ *@a_this: the current instance of #CRNum.
+ *
+ *Returns the newly built string representation
+ *of the current instance of #CRNum. The returned
+ *string is NULL terminated. The caller *must*
+ *free the returned string.
+ */
+guchar *
+cr_num_to_string (CRNum const * a_this)
+{
+ gdouble test_val = 0.0;
+
+ guchar *tmp_char1 = NULL,
+ *tmp_char2 = NULL,
+ *result = NULL;
+
+ g_return_val_if_fail (a_this, NULL);
+
+ test_val = a_this->val - (glong) a_this->val;
+
+ if (!test_val) {
+ tmp_char1 = (guchar *) g_strdup_printf ("%ld", (glong) a_this->val);
+ } else {
+ tmp_char1 = (guchar *) g_new0 (char, G_ASCII_DTOSTR_BUF_SIZE + 1);
+ if (tmp_char1 != NULL)
+ g_ascii_dtostr ((gchar *) tmp_char1, G_ASCII_DTOSTR_BUF_SIZE, a_this->val);
+ }
+
+ g_return_val_if_fail (tmp_char1, NULL);
+
+ switch (a_this->type) {
+ case NUM_LENGTH_EM:
+ tmp_char2 = (guchar *) "em";
+ break;
+
+ case NUM_LENGTH_EX:
+ tmp_char2 = (guchar *) "ex";
+ break;
+
+ case NUM_LENGTH_PX:
+ tmp_char2 = (guchar *) "px";
+ break;
+
+ case NUM_LENGTH_IN:
+ tmp_char2 = (guchar *) "in";
+ break;
+
+ case NUM_LENGTH_CM:
+ tmp_char2 = (guchar *) "cm";
+ break;
+
+ case NUM_LENGTH_MM:
+ tmp_char2 = (guchar *) "mm";
+ break;
+
+ case NUM_LENGTH_PT:
+ tmp_char2 = (guchar *) "pt";
+ break;
+
+ case NUM_LENGTH_PC:
+ tmp_char2 = (guchar *) "pc";
+ break;
+
+ case NUM_ANGLE_DEG:
+ tmp_char2 = (guchar *) "deg";
+ break;
+
+ case NUM_ANGLE_RAD:
+ tmp_char2 = (guchar *) "rad";
+ break;
+
+ case NUM_ANGLE_GRAD:
+ tmp_char2 = (guchar *) "grad";
+ break;
+
+ case NUM_TIME_MS:
+ tmp_char2 = (guchar *) "ms";
+ break;
+
+ case NUM_TIME_S:
+ tmp_char2 = (guchar *) "s";
+ break;
+
+ case NUM_FREQ_HZ:
+ tmp_char2 = (guchar *) "Hz";
+ break;
+
+ case NUM_FREQ_KHZ:
+ tmp_char2 = (guchar *) "KHz";
+ break;
+
+ case NUM_PERCENTAGE:
+ tmp_char2 = (guchar *) "%";
+ break;
+ case NUM_INHERIT:
+ tmp_char2 = (guchar *) "inherit";
+ break ;
+ case NUM_AUTO:
+ tmp_char2 = (guchar *) "auto";
+ break ;
+ case NUM_GENERIC:
+ tmp_char2 = NULL ;
+ break ;
+ default:
+ tmp_char2 = (guchar *) "unknown";
+ break;
+ }
+
+ if (tmp_char2) {
+ result = (guchar *) g_strconcat ((gchar *) tmp_char1, tmp_char2, NULL);
+ g_free (tmp_char1);
+ } else {
+ result = tmp_char1;
+ }
+
+ return result;
+}
+
+/**
+ * cr_num_copy:
+ *@a_src: the instance of #CRNum to copy.
+ *Must be non NULL.
+ *@a_dest: the destination of the copy.
+ *Must be non NULL
+ *
+ *Copies an instance of #CRNum.
+ *
+ *Returns CR_OK upon successful completion, an
+ *error code otherwise.
+ */
+enum CRStatus
+cr_num_copy (CRNum * a_dest, CRNum const * a_src)
+{
+ g_return_val_if_fail (a_dest && a_src, CR_BAD_PARAM_ERROR);
+
+ memcpy (a_dest, a_src, sizeof (CRNum));
+
+ return CR_OK;
+}
+
+/**
+ * cr_num_dup:
+ *@a_this: the instance of #CRNum to duplicate.
+ *
+ *Duplicates an instance of #CRNum
+ *
+ *Returns the newly created (duplicated) instance of #CRNum.
+ *Must be freed by cr_num_destroy().
+ */
+CRNum *
+cr_num_dup (CRNum const * a_this)
+{
+ CRNum *result = NULL;
+ enum CRStatus status = CR_OK;
+
+ g_return_val_if_fail (a_this, NULL);
+
+ result = cr_num_new ();
+ g_return_val_if_fail (result, NULL);
+
+ status = cr_num_copy (result, a_this);
+ g_return_val_if_fail (status == CR_OK, NULL);
+
+ return result;
+}
+
+/**
+ * cr_num_set:
+ *Sets an instance of #CRNum.
+ *@a_this: the current instance of #CRNum to be set.
+ *@a_val: the new numerical value to be hold by the current
+ *instance of #CRNum
+ *@a_type: the new type of #CRNum.
+ *
+ * Returns CR_OK upon successful completion, an error code otherwise.
+ */
+enum CRStatus
+cr_num_set (CRNum * a_this, gdouble a_val, enum CRNumType a_type)
+{
+ g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR);
+
+ a_this->val = a_val;
+ a_this->type = a_type;
+
+ return CR_OK;
+}
+
+/**
+ * cr_num_is_fixed_length:
+ * @a_this: the current instance of #CRNum .
+ *
+ *Tests if the current instance of #CRNum is a fixed
+ *length value or not. Typically a fixed length value
+ *is anything from NUM_LENGTH_EM to NUM_LENGTH_PC.
+ *See the definition of #CRNumType to see what we mean.
+ *
+ *Returns TRUE if the instance of #CRNum is a fixed length number,
+ *FALSE otherwise.
+ */
+gboolean
+cr_num_is_fixed_length (CRNum const * a_this)
+{
+ gboolean result = FALSE;
+
+ g_return_val_if_fail (a_this, FALSE);
+
+ if (a_this->type >= NUM_LENGTH_EM
+ && a_this->type <= NUM_LENGTH_PC) {
+ result = TRUE ;
+ }
+ return result ;
+}
+
+/**
+ * cr_num_destroy:
+ *@a_this: the this pointer of
+ *the current instance of #CRNum.
+ *
+ *The destructor of #CRNum.
+ */
+void
+cr_num_destroy (CRNum * a_this)
+{
+ g_return_if_fail (a_this);
+
+ g_free (a_this);
+}
diff --git a/src/st/croco/cr-num.h b/src/st/croco/cr-num.h
new file mode 100644
index 0000000..2b73aaf
--- /dev/null
+++ b/src/st/croco/cr-num.h
@@ -0,0 +1,127 @@
+/* -*- Mode: C; indent-tabs-mode:nil; c-basic-offset: 8-*- */
+
+/*
+ * This file is part of The Croco Library
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2.1 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * 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 Lesser General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ *
+ * Author: Dodji Seketeli
+ * See COPYRIGHTS file for copyright information
+ */
+
+
+/**
+ *@file
+ *The declaration
+ *of the #CRNum class.
+ */
+
+#ifndef __CR_NUM_H__
+#define __CR_NUM_H__
+
+#include <glib.h>
+#include "cr-utils.h"
+#include "cr-parsing-location.h"
+
+G_BEGIN_DECLS
+
+/**
+ *@file
+ *The declaration of the #CRNum class.
+ *
+ */
+
+/**
+ *The different types
+ *of numbers.
+ *Please, do not modify
+ *the declaration order of the enum
+ *members, unless you know
+ *what you are doing.
+ */
+enum CRNumType
+{
+ NUM_AUTO = 0,
+ NUM_GENERIC,
+ NUM_LENGTH_EM,
+ NUM_LENGTH_EX,
+ NUM_LENGTH_PX,
+ NUM_LENGTH_IN,
+ NUM_LENGTH_CM,
+ NUM_LENGTH_MM,
+ NUM_LENGTH_PT,
+ NUM_LENGTH_PC,
+ NUM_ANGLE_DEG,
+ NUM_ANGLE_RAD,
+ NUM_ANGLE_GRAD,
+ NUM_TIME_MS,
+ NUM_TIME_S,
+ NUM_FREQ_HZ,
+ NUM_FREQ_KHZ,
+ NUM_PERCENTAGE,
+ NUM_INHERIT,
+ NUM_UNKNOWN_TYPE,
+ NB_NUM_TYPE
+} ;
+
+
+/**
+ *An abstraction of a number (num)
+ *as defined in the css2 spec.
+ */
+typedef struct _CRNum CRNum ;
+
+/**
+ *An abstraction of a number (num)
+ *as defined in the css2 spec.
+ */
+struct _CRNum
+{
+ enum CRNumType type ;
+ gdouble val ;
+ CRParsingLocation location ;
+} ;
+
+CRNum *
+cr_num_new (void) ;
+
+CRNum *
+cr_num_new_with_val (gdouble a_val,
+ enum CRNumType a_type) ;
+
+CRNum *
+cr_num_dup (CRNum const *a_this) ;
+
+guchar *
+cr_num_to_string (CRNum const *a_this) ;
+
+enum CRStatus
+cr_num_copy (CRNum *a_dest, CRNum const *a_src) ;
+
+enum CRStatus
+cr_num_set (CRNum *a_this, gdouble a_val,
+ enum CRNumType a_type) ;
+
+gboolean
+cr_num_is_fixed_length (CRNum const *a_this) ;
+
+void
+cr_num_destroy (CRNum *a_this) ;
+
+
+G_END_DECLS
+
+
+#endif /*__CR_NUM_H__*/
diff --git a/src/st/croco/cr-om-parser.c b/src/st/croco/cr-om-parser.c
new file mode 100644
index 0000000..90f7106
--- /dev/null
+++ b/src/st/croco/cr-om-parser.c
@@ -0,0 +1,1142 @@
+/* -*- Mode: C; indent-tabs-mode:nil; c-basic-offset: 8-*- */
+
+/*
+ * This file is part of The Croco Library
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2.1 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * 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 Lesser General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ *
+ * Author: Dodji Seketeli
+ * See COPYRIGHTS file for copyright information.
+ */
+
+#include <string.h>
+#include "cr-utils.h"
+#include "cr-om-parser.h"
+
+/**
+ *@CROMParser:
+ *
+ *The definition of the CSS Object Model Parser.
+ *This parser uses (and sits) the SAC api of libcroco defined
+ *in cr-parser.h and cr-doc-handler.h
+ */
+
+struct _CROMParserPriv {
+ CRParser *parser;
+};
+
+#define PRIVATE(a_this) ((a_this)->priv)
+
+/*
+ *Forward declaration of a type defined later
+ *in this file.
+ */
+struct _ParsingContext;
+typedef struct _ParsingContext ParsingContext;
+
+static ParsingContext *new_parsing_context (void);
+
+static void destroy_context (ParsingContext * a_ctxt);
+
+static void unrecoverable_error (CRDocHandler * a_this);
+
+static void error (CRDocHandler * a_this);
+
+static void property (CRDocHandler * a_this,
+ CRString * a_name,
+ CRTerm * a_expression,
+ gboolean a_important);
+
+static void end_selector (CRDocHandler * a_this,
+ CRSelector * a_selector_list);
+
+static void start_selector (CRDocHandler * a_this,
+ CRSelector * a_selector_list);
+
+static void start_font_face (CRDocHandler * a_this,
+ CRParsingLocation *a_location);
+
+static void end_font_face (CRDocHandler * a_this);
+
+static void end_document (CRDocHandler * a_this);
+
+static void start_document (CRDocHandler * a_this);
+
+static void charset (CRDocHandler * a_this,
+ CRString * a_charset,
+ CRParsingLocation *a_location);
+
+static void start_page (CRDocHandler * a_this, CRString * a_page,
+ CRString * a_pseudo_page,
+ CRParsingLocation *a_location);
+
+static void end_page (CRDocHandler * a_this, CRString * a_page,
+ CRString * a_pseudo_page);
+
+static void start_media (CRDocHandler * a_this,
+ GList * a_media_list,
+ CRParsingLocation *a_location);
+
+static void end_media (CRDocHandler * a_this,
+ GList * a_media_list);
+
+static void import_style (CRDocHandler * a_this,
+ GList * a_media_list,
+ CRString * a_uri,
+ CRString * a_uri_default_ns,
+ CRParsingLocation *a_location);
+
+struct _ParsingContext {
+ CRStyleSheet *stylesheet;
+ CRStatement *cur_stmt;
+ CRStatement *cur_media_stmt;
+};
+
+/********************************************
+ *Private methods
+ ********************************************/
+
+static ParsingContext *
+new_parsing_context (void)
+{
+ ParsingContext *result = NULL;
+
+ result = g_try_malloc (sizeof (ParsingContext));
+ if (!result) {
+ cr_utils_trace_info ("Out of Memory");
+ return NULL;
+ }
+ memset (result, 0, sizeof (ParsingContext));
+ return result;
+}
+
+static void
+destroy_context (ParsingContext * a_ctxt)
+{
+ g_return_if_fail (a_ctxt);
+
+ if (a_ctxt->stylesheet) {
+ cr_stylesheet_destroy (a_ctxt->stylesheet);
+ a_ctxt->stylesheet = NULL;
+ }
+ if (a_ctxt->cur_stmt) {
+ cr_statement_destroy (a_ctxt->cur_stmt);
+ a_ctxt->cur_stmt = NULL;
+ }
+ g_free (a_ctxt);
+}
+
+static enum CRStatus
+cr_om_parser_init_default_sac_handler (CROMParser * a_this)
+{
+ CRDocHandler *sac_handler = NULL;
+ gboolean created_handler = FALSE;
+ enum CRStatus status = CR_OK;
+
+ g_return_val_if_fail (a_this && PRIVATE (a_this)
+ && PRIVATE (a_this)->parser,
+ CR_BAD_PARAM_ERROR);
+
+ status = cr_parser_get_sac_handler (PRIVATE (a_this)->parser,
+ &sac_handler);
+ g_return_val_if_fail (status == CR_OK, status);
+
+ if (!sac_handler) {
+ sac_handler = cr_doc_handler_new ();
+ created_handler = TRUE;
+ }
+
+ /*
+ *initialize here the sac handler.
+ */
+ sac_handler->start_document = start_document;
+ sac_handler->end_document = end_document;
+ sac_handler->start_selector = start_selector;
+ sac_handler->end_selector = end_selector;
+ sac_handler->property = property;
+ sac_handler->start_font_face = start_font_face;
+ sac_handler->end_font_face = end_font_face;
+ sac_handler->error = error;
+ sac_handler->unrecoverable_error = unrecoverable_error;
+ sac_handler->charset = charset;
+ sac_handler->start_page = start_page;
+ sac_handler->end_page = end_page;
+ sac_handler->start_media = start_media;
+ sac_handler->end_media = end_media;
+ sac_handler->import_style = import_style;
+
+ if (created_handler) {
+ status = cr_parser_set_sac_handler (PRIVATE (a_this)->parser,
+ sac_handler);
+ cr_doc_handler_unref (sac_handler);
+ }
+
+ return status;
+
+}
+
+static void
+start_document (CRDocHandler * a_this)
+{
+ ParsingContext *ctxt = NULL;
+ CRStyleSheet *stylesheet = NULL;
+
+ g_return_if_fail (a_this);
+
+ ctxt = new_parsing_context ();
+ g_return_if_fail (ctxt);
+
+ stylesheet = cr_stylesheet_new (NULL);
+ ctxt->stylesheet = stylesheet;
+ cr_doc_handler_set_ctxt (a_this, ctxt);
+}
+
+static void
+start_font_face (CRDocHandler * a_this,
+ CRParsingLocation *a_location)
+{
+ enum CRStatus status = CR_OK;
+ ParsingContext *ctxt = NULL;
+ ParsingContext **ctxtptr = NULL;
+
+ g_return_if_fail (a_this);
+
+ g_return_if_fail (a_this);
+ ctxtptr = &ctxt;
+ status = cr_doc_handler_get_ctxt (a_this, (gpointer *) ctxtptr);
+ g_return_if_fail (status == CR_OK && ctxt);
+ g_return_if_fail (ctxt->cur_stmt == NULL);
+
+ ctxt->cur_stmt =
+ cr_statement_new_at_font_face_rule (ctxt->stylesheet, NULL);
+
+ g_return_if_fail (ctxt->cur_stmt);
+}
+
+static void
+end_font_face (CRDocHandler * a_this)
+{
+ enum CRStatus status = CR_OK;
+ ParsingContext *ctxt = NULL;
+ ParsingContext **ctxtptr = NULL;
+ CRStatement *stmts = NULL;
+
+ g_return_if_fail (a_this);
+
+ g_return_if_fail (a_this);
+ ctxtptr = &ctxt;
+ status = cr_doc_handler_get_ctxt (a_this, (gpointer *) ctxtptr);
+ g_return_if_fail (status == CR_OK && ctxt);
+ g_return_if_fail
+ (ctxt->cur_stmt
+ && ctxt->cur_stmt->type == AT_FONT_FACE_RULE_STMT
+ && ctxt->stylesheet);
+
+ stmts = cr_statement_append (ctxt->stylesheet->statements,
+ ctxt->cur_stmt);
+ if (!stmts)
+ goto error;
+
+ ctxt->stylesheet->statements = stmts;
+ stmts = NULL;
+ ctxt->cur_stmt = NULL;
+
+ return;
+
+ error:
+
+ if (ctxt->cur_stmt) {
+ cr_statement_destroy (ctxt->cur_stmt);
+ ctxt->cur_stmt = NULL;
+ }
+
+ if (!stmts) {
+ cr_statement_destroy (stmts);
+ stmts = NULL;
+ }
+}
+
+static void
+end_document (CRDocHandler * a_this)
+{
+ enum CRStatus status = CR_OK;
+ ParsingContext *ctxt = NULL;
+ ParsingContext **ctxtptr = NULL;
+
+ g_return_if_fail (a_this);
+ ctxtptr = &ctxt;
+ status = cr_doc_handler_get_ctxt (a_this, (gpointer *) ctxtptr);
+ g_return_if_fail (status == CR_OK && ctxt);
+
+ if (!ctxt->stylesheet || ctxt->cur_stmt)
+ goto error;
+
+ status = cr_doc_handler_set_result (a_this, ctxt->stylesheet);
+ g_return_if_fail (status == CR_OK);
+
+ ctxt->stylesheet = NULL;
+ destroy_context (ctxt);
+ cr_doc_handler_set_ctxt (a_this, NULL);
+
+ return;
+
+ error:
+ if (ctxt) {
+ destroy_context (ctxt);
+ }
+}
+
+static void
+charset (CRDocHandler * a_this, CRString * a_charset,
+ CRParsingLocation *a_location)
+{
+ enum CRStatus status = CR_OK;
+ CRStatement *stmt = NULL,
+ *stmt2 = NULL;
+ CRString *charset = NULL;
+
+ ParsingContext *ctxt = NULL;
+ ParsingContext **ctxtptr = NULL;
+
+ g_return_if_fail (a_this);
+ ctxtptr = &ctxt;
+ status = cr_doc_handler_get_ctxt (a_this, (gpointer *) ctxtptr);
+ g_return_if_fail (status == CR_OK && ctxt);
+ g_return_if_fail (ctxt->stylesheet);
+
+ charset = cr_string_dup (a_charset) ;
+ stmt = cr_statement_new_at_charset_rule (ctxt->stylesheet, charset);
+ g_return_if_fail (stmt);
+ stmt2 = cr_statement_append (ctxt->stylesheet->statements, stmt);
+ if (!stmt2) {
+ if (stmt) {
+ cr_statement_destroy (stmt);
+ stmt = NULL;
+ }
+ if (charset) {
+ cr_string_destroy (charset);
+ }
+ return;
+ }
+ ctxt->stylesheet->statements = stmt2;
+ stmt2 = NULL;
+}
+
+static void
+start_page (CRDocHandler * a_this,
+ CRString * a_page,
+ CRString * a_pseudo,
+ CRParsingLocation *a_location)
+{
+ enum CRStatus status = CR_OK;
+ ParsingContext *ctxt = NULL;
+ ParsingContext **ctxtptr = NULL;
+
+ g_return_if_fail (a_this);
+ ctxtptr = &ctxt;
+ status = cr_doc_handler_get_ctxt (a_this, (gpointer *) ctxtptr);
+ g_return_if_fail (status == CR_OK && ctxt);
+ g_return_if_fail (ctxt->cur_stmt == NULL);
+
+ ctxt->cur_stmt = cr_statement_new_at_page_rule
+ (ctxt->stylesheet, NULL, NULL, NULL);
+ if (a_page) {
+ ctxt->cur_stmt->kind.page_rule->name =
+ cr_string_dup (a_page) ;
+
+ if (!ctxt->cur_stmt->kind.page_rule->name) {
+ goto error;
+ }
+ }
+ if (a_pseudo) {
+ ctxt->cur_stmt->kind.page_rule->pseudo =
+ cr_string_dup (a_pseudo) ;
+ if (!ctxt->cur_stmt->kind.page_rule->pseudo) {
+ goto error;
+ }
+ }
+ return;
+
+ error:
+ if (ctxt->cur_stmt) {
+ cr_statement_destroy (ctxt->cur_stmt);
+ ctxt->cur_stmt = NULL;
+ }
+}
+
+static void
+end_page (CRDocHandler * a_this,
+ CRString * a_page,
+ CRString * a_pseudo_page)
+{
+ enum CRStatus status = CR_OK;
+ ParsingContext *ctxt = NULL;
+ ParsingContext **ctxtptr = NULL;
+ CRStatement *stmt = NULL;
+
+ (void) a_page;
+ (void) a_pseudo_page;
+
+ g_return_if_fail (a_this);
+
+ ctxtptr = &ctxt;
+ status = cr_doc_handler_get_ctxt (a_this, (gpointer *) ctxtptr);
+
+ g_return_if_fail (status == CR_OK && ctxt);
+
+ g_return_if_fail (ctxt->cur_stmt
+ && ctxt->cur_stmt->type == AT_PAGE_RULE_STMT
+ && ctxt->stylesheet);
+
+ stmt = cr_statement_append (ctxt->stylesheet->statements,
+ ctxt->cur_stmt);
+
+ if (stmt) {
+ ctxt->stylesheet->statements = stmt;
+ stmt = NULL;
+ ctxt->cur_stmt = NULL;
+ }
+
+ if (ctxt->cur_stmt) {
+ cr_statement_destroy (ctxt->cur_stmt);
+ ctxt->cur_stmt = NULL;
+ }
+ a_page = NULL; /*keep compiler happy */
+ a_pseudo_page = NULL; /*keep compiler happy */
+}
+
+static void
+start_media (CRDocHandler * a_this,
+ GList * a_media_list,
+ CRParsingLocation *a_location)
+{
+ enum CRStatus status = CR_OK;
+ ParsingContext *ctxt = NULL;
+ ParsingContext **ctxtptr = NULL;
+ GList *media_list = NULL;
+
+ g_return_if_fail (a_this);
+ ctxtptr = &ctxt;
+ status = cr_doc_handler_get_ctxt (a_this, (gpointer *) ctxtptr);
+ g_return_if_fail (status == CR_OK && ctxt);
+
+ g_return_if_fail (ctxt
+ && ctxt->cur_stmt == NULL
+ && ctxt->cur_media_stmt == NULL
+ && ctxt->stylesheet);
+ if (a_media_list) {
+ /*duplicate the media_list */
+ media_list = cr_utils_dup_glist_of_cr_string
+ (a_media_list);
+ }
+ ctxt->cur_media_stmt =
+ cr_statement_new_at_media_rule
+ (ctxt->stylesheet, NULL, media_list);
+
+}
+
+static void
+end_media (CRDocHandler * a_this, GList * a_media_list)
+{
+ enum CRStatus status = CR_OK;
+ ParsingContext *ctxt = NULL;
+ ParsingContext **ctxtptr = NULL;
+ CRStatement *stmts = NULL;
+
+ (void) a_media_list;
+
+ g_return_if_fail (a_this);
+
+ ctxtptr = &ctxt;
+ status = cr_doc_handler_get_ctxt (a_this, (gpointer *) ctxtptr);
+
+ g_return_if_fail (status == CR_OK && ctxt);
+
+ g_return_if_fail (ctxt
+ && ctxt->cur_media_stmt
+ && ctxt->cur_media_stmt->type == AT_MEDIA_RULE_STMT
+ && ctxt->stylesheet);
+
+ stmts = cr_statement_append (ctxt->stylesheet->statements,
+ ctxt->cur_media_stmt);
+
+ if (!stmts) {
+ cr_statement_destroy (ctxt->cur_media_stmt);
+ ctxt->cur_media_stmt = NULL;
+ }
+
+ ctxt->stylesheet->statements = stmts;
+ stmts = NULL;
+
+ ctxt->cur_stmt = NULL ;
+ ctxt->cur_media_stmt = NULL ;
+ a_media_list = NULL;
+}
+
+static void
+import_style (CRDocHandler * a_this,
+ GList * a_media_list,
+ CRString * a_uri,
+ CRString * a_uri_default_ns,
+ CRParsingLocation *a_location)
+{
+ enum CRStatus status = CR_OK;
+ CRString *uri = NULL;
+ CRStatement *stmt = NULL,
+ *stmt2 = NULL;
+ ParsingContext *ctxt = NULL;
+ ParsingContext **ctxtptr = NULL;
+ GList *media_list = NULL ;
+
+ (void) a_uri_default_ns;
+
+ g_return_if_fail (a_this);
+
+ ctxtptr = &ctxt;
+ status = cr_doc_handler_get_ctxt (a_this, (gpointer *) ctxtptr);
+
+ g_return_if_fail (status == CR_OK && ctxt);
+
+ g_return_if_fail (ctxt->stylesheet);
+
+ uri = cr_string_dup (a_uri) ;
+
+ if (a_media_list)
+ media_list = cr_utils_dup_glist_of_cr_string (a_media_list) ;
+
+ stmt = cr_statement_new_at_import_rule
+ (ctxt->stylesheet, uri, media_list, NULL);
+
+ if (!stmt)
+ goto error;
+
+ if (ctxt->cur_stmt) {
+ stmt2 = cr_statement_append (ctxt->cur_stmt, stmt);
+ if (!stmt2)
+ goto error;
+ ctxt->cur_stmt = stmt2;
+ stmt2 = NULL;
+ stmt = NULL;
+ } else {
+ stmt2 = cr_statement_append (ctxt->stylesheet->statements,
+ stmt);
+ if (!stmt2)
+ goto error;
+ ctxt->stylesheet->statements = stmt2;
+ stmt2 = NULL;
+ stmt = NULL;
+ }
+
+ return;
+
+ error:
+ if (uri) {
+ cr_string_destroy (uri);
+ }
+
+ if (stmt) {
+ cr_statement_destroy (stmt);
+ stmt = NULL;
+ }
+ a_uri_default_ns = NULL; /*keep compiler happy */
+}
+
+static void
+start_selector (CRDocHandler * a_this, CRSelector * a_selector_list)
+{
+ enum CRStatus status = CR_OK ;
+ ParsingContext *ctxt = NULL;
+ ParsingContext **ctxtptr = NULL;
+
+ g_return_if_fail (a_this);
+ ctxtptr = &ctxt;
+ status = cr_doc_handler_get_ctxt (a_this, (gpointer *) ctxtptr);
+ g_return_if_fail (status == CR_OK && ctxt);
+ if (ctxt->cur_stmt) {
+ /*hmm, this should be NULL so free it */
+ cr_statement_destroy (ctxt->cur_stmt);
+ ctxt->cur_stmt = NULL;
+ }
+
+ ctxt->cur_stmt = cr_statement_new_ruleset
+ (ctxt->stylesheet, a_selector_list, NULL, NULL);
+}
+
+static void
+end_selector (CRDocHandler * a_this, CRSelector * a_selector_list)
+{
+ enum CRStatus status = CR_OK;
+ ParsingContext *ctxt = NULL;
+ ParsingContext **ctxtptr = NULL;
+
+ (void) a_selector_list;
+
+ g_return_if_fail (a_this);
+
+ ctxtptr = &ctxt;
+ status = cr_doc_handler_get_ctxt (a_this, (gpointer *) ctxtptr);
+
+ g_return_if_fail (status == CR_OK && ctxt);
+
+ g_return_if_fail (ctxt->cur_stmt && ctxt->stylesheet);
+
+ if (ctxt->cur_stmt) {
+ CRStatement *stmts = NULL;
+
+ if (ctxt->cur_media_stmt) {
+ CRAtMediaRule *media_rule = NULL;
+
+ media_rule = ctxt->cur_media_stmt->kind.media_rule;
+
+ stmts = cr_statement_append
+ (media_rule->rulesets, ctxt->cur_stmt);
+
+ if (!stmts) {
+ cr_utils_trace_info
+ ("Could not append a new statement");
+ cr_statement_destroy (media_rule->rulesets);
+ ctxt->cur_media_stmt->
+ kind.media_rule->rulesets = NULL;
+ return;
+ }
+ media_rule->rulesets = stmts;
+ ctxt->cur_stmt = NULL;
+ } else {
+ stmts = cr_statement_append
+ (ctxt->stylesheet->statements,
+ ctxt->cur_stmt);
+ if (!stmts) {
+ cr_utils_trace_info
+ ("Could not append a new statement");
+ cr_statement_destroy (ctxt->cur_stmt);
+ ctxt->cur_stmt = NULL;
+ return;
+ }
+ ctxt->stylesheet->statements = stmts;
+ ctxt->cur_stmt = NULL;
+ }
+
+ }
+
+ a_selector_list = NULL; /*keep compiler happy */
+}
+
+static void
+property (CRDocHandler * a_this,
+ CRString * a_name,
+ CRTerm * a_expression,
+ gboolean a_important)
+{
+ enum CRStatus status = CR_OK;
+ ParsingContext *ctxt = NULL;
+ ParsingContext **ctxtptr = NULL;
+ CRDeclaration *decl = NULL,
+ *decl2 = NULL;
+ CRString *str = NULL;
+
+ g_return_if_fail (a_this);
+ ctxtptr = &ctxt;
+ status = cr_doc_handler_get_ctxt (a_this, (gpointer *) ctxtptr);
+ g_return_if_fail (status == CR_OK && ctxt);
+
+ /*
+ *make sure a current ruleset statement has been allocated
+ *already.
+ */
+ g_return_if_fail
+ (ctxt->cur_stmt
+ &&
+ (ctxt->cur_stmt->type == RULESET_STMT
+ || ctxt->cur_stmt->type == AT_FONT_FACE_RULE_STMT
+ || ctxt->cur_stmt->type == AT_PAGE_RULE_STMT));
+
+ if (a_name) {
+ str = cr_string_dup (a_name);
+ g_return_if_fail (str);
+ }
+
+ /*instantiates a new declaration */
+ decl = cr_declaration_new (ctxt->cur_stmt, str, a_expression);
+ g_return_if_fail (decl);
+ str = NULL;
+ decl->important = a_important;
+ /*
+ *add the new declaration to the current statement
+ *being build.
+ */
+ switch (ctxt->cur_stmt->type) {
+ case RULESET_STMT:
+ decl2 = cr_declaration_append
+ (ctxt->cur_stmt->kind.ruleset->decl_list, decl);
+ if (!decl2) {
+ cr_declaration_destroy (decl);
+ cr_utils_trace_info
+ ("Could not append decl to ruleset");
+ goto error;
+ }
+ ctxt->cur_stmt->kind.ruleset->decl_list = decl2;
+ decl = NULL;
+ decl2 = NULL;
+ break;
+
+ case AT_FONT_FACE_RULE_STMT:
+ decl2 = cr_declaration_append
+ (ctxt->cur_stmt->kind.font_face_rule->decl_list,
+ decl);
+ if (!decl2) {
+ cr_declaration_destroy (decl);
+ cr_utils_trace_info
+ ("Could not append decl to ruleset");
+ goto error;
+ }
+ ctxt->cur_stmt->kind.font_face_rule->decl_list = decl2;
+ decl = NULL;
+ decl2 = NULL;
+ break;
+ case AT_PAGE_RULE_STMT:
+ decl2 = cr_declaration_append
+ (ctxt->cur_stmt->kind.page_rule->decl_list, decl);
+ if (!decl2) {
+ cr_declaration_destroy (decl);
+ cr_utils_trace_info
+ ("Could not append decl to ruleset");
+ goto error;
+ }
+ ctxt->cur_stmt->kind.page_rule->decl_list = decl2;
+ decl = NULL;
+ decl2 = NULL;
+ break;
+
+ default:
+ goto error;
+ break;
+ }
+
+ return;
+
+ error:
+ if (str) {
+ g_free (str);
+ str = NULL;
+ }
+
+ if (decl) {
+ cr_declaration_destroy (decl);
+ decl = NULL;
+ }
+}
+
+static void
+error (CRDocHandler * a_this)
+{
+ enum CRStatus status = CR_OK;
+ ParsingContext *ctxt = NULL;
+ ParsingContext **ctxtptr = NULL;
+
+ g_return_if_fail (a_this);
+ ctxtptr = &ctxt;
+ status = cr_doc_handler_get_ctxt (a_this, (gpointer *) ctxtptr);
+ g_return_if_fail (status == CR_OK && ctxt);
+
+ if (ctxt->cur_stmt) {
+ cr_statement_destroy (ctxt->cur_stmt);
+ ctxt->cur_stmt = NULL;
+ }
+}
+
+static void
+unrecoverable_error (CRDocHandler * a_this)
+{
+ enum CRStatus status = CR_OK;
+ ParsingContext *ctxt = NULL;
+ ParsingContext **ctxtptr = NULL;
+
+ ctxtptr = &ctxt;
+ status = cr_doc_handler_get_ctxt (a_this, (gpointer *) ctxtptr);
+ g_return_if_fail (status == CR_OK);
+
+ if (ctxt) {
+ if (ctxt->stylesheet) {
+ status = cr_doc_handler_set_result
+ (a_this, ctxt->stylesheet);
+ g_return_if_fail (status == CR_OK);
+ }
+ g_free (ctxt);
+ cr_doc_handler_set_ctxt (a_this, NULL);
+ }
+}
+
+/********************************************
+ *Public methods
+ ********************************************/
+
+/**
+ * cr_om_parser_new:
+ *@a_input: the input stream.
+ *
+ *Constructor of the CROMParser.
+ *Returns the newly built instance of #CROMParser.
+ */
+CROMParser *
+cr_om_parser_new (CRInput * a_input)
+{
+ CROMParser *result = NULL;
+ enum CRStatus status = CR_OK;
+
+ result = g_try_malloc (sizeof (CROMParser));
+
+ if (!result) {
+ cr_utils_trace_info ("Out of memory");
+ return NULL;
+ }
+
+ memset (result, 0, sizeof (CROMParser));
+ PRIVATE (result) = g_try_malloc (sizeof (CROMParserPriv));
+
+ if (!PRIVATE (result)) {
+ cr_utils_trace_info ("Out of memory");
+ goto error;
+ }
+
+ memset (PRIVATE (result), 0, sizeof (CROMParserPriv));
+
+ PRIVATE (result)->parser = cr_parser_new_from_input (a_input);
+
+ if (!PRIVATE (result)->parser) {
+ cr_utils_trace_info ("parsing instantiation failed");
+ goto error;
+ }
+
+ status = cr_om_parser_init_default_sac_handler (result);
+
+ if (status != CR_OK) {
+ goto error;
+ }
+
+ return result;
+
+ error:
+
+ if (result) {
+ cr_om_parser_destroy (result);
+ }
+
+ return NULL;
+}
+
+/**
+ * cr_om_parser_parse_buf:
+ *@a_this: the current instance of #CROMParser.
+ *@a_buf: the in memory buffer to parse.
+ *@a_len: the length of the in memory buffer in number of bytes.
+ *@a_enc: the encoding of the in memory buffer.
+ *@a_result: out parameter the resulting style sheet
+ *
+ *Parses the content of an in memory buffer.
+ *
+ *Returns CR_OK upon successful completion, an error code otherwise.
+ */
+enum CRStatus
+cr_om_parser_parse_buf (CROMParser * a_this,
+ const guchar * a_buf,
+ gulong a_len,
+ enum CREncoding a_enc, CRStyleSheet ** a_result)
+{
+
+ enum CRStatus status = CR_OK;
+
+ g_return_val_if_fail (a_this && a_result, CR_BAD_PARAM_ERROR);
+
+ if (!PRIVATE (a_this)->parser) {
+ PRIVATE (a_this)->parser = cr_parser_new (NULL);
+ }
+
+ status = cr_parser_parse_buf (PRIVATE (a_this)->parser,
+ a_buf, a_len, a_enc);
+
+ if (status == CR_OK) {
+ CRStyleSheet *result = NULL;
+ CRStyleSheet **resultptr = NULL;
+ CRDocHandler *sac_handler = NULL;
+
+ cr_parser_get_sac_handler (PRIVATE (a_this)->parser,
+ &sac_handler);
+ g_return_val_if_fail (sac_handler, CR_ERROR);
+ resultptr = &result;
+ status = cr_doc_handler_get_result (sac_handler,
+ (gpointer *) resultptr);
+ g_return_val_if_fail (status == CR_OK, status);
+
+ if (result)
+ *a_result = result;
+ }
+
+ return status;
+}
+
+/**
+ * cr_om_parser_simply_parse_buf:
+ *@a_buf: the css2 in memory buffer.
+ *@a_len: the length of the in memory buffer.
+ *@a_enc: the encoding of the in memory buffer.
+ *@a_result: out parameter. The resulting css2 style sheet.
+ *
+ *The simpler way to parse an in memory css2 buffer.
+ *
+ *Returns CR_OK upon successful completion, an error code otherwise.
+ */
+enum CRStatus
+cr_om_parser_simply_parse_buf (const guchar * a_buf,
+ gulong a_len,
+ enum CREncoding a_enc,
+ CRStyleSheet ** a_result)
+{
+ CROMParser *parser = NULL;
+ enum CRStatus status = CR_OK;
+
+ parser = cr_om_parser_new (NULL);
+ if (!parser) {
+ cr_utils_trace_info ("Could not create om parser");
+ cr_utils_trace_info ("System possibly out of memory");
+ return CR_ERROR;
+ }
+
+ status = cr_om_parser_parse_buf (parser, a_buf, a_len,
+ a_enc, a_result);
+
+ if (parser) {
+ cr_om_parser_destroy (parser);
+ parser = NULL;
+ }
+
+ return status;
+}
+
+/**
+ * cr_om_parser_parse_file:
+ *@a_this: the current instance of the cssom parser.
+ *@a_file_uri: the uri of the file.
+ *(only local file paths are supported so far)
+ *@a_enc: the encoding of the file.
+ *@a_result: out parameter. A pointer
+ *the build css object model.
+ *
+ *Parses a css2 stylesheet contained
+ *in a file.
+ *
+ * Returns CR_OK upon successful completion, an error code otherwise.
+ */
+enum CRStatus
+cr_om_parser_parse_file (CROMParser * a_this,
+ const guchar * a_file_uri,
+ enum CREncoding a_enc, CRStyleSheet ** a_result)
+{
+ enum CRStatus status = CR_OK;
+
+ g_return_val_if_fail (a_this && a_file_uri && a_result,
+ CR_BAD_PARAM_ERROR);
+
+ if (!PRIVATE (a_this)->parser) {
+ PRIVATE (a_this)->parser = cr_parser_new_from_file
+ (a_file_uri, a_enc);
+ }
+
+ status = cr_parser_parse_file (PRIVATE (a_this)->parser,
+ a_file_uri, a_enc);
+
+ if (status == CR_OK) {
+ CRStyleSheet *result = NULL;
+ CRStyleSheet **resultptr = NULL;
+ CRDocHandler *sac_handler = NULL;
+
+ cr_parser_get_sac_handler (PRIVATE (a_this)->parser,
+ &sac_handler);
+ g_return_val_if_fail (sac_handler, CR_ERROR);
+ resultptr = &result;
+ status = cr_doc_handler_get_result
+ (sac_handler, (gpointer *) resultptr);
+ g_return_val_if_fail (status == CR_OK, status);
+ if (result)
+ *a_result = result;
+ }
+
+ return status;
+}
+
+/**
+ * cr_om_parser_simply_parse_file:
+ *@a_file_path: the css2 local file path.
+ *@a_enc: the file encoding.
+ *@a_result: out parameter. The returned css stylesheet.
+ *Must be freed by the caller using cr_stylesheet_destroy.
+ *
+ *The simpler method to parse a css2 file.
+ *
+ *Returns CR_OK upon successful completion, an error code otherwise.
+ *Note that this method uses cr_om_parser_parse_file() so both methods
+ *have the same return values.
+ */
+enum CRStatus
+cr_om_parser_simply_parse_file (const guchar * a_file_path,
+ enum CREncoding a_enc,
+ CRStyleSheet ** a_result)
+{
+ CROMParser *parser = NULL;
+ enum CRStatus status = CR_OK;
+
+ parser = cr_om_parser_new (NULL);
+ if (!parser) {
+ cr_utils_trace_info ("Could not allocate om parser");
+ cr_utils_trace_info ("System may be out of memory");
+ return CR_ERROR;
+ }
+
+ status = cr_om_parser_parse_file (parser, a_file_path,
+ a_enc, a_result);
+ if (parser) {
+ cr_om_parser_destroy (parser);
+ parser = NULL;
+ }
+
+ return status;
+}
+
+/**
+ * cr_om_parser_parse_paths_to_cascade:
+ *@a_this: the current instance of #CROMParser
+ *@a_author_path: the path to the author stylesheet
+ *@a_user_path: the path to the user stylesheet
+ *@a_ua_path: the path to the User Agent stylesheet
+ *@a_encoding: the encoding of the sheets.
+ *@a_result: out parameter. The resulting cascade if the parsing
+ *was okay
+ *
+ *Parses three sheets located by their paths and build a cascade
+ *
+ *Returns CR_OK upon successful completion, an error code otherwise
+ */
+enum CRStatus
+cr_om_parser_parse_paths_to_cascade (CROMParser * a_this,
+ const guchar * a_author_path,
+ const guchar * a_user_path,
+ const guchar * a_ua_path,
+ enum CREncoding a_encoding,
+ CRCascade ** a_result)
+{
+ enum CRStatus status = CR_OK;
+
+ /*0->author sheet, 1->user sheet, 2->UA sheet */
+ CRStyleSheet *sheets[3];
+ guchar *paths[3];
+ CRCascade *result = NULL;
+ gint i = 0;
+
+ g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR);
+
+ memset (sheets, 0, sizeof (CRStyleSheet*) * 3);
+ paths[0] = (guchar *) a_author_path;
+ paths[1] = (guchar *) a_user_path;
+ paths[2] = (guchar *) a_ua_path;
+
+ for (i = 0; i < 3; i++) {
+ status = cr_om_parser_parse_file (a_this, paths[i],
+ a_encoding, &sheets[i]);
+ if (status != CR_OK) {
+ if (sheets[i]) {
+ cr_stylesheet_unref (sheets[i]);
+ sheets[i] = NULL;
+ }
+ continue;
+ }
+ }
+ result = cr_cascade_new (sheets[0], sheets[1], sheets[2]);
+ if (!result) {
+ for (i = 0; i < 3; i++) {
+ cr_stylesheet_unref (sheets[i]);
+ sheets[i] = 0;
+ }
+ return CR_ERROR;
+ }
+ *a_result = result;
+ return CR_OK;
+}
+
+/**
+ * cr_om_parser_simply_parse_paths_to_cascade:
+ *@a_author_path: the path to the author stylesheet
+ *@a_user_path: the path to the user stylesheet
+ *@a_ua_path: the path to the User Agent stylesheet
+ *@a_encoding: the encoding of the sheets.
+ *@a_result: out parameter. The resulting cascade if the parsing
+ *was okay
+ *
+ *Parses three sheets located by their paths and build a cascade
+ *
+ *Returns CR_OK upon successful completion, an error code otherwise
+ */
+enum CRStatus
+cr_om_parser_simply_parse_paths_to_cascade (const guchar * a_author_path,
+ const guchar * a_user_path,
+ const guchar * a_ua_path,
+ enum CREncoding a_encoding,
+ CRCascade ** a_result)
+{
+ enum CRStatus status = CR_OK;
+ CROMParser *parser = NULL;
+
+ parser = cr_om_parser_new (NULL);
+ if (!parser) {
+ cr_utils_trace_info ("could not allocated om parser");
+ cr_utils_trace_info ("System may be out of memory");
+ return CR_ERROR;
+ }
+ status = cr_om_parser_parse_paths_to_cascade (parser,
+ a_author_path,
+ a_user_path,
+ a_ua_path,
+ a_encoding, a_result);
+ if (parser) {
+ cr_om_parser_destroy (parser);
+ parser = NULL;
+ }
+ return status;
+}
+
+/**
+ * cr_om_parser_destroy:
+ *@a_this: the current instance of #CROMParser.
+ *
+ *Destructor of the #CROMParser.
+ */
+void
+cr_om_parser_destroy (CROMParser * a_this)
+{
+ g_return_if_fail (a_this && PRIVATE (a_this));
+
+ if (PRIVATE (a_this)->parser) {
+ cr_parser_destroy (PRIVATE (a_this)->parser);
+ PRIVATE (a_this)->parser = NULL;
+ }
+
+ if (PRIVATE (a_this)) {
+ g_free (PRIVATE (a_this));
+ PRIVATE (a_this) = NULL;
+ }
+
+ if (a_this) {
+ g_free (a_this);
+ a_this = NULL;
+ }
+}
diff --git a/src/st/croco/cr-om-parser.h b/src/st/croco/cr-om-parser.h
new file mode 100644
index 0000000..13d35b1
--- /dev/null
+++ b/src/st/croco/cr-om-parser.h
@@ -0,0 +1,98 @@
+/* -*- Mode: C; indent-tabs-mode:nil; c-basic-offset: 8-*- */
+
+/*
+ * This file is part of The Croco Library
+ *
+ * Copyright (C) 2002-2003 Dodji Seketeli <dodji@seketeli.org>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2.1 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * 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 Lesser General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ */
+
+/*
+ *$Id$
+ */
+
+#ifndef __CR_OM_PARSER_H__
+#define __CR_OM_PARSER_H__
+
+#include "cr-parser.h"
+#include "cr-cascade.h"
+
+
+/**
+ *@file
+ *The definition of the CSS Object Model Parser.
+ *This parser uses (and sits) the SAC api of libcroco defined
+ *in cr-parser.h and cr-doc-handler.h
+ */
+
+G_BEGIN_DECLS
+
+typedef struct _CROMParser CROMParser ;
+typedef struct _CROMParserPriv CROMParserPriv ;
+
+/**
+ *The Object model parser.
+ *Can parse a css file and build a css object model.
+ *This parser uses an instance of #CRParser and defines
+ *a set of SAC callbacks to build the Object Model.
+ */
+struct _CROMParser
+{
+ CROMParserPriv *priv ;
+} ;
+
+CROMParser * cr_om_parser_new (CRInput *a_input) ;
+
+
+enum CRStatus cr_om_parser_simply_parse_file (const guchar *a_file_path,
+ enum CREncoding a_enc,
+ CRStyleSheet **a_result) ;
+
+enum CRStatus cr_om_parser_parse_file (CROMParser *a_this,
+ const guchar *a_file_uri,
+ enum CREncoding a_enc,
+ CRStyleSheet **a_result) ;
+
+enum CRStatus cr_om_parser_simply_parse_buf (const guchar *a_buf,
+ gulong a_len,
+ enum CREncoding a_enc,
+ CRStyleSheet **a_result) ;
+
+enum CRStatus cr_om_parser_parse_buf (CROMParser *a_this,
+ const guchar *a_buf,
+ gulong a_len,
+ enum CREncoding a_enc,
+ CRStyleSheet **a_result) ;
+
+enum CRStatus cr_om_parser_parse_paths_to_cascade (CROMParser *a_this,
+ const guchar *a_author_path,
+ const guchar *a_user_path,
+ const guchar *a_ua_path,
+ enum CREncoding a_encoding,
+ CRCascade ** a_result) ;
+
+enum CRStatus cr_om_parser_simply_parse_paths_to_cascade (const guchar *a_author_path,
+ const guchar *a_user_path,
+ const guchar *a_ua_path,
+ enum CREncoding a_encoding,
+ CRCascade ** a_result) ;
+
+void cr_om_parser_destroy (CROMParser *a_this) ;
+
+G_END_DECLS
+
+#endif /*__CR_OM_PARSER_H__*/
diff --git a/src/st/croco/cr-parser.c b/src/st/croco/cr-parser.c
new file mode 100644
index 0000000..d4f40cf
--- /dev/null
+++ b/src/st/croco/cr-parser.c
@@ -0,0 +1,4539 @@
+/* -*- Mode: C; indent-tabs-mode:nil; c-basic-offset: 8-*- */
+
+/*
+ * This file is part of The Croco Library
+ *
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2.1 of the
+ * GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * 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 Lesser General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ *
+ * Author: Dodji Seketeli
+ * See COPYRIGHTS file for copyrights information.
+ */
+
+/**
+ *@CRParser:
+ *
+ *The definition of the #CRParser class.
+ */
+
+#include "string.h"
+#include "cr-parser.h"
+#include "cr-num.h"
+#include "cr-term.h"
+#include "cr-simple-sel.h"
+#include "cr-attr-sel.h"
+
+/*
+ *Random notes:
+ *CSS core syntax vs CSS level 2 syntax
+ *=====================================
+ *
+ *One must keep in mind
+ *that css UA must comply with two syntaxes.
+ *
+ *1/the specific syntax that defines the css language
+ *for a given level of specification (e.g css2 syntax
+ *defined in appendix D.1 of the css2 spec)
+ *
+ *2/the core (general) syntax that is there to allow
+ *UAs to parse style sheets written in levels of CSS that
+ *didn't exist at the time the UAs were created.
+ *
+ *the name of parsing functions (or methods) contained in this file
+ *follows the following scheme: cr_parser_parse_<production_name> (...) ;
+ *where <production_name> is the name
+ *of a production of the css2 language.
+ *When a given production is
+ *defined by the css2 level grammar *and* by the
+ *css core syntax, there will be two functions to parse that production:
+ *one will parse the production defined by the css2 level grammar and the
+ *other will parse the production defined by the css core grammar.
+ *The css2 level grammar related parsing function will be called:
+ *cr_parser_parse_<production_name> (...) ;
+ *Then css core grammar related parsing function will be called:
+ *cr_parser_parse_<production_name>_core (...) ;
+ *
+ *If a production is defined only by the css core grammar, then
+ *it will be named:
+ *cr_parser_parse_<production_name>_core (...) ;
+ */
+
+typedef struct _CRParserError CRParserError;
+
+/**
+ *An abstraction of an error reported by by the
+ *parsing routines.
+ */
+struct _CRParserError {
+ guchar *msg;
+ enum CRStatus status;
+ glong line;
+ glong column;
+ glong byte_num;
+};
+
+enum CRParserState {
+ READY_STATE = 0,
+ TRY_PARSE_CHARSET_STATE,
+ CHARSET_PARSED_STATE,
+ TRY_PARSE_IMPORT_STATE,
+ IMPORT_PARSED_STATE,
+ TRY_PARSE_RULESET_STATE,
+ RULESET_PARSED_STATE,
+ TRY_PARSE_MEDIA_STATE,
+ MEDIA_PARSED_STATE,
+ TRY_PARSE_PAGE_STATE,
+ PAGE_PARSED_STATE,
+ TRY_PARSE_FONT_FACE_STATE,
+ FONT_FACE_PARSED_STATE
+} ;
+
+/**
+ *The private attributes of
+ *#CRParser.
+ */
+struct _CRParserPriv {
+ /**
+ *The tokenizer
+ */
+ CRTknzr *tknzr;
+
+ /**
+ *The sac handlers to call
+ *to notify the parsing of
+ *the css2 constructions.
+ */
+ CRDocHandler *sac_handler;
+
+ /**
+ *A stack of errors reported
+ *by the parsing routines.
+ *Contains instance of #CRParserError.
+ *This pointer is the top of the stack.
+ */
+ GList *err_stack;
+
+ enum CRParserState state;
+ gboolean resolve_import;
+ gboolean is_case_sensitive;
+ gboolean use_core_grammar;
+};
+
+#define PRIVATE(obj) ((obj)->priv)
+
+#define CHARS_TAB_SIZE 12
+
+#define RECURSIVE_CALLERS_LIMIT 100
+
+/**
+ * IS_NUM:
+ *@a_char: the char to test.
+ *return TRUE if the character is a number ([0-9]), FALSE otherwise
+ */
+#define IS_NUM(a_char) (((a_char) >= '0' && (a_char) <= '9')?TRUE:FALSE)
+
+/**
+ *Checks if 'status' equals CR_OK. If not, goto the 'error' label.
+ *
+ *@param status the status (of type enum CRStatus) to test.
+ *@param is_exception if set to FALSE, the final status returned
+ *by the current function will be CR_PARSING_ERROR. If set to TRUE, the
+ *current status will be the current value of the 'status' variable.
+ *
+ */
+#define CHECK_PARSING_STATUS(status, is_exception) \
+if ((status) != CR_OK) \
+{ \
+ if (is_exception == FALSE) \
+ { \
+ status = CR_PARSING_ERROR ; \
+ } \
+ goto error ; \
+}
+
+/**
+ * CHECK_PARSING_STATUS_ERR:
+ *@a_this: the current instance of #CRParser .
+ *@a_status: the status to check. Is of type enum #CRStatus.
+ *@a_is_exception: in case of error, if is TRUE, the status
+ *is set to CR_PARSING_ERROR before goto error. If is false, the
+ *real low level status is kept and will be returned by the
+ *upper level function that called this macro. Usually,this must
+ *be set to FALSE.
+ *
+ *same as CHECK_PARSING_STATUS() but this one pushes an error
+ *on the parser error stack when an error arises.
+ *
+ */
+#define CHECK_PARSING_STATUS_ERR(a_this, a_status, a_is_exception,\
+ a_err_msg, a_err_status) \
+if ((a_status) != CR_OK) \
+{ \
+ if (a_is_exception == FALSE) a_status = CR_PARSING_ERROR ; \
+ cr_parser_push_error (a_this, a_err_msg, a_err_status) ; \
+ goto error ; \
+}
+
+/**
+ *Peeks the next char from the input stream of the current parser
+ *by invoking cr_tknzr_input_peek_char().
+ *invokes CHECK_PARSING_STATUS on the status returned by
+ *cr_tknzr_peek_char().
+ *
+ *@param a_this the current instance of #CRParser.
+ *@param a_to_char a pointer to the char where to store the
+ *char peeked.
+ */
+#define PEEK_NEXT_CHAR(a_this, a_to_char) \
+{\
+status = cr_tknzr_peek_char (PRIVATE (a_this)->tknzr, a_to_char) ; \
+CHECK_PARSING_STATUS (status, TRUE) \
+}
+
+/**
+ *Reads the next char from the input stream of the current parser.
+ *In case of error, jumps to the "error:" label located in the
+ *function where this macro is called.
+ *@param a_this the current instance of #CRParser
+ *@param to_char a pointer to the guint32 char where to store
+ *the character read.
+ */
+#define READ_NEXT_CHAR(a_this, a_to_char) \
+status = cr_tknzr_read_char (PRIVATE (a_this)->tknzr, a_to_char) ; \
+CHECK_PARSING_STATUS (status, TRUE)
+
+/**
+ *Gets information about the current position in
+ *the input of the parser.
+ *In case of failure, this macro returns from the
+ *calling function and
+ *returns a status code of type enum #CRStatus.
+ *@param a_this the current instance of #CRParser.
+ *@param a_pos out parameter. A pointer to the position
+ *inside the current parser input. Must
+ */
+#define RECORD_INITIAL_POS(a_this, a_pos) \
+status = cr_tknzr_get_cur_pos (PRIVATE \
+(a_this)->tknzr, a_pos) ; \
+g_return_val_if_fail (status == CR_OK, status)
+
+/**
+ *Gets the address of the current byte inside the
+ *parser input.
+ *@param parser the current instance of #CRParser.
+ *@param addr out parameter a pointer (guchar*)
+ *to where the address must be put.
+ */
+#define RECORD_CUR_BYTE_ADDR(a_this, a_addr) \
+status = cr_tknzr_get_cur_byte_addr \
+ (PRIVATE (a_this)->tknzr, a_addr) ; \
+CHECK_PARSING_STATUS (status, TRUE)
+
+/**
+ *Peeks a byte from the topmost parser input at
+ *a given offset from the current position.
+ *If it fails, goto the "error:" label.
+ *
+ *@param a_parser the current instance of #CRParser.
+ *@param a_offset the offset of the byte to peek, the
+ *current byte having the offset '0'.
+ *@param a_byte_ptr out parameter a pointer (guchar*) to
+ *where the peeked char is to be stored.
+ */
+#define PEEK_BYTE(a_parser, a_offset, a_byte_ptr) \
+status = cr_tknzr_peek_byte (PRIVATE (a_this)->tknzr, \
+ a_offset, \
+ a_byte_ptr) ; \
+CHECK_PARSING_STATUS (status, TRUE) ;
+
+#define BYTE(a_parser, a_offset, a_eof) \
+cr_tknzr_peek_byte2 (PRIVATE (a_this)->tknzr, a_offset, a_eof)
+
+/**
+ *Reads a byte from the topmost parser input
+ *steam.
+ *If it fails, goto the "error" label.
+ *@param a_this the current instance of #CRParser.
+ *@param a_byte_ptr the guchar * where to put the read char.
+ */
+#define READ_NEXT_BYTE(a_this, a_byte_ptr) \
+status = cr_tknzr_read_byte (PRIVATE (a_this)->tknzr, a_byte_ptr) ; \
+CHECK_PARSING_STATUS (status, TRUE) ;
+
+/**
+ *Skips a given number of byte in the topmost
+ *parser input. Don't update line and column number.
+ *In case of error, jumps to the "error:" label
+ *of the surrounding function.
+ *@param a_parser the current instance of #CRParser.
+ *@param a_nb_bytes the number of bytes to skip.
+ */
+#define SKIP_BYTES(a_this, a_nb_bytes) \
+status = cr_tknzr_seek_index (PRIVATE (a_this)->tknzr, \
+ CR_SEEK_CUR, a_nb_bytes) ; \
+CHECK_PARSING_STATUS (status, TRUE) ;
+
+/**
+ *Skip utf8 encoded characters.
+ *Updates line and column numbers.
+ *@param a_parser the current instance of #CRParser.
+ *@param a_nb_chars the number of chars to skip. Must be of
+ *type glong.
+ */
+#define SKIP_CHARS(a_parser, a_nb_chars) \
+{ \
+glong nb_chars = a_nb_chars ; \
+status = cr_tknzr_consume_chars \
+ (PRIVATE (a_parser)->tknzr,0, &nb_chars) ; \
+CHECK_PARSING_STATUS (status, TRUE) ; \
+}
+
+/**
+ *Tests the condition and if it is false, sets
+ *status to "CR_PARSING_ERROR" and goto the 'error'
+ *label.
+ *@param condition the condition to test.
+ */
+#define ENSURE_PARSING_COND(condition) \
+if (! (condition)) {status = CR_PARSING_ERROR; goto error ;}
+
+#define ENSURE_PARSING_COND_ERR(a_this, a_condition, \
+ a_err_msg, a_err_status) \
+if (! (a_condition)) \
+{ \
+ status = CR_PARSING_ERROR; \
+ cr_parser_push_error (a_this, a_err_msg, a_err_status) ; \
+ goto error ; \
+}
+
+#define GET_NEXT_TOKEN(a_this, a_token_ptr) \
+status = cr_tknzr_get_next_token (PRIVATE (a_this)->tknzr, \
+ a_token_ptr) ; \
+ENSURE_PARSING_COND (status == CR_OK) ;
+
+#ifdef WITH_UNICODE_ESCAPE_AND_RANGE
+static enum CRStatus cr_parser_parse_unicode_escape (CRParser * a_this,
+ guint32 * a_unicode);
+static enum CRStatus cr_parser_parse_escape (CRParser * a_this,
+ guint32 * a_esc_code);
+
+static enum CRStatus cr_parser_parse_unicode_range (CRParser * a_this,
+ CRString ** a_inf,
+ CRString ** a_sup);
+#endif
+
+static enum CRStatus cr_parser_parse_stylesheet_core (CRParser * a_this);
+
+static enum CRStatus cr_parser_parse_atrule_core (CRParser * a_this);
+
+static enum CRStatus cr_parser_parse_ruleset_core (CRParser * a_this);
+
+static enum CRStatus cr_parser_parse_selector_core (CRParser * a_this);
+
+static enum CRStatus cr_parser_parse_declaration_core (CRParser * a_this);
+
+static enum CRStatus cr_parser_parse_any_core (CRParser * a_this,
+ guint n_calls);
+
+static enum CRStatus cr_parser_parse_block_core (CRParser * a_this,
+ guint n_calls);
+
+static enum CRStatus cr_parser_parse_value_core (CRParser * a_this);
+
+static enum CRStatus cr_parser_parse_string (CRParser * a_this,
+ CRString ** a_str);
+
+static enum CRStatus cr_parser_parse_ident (CRParser * a_this,
+ CRString ** a_str);
+
+static enum CRStatus cr_parser_parse_uri (CRParser * a_this,
+ CRString ** a_str);
+
+static enum CRStatus cr_parser_parse_function (CRParser * a_this,
+ CRString ** a_func_name,
+ CRTerm ** a_expr);
+static enum CRStatus cr_parser_parse_property (CRParser * a_this,
+ CRString ** a_property);
+
+static enum CRStatus cr_parser_parse_attribute_selector (CRParser * a_this,
+ CRAttrSel ** a_sel);
+
+static enum CRStatus cr_parser_parse_simple_selector (CRParser * a_this,
+ CRSimpleSel ** a_sel);
+
+static enum CRStatus cr_parser_parse_simple_sels (CRParser * a_this,
+ CRSimpleSel ** a_sel);
+
+static CRParserError *cr_parser_error_new (const guchar * a_msg,
+ enum CRStatus);
+
+static void cr_parser_error_set_msg (CRParserError * a_this,
+ const guchar * a_msg);
+
+static void cr_parser_error_dump (CRParserError * a_this);
+
+static void cr_parser_error_set_status (CRParserError * a_this,
+ enum CRStatus a_status);
+
+static void cr_parser_error_set_pos (CRParserError * a_this,
+ glong a_line,
+ glong a_column, glong a_byte_num);
+static void
+ cr_parser_error_destroy (CRParserError * a_this);
+
+static enum CRStatus cr_parser_push_error (CRParser * a_this,
+ const guchar * a_msg,
+ enum CRStatus a_status);
+
+static enum CRStatus cr_parser_dump_err_stack (CRParser * a_this,
+ gboolean a_clear_errs);
+static enum CRStatus
+ cr_parser_clear_errors (CRParser * a_this);
+
+/*****************************
+ *error managemet methods
+ *****************************/
+
+/**
+ *Constructor of #CRParserError class.
+ *@param a_msg the brute error message.
+ *@param a_status the error status.
+ *@return the newly built instance of #CRParserError.
+ */
+static CRParserError *
+cr_parser_error_new (const guchar * a_msg, enum CRStatus a_status)
+{
+ CRParserError *result = NULL;
+
+ result = g_try_malloc (sizeof (CRParserError));
+
+ if (result == NULL) {
+ cr_utils_trace_info ("Out of memory");
+ return NULL;
+ }
+
+ memset (result, 0, sizeof (CRParserError));
+
+ cr_parser_error_set_msg (result, a_msg);
+ cr_parser_error_set_status (result, a_status);
+
+ return result;
+}
+
+/**
+ *Sets the message associated to this instance of #CRError.
+ *@param a_this the current instance of #CRParserError.
+ *@param a_msg the new message.
+ */
+static void
+cr_parser_error_set_msg (CRParserError * a_this, const guchar * a_msg)
+{
+ g_return_if_fail (a_this);
+
+ if (a_this->msg) {
+ g_free (a_this->msg);
+ }
+
+ a_this->msg = (guchar *) g_strdup ((const gchar *) a_msg);
+}
+
+/**
+ *Sets the error status.
+ *@param a_this the current instance of #CRParserError.
+ *@param a_status the new error status.
+ *
+ */
+static void
+cr_parser_error_set_status (CRParserError * a_this, enum CRStatus a_status)
+{
+ g_return_if_fail (a_this);
+
+ a_this->status = a_status;
+}
+
+/**
+ *Sets the position of the parser error.
+ *@param a_this the current instance of #CRParserError.
+ *@param a_line the line number.
+ *@param a_column the column number.
+ *@param a_byte_num the byte number.
+ */
+static void
+cr_parser_error_set_pos (CRParserError * a_this,
+ glong a_line, glong a_column, glong a_byte_num)
+{
+ g_return_if_fail (a_this);
+
+ a_this->line = a_line;
+ a_this->column = a_column;
+ a_this->byte_num = a_byte_num;
+}
+
+static void
+cr_parser_error_dump (CRParserError * a_this)
+{
+ g_return_if_fail (a_this);
+
+ g_printerr ("parsing error: %ld:%ld:", a_this->line, a_this->column);
+
+ g_printerr ("%s\n", a_this->msg);
+}
+
+/**
+ *The destructor of #CRParserError.
+ *@param a_this the current instance of #CRParserError.
+ */
+static void
+cr_parser_error_destroy (CRParserError * a_this)
+{
+ g_return_if_fail (a_this);
+
+ if (a_this->msg) {
+ g_free (a_this->msg);
+ a_this->msg = NULL;
+ }
+
+ g_free (a_this);
+}
+
+/**
+ *Pushes an error on the parser error stack.
+ *@param a_this the current instance of #CRParser.
+ *@param a_msg the error message.
+ *@param a_status the error status.
+ *@return CR_OK upon successful completion, an error code otherwise.
+ */
+static enum CRStatus
+cr_parser_push_error (CRParser * a_this,
+ const guchar * a_msg, enum CRStatus a_status)
+{
+ enum CRStatus status = CR_OK;
+
+ CRParserError *error = NULL;
+ CRInputPos pos;
+
+ g_return_val_if_fail (a_this && PRIVATE (a_this)
+ && a_msg, CR_BAD_PARAM_ERROR);
+
+ error = cr_parser_error_new (a_msg, a_status);
+
+ g_return_val_if_fail (error, CR_ERROR);
+
+ RECORD_INITIAL_POS (a_this, &pos);
+
+ cr_parser_error_set_pos
+ (error, pos.line, pos.col, pos.next_byte_index - 1);
+
+ PRIVATE (a_this)->err_stack =
+ g_list_prepend (PRIVATE (a_this)->err_stack, error);
+
+ if (PRIVATE (a_this)->err_stack == NULL)
+ goto error;
+
+ return CR_OK;
+
+ error:
+
+ if (error) {
+ cr_parser_error_destroy (error);
+ error = NULL;
+ }
+
+ return status;
+}
+
+/**
+ *Dumps the error stack on stdout.
+ *@param a_this the current instance of #CRParser.
+ *@param a_clear_errs whether to clear the error stack
+ *after the dump or not.
+ *@return CR_OK upon successful completion, an error code
+ *otherwise.
+ */
+static enum CRStatus
+cr_parser_dump_err_stack (CRParser * a_this, gboolean a_clear_errs)
+{
+ GList *cur = NULL;
+
+ g_return_val_if_fail (a_this && PRIVATE (a_this), CR_BAD_PARAM_ERROR);
+
+ if (PRIVATE (a_this)->err_stack == NULL)
+ return CR_OK;
+
+ for (cur = PRIVATE (a_this)->err_stack; cur; cur = cur->next) {
+ cr_parser_error_dump ((CRParserError *) cur->data);
+ }
+
+ if (a_clear_errs == TRUE) {
+ cr_parser_clear_errors (a_this);
+ }
+
+ return CR_OK;
+}
+
+/**
+ *Clears all the errors contained in the parser error stack.
+ *Frees all the errors, and the stack that contains'em.
+ *@param a_this the current instance of #CRParser.
+ */
+static enum CRStatus
+cr_parser_clear_errors (CRParser * a_this)
+{
+ GList *cur = NULL;
+
+ g_return_val_if_fail (a_this && PRIVATE (a_this), CR_BAD_PARAM_ERROR);
+
+ for (cur = PRIVATE (a_this)->err_stack; cur; cur = cur->next) {
+ if (cur->data) {
+ cr_parser_error_destroy ((CRParserError *)
+ cur->data);
+ }
+ }
+
+ if (PRIVATE (a_this)->err_stack) {
+ g_list_free (PRIVATE (a_this)->err_stack);
+ PRIVATE (a_this)->err_stack = NULL;
+ }
+
+ return CR_OK;
+}
+
+/**
+ * cr_parser_try_to_skip_spaces_and_comments:
+ *@a_this: the current instance of #CRParser.
+ *
+ *Same as cr_parser_try_to_skip_spaces() but this one skips
+ *spaces and comments.
+ *
+ *Returns CR_OK upon successful completion, an error code otherwise.
+ */
+enum CRStatus
+cr_parser_try_to_skip_spaces_and_comments (CRParser * a_this)
+{
+ enum CRStatus status = CR_ERROR;
+ CRToken *token = NULL;
+
+ g_return_val_if_fail (a_this && PRIVATE (a_this)
+ && PRIVATE (a_this)->tknzr, CR_BAD_PARAM_ERROR);
+ do {
+ if (token) {
+ cr_token_destroy (token);
+ token = NULL;
+ }
+
+ status = cr_tknzr_get_next_token (PRIVATE (a_this)->tknzr,
+ &token);
+ if (status != CR_OK)
+ goto error;
+ }
+ while ((token != NULL)
+ && (token->type == COMMENT_TK || token->type == S_TK));
+
+ cr_tknzr_unget_token (PRIVATE (a_this)->tknzr, token);
+
+ return status;
+
+ error:
+
+ if (token) {
+ cr_token_destroy (token);
+ token = NULL;
+ }
+
+ return status;
+}
+
+/***************************************
+ *End of Parser input handling routines
+ ***************************************/
+
+
+/*************************************
+ *Non trivial terminal productions
+ *parsing routines
+ *************************************/
+
+/**
+ *Parses a css stylesheet following the core css grammar.
+ *This is mainly done for test purposes.
+ *During the parsing, no callback is called. This is just
+ *to validate that the stylesheet is well formed according to the
+ *css core syntax.
+ *stylesheet : [ CDO | CDC | S | statement ]*;
+ *@param a_this the current instance of #CRParser.
+ *@return CR_OK upon successful completion, an error code otherwise.
+ */
+static enum CRStatus
+cr_parser_parse_stylesheet_core (CRParser * a_this)
+{
+ CRToken *token = NULL;
+ CRInputPos init_pos;
+ enum CRStatus status = CR_ERROR;
+
+ g_return_val_if_fail (a_this && PRIVATE (a_this), CR_BAD_PARAM_ERROR);
+
+ RECORD_INITIAL_POS (a_this, &init_pos);
+
+ continue_parsing:
+
+ if (token) {
+ cr_token_destroy (token);
+ token = NULL;
+ }
+
+ cr_parser_try_to_skip_spaces_and_comments (a_this);
+ status = cr_tknzr_get_next_token (PRIVATE (a_this)->tknzr, &token);
+ if (status == CR_END_OF_INPUT_ERROR) {
+ status = CR_OK;
+ goto done;
+ } else if (status != CR_OK) {
+ goto error;
+ }
+
+ switch (token->type) {
+
+ case CDO_TK:
+ case CDC_TK:
+ goto continue_parsing;
+ break;
+ default:
+ status = cr_tknzr_unget_token (PRIVATE (a_this)->tknzr,
+ token);
+ CHECK_PARSING_STATUS (status, TRUE);
+ token = NULL;
+ status = cr_parser_parse_statement_core (a_this);
+ cr_parser_clear_errors (a_this);
+ if (status == CR_OK) {
+ goto continue_parsing;
+ } else if (status == CR_END_OF_INPUT_ERROR) {
+ goto done;
+ } else {
+ goto error;
+ }
+ }
+
+ done:
+ if (token) {
+ cr_token_destroy (token);
+ token = NULL;
+ }
+
+ cr_parser_clear_errors (a_this);
+ return CR_OK;
+
+ error:
+ cr_parser_push_error
+ (a_this, (const guchar *) "could not recognize next production", CR_ERROR);
+
+ cr_parser_dump_err_stack (a_this, TRUE);
+
+ if (token) {
+ cr_token_destroy (token);
+ token = NULL;
+ }
+
+ cr_tknzr_set_cur_pos (PRIVATE (a_this)->tknzr, &init_pos);
+
+ return status;
+}
+
+/**
+ *Parses an at-rule as defined by the css core grammar
+ *in chapter 4.1 in the css2 spec.
+ *at-rule : ATKEYWORD S* any* [ block | ';' S* ];
+ *@param a_this the current instance of #CRParser.
+ *@return CR_OK upon successful completion, an error code
+ *otherwise.
+ */
+static enum CRStatus
+cr_parser_parse_atrule_core (CRParser * a_this)
+{
+ CRToken *token = NULL;
+ CRInputPos init_pos;
+ enum CRStatus status = CR_ERROR;
+
+ g_return_val_if_fail (a_this && PRIVATE (a_this), CR_BAD_PARAM_ERROR);
+
+ RECORD_INITIAL_POS (a_this, &init_pos);
+
+ status = cr_tknzr_get_next_token (PRIVATE (a_this)->tknzr,
+ &token);
+ ENSURE_PARSING_COND (status == CR_OK
+ && token
+ &&
+ (token->type == ATKEYWORD_TK
+ || token->type == IMPORT_SYM_TK
+ || token->type == PAGE_SYM_TK
+ || token->type == MEDIA_SYM_TK
+ || token->type == FONT_FACE_SYM_TK
+ || token->type == CHARSET_SYM_TK));
+
+ cr_token_destroy (token);
+ token = NULL;
+
+ cr_parser_try_to_skip_spaces_and_comments (a_this);
+
+ do {
+ status = cr_parser_parse_any_core (a_this, 0);
+ } while (status == CR_OK);
+
+ status = cr_tknzr_get_next_token (PRIVATE (a_this)->tknzr,
+ &token);
+ ENSURE_PARSING_COND (status == CR_OK && token);
+
+ if (token->type == CBO_TK) {
+ cr_tknzr_unget_token (PRIVATE (a_this)->tknzr,
+ token);
+ token = NULL;
+ status = cr_parser_parse_block_core (a_this, 0);
+ CHECK_PARSING_STATUS (status,
+ FALSE);
+ goto done;
+ } else if (token->type == SEMICOLON_TK) {
+ goto done;
+ } else {
+ status = CR_PARSING_ERROR ;
+ goto error;
+ }
+
+ done:
+ if (token) {
+ cr_token_destroy (token);
+ token = NULL;
+ }
+ return CR_OK;
+
+ error:
+ if (token) {
+ cr_token_destroy (token);
+ token = NULL;
+ }
+ cr_tknzr_set_cur_pos (PRIVATE (a_this)->tknzr,
+ &init_pos);
+ return status;
+}
+
+/**
+ *Parses a ruleset as defined by the css core grammar in chapter
+ *4.1 of the css2 spec.
+ *ruleset ::= selector? '{' S* declaration? [ ';' S* declaration? ]* '}' S*;
+ *@param a_this the current instance of #CRParser.
+ *@return CR_OK upon successful completion, an error code otherwise.
+ */
+static enum CRStatus
+cr_parser_parse_ruleset_core (CRParser * a_this)
+{
+ CRToken *token = NULL;
+ CRInputPos init_pos;
+ enum CRStatus status = CR_ERROR;
+
+ g_return_val_if_fail (a_this && PRIVATE (a_this), CR_BAD_PARAM_ERROR);
+ RECORD_INITIAL_POS (a_this, &init_pos);
+
+ status = cr_parser_parse_selector_core (a_this);
+
+ ENSURE_PARSING_COND (status == CR_OK
+ || status == CR_PARSING_ERROR
+ || status == CR_END_OF_INPUT_ERROR);
+
+ status = cr_tknzr_get_next_token (PRIVATE (a_this)->tknzr, &token);
+ ENSURE_PARSING_COND (status == CR_OK && token
+ && token->type == CBO_TK);
+ cr_token_destroy (token);
+ token = NULL;
+
+ cr_parser_try_to_skip_spaces_and_comments (a_this);
+ status = cr_parser_parse_declaration_core (a_this);
+
+ parse_declaration_list:
+ if (token) {
+ cr_token_destroy (token);
+ token = NULL;
+ }
+
+ status = cr_tknzr_get_next_token (PRIVATE (a_this)->tknzr, &token);
+ ENSURE_PARSING_COND (status == CR_OK && token);
+ if (token->type == CBC_TK) {
+ goto done;
+ }
+
+ ENSURE_PARSING_COND (status == CR_OK
+ && token && token->type == SEMICOLON_TK);
+
+ cr_token_destroy (token);
+ token = NULL;
+ cr_parser_try_to_skip_spaces_and_comments (a_this);
+ status = cr_parser_parse_declaration_core (a_this);
+ cr_parser_clear_errors (a_this);
+ ENSURE_PARSING_COND (status == CR_OK || status == CR_PARSING_ERROR);
+ status = cr_tknzr_get_next_token (PRIVATE (a_this)->tknzr, &token);
+ ENSURE_PARSING_COND (status == CR_OK && token);
+ if (token->type == CBC_TK) {
+ cr_token_destroy (token);
+ token = NULL;
+ cr_parser_try_to_skip_spaces_and_comments (a_this);
+ goto done;
+ } else {
+ status = cr_tknzr_unget_token (PRIVATE (a_this)->tknzr,
+ token);
+ token = NULL;
+ goto parse_declaration_list;
+ }
+
+ done:
+ if (token) {
+ cr_token_destroy (token);
+ token = NULL;
+ }
+
+ if (status == CR_OK) {
+ return CR_OK;
+ }
+
+ error:
+ if (token) {
+ cr_token_destroy (token);
+ token = NULL;
+ }
+
+ cr_tknzr_set_cur_pos (PRIVATE (a_this)->tknzr, &init_pos);
+
+ return status;
+}
+
+/**
+ *Parses a "selector" as specified by the css core
+ *grammar.
+ *selector : any+;
+ *@param a_this the current instance of #CRParser.
+ *@return CR_OK upon successful completion, an error code
+ *otherwise.
+ */
+static enum CRStatus
+cr_parser_parse_selector_core (CRParser * a_this)
+{
+ CRToken *token = NULL;
+ CRInputPos init_pos;
+ enum CRStatus status = CR_ERROR;
+
+ g_return_val_if_fail (a_this && PRIVATE (a_this), CR_BAD_PARAM_ERROR);
+
+ RECORD_INITIAL_POS (a_this, &init_pos);
+
+ status = cr_parser_parse_any_core (a_this, 0);
+ CHECK_PARSING_STATUS (status, FALSE);
+
+ do {
+ status = cr_parser_parse_any_core (a_this, 0);
+
+ } while (status == CR_OK);
+
+ return CR_OK;
+
+ error:
+ if (token) {
+ cr_token_destroy (token);
+ token = NULL;
+ }
+
+ cr_tknzr_set_cur_pos (PRIVATE (a_this)->tknzr, &init_pos);
+
+ return status;
+}
+
+/**
+ *Parses a "block" as defined in the css core grammar
+ *in chapter 4.1 of the css2 spec.
+ *block ::= '{' S* [ any | block | ATKEYWORD S* | ';' ]* '}' S*;
+ *@param a_this the current instance of #CRParser.
+ *@param n_calls used to limit recursion depth
+ *FIXME: code this function.
+ */
+static enum CRStatus
+cr_parser_parse_block_core (CRParser * a_this,
+ guint n_calls)
+{
+ CRToken *token = NULL;
+ CRInputPos init_pos;
+ enum CRStatus status = CR_ERROR;
+
+ g_return_val_if_fail (a_this && PRIVATE (a_this), CR_BAD_PARAM_ERROR);
+
+ if (n_calls > RECURSIVE_CALLERS_LIMIT)
+ return CR_ERROR;
+
+ RECORD_INITIAL_POS (a_this, &init_pos);
+
+ status = cr_tknzr_get_next_token (PRIVATE (a_this)->tknzr, &token);
+ ENSURE_PARSING_COND (status == CR_OK && token
+ && token->type == CBO_TK);
+
+ parse_block_content:
+
+ if (token) {
+ cr_token_destroy (token);
+ token = NULL;
+ }
+
+ cr_parser_try_to_skip_spaces_and_comments (a_this);
+
+ status = cr_tknzr_get_next_token (PRIVATE (a_this)->tknzr, &token);
+ ENSURE_PARSING_COND (status == CR_OK && token);
+
+ if (token->type == CBC_TK) {
+ cr_parser_try_to_skip_spaces_and_comments (a_this);
+ goto done;
+ } else if (token->type == SEMICOLON_TK) {
+ goto parse_block_content;
+ } else if (token->type == ATKEYWORD_TK) {
+ cr_parser_try_to_skip_spaces_and_comments (a_this);
+ goto parse_block_content;
+ } else if (token->type == CBO_TK) {
+ cr_tknzr_unget_token (PRIVATE (a_this)->tknzr, token);
+ token = NULL;
+ status = cr_parser_parse_block_core (a_this, n_calls + 1);
+ CHECK_PARSING_STATUS (status, FALSE);
+ goto parse_block_content;
+ } else {
+ cr_tknzr_unget_token (PRIVATE (a_this)->tknzr, token);
+ token = NULL;
+ status = cr_parser_parse_any_core (a_this, n_calls + 1);
+ CHECK_PARSING_STATUS (status, FALSE);
+ goto parse_block_content;
+ }
+
+ done:
+ if (token) {
+ cr_token_destroy (token);
+ token = NULL;
+ }
+
+ if (status == CR_OK)
+ return CR_OK;
+
+ error:
+ if (token) {
+ cr_token_destroy (token);
+ token = NULL;
+ }
+
+ cr_tknzr_set_cur_pos (PRIVATE (a_this)->tknzr, &init_pos);
+
+ return status;
+}
+
+static enum CRStatus
+cr_parser_parse_declaration_core (CRParser * a_this)
+{
+ CRToken *token = NULL;
+ CRInputPos init_pos;
+ enum CRStatus status = CR_ERROR;
+ CRString *prop = NULL;
+
+ g_return_val_if_fail (a_this && PRIVATE (a_this), CR_BAD_PARAM_ERROR);
+
+ RECORD_INITIAL_POS (a_this, &init_pos);
+
+ status = cr_parser_parse_property (a_this, &prop);
+ CHECK_PARSING_STATUS (status, FALSE);
+ cr_parser_clear_errors (a_this);
+ ENSURE_PARSING_COND (status == CR_OK && prop);
+ cr_string_destroy (prop);
+ prop = NULL;
+
+ status = cr_tknzr_get_next_token (PRIVATE (a_this)->tknzr, &token);
+ ENSURE_PARSING_COND (status == CR_OK
+ && token
+ && token->type == DELIM_TK
+ && token->u.unichar == ':');
+ cr_token_destroy (token);
+ token = NULL;
+ cr_parser_try_to_skip_spaces_and_comments (a_this);
+ status = cr_parser_parse_value_core (a_this);
+ CHECK_PARSING_STATUS (status, FALSE);
+
+ return CR_OK;
+
+ error:
+
+ if (prop) {
+ cr_string_destroy (prop);
+ prop = NULL;
+ }
+
+ if (token) {
+ cr_token_destroy (token);
+ token = NULL;
+ }
+
+ cr_tknzr_set_cur_pos (PRIVATE (a_this)->tknzr, &init_pos);
+
+ return status;
+}
+
+/**
+ *Parses a "value" production as defined by the css core grammar
+ *in chapter 4.1.
+ *value ::= [ any | block | ATKEYWORD S* ]+;
+ *@param a_this the current instance of #CRParser.
+ *@return CR_OK upon successful completion, an error code otherwise.
+ */
+static enum CRStatus
+cr_parser_parse_value_core (CRParser * a_this)
+{
+ CRToken *token = NULL;
+ CRInputPos init_pos;
+ enum CRStatus status = CR_ERROR;
+ glong ref = 0;
+
+ g_return_val_if_fail (a_this && PRIVATE (a_this), CR_BAD_PARAM_ERROR);
+ RECORD_INITIAL_POS (a_this, &init_pos);
+
+ continue_parsing:
+
+ if (token) {
+ cr_token_destroy (token);
+ token = NULL;
+ }
+
+ status = cr_tknzr_get_next_token (PRIVATE (a_this)->tknzr, &token);
+ ENSURE_PARSING_COND (status == CR_OK && token);
+
+ switch (token->type) {
+ case CBO_TK:
+ status = cr_tknzr_unget_token (PRIVATE (a_this)->tknzr,
+ token);
+ token = NULL;
+ status = cr_parser_parse_block_core (a_this, 0);
+ CHECK_PARSING_STATUS (status, FALSE);
+ ref++;
+ goto continue_parsing;
+
+ case ATKEYWORD_TK:
+ cr_parser_try_to_skip_spaces_and_comments (a_this);
+ ref++;
+ goto continue_parsing;
+
+ default:
+ status = cr_tknzr_unget_token (PRIVATE (a_this)->tknzr,
+ token);
+ token = NULL;
+ status = cr_parser_parse_any_core (a_this, 0);
+ if (status == CR_OK) {
+ ref++;
+ goto continue_parsing;
+ } else if (status == CR_PARSING_ERROR) {
+ status = CR_OK;
+ goto done;
+ } else {
+ goto error;
+ }
+ }
+
+ done:
+ if (token) {
+ cr_token_destroy (token);
+ token = NULL;
+ }
+
+ if (status == CR_OK && ref)
+ return CR_OK;
+ error:
+ if (token) {
+ cr_token_destroy (token);
+ token = NULL;
+ }
+
+ cr_tknzr_set_cur_pos (PRIVATE (a_this)->tknzr, &init_pos);
+
+ return status;
+}
+
+/**
+ *Parses an "any" as defined by the css core grammar in the
+ *css2 spec in chapter 4.1.
+ *any ::= [ IDENT | NUMBER | PERCENTAGE | DIMENSION | STRING
+ * | DELIM | URI | HASH | UNICODE-RANGE | INCLUDES
+ * | FUNCTION | DASHMATCH | '(' any* ')' | '[' any* ']' ] S*;
+ *
+ *@param a_this the current instance of #CRParser.
+ *@param n_calls used to limit recursion depth
+ *@return CR_OK upon successful completion, an error code otherwise.
+ */
+static enum CRStatus
+cr_parser_parse_any_core (CRParser * a_this,
+ guint n_calls)
+{
+ CRToken *token1 = NULL,
+ *token2 = NULL;
+ CRInputPos init_pos;
+ enum CRStatus status = CR_ERROR;
+
+ g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR);
+
+ if (n_calls > RECURSIVE_CALLERS_LIMIT)
+ return CR_ERROR;
+
+ RECORD_INITIAL_POS (a_this, &init_pos);
+
+ status = cr_tknzr_get_next_token (PRIVATE (a_this)->tknzr, &token1);
+
+ ENSURE_PARSING_COND (status == CR_OK && token1);
+
+ switch (token1->type) {
+ case IDENT_TK:
+ case NUMBER_TK:
+ case RGB_TK:
+ case PERCENTAGE_TK:
+ case DIMEN_TK:
+ case EMS_TK:
+ case EXS_TK:
+ case LENGTH_TK:
+ case ANGLE_TK:
+ case FREQ_TK:
+ case TIME_TK:
+ case STRING_TK:
+ case DELIM_TK:
+ case URI_TK:
+ case HASH_TK:
+ case UNICODERANGE_TK:
+ case INCLUDES_TK:
+ case DASHMATCH_TK:
+ case S_TK:
+ case COMMENT_TK:
+ case IMPORTANT_SYM_TK:
+ status = CR_OK;
+ break;
+ case FUNCTION_TK:
+ /*
+ *this case isn't specified by the spec but it
+ *does happen. So we have to handle it.
+ *We must consider function with parameters.
+ *We consider parameter as being an "any*" production.
+ */
+ do {
+ status = cr_parser_parse_any_core (a_this, n_calls + 1);
+ } while (status == CR_OK);
+
+ ENSURE_PARSING_COND (status == CR_PARSING_ERROR);
+ status = cr_tknzr_get_next_token (PRIVATE (a_this)->tknzr,
+ &token2);
+ ENSURE_PARSING_COND (status == CR_OK
+ && token2 && token2->type == PC_TK);
+ break;
+ case PO_TK:
+ status = cr_tknzr_get_next_token (PRIVATE (a_this)->tknzr,
+ &token2);
+ ENSURE_PARSING_COND (status == CR_OK && token2);
+
+ if (token2->type == PC_TK) {
+ cr_token_destroy (token2);
+ token2 = NULL;
+ goto done;
+ } else {
+ status = cr_tknzr_unget_token
+ (PRIVATE (a_this)->tknzr, token2);
+ token2 = NULL;
+ }
+
+ do {
+ status = cr_parser_parse_any_core (a_this, n_calls + 1);
+ } while (status == CR_OK);
+
+ ENSURE_PARSING_COND (status == CR_PARSING_ERROR);
+
+ status = cr_tknzr_get_next_token (PRIVATE (a_this)->tknzr,
+ &token2);
+ ENSURE_PARSING_COND (status == CR_OK
+ && token2 && token2->type == PC_TK);
+ status = CR_OK;
+ break;
+
+ case BO_TK:
+ status = cr_tknzr_get_next_token (PRIVATE (a_this)->tknzr,
+ &token2);
+ ENSURE_PARSING_COND (status == CR_OK && token2);
+
+ if (token2->type == BC_TK) {
+ cr_token_destroy (token2);
+ token2 = NULL;
+ goto done;
+ } else {
+ status = cr_tknzr_unget_token
+ (PRIVATE (a_this)->tknzr, token2);
+ token2 = NULL;
+ }
+
+ do {
+ status = cr_parser_parse_any_core (a_this, n_calls + 1);
+ } while (status == CR_OK);
+
+ ENSURE_PARSING_COND (status == CR_PARSING_ERROR);
+
+ status = cr_tknzr_get_next_token (PRIVATE (a_this)->tknzr,
+ &token2);
+ ENSURE_PARSING_COND (status == CR_OK
+ && token2 && token2->type == BC_TK);
+ status = CR_OK;
+ break;
+ default:
+ status = CR_PARSING_ERROR;
+ goto error;
+ }
+
+ done:
+ if (token1) {
+ cr_token_destroy (token1);
+ token1 = NULL;
+ }
+
+ if (token2) {
+ cr_token_destroy (token2);
+ token2 = NULL;
+ }
+
+ return CR_OK;
+
+ error:
+
+ if (token1) {
+ cr_token_destroy (token1);
+ token1 = NULL;
+ }
+
+ if (token2) {
+ cr_token_destroy (token2);
+ token2 = NULL;
+ }
+
+ cr_tknzr_set_cur_pos (PRIVATE (a_this)->tknzr, &init_pos);
+ return status;
+}
+
+/**
+ *Parses an attribute selector as defined in the css2 spec in
+ *appendix D.1:
+ *attrib ::= '[' S* IDENT S* [ [ '=' | INCLUDES | DASHMATCH ] S*
+ * [ IDENT | STRING ] S* ]? ']'
+ *
+ *@param a_this the "this pointer" of the current instance of
+ *#CRParser .
+ *@param a_sel out parameter. The successfully parsed attribute selector.
+ *@return CR_OK upon successful completion, an error code otherwise.
+ */
+static enum CRStatus
+cr_parser_parse_attribute_selector (CRParser * a_this,
+ CRAttrSel ** a_sel)
+{
+ enum CRStatus status = CR_OK;
+ CRInputPos init_pos;
+ CRToken *token = NULL;
+ CRAttrSel *result = NULL;
+ CRParsingLocation location = {0} ;
+
+ g_return_val_if_fail (a_this && a_sel, CR_BAD_PARAM_ERROR);
+
+ RECORD_INITIAL_POS (a_this, &init_pos);
+
+ status = cr_tknzr_get_next_token (PRIVATE (a_this)->tknzr, &token);
+ ENSURE_PARSING_COND (status == CR_OK && token
+ && token->type == BO_TK);
+ cr_parsing_location_copy
+ (&location, &token->location) ;
+ cr_token_destroy (token);
+ token = NULL;
+
+ cr_parser_try_to_skip_spaces_and_comments (a_this);
+
+ result = cr_attr_sel_new ();
+ if (!result) {
+ cr_utils_trace_info ("result failed") ;
+ status = CR_OUT_OF_MEMORY_ERROR ;
+ goto error ;
+ }
+ cr_parsing_location_copy (&result->location,
+ &location) ;
+ status = cr_tknzr_get_next_token (PRIVATE (a_this)->tknzr, &token);
+ ENSURE_PARSING_COND (status == CR_OK
+ && token && token->type == IDENT_TK);
+
+ result->name = token->u.str;
+ token->u.str = NULL;
+ cr_token_destroy (token);
+ token = NULL;
+
+ cr_parser_try_to_skip_spaces_and_comments (a_this);
+
+ status = cr_tknzr_get_next_token (PRIVATE (a_this)->tknzr, &token);
+ ENSURE_PARSING_COND (status == CR_OK && token);
+
+ if (token->type == INCLUDES_TK) {
+ result->match_way = INCLUDES;
+ goto parse_right_part;
+ } else if (token->type == DASHMATCH_TK) {
+ result->match_way = DASHMATCH;
+ goto parse_right_part;
+ } else if (token->type == DELIM_TK && token->u.unichar == '=') {
+ result->match_way = EQUALS;
+ goto parse_right_part;
+ } else if (token->type == BC_TK) {
+ result->match_way = SET;
+ goto done;
+ }
+
+ parse_right_part:
+
+ if (token) {
+ cr_token_destroy (token);
+ token = NULL;
+ }
+
+ cr_parser_try_to_skip_spaces_and_comments (a_this);
+
+ status = cr_tknzr_get_next_token (PRIVATE (a_this)->tknzr, &token);
+ ENSURE_PARSING_COND (status == CR_OK && token);
+
+ if (token->type == IDENT_TK) {
+ result->value = token->u.str;
+ token->u.str = NULL;
+ } else if (token->type == STRING_TK) {
+ result->value = token->u.str;
+ token->u.str = NULL;
+ } else {
+ status = CR_PARSING_ERROR;
+ goto error;
+ }
+
+ if (token) {
+ cr_token_destroy (token);
+ token = NULL;
+ }
+
+ cr_parser_try_to_skip_spaces_and_comments (a_this);
+
+ status = cr_tknzr_get_next_token (PRIVATE (a_this)->tknzr, &token);
+
+ ENSURE_PARSING_COND (status == CR_OK && token
+ && token->type == BC_TK);
+ done:
+ if (token) {
+ cr_token_destroy (token);
+ token = NULL;
+ }
+
+ if (*a_sel) {
+ status = cr_attr_sel_append_attr_sel (*a_sel, result);
+ CHECK_PARSING_STATUS (status, FALSE);
+ } else {
+ *a_sel = result;
+ }
+
+ cr_parser_clear_errors (a_this);
+ return CR_OK;
+
+ error:
+
+ if (result) {
+ cr_attr_sel_destroy (result);
+ result = NULL;
+ }
+
+ if (token) {
+ cr_token_destroy (token);
+ token = NULL;
+ }
+
+ cr_tknzr_set_cur_pos (PRIVATE (a_this)->tknzr, &init_pos);
+
+ return status;
+}
+
+/**
+ *Parses a "property" as specified by the css2 spec at [4.1.1]:
+ *property : IDENT S*;
+ *
+ *@param a_this the "this pointer" of the current instance of #CRParser.
+ *@param GString a_property out parameter. The parsed property without the
+ *trailing spaces. If *a_property is NULL, this function allocates a
+ *new instance of GString and set it content to the parsed property.
+ *If not, the property is just appended to a_property's previous content.
+ *In both cases, it is up to the caller to free a_property.
+ *@return CR_OK upon successful completion, CR_PARSING_ERROR if the
+ *next construction was not a "property", or an error code.
+ */
+static enum CRStatus
+cr_parser_parse_property (CRParser * a_this,
+ CRString ** a_property)
+{
+ enum CRStatus status = CR_OK;
+ CRInputPos init_pos;
+
+ g_return_val_if_fail (a_this && PRIVATE (a_this)
+ && PRIVATE (a_this)->tknzr
+ && a_property,
+ CR_BAD_PARAM_ERROR);
+
+ RECORD_INITIAL_POS (a_this, &init_pos);
+
+ status = cr_parser_parse_ident (a_this, a_property);
+ CHECK_PARSING_STATUS (status, TRUE);
+
+ cr_parser_try_to_skip_spaces_and_comments (a_this);
+
+ cr_parser_clear_errors (a_this);
+ return CR_OK;
+
+ error:
+
+ cr_tknzr_set_cur_pos (PRIVATE (a_this)->tknzr, &init_pos);
+
+ return status;
+}
+
+/**
+ * cr_parser_parse_term:
+ *@a_term: out parameter. The successfully parsed term.
+ *
+ *Parses a "term" as defined in the css2 spec, appendix D.1:
+ *term ::= unary_operator? [NUMBER S* | PERCENTAGE S* | LENGTH S* |
+ *EMS S* | EXS S* | ANGLE S* | TIME S* | FREQ S* | function ] |
+ *STRING S* | IDENT S* | URI S* | RGB S* | UNICODERANGE S* | hexcolor
+ *
+ *TODO: handle parsing of 'RGB'
+ *
+ *Returns CR_OK upon successful completion, an error code otherwise.
+ */
+enum CRStatus
+cr_parser_parse_term (CRParser * a_this, CRTerm ** a_term)
+{
+ enum CRStatus status = CR_PARSING_ERROR;
+ CRInputPos init_pos;
+ CRTerm *result = NULL;
+ CRTerm *param = NULL;
+ CRToken *token = NULL;
+ CRString *func_name = NULL;
+ CRParsingLocation location = {0} ;
+
+ g_return_val_if_fail (a_this && a_term, CR_BAD_PARAM_ERROR);
+
+ RECORD_INITIAL_POS (a_this, &init_pos);
+
+ result = cr_term_new ();
+
+ status = cr_tknzr_get_next_token (PRIVATE (a_this)->tknzr,
+ &token);
+ if (status != CR_OK || !token)
+ goto error;
+
+ cr_parsing_location_copy (&location, &token->location) ;
+ if (token->type == DELIM_TK && token->u.unichar == '+') {
+ result->unary_op = PLUS_UOP;
+ cr_token_destroy (token) ;
+ token = NULL ;
+ cr_parser_try_to_skip_spaces_and_comments (a_this);
+ status = cr_tknzr_get_next_token (PRIVATE (a_this)->tknzr,
+ &token);
+ if (status != CR_OK || !token)
+ goto error;
+ } else if (token->type == DELIM_TK && token->u.unichar == '-') {
+ result->unary_op = MINUS_UOP;
+ cr_token_destroy (token) ;
+ token = NULL ;
+ cr_parser_try_to_skip_spaces_and_comments (a_this);
+ status = cr_tknzr_get_next_token (PRIVATE (a_this)->tknzr,
+ &token);
+ if (status != CR_OK || !token)
+ goto error;
+ }
+
+ if (token->type == EMS_TK
+ || token->type == EXS_TK
+ || token->type == LENGTH_TK
+ || token->type == ANGLE_TK
+ || token->type == TIME_TK
+ || token->type == FREQ_TK
+ || token->type == PERCENTAGE_TK
+ || token->type == NUMBER_TK) {
+ status = cr_term_set_number (result, token->u.num);
+ CHECK_PARSING_STATUS (status, TRUE);
+ token->u.num = NULL;
+ status = CR_OK;
+ } else if (token && token->type == FUNCTION_TK) {
+ status = cr_tknzr_unget_token (PRIVATE (a_this)->tknzr,
+ token);
+ token = NULL;
+ status = cr_parser_parse_function (a_this, &func_name,
+ &param);
+
+ if (status == CR_OK) {
+ status = cr_term_set_function (result,
+ func_name,
+ param);
+ CHECK_PARSING_STATUS (status, TRUE);
+ }
+ } else if (token && token->type == STRING_TK) {
+ status = cr_term_set_string (result,
+ token->u.str);
+ CHECK_PARSING_STATUS (status, TRUE);
+ token->u.str = NULL;
+ } else if (token && token->type == IDENT_TK) {
+ status = cr_term_set_ident (result, token->u.str);
+ CHECK_PARSING_STATUS (status, TRUE);
+ token->u.str = NULL;
+ } else if (token && token->type == URI_TK) {
+ status = cr_term_set_uri (result, token->u.str);
+ CHECK_PARSING_STATUS (status, TRUE);
+ token->u.str = NULL;
+ } else if (token && token->type == RGB_TK) {
+ status = cr_term_set_rgb (result, token->u.rgb);
+ CHECK_PARSING_STATUS (status, TRUE);
+ token->u.rgb = NULL;
+ } else if (token && token->type == UNICODERANGE_TK) {
+ result->type = TERM_UNICODERANGE;
+ status = CR_PARSING_ERROR;
+ } else if (token && token->type == HASH_TK) {
+ status = cr_term_set_hash (result, token->u.str);
+ CHECK_PARSING_STATUS (status, TRUE);
+ token->u.str = NULL;
+ } else {
+ status = CR_PARSING_ERROR;
+ }
+
+ if (status != CR_OK) {
+ goto error;
+ }
+ cr_parsing_location_copy (&result->location,
+ &location) ;
+ *a_term = cr_term_append_term (*a_term, result);
+
+ result = NULL;
+
+ cr_parser_try_to_skip_spaces_and_comments (a_this);
+
+ if (token) {
+ cr_token_destroy (token);
+ token = NULL;
+ }
+
+ cr_parser_clear_errors (a_this);
+ return CR_OK;
+
+ error:
+
+ if (result) {
+ cr_term_destroy (result);
+ result = NULL;
+ }
+
+ if (token) {
+ cr_token_destroy (token);
+ token = NULL;
+ }
+
+ if (param) {
+ cr_term_destroy (param);
+ param = NULL;
+ }
+
+ if (func_name) {
+ cr_string_destroy (func_name);
+ func_name = NULL;
+ }
+
+ cr_tknzr_set_cur_pos (PRIVATE (a_this)->tknzr, &init_pos);
+
+ return status;
+}
+
+/**
+ * cr_parser_parse_simple_selector:
+ *@a_this: the "this pointer" of the current instance of #CRParser.
+ *@a_sel: out parameter. Is set to the successfully parsed simple
+ *selector.
+ *
+ *Parses a "simple_selector" as defined by the css2 spec in appendix D.1 :
+ *element_name? [ HASH | class | attrib | pseudo ]* S*
+ *and where pseudo is:
+ *pseudo ::= ':' [ IDENT | FUNCTION S* IDENT S* ')' ]
+ *
+ *Returns CR_OK upon successful completion, an error code otherwise.
+ */
+static enum CRStatus
+cr_parser_parse_simple_selector (CRParser * a_this, CRSimpleSel ** a_sel)
+{
+ enum CRStatus status = CR_ERROR;
+ CRInputPos init_pos;
+ CRToken *token = NULL;
+ CRSimpleSel *sel = NULL;
+ CRAdditionalSel *add_sel_list = NULL;
+ gboolean found_sel = FALSE;
+ guint32 cur_char = 0;
+
+ g_return_val_if_fail (a_this && a_sel, CR_BAD_PARAM_ERROR);
+
+ RECORD_INITIAL_POS (a_this, &init_pos);
+
+ status = cr_tknzr_get_next_token (PRIVATE (a_this)->tknzr, &token);
+ if (status != CR_OK)
+ goto error;
+
+ sel = cr_simple_sel_new ();
+ ENSURE_PARSING_COND (sel);
+
+ cr_parsing_location_copy
+ (&sel->location,
+ &token->location) ;
+
+ if (token && token->type == DELIM_TK
+ && token->u.unichar == '*') {
+ sel->type_mask |= UNIVERSAL_SELECTOR;
+ sel->name = cr_string_new_from_string ("*");
+ found_sel = TRUE;
+ } else if (token && token->type == IDENT_TK) {
+ sel->name = token->u.str;
+ sel->type_mask |= TYPE_SELECTOR;
+ token->u.str = NULL;
+ found_sel = TRUE;
+ } else {
+ status = cr_tknzr_unget_token
+ (PRIVATE (a_this)->tknzr,
+ token);
+ token = NULL;
+ }
+
+ if (token) {
+ cr_token_destroy (token);
+ token = NULL;
+ }
+
+ cr_parser_try_to_skip_spaces_and_comments (a_this);
+
+ for (;;) {
+ if (token) {
+ cr_token_destroy (token);
+ token = NULL;
+ }
+
+ status = cr_tknzr_get_next_token
+ (PRIVATE (a_this)->tknzr,
+ &token);
+ if (status != CR_OK)
+ goto error;
+
+ if (token && token->type == HASH_TK) {
+ /*we parsed an attribute id */
+ CRAdditionalSel *add_sel = NULL;
+
+ add_sel = cr_additional_sel_new_with_type
+ (ID_ADD_SELECTOR);
+
+ add_sel->content.id_name = token->u.str;
+ token->u.str = NULL;
+
+ cr_parsing_location_copy
+ (&add_sel->location,
+ &token->location) ;
+ add_sel_list =
+ cr_additional_sel_append
+ (add_sel_list, add_sel);
+ found_sel = TRUE;
+ } else if (token && (token->type == DELIM_TK)
+ && (token->u.unichar == '.')) {
+ cr_token_destroy (token);
+ token = NULL;
+
+ status = cr_tknzr_get_next_token
+ (PRIVATE (a_this)->tknzr, &token);
+ if (status != CR_OK)
+ goto error;
+
+ if (token && token->type == IDENT_TK) {
+ CRAdditionalSel *add_sel = NULL;
+
+ add_sel = cr_additional_sel_new_with_type
+ (CLASS_ADD_SELECTOR);
+
+ add_sel->content.class_name = token->u.str;
+ token->u.str = NULL;
+
+ add_sel_list =
+ cr_additional_sel_append
+ (add_sel_list, add_sel);
+ found_sel = TRUE;
+
+ cr_parsing_location_copy
+ (&add_sel->location,
+ & token->location) ;
+ } else {
+ status = CR_PARSING_ERROR;
+ goto error;
+ }
+ } else if (token && token->type == BO_TK) {
+ CRAttrSel *attr_sel = NULL;
+ CRAdditionalSel *add_sel = NULL;
+
+ status = cr_tknzr_unget_token
+ (PRIVATE (a_this)->tknzr, token);
+ if (status != CR_OK)
+ goto error;
+ token = NULL;
+
+ status = cr_parser_parse_attribute_selector
+ (a_this, &attr_sel);
+ CHECK_PARSING_STATUS (status, FALSE);
+
+ add_sel = cr_additional_sel_new_with_type
+ (ATTRIBUTE_ADD_SELECTOR);
+
+ ENSURE_PARSING_COND (add_sel != NULL);
+
+ add_sel->content.attr_sel = attr_sel;
+
+ add_sel_list =
+ cr_additional_sel_append
+ (add_sel_list, add_sel);
+ found_sel = TRUE;
+ cr_parsing_location_copy
+ (&add_sel->location,
+ &attr_sel->location) ;
+ } else if (token && (token->type == DELIM_TK)
+ && (token->u.unichar == ':')) {
+ CRPseudo *pseudo = NULL;
+
+ /*try to parse a pseudo */
+
+ if (token) {
+ cr_token_destroy (token);
+ token = NULL;
+ }
+
+ pseudo = cr_pseudo_new ();
+
+ status = cr_tknzr_get_next_token
+ (PRIVATE (a_this)->tknzr, &token);
+ ENSURE_PARSING_COND (status == CR_OK && token);
+
+ cr_parsing_location_copy
+ (&pseudo->location,
+ &token->location) ;
+
+ if (token->type == IDENT_TK) {
+ pseudo->type = IDENT_PSEUDO;
+ pseudo->name = token->u.str;
+ token->u.str = NULL;
+ found_sel = TRUE;
+ } else if (token->type == FUNCTION_TK) {
+ pseudo->name = token->u.str;
+ token->u.str = NULL;
+ cr_parser_try_to_skip_spaces_and_comments
+ (a_this);
+ status = cr_parser_parse_ident
+ (a_this, &pseudo->extra);
+
+ ENSURE_PARSING_COND (status == CR_OK);
+ READ_NEXT_CHAR (a_this, &cur_char);
+ ENSURE_PARSING_COND (cur_char == ')');
+ pseudo->type = FUNCTION_PSEUDO;
+ found_sel = TRUE;
+ } else {
+ status = CR_PARSING_ERROR;
+ goto error;
+ }
+
+ if (status == CR_OK) {
+ CRAdditionalSel *add_sel = NULL;
+
+ add_sel = cr_additional_sel_new_with_type
+ (PSEUDO_CLASS_ADD_SELECTOR);
+
+ add_sel->content.pseudo = pseudo;
+ cr_parsing_location_copy
+ (&add_sel->location,
+ &pseudo->location) ;
+ add_sel_list =
+ cr_additional_sel_append
+ (add_sel_list, add_sel);
+ status = CR_OK;
+ }
+ } else {
+ status = cr_tknzr_unget_token
+ (PRIVATE (a_this)->tknzr, token);
+ token = NULL;
+ break;
+ }
+ }
+
+ if (status == CR_OK && found_sel == TRUE) {
+ cr_parser_try_to_skip_spaces_and_comments (a_this);
+
+ sel->add_sel = add_sel_list;
+ add_sel_list = NULL;
+
+ if (*a_sel == NULL) {
+ *a_sel = sel;
+ } else {
+ cr_simple_sel_append_simple_sel (*a_sel, sel);
+ }
+
+ sel = NULL;
+
+ if (token) {
+ cr_token_destroy (token);
+ token = NULL;
+ }
+
+ cr_parser_clear_errors (a_this);
+ return CR_OK;
+ } else {
+ status = CR_PARSING_ERROR;
+ }
+
+ error:
+
+ if (token) {
+ cr_token_destroy (token);
+ token = NULL;
+ }
+
+ if (add_sel_list) {
+ cr_additional_sel_destroy (add_sel_list);
+ add_sel_list = NULL;
+ }
+
+ if (sel) {
+ cr_simple_sel_destroy (sel);
+ sel = NULL;
+ }
+
+ cr_tknzr_set_cur_pos (PRIVATE (a_this)->tknzr, &init_pos);
+
+ return status;
+
+}
+
+/**
+ * cr_parser_parse_simple_sels:
+ *@a_this: the this pointer of the current instance of #CRParser.
+ *@a_start: a pointer to the
+ *first character of the successfully parsed
+ *string.
+ *@a_end: a pointer to the last character of the successfully parsed
+ *string.
+ *
+ *Parses a "selector" as defined by the css2 spec in appendix D.1:
+ *selector ::= simple_selector [ combinator simple_selector ]*
+ *
+ *Returns CR_OK upon successful completion, an error code otherwise.
+ */
+static enum CRStatus
+cr_parser_parse_simple_sels (CRParser * a_this,
+ CRSimpleSel ** a_sel)
+{
+ enum CRStatus status = CR_ERROR;
+ CRInputPos init_pos;
+ CRSimpleSel *sel = NULL;
+ guint32 cur_char = 0;
+
+ g_return_val_if_fail (a_this
+ && PRIVATE (a_this)
+ && a_sel,
+ CR_BAD_PARAM_ERROR);
+
+ RECORD_INITIAL_POS (a_this, &init_pos);
+
+ status = cr_parser_parse_simple_selector (a_this, &sel);
+ CHECK_PARSING_STATUS (status, FALSE);
+
+ *a_sel = cr_simple_sel_append_simple_sel (*a_sel, sel);
+
+ for (;;) {
+ guint32 next_char = 0;
+ enum Combinator comb = 0;
+
+ sel = NULL;
+
+ PEEK_NEXT_CHAR (a_this, &next_char);
+
+ if (next_char == '+') {
+ READ_NEXT_CHAR (a_this, &cur_char);
+ comb = COMB_PLUS;
+ cr_parser_try_to_skip_spaces_and_comments (a_this);
+ } else if (next_char == '>') {
+ READ_NEXT_CHAR (a_this, &cur_char);
+ comb = COMB_GT;
+ cr_parser_try_to_skip_spaces_and_comments (a_this);
+ } else {
+ comb = COMB_WS;
+ }
+
+ status = cr_parser_parse_simple_selector (a_this, &sel);
+ if (status != CR_OK)
+ break;
+
+ if (comb && sel) {
+ sel->combinator = comb;
+ comb = 0;
+ }
+ if (sel) {
+ *a_sel = cr_simple_sel_append_simple_sel (*a_sel,
+ sel) ;
+ }
+ }
+ cr_parser_clear_errors (a_this);
+ return CR_OK;
+
+ error:
+
+ cr_tknzr_set_cur_pos (PRIVATE (a_this)->tknzr, &init_pos);
+
+ return status;
+}
+
+/**
+ * cr_parser_parse_selector:
+ *@a_this: the current instance of #CRParser.
+ *@a_selector: the parsed list of comma separated
+ *selectors.
+ *
+ *Parses a comma separated list of selectors.
+ *
+ *Returns CR_OK upon successful completion, an error
+ *code otherwise.
+ */
+static enum CRStatus
+cr_parser_parse_selector (CRParser * a_this,
+ CRSelector ** a_selector)
+{
+ enum CRStatus status = CR_OK;
+ CRInputPos init_pos;
+ guint32 cur_char = 0,
+ next_char = 0;
+ CRSimpleSel *simple_sels = NULL;
+ CRSelector *selector = NULL;
+
+ g_return_val_if_fail (a_this && a_selector, CR_BAD_PARAM_ERROR);
+
+ RECORD_INITIAL_POS (a_this, &init_pos);
+
+ status = cr_parser_parse_simple_sels (a_this, &simple_sels);
+ CHECK_PARSING_STATUS (status, FALSE);
+
+ if (simple_sels) {
+ selector = cr_selector_append_simple_sel
+ (selector, simple_sels);
+ if (selector) {
+ cr_parsing_location_copy
+ (&selector->location,
+ &simple_sels->location) ;
+ }
+ simple_sels = NULL;
+ } else {
+ status = CR_PARSING_ERROR ;
+ goto error ;
+ }
+
+ status = cr_tknzr_peek_char (PRIVATE (a_this)->tknzr,
+ &next_char);
+ if (status != CR_OK) {
+ if (status == CR_END_OF_INPUT_ERROR) {
+ status = CR_OK;
+ goto okay;
+ } else {
+ goto error;
+ }
+ }
+
+ if (next_char == ',') {
+ for (;;) {
+ simple_sels = NULL;
+
+ status = cr_tknzr_peek_char (PRIVATE (a_this)->tknzr,
+ &next_char);
+ if (status != CR_OK) {
+ if (status == CR_END_OF_INPUT_ERROR) {
+ status = CR_OK;
+ break;
+ } else {
+ goto error;
+ }
+ }
+
+ if (next_char != ',')
+ break;
+
+ /*consume the ',' char */
+ READ_NEXT_CHAR (a_this, &cur_char);
+
+ cr_parser_try_to_skip_spaces_and_comments (a_this);
+
+ status = cr_parser_parse_simple_sels
+ (a_this, &simple_sels);
+
+ CHECK_PARSING_STATUS (status, FALSE);
+
+ if (simple_sels) {
+ selector =
+ cr_selector_append_simple_sel
+ (selector, simple_sels);
+
+ simple_sels = NULL;
+ }
+ }
+ }
+
+ okay:
+ cr_parser_try_to_skip_spaces_and_comments (a_this);
+
+ if (!*a_selector) {
+ *a_selector = selector;
+ } else {
+ *a_selector = cr_selector_append (*a_selector, selector);
+ }
+
+ selector = NULL;
+ return CR_OK;
+
+ error:
+
+ if (simple_sels) {
+ cr_simple_sel_destroy (simple_sels);
+ simple_sels = NULL;
+ }
+
+ if (selector) {
+ cr_selector_unref (selector);
+ selector = NULL;
+ }
+
+ cr_tknzr_set_cur_pos (PRIVATE (a_this)->tknzr, &init_pos);
+
+ return status;
+}
+
+/**
+ * cr_parser_parse_function:
+ *@a_this: the "this pointer" of the current instance of #CRParser.
+ *
+ *@a_func_name: out parameter. The parsed function name
+ *@a_expr: out parameter. The successfully parsed term.
+ *
+ *Parses a "function" as defined in css spec at appendix D.1:
+ *function ::= FUNCTION S* expr ')' S*
+ *FUNCTION ::= ident'('
+ *
+ *Returns CR_OK upon successful completion, an error code otherwise.
+ */
+static enum CRStatus
+cr_parser_parse_function (CRParser * a_this,
+ CRString ** a_func_name,
+ CRTerm ** a_expr)
+{
+ CRInputPos init_pos;
+ enum CRStatus status = CR_OK;
+ CRToken *token = NULL;
+ CRTerm *expr = NULL;
+
+ g_return_val_if_fail (a_this && PRIVATE (a_this)
+ && a_func_name,
+ CR_BAD_PARAM_ERROR);
+
+ RECORD_INITIAL_POS (a_this, &init_pos);
+
+ status = cr_tknzr_get_next_token (PRIVATE (a_this)->tknzr, &token);
+ if (status != CR_OK)
+ goto error;
+
+ if (token && token->type == FUNCTION_TK) {
+ *a_func_name = token->u.str;
+ token->u.str = NULL;
+ } else {
+ status = CR_PARSING_ERROR;
+ goto error;
+ }
+ cr_token_destroy (token);
+ token = NULL;
+
+ cr_parser_try_to_skip_spaces_and_comments (a_this) ;
+
+ status = cr_parser_parse_expr (a_this, &expr);
+
+ CHECK_PARSING_STATUS (status, FALSE);
+
+ status = cr_tknzr_get_next_token (PRIVATE (a_this)->tknzr, &token);
+ if (status != CR_OK)
+ goto error;
+
+ ENSURE_PARSING_COND (token && token->type == PC_TK);
+
+ cr_token_destroy (token);
+ token = NULL;
+
+ if (expr) {
+ *a_expr = cr_term_append_term (*a_expr, expr);
+ expr = NULL;
+ }
+
+ cr_parser_clear_errors (a_this);
+ return CR_OK;
+
+ error:
+
+ if (*a_func_name) {
+ cr_string_destroy (*a_func_name);
+ *a_func_name = NULL;
+ }
+
+ if (expr) {
+ cr_term_destroy (expr);
+ expr = NULL;
+ }
+
+ if (token) {
+ cr_token_destroy (token);
+
+ }
+
+ cr_tknzr_set_cur_pos (PRIVATE (a_this)->tknzr, &init_pos);
+
+ return status;
+}
+
+/**
+ * cr_parser_parse_uri:
+ *@a_this: the current instance of #CRParser.
+ *@a_str: the successfully parsed url.
+ *
+ *Parses an uri as defined by the css spec [4.1.1]:
+ * URI ::= url\({w}{string}{w}\)
+ * |url\({w}([!#$%&*-~]|{nonascii}|{escape})*{w}\)
+ *
+ *Returns CR_OK upon successful completion, an error code otherwise.
+ */
+static enum CRStatus
+cr_parser_parse_uri (CRParser * a_this, CRString ** a_str)
+{
+
+ enum CRStatus status = CR_PARSING_ERROR;
+
+ g_return_val_if_fail (a_this && PRIVATE (a_this)
+ && PRIVATE (a_this)->tknzr, CR_BAD_PARAM_ERROR);
+
+ status = cr_tknzr_parse_token (PRIVATE (a_this)->tknzr,
+ URI_TK, NO_ET, a_str, NULL);
+ return status;
+}
+
+/**
+ * cr_parser_parse_string:
+ *@a_this: the current instance of #CRParser.
+ *@a_start: out parameter. Upon successful completion,
+ *points to the beginning of the string, points to an undefined value
+ *otherwise.
+ *@a_end: out parameter. Upon successful completion, points to
+ *the beginning of the string, points to an undefined value otherwise.
+ *
+ *Parses a string type as defined in css spec [4.1.1]:
+ *
+ *string ::= {string1}|{string2}
+ *string1 ::= \"([\t !#$%&(-~]|\\{nl}|\'|{nonascii}|{escape})*\"
+ *string2 ::= \'([\t !#$%&(-~]|\\{nl}|\"|{nonascii}|{escape})*\'
+ *
+ *Returns CR_OK upon successful completion, an error code otherwise.
+ */
+static enum CRStatus
+cr_parser_parse_string (CRParser * a_this, CRString ** a_str)
+{
+ enum CRStatus status = CR_OK;
+
+ g_return_val_if_fail (a_this && PRIVATE (a_this)
+ && PRIVATE (a_this)->tknzr
+ && a_str, CR_BAD_PARAM_ERROR);
+
+ status = cr_tknzr_parse_token (PRIVATE (a_this)->tknzr,
+ STRING_TK, NO_ET, a_str, NULL);
+ return status;
+}
+
+/**
+ *Parses an "ident" as defined in css spec [4.1.1]:
+ *ident ::= {nmstart}{nmchar}*
+ *
+ *@param a_this the currens instance of #CRParser.
+ *
+ *@param a_str a pointer to parsed ident. If *a_str is NULL,
+ *this function allocates a new instance of #CRString. If not,
+ *the function just appends the parsed string to the one passed.
+ *In both cases it is up to the caller to free *a_str.
+ *
+ *@return CR_OK upon successful completion, an error code
+ *otherwise.
+ */
+static enum CRStatus
+cr_parser_parse_ident (CRParser * a_this, CRString ** a_str)
+{
+ enum CRStatus status = CR_OK;
+
+ g_return_val_if_fail (a_this && PRIVATE (a_this)
+ && PRIVATE (a_this)->tknzr
+ && a_str, CR_BAD_PARAM_ERROR);
+
+ status = cr_tknzr_parse_token (PRIVATE (a_this)->tknzr,
+ IDENT_TK, NO_ET, a_str, NULL);
+ return status;
+}
+
+/**
+ *the next rule is ignored as well. This seems to be a bug
+ *Parses a stylesheet as defined in the css2 spec in appendix D.1:
+ *stylesheet ::= [ CHARSET_SYM S* STRING S* ';' ]?
+ * [S|CDO|CDC]* [ import [S|CDO|CDC]* ]*
+ * [ [ ruleset | media | page | font_face ] [S|CDO|CDC]* ]*
+ *
+ *TODO: Finish the code of this function. Think about splitting it into
+ *smaller functions.
+ *
+ *@param a_this the "this pointer" of the current instance of #CRParser.
+ *@param a_start out parameter. A pointer to the first character of
+ *the successfully parsed string.
+ *@param a_end out parameter. A pointer to the first character of
+ *the successfully parsed string.
+ *
+ *@return CR_OK upon successful completion, an error code otherwise.
+ */
+static enum CRStatus
+cr_parser_parse_stylesheet (CRParser * a_this)
+{
+ enum CRStatus status = CR_OK;
+ CRInputPos init_pos;
+ CRToken *token = NULL;
+ CRString *charset = NULL;
+
+ g_return_val_if_fail (a_this && PRIVATE (a_this)
+ && PRIVATE (a_this)->tknzr, CR_BAD_PARAM_ERROR);
+
+ RECORD_INITIAL_POS (a_this, &init_pos);
+
+ PRIVATE (a_this)->state = READY_STATE;
+
+ if (PRIVATE (a_this)->sac_handler
+ && PRIVATE (a_this)->sac_handler->start_document) {
+ PRIVATE (a_this)->sac_handler->start_document
+ (PRIVATE (a_this)->sac_handler);
+ }
+
+ parse_charset:
+ status = cr_tknzr_get_next_token (PRIVATE (a_this)->tknzr, &token);
+
+ if (status == CR_END_OF_INPUT_ERROR)
+ goto done;
+ CHECK_PARSING_STATUS (status, TRUE);
+
+ if (token && token->type == CHARSET_SYM_TK) {
+ CRParsingLocation location = {0} ;
+ status = cr_tknzr_unget_token (PRIVATE (a_this)->tknzr,
+ token);
+ CHECK_PARSING_STATUS (status, TRUE);
+ token = NULL;
+
+ status = cr_parser_parse_charset (a_this,
+ &charset,
+ &location);
+
+ if (status == CR_OK && charset) {
+ if (PRIVATE (a_this)->sac_handler
+ && PRIVATE (a_this)->sac_handler->charset) {
+ PRIVATE (a_this)->sac_handler->charset
+ (PRIVATE (a_this)->sac_handler,
+ charset, &location);
+ }
+ } else if (status != CR_END_OF_INPUT_ERROR) {
+ status = cr_parser_parse_atrule_core (a_this);
+ CHECK_PARSING_STATUS (status, FALSE);
+ }
+
+ if (charset) {
+ cr_string_destroy (charset);
+ charset = NULL;
+ }
+ } else if (token
+ && (token->type == S_TK
+ || token->type == COMMENT_TK)) {
+ status = cr_tknzr_unget_token (PRIVATE (a_this)->tknzr,
+ token);
+ token = NULL;
+ CHECK_PARSING_STATUS (status, TRUE);
+
+ cr_parser_try_to_skip_spaces_and_comments (a_this);
+ goto parse_charset ;
+ } else if (token) {
+ status = cr_tknzr_unget_token (PRIVATE (a_this)->tknzr,
+ token);
+ token = NULL;
+ CHECK_PARSING_STATUS (status, TRUE);
+ }
+
+/* parse_imports:*/
+ do {
+ if (token) {
+ cr_token_destroy (token);
+ token = NULL;
+ }
+ cr_parser_try_to_skip_spaces_and_comments (a_this) ;
+ status = cr_tknzr_get_next_token
+ (PRIVATE (a_this)->tknzr, &token);
+
+ if (status == CR_END_OF_INPUT_ERROR)
+ goto done;
+ CHECK_PARSING_STATUS (status, TRUE);
+ } while (token
+ && (token->type == S_TK
+ || token->type == CDO_TK || token->type == CDC_TK));
+
+ if (token) {
+ status = cr_tknzr_unget_token (PRIVATE (a_this)->tknzr,
+ token);
+ token = NULL;
+ }
+
+ for (;;) {
+ status = cr_tknzr_get_next_token
+ (PRIVATE (a_this)->tknzr, &token);
+ if (status == CR_END_OF_INPUT_ERROR)
+ goto done;
+ CHECK_PARSING_STATUS (status, TRUE);
+
+ if (token && token->type == IMPORT_SYM_TK) {
+ GList *media_list = NULL;
+ CRString *import_string = NULL;
+ CRParsingLocation location = {0} ;
+
+ status = cr_tknzr_unget_token
+ (PRIVATE (a_this)->tknzr, token);
+ token = NULL;
+ CHECK_PARSING_STATUS (status, TRUE);
+
+ status = cr_parser_parse_import (a_this,
+ &media_list,
+ &import_string,
+ &location);
+ if (status == CR_OK) {
+ if (import_string
+ && PRIVATE (a_this)->sac_handler
+ && PRIVATE (a_this)->sac_handler->import_style) {
+ PRIVATE (a_this)->sac_handler->import_style
+ (PRIVATE(a_this)->sac_handler,
+ media_list,
+ import_string,
+ NULL, &location) ;
+
+ if ((PRIVATE (a_this)->sac_handler->resolve_import == TRUE)) {
+ /*
+ *TODO: resolve the
+ *import rule.
+ */
+ }
+
+ if ((PRIVATE (a_this)->sac_handler->import_style_result)) {
+ PRIVATE (a_this)->sac_handler->import_style_result
+ (PRIVATE (a_this)->sac_handler,
+ media_list, import_string,
+ NULL, NULL);
+ }
+ }
+ } else if (status != CR_END_OF_INPUT_ERROR) {
+ if (PRIVATE (a_this)->sac_handler
+ && PRIVATE (a_this)->sac_handler->error) {
+ PRIVATE (a_this)->sac_handler->error
+ (PRIVATE (a_this)->sac_handler);
+ }
+ status = cr_parser_parse_atrule_core (a_this);
+ CHECK_PARSING_STATUS (status, TRUE) ;
+ } else {
+ goto error ;
+ }
+
+ /*
+ *then, after calling the appropriate
+ *SAC handler, free
+ *the media_list and import_string.
+ */
+ if (media_list) {
+ GList *cur = NULL;
+
+ /*free the medium list */
+ for (cur = media_list; cur; cur = cur->next) {
+ if (cur->data) {
+ cr_string_destroy (cur->data);
+ }
+ }
+
+ g_list_free (media_list);
+ media_list = NULL;
+ }
+
+ if (import_string) {
+ cr_string_destroy (import_string);
+ import_string = NULL;
+ }
+
+ cr_parser_try_to_skip_spaces_and_comments (a_this);
+ } else if (token
+ && (token->type == S_TK
+ || token->type == CDO_TK
+ || token->type == CDC_TK)) {
+ status = cr_tknzr_unget_token
+ (PRIVATE (a_this)->tknzr, token);
+ token = NULL;
+
+ do {
+ if (token) {
+ cr_token_destroy (token);
+ token = NULL;
+ }
+
+ status = cr_tknzr_get_next_token
+ (PRIVATE (a_this)->tknzr, &token);
+
+ if (status == CR_END_OF_INPUT_ERROR)
+ goto done;
+ CHECK_PARSING_STATUS (status, TRUE);
+ } while (token
+ && (token->type == S_TK
+ || token->type == CDO_TK
+ || token->type == CDC_TK));
+ } else {
+ if (token) {
+ status = cr_tknzr_unget_token
+ (PRIVATE (a_this)->tknzr, token);
+ token = NULL;
+ }
+ goto parse_ruleset_and_others;
+ }
+ }
+
+ parse_ruleset_and_others:
+
+ cr_parser_try_to_skip_spaces_and_comments (a_this);
+
+ for (;;) {
+ status = cr_tknzr_get_next_token
+ (PRIVATE (a_this)->tknzr, &token);
+ if (status == CR_END_OF_INPUT_ERROR)
+ goto done;
+ CHECK_PARSING_STATUS (status, TRUE);
+
+ if (token
+ && (token->type == S_TK
+ || token->type == CDO_TK || token->type == CDC_TK)) {
+ status = cr_tknzr_unget_token
+ (PRIVATE (a_this)->tknzr, token);
+ token = NULL;
+
+ do {
+ if (token) {
+ cr_token_destroy (token);
+ token = NULL;
+ }
+
+ cr_parser_try_to_skip_spaces_and_comments
+ (a_this);
+ status = cr_tknzr_get_next_token
+ (PRIVATE (a_this)->tknzr, &token);
+ } while (token
+ && (token->type == S_TK
+ || token->type == COMMENT_TK
+ || token->type == CDO_TK
+ || token->type == CDC_TK));
+ if (token) {
+ cr_tknzr_unget_token
+ (PRIVATE (a_this)->tknzr, token);
+ token = NULL;
+ }
+ } else if (token
+ && (token->type == HASH_TK
+ || (token->type == DELIM_TK
+ && token->u.unichar == '.')
+ || (token->type == DELIM_TK
+ && token->u.unichar == ':')
+ || (token->type == DELIM_TK
+ && token->u.unichar == '*')
+ || (token->type == BO_TK)
+ || token->type == IDENT_TK)) {
+ /*
+ *Try to parse a CSS2 ruleset.
+ *if the parsing fails, try to parse
+ *a css core ruleset.
+ */
+ status = cr_tknzr_unget_token
+ (PRIVATE (a_this)->tknzr, token);
+ CHECK_PARSING_STATUS (status, TRUE);
+ token = NULL;
+
+ status = cr_parser_parse_ruleset (a_this);
+
+ if (status == CR_OK) {
+ continue;
+ } else {
+ if (PRIVATE (a_this)->sac_handler
+ && PRIVATE (a_this)->sac_handler->error) {
+ PRIVATE (a_this)->sac_handler->
+ error
+ (PRIVATE (a_this)->
+ sac_handler);
+ }
+
+ status = cr_parser_parse_ruleset_core
+ (a_this);
+
+ if (status == CR_OK) {
+ continue;
+ } else {
+ break;
+ }
+ }
+ } else if (token && token->type == MEDIA_SYM_TK) {
+ status = cr_tknzr_unget_token
+ (PRIVATE (a_this)->tknzr, token);
+ CHECK_PARSING_STATUS (status, TRUE);
+ token = NULL;
+
+ status = cr_parser_parse_media (a_this);
+ if (status == CR_OK) {
+ continue;
+ } else {
+ if (PRIVATE (a_this)->sac_handler
+ && PRIVATE (a_this)->sac_handler->error) {
+ PRIVATE (a_this)->sac_handler->
+ error
+ (PRIVATE (a_this)->
+ sac_handler);
+ }
+
+ status = cr_parser_parse_atrule_core (a_this);
+
+ if (status == CR_OK) {
+ continue;
+ } else {
+ break;
+ }
+ }
+
+ } else if (token && token->type == PAGE_SYM_TK) {
+ status = cr_tknzr_unget_token
+ (PRIVATE (a_this)->tknzr, token);
+ CHECK_PARSING_STATUS (status, TRUE);
+ token = NULL;
+ status = cr_parser_parse_page (a_this);
+
+ if (status == CR_OK) {
+ continue;
+ } else {
+ if (PRIVATE (a_this)->sac_handler
+ && PRIVATE (a_this)->sac_handler->error) {
+ PRIVATE (a_this)->sac_handler->
+ error
+ (PRIVATE (a_this)->
+ sac_handler);
+ }
+
+ status = cr_parser_parse_atrule_core (a_this);
+
+ if (status == CR_OK) {
+ continue;
+ } else {
+ break;
+ }
+ }
+ } else if (token && token->type == FONT_FACE_SYM_TK) {
+ status = cr_tknzr_unget_token
+ (PRIVATE (a_this)->tknzr, token);
+ CHECK_PARSING_STATUS (status, TRUE);
+ token = NULL;
+ status = cr_parser_parse_font_face (a_this);
+
+ if (status == CR_OK) {
+ continue;
+ } else {
+ if (PRIVATE (a_this)->sac_handler
+ && PRIVATE (a_this)->sac_handler->error) {
+ PRIVATE (a_this)->sac_handler->
+ error
+ (PRIVATE (a_this)->
+ sac_handler);
+ }
+
+ status = cr_parser_parse_atrule_core (a_this);
+
+ if (status == CR_OK) {
+ continue;
+ } else {
+ break;
+ }
+ }
+ } else {
+ status = cr_tknzr_unget_token
+ (PRIVATE (a_this)->tknzr, token);
+ CHECK_PARSING_STATUS (status, TRUE);
+ token = NULL;
+ status = cr_parser_parse_statement_core (a_this);
+
+ if (status == CR_OK) {
+ continue;
+ } else {
+ break;
+ }
+ }
+ }
+
+ done:
+ if (token) {
+ cr_token_destroy (token);
+ token = NULL;
+ }
+
+ if (status == CR_END_OF_INPUT_ERROR || status == CR_OK) {
+
+ if (PRIVATE (a_this)->sac_handler
+ && PRIVATE (a_this)->sac_handler->end_document) {
+ PRIVATE (a_this)->sac_handler->end_document
+ (PRIVATE (a_this)->sac_handler);
+ }
+
+ return CR_OK;
+ }
+
+ cr_parser_push_error
+ (a_this, (const guchar *) "could not recognize next production", CR_ERROR);
+
+ if (PRIVATE (a_this)->sac_handler
+ && PRIVATE (a_this)->sac_handler->unrecoverable_error) {
+ PRIVATE (a_this)->sac_handler->
+ unrecoverable_error (PRIVATE (a_this)->sac_handler);
+ }
+
+ cr_parser_dump_err_stack (a_this, TRUE);
+
+ return status;
+
+ error:
+
+ if (token) {
+ cr_token_destroy (token);
+ token = NULL;
+ }
+
+ if (PRIVATE (a_this)->sac_handler
+ && PRIVATE (a_this)->sac_handler->unrecoverable_error) {
+ PRIVATE (a_this)->sac_handler->
+ unrecoverable_error (PRIVATE (a_this)->sac_handler);
+ }
+
+ cr_tknzr_set_cur_pos (PRIVATE (a_this)->tknzr, &init_pos);
+
+ return status;
+}
+
+/****************************************
+ *Public CRParser Methods
+ ****************************************/
+
+/**
+ * cr_parser_new:
+ * @a_tknzr: the tokenizer to use for the parsing.
+ *
+ *Creates a new parser to parse data
+ *coming the input stream given in parameter.
+ *
+ *Returns the newly created instance of #CRParser,
+ *or NULL if an error occurred.
+ */
+CRParser *
+cr_parser_new (CRTknzr * a_tknzr)
+{
+ CRParser *result = NULL;
+ enum CRStatus status = CR_OK;
+
+ result = g_malloc0 (sizeof (CRParser));
+
+ PRIVATE (result) = g_malloc0 (sizeof (CRParserPriv));
+
+ if (a_tknzr) {
+ status = cr_parser_set_tknzr (result, a_tknzr);
+ }
+
+ g_return_val_if_fail (status == CR_OK, NULL);
+
+ return result;
+}
+
+/**
+ * cr_parser_new_from_buf:
+ *@a_buf: the buffer to parse.
+ *@a_len: the length of the data in the buffer.
+ *@a_enc: the encoding of the input buffer a_buf.
+ *@a_free_buf: if set to TRUE, a_buf will be freed
+ *during the destruction of the newly built instance
+ *of #CRParser. If set to FALSE, it is up to the caller to
+ *eventually free it.
+ *
+ *Instantiates a new parser from a memory buffer.
+ *
+ *Returns the newly built parser, or NULL if an error arises.
+ */
+CRParser *
+cr_parser_new_from_buf (guchar * a_buf,
+ gulong a_len,
+ enum CREncoding a_enc,
+ gboolean a_free_buf)
+{
+ CRParser *result = NULL;
+ CRInput *input = NULL;
+
+ g_return_val_if_fail (a_buf && a_len, NULL);
+
+ input = cr_input_new_from_buf (a_buf, a_len, a_enc, a_free_buf);
+ g_return_val_if_fail (input, NULL);
+
+ result = cr_parser_new_from_input (input);
+ if (!result) {
+ cr_input_destroy (input);
+ input = NULL;
+ return NULL;
+ }
+ return result;
+}
+
+/**
+ * cr_parser_new_from_input:
+ * @a_input: the parser input stream to use.
+ *
+ * Returns a newly built parser input.
+ */
+CRParser *
+cr_parser_new_from_input (CRInput * a_input)
+{
+ CRParser *result = NULL;
+ CRTknzr *tokenizer = NULL;
+
+ if (a_input) {
+ tokenizer = cr_tknzr_new (a_input);
+ g_return_val_if_fail (tokenizer, NULL);
+ }
+
+ result = cr_parser_new (tokenizer);
+ g_return_val_if_fail (result, NULL);
+
+ return result;
+}
+
+/**
+ * cr_parser_new_from_file:
+ * @a_file_uri: the uri of the file to parse.
+ * @a_enc: the file encoding to use.
+ *
+ * Returns the newly built parser.
+ */
+CRParser *
+cr_parser_new_from_file (const guchar * a_file_uri, enum CREncoding a_enc)
+{
+ CRParser *result = NULL;
+ CRTknzr *tokenizer = NULL;
+
+ tokenizer = cr_tknzr_new_from_uri (a_file_uri, a_enc);
+ if (!tokenizer) {
+ cr_utils_trace_info ("Could not open input file");
+ return NULL;
+ }
+
+ result = cr_parser_new (tokenizer);
+ g_return_val_if_fail (result, NULL);
+ return result;
+}
+
+/**
+ * cr_parser_set_sac_handler:
+ *@a_this: the "this pointer" of the current instance of #CRParser.
+ *@a_handler: the handler to set.
+ *
+ *Sets a SAC document handler to the parser.
+ *
+ *Returns CR_OK upon successful completion, an error code otherwise.
+ */
+enum CRStatus
+cr_parser_set_sac_handler (CRParser * a_this, CRDocHandler * a_handler)
+{
+ g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR);
+
+ if (PRIVATE (a_this)->sac_handler) {
+ cr_doc_handler_unref (PRIVATE (a_this)->sac_handler);
+ }
+
+ PRIVATE (a_this)->sac_handler = a_handler;
+ cr_doc_handler_ref (a_handler);
+
+ return CR_OK;
+}
+
+/**
+ * cr_parser_get_sac_handler:
+ *@a_this: the "this pointer" of the current instance of
+ *#CRParser.
+ *@a_handler: out parameter. The returned handler.
+ *
+ *Gets the SAC document handler.
+ *
+ *Returns CR_OK upon successful completion, an error code
+ *otherwise.
+ */
+enum CRStatus
+cr_parser_get_sac_handler (CRParser * a_this, CRDocHandler ** a_handler)
+{
+ g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR);
+
+ *a_handler = PRIVATE (a_this)->sac_handler;
+
+ return CR_OK;
+}
+
+/**
+ * cr_parser_set_default_sac_handler:
+ *@a_this: a pointer to the current instance of #CRParser.
+ *
+ *Sets the SAC handler associated to the current instance
+ *of #CRParser to the default SAC handler.
+ *
+ *Returns CR_OK upon successful completion, an error code otherwise.
+ */
+enum CRStatus
+cr_parser_set_default_sac_handler (CRParser * a_this)
+{
+ CRDocHandler *default_sac_handler = NULL;
+ enum CRStatus status = CR_ERROR;
+
+ g_return_val_if_fail (a_this && PRIVATE (a_this), CR_BAD_PARAM_ERROR);
+
+ default_sac_handler = cr_doc_handler_new ();
+
+ cr_doc_handler_set_default_sac_handler (default_sac_handler);
+
+ status = cr_parser_set_sac_handler (a_this, default_sac_handler);
+
+ if (status != CR_OK) {
+ cr_doc_handler_destroy (default_sac_handler);
+ default_sac_handler = NULL;
+ }
+
+ return status;
+}
+
+/**
+ * cr_parser_set_use_core_grammar:
+ * @a_this: the current instance of #CRParser.
+ * @a_use_core_grammar: where to parse against the css core grammar.
+ *
+ * Returns CR_OK upon successful completion, an error code otherwise.
+ */
+enum CRStatus
+cr_parser_set_use_core_grammar (CRParser * a_this,
+ gboolean a_use_core_grammar)
+{
+ g_return_val_if_fail (a_this && PRIVATE (a_this), CR_BAD_PARAM_ERROR);
+
+ PRIVATE (a_this)->use_core_grammar = a_use_core_grammar;
+
+ return CR_OK;
+}
+
+/**
+ * cr_parser_get_use_core_grammar:
+ * @a_this: the current instance of #CRParser.
+ * @a_use_core_grammar: whether to use the core grammar or not.
+ *
+ * Returns CR_OK upon successful completion, an error code otherwise.
+ */
+enum CRStatus
+cr_parser_get_use_core_grammar (CRParser const * a_this,
+ gboolean * a_use_core_grammar)
+{
+ g_return_val_if_fail (a_this && PRIVATE (a_this), CR_BAD_PARAM_ERROR);
+
+ *a_use_core_grammar = PRIVATE (a_this)->use_core_grammar;
+
+ return CR_OK;
+}
+
+/**
+ * cr_parser_parse_file:
+ *@a_this: a pointer to the current instance of #CRParser.
+ *@a_file_uri: the uri to the file to load. For the time being,
+ *@a_enc: the encoding of the file to parse.
+ *only local files are supported.
+ *
+ *Parses a the given in parameter.
+ *
+ *Returns CR_OK upon successful completion, an error code otherwise.
+ */
+enum CRStatus
+cr_parser_parse_file (CRParser * a_this,
+ const guchar * a_file_uri, enum CREncoding a_enc)
+{
+ enum CRStatus status = CR_ERROR;
+ CRTknzr *tknzr = NULL;
+
+ g_return_val_if_fail (a_this && PRIVATE (a_this)
+ && a_file_uri, CR_BAD_PARAM_ERROR);
+
+ tknzr = cr_tknzr_new_from_uri (a_file_uri, a_enc);
+
+ g_return_val_if_fail (tknzr != NULL, CR_ERROR);
+
+ status = cr_parser_set_tknzr (a_this, tknzr);
+ g_return_val_if_fail (status == CR_OK, CR_ERROR);
+
+ status = cr_parser_parse (a_this);
+
+ return status;
+}
+
+/**
+ * cr_parser_parse_expr:
+ * @a_this: the current instance of #CRParser.
+ * @a_expr: out parameter. the parsed expression.
+ *
+ *Parses an expression as defined by the css2 spec in appendix
+ *D.1:
+ *expr: term [ operator term ]*
+ *
+ *
+ * Returns CR_OK upon successful completion, an error code otherwise.
+ */
+enum CRStatus
+cr_parser_parse_expr (CRParser * a_this, CRTerm ** a_expr)
+{
+ enum CRStatus status = CR_ERROR;
+ CRInputPos init_pos;
+ CRTerm *expr = NULL,
+ *expr2 = NULL;
+ guchar next_byte = 0;
+ gulong nb_terms = 0;
+
+ g_return_val_if_fail (a_this && PRIVATE (a_this)
+ && a_expr, CR_BAD_PARAM_ERROR);
+
+ RECORD_INITIAL_POS (a_this, &init_pos);
+
+ status = cr_parser_parse_term (a_this, &expr);
+
+ CHECK_PARSING_STATUS (status, FALSE);
+
+ for (;;) {
+ guchar operator = 0;
+
+ status = cr_tknzr_peek_byte (PRIVATE (a_this)->tknzr,
+ 1, &next_byte);
+ if (status != CR_OK) {
+ if (status == CR_END_OF_INPUT_ERROR) {
+ /*
+ if (!nb_terms)
+ {
+ goto error ;
+ }
+ */
+ status = CR_OK;
+ break;
+ } else {
+ goto error;
+ }
+ }
+
+ if (next_byte == '/' || next_byte == ',') {
+ READ_NEXT_BYTE (a_this, &operator);
+ }
+
+ cr_parser_try_to_skip_spaces_and_comments (a_this);
+
+ status = cr_parser_parse_term (a_this, &expr2);
+
+ if (status != CR_OK || expr2 == NULL) {
+ status = CR_OK;
+ break;
+ }
+
+ switch (operator) {
+ case '/':
+ expr2->the_operator = DIVIDE;
+ break;
+ case ',':
+ expr2->the_operator = COMMA;
+
+ default:
+ break;
+ }
+
+ expr = cr_term_append_term (expr, expr2);
+ expr2 = NULL;
+ operator = 0;
+ nb_terms++;
+ }
+
+ if (status == CR_OK) {
+ *a_expr = cr_term_append_term (*a_expr, expr);
+ expr = NULL;
+
+ cr_parser_clear_errors (a_this);
+ return CR_OK;
+ }
+
+ error:
+
+ if (expr) {
+ cr_term_destroy (expr);
+ expr = NULL;
+ }
+
+ if (expr2) {
+ cr_term_destroy (expr2);
+ expr2 = NULL;
+ }
+
+ cr_tknzr_set_cur_pos (PRIVATE (a_this)->tknzr, &init_pos);
+
+ return status;
+}
+
+/**
+ * cr_parser_parse_prio:
+ *@a_this: the current instance of #CRParser.
+ *@a_prio: a string representing the priority.
+ *Today, only "!important" is returned as only this
+ *priority is defined by css2.
+ *
+ *Parses a declaration priority as defined by
+ *the css2 grammar in appendix C:
+ *prio: IMPORTANT_SYM S*
+ *
+ * Returns CR_OK upon successful completion, an error code otherwise.
+ */
+enum CRStatus
+cr_parser_parse_prio (CRParser * a_this, CRString ** a_prio)
+{
+ enum CRStatus status = CR_ERROR;
+ CRInputPos init_pos;
+ CRToken *token = NULL;
+
+ g_return_val_if_fail (a_this && PRIVATE (a_this)
+ && a_prio
+ && *a_prio == NULL, CR_BAD_PARAM_ERROR);
+
+ RECORD_INITIAL_POS (a_this, &init_pos);
+
+ status = cr_tknzr_get_next_token (PRIVATE (a_this)->tknzr, &token);
+ if (status == CR_END_OF_INPUT_ERROR) {
+ goto error;
+ }
+ ENSURE_PARSING_COND (status == CR_OK
+ && token && token->type == IMPORTANT_SYM_TK);
+
+ cr_parser_try_to_skip_spaces_and_comments (a_this);
+ *a_prio = cr_string_new_from_string ("!important");
+ cr_token_destroy (token);
+ token = NULL;
+ return CR_OK;
+
+ error:
+ if (token) {
+ cr_token_destroy (token);
+ token = NULL;
+ }
+ cr_tknzr_set_cur_pos (PRIVATE (a_this)->tknzr, &init_pos);
+
+ return status;
+}
+
+/**
+ * cr_parser_parse_declaration:
+ *@a_this: the "this pointer" of the current instance of #CRParser.
+ *@a_property: the successfully parsed property. The caller
+ * *must* free the returned pointer.
+ *@a_expr: the expression that represents the attribute value.
+ *The caller *must* free the returned pointer.
+ *
+ *TODO: return the parsed priority, so that
+ *upper layers can take benefit from it.
+ *Parses a "declaration" as defined by the css2 spec in appendix D.1:
+ *declaration ::= [property ':' S* expr prio?]?
+ *
+ *Returns CR_OK upon successful completion, an error code otherwise.
+ */
+enum CRStatus
+cr_parser_parse_declaration (CRParser * a_this,
+ CRString ** a_property,
+ CRTerm ** a_expr, gboolean * a_important)
+{
+ enum CRStatus status = CR_ERROR;
+ CRInputPos init_pos;
+ guint32 cur_char = 0;
+ CRTerm *expr = NULL;
+ CRString *prio = NULL;
+
+ g_return_val_if_fail (a_this && PRIVATE (a_this)
+ && a_property && a_expr
+ && a_important, CR_BAD_PARAM_ERROR);
+
+ RECORD_INITIAL_POS (a_this, &init_pos);
+
+ status = cr_parser_parse_property (a_this, a_property);
+
+ if (status == CR_END_OF_INPUT_ERROR)
+ goto error;
+
+ CHECK_PARSING_STATUS_ERR
+ (a_this, status, FALSE,
+ (const guchar *) "while parsing declaration: next property is malformed",
+ CR_SYNTAX_ERROR);
+
+ READ_NEXT_CHAR (a_this, &cur_char);
+
+ if (cur_char != ':') {
+ status = CR_PARSING_ERROR;
+ cr_parser_push_error
+ (a_this,
+ (const guchar *) "while parsing declaration: this char must be ':'",
+ CR_SYNTAX_ERROR);
+ goto error;
+ }
+
+ cr_parser_try_to_skip_spaces_and_comments (a_this);
+
+ status = cr_parser_parse_expr (a_this, &expr);
+
+ CHECK_PARSING_STATUS_ERR
+ (a_this, status, FALSE,
+ (const guchar *) "while parsing declaration: next expression is malformed",
+ CR_SYNTAX_ERROR);
+
+ cr_parser_try_to_skip_spaces_and_comments (a_this);
+ status = cr_parser_parse_prio (a_this, &prio);
+ if (prio) {
+ cr_string_destroy (prio);
+ prio = NULL;
+ *a_important = TRUE;
+ } else {
+ *a_important = FALSE;
+ }
+ if (*a_expr) {
+ cr_term_append_term (*a_expr, expr);
+ expr = NULL;
+ } else {
+ *a_expr = expr;
+ expr = NULL;
+ }
+
+ cr_parser_clear_errors (a_this);
+ return CR_OK;
+
+ error:
+
+ if (expr) {
+ cr_term_destroy (expr);
+ expr = NULL;
+ }
+
+ if (*a_property) {
+ cr_string_destroy (*a_property);
+ *a_property = NULL;
+ }
+
+ cr_tknzr_set_cur_pos (PRIVATE (a_this)->tknzr, &init_pos);
+
+ return status;
+}
+
+/**
+ * cr_parser_parse_statement_core:
+ *@a_this: the current instance of #CRParser.
+ *
+ *Parses a statement as defined by the css core grammar in
+ *chapter 4.1 of the css2 spec.
+ *statement : ruleset | at-rule;
+ *
+ *Returns CR_OK upon successful completion, an error code otherwise.
+ */
+enum CRStatus
+cr_parser_parse_statement_core (CRParser * a_this)
+{
+ CRToken *token = NULL;
+ CRInputPos init_pos;
+ enum CRStatus status = CR_ERROR;
+
+ g_return_val_if_fail (a_this && PRIVATE (a_this), CR_BAD_PARAM_ERROR);
+
+ RECORD_INITIAL_POS (a_this, &init_pos);
+
+ status = cr_tknzr_get_next_token (PRIVATE (a_this)->tknzr, &token);
+
+ ENSURE_PARSING_COND (status == CR_OK && token);
+
+ switch (token->type) {
+ case ATKEYWORD_TK:
+ case IMPORT_SYM_TK:
+ case PAGE_SYM_TK:
+ case MEDIA_SYM_TK:
+ case FONT_FACE_SYM_TK:
+ case CHARSET_SYM_TK:
+ cr_tknzr_unget_token (PRIVATE (a_this)->tknzr, token);
+ token = NULL;
+ status = cr_parser_parse_atrule_core (a_this);
+ CHECK_PARSING_STATUS (status, TRUE);
+ break;
+
+ default:
+ cr_tknzr_unget_token (PRIVATE (a_this)->tknzr, token);
+ token = NULL;
+ status = cr_parser_parse_ruleset_core (a_this);
+ cr_parser_clear_errors (a_this);
+ CHECK_PARSING_STATUS (status, TRUE);
+ }
+
+ return CR_OK;
+
+ error:
+ if (token) {
+ cr_token_destroy (token);
+ token = NULL;
+ }
+
+ cr_tknzr_set_cur_pos (PRIVATE (a_this)->tknzr, &init_pos);
+
+ return status;
+}
+
+/**
+ * cr_parser_parse_ruleset:
+ *@a_this: the "this pointer" of the current instance of #CRParser.
+ *
+ *Parses a "ruleset" as defined in the css2 spec at appendix D.1.
+ *ruleset ::= selector [ ',' S* selector ]*
+ *'{' S* declaration? [ ';' S* declaration? ]* '}' S*;
+ *
+ *This methods calls the the SAC handler on the relevant SAC handler
+ *callbacks whenever it encounters some specific constructions.
+ *See the documentation of #CRDocHandler (the SAC handler) to know
+ *when which SAC handler is called.
+ *
+ *Returns CR_OK upon successful completion, an error code otherwise.
+ */
+enum CRStatus
+cr_parser_parse_ruleset (CRParser * a_this)
+{
+ enum CRStatus status = CR_OK;
+ CRInputPos init_pos;
+ guint32 cur_char = 0,
+ next_char = 0;
+ CRString *property = NULL;
+ CRTerm *expr = NULL;
+ CRSimpleSel *simple_sels = NULL;
+ CRSelector *selector = NULL;
+ gboolean start_selector = FALSE,
+ is_important = FALSE;
+ CRParsingLocation end_parsing_location;
+
+ g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR);
+
+ RECORD_INITIAL_POS (a_this, &init_pos);
+
+ status = cr_parser_parse_selector (a_this, &selector);
+ CHECK_PARSING_STATUS (status, FALSE);
+
+ READ_NEXT_CHAR (a_this, &cur_char);
+
+ ENSURE_PARSING_COND_ERR
+ (a_this, cur_char == '{',
+ (const guchar *) "while parsing rulset: current char should be '{'",
+ CR_SYNTAX_ERROR);
+
+ if (PRIVATE (a_this)->sac_handler
+ && PRIVATE (a_this)->sac_handler->start_selector) {
+ /*
+ *the selector is ref counted so that the parser's user
+ *can choose to keep it.
+ */
+ if (selector) {
+ cr_selector_ref (selector);
+ }
+
+ PRIVATE (a_this)->sac_handler->start_selector
+ (PRIVATE (a_this)->sac_handler, selector);
+ start_selector = TRUE;
+ }
+
+ cr_parser_try_to_skip_spaces_and_comments (a_this);
+
+ PRIVATE (a_this)->state = TRY_PARSE_RULESET_STATE;
+
+ status = cr_parser_parse_declaration (a_this, &property,
+ &expr,
+ &is_important);
+ if (expr) {
+ cr_term_ref (expr);
+ }
+ if (status == CR_OK
+ && PRIVATE (a_this)->sac_handler
+ && PRIVATE (a_this)->sac_handler->property) {
+ PRIVATE (a_this)->sac_handler->property
+ (PRIVATE (a_this)->sac_handler, property, expr,
+ is_important);
+ }
+ if (status == CR_OK) {
+ /*
+ *free the allocated
+ *'property' and 'term' before parsing
+ *next declarations.
+ */
+ if (property) {
+ cr_string_destroy (property);
+ property = NULL;
+ }
+ if (expr) {
+ cr_term_unref (expr);
+ expr = NULL;
+ }
+ } else {/*status != CR_OK*/
+ guint32 c = 0 ;
+ /*
+ *test if we have reached '}', which
+ *would mean that we are parsing an empty ruleset (eg. x{ })
+ *In that case, goto end_of_ruleset.
+ */
+ status = cr_tknzr_peek_char (PRIVATE (a_this)->tknzr, &c) ;
+ if (status == CR_OK && c == '}') {
+ status = CR_OK ;
+ goto end_of_ruleset ;
+ }
+ }
+ CHECK_PARSING_STATUS_ERR
+ (a_this, status, FALSE,
+ (const guchar *) "while parsing ruleset: next construction should be a declaration",
+ CR_SYNTAX_ERROR);
+
+ for (;;) {
+ PEEK_NEXT_CHAR (a_this, &next_char);
+ if (next_char != ';')
+ break;
+
+ /*consume the ';' char */
+ READ_NEXT_CHAR (a_this, &cur_char);
+
+ cr_parser_try_to_skip_spaces_and_comments (a_this);
+
+ status = cr_parser_parse_declaration (a_this, &property,
+ &expr, &is_important);
+
+ if (expr) {
+ cr_term_ref (expr);
+ }
+ if (status == CR_OK
+ && PRIVATE (a_this)->sac_handler
+ && PRIVATE (a_this)->sac_handler->property) {
+ PRIVATE (a_this)->sac_handler->property
+ (PRIVATE (a_this)->sac_handler,
+ property, expr, is_important);
+ }
+ if (property) {
+ cr_string_destroy (property);
+ property = NULL;
+ }
+ if (expr) {
+ cr_term_unref (expr);
+ expr = NULL;
+ }
+ }
+
+ end_of_ruleset:
+ cr_parser_try_to_skip_spaces_and_comments (a_this);
+ cr_parser_get_parsing_location (a_this, &end_parsing_location);
+ READ_NEXT_CHAR (a_this, &cur_char);
+ ENSURE_PARSING_COND_ERR
+ (a_this, cur_char == '}',
+ (const guchar *) "while parsing rulset: current char must be a '}'",
+ CR_SYNTAX_ERROR);
+
+ selector->location = end_parsing_location;
+ if (PRIVATE (a_this)->sac_handler
+ && PRIVATE (a_this)->sac_handler->end_selector) {
+ PRIVATE (a_this)->sac_handler->end_selector
+ (PRIVATE (a_this)->sac_handler, selector);
+ start_selector = FALSE;
+ }
+
+ if (expr) {
+ cr_term_unref (expr);
+ expr = NULL;
+ }
+
+ if (simple_sels) {
+ cr_simple_sel_destroy (simple_sels);
+ simple_sels = NULL;
+ }
+
+ if (selector) {
+ cr_selector_unref (selector);
+ selector = NULL;
+ }
+
+ cr_parser_clear_errors (a_this);
+ PRIVATE (a_this)->state = RULESET_PARSED_STATE;
+
+ return CR_OK;
+
+ error:
+ if (start_selector == TRUE
+ && PRIVATE (a_this)->sac_handler
+ && PRIVATE (a_this)->sac_handler->error) {
+ PRIVATE (a_this)->sac_handler->error
+ (PRIVATE (a_this)->sac_handler);
+ }
+ if (expr) {
+ cr_term_unref (expr);
+ expr = NULL;
+ }
+ if (simple_sels) {
+ cr_simple_sel_destroy (simple_sels);
+ simple_sels = NULL;
+ }
+ if (property) {
+ cr_string_destroy (property);
+ }
+ if (selector) {
+ cr_selector_unref (selector);
+ selector = NULL;
+ }
+
+ cr_tknzr_set_cur_pos (PRIVATE (a_this)->tknzr, &init_pos);
+
+ return status;
+}
+
+/**
+ * cr_parser_parse_import:
+ *@a_this: the "this pointer" of the current instance
+ *of #CRParser.
+ *@a_media_list: out parameter. A linked list of
+ *#CRString
+ *Each CRString is a string that contains
+ *a 'medium' declaration part of the successfully
+ *parsed 'import' declaration.
+ *@a_import_string: out parameter.
+ *A string that contains the 'import
+ *string". The import string can be either an uri (if it starts with
+ *the substring "uri(") or a any other css2 string. Note that
+ * *a_import_string must be initially set to NULL or else, this function
+ *will return CR_BAD_PARAM_ERROR.
+ *@a_location: the location (line, column) where the import has been parsed
+ *
+ *Parses an 'import' declaration as defined in the css2 spec
+ *in appendix D.1:
+ *
+ *import ::=
+ *\@import [STRING|URI] S* [ medium [ ',' S* medium]* ]? ';' S*
+ *
+ *Returns CR_OK upon successful completion, an error code otherwise.
+ */
+enum CRStatus
+cr_parser_parse_import (CRParser * a_this,
+ GList ** a_media_list,
+ CRString ** a_import_string,
+ CRParsingLocation *a_location)
+{
+ enum CRStatus status = CR_OK;
+ CRInputPos init_pos;
+ guint32 cur_char = 0,
+ next_char = 0;
+ CRString *medium = NULL;
+
+ g_return_val_if_fail (a_this
+ && a_import_string
+ && (*a_import_string == NULL),
+ CR_BAD_PARAM_ERROR);
+
+ RECORD_INITIAL_POS (a_this, &init_pos);
+
+ if (BYTE (a_this, 1, NULL) == '@'
+ && BYTE (a_this, 2, NULL) == 'i'
+ && BYTE (a_this, 3, NULL) == 'm'
+ && BYTE (a_this, 4, NULL) == 'p'
+ && BYTE (a_this, 5, NULL) == 'o'
+ && BYTE (a_this, 6, NULL) == 'r'
+ && BYTE (a_this, 7, NULL) == 't') {
+ SKIP_CHARS (a_this, 1);
+ if (a_location) {
+ cr_parser_get_parsing_location
+ (a_this, a_location) ;
+ }
+ SKIP_CHARS (a_this, 6);
+ status = CR_OK;
+ } else {
+ status = CR_PARSING_ERROR;
+ goto error;
+ }
+
+ cr_parser_try_to_skip_spaces_and_comments (a_this);
+
+ PRIVATE (a_this)->state = TRY_PARSE_IMPORT_STATE;
+
+ PEEK_NEXT_CHAR (a_this, &next_char);
+
+ if (next_char == '"' || next_char == '\'') {
+ status = cr_parser_parse_string (a_this, a_import_string);
+
+ CHECK_PARSING_STATUS (status, FALSE);
+ } else {
+ status = cr_parser_parse_uri (a_this, a_import_string);
+
+ CHECK_PARSING_STATUS (status, FALSE);
+ }
+
+ cr_parser_try_to_skip_spaces_and_comments (a_this);
+
+ status = cr_parser_parse_ident (a_this, &medium);
+
+ if (status == CR_OK && medium) {
+ *a_media_list = g_list_append (*a_media_list, medium);
+ medium = NULL;
+ }
+
+ cr_parser_try_to_skip_spaces_and_comments (a_this);
+
+ for (; status == CR_OK;) {
+ if ((status = cr_tknzr_peek_char (PRIVATE (a_this)->tknzr,
+ &next_char)) != CR_OK) {
+ if (status == CR_END_OF_INPUT_ERROR) {
+ status = CR_OK;
+ goto okay;
+ }
+ goto error;
+ }
+
+ if (next_char == ',') {
+ READ_NEXT_CHAR (a_this, &cur_char);
+ } else {
+ break;
+ }
+
+ cr_parser_try_to_skip_spaces_and_comments (a_this);
+
+ status = cr_parser_parse_ident (a_this, &medium);
+
+ cr_parser_try_to_skip_spaces_and_comments (a_this);
+
+ if ((status == CR_OK) && medium) {
+ *a_media_list = g_list_append (*a_media_list, medium);
+
+ medium = NULL;
+ }
+
+ CHECK_PARSING_STATUS (status, FALSE);
+ cr_parser_try_to_skip_spaces_and_comments (a_this);
+ }
+ cr_parser_try_to_skip_spaces_and_comments (a_this);
+ READ_NEXT_CHAR (a_this, &cur_char);
+ ENSURE_PARSING_COND (cur_char == ';');
+ cr_parser_try_to_skip_spaces_and_comments (a_this);
+ okay:
+ cr_parser_clear_errors (a_this);
+ PRIVATE (a_this)->state = IMPORT_PARSED_STATE;
+
+ return CR_OK;
+
+ error:
+
+ if (*a_media_list) {
+ GList *cur = NULL;
+
+ /*
+ *free each element of *a_media_list.
+ *Note that each element of *a_medium list *must*
+ *be a GString* or else, the code that is coming next
+ *will corrupt the memory and lead to hard to debug
+ *random crashes.
+ *This is where C++ and its compile time
+ *type checking mechanism (through STL containers) would
+ *have prevented us to go through this hassle.
+ */
+ for (cur = *a_media_list; cur; cur = cur->next) {
+ if (cur->data) {
+ cr_string_destroy (cur->data);
+ }
+ }
+
+ g_list_free (*a_media_list);
+ *a_media_list = NULL;
+ }
+
+ if (*a_import_string) {
+ cr_string_destroy (*a_import_string);
+ *a_import_string = NULL;
+ }
+
+ if (medium) {
+ cr_string_destroy (medium);
+ medium = NULL;
+ }
+
+ cr_tknzr_set_cur_pos (PRIVATE (a_this)->tknzr, &init_pos);
+
+ return status;
+}
+
+/**
+ * cr_parser_parse_media:
+ *@a_this: the "this pointer" of the current instance of #CRParser.
+ *
+ *Parses a 'media' declaration as specified in the css2 spec at
+ *appendix D.1:
+ *
+ *media ::= \@media S* medium [ ',' S* medium ]* '{' S* ruleset* '}' S*
+ *
+ *Note that this function calls the required sac handlers during the parsing
+ *to notify media productions. See #CRDocHandler to know the callback called
+ *during \@media parsing.
+ *
+ *Returns CR_OK upon successful completion, an error code otherwise.
+ */
+enum CRStatus
+cr_parser_parse_media (CRParser * a_this)
+{
+ enum CRStatus status = CR_OK;
+ CRInputPos init_pos;
+ CRToken *token = NULL;
+ guint32 next_char = 0,
+ cur_char = 0;
+ CRString *medium = NULL;
+ GList *media_list = NULL;
+ CRParsingLocation location = {0} ;
+
+ g_return_val_if_fail (a_this
+ && PRIVATE (a_this),
+ CR_BAD_PARAM_ERROR);
+
+ RECORD_INITIAL_POS (a_this, &init_pos);
+
+ status = cr_tknzr_get_next_token (PRIVATE (a_this)->tknzr,
+ &token);
+ ENSURE_PARSING_COND (status == CR_OK
+ && token
+ && token->type == MEDIA_SYM_TK);
+ cr_parsing_location_copy (&location, &token->location) ;
+ cr_token_destroy (token);
+ token = NULL;
+
+ cr_parser_try_to_skip_spaces_and_comments (a_this);
+
+ status = cr_tknzr_get_next_token (PRIVATE (a_this)->tknzr, &token);
+ ENSURE_PARSING_COND (status == CR_OK
+ && token && token->type == IDENT_TK);
+
+ medium = token->u.str;
+ token->u.str = NULL;
+ cr_token_destroy (token);
+ token = NULL;
+
+ if (medium) {
+ media_list = g_list_append (media_list, medium);
+ medium = NULL;
+ }
+
+ for (; status == CR_OK;) {
+ cr_parser_try_to_skip_spaces_and_comments (a_this);
+ PEEK_NEXT_CHAR (a_this, &next_char);
+
+ if (next_char == ',') {
+ READ_NEXT_CHAR (a_this, &cur_char);
+ } else {
+ break;
+ }
+
+ cr_parser_try_to_skip_spaces_and_comments (a_this);
+
+ status = cr_parser_parse_ident (a_this, &medium);
+
+ CHECK_PARSING_STATUS (status, FALSE);
+
+ if (medium) {
+ media_list = g_list_append (media_list, medium);
+ medium = NULL;
+ }
+ }
+
+ READ_NEXT_CHAR (a_this, &cur_char);
+
+ ENSURE_PARSING_COND (cur_char == '{');
+
+ /*
+ *call the SAC handler api here.
+ */
+ if (PRIVATE (a_this)->sac_handler
+ && PRIVATE (a_this)->sac_handler->start_media) {
+ PRIVATE (a_this)->sac_handler->start_media
+ (PRIVATE (a_this)->sac_handler, media_list,
+ &location);
+ }
+
+ cr_parser_try_to_skip_spaces_and_comments (a_this);
+
+ PRIVATE (a_this)->state = TRY_PARSE_MEDIA_STATE;
+
+ for (; status == CR_OK;) {
+ status = cr_parser_parse_ruleset (a_this);
+ cr_parser_try_to_skip_spaces_and_comments (a_this);
+ }
+
+ READ_NEXT_CHAR (a_this, &cur_char);
+
+ ENSURE_PARSING_COND (cur_char == '}');
+
+ /*
+ *call the right SAC handler api here.
+ */
+ if (PRIVATE (a_this)->sac_handler
+ && PRIVATE (a_this)->sac_handler->end_media) {
+ PRIVATE (a_this)->sac_handler->end_media
+ (PRIVATE (a_this)->sac_handler, media_list);
+ }
+
+ cr_parser_try_to_skip_spaces_and_comments (a_this);
+
+ /*
+ *Then, free the data structures passed to
+ *the last call to the SAC handler.
+ */
+ if (medium) {
+ cr_string_destroy (medium);
+ medium = NULL;
+ }
+
+ if (media_list) {
+ GList *cur = NULL;
+
+ for (cur = media_list; cur; cur = cur->next) {
+ cr_string_destroy (cur->data);
+ }
+
+ g_list_free (media_list);
+ media_list = NULL;
+ }
+
+ cr_parser_clear_errors (a_this);
+ PRIVATE (a_this)->state = MEDIA_PARSED_STATE;
+
+ return CR_OK;
+
+ error:
+
+ if (token) {
+ cr_token_destroy (token);
+ token = NULL;
+ }
+
+ if (medium) {
+ cr_string_destroy (medium);
+ medium = NULL;
+ }
+
+ if (media_list) {
+ GList *cur = NULL;
+
+ for (cur = media_list; cur; cur = cur->next) {
+ cr_string_destroy (cur->data);
+ }
+
+ g_list_free (media_list);
+ media_list = NULL;
+ }
+
+ cr_tknzr_set_cur_pos (PRIVATE (a_this)->tknzr, &init_pos);
+
+ return status;
+}
+
+/**
+ * cr_parser_parse_page:
+ *@a_this: the "this pointer" of the current instance of #CRParser.
+ *
+ *Parses '\@page' rule as specified in the css2 spec in appendix D.1:
+ *page ::= PAGE_SYM S* IDENT? pseudo_page? S*
+ *'{' S* declaration [ ';' S* declaration ]* '}' S*
+ *
+ *This function also calls the relevant SAC handlers whenever it
+ *encounters a construction that must
+ *be reported to the calling application.
+ *
+ *Returns CR_OK upon successful completion, an error code otherwise.
+ */
+enum CRStatus
+cr_parser_parse_page (CRParser * a_this)
+{
+ enum CRStatus status = CR_OK;
+ CRInputPos init_pos;
+ CRToken *token = NULL;
+ CRTerm *css_expression = NULL;
+ CRString *page_selector = NULL,
+ *page_pseudo_class = NULL,
+ *property = NULL;
+ gboolean important = TRUE;
+ CRParsingLocation location = {0} ;
+
+ g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR);
+
+ RECORD_INITIAL_POS (a_this, &init_pos);
+
+ status = cr_tknzr_get_next_token (PRIVATE (a_this)->tknzr,
+ &token) ;
+ ENSURE_PARSING_COND (status == CR_OK
+ && token
+ && token->type == PAGE_SYM_TK);
+
+ cr_parsing_location_copy (&location, &token->location) ;
+ cr_token_destroy (token);
+ token = NULL;
+
+ cr_parser_try_to_skip_spaces_and_comments (a_this);
+
+ status = cr_tknzr_get_next_token (PRIVATE (a_this)->tknzr, &token);
+ ENSURE_PARSING_COND (status == CR_OK && token);
+
+ if (token->type == IDENT_TK) {
+ page_selector = token->u.str;
+ token->u.str = NULL;
+ cr_token_destroy (token);
+ token = NULL;
+ } else {
+ cr_tknzr_unget_token (PRIVATE (a_this)->tknzr, token);
+ token = NULL;
+ }
+
+ /*
+ *try to parse pseudo_page
+ */
+ cr_parser_try_to_skip_spaces_and_comments (a_this);
+ status = cr_tknzr_get_next_token (PRIVATE (a_this)->tknzr, &token);
+ ENSURE_PARSING_COND (status == CR_OK && token);
+
+ if (token->type == DELIM_TK && token->u.unichar == ':') {
+ cr_token_destroy (token);
+ token = NULL;
+ status = cr_parser_parse_ident (a_this, &page_pseudo_class);
+ CHECK_PARSING_STATUS (status, FALSE);
+ } else {
+ cr_tknzr_unget_token (PRIVATE (a_this)->tknzr, token);
+ token = NULL;
+ }
+
+ /*
+ *parse_block
+ *
+ */
+ cr_parser_try_to_skip_spaces_and_comments (a_this);
+
+ status = cr_tknzr_get_next_token (PRIVATE (a_this)->tknzr, &token);
+
+ ENSURE_PARSING_COND (status == CR_OK && token
+ && token->type == CBO_TK);
+
+ cr_token_destroy (token);
+ token = NULL;
+
+ /*
+ *Call the appropriate SAC handler here.
+ */
+ if (PRIVATE (a_this)->sac_handler
+ && PRIVATE (a_this)->sac_handler->start_page) {
+ PRIVATE (a_this)->sac_handler->start_page
+ (PRIVATE (a_this)->sac_handler,
+ page_selector, page_pseudo_class,
+ &location);
+ }
+ cr_parser_try_to_skip_spaces_and_comments (a_this);
+
+ PRIVATE (a_this)->state = TRY_PARSE_PAGE_STATE;
+
+ status = cr_parser_parse_declaration (a_this, &property,
+ &css_expression,
+ &important);
+ ENSURE_PARSING_COND (status == CR_OK);
+
+ /*
+ *call the relevant SAC handler here...
+ */
+ if (PRIVATE (a_this)->sac_handler
+ && PRIVATE (a_this)->sac_handler->property) {
+ if (css_expression)
+ cr_term_ref (css_expression);
+
+ PRIVATE (a_this)->sac_handler->property
+ (PRIVATE (a_this)->sac_handler,
+ property, css_expression, important);
+ }
+ /*
+ *... and free the data structure passed to that last
+ *SAC handler.
+ */
+ if (property) {
+ cr_string_destroy (property);
+ property = NULL;
+ }
+ if (css_expression) {
+ cr_term_unref (css_expression);
+ css_expression = NULL;
+ }
+
+ for (;;) {
+ /*parse the other ';' separated declarations */
+ if (token) {
+ cr_token_destroy (token);
+ token = NULL;
+ }
+ status = cr_tknzr_get_next_token
+ (PRIVATE (a_this)->tknzr, &token);
+
+ ENSURE_PARSING_COND (status == CR_OK && token);
+
+ if (token->type != SEMICOLON_TK) {
+ cr_tknzr_unget_token
+ (PRIVATE (a_this)->tknzr,
+ token);
+ token = NULL ;
+ break;
+ }
+
+ cr_token_destroy (token);
+ token = NULL;
+ cr_parser_try_to_skip_spaces_and_comments (a_this);
+
+ status = cr_parser_parse_declaration (a_this, &property,
+ &css_expression,
+ &important);
+ if (status != CR_OK)
+ break ;
+
+ /*
+ *call the relevant SAC handler here...
+ */
+ if (PRIVATE (a_this)->sac_handler
+ && PRIVATE (a_this)->sac_handler->property) {
+ cr_term_ref (css_expression);
+ PRIVATE (a_this)->sac_handler->property
+ (PRIVATE (a_this)->sac_handler,
+ property, css_expression, important);
+ }
+ /*
+ *... and free the data structure passed to that last
+ *SAC handler.
+ */
+ if (property) {
+ cr_string_destroy (property);
+ property = NULL;
+ }
+ if (css_expression) {
+ cr_term_unref (css_expression);
+ css_expression = NULL;
+ }
+ }
+ cr_parser_try_to_skip_spaces_and_comments
+ (a_this) ;
+ if (token) {
+ cr_token_destroy (token) ;
+ token = NULL ;
+ }
+
+ status = cr_tknzr_get_next_token
+ (PRIVATE (a_this)->tknzr, &token);
+ ENSURE_PARSING_COND (status == CR_OK
+ && token
+ && token->type == CBC_TK) ;
+ cr_token_destroy (token) ;
+ token = NULL ;
+ /*
+ *call the relevant SAC handler here.
+ */
+ if (PRIVATE (a_this)->sac_handler
+ && PRIVATE (a_this)->sac_handler->end_page) {
+ PRIVATE (a_this)->sac_handler->end_page
+ (PRIVATE (a_this)->sac_handler,
+ page_selector, page_pseudo_class);
+ }
+
+ if (page_selector) {
+ cr_string_destroy (page_selector);
+ page_selector = NULL;
+ }
+
+ if (page_pseudo_class) {
+ cr_string_destroy (page_pseudo_class);
+ page_pseudo_class = NULL;
+ }
+
+ cr_parser_try_to_skip_spaces_and_comments (a_this);
+
+ /*here goes the former implem of this function ... */
+
+ cr_parser_clear_errors (a_this);
+ PRIVATE (a_this)->state = PAGE_PARSED_STATE;
+
+ return CR_OK;
+
+ error:
+ if (token) {
+ cr_token_destroy (token);
+ token = NULL;
+ }
+ if (page_selector) {
+ cr_string_destroy (page_selector);
+ page_selector = NULL;
+ }
+ if (page_pseudo_class) {
+ cr_string_destroy (page_pseudo_class);
+ page_pseudo_class = NULL;
+ }
+ if (property) {
+ cr_string_destroy (property);
+ property = NULL;
+ }
+ if (css_expression) {
+ cr_term_destroy (css_expression);
+ css_expression = NULL;
+ }
+ cr_tknzr_set_cur_pos (PRIVATE (a_this)->tknzr, &init_pos);
+ return status;
+}
+
+/**
+ * cr_parser_parse_charset:
+ *@a_this: the "this pointer" of the current instance of #CRParser.
+ *@a_value: out parameter. The actual parsed value of the charset
+ *declararation. Note that for safety check reasons, *a_value must be
+ *set to NULL.
+ *@a_charset_sym_location: the parsing location of the charset rule
+ *
+ *Parses a charset declaration as defined implicitly by the css2 spec in
+ *appendix D.1:
+ *charset ::= CHARSET_SYM S* STRING S* ';'
+ *
+ *Returns CR_OK upon successful completion, an error code otherwise.
+ */
+enum CRStatus
+cr_parser_parse_charset (CRParser * a_this, CRString ** a_value,
+ CRParsingLocation *a_charset_sym_location)
+{
+ enum CRStatus status = CR_OK;
+ CRInputPos init_pos;
+ CRToken *token = NULL;
+ CRString *charset_str = NULL;
+
+ g_return_val_if_fail (a_this && a_value
+ && (*a_value == NULL),
+ CR_BAD_PARAM_ERROR);
+
+ RECORD_INITIAL_POS (a_this, &init_pos);
+
+ status = cr_tknzr_get_next_token (PRIVATE (a_this)->tknzr, &token);
+
+ ENSURE_PARSING_COND (status == CR_OK
+ && token && token->type == CHARSET_SYM_TK);
+ if (a_charset_sym_location) {
+ cr_parsing_location_copy (a_charset_sym_location,
+ &token->location) ;
+ }
+ cr_token_destroy (token);
+ token = NULL;
+
+ PRIVATE (a_this)->state = TRY_PARSE_CHARSET_STATE;
+
+ cr_parser_try_to_skip_spaces_and_comments (a_this);
+
+ status = cr_tknzr_get_next_token (PRIVATE (a_this)->tknzr, &token);
+ ENSURE_PARSING_COND (status == CR_OK
+ && token && token->type == STRING_TK);
+ charset_str = token->u.str;
+ token->u.str = NULL;
+ cr_token_destroy (token);
+ token = NULL;
+
+ cr_parser_try_to_skip_spaces_and_comments (a_this);
+
+ status = cr_tknzr_get_next_token (PRIVATE (a_this)->tknzr, &token);
+
+ ENSURE_PARSING_COND (status == CR_OK
+ && token && token->type == SEMICOLON_TK);
+ cr_token_destroy (token);
+ token = NULL;
+
+ if (charset_str) {
+ *a_value = charset_str;
+ charset_str = NULL;
+ }
+
+ PRIVATE (a_this)->state = CHARSET_PARSED_STATE;
+ return CR_OK;
+
+ error:
+
+ if (token) {
+ cr_token_destroy (token);
+ token = NULL;
+ }
+
+ if (*a_value) {
+ cr_string_destroy (*a_value);
+ *a_value = NULL;
+ }
+
+ if (charset_str) {
+ cr_string_destroy (charset_str);
+ charset_str = NULL;
+ }
+
+ cr_tknzr_set_cur_pos (PRIVATE (a_this)->tknzr, &init_pos);
+
+ return status;
+}
+
+/**
+ * cr_parser_parse_font_face:
+ *@a_this: the current instance of #CRParser.
+ *
+ *Parses the "\@font-face" rule specified in the css1 spec in
+ *appendix D.1:
+ *
+ *font_face ::= FONT_FACE_SYM S*
+ *'{' S* declaration [ ';' S* declaration ]* '}' S*
+ *
+ *This function will call SAC handlers whenever it is necessary.
+ *
+ *Returns CR_OK upon successful completion, an error code otherwise.
+ */
+enum CRStatus
+cr_parser_parse_font_face (CRParser * a_this)
+{
+ enum CRStatus status = CR_ERROR;
+ CRInputPos init_pos;
+ CRString *property = NULL;
+ CRTerm *css_expression = NULL;
+ CRToken *token = NULL;
+ gboolean important = FALSE;
+ guint32 next_char = 0,
+ cur_char = 0;
+ CRParsingLocation location = {0} ;
+
+ g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR);
+
+ RECORD_INITIAL_POS (a_this, &init_pos);
+
+ status = cr_tknzr_get_next_token (PRIVATE (a_this)->tknzr, &token);
+ ENSURE_PARSING_COND (status == CR_OK
+ && token
+ && token->type == FONT_FACE_SYM_TK);
+
+ cr_parser_try_to_skip_spaces_and_comments (a_this);
+ if (token) {
+ cr_parsing_location_copy (&location,
+ &token->location) ;
+ cr_token_destroy (token);
+ token = NULL;
+ }
+ status = cr_tknzr_get_next_token (PRIVATE (a_this)->tknzr,
+ &token);
+ ENSURE_PARSING_COND (status == CR_OK && token
+ && token->type == CBO_TK);
+ if (token) {
+ cr_token_destroy (token);
+ token = NULL;
+ }
+ /*
+ *here, call the relevant SAC handler.
+ */
+ if (PRIVATE (a_this)->sac_handler
+ && PRIVATE (a_this)->sac_handler->start_font_face) {
+ PRIVATE (a_this)->sac_handler->start_font_face
+ (PRIVATE (a_this)->sac_handler, &location);
+ }
+ PRIVATE (a_this)->state = TRY_PARSE_FONT_FACE_STATE;
+ /*
+ *and resume the parsing.
+ */
+ cr_parser_try_to_skip_spaces_and_comments (a_this);
+ status = cr_parser_parse_declaration (a_this, &property,
+ &css_expression, &important);
+ if (status == CR_OK) {
+ /*
+ *here, call the relevant SAC handler.
+ */
+ cr_term_ref (css_expression);
+ if (PRIVATE (a_this)->sac_handler &&
+ PRIVATE (a_this)->sac_handler->property) {
+ PRIVATE (a_this)->sac_handler->property
+ (PRIVATE (a_this)->sac_handler,
+ property, css_expression, important);
+ }
+ ENSURE_PARSING_COND (css_expression && property);
+ }
+ /*free the data structures allocated during last parsing. */
+ if (property) {
+ cr_string_destroy (property);
+ property = NULL;
+ }
+ if (css_expression) {
+ cr_term_unref (css_expression);
+ css_expression = NULL;
+ }
+ for (;;) {
+ PEEK_NEXT_CHAR (a_this, &next_char);
+ if (next_char == ';') {
+ READ_NEXT_CHAR (a_this, &cur_char);
+ } else {
+ break;
+ }
+ cr_parser_try_to_skip_spaces_and_comments (a_this);
+ status = cr_parser_parse_declaration (a_this,
+ &property,
+ &css_expression,
+ &important);
+ if (status != CR_OK)
+ break;
+ /*
+ *here, call the relevant SAC handler.
+ */
+ cr_term_ref (css_expression);
+ if (PRIVATE (a_this)->sac_handler->property) {
+ PRIVATE (a_this)->sac_handler->property
+ (PRIVATE (a_this)->sac_handler,
+ property, css_expression, important);
+ }
+ /*
+ *Then, free the data structures allocated during
+ *last parsing.
+ */
+ if (property) {
+ cr_string_destroy (property);
+ property = NULL;
+ }
+ if (css_expression) {
+ cr_term_unref (css_expression);
+ css_expression = NULL;
+ }
+ }
+ cr_parser_try_to_skip_spaces_and_comments (a_this);
+ READ_NEXT_CHAR (a_this, &cur_char);
+ ENSURE_PARSING_COND (cur_char == '}');
+ /*
+ *here, call the relevant SAC handler.
+ */
+ if (PRIVATE (a_this)->sac_handler->end_font_face) {
+ PRIVATE (a_this)->sac_handler->end_font_face
+ (PRIVATE (a_this)->sac_handler);
+ }
+ cr_parser_try_to_skip_spaces_and_comments (a_this);
+
+ if (token) {
+ cr_token_destroy (token);
+ token = NULL;
+ }
+ cr_parser_clear_errors (a_this);
+ PRIVATE (a_this)->state = FONT_FACE_PARSED_STATE;
+ return CR_OK;
+
+ error:
+ if (token) {
+ cr_token_destroy (token);
+ token = NULL;
+ }
+ if (property) {
+ cr_string_destroy (property);
+ property = NULL;
+ }
+ if (css_expression) {
+ cr_term_destroy (css_expression);
+ css_expression = NULL;
+ }
+ cr_tknzr_set_cur_pos (PRIVATE (a_this)->tknzr, &init_pos);
+ return status;
+}
+
+/**
+ * cr_parser_parse:
+ *@a_this: the current instance of #CRParser.
+ *
+ *Parses the data that comes from the
+ *input previously associated to the current instance of
+ *#CRParser.
+ *
+ *Returns CR_OK upon successful completion, an error code otherwise.
+ */
+enum CRStatus
+cr_parser_parse (CRParser * a_this)
+{
+ enum CRStatus status = CR_ERROR;
+
+ g_return_val_if_fail (a_this && PRIVATE (a_this)
+ && PRIVATE (a_this)->tknzr, CR_BAD_PARAM_ERROR);
+
+ if (PRIVATE (a_this)->use_core_grammar == FALSE) {
+ status = cr_parser_parse_stylesheet (a_this);
+ } else {
+ status = cr_parser_parse_stylesheet_core (a_this);
+ }
+
+ return status;
+}
+
+/**
+ * cr_parser_set_tknzr:
+ * @a_this: the current instance of #CRParser;
+ * @a_tknzr: the new tokenizer.
+ *
+ * Returns CR_OK upon successful completion, an error code otherwise.
+ */
+enum CRStatus
+cr_parser_set_tknzr (CRParser * a_this, CRTknzr * a_tknzr)
+{
+ g_return_val_if_fail (a_this && PRIVATE (a_this), CR_BAD_PARAM_ERROR);
+
+ if (PRIVATE (a_this)->tknzr) {
+ cr_tknzr_unref (PRIVATE (a_this)->tknzr);
+ }
+
+ PRIVATE (a_this)->tknzr = a_tknzr;
+
+ if (a_tknzr)
+ cr_tknzr_ref (a_tknzr);
+
+ return CR_OK;
+}
+
+/**
+ * cr_parser_get_tknzr:
+ *@a_this: the current instance of #CRParser
+ *@a_tknzr: out parameter. The returned tokenizer
+ *
+ *Getter of the parser's underlying tokenizer
+ *
+ *Returns CR_OK upon successful completion, an error code
+ *otherwise
+ */
+enum CRStatus
+cr_parser_get_tknzr (CRParser * a_this, CRTknzr ** a_tknzr)
+{
+ g_return_val_if_fail (a_this && PRIVATE (a_this)
+ && a_tknzr, CR_BAD_PARAM_ERROR);
+
+ *a_tknzr = PRIVATE (a_this)->tknzr;
+ return CR_OK;
+}
+
+/**
+ * cr_parser_get_parsing_location:
+ *@a_this: the current instance of #CRParser
+ *@a_loc: the parsing location to get.
+ *
+ *Gets the current parsing location.
+ *
+ *Returns CR_OK upon successful completion, an error code
+ *otherwise.
+ */
+enum CRStatus
+cr_parser_get_parsing_location (CRParser const *a_this,
+ CRParsingLocation *a_loc)
+{
+ g_return_val_if_fail (a_this
+ && PRIVATE (a_this)
+ && a_loc, CR_BAD_PARAM_ERROR) ;
+
+ return cr_tknzr_get_parsing_location
+ (PRIVATE (a_this)->tknzr, a_loc) ;
+}
+
+/**
+ * cr_parser_parse_buf:
+ *@a_this: the current instance of #CRparser
+ *@a_buf: the input buffer
+ *@a_len: the length of the input buffer
+ *@a_enc: the encoding of the buffer
+ *
+ *Parses a stylesheet from a buffer
+ *
+ *Returns CR_OK upon successful completion, an error code otherwise.
+ */
+enum CRStatus
+cr_parser_parse_buf (CRParser * a_this,
+ const guchar * a_buf,
+ gulong a_len, enum CREncoding a_enc)
+{
+ enum CRStatus status = CR_ERROR;
+ CRTknzr *tknzr = NULL;
+
+ g_return_val_if_fail (a_this && PRIVATE (a_this)
+ && a_buf, CR_BAD_PARAM_ERROR);
+
+ tknzr = cr_tknzr_new_from_buf ((guchar*)a_buf, a_len, a_enc, FALSE);
+
+ g_return_val_if_fail (tknzr != NULL, CR_ERROR);
+
+ status = cr_parser_set_tknzr (a_this, tknzr);
+ g_return_val_if_fail (status == CR_OK, CR_ERROR);
+
+ status = cr_parser_parse (a_this);
+
+ return status;
+}
+
+/**
+ * cr_parser_destroy:
+ *@a_this: the current instance of #CRParser to
+ *destroy.
+ *
+ *Destroys the current instance
+ *of #CRParser.
+ */
+void
+cr_parser_destroy (CRParser * a_this)
+{
+ g_return_if_fail (a_this && PRIVATE (a_this));
+
+ if (PRIVATE (a_this)->tknzr) {
+ if (cr_tknzr_unref (PRIVATE (a_this)->tknzr) == TRUE)
+ PRIVATE (a_this)->tknzr = NULL;
+ }
+
+ if (PRIVATE (a_this)->sac_handler) {
+ cr_doc_handler_unref (PRIVATE (a_this)->sac_handler);
+ PRIVATE (a_this)->sac_handler = NULL;
+ }
+
+ if (PRIVATE (a_this)->err_stack) {
+ cr_parser_clear_errors (a_this);
+ PRIVATE (a_this)->err_stack = NULL;
+ }
+
+ if (PRIVATE (a_this)) {
+ g_free (PRIVATE (a_this));
+ PRIVATE (a_this) = NULL;
+ }
+
+ if (a_this) {
+ g_free (a_this);
+ a_this = NULL; /*useless. Just for the sake of coherence */
+ }
+}
diff --git a/src/st/croco/cr-parser.h b/src/st/croco/cr-parser.h
new file mode 100644
index 0000000..6dce943
--- /dev/null
+++ b/src/st/croco/cr-parser.h
@@ -0,0 +1,128 @@
+/* -*- Mode: C; indent-tabs-mode:nil; c-basic-offset: 8-*- */
+
+/*
+ * This file is part of The Croco Library
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2.1 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * 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 Lesser General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ *
+ * Author: Dodji Seketeli
+ * See COPYRIGHTS file for copyrights information.
+ */
+
+#ifndef __CR_PARSER_H__
+#define __CR_PARSER_H__
+
+#include <glib.h>
+#include "cr-input.h"
+#include "cr-tknzr.h"
+#include "cr-utils.h"
+#include "cr-doc-handler.h"
+
+G_BEGIN_DECLS
+
+/**
+ *@file
+ *The declaration file
+ *of the #CRParser class.
+ */
+typedef struct _CRParser CRParser ;
+typedef struct _CRParserPriv CRParserPriv ;
+
+
+/**
+ *The implementation of
+ *the SAC parser.
+ *The Class is opaque
+ *and must be manipulated through
+ *the provided methods.
+ */
+struct _CRParser {
+ CRParserPriv *priv ;
+} ;
+
+
+CRParser * cr_parser_new (CRTknzr *a_tknzr) ;
+
+CRParser * cr_parser_new_from_buf (guchar *a_buf, gulong a_len,
+ enum CREncoding a_enc,
+ gboolean a_free_buf) ;
+
+CRParser * cr_parser_new_from_file (const guchar *a_file_uri,
+ enum CREncoding a_enc) ;
+
+CRParser * cr_parser_new_from_input (CRInput *a_input) ;
+
+enum CRStatus cr_parser_set_tknzr (CRParser *a_this, CRTknzr *a_tknzr) ;
+
+enum CRStatus cr_parser_get_tknzr (CRParser *a_this, CRTknzr **a_tknzr) ;
+
+enum CRStatus cr_parser_get_parsing_location (CRParser const *a_this, CRParsingLocation *a_loc) ;
+
+enum CRStatus cr_parser_try_to_skip_spaces_and_comments (CRParser *a_this) ;
+
+
+enum CRStatus cr_parser_set_sac_handler (CRParser *a_this,
+ CRDocHandler *a_handler) ;
+
+enum CRStatus cr_parser_get_sac_handler (CRParser *a_this,
+ CRDocHandler **a_handler) ;
+
+enum CRStatus cr_parser_set_use_core_grammar (CRParser *a_this,
+ gboolean a_use_core_grammar) ;
+enum CRStatus cr_parser_get_use_core_grammar (CRParser const *a_this,
+ gboolean *a_use_core_grammar) ;
+
+enum CRStatus cr_parser_parse (CRParser *a_this) ;
+
+enum CRStatus cr_parser_parse_file (CRParser *a_this,
+ const guchar *a_file_uri,
+ enum CREncoding a_enc) ;
+
+enum CRStatus cr_parser_parse_buf (CRParser *a_this, const guchar *a_buf,
+ gulong a_len, enum CREncoding a_enc) ;
+
+enum CRStatus cr_parser_set_default_sac_handler (CRParser *a_this) ;
+
+enum CRStatus cr_parser_parse_term (CRParser *a_this, CRTerm **a_term) ;
+
+enum CRStatus cr_parser_parse_expr (CRParser *a_this, CRTerm **a_expr) ;
+
+enum CRStatus cr_parser_parse_prio (CRParser *a_this, CRString **a_prio) ;
+
+enum CRStatus cr_parser_parse_declaration (CRParser *a_this, CRString **a_property,
+ CRTerm **a_expr, gboolean *a_important) ;
+
+enum CRStatus cr_parser_parse_statement_core (CRParser *a_this) ;
+
+enum CRStatus cr_parser_parse_ruleset (CRParser *a_this) ;
+
+enum CRStatus cr_parser_parse_import (CRParser *a_this, GList ** a_media_list,
+ CRString **a_import_string,
+ CRParsingLocation *a_location) ;
+
+enum CRStatus cr_parser_parse_media (CRParser *a_this) ;
+
+enum CRStatus cr_parser_parse_page (CRParser *a_this) ;
+
+enum CRStatus cr_parser_parse_charset (CRParser *a_this, CRString **a_value,
+ CRParsingLocation *a_charset_sym_location) ;
+
+enum CRStatus cr_parser_parse_font_face (CRParser *a_this) ;
+
+void cr_parser_destroy (CRParser *a_this) ;
+
+G_END_DECLS
+
+#endif /*__CR_PARSER_H__*/
diff --git a/src/st/croco/cr-parsing-location.c b/src/st/croco/cr-parsing-location.c
new file mode 100644
index 0000000..2b40974
--- /dev/null
+++ b/src/st/croco/cr-parsing-location.c
@@ -0,0 +1,171 @@
+/* -*- Mode: C; indent-tabs-mode: nil; c-basic-offset: 8 -*- */
+
+/*
+ * This file is part of The Croco Library
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2.1 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * 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 Lesser General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ *
+ * Author: Dodji Seketeli.
+ * See the COPYRIGHTS file for copyright information.
+ */
+
+#include <string.h>
+#include "cr-parsing-location.h"
+
+/**
+ *@CRParsingLocation:
+ *
+ *Definition of the #CRparsingLocation class.
+ */
+
+
+/**
+ * cr_parsing_location_new:
+ *Instantiates a new parsing location.
+ *
+ *Returns the newly instantiated #CRParsingLocation.
+ *Must be freed by cr_parsing_location_destroy()
+ */
+CRParsingLocation *
+cr_parsing_location_new (void)
+{
+ CRParsingLocation * result = NULL ;
+
+ result = g_try_malloc (sizeof (CRParsingLocation)) ;
+ if (!result) {
+ cr_utils_trace_info ("Out of memory error") ;
+ return NULL ;
+ }
+ cr_parsing_location_init (result) ;
+ return result ;
+}
+
+/**
+ * cr_parsing_location_init:
+ *@a_this: the current instance of #CRParsingLocation.
+ *
+ *Initializes the an instance of #CRparsingLocation.
+ *
+ *Returns CR_OK upon successful completion, an error code otherwise.
+ */
+enum CRStatus
+cr_parsing_location_init (CRParsingLocation *a_this)
+{
+ g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR) ;
+
+ memset (a_this, 0, sizeof (CRParsingLocation)) ;
+ return CR_OK ;
+}
+
+/**
+ * cr_parsing_location_copy:
+ *@a_to: the destination of the copy.
+ *Must be allocated by the caller.
+ *@a_from: the source of the copy.
+ *
+ *Copies an instance of CRParsingLocation into another one.
+ *
+ *Returns CR_OK upon successful completion, an error code
+ *otherwise.
+ */
+enum CRStatus
+cr_parsing_location_copy (CRParsingLocation *a_to,
+ CRParsingLocation const *a_from)
+{
+ g_return_val_if_fail (a_to && a_from, CR_BAD_PARAM_ERROR) ;
+
+ memcpy (a_to, a_from, sizeof (CRParsingLocation)) ;
+ return CR_OK ;
+}
+
+/**
+ * cr_parsing_location_to_string:
+ *@a_this: the current instance of #CRParsingLocation.
+ *@a_mask: a bitmap that defines which parts of the
+ *parsing location are to be serialized (line, column or byte offset)
+ *
+ *Returns the serialized string or NULL in case of an error.
+ */
+gchar *
+cr_parsing_location_to_string (CRParsingLocation const *a_this,
+ enum CRParsingLocationSerialisationMask a_mask)
+{
+ GString *result = NULL ;
+ gchar *str = NULL ;
+
+ g_return_val_if_fail (a_this, NULL) ;
+
+ if (!a_mask) {
+ a_mask = DUMP_LINE | DUMP_COLUMN | DUMP_BYTE_OFFSET ;
+ }
+ result =g_string_new (NULL) ;
+ if (!result)
+ return NULL ;
+ if (a_mask & DUMP_LINE) {
+ g_string_append_printf (result, "line:%d ",
+ a_this->line) ;
+ }
+ if (a_mask & DUMP_COLUMN) {
+ g_string_append_printf (result, "column:%d ",
+ a_this->column) ;
+ }
+ if (a_mask & DUMP_BYTE_OFFSET) {
+ g_string_append_printf (result, "byte offset:%d ",
+ a_this->byte_offset) ;
+ }
+ if (result->len) {
+ str = g_string_free (result, FALSE) ;
+ } else {
+ g_string_free (result, TRUE) ;
+ }
+ return str ;
+}
+
+/**
+ * cr_parsing_location_dump:
+ * @a_this: current instance of #CRParsingLocation
+ * @a_mask: the serialization mask.
+ * @a_fp: the file pointer to dump the parsing location to.
+ */
+void
+cr_parsing_location_dump (CRParsingLocation const *a_this,
+ enum CRParsingLocationSerialisationMask a_mask,
+ FILE *a_fp)
+{
+ gchar *str = NULL ;
+
+ g_return_if_fail (a_this && a_fp) ;
+ str = cr_parsing_location_to_string (a_this, a_mask) ;
+ if (str) {
+ fprintf (a_fp, "%s", str) ;
+ g_free (str) ;
+ str = NULL ;
+ }
+}
+
+/**
+ * cr_parsing_location_destroy:
+ *@a_this: the current instance of #CRParsingLocation. Must
+ *have been allocated with cr_parsing_location_new().
+ *
+ *Destroys the current instance of #CRParsingLocation
+ */
+void
+cr_parsing_location_destroy (CRParsingLocation *a_this)
+{
+ g_return_if_fail (a_this) ;
+ g_free (a_this) ;
+}
+
diff --git a/src/st/croco/cr-parsing-location.h b/src/st/croco/cr-parsing-location.h
new file mode 100644
index 0000000..b8064a5
--- /dev/null
+++ b/src/st/croco/cr-parsing-location.h
@@ -0,0 +1,70 @@
+/* -*- Mode: C; indent-tabs-mode: nil; c-basic-offset: 8 -*- */
+
+/*
+ * This file is part of The Croco Library
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2.1 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * 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 Lesser General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ *
+ * Author: Dodji Seketeli.
+ * See the COPYRIGHTS file for copyright information.
+ */
+
+#ifndef __CR_PARSING_LOCATION_H__
+#define __CR_PARSING_LOCATION_H__
+
+#include "cr-utils.h"
+
+G_BEGIN_DECLS
+
+/**
+ *@file
+ *The declaration of the CRParsingLocation
+ *object. This object keeps track of line/column/byte offset/
+ *at which the parsing of a given CSS construction appears.
+ */
+
+typedef struct _CRParsingLocation CRParsingLocation;
+struct _CRParsingLocation {
+ guint line ;
+ guint column ;
+ guint byte_offset ;
+} ;
+
+
+enum CRParsingLocationSerialisationMask {
+ DUMP_LINE = 1,
+ DUMP_COLUMN = 1 << 1,
+ DUMP_BYTE_OFFSET = 1 << 2
+} ;
+
+CRParsingLocation * cr_parsing_location_new (void) ;
+
+enum CRStatus cr_parsing_location_init (CRParsingLocation *a_this) ;
+
+enum CRStatus cr_parsing_location_copy (CRParsingLocation *a_to,
+ CRParsingLocation const *a_from) ;
+
+gchar * cr_parsing_location_to_string (CRParsingLocation const *a_this,
+ enum CRParsingLocationSerialisationMask a_mask) ;
+void cr_parsing_location_dump (CRParsingLocation const *a_this,
+ enum CRParsingLocationSerialisationMask a_mask,
+ FILE *a_fp) ;
+
+void cr_parsing_location_destroy (CRParsingLocation *a_this) ;
+
+
+
+G_END_DECLS
+#endif
diff --git a/src/st/croco/cr-prop-list.c b/src/st/croco/cr-prop-list.c
new file mode 100644
index 0000000..03c4478
--- /dev/null
+++ b/src/st/croco/cr-prop-list.c
@@ -0,0 +1,404 @@
+/*
+ * This file is part of The Croco Library
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2.1 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * 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 Lesser General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ *
+ * Author: Dodji Seketeli
+ * See COPYRIGHTS file for copyrights information.
+ */
+
+#include <string.h>
+#include "cr-prop-list.h"
+
+#define PRIVATE(a_obj) (a_obj)->priv
+
+struct _CRPropListPriv {
+ CRString *prop;
+ CRDeclaration *decl;
+ CRPropList *next;
+ CRPropList *prev;
+};
+
+static CRPropList *cr_prop_list_allocate (void);
+
+/**
+ *Default allocator of CRPropList
+ *@return the newly allocated CRPropList or NULL
+ *if an error arises.
+ */
+static CRPropList *
+cr_prop_list_allocate (void)
+{
+ CRPropList *result = NULL;
+
+ result = g_try_malloc (sizeof (CRPropList));
+ if (!result) {
+ cr_utils_trace_info ("could not allocate CRPropList");
+ return NULL;
+ }
+ memset (result, 0, sizeof (CRPropList));
+ PRIVATE (result) = g_try_malloc (sizeof (CRPropListPriv));
+ if (!result) {
+ cr_utils_trace_info ("could not allocate CRPropListPriv");
+ g_free (result);
+ return NULL;
+ }
+ memset (PRIVATE (result), 0, sizeof (CRPropListPriv));
+ return result;
+}
+
+/****************
+ *public methods
+ ***************/
+
+/**
+ * cr_prop_list_append:
+ *@a_this: the current instance of #CRPropList
+ *@a_to_append: the property list to append
+ *
+ *Appends a property list to the current one.
+ *
+ *Returns the resulting prop list, or NULL if an error
+ *occurred
+ */
+CRPropList *
+cr_prop_list_append (CRPropList * a_this, CRPropList * a_to_append)
+{
+ CRPropList *cur = NULL;
+
+ g_return_val_if_fail (a_to_append, NULL);
+
+ if (!a_this)
+ return a_to_append;
+
+ /*go fetch the last element of the list */
+ for (cur = a_this;
+ cur && PRIVATE (cur) && PRIVATE (cur)->next;
+ cur = PRIVATE (cur)->next) ;
+ g_return_val_if_fail (cur, NULL);
+ PRIVATE (cur)->next = a_to_append;
+ PRIVATE (a_to_append)->prev = cur;
+ return a_this;
+}
+
+/**
+ * cr_prop_list_append2:
+ *Appends a pair of prop/declaration to
+ *the current prop list.
+ *@a_this: the current instance of #CRPropList
+ *@a_prop: the property to consider
+ *@a_decl: the declaration to consider
+ *
+ *Returns the resulting property list, or NULL in case
+ *of an error.
+ */
+CRPropList *
+cr_prop_list_append2 (CRPropList * a_this,
+ CRString * a_prop,
+ CRDeclaration * a_decl)
+{
+ CRPropList *list = NULL,
+ *result = NULL;
+
+ g_return_val_if_fail (a_prop && a_decl, NULL);
+
+ list = cr_prop_list_allocate ();
+ g_return_val_if_fail (list && PRIVATE (list), NULL);
+
+ PRIVATE (list)->prop = a_prop;
+ PRIVATE (list)->decl = a_decl;
+
+ result = cr_prop_list_append (a_this, list);
+ return result;
+}
+
+/**
+ * cr_prop_list_prepend:
+ *@a_this: the current instance of #CRPropList
+ *@a_to_prepend: the new list to prepend.
+ *
+ *Prepends a list to the current list
+ *Returns the new properties list.
+ */
+CRPropList *
+cr_prop_list_prepend (CRPropList * a_this, CRPropList * a_to_prepend)
+{
+ CRPropList *cur = NULL;
+
+ g_return_val_if_fail (a_to_prepend, NULL);
+
+ if (!a_this)
+ return a_to_prepend;
+
+ for (cur = a_to_prepend; cur && PRIVATE (cur)->next;
+ cur = PRIVATE (cur)->next) ;
+ g_return_val_if_fail (cur, NULL);
+ PRIVATE (cur)->next = a_this;
+ PRIVATE (a_this)->prev = cur;
+ return a_to_prepend;
+}
+
+/**
+ * cr_prop_list_prepend2:
+ *@a_this: the current instance of #CRPropList
+ *@a_prop_name: property name to append
+ *@a_decl: the property value to append.
+ *
+ *Prepends a property to a list of properties
+ *
+ *Returns the new property list.
+ */
+CRPropList *
+cr_prop_list_prepend2 (CRPropList * a_this,
+ CRString * a_prop_name, CRDeclaration * a_decl)
+{
+ CRPropList *list = NULL,
+ *result = NULL;
+
+ g_return_val_if_fail (a_this && PRIVATE (a_this)
+ && a_prop_name && a_decl, NULL);
+
+ list = cr_prop_list_allocate ();
+ g_return_val_if_fail (list, NULL);
+ PRIVATE (list)->prop = a_prop_name;
+ PRIVATE (list)->decl = a_decl;
+ result = cr_prop_list_prepend (a_this, list);
+ return result;
+}
+
+/**
+ * cr_prop_list_set_prop:
+ *@a_this: the current instance of #CRPropList
+ *@a_prop: the property to set
+ *
+ *Sets the property of a CRPropList
+ */
+enum CRStatus
+cr_prop_list_set_prop (CRPropList * a_this, CRString * a_prop)
+{
+ g_return_val_if_fail (a_this && PRIVATE (a_this)
+ && a_prop, CR_BAD_PARAM_ERROR);
+
+ PRIVATE (a_this)->prop = a_prop;
+ return CR_OK;
+}
+
+/**
+ * cr_prop_list_get_prop:
+ *@a_this: the current instance of #CRPropList
+ *@a_prop: out parameter. The returned property
+ *
+ *Getter of the property associated to the current instance
+ *of #CRPropList
+ *
+ *Returns CR_OK upon successful completion, an error code
+ *otherwise.
+ */
+enum CRStatus
+cr_prop_list_get_prop (CRPropList const * a_this, CRString ** a_prop)
+{
+ g_return_val_if_fail (a_this && PRIVATE (a_this)
+ && a_prop, CR_BAD_PARAM_ERROR);
+
+ *a_prop = PRIVATE (a_this)->prop;
+ return CR_OK;
+}
+
+/**
+ * cr_prop_list_set_decl:
+ * @a_this: the current instance of #CRPropList
+ * @a_decl: the new property value.
+ *
+ * Returns CR_OK upon successful completion, an error code otherwise.
+ */
+enum CRStatus
+cr_prop_list_set_decl (CRPropList * a_this, CRDeclaration * a_decl)
+{
+ g_return_val_if_fail (a_this && PRIVATE (a_this)
+ && a_decl, CR_BAD_PARAM_ERROR);
+
+ PRIVATE (a_this)->decl = a_decl;
+ return CR_OK;
+}
+
+/**
+ * cr_prop_list_get_decl:
+ * @a_this: the current instance of #CRPropList
+ * @a_decl: out parameter. The property value
+ *
+ * Returns CR_OK upon successful completion.
+ */
+enum CRStatus
+cr_prop_list_get_decl (CRPropList const * a_this, CRDeclaration ** a_decl)
+{
+ g_return_val_if_fail (a_this && PRIVATE (a_this)
+ && a_decl, CR_BAD_PARAM_ERROR);
+
+ *a_decl = PRIVATE (a_this)->decl;
+ return CR_OK;
+}
+
+/**
+ * cr_prop_list_lookup_prop:
+ *@a_this: the current instance of #CRPropList
+ *@a_prop: the property to lookup
+ *@a_prop_list: out parameter. The property/declaration
+ *pair found (if and only if the function returned code if CR_OK)
+ *
+ *Lookup a given property/declaration pair
+ *
+ *Returns CR_OK if a prop/decl pair has been found,
+ *CR_VALUE_NOT_FOUND_ERROR if not, or an error code if something
+ *bad happens.
+ */
+enum CRStatus
+cr_prop_list_lookup_prop (CRPropList * a_this,
+ CRString * a_prop, CRPropList ** a_pair)
+{
+ CRPropList *cur = NULL;
+
+ g_return_val_if_fail (a_prop && a_pair, CR_BAD_PARAM_ERROR);
+
+ if (!a_this)
+ return CR_VALUE_NOT_FOUND_ERROR;
+
+ g_return_val_if_fail (PRIVATE (a_this), CR_BAD_PARAM_ERROR);
+
+ for (cur = a_this; cur; cur = PRIVATE (cur)->next) {
+ if (PRIVATE (cur)->prop
+ && PRIVATE (cur)->prop->stryng
+ && PRIVATE (cur)->prop->stryng->str
+ && a_prop->stryng
+ && a_prop->stryng->str
+ && !strcmp (PRIVATE (cur)->prop->stryng->str,
+ a_prop->stryng->str))
+ break;
+ }
+
+ if (cur) {
+ *a_pair = cur;
+ return CR_OK;
+ }
+
+ return CR_VALUE_NOT_FOUND_ERROR;
+}
+
+/**
+ * cr_prop_list_get_next:
+ *@a_this: the current instance of CRPropList
+ *
+ *Gets the next prop/decl pair in the list
+ *
+ *Returns the next prop/declaration pair of the list,
+ *or NULL if we reached end of list (or if an error occurs)
+ */
+CRPropList *
+cr_prop_list_get_next (CRPropList * a_this)
+{
+ g_return_val_if_fail (a_this && PRIVATE (a_this), NULL);
+
+ return PRIVATE (a_this)->next;
+}
+
+/**
+ * cr_prop_list_get_prev:
+ *@a_this: the current instance of CRPropList
+ *
+ *Gets the previous prop/decl pair in the list
+ *
+ *Returns the previous prop/declaration pair of the list,
+ *or NULL if we reached end of list (or if an error occurs)
+ */
+CRPropList *
+cr_prop_list_get_prev (CRPropList * a_this)
+{
+ g_return_val_if_fail (a_this && PRIVATE (a_this), NULL);
+
+ return PRIVATE (a_this)->prev;
+}
+
+/**
+ * cr_prop_list_unlink:
+ *@a_this: the current list of prop/decl pairs
+ *@a_pair: the prop/decl pair to unlink.
+ *
+ *Unlinks a prop/decl pair from the list
+ *
+ *Returns the new list or NULL in case of an error.
+ */
+CRPropList *
+cr_prop_list_unlink (CRPropList * a_this, CRPropList * a_pair)
+{
+ CRPropList *prev = NULL,
+ *next = NULL;
+
+ g_return_val_if_fail (a_this && PRIVATE (a_this) && a_pair, NULL);
+
+ /*some sanity checks */
+ if (PRIVATE (a_pair)->next) {
+ next = PRIVATE (a_pair)->next;
+ g_return_val_if_fail (PRIVATE (next), NULL);
+ g_return_val_if_fail (PRIVATE (next)->prev == a_pair, NULL);
+ }
+ if (PRIVATE (a_pair)->prev) {
+ prev = PRIVATE (a_pair)->prev;
+ g_return_val_if_fail (PRIVATE (prev), NULL);
+ g_return_val_if_fail (PRIVATE (prev)->next == a_pair, NULL);
+ }
+ if (prev) {
+ PRIVATE (prev)->next = next;
+ }
+ if (next) {
+ PRIVATE (next)->prev = prev;
+ }
+ PRIVATE (a_pair)->prev = PRIVATE (a_pair)->next = NULL;
+ if (a_this == a_pair) {
+ if (next)
+ return next;
+ return NULL;
+ }
+ return a_this;
+}
+
+/**
+ * cr_prop_list_destroy:
+ * @a_this: the current instance of #CRPropList
+ */
+void
+cr_prop_list_destroy (CRPropList * a_this)
+{
+ CRPropList *tail = NULL,
+ *cur = NULL;
+
+ g_return_if_fail (a_this && PRIVATE (a_this));
+
+ for (tail = a_this;
+ tail && PRIVATE (tail) && PRIVATE (tail)->next;
+ tail = cr_prop_list_get_next (tail)) ;
+ g_return_if_fail (tail);
+
+ cur = tail;
+
+ while (cur) {
+ tail = PRIVATE (cur)->prev;
+ if (tail && PRIVATE (tail))
+ PRIVATE (tail)->next = NULL;
+ PRIVATE (cur)->prev = NULL;
+ g_free (PRIVATE (cur));
+ PRIVATE (cur) = NULL;
+ g_free (cur);
+ cur = tail;
+ }
+}
diff --git a/src/st/croco/cr-prop-list.h b/src/st/croco/cr-prop-list.h
new file mode 100644
index 0000000..797ba43
--- /dev/null
+++ b/src/st/croco/cr-prop-list.h
@@ -0,0 +1,80 @@
+/*
+ * This file is part of The Croco Library
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2.1 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * 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 Lesser General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ *
+ * Author: Dodji Seketeli
+ * See COPYRIGHTS file for copyrights information.
+ */
+
+#ifndef __CR_PROP_LIST_H__
+#define __CR_PROP_LIST_H__
+
+#include "cr-utils.h"
+#include "cr-declaration.h"
+#include "cr-string.h"
+
+G_BEGIN_DECLS
+
+typedef struct _CRPropList CRPropList ;
+typedef struct _CRPropListPriv CRPropListPriv ;
+
+struct _CRPropList
+{
+ CRPropListPriv * priv;
+} ;
+
+CRPropList * cr_prop_list_append (CRPropList *a_this,
+ CRPropList *a_to_append) ;
+
+CRPropList * cr_prop_list_append2 (CRPropList *a_this,
+ CRString *a_prop,
+ CRDeclaration *a_decl) ;
+
+CRPropList * cr_prop_list_prepend (CRPropList *a_this,
+ CRPropList *a_to_append) ;
+
+CRPropList * cr_prop_list_prepend2 (CRPropList *a_this,
+ CRString *a_prop,
+ CRDeclaration *a_decl) ;
+
+enum CRStatus cr_prop_list_set_prop (CRPropList *a_this,
+ CRString *a_prop) ;
+
+enum CRStatus cr_prop_list_get_prop (CRPropList const *a_this,
+ CRString **a_prop) ;
+
+enum CRStatus cr_prop_list_lookup_prop (CRPropList *a_this,
+ CRString *a_prop,
+ CRPropList**a_pair) ;
+
+CRPropList * cr_prop_list_get_next (CRPropList *a_this) ;
+
+CRPropList * cr_prop_list_get_prev (CRPropList *a_this) ;
+
+enum CRStatus cr_prop_list_set_decl (CRPropList *a_this,
+ CRDeclaration *a_decl);
+
+enum CRStatus cr_prop_list_get_decl (CRPropList const *a_this,
+ CRDeclaration **a_decl) ;
+
+CRPropList * cr_prop_list_unlink (CRPropList *a_this,
+ CRPropList *a_pair) ;
+
+void cr_prop_list_destroy (CRPropList *a_this) ;
+
+G_END_DECLS
+
+#endif /*__CR_PROP_LIST_H__*/
diff --git a/src/st/croco/cr-pseudo.c b/src/st/croco/cr-pseudo.c
new file mode 100644
index 0000000..f81f9a6
--- /dev/null
+++ b/src/st/croco/cr-pseudo.c
@@ -0,0 +1,166 @@
+/* -*- Mode: C; indent-tabs-mode:nil; c-basic-offset: 8-*- */
+
+/*
+ * This file is part of The Croco Library
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2.1 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * 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 Lesser General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ *
+ * Author: Dodji Seketeli
+ * See COPYRIGHTS file for copyright information.
+ */
+
+#include "cr-pseudo.h"
+
+/**
+ *@CRPseudo:
+ *The definition of the #CRPseudo class.
+ */
+
+/**
+ * cr_pseudo_new:
+ *Constructor of the #CRPseudo class.
+ *
+ *Returns the newly build instance.
+ */
+CRPseudo *
+cr_pseudo_new (void)
+{
+ CRPseudo *result = NULL;
+
+ result = g_malloc0 (sizeof (CRPseudo));
+
+ return result;
+}
+
+/**
+ * cr_pseudo_to_string:
+ * @a_this: the current instance of #CRPseud.
+ *
+ * Returns the serialized pseudo. Caller must free the returned
+ * string using g_free().
+ */
+guchar *
+cr_pseudo_to_string (CRPseudo const * a_this)
+{
+ guchar *result = NULL;
+ GString *str_buf = NULL;
+
+ g_return_val_if_fail (a_this, NULL);
+
+ str_buf = g_string_new (NULL);
+
+ if (a_this->type == IDENT_PSEUDO) {
+ guchar *name = NULL;
+
+ if (a_this->name == NULL) {
+ goto error;
+ }
+
+ name = (guchar *) g_strndup (a_this->name->stryng->str,
+ a_this->name->stryng->len);
+
+ if (name) {
+ g_string_append (str_buf, (const gchar *) name);
+ g_free (name);
+ name = NULL;
+ }
+ } else if (a_this->type == FUNCTION_PSEUDO) {
+ guchar *name = NULL,
+ *arg = NULL;
+
+ if (a_this->name == NULL)
+ goto error;
+
+ name = (guchar *) g_strndup (a_this->name->stryng->str,
+ a_this->name->stryng->len);
+
+ if (a_this->extra) {
+ arg = (guchar *) g_strndup (a_this->extra->stryng->str,
+ a_this->extra->stryng->len);
+ }
+
+ if (name) {
+ g_string_append_printf (str_buf, "%s(", name);
+ g_free (name);
+ name = NULL;
+
+ if (arg) {
+ g_string_append (str_buf, (const gchar *) arg);
+ g_free (arg);
+ arg = NULL;
+ }
+
+ g_string_append_c (str_buf, ')');
+ }
+ }
+
+ if (str_buf) {
+ result = (guchar *) g_string_free (str_buf, FALSE);
+ str_buf = NULL;
+ }
+
+ return result;
+
+ error:
+ g_string_free (str_buf, TRUE);
+ return NULL;
+}
+
+/**
+ * cr_pseudo_dump:
+ *@a_this: the current instance of pseudo
+ *@a_fp: the destination file pointer.
+ *
+ *Dumps the pseudo to a file.
+ *
+ */
+void
+cr_pseudo_dump (CRPseudo const * a_this, FILE * a_fp)
+{
+ guchar *tmp_str = NULL;
+
+ if (a_this) {
+ tmp_str = cr_pseudo_to_string (a_this);
+ if (tmp_str) {
+ fprintf (a_fp, "%s", tmp_str);
+ g_free (tmp_str);
+ tmp_str = NULL;
+ }
+ }
+}
+
+/**
+ * cr_pseudo_destroy:
+ *@a_this: the current instance to destroy.
+ *
+ *destructor of the #CRPseudo class.
+ */
+void
+cr_pseudo_destroy (CRPseudo * a_this)
+{
+ g_return_if_fail (a_this);
+
+ if (a_this->name) {
+ cr_string_destroy (a_this->name);
+ a_this->name = NULL;
+ }
+
+ if (a_this->extra) {
+ cr_string_destroy (a_this->extra);
+ a_this->extra = NULL;
+ }
+
+ g_free (a_this);
+}
diff --git a/src/st/croco/cr-pseudo.h b/src/st/croco/cr-pseudo.h
new file mode 100644
index 0000000..8917da4
--- /dev/null
+++ b/src/st/croco/cr-pseudo.h
@@ -0,0 +1,64 @@
+/* -*- Mode: C; indent-tabs-mode:nil; c-basic-offset: 8-*- */
+
+/*
+ * This file is part of The Croco Library
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2.1 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * 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 Lesser General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ *
+ * See COPYRIGHTS file for copyright information
+ */
+
+#ifndef __CR_PSEUDO_H__
+#define __CR_PSEUDO_H__
+
+#include <stdio.h>
+#include <glib.h>
+#include "cr-attr-sel.h"
+#include "cr-parsing-location.h"
+
+G_BEGIN_DECLS
+
+enum CRPseudoType
+{
+ IDENT_PSEUDO = 0,
+ FUNCTION_PSEUDO
+} ;
+
+typedef struct _CRPseudo CRPseudo ;
+
+/**
+ *The CRPseudo Class.
+ *Abstract a "pseudo" as defined by the css2 spec
+ *in appendix D.1 .
+ */
+struct _CRPseudo
+{
+ enum CRPseudoType type ;
+ CRString *name ;
+ CRString *extra ;
+ CRParsingLocation location ;
+} ;
+
+CRPseudo * cr_pseudo_new (void) ;
+
+guchar * cr_pseudo_to_string (CRPseudo const *a_this) ;
+
+void cr_pseudo_dump (CRPseudo const *a_this, FILE *a_fp) ;
+
+void cr_pseudo_destroy (CRPseudo *a_this) ;
+
+G_END_DECLS
+
+#endif /*__CR_PSEUDO_H__*/
diff --git a/src/st/croco/cr-rgb.c b/src/st/croco/cr-rgb.c
new file mode 100644
index 0000000..a2b478f
--- /dev/null
+++ b/src/st/croco/cr-rgb.c
@@ -0,0 +1,604 @@
+/* -*- Mode: C; indent-tabs-mode:nil; c-basic-offset: 8-*- */
+
+/*
+ * This file is part of The Croco Library
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2.1 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * 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 Lesser General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ *
+ * Author: Dodji Seketeli
+ * See COPYRIGHTS file for copyrights information.
+ */
+
+#include "config.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include "cr-rgb.h"
+#include "cr-term.h"
+#include "cr-parser.h"
+
+static const CRRgb gv_standard_colors[] = {
+ {(const guchar*)"aliceblue", 240, 248, 255, FALSE, {0,0,0}},
+ {(const guchar*)"antiquewhite", 250, 235, 215, FALSE, {0,0,0}},
+ {(const guchar*)"aqua", 0, 255, 255, FALSE, {0,0,0}},
+ {(const guchar*)"aquamarine", 127, 255, 212, FALSE, {0,0,0}},
+ {(const guchar*)"azure", 240, 255, 255, FALSE, {0,0,0}},
+ {(const guchar*)"beige", 245, 245, 220, FALSE, {0,0,0}},
+ {(const guchar*)"bisque", 255, 228, 196, FALSE, {0,0,0}},
+ {(const guchar*)"black", 0, 0, 0, FALSE, {0,0,0}},
+ {(const guchar*)"blanchedalmond", 255, 235, 205, FALSE, {0,0,0}},
+ {(const guchar*)"blue", 0, 0, 255, FALSE, {0,0,0}},
+ {(const guchar*)"blueviolet", 138, 43, 226, FALSE, {0,0,0}},
+ {(const guchar*)"brown", 165, 42, 42, FALSE, {0,0,0}},
+ {(const guchar*)"burlywood", 222, 184, 135, FALSE, {0,0,0}},
+ {(const guchar*)"cadetblue", 95, 158, 160, FALSE, {0,0,0}},
+ {(const guchar*)"chartreuse", 127, 255, 0, FALSE, {0,0,0}},
+ {(const guchar*)"chocolate", 210, 105, 30, FALSE, {0,0,0}},
+ {(const guchar*)"coral", 255, 127, 80, FALSE, {0,0,0}},
+ {(const guchar*)"cornflowerblue", 100, 149, 237, FALSE, {0,0,0}},
+ {(const guchar*)"cornsilk", 255, 248, 220, FALSE, {0,0,0}},
+ {(const guchar*)"crimson", 220, 20, 60, FALSE, {0,0,0}},
+ {(const guchar*)"cyan", 0, 255, 255, FALSE, {0,0,0}},
+ {(const guchar*)"darkblue", 0, 0, 139, FALSE, {0,0,0}},
+ {(const guchar*)"darkcyan", 0, 139, 139, FALSE, {0,0,0}},
+ {(const guchar*)"darkgoldenrod", 184, 134, 11, FALSE, {0,0,0}},
+ {(const guchar*)"darkgray", 169, 169, 169, FALSE, {0,0,0}},
+ {(const guchar*)"darkgreen", 0, 100, 0, FALSE, {0,0,0}},
+ {(const guchar*)"darkgrey", 169, 169, 169, FALSE, {0,0,0}},
+ {(const guchar*)"darkkhaki", 189, 183, 107, FALSE, {0,0,0}},
+ {(const guchar*)"darkmagenta", 139, 0, 139, FALSE, {0,0,0}},
+ {(const guchar*)"darkolivegreen", 85, 107, 47, FALSE, {0,0,0}},
+ {(const guchar*)"darkorange", 255, 140, 0, FALSE, {0,0,0}},
+ {(const guchar*)"darkorchid", 153, 50, 204, FALSE, {0,0,0}},
+ {(const guchar*)"darkred", 139, 0, 0, FALSE, {0,0,0}},
+ {(const guchar*)"darksalmon", 233, 150, 122, FALSE, {0,0,0}},
+ {(const guchar*)"darkseagreen", 143, 188, 143, FALSE, {0,0,0}},
+ {(const guchar*)"darkslateblue", 72, 61, 139, FALSE, {0,0,0}},
+ {(const guchar*)"darkslategray", 47, 79, 79, FALSE, {0,0,0}},
+ {(const guchar*)"darkslategrey", 47, 79, 79, FALSE, {0,0,0}},
+ {(const guchar*)"darkturquoise", 0, 206, 209, FALSE, {0,0,0}},
+ {(const guchar*)"darkviolet", 148, 0, 211, FALSE, {0,0,0}},
+ {(const guchar*)"deeppink", 255, 20, 147, FALSE, {0,0,0}},
+ {(const guchar*)"deepskyblue", 0, 191, 255, FALSE, {0,0,0}},
+ {(const guchar*)"dimgray", 105, 105, 105, FALSE, {0,0,0}},
+ {(const guchar*)"dimgrey", 105, 105, 105, FALSE, {0,0,0}},
+ {(const guchar*)"dodgerblue", 30, 144, 255, FALSE, {0,0,0}},
+ {(const guchar*)"firebrick", 178, 34, 34, FALSE, {0,0,0}},
+ {(const guchar*)"floralwhite", 255, 250, 240, FALSE, {0,0,0}},
+ {(const guchar*)"forestgreen", 34, 139, 34, FALSE, {0,0,0}},
+ {(const guchar*)"fuchsia", 255, 0, 255, FALSE, {0,0,0}},
+ {(const guchar*)"gainsboro", 220, 220, 220, FALSE, {0,0,0}},
+ {(const guchar*)"ghostwhite", 248, 248, 255, FALSE, {0,0,0}},
+ {(const guchar*)"gold", 255, 215, 0, FALSE, {0,0,0}},
+ {(const guchar*)"goldenrod", 218, 165, 32, FALSE, {0,0,0}},
+ {(const guchar*)"gray", 128, 128, 128, FALSE, {0,0,0}},
+ {(const guchar*)"green", 0, 128, 0, FALSE, {0,0,0}},
+ {(const guchar*)"greenyellow", 173, 255, 47, FALSE, {0,0,0}},
+ {(const guchar*)"grey", 128, 128, 128, FALSE, {0,0,0}},
+ {(const guchar*)"honeydew", 240, 255, 240, FALSE, {0,0,0}},
+ {(const guchar*)"hotpink", 255, 105, 180, FALSE, {0,0,0}},
+ {(const guchar*)"indianred", 205, 92, 92, FALSE, {0,0,0}},
+ {(const guchar*)"indigo", 75, 0, 130, FALSE, {0,0,0}},
+ {(const guchar*)"ivory", 255, 255, 240, FALSE, {0,0,0}},
+ {(const guchar*)"khaki", 240, 230, 140, FALSE, {0,0,0}},
+ {(const guchar*)"lavender", 230, 230, 250, FALSE, {0,0,0}},
+ {(const guchar*)"lavenderblush", 255, 240, 245, FALSE, {0,0,0}},
+ {(const guchar*)"lawngreen", 124, 252, 0, FALSE, {0,0,0}},
+ {(const guchar*)"lemonchiffon", 255, 250, 205, FALSE, {0,0,0}},
+ {(const guchar*)"lightblue", 173, 216, 230, FALSE, {0,0,0}},
+ {(const guchar*)"lightcoral", 240, 128, 128, FALSE, {0,0,0}},
+ {(const guchar*)"lightcyan", 224, 255, 255, FALSE, {0,0,0}},
+ {(const guchar*)"lightgoldenrodyellow", 250, 250, 210, FALSE, {0,0,0}},
+ {(const guchar*)"lightgray", 211, 211, 211, FALSE, {0,0,0}},
+ {(const guchar*)"lightgreen", 144, 238, 144, FALSE, {0,0,0}},
+ {(const guchar*)"lightgrey", 211, 211, 211, FALSE, {0,0,0}},
+ {(const guchar*)"lightpink", 255, 182, 193, FALSE, {0,0,0}},
+ {(const guchar*)"lightsalmon", 255, 160, 122, FALSE, {0,0,0}},
+ {(const guchar*)"lightseagreen", 32, 178, 170, FALSE, {0,0,0}},
+ {(const guchar*)"lightskyblue", 135, 206, 250, FALSE, {0,0,0}},
+ {(const guchar*)"lightslategray", 119, 136, 153, FALSE, {0,0,0}},
+ {(const guchar*)"lightslategrey", 119, 136, 153, FALSE, {0,0,0}},
+ {(const guchar*)"lightsteelblue", 176, 196, 222, FALSE, {0,0,0}},
+ {(const guchar*)"lightyellow", 255, 255, 224, FALSE, {0,0,0}},
+ {(const guchar*)"lime", 0, 255, 0, FALSE, {0,0,0}},
+ {(const guchar*)"limegreen", 50, 205, 50, FALSE, {0,0,0}},
+ {(const guchar*)"linen", 250, 240, 230, FALSE, {0,0,0}},
+ {(const guchar*)"magenta", 255, 0, 255, FALSE, {0,0,0}},
+ {(const guchar*)"maroon", 128, 0, 0, FALSE, {0,0,0}},
+ {(const guchar*)"mediumaquamarine", 102, 205, 170, FALSE, {0,0,0}},
+ {(const guchar*)"mediumblue", 0, 0, 205, FALSE, {0,0,0}},
+ {(const guchar*)"mediumorchid", 186, 85, 211, FALSE, {0,0,0}},
+ {(const guchar*)"mediumpurple", 147, 112, 219, FALSE, {0,0,0}},
+ {(const guchar*)"mediumseagreen", 60, 179, 113, FALSE, {0,0,0}},
+ {(const guchar*)"mediumslateblue", 123, 104, 238, FALSE, {0,0,0}},
+ {(const guchar*)"mediumspringgreen", 0, 250, 154, FALSE, {0,0,0}},
+ {(const guchar*)"mediumturquoise", 72, 209, 204, FALSE, {0,0,0}},
+ {(const guchar*)"mediumvioletred", 199, 21, 133, FALSE, {0,0,0}},
+ {(const guchar*)"midnightblue", 25, 25, 112, FALSE, {0,0,0}},
+ {(const guchar*)"mintcream", 245, 255, 250, FALSE, {0,0,0}},
+ {(const guchar*)"mistyrose", 255, 228, 225, FALSE, {0,0,0}},
+ {(const guchar*)"moccasin", 255, 228, 181, FALSE, {0,0,0}},
+ {(const guchar*)"navajowhite", 255, 222, 173, FALSE, {0,0,0}},
+ {(const guchar*)"navy", 0, 0, 128, FALSE, {0,0,0}},
+ {(const guchar*)"oldlace", 253, 245, 230, FALSE, {0,0,0}},
+ {(const guchar*)"olive", 128, 128, 0, FALSE, {0,0,0}},
+ {(const guchar*)"olivedrab", 107, 142, 35, FALSE, {0,0,0}},
+ {(const guchar*)"orange", 255, 165, 0, FALSE, {0,0,0}},
+ {(const guchar*)"orangered", 255, 69, 0, FALSE, {0,0,0}},
+ {(const guchar*)"orchid", 218, 112, 214, FALSE, {0,0,0}},
+ {(const guchar*)"palegoldenrod", 238, 232, 170, FALSE, {0,0,0}},
+ {(const guchar*)"palegreen", 152, 251, 152, FALSE, {0,0,0}},
+ {(const guchar*)"paleturquoise", 175, 238, 238, FALSE, {0,0,0}},
+ {(const guchar*)"palevioletred", 219, 112, 147, FALSE, {0,0,0}},
+ {(const guchar*)"papayawhip", 255, 239, 213, FALSE, {0,0,0}},
+ {(const guchar*)"peachpuff", 255, 218, 185, FALSE, {0,0,0}},
+ {(const guchar*)"peru", 205, 133, 63, FALSE, {0,0,0}},
+ {(const guchar*)"pink", 255, 192, 203, FALSE, {0,0,0}},
+ {(const guchar*)"plum", 221, 160, 221, FALSE, {0,0,0}},
+ {(const guchar*)"powderblue", 176, 224, 230, FALSE, {0,0,0}},
+ {(const guchar*)"purple", 128, 0, 128, FALSE, {0,0,0}},
+ {(const guchar*)"red", 255, 0, 0, FALSE, {0,0,0}},
+ {(const guchar*)"rosybrown", 188, 143, 143, FALSE, {0,0,0}},
+ {(const guchar*)"royalblue", 65, 105, 225, FALSE, {0,0,0}},
+ {(const guchar*)"saddlebrown", 139, 69, 19, FALSE, {0,0,0}},
+ {(const guchar*)"salmon", 250, 128, 114, FALSE, {0,0,0}},
+ {(const guchar*)"sandybrown", 244, 164, 96, FALSE, {0,0,0}},
+ {(const guchar*)"seagreen", 46, 139, 87, FALSE, {0,0,0}},
+ {(const guchar*)"seashell", 255, 245, 238, FALSE, {0,0,0}},
+ {(const guchar*)"sienna", 160, 82, 45, FALSE, {0,0,0}},
+ {(const guchar*)"silver", 192, 192, 192, FALSE, {0,0,0}},
+ {(const guchar*)"skyblue", 135, 206, 235, FALSE, {0,0,0}},
+ {(const guchar*)"slateblue", 106, 90, 205, FALSE, {0,0,0}},
+ {(const guchar*)"slategray", 112, 128, 144, FALSE, {0,0,0}},
+ {(const guchar*)"slategrey", 112, 128, 144, FALSE, {0,0,0}},
+ {(const guchar*)"snow", 255, 250, 250, FALSE, {0,0,0}},
+ {(const guchar*)"springgreen", 0, 255, 127, FALSE, {0,0,0}},
+ {(const guchar*)"steelblue", 70, 130, 180, FALSE, {0,0,0}},
+ {(const guchar*)"tan", 210, 180, 140, FALSE, {0,0,0}},
+ {(const guchar*)"teal", 0, 128, 128, FALSE, {0,0,0}},
+ {(const guchar*)"thistle", 216, 191, 216, FALSE, {0,0,0}},
+ {(const guchar*)"tomato", 255, 99, 71, FALSE, {0,0,0}},
+ {(const guchar*)"turquoise", 64, 224, 208, FALSE, {0,0,0}},
+ {(const guchar*)"violet", 238, 130, 238, FALSE, {0,0,0}},
+ {(const guchar*)"wheat", 245, 222, 179, FALSE, {0,0,0}},
+ {(const guchar*)"white", 255, 255, 255, FALSE, {0,0,0}},
+ {(const guchar*)"whitesmoke", 245, 245, 245, FALSE, {0,0,0}},
+ {(const guchar*)"yellow", 255, 255, 0, FALSE, {0,0,0}},
+ {(const guchar*)"yellowgreen", 154, 205, 50, FALSE, {0,0,0}}
+};
+
+/**
+ * cr_rgb_new:
+ *
+ *The default constructor of #CRRgb.
+ *
+ *Returns the newly built instance of #CRRgb
+ */
+CRRgb *
+cr_rgb_new (void)
+{
+ CRRgb *result = NULL;
+
+ result = g_try_malloc (sizeof (CRRgb));
+
+ if (result == NULL) {
+ cr_utils_trace_info ("No more memory");
+ return NULL;
+ }
+
+ memset (result, 0, sizeof (CRRgb));
+
+ return result;
+}
+
+/**
+ * cr_rgb_new_with_vals:
+ *@a_red: the red component of the color.
+ *@a_green: the green component of the color.
+ *@a_blue: the blue component of the color.
+ *@a_unit: the unit of the rgb values.
+ *(either percentage or integer values)
+ *
+ *A constructor of #CRRgb.
+ *
+ *Returns the newly built instance of #CRRgb.
+ */
+CRRgb *
+cr_rgb_new_with_vals (gulong a_red, gulong a_green,
+ gulong a_blue, gboolean a_is_percentage)
+{
+ CRRgb *result = NULL;
+
+ result = cr_rgb_new ();
+
+ g_return_val_if_fail (result, NULL);
+
+ result->red = a_red;
+ result->green = a_green;
+ result->blue = a_blue;
+ result->is_percentage = a_is_percentage;
+
+ return result;
+}
+
+/**
+ * cr_rgb_to_string:
+ *@a_this: the instance of #CRRgb to serialize.
+ *
+ *Serializes the rgb into a zero terminated string.
+ *
+ *Returns the zero terminated string containing the serialized
+ *rgb. MUST BE FREED by the caller using g_free().
+ */
+guchar *
+cr_rgb_to_string (CRRgb const * a_this)
+{
+ guchar *result = NULL;
+ GString *str_buf = NULL;
+
+ str_buf = g_string_new (NULL);
+ g_return_val_if_fail (str_buf, NULL);
+
+ if (a_this->is_percentage == 1) {
+ g_string_append_printf (str_buf, "%ld", a_this->red);
+
+ g_string_append (str_buf, "%, ");
+
+ g_string_append_printf (str_buf, "%ld", a_this->green);
+ g_string_append (str_buf, "%, ");
+
+ g_string_append_printf (str_buf, "%ld", a_this->blue);
+ g_string_append_c (str_buf, '%');
+ } else {
+ g_string_append_printf (str_buf, "%ld", a_this->red);
+ g_string_append (str_buf, ", ");
+
+ g_string_append_printf (str_buf, "%ld", a_this->green);
+ g_string_append (str_buf, ", ");
+
+ g_string_append_printf (str_buf, "%ld", a_this->blue);
+ }
+
+ if (str_buf) {
+ result = (guchar *) g_string_free (str_buf, FALSE);
+ }
+
+ return result;
+}
+
+/**
+ * cr_rgb_dump:
+ *@a_this: the "this pointer" of
+ *the current instance of #CRRgb.
+ *@a_fp: the destination file pointer.
+ *
+ *Dumps the current instance of #CRRgb
+ *to a file.
+ */
+void
+cr_rgb_dump (CRRgb const * a_this, FILE * a_fp)
+{
+ guchar *str = NULL;
+
+ g_return_if_fail (a_this);
+
+ str = cr_rgb_to_string (a_this);
+
+ if (str) {
+ fprintf (a_fp, "%s", str);
+ g_free (str);
+ str = NULL;
+ }
+}
+
+/**
+ * cr_rgb_compute_from_percentage:
+ *@a_this: the current instance of #CRRgb
+ *
+ *If the rgb values are expressed in percentage,
+ *compute their real value.
+ *
+ *Returns CR_OK upon successful completion, an error code otherwise.
+ */
+enum CRStatus
+cr_rgb_compute_from_percentage (CRRgb * a_this)
+{
+ g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR);
+
+ if (a_this->is_percentage == FALSE)
+ return CR_OK;
+ a_this->red = a_this->red * 255 / 100;
+ a_this->green = a_this->green * 255 / 100;
+ a_this->blue = a_this->blue * 255 / 100;
+ a_this->is_percentage = FALSE;
+ return CR_OK;
+}
+
+/**
+ * cr_rgb_set:
+ *@a_this: the current instance of #CRRgb.
+ *@a_red: the red value.
+ *@a_green: the green value.
+ *@a_blue: the blue value.
+ *
+ *Sets rgb values to the RGB.
+ *
+ *Returns CR_OK upon successful completion, an error code
+ *otherwise.
+ */
+enum CRStatus
+cr_rgb_set (CRRgb * a_this, gulong a_red,
+ gulong a_green, gulong a_blue, gboolean a_is_percentage)
+{
+ g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR);
+ if (a_is_percentage != FALSE) {
+ g_return_val_if_fail (a_red <= 100
+ && a_green <= 100
+ && a_blue <= 100, CR_BAD_PARAM_ERROR);
+ }
+
+ a_this->is_percentage = a_is_percentage;
+
+ a_this->red = a_red;
+ a_this->green = a_green;
+ a_this->blue = a_blue;
+ return CR_OK;
+}
+
+/**
+ * cr_rgb_set_from_rgb:
+ *@a_this: the current instance of #CRRgb.
+ *@a_rgb: the rgb to "copy"
+ *
+ *Sets the rgb from an other one.
+ *
+ *Returns CR_OK upon successful completion, an error code otherwise.
+ */
+enum CRStatus
+cr_rgb_set_from_rgb (CRRgb * a_this, CRRgb const * a_rgb)
+{
+ g_return_val_if_fail (a_this && a_rgb, CR_BAD_PARAM_ERROR);
+
+ cr_rgb_copy (a_this, a_rgb) ;
+
+ return CR_OK;
+}
+
+static int
+cr_rgb_color_name_compare (const void *a,
+ const void *b)
+{
+ const char *a_color_name = a;
+ const CRRgb *rgb = b;
+
+ return g_ascii_strcasecmp (a_color_name, (const char *) rgb->name);
+}
+
+/**
+ * cr_rgb_set_from_name:
+ * @a_this: the current instance of #CRRgb
+ * @a_color_name: the color name
+ *
+ * Returns CR_OK upon successful completion, an error code otherwise.
+ */
+enum CRStatus
+cr_rgb_set_from_name (CRRgb * a_this, const guchar * a_color_name)
+{
+ enum CRStatus status = CR_OK;
+ CRRgb *result;
+
+ g_return_val_if_fail (a_this && a_color_name, CR_BAD_PARAM_ERROR);
+
+ result = bsearch (a_color_name,
+ gv_standard_colors,
+ G_N_ELEMENTS (gv_standard_colors),
+ sizeof (gv_standard_colors[0]),
+ cr_rgb_color_name_compare);
+ if (result != NULL)
+ cr_rgb_set_from_rgb (a_this, result);
+ else
+ status = CR_UNKNOWN_TYPE_ERROR;
+
+ return status;
+}
+
+/**
+ * cr_rgb_set_from_hex_str:
+ * @a_this: the current instance of #CRRgb
+ * @a_hex: the hexadecimal value to set.
+ *
+ * Returns CR_OK upon successful completion.
+ */
+enum CRStatus
+cr_rgb_set_from_hex_str (CRRgb * a_this, const guchar * a_hex)
+{
+ enum CRStatus status = CR_OK;
+ gulong i = 0;
+ guchar colors[3] = { 0 };
+
+ g_return_val_if_fail (a_this && a_hex, CR_BAD_PARAM_ERROR);
+
+ if (strlen ((const char *) a_hex) == 3) {
+ for (i = 0; i < 3; i++) {
+ if (a_hex[i] >= '0' && a_hex[i] <= '9') {
+ colors[i] = a_hex[i] - '0';
+ colors[i] = (colors[i] << 4) | colors[i];
+ } else if (a_hex[i] >= 'a' && a_hex[i] <= 'z') {
+ colors[i] = 10 + a_hex[i] - 'a';
+ colors[i] = (colors[i] << 4) | colors[i];
+ } else if (a_hex[i] >= 'A' && a_hex[i] <= 'Z') {
+ colors[i] = 10 + a_hex[i] - 'A';
+ colors[i] = (colors[i] << 4) | colors[i];
+ } else {
+ status = CR_UNKNOWN_TYPE_ERROR;
+ }
+ }
+ } else if (strlen ((const char *) a_hex) == 6) {
+ for (i = 0; i < 6; i++) {
+ if (a_hex[i] >= '0' && a_hex[i] <= '9') {
+ colors[i / 2] <<= 4;
+ colors[i / 2] |= a_hex[i] - '0';
+ status = CR_OK;
+ } else if (a_hex[i] >= 'a' && a_hex[i] <= 'z') {
+ colors[i / 2] <<= 4;
+ colors[i / 2] |= 10 + a_hex[i] - 'a';
+ status = CR_OK;
+ } else if (a_hex[i] >= 'A' && a_hex[i] <= 'Z') {
+ colors[i / 2] <<= 4;
+ colors[i / 2] |= 10 + a_hex[i] - 'A';
+ status = CR_OK;
+ } else {
+ status = CR_UNKNOWN_TYPE_ERROR;
+ }
+ }
+ } else {
+ status = CR_UNKNOWN_TYPE_ERROR;
+ }
+
+ if (status == CR_OK) {
+ status = cr_rgb_set (a_this, colors[0],
+ colors[1], colors[2], FALSE);
+ }
+ return status;
+}
+
+/**
+ * cr_rgb_set_from_term:
+ *@a_this: the instance of #CRRgb to set
+ *@a_value: the terminal from which to set
+ *
+ *Set the rgb from a terminal symbol
+ *
+ * Returns CR_OK upon successful completion, an error code otherwise.
+ */
+enum CRStatus
+cr_rgb_set_from_term (CRRgb *a_this, const struct _CRTerm *a_value)
+{
+ enum CRStatus status = CR_OK ;
+ g_return_val_if_fail (a_this && a_value,
+ CR_BAD_PARAM_ERROR) ;
+
+ switch(a_value->type) {
+ case TERM_RGB:
+ if (a_value->content.rgb) {
+ cr_rgb_set_from_rgb
+ (a_this, a_value->content.rgb) ;
+ }
+ break ;
+ case TERM_IDENT:
+ if (a_value->content.str
+ && a_value->content.str->stryng
+ && a_value->content.str->stryng->str) {
+ status = cr_rgb_set_from_name
+ (a_this,
+ (const guchar *) a_value->content.str->stryng->str) ;
+ } else {
+ cr_utils_trace_info
+ ("a_value has NULL string value") ;
+ }
+ break ;
+ case TERM_HASH:
+ if (a_value->content.str
+ && a_value->content.str->stryng
+ && a_value->content.str->stryng->str) {
+ status = cr_rgb_set_from_hex_str
+ (a_this,
+ (const guchar *) a_value->content.str->stryng->str) ;
+ } else {
+ cr_utils_trace_info
+ ("a_value has NULL string value") ;
+ }
+ break ;
+ default:
+ status = CR_UNKNOWN_TYPE_ERROR ;
+ }
+ return status ;
+}
+
+enum CRStatus
+cr_rgb_copy (CRRgb *a_dest, CRRgb const *a_src)
+{
+ g_return_val_if_fail (a_dest && a_src,
+ CR_BAD_PARAM_ERROR) ;
+
+ memcpy (a_dest, a_src, sizeof (CRRgb)) ;
+ return CR_OK ;
+}
+
+/**
+ * cr_rgb_destroy:
+ *@a_this: the "this pointer" of the
+ *current instance of #CRRgb.
+ *
+ *Destructor of #CRRgb.
+ */
+void
+cr_rgb_destroy (CRRgb * a_this)
+{
+ g_return_if_fail (a_this);
+ g_free (a_this);
+}
+
+/**
+ * cr_rgb_parse_from_buf:
+ *@a_str: a string that contains a color description
+ *@a_enc: the encoding of a_str
+ *
+ *Parses a text buffer that contains a rgb color
+ *
+ *Returns the parsed color, or NULL in case of error
+ */
+CRRgb *
+cr_rgb_parse_from_buf (const guchar *a_str,
+ enum CREncoding a_enc)
+{
+ enum CRStatus status = CR_OK ;
+ CRTerm *value = NULL ;
+ CRParser * parser = NULL;
+ CRRgb *result = NULL;
+
+ g_return_val_if_fail (a_str, NULL);
+
+ parser = cr_parser_new_from_buf ((guchar *) a_str, strlen ((const char *) a_str), a_enc, FALSE);
+
+ g_return_val_if_fail (parser, NULL);
+
+ status = cr_parser_try_to_skip_spaces_and_comments (parser) ;
+ if (status != CR_OK)
+ goto cleanup;
+
+ status = cr_parser_parse_term (parser, &value);
+ if (status != CR_OK)
+ goto cleanup;
+
+ result = cr_rgb_new ();
+ if (!result)
+ goto cleanup;
+
+ status = cr_rgb_set_from_term (result, value);
+
+cleanup:
+ if (parser) {
+ cr_parser_destroy (parser);
+ parser = NULL;
+ }
+ if (value) {
+ cr_term_destroy(value);
+ value = NULL;
+ }
+ return result ;
+}
+
diff --git a/src/st/croco/cr-rgb.h b/src/st/croco/cr-rgb.h
new file mode 100644
index 0000000..a77e309
--- /dev/null
+++ b/src/st/croco/cr-rgb.h
@@ -0,0 +1,84 @@
+/* -*- Mode: C; indent-tabs-mode:nil; c-basic-offset: 8-*- */
+
+/*
+ * This file is part of The Croco Library
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2.1 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * 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 Lesser General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ *
+ * see COPYRIGHTS file for copyright information.
+ */
+
+#ifndef __CR_RGB_H__
+#define __CR_RGB_H__
+
+#include <stdio.h>
+#include <glib.h>
+#include "cr-utils.h"
+#include "cr-parsing-location.h"
+
+G_BEGIN_DECLS
+
+
+typedef struct _CRRgb CRRgb ;
+struct _CRRgb
+{
+ /*
+ *the unit of the rgb.
+ *Either NO_UNIT (integer) or
+ *UNIT_PERCENTAGE (percentage).
+ */
+ const guchar *name ;
+ glong red ;
+ glong green ;
+ glong blue ;
+ gboolean is_percentage ;
+ CRParsingLocation location ;
+} ;
+
+CRRgb * cr_rgb_new (void) ;
+
+CRRgb * cr_rgb_new_with_vals (gulong a_red, gulong a_green,
+ gulong a_blue, gboolean a_is_percentage) ;
+
+CRRgb *cr_rgb_parse_from_buf(const guchar *a_str,
+ enum CREncoding a_enc);
+
+enum CRStatus cr_rgb_compute_from_percentage (CRRgb *a_this) ;
+
+enum CRStatus cr_rgb_set (CRRgb *a_this, gulong a_red,
+ gulong a_green, gulong a_blue,
+ gboolean a_is_percentage) ;
+
+enum CRStatus cr_rgb_copy (CRRgb *a_dest, CRRgb const *a_src) ;
+
+enum CRStatus cr_rgb_set_from_rgb (CRRgb *a_this, CRRgb const *a_rgb) ;
+
+enum CRStatus cr_rgb_set_from_name (CRRgb *a_this, const guchar *a_color_name) ;
+
+enum CRStatus cr_rgb_set_from_hex_str (CRRgb *a_this, const guchar * a_hex_value) ;
+
+struct _CRTerm;
+
+enum CRStatus cr_rgb_set_from_term (CRRgb *a_this, const struct _CRTerm *a_value);
+
+guchar * cr_rgb_to_string (CRRgb const *a_this) ;
+
+void cr_rgb_dump (CRRgb const *a_this, FILE *a_fp) ;
+
+void cr_rgb_destroy (CRRgb *a_this) ;
+
+G_END_DECLS
+
+#endif /*__CR_RGB_H__*/
diff --git a/src/st/croco/cr-selector.c b/src/st/croco/cr-selector.c
new file mode 100644
index 0000000..c9aad43
--- /dev/null
+++ b/src/st/croco/cr-selector.c
@@ -0,0 +1,305 @@
+/* -*- Mode: C; indent-tabs-mode: nil; c-basic-offset: 8 -*- */
+
+/*
+ * This file is part of The Croco Library
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2.1 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * 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 Lesser General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ *
+ * See COPYRIGHTS file for copyright information.
+ */
+
+#include <string.h>
+#include "cr-selector.h"
+#include "cr-parser.h"
+
+/**
+ * cr_selector_new:
+ *
+ *@a_simple_sel: the initial simple selector list
+ *of the current instance of #CRSelector.
+ *
+ *Creates a new instance of #CRSelector.
+ *
+ *Returns the newly built instance of #CRSelector, or
+ *NULL in case of failure.
+ */
+CRSelector *
+cr_selector_new (CRSimpleSel * a_simple_sel)
+{
+ CRSelector *result = NULL;
+
+ result = g_try_malloc (sizeof (CRSelector));
+ if (!result) {
+ cr_utils_trace_info ("Out of memory");
+ return NULL;
+ }
+ memset (result, 0, sizeof (CRSelector));
+ result->simple_sel = a_simple_sel;
+ return result;
+}
+
+CRSelector *
+cr_selector_parse_from_buf (const guchar * a_char_buf, enum CREncoding a_enc)
+{
+ CRParser *parser = NULL;
+
+ g_return_val_if_fail (a_char_buf, NULL);
+
+ parser = cr_parser_new_from_buf ((guchar*)a_char_buf, strlen ((const char *) a_char_buf),
+ a_enc, FALSE);
+ g_return_val_if_fail (parser, NULL);
+
+ return NULL;
+}
+
+/**
+ * cr_selector_append:
+ *
+ *@a_this: the current instance of #CRSelector.
+ *@a_new: the instance of #CRSelector to be appended.
+ *
+ *Appends a new instance of #CRSelector to the current selector list.
+ *
+ *Returns the new list.
+ */
+CRSelector *
+cr_selector_append (CRSelector * a_this, CRSelector * a_new)
+{
+ CRSelector *cur = NULL;
+
+ if (!a_this) {
+ return a_new;
+ }
+
+ /*walk forward the list headed by a_this to get the list tail */
+ for (cur = a_this; cur && cur->next; cur = cur->next) ;
+
+ cur->next = a_new;
+ a_new->prev = cur;
+
+ return a_this;
+}
+
+/**
+ * cr_selector_prepend:
+ *
+ *@a_this: the current instance of #CRSelector list.
+ *@a_new: the instance of #CRSelector.
+ *
+ *Prepends an element to the #CRSelector list.
+ *
+ *Returns the new list.
+ */
+CRSelector *
+cr_selector_prepend (CRSelector * a_this, CRSelector * a_new)
+{
+ CRSelector *cur = NULL;
+
+ a_new->next = a_this;
+ a_this->prev = a_new;
+
+ for (cur = a_new; cur && cur->prev; cur = cur->prev) ;
+
+ return cur;
+}
+
+/**
+ * cr_selector_append_simple_sel:
+ *
+ *@a_this: the current instance of #CRSelector.
+ *@a_simple_sel: the simple selector to append.
+ *
+ *append a simple selector to the current #CRSelector list.
+ *
+ *Returns the new list or NULL in case of failure.
+ */
+CRSelector *
+cr_selector_append_simple_sel (CRSelector * a_this,
+ CRSimpleSel * a_simple_sel)
+{
+ CRSelector *selector = NULL;
+
+ selector = cr_selector_new (a_simple_sel);
+ g_return_val_if_fail (selector, NULL);
+
+ return cr_selector_append (a_this, selector);
+}
+
+guchar *
+cr_selector_to_string (CRSelector const * a_this)
+{
+ guchar *result = NULL;
+ GString *str_buf = NULL;
+
+ str_buf = g_string_new (NULL);
+ g_return_val_if_fail (str_buf, NULL);
+
+ if (a_this) {
+ CRSelector const *cur = NULL;
+
+ for (cur = a_this; cur; cur = cur->next) {
+ if (cur->simple_sel) {
+ guchar *tmp_str = NULL;
+
+ tmp_str = cr_simple_sel_to_string
+ (cur->simple_sel);
+
+ if (tmp_str) {
+ if (cur->prev)
+ g_string_append (str_buf,
+ ", ");
+
+ g_string_append (str_buf, (const gchar *) tmp_str);
+
+ g_free (tmp_str);
+ tmp_str = NULL;
+ }
+ }
+ }
+ }
+
+ if (str_buf) {
+ result = (guchar *) g_string_free (str_buf, FALSE);
+ str_buf = NULL;
+ }
+
+ return result;
+}
+
+/**
+ * cr_selector_dump:
+ *
+ *@a_this: the current instance of #CRSelector.
+ *@a_fp: the destination file.
+ *
+ *Serializes the current instance of #CRSelector to a file.
+ */
+void
+cr_selector_dump (CRSelector const * a_this, FILE * a_fp)
+{
+ guchar *tmp_buf = NULL;
+
+ if (a_this) {
+ tmp_buf = cr_selector_to_string (a_this);
+ if (tmp_buf) {
+ fprintf (a_fp, "%s", tmp_buf);
+ g_free (tmp_buf);
+ tmp_buf = NULL;
+ }
+ }
+}
+
+/**
+ * cr_selector_ref:
+ *
+ *@a_this: the current instance of #CRSelector.
+ *
+ *Increments the ref count of the current instance
+ *of #CRSelector.
+ */
+void
+cr_selector_ref (CRSelector * a_this)
+{
+ g_return_if_fail (a_this);
+
+ a_this->ref_count++;
+}
+
+/**
+ * cr_selector_unref:
+ *
+ *@a_this: the current instance of #CRSelector.
+ *
+ *Decrements the ref count of the current instance of
+ *#CRSelector.
+ *If the ref count reaches zero, the current instance of
+ *#CRSelector is destroyed.
+ *
+ *Returns TRUE if this function destroyed the current instance
+ *of #CRSelector, FALSE otherwise.
+ */
+gboolean
+cr_selector_unref (CRSelector * a_this)
+{
+ g_return_val_if_fail (a_this, FALSE);
+
+ if (a_this->ref_count) {
+ a_this->ref_count--;
+ }
+
+ if (a_this->ref_count == 0) {
+ cr_selector_destroy (a_this);
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+/**
+ * cr_selector_destroy:
+ *
+ *@a_this: the current instance of #CRSelector.
+ *
+ *Destroys the selector list.
+ */
+void
+cr_selector_destroy (CRSelector * a_this)
+{
+ CRSelector *cur = NULL;
+
+ g_return_if_fail (a_this);
+
+ /*
+ *go and get the list tail. In the same time, free
+ *all the simple selectors contained in the list.
+ */
+ for (cur = a_this; cur && cur->next; cur = cur->next) {
+ if (cur->simple_sel) {
+ cr_simple_sel_destroy (cur->simple_sel);
+ cur->simple_sel = NULL;
+ }
+ }
+
+ if (cur) {
+ if (cur->simple_sel) {
+ cr_simple_sel_destroy (cur->simple_sel);
+ cur->simple_sel = NULL;
+ }
+ }
+
+ /*in case the list has only one element */
+ if (cur && !cur->prev) {
+ g_free (cur);
+ return;
+ }
+
+ /*walk backward the list and free each "next element" */
+ for (cur = cur->prev; cur && cur->prev; cur = cur->prev) {
+ if (cur->next) {
+ g_free (cur->next);
+ cur->next = NULL;
+ }
+ }
+
+ if (!cur)
+ return;
+
+ if (cur->next) {
+ g_free (cur->next);
+ cur->next = NULL;
+ }
+
+ g_free (cur);
+}
diff --git a/src/st/croco/cr-selector.h b/src/st/croco/cr-selector.h
new file mode 100644
index 0000000..dd6a7f7
--- /dev/null
+++ b/src/st/croco/cr-selector.h
@@ -0,0 +1,95 @@
+/* -*- Mode: C; indent-tabs-mode: nil; c-basic-offset: 8 -*- */
+
+/*
+ * This file is part of The Croco Library
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2.1 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * 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 Lesser General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ *
+ *
+ * Author: Dodji Seketeli
+ * See COPYRIGHTS file for copyright information.
+ */
+
+#ifndef __CR_SELECTOR_H__
+#define __CR_SELECTOR_H__
+
+#include <stdio.h>
+#include "cr-utils.h"
+#include "cr-simple-sel.h"
+#include "cr-parsing-location.h"
+
+/**
+ *@file
+ *The declaration file of the #CRSelector file.
+ */
+
+G_BEGIN_DECLS
+
+typedef struct _CRSelector CRSelector ;
+
+/**
+ *Abstracts a CSS2 selector as defined in the right part
+ *of the 'ruleset" production in the appendix D.1 of the
+ *css2 spec.
+ *It is actually the abstraction of a comma separated list
+ *of simple selectors list.
+ *In a css2 file, a selector is a list of simple selectors
+ *separated by a comma.
+ *e.g: sel0, sel1, sel2 ...
+ *Each seln is a simple selector
+ */
+struct _CRSelector
+{
+ /**
+ *A Selection expression.
+ *It is a list of basic selectors.
+ *Each basic selector can be either an element
+ *selector, an id selector, a class selector, an
+ *attribute selector, an universal selector etc ...
+ */
+ CRSimpleSel *simple_sel ;
+
+ /**The next selector list element*/
+ CRSelector *next ;
+ CRSelector *prev ;
+ CRParsingLocation location ;
+ glong ref_count ;
+};
+
+CRSelector* cr_selector_new (CRSimpleSel *a_sel_expr) ;
+
+CRSelector * cr_selector_parse_from_buf (const guchar * a_char_buf,
+ enum CREncoding a_enc) ;
+
+CRSelector* cr_selector_append (CRSelector *a_this, CRSelector *a_new) ;
+
+CRSelector* cr_selector_append_simple_sel (CRSelector *a_this,
+ CRSimpleSel *a_simple_sel) ;
+
+CRSelector* cr_selector_prepend (CRSelector *a_this, CRSelector *a_new) ;
+
+guchar * cr_selector_to_string (CRSelector const *a_this) ;
+
+void cr_selector_dump (CRSelector const *a_this, FILE *a_fp) ;
+
+void cr_selector_ref (CRSelector *a_this) ;
+
+gboolean cr_selector_unref (CRSelector *a_this) ;
+
+void cr_selector_destroy (CRSelector *a_this) ;
+
+G_END_DECLS
+
+#endif /*__CR_SELECTOR_H__*/
diff --git a/src/st/croco/cr-simple-sel.c b/src/st/croco/cr-simple-sel.c
new file mode 100644
index 0000000..bac8621
--- /dev/null
+++ b/src/st/croco/cr-simple-sel.c
@@ -0,0 +1,323 @@
+/* -*- Mode: C; indent-tabs-mode:nil; c-basic-offset: 8-*- */
+
+/*
+ * This file is part of The Croco Library
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2.1 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * 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 Lesser General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ *
+ * Author: Dodji Seketeli
+ * See COPYRIGHTS file for copyright information.
+ */
+
+#include <string.h>
+#include <glib.h>
+#include "cr-simple-sel.h"
+
+/**
+ * cr_simple_sel_new:
+ *
+ *The constructor of #CRSimpleSel.
+ *
+ *Returns the new instance of #CRSimpleSel.
+ */
+CRSimpleSel *
+cr_simple_sel_new (void)
+{
+ CRSimpleSel *result = NULL;
+
+ result = g_try_malloc (sizeof (CRSimpleSel));
+ if (!result) {
+ cr_utils_trace_info ("Out of memory");
+ return NULL;
+ }
+ memset (result, 0, sizeof (CRSimpleSel));
+
+ return result;
+}
+
+/**
+ * cr_simple_sel_append_simple_sel:
+ *
+ *Appends a simpe selector to the current list of simple selector.
+ *
+ *@a_this: the this pointer of the current instance of #CRSimpleSel.
+ *@a_sel: the simple selector to append.
+ *
+ *Returns: the new list upon successful completion, an error code otherwise.
+ */
+CRSimpleSel *
+cr_simple_sel_append_simple_sel (CRSimpleSel * a_this, CRSimpleSel * a_sel)
+{
+ CRSimpleSel *cur = NULL;
+
+ g_return_val_if_fail (a_sel, NULL);
+
+ if (a_this == NULL)
+ return a_sel;
+
+ for (cur = a_this; cur->next; cur = cur->next) ;
+
+ cur->next = a_sel;
+ a_sel->prev = cur;
+
+ return a_this;
+}
+
+/**
+ * cr_simple_sel_prepend_simple_sel:
+ *
+ *@a_this: the this pointer of the current instance of #CRSimpleSel.
+ *@a_sel: the simple selector to prepend.
+ *
+ *Prepends a simple selector to the current list of simple selectors.
+ *
+ *Returns the new list upon successful completion, an error code otherwise.
+ */
+CRSimpleSel *
+cr_simple_sel_prepend_simple_sel (CRSimpleSel * a_this, CRSimpleSel * a_sel)
+{
+ g_return_val_if_fail (a_sel, NULL);
+
+ if (a_this == NULL)
+ return a_sel;
+
+ a_sel->next = a_this;
+ a_this->prev = a_sel;
+
+ return a_sel;
+}
+
+guchar *
+cr_simple_sel_to_string (CRSimpleSel const * a_this)
+{
+ GString *str_buf = NULL;
+ guchar *result = NULL;
+
+ CRSimpleSel const *cur = NULL;
+
+ g_return_val_if_fail (a_this, NULL);
+
+ str_buf = g_string_new (NULL);
+ for (cur = a_this; cur; cur = cur->next) {
+ if (cur->name) {
+ guchar *str = (guchar *) g_strndup (cur->name->stryng->str,
+ cur->name->stryng->len);
+
+ if (str) {
+ switch (cur->combinator) {
+ case COMB_WS:
+ g_string_append (str_buf, " ");
+ break;
+
+ case COMB_PLUS:
+ g_string_append (str_buf, "+");
+ break;
+
+ case COMB_GT:
+ g_string_append (str_buf, ">");
+ break;
+
+ default:
+ break;
+ }
+
+ g_string_append (str_buf, (const gchar *) str);
+ g_free (str);
+ str = NULL;
+ }
+ }
+
+ if (cur->add_sel) {
+ guchar *tmp_str = NULL;
+
+ tmp_str = cr_additional_sel_to_string (cur->add_sel);
+ if (tmp_str) {
+ g_string_append (str_buf, (const gchar *) tmp_str);
+ g_free (tmp_str);
+ tmp_str = NULL;
+ }
+ }
+ }
+
+ if (str_buf) {
+ result = (guchar *) g_string_free (str_buf, FALSE);
+ str_buf = NULL;
+ }
+
+ return result;
+}
+
+
+guchar *
+cr_simple_sel_one_to_string (CRSimpleSel const * a_this)
+{
+ GString *str_buf = NULL;
+ guchar *result = NULL;
+
+ g_return_val_if_fail (a_this, NULL);
+
+ str_buf = g_string_new (NULL);
+ if (a_this->name) {
+ guchar *str = (guchar *) g_strndup (a_this->name->stryng->str,
+ a_this->name->stryng->len);
+
+ if (str) {
+ g_string_append_printf (str_buf, "%s", str);
+ g_free (str);
+ str = NULL;
+ }
+ }
+
+ if (a_this->add_sel) {
+ guchar *tmp_str = NULL;
+
+ tmp_str = cr_additional_sel_to_string (a_this->add_sel);
+ if (tmp_str) {
+ g_string_append_printf
+ (str_buf, "%s", tmp_str);
+ g_free (tmp_str);
+ tmp_str = NULL;
+ }
+ }
+
+ if (str_buf) {
+ result = (guchar *) g_string_free (str_buf, FALSE);
+ str_buf = NULL;
+ }
+
+ return result;
+}
+
+/**
+ * cr_simple_sel_dump:
+ *@a_this: the current instance of #CRSimpleSel.
+ *@a_fp: the destination file pointer.
+ *
+ *Dumps the selector to a file.
+ *TODO: add the support of unicode in the dump.
+ *
+ *Returns CR_OK upon successful completion, an error code
+ *otherwise.
+ */
+enum CRStatus
+cr_simple_sel_dump (CRSimpleSel const * a_this, FILE * a_fp)
+{
+ guchar *tmp_str = NULL;
+
+ g_return_val_if_fail (a_fp, CR_BAD_PARAM_ERROR);
+
+ if (a_this) {
+ tmp_str = cr_simple_sel_to_string (a_this);
+ if (tmp_str) {
+ fprintf (a_fp, "%s", tmp_str);
+ g_free (tmp_str);
+ tmp_str = NULL;
+ }
+ }
+
+ return CR_OK;
+}
+
+/**
+ * cr_simple_sel_compute_specificity:
+ *
+ *@a_this: the current instance of #CRSimpleSel
+ *
+ *Computes the selector (combinator separated list of simple selectors)
+ *as defined in the css2 spec in chapter 6.4.3
+ *
+ *Returns CR_OK upon successful completion, an error code otherwise.
+ */
+enum CRStatus
+cr_simple_sel_compute_specificity (CRSimpleSel * a_this)
+{
+ CRAdditionalSel const *cur_add_sel = NULL;
+ CRSimpleSel const *cur_sel = NULL;
+ gulong a = 0,
+ b = 0,
+ c = 0;
+
+ g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR);
+
+ for (cur_sel = a_this; cur_sel; cur_sel = cur_sel->next) {
+ if (cur_sel->type_mask & TYPE_SELECTOR) {
+ c++; /*hmmh, is this a new language ? */
+ } else if (!cur_sel->name
+ || !cur_sel->name->stryng
+ || !cur_sel->name->stryng->str) {
+ if (cur_sel->add_sel->type ==
+ PSEUDO_CLASS_ADD_SELECTOR) {
+ /*
+ *this is a pseudo element, and
+ *the spec says, "ignore pseudo elements".
+ */
+ continue;
+ }
+ }
+
+ for (cur_add_sel = cur_sel->add_sel;
+ cur_add_sel; cur_add_sel = cur_add_sel->next) {
+ switch (cur_add_sel->type) {
+ case ID_ADD_SELECTOR:
+ a++;
+ break;
+
+ case NO_ADD_SELECTOR:
+ continue;
+
+ default:
+ b++;
+ break;
+ }
+ }
+ }
+
+ /*we suppose a, b and c have 1 to 3 digits */
+ a_this->specificity = a * 1000000 + b * 1000 + c;
+
+ return CR_OK;
+}
+
+/**
+ * cr_simple_sel_destroy:
+ *
+ *@a_this: the this pointer of the current instance of #CRSimpleSel.
+ *
+ *The destructor of the current instance of
+ *#CRSimpleSel.
+ */
+void
+cr_simple_sel_destroy (CRSimpleSel * a_this)
+{
+ g_return_if_fail (a_this);
+
+ if (a_this->name) {
+ cr_string_destroy (a_this->name);
+ a_this->name = NULL;
+ }
+
+ if (a_this->add_sel) {
+ cr_additional_sel_destroy (a_this->add_sel);
+ a_this->add_sel = NULL;
+ }
+
+ if (a_this->next) {
+ cr_simple_sel_destroy (a_this->next);
+ }
+
+ if (a_this) {
+ g_free (a_this);
+ }
+}
diff --git a/src/st/croco/cr-simple-sel.h b/src/st/croco/cr-simple-sel.h
new file mode 100644
index 0000000..72b15fd
--- /dev/null
+++ b/src/st/croco/cr-simple-sel.h
@@ -0,0 +1,130 @@
+/* -*- Mode: C; indent-tabs-mode:nil; c-basic-offset: 8-*- */
+
+/*
+ * This file is part of The Croco Library
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2.1 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * 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 Lesser General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ *
+ * Author: Dodji Seketeli
+ * See COPYRIGHTS file for copyright information.
+ */
+
+
+#ifndef __CR_SEL_H__
+#define __CR_SEL_H__
+
+#include <stdio.h>
+#include <glib.h>
+#include "cr-additional-sel.h"
+#include "cr-parsing-location.h"
+
+G_BEGIN_DECLS
+
+/**
+ *@file
+ *the declaration of the #CRSimpleSel class.
+ *
+ */
+enum Combinator
+{
+ NO_COMBINATOR,
+ COMB_WS,/*whitespace: descendent*/
+ COMB_PLUS,/*'+': preceded by*/
+ COMB_GT/*greater than ('>'): child*/
+} ;
+
+enum SimpleSelectorType
+{
+ NO_SELECTOR_TYPE = 0,
+ UNIVERSAL_SELECTOR = 1,
+ TYPE_SELECTOR = 1 << 1
+} ;
+
+typedef struct _CRSimpleSel CRSimpleSel ;
+
+/**
+ *The abstraction of a css2 simple selection list
+ *as defined by the right part of the "selector" production in the
+ *appendix D.1 of the css2 spec.
+ *It is basically a list of simple selector, each
+ *simple selector being separated by a combinator.
+ *
+ *In the libcroco's implementation, each simple selector
+ *is made of at most two parts:
+ *
+ *1/An element name or 'type selector' (which can hold a '*' and
+ *then been called 'universal selector')
+ *
+ *2/An additional selector that "specializes" the preceding type or
+ *universal selector. The additional selector can be either
+ *an id selector, or a class selector, or an attribute selector.
+ */
+struct _CRSimpleSel
+{
+ enum SimpleSelectorType type_mask ;
+ gboolean is_case_sentive ;
+ CRString * name ;
+ /**
+ *The combinator that separates
+ *this simple selector from the previous
+ *one.
+ */
+ enum Combinator combinator ;
+
+ /**
+ *The additional selector list of the
+ *current simple selector.
+ *An additional selector may
+ *be a class selector, an id selector,
+ *or an attribute selector.
+ *Note that this field is a linked list.
+ */
+ CRAdditionalSel *add_sel ;
+
+ /*
+ *the specificity as specified by
+ *chapter 6.4.3 of the spec.
+ */
+ gulong specificity ;
+
+ CRSimpleSel *next ;
+ CRSimpleSel *prev ;
+ CRParsingLocation location ;
+} ;
+
+CRSimpleSel * cr_simple_sel_new (void) ;
+
+CRSimpleSel * cr_simple_sel_append_simple_sel (CRSimpleSel *a_this,
+ CRSimpleSel *a_sel) ;
+
+CRSimpleSel * cr_simple_sel_prepend_simple_sel (CRSimpleSel *a_this,
+ CRSimpleSel *a_sel) ;
+
+guchar * cr_simple_sel_to_string (CRSimpleSel const *a_this) ;
+
+guchar * cr_simple_sel_one_to_string (CRSimpleSel const * a_this) ;
+
+enum CRStatus cr_simple_sel_dump (CRSimpleSel const *a_this, FILE *a_fp) ;
+
+enum CRStatus cr_simple_sel_dump_attr_sel_list (CRSimpleSel const *a_this) ;
+
+enum CRStatus cr_simple_sel_compute_specificity (CRSimpleSel *a_this) ;
+
+void cr_simple_sel_destroy (CRSimpleSel *a_this) ;
+
+G_END_DECLS
+
+
+#endif /*__CR_SIMPLE_SEL_H__*/
diff --git a/src/st/croco/cr-statement.c b/src/st/croco/cr-statement.c
new file mode 100644
index 0000000..eaeb49f
--- /dev/null
+++ b/src/st/croco/cr-statement.c
@@ -0,0 +1,2784 @@
+/* -*- Mode: C; indent-tabs-mode: nil; c-basic-offset: 8 -*- */
+
+/*
+ * This file is part of The Croco Library
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2.1 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * 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 Lesser General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ *
+ * Author: Dodji Seketeli.
+ * See COPYRIGHTS files for copyrights information.
+ */
+
+#include <string.h>
+#include "cr-statement.h"
+#include "cr-parser.h"
+
+/**
+ *@file
+ *Definition of the #CRStatement class.
+ */
+
+#define DECLARATION_INDENT_NB 2
+
+static void cr_statement_clear (CRStatement * a_this);
+
+static void
+parse_font_face_start_font_face_cb (CRDocHandler * a_this,
+ CRParsingLocation *a_location)
+{
+ CRStatement *stmt = NULL;
+ enum CRStatus status = CR_OK;
+
+ stmt = cr_statement_new_at_font_face_rule (NULL, NULL);
+ g_return_if_fail (stmt);
+
+ status = cr_doc_handler_set_ctxt (a_this, stmt);
+ g_return_if_fail (status == CR_OK);
+}
+
+static void
+parse_font_face_unrecoverable_error_cb (CRDocHandler * a_this)
+{
+ CRStatement *stmt = NULL;
+ CRStatement **stmtptr = NULL;
+ enum CRStatus status = CR_OK;
+
+ g_return_if_fail (a_this);
+
+ stmtptr = &stmt;
+ status = cr_doc_handler_get_ctxt (a_this, (gpointer *) stmtptr);
+ if (status != CR_OK) {
+ cr_utils_trace_info ("Couldn't get parsing context. "
+ "This may lead to some memory leaks.");
+ return;
+ }
+ if (stmt) {
+ cr_statement_destroy (stmt);
+ cr_doc_handler_set_ctxt (a_this, NULL);
+ return;
+ }
+}
+
+static void
+parse_font_face_property_cb (CRDocHandler * a_this,
+ CRString * a_name,
+ CRTerm * a_value, gboolean a_important)
+{
+ enum CRStatus status = CR_OK;
+ CRString *name = NULL;
+ CRDeclaration *decl = NULL;
+ CRStatement *stmt = NULL;
+ CRStatement **stmtptr = NULL;
+
+ g_return_if_fail (a_this && a_name);
+
+ stmtptr = &stmt;
+ status = cr_doc_handler_get_ctxt (a_this, (gpointer *) stmtptr);
+ g_return_if_fail (status == CR_OK && stmt);
+ g_return_if_fail (stmt->type == AT_FONT_FACE_RULE_STMT);
+
+ name = cr_string_dup (a_name) ;
+ g_return_if_fail (name);
+ decl = cr_declaration_new (stmt, name, a_value);
+ if (!decl) {
+ cr_utils_trace_info ("cr_declaration_new () failed.");
+ goto error;
+ }
+ name = NULL;
+
+ stmt->kind.font_face_rule->decl_list =
+ cr_declaration_append (stmt->kind.font_face_rule->decl_list,
+ decl);
+ if (!stmt->kind.font_face_rule->decl_list)
+ goto error;
+ decl = NULL;
+
+ error:
+ if (decl) {
+ cr_declaration_unref (decl);
+ decl = NULL;
+ }
+ if (name) {
+ cr_string_destroy (name);
+ name = NULL;
+ }
+}
+
+static void
+parse_font_face_end_font_face_cb (CRDocHandler * a_this)
+{
+ CRStatement *result = NULL;
+ CRStatement **resultptr = NULL;
+ enum CRStatus status = CR_OK;
+
+ g_return_if_fail (a_this);
+
+ resultptr = &result;
+ status = cr_doc_handler_get_ctxt (a_this, (gpointer *) resultptr);
+ g_return_if_fail (status == CR_OK && result);
+ g_return_if_fail (result->type == AT_FONT_FACE_RULE_STMT);
+
+ status = cr_doc_handler_set_result (a_this, result);
+ g_return_if_fail (status == CR_OK);
+}
+
+static void
+parse_page_start_page_cb (CRDocHandler * a_this,
+ CRString * a_name,
+ CRString * a_pseudo_page,
+ CRParsingLocation *a_location)
+{
+ CRStatement *stmt = NULL;
+ enum CRStatus status = CR_OK;
+ CRString *page_name = NULL, *pseudo_name = NULL ;
+
+ if (a_name)
+ page_name = cr_string_dup (a_name) ;
+ if (a_pseudo_page)
+ pseudo_name = cr_string_dup (a_pseudo_page) ;
+
+ stmt = cr_statement_new_at_page_rule (NULL, NULL,
+ page_name,
+ pseudo_name);
+ page_name = NULL ;
+ pseudo_name = NULL ;
+ g_return_if_fail (stmt);
+ status = cr_doc_handler_set_ctxt (a_this, stmt);
+ g_return_if_fail (status == CR_OK);
+}
+
+static void
+parse_page_unrecoverable_error_cb (CRDocHandler * a_this)
+{
+ CRStatement *stmt = NULL;
+ CRStatement **stmtptr = NULL;
+ enum CRStatus status = CR_OK;
+
+ g_return_if_fail (a_this);
+
+ stmtptr = &stmt;
+ status = cr_doc_handler_get_ctxt (a_this, (gpointer *) stmtptr);
+ if (status != CR_OK) {
+ cr_utils_trace_info ("Couldn't get parsing context. "
+ "This may lead to some memory leaks.");
+ return;
+ }
+ if (stmt) {
+ cr_statement_destroy (stmt);
+ stmt = NULL;
+ cr_doc_handler_set_ctxt (a_this, NULL);
+ }
+}
+
+static void
+parse_page_property_cb (CRDocHandler * a_this,
+ CRString * a_name,
+ CRTerm * a_expression, gboolean a_important)
+{
+ CRString *name = NULL;
+ CRStatement *stmt = NULL;
+ CRStatement **stmtptr = NULL;
+ CRDeclaration *decl = NULL;
+ enum CRStatus status = CR_OK;
+
+ stmtptr = &stmt;
+ status = cr_doc_handler_get_ctxt (a_this, (gpointer *) stmtptr);
+ g_return_if_fail (status == CR_OK && stmt->type == AT_PAGE_RULE_STMT);
+
+ name = cr_string_dup (a_name);
+ g_return_if_fail (name);
+
+ decl = cr_declaration_new (stmt, name, a_expression);
+ g_return_if_fail (decl);
+ decl->important = a_important;
+ stmt->kind.page_rule->decl_list =
+ cr_declaration_append (stmt->kind.page_rule->decl_list, decl);
+ g_return_if_fail (stmt->kind.page_rule->decl_list);
+}
+
+static void
+parse_page_end_page_cb (CRDocHandler * a_this,
+ CRString * a_name,
+ CRString * a_pseudo_page)
+{
+ enum CRStatus status = CR_OK;
+ CRStatement *stmt = NULL;
+ CRStatement **stmtptr = NULL;
+
+ stmtptr = &stmt;
+ status = cr_doc_handler_get_ctxt (a_this, (gpointer *) stmtptr);
+ g_return_if_fail (status == CR_OK && stmt);
+ g_return_if_fail (stmt->type == AT_PAGE_RULE_STMT);
+
+ status = cr_doc_handler_set_result (a_this, stmt);
+ g_return_if_fail (status == CR_OK);
+}
+
+static void
+parse_at_media_start_media_cb (CRDocHandler * a_this,
+ GList * a_media_list,
+ CRParsingLocation *a_location)
+{
+ enum CRStatus status = CR_OK;
+ CRStatement *at_media = NULL;
+ GList *media_list = NULL;
+
+ g_return_if_fail (a_this && a_this->priv);
+
+ if (a_media_list) {
+ /*duplicate media list */
+ media_list = cr_utils_dup_glist_of_cr_string
+ (a_media_list);
+ }
+
+ g_return_if_fail (media_list);
+
+ /*make sure cr_statement_new_at_media_rule works in this case. */
+ at_media = cr_statement_new_at_media_rule (NULL, NULL, media_list);
+
+ status = cr_doc_handler_set_ctxt (a_this, at_media);
+ g_return_if_fail (status == CR_OK);
+ status = cr_doc_handler_set_result (a_this, at_media);
+ g_return_if_fail (status == CR_OK);
+}
+
+static void
+parse_at_media_unrecoverable_error_cb (CRDocHandler * a_this)
+{
+ enum CRStatus status = CR_OK;
+ CRStatement *stmt = NULL;
+ CRStatement **stmtptr = NULL;
+
+ g_return_if_fail (a_this);
+
+ stmtptr = &stmt;
+ status = cr_doc_handler_get_result (a_this, (gpointer *) stmtptr);
+ if (status != CR_OK) {
+ cr_utils_trace_info ("Couldn't get parsing context. "
+ "This may lead to some memory leaks.");
+ return;
+ }
+ if (stmt) {
+ cr_statement_destroy (stmt);
+ stmt = NULL;
+ cr_doc_handler_set_ctxt (a_this, NULL);
+ cr_doc_handler_set_result (a_this, NULL);
+ }
+}
+
+static void
+parse_at_media_start_selector_cb (CRDocHandler * a_this,
+ CRSelector * a_sellist)
+{
+ enum CRStatus status = CR_OK;
+ CRStatement *at_media = NULL;
+ CRStatement **at_media_ptr = NULL;
+ CRStatement *ruleset = NULL;
+
+ g_return_if_fail (a_this && a_this->priv && a_sellist);
+
+ at_media_ptr = &at_media;
+ status = cr_doc_handler_get_ctxt (a_this, (gpointer *) at_media_ptr);
+ g_return_if_fail (status == CR_OK && at_media);
+ g_return_if_fail (at_media->type == AT_MEDIA_RULE_STMT);
+ ruleset = cr_statement_new_ruleset (NULL, a_sellist, NULL, at_media);
+ g_return_if_fail (ruleset);
+ status = cr_doc_handler_set_ctxt (a_this, ruleset);
+ g_return_if_fail (status == CR_OK);
+}
+
+static void
+parse_at_media_property_cb (CRDocHandler * a_this,
+ CRString * a_name, CRTerm * a_value,
+ gboolean a_important)
+{
+ enum CRStatus status = CR_OK;
+
+ /*
+ *the current ruleset stmt, child of the
+ *current at-media being parsed.
+ */
+ CRStatement *stmt = NULL;
+ CRStatement **stmtptr = NULL;
+ CRDeclaration *decl = NULL;
+ CRString *name = NULL;
+
+ g_return_if_fail (a_this && a_name);
+
+ name = cr_string_dup (a_name) ;
+ g_return_if_fail (name);
+
+ stmtptr = &stmt;
+ status = cr_doc_handler_get_ctxt (a_this,
+ (gpointer *) stmtptr);
+ g_return_if_fail (status == CR_OK && stmt);
+ g_return_if_fail (stmt->type == RULESET_STMT);
+
+ decl = cr_declaration_new (stmt, name, a_value);
+ g_return_if_fail (decl);
+ decl->important = a_important;
+ status = cr_statement_ruleset_append_decl (stmt, decl);
+ g_return_if_fail (status == CR_OK);
+}
+
+static void
+parse_at_media_end_selector_cb (CRDocHandler * a_this,
+ CRSelector * a_sellist)
+{
+ enum CRStatus status = CR_OK;
+
+ /*
+ *the current ruleset stmt, child of the
+ *current at-media being parsed.
+ */
+ CRStatement *stmt = NULL;
+ CRStatement **stmtptr = NULL;
+
+ g_return_if_fail (a_this && a_sellist);
+
+ stmtptr = &stmt;
+ status = cr_doc_handler_get_ctxt (a_this, (gpointer *) stmtptr);
+ g_return_if_fail (status == CR_OK && stmt
+ && stmt->type == RULESET_STMT);
+ g_return_if_fail (stmt->kind.ruleset->parent_media_rule);
+
+ status = cr_doc_handler_set_ctxt
+ (a_this, stmt->kind.ruleset->parent_media_rule);
+ g_return_if_fail (status == CR_OK);
+}
+
+static void
+parse_at_media_end_media_cb (CRDocHandler * a_this,
+ GList * a_media_list)
+{
+ enum CRStatus status = CR_OK;
+ CRStatement *at_media = NULL;
+ CRStatement **at_media_ptr = NULL;
+
+ g_return_if_fail (a_this && a_this->priv);
+
+ at_media_ptr = &at_media;
+ status = cr_doc_handler_get_ctxt (a_this,
+ (gpointer *) at_media_ptr);
+ g_return_if_fail (status == CR_OK && at_media);
+ status = cr_doc_handler_set_result (a_this, at_media);
+}
+
+static void
+parse_ruleset_start_selector_cb (CRDocHandler * a_this,
+ CRSelector * a_sellist)
+{
+ CRStatement *ruleset = NULL;
+
+ g_return_if_fail (a_this && a_this->priv && a_sellist);
+
+ ruleset = cr_statement_new_ruleset (NULL, a_sellist, NULL, NULL);
+ g_return_if_fail (ruleset);
+
+ cr_doc_handler_set_result (a_this, ruleset);
+}
+
+static void
+parse_ruleset_unrecoverable_error_cb (CRDocHandler * a_this)
+{
+ CRStatement *stmt = NULL;
+ CRStatement **stmtptr = NULL;
+ enum CRStatus status = CR_OK;
+
+ stmtptr = &stmt;
+ status = cr_doc_handler_get_result (a_this, (gpointer *) stmtptr);
+ if (status != CR_OK) {
+ cr_utils_trace_info ("Couldn't get parsing context. "
+ "This may lead to some memory leaks.");
+ return;
+ }
+ if (stmt) {
+ cr_statement_destroy (stmt);
+ stmt = NULL;
+ cr_doc_handler_set_result (a_this, NULL);
+ }
+}
+
+static void
+parse_ruleset_property_cb (CRDocHandler * a_this,
+ CRString * a_name,
+ CRTerm * a_value, gboolean a_important)
+{
+ enum CRStatus status = CR_OK;
+ CRStatement *ruleset = NULL;
+ CRStatement **rulesetptr = NULL;
+ CRDeclaration *decl = NULL;
+ CRString *stringue = NULL;
+
+ g_return_if_fail (a_this && a_this->priv && a_name);
+
+ stringue = cr_string_dup (a_name);
+ g_return_if_fail (stringue);
+
+ rulesetptr = &ruleset;
+ status = cr_doc_handler_get_result (a_this, (gpointer *) rulesetptr);
+ g_return_if_fail (status == CR_OK
+ && ruleset
+ && ruleset->type == RULESET_STMT);
+
+ decl = cr_declaration_new (ruleset, stringue, a_value);
+ g_return_if_fail (decl);
+ decl->important = a_important;
+ status = cr_statement_ruleset_append_decl (ruleset, decl);
+ g_return_if_fail (status == CR_OK);
+}
+
+static void
+parse_ruleset_end_selector_cb (CRDocHandler * a_this,
+ CRSelector * a_sellist)
+{
+ CRStatement *result = NULL;
+ CRStatement **resultptr = NULL;
+ enum CRStatus status = CR_OK;
+
+ g_return_if_fail (a_this && a_sellist);
+
+ resultptr = &result;
+ status = cr_doc_handler_get_result (a_this, (gpointer *) resultptr);
+
+ g_return_if_fail (status == CR_OK
+ && result
+ && result->type == RULESET_STMT);
+}
+
+static void
+cr_statement_clear (CRStatement * a_this)
+{
+ g_return_if_fail (a_this);
+
+ switch (a_this->type) {
+ case AT_RULE_STMT:
+ break;
+ case RULESET_STMT:
+ if (!a_this->kind.ruleset)
+ return;
+ if (a_this->kind.ruleset->sel_list) {
+ cr_selector_unref (a_this->kind.ruleset->sel_list);
+ a_this->kind.ruleset->sel_list = NULL;
+ }
+ if (a_this->kind.ruleset->decl_list) {
+ cr_declaration_destroy
+ (a_this->kind.ruleset->decl_list);
+ a_this->kind.ruleset->decl_list = NULL;
+ }
+ g_free (a_this->kind.ruleset);
+ a_this->kind.ruleset = NULL;
+ break;
+
+ case AT_IMPORT_RULE_STMT:
+ if (!a_this->kind.import_rule)
+ return;
+ if (a_this->kind.import_rule->url) {
+ cr_string_destroy
+ (a_this->kind.import_rule->url) ;
+ a_this->kind.import_rule->url = NULL;
+ }
+ g_free (a_this->kind.import_rule);
+ a_this->kind.import_rule = NULL;
+ break;
+
+ case AT_MEDIA_RULE_STMT:
+ if (!a_this->kind.media_rule)
+ return;
+ if (a_this->kind.media_rule->rulesets) {
+ cr_statement_destroy
+ (a_this->kind.media_rule->rulesets);
+ a_this->kind.media_rule->rulesets = NULL;
+ }
+ if (a_this->kind.media_rule->media_list) {
+ GList *cur = NULL;
+
+ for (cur = a_this->kind.media_rule->media_list;
+ cur; cur = cur->next) {
+ if (cur->data) {
+ cr_string_destroy ((CRString *) cur->data);
+ cur->data = NULL;
+ }
+
+ }
+ g_list_free (a_this->kind.media_rule->media_list);
+ a_this->kind.media_rule->media_list = NULL;
+ }
+ g_free (a_this->kind.media_rule);
+ a_this->kind.media_rule = NULL;
+ break;
+
+ case AT_PAGE_RULE_STMT:
+ if (!a_this->kind.page_rule)
+ return;
+
+ if (a_this->kind.page_rule->decl_list) {
+ cr_declaration_destroy
+ (a_this->kind.page_rule->decl_list);
+ a_this->kind.page_rule->decl_list = NULL;
+ }
+ if (a_this->kind.page_rule->name) {
+ cr_string_destroy
+ (a_this->kind.page_rule->name);
+ a_this->kind.page_rule->name = NULL;
+ }
+ if (a_this->kind.page_rule->pseudo) {
+ cr_string_destroy
+ (a_this->kind.page_rule->pseudo);
+ a_this->kind.page_rule->pseudo = NULL;
+ }
+ g_free (a_this->kind.page_rule);
+ a_this->kind.page_rule = NULL;
+ break;
+
+ case AT_CHARSET_RULE_STMT:
+ if (!a_this->kind.charset_rule)
+ return;
+
+ if (a_this->kind.charset_rule->charset) {
+ cr_string_destroy
+ (a_this->kind.charset_rule->charset);
+ a_this->kind.charset_rule->charset = NULL;
+ }
+ g_free (a_this->kind.charset_rule);
+ a_this->kind.charset_rule = NULL;
+ break;
+
+ case AT_FONT_FACE_RULE_STMT:
+ if (!a_this->kind.font_face_rule)
+ return;
+
+ if (a_this->kind.font_face_rule->decl_list) {
+ cr_declaration_unref
+ (a_this->kind.font_face_rule->decl_list);
+ a_this->kind.font_face_rule->decl_list = NULL;
+ }
+ g_free (a_this->kind.font_face_rule);
+ a_this->kind.font_face_rule = NULL;
+ break;
+
+ default:
+ break;
+ }
+}
+
+/**
+ * cr_statement_ruleset_to_string:
+ *
+ *@a_this: the current instance of #CRStatement
+ *@a_indent: the number of whitespace to use for indentation
+ *
+ *Serializes the ruleset statement into a string
+ *
+ *Returns the newly allocated serialised string. Must be freed
+ *by the caller, using g_free().
+ */
+static gchar *
+cr_statement_ruleset_to_string (CRStatement const * a_this, glong a_indent)
+{
+ GString *stringue = NULL;
+ gchar *tmp_str = NULL,
+ *result = NULL;
+
+ g_return_val_if_fail (a_this && a_this->type == RULESET_STMT, NULL);
+
+ stringue = g_string_new (NULL);
+
+ if (a_this->kind.ruleset->sel_list) {
+ if (a_indent)
+ cr_utils_dump_n_chars2 (' ', stringue, a_indent);
+
+ tmp_str =
+ (gchar *) cr_selector_to_string (a_this->kind.ruleset->
+ sel_list);
+ if (tmp_str) {
+ g_string_append (stringue, tmp_str);
+ g_free (tmp_str);
+ tmp_str = NULL;
+ }
+ }
+ g_string_append (stringue, " {\n");
+ if (a_this->kind.ruleset->decl_list) {
+ tmp_str = (gchar *) cr_declaration_list_to_string2
+ (a_this->kind.ruleset->decl_list,
+ a_indent + DECLARATION_INDENT_NB, TRUE);
+ if (tmp_str) {
+ g_string_append (stringue, tmp_str);
+ g_free (tmp_str);
+ tmp_str = NULL;
+ }
+ g_string_append (stringue, "\n");
+ cr_utils_dump_n_chars2 (' ', stringue, a_indent);
+ }
+ g_string_append (stringue, "}");
+ result = g_string_free (stringue, FALSE);
+
+ if (tmp_str) {
+ g_free (tmp_str);
+ tmp_str = NULL;
+ }
+ return result;
+}
+
+
+/**
+ * cr_statement_font_face_rule_to_string:
+ *
+ *@a_this: the current instance of #CRStatement to consider
+ *It must be a font face rule statement.
+ *@a_indent: the number of white spaces of indentation.
+ *
+ *Serializes a font face rule statement into a string.
+ *
+ *Returns the serialized string. Must be deallocated by the caller
+ *using g_free().
+ */
+static gchar *
+cr_statement_font_face_rule_to_string (CRStatement const * a_this,
+ glong a_indent)
+{
+ gchar *result = NULL, *tmp_str = NULL ;
+ GString *stringue = NULL ;
+
+ g_return_val_if_fail (a_this
+ && a_this->type == AT_FONT_FACE_RULE_STMT,
+ NULL);
+
+ if (a_this->kind.font_face_rule->decl_list) {
+ stringue = g_string_new (NULL) ;
+ g_return_val_if_fail (stringue, NULL) ;
+ if (a_indent)
+ cr_utils_dump_n_chars2 (' ', stringue,
+ a_indent);
+ g_string_append (stringue, "@font-face {\n");
+ tmp_str = (gchar *) cr_declaration_list_to_string2
+ (a_this->kind.font_face_rule->decl_list,
+ a_indent + DECLARATION_INDENT_NB, TRUE) ;
+ if (tmp_str) {
+ g_string_append (stringue,
+ tmp_str) ;
+ g_free (tmp_str) ;
+ tmp_str = NULL ;
+ }
+ g_string_append (stringue, "\n}");
+ }
+ if (stringue) {
+ result = g_string_free (stringue, FALSE);
+ stringue = NULL ;
+ }
+ return result ;
+}
+
+
+/**
+ * cr_statement_charset_to_string:
+ *
+ *Serialises an \@charset statement into a string.
+ *@a_this: the statement to serialize.
+ *@a_indent: the number of indentation spaces
+ *
+ *Returns the serialized charset statement. Must be
+ *freed by the caller using g_free().
+ */
+static gchar *
+cr_statement_charset_to_string (CRStatement const *a_this,
+ gulong a_indent)
+{
+ gchar *str = NULL ;
+ GString *stringue = NULL ;
+
+ g_return_val_if_fail (a_this
+ && a_this->type == AT_CHARSET_RULE_STMT,
+ NULL) ;
+
+ if (a_this->kind.charset_rule
+ && a_this->kind.charset_rule->charset
+ && a_this->kind.charset_rule->charset->stryng
+ && a_this->kind.charset_rule->charset->stryng->str) {
+ str = g_strndup (a_this->kind.charset_rule->charset->stryng->str,
+ a_this->kind.charset_rule->charset->stryng->len);
+ g_return_val_if_fail (str, NULL);
+ stringue = g_string_new (NULL) ;
+ g_return_val_if_fail (stringue, NULL) ;
+ cr_utils_dump_n_chars2 (' ', stringue, a_indent);
+ g_string_append_printf (stringue,
+ "@charset \"%s\" ;", str);
+ if (str) {
+ g_free (str);
+ str = NULL;
+ }
+ }
+ if (stringue) {
+ str = g_string_free (stringue, FALSE);
+ }
+ return str ;
+}
+
+
+/**
+ * cr_statement_at_page_rule_to_string:
+ *
+ *Serialises the at page rule statement into a string
+ *@a_this: the current instance of #CRStatement. Must
+ *be an "\@page" rule statement.
+ *
+ *Returns the serialized string. Must be freed by the caller
+ */
+static gchar *
+cr_statement_at_page_rule_to_string (CRStatement const *a_this,
+ gulong a_indent)
+{
+ GString *stringue = NULL;
+ gchar *result = NULL ;
+
+ stringue = g_string_new (NULL) ;
+
+ cr_utils_dump_n_chars2 (' ', stringue, a_indent) ;
+ g_string_append (stringue, "@page");
+ if (a_this->kind.page_rule->name
+ && a_this->kind.page_rule->name->stryng) {
+ g_string_append_printf
+ (stringue, " %s",
+ a_this->kind.page_rule->name->stryng->str) ;
+ } else {
+ g_string_append (stringue, " ");
+ }
+ if (a_this->kind.page_rule->pseudo
+ && a_this->kind.page_rule->pseudo->stryng) {
+ g_string_append_printf
+ (stringue, " :%s",
+ a_this->kind.page_rule->pseudo->stryng->str) ;
+ }
+ if (a_this->kind.page_rule->decl_list) {
+ gchar *str = NULL ;
+ g_string_append (stringue, " {\n");
+ str = (gchar *) cr_declaration_list_to_string2
+ (a_this->kind.page_rule->decl_list,
+ a_indent + DECLARATION_INDENT_NB, TRUE) ;
+ if (str) {
+ g_string_append (stringue, str) ;
+ g_free (str) ;
+ str = NULL ;
+ }
+ g_string_append (stringue, "\n}\n");
+ }
+ result = g_string_free (stringue, FALSE) ;
+ stringue = NULL ;
+ return result ;
+}
+
+
+/**
+ *Serializes an \@media statement.
+ *@param a_this the current instance of #CRStatement
+ *@param a_indent the number of spaces of indentation.
+ *@return the serialized \@media statement. Must be freed
+ *by the caller using g_free().
+ */
+static gchar *
+cr_statement_media_rule_to_string (CRStatement const *a_this,
+ gulong a_indent)
+{
+ gchar *str = NULL ;
+ GString *stringue = NULL ;
+ GList const *cur = NULL;
+
+ g_return_val_if_fail (a_this->type == AT_MEDIA_RULE_STMT,
+ NULL);
+
+ if (a_this->kind.media_rule) {
+ stringue = g_string_new (NULL) ;
+ cr_utils_dump_n_chars2 (' ', stringue, a_indent);
+ g_string_append (stringue, "@media");
+
+ for (cur = a_this->kind.media_rule->media_list; cur;
+ cur = cur->next) {
+ if (cur->data) {
+ gchar *str2 = cr_string_dup2
+ ((CRString const *) cur->data);
+
+ if (str2) {
+ if (cur->prev) {
+ g_string_append
+ (stringue,
+ ",");
+ }
+ g_string_append_printf
+ (stringue,
+ " %s", str2);
+ g_free (str2);
+ str2 = NULL;
+ }
+ }
+ }
+ g_string_append (stringue, " {\n");
+ str = cr_statement_list_to_string
+ (a_this->kind.media_rule->rulesets,
+ a_indent + DECLARATION_INDENT_NB) ;
+ if (str) {
+ g_string_append (stringue, str) ;
+ g_free (str) ;
+ str = NULL ;
+ }
+ g_string_append (stringue, "\n}");
+ }
+ if (stringue) {
+ str = g_string_free (stringue, FALSE) ;
+ }
+ return str ;
+}
+
+
+static gchar *
+cr_statement_import_rule_to_string (CRStatement const *a_this,
+ gulong a_indent)
+{
+ GString *stringue = NULL ;
+ gchar *str = NULL;
+
+ g_return_val_if_fail (a_this
+ && a_this->type == AT_IMPORT_RULE_STMT
+ && a_this->kind.import_rule,
+ NULL) ;
+
+ if (a_this->kind.import_rule->url
+ && a_this->kind.import_rule->url->stryng) {
+ stringue = g_string_new (NULL) ;
+ g_return_val_if_fail (stringue, NULL) ;
+ str = g_strndup (a_this->kind.import_rule->url->stryng->str,
+ a_this->kind.import_rule->url->stryng->len);
+ cr_utils_dump_n_chars2 (' ', stringue, a_indent);
+ if (str) {
+ g_string_append_printf (stringue,
+ "@import url(\"%s\")",
+ str);
+ g_free (str);
+ str = NULL ;
+ } else /*there is no url, so no import rule, get out! */
+ return NULL;
+
+ if (a_this->kind.import_rule->media_list) {
+ GList const *cur = NULL;
+
+ for (cur = a_this->kind.import_rule->media_list;
+ cur; cur = cur->next) {
+ if (cur->data) {
+ CRString const *crstr = cur->data;
+
+ if (cur->prev) {
+ g_string_append
+ (stringue, ", ");
+ }
+ if (crstr
+ && crstr->stryng
+ && crstr->stryng->str) {
+ g_string_append_len
+ (stringue,
+ crstr->stryng->str,
+ crstr->stryng->len) ;
+ }
+ }
+ }
+ }
+ g_string_append (stringue, " ;");
+ }
+ if (stringue) {
+ str = g_string_free (stringue, FALSE) ;
+ stringue = NULL ;
+ }
+ return str ;
+}
+
+
+/*******************
+ *public functions
+ ******************/
+
+/**
+ * cr_statement_does_buf_parses_against_core:
+ *
+ *@a_buf: the buffer to parse.
+ *@a_encoding: the character encoding of a_buf.
+ *
+ *Tries to parse a buffer and says whether if the content of the buffer
+ *is a css statement as defined by the "Core CSS Grammar" (chapter 4 of the
+ *css spec) or not.
+ *
+ *Returns TRUE if the buffer parses against the core grammar, false otherwise.
+ */
+gboolean
+cr_statement_does_buf_parses_against_core (const guchar * a_buf,
+ enum CREncoding a_encoding)
+{
+ CRParser *parser = NULL;
+ enum CRStatus status = CR_OK;
+ gboolean result = FALSE;
+
+ parser = cr_parser_new_from_buf ((guchar*)a_buf, strlen ((const char *) a_buf),
+ a_encoding, FALSE);
+ g_return_val_if_fail (parser, FALSE);
+
+ status = cr_parser_set_use_core_grammar (parser, TRUE);
+ if (status != CR_OK) {
+ goto cleanup;
+ }
+
+ status = cr_parser_parse_statement_core (parser);
+ if (status == CR_OK) {
+ result = TRUE;
+ }
+
+ cleanup:
+ if (parser) {
+ cr_parser_destroy (parser);
+ }
+
+ return result;
+}
+
+/**
+ * cr_statement_parse_from_buf:
+ *
+ *@a_buf: the buffer to parse.
+ *@a_encoding: the character encoding of a_buf.
+ *
+ *Parses a buffer that contains a css statement and returns
+ *an instance of #CRStatement in case of successful parsing.
+ *TODO: at support of "\@import" rules.
+ *
+ *Returns the newly built instance of #CRStatement in case
+ *of successful parsing, NULL otherwise.
+ */
+CRStatement *
+cr_statement_parse_from_buf (const guchar * a_buf, enum CREncoding a_encoding)
+{
+ CRStatement *result = NULL;
+
+ /*
+ *The strategy of this function is "brute force".
+ *It tries to parse all the types of CRStatement it knows about.
+ *I could do this a smarter way but I don't have the time now.
+ *I think I will revisit this when time of performances and
+ *pull based incremental parsing comes.
+ */
+
+ result = cr_statement_ruleset_parse_from_buf (a_buf, a_encoding);
+ if (!result) {
+ result = cr_statement_at_charset_rule_parse_from_buf
+ (a_buf, a_encoding);
+ } else {
+ goto out;
+ }
+
+ if (!result) {
+ result = cr_statement_at_media_rule_parse_from_buf
+ (a_buf, a_encoding);
+ } else {
+ goto out;
+ }
+
+ if (!result) {
+ result = cr_statement_at_charset_rule_parse_from_buf
+ (a_buf, a_encoding);
+ } else {
+ goto out;
+ }
+
+ if (!result) {
+ result = cr_statement_font_face_rule_parse_from_buf
+ (a_buf, a_encoding);
+
+ } else {
+ goto out;
+ }
+
+ if (!result) {
+ result = cr_statement_at_page_rule_parse_from_buf
+ (a_buf, a_encoding);
+ } else {
+ goto out;
+ }
+
+ if (!result) {
+ result = cr_statement_at_import_rule_parse_from_buf
+ (a_buf, a_encoding);
+ } else {
+ goto out;
+ }
+
+ out:
+ return result;
+}
+
+/**
+ * cr_statement_ruleset_parse_from_buf:
+ *
+ *@a_buf: the buffer to parse.
+ *@a_enc: the character encoding of a_buf.
+ *
+ *Parses a buffer that contains a ruleset statement an instantiates
+ *a #CRStatement of type RULESET_STMT.
+ *
+ *Returns the newly built instance of #CRStatement in case of successful parsing,
+ *NULL otherwise.
+ */
+CRStatement *
+cr_statement_ruleset_parse_from_buf (const guchar * a_buf,
+ enum CREncoding a_enc)
+{
+ enum CRStatus status = CR_OK;
+ CRStatement *result = NULL;
+ CRStatement **resultptr = NULL;
+ CRParser *parser = NULL;
+ CRDocHandler *sac_handler = NULL;
+
+ g_return_val_if_fail (a_buf, NULL);
+
+ parser = cr_parser_new_from_buf ((guchar*)a_buf, strlen ((const char *) a_buf),
+ a_enc, FALSE);
+
+ g_return_val_if_fail (parser, NULL);
+
+ sac_handler = cr_doc_handler_new ();
+ g_return_val_if_fail (parser, NULL);
+
+ sac_handler->start_selector = parse_ruleset_start_selector_cb;
+ sac_handler->end_selector = parse_ruleset_end_selector_cb;
+ sac_handler->property = parse_ruleset_property_cb;
+ sac_handler->unrecoverable_error =
+ parse_ruleset_unrecoverable_error_cb;
+
+ cr_parser_set_sac_handler (parser, sac_handler);
+ cr_parser_try_to_skip_spaces_and_comments (parser);
+ status = cr_parser_parse_ruleset (parser);
+ if (status != CR_OK) {
+ goto cleanup;
+ }
+
+ resultptr = &result;
+ status = cr_doc_handler_get_result (sac_handler,
+ (gpointer *) resultptr);
+ if (!((status == CR_OK) && result)) {
+ if (result) {
+ cr_statement_destroy (result);
+ result = NULL;
+ }
+ }
+
+ cleanup:
+ if (parser) {
+ cr_parser_destroy (parser);
+ parser = NULL;
+ sac_handler = NULL ;
+ }
+ if (sac_handler) {
+ cr_doc_handler_unref (sac_handler);
+ sac_handler = NULL;
+ }
+ return result;
+}
+
+/**
+ * cr_statement_new_ruleset:
+ *
+ *@a_sel_list: the list of #CRSimpleSel (selectors)
+ *the rule applies to.
+ *@a_decl_list: the list of instances of #CRDeclaration
+ *that composes the ruleset.
+ *@a_media_types: a list of instances of GString that
+ *describe the media list this ruleset applies to.
+ *
+ *Creates a new instance of #CRStatement of type
+ *#CRRulSet.
+ *
+ *Returns the new instance of #CRStatement or NULL if something
+ *went wrong.
+ */
+CRStatement *
+cr_statement_new_ruleset (CRStyleSheet * a_sheet,
+ CRSelector * a_sel_list,
+ CRDeclaration * a_decl_list,
+ CRStatement * a_parent_media_rule)
+{
+ CRStatement *result = NULL;
+
+ g_return_val_if_fail (a_sel_list, NULL);
+
+ if (a_parent_media_rule) {
+ g_return_val_if_fail
+ (a_parent_media_rule->type == AT_MEDIA_RULE_STMT,
+ NULL);
+ g_return_val_if_fail (a_parent_media_rule->kind.media_rule,
+ NULL);
+ }
+
+ result = g_try_malloc (sizeof (CRStatement));
+
+ if (!result) {
+ cr_utils_trace_info ("Out of memory");
+ return NULL;
+ }
+
+ memset (result, 0, sizeof (CRStatement));
+ result->type = RULESET_STMT;
+ result->kind.ruleset = g_try_malloc (sizeof (CRRuleSet));
+
+ if (!result->kind.ruleset) {
+ cr_utils_trace_info ("Out of memory");
+ if (result)
+ g_free (result);
+ return NULL;
+ }
+
+ memset (result->kind.ruleset, 0, sizeof (CRRuleSet));
+ result->kind.ruleset->sel_list = a_sel_list;
+ if (a_sel_list)
+ cr_selector_ref (a_sel_list);
+ result->kind.ruleset->decl_list = a_decl_list;
+
+ if (a_parent_media_rule) {
+ result->kind.ruleset->parent_media_rule = a_parent_media_rule;
+ a_parent_media_rule->kind.media_rule->rulesets =
+ cr_statement_append
+ (a_parent_media_rule->kind.media_rule->rulesets,
+ result);
+ }
+
+ cr_statement_set_parent_sheet (result, a_sheet);
+
+ return result;
+}
+
+/**
+ * cr_statement_at_media_rule_parse_from_buf:
+ *
+ *@a_buf: the input to parse.
+ *@a_enc: the encoding of the buffer.
+ *
+ *Parses a buffer that contains an "\@media" declaration
+ *and builds an \@media css statement.
+ *
+ *Returns the \@media statement, or NULL if the buffer could not
+ *be successfully parsed.
+ */
+CRStatement *
+cr_statement_at_media_rule_parse_from_buf (const guchar * a_buf,
+ enum CREncoding a_enc)
+{
+ CRParser *parser = NULL;
+ CRStatement *result = NULL;
+ CRStatement **resultptr = NULL;
+ CRDocHandler *sac_handler = NULL;
+ enum CRStatus status = CR_OK;
+
+ parser = cr_parser_new_from_buf ((guchar*)a_buf, strlen ((const char *) a_buf),
+ a_enc, FALSE);
+ if (!parser) {
+ cr_utils_trace_info ("Instantiation of the parser failed");
+ goto cleanup;
+ }
+
+ sac_handler = cr_doc_handler_new ();
+ if (!sac_handler) {
+ cr_utils_trace_info
+ ("Instantiation of the sac handler failed");
+ goto cleanup;
+ }
+
+ sac_handler->start_media = parse_at_media_start_media_cb;
+ sac_handler->start_selector = parse_at_media_start_selector_cb;
+ sac_handler->property = parse_at_media_property_cb;
+ sac_handler->end_selector = parse_at_media_end_selector_cb;
+ sac_handler->end_media = parse_at_media_end_media_cb;
+ sac_handler->unrecoverable_error =
+ parse_at_media_unrecoverable_error_cb;
+
+ status = cr_parser_set_sac_handler (parser, sac_handler);
+ if (status != CR_OK)
+ goto cleanup;
+
+ status = cr_parser_try_to_skip_spaces_and_comments (parser);
+ if (status != CR_OK)
+ goto cleanup;
+
+ status = cr_parser_parse_media (parser);
+ if (status != CR_OK)
+ goto cleanup;
+
+ resultptr = &result;
+ status = cr_doc_handler_get_result (sac_handler,
+ (gpointer *) resultptr);
+ if (status != CR_OK)
+ goto cleanup;
+
+ cleanup:
+
+ if (parser) {
+ cr_parser_destroy (parser);
+ parser = NULL;
+ sac_handler = NULL ;
+ }
+ if (sac_handler) {
+ cr_doc_handler_unref (sac_handler);
+ sac_handler = NULL;
+ }
+
+ return result;
+}
+
+/**
+ * cr_statement_new_at_media_rule:
+ *
+ *@a_ruleset: the ruleset statements contained
+ *in the \@media rule.
+ *@a_media: the media string list. A list of GString pointers.
+ *
+ *Instantiates an instance of #CRStatement of type
+ *AT_MEDIA_RULE_STMT (\@media ruleset).
+ *
+ */
+CRStatement *
+cr_statement_new_at_media_rule (CRStyleSheet * a_sheet,
+ CRStatement * a_rulesets, GList * a_media)
+{
+ CRStatement *result = NULL,
+ *cur = NULL;
+
+ if (a_rulesets)
+ g_return_val_if_fail (a_rulesets->type == RULESET_STMT, NULL);
+
+ result = g_try_malloc (sizeof (CRStatement));
+
+ if (!result) {
+ cr_utils_trace_info ("Out of memory");
+ return NULL;
+ }
+
+ memset (result, 0, sizeof (CRStatement));
+ result->type = AT_MEDIA_RULE_STMT;
+
+ result->kind.media_rule = g_try_malloc (sizeof (CRAtMediaRule));
+ if (!result->kind.media_rule) {
+ cr_utils_trace_info ("Out of memory");
+ g_free (result);
+ return NULL;
+ }
+ memset (result->kind.media_rule, 0, sizeof (CRAtMediaRule));
+ result->kind.media_rule->rulesets = a_rulesets;
+ for (cur = a_rulesets; cur; cur = cur->next) {
+ if (cur->type != RULESET_STMT || !cur->kind.ruleset) {
+ cr_utils_trace_info ("Bad parameter a_rulesets. "
+ "It should be a list of "
+ "correct ruleset statement only !");
+ goto error;
+ }
+ cur->kind.ruleset->parent_media_rule = result;
+ }
+
+ result->kind.media_rule->media_list = a_media;
+ if (a_sheet) {
+ cr_statement_set_parent_sheet (result, a_sheet);
+ }
+
+ return result;
+
+ error:
+ return NULL;
+}
+
+/**
+ * cr_statement_new_at_import_rule:
+ *
+ *@a_url: the url to connect to the get the file
+ *to be imported.
+ *@a_sheet: the imported parsed stylesheet.
+ *
+ *Creates a new instance of #CRStatment of type
+ *#CRAtImportRule.
+ *
+ *Returns the newly built instance of #CRStatement.
+ */
+CRStatement *
+cr_statement_new_at_import_rule (CRStyleSheet * a_container_sheet,
+ CRString * a_url,
+ GList * a_media_list,
+ CRStyleSheet * a_imported_sheet)
+{
+ CRStatement *result = NULL;
+
+ result = g_try_malloc (sizeof (CRStatement));
+
+ if (!result) {
+ cr_utils_trace_info ("Out of memory");
+ return NULL;
+ }
+
+ memset (result, 0, sizeof (CRStatement));
+ result->type = AT_IMPORT_RULE_STMT;
+
+ result->kind.import_rule = g_try_malloc (sizeof (CRAtImportRule));
+
+ if (!result->kind.import_rule) {
+ cr_utils_trace_info ("Out of memory");
+ g_free (result);
+ return NULL;
+ }
+
+ memset (result->kind.import_rule, 0, sizeof (CRAtImportRule));
+ result->kind.import_rule->url = a_url;
+ result->kind.import_rule->media_list = a_media_list;
+ result->kind.import_rule->sheet = a_imported_sheet;
+ if (a_container_sheet)
+ cr_statement_set_parent_sheet (result, a_container_sheet);
+
+ return result;
+}
+
+/**
+ * cr_statement_at_import_rule_parse_from_buf:
+ *
+ *@a_buf: the buffer to parse.
+ *@a_encoding: the encoding of a_buf.
+ *
+ *Parses a buffer that contains an "\@import" rule and
+ *instantiate a #CRStatement of type AT_IMPORT_RULE_STMT
+ *
+ *Returns the newly built instance of #CRStatement in case of
+ *a successful parsing, NULL otherwise.
+ */
+CRStatement *
+cr_statement_at_import_rule_parse_from_buf (const guchar * a_buf,
+ enum CREncoding a_encoding)
+{
+ enum CRStatus status = CR_OK;
+ CRParser *parser = NULL;
+ CRStatement *result = NULL;
+ GList *media_list = NULL;
+ CRString *import_string = NULL;
+ CRParsingLocation location = {0} ;
+
+ parser = cr_parser_new_from_buf ((guchar*)a_buf, strlen ((const char *) a_buf),
+ a_encoding, FALSE);
+ if (!parser) {
+ cr_utils_trace_info ("Instantiation of parser failed.");
+ goto cleanup;
+ }
+
+ status = cr_parser_try_to_skip_spaces_and_comments (parser);
+ if (status != CR_OK)
+ goto cleanup;
+
+ status = cr_parser_parse_import (parser,
+ &media_list,
+ &import_string,
+ &location);
+ if (status != CR_OK || !import_string)
+ goto cleanup;
+
+ result = cr_statement_new_at_import_rule (NULL, import_string,
+ media_list, NULL);
+ if (result) {
+ cr_parsing_location_copy (&result->location,
+ &location) ;
+ import_string = NULL;
+ media_list = NULL;
+ }
+
+ cleanup:
+ if (parser) {
+ cr_parser_destroy (parser);
+ parser = NULL;
+ }
+ if (media_list) {
+ for (; media_list;
+ media_list = g_list_next (media_list)) {
+ if (media_list->data) {
+ cr_string_destroy ((CRString*)media_list->data);
+ media_list->data = NULL;
+ }
+ }
+ g_list_free (media_list);
+ media_list = NULL;
+ }
+ if (import_string) {
+ cr_string_destroy (import_string);
+ import_string = NULL;
+ }
+
+ return result;
+}
+
+/**
+ * cr_statement_new_at_page_rule:
+ *
+ *@a_decl_list: a list of instances of #CRDeclarations
+ *which is actually the list of declarations that applies to
+ *this page rule.
+ *@a_selector: the page rule selector.
+ *
+ *Creates a new instance of #CRStatement of type
+ *#CRAtPageRule.
+ *
+ *Returns the newly built instance of #CRStatement or NULL
+ *in case of error.
+ */
+CRStatement *
+cr_statement_new_at_page_rule (CRStyleSheet * a_sheet,
+ CRDeclaration * a_decl_list,
+ CRString * a_name, CRString * a_pseudo)
+{
+ CRStatement *result = NULL;
+
+ result = g_try_malloc (sizeof (CRStatement));
+
+ if (!result) {
+ cr_utils_trace_info ("Out of memory");
+ return NULL;
+ }
+
+ memset (result, 0, sizeof (CRStatement));
+ result->type = AT_PAGE_RULE_STMT;
+
+ result->kind.page_rule = g_try_malloc (sizeof (CRAtPageRule));
+
+ if (!result->kind.page_rule) {
+ cr_utils_trace_info ("Out of memory");
+ g_free (result);
+ return NULL;
+ }
+
+ memset (result->kind.page_rule, 0, sizeof (CRAtPageRule));
+ if (a_decl_list) {
+ result->kind.page_rule->decl_list = a_decl_list;
+ cr_declaration_ref (a_decl_list);
+ }
+ result->kind.page_rule->name = a_name;
+ result->kind.page_rule->pseudo = a_pseudo;
+ if (a_sheet)
+ cr_statement_set_parent_sheet (result, a_sheet);
+
+ return result;
+}
+
+/**
+ * cr_statement_at_page_rule_parse_from_buf:
+ *
+ *@a_buf: the character buffer to parse.
+ *@a_encoding: the character encoding of a_buf.
+ *
+ *Parses a buffer that contains an "\@page" production and,
+ *if the parsing succeeds, builds the page statement.
+ *
+ *Returns the newly built at page statement in case of successful parsing,
+ *NULL otherwise.
+ */
+CRStatement *
+cr_statement_at_page_rule_parse_from_buf (const guchar * a_buf,
+ enum CREncoding a_encoding)
+{
+ enum CRStatus status = CR_OK;
+ CRParser *parser = NULL;
+ CRDocHandler *sac_handler = NULL;
+ CRStatement *result = NULL;
+ CRStatement **resultptr = NULL;
+
+ g_return_val_if_fail (a_buf, NULL);
+
+ parser = cr_parser_new_from_buf ((guchar*)a_buf, strlen ((const char *) a_buf),
+ a_encoding, FALSE);
+ if (!parser) {
+ cr_utils_trace_info ("Instantiation of the parser failed.");
+ goto cleanup;
+ }
+
+ sac_handler = cr_doc_handler_new ();
+ if (!sac_handler) {
+ cr_utils_trace_info
+ ("Instantiation of the sac handler failed.");
+ goto cleanup;
+ }
+
+ sac_handler->start_page = parse_page_start_page_cb;
+ sac_handler->property = parse_page_property_cb;
+ sac_handler->end_page = parse_page_end_page_cb;
+ sac_handler->unrecoverable_error = parse_page_unrecoverable_error_cb;
+
+ status = cr_parser_set_sac_handler (parser, sac_handler);
+ if (status != CR_OK)
+ goto cleanup;
+
+ /*Now, invoke the parser to parse the "@page production" */
+ cr_parser_try_to_skip_spaces_and_comments (parser);
+ if (status != CR_OK)
+ goto cleanup;
+ status = cr_parser_parse_page (parser);
+ if (status != CR_OK)
+ goto cleanup;
+
+ resultptr = &result;
+ status = cr_doc_handler_get_result (sac_handler,
+ (gpointer *) resultptr);
+
+ cleanup:
+
+ if (parser) {
+ cr_parser_destroy (parser);
+ parser = NULL;
+ sac_handler = NULL ;
+ }
+ if (sac_handler) {
+ cr_doc_handler_unref (sac_handler);
+ sac_handler = NULL;
+ }
+ return result;
+}
+
+/**
+ * cr_statement_new_at_charset_rule:
+ *
+ *@a_charset: the string representing the charset.
+ *Note that the newly built instance of #CRStatement becomes
+ *the owner of a_charset. The caller must not free a_charset !!!.
+ *
+ *Creates a new instance of #CRStatement of type
+ *#CRAtCharsetRule.
+ *
+ *Returns the newly built instance of #CRStatement or NULL
+ *if an error arises.
+ */
+CRStatement *
+cr_statement_new_at_charset_rule (CRStyleSheet * a_sheet,
+ CRString * a_charset)
+{
+ CRStatement *result = NULL;
+
+ g_return_val_if_fail (a_charset, NULL);
+
+ result = g_try_malloc (sizeof (CRStatement));
+
+ if (!result) {
+ cr_utils_trace_info ("Out of memory");
+ return NULL;
+ }
+
+ memset (result, 0, sizeof (CRStatement));
+ result->type = AT_CHARSET_RULE_STMT;
+
+ result->kind.charset_rule = g_try_malloc (sizeof (CRAtCharsetRule));
+
+ if (!result->kind.charset_rule) {
+ cr_utils_trace_info ("Out of memory");
+ g_free (result);
+ return NULL;
+ }
+ memset (result->kind.charset_rule, 0, sizeof (CRAtCharsetRule));
+ result->kind.charset_rule->charset = a_charset;
+ cr_statement_set_parent_sheet (result, a_sheet);
+
+ return result;
+}
+
+/**
+ * cr_statement_at_charset_rule_parse_from_buf:
+ *
+ *@a_buf: the buffer to parse.
+ *@a_encoding: the character encoding of the buffer.
+ *
+ *Parses a buffer that contains an '\@charset' rule and
+ *creates an instance of #CRStatement of type AT_CHARSET_RULE_STMT.
+ *
+ *Returns the newly built instance of #CRStatement.
+ */
+CRStatement *
+cr_statement_at_charset_rule_parse_from_buf (const guchar * a_buf,
+ enum CREncoding a_encoding)
+{
+ enum CRStatus status = CR_OK;
+ CRParser *parser = NULL;
+ CRStatement *result = NULL;
+ CRString *charset = NULL;
+
+ g_return_val_if_fail (a_buf, NULL);
+
+ parser = cr_parser_new_from_buf ((guchar*)a_buf, strlen ((const char *) a_buf),
+ a_encoding, FALSE);
+ if (!parser) {
+ cr_utils_trace_info ("Instantiation of the parser failed.");
+ goto cleanup;
+ }
+
+ /*Now, invoke the parser to parse the "@charset production" */
+ cr_parser_try_to_skip_spaces_and_comments (parser);
+ if (status != CR_OK)
+ goto cleanup;
+ status = cr_parser_parse_charset (parser, &charset, NULL);
+ if (status != CR_OK || !charset)
+ goto cleanup;
+
+ result = cr_statement_new_at_charset_rule (NULL, charset);
+ if (result)
+ charset = NULL;
+
+ cleanup:
+
+ if (parser) {
+ cr_parser_destroy (parser);
+ parser = NULL;
+ }
+ if (charset) {
+ cr_string_destroy (charset);
+ }
+
+ return result;
+}
+
+/**
+ * cr_statement_new_at_font_face_rule:
+ *
+ *@a_font_decls: a list of instances of #CRDeclaration. Each declaration
+ *is actually a font declaration.
+ *
+ *Creates an instance of #CRStatement of type #CRAtFontFaceRule.
+ *
+ *Returns the newly built instance of #CRStatement.
+ */
+CRStatement *
+cr_statement_new_at_font_face_rule (CRStyleSheet * a_sheet,
+ CRDeclaration * a_font_decls)
+{
+ CRStatement *result = NULL;
+
+ result = g_try_malloc (sizeof (CRStatement));
+
+ if (!result) {
+ cr_utils_trace_info ("Out of memory");
+ return NULL;
+ }
+ memset (result, 0, sizeof (CRStatement));
+ result->type = AT_FONT_FACE_RULE_STMT;
+
+ result->kind.font_face_rule = g_try_malloc
+ (sizeof (CRAtFontFaceRule));
+
+ if (!result->kind.font_face_rule) {
+ cr_utils_trace_info ("Out of memory");
+ g_free (result);
+ return NULL;
+ }
+ memset (result->kind.font_face_rule, 0, sizeof (CRAtFontFaceRule));
+
+ result->kind.font_face_rule->decl_list = a_font_decls;
+ if (a_sheet)
+ cr_statement_set_parent_sheet (result, a_sheet);
+
+ return result;
+}
+
+/**
+ * cr_statement_font_face_rule_parse_from_buf:
+ *
+ *
+ *@a_buf: the buffer to parse.
+ *@a_encoding: the character encoding of a_buf.
+ *
+ *Parses a buffer that contains an "\@font-face" rule and builds
+ *an instance of #CRStatement of type AT_FONT_FACE_RULE_STMT out of it.
+ *
+ *Returns the newly built instance of #CRStatement in case of successufull
+ *parsing, NULL otherwise.
+ */
+CRStatement *
+cr_statement_font_face_rule_parse_from_buf (const guchar * a_buf,
+ enum CREncoding a_encoding)
+{
+ CRStatement *result = NULL;
+ CRStatement **resultptr = NULL;
+ CRParser *parser = NULL;
+ CRDocHandler *sac_handler = NULL;
+ enum CRStatus status = CR_OK;
+
+ parser = cr_parser_new_from_buf ((guchar*)a_buf, strlen ((const char *) a_buf),
+ a_encoding, FALSE);
+ if (!parser)
+ goto cleanup;
+
+ sac_handler = cr_doc_handler_new ();
+ if (!sac_handler)
+ goto cleanup;
+
+ /*
+ *set sac callbacks here
+ */
+ sac_handler->start_font_face = parse_font_face_start_font_face_cb;
+ sac_handler->property = parse_font_face_property_cb;
+ sac_handler->end_font_face = parse_font_face_end_font_face_cb;
+ sac_handler->unrecoverable_error =
+ parse_font_face_unrecoverable_error_cb;
+
+ status = cr_parser_set_sac_handler (parser, sac_handler);
+ if (status != CR_OK)
+ goto cleanup;
+
+ /*
+ *cleanup spaces of comment that may be there before the real
+ *"@font-face" thing.
+ */
+ status = cr_parser_try_to_skip_spaces_and_comments (parser);
+ if (status != CR_OK)
+ goto cleanup;
+
+ status = cr_parser_parse_font_face (parser);
+ if (status != CR_OK)
+ goto cleanup;
+
+ resultptr = &result;
+ status = cr_doc_handler_get_result (sac_handler,
+ (gpointer *) resultptr);
+ if (status != CR_OK || !result)
+ goto cleanup;
+
+ cleanup:
+ if (parser) {
+ cr_parser_destroy (parser);
+ parser = NULL;
+ sac_handler = NULL ;
+ }
+ if (sac_handler) {
+ cr_doc_handler_unref (sac_handler);
+ sac_handler = NULL;
+ }
+ return result;
+}
+
+/**
+ * cr_statement_set_parent_sheet:
+ *
+ *@a_this: the current instance of #CRStatement.
+ *@a_sheet: the sheet that contains the current statement.
+ *
+ *Sets the container stylesheet.
+ *
+ *Returns CR_OK upon successful completion, an error code otherwise.
+ */
+enum CRStatus
+cr_statement_set_parent_sheet (CRStatement * a_this, CRStyleSheet * a_sheet)
+{
+ g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR);
+ a_this->parent_sheet = a_sheet;
+ return CR_OK;
+}
+
+/**
+ * cr_statement_get_parent_sheet:
+ *
+ *@a_this: the current #CRStatement.
+ *@a_sheet: out parameter. A pointer to the sheets that
+ *
+ *Gets the sheets that contains the current statement.
+ *
+ *Returns CR_OK upon successful completion, an error code otherwise.
+ */
+enum CRStatus
+cr_statement_get_parent_sheet (CRStatement * a_this, CRStyleSheet ** a_sheet)
+{
+ g_return_val_if_fail (a_this && a_sheet, CR_BAD_PARAM_ERROR);
+ *a_sheet = a_this->parent_sheet;
+ return CR_OK;
+}
+
+/**
+ * cr_statement_append:
+ *
+ *@a_this: the current instance of the statement list.
+ *@a_new: a_new the new instance of #CRStatement to append.
+ *
+ *Appends a new statement to the statement list.
+ *
+ *Returns the new list statement list, or NULL in cas of failure.
+ */
+CRStatement *
+cr_statement_append (CRStatement * a_this, CRStatement * a_new)
+{
+ CRStatement *cur = NULL;
+
+ g_return_val_if_fail (a_new, NULL);
+
+ if (!a_this) {
+ return a_new;
+ }
+
+ /*walk forward in the current list to find the tail list element */
+ for (cur = a_this; cur && cur->next; cur = cur->next) ;
+
+ cur->next = a_new;
+ a_new->prev = cur;
+
+ return a_this;
+}
+
+/**
+ * cr_statement_prepend:
+ *
+ *@a_this: the current instance of #CRStatement.
+ *@a_new: the new statement to prepend.
+ *
+ *Prepends the an instance of #CRStatement to
+ *the current statement list.
+ *
+ *Returns the new list with the new statement prepended,
+ *or NULL in case of an error.
+ */
+CRStatement *
+cr_statement_prepend (CRStatement * a_this, CRStatement * a_new)
+{
+ CRStatement *cur = NULL;
+
+ g_return_val_if_fail (a_new, NULL);
+
+ if (!a_this)
+ return a_new;
+
+ a_new->next = a_this;
+ a_this->prev = a_new;
+
+ /*walk backward in the prepended list to find the head list element */
+ for (cur = a_new; cur && cur->prev; cur = cur->prev) ;
+
+ return cur;
+}
+
+/**
+ * cr_statement_unlink:
+ *
+ *@a_this: the current statements list.
+ *@a_to_unlink: the statement to unlink from the list.
+ *
+ *Unlinks a statement from the statements list.
+ *
+ *Returns the new list where a_to_unlink has been unlinked
+ *from, or NULL in case of error.
+ */
+CRStatement *
+cr_statement_unlink (CRStatement * a_stmt)
+{
+ CRStatement *result = a_stmt;
+
+ g_return_val_if_fail (result, NULL);
+
+ /**
+ *Some sanity checks first
+ */
+ if (a_stmt->next) {
+ g_return_val_if_fail (a_stmt->next->prev == a_stmt, NULL);
+ }
+ if (a_stmt->prev) {
+ g_return_val_if_fail (a_stmt->prev->next == a_stmt, NULL);
+ }
+
+ /**
+ *Now, the real unlinking job.
+ */
+ if (a_stmt->next) {
+ a_stmt->next->prev = a_stmt->prev;
+ }
+ if (a_stmt->prev) {
+ a_stmt->prev->next = a_stmt->next;
+ }
+
+ if (a_stmt->parent_sheet
+ && a_stmt->parent_sheet->statements == a_stmt) {
+ a_stmt->parent_sheet->statements =
+ a_stmt->parent_sheet->statements->next;
+ }
+
+ a_stmt->next = NULL;
+ a_stmt->prev = NULL;
+ a_stmt->parent_sheet = NULL;
+
+ return result;
+}
+
+/**
+ * cr_statement_nr_rules:
+ *
+ *@a_this: the current instance of #CRStatement.
+ *
+ *Gets the number of rules in the statement list;
+ *
+ *Returns number of rules in the statement list.
+ */
+gint
+cr_statement_nr_rules (CRStatement const * a_this)
+{
+ CRStatement const *cur = NULL;
+ int nr = 0;
+
+ g_return_val_if_fail (a_this, -1);
+
+ for (cur = a_this; cur; cur = cur->next)
+ nr++;
+ return nr;
+}
+
+/**
+ * cr_statement_get_from_list:
+ *
+ *@a_this: the current instance of #CRStatement.
+ *@itemnr: the index into the statement list.
+ *
+ *Use an index to get a CRStatement from the statement list.
+ *
+ *Returns CRStatement at position itemnr, if itemnr > number of statements - 1,
+ *it will return NULL.
+ */
+CRStatement *
+cr_statement_get_from_list (CRStatement * a_this, int itemnr)
+{
+ CRStatement *cur = NULL;
+ int nr = 0;
+
+ g_return_val_if_fail (a_this, NULL);
+
+ for (cur = a_this; cur; cur = cur->next)
+ if (nr++ == itemnr)
+ return cur;
+ return NULL;
+}
+
+/**
+ * cr_statement_ruleset_set_sel_list:
+ *
+ *@a_this: the current ruleset statement.
+ *@a_sel_list: the selector list to set. Note
+ *that this function increments the ref count of a_sel_list.
+ *The sel list will be destroyed at the destruction of the
+ *current instance of #CRStatement.
+ *
+ *Sets a selector list to a ruleset statement.
+ *
+ *Returns CR_OK upon successful completion, an error code otherwise.
+ */
+enum CRStatus
+cr_statement_ruleset_set_sel_list (CRStatement * a_this,
+ CRSelector * a_sel_list)
+{
+ g_return_val_if_fail (a_this && a_this->type == RULESET_STMT,
+ CR_BAD_PARAM_ERROR);
+
+ if (a_this->kind.ruleset->sel_list)
+ cr_selector_unref (a_this->kind.ruleset->sel_list);
+
+ a_this->kind.ruleset->sel_list = a_sel_list;
+
+ if (a_sel_list)
+ cr_selector_ref (a_sel_list);
+
+ return CR_OK;
+}
+
+/**
+ * cr_statement_ruleset_get_declarations:
+ *
+ *@a_this: the current instance of #CRStatement.
+ *@a_decl_list: out parameter. A pointer to the the returned
+ *list of declaration. Must not be NULL.
+ *
+ *Gets a pointer to the list of declaration contained
+ *in the ruleset statement.
+ *
+ *Returns CR_OK upon successful completion, an error code if something
+ *bad happened.
+ */
+enum CRStatus
+cr_statement_ruleset_get_declarations (CRStatement * a_this,
+ CRDeclaration ** a_decl_list)
+{
+ g_return_val_if_fail (a_this
+ && a_this->type == RULESET_STMT
+ && a_this->kind.ruleset
+ && a_decl_list, CR_BAD_PARAM_ERROR);
+
+ *a_decl_list = a_this->kind.ruleset->decl_list;
+
+ return CR_OK;
+}
+
+/**
+ * cr_statement_ruleset_get_sel_list:
+ *
+ *@a_this: the current ruleset statement.
+ *@a_list: out parameter. The returned selector list,
+ *if and only if the function returned CR_OK.
+ *
+ *Gets a pointer to the selector list contained in
+ *the current ruleset statement.
+ *
+ *Returns CR_OK upon successful completion, an error code otherwise.
+ */
+enum CRStatus
+cr_statement_ruleset_get_sel_list (CRStatement const * a_this, CRSelector ** a_list)
+{
+ g_return_val_if_fail (a_this && a_this->type == RULESET_STMT
+ && a_this->kind.ruleset, CR_BAD_PARAM_ERROR);
+
+ *a_list = a_this->kind.ruleset->sel_list;
+
+ return CR_OK;
+}
+
+/**
+ * cr_statement_ruleset_set_decl_list:
+ *
+ *@a_this: the current ruleset statement.
+ *@a_list: the declaration list to be added to the current
+ *ruleset statement.
+ *
+ *Sets a declaration list to the current ruleset statement.
+ *
+ *Returns CR_OK upon successful completion, an error code otherwise.
+ */
+enum CRStatus
+cr_statement_ruleset_set_decl_list (CRStatement * a_this,
+ CRDeclaration * a_list)
+{
+ g_return_val_if_fail (a_this && a_this->type == RULESET_STMT
+ && a_this->kind.ruleset, CR_BAD_PARAM_ERROR);
+
+ if (a_this->kind.ruleset->decl_list == a_list)
+ return CR_OK;
+
+ if (a_this->kind.ruleset->sel_list) {
+ cr_declaration_destroy (a_this->kind.ruleset->decl_list);
+ }
+
+ a_this->kind.ruleset->sel_list = NULL;
+
+ return CR_OK;
+}
+
+/**
+ * cr_statement_ruleset_append_decl2:
+ *
+ *@a_this: the current statement.
+ *@a_prop: the property of the declaration.
+ *@a_value: the value of the declaration.
+ *
+ *Appends a declaration to the current ruleset statement.
+ *
+ *Returns CR_OK upon successful completion, an error code
+ *otherwise.
+ */
+enum CRStatus
+cr_statement_ruleset_append_decl2 (CRStatement * a_this,
+ CRString * a_prop,
+ CRTerm * a_value)
+{
+ CRDeclaration *new_decls = NULL;
+
+ g_return_val_if_fail (a_this && a_this->type == RULESET_STMT
+ && a_this->kind.ruleset, CR_BAD_PARAM_ERROR);
+
+ new_decls = cr_declaration_append2
+ (a_this->kind.ruleset->decl_list,
+ a_prop, a_value);
+ g_return_val_if_fail (new_decls, CR_ERROR);
+ a_this->kind.ruleset->decl_list = new_decls;
+
+ return CR_OK;
+}
+
+/**
+ * cr_statement_ruleset_append_decl:
+ *
+ *Appends a declaration to the current statement.
+ *
+ *@a_this: the current statement.
+ *@a_declaration: the declaration to append.
+ *
+ *Returns CR_OK upon successful completion, an error code
+ *otherwise.
+ */
+enum CRStatus
+cr_statement_ruleset_append_decl (CRStatement * a_this,
+ CRDeclaration * a_decl)
+{
+ CRDeclaration *new_decls = NULL;
+
+ g_return_val_if_fail (a_this && a_this->type == RULESET_STMT
+ && a_this->kind.ruleset, CR_BAD_PARAM_ERROR);
+
+ new_decls = cr_declaration_append
+ (a_this->kind.ruleset->decl_list, a_decl);
+ g_return_val_if_fail (new_decls, CR_ERROR);
+ a_this->kind.ruleset->decl_list = new_decls;
+
+ return CR_OK;
+}
+
+/**
+ * cr_statement_at_import_rule_set_imported_sheet:
+ *
+ *Sets a stylesheet to the current \@import rule.
+ *@a_this: the current \@import rule.
+ *@a_sheet: the stylesheet. The stylesheet is owned
+ *by the current instance of #CRStatement, that is, the
+ *stylesheet will be destroyed when the current instance
+ *of #CRStatement is destroyed.
+ *
+ *Returns CR_OK upon successful completion, an error code otherwise.
+ */
+enum CRStatus
+cr_statement_at_import_rule_set_imported_sheet (CRStatement * a_this,
+ CRStyleSheet * a_sheet)
+{
+ g_return_val_if_fail (a_this
+ && a_this->type == AT_IMPORT_RULE_STMT
+ && a_this->kind.import_rule,
+ CR_BAD_PARAM_ERROR);
+
+ a_this->kind.import_rule->sheet = a_sheet;
+
+ return CR_OK;
+}
+
+/**
+ * cr_statement_at_import_rule_get_imported_sheet:
+ *
+ *@a_this: the current \@import rule statement.
+ *@a_sheet: out parameter. The returned stylesheet if and
+ *only if the function returns CR_OK.
+ *
+ *Gets the stylesheet contained by the \@import rule statement.
+ *Returns CR_OK upon successful completion, an error code otherwise.
+ */
+enum CRStatus
+cr_statement_at_import_rule_get_imported_sheet (CRStatement * a_this,
+ CRStyleSheet ** a_sheet)
+{
+ g_return_val_if_fail (a_this
+ && a_this->type == AT_IMPORT_RULE_STMT
+ && a_this->kind.import_rule,
+ CR_BAD_PARAM_ERROR);
+ *a_sheet = a_this->kind.import_rule->sheet;
+ return CR_OK;
+
+}
+
+/**
+ * cr_statement_at_import_rule_set_url:
+ *
+ *@a_this: the current \@import rule statement.
+ *@a_url: the url to set.
+ *
+ *Sets an url to the current \@import rule statement.
+ *
+ *Returns CR_OK upon successful completion, an error code otherwise.
+ */
+enum CRStatus
+cr_statement_at_import_rule_set_url (CRStatement * a_this,
+ CRString * a_url)
+{
+ g_return_val_if_fail (a_this
+ && a_this->type == AT_IMPORT_RULE_STMT
+ && a_this->kind.import_rule,
+ CR_BAD_PARAM_ERROR);
+
+ if (a_this->kind.import_rule->url) {
+ cr_string_destroy (a_this->kind.import_rule->url);
+ }
+
+ a_this->kind.import_rule->url = a_url;
+
+ return CR_OK;
+}
+
+/**
+ * cr_statement_at_import_rule_get_url:
+ *
+ *@a_this: the current \@import rule statement.
+ *@a_url: out parameter. The returned url if
+ *and only if the function returned CR_OK.
+ *
+ *Gets the url of the \@import rule statement.
+ *Returns CR_OK upon successful completion, an error code otherwise.
+ */
+enum CRStatus
+cr_statement_at_import_rule_get_url (CRStatement const * a_this,
+ CRString ** a_url)
+{
+ g_return_val_if_fail (a_this
+ && a_this->type == AT_IMPORT_RULE_STMT
+ && a_this->kind.import_rule,
+ CR_BAD_PARAM_ERROR);
+
+ *a_url = a_this->kind.import_rule->url;
+
+ return CR_OK;
+}
+
+/**
+ * cr_statement_at_media_nr_rules:
+ *
+ *@a_this: the current instance of #CRStatement.
+ *
+ *Returns the number of rules in the media rule;
+ */
+int
+cr_statement_at_media_nr_rules (CRStatement const * a_this)
+{
+ g_return_val_if_fail (a_this
+ && a_this->type == AT_MEDIA_RULE_STMT
+ && a_this->kind.media_rule, CR_BAD_PARAM_ERROR);
+
+ return cr_statement_nr_rules (a_this->kind.media_rule->rulesets);
+}
+
+/**
+ * cr_statement_at_media_get_from_list:
+ *
+ *@a_this: the current instance of #CRStatement.
+ *@itemnr: the index into the media rule list of rules.
+ *
+ *Use an index to get a CRStatement from the media rule list of rules.
+ *
+ *Returns CRStatement at position itemnr, if itemnr > number of rules - 1,
+ *it will return NULL.
+ */
+CRStatement *
+cr_statement_at_media_get_from_list (CRStatement * a_this, int itemnr)
+{
+ g_return_val_if_fail (a_this
+ && a_this->type == AT_MEDIA_RULE_STMT
+ && a_this->kind.media_rule, NULL);
+
+ return cr_statement_get_from_list (a_this->kind.media_rule->rulesets,
+ itemnr);
+}
+
+/**
+ * cr_statement_at_page_rule_set_declarations:
+ *
+ *@a_this: the current \@page rule statement.
+ *@a_decl_list: the declaration list to add. Will be freed
+ *by the current instance of #CRStatement when it is destroyed.
+ *
+ *Sets a declaration list to the current \@page rule statement.
+ *
+ *Returns CR_OK upon successful completion, an error code otherwise.
+ */
+enum CRStatus
+cr_statement_at_page_rule_set_declarations (CRStatement * a_this,
+ CRDeclaration * a_decl_list)
+{
+ g_return_val_if_fail (a_this
+ && a_this->type == AT_PAGE_RULE_STMT
+ && a_this->kind.page_rule, CR_BAD_PARAM_ERROR);
+
+ if (a_this->kind.page_rule->decl_list) {
+ cr_declaration_unref (a_this->kind.page_rule->decl_list);
+ }
+
+ a_this->kind.page_rule->decl_list = a_decl_list;
+
+ if (a_decl_list) {
+ cr_declaration_ref (a_decl_list);
+ }
+
+ return CR_OK;
+}
+
+/**
+ * cr_statement_at_page_rule_get_declarations:
+ *
+ *@a_this: the current \@page rule statement.
+ *@a_decl_list: out parameter. The returned declaration list.
+ *
+ *Gets the declaration list associated to the current \@page rule
+ *statement.
+ *
+ *Returns CR_OK upon successful completion, an error code otherwise.
+ */
+enum CRStatus
+cr_statement_at_page_rule_get_declarations (CRStatement * a_this,
+ CRDeclaration ** a_decl_list)
+{
+ g_return_val_if_fail (a_this
+ && a_this->type == AT_PAGE_RULE_STMT
+ && a_this->kind.page_rule, CR_BAD_PARAM_ERROR);
+
+ *a_decl_list = a_this->kind.page_rule->decl_list;
+
+ return CR_OK;
+}
+
+/**
+ * cr_statement_at_charset_rule_set_charset:
+ *
+ *
+ *@a_this: the current \@charset rule statement.
+ *@a_charset: the charset to set.
+ *
+ *Sets the charset of the current \@charset rule statement.
+ *
+ *Returns CR_OK upon successful completion, an error code otherwise.
+ */
+enum CRStatus
+cr_statement_at_charset_rule_set_charset (CRStatement * a_this,
+ CRString * a_charset)
+{
+ g_return_val_if_fail (a_this
+ && a_this->type == AT_CHARSET_RULE_STMT
+ && a_this->kind.charset_rule,
+ CR_BAD_PARAM_ERROR);
+
+ if (a_this->kind.charset_rule->charset) {
+ cr_string_destroy (a_this->kind.charset_rule->charset);
+ }
+ a_this->kind.charset_rule->charset = a_charset;
+ return CR_OK;
+}
+
+/**
+ * cr_statement_at_charset_rule_get_charset:
+ *@a_this: the current \@charset rule statement.
+ *@a_charset: out parameter. The returned charset string if
+ *and only if the function returned CR_OK.
+ *
+ *Gets the charset string associated to the current
+ *\@charset rule statement.
+ *
+ * Returns CR_OK upon successful completion, an error code otherwise.
+ */
+enum CRStatus
+cr_statement_at_charset_rule_get_charset (CRStatement const * a_this,
+ CRString ** a_charset)
+{
+ g_return_val_if_fail (a_this
+ && a_this->type == AT_CHARSET_RULE_STMT
+ && a_this->kind.charset_rule,
+ CR_BAD_PARAM_ERROR);
+
+ *a_charset = a_this->kind.charset_rule->charset;
+
+ return CR_OK;
+}
+
+/**
+ * cr_statement_at_font_face_rule_set_decls:
+ *
+ *@a_this: the current \@font-face rule statement.
+ *@a_decls: the declarations list to set.
+ *
+ *Sets a declaration list to the current \@font-face rule statement.
+ *
+ *Returns CR_OK upon successful completion, an error code otherwise.
+ */
+enum CRStatus
+cr_statement_at_font_face_rule_set_decls (CRStatement * a_this,
+ CRDeclaration * a_decls)
+{
+ g_return_val_if_fail (a_this
+ && a_this->type == AT_FONT_FACE_RULE_STMT
+ && a_this->kind.font_face_rule,
+ CR_BAD_PARAM_ERROR);
+
+ if (a_this->kind.font_face_rule->decl_list) {
+ cr_declaration_unref (a_this->kind.font_face_rule->decl_list);
+ }
+
+ a_this->kind.font_face_rule->decl_list = a_decls;
+ cr_declaration_ref (a_decls);
+
+ return CR_OK;
+}
+
+/**
+ * cr_statement_at_font_face_rule_get_decls:
+ *
+ *@a_this: the current \@font-face rule statement.
+ *@a_decls: out parameter. The returned declaration list if
+ *and only if this function returns CR_OK.
+ *
+ *Gets the declaration list associated to the current instance
+ *of \@font-face rule statement.
+ *
+ *Returns CR_OK upon successful completion, an error code otherwise.
+ */
+enum CRStatus
+cr_statement_at_font_face_rule_get_decls (CRStatement * a_this,
+ CRDeclaration ** a_decls)
+{
+ g_return_val_if_fail (a_this
+ && a_this->type == AT_FONT_FACE_RULE_STMT
+ && a_this->kind.font_face_rule,
+ CR_BAD_PARAM_ERROR);
+
+ *a_decls = a_this->kind.font_face_rule->decl_list;
+
+ return CR_OK;
+}
+
+/**
+ * cr_statement_at_font_face_rule_add_decl:
+ *
+ *@a_this: the current \@font-face rule statement.
+ *@a_prop: the property of the declaration.
+ *@a_value: the value of the declaration.
+ *
+ *Adds a declaration to the current \@font-face rule
+ *statement.
+ *
+ *Returns CR_OK upon successful completion, an error code otherwise.
+ */
+enum CRStatus
+cr_statement_at_font_face_rule_add_decl (CRStatement * a_this,
+ CRString * a_prop, CRTerm * a_value)
+{
+ CRDeclaration *decls = NULL;
+
+ g_return_val_if_fail (a_this
+ && a_this->type == AT_FONT_FACE_RULE_STMT
+ && a_this->kind.font_face_rule,
+ CR_BAD_PARAM_ERROR);
+
+ decls = cr_declaration_append2
+ (a_this->kind.font_face_rule->decl_list,
+ a_prop, a_value);
+
+ g_return_val_if_fail (decls, CR_ERROR);
+
+ if (a_this->kind.font_face_rule->decl_list == NULL)
+ cr_declaration_ref (decls);
+
+ a_this->kind.font_face_rule->decl_list = decls;
+
+ return CR_OK;
+}
+
+
+/**
+ * cr_statement_to_string:
+ *
+ *@a_this: the current statement to serialize
+ *@a_indent: the number of white space of indentation.
+ *
+ *Serializes a css statement into a string
+ *
+ *Returns the serialized statement. Must be freed by the caller
+ *using g_free().
+ */
+gchar *
+cr_statement_to_string (CRStatement const * a_this, gulong a_indent)
+{
+ gchar *str = NULL ;
+
+ if (!a_this)
+ return NULL;
+
+ switch (a_this->type) {
+ case RULESET_STMT:
+ str = cr_statement_ruleset_to_string
+ (a_this, a_indent);
+ break;
+
+ case AT_FONT_FACE_RULE_STMT:
+ str = cr_statement_font_face_rule_to_string
+ (a_this, a_indent) ;
+ break;
+
+ case AT_CHARSET_RULE_STMT:
+ str = cr_statement_charset_to_string
+ (a_this, a_indent);
+ break;
+
+ case AT_PAGE_RULE_STMT:
+ str = cr_statement_at_page_rule_to_string
+ (a_this, a_indent);
+ break;
+
+ case AT_MEDIA_RULE_STMT:
+ str = cr_statement_media_rule_to_string
+ (a_this, a_indent);
+ break;
+
+ case AT_IMPORT_RULE_STMT:
+ str = cr_statement_import_rule_to_string
+ (a_this, a_indent);
+ break;
+
+ default:
+ cr_utils_trace_info ("Statement unrecognized");
+ break;
+ }
+ return str ;
+}
+
+gchar*
+cr_statement_list_to_string (CRStatement const *a_this, gulong a_indent)
+{
+ CRStatement const *cur_stmt = NULL ;
+ GString *stringue = NULL ;
+ gchar *str = NULL ;
+
+ g_return_val_if_fail (a_this, NULL) ;
+
+ stringue = g_string_new (NULL) ;
+ if (!stringue) {
+ cr_utils_trace_info ("Out of memory") ;
+ return NULL ;
+ }
+ for (cur_stmt = a_this ; cur_stmt;
+ cur_stmt = cur_stmt->next) {
+ str = cr_statement_to_string (cur_stmt, a_indent) ;
+ if (str) {
+ if (!cur_stmt->prev) {
+ g_string_append (stringue, str) ;
+ } else {
+ g_string_append_printf
+ (stringue, "\n%s", str) ;
+ }
+ g_free (str) ;
+ str = NULL ;
+ }
+ }
+ str = g_string_free (stringue, FALSE) ;
+ return str ;
+}
+
+/**
+ * cr_statement_dump:
+ *
+ *@a_this: the current css2 statement.
+ *@a_fp: the destination file pointer.
+ *@a_indent: the number of white space indentation characters.
+ *
+ *Dumps the css2 statement to a file.
+ */
+void
+cr_statement_dump (CRStatement const * a_this, FILE * a_fp, gulong a_indent)
+{
+ gchar *str = NULL ;
+
+ if (!a_this)
+ return;
+
+ str = cr_statement_to_string (a_this, a_indent) ;
+ if (str) {
+ fprintf (a_fp, "%s",str) ;
+ g_free (str) ;
+ str = NULL ;
+ }
+}
+
+/**
+ * cr_statement_dump_ruleset:
+ *
+ *@a_this: the current instance of #CRStatement.
+ *@a_fp: the destination file pointer.
+ *@a_indent: the number of indentation white spaces to add.
+ *
+ *Dumps a ruleset statement to a file.
+ */
+void
+cr_statement_dump_ruleset (CRStatement const * a_this, FILE * a_fp, glong a_indent)
+{
+ gchar *str = NULL;
+
+ g_return_if_fail (a_fp && a_this);
+ str = cr_statement_ruleset_to_string (a_this, a_indent);
+ if (str) {
+ fprintf (a_fp, "%s", str);
+ g_free (str);
+ str = NULL;
+ }
+}
+
+/**
+ * cr_statement_dump_font_face_rule:
+ *
+ *@a_this: the current instance of font face rule statement.
+ *@a_fp: the destination file pointer.
+ *@a_indent: the number of white space indentation.
+ *
+ *Dumps a font face rule statement to a file.
+ */
+void
+cr_statement_dump_font_face_rule (CRStatement const * a_this, FILE * a_fp,
+ glong a_indent)
+{
+ gchar *str = NULL ;
+ g_return_if_fail (a_this
+ && a_this->type == AT_FONT_FACE_RULE_STMT);
+
+ str = cr_statement_font_face_rule_to_string (a_this,
+ a_indent) ;
+ if (str) {
+ fprintf (a_fp, "%s", str) ;
+ g_free (str) ;
+ str = NULL ;
+ }
+}
+
+/**
+ * cr_statement_dump_charset:
+ *
+ *@a_this: the current instance of the \@charset rule statement.
+ *@a_fp: the destination file pointer.
+ *@a_indent: the number of indentation white spaces.
+ *
+ *Dumps an \@charset rule statement to a file.
+ */
+void
+cr_statement_dump_charset (CRStatement const * a_this, FILE * a_fp, gulong a_indent)
+{
+ gchar *str = NULL;
+
+ g_return_if_fail (a_this && a_this->type == AT_CHARSET_RULE_STMT);
+
+ str = cr_statement_charset_to_string (a_this,
+ a_indent) ;
+ if (str) {
+ fprintf (a_fp, "%s", str) ;
+ g_free (str) ;
+ str = NULL ;
+ }
+}
+
+
+/**
+ * cr_statement_dump_page:
+ *
+ *@a_this: the statement to dump on stdout.
+ *@a_fp: the destination file pointer.
+ *@a_indent: the number of indentation white spaces.
+ *
+ *Dumps an \@page rule statement on stdout.
+ */
+void
+cr_statement_dump_page (CRStatement const * a_this, FILE * a_fp, gulong a_indent)
+{
+ gchar *str = NULL;
+
+ g_return_if_fail (a_this
+ && a_this->type == AT_PAGE_RULE_STMT
+ && a_this->kind.page_rule);
+
+ str = cr_statement_at_page_rule_to_string (a_this, a_indent) ;
+ if (str) {
+ fprintf (a_fp, "%s", str);
+ g_free (str) ;
+ str = NULL ;
+ }
+}
+
+
+/**
+ * cr_statement_dump_media_rule:
+ *
+ *@a_this: the statement to dump.
+ *@a_fp: the destination file pointer
+ *@a_indent: the number of white spaces indentation.
+ *
+ *Dumps an \@media rule statement to a file.
+ */
+void
+cr_statement_dump_media_rule (CRStatement const * a_this,
+ FILE * a_fp,
+ gulong a_indent)
+{
+ gchar *str = NULL ;
+ g_return_if_fail (a_this->type == AT_MEDIA_RULE_STMT);
+
+ str = cr_statement_media_rule_to_string (a_this, a_indent) ;
+ if (str) {
+ fprintf (a_fp, "%s", str) ;
+ g_free (str) ;
+ str = NULL ;
+ }
+}
+
+/**
+ * cr_statement_dump_import_rule:
+ *
+ *@a_fp: the destination file pointer.
+ *@a_indent: the number of white space indentations.
+ *
+ *Dumps an \@import rule statement to a file.
+ */
+void
+cr_statement_dump_import_rule (CRStatement const * a_this, FILE * a_fp,
+ gulong a_indent)
+{
+ gchar *str = NULL ;
+ g_return_if_fail (a_this
+ && a_this->type == AT_IMPORT_RULE_STMT
+ && a_fp
+ && a_this->kind.import_rule);
+
+ str = cr_statement_import_rule_to_string (a_this, a_indent) ;
+ if (str) {
+ fprintf (a_fp, "%s", str) ;
+ g_free (str) ;
+ str = NULL ;
+ }
+}
+
+/**
+ * cr_statement_destroy:
+ *
+ * @a_this: the current instance of #CRStatement.
+ *
+ *Destructor of #CRStatement.
+ */
+void
+cr_statement_destroy (CRStatement * a_this)
+{
+ CRStatement *cur = NULL;
+
+ g_return_if_fail (a_this);
+
+ /*go get the tail of the list */
+ for (cur = a_this; cur && cur->next; cur = cur->next) {
+ cr_statement_clear (cur);
+ }
+
+ if (cur)
+ cr_statement_clear (cur);
+
+ if (cur->prev == NULL) {
+ g_free (a_this);
+ return;
+ }
+
+ /*walk backward and free next element */
+ for (cur = cur->prev; cur && cur->prev; cur = cur->prev) {
+ if (cur->next) {
+ g_free (cur->next);
+ cur->next = NULL;
+ }
+ }
+
+ if (!cur)
+ return;
+
+ /*free the one remaining list */
+ if (cur->next) {
+ g_free (cur->next);
+ cur->next = NULL;
+ }
+
+ g_free (cur);
+ cur = NULL;
+}
diff --git a/src/st/croco/cr-statement.h b/src/st/croco/cr-statement.h
new file mode 100644
index 0000000..c5bec97
--- /dev/null
+++ b/src/st/croco/cr-statement.h
@@ -0,0 +1,440 @@
+/* -*- Mode: C; indent-tabs-mode: nil; c-basic-offset: 8 -*- */
+
+/*
+ * This file is part of The Croco Library
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2.1 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * 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 Lesser General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ *
+ * Author: Dodji Seketeli
+ * See COPYRIGHTS file for copyright information.
+ */
+
+#include <stdio.h>
+#include "cr-utils.h"
+#include "cr-term.h"
+#include "cr-selector.h"
+#include "cr-declaration.h"
+
+#ifndef __CR_STATEMENT_H__
+#define __CR_STATEMENT_H__
+
+G_BEGIN_DECLS
+
+/**
+ *@file
+ *Declaration of the #CRStatement class.
+ */
+
+/*
+ *forward declaration of CRStyleSheet which is defined in
+ *cr-stylesheet.h
+ */
+
+struct _CRStatement ;
+
+/*
+ *typedef struct _CRStatement CRStatement ;
+ *this is forward declared in
+ *cr-declaration.h already.
+ */
+
+struct _CRAtMediaRule ;
+typedef struct _CRAtMediaRule CRAtMediaRule ;
+
+typedef struct _CRRuleSet CRRuleSet ;
+
+/**
+ *The abstraction of a css ruleset.
+ *A ruleset is made of a list of selectors,
+ *followed by a list of declarations.
+ */
+struct _CRRuleSet
+{
+ /**A list of instances of #CRSimpeSel*/
+ CRSelector *sel_list ;
+
+ /**A list of instances of #CRDeclaration*/
+ CRDeclaration *decl_list ;
+
+ /**
+ *The parent media rule, or NULL if
+ *no parent media rule exists.
+ */
+ CRStatement *parent_media_rule ;
+} ;
+
+/*
+ *a forward declaration of CRStylesheet.
+ *CRStylesheet is actually declared in
+ *cr-stylesheet.h
+ */
+struct _CRStyleSheet ;
+typedef struct _CRStyleSheet CRStyleSheet;
+
+
+/**The \@import rule abstraction.*/
+typedef struct _CRAtImportRule CRAtImportRule ;
+struct _CRAtImportRule
+{
+ /**the url of the import rule*/
+ CRString *url ;
+
+ GList *media_list ;
+
+ /**
+ *the stylesheet fetched from the url, if any.
+ *this is not "owned" by #CRAtImportRule which means
+ *it is not destroyed by the destructor of #CRAtImportRule.
+ */
+ CRStyleSheet * sheet;
+};
+
+
+/**abstraction of an \@media rule*/
+struct _CRAtMediaRule
+{
+ GList *media_list ;
+ CRStatement *rulesets ;
+} ;
+
+
+typedef struct _CRAtPageRule CRAtPageRule ;
+/**The \@page rule abstraction*/
+struct _CRAtPageRule
+{
+ /**a list of instances of #CRDeclaration*/
+ CRDeclaration *decl_list ;
+
+ /**page selector. Is a pseudo selector*/
+ CRString *name ;
+ CRString *pseudo ;
+} ;
+
+/**The \@charset rule abstraction*/
+typedef struct _CRAtCharsetRule CRAtCharsetRule ;
+struct _CRAtCharsetRule
+{
+ CRString * charset ;
+};
+
+/**The abstraction of the \@font-face rule.*/
+typedef struct _CRAtFontFaceRule CRAtFontFaceRule ;
+struct _CRAtFontFaceRule
+{
+ /*a list of instanaces of #CRDeclaration*/
+ CRDeclaration *decl_list ;
+} ;
+
+
+/**
+ *The possible types of css2 statements.
+ */
+enum CRStatementType
+{
+ /**
+ *A generic css at-rule
+ *each unknown at-rule will
+ *be of this type.
+ */
+
+ /**A css at-rule*/
+ AT_RULE_STMT = 0,
+
+ /*A css ruleset*/
+ RULESET_STMT,
+
+ /**A css2 import rule*/
+ AT_IMPORT_RULE_STMT,
+
+ /**A css2 media rule*/
+ AT_MEDIA_RULE_STMT,
+
+ /**A css2 page rule*/
+ AT_PAGE_RULE_STMT,
+
+ /**A css2 charset rule*/
+ AT_CHARSET_RULE_STMT,
+
+ /**A css2 font face rule*/
+ AT_FONT_FACE_RULE_STMT
+} ;
+
+
+/**
+ *The abstraction of css statement as defined
+ *in the chapter 4 and appendix D.1 of the css2 spec.
+ *A statement is actually a double chained list of
+ *statements.A statement can be a ruleset, an \@import
+ *rule, an \@page rule etc ...
+ */
+struct _CRStatement
+{
+ /**
+ *The type of the statement.
+ */
+ enum CRStatementType type ;
+
+ union
+ {
+ CRRuleSet *ruleset ;
+ CRAtImportRule *import_rule ;
+ CRAtMediaRule *media_rule ;
+ CRAtPageRule *page_rule ;
+ CRAtCharsetRule *charset_rule ;
+ CRAtFontFaceRule *font_face_rule ;
+ } kind ;
+
+ /*
+ *the specificity of the selector
+ *that matched this statement.
+ *This is only used by the cascading
+ *order determination algorithm.
+ */
+ gulong specificity ;
+
+ /*
+ *the style sheet that contains
+ *this css statement.
+ */
+ CRStyleSheet *parent_sheet ;
+ CRStatement *next ;
+ CRStatement *prev ;
+
+ CRParsingLocation location ;
+
+ /**
+ *a custom pointer useable by
+ *applications that use libcroco.
+ *libcroco itself will never modify
+ *this pointer.
+ */
+ gpointer app_data ;
+
+ /**
+ *a custom pointer used
+ *by the upper layers of libcroco.
+ *application should never use this
+ *pointer.
+ */
+ gpointer croco_data ;
+
+} ;
+
+
+gboolean
+cr_statement_does_buf_parses_against_core (const guchar *a_buf,
+ enum CREncoding a_encoding) ;
+CRStatement *
+cr_statement_parse_from_buf (const guchar *a_buf,
+ enum CREncoding a_encoding) ;
+CRStatement*
+cr_statement_new_ruleset (CRStyleSheet *a_sheet,
+ CRSelector *a_sel_list,
+ CRDeclaration *a_decl_list,
+ CRStatement *a_media_rule) ;
+CRStatement *
+cr_statement_ruleset_parse_from_buf (const guchar * a_buf,
+ enum CREncoding a_enc) ;
+
+CRStatement*
+cr_statement_new_at_import_rule (CRStyleSheet *a_container_sheet,
+ CRString *a_url,
+ GList *a_media_list,
+ CRStyleSheet *a_imported_sheet) ;
+
+CRStatement *
+cr_statement_at_import_rule_parse_from_buf (const guchar * a_buf,
+ enum CREncoding a_encoding) ;
+
+CRStatement *
+cr_statement_new_at_media_rule (CRStyleSheet *a_sheet,
+ CRStatement *a_ruleset,
+ GList *a_media) ;
+CRStatement *
+cr_statement_at_media_rule_parse_from_buf (const guchar *a_buf,
+ enum CREncoding a_enc) ;
+
+CRStatement *
+cr_statement_new_at_charset_rule (CRStyleSheet *a_sheet,
+ CRString *a_charset) ;
+CRStatement *
+cr_statement_at_charset_rule_parse_from_buf (const guchar *a_buf,
+ enum CREncoding a_encoding);
+
+
+CRStatement *
+cr_statement_new_at_font_face_rule (CRStyleSheet *a_sheet,
+ CRDeclaration *a_font_decls) ;
+CRStatement *
+cr_statement_font_face_rule_parse_from_buf (const guchar *a_buf,
+ enum CREncoding a_encoding) ;
+
+CRStatement *
+cr_statement_new_at_page_rule (CRStyleSheet *a_sheet,
+ CRDeclaration *a_decl_list,
+ CRString *a_name,
+ CRString *a_pseudo) ;
+CRStatement *
+cr_statement_at_page_rule_parse_from_buf (const guchar *a_buf,
+ enum CREncoding a_encoding) ;
+
+enum CRStatus
+cr_statement_set_parent_sheet (CRStatement *a_this,
+ CRStyleSheet *a_sheet) ;
+
+enum CRStatus
+cr_statement_get_parent_sheet (CRStatement *a_this,
+ CRStyleSheet **a_sheet) ;
+
+CRStatement *
+cr_statement_append (CRStatement *a_this,
+ CRStatement *a_new) ;
+
+CRStatement*
+cr_statement_prepend (CRStatement *a_this,
+ CRStatement *a_new) ;
+
+CRStatement *
+cr_statement_unlink (CRStatement *a_stmt) ;
+
+enum CRStatus
+cr_statement_ruleset_set_sel_list (CRStatement *a_this,
+ CRSelector *a_sel_list) ;
+
+enum CRStatus
+cr_statement_ruleset_get_sel_list (CRStatement const *a_this,
+ CRSelector **a_list) ;
+
+enum CRStatus
+cr_statement_ruleset_set_decl_list (CRStatement *a_this,
+ CRDeclaration *a_list) ;
+
+enum CRStatus
+cr_statement_ruleset_get_declarations (CRStatement *a_this,
+ CRDeclaration **a_decl_list) ;
+
+enum CRStatus
+cr_statement_ruleset_append_decl2 (CRStatement *a_this,
+ CRString *a_prop, CRTerm *a_value) ;
+
+enum CRStatus
+cr_statement_ruleset_append_decl (CRStatement *a_this,
+ CRDeclaration *a_decl) ;
+
+enum CRStatus
+cr_statement_at_import_rule_set_imported_sheet (CRStatement *a_this,
+ CRStyleSheet *a_sheet) ;
+
+enum CRStatus
+cr_statement_at_import_rule_get_imported_sheet (CRStatement *a_this,
+ CRStyleSheet **a_sheet) ;
+
+enum CRStatus
+cr_statement_at_import_rule_set_url (CRStatement *a_this,
+ CRString *a_url) ;
+
+enum CRStatus
+cr_statement_at_import_rule_get_url (CRStatement const *a_this,
+ CRString **a_url) ;
+
+gint
+cr_statement_at_media_nr_rules (CRStatement const *a_this) ;
+
+CRStatement *
+cr_statement_at_media_get_from_list (CRStatement *a_this, int itemnr) ;
+
+enum CRStatus
+cr_statement_at_page_rule_set_sel (CRStatement *a_this,
+ CRSelector *a_sel) ;
+
+enum CRStatus
+cr_statement_at_page_rule_get_sel (CRStatement const *a_this,
+ CRSelector **a_sel) ;
+
+enum CRStatus
+cr_statement_at_page_rule_set_declarations (CRStatement *a_this,
+ CRDeclaration *a_decl_list) ;
+
+enum CRStatus
+cr_statement_at_page_rule_get_declarations (CRStatement *a_this,
+ CRDeclaration **a_decl_list) ;
+
+enum CRStatus
+cr_statement_at_charset_rule_set_charset (CRStatement *a_this,
+ CRString *a_charset) ;
+
+enum CRStatus
+cr_statement_at_charset_rule_get_charset (CRStatement const *a_this,
+ CRString **a_charset) ;
+
+enum CRStatus
+cr_statement_at_font_face_rule_set_decls (CRStatement *a_this,
+ CRDeclaration *a_decls) ;
+
+enum CRStatus
+cr_statement_at_font_face_rule_get_decls (CRStatement *a_this,
+ CRDeclaration **a_decls) ;
+
+enum CRStatus
+cr_statement_at_font_face_rule_add_decl (CRStatement *a_this,
+ CRString *a_prop,
+ CRTerm *a_value) ;
+
+gchar *
+cr_statement_to_string (CRStatement const * a_this, gulong a_indent) ;
+
+gchar*
+cr_statement_list_to_string (CRStatement const *a_this, gulong a_indent) ;
+
+void
+cr_statement_dump (CRStatement const *a_this, FILE *a_fp, gulong a_indent) ;
+
+void
+cr_statement_dump_ruleset (CRStatement const * a_this, FILE * a_fp,
+ glong a_indent) ;
+
+void
+cr_statement_dump_font_face_rule (CRStatement const * a_this,
+ FILE * a_fp,
+ glong a_indent) ;
+
+void
+cr_statement_dump_page (CRStatement const * a_this, FILE * a_fp,
+ gulong a_indent) ;
+
+
+void
+cr_statement_dump_media_rule (CRStatement const * a_this,
+ FILE * a_fp,
+ gulong a_indent) ;
+
+void
+cr_statement_dump_import_rule (CRStatement const * a_this, FILE * a_fp,
+ gulong a_indent) ;
+void
+cr_statement_dump_charset (CRStatement const * a_this, FILE * a_fp,
+ gulong a_indent) ;
+gint
+cr_statement_nr_rules (CRStatement const *a_this) ;
+
+CRStatement *
+cr_statement_get_from_list (CRStatement *a_this, int itemnr) ;
+
+void
+cr_statement_destroy (CRStatement *a_this) ;
+
+G_END_DECLS
+
+#endif /*__CR_STATEMENT_H__*/
diff --git a/src/st/croco/cr-string.c b/src/st/croco/cr-string.c
new file mode 100644
index 0000000..6a16676
--- /dev/null
+++ b/src/st/croco/cr-string.c
@@ -0,0 +1,168 @@
+/* -*- Mode: C; indent-tabs-mode:nil; c-basic-offset: 8-*- */
+
+/*
+ * This file is part of The Croco Library
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2.1 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * 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 Lesser General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ *
+ * Author: Dodji Seketeli.
+ * See COPYRIGHTS file for copyright information.
+ */
+
+#include <string.h>
+#include "cr-string.h"
+
+/**
+ *Instantiates a #CRString
+ *@return the newly instantiated #CRString
+ *Must be freed with cr_string_destroy().
+ */
+CRString *
+cr_string_new (void)
+{
+ CRString *result = NULL ;
+
+ result = g_try_malloc (sizeof (CRString)) ;
+ if (!result) {
+ cr_utils_trace_info ("Out of memory") ;
+ return NULL ;
+ }
+ memset (result, 0, sizeof (CRString)) ;
+ result->stryng = g_string_new (NULL) ;
+ return result ;
+}
+
+/**
+ *Instantiate a string and initialise it to
+ *a_string.
+ *@param a_string the initial string
+ *@return the newly instantiated string.
+ */
+CRString *
+cr_string_new_from_string (const gchar * a_string)
+{
+ CRString *result = NULL ;
+
+ result = cr_string_new () ;
+ if (!result) {
+ cr_utils_trace_info ("Out of memory") ;
+ return NULL ;
+ }
+ if (a_string)
+ g_string_append (result->stryng, a_string) ;
+ return result ;
+}
+
+/**
+ *Instantiates a #CRString from an instance of GString.
+ *@param a_string the input string that will be copied into
+ *the newly instantiated #CRString
+ *@return the newly instantiated #CRString.
+ */
+CRString *
+cr_string_new_from_gstring (GString const *a_string)
+{
+ CRString *result = NULL ;
+
+ result = cr_string_new () ;
+ if (!result) {
+ cr_utils_trace_info ("Out of memory") ;
+ return NULL ;
+ }
+ if (a_string) {
+ g_string_append_len (result->stryng,
+ a_string->str,
+ a_string->len);
+
+ }
+ return result ;
+}
+
+CRString *
+cr_string_dup (CRString const *a_this)
+{
+ CRString *result = NULL ;
+ g_return_val_if_fail (a_this, NULL) ;
+
+ result = cr_string_new_from_gstring (a_this->stryng) ;
+ if (!result) {
+ cr_utils_trace_info ("Out of memory") ;
+ return NULL ;
+ }
+ cr_parsing_location_copy (&result->location,
+ &a_this->location) ;
+ return result ;
+}
+
+gchar *
+cr_string_dup2 (CRString const *a_this)
+{
+ gchar *result = NULL ;
+
+ g_return_val_if_fail (a_this, NULL) ;
+
+ if (a_this
+ && a_this->stryng
+ && a_this->stryng->str) {
+ result = g_strndup (a_this->stryng->str,
+ a_this->stryng->len) ;
+ }
+ return result ;
+}
+
+/**
+ *Returns a pointer to the internal raw NULL terminated string
+ *of the current instance of #CRString.
+ *@param a_this the current instance of #CRString
+ */
+const gchar *
+cr_string_peek_raw_str (CRString const *a_this)
+{
+ g_return_val_if_fail (a_this, NULL) ;
+
+ if (a_this->stryng && a_this->stryng->str)
+ return a_this->stryng->str ;
+ return NULL ;
+}
+
+/**
+ *Returns the length of the internal raw NULL terminated
+ *string of the current instance of #CRString.
+ *@param a_this the current instance of #CRString.
+ *@return the len of the internal raw NULL termninated string,
+ *of -1 if no length can be returned.
+ */
+gint
+cr_string_peek_raw_str_len (CRString const *a_this)
+{
+ g_return_val_if_fail (a_this && a_this->stryng,
+ -1) ;
+ return a_this->stryng->len ;
+}
+
+/**
+ *@param a_this the #CRString to destroy.
+ */
+void
+cr_string_destroy (CRString *a_this)
+{
+ g_return_if_fail (a_this) ;
+
+ if (a_this->stryng) {
+ g_string_free (a_this->stryng, TRUE) ;
+ a_this->stryng = NULL ;
+ }
+ g_free (a_this) ;
+}
diff --git a/src/st/croco/cr-string.h b/src/st/croco/cr-string.h
new file mode 100644
index 0000000..2700f0e
--- /dev/null
+++ b/src/st/croco/cr-string.h
@@ -0,0 +1,76 @@
+/* -*- Mode: C; indent-tabs-mode:nil; c-basic-offset: 8-*- */
+
+/*
+ * This file is part of The Croco Library
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2.1 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * 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 Lesser General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ *
+ * See COPYRIGHTS file for copyright information.
+ */
+
+/**
+ *@file
+ *Declaration file of the #CRString class.
+ */
+
+#ifndef __CR_STRING_H__
+#define __CR_STRING_H__
+
+#include <glib.h>
+#include "cr-utils.h"
+#include "cr-parsing-location.h"
+
+G_BEGIN_DECLS
+
+typedef struct _CRString CRString ;
+
+/**
+ *This is a ship implementation of string based on GString.
+ *Actually, the aim of CRString is to store the parsing location
+ *(line,column,byte offset) at which a given string has been parsed
+ *in the input CSS.
+ *So this class has a gstring field of type GString that users can
+ *freely manipulate, and also a CRParginLocation type where the
+ *parsing location is store. If you don't want to deal with parsing
+ *location stuffs, then use GString instead. If we were in C++ for example,
+ *CRString would just inherit GString and just add accessors to
+ *the CRParsingLocation data ... but we are not and we still have
+ *to provide the parsing location information.
+ */
+struct _CRString {
+ /**
+ *The GString where all the string
+ *operation happen.
+ */
+ GString *stryng ;
+ /**
+ *The parsing location storage area.
+ */
+ CRParsingLocation location ;
+} ;
+
+CRString * cr_string_new (void) ;
+
+CRString *cr_string_new_from_string (const gchar * a_string) ;
+CRString * cr_string_new_from_gstring (GString const *a_string) ;
+CRString *cr_string_dup (CRString const *a_this) ;
+gchar *cr_string_dup2 (CRString const *a_this) ;
+const gchar *cr_string_peek_raw_str (CRString const *a_this) ;
+gint cr_string_peek_raw_str_len (CRString const *a_this) ;
+void cr_string_destroy (CRString *a_this) ;
+
+G_END_DECLS
+
+#endif
diff --git a/src/st/croco/cr-stylesheet.c b/src/st/croco/cr-stylesheet.c
new file mode 100644
index 0000000..63e763f
--- /dev/null
+++ b/src/st/croco/cr-stylesheet.c
@@ -0,0 +1,177 @@
+/* -*- Mode: C; indent-tabs-mode: nil; c-basic-offset: 8 -*- */
+
+/*
+ * This file is part of The Croco Library
+ *
+ * Copyright (C) 2002-2004 Dodji Seketeli
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2.1 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * 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 Lesser General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ */
+
+#include "string.h"
+#include "cr-stylesheet.h"
+
+/**
+ *@file
+ *The definition of the #CRStyleSheet class
+ */
+
+/**
+ *Constructor of the #CRStyleSheet class.
+ *@param the initial list of css statements.
+ *@return the newly built css2 stylesheet, or NULL in case of error.
+ */
+CRStyleSheet *
+cr_stylesheet_new (CRStatement * a_stmts)
+{
+ CRStyleSheet *result;
+
+ result = g_try_malloc (sizeof (CRStyleSheet));
+ if (!result) {
+ cr_utils_trace_info ("Out of memory");
+ return NULL;
+ }
+
+ memset (result, 0, sizeof (CRStyleSheet));
+
+ if (a_stmts)
+ result->statements = a_stmts;
+
+ return result;
+}
+
+/**
+ *@param a_this the current instance of #CRStyleSheet
+ *@return the serialized stylesheet.
+ */
+gchar *
+cr_stylesheet_to_string (CRStyleSheet const *a_this)
+{
+ gchar *str = NULL;
+ GString *stringue = NULL;
+ CRStatement const *cur_stmt = NULL;
+
+ g_return_val_if_fail (a_this, NULL);
+
+ if (a_this->statements) {
+ stringue = g_string_new (NULL) ;
+ g_return_val_if_fail (stringue, NULL) ;
+ }
+ for (cur_stmt = a_this->statements;
+ cur_stmt; cur_stmt = cur_stmt->next) {
+ if (cur_stmt->prev) {
+ g_string_append (stringue, "\n\n") ;
+ }
+ str = cr_statement_to_string (cur_stmt, 0) ;
+ if (str) {
+ g_string_append (stringue, str) ;
+ g_free (str) ;
+ str = NULL ;
+ }
+ }
+ if (stringue) {
+ str = g_string_free (stringue, FALSE) ;
+ stringue = NULL ;
+ }
+ return str ;
+}
+
+/**
+ *Dumps the current css2 stylesheet to a file.
+ *@param a_this the current instance of #CRStyleSheet.
+ *@param a_fp the destination file
+ */
+void
+cr_stylesheet_dump (CRStyleSheet const * a_this, FILE * a_fp)
+{
+ gchar *str = NULL ;
+
+ g_return_if_fail (a_this);
+
+ str = cr_stylesheet_to_string (a_this) ;
+ if (str) {
+ fprintf (a_fp, "%s", str) ;
+ g_free (str) ;
+ str = NULL ;
+ }
+}
+
+/**
+ *Return the number of rules in the stylesheet.
+ *@param a_this the current instance of #CRStyleSheet.
+ *@return number of rules in the stylesheet.
+ */
+gint
+cr_stylesheet_nr_rules (CRStyleSheet const * a_this)
+{
+ g_return_val_if_fail (a_this, -1);
+
+ return cr_statement_nr_rules (a_this->statements);
+}
+
+/**
+ *Use an index to get a CRStatement from the rules in a given stylesheet.
+ *@param a_this the current instance of #CRStatement.
+ *@param itemnr the index into the rules.
+ *@return CRStatement at position itemnr, if itemnr > number of rules - 1,
+ *it will return NULL.
+ */
+CRStatement *
+cr_stylesheet_statement_get_from_list (CRStyleSheet * a_this, int itemnr)
+{
+ g_return_val_if_fail (a_this, NULL);
+
+ return cr_statement_get_from_list (a_this->statements, itemnr);
+}
+
+void
+cr_stylesheet_ref (CRStyleSheet * a_this)
+{
+ g_return_if_fail (a_this);
+
+ a_this->ref_count++;
+}
+
+gboolean
+cr_stylesheet_unref (CRStyleSheet * a_this)
+{
+ g_return_val_if_fail (a_this, FALSE);
+
+ if (a_this->ref_count)
+ a_this->ref_count--;
+
+ if (!a_this->ref_count) {
+ cr_stylesheet_destroy (a_this);
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+/**
+ *Destructor of the #CRStyleSheet class.
+ *@param a_this the current instance of the #CRStyleSheet class.
+ */
+void
+cr_stylesheet_destroy (CRStyleSheet * a_this)
+{
+ g_return_if_fail (a_this);
+
+ if (a_this->statements) {
+ cr_statement_destroy (a_this->statements);
+ a_this->statements = NULL;
+ }
+ g_free (a_this);
+}
diff --git a/src/st/croco/cr-stylesheet.h b/src/st/croco/cr-stylesheet.h
new file mode 100644
index 0000000..2d6b4fa
--- /dev/null
+++ b/src/st/croco/cr-stylesheet.h
@@ -0,0 +1,102 @@
+/* -*- Mode: C; indent-tabs-mode: nil; c-basic-offset: 8 -*- */
+
+/*
+ * This file is part of The Croco Library
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2.1 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * 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 Lesser General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ *
+ * see COPYRIGHTS file for copyright information.
+ */
+
+
+#ifndef __CR_STYLESHEET_H__
+#define __CR_STYLESHEET_H__
+
+#include "cr-utils.h"
+#include "cr-statement.h"
+
+G_BEGIN_DECLS
+
+/**
+ *@file
+ *The declaration of the #CRStyleSheet class.
+ */
+
+
+enum CRStyleOrigin
+{
+ /*Please don't change the order of
+ *the values enumerated here ...
+ *New values should be added at the end,
+ *just before ORIGIN_END.
+ */
+ ORIGIN_UA = 0,
+ ORIGIN_USER,
+ ORIGIN_AUTHOR,
+
+ /*must always be the last one*/
+ NB_ORIGINS
+} ;
+
+/**
+ *An abstraction of a css stylesheet as defined
+ *by the css2 spec in chapter 4.
+ */
+struct _CRStyleSheet
+{
+ /**The css statements list*/
+ CRStatement *statements ;
+
+ enum CRStyleOrigin origin ;
+
+ /*the parent import rule, if any.*/
+ CRStatement *parent_import_rule ;
+
+ /**custom data used by libcroco*/
+ gpointer croco_data ;
+
+ /**
+ *custom application data pointer
+ *Can be used by applications.
+ */
+ gpointer app_data ;
+
+ /**
+ *the reference count of this instance
+ *Please, don't never ever modify it
+ *directly. Use cr_stylesheet_ref()
+ *and cr_stylesheet_unref() instead.
+ */
+ gulong ref_count ;
+} ;
+
+CRStyleSheet * cr_stylesheet_new (CRStatement *a_stmts) ;
+
+gchar * cr_stylesheet_to_string (CRStyleSheet const *a_this) ;
+void cr_stylesheet_dump (CRStyleSheet const *a_this, FILE *a_fp) ;
+
+gint cr_stylesheet_nr_rules (CRStyleSheet const *a_this) ;
+
+CRStatement * cr_stylesheet_statement_get_from_list (CRStyleSheet *a_this, int itemnr) ;
+
+void cr_stylesheet_ref (CRStyleSheet *a_this) ;
+
+gboolean cr_stylesheet_unref (CRStyleSheet *a_this) ;
+
+void cr_stylesheet_destroy (CRStyleSheet *a_this) ;
+
+G_END_DECLS
+
+#endif /*__CR_STYLESHEET_H__*/
diff --git a/src/st/croco/cr-term.c b/src/st/croco/cr-term.c
new file mode 100644
index 0000000..b527d95
--- /dev/null
+++ b/src/st/croco/cr-term.c
@@ -0,0 +1,786 @@
+/* -*- Mode: C; indent-tabs-mode:nil; c-basic-offset: 8-*- */
+
+/*
+ * This file is part of The Croco Library
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2.1 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * 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 Lesser General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ *
+ * Author: Dodji Seketeli
+ * See COPYRIGHTS file for copyright information.
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include "cr-term.h"
+#include "cr-num.h"
+#include "cr-parser.h"
+
+/**
+ *@file
+ *Definition of the #CRTem class.
+ */
+
+static void
+cr_term_clear (CRTerm * a_this)
+{
+ g_return_if_fail (a_this);
+
+ switch (a_this->type) {
+ case TERM_NUMBER:
+ if (a_this->content.num) {
+ cr_num_destroy (a_this->content.num);
+ a_this->content.num = NULL;
+ }
+ break;
+
+ case TERM_FUNCTION:
+ if (a_this->ext_content.func_param) {
+ cr_term_destroy (a_this->ext_content.func_param);
+ a_this->ext_content.func_param = NULL;
+ }
+ case TERM_STRING:
+ case TERM_IDENT:
+ case TERM_URI:
+ case TERM_HASH:
+ if (a_this->content.str) {
+ cr_string_destroy (a_this->content.str);
+ a_this->content.str = NULL;
+ }
+ break;
+
+ case TERM_RGB:
+ if (a_this->content.rgb) {
+ cr_rgb_destroy (a_this->content.rgb);
+ a_this->content.rgb = NULL;
+ }
+ break;
+
+ case TERM_UNICODERANGE:
+ case TERM_NO_TYPE:
+ default:
+ break;
+ }
+
+ a_this->type = TERM_NO_TYPE;
+}
+
+/**
+ *Instantiate a #CRTerm.
+ *@return the newly build instance
+ *of #CRTerm.
+ */
+CRTerm *
+cr_term_new (void)
+{
+ CRTerm *result = NULL;
+
+ result = g_try_malloc (sizeof (CRTerm));
+ if (!result) {
+ cr_utils_trace_info ("Out of memory");
+ return NULL;
+ }
+ memset (result, 0, sizeof (CRTerm));
+ return result;
+}
+
+/**
+ *Parses an expression as defined by the css2 spec
+ *and builds the expression as a list of terms.
+ *@param a_buf the buffer to parse.
+ *@return a pointer to the first term of the expression or
+ *NULL if parsing failed.
+ */
+CRTerm *
+cr_term_parse_expression_from_buf (const guchar * a_buf,
+ enum CREncoding a_encoding)
+{
+ CRParser *parser = NULL;
+ CRTerm *result = NULL;
+ enum CRStatus status = CR_OK;
+
+ g_return_val_if_fail (a_buf, NULL);
+
+ parser = cr_parser_new_from_buf ((guchar*)a_buf, strlen ((const char *) a_buf),
+ a_encoding, FALSE);
+ g_return_val_if_fail (parser, NULL);
+
+ status = cr_parser_try_to_skip_spaces_and_comments (parser);
+ if (status != CR_OK) {
+ goto cleanup;
+ }
+ status = cr_parser_parse_expr (parser, &result);
+ if (status != CR_OK) {
+ if (result) {
+ cr_term_destroy (result);
+ result = NULL;
+ }
+ }
+
+ cleanup:
+ if (parser) {
+ cr_parser_destroy (parser);
+ parser = NULL;
+ }
+
+ return result;
+}
+
+enum CRStatus
+cr_term_set_number (CRTerm * a_this, CRNum * a_num)
+{
+ g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR);
+
+ cr_term_clear (a_this);
+
+ a_this->type = TERM_NUMBER;
+ a_this->content.num = a_num;
+ return CR_OK;
+}
+
+enum CRStatus
+cr_term_set_function (CRTerm * a_this, CRString * a_func_name,
+ CRTerm * a_func_param)
+{
+ g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR);
+
+ cr_term_clear (a_this);
+
+ a_this->type = TERM_FUNCTION;
+ a_this->content.str = a_func_name;
+ a_this->ext_content.func_param = a_func_param;
+ return CR_OK;
+}
+
+enum CRStatus
+cr_term_set_string (CRTerm * a_this, CRString * a_str)
+{
+ g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR);
+
+ cr_term_clear (a_this);
+
+ a_this->type = TERM_STRING;
+ a_this->content.str = a_str;
+ return CR_OK;
+}
+
+enum CRStatus
+cr_term_set_ident (CRTerm * a_this, CRString * a_str)
+{
+ g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR);
+
+ cr_term_clear (a_this);
+
+ a_this->type = TERM_IDENT;
+ a_this->content.str = a_str;
+ return CR_OK;
+}
+
+enum CRStatus
+cr_term_set_uri (CRTerm * a_this, CRString * a_str)
+{
+ g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR);
+
+ cr_term_clear (a_this);
+
+ a_this->type = TERM_URI;
+ a_this->content.str = a_str;
+ return CR_OK;
+}
+
+enum CRStatus
+cr_term_set_rgb (CRTerm * a_this, CRRgb * a_rgb)
+{
+ g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR);
+
+ cr_term_clear (a_this);
+
+ a_this->type = TERM_RGB;
+ a_this->content.rgb = a_rgb;
+ return CR_OK;
+}
+
+enum CRStatus
+cr_term_set_hash (CRTerm * a_this, CRString * a_str)
+{
+ g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR);
+
+ cr_term_clear (a_this);
+
+ a_this->type = TERM_HASH;
+ a_this->content.str = a_str;
+ return CR_OK;
+}
+
+/**
+ *Appends a new term to the current list of #CRTerm.
+ *
+ *@param a_this the "this pointer" of the current instance
+ *of #CRTerm .
+ *@param a_new_term the term to append.
+ *@return the list of terms with the a_new_term appended to it.
+ */
+CRTerm *
+cr_term_append_term (CRTerm * a_this, CRTerm * a_new_term)
+{
+ CRTerm *cur = NULL;
+
+ g_return_val_if_fail (a_new_term, NULL);
+
+ if (a_this == NULL)
+ return a_new_term;
+
+ for (cur = a_this; cur->next; cur = cur->next) ;
+
+ cur->next = a_new_term;
+ a_new_term->prev = cur;
+
+ return a_this;
+}
+
+/**
+ *Prepends a term to the list of terms represented by a_this.
+ *
+ *@param a_this the "this pointer" of the current instance of
+ *#CRTerm .
+ *@param a_new_term the term to prepend.
+ *@return the head of the new list.
+ */
+CRTerm *
+cr_term_prepend_term (CRTerm * a_this, CRTerm * a_new_term)
+{
+ g_return_val_if_fail (a_this && a_new_term, NULL);
+
+ a_new_term->next = a_this;
+ a_this->prev = a_new_term;
+
+ return a_new_term;
+}
+
+/**
+ *Serializes the expression represented by
+ *the chained instances of #CRterm.
+ *@param a_this the current instance of #CRTerm
+ *@return the zero terminated string containing the serialized
+ *form of #CRTerm. MUST BE FREED BY THE CALLER using g_free().
+ */
+guchar *
+cr_term_to_string (CRTerm const * a_this)
+{
+ GString *str_buf = NULL;
+ CRTerm const *cur = NULL;
+ guchar *result = NULL,
+ *content = NULL;
+
+ g_return_val_if_fail (a_this, NULL);
+
+ str_buf = g_string_new (NULL);
+ g_return_val_if_fail (str_buf, NULL);
+
+ for (cur = a_this; cur; cur = cur->next) {
+ if ((cur->content.str == NULL)
+ && (cur->content.num == NULL)
+ && (cur->content.rgb == NULL))
+ continue;
+
+ switch (cur->the_operator) {
+ case DIVIDE:
+ g_string_append (str_buf, " / ");
+ break;
+
+ case COMMA:
+ g_string_append (str_buf, ", ");
+ break;
+
+ case NO_OP:
+ if (cur->prev) {
+ g_string_append (str_buf, " ");
+ }
+ break;
+ default:
+
+ break;
+ }
+
+ switch (cur->unary_op) {
+ case PLUS_UOP:
+ g_string_append (str_buf, "+");
+ break;
+
+ case MINUS_UOP:
+ g_string_append (str_buf, "-");
+ break;
+
+ default:
+ break;
+ }
+
+ switch (cur->type) {
+ case TERM_NUMBER:
+ if (cur->content.num) {
+ content = cr_num_to_string (cur->content.num);
+ }
+
+ if (content) {
+ g_string_append (str_buf, (const gchar *) content);
+ g_free (content);
+ content = NULL;
+ }
+
+ break;
+
+ case TERM_FUNCTION:
+ if (cur->content.str) {
+ content = (guchar *) g_strndup
+ (cur->content.str->stryng->str,
+ cur->content.str->stryng->len);
+ }
+
+ if (content) {
+ g_string_append_printf (str_buf, "%s(",
+ content);
+
+ if (cur->ext_content.func_param) {
+ guchar *tmp_str = NULL;
+
+ tmp_str = cr_term_to_string
+ (cur->
+ ext_content.func_param);
+
+ if (tmp_str) {
+ g_string_append (str_buf,
+ (const gchar *) tmp_str);
+ g_free (tmp_str);
+ tmp_str = NULL;
+ }
+ }
+ g_string_append (str_buf, ")");
+ g_free (content);
+ content = NULL;
+ }
+
+ break;
+
+ case TERM_STRING:
+ if (cur->content.str) {
+ content = (guchar *) g_strndup
+ (cur->content.str->stryng->str,
+ cur->content.str->stryng->len);
+ }
+
+ if (content) {
+ g_string_append_printf (str_buf,
+ "\"%s\"", content);
+ g_free (content);
+ content = NULL;
+ }
+ break;
+
+ case TERM_IDENT:
+ if (cur->content.str) {
+ content = (guchar *) g_strndup
+ (cur->content.str->stryng->str,
+ cur->content.str->stryng->len);
+ }
+
+ if (content) {
+ g_string_append (str_buf, (const gchar *) content);
+ g_free (content);
+ content = NULL;
+ }
+ break;
+
+ case TERM_URI:
+ if (cur->content.str) {
+ content = (guchar *) g_strndup
+ (cur->content.str->stryng->str,
+ cur->content.str->stryng->len);
+ }
+
+ if (content) {
+ g_string_append_printf
+ (str_buf, "url(%s)", content);
+ g_free (content);
+ content = NULL;
+ }
+ break;
+
+ case TERM_RGB:
+ if (cur->content.rgb) {
+ guchar *tmp_str = NULL;
+
+ g_string_append (str_buf, "rgb(");
+ tmp_str = cr_rgb_to_string (cur->content.rgb);
+
+ if (tmp_str) {
+ g_string_append (str_buf, (const gchar *) tmp_str);
+ g_free (tmp_str);
+ tmp_str = NULL;
+ }
+ g_string_append (str_buf, ")");
+ }
+
+ break;
+
+ case TERM_UNICODERANGE:
+ g_string_append
+ (str_buf,
+ "?found unicoderange: dump not supported yet?");
+ break;
+
+ case TERM_HASH:
+ if (cur->content.str) {
+ content = (guchar *) g_strndup
+ (cur->content.str->stryng->str,
+ cur->content.str->stryng->len);
+ }
+
+ if (content) {
+ g_string_append_printf (str_buf,
+ "#%s", content);
+ g_free (content);
+ content = NULL;
+ }
+ break;
+
+ default:
+ g_string_append (str_buf,
+ "Unrecognized Term type");
+ break;
+ }
+ }
+
+ if (str_buf) {
+ result = (guchar *) g_string_free (str_buf, FALSE);
+ str_buf = NULL;
+ }
+
+ return result;
+}
+
+guchar *
+cr_term_one_to_string (CRTerm const * a_this)
+{
+ GString *str_buf = NULL;
+ guchar *result = NULL,
+ *content = NULL;
+
+ g_return_val_if_fail (a_this, NULL);
+
+ str_buf = g_string_new (NULL);
+ g_return_val_if_fail (str_buf, NULL);
+
+ if ((a_this->content.str == NULL)
+ && (a_this->content.num == NULL)
+ && (a_this->content.rgb == NULL))
+ return NULL ;
+
+ switch (a_this->the_operator) {
+ case DIVIDE:
+ g_string_append_printf (str_buf, " / ");
+ break;
+
+ case COMMA:
+ g_string_append_printf (str_buf, ", ");
+ break;
+
+ case NO_OP:
+ if (a_this->prev) {
+ g_string_append_printf (str_buf, " ");
+ }
+ break;
+ default:
+
+ break;
+ }
+
+ switch (a_this->unary_op) {
+ case PLUS_UOP:
+ g_string_append_printf (str_buf, "+");
+ break;
+
+ case MINUS_UOP:
+ g_string_append_printf (str_buf, "-");
+ break;
+
+ default:
+ break;
+ }
+
+ switch (a_this->type) {
+ case TERM_NUMBER:
+ if (a_this->content.num) {
+ content = cr_num_to_string (a_this->content.num);
+ }
+
+ if (content) {
+ g_string_append (str_buf, (const gchar *) content);
+ g_free (content);
+ content = NULL;
+ }
+
+ break;
+
+ case TERM_FUNCTION:
+ if (a_this->content.str) {
+ content = (guchar *) g_strndup
+ (a_this->content.str->stryng->str,
+ a_this->content.str->stryng->len);
+ }
+
+ if (content) {
+ g_string_append_printf (str_buf, "%s(",
+ content);
+
+ if (a_this->ext_content.func_param) {
+ guchar *tmp_str = NULL;
+
+ tmp_str = cr_term_to_string
+ (a_this->
+ ext_content.func_param);
+
+ if (tmp_str) {
+ g_string_append_printf
+ (str_buf,
+ "%s", tmp_str);
+ g_free (tmp_str);
+ tmp_str = NULL;
+ }
+
+ g_string_append_printf (str_buf, ")");
+ g_free (content);
+ content = NULL;
+ }
+ }
+
+ break;
+
+ case TERM_STRING:
+ if (a_this->content.str) {
+ content = (guchar *) g_strndup
+ (a_this->content.str->stryng->str,
+ a_this->content.str->stryng->len);
+ }
+
+ if (content) {
+ g_string_append_printf (str_buf,
+ "\"%s\"", content);
+ g_free (content);
+ content = NULL;
+ }
+ break;
+
+ case TERM_IDENT:
+ if (a_this->content.str) {
+ content = (guchar *) g_strndup
+ (a_this->content.str->stryng->str,
+ a_this->content.str->stryng->len);
+ }
+
+ if (content) {
+ g_string_append (str_buf, (const gchar *) content);
+ g_free (content);
+ content = NULL;
+ }
+ break;
+
+ case TERM_URI:
+ if (a_this->content.str) {
+ content = (guchar *) g_strndup
+ (a_this->content.str->stryng->str,
+ a_this->content.str->stryng->len);
+ }
+
+ if (content) {
+ g_string_append_printf
+ (str_buf, "url(%s)", content);
+ g_free (content);
+ content = NULL;
+ }
+ break;
+
+ case TERM_RGB:
+ if (a_this->content.rgb) {
+ guchar *tmp_str = NULL;
+
+ g_string_append_printf (str_buf, "rgb(");
+ tmp_str = cr_rgb_to_string (a_this->content.rgb);
+
+ if (tmp_str) {
+ g_string_append (str_buf, (const gchar *) tmp_str);
+ g_free (tmp_str);
+ tmp_str = NULL;
+ }
+ g_string_append_printf (str_buf, ")");
+ }
+
+ break;
+
+ case TERM_UNICODERANGE:
+ g_string_append_printf
+ (str_buf,
+ "?found unicoderange: dump not supported yet?");
+ break;
+
+ case TERM_HASH:
+ if (a_this->content.str) {
+ content = (guchar *) g_strndup
+ (a_this->content.str->stryng->str,
+ a_this->content.str->stryng->len);
+ }
+
+ if (content) {
+ g_string_append_printf (str_buf,
+ "#%s", content);
+ g_free (content);
+ content = NULL;
+ }
+ break;
+
+ default:
+ g_string_append_printf (str_buf,
+ "%s",
+ "Unrecognized Term type");
+ break;
+ }
+
+ if (str_buf) {
+ result = (guchar *) g_string_free (str_buf, FALSE);
+ str_buf = NULL;
+ }
+
+ return result;
+}
+
+/**
+ *Dumps the expression (a list of terms connected by operators)
+ *to a file.
+ *TODO: finish the dump. The dump of some type of terms have not yet been
+ *implemented.
+ *@param a_this the current instance of #CRTerm.
+ *@param a_fp the destination file pointer.
+ */
+void
+cr_term_dump (CRTerm const * a_this, FILE * a_fp)
+{
+ guchar *content = NULL;
+
+ g_return_if_fail (a_this);
+
+ content = cr_term_to_string (a_this);
+
+ if (content) {
+ fprintf (a_fp, "%s", content);
+ g_free (content);
+ }
+}
+
+/**
+ *Return the number of terms in the expression.
+ *@param a_this the current instance of #CRTerm.
+ *@return number of terms in the expression.
+ */
+int
+cr_term_nr_values (CRTerm const *a_this)
+{
+ CRTerm const *cur = NULL ;
+ int nr = 0;
+
+ g_return_val_if_fail (a_this, -1) ;
+
+ for (cur = a_this ; cur ; cur = cur->next)
+ nr ++;
+ return nr;
+}
+
+/**
+ *Use an index to get a CRTerm from the expression.
+ *@param a_this the current instance of #CRTerm.
+ *@param itemnr the index into the expression.
+ *@return CRTerm at position itemnr, if itemnr > number of terms - 1,
+ *it will return NULL.
+ */
+CRTerm *
+cr_term_get_from_list (CRTerm *a_this, int itemnr)
+{
+ CRTerm *cur = NULL ;
+ int nr = 0;
+
+ g_return_val_if_fail (a_this, NULL) ;
+
+ for (cur = a_this ; cur ; cur = cur->next)
+ if (nr++ == itemnr)
+ return cur;
+ return NULL;
+}
+
+/**
+ *Increments the reference counter of the current instance
+ *of #CRTerm.*
+ *@param a_this the current instance of #CRTerm.
+ */
+void
+cr_term_ref (CRTerm * a_this)
+{
+ g_return_if_fail (a_this);
+
+ a_this->ref_count++;
+}
+
+/**
+ *Decrements the ref count of the current instance of
+ *#CRTerm. If the ref count reaches zero, the instance is
+ *destroyed.
+ *@param a_this the current instance of #CRTerm.
+ *@return TRUE if the current instance has been destroyed, FALSE otherwise.
+ */
+gboolean
+cr_term_unref (CRTerm * a_this)
+{
+ g_return_val_if_fail (a_this, FALSE);
+
+ if (a_this->ref_count) {
+ a_this->ref_count--;
+ }
+
+ if (a_this->ref_count == 0) {
+ cr_term_destroy (a_this);
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+/**
+ *The destructor of the the #CRTerm class.
+ *@param a_this the "this pointer" of the current instance
+ *of #CRTerm.
+ */
+void
+cr_term_destroy (CRTerm * a_this)
+{
+ g_return_if_fail (a_this);
+
+ cr_term_clear (a_this);
+
+ if (a_this->next) {
+ cr_term_destroy (a_this->next);
+ a_this->next = NULL;
+ }
+
+ if (a_this) {
+ g_free (a_this);
+ }
+
+}
diff --git a/src/st/croco/cr-term.h b/src/st/croco/cr-term.h
new file mode 100644
index 0000000..0f22dda
--- /dev/null
+++ b/src/st/croco/cr-term.h
@@ -0,0 +1,190 @@
+/* -*- Mode: C; indent-tabs-mode:nil; c-basic-offset: 8-*- */
+
+/*
+ * This file is part of The Croco Library
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2.1 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * 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 Lesser General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ *
+ * Author: Dodji Seketeli
+ * See COPYRIGHTS file for copyright information.
+ */
+
+#include <stdio.h>
+#include <glib.h>
+#include "cr-utils.h"
+#include "cr-rgb.h"
+#include "cr-num.h"
+#include "cr-string.h"
+
+#ifndef __CR_TERM_H__
+#define __CR_TERM_H__
+
+G_BEGIN_DECLS
+
+/**
+ *@file
+ *Declaration of the #CRTem class.
+ */
+
+enum CRTermType
+{
+ TERM_NO_TYPE = 0,
+ TERM_NUMBER,
+ TERM_FUNCTION,
+ TERM_STRING,
+ TERM_IDENT,
+ TERM_URI,
+ TERM_RGB,
+ TERM_UNICODERANGE,
+ TERM_HASH
+} ;
+
+
+enum UnaryOperator
+{
+ NO_UNARY_UOP = 0,
+ PLUS_UOP,
+ MINUS_UOP,
+ EMPTY_UNARY_UOP
+} ;
+
+enum Operator
+{
+ NO_OP = 0,
+ DIVIDE,
+ COMMA
+} ;
+
+struct _CRTerm ;
+typedef struct _CRTerm CRTerm ;
+
+/**
+ *An abstraction of a css2 term as
+ *defined in the CSS2 spec in appendix D.1:
+ *term ::=
+ *[ NUMBER S* | PERCENTAGE S* | LENGTH S* | EMS S* | EXS S*
+ *| ANGLE S* | TIME S* | FREQ S* | function ]
+ * | STRING S* | IDENT S* | URI S* | RGB S*
+ *| UNICODERANGE S* | hexcolor
+ */
+struct _CRTerm
+{
+ /**
+ *The type of the term.
+ */
+ enum CRTermType type ;
+
+ /**
+ *The unary operator associated to
+ *the current term.
+ */
+ enum UnaryOperator unary_op ;
+
+ /**
+ *The operator associated to the current term.
+ */
+ enum Operator the_operator ;
+
+
+ /**
+ *The content of the term.
+ *Depending of the type of the term,
+ *this holds either a number, a percentage ...
+ */
+ union
+ {
+ CRNum *num ;
+ CRString * str ;
+ CRRgb * rgb ;
+ } content ;
+
+ /**
+ *If the term is of type UNICODERANGE,
+ *this field holds the upper bound of the range.
+ *if the term is of type FUNCTION, this holds
+ *an instance of CRTerm that represents
+ * the expression which is the argument of the function.
+ */
+ union
+ {
+ CRTerm *func_param ;
+ } ext_content ;
+
+ /**
+ *A spare pointer, just in case.
+ *Can be used by the application.
+ */
+ gpointer app_data ;
+
+ glong ref_count ;
+
+ /**
+ *A pointer to the next term,
+ *just in case this term is part of
+ *an expression.
+ */
+ CRTerm *next ;
+
+ /**
+ *A pointer to the previous
+ *term.
+ */
+ CRTerm *prev ;
+ CRParsingLocation location ;
+} ;
+
+CRTerm * cr_term_parse_expression_from_buf (const guchar *a_buf,
+ enum CREncoding a_encoding) ;
+CRTerm * cr_term_new (void) ;
+
+enum CRStatus cr_term_set_number (CRTerm *a_this, CRNum *a_num) ;
+
+enum CRStatus cr_term_set_function (CRTerm *a_this,
+ CRString *a_func_name,
+ CRTerm *a_func_param) ;
+
+enum CRStatus cr_term_set_string (CRTerm *a_this, CRString *a_str) ;
+
+enum CRStatus cr_term_set_ident (CRTerm *a_this, CRString *a_str) ;
+
+enum CRStatus cr_term_set_uri (CRTerm *a_this, CRString *a_str) ;
+
+enum CRStatus cr_term_set_rgb (CRTerm *a_this, CRRgb *a_rgb) ;
+
+enum CRStatus cr_term_set_hash (CRTerm *a_this, CRString *a_str) ;
+
+CRTerm * cr_term_append_term (CRTerm *a_this, CRTerm *a_new_term) ;
+
+CRTerm * cr_term_prepend_term (CRTerm *a_this, CRTerm *a_new_term) ;
+
+guchar * cr_term_to_string (CRTerm const *a_this) ;
+
+guchar * cr_term_one_to_string (CRTerm const * a_this) ;
+
+void cr_term_dump (CRTerm const *a_this, FILE *a_fp) ;
+
+int cr_term_nr_values (CRTerm const *a_this) ;
+
+CRTerm * cr_term_get_from_list (CRTerm *a_this, int itemnr) ;
+
+void cr_term_ref (CRTerm *a_this) ;
+
+gboolean cr_term_unref (CRTerm *a_this) ;
+
+void cr_term_destroy (CRTerm * a_term) ;
+
+G_END_DECLS
+
+#endif /*__CR_TERM_H__*/
diff --git a/src/st/croco/cr-tknzr.c b/src/st/croco/cr-tknzr.c
new file mode 100644
index 0000000..54f18f2
--- /dev/null
+++ b/src/st/croco/cr-tknzr.c
@@ -0,0 +1,2762 @@
+/* -*- Mode: C; indent-tabs-mode:nil; c-basic-offset: 8-*- */
+
+/*
+ * This file is part of The Croco Library
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2.1 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * 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 Lesser General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ *
+ * Author: Dodji Seketeli
+ * See the COPYRIGHTS file for copyrights information.
+ */
+
+/**
+ *@file
+ *The definition of the #CRTknzr (tokenizer)
+ *class.
+ */
+
+#include "string.h"
+#include "cr-tknzr.h"
+#include "cr-doc-handler.h"
+
+struct _CRTknzrPriv {
+ /**The parser input stream of bytes*/
+ CRInput *input;
+
+ /**
+ *A cache where tknzr_unget_token()
+ *puts back the token. tknzr_get_next_token()
+ *first look in this cache, and if and
+ *only if it's empty, fetches the next token
+ *from the input stream.
+ */
+ CRToken *token_cache;
+
+ /**
+ *The position of the end of the previous token
+ *or char fetched.
+ */
+ CRInputPos prev_pos;
+
+ CRDocHandler *sac_handler;
+
+ /**
+ *The reference count of the current instance
+ *of #CRTknzr. Is manipulated by cr_tknzr_ref()
+ *and cr_tknzr_unref().
+ */
+ glong ref_count;
+};
+
+#define PRIVATE(obj) ((obj)->priv)
+
+/**
+ *return TRUE if the character is a number ([0-9]), FALSE otherwise
+ *@param a_char the char to test.
+ */
+#define IS_NUM(a_char) (((a_char) >= '0' && (a_char) <= '9')?TRUE:FALSE)
+
+/**
+ *Checks if 'status' equals CR_OK. If not, goto the 'error' label.
+ *
+ *@param status the status (of type enum CRStatus) to test.
+ *@param is_exception if set to FALSE, the final status returned the
+ *current function will be CR_PARSING_ERROR. If set to TRUE, the
+ *current status will be the current value of the 'status' variable.
+ *
+ */
+#define CHECK_PARSING_STATUS(status, is_exception) \
+if ((status) != CR_OK) \
+{ \
+ if (is_exception == FALSE) \
+ { \
+ status = CR_PARSING_ERROR ; \
+ } \
+ goto error ; \
+}
+
+/**
+ *Peeks the next char from the input stream of the current tokenizer.
+ *invokes CHECK_PARSING_STATUS on the status returned by
+ *cr_tknzr_input_peek_char().
+ *
+ *@param the current instance of #CRTkzr.
+ *@param to_char a pointer to the char where to store the
+ *char peeked.
+ */
+#define PEEK_NEXT_CHAR(a_tknzr, a_to_char) \
+{\
+status = cr_tknzr_peek_char (a_tknzr, a_to_char) ; \
+CHECK_PARSING_STATUS (status, TRUE) \
+}
+
+/**
+ *Reads the next char from the input stream of the current parser.
+ *In case of error, jumps to the "error:" label located in the
+ *function where this macro is called.
+ *@param parser the current instance of #CRTknzr
+ *@param to_char a pointer to the guint32 char where to store
+ *the character read.
+ */
+#define READ_NEXT_CHAR(a_tknzr, to_char) \
+status = cr_tknzr_read_char (a_tknzr, to_char) ;\
+CHECK_PARSING_STATUS (status, TRUE)
+
+/**
+ *Gets information about the current position in
+ *the input of the parser.
+ *In case of failure, this macro returns from the
+ *calling function and
+ *returns a status code of type enum #CRStatus.
+ *@param parser the current instance of #CRTknzr.
+ *@param pos out parameter. A pointer to the position
+ *inside the current parser input. Must
+ */
+#define RECORD_INITIAL_POS(a_tknzr, a_pos) \
+status = cr_input_get_cur_pos (PRIVATE \
+(a_tknzr)->input, a_pos) ; \
+g_return_val_if_fail (status == CR_OK, status)
+
+/**
+ *Gets the address of the current byte inside the
+ *parser input.
+ *@param parser the current instance of #CRTknzr.
+ *@param addr out parameter a pointer (guchar*)
+ *to where the address must be put.
+ */
+#define RECORD_CUR_BYTE_ADDR(a_tknzr, a_addr) \
+status = cr_input_get_cur_byte_addr \
+ (PRIVATE (a_tknzr)->input, a_addr) ; \
+CHECK_PARSING_STATUS (status, TRUE)
+
+/**
+ *Peeks a byte from the topmost parser input at
+ *a given offset from the current position.
+ *If it fails, goto the "error:" label.
+ *
+ *@param a_parser the current instance of #CRTknzr.
+ *@param a_offset the offset of the byte to peek, the
+ *current byte having the offset '0'.
+ *@param a_byte_ptr out parameter a pointer (guchar*) to
+ *where the peeked char is to be stored.
+ */
+#define PEEK_BYTE(a_tknzr, a_offset, a_byte_ptr) \
+status = cr_tknzr_peek_byte (a_tknzr, \
+ a_offset, \
+ a_byte_ptr) ; \
+CHECK_PARSING_STATUS (status, TRUE) ;
+
+#define BYTE(a_input, a_n, a_eof) \
+cr_input_peek_byte2 (a_input, a_n, a_eof)
+
+/**
+ *Reads a byte from the topmost parser input
+ *steam.
+ *If it fails, goto the "error" label.
+ *@param a_parser the current instance of #CRTknzr.
+ *@param a_byte_ptr the guchar * where to put the read char.
+ */
+#define READ_NEXT_BYTE(a_tknzr, a_byte_ptr) \
+status = \
+cr_input_read_byte (PRIVATE (a_tknzr)->input, a_byte_ptr) ;\
+CHECK_PARSING_STATUS (status, TRUE) ;
+
+/**
+ *Skips a given number of byte in the topmost
+ *parser input. Don't update line and column number.
+ *In case of error, jumps to the "error:" label
+ *of the surrounding function.
+ *@param a_parser the current instance of #CRTknzr.
+ *@param a_nb_bytes the number of bytes to skip.
+ */
+#define SKIP_BYTES(a_tknzr, a_nb_bytes) \
+status = cr_input_seek_index (PRIVATE (a_tknzr)->input, \
+ CR_SEEK_CUR, a_nb_bytes) ; \
+CHECK_PARSING_STATUS (status, TRUE) ;
+
+/**
+ *Skip utf8 encoded characters.
+ *Updates line and column numbers.
+ *@param a_parser the current instance of #CRTknzr.
+ *@param a_nb_chars the number of chars to skip. Must be of
+ *type glong.
+ */
+#define SKIP_CHARS(a_tknzr, a_nb_chars) \
+{ \
+gulong nb_chars = a_nb_chars ; \
+status = cr_input_consume_chars \
+ (PRIVATE (a_tknzr)->input,0, &nb_chars) ; \
+CHECK_PARSING_STATUS (status, TRUE) ; \
+}
+
+/**
+ *Tests the condition and if it is false, sets
+ *status to "CR_PARSING_ERROR" and goto the 'error'
+ *label.
+ *@param condition the condition to test.
+ */
+#define ENSURE_PARSING_COND(condition) \
+if (! (condition)) {status = CR_PARSING_ERROR; goto error ;}
+
+static enum CRStatus cr_tknzr_parse_nl (CRTknzr * a_this,
+ guchar ** a_start,
+ guchar ** a_end,
+ CRParsingLocation *a_location);
+
+static enum CRStatus cr_tknzr_parse_w (CRTknzr * a_this,
+ guchar ** a_start,
+ guchar ** a_end,
+ CRParsingLocation *a_location) ;
+
+static enum CRStatus cr_tknzr_parse_unicode_escape (CRTknzr * a_this,
+ guint32 * a_unicode,
+ CRParsingLocation *a_location) ;
+
+static enum CRStatus cr_tknzr_parse_escape (CRTknzr * a_this,
+ guint32 * a_esc_code,
+ CRParsingLocation *a_location);
+
+static enum CRStatus cr_tknzr_parse_string (CRTknzr * a_this,
+ CRString ** a_str);
+
+static enum CRStatus cr_tknzr_parse_comment (CRTknzr * a_this,
+ CRString ** a_comment);
+
+static enum CRStatus cr_tknzr_parse_nmstart (CRTknzr * a_this,
+ guint32 * a_char,
+ CRParsingLocation *a_location);
+
+static enum CRStatus cr_tknzr_parse_num (CRTknzr * a_this,
+ CRNum ** a_num);
+
+/**********************************
+ *PRIVATE methods
+ **********************************/
+
+/**
+ *Parses a "w" as defined by the css spec at [4.1.1]:
+ * w ::= [ \t\r\n\f]*
+ *
+ *@param a_this the current instance of #CRTknzr.
+ *@param a_start out param. Upon successful completion, points
+ *to the beginning of the parsed white space, points to NULL otherwise.
+ *Can also point to NULL is there is no white space actually.
+ *@param a_end out param. Upon successful completion, points
+ *to the end of the parsed white space, points to NULL otherwise.
+ *Can also point to NULL is there is no white space actually.
+ */
+static enum CRStatus
+cr_tknzr_parse_w (CRTknzr * a_this,
+ guchar ** a_start,
+ guchar ** a_end,
+ CRParsingLocation *a_location)
+{
+ guint32 cur_char = 0;
+ CRInputPos init_pos;
+ enum CRStatus status = CR_OK;
+
+ g_return_val_if_fail (a_this && PRIVATE (a_this)
+ && PRIVATE (a_this)->input
+ && a_start && a_end,
+ CR_BAD_PARAM_ERROR);
+
+ RECORD_INITIAL_POS (a_this, &init_pos);
+
+ *a_start = NULL;
+ *a_end = NULL;
+
+ READ_NEXT_CHAR (a_this, &cur_char);
+
+ if (cr_utils_is_white_space (cur_char) == FALSE) {
+ status = CR_PARSING_ERROR;
+ goto error;
+ }
+ if (a_location) {
+ cr_tknzr_get_parsing_location (a_this,
+ a_location) ;
+ }
+ RECORD_CUR_BYTE_ADDR (a_this, a_start);
+ *a_end = *a_start;
+
+ for (;;) {
+ gboolean is_eof = FALSE;
+
+ cr_input_get_end_of_file (PRIVATE (a_this)->input, &is_eof);
+ if (is_eof)
+ break;
+
+ status = cr_tknzr_peek_char (a_this, &cur_char);
+ if (status == CR_END_OF_INPUT_ERROR) {
+ break;
+ } else if (status != CR_OK) {
+ goto error;
+ }
+
+ if (cr_utils_is_white_space (cur_char) == TRUE) {
+ READ_NEXT_CHAR (a_this, &cur_char);
+ RECORD_CUR_BYTE_ADDR (a_this, a_end);
+ } else {
+ break;
+ }
+ }
+
+ return CR_OK;
+
+ error:
+ cr_tknzr_set_cur_pos (a_this, &init_pos);
+
+ return status;
+}
+
+/**
+ *Parses a newline as defined in the css2 spec:
+ * nl ::= \n|\r\n|\r|\f
+ *
+ *@param a_this the "this pointer" of the current instance of #CRTknzr.
+ *@param a_start a pointer to the first character of the successfully
+ *parsed string.
+ *@param a_end a pointer to the last character of the successfully parsed
+ *string.
+ *@result CR_OK upon successful completion, an error code otherwise.
+ */
+static enum CRStatus
+cr_tknzr_parse_nl (CRTknzr * a_this,
+ guchar ** a_start,
+ guchar ** a_end,
+ CRParsingLocation *a_location)
+{
+ CRInputPos init_pos;
+ guchar next_chars[2] = { 0 };
+ enum CRStatus status = CR_PARSING_ERROR;
+
+ g_return_val_if_fail (a_this && PRIVATE (a_this)
+ && a_start && a_end, CR_BAD_PARAM_ERROR);
+
+ RECORD_INITIAL_POS (a_this, &init_pos);
+
+ PEEK_BYTE (a_this, 1, &next_chars[0]);
+ PEEK_BYTE (a_this, 2, &next_chars[1]);
+
+ if ((next_chars[0] == '\r' && next_chars[1] == '\n')) {
+ SKIP_BYTES (a_this, 1);
+ if (a_location) {
+ cr_tknzr_get_parsing_location
+ (a_this, a_location) ;
+ }
+ SKIP_CHARS (a_this, 1);
+
+ RECORD_CUR_BYTE_ADDR (a_this, a_end);
+
+ status = CR_OK;
+ } else if (next_chars[0] == '\n'
+ || next_chars[0] == '\r' || next_chars[0] == '\f') {
+ SKIP_CHARS (a_this, 1);
+ if (a_location) {
+ cr_tknzr_get_parsing_location
+ (a_this, a_location) ;
+ }
+ RECORD_CUR_BYTE_ADDR (a_this, a_start);
+ *a_end = *a_start;
+ status = CR_OK;
+ } else {
+ status = CR_PARSING_ERROR;
+ goto error;
+ }
+ return CR_OK ;
+
+ error:
+ cr_tknzr_set_cur_pos (a_this, &init_pos) ;
+ return status;
+}
+
+/**
+ *Go ahead in the parser input, skipping all the spaces.
+ *If the next char if not a white space, this function does nothing.
+ *In any cases, it stops when it encounters a non white space character.
+ *
+ *@param a_this the current instance of #CRTknzr.
+ *@return CR_OK upon successful completion, an error code otherwise.
+ */
+static enum CRStatus
+cr_tknzr_try_to_skip_spaces (CRTknzr * a_this)
+{
+ enum CRStatus status = CR_ERROR;
+ guint32 cur_char = 0;
+
+ g_return_val_if_fail (a_this && PRIVATE (a_this)
+ && PRIVATE (a_this)->input, CR_BAD_PARAM_ERROR);
+
+ status = cr_input_peek_char (PRIVATE (a_this)->input, &cur_char);
+
+ if (status != CR_OK) {
+ if (status == CR_END_OF_INPUT_ERROR)
+ return CR_OK;
+ return status;
+ }
+
+ if (cr_utils_is_white_space (cur_char) == TRUE) {
+ gulong nb_chars = -1; /*consume all spaces */
+
+ status = cr_input_consume_white_spaces
+ (PRIVATE (a_this)->input, &nb_chars);
+ }
+
+ return status;
+}
+
+/**
+ *Parses a "comment" as defined in the css spec at [4.1.1]:
+ *COMMENT ::= \/\*[^*]*\*+([^/][^*]*\*+)*\/ .
+ *This complex regexp is just to say that comments start
+ *with the two chars '/''*' and ends with the two chars '*''/'.
+ *It also means that comments cannot be nested.
+ *So based on that, I've just tried to implement the parsing function
+ *simply and in a straight forward manner.
+ */
+static enum CRStatus
+cr_tknzr_parse_comment (CRTknzr * a_this,
+ CRString ** a_comment)
+{
+ enum CRStatus status = CR_OK;
+ CRInputPos init_pos;
+ guint32 cur_char = 0, next_char= 0;
+ CRString *comment = NULL;
+ CRParsingLocation loc = {0} ;
+
+ g_return_val_if_fail (a_this && PRIVATE (a_this)
+ && PRIVATE (a_this)->input,
+ CR_BAD_PARAM_ERROR);
+
+ RECORD_INITIAL_POS (a_this, &init_pos);
+ READ_NEXT_CHAR (a_this, &cur_char) ;
+ ENSURE_PARSING_COND (cur_char == '/');
+ cr_tknzr_get_parsing_location (a_this, &loc) ;
+
+ READ_NEXT_CHAR (a_this, &cur_char);
+ ENSURE_PARSING_COND (cur_char == '*');
+ comment = cr_string_new ();
+ for (;;) { /* [^*]* */
+ PEEK_NEXT_CHAR (a_this, &next_char);
+ if (next_char == '*')
+ break;
+ READ_NEXT_CHAR (a_this, &cur_char);
+ g_string_append_unichar (comment->stryng, cur_char);
+ }
+ /* Stop condition: next_char == '*' */
+ for (;;) { /* \*+ */
+ READ_NEXT_CHAR(a_this, &cur_char);
+ ENSURE_PARSING_COND (cur_char == '*');
+ g_string_append_unichar (comment->stryng, cur_char);
+ PEEK_NEXT_CHAR (a_this, &next_char);
+ if (next_char != '*')
+ break;
+ }
+ /* Stop condition: next_char != '*' */
+ for (;;) { /* ([^/][^*]*\*+)* */
+ if (next_char == '/')
+ break;
+ READ_NEXT_CHAR(a_this, &cur_char);
+ g_string_append_unichar (comment->stryng, cur_char);
+ for (;;) { /* [^*]* */
+ PEEK_NEXT_CHAR (a_this, &next_char);
+ if (next_char == '*')
+ break;
+ READ_NEXT_CHAR (a_this, &cur_char);
+ g_string_append_unichar (comment->stryng, cur_char);
+ }
+ /* Stop condition: next_char = '*', no need to verify, because peek and read exit to error anyway */
+ for (;;) { /* \*+ */
+ READ_NEXT_CHAR(a_this, &cur_char);
+ ENSURE_PARSING_COND (cur_char == '*');
+ g_string_append_unichar (comment->stryng, cur_char);
+ PEEK_NEXT_CHAR (a_this, &next_char);
+ if (next_char != '*')
+ break;
+ }
+ /* Continue condition: next_char != '*' */
+ }
+ /* Stop condition: next_char == '\/' */
+ READ_NEXT_CHAR(a_this, &cur_char);
+ g_string_append_unichar (comment->stryng, cur_char);
+
+ if (status == CR_OK) {
+ cr_parsing_location_copy (&comment->location,
+ &loc) ;
+ *a_comment = comment;
+ return CR_OK;
+ }
+ error:
+
+ if (comment) {
+ cr_string_destroy (comment);
+ comment = NULL;
+ }
+
+ cr_tknzr_set_cur_pos (a_this, &init_pos);
+
+ return status;
+}
+
+/**
+ *Parses an 'unicode' escape sequence defined
+ *in css spec at chap 4.1.1:
+ *unicode ::= \\[0-9a-f]{1,6}[ \n\r\t\f]?
+ *@param a_this the current instance of #CRTknzr.
+ *@param a_start out parameter. A pointer to the start
+ *of the unicode escape sequence. Must *NOT* be deleted by
+ *the caller.
+ *@param a_end out parameter. A pointer to the last character
+ *of the unicode escape sequence. Must *NOT* be deleted by the caller.
+ *@return CR_OK if parsing succeeded, an error code otherwise.
+ *Error code can be either CR_PARSING_ERROR if the string
+ *parsed just doesn't
+ *respect the production or another error if a
+ *lower level error occurred.
+ */
+static enum CRStatus
+cr_tknzr_parse_unicode_escape (CRTknzr * a_this,
+ guint32 * a_unicode,
+ CRParsingLocation *a_location)
+{
+ guint32 cur_char;
+ CRInputPos init_pos;
+ glong occur = 0;
+ guint32 unicode = 0;
+ guchar *tmp_char_ptr1 = NULL,
+ *tmp_char_ptr2 = NULL;
+ enum CRStatus status = CR_OK;
+
+ g_return_val_if_fail (a_this && PRIVATE (a_this)
+ && a_unicode, CR_BAD_PARAM_ERROR);
+
+ /*first, let's backup the current position pointer */
+ RECORD_INITIAL_POS (a_this, &init_pos);
+
+ READ_NEXT_CHAR (a_this, &cur_char);
+
+ if (cur_char != '\\') {
+ status = CR_PARSING_ERROR;
+ goto error;
+ }
+ if (a_location) {
+ cr_tknzr_get_parsing_location
+ (a_this, a_location) ;
+ }
+ PEEK_NEXT_CHAR (a_this, &cur_char);
+
+ for (occur = 0, unicode = 0; ((cur_char >= '0' && cur_char <= '9')
+ || (cur_char >= 'a' && cur_char <= 'f')
+ || (cur_char >= 'A' && cur_char <= 'F'))
+ && occur < 6; occur++) {
+ gint cur_char_val = 0;
+
+ READ_NEXT_CHAR (a_this, &cur_char);
+
+ if ((cur_char >= '0' && cur_char <= '9')) {
+ cur_char_val = (cur_char - '0');
+ } else if ((cur_char >= 'a' && cur_char <= 'f')) {
+ cur_char_val = 10 + (cur_char - 'a');
+ } else if ((cur_char >= 'A' && cur_char <= 'F')) {
+ cur_char_val = 10 + (cur_char - 'A');
+ }
+
+ unicode = unicode * 16 + cur_char_val;
+
+ PEEK_NEXT_CHAR (a_this, &cur_char);
+ }
+
+ /* Eat a whitespace if possible. */
+ cr_tknzr_parse_w (a_this, &tmp_char_ptr1,
+ &tmp_char_ptr2, NULL);
+ *a_unicode = unicode;
+ return CR_OK;
+
+ error:
+ /*
+ *restore the initial position pointer backuped at
+ *the beginning of this function.
+ */
+ cr_tknzr_set_cur_pos (a_this, &init_pos);
+
+ return status;
+}
+
+/**
+ *parses an escape sequence as defined by the css spec:
+ *escape ::= {unicode}|\\[ -~\200-\4177777]
+ *@param a_this the current instance of #CRTknzr .
+ */
+static enum CRStatus
+cr_tknzr_parse_escape (CRTknzr * a_this, guint32 * a_esc_code,
+ CRParsingLocation *a_location)
+{
+ enum CRStatus status = CR_OK;
+ guint32 cur_char = 0;
+ CRInputPos init_pos;
+ guchar next_chars[2];
+
+ g_return_val_if_fail (a_this && PRIVATE (a_this)
+ && a_esc_code, CR_BAD_PARAM_ERROR);
+
+ RECORD_INITIAL_POS (a_this, &init_pos);
+
+ PEEK_BYTE (a_this, 1, &next_chars[0]);
+ PEEK_BYTE (a_this, 2, &next_chars[1]);
+
+ if (next_chars[0] != '\\') {
+ status = CR_PARSING_ERROR;
+ goto error;
+ }
+
+ if ((next_chars[1] >= '0' && next_chars[1] <= '9')
+ || (next_chars[1] >= 'a' && next_chars[1] <= 'f')
+ || (next_chars[1] >= 'A' && next_chars[1] <= 'F')) {
+ status = cr_tknzr_parse_unicode_escape (a_this, a_esc_code,
+ a_location);
+ } else {
+ /*consume the '\' char */
+ READ_NEXT_CHAR (a_this, &cur_char);
+ if (a_location) {
+ cr_tknzr_get_parsing_location (a_this,
+ a_location) ;
+ }
+ /*then read the char after the '\' */
+ READ_NEXT_CHAR (a_this, &cur_char);
+
+ if (cur_char != ' ' && (cur_char < 200 || cur_char > 4177777)) {
+ status = CR_PARSING_ERROR;
+ goto error;
+ }
+ *a_esc_code = cur_char;
+
+ }
+ if (status == CR_OK) {
+ return CR_OK;
+ }
+ error:
+ cr_tknzr_set_cur_pos (a_this, &init_pos);
+ return status;
+}
+
+/**
+ *Parses a string type as defined in css spec [4.1.1]:
+ *
+ *string ::= {string1}|{string2}
+ *string1 ::= \"([\t !#$%&(-~]|\\{nl}|\'|{nonascii}|{escape})*\"
+ *string2 ::= \'([\t !#$%&(-~]|\\{nl}|\"|{nonascii}|{escape})*\'
+ *
+ *@param a_this the current instance of #CRTknzr.
+ *@param a_start out parameter. Upon successful completion,
+ *points to the beginning of the string, points to an undefined value
+ *otherwise.
+ *@param a_end out parameter. Upon successful completion, points to
+ *the beginning of the string, points to an undefined value otherwise.
+ *@return CR_OK upon successful completion, an error code otherwise.
+ */
+static enum CRStatus
+cr_tknzr_parse_string (CRTknzr * a_this, CRString ** a_str)
+{
+ guint32 cur_char = 0,
+ delim = 0;
+ CRInputPos init_pos;
+ enum CRStatus status = CR_OK;
+ CRString *str = NULL;
+
+ g_return_val_if_fail (a_this && PRIVATE (a_this)
+ && PRIVATE (a_this)->input
+ && a_str, CR_BAD_PARAM_ERROR);
+
+ RECORD_INITIAL_POS (a_this, &init_pos);
+ READ_NEXT_CHAR (a_this, &cur_char);
+
+ if (cur_char == '"')
+ delim = '"';
+ else if (cur_char == '\'')
+ delim = '\'';
+ else {
+ status = CR_PARSING_ERROR;
+ goto error;
+ }
+ str = cr_string_new ();
+ if (str) {
+ cr_tknzr_get_parsing_location
+ (a_this, &str->location) ;
+ }
+ for (;;) {
+ guchar next_chars[2] = { 0 };
+
+ PEEK_BYTE (a_this, 1, &next_chars[0]);
+ PEEK_BYTE (a_this, 2, &next_chars[1]);
+
+ if (next_chars[0] == '\\') {
+ guchar *tmp_char_ptr1 = NULL,
+ *tmp_char_ptr2 = NULL;
+ guint32 esc_code = 0;
+
+ if (next_chars[1] == '\'' || next_chars[1] == '"') {
+ g_string_append_unichar (str->stryng,
+ next_chars[1]);
+ SKIP_BYTES (a_this, 2);
+ status = CR_OK;
+ } else {
+ status = cr_tknzr_parse_escape
+ (a_this, &esc_code, NULL);
+
+ if (status == CR_OK) {
+ g_string_append_unichar
+ (str->stryng,
+ esc_code);
+ }
+ }
+
+ if (status != CR_OK) {
+ /*
+ *consume the '\' char, and try to parse
+ *a newline.
+ */
+ READ_NEXT_CHAR (a_this, &cur_char);
+
+ status = cr_tknzr_parse_nl
+ (a_this, &tmp_char_ptr1,
+ &tmp_char_ptr2, NULL);
+ }
+
+ CHECK_PARSING_STATUS (status, FALSE);
+ } else if (strchr ("\t !#$%&", next_chars[0])
+ || (next_chars[0] >= '(' && next_chars[0] <= '~')) {
+ READ_NEXT_CHAR (a_this, &cur_char);
+ g_string_append_unichar (str->stryng,
+ cur_char);
+ status = CR_OK;
+ }
+
+ else if (cr_utils_is_nonascii (next_chars[0])) {
+ READ_NEXT_CHAR (a_this, &cur_char);
+ g_string_append_unichar (str->stryng, cur_char);
+ } else if (next_chars[0] == delim) {
+ READ_NEXT_CHAR (a_this, &cur_char);
+ break;
+ } else {
+ status = CR_PARSING_ERROR;
+ goto error;
+ }
+ }
+
+ if (status == CR_OK) {
+ if (*a_str == NULL) {
+ *a_str = str;
+ str = NULL;
+ } else {
+ (*a_str)->stryng = g_string_append_len
+ ((*a_str)->stryng,
+ str->stryng->str,
+ str->stryng->len);
+ cr_string_destroy (str);
+ }
+ return CR_OK;
+ }
+
+ error:
+
+ if (str) {
+ cr_string_destroy (str) ;
+ str = NULL;
+ }
+ cr_tknzr_set_cur_pos (a_this, &init_pos);
+ return status;
+}
+
+/**
+ *Parses the an nmstart as defined by the css2 spec [4.1.1]:
+ * nmstart [a-zA-Z]|{nonascii}|{escape}
+ *
+ *@param a_this the current instance of #CRTknzr.
+ *@param a_start out param. A pointer to the starting point of
+ *the token.
+ *@param a_end out param. A pointer to the ending point of the
+ *token.
+ *@param a_char out param. The actual parsed nmchar.
+ *@return CR_OK upon successful completion,
+ *an error code otherwise.
+ */
+static enum CRStatus
+cr_tknzr_parse_nmstart (CRTknzr * a_this,
+ guint32 * a_char,
+ CRParsingLocation *a_location)
+{
+ CRInputPos init_pos;
+ enum CRStatus status = CR_OK;
+ guint32 cur_char = 0,
+ next_char = 0;
+
+ g_return_val_if_fail (a_this && PRIVATE (a_this)
+ && PRIVATE (a_this)->input
+ && a_char, CR_BAD_PARAM_ERROR);
+
+ RECORD_INITIAL_POS (a_this, &init_pos);
+
+ PEEK_NEXT_CHAR (a_this, &next_char);
+
+ if (next_char == '\\') {
+ status = cr_tknzr_parse_escape (a_this, a_char,
+ a_location);
+
+ if (status != CR_OK)
+ goto error;
+
+ } else if (cr_utils_is_nonascii (next_char) == TRUE
+ || ((next_char >= 'a') && (next_char <= 'z'))
+ || ((next_char >= 'A') && (next_char <= 'Z'))
+ ) {
+ READ_NEXT_CHAR (a_this, &cur_char);
+ if (a_location) {
+ cr_tknzr_get_parsing_location (a_this,
+ a_location) ;
+ }
+ *a_char = cur_char;
+ status = CR_OK;
+ } else {
+ status = CR_PARSING_ERROR;
+ goto error;
+ }
+
+ return CR_OK;
+
+ error:
+ cr_tknzr_set_cur_pos (a_this, &init_pos);
+
+ return status;
+
+}
+
+/**
+ *Parses an nmchar as described in the css spec at
+ *chap 4.1.1:
+ *nmchar ::= [a-z0-9-]|{nonascii}|{escape}
+ *
+ *Humm, I have added the possibility for nmchar to
+ *contain upper case letters.
+ *
+ *@param a_this the current instance of #CRTknzr.
+ *@param a_start out param. A pointer to the starting point of
+ *the token.
+ *@param a_end out param. A pointer to the ending point of the
+ *token.
+ *@param a_char out param. The actual parsed nmchar.
+ *@return CR_OK upon successful completion,
+ *an error code otherwise.
+ */
+static enum CRStatus
+cr_tknzr_parse_nmchar (CRTknzr * a_this, guint32 * a_char,
+ CRParsingLocation *a_location)
+{
+ guint32 cur_char = 0,
+ next_char = 0;
+ enum CRStatus status = CR_OK;
+ CRInputPos init_pos;
+
+ g_return_val_if_fail (a_this && PRIVATE (a_this) && a_char,
+ CR_BAD_PARAM_ERROR);
+
+ RECORD_INITIAL_POS (a_this, &init_pos);
+
+ status = cr_input_peek_char (PRIVATE (a_this)->input,
+ &next_char) ;
+ if (status != CR_OK)
+ goto error;
+
+ if (next_char == '\\') {
+ status = cr_tknzr_parse_escape (a_this, a_char,
+ a_location);
+
+ if (status != CR_OK)
+ goto error;
+
+ } else if (cr_utils_is_nonascii (next_char) == TRUE
+ || ((next_char >= 'a') && (next_char <= 'z'))
+ || ((next_char >= 'A') && (next_char <= 'Z'))
+ || ((next_char >= '0') && (next_char <= '9'))
+ || (next_char == '-')
+ || (next_char == '_') /*'_' not allowed by the spec. */
+ ) {
+ READ_NEXT_CHAR (a_this, &cur_char);
+ *a_char = cur_char;
+ status = CR_OK;
+ if (a_location) {
+ cr_tknzr_get_parsing_location
+ (a_this, a_location) ;
+ }
+ } else {
+ status = CR_PARSING_ERROR;
+ goto error;
+ }
+ return CR_OK;
+
+ error:
+ cr_tknzr_set_cur_pos (a_this, &init_pos);
+ return status;
+}
+
+/**
+ *Parses an "ident" as defined in css spec [4.1.1]:
+ *ident ::= {nmstart}{nmchar}*
+ *
+ *Actually parses it using the css3 grammar:
+ *ident ::= -?{nmstart}{nmchar}*
+ *@param a_this the currens instance of #CRTknzr.
+ *
+ *@param a_str a pointer to parsed ident. If *a_str is NULL,
+ *this function allocates a new instance of CRString. If not,
+ *the function just appends the parsed string to the one passed.
+ *In both cases it is up to the caller to free *a_str.
+ *
+ *@return CR_OK upon successful completion, an error code
+ *otherwise.
+ */
+static enum CRStatus
+cr_tknzr_parse_ident (CRTknzr * a_this, CRString ** a_str)
+{
+ guint32 tmp_char = 0;
+ CRString *stringue = NULL ;
+ CRInputPos init_pos;
+ enum CRStatus status = CR_OK;
+ gboolean location_is_set = FALSE ;
+
+ g_return_val_if_fail (a_this && PRIVATE (a_this)
+ && PRIVATE (a_this)->input
+ && a_str, CR_BAD_PARAM_ERROR);
+
+ RECORD_INITIAL_POS (a_this, &init_pos);
+ PEEK_NEXT_CHAR (a_this, &tmp_char) ;
+ stringue = cr_string_new () ;
+ g_return_val_if_fail (stringue,
+ CR_OUT_OF_MEMORY_ERROR) ;
+
+ if (tmp_char == '-') {
+ READ_NEXT_CHAR (a_this, &tmp_char) ;
+ cr_tknzr_get_parsing_location
+ (a_this, &stringue->location) ;
+ location_is_set = TRUE ;
+ g_string_append_unichar (stringue->stryng,
+ tmp_char) ;
+ }
+ status = cr_tknzr_parse_nmstart (a_this, &tmp_char, NULL);
+ if (status != CR_OK) {
+ status = CR_PARSING_ERROR;
+ goto end ;
+ }
+ if (location_is_set == FALSE) {
+ cr_tknzr_get_parsing_location
+ (a_this, &stringue->location) ;
+ location_is_set = TRUE ;
+ }
+ g_string_append_unichar (stringue->stryng, tmp_char);
+ for (;;) {
+ status = cr_tknzr_parse_nmchar (a_this,
+ &tmp_char,
+ NULL);
+ if (status != CR_OK) {
+ status = CR_OK ;
+ break;
+ }
+ g_string_append_unichar (stringue->stryng, tmp_char);
+ }
+ if (status == CR_OK) {
+ if (!*a_str) {
+ *a_str = stringue ;
+
+ } else {
+ g_string_append_len ((*a_str)->stryng,
+ stringue->stryng->str,
+ stringue->stryng->len) ;
+ cr_string_destroy (stringue) ;
+ }
+ stringue = NULL ;
+ }
+
+ error:
+ end:
+ if (stringue) {
+ cr_string_destroy (stringue) ;
+ stringue = NULL ;
+ }
+ if (status != CR_OK ) {
+ cr_tknzr_set_cur_pos (a_this, &init_pos) ;
+ }
+ return status ;
+}
+
+
+/**
+ *Parses a "name" as defined by css spec [4.1.1]:
+ *name ::= {nmchar}+
+ *
+ *@param a_this the current instance of #CRTknzr.
+ *
+ *@param a_str out parameter. A pointer to the successfully parsed
+ *name. If *a_str is set to NULL, this function allocates a new instance
+ *of CRString. If not, it just appends the parsed name to the passed *a_str.
+ *In both cases, it is up to the caller to free *a_str.
+ *
+ *@return CR_OK upon successful completion, an error code otherwise.
+ */
+static enum CRStatus
+cr_tknzr_parse_name (CRTknzr * a_this,
+ CRString ** a_str)
+{
+ guint32 tmp_char = 0;
+ CRInputPos init_pos;
+ enum CRStatus status = CR_OK;
+ gboolean str_needs_free = FALSE,
+ is_first_nmchar=TRUE ;
+ glong i = 0;
+ CRParsingLocation loc = {0} ;
+
+ g_return_val_if_fail (a_this && PRIVATE (a_this)
+ && PRIVATE (a_this)->input
+ && a_str,
+ CR_BAD_PARAM_ERROR) ;
+
+ RECORD_INITIAL_POS (a_this, &init_pos);
+
+ if (*a_str == NULL) {
+ *a_str = cr_string_new ();
+ str_needs_free = TRUE;
+ }
+ for (i = 0;; i++) {
+ if (is_first_nmchar == TRUE) {
+ status = cr_tknzr_parse_nmchar
+ (a_this, &tmp_char,
+ &loc) ;
+ is_first_nmchar = FALSE ;
+ } else {
+ status = cr_tknzr_parse_nmchar
+ (a_this, &tmp_char, NULL) ;
+ }
+ if (status != CR_OK)
+ break;
+ g_string_append_unichar ((*a_str)->stryng,
+ tmp_char);
+ }
+ if (i > 0) {
+ cr_parsing_location_copy
+ (&(*a_str)->location, &loc) ;
+ return CR_OK;
+ }
+ if (str_needs_free == TRUE && *a_str) {
+ cr_string_destroy (*a_str);
+ *a_str = NULL;
+ }
+ cr_tknzr_set_cur_pos (a_this, &init_pos);
+ return CR_PARSING_ERROR;
+}
+
+/**
+ *Parses a "hash" as defined by the css spec in [4.1.1]:
+ *HASH ::= #{name}
+ */
+static enum CRStatus
+cr_tknzr_parse_hash (CRTknzr * a_this, CRString ** a_str)
+{
+ guint32 cur_char = 0;
+ CRInputPos init_pos;
+ enum CRStatus status = CR_OK;
+ gboolean str_needs_free = FALSE;
+ CRParsingLocation loc = {0} ;
+
+ g_return_val_if_fail (a_this && PRIVATE (a_this)
+ && PRIVATE (a_this)->input,
+ CR_BAD_PARAM_ERROR);
+
+ RECORD_INITIAL_POS (a_this, &init_pos);
+ READ_NEXT_CHAR (a_this, &cur_char);
+ if (cur_char != '#') {
+ status = CR_PARSING_ERROR;
+ goto error;
+ }
+ if (*a_str == NULL) {
+ *a_str = cr_string_new ();
+ str_needs_free = TRUE;
+ }
+ cr_tknzr_get_parsing_location (a_this,
+ &loc) ;
+ status = cr_tknzr_parse_name (a_this, a_str);
+ cr_parsing_location_copy (&(*a_str)->location, &loc) ;
+ if (status != CR_OK) {
+ goto error;
+ }
+ return CR_OK;
+
+ error:
+ if (str_needs_free == TRUE && *a_str) {
+ cr_string_destroy (*a_str);
+ *a_str = NULL;
+ }
+
+ cr_tknzr_set_cur_pos (a_this, &init_pos);
+ return status;
+}
+
+/**
+ *Parses an uri as defined by the css spec [4.1.1]:
+ * URI ::= url\({w}{string}{w}\)
+ * |url\({w}([!#$%&*-~]|{nonascii}|{escape})*{w}\)
+ *
+ *@param a_this the current instance of #CRTknzr.
+ *@param a_str the successfully parsed url.
+ *@return CR_OK upon successful completion, an error code otherwise.
+ */
+static enum CRStatus
+cr_tknzr_parse_uri (CRTknzr * a_this,
+ CRString ** a_str)
+{
+ guint32 cur_char = 0;
+ CRInputPos init_pos;
+ enum CRStatus status = CR_PARSING_ERROR;
+ guchar tab[4] = { 0 }, *tmp_ptr1 = NULL, *tmp_ptr2 = NULL;
+ CRString *str = NULL;
+ CRParsingLocation location = {0} ;
+
+ g_return_val_if_fail (a_this
+ && PRIVATE (a_this)
+ && PRIVATE (a_this)->input
+ && a_str,
+ CR_BAD_PARAM_ERROR);
+
+ RECORD_INITIAL_POS (a_this, &init_pos);
+
+ PEEK_BYTE (a_this, 1, &tab[0]);
+ PEEK_BYTE (a_this, 2, &tab[1]);
+ PEEK_BYTE (a_this, 3, &tab[2]);
+ PEEK_BYTE (a_this, 4, &tab[3]);
+
+ if (tab[0] != 'u' || tab[1] != 'r' || tab[2] != 'l' || tab[3] != '(') {
+ status = CR_PARSING_ERROR;
+ goto error;
+ }
+ /*
+ *Here, we want to skip 4 bytes ('u''r''l''(').
+ *But we also need to keep track of the parsing location
+ *of the 'u'. So, we skip 1 byte, we record the parsing
+ *location, then we skip the 3 remaining bytes.
+ */
+ SKIP_CHARS (a_this, 1);
+ cr_tknzr_get_parsing_location (a_this, &location) ;
+ SKIP_CHARS (a_this, 3);
+ cr_tknzr_try_to_skip_spaces (a_this);
+ status = cr_tknzr_parse_string (a_this, a_str);
+
+ if (status == CR_OK) {
+ guint32 next_char = 0;
+ status = cr_tknzr_parse_w (a_this, &tmp_ptr1,
+ &tmp_ptr2, NULL);
+ cr_tknzr_try_to_skip_spaces (a_this);
+ PEEK_NEXT_CHAR (a_this, &next_char);
+ if (next_char == ')') {
+ READ_NEXT_CHAR (a_this, &cur_char);
+ status = CR_OK;
+ } else {
+ status = CR_PARSING_ERROR;
+ }
+ }
+ if (status != CR_OK) {
+ str = cr_string_new ();
+ for (;;) {
+ guint32 next_char = 0;
+ PEEK_NEXT_CHAR (a_this, &next_char);
+ if (strchr ("!#$%&", next_char)
+ || (next_char >= '*' && next_char <= '~')
+ || (cr_utils_is_nonascii (next_char) == TRUE)) {
+ READ_NEXT_CHAR (a_this, &cur_char);
+ g_string_append_unichar
+ (str->stryng, cur_char);
+ status = CR_OK;
+ } else {
+ guint32 esc_code = 0;
+ status = cr_tknzr_parse_escape
+ (a_this, &esc_code, NULL);
+ if (status == CR_OK) {
+ g_string_append_unichar
+ (str->stryng,
+ esc_code);
+ } else {
+ status = CR_OK;
+ break;
+ }
+ }
+ }
+ cr_tknzr_try_to_skip_spaces (a_this);
+ READ_NEXT_CHAR (a_this, &cur_char);
+ if (cur_char == ')') {
+ status = CR_OK;
+ } else {
+ status = CR_PARSING_ERROR;
+ goto error;
+ }
+ if (str) {
+ if (*a_str == NULL) {
+ *a_str = str;
+ str = NULL;
+ } else {
+ g_string_append_len
+ ((*a_str)->stryng,
+ str->stryng->str,
+ str->stryng->len);
+ cr_string_destroy (str);
+ }
+ }
+ }
+
+ cr_parsing_location_copy
+ (&(*a_str)->location,
+ &location) ;
+ return CR_OK ;
+ error:
+ if (str) {
+ cr_string_destroy (str);
+ str = NULL;
+ }
+ cr_tknzr_set_cur_pos (a_this, &init_pos);
+ return status;
+}
+
+/**
+ *parses an RGB as defined in the css2 spec.
+ *rgb: rgb '('S*{num}%?S* ',' {num}#?S*,S*{num}#?S*')'
+ *
+ *@param a_this the "this pointer" of the current instance of
+ *@param a_rgb out parameter the parsed rgb.
+ *@return CR_OK upon successful completion, an error code otherwise.
+ */
+static enum CRStatus
+cr_tknzr_parse_rgb (CRTknzr * a_this, CRRgb ** a_rgb)
+{
+ enum CRStatus status = CR_OK;
+ CRInputPos init_pos;
+ CRNum *num = NULL;
+ guchar next_bytes[3] = { 0 }, cur_byte = 0;
+ glong red = 0,
+ green = 0,
+ blue = 0,
+ i = 0;
+ gboolean is_percentage = FALSE;
+ CRParsingLocation location = {0} ;
+
+ g_return_val_if_fail (a_this && PRIVATE (a_this), CR_BAD_PARAM_ERROR);
+
+ RECORD_INITIAL_POS (a_this, &init_pos);
+
+ PEEK_BYTE (a_this, 1, &next_bytes[0]);
+ PEEK_BYTE (a_this, 2, &next_bytes[1]);
+ PEEK_BYTE (a_this, 3, &next_bytes[2]);
+
+ if (((next_bytes[0] == 'r') || (next_bytes[0] == 'R'))
+ && ((next_bytes[1] == 'g') || (next_bytes[1] == 'G'))
+ && ((next_bytes[2] == 'b') || (next_bytes[2] == 'B'))) {
+ SKIP_CHARS (a_this, 1);
+ cr_tknzr_get_parsing_location (a_this, &location) ;
+ SKIP_CHARS (a_this, 2);
+ } else {
+ status = CR_PARSING_ERROR;
+ goto error;
+ }
+ READ_NEXT_BYTE (a_this, &cur_byte);
+ ENSURE_PARSING_COND (cur_byte == '(');
+
+ cr_tknzr_try_to_skip_spaces (a_this);
+ status = cr_tknzr_parse_num (a_this, &num);
+ ENSURE_PARSING_COND ((status == CR_OK) && (num != NULL));
+
+ if (num->val > G_MAXLONG) {
+ status = CR_PARSING_ERROR;
+ goto error;
+ }
+
+ red = num->val;
+ cr_num_destroy (num);
+ num = NULL;
+
+ PEEK_BYTE (a_this, 1, &next_bytes[0]);
+ if (next_bytes[0] == '%') {
+ SKIP_CHARS (a_this, 1);
+ is_percentage = TRUE;
+ }
+ cr_tknzr_try_to_skip_spaces (a_this);
+
+ for (i = 0; i < 2; i++) {
+ READ_NEXT_BYTE (a_this, &cur_byte);
+ ENSURE_PARSING_COND (cur_byte == ',');
+
+ cr_tknzr_try_to_skip_spaces (a_this);
+ status = cr_tknzr_parse_num (a_this, &num);
+ ENSURE_PARSING_COND ((status == CR_OK) && (num != NULL));
+
+ if (num->val > G_MAXLONG) {
+ status = CR_PARSING_ERROR;
+ goto error;
+ }
+
+ PEEK_BYTE (a_this, 1, &next_bytes[0]);
+ if (next_bytes[0] == '%') {
+ SKIP_CHARS (a_this, 1);
+ is_percentage = 1;
+ }
+
+ if (i == 0) {
+ green = num->val;
+ } else if (i == 1) {
+ blue = num->val;
+ }
+
+ if (num) {
+ cr_num_destroy (num);
+ num = NULL;
+ }
+ cr_tknzr_try_to_skip_spaces (a_this);
+ }
+
+ READ_NEXT_BYTE (a_this, &cur_byte);
+ if (*a_rgb == NULL) {
+ *a_rgb = cr_rgb_new_with_vals (red, green, blue,
+ is_percentage);
+
+ if (*a_rgb == NULL) {
+ status = CR_ERROR;
+ goto error;
+ }
+ status = CR_OK;
+ } else {
+ (*a_rgb)->red = red;
+ (*a_rgb)->green = green;
+ (*a_rgb)->blue = blue;
+ (*a_rgb)->is_percentage = is_percentage;
+
+ status = CR_OK;
+ }
+
+ if (status == CR_OK) {
+ if (a_rgb && *a_rgb) {
+ cr_parsing_location_copy
+ (&(*a_rgb)->location,
+ &location) ;
+ }
+ return CR_OK;
+ }
+
+ error:
+ if (num) {
+ cr_num_destroy (num);
+ num = NULL;
+ }
+
+ cr_tknzr_set_cur_pos (a_this, &init_pos);
+ return CR_OK;
+}
+
+/**
+ *Parses a atkeyword as defined by the css spec in [4.1.1]:
+ *ATKEYWORD ::= @{ident}
+ *
+ *@param a_this the "this pointer" of the current instance of
+ *#CRTknzr.
+ *
+ *@param a_str out parameter. The parsed atkeyword. If *a_str is
+ *set to NULL this function allocates a new instance of CRString and
+ *sets it to the parsed atkeyword. If not, this function just appends
+ *the parsed atkeyword to the end of *a_str. In both cases it is up to
+ *the caller to free *a_str.
+ *
+ *@return CR_OK upon successful completion, an error code otherwise.
+ */
+static enum CRStatus
+cr_tknzr_parse_atkeyword (CRTknzr * a_this,
+ CRString ** a_str)
+{
+ guint32 cur_char = 0;
+ CRInputPos init_pos;
+ gboolean str_needs_free = FALSE;
+ enum CRStatus status = CR_OK;
+
+ g_return_val_if_fail (a_this && PRIVATE (a_this)
+ && PRIVATE (a_this)->input
+ && a_str, CR_BAD_PARAM_ERROR);
+
+ RECORD_INITIAL_POS (a_this, &init_pos);
+
+ READ_NEXT_CHAR (a_this, &cur_char);
+
+ if (cur_char != '@') {
+ status = CR_PARSING_ERROR;
+ goto error;
+ }
+
+ if (*a_str == NULL) {
+ *a_str = cr_string_new ();
+ str_needs_free = TRUE;
+ }
+ status = cr_tknzr_parse_ident (a_this, a_str);
+ if (status != CR_OK) {
+ goto error;
+ }
+ return CR_OK;
+ error:
+
+ if (str_needs_free == TRUE && *a_str) {
+ cr_string_destroy (*a_str);
+ *a_str = NULL;
+ }
+ cr_tknzr_set_cur_pos (a_this, &init_pos);
+ return status;
+}
+
+static enum CRStatus
+cr_tknzr_parse_important (CRTknzr * a_this,
+ CRParsingLocation *a_location)
+{
+ guint32 cur_char = 0;
+ CRInputPos init_pos;
+ enum CRStatus status = CR_OK;
+
+ g_return_val_if_fail (a_this && PRIVATE (a_this)
+ && PRIVATE (a_this)->input,
+ CR_BAD_PARAM_ERROR);
+
+ RECORD_INITIAL_POS (a_this, &init_pos);
+ READ_NEXT_CHAR (a_this, &cur_char);
+ ENSURE_PARSING_COND (cur_char == '!');
+ if (a_location) {
+ cr_tknzr_get_parsing_location (a_this,
+ a_location) ;
+ }
+ cr_tknzr_try_to_skip_spaces (a_this);
+
+ if (BYTE (PRIVATE (a_this)->input, 1, NULL) == 'i'
+ && BYTE (PRIVATE (a_this)->input, 2, NULL) == 'm'
+ && BYTE (PRIVATE (a_this)->input, 3, NULL) == 'p'
+ && BYTE (PRIVATE (a_this)->input, 4, NULL) == 'o'
+ && BYTE (PRIVATE (a_this)->input, 5, NULL) == 'r'
+ && BYTE (PRIVATE (a_this)->input, 6, NULL) == 't'
+ && BYTE (PRIVATE (a_this)->input, 7, NULL) == 'a'
+ && BYTE (PRIVATE (a_this)->input, 8, NULL) == 'n'
+ && BYTE (PRIVATE (a_this)->input, 9, NULL) == 't') {
+ SKIP_BYTES (a_this, 9);
+ if (a_location) {
+ cr_tknzr_get_parsing_location (a_this,
+ a_location) ;
+ }
+ return CR_OK;
+ } else {
+ status = CR_PARSING_ERROR;
+ }
+
+ error:
+ cr_tknzr_set_cur_pos (a_this, &init_pos);
+
+ return status;
+}
+
+/**
+ *Parses a num as defined in the css spec [4.1.1]:
+ *[0-9]+|[0-9]*\.[0-9]+
+ *@param a_this the current instance of #CRTknzr.
+ *@param a_num out parameter. The parsed number.
+ *@return CR_OK upon successful completion,
+ *an error code otherwise.
+ *
+ *The CSS specification says that numbers may be
+ *preceded by '+' or '-' to indicate the sign.
+ *Technically, the "num" construction as defined
+ *by the tokenizer doesn't allow this, but we parse
+ *it here for simplicity.
+ */
+static enum CRStatus
+cr_tknzr_parse_num (CRTknzr * a_this,
+ CRNum ** a_num)
+{
+ enum CRStatus status = CR_PARSING_ERROR;
+ enum CRNumType val_type = NUM_GENERIC;
+ gboolean parsing_dec, /* true iff seen decimal point. */
+ parsed; /* true iff the substring seen so far is a valid CSS
+ number, i.e. `[0-9]+|[0-9]*\.[0-9]+'. */
+ guint32 cur_char = 0,
+ next_char = 0;
+ gdouble numerator, denominator = 1;
+ CRInputPos init_pos;
+ CRParsingLocation location = {0} ;
+ int sign = 1;
+
+ g_return_val_if_fail (a_this && PRIVATE (a_this)
+ && PRIVATE (a_this)->input,
+ CR_BAD_PARAM_ERROR);
+
+ RECORD_INITIAL_POS (a_this, &init_pos);
+ READ_NEXT_CHAR (a_this, &cur_char);
+
+ if (cur_char == '+' || cur_char == '-') {
+ if (cur_char == '-') {
+ sign = -1;
+ }
+ READ_NEXT_CHAR (a_this, &cur_char);
+ }
+
+ if (IS_NUM (cur_char)) {
+ numerator = (cur_char - '0');
+ parsing_dec = FALSE;
+ parsed = TRUE;
+ } else if (cur_char == '.') {
+ numerator = 0;
+ parsing_dec = TRUE;
+ parsed = FALSE;
+ } else {
+ status = CR_PARSING_ERROR;
+ goto error;
+ }
+ cr_tknzr_get_parsing_location (a_this, &location) ;
+
+ for (;;) {
+ status = cr_tknzr_peek_char (a_this, &next_char);
+ if (status != CR_OK) {
+ if (status == CR_END_OF_INPUT_ERROR)
+ status = CR_OK;
+ break;
+ }
+ if (next_char == '.') {
+ if (parsing_dec) {
+ status = CR_PARSING_ERROR;
+ goto error;
+ }
+
+ READ_NEXT_CHAR (a_this, &cur_char);
+ parsing_dec = TRUE;
+ parsed = FALSE; /* In CSS, there must be at least
+ one digit after `.'. */
+ } else if (IS_NUM (next_char)) {
+ READ_NEXT_CHAR (a_this, &cur_char);
+ parsed = TRUE;
+
+ numerator = numerator * 10 + (cur_char - '0');
+ if (parsing_dec) {
+ denominator *= 10;
+ }
+ } else {
+ break;
+ }
+ }
+
+ if (!parsed) {
+ status = CR_PARSING_ERROR;
+ }
+
+ /*
+ *Now, set the output param values.
+ */
+ if (status == CR_OK) {
+ gdouble val = (numerator / denominator) * sign;
+ if (*a_num == NULL) {
+ *a_num = cr_num_new_with_val (val, val_type);
+
+ if (*a_num == NULL) {
+ status = CR_ERROR;
+ goto error;
+ }
+ } else {
+ (*a_num)->val = val;
+ (*a_num)->type = val_type;
+ }
+ cr_parsing_location_copy (&(*a_num)->location,
+ &location) ;
+ return CR_OK;
+ }
+
+ error:
+
+ cr_tknzr_set_cur_pos (a_this, &init_pos);
+
+ return status;
+}
+
+/*********************************************
+ *PUBLIC methods
+ ********************************************/
+
+CRTknzr *
+cr_tknzr_new (CRInput * a_input)
+{
+ CRTknzr *result = NULL;
+
+ result = g_try_malloc (sizeof (CRTknzr));
+
+ if (result == NULL) {
+ cr_utils_trace_info ("Out of memory");
+ return NULL;
+ }
+
+ memset (result, 0, sizeof (CRTknzr));
+
+ result->priv = g_try_malloc (sizeof (CRTknzrPriv));
+
+ if (result->priv == NULL) {
+ cr_utils_trace_info ("Out of memory");
+
+ if (result) {
+ g_free (result);
+ result = NULL;
+ }
+
+ return NULL;
+ }
+ memset (result->priv, 0, sizeof (CRTknzrPriv));
+ if (a_input)
+ cr_tknzr_set_input (result, a_input);
+ return result;
+}
+
+CRTknzr *
+cr_tknzr_new_from_buf (guchar * a_buf, gulong a_len,
+ enum CREncoding a_enc,
+ gboolean a_free_at_destroy)
+{
+ CRTknzr *result = NULL;
+ CRInput *input = NULL;
+
+ input = cr_input_new_from_buf (a_buf, a_len, a_enc,
+ a_free_at_destroy);
+
+ g_return_val_if_fail (input != NULL, NULL);
+
+ result = cr_tknzr_new (input);
+
+ return result;
+}
+
+CRTknzr *
+cr_tknzr_new_from_uri (const guchar * a_file_uri,
+ enum CREncoding a_enc)
+{
+ CRTknzr *result = NULL;
+ CRInput *input = NULL;
+
+ input = cr_input_new_from_uri ((const gchar *) a_file_uri, a_enc);
+ g_return_val_if_fail (input != NULL, NULL);
+
+ result = cr_tknzr_new (input);
+
+ return result;
+}
+
+void
+cr_tknzr_ref (CRTknzr * a_this)
+{
+ g_return_if_fail (a_this && PRIVATE (a_this));
+
+ PRIVATE (a_this)->ref_count++;
+}
+
+gboolean
+cr_tknzr_unref (CRTknzr * a_this)
+{
+ g_return_val_if_fail (a_this && PRIVATE (a_this), FALSE);
+
+ if (PRIVATE (a_this)->ref_count > 0) {
+ PRIVATE (a_this)->ref_count--;
+ }
+
+ if (PRIVATE (a_this)->ref_count == 0) {
+ cr_tknzr_destroy (a_this);
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+enum CRStatus
+cr_tknzr_set_input (CRTknzr * a_this, CRInput * a_input)
+{
+ g_return_val_if_fail (a_this && PRIVATE (a_this), CR_BAD_PARAM_ERROR);
+
+ if (PRIVATE (a_this)->input) {
+ cr_input_unref (PRIVATE (a_this)->input);
+ }
+
+ PRIVATE (a_this)->input = a_input;
+
+ cr_input_ref (PRIVATE (a_this)->input);
+
+ return CR_OK;
+}
+
+enum CRStatus
+cr_tknzr_get_input (CRTknzr * a_this, CRInput ** a_input)
+{
+ g_return_val_if_fail (a_this && PRIVATE (a_this), CR_BAD_PARAM_ERROR);
+
+ *a_input = PRIVATE (a_this)->input;
+
+ return CR_OK;
+}
+
+/*********************************
+ *Tokenizer input handling routines
+ *********************************/
+
+/**
+ *Reads the next byte from the parser input stream.
+ *@param a_this the "this pointer" of the current instance of
+ *#CRParser.
+ *@param a_byte out parameter the place where to store the byte
+ *read.
+ *@return CR_OK upon successful completion, an error
+ *code otherwise.
+ */
+enum CRStatus
+cr_tknzr_read_byte (CRTknzr * a_this, guchar * a_byte)
+{
+ g_return_val_if_fail (a_this && PRIVATE (a_this), CR_BAD_PARAM_ERROR);
+
+ return cr_input_read_byte (PRIVATE (a_this)->input, a_byte);
+
+}
+
+/**
+ *Reads the next char from the parser input stream.
+ *@param a_this the current instance of #CRTknzr.
+ *@param a_char out parameter. The read char.
+ *@return CR_OK upon successful completion, an error code
+ *otherwise.
+ */
+enum CRStatus
+cr_tknzr_read_char (CRTknzr * a_this, guint32 * a_char)
+{
+ g_return_val_if_fail (a_this && PRIVATE (a_this)
+ && PRIVATE (a_this)->input
+ && a_char, CR_BAD_PARAM_ERROR);
+
+ if (PRIVATE (a_this)->token_cache) {
+ cr_input_set_cur_pos (PRIVATE (a_this)->input,
+ &PRIVATE (a_this)->prev_pos);
+ cr_token_destroy (PRIVATE (a_this)->token_cache);
+ PRIVATE (a_this)->token_cache = NULL;
+ }
+
+ return cr_input_read_char (PRIVATE (a_this)->input, a_char);
+}
+
+/**
+ *Peeks a char from the parser input stream.
+ *To "peek a char" means reads the next char without consuming it.
+ *Subsequent calls to this function return the same char.
+ *@param a_this the current instance of #CRTknzr.
+ *@param a_char out parameter. The peeked char upon successful completion.
+ *@return CR_OK upon successful completion, an error code otherwise.
+ */
+enum CRStatus
+cr_tknzr_peek_char (CRTknzr * a_this, guint32 * a_char)
+{
+ g_return_val_if_fail (a_this && PRIVATE (a_this)
+ && PRIVATE (a_this)->input
+ && a_char, CR_BAD_PARAM_ERROR);
+
+ if (PRIVATE (a_this)->token_cache) {
+ cr_input_set_cur_pos (PRIVATE (a_this)->input,
+ &PRIVATE (a_this)->prev_pos);
+ cr_token_destroy (PRIVATE (a_this)->token_cache);
+ PRIVATE (a_this)->token_cache = NULL;
+ }
+
+ return cr_input_peek_char (PRIVATE (a_this)->input, a_char);
+}
+
+/**
+ *Peeks a byte ahead at a given position in the parser input stream.
+ *@param a_this the current instance of #CRTknzr.
+ *@param a_offset the offset of the peeked byte starting from the current
+ *byte in the parser input stream.
+ *@param a_byte out parameter. The peeked byte upon
+ *successful completion.
+ *@return CR_OK upon successful completion, an error code otherwise.
+ */
+enum CRStatus
+cr_tknzr_peek_byte (CRTknzr * a_this, gulong a_offset, guchar * a_byte)
+{
+ g_return_val_if_fail (a_this && PRIVATE (a_this)
+ && PRIVATE (a_this)->input && a_byte,
+ CR_BAD_PARAM_ERROR);
+
+ if (PRIVATE (a_this)->token_cache) {
+ cr_input_set_cur_pos (PRIVATE (a_this)->input,
+ &PRIVATE (a_this)->prev_pos);
+ cr_token_destroy (PRIVATE (a_this)->token_cache);
+ PRIVATE (a_this)->token_cache = NULL;
+ }
+
+ return cr_input_peek_byte (PRIVATE (a_this)->input,
+ CR_SEEK_CUR, a_offset, a_byte);
+}
+
+/**
+ *Same as cr_tknzr_peek_byte() but this api returns the byte peeked.
+ *@param a_this the current instance of #CRTknzr.
+ *@param a_offset the offset of the peeked byte starting from the current
+ *byte in the parser input stream.
+ *@param a_eof out parameter. If not NULL, is set to TRUE if we reached end of
+ *file, FALE otherwise. If the caller sets it to NULL, this parameter
+ *is just ignored.
+ *@return the peeked byte.
+ */
+guchar
+cr_tknzr_peek_byte2 (CRTknzr * a_this, gulong a_offset, gboolean * a_eof)
+{
+ g_return_val_if_fail (a_this && PRIVATE (a_this)
+ && PRIVATE (a_this)->input, 0);
+
+ return cr_input_peek_byte2 (PRIVATE (a_this)->input, a_offset, a_eof);
+}
+
+/**
+ *Gets the number of bytes left in the topmost input stream
+ *associated to this parser.
+ *@param a_this the current instance of #CRTknzr
+ *@return the number of bytes left or -1 in case of error.
+ */
+glong
+cr_tknzr_get_nb_bytes_left (CRTknzr * a_this)
+{
+ g_return_val_if_fail (a_this && PRIVATE (a_this)
+ && PRIVATE (a_this)->input, CR_BAD_PARAM_ERROR);
+
+ if (PRIVATE (a_this)->token_cache) {
+ cr_input_set_cur_pos (PRIVATE (a_this)->input,
+ &PRIVATE (a_this)->prev_pos);
+ cr_token_destroy (PRIVATE (a_this)->token_cache);
+ PRIVATE (a_this)->token_cache = NULL;
+ }
+
+ return cr_input_get_nb_bytes_left (PRIVATE (a_this)->input);
+}
+
+enum CRStatus
+cr_tknzr_get_cur_pos (CRTknzr * a_this, CRInputPos * a_pos)
+{
+ g_return_val_if_fail (a_this && PRIVATE (a_this)
+ && PRIVATE (a_this)->input
+ && a_pos, CR_BAD_PARAM_ERROR);
+
+ if (PRIVATE (a_this)->token_cache) {
+ cr_input_set_cur_pos (PRIVATE (a_this)->input,
+ &PRIVATE (a_this)->prev_pos);
+ cr_token_destroy (PRIVATE (a_this)->token_cache);
+ PRIVATE (a_this)->token_cache = NULL;
+ }
+
+ return cr_input_get_cur_pos (PRIVATE (a_this)->input, a_pos);
+}
+
+enum CRStatus
+cr_tknzr_get_parsing_location (CRTknzr *a_this,
+ CRParsingLocation *a_loc)
+{
+ g_return_val_if_fail (a_this
+ && PRIVATE (a_this)
+ && a_loc,
+ CR_BAD_PARAM_ERROR) ;
+
+ return cr_input_get_parsing_location
+ (PRIVATE (a_this)->input, a_loc) ;
+}
+
+enum CRStatus
+cr_tknzr_get_cur_byte_addr (CRTknzr * a_this, guchar ** a_addr)
+{
+ g_return_val_if_fail (a_this && PRIVATE (a_this)
+ && PRIVATE (a_this)->input, CR_BAD_PARAM_ERROR);
+ if (PRIVATE (a_this)->token_cache) {
+ cr_input_set_cur_pos (PRIVATE (a_this)->input,
+ &PRIVATE (a_this)->prev_pos);
+ cr_token_destroy (PRIVATE (a_this)->token_cache);
+ PRIVATE (a_this)->token_cache = NULL;
+ }
+
+ return cr_input_get_cur_byte_addr (PRIVATE (a_this)->input, a_addr);
+}
+
+enum CRStatus
+cr_tknzr_seek_index (CRTknzr * a_this, enum CRSeekPos a_origin, gint a_pos)
+{
+ g_return_val_if_fail (a_this && PRIVATE (a_this)
+ && PRIVATE (a_this)->input, CR_BAD_PARAM_ERROR);
+
+ if (PRIVATE (a_this)->token_cache) {
+ cr_input_set_cur_pos (PRIVATE (a_this)->input,
+ &PRIVATE (a_this)->prev_pos);
+ cr_token_destroy (PRIVATE (a_this)->token_cache);
+ PRIVATE (a_this)->token_cache = NULL;
+ }
+
+ return cr_input_seek_index (PRIVATE (a_this)->input, a_origin, a_pos);
+}
+
+enum CRStatus
+cr_tknzr_consume_chars (CRTknzr * a_this, guint32 a_char, glong * a_nb_char)
+{
+ gulong consumed = *(gulong *) a_nb_char;
+ enum CRStatus status;
+ g_return_val_if_fail (a_this && PRIVATE (a_this)
+ && PRIVATE (a_this)->input, CR_BAD_PARAM_ERROR);
+
+ if (PRIVATE (a_this)->token_cache) {
+ cr_input_set_cur_pos (PRIVATE (a_this)->input,
+ &PRIVATE (a_this)->prev_pos);
+ cr_token_destroy (PRIVATE (a_this)->token_cache);
+ PRIVATE (a_this)->token_cache = NULL;
+ }
+
+ status = cr_input_consume_chars (PRIVATE (a_this)->input,
+ a_char, &consumed);
+ *a_nb_char = (glong) consumed;
+ return status;
+}
+
+enum CRStatus
+cr_tknzr_set_cur_pos (CRTknzr * a_this, CRInputPos * a_pos)
+{
+ g_return_val_if_fail (a_this && PRIVATE (a_this)
+ && PRIVATE (a_this)->input, CR_BAD_PARAM_ERROR);
+
+ if (PRIVATE (a_this)->token_cache) {
+ cr_token_destroy (PRIVATE (a_this)->token_cache);
+ PRIVATE (a_this)->token_cache = NULL;
+ }
+
+ return cr_input_set_cur_pos (PRIVATE (a_this)->input, a_pos);
+}
+
+enum CRStatus
+cr_tknzr_unget_token (CRTknzr * a_this, CRToken * a_token)
+{
+ g_return_val_if_fail (a_this && PRIVATE (a_this)
+ && PRIVATE (a_this)->token_cache == NULL,
+ CR_BAD_PARAM_ERROR);
+
+ PRIVATE (a_this)->token_cache = a_token;
+
+ return CR_OK;
+}
+
+/**
+ *Returns the next token of the input stream.
+ *This method is really central. Each parsing
+ *method calls it.
+ *@param a_this the current tokenizer.
+ *@param a_tk out parameter. The returned token.
+ *for the sake of mem leak avoidance, *a_tk must
+ *be NULL.
+ *@param CR_OK upon successful completion, an error code
+ *otherwise.
+ */
+enum CRStatus
+cr_tknzr_get_next_token (CRTknzr * a_this, CRToken ** a_tk)
+{
+ enum CRStatus status = CR_OK;
+ CRToken *token = NULL;
+ CRInputPos init_pos;
+ guint32 next_char = 0;
+ guchar next_bytes[4] = { 0 };
+ gboolean reached_eof = FALSE;
+ CRInput *input = NULL;
+ CRString *str = NULL;
+ CRRgb *rgb = NULL;
+ CRParsingLocation location = {0} ;
+
+ g_return_val_if_fail (a_this && PRIVATE (a_this)
+ && a_tk && *a_tk == NULL
+ && PRIVATE (a_this)->input,
+ CR_BAD_PARAM_ERROR);
+
+ if (PRIVATE (a_this)->token_cache) {
+ *a_tk = PRIVATE (a_this)->token_cache;
+ PRIVATE (a_this)->token_cache = NULL;
+ return CR_OK;
+ }
+
+ RECORD_INITIAL_POS (a_this, &init_pos);
+
+ status = cr_input_get_end_of_file
+ (PRIVATE (a_this)->input, &reached_eof);
+ ENSURE_PARSING_COND (status == CR_OK);
+
+ if (reached_eof == TRUE) {
+ status = CR_END_OF_INPUT_ERROR;
+ goto error;
+ }
+
+ input = PRIVATE (a_this)->input;
+
+ PEEK_NEXT_CHAR (a_this, &next_char);
+ token = cr_token_new ();
+ ENSURE_PARSING_COND (token);
+
+ switch (next_char) {
+ case '@':
+ {
+ if (BYTE (input, 2, NULL) == 'f'
+ && BYTE (input, 3, NULL) == 'o'
+ && BYTE (input, 4, NULL) == 'n'
+ && BYTE (input, 5, NULL) == 't'
+ && BYTE (input, 6, NULL) == '-'
+ && BYTE (input, 7, NULL) == 'f'
+ && BYTE (input, 8, NULL) == 'a'
+ && BYTE (input, 9, NULL) == 'c'
+ && BYTE (input, 10, NULL) == 'e') {
+ SKIP_CHARS (a_this, 1);
+ cr_tknzr_get_parsing_location
+ (a_this, &location) ;
+ SKIP_CHARS (a_this, 9);
+ status = cr_token_set_font_face_sym (token);
+ CHECK_PARSING_STATUS (status, TRUE);
+ cr_parsing_location_copy (&token->location,
+ &location) ;
+ goto done;
+ }
+
+ if (BYTE (input, 2, NULL) == 'c'
+ && BYTE (input, 3, NULL) == 'h'
+ && BYTE (input, 4, NULL) == 'a'
+ && BYTE (input, 5, NULL) == 'r'
+ && BYTE (input, 6, NULL) == 's'
+ && BYTE (input, 7, NULL) == 'e'
+ && BYTE (input, 8, NULL) == 't') {
+ SKIP_CHARS (a_this, 1);
+ cr_tknzr_get_parsing_location
+ (a_this, &location) ;
+ SKIP_CHARS (a_this, 7);
+ status = cr_token_set_charset_sym (token);
+ CHECK_PARSING_STATUS (status, TRUE);
+ cr_parsing_location_copy (&token->location,
+ &location) ;
+ goto done;
+ }
+
+ if (BYTE (input, 2, NULL) == 'i'
+ && BYTE (input, 3, NULL) == 'm'
+ && BYTE (input, 4, NULL) == 'p'
+ && BYTE (input, 5, NULL) == 'o'
+ && BYTE (input, 6, NULL) == 'r'
+ && BYTE (input, 7, NULL) == 't') {
+ SKIP_CHARS (a_this, 1);
+ cr_tknzr_get_parsing_location
+ (a_this, &location) ;
+ SKIP_CHARS (a_this, 6);
+ status = cr_token_set_import_sym (token);
+ CHECK_PARSING_STATUS (status, TRUE);
+ cr_parsing_location_copy (&token->location,
+ &location) ;
+ goto done;
+ }
+
+ if (BYTE (input, 2, NULL) == 'm'
+ && BYTE (input, 3, NULL) == 'e'
+ && BYTE (input, 4, NULL) == 'd'
+ && BYTE (input, 5, NULL) == 'i'
+ && BYTE (input, 6, NULL) == 'a') {
+ SKIP_CHARS (a_this, 1);
+ cr_tknzr_get_parsing_location (a_this,
+ &location) ;
+ SKIP_CHARS (a_this, 5);
+ status = cr_token_set_media_sym (token);
+ CHECK_PARSING_STATUS (status, TRUE);
+ cr_parsing_location_copy (&token->location,
+ &location) ;
+ goto done;
+ }
+
+ if (BYTE (input, 2, NULL) == 'p'
+ && BYTE (input, 3, NULL) == 'a'
+ && BYTE (input, 4, NULL) == 'g'
+ && BYTE (input, 5, NULL) == 'e') {
+ SKIP_CHARS (a_this, 1);
+ cr_tknzr_get_parsing_location (a_this,
+ &location) ;
+ SKIP_CHARS (a_this, 4);
+ status = cr_token_set_page_sym (token);
+ CHECK_PARSING_STATUS (status, TRUE);
+ cr_parsing_location_copy (&token->location,
+ &location) ;
+ goto done;
+ }
+ status = cr_tknzr_parse_atkeyword (a_this, &str);
+ if (status == CR_OK) {
+ status = cr_token_set_atkeyword (token, str);
+ CHECK_PARSING_STATUS (status, TRUE);
+ if (str) {
+ cr_parsing_location_copy (&token->location,
+ &str->location) ;
+ }
+ goto done;
+ }
+ }
+ break;
+
+ case 'u':
+
+ if (BYTE (input, 2, NULL) == 'r'
+ && BYTE (input, 3, NULL) == 'l'
+ && BYTE (input, 4, NULL) == '(') {
+ CRString *str2 = NULL;
+
+ status = cr_tknzr_parse_uri (a_this, &str2);
+ if (status == CR_OK) {
+ status = cr_token_set_uri (token, str2);
+ CHECK_PARSING_STATUS (status, TRUE);
+ if (str2) {
+ cr_parsing_location_copy (&token->location,
+ &str2->location) ;
+ }
+ goto done;
+ }
+ }
+ goto fallback;
+ break;
+
+ case 'r':
+ if (BYTE (input, 2, NULL) == 'g'
+ && BYTE (input, 3, NULL) == 'b'
+ && BYTE (input, 4, NULL) == '(') {
+ status = cr_tknzr_parse_rgb (a_this, &rgb);
+ if (status == CR_OK && rgb) {
+ status = cr_token_set_rgb (token, rgb);
+ CHECK_PARSING_STATUS (status, TRUE);
+ if (rgb) {
+ cr_parsing_location_copy (&token->location,
+ &rgb->location) ;
+ }
+ rgb = NULL;
+ goto done;
+ }
+
+ }
+ goto fallback;
+ break;
+
+ case '<':
+ if (BYTE (input, 2, NULL) == '!'
+ && BYTE (input, 3, NULL) == '-'
+ && BYTE (input, 4, NULL) == '-') {
+ SKIP_CHARS (a_this, 1);
+ cr_tknzr_get_parsing_location (a_this,
+ &location) ;
+ SKIP_CHARS (a_this, 3);
+ status = cr_token_set_cdo (token);
+ CHECK_PARSING_STATUS (status, TRUE);
+ cr_parsing_location_copy (&token->location,
+ &location) ;
+ goto done;
+ }
+ break;
+
+ case '-':
+ if (BYTE (input, 2, NULL) == '-'
+ && BYTE (input, 3, NULL) == '>') {
+ SKIP_CHARS (a_this, 1);
+ cr_tknzr_get_parsing_location (a_this,
+ &location) ;
+ SKIP_CHARS (a_this, 2);
+ status = cr_token_set_cdc (token);
+ CHECK_PARSING_STATUS (status, TRUE);
+ cr_parsing_location_copy (&token->location,
+ &location) ;
+ goto done;
+ } else {
+ status = cr_tknzr_parse_ident
+ (a_this, &str);
+ if (status == CR_OK) {
+ cr_token_set_ident
+ (token, str);
+ if (str) {
+ cr_parsing_location_copy (&token->location,
+ &str->location) ;
+ }
+ goto done;
+ } else {
+ goto parse_number;
+ }
+ }
+ break;
+
+ case '~':
+ if (BYTE (input, 2, NULL) == '=') {
+ SKIP_CHARS (a_this, 1);
+ cr_tknzr_get_parsing_location (a_this,
+ &location) ;
+ SKIP_CHARS (a_this, 1);
+ status = cr_token_set_includes (token);
+ CHECK_PARSING_STATUS (status, TRUE);
+ cr_parsing_location_copy (&token->location,
+ &location) ;
+ goto done;
+ }
+ break;
+
+ case '|':
+ if (BYTE (input, 2, NULL) == '=') {
+ SKIP_CHARS (a_this, 1);
+ cr_tknzr_get_parsing_location (a_this,
+ &location) ;
+ SKIP_CHARS (a_this, 1);
+ status = cr_token_set_dashmatch (token);
+ CHECK_PARSING_STATUS (status, TRUE);
+ cr_parsing_location_copy (&token->location,
+ &location) ;
+ goto done;
+ }
+ break;
+
+ case '/':
+ if (BYTE (input, 2, NULL) == '*') {
+ status = cr_tknzr_parse_comment (a_this, &str);
+
+ if (status == CR_OK) {
+ status = cr_token_set_comment (token, str);
+ str = NULL;
+ CHECK_PARSING_STATUS (status, TRUE);
+ if (str) {
+ cr_parsing_location_copy (&token->location,
+ &str->location) ;
+ }
+ goto done;
+ }
+ }
+ break ;
+
+ case ';':
+ SKIP_CHARS (a_this, 1);
+ cr_tknzr_get_parsing_location (a_this,
+ &location) ;
+ status = cr_token_set_semicolon (token);
+ CHECK_PARSING_STATUS (status, TRUE);
+ cr_parsing_location_copy (&token->location,
+ &location) ;
+ goto done;
+
+ case '{':
+ SKIP_CHARS (a_this, 1);
+ cr_tknzr_get_parsing_location (a_this,
+ &location) ;
+ status = cr_token_set_cbo (token);
+ CHECK_PARSING_STATUS (status, TRUE);
+ cr_tknzr_get_parsing_location (a_this,
+ &location) ;
+ goto done;
+
+ case '}':
+ SKIP_CHARS (a_this, 1);
+ cr_tknzr_get_parsing_location (a_this,
+ &location) ;
+ status = cr_token_set_cbc (token);
+ CHECK_PARSING_STATUS (status, TRUE);
+ cr_parsing_location_copy (&token->location,
+ &location) ;
+ goto done;
+
+ case '(':
+ SKIP_CHARS (a_this, 1);
+ cr_tknzr_get_parsing_location (a_this,
+ &location) ;
+ status = cr_token_set_po (token);
+ CHECK_PARSING_STATUS (status, TRUE);
+ cr_parsing_location_copy (&token->location,
+ &location) ;
+ goto done;
+
+ case ')':
+ SKIP_CHARS (a_this, 1);
+ cr_tknzr_get_parsing_location (a_this,
+ &location) ;
+ status = cr_token_set_pc (token);
+ CHECK_PARSING_STATUS (status, TRUE);
+ cr_parsing_location_copy (&token->location,
+ &location) ;
+ goto done;
+
+ case '[':
+ SKIP_CHARS (a_this, 1);
+ cr_tknzr_get_parsing_location (a_this,
+ &location) ;
+ status = cr_token_set_bo (token);
+ CHECK_PARSING_STATUS (status, TRUE);
+ cr_parsing_location_copy (&token->location,
+ &location) ;
+ goto done;
+
+ case ']':
+ SKIP_CHARS (a_this, 1);
+ cr_tknzr_get_parsing_location (a_this,
+ &location) ;
+ status = cr_token_set_bc (token);
+ CHECK_PARSING_STATUS (status, TRUE);
+ cr_parsing_location_copy (&token->location,
+ &location) ;
+ goto done;
+
+ case ' ':
+ case '\t':
+ case '\n':
+ case '\f':
+ case '\r':
+ {
+ guchar *start = NULL,
+ *end = NULL;
+
+ status = cr_tknzr_parse_w (a_this, &start,
+ &end, &location);
+ if (status == CR_OK) {
+ status = cr_token_set_s (token);
+ CHECK_PARSING_STATUS (status, TRUE);
+ cr_tknzr_get_parsing_location (a_this,
+ &location) ;
+ goto done;
+ }
+ }
+ break;
+
+ case '#':
+ {
+ status = cr_tknzr_parse_hash (a_this, &str);
+ if (status == CR_OK && str) {
+ status = cr_token_set_hash (token, str);
+ CHECK_PARSING_STATUS (status, TRUE);
+ if (str) {
+ cr_parsing_location_copy (&token->location,
+ &str->location) ;
+ }
+ str = NULL;
+ goto done;
+ }
+ }
+ break;
+
+ case '\'':
+ case '"':
+ status = cr_tknzr_parse_string (a_this, &str);
+ if (status == CR_OK && str) {
+ status = cr_token_set_string (token, str);
+ CHECK_PARSING_STATUS (status, TRUE);
+ if (str) {
+ cr_parsing_location_copy (&token->location,
+ &str->location) ;
+ }
+ str = NULL;
+ goto done;
+ }
+ break;
+
+ case '!':
+ status = cr_tknzr_parse_important (a_this, &location);
+ if (status == CR_OK) {
+ status = cr_token_set_important_sym (token);
+ CHECK_PARSING_STATUS (status, TRUE);
+ cr_parsing_location_copy (&token->location,
+ &location) ;
+ goto done;
+ }
+ break;
+
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ case '.':
+ case '+':
+ /* '-' case is handled separately above for --> comments */
+ parse_number:
+ {
+ CRNum *num = NULL;
+
+ status = cr_tknzr_parse_num (a_this, &num);
+ if (status == CR_OK && num) {
+ next_bytes[0] = BYTE (input, 1, NULL);
+ next_bytes[1] = BYTE (input, 2, NULL);
+ next_bytes[2] = BYTE (input, 3, NULL);
+ next_bytes[3] = BYTE (input, 4, NULL);
+
+ if (next_bytes[0] == 'e'
+ && next_bytes[1] == 'm') {
+ num->type = NUM_LENGTH_EM;
+ status = cr_token_set_ems (token,
+ num);
+ num = NULL;
+ SKIP_CHARS (a_this, 2);
+ } else if (next_bytes[0] == 'e'
+ && next_bytes[1] == 'x') {
+ num->type = NUM_LENGTH_EX;
+ status = cr_token_set_exs (token,
+ num);
+ num = NULL;
+ SKIP_CHARS (a_this, 2);
+ } else if (next_bytes[0] == 'p'
+ && next_bytes[1] == 'x') {
+ num->type = NUM_LENGTH_PX;
+ status = cr_token_set_length
+ (token, num, LENGTH_PX_ET);
+ num = NULL;
+ SKIP_CHARS (a_this, 2);
+ } else if (next_bytes[0] == 'c'
+ && next_bytes[1] == 'm') {
+ num->type = NUM_LENGTH_CM;
+ status = cr_token_set_length
+ (token, num, LENGTH_CM_ET);
+ num = NULL;
+ SKIP_CHARS (a_this, 2);
+ } else if (next_bytes[0] == 'm'
+ && next_bytes[1] == 'm') {
+ num->type = NUM_LENGTH_MM;
+ status = cr_token_set_length
+ (token, num, LENGTH_MM_ET);
+ num = NULL;
+ SKIP_CHARS (a_this, 2);
+ } else if (next_bytes[0] == 'i'
+ && next_bytes[1] == 'n') {
+ num->type = NUM_LENGTH_IN;
+ status = cr_token_set_length
+ (token, num, LENGTH_IN_ET);
+ num = NULL;
+ SKIP_CHARS (a_this, 2);
+ } else if (next_bytes[0] == 'p'
+ && next_bytes[1] == 't') {
+ num->type = NUM_LENGTH_PT;
+ status = cr_token_set_length
+ (token, num, LENGTH_PT_ET);
+ num = NULL;
+ SKIP_CHARS (a_this, 2);
+ } else if (next_bytes[0] == 'p'
+ && next_bytes[1] == 'c') {
+ num->type = NUM_LENGTH_PC;
+ status = cr_token_set_length
+ (token, num, LENGTH_PC_ET);
+ num = NULL;
+ SKIP_CHARS (a_this, 2);
+ } else if (next_bytes[0] == 'd'
+ && next_bytes[1] == 'e'
+ && next_bytes[2] == 'g') {
+ num->type = NUM_ANGLE_DEG;
+ status = cr_token_set_angle
+ (token, num, ANGLE_DEG_ET);
+ num = NULL;
+ SKIP_CHARS (a_this, 3);
+ } else if (next_bytes[0] == 'r'
+ && next_bytes[1] == 'a'
+ && next_bytes[2] == 'd') {
+ num->type = NUM_ANGLE_RAD;
+ status = cr_token_set_angle
+ (token, num, ANGLE_RAD_ET);
+ num = NULL;
+ SKIP_CHARS (a_this, 3);
+ } else if (next_bytes[0] == 'g'
+ && next_bytes[1] == 'r'
+ && next_bytes[2] == 'a'
+ && next_bytes[3] == 'd') {
+ num->type = NUM_ANGLE_GRAD;
+ status = cr_token_set_angle
+ (token, num, ANGLE_GRAD_ET);
+ num = NULL;
+ SKIP_CHARS (a_this, 4);
+ } else if (next_bytes[0] == 'm'
+ && next_bytes[1] == 's') {
+ num->type = NUM_TIME_MS;
+ status = cr_token_set_time
+ (token, num, TIME_MS_ET);
+ num = NULL;
+ SKIP_CHARS (a_this, 2);
+ } else if (next_bytes[0] == 's') {
+ num->type = NUM_TIME_S;
+ status = cr_token_set_time
+ (token, num, TIME_S_ET);
+ num = NULL;
+ SKIP_CHARS (a_this, 1);
+ } else if (next_bytes[0] == 'H'
+ && next_bytes[1] == 'z') {
+ num->type = NUM_FREQ_HZ;
+ status = cr_token_set_freq
+ (token, num, FREQ_HZ_ET);
+ num = NULL;
+ SKIP_CHARS (a_this, 2);
+ } else if (next_bytes[0] == 'k'
+ && next_bytes[1] == 'H'
+ && next_bytes[2] == 'z') {
+ num->type = NUM_FREQ_KHZ;
+ status = cr_token_set_freq
+ (token, num, FREQ_KHZ_ET);
+ num = NULL;
+ SKIP_CHARS (a_this, 3);
+ } else if (next_bytes[0] == '%') {
+ num->type = NUM_PERCENTAGE;
+ status = cr_token_set_percentage
+ (token, num);
+ num = NULL;
+ SKIP_CHARS (a_this, 1);
+ } else {
+ status = cr_tknzr_parse_ident (a_this,
+ &str);
+ if (status == CR_OK && str) {
+ num->type = NUM_UNKNOWN_TYPE;
+ status = cr_token_set_dimen
+ (token, num, str);
+ num = NULL;
+ CHECK_PARSING_STATUS (status,
+ TRUE);
+ str = NULL;
+ } else {
+ status = cr_token_set_number
+ (token, num);
+ num = NULL;
+ CHECK_PARSING_STATUS (status, CR_OK);
+ str = NULL;
+ }
+ }
+ if (token && token->u.num) {
+ cr_parsing_location_copy (&token->location,
+ &token->u.num->location) ;
+ } else {
+ status = CR_ERROR ;
+ }
+ goto done ;
+ }
+ }
+ break;
+
+ default:
+ fallback:
+ /*process the fallback cases here */
+
+ if (next_char == '\\'
+ || (cr_utils_is_nonascii (next_bytes[0]) == TRUE)
+ || ((next_char >= 'a') && (next_char <= 'z'))
+ || ((next_char >= 'A') && (next_char <= 'Z'))) {
+ status = cr_tknzr_parse_ident (a_this, &str);
+ if (status == CR_OK && str) {
+ guint32 next_c = 0;
+
+ status = cr_input_peek_char
+ (PRIVATE (a_this)->input, &next_c);
+
+ if (status == CR_OK && next_c == '(') {
+
+ SKIP_CHARS (a_this, 1);
+ status = cr_token_set_function
+ (token, str);
+ CHECK_PARSING_STATUS (status, TRUE);
+ /*ownership is transferred
+ *to token by cr_token_set_function.
+ */
+ if (str) {
+ cr_parsing_location_copy (&token->location,
+ &str->location) ;
+ }
+ str = NULL;
+ } else {
+ status = cr_token_set_ident (token,
+ str);
+ CHECK_PARSING_STATUS (status, TRUE);
+ if (str) {
+ cr_parsing_location_copy (&token->location,
+ &str->location) ;
+ }
+ str = NULL;
+ }
+ goto done;
+ } else {
+ if (str) {
+ cr_string_destroy (str);
+ str = NULL;
+ }
+ }
+ }
+ break;
+ }
+
+ READ_NEXT_CHAR (a_this, &next_char);
+ cr_tknzr_get_parsing_location (a_this,
+ &location) ;
+ status = cr_token_set_delim (token, next_char);
+ CHECK_PARSING_STATUS (status, TRUE);
+ cr_parsing_location_copy (&token->location,
+ &location) ;
+ done:
+
+ if (status == CR_OK && token) {
+ *a_tk = token;
+ /*
+ *store the previous position input stream pos.
+ */
+ memmove (&PRIVATE (a_this)->prev_pos,
+ &init_pos, sizeof (CRInputPos));
+ return CR_OK;
+ }
+
+ error:
+ if (token) {
+ cr_token_destroy (token);
+ token = NULL;
+ }
+
+ if (str) {
+ cr_string_destroy (str);
+ str = NULL;
+ }
+ cr_tknzr_set_cur_pos (a_this, &init_pos);
+ return status;
+
+}
+
+enum CRStatus
+cr_tknzr_parse_token (CRTknzr * a_this, enum CRTokenType a_type,
+ enum CRTokenExtraType a_et, gpointer a_res,
+ gpointer a_extra_res)
+{
+ enum CRStatus status = CR_OK;
+ CRToken *token = NULL;
+
+ g_return_val_if_fail (a_this && PRIVATE (a_this)
+ && PRIVATE (a_this)->input
+ && a_res, CR_BAD_PARAM_ERROR);
+
+ status = cr_tknzr_get_next_token (a_this, &token);
+ if (status != CR_OK)
+ return status;
+ if (token == NULL)
+ return CR_PARSING_ERROR;
+
+ if (token->type == a_type) {
+ switch (a_type) {
+ case NO_TK:
+ case S_TK:
+ case CDO_TK:
+ case CDC_TK:
+ case INCLUDES_TK:
+ case DASHMATCH_TK:
+ case IMPORT_SYM_TK:
+ case PAGE_SYM_TK:
+ case MEDIA_SYM_TK:
+ case FONT_FACE_SYM_TK:
+ case CHARSET_SYM_TK:
+ case IMPORTANT_SYM_TK:
+ status = CR_OK;
+ break;
+
+ case STRING_TK:
+ case IDENT_TK:
+ case HASH_TK:
+ case ATKEYWORD_TK:
+ case FUNCTION_TK:
+ case COMMENT_TK:
+ case URI_TK:
+ *((CRString **) a_res) = token->u.str;
+ token->u.str = NULL;
+ status = CR_OK;
+ break;
+
+ case EMS_TK:
+ case EXS_TK:
+ case PERCENTAGE_TK:
+ case NUMBER_TK:
+ *((CRNum **) a_res) = token->u.num;
+ token->u.num = NULL;
+ status = CR_OK;
+ break;
+
+ case LENGTH_TK:
+ case ANGLE_TK:
+ case TIME_TK:
+ case FREQ_TK:
+ if (token->extra_type == a_et) {
+ *((CRNum **) a_res) = token->u.num;
+ token->u.num = NULL;
+ status = CR_OK;
+ }
+ break;
+
+ case DIMEN_TK:
+ *((CRNum **) a_res) = token->u.num;
+ if (a_extra_res == NULL) {
+ status = CR_BAD_PARAM_ERROR;
+ goto error;
+ }
+
+ *((CRString **) a_extra_res) = token->dimen;
+ token->u.num = NULL;
+ token->dimen = NULL;
+ status = CR_OK;
+ break;
+
+ case DELIM_TK:
+ *((guint32 *) a_res) = token->u.unichar;
+ status = CR_OK;
+ break;
+
+ case UNICODERANGE_TK:
+ default:
+ status = CR_PARSING_ERROR;
+ break;
+ }
+
+ cr_token_destroy (token);
+ token = NULL;
+ } else {
+ cr_tknzr_unget_token (a_this, token);
+ token = NULL;
+ status = CR_PARSING_ERROR;
+ }
+
+ return status;
+
+ error:
+
+ if (token) {
+ cr_tknzr_unget_token (a_this, token);
+ token = NULL;
+ }
+
+ return status;
+}
+
+void
+cr_tknzr_destroy (CRTknzr * a_this)
+{
+ g_return_if_fail (a_this);
+
+ if (PRIVATE (a_this) && PRIVATE (a_this)->input) {
+ if (cr_input_unref (PRIVATE (a_this)->input)
+ == TRUE) {
+ PRIVATE (a_this)->input = NULL;
+ }
+ }
+
+ if (PRIVATE (a_this)->token_cache) {
+ cr_token_destroy (PRIVATE (a_this)->token_cache);
+ PRIVATE (a_this)->token_cache = NULL;
+ }
+
+ if (PRIVATE (a_this)) {
+ g_free (PRIVATE (a_this));
+ PRIVATE (a_this) = NULL;
+ }
+
+ g_free (a_this);
+}
diff --git a/src/st/croco/cr-tknzr.h b/src/st/croco/cr-tknzr.h
new file mode 100644
index 0000000..13985b3
--- /dev/null
+++ b/src/st/croco/cr-tknzr.h
@@ -0,0 +1,115 @@
+/* -*- Mode: C; indent-tabs-mode:nil; c-basic-offset: 8-*- */
+
+/*
+ * This file is part of The Croco Library
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2.1 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * 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 Lesser General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ *
+ * Author: Dodji Seketeli
+ * See COPYRIGHTS file for coypyright information.
+ */
+
+/**
+ *@file
+ *The declaration of the #CRTknzr (tokenizer)
+ *class.
+ */
+
+#ifndef __CR_TKNZR_H__
+#define __CR_TKNZR_H__
+
+#include "cr-utils.h"
+#include "cr-input.h"
+#include "cr-token.h"
+
+G_BEGIN_DECLS
+
+
+typedef struct _CRTknzr CRTknzr ;
+typedef struct _CRTknzrPriv CRTknzrPriv ;
+
+/**
+ *The tokenizer is the class that knows
+ *about all the css token. Its main job is
+ *to return the next token found in the character
+ *input stream.
+ */
+struct _CRTknzr
+{
+ /*the private data of the tokenizer.*/
+ CRTknzrPriv *priv ;
+} ;
+
+CRTknzr * cr_tknzr_new (CRInput *a_input) ;
+
+CRTknzr * cr_tknzr_new_from_uri (const guchar *a_file_uri,
+ enum CREncoding a_enc) ;
+
+CRTknzr * cr_tknzr_new_from_buf (guchar *a_buf, gulong a_len,
+ enum CREncoding a_enc,
+ gboolean a_free_at_destroy) ;
+
+gboolean cr_tknzr_unref (CRTknzr *a_this) ;
+
+void cr_tknzr_ref (CRTknzr *a_this) ;
+
+enum CRStatus cr_tknzr_read_byte (CRTknzr *a_this, guchar *a_byte) ;
+
+enum CRStatus cr_tknzr_read_char (CRTknzr *a_this, guint32 *a_char);
+
+enum CRStatus cr_tknzr_peek_char (CRTknzr *a_this, guint32 *a_char) ;
+
+enum CRStatus cr_tknzr_peek_byte (CRTknzr *a_this, gulong a_offset,
+ guchar *a_byte) ;
+
+guchar cr_tknzr_peek_byte2 (CRTknzr *a_this, gulong a_offset,
+ gboolean *a_eof) ;
+
+enum CRStatus cr_tknzr_set_cur_pos (CRTknzr *a_this, CRInputPos *a_pos) ;
+
+glong cr_tknzr_get_nb_bytes_left (CRTknzr *a_this) ;
+
+enum CRStatus cr_tknzr_get_cur_pos (CRTknzr *a_this, CRInputPos *a_pos) ;
+
+enum CRStatus cr_tknzr_get_parsing_location (CRTknzr *a_this,
+ CRParsingLocation *a_loc) ;
+
+enum CRStatus cr_tknzr_seek_index (CRTknzr *a_this,
+ enum CRSeekPos a_origin,
+ gint a_pos) ;
+
+enum CRStatus cr_tknzr_get_cur_byte_addr (CRTknzr *a_this, guchar **a_addr) ;
+
+
+enum CRStatus cr_tknzr_consume_chars (CRTknzr *a_this, guint32 a_char,
+ glong *a_nb_char) ;
+
+enum CRStatus cr_tknzr_get_next_token (CRTknzr *a_this, CRToken ** a_tk) ;
+
+enum CRStatus cr_tknzr_unget_token (CRTknzr *a_this, CRToken *a_token) ;
+
+
+enum CRStatus cr_tknzr_parse_token (CRTknzr *a_this, enum CRTokenType a_type,
+ enum CRTokenExtraType a_et, gpointer a_res,
+ gpointer a_extra_res) ;
+enum CRStatus cr_tknzr_set_input (CRTknzr *a_this, CRInput *a_input) ;
+
+enum CRStatus cr_tknzr_get_input (CRTknzr *a_this, CRInput **a_input) ;
+
+void cr_tknzr_destroy (CRTknzr *a_this) ;
+
+G_END_DECLS
+
+#endif /*__CR_TKZNR_H__*/
diff --git a/src/st/croco/cr-token.c b/src/st/croco/cr-token.c
new file mode 100644
index 0000000..91dd632
--- /dev/null
+++ b/src/st/croco/cr-token.c
@@ -0,0 +1,636 @@
+/* -*- Mode: C; indent-tabs-mode:nil; c-basic-offset: 8-*- */
+
+/*
+ * This file is part of The Croco Library
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2.1 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * 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 Lesser General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ *
+ * Author: Dodji Seketeli
+ * see COPYRIGHTS file for copyright information.
+ */
+
+/**
+ *@file
+ *The definition of the #CRToken class.
+ *Abstracts a css2 token.
+ */
+#include <string.h>
+#include "cr-token.h"
+
+/*
+ *TODO: write a CRToken::to_string() method.
+ */
+
+/**
+ *Frees the attributes of the current instance
+ *of #CRtoken.
+ *@param a_this the current instance of #CRToken.
+ */
+static void
+cr_token_clear (CRToken * a_this)
+{
+ g_return_if_fail (a_this);
+
+ switch (a_this->type) {
+ case S_TK:
+ case CDO_TK:
+ case CDC_TK:
+ case INCLUDES_TK:
+ case DASHMATCH_TK:
+ case PAGE_SYM_TK:
+ case MEDIA_SYM_TK:
+ case FONT_FACE_SYM_TK:
+ case CHARSET_SYM_TK:
+ case IMPORT_SYM_TK:
+ case IMPORTANT_SYM_TK:
+ case SEMICOLON_TK:
+ case NO_TK:
+ case DELIM_TK:
+ case CBO_TK:
+ case CBC_TK:
+ case BO_TK:
+ case BC_TK:
+ break;
+
+ case STRING_TK:
+ case IDENT_TK:
+ case HASH_TK:
+ case URI_TK:
+ case FUNCTION_TK:
+ case COMMENT_TK:
+ case ATKEYWORD_TK:
+ if (a_this->u.str) {
+ cr_string_destroy (a_this->u.str);
+ a_this->u.str = NULL;
+ }
+ break;
+
+ case EMS_TK:
+ case EXS_TK:
+ case LENGTH_TK:
+ case ANGLE_TK:
+ case TIME_TK:
+ case FREQ_TK:
+ case PERCENTAGE_TK:
+ case NUMBER_TK:
+ case PO_TK:
+ case PC_TK:
+ if (a_this->u.num) {
+ cr_num_destroy (a_this->u.num);
+ a_this->u.num = NULL;
+ }
+ break;
+
+ case DIMEN_TK:
+ if (a_this->u.num) {
+ cr_num_destroy (a_this->u.num);
+ a_this->u.num = NULL;
+ }
+
+ if (a_this->dimen) {
+ cr_string_destroy (a_this->dimen);
+ a_this->dimen = NULL;
+ }
+
+ break;
+
+ case RGB_TK:
+ if (a_this->u.rgb) {
+ cr_rgb_destroy (a_this->u.rgb) ;
+ a_this->u.rgb = NULL ;
+ }
+ break ;
+
+ case UNICODERANGE_TK:
+ /*not supported yet. */
+ break;
+
+ default:
+ cr_utils_trace_info ("I don't know how to clear this token\n") ;
+ break;
+ }
+
+ a_this->type = NO_TK;
+}
+
+/**
+ *Default constructor of
+ *the #CRToken class.
+ *@return the newly built instance of #CRToken.
+ */
+CRToken *
+cr_token_new (void)
+{
+ CRToken *result = NULL;
+
+ result = g_try_malloc (sizeof (CRToken));
+
+ if (result == NULL) {
+ cr_utils_trace_info ("Out of memory");
+ return NULL;
+ }
+
+ memset (result, 0, sizeof (CRToken));
+
+ return result;
+}
+
+/**
+ *Sets the type of curren instance of
+ *#CRToken to 'S_TK' (S in the css2 spec)
+ *@param a_this the current instance of #CRToken.
+ *@return CR_OK upon successful completion, an error
+ *code otherwise.
+ */
+enum CRStatus
+cr_token_set_s (CRToken * a_this)
+{
+ g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR);
+
+ cr_token_clear (a_this);
+
+ a_this->type = S_TK;
+
+ return CR_OK;
+}
+
+/**
+ *Sets the type of the current instance of
+ *#CRToken to 'CDO_TK' (CDO as said by the css2 spec)
+ *@param a_this the current instance of #CRToken.
+ *@return CR_OK upon successful completion, an error
+ *code otherwise.
+ */
+enum CRStatus
+cr_token_set_cdo (CRToken * a_this)
+{
+ g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR);
+
+ cr_token_clear (a_this);
+
+ a_this->type = CDO_TK;
+
+ return CR_OK;
+}
+
+/**
+ *Sets the type of the current token to
+ *CDC_TK (CDC as said by the css2 spec).
+ *@param a_this the current instance of #CRToken.
+ *@return CR_OK upon successful completion, an error
+ *code otherwise.
+ */
+enum CRStatus
+cr_token_set_cdc (CRToken * a_this)
+{
+ g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR);
+
+ cr_token_clear (a_this);
+
+ a_this->type = CDC_TK;
+
+ return CR_OK;
+}
+
+/**
+ *Sets the type of the current instance of
+ *#CRToken to INCLUDES_TK (INCLUDES as said by the css2 spec).
+ *@param a_this the current instance of #CRToken.
+ *@return CR_OK upon successful completion, an error
+ *code otherwise.
+ */
+enum CRStatus
+cr_token_set_includes (CRToken * a_this)
+{
+ g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR);
+
+ cr_token_clear (a_this);
+
+ a_this->type = INCLUDES_TK;
+
+ return CR_OK;
+}
+
+/**
+ *Sets the type of the current instance of
+ *#CRToken to DASHMATCH_TK (DASHMATCH as said by the css2 spec).
+ *@param a_this the current instance of #CRToken.
+ *@return CR_OK upon successful completion, an error
+ *code otherwise.
+ */
+enum CRStatus
+cr_token_set_dashmatch (CRToken * a_this)
+{
+ g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR);
+
+ cr_token_clear (a_this);
+
+ a_this->type = DASHMATCH_TK;
+
+ return CR_OK;
+}
+
+enum CRStatus
+cr_token_set_comment (CRToken * a_this, CRString * a_str)
+{
+ g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR);
+
+ cr_token_clear (a_this);
+ a_this->type = COMMENT_TK;
+ a_this->u.str = a_str ;
+ return CR_OK;
+}
+
+enum CRStatus
+cr_token_set_string (CRToken * a_this, CRString * a_str)
+{
+ g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR);
+
+ cr_token_clear (a_this);
+
+ a_this->type = STRING_TK;
+
+ a_this->u.str = a_str ;
+
+ return CR_OK;
+}
+
+enum CRStatus
+cr_token_set_ident (CRToken * a_this, CRString * a_ident)
+{
+ g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR);
+
+ cr_token_clear (a_this);
+ a_this->type = IDENT_TK;
+ a_this->u.str = a_ident;
+ return CR_OK;
+}
+
+
+enum CRStatus
+cr_token_set_function (CRToken * a_this, CRString * a_fun_name)
+{
+ g_return_val_if_fail (a_this,
+ CR_BAD_PARAM_ERROR);
+
+ cr_token_clear (a_this);
+ a_this->type = FUNCTION_TK;
+ a_this->u.str = a_fun_name;
+ return CR_OK;
+}
+
+enum CRStatus
+cr_token_set_hash (CRToken * a_this, CRString * a_hash)
+{
+ g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR);
+
+ cr_token_clear (a_this);
+ a_this->type = HASH_TK;
+ a_this->u.str = a_hash;
+
+ return CR_OK;
+}
+
+enum CRStatus
+cr_token_set_rgb (CRToken * a_this, CRRgb * a_rgb)
+{
+ g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR);
+
+ cr_token_clear (a_this);
+ a_this->type = RGB_TK;
+ a_this->u.rgb = a_rgb;
+
+ return CR_OK;
+}
+
+enum CRStatus
+cr_token_set_import_sym (CRToken * a_this)
+{
+ g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR);
+
+ cr_token_clear (a_this);
+
+ a_this->type = IMPORT_SYM_TK;
+
+ return CR_OK;
+}
+
+enum CRStatus
+cr_token_set_page_sym (CRToken * a_this)
+{
+ g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR);
+
+ cr_token_clear (a_this);
+
+ a_this->type = PAGE_SYM_TK;
+
+ return CR_OK;
+}
+
+enum CRStatus
+cr_token_set_media_sym (CRToken * a_this)
+{
+ g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR);
+
+ cr_token_clear (a_this);
+
+ a_this->type = MEDIA_SYM_TK;
+
+ return CR_OK;
+}
+
+enum CRStatus
+cr_token_set_font_face_sym (CRToken * a_this)
+{
+ g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR);
+
+ cr_token_clear (a_this);
+ a_this->type = FONT_FACE_SYM_TK;
+
+ return CR_OK;
+}
+
+enum CRStatus
+cr_token_set_charset_sym (CRToken * a_this)
+{
+ g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR);
+
+ cr_token_clear (a_this);
+ a_this->type = CHARSET_SYM_TK;
+
+ return CR_OK;
+}
+
+enum CRStatus
+cr_token_set_atkeyword (CRToken * a_this, CRString * a_atname)
+{
+ g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR);
+
+ cr_token_clear (a_this);
+ a_this->type = ATKEYWORD_TK;
+ a_this->u.str = a_atname;
+ return CR_OK;
+}
+
+enum CRStatus
+cr_token_set_important_sym (CRToken * a_this)
+{
+ g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR);
+ cr_token_clear (a_this);
+ a_this->type = IMPORTANT_SYM_TK;
+ return CR_OK;
+}
+
+enum CRStatus
+cr_token_set_ems (CRToken * a_this, CRNum * a_num)
+{
+ g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR);
+ cr_token_clear (a_this);
+ a_this->type = EMS_TK;
+ a_this->u.num = a_num;
+ return CR_OK;
+}
+
+enum CRStatus
+cr_token_set_exs (CRToken * a_this, CRNum * a_num)
+{
+ g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR);
+ cr_token_clear (a_this);
+ a_this->type = EXS_TK;
+ a_this->u.num = a_num;
+ return CR_OK;
+}
+
+enum CRStatus
+cr_token_set_length (CRToken * a_this, CRNum * a_num,
+ enum CRTokenExtraType a_et)
+{
+ g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR);
+
+ cr_token_clear (a_this);
+
+ a_this->type = LENGTH_TK;
+ a_this->extra_type = a_et;
+ a_this->u.num = a_num;
+
+ return CR_OK;
+}
+
+enum CRStatus
+cr_token_set_angle (CRToken * a_this, CRNum * a_num,
+ enum CRTokenExtraType a_et)
+{
+ g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR);
+
+ cr_token_clear (a_this);
+
+ a_this->type = ANGLE_TK;
+ a_this->extra_type = a_et;
+ a_this->u.num = a_num;
+
+ return CR_OK;
+}
+
+enum CRStatus
+cr_token_set_time (CRToken * a_this, CRNum * a_num,
+ enum CRTokenExtraType a_et)
+{
+ g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR);
+
+ cr_token_clear (a_this);
+
+ a_this->type = TIME_TK;
+ a_this->extra_type = a_et;
+ a_this->u.num = a_num;
+
+ return CR_OK;
+}
+
+enum CRStatus
+cr_token_set_freq (CRToken * a_this, CRNum * a_num,
+ enum CRTokenExtraType a_et)
+{
+ g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR);
+
+ cr_token_clear (a_this);
+
+ a_this->type = FREQ_TK;
+ a_this->extra_type = a_et;
+ a_this->u.num = a_num;
+
+ return CR_OK;
+}
+
+enum CRStatus
+cr_token_set_dimen (CRToken * a_this, CRNum * a_num,
+ CRString * a_dim)
+{
+ g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR);
+ cr_token_clear (a_this);
+ a_this->type = DIMEN_TK;
+ a_this->u.num = a_num;
+ a_this->dimen = a_dim;
+ return CR_OK;
+
+}
+
+enum CRStatus
+cr_token_set_percentage (CRToken * a_this, CRNum * a_num)
+{
+ g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR);
+
+ cr_token_clear (a_this);
+
+ a_this->type = PERCENTAGE_TK;
+ a_this->u.num = a_num;
+
+ return CR_OK;
+}
+
+enum CRStatus
+cr_token_set_number (CRToken * a_this, CRNum * a_num)
+{
+ g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR);
+
+ cr_token_clear (a_this);
+
+ a_this->type = NUMBER_TK;
+ a_this->u.num = a_num;
+ return CR_OK;
+}
+
+enum CRStatus
+cr_token_set_uri (CRToken * a_this, CRString * a_uri)
+{
+ g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR);
+
+ cr_token_clear (a_this);
+
+ a_this->type = URI_TK;
+ a_this->u.str = a_uri;
+
+ return CR_OK;
+}
+
+enum CRStatus
+cr_token_set_delim (CRToken * a_this, guint32 a_char)
+{
+ g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR);
+
+ cr_token_clear (a_this);
+
+ a_this->type = DELIM_TK;
+ a_this->u.unichar = a_char;
+
+ return CR_OK;
+}
+
+enum CRStatus
+cr_token_set_semicolon (CRToken * a_this)
+{
+ g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR);
+
+ cr_token_clear (a_this);
+
+ a_this->type = SEMICOLON_TK;
+
+ return CR_OK;
+}
+
+enum CRStatus
+cr_token_set_cbo (CRToken * a_this)
+{
+ g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR);
+
+ cr_token_clear (a_this);
+
+ a_this->type = CBO_TK;
+
+ return CR_OK;
+}
+
+enum CRStatus
+cr_token_set_cbc (CRToken * a_this)
+{
+ g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR);
+
+ cr_token_clear (a_this);
+
+ a_this->type = CBC_TK;
+
+ return CR_OK;
+}
+
+enum CRStatus
+cr_token_set_po (CRToken * a_this)
+{
+ g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR);
+
+ cr_token_clear (a_this);
+
+ a_this->type = PO_TK;
+
+ return CR_OK;
+}
+
+enum CRStatus
+cr_token_set_pc (CRToken * a_this)
+{
+ g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR);
+
+ cr_token_clear (a_this);
+
+ a_this->type = PC_TK;
+
+ return CR_OK;
+}
+
+enum CRStatus
+cr_token_set_bo (CRToken * a_this)
+{
+ g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR);
+
+ cr_token_clear (a_this);
+
+ a_this->type = BO_TK;
+
+ return CR_OK;
+}
+
+enum CRStatus
+cr_token_set_bc (CRToken * a_this)
+{
+ g_return_val_if_fail (a_this, CR_BAD_PARAM_ERROR);
+
+ cr_token_clear (a_this);
+
+ a_this->type = BC_TK;
+
+ return CR_OK;
+}
+
+/**
+ *The destructor of the #CRToken class.
+ *@param a_this the current instance of #CRToken.
+ */
+void
+cr_token_destroy (CRToken * a_this)
+{
+ g_return_if_fail (a_this);
+
+ cr_token_clear (a_this);
+
+ g_free (a_this);
+}
diff --git a/src/st/croco/cr-token.h b/src/st/croco/cr-token.h
new file mode 100644
index 0000000..f1257b7
--- /dev/null
+++ b/src/st/croco/cr-token.h
@@ -0,0 +1,212 @@
+/* -*- Mode: C; indent-tabs-mode:nil; c-basic-offset: 8-*- */
+
+/*
+ * This file is part of The Croco Library
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2.1 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * 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 Lesser General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ *
+ * Author: Dodji Seketeli
+ * See COPYRIGHTS file for copyright information.
+ */
+
+#ifndef __CR_TOKEN_H__
+#define __CR_TOKEN_H__
+
+#include "cr-utils.h"
+#include "cr-input.h"
+#include "cr-num.h"
+#include "cr-rgb.h"
+#include "cr-string.h"
+#include "cr-parsing-location.h"
+
+G_BEGIN_DECLS
+
+enum CRTokenType
+{
+ NO_TK,
+ S_TK,
+ CDO_TK,
+ CDC_TK,
+ INCLUDES_TK,
+ DASHMATCH_TK,
+ COMMENT_TK,
+ STRING_TK,
+ IDENT_TK,
+ HASH_TK,
+ IMPORT_SYM_TK,
+ PAGE_SYM_TK,
+ MEDIA_SYM_TK,
+ FONT_FACE_SYM_TK,
+ CHARSET_SYM_TK,
+ ATKEYWORD_TK,
+ IMPORTANT_SYM_TK,
+ EMS_TK,
+ EXS_TK,
+ LENGTH_TK,
+ ANGLE_TK,
+ TIME_TK,
+ FREQ_TK,
+ DIMEN_TK,
+ PERCENTAGE_TK,
+ NUMBER_TK,
+ RGB_TK,
+ URI_TK,
+ FUNCTION_TK,
+ UNICODERANGE_TK,
+ SEMICOLON_TK,
+ CBO_TK, /*opening curly bracket*/
+ CBC_TK, /*closing curly bracket*/
+ PO_TK, /*opening parenthesis*/
+ PC_TK, /*closing parenthesis*/
+ BO_TK, /*opening bracket*/
+ BC_TK, /*closing bracket*/
+ DELIM_TK
+} ;
+
+enum CRTokenExtraType
+{
+ NO_ET = 0,
+ LENGTH_PX_ET,
+ LENGTH_CM_ET,
+ LENGTH_MM_ET,
+ LENGTH_IN_ET,
+ LENGTH_PT_ET,
+ LENGTH_PC_ET,
+ ANGLE_DEG_ET,
+ ANGLE_RAD_ET,
+ ANGLE_GRAD_ET,
+ TIME_MS_ET,
+ TIME_S_ET,
+ FREQ_HZ_ET,
+ FREQ_KHZ_ET
+} ;
+
+typedef struct _CRToken CRToken ;
+
+/**
+ *This class abstracts a css2 token.
+ */
+struct _CRToken
+{
+ enum CRTokenType type ;
+ enum CRTokenExtraType extra_type ;
+ CRInputPos pos ;
+
+ union
+ {
+ CRString *str ;
+ CRRgb *rgb ;
+ CRNum *num ;
+ guint32 unichar ;
+ } u ;
+
+ CRString * dimen ;
+ CRParsingLocation location ;
+} ;
+
+CRToken* cr_token_new (void) ;
+
+enum CRStatus cr_token_set_s (CRToken *a_this) ;
+
+enum CRStatus cr_token_set_cdo (CRToken *a_this) ;
+
+enum CRStatus cr_token_set_cdc (CRToken *a_this) ;
+
+enum CRStatus cr_token_set_includes (CRToken *a_this) ;
+
+enum CRStatus cr_token_set_dashmatch (CRToken *a_this) ;
+
+enum CRStatus cr_token_set_comment (CRToken *a_this, CRString *a_str) ;
+
+enum CRStatus cr_token_set_string (CRToken *a_this, CRString *a_str) ;
+
+enum CRStatus cr_token_set_ident (CRToken *a_this, CRString * a_ident) ;
+
+enum CRStatus cr_token_set_hash (CRToken *a_this, CRString *a_hash) ;
+
+enum CRStatus cr_token_set_rgb (CRToken *a_this, CRRgb *a_rgb) ;
+
+enum CRStatus cr_token_set_import_sym (CRToken *a_this) ;
+
+enum CRStatus cr_token_set_page_sym (CRToken *a_this) ;
+
+enum CRStatus cr_token_set_media_sym (CRToken *a_this) ;
+
+enum CRStatus cr_token_set_font_face_sym (CRToken *a_this) ;
+
+enum CRStatus cr_token_set_charset_sym (CRToken *a_this) ;
+
+enum CRStatus cr_token_set_atkeyword (CRToken *a_this, CRString *a_atname) ;
+
+enum CRStatus cr_token_set_important_sym (CRToken *a_this) ;
+
+enum CRStatus cr_token_set_ems (CRToken *a_this, CRNum *a_num) ;
+
+enum CRStatus cr_token_set_exs (CRToken *a_this, CRNum *a_num) ;
+
+enum CRStatus cr_token_set_length (CRToken *a_this, CRNum *a_num,
+ enum CRTokenExtraType a_et) ;
+
+enum CRStatus cr_token_set_angle (CRToken *a_this, CRNum *a_num,
+ enum CRTokenExtraType a_et) ;
+
+enum CRStatus cr_token_set_time (CRToken *a_this, CRNum *a_num,
+ enum CRTokenExtraType a_et) ;
+
+enum CRStatus cr_token_set_freq (CRToken *a_this, CRNum *a_num,
+ enum CRTokenExtraType a_et) ;
+
+enum CRStatus cr_token_set_dimen (CRToken *a_this, CRNum *a_num,
+ CRString *a_dim) ;
+
+enum CRStatus cr_token_set_percentage (CRToken *a_this, CRNum *a_num) ;
+
+enum CRStatus cr_token_set_number (CRToken *a_this, CRNum *a_num) ;
+
+enum CRStatus cr_token_set_uri (CRToken *a_this, CRString *a_uri) ;
+
+enum CRStatus cr_token_set_function (CRToken *a_this,
+ CRString *a_fun_name) ;
+
+enum CRStatus cr_token_set_bc (CRToken *a_this) ;
+
+enum CRStatus cr_token_set_bo (CRToken *a_this) ;
+
+enum CRStatus cr_token_set_po (CRToken *a_this) ;
+
+enum CRStatus cr_token_set_pc (CRToken *a_this) ;
+
+enum CRStatus cr_token_set_cbc (CRToken *a_this) ;
+
+enum CRStatus cr_token_set_cbo (CRToken *a_this) ;
+
+enum CRStatus cr_token_set_semicolon (CRToken *a_this) ;
+
+enum CRStatus cr_token_set_delim (CRToken *a_this, guint32 a_char) ;
+
+
+/*
+ enum CRStatus
+ cr_token_set_unicoderange (CRToken *a_this,
+ CRUnicodeRange *a_range) ;
+*/
+
+void
+cr_token_destroy (CRToken *a_this) ;
+
+
+G_END_DECLS
+
+#endif /*__CR_TOKEN_H__*/
diff --git a/src/st/croco/cr-utils.c b/src/st/croco/cr-utils.c
new file mode 100644
index 0000000..5fafade
--- /dev/null
+++ b/src/st/croco/cr-utils.c
@@ -0,0 +1,1330 @@
+/* -*- Mode: C; indent-tabs-mode: nil; c-basic-offset: 8 -*- */
+
+/*
+ * This file is part of The Croco Library
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2.1 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * 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 Lesser General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ *
+ * Author: Dodji Seketeli
+ * See COPYRIGHTS file for copyright information.
+ */
+
+#include "cr-utils.h"
+#include "cr-string.h"
+
+/**
+ *@file:
+ *Some misc utility functions used
+ *in the libcroco.
+ *Note that troughout this file I will
+ *refer to the CSS SPECIFICATIONS DOCUMENTATION
+ *written by the w3c guys. You can find that document
+ *at http://www.w3.org/TR/REC-CSS2/ .
+ */
+
+/****************************
+ *Encoding transformations and
+ *encoding helpers
+ ****************************/
+
+/*
+ *Here is the correspondence between the ucs-4 charactere codes
+ *and there matching utf-8 encoding pattern as described by RFC 2279:
+ *
+ *UCS-4 range (hex.) UTF-8 octet sequence (binary)
+ *------------------ -----------------------------
+ *0000 0000-0000 007F 0xxxxxxx
+ *0000 0080-0000 07FF 110xxxxx 10xxxxxx
+ *0000 0800-0000 FFFF 1110xxxx 10xxxxxx 10xxxxxx
+ *0001 0000-001F FFFF 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
+ *0020 0000-03FF FFFF 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
+ *0400 0000-7FFF FFFF 1111110x 10xxxxxx ... 10xxxxxx
+ */
+
+/**
+ *Given an utf8 string buffer, calculates
+ *the length of this string if it was encoded
+ *in ucs4.
+ *@param a_in_start a pointer to the beginning of
+ *the input utf8 string.
+ *@param a_in_end a pointre to the end of the input
+ *utf8 string (points to the last byte of the buffer)
+ *@param a_len out parameter the calculated length.
+ *@return CR_OK upon successful completion, an error code
+ *otherwise.
+ */
+enum CRStatus
+cr_utils_utf8_str_len_as_ucs4 (const guchar * a_in_start,
+ const guchar * a_in_end, gulong * a_len)
+{
+ guchar *byte_ptr = NULL;
+ gint len = 0;
+
+ /*
+ *to store the final decoded
+ *unicode char
+ */
+ guint c = 0;
+
+ g_return_val_if_fail (a_in_start && a_in_end && a_len,
+ CR_BAD_PARAM_ERROR);
+ *a_len = 0;
+
+ for (byte_ptr = (guchar *) a_in_start;
+ byte_ptr <= a_in_end; byte_ptr++) {
+ gint nb_bytes_2_decode = 0;
+
+ if (*byte_ptr <= 0x7F) {
+ /*
+ *7 bits long char
+ *encoded over 1 byte:
+ * 0xxx xxxx
+ */
+ c = *byte_ptr;
+ nb_bytes_2_decode = 1;
+
+ } else if ((*byte_ptr & 0xE0) == 0xC0) {
+ /*
+ *up to 11 bits long char.
+ *encoded over 2 bytes:
+ *110x xxxx 10xx xxxx
+ */
+ c = *byte_ptr & 0x1F;
+ nb_bytes_2_decode = 2;
+
+ } else if ((*byte_ptr & 0xF0) == 0xE0) {
+ /*
+ *up to 16 bit long char
+ *encoded over 3 bytes:
+ *1110 xxxx 10xx xxxx 10xx xxxx
+ */
+ c = *byte_ptr & 0x0F;
+ nb_bytes_2_decode = 3;
+
+ } else if ((*byte_ptr & 0xF8) == 0xF0) {
+ /*
+ *up to 21 bits long char
+ *encoded over 4 bytes:
+ *1111 0xxx 10xx xxxx 10xx xxxx 10xx xxxx
+ */
+ c = *byte_ptr & 0x7;
+ nb_bytes_2_decode = 4;
+
+ } else if ((*byte_ptr & 0xFC) == 0xF8) {
+ /*
+ *up to 26 bits long char
+ *encoded over 5 bytes.
+ *1111 10xx 10xx xxxx 10xx xxxx
+ *10xx xxxx 10xx xxxx
+ */
+ c = *byte_ptr & 3;
+ nb_bytes_2_decode = 5;
+
+ } else if ((*byte_ptr & 0xFE) == 0xFC) {
+ /*
+ *up to 31 bits long char
+ *encoded over 6 bytes:
+ *1111 110x 10xx xxxx 10xx xxxx
+ *10xx xxxx 10xx xxxx 10xx xxxx
+ */
+ c = *byte_ptr & 1;
+ nb_bytes_2_decode = 6;
+
+ } else {
+ /*
+ *BAD ENCODING
+ */
+ return CR_ENCODING_ERROR;
+ }
+
+ /*
+ *Go and decode the remaining byte(s)
+ *(if any) to get the current character.
+ */
+ for (; nb_bytes_2_decode > 1; nb_bytes_2_decode--) {
+ /*decode the next byte */
+ byte_ptr++;
+
+ /*byte pattern must be: 10xx xxxx */
+ if ((*byte_ptr & 0xC0) != 0x80) {
+ return CR_ENCODING_ERROR;
+ }
+
+ c = (c << 6) | (*byte_ptr & 0x3F);
+ }
+
+ len++;
+ }
+
+ *a_len = len;
+
+ return CR_OK;
+}
+
+/**
+ *Given an ucs4 string, this function
+ *returns the size (in bytes) this string
+ *would have occupied if it was encoded in utf-8.
+ *@param a_in_start a pointer to the beginning of the input
+ *buffer.
+ *@param a_in_end a pointer to the end of the input buffer.
+ *@param a_len out parameter. The computed length.
+ *@return CR_OK upon successful completion, an error code otherwise.
+ */
+enum CRStatus
+cr_utils_ucs4_str_len_as_utf8 (const guint32 * a_in_start,
+ const guint32 * a_in_end, gulong * a_len)
+{
+ gint len = 0;
+ guint32 *char_ptr = NULL;
+
+ g_return_val_if_fail (a_in_start && a_in_end && a_len,
+ CR_BAD_PARAM_ERROR);
+
+ for (char_ptr = (guint32 *) a_in_start;
+ char_ptr <= a_in_end; char_ptr++) {
+ if (*char_ptr <= 0x7F) {
+ /*the utf-8 char would take 1 byte */
+ len += 1;
+ } else if (*char_ptr <= 0x7FF) {
+ /*the utf-8 char would take 2 bytes */
+ len += 2;
+ } else if (*char_ptr <= 0xFFFF) {
+ len += 3;
+ } else if (*char_ptr <= 0x1FFFFF) {
+ len += 4;
+ } else if (*char_ptr <= 0x3FFFFFF) {
+ len += 5;
+ } else if (*char_ptr <= 0x7FFFFFFF) {
+ len += 6;
+ }
+ }
+
+ *a_len = len;
+ return CR_OK;
+}
+
+/**
+ *Given an ucsA string, this function
+ *returns the size (in bytes) this string
+ *would have occupied if it was encoded in utf-8.
+ *@param a_in_start a pointer to the beginning of the input
+ *buffer.
+ *@param a_in_end a pointer to the end of the input buffer.
+ *@param a_len out parameter. The computed length.
+ *@return CR_OK upon successful completion, an error code otherwise.
+ */
+enum CRStatus
+cr_utils_ucs1_str_len_as_utf8 (const guchar * a_in_start,
+ const guchar * a_in_end, gulong * a_len)
+{
+ gint len = 0;
+ guchar *char_ptr = NULL;
+
+ g_return_val_if_fail (a_in_start && a_in_end && a_len,
+ CR_BAD_PARAM_ERROR);
+
+ for (char_ptr = (guchar *) a_in_start;
+ char_ptr <= a_in_end; char_ptr++) {
+ if (*char_ptr <= 0x7F) {
+ /*the utf-8 char would take 1 byte */
+ len += 1;
+ } else {
+ /*the utf-8 char would take 2 bytes */
+ len += 2;
+ }
+ }
+
+ *a_len = len;
+ return CR_OK;
+}
+
+/**
+ *Converts an utf8 buffer into an ucs4 buffer.
+ *
+ *@param a_in the input utf8 buffer to convert.
+ *@param a_in_len in/out parameter. The size of the
+ *input buffer to convert. After return, this parameter contains
+ *the actual number of bytes consumed.
+ *@param a_out the output converted ucs4 buffer. Must be allocated by
+ *the caller.
+ *@param a_out_len in/out parameter. The size of the output buffer.
+ *If this size is actually smaller than the real needed size, the function
+ *just converts what it can and returns a success status. After return,
+ *this param points to the actual number of characters decoded.
+ *@return CR_OK upon successful completion, an error code otherwise.
+ */
+enum CRStatus
+cr_utils_utf8_to_ucs4 (const guchar * a_in,
+ gulong * a_in_len, guint32 * a_out, gulong * a_out_len)
+{
+ gulong in_len = 0,
+ out_len = 0,
+ in_index = 0,
+ out_index = 0;
+ enum CRStatus status = CR_OK;
+
+ /*
+ *to store the final decoded
+ *unicode char
+ */
+ guint c = 0;
+
+ g_return_val_if_fail (a_in && a_in_len
+ && a_out && a_out_len, CR_BAD_PARAM_ERROR);
+
+ if (*a_in_len < 1) {
+ status = CR_OK;
+ goto end;
+ }
+
+ in_len = *a_in_len;
+ out_len = *a_out_len;
+
+ for (in_index = 0, out_index = 0;
+ (in_index < in_len) && (out_index < out_len);
+ in_index++, out_index++) {
+ gint nb_bytes_2_decode = 0;
+
+ if (a_in[in_index] <= 0x7F) {
+ /*
+ *7 bits long char
+ *encoded over 1 byte:
+ * 0xxx xxxx
+ */
+ c = a_in[in_index];
+ nb_bytes_2_decode = 1;
+
+ } else if ((a_in[in_index] & 0xE0) == 0xC0) {
+ /*
+ *up to 11 bits long char.
+ *encoded over 2 bytes:
+ *110x xxxx 10xx xxxx
+ */
+ c = a_in[in_index] & 0x1F;
+ nb_bytes_2_decode = 2;
+
+ } else if ((a_in[in_index] & 0xF0) == 0xE0) {
+ /*
+ *up to 16 bit long char
+ *encoded over 3 bytes:
+ *1110 xxxx 10xx xxxx 10xx xxxx
+ */
+ c = a_in[in_index] & 0x0F;
+ nb_bytes_2_decode = 3;
+
+ } else if ((a_in[in_index] & 0xF8) == 0xF0) {
+ /*
+ *up to 21 bits long char
+ *encoded over 4 bytes:
+ *1111 0xxx 10xx xxxx 10xx xxxx 10xx xxxx
+ */
+ c = a_in[in_index] & 0x7;
+ nb_bytes_2_decode = 4;
+
+ } else if ((a_in[in_index] & 0xFC) == 0xF8) {
+ /*
+ *up to 26 bits long char
+ *encoded over 5 bytes.
+ *1111 10xx 10xx xxxx 10xx xxxx
+ *10xx xxxx 10xx xxxx
+ */
+ c = a_in[in_index] & 3;
+ nb_bytes_2_decode = 5;
+
+ } else if ((a_in[in_index] & 0xFE) == 0xFC) {
+ /*
+ *up to 31 bits long char
+ *encoded over 6 bytes:
+ *1111 110x 10xx xxxx 10xx xxxx
+ *10xx xxxx 10xx xxxx 10xx xxxx
+ */
+ c = a_in[in_index] & 1;
+ nb_bytes_2_decode = 6;
+
+ } else {
+ /*BAD ENCODING */
+ goto end;
+ }
+
+ /*
+ *Go and decode the remaining byte(s)
+ *(if any) to get the current character.
+ */
+ for (; nb_bytes_2_decode > 1; nb_bytes_2_decode--) {
+ /*decode the next byte */
+ in_index++;
+
+ /*byte pattern must be: 10xx xxxx */
+ if ((a_in[in_index] & 0xC0) != 0x80) {
+ goto end;
+ }
+
+ c = (c << 6) | (a_in[in_index] & 0x3F);
+ }
+
+ /*
+ *The decoded ucs4 char is now
+ *in c.
+ */
+
+ /************************
+ *Some security tests
+ ***********************/
+
+ /*be sure c is a char */
+ if (c == 0xFFFF || c == 0xFFFE)
+ goto end;
+
+ /*be sure c is inferior to the max ucs4 char value */
+ if (c > 0x10FFFF)
+ goto end;
+
+ /*
+ *c must be less than UTF16 "lower surrogate begin"
+ *or higher than UTF16 "High surrogate end"
+ */
+ if (c >= 0xD800 && c <= 0xDFFF)
+ goto end;
+
+ /*Avoid characters that equals zero */
+ if (c == 0)
+ goto end;
+
+ a_out[out_index] = c;
+ }
+
+ end:
+ *a_out_len = out_index + 1;
+ *a_in_len = in_index + 1;
+
+ return status;
+}
+
+/**
+ *Reads a character from an utf8 buffer.
+ *Actually decode the next character code (unicode character code)
+ *and returns it.
+ *@param a_in the starting address of the utf8 buffer.
+ *@param a_in_len the length of the utf8 buffer.
+ *@param a_out output parameter. The resulting read char.
+ *@param a_consumed the number of the bytes consumed to
+ *decode the returned character code.
+ *@return CR_OK upon successful completion, an error code otherwise.
+ */
+enum CRStatus
+cr_utils_read_char_from_utf8_buf (const guchar * a_in,
+ gulong a_in_len,
+ guint32 * a_out, gulong * a_consumed)
+{
+ gulong in_index = 0,
+ nb_bytes_2_decode = 0;
+ enum CRStatus status = CR_OK;
+
+ /*
+ *to store the final decoded
+ *unicode char
+ */
+ guint32 c = 0;
+
+ g_return_val_if_fail (a_in && a_out && a_out
+ && a_consumed, CR_BAD_PARAM_ERROR);
+
+ if (a_in_len < 1) {
+ status = CR_OK;
+ goto end;
+ }
+
+ if (*a_in <= 0x7F) {
+ /*
+ *7 bits long char
+ *encoded over 1 byte:
+ * 0xxx xxxx
+ */
+ c = *a_in;
+ nb_bytes_2_decode = 1;
+
+ } else if ((*a_in & 0xE0) == 0xC0) {
+ /*
+ *up to 11 bits long char.
+ *encoded over 2 bytes:
+ *110x xxxx 10xx xxxx
+ */
+ c = *a_in & 0x1F;
+ nb_bytes_2_decode = 2;
+
+ } else if ((*a_in & 0xF0) == 0xE0) {
+ /*
+ *up to 16 bit long char
+ *encoded over 3 bytes:
+ *1110 xxxx 10xx xxxx 10xx xxxx
+ */
+ c = *a_in & 0x0F;
+ nb_bytes_2_decode = 3;
+
+ } else if ((*a_in & 0xF8) == 0xF0) {
+ /*
+ *up to 21 bits long char
+ *encoded over 4 bytes:
+ *1111 0xxx 10xx xxxx 10xx xxxx 10xx xxxx
+ */
+ c = *a_in & 0x7;
+ nb_bytes_2_decode = 4;
+
+ } else if ((*a_in & 0xFC) == 0xF8) {
+ /*
+ *up to 26 bits long char
+ *encoded over 5 bytes.
+ *1111 10xx 10xx xxxx 10xx xxxx
+ *10xx xxxx 10xx xxxx
+ */
+ c = *a_in & 3;
+ nb_bytes_2_decode = 5;
+
+ } else if ((*a_in & 0xFE) == 0xFC) {
+ /*
+ *up to 31 bits long char
+ *encoded over 6 bytes:
+ *1111 110x 10xx xxxx 10xx xxxx
+ *10xx xxxx 10xx xxxx 10xx xxxx
+ */
+ c = *a_in & 1;
+ nb_bytes_2_decode = 6;
+
+ } else {
+ /*BAD ENCODING */
+ goto end;
+ }
+
+ if (nb_bytes_2_decode > a_in_len) {
+ status = CR_END_OF_INPUT_ERROR;
+ goto end;
+ }
+
+ /*
+ *Go and decode the remaining byte(s)
+ *(if any) to get the current character.
+ */
+ for (in_index = 1; in_index < nb_bytes_2_decode; in_index++) {
+ /*byte pattern must be: 10xx xxxx */
+ if ((a_in[in_index] & 0xC0) != 0x80) {
+ goto end;
+ }
+
+ c = (c << 6) | (a_in[in_index] & 0x3F);
+ }
+
+ /*
+ *The decoded ucs4 char is now
+ *in c.
+ */
+
+ /************************
+ *Some security tests
+ ***********************/
+
+ /*be sure c is a char */
+ if (c == 0xFFFF || c == 0xFFFE)
+ goto end;
+
+ /*be sure c is inferior to the max ucs4 char value */
+ if (c > 0x10FFFF)
+ goto end;
+
+ /*
+ *c must be less than UTF16 "lower surrogate begin"
+ *or higher than UTF16 "High surrogate end"
+ */
+ if (c >= 0xD800 && c <= 0xDFFF)
+ goto end;
+
+ /*Avoid characters that equals zero */
+ if (c == 0)
+ goto end;
+
+ *a_out = c;
+
+ end:
+ *a_consumed = nb_bytes_2_decode;
+
+ return status;
+}
+
+/**
+ *
+ */
+enum CRStatus
+cr_utils_utf8_str_len_as_ucs1 (const guchar * a_in_start,
+ const guchar * a_in_end, gulong * a_len)
+{
+ /*
+ *Note: this function can be made shorter
+ *but it considers all the cases of the utf8 encoding
+ *to ease further extensions ...
+ */
+
+ guchar *byte_ptr = NULL;
+ gint len = 0;
+
+ /*
+ *to store the final decoded
+ *unicode char
+ */
+ guint c = 0;
+
+ g_return_val_if_fail (a_in_start && a_in_end && a_len,
+ CR_BAD_PARAM_ERROR);
+ *a_len = 0;
+
+ for (byte_ptr = (guchar *) a_in_start;
+ byte_ptr <= a_in_end; byte_ptr++) {
+ gint nb_bytes_2_decode = 0;
+
+ if (*byte_ptr <= 0x7F) {
+ /*
+ *7 bits long char
+ *encoded over 1 byte:
+ * 0xxx xxxx
+ */
+ c = *byte_ptr;
+ nb_bytes_2_decode = 1;
+
+ } else if ((*byte_ptr & 0xE0) == 0xC0) {
+ /*
+ *up to 11 bits long char.
+ *encoded over 2 bytes:
+ *110x xxxx 10xx xxxx
+ */
+ c = *byte_ptr & 0x1F;
+ nb_bytes_2_decode = 2;
+
+ } else if ((*byte_ptr & 0xF0) == 0xE0) {
+ /*
+ *up to 16 bit long char
+ *encoded over 3 bytes:
+ *1110 xxxx 10xx xxxx 10xx xxxx
+ */
+ c = *byte_ptr & 0x0F;
+ nb_bytes_2_decode = 3;
+
+ } else if ((*byte_ptr & 0xF8) == 0xF0) {
+ /*
+ *up to 21 bits long char
+ *encoded over 4 bytes:
+ *1111 0xxx 10xx xxxx 10xx xxxx 10xx xxxx
+ */
+ c = *byte_ptr & 0x7;
+ nb_bytes_2_decode = 4;
+
+ } else if ((*byte_ptr & 0xFC) == 0xF8) {
+ /*
+ *up to 26 bits long char
+ *encoded over 5 bytes.
+ *1111 10xx 10xx xxxx 10xx xxxx
+ *10xx xxxx 10xx xxxx
+ */
+ c = *byte_ptr & 3;
+ nb_bytes_2_decode = 5;
+
+ } else if ((*byte_ptr & 0xFE) == 0xFC) {
+ /*
+ *up to 31 bits long char
+ *encoded over 6 bytes:
+ *1111 110x 10xx xxxx 10xx xxxx
+ *10xx xxxx 10xx xxxx 10xx xxxx
+ */
+ c = *byte_ptr & 1;
+ nb_bytes_2_decode = 6;
+
+ } else {
+ /*
+ *BAD ENCODING
+ */
+ return CR_ENCODING_ERROR;
+ }
+
+ /*
+ *Go and decode the remaining byte(s)
+ *(if any) to get the current character.
+ */
+ for (; nb_bytes_2_decode > 1; nb_bytes_2_decode--) {
+ /*decode the next byte */
+ byte_ptr++;
+
+ /*byte pattern must be: 10xx xxxx */
+ if ((*byte_ptr & 0xC0) != 0x80) {
+ return CR_ENCODING_ERROR;
+ }
+
+ c = (c << 6) | (*byte_ptr & 0x3F);
+ }
+
+ /*
+ *The decoded ucs4 char is now
+ *in c.
+ */
+
+ if (c <= 0xFF) { /*Add other conditions to support
+ *other char sets (ucs2, ucs3, ucs4).
+ */
+ len++;
+ } else {
+ /*the char is too long to fit
+ *into the supposed charset len.
+ */
+ return CR_ENCODING_ERROR;
+ }
+ }
+
+ *a_len = len;
+
+ return CR_OK;
+}
+
+/**
+ *Converts an utf8 string into an ucs4 string.
+ *@param a_in the input string to convert.
+ *@param a_in_len in/out parameter. The length of the input
+ *string. After return, points to the actual number of bytes
+ *consumed. This can be useful to debug the input stream in case
+ *of encoding error.
+ *@param a_out out parameter. Points to the output string. It is allocated
+ *by this function and must be freed by the caller.
+ *@param a_out_len out parameter. The length of the output string.
+ *@return CR_OK upon successful completion, an error code otherwise.
+ *
+ */
+enum CRStatus
+cr_utils_utf8_str_to_ucs4 (const guchar * a_in,
+ gulong * a_in_len,
+ guint32 ** a_out, gulong * a_out_len)
+{
+ enum CRStatus status = CR_OK;
+
+ g_return_val_if_fail (a_in && a_in_len
+ && a_out && a_out_len, CR_BAD_PARAM_ERROR);
+
+ status = cr_utils_utf8_str_len_as_ucs4 (a_in,
+ &a_in[*a_in_len - 1],
+ a_out_len);
+
+ g_return_val_if_fail (status == CR_OK, status);
+
+ *a_out = g_malloc0 (*a_out_len * sizeof (guint32));
+
+ status = cr_utils_utf8_to_ucs4 (a_in, a_in_len, *a_out, a_out_len);
+
+ return status;
+}
+
+/**
+ *Converts an ucs4 buffer into an utf8 buffer.
+ *
+ *@param a_in the input ucs4 buffer to convert.
+ *@param a_in_len in/out parameter. The size of the
+ *input buffer to convert. After return, this parameter contains
+ *the actual number of characters consumed.
+ *@param a_out the output converted utf8 buffer. Must be allocated by
+ *the caller.
+ *@param a_out_len in/out parameter. The size of the output buffer.
+ *If this size is actually smaller than the real needed size, the function
+ *just converts what it can and returns a success status. After return,
+ *this param points to the actual number of bytes in the buffer.
+ *@return CR_OK upon successful completion, an error code otherwise.
+ */
+enum CRStatus
+cr_utils_ucs4_to_utf8 (const guint32 * a_in,
+ gulong * a_in_len, guchar * a_out, gulong * a_out_len)
+{
+ gulong in_len = 0,
+ in_index = 0,
+ out_index = 0;
+ enum CRStatus status = CR_OK;
+
+ g_return_val_if_fail (a_in && a_in_len && a_out && a_out_len,
+ CR_BAD_PARAM_ERROR);
+
+ if (*a_in_len < 1) {
+ status = CR_OK;
+ goto end;
+ }
+
+ in_len = *a_in_len;
+
+ for (in_index = 0; in_index < in_len; in_index++) {
+ /*
+ *FIXME: return whenever we encounter forbidden char values.
+ */
+
+ if (a_in[in_index] <= 0x7F) {
+ a_out[out_index] = a_in[in_index];
+ out_index++;
+ } else if (a_in[in_index] <= 0x7FF) {
+ a_out[out_index] = (0xC0 | (a_in[in_index] >> 6));
+ a_out[out_index + 1] =
+ (0x80 | (a_in[in_index] & 0x3F));
+ out_index += 2;
+ } else if (a_in[in_index] <= 0xFFFF) {
+ a_out[out_index] = (0xE0 | (a_in[in_index] >> 12));
+ a_out[out_index + 1] =
+ (0x80 | ((a_in[in_index] >> 6) & 0x3F));
+ a_out[out_index + 2] =
+ (0x80 | (a_in[in_index] & 0x3F));
+ out_index += 3;
+ } else if (a_in[in_index] <= 0x1FFFFF) {
+ a_out[out_index] = (0xF0 | (a_in[in_index] >> 18));
+ a_out[out_index + 1]
+ = (0x80 | ((a_in[in_index] >> 12) & 0x3F));
+ a_out[out_index + 2]
+ = (0x80 | ((a_in[in_index] >> 6) & 0x3F));
+ a_out[out_index + 3]
+ = (0x80 | (a_in[in_index] & 0x3F));
+ out_index += 4;
+ } else if (a_in[in_index] <= 0x3FFFFFF) {
+ a_out[out_index] = (0xF8 | (a_in[in_index] >> 24));
+ a_out[out_index + 1] =
+ (0x80 | (a_in[in_index] >> 18));
+ a_out[out_index + 2]
+ = (0x80 | ((a_in[in_index] >> 12) & 0x3F));
+ a_out[out_index + 3]
+ = (0x80 | ((a_in[in_index] >> 6) & 0x3F));
+ a_out[out_index + 4]
+ = (0x80 | (a_in[in_index] & 0x3F));
+ out_index += 5;
+ } else if (a_in[in_index] <= 0x7FFFFFFF) {
+ a_out[out_index] = (0xFC | (a_in[in_index] >> 30));
+ a_out[out_index + 1] =
+ (0x80 | (a_in[in_index] >> 24));
+ a_out[out_index + 2]
+ = (0x80 | ((a_in[in_index] >> 18) & 0x3F));
+ a_out[out_index + 3]
+ = (0x80 | ((a_in[in_index] >> 12) & 0x3F));
+ a_out[out_index + 4]
+ = (0x80 | ((a_in[in_index] >> 6) & 0x3F));
+ a_out[out_index + 4]
+ = (0x80 | (a_in[in_index] & 0x3F));
+ out_index += 6;
+ } else {
+ status = CR_ENCODING_ERROR;
+ goto end;
+ }
+ } /*end for */
+
+ end:
+ *a_in_len = in_index + 1;
+ *a_out_len = out_index + 1;
+
+ return status;
+}
+
+/**
+ *Converts an ucs4 string into an utf8 string.
+ *@param a_in the input string to convert.
+ *@param a_in_len in/out parameter. The length of the input
+ *string. After return, points to the actual number of characters
+ *consumed. This can be useful to debug the input string in case
+ *of encoding error.
+ *@param a_out out parameter. Points to the output string. It is allocated
+ *by this function and must be freed by the caller.
+ *@param a_out_len out parameter. The length (in bytes) of the output string.
+ *@return CR_OK upon successful completion, an error code otherwise.
+ */
+enum CRStatus
+cr_utils_ucs4_str_to_utf8 (const guint32 * a_in,
+ gulong * a_in_len,
+ guchar ** a_out, gulong * a_out_len)
+{
+ enum CRStatus status = CR_OK;
+
+ g_return_val_if_fail (a_in && a_in_len && a_out
+ && a_out_len, CR_BAD_PARAM_ERROR);
+
+ status = cr_utils_ucs4_str_len_as_utf8 (a_in,
+ &a_in[*a_out_len - 1],
+ a_out_len);
+
+ g_return_val_if_fail (status == CR_OK, status);
+
+ status = cr_utils_ucs4_to_utf8 (a_in, a_in_len, *a_out, a_out_len);
+
+ return status;
+}
+
+/**
+ *Converts an ucs1 buffer into an utf8 buffer.
+ *The caller must know the size of the resulting buffer and
+ *allocate it prior to calling this function.
+ *
+ *@param a_in the input ucs1 buffer.
+ *
+ *@param a_in_len in/out parameter. The length of the input buffer.
+ *After return, points to the number of bytes actually consumed even
+ *in case of encoding error.
+ *
+ *@param a_out out parameter. The output utf8 converted buffer.
+ *
+ *@param a_out_len in/out parameter. The size of the output buffer.
+ *If the output buffer size is shorter than the actual needed size,
+ *this function just convert what it can.
+ *
+ *@return CR_OK upon successful completion, an error code otherwise.
+ *
+ */
+enum CRStatus
+cr_utils_ucs1_to_utf8 (const guchar * a_in,
+ gulong * a_in_len, guchar * a_out, gulong * a_out_len)
+{
+ gulong out_index = 0,
+ in_index = 0,
+ in_len = 0,
+ out_len = 0;
+ enum CRStatus status = CR_OK;
+
+ g_return_val_if_fail (a_in && a_in_len
+ && a_out_len,
+ CR_BAD_PARAM_ERROR);
+
+ if (*a_in_len == 0) {
+ *a_out_len = 0 ;
+ return status;
+ }
+ g_return_val_if_fail (a_out, CR_BAD_PARAM_ERROR) ;
+
+ in_len = *a_in_len;
+ out_len = *a_out_len;
+
+ for (in_index = 0, out_index = 0;
+ (in_index < in_len) && (out_index < out_len); in_index++) {
+ /*
+ *FIXME: return whenever we encounter forbidden char values.
+ */
+
+ if (a_in[in_index] <= 0x7F) {
+ a_out[out_index] = a_in[in_index];
+ out_index++;
+ } else {
+ a_out[out_index] = (0xC0 | (a_in[in_index] >> 6));
+ a_out[out_index + 1] =
+ (0x80 | (a_in[in_index] & 0x3F));
+ out_index += 2;
+ }
+ } /*end for */
+
+ *a_in_len = in_index;
+ *a_out_len = out_index;
+
+ return status;
+}
+
+/**
+ *Converts an ucs1 string into an utf8 string.
+ *@param a_in_start the beginning of the input string to convert.
+ *@param a_in_end the end of the input string to convert.
+ *@param a_out out parameter. The converted string.
+ *@param a_out out parameter. The length of the converted string.
+ *@return CR_OK upon successful completion, an error code otherwise.
+ *
+ */
+enum CRStatus
+cr_utils_ucs1_str_to_utf8 (const guchar * a_in,
+ gulong * a_in_len,
+ guchar ** a_out, gulong * a_out_len)
+{
+ gulong out_len = 0;
+ enum CRStatus status = CR_OK;
+
+ g_return_val_if_fail (a_in && a_in_len && a_out
+ && a_out_len, CR_BAD_PARAM_ERROR);
+
+ if (*a_in_len < 1) {
+ *a_out_len = 0;
+ *a_out = NULL;
+ return CR_OK;
+ }
+
+ status = cr_utils_ucs1_str_len_as_utf8 (a_in, &a_in[*a_in_len - 1],
+ &out_len);
+
+ g_return_val_if_fail (status == CR_OK, status);
+
+ *a_out = g_malloc0 (out_len);
+
+ status = cr_utils_ucs1_to_utf8 (a_in, a_in_len, *a_out, &out_len);
+
+ *a_out_len = out_len;
+
+ return status;
+}
+
+/**
+ *Converts an utf8 buffer into an ucs1 buffer.
+ *The caller must know the size of the resulting
+ *converted buffer, and allocated it prior to calling this
+ *function.
+ *
+ *@param a_in the input utf8 buffer to convert.
+ *
+ *@param a_in_len in/out parameter. The size of the input utf8 buffer.
+ *After return, points to the number of bytes consumed
+ *by the function even in case of encoding error.
+ *
+ *@param a_out out parameter. Points to the resulting buffer.
+ *Must be allocated by the caller. If the size of a_out is shorter
+ *than its required size, this function converts what it can and return
+ *a successful status.
+ *
+ *@param a_out_len in/out parameter. The size of the output buffer.
+ *After return, points to the number of bytes consumed even in case of
+ *encoding error.
+ *
+ *@return CR_OK upon successful completion, an error code otherwise.
+ */
+enum CRStatus
+cr_utils_utf8_to_ucs1 (const guchar * a_in,
+ gulong * a_in_len, guchar * a_out, gulong * a_out_len)
+{
+ gulong in_index = 0,
+ out_index = 0,
+ in_len = 0,
+ out_len = 0;
+ enum CRStatus status = CR_OK;
+
+ /*
+ *to store the final decoded
+ *unicode char
+ */
+ guint32 c = 0;
+
+ g_return_val_if_fail (a_in && a_in_len
+ && a_out && a_out_len, CR_BAD_PARAM_ERROR);
+
+ if (*a_in_len < 1) {
+ goto end;
+ }
+
+ in_len = *a_in_len;
+ out_len = *a_out_len;
+
+ for (in_index = 0, out_index = 0;
+ (in_index < in_len) && (out_index < out_len);
+ in_index++, out_index++) {
+ gint nb_bytes_2_decode = 0;
+
+ if (a_in[in_index] <= 0x7F) {
+ /*
+ *7 bits long char
+ *encoded over 1 byte:
+ * 0xxx xxxx
+ */
+ c = a_in[in_index];
+ nb_bytes_2_decode = 1;
+
+ } else if ((a_in[in_index] & 0xE0) == 0xC0) {
+ /*
+ *up to 11 bits long char.
+ *encoded over 2 bytes:
+ *110x xxxx 10xx xxxx
+ */
+ c = a_in[in_index] & 0x1F;
+ nb_bytes_2_decode = 2;
+
+ } else if ((a_in[in_index] & 0xF0) == 0xE0) {
+ /*
+ *up to 16 bit long char
+ *encoded over 3 bytes:
+ *1110 xxxx 10xx xxxx 10xx xxxx
+ */
+ c = a_in[in_index] & 0x0F;
+ nb_bytes_2_decode = 3;
+
+ } else if ((a_in[in_index] & 0xF8) == 0xF0) {
+ /*
+ *up to 21 bits long char
+ *encoded over 4 bytes:
+ *1111 0xxx 10xx xxxx 10xx xxxx 10xx xxxx
+ */
+ c = a_in[in_index] & 0x7;
+ nb_bytes_2_decode = 4;
+
+ } else if ((a_in[in_index] & 0xFC) == 0xF8) {
+ /*
+ *up to 26 bits long char
+ *encoded over 5 bytes.
+ *1111 10xx 10xx xxxx 10xx xxxx
+ *10xx xxxx 10xx xxxx
+ */
+ c = a_in[in_index] & 3;
+ nb_bytes_2_decode = 5;
+
+ } else if ((a_in[in_index] & 0xFE) == 0xFC) {
+ /*
+ *up to 31 bits long char
+ *encoded over 6 bytes:
+ *1111 110x 10xx xxxx 10xx xxxx
+ *10xx xxxx 10xx xxxx 10xx xxxx
+ */
+ c = a_in[in_index] & 1;
+ nb_bytes_2_decode = 6;
+
+ } else {
+ /*BAD ENCODING */
+ status = CR_ENCODING_ERROR;
+ goto end;
+ }
+
+ /*
+ *Go and decode the remaining byte(s)
+ *(if any) to get the current character.
+ */
+ if (in_index + nb_bytes_2_decode - 1 >= in_len) {
+ goto end;
+ }
+
+ for (; nb_bytes_2_decode > 1; nb_bytes_2_decode--) {
+ /*decode the next byte */
+ in_index++;
+
+ /*byte pattern must be: 10xx xxxx */
+ if ((a_in[in_index] & 0xC0) != 0x80) {
+ status = CR_ENCODING_ERROR;
+ goto end;
+ }
+
+ c = (c << 6) | (a_in[in_index] & 0x3F);
+ }
+
+ /*
+ *The decoded ucs4 char is now
+ *in c.
+ */
+
+ if (c > 0xFF) {
+ status = CR_ENCODING_ERROR;
+ goto end;
+ }
+
+ a_out[out_index] = c;
+ }
+
+ end:
+ *a_out_len = out_index;
+ *a_in_len = in_index;
+
+ return status;
+}
+
+/**
+ *Converts an utf8 buffer into an
+ *ucs1 buffer.
+ *@param a_in_start the start of the input buffer.
+ *@param a_in_end the end of the input buffer.
+ *@param a_out out parameter. The resulting converted ucs4 buffer.
+ *Must be freed by the caller.
+ *@param a_out_len out parameter. The length of the converted buffer.
+ *@return CR_OK upon successful completion, an error code otherwise.
+ *Note that out parameters are valid if and only if this function
+ *returns CR_OK.
+ */
+enum CRStatus
+cr_utils_utf8_str_to_ucs1 (const guchar * a_in,
+ gulong * a_in_len,
+ guchar ** a_out, gulong * a_out_len)
+{
+ enum CRStatus status = CR_OK;
+
+ g_return_val_if_fail (a_in && a_in_len
+ && a_out && a_out_len, CR_BAD_PARAM_ERROR);
+
+ if (*a_in_len < 1) {
+ *a_out_len = 0;
+ *a_out = NULL;
+ return CR_OK;
+ }
+
+ status = cr_utils_utf8_str_len_as_ucs4 (a_in, &a_in[*a_in_len - 1],
+ a_out_len);
+
+ g_return_val_if_fail (status == CR_OK, status);
+
+ *a_out = g_malloc0 (*a_out_len * sizeof (guint32));
+
+ status = cr_utils_utf8_to_ucs1 (a_in, a_in_len, *a_out, a_out_len);
+ return status;
+}
+
+/*****************************************
+ *CSS basic types identification utilities
+ *****************************************/
+
+/**
+ *Returns TRUE if a_char is a white space as
+ *defined in the css spec in chap 4.1.1.
+ *
+ *white-space ::= ' '| \t|\r|\n|\f
+ *
+ *@param a_char the character to test.
+ *return TRUE if is a white space, false otherwise.
+ */
+gboolean
+cr_utils_is_white_space (guint32 a_char)
+{
+ switch (a_char) {
+ case ' ':
+ case '\t':
+ case '\r':
+ case '\n':
+ case '\f':
+ return TRUE;
+ break;
+ default:
+ return FALSE;
+ }
+}
+
+/**
+ *Returns true if the character is a newline
+ *as defined in the css spec in the chap 4.1.1.
+ *
+ *nl ::= \n|\r\n|\r|\f
+ *
+ *@param a_char the character to test.
+ *@return TRUE if the character is a newline, FALSE otherwise.
+ */
+gboolean
+cr_utils_is_newline (guint32 a_char)
+{
+ switch (a_char) {
+ case '\n':
+ case '\r':
+ case '\f':
+ return TRUE;
+ break;
+ default:
+ return FALSE;
+ }
+}
+
+/**
+ *returns TRUE if the char is part of an hexa num char:
+ *i.e hexa_char ::= [0-9A-F]
+ */
+gboolean
+cr_utils_is_hexa_char (guint32 a_char)
+{
+ if ((a_char >= '0' && a_char <= '9')
+ || (a_char >= 'A' && a_char <= 'F')) {
+ return TRUE;
+ }
+ return FALSE;
+}
+
+/**
+ *Returns true if the character is a nonascii
+ *character (as defined in the css spec chap 4.1.1):
+ *
+ *nonascii ::= [^\0-\177]
+ *
+ *@param a_char the character to test.
+ *@return TRUE if the character is a nonascii char,
+ *FALSE otherwise.
+ */
+gboolean
+cr_utils_is_nonascii (guint32 a_char)
+{
+ if (a_char <= 177) {
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+/**
+ *Dumps a character a_nb times on a file.
+ *@param a_char the char to dump
+ *@param a_fp the destination file pointer
+ *@param a_nb the number of times a_char is to be dumped.
+ */
+void
+cr_utils_dump_n_chars (guchar a_char, FILE * a_fp, glong a_nb)
+{
+ glong i = 0;
+
+ for (i = 0; i < a_nb; i++) {
+ fprintf (a_fp, "%c", a_char);
+ }
+}
+
+void
+cr_utils_dump_n_chars2 (guchar a_char, GString * a_string, glong a_nb)
+{
+ glong i = 0;
+
+ g_return_if_fail (a_string);
+
+ for (i = 0; i < a_nb; i++) {
+ g_string_append_printf (a_string, "%c", a_char);
+ }
+}
+
+/**
+ *Duplicates a list of GString instances.
+ *@return the duplicated list of GString instances or NULL if
+ *something bad happened.
+ *@param a_list_of_strings the list of strings to be duplicated.
+ */
+GList *
+cr_utils_dup_glist_of_string (GList const * a_list_of_strings)
+{
+ GList const *cur = NULL;
+ GList *result = NULL;
+
+ g_return_val_if_fail (a_list_of_strings, NULL);
+
+ for (cur = a_list_of_strings; cur; cur = cur->next) {
+ GString *str = NULL;
+
+ str = g_string_new_len (((GString *) cur->data)->str,
+ ((GString *) cur->data)->len);
+ if (str)
+ result = g_list_append (result, str);
+ }
+
+ return result;
+}
+
+/**
+ *Duplicate a GList where the GList::data is a CRString.
+ *@param a_list_of_strings the list to duplicate
+ *@return the duplicated list, or NULL if something bad
+ *happened.
+ */
+GList *
+cr_utils_dup_glist_of_cr_string (GList const * a_list_of_strings)
+{
+ GList const *cur = NULL;
+ GList *result = NULL;
+
+ g_return_val_if_fail (a_list_of_strings, NULL);
+
+ for (cur = a_list_of_strings; cur; cur = cur->next) {
+ CRString *str = NULL;
+
+ str = cr_string_dup ((CRString const *) cur->data) ;
+ if (str)
+ result = g_list_append (result, str);
+ }
+
+ return result;
+}
diff --git a/src/st/croco/cr-utils.h b/src/st/croco/cr-utils.h
new file mode 100644
index 0000000..54aa249
--- /dev/null
+++ b/src/st/croco/cr-utils.h
@@ -0,0 +1,246 @@
+/* -*- Mode: C; indent-tabs-mode: nil; c-basic-offset: 8 -*- */
+
+/*
+ * This file is part of The Croco Library
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2.1 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * 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 Lesser General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ *
+ * Author: Dodji Seketeli
+ * Look at file COPYRIGHTS for copyright information
+ */
+
+#ifndef __CR_DEFS_H__
+#define __CR_DEFS_H__
+
+#include <stdio.h>
+#include <glib.h>
+#include "libcroco-config.h"
+
+G_BEGIN_DECLS
+
+/**
+ *@file
+ *The Croco library basic types definitions
+ *And global definitions.
+ */
+
+/**
+ *The status type returned
+ *by the methods of the croco library.
+ */
+enum CRStatus {
+ CR_OK,
+ CR_BAD_PARAM_ERROR,
+ CR_INSTANCIATION_FAILED_ERROR,
+ CR_UNKNOWN_TYPE_ERROR,
+ CR_UNKNOWN_PROP_ERROR,
+ CR_UNKNOWN_PROP_VAL_ERROR,
+ CR_UNEXPECTED_POSITION_SCHEME,
+ CR_START_OF_INPUT_ERROR,
+ CR_END_OF_INPUT_ERROR,
+ CR_OUTPUT_TOO_SHORT_ERROR,
+ CR_INPUT_TOO_SHORT_ERROR,
+ CR_OUT_OF_BOUNDS_ERROR,
+ CR_EMPTY_PARSER_INPUT_ERROR,
+ CR_ENCODING_ERROR,
+ CR_ENCODING_NOT_FOUND_ERROR,
+ CR_PARSING_ERROR,
+ CR_SYNTAX_ERROR,
+ CR_NO_ROOT_NODE_ERROR,
+ CR_NO_TOKEN,
+ CR_OUT_OF_MEMORY_ERROR,
+ CR_PSEUDO_CLASS_SEL_HANDLER_NOT_FOUND_ERROR,
+ CR_BAD_PSEUDO_CLASS_SEL_HANDLER_ERROR,
+ CR_ERROR,
+ CR_FILE_NOT_FOUND_ERROR,
+ CR_VALUE_NOT_FOUND_ERROR
+} ;
+
+/**
+ *Values used by
+ *cr_input_seek_position() ;
+ */
+enum CRSeekPos {
+ CR_SEEK_CUR,
+ CR_SEEK_BEGIN,
+ CR_SEEK_END
+} ;
+
+/**
+ *Encoding values.
+ */
+enum CREncoding
+{
+ CR_UCS_4 = 1/*Must be not NULL*/,
+ CR_UCS_1,
+ CR_ISO_8859_1,
+ CR_ASCII,
+ CR_UTF_8,
+ CR_UTF_16,
+ CR_AUTO/*should be the last one*/
+} ;
+
+
+
+
+#define CROCO_LOG_DOMAIN "LIBCROCO"
+
+#ifdef __GNUC__
+#define cr_utils_trace(a_log_level, a_msg) \
+g_log (CROCO_LOG_DOMAIN, \
+ G_LOG_LEVEL_CRITICAL, \
+ "file %s: line %d (%s): %s\n", \
+ __FILE__, \
+ __LINE__, \
+ __PRETTY_FUNCTION__, \
+ a_msg)
+#else /*__GNUC__*/
+
+#define cr_utils_trace(a_log_level, a_msg) \
+g_log (CROCO_LOG_DOMAIN, \
+ G_LOG_LEVEL_CRITICAL, \
+ "file %s: line %d: %s\n", \
+ __FILE__, \
+ __LINE__, \
+ a_msg)
+#endif
+
+/**
+ *Traces an info message.
+ *The file, line and enclosing function
+ *of the message will be automatically
+ *added to the message.
+ *@param a_msg the msg to trace.
+ */
+#define cr_utils_trace_info(a_msg) \
+cr_utils_trace (G_LOG_LEVEL_INFO, a_msg)
+
+/**
+ *Trace a debug message.
+ *The file, line and enclosing function
+ *of the message will be automatically
+ *added to the message.
+ *@param a_msg the msg to trace.
+ */
+#define cr_utils_trace_debug(a_msg) \
+cr_utils_trace (G_LOG_LEVEL_DEBUG, a_msg) ;
+
+
+/****************************
+ *Encoding transformations and
+ *encoding helpers
+ ****************************/
+
+enum CRStatus
+cr_utils_read_char_from_utf8_buf (const guchar * a_in, gulong a_in_len,
+ guint32 *a_out, gulong *a_consumed) ;
+
+enum CRStatus
+cr_utils_ucs1_to_utf8 (const guchar *a_in, gulong *a_in_len,
+ guchar *a_out, gulong *a_out_len) ;
+
+enum CRStatus
+cr_utils_utf8_to_ucs1 (const guchar * a_in, gulong * a_in_len,
+ guchar *a_out, gulong *a_out_len) ;
+
+enum CRStatus
+cr_utils_ucs4_to_utf8 (const guint32 *a_in, gulong *a_in_len,
+ guchar *a_out, gulong *a_out_len) ;
+
+enum CRStatus
+cr_utils_utf8_str_len_as_ucs4 (const guchar *a_in_start,
+ const guchar *a_in_end,
+ gulong *a_len) ;
+enum CRStatus
+cr_utils_ucs1_str_len_as_utf8 (const guchar *a_in_start,
+ const guchar *a_in_end,
+ gulong *a_len) ;
+enum CRStatus
+cr_utils_utf8_str_len_as_ucs1 (const guchar *a_in_start,
+ const guchar *a_in_end,
+ gulong *a_len) ;
+enum CRStatus
+cr_utils_ucs4_str_len_as_utf8 (const guint32 *a_in_start,
+ const guint32 *a_in_end,
+ gulong *a_len) ;
+
+enum CRStatus
+cr_utils_ucs1_str_to_utf8 (const guchar *a_in_start,
+ gulong *a_in_len,
+ guchar **a_out,
+ gulong *a_len) ;
+
+enum CRStatus
+cr_utils_utf8_str_to_ucs1 (const guchar * a_in_start,
+ gulong * a_in_len,
+ guchar **a_out,
+ gulong *a_out_len) ;
+
+enum CRStatus
+cr_utils_utf8_to_ucs4 (const guchar * a_in,
+ gulong * a_in_len,
+ guint32 *a_out, gulong *a_out_len) ;
+
+enum CRStatus
+cr_utils_ucs4_str_to_utf8 (const guint32 *a_in,
+ gulong *a_in_len,
+ guchar **a_out, gulong *a_out_len) ;
+
+enum CRStatus
+cr_utils_utf8_str_to_ucs4 (const guchar * a_in,
+ gulong *a_in_len,
+ guint32 **a_out,
+ gulong *a_out_len) ;
+
+
+/*****************************************
+ *CSS basic types identification utilities
+ *****************************************/
+
+gboolean
+cr_utils_is_newline (guint32 a_char) ;
+
+gboolean
+cr_utils_is_white_space (guint32 a_char) ;
+
+gboolean
+cr_utils_is_nonascii (guint32 a_char) ;
+
+gboolean
+cr_utils_is_hexa_char (guint32 a_char) ;
+
+
+/**********************************
+ *Miscellaneous utility functions
+ ***********************************/
+
+void
+cr_utils_dump_n_chars (guchar a_char,
+ FILE *a_fp,
+ glong a_nb) ;
+
+void
+cr_utils_dump_n_chars2 (guchar a_char,
+ GString *a_string,
+ glong a_nb) ;
+GList *
+cr_utils_dup_glist_of_string (GList const *a_list) ;
+
+GList *
+cr_utils_dup_glist_of_cr_string (GList const * a_list_of_strings) ;
+
+G_END_DECLS
+
+#endif /*__CR_DEFS_H__*/
diff --git a/src/st/croco/libcroco-config.h b/src/st/croco/libcroco-config.h
new file mode 100644
index 0000000..1ffb758
--- /dev/null
+++ b/src/st/croco/libcroco-config.h
@@ -0,0 +1,13 @@
+#ifndef LIBCROCO_VERSION_NUMBER
+#define LIBCROCO_VERSION_NUMBER 612
+#endif
+
+#ifndef LIBCROCO_VERSION
+#define LIBCROCO_VERSION "0.6.12"
+#endif
+
+#ifndef G_DISABLE_CHECKS
+#if 0
+#define G_DISABLE_CHECKS 0
+#endif
+#endif
diff --git a/src/st/croco/libcroco.h b/src/st/croco/libcroco.h
new file mode 100644
index 0000000..6187a7c
--- /dev/null
+++ b/src/st/croco/libcroco.h
@@ -0,0 +1,42 @@
+/*
+ * This file is part of The Croco Library
+ *
+ * Copyright (C) 2002-2003 Dodji Seketeli <dodji@seketeli.org>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2.1 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * 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 Lesser General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ */
+
+#ifndef __LIBCROCO_H__
+#define __LIBCROCO_H__
+
+#include "libcroco-config.h"
+
+#include "cr-utils.h"
+#include "cr-pseudo.h"
+#include "cr-term.h"
+#include "cr-attr-sel.h"
+#include "cr-simple-sel.h"
+#include "cr-selector.h"
+#include "cr-enc-handler.h"
+#include "cr-doc-handler.h"
+#include "cr-input.h"
+#include "cr-parser.h"
+#include "cr-statement.h"
+#include "cr-stylesheet.h"
+#include "cr-om-parser.h"
+#include "cr-prop-list.h"
+#include "cr-string.h"
+
+#endif /*__LIBCROCO_H__*/
diff --git a/src/st/meson.build b/src/st/meson.build
new file mode 100644
index 0000000..717aa05
--- /dev/null
+++ b/src/st/meson.build
@@ -0,0 +1,220 @@
+# please, keep this sorted alphabetically
+st_headers = [
+ 'st-adjustment.h',
+ 'st-bin.h',
+ 'st-border-image.h',
+ 'st-box-layout.h',
+ 'st-button.h',
+ 'st-clipboard.h',
+ 'st-drawing-area.h',
+ 'st-entry.h',
+ 'st-focus-manager.h',
+ 'st-generic-accessible.h',
+ 'st-icon.h',
+ 'st-icon-colors.h',
+ 'st-image-content.h',
+ 'st-label.h',
+ 'st-password-entry.h',
+ 'st-scrollable.h',
+ 'st-scroll-bar.h',
+ 'st-scroll-view.h',
+ 'st-scroll-view-fade.h',
+ 'st-settings.h',
+ 'st-shadow.h',
+ 'st-texture-cache.h',
+ 'st-theme.h',
+ 'st-theme-context.h',
+ 'st-theme-node.h',
+ 'st-types.h',
+ 'st-viewport.h',
+ 'st-widget.h',
+ 'st-widget-accessible.h'
+]
+
+st_includes = []
+foreach include : st_headers
+ st_includes += '#include <@0@>'.format(include)
+endforeach
+
+st_h_data = configuration_data()
+st_h_data.set('includes', '\n'.join(st_includes))
+
+st_h = configure_file(
+ input: 'st.h.in',
+ output: 'st.h',
+ configuration: st_h_data
+)
+
+st_inc = include_directories('.', '..')
+
+# please, keep this sorted alphabetically
+st_private_headers = [
+ 'croco/cr-additional-sel.h',
+ 'croco/cr-attr-sel.h',
+ 'croco/cr-cascade.h',
+ 'croco/cr-declaration.h',
+ 'croco/cr-doc-handler.h',
+ 'croco/cr-enc-handler.h',
+ 'croco/cr-fonts.h',
+ 'croco/cr-input.h',
+ 'croco/cr-num.h',
+ 'croco/cr-om-parser.h',
+ 'croco/cr-parser.h',
+ 'croco/cr-parsing-location.h',
+ 'croco/cr-prop-list.h',
+ 'croco/cr-pseudo.h',
+ 'croco/cr-rgb.h',
+ 'croco/cr-selector.h',
+ 'croco/cr-simple-sel.h',
+ 'croco/cr-statement.h',
+ 'croco/cr-string.h',
+ 'croco/cr-stylesheet.h',
+ 'croco/cr-term.h',
+ 'croco/cr-tknzr.h',
+ 'croco/cr-token.h',
+ 'croco/cr-utils.h',
+ 'croco/libcroco-config.h',
+ 'croco/libcroco.h',
+ 'st-private.h',
+ 'st-theme-private.h',
+ 'st-theme-node-private.h',
+ 'st-theme-node-transition.h'
+]
+
+# please, keep this sorted alphabetically
+croco_sources = [
+ 'croco/cr-additional-sel.c',
+ 'croco/cr-attr-sel.c',
+ 'croco/cr-cascade.c',
+ 'croco/cr-declaration.c',
+ 'croco/cr-doc-handler.c',
+ 'croco/cr-enc-handler.c',
+ 'croco/cr-fonts.c',
+ 'croco/cr-input.c',
+ 'croco/cr-num.c',
+ 'croco/cr-om-parser.c',
+ 'croco/cr-parser.c',
+ 'croco/cr-parsing-location.c',
+ 'croco/cr-prop-list.c',
+ 'croco/cr-pseudo.c',
+ 'croco/cr-rgb.c',
+ 'croco/cr-selector.c',
+ 'croco/cr-simple-sel.c',
+ 'croco/cr-statement.c',
+ 'croco/cr-string.c',
+ 'croco/cr-stylesheet.c',
+ 'croco/cr-term.c',
+ 'croco/cr-tknzr.c',
+ 'croco/cr-token.c',
+ 'croco/cr-utils.c',
+]
+
+# please, keep this sorted alphabetically
+st_sources = [
+ 'st-adjustment.c',
+ 'st-bin.c',
+ 'st-border-image.c',
+ 'st-box-layout.c',
+ 'st-button.c',
+ 'st-clipboard.c',
+ 'st-drawing-area.c',
+ 'st-entry.c',
+ 'st-focus-manager.c',
+ 'st-generic-accessible.c',
+ 'st-icon.c',
+ 'st-icon-colors.c',
+ 'st-image-content.c',
+ 'st-label.c',
+ 'st-password-entry.c',
+ 'st-private.c',
+ 'st-scrollable.c',
+ 'st-scroll-bar.c',
+ 'st-scroll-view.c',
+ 'st-scroll-view-fade.c',
+ 'st-settings.c',
+ 'st-shadow.c',
+ 'st-texture-cache.c',
+ 'st-theme.c',
+ 'st-theme-context.c',
+ 'st-theme-node.c',
+ 'st-theme-node-drawing.c',
+ 'st-theme-node-transition.c',
+ 'st-viewport.c',
+ 'st-widget.c'
+]
+
+st_enums = gnome.mkenums_simple('st-enum-types',
+ sources: st_headers,
+ header_prefix: '''
+#if !defined(ST_H_INSIDE) && !defined(ST_COMPILATION)
+#error "Only <st/st.h> can be included directly.h"
+#endif'''
+)
+
+st_gir_sources = st_sources + st_headers + st_enums
+
+data_to_c = find_program(meson.project_source_root() + '/src/data-to-c.pl')
+
+glsl_sources = custom_target('scroll-view-fade-glsl',
+ input: ['st-scroll-view-fade.glsl'],
+ output: ['st-scroll-view-fade-generated.h'],
+ capture: true,
+ command: [data_to_c, '@INPUT@', 'st_scroll_view_fade_glsl']
+)
+
+st_nogir_sources = [glsl_sources]
+
+st_cflags = [
+ '-I@0@/src'.format(meson.project_source_root()),
+ '-I@0@'.format(meson.project_build_root()),
+ '-DPREFIX="@0@"'.format(prefix),
+ '-DLIBDIR="@0@"'.format(libdir),
+ '-DG_LOG_DOMAIN="St"',
+ '-DST_COMPILATION',
+ '-DCLUTTER_ENABLE_EXPERIMENTAL_API',
+ '-DCOGL_ENABLE_EXPERIMENTAL_API',
+ '-DPACKAGE_DATA_DIR="@0@"'.format(pkgdatadir)
+]
+
+# Currently meson requires a shared library for building girs
+libst = shared_library('st-1.0',
+ sources: st_gir_sources + st_nogir_sources + croco_sources,
+ c_args: st_cflags,
+ dependencies: [clutter_dep, gtk_dep, mutter_dep, libxml_dep, m_dep],
+ build_rpath: mutter_typelibdir,
+ install_rpath: mutter_typelibdir,
+ install_dir: pkglibdir,
+ install: true
+)
+
+libst_dep = declare_dependency(link_with: libst,
+ sources: st_enums[1]
+)
+
+if get_option('tests')
+ mutter_test_dep = dependency(libmutter_test_pc, version: mutter_req)
+ test_theme = executable('test-theme',
+ sources: 'test-theme.c',
+ c_args: st_cflags,
+ dependencies: [mutter_test_dep, gtk_dep, libxml_dep],
+ build_rpath: mutter_typelibdir,
+ link_with: libst
+ )
+
+ test('CSS styling support', test_theme,
+ workdir: meson.current_source_dir()
+ )
+endif
+
+libst_gir = gnome.generate_gir(libst,
+ sources: st_gir_sources,
+ nsversion: '1.0',
+ namespace: 'St',
+ includes: ['Clutter-' + mutter_api_version, 'Cally-' + mutter_api_version, 'Meta-' + mutter_api_version, 'Gtk-3.0'],
+ dependencies: [mutter_dep],
+ include_directories: include_directories('..'),
+ extra_args: ['-DST_COMPILATION', '--quiet'],
+ install_dir_gir: pkgdatadir,
+ install_dir_typelib: pkglibdir,
+ install: true
+)
diff --git a/src/st/st-adjustment.c b/src/st/st-adjustment.c
new file mode 100644
index 0000000..d2baa66
--- /dev/null
+++ b/src/st/st-adjustment.c
@@ -0,0 +1,1013 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * st-adjustment.c: Adjustment object
+ *
+ * Copyright 2008 OpenedHand
+ * Copyright 2009 Intel Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU Lesser General Public License,
+ * version 2.1, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * SECTION:st-adjustment
+ * @short_description: A GObject representing an adjustable bounded value
+ *
+ * The #StAdjustment object represents a range of values bounded between a
+ * minimum and maximum, together with step and page increments and a page size.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <glib-object.h>
+#include <clutter/clutter.h>
+#include <math.h>
+
+#include "st-adjustment.h"
+#include "st-private.h"
+
+typedef struct _StAdjustmentPrivate StAdjustmentPrivate;
+
+struct _StAdjustmentPrivate
+{
+ ClutterActor *actor;
+
+ /* Do not sanity-check values while constructing,
+ * not all properties may be set yet. */
+ guint is_constructing : 1;
+
+ GHashTable *transitions;
+
+ gdouble lower;
+ gdouble upper;
+ gdouble value;
+ gdouble step_increment;
+ gdouble page_increment;
+ gdouble page_size;
+};
+
+static void animatable_iface_init (ClutterAnimatableInterface *iface);
+
+G_DEFINE_TYPE_WITH_CODE (StAdjustment, st_adjustment, G_TYPE_OBJECT,
+ G_ADD_PRIVATE (StAdjustment)
+ G_IMPLEMENT_INTERFACE (CLUTTER_TYPE_ANIMATABLE,
+ animatable_iface_init));
+
+enum
+{
+ PROP_0,
+
+ PROP_ACTOR,
+ PROP_LOWER,
+ PROP_UPPER,
+ PROP_VALUE,
+ PROP_STEP_INC,
+ PROP_PAGE_INC,
+ PROP_PAGE_SIZE,
+
+ N_PROPS
+};
+
+static GParamSpec *props[N_PROPS] = { NULL, };
+
+enum
+{
+ CHANGED,
+
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = { 0, };
+
+typedef struct _TransitionClosure
+{
+ StAdjustment *adjustment;
+ ClutterTransition *transition;
+ char *name;
+ gulong completed_id;
+} TransitionClosure;
+
+static gboolean st_adjustment_set_lower (StAdjustment *adjustment,
+ gdouble lower);
+static gboolean st_adjustment_set_upper (StAdjustment *adjustment,
+ gdouble upper);
+static gboolean st_adjustment_set_step_increment (StAdjustment *adjustment,
+ gdouble step);
+static gboolean st_adjustment_set_page_increment (StAdjustment *adjustment,
+ gdouble page);
+static gboolean st_adjustment_set_page_size (StAdjustment *adjustment,
+ gdouble size);
+
+static ClutterActor *
+st_adjustment_get_actor (ClutterAnimatable *animatable)
+{
+ StAdjustment *adjustment = ST_ADJUSTMENT (animatable);
+ StAdjustmentPrivate *priv = st_adjustment_get_instance_private (adjustment);
+
+ g_warn_if_fail (priv->actor);
+
+ return priv->actor;
+}
+
+static void
+animatable_iface_init (ClutterAnimatableInterface *iface)
+{
+ iface->get_actor = st_adjustment_get_actor;
+}
+
+static void
+st_adjustment_constructed (GObject *object)
+{
+ GObjectClass *g_class;
+ StAdjustment *self = ST_ADJUSTMENT (object);
+ StAdjustmentPrivate *priv = st_adjustment_get_instance_private (self);
+
+ g_class = G_OBJECT_CLASS (st_adjustment_parent_class);
+ /* The docs say we're suppose to chain up, but would crash without
+ * some extra care. */
+ if (g_class && g_class->constructed &&
+ g_class->constructed != st_adjustment_constructed)
+ {
+ g_class->constructed (object);
+ }
+
+ priv->is_constructing = FALSE;
+ st_adjustment_clamp_page (self, priv->lower, priv->upper);
+}
+
+static void
+st_adjustment_get_property (GObject *gobject,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ StAdjustmentPrivate *priv = st_adjustment_get_instance_private (ST_ADJUSTMENT (gobject));
+
+ switch (prop_id)
+ {
+ case PROP_ACTOR:
+ g_value_set_object (value, priv->actor);
+ break;
+
+ case PROP_LOWER:
+ g_value_set_double (value, priv->lower);
+ break;
+
+ case PROP_UPPER:
+ g_value_set_double (value, priv->upper);
+ break;
+
+ case PROP_VALUE:
+ g_value_set_double (value, priv->value);
+ break;
+
+ case PROP_STEP_INC:
+ g_value_set_double (value, priv->step_increment);
+ break;
+
+ case PROP_PAGE_INC:
+ g_value_set_double (value, priv->page_increment);
+ break;
+
+ case PROP_PAGE_SIZE:
+ g_value_set_double (value, priv->page_size);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+actor_destroyed (gpointer user_data,
+ GObject *where_the_object_was)
+{
+ StAdjustment *adj = ST_ADJUSTMENT (user_data);
+ StAdjustmentPrivate *priv = st_adjustment_get_instance_private (adj);
+
+ priv->actor = NULL;
+
+ g_object_notify_by_pspec (G_OBJECT (adj), props[PROP_ACTOR]);
+}
+
+static void
+st_adjustment_set_actor (StAdjustment *adj,
+ ClutterActor *actor)
+{
+ StAdjustmentPrivate *priv;
+
+ priv = st_adjustment_get_instance_private (adj);
+
+ if (priv->actor == actor)
+ return;
+
+ if (priv->actor)
+ g_object_weak_unref (G_OBJECT (priv->actor), actor_destroyed, adj);
+ priv->actor = actor;
+ if (priv->actor)
+ g_object_weak_ref (G_OBJECT (priv->actor), actor_destroyed, adj);
+
+ g_object_notify_by_pspec (G_OBJECT (adj), props[PROP_ACTOR]);
+}
+
+static void
+st_adjustment_set_property (GObject *gobject,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ StAdjustment *adj = ST_ADJUSTMENT (gobject);
+
+ switch (prop_id)
+ {
+ case PROP_ACTOR:
+ st_adjustment_set_actor (adj, g_value_get_object (value));
+ break;
+
+ case PROP_LOWER:
+ st_adjustment_set_lower (adj, g_value_get_double (value));
+ break;
+
+ case PROP_UPPER:
+ st_adjustment_set_upper (adj, g_value_get_double (value));
+ break;
+
+ case PROP_VALUE:
+ st_adjustment_set_value (adj, g_value_get_double (value));
+ break;
+
+ case PROP_STEP_INC:
+ st_adjustment_set_step_increment (adj, g_value_get_double (value));
+ break;
+
+ case PROP_PAGE_INC:
+ st_adjustment_set_page_increment (adj, g_value_get_double (value));
+ break;
+
+ case PROP_PAGE_SIZE:
+ st_adjustment_set_page_size (adj, g_value_get_double (value));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+st_adjustment_dispose (GObject *object)
+{
+ StAdjustmentPrivate *priv;
+
+ priv = st_adjustment_get_instance_private (ST_ADJUSTMENT (object));
+ if (priv->actor)
+ {
+ g_object_weak_unref (G_OBJECT (priv->actor), actor_destroyed, object);
+ priv->actor = NULL;
+ }
+ g_clear_pointer (&priv->transitions, g_hash_table_unref);
+
+ G_OBJECT_CLASS (st_adjustment_parent_class)->dispose (object);
+}
+
+static void
+st_adjustment_class_init (StAdjustmentClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->constructed = st_adjustment_constructed;
+ object_class->get_property = st_adjustment_get_property;
+ object_class->set_property = st_adjustment_set_property;
+ object_class->dispose = st_adjustment_dispose;
+
+ /**
+ * StAdjustment:actor:
+ *
+ * If the adjustment is used as #ClutterAnimatable for a
+ * #ClutterPropertyTransition, this property is used to determine which
+ * monitor should drive the animation.
+ */
+ props[PROP_ACTOR] =
+ g_param_spec_object ("actor", "Actor", "Actor",
+ CLUTTER_TYPE_ACTOR,
+ ST_PARAM_READWRITE |
+ G_PARAM_EXPLICIT_NOTIFY);
+
+ /**
+ * StAdjustment:lower:
+ *
+ * The minimum value of the adjustment.
+ */
+ props[PROP_LOWER] =
+ g_param_spec_double ("lower", "Lower", "Lower bound",
+ -G_MAXDOUBLE, G_MAXDOUBLE, 0.0,
+ ST_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT |
+ G_PARAM_EXPLICIT_NOTIFY);
+
+ /**
+ * StAdjustment:upper:
+ *
+ * The maximum value of the adjustment.
+ *
+ * Note that values will be restricted by `upper - page-size` if
+ * #StAdjustment:page-size is non-zero.
+ */
+ props[PROP_UPPER] =
+ g_param_spec_double ("upper", "Upper", "Upper bound",
+ -G_MAXDOUBLE, G_MAXDOUBLE, 0.0,
+ ST_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT |
+ G_PARAM_EXPLICIT_NOTIFY);
+
+ /**
+ * StAdjustment:value:
+ *
+ * The value of the adjustment.
+ */
+ props[PROP_VALUE] =
+ g_param_spec_double ("value", "Value", "Current value",
+ -G_MAXDOUBLE, G_MAXDOUBLE, 0.0,
+ ST_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT |
+ G_PARAM_EXPLICIT_NOTIFY);
+
+ /**
+ * StAdjustment:step-increment:
+ *
+ * The step increment of the adjustment.
+ */
+ props[PROP_STEP_INC] =
+ g_param_spec_double ("step-increment", "Step Increment", "Step increment",
+ 0.0, G_MAXDOUBLE, 0.0,
+ ST_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT |
+ G_PARAM_EXPLICIT_NOTIFY);
+
+ /**
+ * StAdjustment:page-increment:
+ *
+ * The page increment of the adjustment.
+ */
+ props[PROP_PAGE_INC] =
+ g_param_spec_double ("page-increment", "Page Increment", "Page increment",
+ 0.0, G_MAXDOUBLE, 0.0,
+ ST_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT |
+ G_PARAM_EXPLICIT_NOTIFY);
+
+ /**
+ * StAdjustment:page-size:
+ *
+ * The page size of the adjustment.
+ *
+ * Note that the page-size is irrelevant and should be set to zero if the
+ * adjustment is used for a simple scalar value.
+ */
+ props[PROP_PAGE_SIZE] =
+ g_param_spec_double ("page-size", "Page Size", "Page size",
+ 0.0, G_MAXDOUBLE, 0.0,
+ ST_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT |
+ G_PARAM_EXPLICIT_NOTIFY);
+
+ g_object_class_install_properties (object_class, N_PROPS, props);
+
+ /**
+ * StAdjustment::changed:
+ * @self: the #StAdjustment
+ *
+ * Emitted when any of the adjustment properties have changed, except for
+ * #StAdjustment:value.
+ */
+ signals[CHANGED] =
+ g_signal_new ("changed",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (StAdjustmentClass, changed),
+ NULL, NULL, NULL,
+ G_TYPE_NONE, 0);
+}
+
+static void
+st_adjustment_init (StAdjustment *self)
+{
+ StAdjustmentPrivate *priv = st_adjustment_get_instance_private (self);
+ priv->is_constructing = TRUE;
+}
+
+/**
+ * st_adjustment_new:
+ * @actor: (nullable): a #ClutterActor
+ * @value: the initial value
+ * @lower: the minimum value
+ * @upper: the maximum value
+ * @step_increment: the step increment
+ * @page_increment: the page increment
+ * @page_size: the page size
+ *
+ * Creates a new #StAdjustment
+ *
+ * Returns: a new #StAdjustment
+ */
+StAdjustment *
+st_adjustment_new (ClutterActor *actor,
+ gdouble value,
+ gdouble lower,
+ gdouble upper,
+ gdouble step_increment,
+ gdouble page_increment,
+ gdouble page_size)
+{
+ return g_object_new (ST_TYPE_ADJUSTMENT,
+ "actor", actor,
+ "value", value,
+ "lower", lower,
+ "upper", upper,
+ "step-increment", step_increment,
+ "page-increment", page_increment,
+ "page-size", page_size,
+ NULL);
+}
+
+/**
+ * st_adjustment_get_value:
+ * @adjustment: a #StAdjustment
+ *
+ * Gets the current value of the adjustment. See st_adjustment_set_value().
+ *
+ * Returns: The current value of the adjustment
+ */
+gdouble
+st_adjustment_get_value (StAdjustment *adjustment)
+{
+ g_return_val_if_fail (ST_IS_ADJUSTMENT (adjustment), 0);
+
+ return ((StAdjustmentPrivate *)st_adjustment_get_instance_private (adjustment))->value;
+}
+
+/**
+ * st_adjustment_set_value:
+ * @adjustment: a #StAdjustment
+ * @value: the new value
+ *
+ * Sets the #StAdjustment value. The value is clamped to lie between
+ * #StAdjustment:lower and #StAdjustment:upper - #StAdjustment:page-size.
+ */
+void
+st_adjustment_set_value (StAdjustment *adjustment,
+ gdouble value)
+{
+ StAdjustmentPrivate *priv;
+
+ g_return_if_fail (ST_IS_ADJUSTMENT (adjustment));
+
+ priv = st_adjustment_get_instance_private (adjustment);
+
+ /* Defer clamp until after construction. */
+ if (!priv->is_constructing)
+ {
+ value = CLAMP (value,
+ priv->lower,
+ MAX (priv->lower, priv->upper - priv->page_size));
+ }
+
+ if (priv->value != value)
+ {
+ priv->value = value;
+
+ g_object_notify_by_pspec (G_OBJECT (adjustment), props[PROP_VALUE]);
+ }
+}
+
+/**
+ * st_adjustment_clamp_page:
+ * @adjustment: a #StAdjustment
+ * @lower: the lower value
+ * @upper: the upper value
+ *
+ * Set #StAdjustment:value to a value clamped between @lower and @upper. The
+ * clamping described by st_adjustment_set_value() still applies.
+ */
+void
+st_adjustment_clamp_page (StAdjustment *adjustment,
+ gdouble lower,
+ gdouble upper)
+{
+ StAdjustmentPrivate *priv;
+ gboolean changed;
+
+ g_return_if_fail (ST_IS_ADJUSTMENT (adjustment));
+
+ priv = st_adjustment_get_instance_private (adjustment);
+
+ lower = CLAMP (lower, priv->lower, priv->upper - priv->page_size);
+ upper = CLAMP (upper, priv->lower + priv->page_size, priv->upper);
+
+ changed = FALSE;
+
+ if (priv->value + priv->page_size > upper)
+ {
+ priv->value = upper - priv->page_size;
+ changed = TRUE;
+ }
+
+ if (priv->value < lower)
+ {
+ priv->value = lower;
+ changed = TRUE;
+ }
+
+ if (changed)
+ g_object_notify_by_pspec (G_OBJECT (adjustment), props[PROP_VALUE]);
+}
+
+/**
+ * st_adjustment_set_lower:
+ * @adjustment: a #StAdjustment
+ * @lower: the new minimum value
+ *
+ * Sets the minimum value of the adjustment.
+ *
+ * When setting multiple adjustment properties via their individual
+ * setters, multiple #GObject::notify and #StAdjustment::changed
+ * signals will be emitted. However, it’s possible to compress the
+ * #GObject::notify signals into one by calling
+ * g_object_freeze_notify() and g_object_thaw_notify() around the
+ * calls to the individual setters.
+ *
+ * Alternatively, using st_adjustment_set_values() will compress both
+ * #GObject::notify and #StAdjustment::changed emissions.
+ */
+static gboolean
+st_adjustment_set_lower (StAdjustment *adjustment,
+ gdouble lower)
+{
+ StAdjustmentPrivate *priv = st_adjustment_get_instance_private (adjustment);
+
+ if (priv->lower != lower)
+ {
+ priv->lower = lower;
+
+ g_signal_emit (adjustment, signals[CHANGED], 0);
+
+ g_object_notify_by_pspec (G_OBJECT (adjustment), props[PROP_LOWER]);
+
+ /* Defer clamp until after construction. */
+ if (!priv->is_constructing)
+ st_adjustment_clamp_page (adjustment, priv->lower, priv->upper);
+
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+/**
+ * st_adjustment_set_upper:
+ * @adjustment: a #StAdjustment
+ * @upper: the new maximum value
+ *
+ * Sets the maximum value of the adjustment.
+ *
+ * Note that values will be restricted by `upper - page-size`
+ * if the page-size property is nonzero.
+ *
+ * See st_adjustment_set_lower() about how to compress multiple
+ * signal emissions when setting multiple adjustment properties.
+ *
+ * Returns: %TRUE if the value was changed
+ */
+static gboolean
+st_adjustment_set_upper (StAdjustment *adjustment,
+ gdouble upper)
+{
+ StAdjustmentPrivate *priv = st_adjustment_get_instance_private (adjustment);
+
+ if (priv->upper != upper)
+ {
+ priv->upper = upper;
+
+ g_signal_emit (adjustment, signals[CHANGED], 0);
+
+ g_object_notify_by_pspec (G_OBJECT (adjustment), props[PROP_UPPER]);
+
+ /* Defer clamp until after construction. */
+ if (!priv->is_constructing)
+ st_adjustment_clamp_page (adjustment, priv->lower, priv->upper);
+
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+/**
+ * st_adjustment_set_step_increment:
+ * @adjustment: a #StAdjustment
+ * @step: the new step increment
+ *
+ * Sets the step increment of the adjustment.
+ *
+ * See st_adjustment_set_lower() about how to compress multiple
+ * signal emissions when setting multiple adjustment properties.
+ *
+ * Returns: %TRUE if the value was changed
+ */
+static gboolean
+st_adjustment_set_step_increment (StAdjustment *adjustment,
+ gdouble step)
+{
+ StAdjustmentPrivate *priv = st_adjustment_get_instance_private (adjustment);
+
+ if (priv->step_increment != step)
+ {
+ priv->step_increment = step;
+
+ g_signal_emit (adjustment, signals[CHANGED], 0);
+
+ g_object_notify_by_pspec (G_OBJECT (adjustment), props[PROP_STEP_INC]);
+
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+/**
+ * st_adjustment_set_page_increment:
+ * @adjustment: a #StAdjustment
+ * @page: the new page increment
+ *
+ * Sets the page increment of the adjustment.
+ *
+ * See st_adjustment_set_lower() about how to compress multiple
+ * signal emissions when setting multiple adjustment properties.
+ *
+ * Returns: %TRUE if the value was changed
+ */
+static gboolean
+st_adjustment_set_page_increment (StAdjustment *adjustment,
+ gdouble page)
+{
+ StAdjustmentPrivate *priv = st_adjustment_get_instance_private (adjustment);
+
+ if (priv->page_increment != page)
+ {
+ priv->page_increment = page;
+
+ g_signal_emit (adjustment, signals[CHANGED], 0);
+
+ g_object_notify_by_pspec (G_OBJECT (adjustment), props[PROP_PAGE_INC]);
+
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+/**
+ * st_adjustment_set_page_size:
+ * @adjustment: a #StAdjustment
+ * @size: the new page size
+ *
+ * Sets the page size of the adjustment.
+ *
+ * See st_adjustment_set_lower() about how to compress multiple
+ * signal emissions when setting multiple adjustment properties.
+ *
+ * Returns: %TRUE if the value was changed
+ */
+static gboolean
+st_adjustment_set_page_size (StAdjustment *adjustment,
+ gdouble size)
+{
+ StAdjustmentPrivate *priv = st_adjustment_get_instance_private (adjustment);
+
+ if (priv->page_size != size)
+ {
+ priv->page_size = size;
+
+ g_signal_emit (adjustment, signals[CHANGED], 0);
+
+ g_object_notify_by_pspec (G_OBJECT (adjustment), props[PROP_PAGE_SIZE]);
+
+ /* We'll explicitly clamp after construction. */
+ if (!priv->is_constructing)
+ st_adjustment_clamp_page (adjustment, priv->lower, priv->upper);
+
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+/**
+ * st_adjustment_set_values:
+ * @adjustment: a #StAdjustment
+ * @value: the new value
+ * @lower: the new minimum value
+ * @upper: the new maximum value
+ * @step_increment: the new step increment
+ * @page_increment: the new page increment
+ * @page_size: the new page size
+ *
+ * Sets all properties of the adjustment at once.
+ *
+ * Use this function to avoid multiple emissions of the #GObject::notify and
+ * #StAdjustment::changed signals. See st_adjustment_set_lower() for an
+ * alternative way of compressing multiple emissions of #GObject::notify into
+ * one.
+ */
+void
+st_adjustment_set_values (StAdjustment *adjustment,
+ gdouble value,
+ gdouble lower,
+ gdouble upper,
+ gdouble step_increment,
+ gdouble page_increment,
+ gdouble page_size)
+{
+ StAdjustmentPrivate *priv;
+ gboolean emit_changed = FALSE;
+
+ g_return_if_fail (ST_IS_ADJUSTMENT (adjustment));
+ g_return_if_fail (page_size >= 0 && page_size <= G_MAXDOUBLE);
+ g_return_if_fail (step_increment >= 0 && step_increment <= G_MAXDOUBLE);
+ g_return_if_fail (page_increment >= 0 && page_increment <= G_MAXDOUBLE);
+
+ priv = st_adjustment_get_instance_private (adjustment);
+
+ emit_changed = FALSE;
+
+ g_object_freeze_notify (G_OBJECT (adjustment));
+
+ emit_changed |= st_adjustment_set_lower (adjustment, lower);
+ emit_changed |= st_adjustment_set_upper (adjustment, upper);
+ emit_changed |= st_adjustment_set_step_increment (adjustment, step_increment);
+ emit_changed |= st_adjustment_set_page_increment (adjustment, page_increment);
+ emit_changed |= st_adjustment_set_page_size (adjustment, page_size);
+
+ if (value != priv->value)
+ {
+ st_adjustment_set_value (adjustment, value);
+ emit_changed = TRUE;
+ }
+
+ if (emit_changed)
+ g_signal_emit (G_OBJECT (adjustment), signals[CHANGED], 0);
+
+ g_object_thaw_notify (G_OBJECT (adjustment));
+}
+
+/**
+ * st_adjustment_get_values:
+ * @adjustment: an #StAdjustment
+ * @value: (out) (optional): the current value
+ * @lower: (out) (optional): the lower bound
+ * @upper: (out) (optional): the upper bound
+ * @step_increment: (out) (optional): the step increment
+ * @page_increment: (out) (optional): the page increment
+ * @page_size: (out) (optional): the page size
+ *
+ * Gets all of @adjustment's values at once.
+ */
+void
+st_adjustment_get_values (StAdjustment *adjustment,
+ gdouble *value,
+ gdouble *lower,
+ gdouble *upper,
+ gdouble *step_increment,
+ gdouble *page_increment,
+ gdouble *page_size)
+{
+ StAdjustmentPrivate *priv;
+
+ g_return_if_fail (ST_IS_ADJUSTMENT (adjustment));
+
+ priv = st_adjustment_get_instance_private (adjustment);
+
+ if (lower)
+ *lower = priv->lower;
+
+ if (upper)
+ *upper = priv->upper;
+
+ if (value)
+ *value = st_adjustment_get_value (adjustment);
+
+ if (step_increment)
+ *step_increment = priv->step_increment;
+
+ if (page_increment)
+ *page_increment = priv->page_increment;
+
+ if (page_size)
+ *page_size = priv->page_size;
+}
+
+/**
+ * st_adjustment_adjust_for_scroll_event:
+ * @adjustment: An #StAdjustment
+ * @delta: A delta, retrieved directly from clutter_event_get_scroll_delta()
+ * or similar.
+ *
+ * Adjusts the adjustment using delta values from a scroll event.
+ * You should use this instead of using st_adjustment_set_value()
+ * as this method will tweak the values directly using the same
+ * math as GTK+, to ensure that scrolling is consistent across
+ * the environment.
+ */
+void
+st_adjustment_adjust_for_scroll_event (StAdjustment *adjustment,
+ gdouble delta)
+{
+ StAdjustmentPrivate *priv;
+ gdouble new_value, scroll_unit;
+
+ g_return_if_fail (ST_IS_ADJUSTMENT (adjustment));
+
+ priv = st_adjustment_get_instance_private (adjustment);
+
+ scroll_unit = pow (priv->page_size, 2.0 / 3.0);
+
+ new_value = priv->value + delta * scroll_unit;
+ st_adjustment_set_value (adjustment, new_value);
+}
+
+static void
+transition_closure_free (gpointer data)
+{
+ TransitionClosure *clos;
+ ClutterTimeline *timeline;
+
+ if (G_UNLIKELY (data == NULL))
+ return;
+
+ clos = data;
+ timeline = CLUTTER_TIMELINE (clos->transition);
+
+ g_clear_signal_handler (&clos->completed_id, clos->transition);
+
+ if (clutter_timeline_is_playing (timeline))
+ clutter_timeline_stop (timeline);
+
+ g_object_unref (clos->transition);
+ g_free (clos->name);
+ g_free (clos);
+}
+
+static void
+remove_transition (StAdjustment *adjustment,
+ const char *name)
+{
+ StAdjustmentPrivate *priv = st_adjustment_get_instance_private (adjustment);
+
+ g_hash_table_remove (priv->transitions, name);
+
+ if (g_hash_table_size (priv->transitions) == 0)
+ g_clear_pointer (&priv->transitions, g_hash_table_unref);
+}
+
+static void
+on_transition_stopped (ClutterTransition *transition,
+ gboolean is_finished,
+ TransitionClosure *clos)
+{
+ StAdjustment *adjustment = clos->adjustment;
+
+ if (!clutter_transition_get_remove_on_complete (transition))
+ return;
+
+ /* Take a reference, because removing the closure will
+ * release the reference on the transition, and we want
+ * it to survive the signal emission; ClutterTransition's
+ * own ::stopped signal closure will release it after all
+ * other handlers have run.
+ */
+ g_object_ref (transition);
+
+ remove_transition (adjustment, clos->name);
+}
+
+/**
+ * st_adjustment_get_transition:
+ * @adjustment: a #StAdjustment
+ * @name: a transition name
+ *
+ * Get the #ClutterTransition for @name previously added with
+ * st_adjustment_add_transition() or %NULL if not found.
+ *
+ * Returns: (transfer none) (nullable): a #ClutterTransition
+ */
+ClutterTransition *
+st_adjustment_get_transition (StAdjustment *adjustment,
+ const char *name)
+{
+ StAdjustmentPrivate *priv;
+ TransitionClosure *clos;
+
+ g_return_val_if_fail (ST_IS_ADJUSTMENT (adjustment), NULL);
+
+ priv = st_adjustment_get_instance_private (adjustment);
+
+ if (priv->transitions == NULL)
+ return NULL;
+
+ clos = g_hash_table_lookup (priv->transitions, name);
+ if (clos == NULL)
+ return NULL;
+
+ return clos->transition;
+}
+
+/**
+ * st_adjustment_add_transition:
+ * @adjustment: a #StAdjustment
+ * @name: a unique name for the transition
+ * @transition: a #ClutterTransition
+ *
+ * Add a #ClutterTransition for the adjustment. If the transition stops, it will
+ * be automatically removed if #ClutterTransition:remove-on-complete is %TRUE.
+ */
+void
+st_adjustment_add_transition (StAdjustment *adjustment,
+ const char *name,
+ ClutterTransition *transition)
+{
+ StAdjustmentPrivate *priv;
+ TransitionClosure *clos;
+
+ g_return_if_fail (ST_IS_ADJUSTMENT (adjustment));
+ g_return_if_fail (name != NULL);
+ g_return_if_fail (CLUTTER_IS_TRANSITION (transition));
+
+ priv = st_adjustment_get_instance_private (adjustment);
+
+ if (priv->transitions == NULL)
+ priv->transitions = g_hash_table_new_full (g_str_hash, g_str_equal,
+ NULL,
+ transition_closure_free);
+
+ if (g_hash_table_lookup (priv->transitions, name) != NULL)
+ {
+ g_warning ("A transition with name '%s' already exists for "
+ "adjustment '%p'", name, adjustment);
+ return;
+ }
+
+ clutter_transition_set_animatable (transition, CLUTTER_ANIMATABLE (adjustment));
+
+ clos = g_new (TransitionClosure, 1);
+ clos->adjustment = adjustment;
+ clos->transition = g_object_ref (transition);
+ clos->name = g_strdup (name);
+ clos->completed_id = g_signal_connect (transition, "stopped",
+ G_CALLBACK (on_transition_stopped),
+ clos);
+
+ g_hash_table_insert (priv->transitions, clos->name, clos);
+ clutter_timeline_start (CLUTTER_TIMELINE (transition));
+}
+
+/**
+ * st_adjusmtent_remove_transition:
+ * @adjustment: a #StAdjustment
+ * @name: the name of the transition to remove
+ *
+ * Remove a #ClutterTransition previously added by st_adjustment_add_transtion()
+ * with @name.
+ */
+void
+st_adjustment_remove_transition (StAdjustment *adjustment,
+ const char *name)
+{
+ StAdjustmentPrivate *priv;
+ TransitionClosure *clos;
+
+ g_return_if_fail (ST_IS_ADJUSTMENT (adjustment));
+ g_return_if_fail (name != NULL);
+
+ priv = st_adjustment_get_instance_private (adjustment);
+
+ if (priv->transitions == NULL)
+ return;
+
+ clos = g_hash_table_lookup (priv->transitions, name);
+ if (clos == NULL)
+ return;
+
+ remove_transition (adjustment, name);
+}
diff --git a/src/st/st-adjustment.h b/src/st/st-adjustment.h
new file mode 100644
index 0000000..08a0fc3
--- /dev/null
+++ b/src/st/st-adjustment.h
@@ -0,0 +1,92 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * st-adjustment.h: Adjustment object
+ *
+ * Copyright 2008 OpenedHand
+ * Copyright 2009 Intel Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU Lesser General Public License,
+ * version 2.1, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#if !defined(ST_H_INSIDE) && !defined(ST_COMPILATION)
+#error "Only <st/st.h> can be included directly.h"
+#endif
+
+#ifndef __ST_ADJUSTMENT_H__
+#define __ST_ADJUSTMENT_H__
+
+#include <glib-object.h>
+#include <clutter/clutter.h>
+
+G_BEGIN_DECLS
+
+#define ST_TYPE_ADJUSTMENT (st_adjustment_get_type())
+G_DECLARE_DERIVABLE_TYPE (StAdjustment, st_adjustment, ST, ADJUSTMENT, GObject)
+
+/**
+ * StAdjustmentClass:
+ * @changed: Class handler for the ::changed signal.
+ *
+ * Base class for #StAdjustment.
+ */
+struct _StAdjustmentClass
+{
+ /*< private >*/
+ GObjectClass parent_class;
+
+ /*< public >*/
+ void (* changed) (StAdjustment *adjustment);
+};
+
+StAdjustment *st_adjustment_new (ClutterActor *actor,
+ gdouble value,
+ gdouble lower,
+ gdouble upper,
+ gdouble step_increment,
+ gdouble page_increment,
+ gdouble page_size);
+gdouble st_adjustment_get_value (StAdjustment *adjustment);
+void st_adjustment_set_value (StAdjustment *adjustment,
+ gdouble value);
+void st_adjustment_clamp_page (StAdjustment *adjustment,
+ gdouble lower,
+ gdouble upper);
+void st_adjustment_set_values (StAdjustment *adjustment,
+ gdouble value,
+ gdouble lower,
+ gdouble upper,
+ gdouble step_increment,
+ gdouble page_increment,
+ gdouble page_size);
+void st_adjustment_get_values (StAdjustment *adjustment,
+ gdouble *value,
+ gdouble *lower,
+ gdouble *upper,
+ gdouble *step_increment,
+ gdouble *page_increment,
+ gdouble *page_size);
+
+void st_adjustment_adjust_for_scroll_event (StAdjustment *adjustment,
+ gdouble delta);
+
+ClutterTransition * st_adjustment_get_transition (StAdjustment *adjustment,
+ const char *name);
+void st_adjustment_add_transition (StAdjustment *adjustment,
+ const char *name,
+ ClutterTransition *transition);
+void st_adjustment_remove_transition (StAdjustment *adjustment,
+ const char *name);
+
+G_END_DECLS
+
+#endif /* __ST_ADJUSTMENT_H__ */
diff --git a/src/st/st-bin.c b/src/st/st-bin.c
new file mode 100644
index 0000000..9d86ea2
--- /dev/null
+++ b/src/st/st-bin.c
@@ -0,0 +1,404 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * st-bin.c: Basic container actor
+ *
+ * Copyright 2009 Intel Corporation.
+ * Copyright 2009, 2010 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU Lesser General Public License,
+ * version 2.1, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * SECTION:st-bin
+ * @short_description: a simple container with one actor
+ *
+ * #StBin is a simple container capable of having only one
+ * #ClutterActor as a child.
+ *
+ * #StBin inherits from #StWidget, so it is fully themable.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <clutter/clutter.h>
+
+#include "st-bin.h"
+#include "st-enum-types.h"
+#include "st-private.h"
+
+typedef struct _StBinPrivate StBinPrivate;
+struct _StBinPrivate
+{
+ ClutterActor *child;
+};
+
+enum
+{
+ PROP_0,
+
+ PROP_CHILD,
+
+ N_PROPS
+};
+
+static GParamSpec *props[N_PROPS] = { NULL, };
+
+static void clutter_container_iface_init (ClutterContainerIface *iface);
+
+G_DEFINE_TYPE_WITH_CODE (StBin, st_bin, ST_TYPE_WIDGET,
+ G_ADD_PRIVATE (StBin)
+ G_IMPLEMENT_INTERFACE (CLUTTER_TYPE_CONTAINER,
+ clutter_container_iface_init));
+
+static void
+st_bin_add (ClutterContainer *container,
+ ClutterActor *actor)
+{
+ st_bin_set_child (ST_BIN (container), actor);
+}
+
+static void
+st_bin_remove (ClutterContainer *container,
+ ClutterActor *actor)
+{
+ StBin *bin = ST_BIN (container);
+ StBinPrivate *priv = st_bin_get_instance_private (bin);
+
+ if (priv->child == actor)
+ st_bin_set_child (bin, NULL);
+}
+
+static void
+clutter_container_iface_init (ClutterContainerIface *iface)
+{
+ iface->add = st_bin_add;
+ iface->remove = st_bin_remove;
+}
+
+static double
+get_align_factor (ClutterActorAlign align)
+{
+ switch (align)
+ {
+ case CLUTTER_ACTOR_ALIGN_CENTER:
+ return 0.5;
+
+ case CLUTTER_ACTOR_ALIGN_START:
+ return 0.0;
+
+ case CLUTTER_ACTOR_ALIGN_END:
+ return 1.0;
+
+ case CLUTTER_ACTOR_ALIGN_FILL:
+ break;
+ }
+
+ return 0.0;
+}
+
+static void
+st_bin_allocate (ClutterActor *self,
+ const ClutterActorBox *box)
+{
+ StBinPrivate *priv = st_bin_get_instance_private (ST_BIN (self));
+
+ clutter_actor_set_allocation (self, box);
+
+ if (priv->child && clutter_actor_is_visible (priv->child))
+ {
+ StThemeNode *theme_node = st_widget_get_theme_node (ST_WIDGET (self));
+ ClutterActorAlign x_align = clutter_actor_get_x_align (priv->child);
+ ClutterActorAlign y_align = clutter_actor_get_y_align (priv->child);
+ ClutterActorBox childbox;
+
+ st_theme_node_get_content_box (theme_node, box, &childbox);
+ clutter_actor_allocate_align_fill (priv->child, &childbox,
+ get_align_factor (x_align),
+ get_align_factor (y_align),
+ x_align == CLUTTER_ACTOR_ALIGN_FILL,
+ y_align == CLUTTER_ACTOR_ALIGN_FILL);
+ }
+}
+
+static void
+st_bin_get_preferred_width (ClutterActor *self,
+ gfloat for_height,
+ gfloat *min_width_p,
+ gfloat *natural_width_p)
+{
+ StBinPrivate *priv = st_bin_get_instance_private (ST_BIN (self));
+ StThemeNode *theme_node = st_widget_get_theme_node (ST_WIDGET (self));
+
+ st_theme_node_adjust_for_height (theme_node, &for_height);
+
+ if (priv->child == NULL || !clutter_actor_is_visible (priv->child))
+ {
+ if (min_width_p)
+ *min_width_p = 0;
+
+ if (natural_width_p)
+ *natural_width_p = 0;
+ }
+ else
+ {
+ ClutterActorAlign y_align = clutter_actor_get_y_align (priv->child);
+
+ _st_actor_get_preferred_width (priv->child, for_height,
+ y_align == CLUTTER_ACTOR_ALIGN_FILL,
+ min_width_p,
+ natural_width_p);
+ }
+
+ st_theme_node_adjust_preferred_width (theme_node, min_width_p, natural_width_p);
+}
+
+static void
+st_bin_get_preferred_height (ClutterActor *self,
+ gfloat for_width,
+ gfloat *min_height_p,
+ gfloat *natural_height_p)
+{
+ StBinPrivate *priv = st_bin_get_instance_private (ST_BIN (self));
+ StThemeNode *theme_node = st_widget_get_theme_node (ST_WIDGET (self));
+
+ st_theme_node_adjust_for_width (theme_node, &for_width);
+
+ if (priv->child == NULL || !clutter_actor_is_visible (priv->child))
+ {
+ if (min_height_p)
+ *min_height_p = 0;
+
+ if (natural_height_p)
+ *natural_height_p = 0;
+ }
+ else
+ {
+ ClutterActorAlign x_align = clutter_actor_get_y_align (priv->child);
+
+ _st_actor_get_preferred_height (priv->child, for_width,
+ x_align == CLUTTER_ACTOR_ALIGN_FILL,
+ min_height_p,
+ natural_height_p);
+ }
+
+ st_theme_node_adjust_preferred_height (theme_node, min_height_p, natural_height_p);
+}
+
+static void
+st_bin_destroy (ClutterActor *actor)
+{
+ StBinPrivate *priv = st_bin_get_instance_private (ST_BIN (actor));
+
+ if (priv->child)
+ clutter_actor_destroy (priv->child);
+ g_assert (priv->child == NULL);
+
+ CLUTTER_ACTOR_CLASS (st_bin_parent_class)->destroy (actor);
+}
+
+static void
+st_bin_popup_menu (StWidget *widget)
+{
+ StBinPrivate *priv = st_bin_get_instance_private (ST_BIN (widget));
+
+ if (priv->child && ST_IS_WIDGET (priv->child))
+ st_widget_popup_menu (ST_WIDGET (priv->child));
+}
+
+static gboolean
+st_bin_navigate_focus (StWidget *widget,
+ ClutterActor *from,
+ StDirectionType direction)
+{
+ StBinPrivate *priv = st_bin_get_instance_private (ST_BIN (widget));
+ ClutterActor *bin_actor = CLUTTER_ACTOR (widget);
+
+ if (st_widget_get_can_focus (widget))
+ {
+ if (from && clutter_actor_contains (bin_actor, from))
+ return FALSE;
+
+ if (clutter_actor_is_mapped (bin_actor))
+ {
+ clutter_actor_grab_key_focus (bin_actor);
+ return TRUE;
+ }
+ else
+ {
+ return FALSE;
+ }
+ }
+ else if (priv->child && ST_IS_WIDGET (priv->child))
+ return st_widget_navigate_focus (ST_WIDGET (priv->child), from, direction, FALSE);
+ else
+ return FALSE;
+}
+
+static void
+st_bin_set_property (GObject *gobject,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ StBin *bin = ST_BIN (gobject);
+
+ switch (prop_id)
+ {
+ case PROP_CHILD:
+ st_bin_set_child (bin, g_value_get_object (value));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
+ }
+}
+
+static void
+st_bin_get_property (GObject *gobject,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ StBinPrivate *priv = st_bin_get_instance_private (ST_BIN (gobject));
+
+ switch (prop_id)
+ {
+ case PROP_CHILD:
+ g_value_set_object (value, priv->child);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
+ }
+}
+
+static void
+st_bin_class_init (StBinClass *klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+ ClutterActorClass *actor_class = CLUTTER_ACTOR_CLASS (klass);
+ StWidgetClass *widget_class = ST_WIDGET_CLASS (klass);
+
+ gobject_class->set_property = st_bin_set_property;
+ gobject_class->get_property = st_bin_get_property;
+
+ actor_class->get_preferred_width = st_bin_get_preferred_width;
+ actor_class->get_preferred_height = st_bin_get_preferred_height;
+ actor_class->allocate = st_bin_allocate;
+ actor_class->destroy = st_bin_destroy;
+
+ widget_class->popup_menu = st_bin_popup_menu;
+ widget_class->navigate_focus = st_bin_navigate_focus;
+
+ /**
+ * StBin:child:
+ *
+ * The child #ClutterActor of the #StBin container.
+ */
+ props[PROP_CHILD] =
+ g_param_spec_object ("child",
+ "Child",
+ "The child of the Bin",
+ CLUTTER_TYPE_ACTOR,
+ ST_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
+
+ g_object_class_install_properties (gobject_class, N_PROPS, props);
+}
+
+static void
+st_bin_init (StBin *bin)
+{
+}
+
+/**
+ * st_bin_new:
+ *
+ * Creates a new #StBin, a simple container for one child.
+ *
+ * Returns: the newly created #StBin actor
+ */
+StWidget *
+st_bin_new (void)
+{
+ return g_object_new (ST_TYPE_BIN, NULL);
+}
+
+/**
+ * st_bin_set_child:
+ * @bin: a #StBin
+ * @child: (nullable): a #ClutterActor, or %NULL
+ *
+ * Sets @child as the child of @bin.
+ *
+ * If @bin already has a child, the previous child is removed.
+ */
+void
+st_bin_set_child (StBin *bin,
+ ClutterActor *child)
+{
+ StBinPrivate *priv;
+
+ g_return_if_fail (ST_IS_BIN (bin));
+ g_return_if_fail (child == NULL || CLUTTER_IS_ACTOR (child));
+
+ priv = st_bin_get_instance_private (bin);
+
+ if (priv->child == child)
+ return;
+
+ if (child)
+ {
+ ClutterActor *parent = clutter_actor_get_parent (child);
+
+ if (parent)
+ {
+ g_warning ("%s: The provided 'child' actor %p already has a "
+ "(different) parent %p and can't be made a child of %p.",
+ G_STRFUNC, child, parent, bin);
+ return;
+ }
+ }
+
+ if (priv->child)
+ clutter_actor_remove_child (CLUTTER_ACTOR (bin), priv->child);
+
+ priv->child = NULL;
+
+ if (child)
+ {
+ priv->child = child;
+ clutter_actor_add_child (CLUTTER_ACTOR (bin), child);
+ }
+
+ clutter_actor_queue_relayout (CLUTTER_ACTOR (bin));
+
+ g_object_notify_by_pspec (G_OBJECT (bin), props[PROP_CHILD]);
+}
+
+/**
+ * st_bin_get_child:
+ * @bin: a #StBin
+ *
+ * Gets the #ClutterActor child for @bin.
+ *
+ * Returns: (transfer none) (nullable): a #ClutterActor, or %NULL
+ */
+ClutterActor *
+st_bin_get_child (StBin *bin)
+{
+ g_return_val_if_fail (ST_IS_BIN (bin), NULL);
+
+ return ((StBinPrivate *)st_bin_get_instance_private (bin))->child;
+}
diff --git a/src/st/st-bin.h b/src/st/st-bin.h
new file mode 100644
index 0000000..7784f45
--- /dev/null
+++ b/src/st/st-bin.h
@@ -0,0 +1,53 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * st-bin.h: Basic container actor
+ *
+ * Copyright 2009, 2008 Intel Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU Lesser General Public License,
+ * version 2.1, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#if !defined(ST_H_INSIDE) && !defined(ST_COMPILATION)
+#error "Only <st/st.h> can be included directly.h"
+#endif
+
+#ifndef __ST_BIN_H__
+#define __ST_BIN_H__
+
+#include <st/st-types.h>
+#include <st/st-widget.h>
+
+G_BEGIN_DECLS
+
+#define ST_TYPE_BIN (st_bin_get_type ())
+G_DECLARE_DERIVABLE_TYPE (StBin, st_bin, ST, BIN, StWidget)
+
+/**
+ * StBinClass:
+ *
+ * The #StBinClass struct contains only private data
+ */
+struct _StBinClass
+{
+ /*< private >*/
+ StWidgetClass parent_class;
+};
+
+StWidget * st_bin_new (void);
+void st_bin_set_child (StBin *bin,
+ ClutterActor *child);
+ClutterActor *st_bin_get_child (StBin *bin);
+
+G_END_DECLS
+
+#endif /* __ST_BIN_H__ */
diff --git a/src/st/st-border-image.c b/src/st/st-border-image.c
new file mode 100644
index 0000000..ee09d02
--- /dev/null
+++ b/src/st/st-border-image.c
@@ -0,0 +1,171 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * st-border-image.c: store information about an image with borders
+ *
+ * Copyright 2009, 2010 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation, either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+
+#include <string.h>
+
+#include "st-border-image.h"
+
+struct _StBorderImage {
+ GObject parent;
+
+ GFile *file;
+ int border_top;
+ int border_right;
+ int border_bottom;
+ int border_left;
+
+ int scale_factor;
+};
+
+struct _StBorderImageClass {
+ GObjectClass parent_class;
+
+};
+
+G_DEFINE_TYPE (StBorderImage, st_border_image, G_TYPE_OBJECT)
+
+static void
+st_border_image_finalize (GObject *object)
+{
+ StBorderImage *image = ST_BORDER_IMAGE (object);
+
+ g_object_unref (image->file);
+
+ G_OBJECT_CLASS (st_border_image_parent_class)->finalize (object);
+}
+
+static void
+st_border_image_class_init (StBorderImageClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->finalize = st_border_image_finalize;
+}
+
+static void
+st_border_image_init (StBorderImage *image)
+{
+}
+
+/**
+ * st_border_image_new:
+ * @file: a #GFile
+ * @border_top: the top border
+ * @border_right: the right border
+ * @border_bottom: the bottom border
+ * @border_left: the left border
+ * @scale_factor: the scale factor
+ *
+ * Creates a new #StBorderImage.
+ *
+ * Returns: a new #StBorderImage.
+ */
+StBorderImage *
+st_border_image_new (GFile *file,
+ int border_top,
+ int border_right,
+ int border_bottom,
+ int border_left,
+ int scale_factor)
+{
+ StBorderImage *image;
+
+ image = g_object_new (ST_TYPE_BORDER_IMAGE, NULL);
+
+ image->file = g_object_ref (file);
+ image->border_top = border_top;
+ image->border_right = border_right;
+ image->border_bottom = border_bottom;
+ image->border_left = border_left;
+ image->scale_factor = scale_factor;
+
+ return image;
+}
+
+/**
+ * st_border_image_get_file:
+ * @image: a #StBorderImage
+ *
+ * Get the #GFile for @image.
+ *
+ * Returns: (transfer none): a #GFile
+ */
+GFile *
+st_border_image_get_file (StBorderImage *image)
+{
+ g_return_val_if_fail (ST_IS_BORDER_IMAGE (image), NULL);
+
+ return image->file;
+}
+
+/**
+ * st_border_image_get_border:
+ * @image: a #StBorderImage
+ * @border_top: (out) (optional): the top border
+ * @border_right: (out) (optional): the right border
+ * @border_bottom: (out) (optional): the bottom border
+ * @border_left: (out) (optional): the left border
+ *
+ * Get the border widths for @image, taking into account the scale factor
+ * provided at construction.
+ */
+void
+st_border_image_get_borders (StBorderImage *image,
+ int *border_top,
+ int *border_right,
+ int *border_bottom,
+ int *border_left)
+{
+ g_return_if_fail (ST_IS_BORDER_IMAGE (image));
+
+ if (border_top)
+ *border_top = image->border_top * image->scale_factor;
+ if (border_right)
+ *border_right = image->border_right * image->scale_factor;
+ if (border_bottom)
+ *border_bottom = image->border_bottom * image->scale_factor;
+ if (border_left)
+ *border_left = image->border_left * image->scale_factor;
+}
+
+/**
+ * st_border_image_equal:
+ * @image: a #StBorderImage
+ * @other: a different #StBorderImage
+ *
+ * Check if two #StBorderImage objects are identical.
+ *
+ * Returns: %TRUE if the two border image objects are identical
+ */
+gboolean
+st_border_image_equal (StBorderImage *image,
+ StBorderImage *other)
+{
+ g_return_val_if_fail (ST_IS_BORDER_IMAGE (image), FALSE);
+ g_return_val_if_fail (ST_IS_BORDER_IMAGE (other), FALSE);
+
+ return (image->border_top == other->border_top &&
+ image->border_right == other->border_right &&
+ image->border_bottom == other->border_bottom &&
+ image->border_left == other->border_left &&
+ g_file_equal (image->file, other->file));
+}
diff --git a/src/st/st-border-image.h b/src/st/st-border-image.h
new file mode 100644
index 0000000..9c58152
--- /dev/null
+++ b/src/st/st-border-image.h
@@ -0,0 +1,54 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * st-border-image.h: store information about an image with borders
+ *
+ * Copyright 2009, 2010 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation, either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __ST_BORDER_IMAGE_H__
+#define __ST_BORDER_IMAGE_H__
+
+#include <glib-object.h>
+#include <gio/gio.h>
+
+G_BEGIN_DECLS
+
+/* A StBorderImage encapsulates an image with specified unscaled borders on each edge.
+ */
+
+#define ST_TYPE_BORDER_IMAGE (st_border_image_get_type ())
+G_DECLARE_FINAL_TYPE (StBorderImage, st_border_image, ST, BORDER_IMAGE, GObject)
+
+StBorderImage *st_border_image_new (GFile *file,
+ int border_top,
+ int border_right,
+ int border_bottom,
+ int border_left,
+ int scale_factor);
+
+GFile *st_border_image_get_file (StBorderImage *image);
+void st_border_image_get_borders (StBorderImage *image,
+ int *border_top,
+ int *border_right,
+ int *border_bottom,
+ int *border_left);
+
+gboolean st_border_image_equal (StBorderImage *image,
+ StBorderImage *other);
+
+G_END_DECLS
+
+#endif /* __ST_BORDER_IMAGE_H__ */
diff --git a/src/st/st-box-layout.c b/src/st/st-box-layout.c
new file mode 100644
index 0000000..990fb9e
--- /dev/null
+++ b/src/st/st-box-layout.c
@@ -0,0 +1,307 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * st-box-layout.h: box layout actor
+ *
+ * Copyright 2009 Intel Corporation.
+ * Copyright 2009 Abderrahim Kitouni
+ * Copyright 2009, 2010 Red Hat, Inc.
+ * Copyright 2010 Florian Muellner
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU Lesser General Public License,
+ * version 2.1, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/* Portions copied from Clutter:
+ * Clutter.
+ *
+ * An OpenGL based 'interactive canvas' library.
+ *
+ * Authored By Matthew Allum <mallum@openedhand.com>
+ *
+ * Copyright (C) 2006 OpenedHand
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ */
+
+/**
+ * SECTION:st-box-layout
+ * @short_description: a layout container arranging children in a single line
+ *
+ * The #StBoxLayout arranges its children along a single line, where each
+ * child can be allocated either its preferred size or larger if the expand
+ * option is set. If the fill option is set, the actor will be allocated more
+ * than its requested size. If the fill option is not set, but the expand option
+ * is enabled, then the position of the actor within the available space can
+ * be determined by the alignment child property.
+ *
+ */
+
+#include <stdlib.h>
+
+#include "st-box-layout.h"
+
+#include "st-private.h"
+#include "st-scrollable.h"
+
+
+enum {
+ PROP_0,
+
+ PROP_VERTICAL,
+ PROP_PACK_START,
+
+ N_PROPS
+};
+
+static GParamSpec *props[N_PROPS] = { NULL, };
+
+struct _StBoxLayoutPrivate
+{
+ StAdjustment *hadjustment;
+ StAdjustment *vadjustment;
+};
+
+G_DEFINE_TYPE_WITH_PRIVATE (StBoxLayout, st_box_layout, ST_TYPE_VIEWPORT);
+
+
+static void
+st_box_layout_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ ClutterLayoutManager *layout;
+ ClutterOrientation orientation;
+
+ switch (property_id)
+ {
+ case PROP_VERTICAL:
+ layout = clutter_actor_get_layout_manager (CLUTTER_ACTOR (object));
+ orientation = clutter_box_layout_get_orientation (CLUTTER_BOX_LAYOUT (layout));
+ g_value_set_boolean (value, orientation == CLUTTER_ORIENTATION_VERTICAL);
+ break;
+
+ case PROP_PACK_START:
+ g_value_set_boolean (value, FALSE);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ }
+}
+
+static void
+st_box_layout_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ StBoxLayout *box = ST_BOX_LAYOUT (object);
+
+ switch (property_id)
+ {
+ case PROP_VERTICAL:
+ st_box_layout_set_vertical (box, g_value_get_boolean (value));
+ break;
+
+ case PROP_PACK_START:
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ }
+}
+
+static void
+st_box_layout_style_changed (StWidget *self)
+{
+ StThemeNode *theme_node = st_widget_get_theme_node (self);
+ ClutterBoxLayout *layout;
+ double spacing;
+
+ layout = CLUTTER_BOX_LAYOUT (clutter_actor_get_layout_manager (CLUTTER_ACTOR (self)));
+
+ spacing = st_theme_node_get_length (theme_node, "spacing");
+ clutter_box_layout_set_spacing (layout, (int)(spacing + 0.5));
+
+ ST_WIDGET_CLASS (st_box_layout_parent_class)->style_changed (self);
+}
+
+static void
+layout_notify (GObject *object,
+ GParamSpec *pspec,
+ gpointer user_data)
+{
+ GObject *self = user_data;
+ const char *prop_name = g_param_spec_get_name (pspec);
+
+ if (g_object_class_find_property (G_OBJECT_GET_CLASS (self), prop_name))
+ g_object_notify (self, prop_name);
+}
+
+static void
+on_layout_manager_notify (GObject *object,
+ GParamSpec *pspec,
+ gpointer user_data)
+{
+ ClutterActor *actor = CLUTTER_ACTOR (object);
+ ClutterLayoutManager *layout = clutter_actor_get_layout_manager (actor);
+
+ g_warn_if_fail (CLUTTER_IS_BOX_LAYOUT (layout));
+
+ if (layout == NULL)
+ return;
+
+ g_signal_connect_swapped (layout, "layout-changed",
+ G_CALLBACK (clutter_actor_queue_relayout), actor);
+ g_signal_connect (layout, "notify", G_CALLBACK (layout_notify), object);
+}
+
+static void
+st_box_layout_class_init (StBoxLayoutClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ StWidgetClass *widget_class = ST_WIDGET_CLASS (klass);
+
+ object_class->get_property = st_box_layout_get_property;
+ object_class->set_property = st_box_layout_set_property;
+
+ widget_class->style_changed = st_box_layout_style_changed;
+
+ /**
+ * StBoxLayout:vertical:
+ *
+ * A convenience property for the #ClutterBoxLayout:vertical property of the
+ * internal layout for #StBoxLayout.
+ */
+ props[PROP_VERTICAL] =
+ g_param_spec_boolean ("vertical",
+ "Vertical",
+ "Whether the layout should be vertical, rather"
+ "than horizontal",
+ FALSE,
+ ST_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
+
+ /**
+ * StBoxLayout:pack-start:
+ *
+ * A convenience property for the #ClutterBoxLayout:pack-start property of the
+ * internal layout for #StBoxLayout.
+ */
+ props[PROP_PACK_START] =
+ g_param_spec_boolean ("pack-start",
+ "Pack Start",
+ "Whether to pack items at the start of the box",
+ FALSE,
+ ST_PARAM_READWRITE | G_PARAM_DEPRECATED);
+
+ g_object_class_install_properties (object_class, N_PROPS, props);
+}
+
+static void
+st_box_layout_init (StBoxLayout *self)
+{
+ self->priv = st_box_layout_get_instance_private (self);
+
+ g_signal_connect (self, "notify::layout-manager",
+ G_CALLBACK (on_layout_manager_notify), NULL);
+ clutter_actor_set_layout_manager (CLUTTER_ACTOR (self), clutter_box_layout_new ());
+}
+
+/**
+ * st_box_layout_new:
+ *
+ * Create a new #StBoxLayout.
+ *
+ * Returns: a newly allocated #StBoxLayout
+ */
+StWidget *
+st_box_layout_new (void)
+{
+ return g_object_new (ST_TYPE_BOX_LAYOUT, NULL);
+}
+
+/**
+ * st_box_layout_set_vertical:
+ * @box: A #StBoxLayout
+ * @vertical: %TRUE if the layout should be vertical
+ *
+ * Set the value of the #StBoxLayout:vertical property
+ */
+void
+st_box_layout_set_vertical (StBoxLayout *box,
+ gboolean vertical)
+{
+ ClutterLayoutManager *layout;
+ ClutterOrientation orientation;
+
+ g_return_if_fail (ST_IS_BOX_LAYOUT (box));
+
+ layout = clutter_actor_get_layout_manager (CLUTTER_ACTOR (box));
+ orientation = vertical ? CLUTTER_ORIENTATION_VERTICAL
+ : CLUTTER_ORIENTATION_HORIZONTAL;
+
+ if (clutter_box_layout_get_orientation (CLUTTER_BOX_LAYOUT (layout)) != orientation)
+ {
+ clutter_box_layout_set_orientation (CLUTTER_BOX_LAYOUT (layout), orientation);
+ g_object_notify_by_pspec (G_OBJECT (box), props[PROP_VERTICAL]);
+ }
+}
+
+/**
+ * st_box_layout_get_vertical:
+ * @box: A #StBoxLayout
+ *
+ * Get the value of the #StBoxLayout:vertical property.
+ *
+ * Returns: %TRUE if the layout is vertical
+ */
+gboolean
+st_box_layout_get_vertical (StBoxLayout *box)
+{
+ ClutterLayoutManager *layout;
+ ClutterOrientation orientation;
+
+ g_return_val_if_fail (ST_IS_BOX_LAYOUT (box), FALSE);
+
+ layout = clutter_actor_get_layout_manager (CLUTTER_ACTOR (box));
+ orientation = clutter_box_layout_get_orientation (CLUTTER_BOX_LAYOUT (layout));
+ return orientation == CLUTTER_ORIENTATION_VERTICAL;
+}
+
+/**
+ * st_box_layout_set_pack_start:
+ * @box: A #StBoxLayout
+ * @pack_start: %TRUE if the layout should use pack-start
+ *
+ * Deprecated: No longer has any effect
+ */
+void
+st_box_layout_set_pack_start (StBoxLayout *box,
+ gboolean pack_start)
+{
+}
+
+/**
+ * st_box_layout_get_pack_start:
+ * @box: A #StBoxLayout
+ *
+ * Returns: the value of the #StBoxLayout:pack-start property,
+ * always %FALSE
+ */
+gboolean
+st_box_layout_get_pack_start (StBoxLayout *box)
+{
+ return FALSE;
+}
diff --git a/src/st/st-box-layout.h b/src/st/st-box-layout.h
new file mode 100644
index 0000000..82f5a70
--- /dev/null
+++ b/src/st/st-box-layout.h
@@ -0,0 +1,65 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * st-box-layout.h: box layout actor
+ *
+ * Copyright 2009 Intel Corporation.
+ * Copyright 2009, 2010 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU Lesser General Public License,
+ * version 2.1, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#if !defined(ST_H_INSIDE) && !defined(ST_COMPILATION)
+#error "Only <st/st.h> can be included directly.h"
+#endif
+
+#ifndef _ST_BOX_LAYOUT_H
+#define _ST_BOX_LAYOUT_H
+
+#include <st/st-widget.h>
+#include <st/st-viewport.h>
+
+G_BEGIN_DECLS
+
+#define ST_TYPE_BOX_LAYOUT st_box_layout_get_type()
+G_DECLARE_FINAL_TYPE (StBoxLayout, st_box_layout, ST, BOX_LAYOUT, StViewport)
+
+typedef struct _StBoxLayout StBoxLayout;
+typedef struct _StBoxLayoutPrivate StBoxLayoutPrivate;
+
+/**
+ * StBoxLayout:
+ *
+ * The contents of this structure are private and should only be accessed
+ * through the public API.
+ */
+struct _StBoxLayout
+{
+ /*< private >*/
+ StViewport parent;
+
+ StBoxLayoutPrivate *priv;
+};
+
+StWidget *st_box_layout_new (void);
+
+void st_box_layout_set_vertical (StBoxLayout *box,
+ gboolean vertical);
+gboolean st_box_layout_get_vertical (StBoxLayout *box);
+
+void st_box_layout_set_pack_start (StBoxLayout *box,
+ gboolean pack_start);
+gboolean st_box_layout_get_pack_start (StBoxLayout *box);
+
+G_END_DECLS
+
+#endif /* _ST_BOX_LAYOUT_H */
diff --git a/src/st/st-button.c b/src/st/st-button.c
new file mode 100644
index 0000000..148197c
--- /dev/null
+++ b/src/st/st-button.c
@@ -0,0 +1,1043 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * st-button.c: Plain button actor
+ *
+ * Copyright 2007 OpenedHand
+ * Copyright 2008, 2009 Intel Corporation.
+ * Copyright 2009, 2010 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU Lesser General Public License,
+ * version 2.1, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * SECTION:st-button
+ * @short_description: Button widget
+ *
+ * A button widget with support for either a text label or icon, toggle mode
+ * and transitions effects between states.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <stdlib.h>
+#include <string.h>
+
+#include <glib.h>
+
+#include <clutter/clutter.h>
+
+#include "st-button.h"
+
+#include "st-icon.h"
+#include "st-enum-types.h"
+#include "st-texture-cache.h"
+#include "st-private.h"
+
+#include <st/st-widget-accessible.h>
+
+enum
+{
+ PROP_0,
+
+ PROP_LABEL,
+ PROP_ICON_NAME,
+ PROP_BUTTON_MASK,
+ PROP_TOGGLE_MODE,
+ PROP_CHECKED,
+ PROP_PRESSED,
+
+ N_PROPS
+};
+
+static GParamSpec *props[N_PROPS] = { NULL, };
+
+enum
+{
+ CLICKED,
+
+ LAST_SIGNAL
+};
+
+typedef struct _StButtonPrivate StButtonPrivate;
+
+struct _StButtonPrivate
+{
+ gchar *text;
+
+ ClutterInputDevice *device;
+ ClutterEventSequence *press_sequence;
+ ClutterGrab *grab;
+
+ guint button_mask : 3;
+ guint is_toggle : 1;
+
+ guint pressed : 3;
+ guint grabbed : 3;
+ guint is_checked : 1;
+};
+
+static guint button_signals[LAST_SIGNAL] = { 0, };
+
+G_DEFINE_TYPE_WITH_PRIVATE (StButton, st_button, ST_TYPE_BIN);
+
+static GType st_button_accessible_get_type (void) G_GNUC_CONST;
+
+static void
+st_button_update_label_style (StButton *button)
+{
+ ClutterActor *label;
+
+ label = st_bin_get_child (ST_BIN (button));
+
+ /* check the child is really a label */
+ if (!CLUTTER_IS_TEXT (label))
+ return;
+
+ _st_set_text_from_style (CLUTTER_TEXT (label), st_widget_get_theme_node (ST_WIDGET (button)));
+}
+
+static void
+st_button_style_changed (StWidget *widget)
+{
+ StButton *button = ST_BUTTON (widget);
+ StButtonClass *button_class = ST_BUTTON_GET_CLASS (button);
+
+ ST_WIDGET_CLASS (st_button_parent_class)->style_changed (widget);
+
+ /* update the label styling */
+ st_button_update_label_style (button);
+
+ /* run a transition if applicable */
+ if (button_class->transition)
+ {
+ button_class->transition (button);
+ }
+}
+
+static void
+st_button_press (StButton *button,
+ ClutterInputDevice *device,
+ StButtonMask mask,
+ ClutterEventSequence *sequence)
+{
+ StButtonPrivate *priv = st_button_get_instance_private (button);
+ gboolean active_changed = priv->pressed == 0 || sequence;
+
+ if (active_changed)
+ st_widget_add_style_pseudo_class (ST_WIDGET (button), "active");
+
+ priv->pressed |= mask;
+ priv->press_sequence = sequence;
+ priv->device = device;
+
+ if (active_changed)
+ g_object_notify_by_pspec (G_OBJECT (button), props[PROP_PRESSED]);
+}
+
+static void
+st_button_release (StButton *button,
+ ClutterInputDevice *device,
+ StButtonMask mask,
+ int clicked_button,
+ ClutterEventSequence *sequence)
+{
+ StButtonPrivate *priv = st_button_get_instance_private (button);
+
+ if ((device && priv->device != device) ||
+ (sequence && priv->press_sequence != sequence))
+ return;
+ else if (!sequence)
+ {
+ priv->pressed &= ~mask;
+
+ if (priv->pressed != 0)
+ return;
+ }
+
+ priv->press_sequence = NULL;
+ priv->device = NULL;
+ st_widget_remove_style_pseudo_class (ST_WIDGET (button), "active");
+ g_object_notify_by_pspec (G_OBJECT (button), props[PROP_PRESSED]);
+
+ if (clicked_button || sequence)
+ {
+ if (priv->is_toggle)
+ st_button_set_checked (button, !priv->is_checked);
+
+ g_signal_emit (button, button_signals[CLICKED], 0, clicked_button);
+ }
+}
+
+static gboolean
+st_button_button_press (ClutterActor *actor,
+ ClutterButtonEvent *event)
+{
+ StButton *button = ST_BUTTON (actor);
+ StButtonPrivate *priv = st_button_get_instance_private (button);
+ StButtonMask mask = ST_BUTTON_MASK_FROM_BUTTON (event->button);
+ ClutterInputDevice *device = clutter_event_get_device ((ClutterEvent*) event);
+
+ if (priv->press_sequence)
+ return CLUTTER_EVENT_PROPAGATE;
+
+ if (priv->button_mask & mask)
+ {
+ ClutterActor *stage;
+
+ stage = clutter_actor_get_stage (actor);
+
+ if (priv->grabbed == 0)
+ priv->grab = clutter_stage_grab (CLUTTER_STAGE (stage), actor);
+
+ priv->grabbed |= mask;
+ st_button_press (button, device, mask, NULL);
+
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static gboolean
+st_button_button_release (ClutterActor *actor,
+ ClutterButtonEvent *event)
+{
+ StButton *button = ST_BUTTON (actor);
+ StButtonPrivate *priv = st_button_get_instance_private (button);
+ StButtonMask mask = ST_BUTTON_MASK_FROM_BUTTON (event->button);
+ ClutterInputDevice *device = clutter_event_get_device ((ClutterEvent*) event);
+
+ if (priv->button_mask & mask)
+ {
+ ClutterStage *stage;
+ ClutterActor *target;
+ gboolean is_click;
+
+ stage = clutter_event_get_stage ((ClutterEvent *) event);
+ target = clutter_stage_get_event_actor (stage, (ClutterEvent *) event);
+
+ is_click = priv->grabbed && clutter_actor_contains (actor, target);
+ st_button_release (button, device, mask, is_click ? event->button : 0, NULL);
+
+ priv->grabbed &= ~mask;
+ if (priv->grab && priv->grabbed == 0)
+ {
+ clutter_grab_dismiss (priv->grab);
+ g_clear_pointer (&priv->grab, clutter_grab_unref);
+ }
+
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static gboolean
+st_button_touch_event (ClutterActor *actor,
+ ClutterTouchEvent *event)
+{
+ StButton *button = ST_BUTTON (actor);
+ StButtonPrivate *priv = st_button_get_instance_private (button);
+ StButtonMask mask = ST_BUTTON_MASK_FROM_BUTTON (1);
+ ClutterEventSequence *sequence;
+ ClutterInputDevice *device;
+
+ if (priv->pressed != 0)
+ return CLUTTER_EVENT_PROPAGATE;
+ if ((priv->button_mask & mask) == 0)
+ return CLUTTER_EVENT_PROPAGATE;
+
+ device = clutter_event_get_device ((ClutterEvent*) event);
+ sequence = clutter_event_get_event_sequence ((ClutterEvent*) event);
+
+ if (event->type == CLUTTER_TOUCH_BEGIN && !priv->grab && !priv->press_sequence)
+ {
+ st_button_press (button, device, 0, sequence);
+ return CLUTTER_EVENT_STOP;
+ }
+ else if (event->type == CLUTTER_TOUCH_END &&
+ priv->device == device &&
+ priv->press_sequence == sequence)
+ {
+ st_button_release (button, device, mask, 0, sequence);
+ return CLUTTER_EVENT_STOP;
+ }
+ else if (event->type == CLUTTER_TOUCH_CANCEL)
+ {
+ st_button_fake_release (button);
+ }
+
+ return CLUTTER_EVENT_PROPAGATE;
+}
+
+static gboolean
+st_button_key_press (ClutterActor *actor,
+ ClutterKeyEvent *event)
+{
+ StButton *button = ST_BUTTON (actor);
+ StButtonPrivate *priv = st_button_get_instance_private (button);
+
+ if (priv->button_mask & ST_BUTTON_ONE)
+ {
+ if (event->keyval == CLUTTER_KEY_space ||
+ event->keyval == CLUTTER_KEY_Return ||
+ event->keyval == CLUTTER_KEY_KP_Enter ||
+ event->keyval == CLUTTER_KEY_ISO_Enter)
+ {
+ st_button_press (button, NULL, ST_BUTTON_ONE, NULL);
+ return TRUE;
+ }
+ }
+
+ return CLUTTER_ACTOR_CLASS (st_button_parent_class)->key_press_event (actor, event);
+}
+
+static gboolean
+st_button_key_release (ClutterActor *actor,
+ ClutterKeyEvent *event)
+{
+ StButton *button = ST_BUTTON (actor);
+ StButtonPrivate *priv = st_button_get_instance_private (button);
+
+ if (priv->button_mask & ST_BUTTON_ONE)
+ {
+ if (event->keyval == CLUTTER_KEY_space ||
+ event->keyval == CLUTTER_KEY_Return ||
+ event->keyval == CLUTTER_KEY_KP_Enter ||
+ event->keyval == CLUTTER_KEY_ISO_Enter)
+ {
+ gboolean is_click;
+
+ is_click = (priv->pressed & ST_BUTTON_ONE);
+ st_button_release (button, NULL, ST_BUTTON_ONE, is_click ? 1 : 0, NULL);
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+static void
+st_button_key_focus_out (ClutterActor *actor)
+{
+ StButton *button = ST_BUTTON (actor);
+ StButtonPrivate *priv = st_button_get_instance_private (button);
+
+ /* If we lose focus between a key press and release, undo the press */
+ if ((priv->pressed & ST_BUTTON_ONE) &&
+ !(priv->grabbed & ST_BUTTON_ONE))
+ st_button_release (button, NULL, ST_BUTTON_ONE, 0, NULL);
+
+ CLUTTER_ACTOR_CLASS (st_button_parent_class)->key_focus_out (actor);
+}
+
+static gboolean
+st_button_enter (ClutterActor *actor,
+ ClutterCrossingEvent *event)
+{
+ StButton *button = ST_BUTTON (actor);
+ StButtonPrivate *priv = st_button_get_instance_private (button);
+ gboolean ret;
+
+ ret = CLUTTER_ACTOR_CLASS (st_button_parent_class)->enter_event (actor, event);
+
+ if (priv->grabbed)
+ {
+ if (st_widget_get_hover (ST_WIDGET (button)))
+ st_button_press (button, priv->device,
+ priv->grabbed, NULL);
+ else
+ st_button_release (button, priv->device,
+ priv->grabbed, 0, NULL);
+ }
+
+ return ret;
+}
+
+static gboolean
+st_button_leave (ClutterActor *actor,
+ ClutterCrossingEvent *event)
+{
+ StButton *button = ST_BUTTON (actor);
+ StButtonPrivate *priv = st_button_get_instance_private (button);
+ gboolean ret;
+
+ ret = CLUTTER_ACTOR_CLASS (st_button_parent_class)->leave_event (actor, event);
+
+ if (priv->grabbed)
+ {
+ if (st_widget_get_hover (ST_WIDGET (button)))
+ st_button_press (button, priv->device,
+ priv->grabbed, NULL);
+ else
+ st_button_release (button, priv->device,
+ priv->grabbed, 0, NULL);
+ }
+
+ return ret;
+}
+
+static void
+st_button_set_property (GObject *gobject,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ StButton *button = ST_BUTTON (gobject);
+
+ switch (prop_id)
+ {
+ case PROP_LABEL:
+ st_button_set_label (button, g_value_get_string (value));
+ break;
+ case PROP_ICON_NAME:
+ st_button_set_icon_name (button, g_value_get_string (value));
+ break;
+ case PROP_BUTTON_MASK:
+ st_button_set_button_mask (button, g_value_get_flags (value));
+ break;
+ case PROP_TOGGLE_MODE:
+ st_button_set_toggle_mode (button, g_value_get_boolean (value));
+ break;
+ case PROP_CHECKED:
+ st_button_set_checked (button, g_value_get_boolean (value));
+ break;
+
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+st_button_get_property (GObject *gobject,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ StButtonPrivate *priv = st_button_get_instance_private (ST_BUTTON (gobject));
+
+ switch (prop_id)
+ {
+ case PROP_LABEL:
+ g_value_set_string (value, priv->text);
+ break;
+ case PROP_ICON_NAME:
+ g_value_set_string (value, st_button_get_icon_name (ST_BUTTON (gobject)));
+ break;
+ case PROP_BUTTON_MASK:
+ g_value_set_flags (value, priv->button_mask);
+ break;
+ case PROP_TOGGLE_MODE:
+ g_value_set_boolean (value, priv->is_toggle);
+ break;
+ case PROP_CHECKED:
+ g_value_set_boolean (value, priv->is_checked);
+ break;
+ case PROP_PRESSED:
+ g_value_set_boolean (value, priv->pressed != 0 || priv->press_sequence != NULL);
+ break;
+
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+st_button_finalize (GObject *gobject)
+{
+ StButtonPrivate *priv = st_button_get_instance_private (ST_BUTTON (gobject));
+
+ g_free (priv->text);
+
+ G_OBJECT_CLASS (st_button_parent_class)->finalize (gobject);
+}
+
+static void
+st_button_class_init (StButtonClass *klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+ ClutterActorClass *actor_class = CLUTTER_ACTOR_CLASS (klass);
+ StWidgetClass *widget_class = ST_WIDGET_CLASS (klass);
+
+ gobject_class->set_property = st_button_set_property;
+ gobject_class->get_property = st_button_get_property;
+ gobject_class->finalize = st_button_finalize;
+
+ actor_class->button_press_event = st_button_button_press;
+ actor_class->button_release_event = st_button_button_release;
+ actor_class->key_press_event = st_button_key_press;
+ actor_class->key_release_event = st_button_key_release;
+ actor_class->key_focus_out = st_button_key_focus_out;
+ actor_class->enter_event = st_button_enter;
+ actor_class->leave_event = st_button_leave;
+ actor_class->touch_event = st_button_touch_event;
+
+ widget_class->style_changed = st_button_style_changed;
+ widget_class->get_accessible_type = st_button_accessible_get_type;
+
+ /**
+ * StButton:label:
+ *
+ * The label of the #StButton.
+ */
+ props[PROP_LABEL] =
+ g_param_spec_string ("label",
+ "Label",
+ "Label of the button",
+ NULL,
+ ST_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
+
+ /**
+ * StButton:icon-name:
+ *
+ * The icon name of the #StButton.
+ */
+ props[PROP_ICON_NAME] =
+ g_param_spec_string ("icon-name",
+ "Icon name",
+ "Icon name of the button",
+ NULL,
+ ST_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
+
+ /**
+ * StButton:button-mask:
+ *
+ * Which buttons will trigger the #StButton::clicked signal.
+ */
+ props[PROP_BUTTON_MASK] =
+ g_param_spec_flags ("button-mask",
+ "Button mask",
+ "Which buttons trigger the 'clicked' signal",
+ ST_TYPE_BUTTON_MASK, ST_BUTTON_ONE,
+ ST_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
+
+ /**
+ * StButton:toggle-mode:
+ *
+ * Whether the #StButton is operating in toggle mode (on/off).
+ */
+ props[PROP_TOGGLE_MODE] =
+ g_param_spec_boolean ("toggle-mode",
+ "Toggle Mode",
+ "Enable or disable toggling",
+ FALSE,
+ ST_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
+
+ /**
+ * StButton:checked:
+ *
+ * If #StButton:toggle-mode is %TRUE, indicates if the #StButton is toggled
+ * "on" or "off".
+ *
+ * When the value is %TRUE, the #StButton will have the `checked` CSS
+ * pseudo-class set.
+ */
+ props[PROP_CHECKED] =
+ g_param_spec_boolean ("checked",
+ "Checked",
+ "Indicates if a toggle button is \"on\" or \"off\"",
+ FALSE,
+ ST_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
+
+ /**
+ * StButton:pressed:
+ *
+ * In contrast to #StButton:checked, this property indicates whether the
+ * #StButton is being actively pressed, rather than just in the "on" state.
+ */
+ props[PROP_PRESSED] =
+ g_param_spec_boolean ("pressed",
+ "Pressed",
+ "Indicates if the button is pressed in",
+ FALSE,
+ ST_PARAM_READABLE);
+
+ g_object_class_install_properties (gobject_class, N_PROPS, props);
+
+
+ /**
+ * StButton::clicked:
+ * @button: the object that received the signal
+ * @clicked_button: the mouse button that was used
+ *
+ * Emitted when the user activates the button, either with a mouse press and
+ * release or with the keyboard.
+ */
+ button_signals[CLICKED] =
+ g_signal_new ("clicked",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (StButtonClass, clicked),
+ NULL, NULL, NULL,
+ G_TYPE_NONE, 1,
+ G_TYPE_INT);
+}
+
+static void
+st_button_init (StButton *button)
+{
+ StButtonPrivate *priv = st_button_get_instance_private (button);
+
+ priv->button_mask = ST_BUTTON_ONE;
+
+ clutter_actor_set_reactive (CLUTTER_ACTOR (button), TRUE);
+ st_widget_set_track_hover (ST_WIDGET (button), TRUE);
+}
+
+/**
+ * st_button_new:
+ *
+ * Create a new button
+ *
+ * Returns: a new #StButton
+ */
+StWidget *
+st_button_new (void)
+{
+ return g_object_new (ST_TYPE_BUTTON, NULL);
+}
+
+/**
+ * st_button_new_with_label:
+ * @text: text to set the label to
+ *
+ * Create a new #StButton with the specified label
+ *
+ * Returns: a new #StButton
+ */
+StWidget *
+st_button_new_with_label (const gchar *text)
+{
+ return g_object_new (ST_TYPE_BUTTON, "label", text, NULL);
+}
+
+/**
+ * st_button_get_label:
+ * @button: a #StButton
+ *
+ * Get the text displayed on the button. If the label is empty, an empty string
+ * will be returned instead of %NULL.
+ *
+ * Returns: (transfer none): the text for the button
+ */
+const gchar *
+st_button_get_label (StButton *button)
+{
+ g_return_val_if_fail (ST_IS_BUTTON (button), NULL);
+
+ return ((StButtonPrivate *)st_button_get_instance_private (button))->text;
+}
+
+/**
+ * st_button_set_label:
+ * @button: a #Stbutton
+ * @text: (nullable): text to set the label to
+ *
+ * Sets the text displayed on the button.
+ */
+void
+st_button_set_label (StButton *button,
+ const gchar *text)
+{
+ StButtonPrivate *priv;
+ ClutterActor *label;
+
+ g_return_if_fail (ST_IS_BUTTON (button));
+
+ priv = st_button_get_instance_private (button);
+
+ if (g_strcmp0 (priv->text, text) == 0)
+ return;
+
+ g_free (priv->text);
+
+ if (text)
+ priv->text = g_strdup (text);
+ else
+ priv->text = g_strdup ("");
+
+ label = st_bin_get_child (ST_BIN (button));
+
+ if (label && CLUTTER_IS_TEXT (label))
+ {
+ clutter_text_set_text (CLUTTER_TEXT (label), priv->text);
+ }
+ else
+ {
+ label = g_object_new (CLUTTER_TYPE_TEXT,
+ "text", priv->text,
+ "line-alignment", PANGO_ALIGN_CENTER,
+ "ellipsize", PANGO_ELLIPSIZE_END,
+ "use-markup", TRUE,
+ "x-align", CLUTTER_ACTOR_ALIGN_CENTER,
+ "y-align", CLUTTER_ACTOR_ALIGN_CENTER,
+ NULL);
+ st_bin_set_child (ST_BIN (button), label);
+ }
+
+ /* Fake a style change so that we reset the style properties on the label */
+ st_widget_style_changed (ST_WIDGET (button));
+
+ g_object_notify_by_pspec (G_OBJECT (button), props[PROP_LABEL]);
+}
+
+/**
+ * st_button_get_icon_name:
+ * @button: a #StButton
+ *
+ * Get the icon name of the button. If the button isn't showing an icon,
+ * the return value will be %NULL.
+ *
+ * Returns: (transfer none) (nullable): the icon name of the button
+ */
+const char *
+st_button_get_icon_name (StButton *button)
+{
+ ClutterActor *icon;
+
+ g_return_val_if_fail (ST_IS_BUTTON (button), NULL);
+
+ icon = st_bin_get_child (ST_BIN (button));
+ if (ST_IS_ICON (icon))
+ return st_icon_get_icon_name (ST_ICON (icon));
+ return NULL;
+}
+
+/**
+ * st_button_set_icon_name:
+ * @button: a #Stbutton
+ * @icon_name: an icon name
+ *
+ * Adds an `StIcon` with the given icon name as a child.
+ *
+ * If @button already contains a child actor, that child will
+ * be removed and replaced with the icon.
+ */
+void
+st_button_set_icon_name (StButton *button,
+ const char *icon_name)
+{
+ ClutterActor *icon;
+
+ g_return_if_fail (ST_IS_BUTTON (button));
+ g_return_if_fail (icon_name != NULL);
+
+ icon = st_bin_get_child (ST_BIN (button));
+
+ if (ST_IS_ICON (icon))
+ {
+ if (g_strcmp0 (st_icon_get_icon_name (ST_ICON (icon)), icon_name) == 0)
+ return;
+
+ st_icon_set_icon_name (ST_ICON (icon), icon_name);
+ }
+ else
+ {
+ icon = g_object_new (ST_TYPE_ICON,
+ "icon-name", icon_name,
+ "x-align", CLUTTER_ACTOR_ALIGN_CENTER,
+ "y-align", CLUTTER_ACTOR_ALIGN_CENTER,
+ NULL);
+ st_bin_set_child (ST_BIN (button), icon);
+ }
+
+ g_object_notify_by_pspec (G_OBJECT (button), props[PROP_ICON_NAME]);
+}
+
+/**
+ * st_button_get_button_mask:
+ * @button: a #StButton
+ *
+ * Gets the mask of mouse buttons that @button emits the
+ * #StButton::clicked signal for.
+ *
+ * Returns: the mask of mouse buttons that @button emits the
+ * #StButton::clicked signal for.
+ */
+StButtonMask
+st_button_get_button_mask (StButton *button)
+{
+ g_return_val_if_fail (ST_IS_BUTTON (button), 0);
+
+ return ((StButtonPrivate *)st_button_get_instance_private (button))->button_mask;
+}
+
+/**
+ * st_button_set_button_mask:
+ * @button: a #Stbutton
+ * @mask: the mask of mouse buttons that @button responds to
+ *
+ * Sets which mouse buttons @button emits #StButton::clicked for.
+ */
+void
+st_button_set_button_mask (StButton *button,
+ StButtonMask mask)
+{
+ StButtonPrivate *priv;
+
+ g_return_if_fail (ST_IS_BUTTON (button));
+
+ priv = st_button_get_instance_private (button);
+
+ if (priv->button_mask == mask)
+ return;
+
+ priv->button_mask = mask;
+
+ g_object_notify_by_pspec (G_OBJECT (button), props[PROP_BUTTON_MASK]);
+}
+
+/**
+ * st_button_get_toggle_mode:
+ * @button: a #StButton
+ *
+ * Get the toggle mode status of the button.
+ *
+ * Returns: %TRUE if toggle mode is set, otherwise %FALSE
+ */
+gboolean
+st_button_get_toggle_mode (StButton *button)
+{
+ g_return_val_if_fail (ST_IS_BUTTON (button), FALSE);
+
+ return ((StButtonPrivate *)st_button_get_instance_private (button))->is_toggle;
+}
+
+/**
+ * st_button_set_toggle_mode:
+ * @button: a #Stbutton
+ * @toggle: %TRUE or %FALSE
+ *
+ * Enables or disables toggle mode for the button. In toggle mode, the active
+ * state will be "toggled" when the user clicks the button.
+ */
+void
+st_button_set_toggle_mode (StButton *button,
+ gboolean toggle)
+{
+ StButtonPrivate *priv;
+
+ g_return_if_fail (ST_IS_BUTTON (button));
+
+ priv = st_button_get_instance_private (button);
+
+ if (priv->is_toggle == toggle)
+ return;
+
+ priv->is_toggle = toggle;
+
+ g_object_notify_by_pspec (G_OBJECT (button), props[PROP_TOGGLE_MODE]);
+}
+
+/**
+ * st_button_get_checked:
+ * @button: a #StButton
+ *
+ * Get the #StButton:checked property of a #StButton that is in toggle mode.
+ *
+ * Returns: %TRUE if the button is checked, or %FALSE if not
+ */
+gboolean
+st_button_get_checked (StButton *button)
+{
+ g_return_val_if_fail (ST_IS_BUTTON (button), FALSE);
+
+ return ((StButtonPrivate *)st_button_get_instance_private (button))->is_checked;
+}
+
+/**
+ * st_button_set_checked:
+ * @button: a #Stbutton
+ * @checked: %TRUE or %FALSE
+ *
+ * Set the #StButton:checked property of the button. This is only really useful
+ * if the button has #StButton:toggle-mode property set to %TRUE.
+ */
+void
+st_button_set_checked (StButton *button,
+ gboolean checked)
+{
+ StButtonPrivate *priv;
+
+ g_return_if_fail (ST_IS_BUTTON (button));
+
+ priv = st_button_get_instance_private (button);
+ if (priv->is_checked == checked)
+ return;
+
+ priv->is_checked = checked;
+
+ if (checked)
+ st_widget_add_style_pseudo_class (ST_WIDGET (button), "checked");
+ else
+ st_widget_remove_style_pseudo_class (ST_WIDGET (button), "checked");
+
+ g_object_notify_by_pspec (G_OBJECT (button), props[PROP_CHECKED]);
+}
+
+/**
+ * st_button_fake_release:
+ * @button: an #StButton
+ *
+ * If this widget is holding a pointer grab, this function will
+ * will ungrab it, and reset the #StButton:pressed state. The effect is
+ * similar to if the user had released the mouse button, but without
+ * emitting the #StButton::clicked signal.
+ *
+ * This function is useful if for example you want to do something
+ * after the user is holding the mouse button for a given period of
+ * time, breaking the grab.
+ */
+void
+st_button_fake_release (StButton *button)
+{
+ StButtonPrivate *priv;
+
+ g_return_if_fail (ST_IS_BUTTON (button));
+
+ priv = st_button_get_instance_private (button);
+
+ if (priv->grab)
+ {
+ clutter_grab_dismiss (priv->grab);
+ g_clear_pointer (&priv->grab, clutter_grab_unref);
+ }
+
+ priv->grabbed = 0;
+
+ if (priv->pressed || priv->press_sequence)
+ st_button_release (button, priv->device,
+ priv->pressed, 0, NULL);
+}
+
+/******************************************************************************/
+/*************************** ACCESSIBILITY SUPPORT ****************************/
+/******************************************************************************/
+
+#define ST_TYPE_BUTTON_ACCESSIBLE st_button_accessible_get_type ()
+
+#define ST_BUTTON_ACCESSIBLE(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), \
+ ST_TYPE_BUTTON_ACCESSIBLE, StButtonAccessible))
+
+#define ST_IS_BUTTON_ACCESSIBLE(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), \
+ ST_TYPE_BUTTON_ACCESSIBLE))
+
+#define ST_BUTTON_ACCESSIBLE_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), \
+ ST_TYPE_BUTTON_ACCESSIBLE, StButtonAccessibleClass))
+
+#define ST_IS_BUTTON_ACCESSIBLE_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), \
+ ST_TYPE_BUTTON_ACCESSIBLE))
+
+#define ST_BUTTON_ACCESSIBLE_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), \
+ ST_TYPE_BUTTON_ACCESSIBLE, StButtonAccessibleClass))
+
+typedef struct _StButtonAccessible StButtonAccessible;
+typedef struct _StButtonAccessibleClass StButtonAccessibleClass;
+
+struct _StButtonAccessible
+{
+ StWidgetAccessible parent;
+};
+
+struct _StButtonAccessibleClass
+{
+ StWidgetAccessibleClass parent_class;
+};
+
+/* AtkObject */
+static void st_button_accessible_initialize (AtkObject *obj,
+ gpointer data);
+
+G_DEFINE_TYPE (StButtonAccessible, st_button_accessible, ST_TYPE_WIDGET_ACCESSIBLE)
+
+static const gchar *
+st_button_accessible_get_name (AtkObject *obj)
+{
+ StButton *button = NULL;
+ const gchar *name = NULL;
+
+ button = ST_BUTTON (atk_gobject_accessible_get_object (ATK_GOBJECT_ACCESSIBLE (obj)));
+
+ if (button == NULL)
+ return NULL;
+
+ name = ATK_OBJECT_CLASS (st_button_accessible_parent_class)->get_name (obj);
+ if (name != NULL)
+ return name;
+
+ return st_button_get_label (button);
+}
+
+static void
+st_button_accessible_class_init (StButtonAccessibleClass *klass)
+{
+ AtkObjectClass *atk_class = ATK_OBJECT_CLASS (klass);
+
+ atk_class->initialize = st_button_accessible_initialize;
+ atk_class->get_name = st_button_accessible_get_name;
+}
+
+static void
+st_button_accessible_init (StButtonAccessible *self)
+{
+ /* initialization done on AtkObject->initialize */
+}
+
+static void
+st_button_accessible_notify_label_cb (StButton *button,
+ GParamSpec *psec,
+ AtkObject *accessible)
+{
+ g_object_notify (G_OBJECT (accessible), "accessible-name");
+}
+
+static void
+st_button_accessible_compute_role (AtkObject *accessible,
+ StButton *button)
+{
+ atk_object_set_role (accessible, st_button_get_toggle_mode (button)
+ ? ATK_ROLE_TOGGLE_BUTTON : ATK_ROLE_PUSH_BUTTON);
+}
+
+static void
+st_button_accessible_notify_toggle_mode_cb (StButton *button,
+ GParamSpec *psec,
+ AtkObject *accessible)
+{
+ st_button_accessible_compute_role (accessible, button);
+}
+
+static void
+st_button_accessible_initialize (AtkObject *obj,
+ gpointer data)
+{
+ ATK_OBJECT_CLASS (st_button_accessible_parent_class)->initialize (obj, data);
+
+ st_button_accessible_compute_role (obj, ST_BUTTON (data));
+
+ g_signal_connect (data, "notify::label",
+ G_CALLBACK (st_button_accessible_notify_label_cb), obj);
+ g_signal_connect (data, "notify::toggle-mode",
+ G_CALLBACK (st_button_accessible_notify_toggle_mode_cb), obj);
+}
diff --git a/src/st/st-button.h b/src/st/st-button.h
new file mode 100644
index 0000000..3f7dbb0
--- /dev/null
+++ b/src/st/st-button.h
@@ -0,0 +1,85 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * st-button.h: Plain button actor
+ *
+ * Copyright 2007 OpenedHand
+ * Copyright 2008, 2009 Intel Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU Lesser General Public License,
+ * version 2.1, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#if !defined(ST_H_INSIDE) && !defined(ST_COMPILATION)
+#error "Only <st/st.h> can be included directly.h"
+#endif
+
+#ifndef __ST_BUTTON_H__
+#define __ST_BUTTON_H__
+
+G_BEGIN_DECLS
+
+#include <st/st-bin.h>
+
+#define ST_TYPE_BUTTON (st_button_get_type ())
+G_DECLARE_DERIVABLE_TYPE (StButton, st_button, ST, BUTTON, StBin)
+
+struct _StButtonClass
+{
+ StBinClass parent_class;
+
+ /* vfuncs, not signals */
+ void (* transition) (StButton *button);
+
+ /* signals */
+ void (* clicked) (StButton *button, int clicked_button);
+};
+
+StWidget *st_button_new (void);
+StWidget *st_button_new_with_label (const gchar *text);
+const gchar *st_button_get_label (StButton *button);
+void st_button_set_label (StButton *button,
+ const gchar *text);
+const char *st_button_get_icon_name (StButton *button);
+void st_button_set_icon_name (StButton *button,
+ const char *icon_name);
+void st_button_set_toggle_mode (StButton *button,
+ gboolean toggle);
+gboolean st_button_get_toggle_mode (StButton *button);
+void st_button_set_checked (StButton *button,
+ gboolean checked);
+gboolean st_button_get_checked (StButton *button);
+
+void st_button_fake_release (StButton *button);
+
+/**
+ * StButtonMask:
+ * @ST_BUTTON_ONE: button 1 (left)
+ * @ST_BUTTON_TWO: button 2 (middle)
+ * @ST_BUTTON_THREE: button 3 (right)
+ *
+ * A mask representing which mouse buttons an #StButton responds to.
+ */
+typedef enum {
+ ST_BUTTON_ONE = (1 << 0),
+ ST_BUTTON_TWO = (1 << 1),
+ ST_BUTTON_THREE = (1 << 2),
+} StButtonMask;
+
+#define ST_BUTTON_MASK_FROM_BUTTON(button) (1 << ((button) - 1))
+
+void st_button_set_button_mask (StButton *button,
+ StButtonMask mask);
+StButtonMask st_button_get_button_mask (StButton *button);
+
+G_END_DECLS
+
+#endif /* __ST_BUTTON_H__ */
diff --git a/src/st/st-clipboard.c b/src/st/st-clipboard.c
new file mode 100644
index 0000000..4b730c9
--- /dev/null
+++ b/src/st/st-clipboard.c
@@ -0,0 +1,348 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * st-clipboard.c: clipboard object
+ *
+ * Copyright 2009 Intel Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU Lesser General Public License,
+ * version 2.1, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * SECTION:st-clipboard
+ * @short_description: a simple representation of the clipboard
+ *
+ * #StCliboard is a very simple object representation of the clipboard
+ * available to applications. Text is always assumed to be UTF-8 and non-text
+ * items are not handled.
+ */
+
+#include "config.h"
+
+#include "st-clipboard.h"
+
+#include <meta/display.h>
+#include <meta/meta-selection-source-memory.h>
+#include <meta/meta-selection.h>
+
+G_DEFINE_TYPE (StClipboard, st_clipboard, G_TYPE_OBJECT)
+
+typedef struct _TransferData TransferData;
+struct _TransferData
+{
+ StClipboard *clipboard;
+ GCallback callback;
+ gpointer user_data;
+ GOutputStream *stream;
+};
+
+const char *supported_mimetypes[] = {
+ "text/plain;charset=utf-8",
+ "UTF8_STRING",
+ "text/plain",
+ "STRING",
+};
+
+static MetaSelection *meta_selection = NULL;
+
+static void
+st_clipboard_class_init (StClipboardClass *klass)
+{
+}
+
+static void
+st_clipboard_init (StClipboard *self)
+{
+}
+
+/**
+ * st_clipboard_get_default:
+ *
+ * Get the global #StClipboard object that represents the clipboard.
+ *
+ * Returns: (transfer none): a #StClipboard owned by St and must not be
+ * unrefferenced or freed.
+ */
+StClipboard*
+st_clipboard_get_default (void)
+{
+ static StClipboard *default_clipboard = NULL;
+
+ if (!default_clipboard)
+ {
+ default_clipboard = g_object_new (ST_TYPE_CLIPBOARD, NULL);
+ }
+
+ return default_clipboard;
+}
+
+static gboolean
+convert_type (StClipboardType type,
+ MetaSelectionType *type_out)
+{
+ if (type == ST_CLIPBOARD_TYPE_PRIMARY)
+ *type_out = META_SELECTION_PRIMARY;
+ else if (type == ST_CLIPBOARD_TYPE_CLIPBOARD)
+ *type_out = META_SELECTION_CLIPBOARD;
+ else
+ return FALSE;
+
+ return TRUE;
+}
+
+static const char *
+pick_mimetype (MetaSelection *meta_selection,
+ MetaSelectionType selection_type)
+{
+ const char *selected_mimetype = NULL;
+ GList *mimetypes;
+ int i;
+
+ mimetypes = meta_selection_get_mimetypes (meta_selection, selection_type);
+
+ for (i = 0; i < G_N_ELEMENTS (supported_mimetypes); i++)
+ {
+ if (g_list_find_custom (mimetypes, supported_mimetypes[i],
+ (GCompareFunc) g_strcmp0))
+ {
+ selected_mimetype = supported_mimetypes[i];
+ break;
+ }
+ }
+
+ g_list_free_full (mimetypes, g_free);
+ return selected_mimetype;
+}
+
+static void
+transfer_cb (MetaSelection *selection,
+ GAsyncResult *res,
+ TransferData *data)
+{
+ gchar *text = NULL;
+
+ if (meta_selection_transfer_finish (selection, res, NULL))
+ {
+ gsize data_size;
+
+ data_size =
+ g_memory_output_stream_get_data_size (G_MEMORY_OUTPUT_STREAM (data->stream));
+ text = g_new0 (char, data_size + 1);
+ memcpy (text, g_memory_output_stream_get_data (G_MEMORY_OUTPUT_STREAM (data->stream)), data_size);
+ }
+
+ ((StClipboardCallbackFunc) data->callback) (data->clipboard, text,
+ data->user_data);
+ g_object_unref (data->stream);
+ g_free (data);
+ g_free (text);
+}
+
+static void
+transfer_bytes_cb (MetaSelection *selection,
+ GAsyncResult *res,
+ TransferData *data)
+{
+ GBytes *bytes = NULL;
+
+ if (meta_selection_transfer_finish (selection, res, NULL))
+ bytes = g_memory_output_stream_steal_as_bytes (G_MEMORY_OUTPUT_STREAM (data->stream));
+
+ ((StClipboardContentCallbackFunc) data->callback) (data->clipboard, bytes,
+ data->user_data);
+ g_object_unref (data->stream);
+ g_clear_pointer (&bytes, g_bytes_unref);
+}
+
+/**
+ * st_clipboard_get_mimetypes:
+ * @clipboard: a #StClipboard
+ *
+ * Gets a list of the mimetypes supported by the default #StClipboard.
+ *
+ * Returns: (element-type utf8) (transfer full): the supported mimetypes
+ */
+GList *
+st_clipboard_get_mimetypes (StClipboard *clipboard,
+ StClipboardType type)
+{
+ MetaSelectionType selection_type;
+
+ g_return_val_if_fail (ST_IS_CLIPBOARD (clipboard), NULL);
+ g_return_val_if_fail (meta_selection != NULL, NULL);
+
+ if (!convert_type (type, &selection_type))
+ return NULL;
+
+ return meta_selection_get_mimetypes (meta_selection, selection_type);
+}
+
+/**
+ * st_clipboard_get_text:
+ * @clipboard: A #StCliboard
+ * @type: The type of clipboard data you want
+ * @callback: (scope async): function to be called when the text is retrieved
+ * @user_data: data to be passed to the callback
+ *
+ * Request the data from the clipboard in text form. @callback is executed
+ * when the data is retrieved.
+ */
+void
+st_clipboard_get_text (StClipboard *clipboard,
+ StClipboardType type,
+ StClipboardCallbackFunc callback,
+ gpointer user_data)
+{
+ MetaSelectionType selection_type;
+ TransferData *data;
+ const char *mimetype = NULL;
+
+ g_return_if_fail (ST_IS_CLIPBOARD (clipboard));
+ g_return_if_fail (meta_selection != NULL);
+ g_return_if_fail (callback != NULL);
+
+ if (convert_type (type, &selection_type))
+ mimetype = pick_mimetype (meta_selection, selection_type);
+
+ if (!mimetype)
+ {
+ callback (clipboard, NULL, user_data);
+ return;
+ }
+
+ data = g_new0 (TransferData, 1);
+ data->clipboard = clipboard;
+ data->callback = G_CALLBACK (callback);
+ data->user_data = user_data;
+ data->stream = g_memory_output_stream_new_resizable ();
+
+ meta_selection_transfer_async (meta_selection,
+ selection_type,
+ mimetype, -1,
+ data->stream, NULL,
+ (GAsyncReadyCallback) transfer_cb,
+ data);
+}
+
+/**
+ * st_clipboard_get_content:
+ * @clipboard: A #StCliboard
+ * @type: The type of clipboard data you want
+ * @mimetype: The mimetype to get content for
+ * @callback: (scope async): function to be called when the type is retrieved
+ * @user_data: data to be passed to the callback
+ *
+ * Request the data from the clipboard in #GBytes form. @callback is executed
+ * when the data is retrieved.
+ */
+void
+st_clipboard_get_content (StClipboard *clipboard,
+ StClipboardType type,
+ const gchar *mimetype,
+ StClipboardContentCallbackFunc callback,
+ gpointer user_data)
+{
+ MetaSelectionType selection_type;
+ TransferData *data;
+
+ g_return_if_fail (ST_IS_CLIPBOARD (clipboard));
+ g_return_if_fail (meta_selection != NULL);
+ g_return_if_fail (callback != NULL);
+
+ if (!mimetype || !convert_type (type, &selection_type))
+ {
+ callback (clipboard, NULL, user_data);
+ return;
+ }
+
+ data = g_new0 (TransferData, 1);
+ data->clipboard = clipboard;
+ data->callback = G_CALLBACK (callback);
+ data->user_data = user_data;
+ data->stream = g_memory_output_stream_new_resizable ();
+
+ meta_selection_transfer_async (meta_selection,
+ selection_type,
+ mimetype, -1,
+ data->stream, NULL,
+ (GAsyncReadyCallback) transfer_bytes_cb,
+ data);
+}
+
+/**
+ * st_clipboard_set_content:
+ * @clipboard: A #StClipboard
+ * @type: The type of clipboard that you want to set
+ * @mimetype: content mimetype
+ * @bytes: content data
+ *
+ * Sets the clipboard content to @bytes.
+ *
+ * @mimetype is a semi-colon separated list of mime-type strings.
+ **/
+void
+st_clipboard_set_content (StClipboard *clipboard,
+ StClipboardType type,
+ const gchar *mimetype,
+ GBytes *bytes)
+{
+ MetaSelectionType selection_type;
+ MetaSelectionSource *source;
+
+ g_return_if_fail (ST_IS_CLIPBOARD (clipboard));
+ g_return_if_fail (meta_selection != NULL);
+ g_return_if_fail (bytes != NULL);
+
+ if (!convert_type (type, &selection_type))
+ return;
+
+ source = meta_selection_source_memory_new (mimetype, bytes);
+ meta_selection_set_owner (meta_selection, selection_type, source);
+ g_object_unref (source);
+}
+
+/**
+ * st_clipboard_set_text:
+ * @clipboard: A #StClipboard
+ * @type: The type of clipboard that you want to set
+ * @text: text to copy to the clipboard
+ *
+ * Sets text as the current contents of the clipboard.
+ */
+void
+st_clipboard_set_text (StClipboard *clipboard,
+ StClipboardType type,
+ const gchar *text)
+{
+ GBytes *bytes;
+
+ g_return_if_fail (ST_IS_CLIPBOARD (clipboard));
+ g_return_if_fail (meta_selection != NULL);
+ g_return_if_fail (text != NULL);
+
+ bytes = g_bytes_new_take (g_strdup (text), strlen (text));
+ st_clipboard_set_content (clipboard, type, "text/plain;charset=utf-8", bytes);
+ g_bytes_unref (bytes);
+}
+
+/**
+ * st_clipboard_set_selection: (skip)
+ *
+ * Sets the #MetaSelection of the default #StClipboard.
+ *
+ * This function is called during the initialization of GNOME Shell.
+ */
+void
+st_clipboard_set_selection (MetaSelection *selection)
+{
+ meta_selection = selection;
+}
diff --git a/src/st/st-clipboard.h b/src/st/st-clipboard.h
new file mode 100644
index 0000000..022b832
--- /dev/null
+++ b/src/st/st-clipboard.h
@@ -0,0 +1,105 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * st-clipboard.h: clipboard object
+ *
+ * Copyright 2009 Intel Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU Lesser General Public License,
+ * version 2.1, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#if !defined(ST_H_INSIDE) && !defined(ST_COMPILATION)
+#error "Only <st/st.h> can be included directly.h"
+#endif
+
+#ifndef _ST_CLIPBOARD_H
+#define _ST_CLIPBOARD_H
+
+#include <glib-object.h>
+#include <meta/meta-selection.h>
+
+G_BEGIN_DECLS
+
+#define ST_TYPE_CLIPBOARD st_clipboard_get_type()
+G_DECLARE_FINAL_TYPE (StClipboard, st_clipboard, ST, CLIPBOARD, GObject)
+
+typedef struct _StClipboard StClipboard;
+
+/**
+ * StClipboard:
+ *
+ * The contents of this structure is private and should only be accessed using
+ * the provided API.
+ */
+struct _StClipboard
+{
+ /*< private >*/
+ GObject parent;
+};
+
+typedef enum {
+ ST_CLIPBOARD_TYPE_PRIMARY,
+ ST_CLIPBOARD_TYPE_CLIPBOARD
+} StClipboardType;
+
+/**
+ * StClipboardCallbackFunc:
+ * @clipboard: A #StClipboard
+ * @text: text from the clipboard
+ * @user_data: user data
+ *
+ * Callback function called when text is retrieved from the clipboard.
+ */
+typedef void (*StClipboardCallbackFunc) (StClipboard *clipboard,
+ const gchar *text,
+ gpointer user_data);
+
+/**
+ * StClipboardContentCallbackFunc:
+ * @clipboard: A #StClipboard
+ * @bytes: content from the clipboard
+ * @user_data: user data
+ *
+ * Callback function called when content is retrieved from the clipboard.
+ */
+typedef void (*StClipboardContentCallbackFunc) (StClipboard *clipboard,
+ GBytes *bytes,
+ gpointer user_data);
+
+StClipboard* st_clipboard_get_default (void);
+
+GList * st_clipboard_get_mimetypes (StClipboard *clipboard,
+ StClipboardType type);
+
+void st_clipboard_get_text (StClipboard *clipboard,
+ StClipboardType type,
+ StClipboardCallbackFunc callback,
+ gpointer user_data);
+void st_clipboard_set_text (StClipboard *clipboard,
+ StClipboardType type,
+ const gchar *text);
+
+void st_clipboard_set_content (StClipboard *clipboard,
+ StClipboardType type,
+ const gchar *mimetype,
+ GBytes *bytes);
+void st_clipboard_get_content (StClipboard *clipboard,
+ StClipboardType type,
+ const gchar *mimetype,
+ StClipboardContentCallbackFunc callback,
+ gpointer user_data);
+
+void st_clipboard_set_selection (MetaSelection *selection);
+
+G_END_DECLS
+
+#endif /* _ST_CLIPBOARD_H */
diff --git a/src/st/st-drawing-area.c b/src/st/st-drawing-area.c
new file mode 100644
index 0000000..38737fd
--- /dev/null
+++ b/src/st/st-drawing-area.c
@@ -0,0 +1,242 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * st-drawing-area.c: A dynamically-sized Cairo drawing area
+ *
+ * Copyright 2009, 2010 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation, either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * SECTION:st-drawing-area
+ * @short_description: A dynamically-sized Cairo drawing area
+ *
+ * #StDrawingArea allows drawing via Cairo; the primary difference is that
+ * it is dynamically sized. To use, connect to the #StDrawingArea::repaint
+ * signal, and inside the signal handler, call
+ * st_drawing_area_get_context() to get the Cairo context to draw to. The
+ * #StDrawingArea::repaint signal will be emitted by default when the area is
+ * resized or the CSS style changes; you can use the
+ * st_drawing_area_queue_repaint() as well.
+ */
+
+#include "st-drawing-area.h"
+
+#include <cairo.h>
+#include <math.h>
+
+typedef struct _StDrawingAreaPrivate StDrawingAreaPrivate;
+struct _StDrawingAreaPrivate {
+ cairo_t *context;
+ guint in_repaint : 1;
+};
+
+G_DEFINE_TYPE_WITH_PRIVATE (StDrawingArea, st_drawing_area, ST_TYPE_WIDGET);
+
+/* Signals */
+enum
+{
+ REPAINT,
+ LAST_SIGNAL
+};
+
+static guint st_drawing_area_signals [LAST_SIGNAL] = { 0 };
+
+static gboolean
+draw_content (ClutterCanvas *canvas,
+ cairo_t *cr,
+ int width,
+ int height,
+ gpointer user_data)
+{
+ StDrawingArea *area = ST_DRAWING_AREA (user_data);
+ StDrawingAreaPrivate *priv = st_drawing_area_get_instance_private (area);
+
+ priv->context = cr;
+ priv->in_repaint = TRUE;
+
+ clutter_cairo_clear (cr);
+ g_signal_emit (area, st_drawing_area_signals[REPAINT], 0);
+
+ priv->context = NULL;
+ priv->in_repaint = FALSE;
+
+ return TRUE;
+}
+
+static void
+st_drawing_area_allocate (ClutterActor *self,
+ const ClutterActorBox *box)
+{
+ StThemeNode *theme_node = st_widget_get_theme_node (ST_WIDGET (self));
+ ClutterContent *content = clutter_actor_get_content (self);
+ ClutterActorBox content_box;
+ int width, height;
+ float resource_scale;
+
+ resource_scale = clutter_actor_get_resource_scale (self);
+
+ clutter_actor_set_allocation (self, box);
+ st_theme_node_get_content_box (theme_node, box, &content_box);
+
+ width = (int)(0.5 + content_box.x2 - content_box.x1);
+ height = (int)(0.5 + content_box.y2 - content_box.y1);
+
+ clutter_canvas_set_scale_factor (CLUTTER_CANVAS (content), resource_scale);
+ clutter_canvas_set_size (CLUTTER_CANVAS (content), width, height);
+}
+
+static void
+st_drawing_area_style_changed (StWidget *self)
+{
+ (ST_WIDGET_CLASS (st_drawing_area_parent_class))->style_changed (self);
+
+ st_drawing_area_queue_repaint (ST_DRAWING_AREA (self));
+}
+
+static void
+st_drawing_area_resource_scale_changed (ClutterActor *self)
+{
+ float resource_scale;
+ ClutterContent *content = clutter_actor_get_content (self);
+
+ resource_scale = clutter_actor_get_resource_scale (self);
+ clutter_canvas_set_scale_factor (CLUTTER_CANVAS (content), resource_scale);
+
+ if (CLUTTER_ACTOR_CLASS (st_drawing_area_parent_class)->resource_scale_changed)
+ CLUTTER_ACTOR_CLASS (st_drawing_area_parent_class)->resource_scale_changed (self);
+}
+
+static void
+st_drawing_area_class_init (StDrawingAreaClass *klass)
+{
+ ClutterActorClass *actor_class = CLUTTER_ACTOR_CLASS (klass);
+ StWidgetClass *widget_class = ST_WIDGET_CLASS (klass);
+
+ actor_class->allocate = st_drawing_area_allocate;
+ widget_class->style_changed = st_drawing_area_style_changed;
+ actor_class->resource_scale_changed = st_drawing_area_resource_scale_changed;
+
+ st_drawing_area_signals[REPAINT] =
+ g_signal_new ("repaint",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (StDrawingAreaClass, repaint),
+ NULL, NULL, NULL,
+ G_TYPE_NONE, 0);
+}
+
+static void
+st_drawing_area_init (StDrawingArea *area)
+{
+ ClutterContent *content = clutter_canvas_new ();
+ g_signal_connect (content, "draw", G_CALLBACK (draw_content), area);
+ clutter_actor_set_content (CLUTTER_ACTOR (area), content);
+ g_object_unref (content);
+}
+
+/**
+ * st_drawing_area_queue_repaint:
+ * @area: the #StDrawingArea
+ *
+ * Will cause the actor to emit a #StDrawingArea::repaint signal before it is
+ * next drawn to the scene. Useful if some parameters for the area being
+ * drawn other than the size or style have changed. Note that
+ * clutter_actor_queue_redraw() will simply result in the same
+ * contents being drawn to the scene again.
+ */
+void
+st_drawing_area_queue_repaint (StDrawingArea *area)
+{
+ g_return_if_fail (ST_IS_DRAWING_AREA (area));
+
+ clutter_content_invalidate (clutter_actor_get_content (CLUTTER_ACTOR (area)));
+}
+
+/**
+ * st_drawing_area_get_context:
+ * @area: the #StDrawingArea
+ *
+ * Gets the Cairo context to paint to. This function must only be called
+ * from a signal handler or virtual function for the #StDrawingArea::repaint
+ * signal.
+ *
+ * JavaScript code must call the special dispose function before returning from
+ * the signal handler or virtual function to avoid leaking memory:
+ *
+ * |[<!-- language="JavaScript" -->
+ * function onRepaint(area) {
+ * let cr = area.get_context();
+ *
+ * // Draw to the context
+ *
+ * cr.$dispose();
+ * }
+ *
+ * let area = new St.DrawingArea();
+ * area.connect('repaint', onRepaint);
+ * ]|
+ *
+ * Returns: (transfer none): the Cairo context for the paint operation
+ */
+cairo_t *
+st_drawing_area_get_context (StDrawingArea *area)
+{
+ StDrawingAreaPrivate *priv;
+
+ g_return_val_if_fail (ST_IS_DRAWING_AREA (area), NULL);
+
+ priv = st_drawing_area_get_instance_private (area);
+ g_return_val_if_fail (priv->in_repaint, NULL);
+
+ return priv->context;
+}
+
+/**
+ * st_drawing_area_get_surface_size:
+ * @area: the #StDrawingArea
+ * @width: (out) (optional): location to store the width of the painted area
+ * @height: (out) (optional): location to store the height of the painted area
+ *
+ * Gets the size of the cairo surface being painted to, which is equal
+ * to the size of the content area of the widget. This function must
+ * only be called from a signal handler for the #StDrawingArea::repaint signal.
+ */
+void
+st_drawing_area_get_surface_size (StDrawingArea *area,
+ guint *width,
+ guint *height)
+{
+ StDrawingAreaPrivate *priv;
+ ClutterContent *content;
+ float w, h, resource_scale;
+
+ g_return_if_fail (ST_IS_DRAWING_AREA (area));
+
+ priv = st_drawing_area_get_instance_private (area);
+ g_return_if_fail (priv->in_repaint);
+
+ content = clutter_actor_get_content (CLUTTER_ACTOR (area));
+ clutter_content_get_preferred_size (content, &w, &h);
+
+ resource_scale = clutter_actor_get_resource_scale (CLUTTER_ACTOR (area));
+
+ w /= resource_scale;
+ h /= resource_scale;
+
+ if (width)
+ *width = ceilf (w);
+ if (height)
+ *height = ceilf (h);
+}
diff --git a/src/st/st-drawing-area.h b/src/st/st-drawing-area.h
new file mode 100644
index 0000000..e09f9c5
--- /dev/null
+++ b/src/st/st-drawing-area.h
@@ -0,0 +1,44 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * st-drawing-area.h: A dynamically-sized Cairo drawing area
+ *
+ * Copyright 2009, 2010 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation, either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __ST_DRAWING_AREA_H__
+#define __ST_DRAWING_AREA_H__
+
+#include "st-widget.h"
+#include <cairo.h>
+
+#define ST_TYPE_DRAWING_AREA (st_drawing_area_get_type ())
+G_DECLARE_DERIVABLE_TYPE (StDrawingArea, st_drawing_area,
+ ST, DRAWING_AREA, StWidget)
+
+struct _StDrawingAreaClass
+{
+ StWidgetClass parent_class;
+
+ void (*repaint) (StDrawingArea *area);
+};
+
+void st_drawing_area_queue_repaint (StDrawingArea *area);
+cairo_t *st_drawing_area_get_context (StDrawingArea *area);
+void st_drawing_area_get_surface_size (StDrawingArea *area,
+ guint *width,
+ guint *height);
+
+#endif /* __ST_DRAWING_AREA_H__ */
diff --git a/src/st/st-entry.c b/src/st/st-entry.c
new file mode 100644
index 0000000..64f85fd
--- /dev/null
+++ b/src/st/st-entry.c
@@ -0,0 +1,1626 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * st-entry.c: Plain entry actor
+ *
+ * Copyright 2008, 2009 Intel Corporation
+ * Copyright 2009, 2010 Red Hat, Inc.
+ * Copyright 2010 Florian Müllner
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU Lesser General Public License,
+ * version 2.1, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * SECTION:st-entry
+ * @short_description: Widget for displaying text
+ *
+ * #StEntry is a simple widget for displaying text. It derives from
+ * #StWidget to add extra style and placement functionality over
+ * #ClutterText. The internal #ClutterText is publicly accessibly to allow
+ * applications to set further properties.
+ *
+ * #StEntry supports the following pseudo style states:
+ *
+ * - `focus`: the widget has focus
+ * - `indeterminate`: the widget is showing the hint text or actor
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <math.h>
+
+#include <stdlib.h>
+#include <string.h>
+
+#include <glib.h>
+
+#include <clutter/clutter.h>
+
+#include "st-entry.h"
+
+#include "st-icon.h"
+#include "st-label.h"
+#include "st-settings.h"
+#include "st-widget.h"
+#include "st-texture-cache.h"
+#include "st-clipboard.h"
+#include "st-private.h"
+
+#include "st-widget-accessible.h"
+
+
+/* properties */
+enum
+{
+ PROP_0,
+
+ PROP_CLUTTER_TEXT,
+ PROP_PRIMARY_ICON,
+ PROP_SECONDARY_ICON,
+ PROP_HINT_TEXT,
+ PROP_HINT_ACTOR,
+ PROP_TEXT,
+ PROP_INPUT_PURPOSE,
+ PROP_INPUT_HINTS,
+
+ N_PROPS
+};
+
+static GParamSpec *props[N_PROPS] = { NULL, };
+
+/* signals */
+enum
+{
+ PRIMARY_ICON_CLICKED,
+ SECONDARY_ICON_CLICKED,
+
+ LAST_SIGNAL
+};
+
+#define ST_ENTRY_PRIV(x) st_entry_get_instance_private ((StEntry *) x)
+
+
+typedef struct _StEntryPrivate StEntryPrivate;
+struct _StEntryPrivate
+{
+ ClutterActor *entry;
+
+ ClutterActor *primary_icon;
+ ClutterActor *secondary_icon;
+
+ ClutterActor *hint_actor;
+
+ gfloat spacing;
+
+ gboolean has_ibeam;
+
+ StShadow *shadow_spec;
+
+ CoglPipeline *text_shadow_material;
+ gfloat shadow_width;
+ gfloat shadow_height;
+};
+
+static guint entry_signals[LAST_SIGNAL] = { 0, };
+
+G_DEFINE_TYPE_WITH_PRIVATE (StEntry, st_entry, ST_TYPE_WIDGET);
+
+static GType st_entry_accessible_get_type (void) G_GNUC_CONST;
+
+static void
+st_entry_set_property (GObject *gobject,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ StEntry *entry = ST_ENTRY (gobject);
+
+ switch (prop_id)
+ {
+ case PROP_PRIMARY_ICON:
+ st_entry_set_primary_icon (entry, g_value_get_object (value));
+ break;
+
+ case PROP_SECONDARY_ICON:
+ st_entry_set_secondary_icon (entry, g_value_get_object (value));
+ break;
+
+ case PROP_HINT_TEXT:
+ st_entry_set_hint_text (entry, g_value_get_string (value));
+ break;
+
+ case PROP_HINT_ACTOR:
+ st_entry_set_hint_actor (entry, g_value_get_object (value));
+ break;
+
+ case PROP_TEXT:
+ st_entry_set_text (entry, g_value_get_string (value));
+ break;
+
+ case PROP_INPUT_PURPOSE:
+ st_entry_set_input_purpose (entry, g_value_get_enum (value));
+ break;
+
+ case PROP_INPUT_HINTS:
+ st_entry_set_input_hints (entry, g_value_get_flags (value));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+st_entry_get_property (GObject *gobject,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ StEntryPrivate *priv = ST_ENTRY_PRIV (gobject);
+
+ switch (prop_id)
+ {
+ case PROP_CLUTTER_TEXT:
+ g_value_set_object (value, priv->entry);
+ break;
+
+ case PROP_PRIMARY_ICON:
+ g_value_set_object (value, priv->primary_icon);
+ break;
+
+ case PROP_SECONDARY_ICON:
+ g_value_set_object (value, priv->secondary_icon);
+ break;
+
+ case PROP_HINT_TEXT:
+ g_value_set_string (value, st_entry_get_hint_text (ST_ENTRY (gobject)));
+ break;
+
+ case PROP_HINT_ACTOR:
+ g_value_set_object (value, priv->hint_actor);
+ break;
+
+ case PROP_TEXT:
+ g_value_set_string (value, clutter_text_get_text (CLUTTER_TEXT (priv->entry)));
+ break;
+
+ case PROP_INPUT_PURPOSE:
+ g_value_set_enum (value, clutter_text_get_input_purpose (CLUTTER_TEXT (priv->entry)));
+ break;
+
+ case PROP_INPUT_HINTS:
+ g_value_set_flags (value, clutter_text_get_input_hints (CLUTTER_TEXT (priv->entry)));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+st_entry_dispose (GObject *object)
+{
+ StEntry *entry = ST_ENTRY (object);
+ StEntryPrivate *priv = ST_ENTRY_PRIV (entry);
+
+ cogl_clear_object (&priv->text_shadow_material);
+
+ G_OBJECT_CLASS (st_entry_parent_class)->dispose (object);
+}
+
+static void
+st_entry_update_hint_visibility (StEntry *self)
+{
+ StEntryPrivate *priv = ST_ENTRY_PRIV (self);
+ gboolean hint_visible =
+ priv->hint_actor != NULL &&
+ !clutter_text_has_preedit (CLUTTER_TEXT (priv->entry)) &&
+ strcmp (clutter_text_get_text (CLUTTER_TEXT (priv->entry)), "") == 0;
+
+ if (priv->hint_actor)
+ g_object_set (priv->hint_actor, "visible", hint_visible, NULL);
+
+ if (hint_visible)
+ st_widget_add_style_pseudo_class (ST_WIDGET (self), "indeterminate");
+ else
+ st_widget_remove_style_pseudo_class (ST_WIDGET (self), "indeterminate");
+}
+
+static void
+st_entry_style_changed (StWidget *self)
+{
+ StEntryPrivate *priv = ST_ENTRY_PRIV (self);
+ StThemeNode *theme_node;
+ StShadow *shadow_spec;
+ ClutterColor color;
+ gdouble size;
+
+ theme_node = st_widget_get_theme_node (self);
+
+ shadow_spec = st_theme_node_get_text_shadow (theme_node);
+ if (!priv->shadow_spec || !shadow_spec ||
+ !st_shadow_equal (shadow_spec, priv->shadow_spec))
+ {
+ g_clear_pointer (&priv->text_shadow_material, cogl_object_unref);
+
+ g_clear_pointer (&priv->shadow_spec, st_shadow_unref);
+ if (shadow_spec)
+ priv->shadow_spec = st_shadow_ref (shadow_spec);
+ }
+
+ _st_set_text_from_style (CLUTTER_TEXT (priv->entry), theme_node);
+
+ if (st_theme_node_lookup_length (theme_node, "caret-size", TRUE, &size))
+ clutter_text_set_cursor_size (CLUTTER_TEXT (priv->entry), (int)(.5 + size));
+
+ if (st_theme_node_lookup_color (theme_node, "caret-color", TRUE, &color))
+ clutter_text_set_cursor_color (CLUTTER_TEXT (priv->entry), &color);
+
+ if (st_theme_node_lookup_color (theme_node, "selection-background-color", TRUE, &color))
+ clutter_text_set_selection_color (CLUTTER_TEXT (priv->entry), &color);
+
+ if (st_theme_node_lookup_color (theme_node, "selected-color", TRUE, &color))
+ clutter_text_set_selected_text_color (CLUTTER_TEXT (priv->entry), &color);
+
+ ST_WIDGET_CLASS (st_entry_parent_class)->style_changed (self);
+}
+
+static gboolean
+st_entry_navigate_focus (StWidget *widget,
+ ClutterActor *from,
+ StDirectionType direction)
+{
+ StEntryPrivate *priv = ST_ENTRY_PRIV (widget);
+
+ /* This is basically the same as st_widget_real_navigate_focus(),
+ * except that widget is behaving as a proxy for priv->entry (which
+ * isn't an StWidget and so has no can-focus flag of its own).
+ */
+
+ if (from == priv->entry)
+ return FALSE;
+ else if (st_widget_get_can_focus (widget) &&
+ clutter_actor_is_mapped (priv->entry))
+ {
+ clutter_actor_grab_key_focus (priv->entry);
+ return TRUE;
+ }
+ else
+ return FALSE;
+}
+
+static void
+st_entry_get_preferred_width (ClutterActor *actor,
+ gfloat for_height,
+ gfloat *min_width_p,
+ gfloat *natural_width_p)
+{
+ StEntryPrivate *priv = ST_ENTRY_PRIV (actor);
+ StThemeNode *theme_node = st_widget_get_theme_node (ST_WIDGET (actor));
+ gfloat hint_w, hint_min_w, icon_w;
+
+ st_theme_node_adjust_for_height (theme_node, &for_height);
+
+ clutter_actor_get_preferred_width (priv->entry, for_height,
+ min_width_p,
+ natural_width_p);
+
+ if (priv->hint_actor)
+ {
+ clutter_actor_get_preferred_width (priv->hint_actor, -1,
+ &hint_min_w, &hint_w);
+
+ if (min_width_p && hint_min_w > *min_width_p)
+ *min_width_p = hint_min_w;
+
+ if (natural_width_p && hint_w > *natural_width_p)
+ *natural_width_p = hint_w;
+ }
+
+ if (priv->primary_icon)
+ {
+ clutter_actor_get_preferred_width (priv->primary_icon, -1, NULL, &icon_w);
+
+ if (min_width_p)
+ *min_width_p += icon_w + priv->spacing;
+
+ if (natural_width_p)
+ *natural_width_p += icon_w + priv->spacing;
+ }
+
+ if (priv->secondary_icon)
+ {
+ clutter_actor_get_preferred_width (priv->secondary_icon,
+ -1, NULL, &icon_w);
+
+ if (min_width_p)
+ *min_width_p += icon_w + priv->spacing;
+
+ if (natural_width_p)
+ *natural_width_p += icon_w + priv->spacing;
+ }
+
+ st_theme_node_adjust_preferred_width (theme_node, min_width_p, natural_width_p);
+}
+
+static void
+st_entry_get_preferred_height (ClutterActor *actor,
+ gfloat for_width,
+ gfloat *min_height_p,
+ gfloat *natural_height_p)
+{
+ StEntryPrivate *priv = ST_ENTRY_PRIV (actor);
+ StThemeNode *theme_node = st_widget_get_theme_node (ST_WIDGET (actor));
+ gfloat hint_h, icon_h;
+
+ st_theme_node_adjust_for_width (theme_node, &for_width);
+
+ clutter_actor_get_preferred_height (priv->entry, for_width,
+ min_height_p,
+ natural_height_p);
+
+ if (priv->hint_actor)
+ {
+ clutter_actor_get_preferred_height (priv->hint_actor, -1, NULL, &hint_h);
+
+ if (min_height_p && hint_h > *min_height_p)
+ *min_height_p = hint_h;
+
+ if (natural_height_p && hint_h > *natural_height_p)
+ *natural_height_p = hint_h;
+ }
+
+ if (priv->primary_icon)
+ {
+ clutter_actor_get_preferred_height (priv->primary_icon,
+ -1, NULL, &icon_h);
+
+ if (min_height_p && icon_h > *min_height_p)
+ *min_height_p = icon_h;
+
+ if (natural_height_p && icon_h > *natural_height_p)
+ *natural_height_p = icon_h;
+ }
+
+ if (priv->secondary_icon)
+ {
+ clutter_actor_get_preferred_height (priv->secondary_icon,
+ -1, NULL, &icon_h);
+
+ if (min_height_p && icon_h > *min_height_p)
+ *min_height_p = icon_h;
+
+ if (natural_height_p && icon_h > *natural_height_p)
+ *natural_height_p = icon_h;
+ }
+
+ st_theme_node_adjust_preferred_height (theme_node, min_height_p, natural_height_p);
+}
+
+static void
+st_entry_allocate (ClutterActor *actor,
+ const ClutterActorBox *box)
+{
+ StEntryPrivate *priv = ST_ENTRY_PRIV (actor);
+ StThemeNode *theme_node = st_widget_get_theme_node (ST_WIDGET (actor));
+ ClutterActorBox content_box, child_box, icon_box, hint_box;
+ gfloat icon_w, icon_h;
+ gfloat hint_w, hint_min_w, hint_h;
+ gfloat entry_h, min_h, pref_h, avail_h;
+ ClutterActor *left_icon, *right_icon;
+ gboolean is_rtl;
+
+ is_rtl = clutter_actor_get_text_direction (actor) == CLUTTER_TEXT_DIRECTION_RTL;
+
+ if (is_rtl)
+ {
+ right_icon = priv->primary_icon;
+ left_icon = priv->secondary_icon;
+ }
+ else
+ {
+ left_icon = priv->primary_icon;
+ right_icon = priv->secondary_icon;
+ }
+
+ clutter_actor_set_allocation (actor, box);
+
+ st_theme_node_get_content_box (theme_node, box, &content_box);
+
+ avail_h = content_box.y2 - content_box.y1;
+
+ child_box.x1 = content_box.x1;
+ child_box.x2 = content_box.x2;
+
+ if (left_icon)
+ {
+ clutter_actor_get_preferred_width (left_icon, -1, NULL, &icon_w);
+ clutter_actor_get_preferred_height (left_icon, -1, NULL, &icon_h);
+
+ icon_box.x1 = content_box.x1;
+ icon_box.x2 = icon_box.x1 + icon_w;
+
+ icon_box.y1 = (int) (content_box.y1 + avail_h / 2 - icon_h / 2);
+ icon_box.y2 = icon_box.y1 + icon_h;
+
+ clutter_actor_allocate (left_icon, &icon_box);
+
+ /* reduce the size for the entry */
+ child_box.x1 = MIN (child_box.x2, child_box.x1 + icon_w + priv->spacing);
+ }
+
+ if (right_icon)
+ {
+ clutter_actor_get_preferred_width (right_icon, -1, NULL, &icon_w);
+ clutter_actor_get_preferred_height (right_icon, -1, NULL, &icon_h);
+
+ icon_box.x2 = content_box.x2;
+ icon_box.x1 = icon_box.x2 - icon_w;
+
+ icon_box.y1 = (int) (content_box.y1 + avail_h / 2 - icon_h / 2);
+ icon_box.y2 = icon_box.y1 + icon_h;
+
+ clutter_actor_allocate (right_icon, &icon_box);
+
+ /* reduce the size for the entry */
+ child_box.x2 = MAX (child_box.x1, child_box.x2 - icon_w - priv->spacing);
+ }
+
+ if (priv->hint_actor)
+ {
+ /* now allocate the hint actor */
+ hint_box = child_box;
+
+ clutter_actor_get_preferred_width (priv->hint_actor, -1, &hint_min_w, &hint_w);
+ clutter_actor_get_preferred_height (priv->hint_actor, -1, NULL, &hint_h);
+
+ hint_w = CLAMP (hint_w, hint_min_w, child_box.x2 - child_box.x1);
+
+ if (is_rtl)
+ hint_box.x1 = hint_box.x2 - hint_w;
+ else
+ hint_box.x2 = hint_box.x1 + hint_w;
+
+ hint_box.y1 = ceil (content_box.y1 + avail_h / 2 - hint_h / 2);
+ hint_box.y2 = hint_box.y1 + hint_h;
+
+ clutter_actor_allocate (priv->hint_actor, &hint_box);
+ }
+
+ clutter_actor_get_preferred_height (priv->entry, child_box.x2 - child_box.x1,
+ &min_h, &pref_h);
+
+ entry_h = CLAMP (pref_h, min_h, avail_h);
+
+ child_box.y1 = (int) (content_box.y1 + avail_h / 2 - entry_h / 2);
+ child_box.y2 = child_box.y1 + entry_h;
+
+ clutter_actor_allocate (priv->entry, &child_box);
+}
+
+static void
+clutter_text_reactive_changed_cb (ClutterActor *text,
+ GParamSpec *pspec,
+ gpointer user_data)
+{
+ ClutterActor *stage;
+
+ if (clutter_actor_get_reactive (text))
+ return;
+
+ if (!clutter_actor_has_key_focus (text))
+ return;
+
+ stage = clutter_actor_get_stage (text);
+ if (stage == NULL)
+ return;
+
+ clutter_stage_set_key_focus (CLUTTER_STAGE (stage), NULL);
+}
+
+static void
+clutter_text_focus_in_cb (ClutterText *text,
+ ClutterActor *actor)
+{
+ st_widget_add_style_pseudo_class (ST_WIDGET (actor), "focus");
+ clutter_text_set_cursor_visible (text, TRUE);
+}
+
+static void
+clutter_text_focus_out_cb (ClutterText *text,
+ ClutterActor *actor)
+{
+ st_widget_remove_style_pseudo_class (ST_WIDGET (actor), "focus");
+ clutter_text_set_cursor_visible (text, FALSE);
+}
+
+static void
+clutter_text_cursor_changed (ClutterText *text,
+ StEntry *entry)
+{
+ StEntryPrivate *priv = ST_ENTRY_PRIV (entry);
+
+ st_entry_update_hint_visibility (entry);
+
+ g_clear_pointer (&priv->text_shadow_material, cogl_object_unref);
+}
+
+static void
+clutter_text_changed_cb (GObject *object,
+ GParamSpec *pspec,
+ gpointer user_data)
+{
+ StEntry *entry = ST_ENTRY (user_data);
+ StEntryPrivate *priv = ST_ENTRY_PRIV (entry);
+
+ st_entry_update_hint_visibility (entry);
+
+ /* Since the text changed, force a regen of the shadow texture */
+ cogl_clear_object (&priv->text_shadow_material);
+
+ g_object_notify_by_pspec (G_OBJECT (entry), props[PROP_TEXT]);
+}
+
+static void
+invalidate_shadow_pipeline (GObject *object,
+ GParamSpec *pspec,
+ StEntry *entry)
+{
+ StEntryPrivate *priv = ST_ENTRY_PRIV (entry);
+
+ g_clear_pointer (&priv->text_shadow_material, cogl_object_unref);
+}
+
+static void
+st_entry_clipboard_callback (StClipboard *clipboard,
+ const gchar *text,
+ gpointer data)
+{
+ StEntryPrivate *priv = ST_ENTRY_PRIV (data);
+ ClutterText *ctext = (ClutterText*)priv->entry;
+ gint cursor_pos;
+
+ if (!text)
+ return;
+
+ /* delete the current selection before pasting */
+ clutter_text_delete_selection (ctext);
+
+ /* "paste" the clipboard text into the entry */
+ cursor_pos = clutter_text_get_cursor_position (ctext);
+ clutter_text_insert_text (ctext, text, cursor_pos);
+}
+
+static gboolean
+clutter_text_button_press_event (ClutterActor *actor,
+ ClutterButtonEvent *event,
+ gpointer user_data)
+{
+ StEntryPrivate *priv = ST_ENTRY_PRIV (user_data);
+
+ if (event->button == 2 &&
+ clutter_text_get_editable (CLUTTER_TEXT (priv->entry)))
+ {
+ StSettings *settings;
+ gboolean primary_paste_enabled;
+
+ settings = st_settings_get ();
+ g_object_get (settings, "primary-paste", &primary_paste_enabled, NULL);
+
+ if (primary_paste_enabled)
+ {
+ StClipboard *clipboard;
+
+ clipboard = st_clipboard_get_default ();
+
+ /* By the time the clipboard callback is called,
+ * the rest of the signal handlers will have
+ * run, making the text cursor to be in the correct
+ * place.
+ */
+ st_clipboard_get_text (clipboard,
+ ST_CLIPBOARD_TYPE_PRIMARY,
+ st_entry_clipboard_callback,
+ user_data);
+ }
+ }
+
+ return FALSE;
+}
+
+static gboolean
+st_entry_key_press_event (ClutterActor *actor,
+ ClutterKeyEvent *event)
+{
+ StEntryPrivate *priv = ST_ENTRY_PRIV (actor);
+
+ /* This is expected to handle events that were emitted for the inner
+ ClutterText. They only reach this function if the ClutterText
+ didn't handle them */
+
+ /* paste */
+ if (((event->modifier_state & CLUTTER_CONTROL_MASK)
+ && event->keyval == CLUTTER_KEY_v) ||
+ ((event->modifier_state & CLUTTER_CONTROL_MASK)
+ && event->keyval == CLUTTER_KEY_V) ||
+ ((event->modifier_state & CLUTTER_SHIFT_MASK)
+ && event->keyval == CLUTTER_KEY_Insert))
+ {
+ StClipboard *clipboard;
+
+ clipboard = st_clipboard_get_default ();
+
+ st_clipboard_get_text (clipboard,
+ ST_CLIPBOARD_TYPE_CLIPBOARD,
+ st_entry_clipboard_callback,
+ actor);
+
+ return TRUE;
+ }
+
+ /* copy */
+ if ((event->modifier_state & CLUTTER_CONTROL_MASK)
+ && (event->keyval == CLUTTER_KEY_c || event->keyval == CLUTTER_KEY_C) &&
+ clutter_text_get_password_char ((ClutterText*) priv->entry) == 0)
+ {
+ StClipboard *clipboard;
+ gchar *text;
+
+ clipboard = st_clipboard_get_default ();
+
+ text = clutter_text_get_selection ((ClutterText*) priv->entry);
+
+ if (text && strlen (text))
+ st_clipboard_set_text (clipboard,
+ ST_CLIPBOARD_TYPE_CLIPBOARD,
+ text);
+
+ g_free (text);
+
+ return TRUE;
+ }
+
+
+ /* cut */
+ if ((event->modifier_state & CLUTTER_CONTROL_MASK)
+ && (event->keyval == CLUTTER_KEY_x || event->keyval == CLUTTER_KEY_X) &&
+ clutter_text_get_password_char ((ClutterText*) priv->entry) == 0)
+ {
+ StClipboard *clipboard;
+ gchar *text;
+
+ clipboard = st_clipboard_get_default ();
+
+ text = clutter_text_get_selection ((ClutterText*) priv->entry);
+
+ if (text && strlen (text))
+ {
+ st_clipboard_set_text (clipboard,
+ ST_CLIPBOARD_TYPE_CLIPBOARD,
+ text);
+
+ /* now delete the text */
+ clutter_text_delete_selection ((ClutterText *) priv->entry);
+ }
+
+ g_free (text);
+
+ return TRUE;
+ }
+
+
+ /* delete to beginning of line */
+ if ((event->modifier_state & CLUTTER_CONTROL_MASK) &&
+ (event->keyval == CLUTTER_KEY_u || event->keyval == CLUTTER_KEY_U))
+ {
+ int pos = clutter_text_get_cursor_position ((ClutterText *)priv->entry);
+ clutter_text_delete_text ((ClutterText *)priv->entry, 0, pos);
+
+ return TRUE;
+ }
+
+
+ /* delete to end of line */
+ if ((event->modifier_state & CLUTTER_CONTROL_MASK) &&
+ (event->keyval == CLUTTER_KEY_k || event->keyval == CLUTTER_KEY_K))
+ {
+ ClutterTextBuffer *buffer = clutter_text_get_buffer ((ClutterText *)priv->entry);
+ int pos = clutter_text_get_cursor_position ((ClutterText *)priv->entry);
+ clutter_text_buffer_delete_text (buffer, pos, -1);
+
+ return TRUE;
+ }
+
+ return CLUTTER_ACTOR_CLASS (st_entry_parent_class)->key_press_event (actor, event);
+}
+
+static void
+st_entry_key_focus_in (ClutterActor *actor)
+{
+ StEntryPrivate *priv = ST_ENTRY_PRIV (actor);
+
+ /* We never want key focus. The ClutterText should be given first
+ pass for all key events */
+ clutter_actor_grab_key_focus (priv->entry);
+}
+
+static StEntryCursorFunc cursor_func = NULL;
+static gpointer cursor_func_data = NULL;
+
+/**
+ * st_entry_set_cursor_func: (skip)
+ *
+ * This function is for private use by libgnome-shell.
+ * Do not ever use.
+ */
+void
+st_entry_set_cursor_func (StEntryCursorFunc func,
+ gpointer data)
+{
+ cursor_func = func;
+ cursor_func_data = data;
+}
+
+static void
+st_entry_set_cursor (StEntry *entry,
+ gboolean use_ibeam)
+{
+ if (cursor_func)
+ cursor_func (entry, use_ibeam, cursor_func_data);
+
+ ((StEntryPrivate *)ST_ENTRY_PRIV (entry))->has_ibeam = use_ibeam;
+}
+
+static gboolean
+st_entry_enter_event (ClutterActor *actor,
+ ClutterCrossingEvent *event)
+{
+ StEntryPrivate *priv = ST_ENTRY_PRIV (actor);
+ ClutterStage *stage;
+ ClutterActor *target;
+
+ stage = clutter_event_get_stage ((ClutterEvent *) event);
+ target = clutter_stage_get_event_actor (stage, (ClutterEvent *) event);
+
+ if (target == priv->entry && event->related != NULL)
+ st_entry_set_cursor (ST_ENTRY (actor), TRUE);
+
+ return CLUTTER_ACTOR_CLASS (st_entry_parent_class)->enter_event (actor, event);
+}
+
+static gboolean
+st_entry_leave_event (ClutterActor *actor,
+ ClutterCrossingEvent *event)
+{
+ st_entry_set_cursor (ST_ENTRY (actor), FALSE);
+
+ return CLUTTER_ACTOR_CLASS (st_entry_parent_class)->leave_event (actor, event);
+}
+
+static void
+st_entry_paint (ClutterActor *actor,
+ ClutterPaintContext *paint_context)
+{
+ StEntryPrivate *priv = ST_ENTRY_PRIV (actor);
+ ClutterActorClass *parent_class;
+
+ st_widget_paint_background (ST_WIDGET (actor), paint_context);
+
+ if (priv->shadow_spec)
+ {
+ ClutterActorBox allocation;
+ float width, height;
+
+ clutter_actor_get_allocation_box (priv->entry, &allocation);
+ clutter_actor_box_get_size (&allocation, &width, &height);
+
+ if (priv->text_shadow_material == NULL ||
+ width != priv->shadow_width ||
+ height != priv->shadow_height)
+ {
+ CoglPipeline *material;
+
+ cogl_clear_object (&priv->text_shadow_material);
+
+ material = _st_create_shadow_pipeline_from_actor (priv->shadow_spec,
+ priv->entry);
+
+ priv->shadow_width = width;
+ priv->shadow_height = height;
+ priv->text_shadow_material = material;
+ }
+
+ if (priv->text_shadow_material != NULL)
+ {
+ CoglFramebuffer *framebuffer =
+ clutter_paint_context_get_framebuffer (paint_context);
+
+ _st_paint_shadow_with_opacity (priv->shadow_spec,
+ framebuffer,
+ priv->text_shadow_material,
+ &allocation,
+ clutter_actor_get_paint_opacity (priv->entry));
+ }
+ }
+
+ /* Since we paint the background ourselves, chain to the parent class
+ * of StWidget, to avoid painting it twice.
+ * This is needed as we still want to paint children.
+ */
+ parent_class = g_type_class_peek_parent (st_entry_parent_class);
+ parent_class->paint (actor, paint_context);
+}
+
+static void
+st_entry_unmap (ClutterActor *actor)
+{
+ StEntryPrivate *priv = ST_ENTRY_PRIV (actor);
+ if (priv->has_ibeam)
+ st_entry_set_cursor (ST_ENTRY (actor), FALSE);
+
+ CLUTTER_ACTOR_CLASS (st_entry_parent_class)->unmap (actor);
+}
+
+static gboolean
+st_entry_get_paint_volume (ClutterActor *actor,
+ ClutterPaintVolume *volume)
+{
+ return clutter_paint_volume_set_from_allocation (volume, actor);
+}
+
+static void
+st_entry_class_init (StEntryClass *klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+ ClutterActorClass *actor_class = CLUTTER_ACTOR_CLASS (klass);
+ StWidgetClass *widget_class = ST_WIDGET_CLASS (klass);
+
+ gobject_class->set_property = st_entry_set_property;
+ gobject_class->get_property = st_entry_get_property;
+ gobject_class->dispose = st_entry_dispose;
+
+ actor_class->get_preferred_width = st_entry_get_preferred_width;
+ actor_class->get_preferred_height = st_entry_get_preferred_height;
+ actor_class->allocate = st_entry_allocate;
+ actor_class->paint = st_entry_paint;
+ actor_class->unmap = st_entry_unmap;
+ actor_class->get_paint_volume = st_entry_get_paint_volume;
+
+ actor_class->key_press_event = st_entry_key_press_event;
+ actor_class->key_focus_in = st_entry_key_focus_in;
+
+ actor_class->enter_event = st_entry_enter_event;
+ actor_class->leave_event = st_entry_leave_event;
+
+ widget_class->style_changed = st_entry_style_changed;
+ widget_class->navigate_focus = st_entry_navigate_focus;
+ widget_class->get_accessible_type = st_entry_accessible_get_type;
+
+ /**
+ * StEntry:clutter-text:
+ *
+ * The internal #ClutterText actor supporting the #StEntry.
+ */
+ props[PROP_CLUTTER_TEXT] =
+ g_param_spec_object ("clutter-text",
+ "Clutter Text",
+ "Internal ClutterText actor",
+ CLUTTER_TYPE_TEXT,
+ ST_PARAM_READABLE);
+
+ /**
+ * StEntry:primary-icon:
+ *
+ * The #ClutterActor acting as the primary icon at the start of the #StEntry.
+ */
+ props[PROP_PRIMARY_ICON] =
+ g_param_spec_object ("primary-icon",
+ "Primary Icon",
+ "Primary Icon actor",
+ CLUTTER_TYPE_ACTOR,
+ ST_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
+
+ /**
+ * StEntry:secondary-icon:
+ *
+ * The #ClutterActor acting as the secondary icon at the end of the #StEntry.
+ */
+ props[PROP_SECONDARY_ICON] =
+ g_param_spec_object ("secondary-icon",
+ "Secondary Icon",
+ "Secondary Icon actor",
+ CLUTTER_TYPE_ACTOR,
+ ST_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
+
+ /**
+ * StEntry:hint-text:
+ *
+ * The text to display when the entry is empty and unfocused. Setting this
+ * will replace the actor of #StEntry::hint-actor.
+ */
+ props[PROP_HINT_TEXT] =
+ g_param_spec_string ("hint-text",
+ "Hint Text",
+ "Text to display when the entry is not focused "
+ "and the text property is empty",
+ NULL,
+ ST_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
+
+ /**
+ * StEntry:hint-actor:
+ *
+ * A #ClutterActor to display when the entry is empty and unfocused. Setting
+ * this will replace the actor displaying #StEntry:hint-text.
+ */
+ props[PROP_HINT_ACTOR] =
+ g_param_spec_object ("hint-actor",
+ "Hint Actor",
+ "An actor to display when the entry is not focused "
+ "and the text property is empty",
+ CLUTTER_TYPE_ACTOR,
+ ST_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
+
+ /**
+ * StEntry:text:
+ *
+ * The current text value of the #StEntry.
+ */
+ props[PROP_TEXT] =
+ g_param_spec_string ("text",
+ "Text",
+ "Text of the entry",
+ NULL,
+ ST_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
+
+ /**
+ * StEntry:input-purpose:
+ *
+ * The #ClutterInputContentPurpose that helps on-screen keyboards and similar
+ * input methods to decide which keys should be presented to the user.
+ */
+ props[PROP_INPUT_PURPOSE] =
+ g_param_spec_enum ("input-purpose",
+ "Purpose",
+ "Purpose of the text field",
+ CLUTTER_TYPE_INPUT_CONTENT_PURPOSE,
+ CLUTTER_INPUT_CONTENT_PURPOSE_NORMAL,
+ ST_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
+
+ /**
+ * StEntry:input-hints:
+ *
+ * The #ClutterInputContentHintFlags providing additional hints (beyond
+ * #StEntry:input-purpose) that allow input methods to fine-tune their
+ * behaviour.
+ */
+ props[PROP_INPUT_HINTS] =
+ g_param_spec_flags ("input-hints",
+ "hints",
+ "Hints for the text field behaviour",
+ CLUTTER_TYPE_INPUT_CONTENT_HINT_FLAGS,
+ 0,
+ ST_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
+
+ g_object_class_install_properties (gobject_class, N_PROPS, props);
+
+ /* signals */
+ /**
+ * StEntry::primary-icon-clicked:
+ * @self: the #StEntry
+ *
+ * Emitted when the primary icon is clicked.
+ */
+ entry_signals[PRIMARY_ICON_CLICKED] =
+ g_signal_new ("primary-icon-clicked",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (StEntryClass, primary_icon_clicked),
+ NULL, NULL, NULL,
+ G_TYPE_NONE, 0);
+
+ /**
+ * StEntry::secondary-icon-clicked:
+ * @self: the #StEntry
+ *
+ * Emitted when the secondary icon is clicked.
+ */
+ entry_signals[SECONDARY_ICON_CLICKED] =
+ g_signal_new ("secondary-icon-clicked",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (StEntryClass, secondary_icon_clicked),
+ NULL, NULL, NULL,
+ G_TYPE_NONE, 0);
+}
+
+static void
+st_entry_init (StEntry *entry)
+{
+ StEntryPrivate *priv;
+
+ priv = st_entry_get_instance_private (entry);
+
+ priv->entry = g_object_new (CLUTTER_TYPE_TEXT,
+ "line-alignment", PANGO_ALIGN_LEFT,
+ "editable", TRUE,
+ "reactive", TRUE,
+ "single-line-mode", TRUE,
+ NULL);
+
+ g_object_bind_property (G_OBJECT (entry), "reactive",
+ priv->entry, "reactive",
+ G_BINDING_DEFAULT);
+
+ g_signal_connect(priv->entry, "notify::reactive",
+ G_CALLBACK (clutter_text_reactive_changed_cb), entry);
+
+ g_signal_connect (priv->entry, "key-focus-in",
+ G_CALLBACK (clutter_text_focus_in_cb), entry);
+
+ g_signal_connect (priv->entry, "key-focus-out",
+ G_CALLBACK (clutter_text_focus_out_cb), entry);
+
+ g_signal_connect (priv->entry, "button-press-event",
+ G_CALLBACK (clutter_text_button_press_event), entry);
+
+ g_signal_connect (priv->entry, "cursor-changed",
+ G_CALLBACK (clutter_text_cursor_changed), entry);
+
+ g_signal_connect (priv->entry, "notify::text",
+ G_CALLBACK (clutter_text_changed_cb), entry);
+
+ /* These properties might get set from CSS using _st_set_text_from_style */
+ g_signal_connect (priv->entry, "notify::font-description",
+ G_CALLBACK (invalidate_shadow_pipeline), entry);
+
+ g_signal_connect (priv->entry, "notify::attributes",
+ G_CALLBACK (invalidate_shadow_pipeline), entry);
+
+ g_signal_connect (priv->entry, "notify::justify",
+ G_CALLBACK (invalidate_shadow_pipeline), entry);
+
+ g_signal_connect (priv->entry, "notify::line-alignment",
+ G_CALLBACK (invalidate_shadow_pipeline), entry);
+
+
+ priv->spacing = 6.0f;
+
+ priv->text_shadow_material = NULL;
+ priv->shadow_width = -1.;
+ priv->shadow_height = -1.;
+
+ clutter_actor_add_child (CLUTTER_ACTOR (entry), priv->entry);
+ clutter_actor_set_reactive ((ClutterActor *) entry, TRUE);
+
+ /* set cursor hidden until we receive focus */
+ clutter_text_set_cursor_visible ((ClutterText *) priv->entry, FALSE);
+}
+
+/**
+ * st_entry_new:
+ * @text: (nullable): text to set the entry to
+ *
+ * Create a new #StEntry with the specified text.
+ *
+ * Returns: a new #StEntry
+ */
+StWidget *
+st_entry_new (const gchar *text)
+{
+ StWidget *entry;
+
+ /* add the entry to the stage, but don't allow it to be visible */
+ entry = g_object_new (ST_TYPE_ENTRY,
+ "text", text,
+ NULL);
+
+ return entry;
+}
+
+/**
+ * st_entry_get_text:
+ * @entry: a #StEntry
+ *
+ * Get the text displayed on the entry. If @entry is empty, an empty string will
+ * be returned instead of %NULL.
+ *
+ * Returns: (transfer none): the text for the entry
+ */
+const gchar *
+st_entry_get_text (StEntry *entry)
+{
+ StEntryPrivate *priv;
+
+ g_return_val_if_fail (ST_IS_ENTRY (entry), NULL);
+
+ priv = st_entry_get_instance_private (entry);
+
+ return clutter_text_get_text (CLUTTER_TEXT (priv->entry));
+}
+
+/**
+ * st_entry_set_text:
+ * @entry: a #StEntry
+ * @text: (nullable): text to set the entry to
+ *
+ * Sets the text displayed on the entry. If @text is %NULL, the #ClutterText
+ * will instead be set to an empty string.
+ */
+void
+st_entry_set_text (StEntry *entry,
+ const gchar *text)
+{
+ StEntryPrivate *priv;
+
+ g_return_if_fail (ST_IS_ENTRY (entry));
+
+ priv = st_entry_get_instance_private (entry);
+
+ clutter_text_set_text (CLUTTER_TEXT (priv->entry), text);
+
+ /* Note: PROP_TEXT will get notfied from our notify::text handler connected
+ * to priv->entry. */
+}
+
+/**
+ * st_entry_get_clutter_text:
+ * @entry: a #StEntry
+ *
+ * Retrieve the internal #ClutterText so that extra parameters can be set.
+ *
+ * Returns: (transfer none): the #ClutterText used by @entry
+ */
+ClutterActor*
+st_entry_get_clutter_text (StEntry *entry)
+{
+ g_return_val_if_fail (ST_ENTRY (entry), NULL);
+
+ return ((StEntryPrivate *)ST_ENTRY_PRIV (entry))->entry;
+}
+
+/**
+ * st_entry_set_hint_text:
+ * @entry: a #StEntry
+ * @text: (nullable): text to set as the entry hint
+ *
+ * Sets the text to display when the entry is empty and unfocused. When the
+ * entry is displaying the hint, it has a pseudo class of `indeterminate`.
+ * A value of %NULL unsets the hint.
+ */
+void
+st_entry_set_hint_text (StEntry *entry,
+ const gchar *text)
+{
+ StWidget *label;
+
+ g_return_if_fail (ST_IS_ENTRY (entry));
+
+ label = st_label_new (text);
+ st_widget_add_style_class_name (label, "hint-text");
+
+ st_entry_set_hint_actor (ST_ENTRY (entry), CLUTTER_ACTOR (label));
+ g_object_notify_by_pspec (G_OBJECT (entry), props[PROP_HINT_TEXT]);
+}
+
+/**
+ * st_entry_get_hint_text:
+ * @entry: a #StEntry
+ *
+ * Gets the text that is displayed when the entry is empty and unfocused or
+ * %NULL if the #StEntry:hint-actor was set to an actor that is not a #StLabel.
+ *
+ * Unlike st_entry_get_text() this function may return %NULL if
+ * #StEntry:hint-actor is not a #StLabel.
+ *
+ * Returns: (nullable) (transfer none): the current value of the hint property
+ */
+const gchar *
+st_entry_get_hint_text (StEntry *entry)
+{
+ StEntryPrivate *priv;
+
+ g_return_val_if_fail (ST_IS_ENTRY (entry), NULL);
+
+ priv = ST_ENTRY_PRIV (entry);
+
+ if (priv->hint_actor != NULL && ST_IS_LABEL (priv->hint_actor))
+ return st_label_get_text (ST_LABEL (priv->hint_actor));
+
+ return NULL;
+}
+
+/**
+ * st_entry_set_input_purpose:
+ * @entry: a #StEntry
+ * @purpose: the purpose
+ *
+ * Sets the #StEntry:input-purpose property which
+ * can be used by on-screen keyboards and other input
+ * methods to adjust their behaviour.
+ */
+void
+st_entry_set_input_purpose (StEntry *entry,
+ ClutterInputContentPurpose purpose)
+{
+ StEntryPrivate *priv;
+ ClutterText *editable;
+
+ g_return_if_fail (ST_IS_ENTRY (entry));
+
+ priv = st_entry_get_instance_private (entry);
+ editable = CLUTTER_TEXT (priv->entry);
+
+ if (clutter_text_get_input_purpose (editable) != purpose)
+ {
+ clutter_text_set_input_purpose (editable, purpose);
+
+ g_object_notify_by_pspec (G_OBJECT (entry), props[PROP_INPUT_PURPOSE]);
+ }
+}
+
+/**
+ * st_entry_get_input_purpose:
+ * @entry: a #StEntry
+ *
+ * Gets the value of the #StEntry:input-purpose property.
+ *
+ * Returns: the input purpose of the entry
+ */
+ClutterInputContentPurpose
+st_entry_get_input_purpose (StEntry *entry)
+{
+ StEntryPrivate *priv;
+
+ g_return_val_if_fail (ST_IS_ENTRY (entry), CLUTTER_INPUT_CONTENT_PURPOSE_NORMAL);
+
+ priv = st_entry_get_instance_private (entry);
+ return clutter_text_get_input_purpose (CLUTTER_TEXT (priv->entry));
+}
+
+/**
+ * st_entry_set_input_hints:
+ * @entry: a #StEntry
+ * @hints: the hints
+ *
+ * Sets the #StEntry:input-hints property, which
+ * allows input methods to fine-tune their behaviour.
+ */
+void
+st_entry_set_input_hints (StEntry *entry,
+ ClutterInputContentHintFlags hints)
+{
+ StEntryPrivate *priv;
+ ClutterText *editable;
+
+ g_return_if_fail (ST_IS_ENTRY (entry));
+
+ priv = st_entry_get_instance_private (entry);
+ editable = CLUTTER_TEXT (priv->entry);
+
+ if (clutter_text_get_input_hints (editable) != hints)
+ {
+ clutter_text_set_input_hints (editable, hints);
+
+ g_object_notify_by_pspec (G_OBJECT (entry), props[PROP_INPUT_HINTS]);
+ }
+}
+
+/**
+ * st_entry_get_input_hints:
+ * @entry: a #StEntry
+ *
+ * Gets the value of the #StEntry:input-hints property.
+ *
+ * Returns: the input hints for the entry
+ */
+ClutterInputContentHintFlags
+st_entry_get_input_hints (StEntry *entry)
+{
+ StEntryPrivate *priv;
+
+ g_return_val_if_fail (ST_IS_ENTRY (entry), 0);
+
+ priv = st_entry_get_instance_private (entry);
+ return clutter_text_get_input_hints (CLUTTER_TEXT (priv->entry));
+}
+
+static void
+_st_entry_icon_clicked_cb (ClutterClickAction *action,
+ ClutterActor *actor,
+ StEntry *entry)
+{
+ StEntryPrivate *priv = ST_ENTRY_PRIV (entry);
+
+ if (!clutter_actor_get_reactive (CLUTTER_ACTOR (entry)))
+ return;
+
+ if (actor == priv->primary_icon)
+ g_signal_emit (entry, entry_signals[PRIMARY_ICON_CLICKED], 0);
+ else
+ g_signal_emit (entry, entry_signals[SECONDARY_ICON_CLICKED], 0);
+}
+
+static void
+_st_entry_set_icon (StEntry *entry,
+ ClutterActor **icon,
+ ClutterActor *new_icon)
+{
+ if (*icon)
+ {
+ clutter_actor_remove_action_by_name (*icon, "entry-icon-action");
+ clutter_actor_remove_child (CLUTTER_ACTOR (entry), *icon);
+ *icon = NULL;
+ }
+
+ if (new_icon)
+ {
+ ClutterAction *action;
+
+ *icon = g_object_ref (new_icon);
+
+ clutter_actor_set_reactive (*icon, TRUE);
+ clutter_actor_add_child (CLUTTER_ACTOR (entry), *icon);
+
+ action = clutter_click_action_new ();
+ clutter_actor_add_action_with_name (*icon, "entry-icon-action", action);
+ g_signal_connect (action, "clicked",
+ G_CALLBACK (_st_entry_icon_clicked_cb), entry);
+ }
+
+ clutter_actor_queue_relayout (CLUTTER_ACTOR (entry));
+}
+
+/**
+ * st_entry_set_primary_icon:
+ * @entry: a #StEntry
+ * @icon: (nullable): a #ClutterActor
+ *
+ * Set the primary icon of the entry to @icon.
+ */
+void
+st_entry_set_primary_icon (StEntry *entry,
+ ClutterActor *icon)
+{
+ StEntryPrivate *priv;
+
+ g_return_if_fail (ST_IS_ENTRY (entry));
+
+ priv = st_entry_get_instance_private (entry);
+
+ if (priv->primary_icon == icon)
+ return;
+
+ _st_entry_set_icon (entry, &priv->primary_icon, icon);
+ g_object_notify_by_pspec (G_OBJECT (entry), props[PROP_PRIMARY_ICON]);
+}
+
+/**
+ * st_entry_get_primary_icon:
+ * @entry: a #StEntry
+ *
+ * Get the value of the #StEntry:primary-icon property.
+ *
+ * Returns: (nullable) (transfer none): a #ClutterActor
+ */
+ClutterActor *
+st_entry_get_primary_icon (StEntry *entry)
+{
+ StEntryPrivate *priv;
+
+ g_return_val_if_fail (ST_IS_ENTRY (entry), NULL);
+
+ priv = ST_ENTRY_PRIV (entry);
+ return priv->primary_icon;
+}
+
+/**
+ * st_entry_set_secondary_icon:
+ * @entry: a #StEntry
+ * @icon: (nullable): an #ClutterActor
+ *
+ * Set the secondary icon of the entry to @icon.
+ */
+void
+st_entry_set_secondary_icon (StEntry *entry,
+ ClutterActor *icon)
+{
+ StEntryPrivate *priv;
+
+ g_return_if_fail (ST_IS_ENTRY (entry));
+
+ priv = st_entry_get_instance_private (entry);
+
+ if (priv->secondary_icon == icon)
+ return;
+
+ _st_entry_set_icon (entry, &priv->secondary_icon, icon);
+ g_object_notify_by_pspec (G_OBJECT (entry), props[PROP_SECONDARY_ICON]);
+}
+
+/**
+ * st_entry_get_secondary_icon:
+ * @entry: a #StEntry
+ *
+ * Get the value of the #StEntry:secondary-icon property.
+ *
+ * Returns: (nullable) (transfer none): a #ClutterActor
+ */
+ClutterActor *
+st_entry_get_secondary_icon (StEntry *entry)
+{
+ StEntryPrivate *priv;
+
+ g_return_val_if_fail (ST_IS_ENTRY (entry), NULL);
+
+ priv = ST_ENTRY_PRIV (entry);
+ return priv->secondary_icon;
+}
+
+/**
+ * st_entry_set_hint_actor:
+ * @entry: a #StEntry
+ * @hint_actor: (nullable): a #ClutterActor
+ *
+ * Set the hint actor of the entry to @hint_actor.
+ */
+void
+st_entry_set_hint_actor (StEntry *entry,
+ ClutterActor *hint_actor)
+{
+ StEntryPrivate *priv;
+
+ g_return_if_fail (ST_IS_ENTRY (entry));
+
+ priv = ST_ENTRY_PRIV (entry);
+
+ if (priv->hint_actor == hint_actor)
+ return;
+
+ if (priv->hint_actor != NULL)
+ {
+ clutter_actor_remove_child (CLUTTER_ACTOR (entry), priv->hint_actor);
+ priv->hint_actor = NULL;
+ }
+
+ if (hint_actor != NULL)
+ {
+ priv->hint_actor = hint_actor;
+ clutter_actor_add_child (CLUTTER_ACTOR (entry), priv->hint_actor);
+ }
+
+ st_entry_update_hint_visibility (entry);
+ g_object_notify_by_pspec (G_OBJECT (entry), props[PROP_HINT_ACTOR]);
+
+ clutter_actor_queue_relayout (CLUTTER_ACTOR (entry));
+}
+
+/**
+ * st_entry_get_hint_actor:
+ * @entry: a #StEntry
+ *
+ * Get the value of the #StEntry:hint-actor property.
+ *
+ * Returns: (nullable) (transfer none): a #ClutterActor
+ */
+ClutterActor *
+st_entry_get_hint_actor (StEntry *entry)
+{
+ StEntryPrivate *priv;
+
+ g_return_val_if_fail (ST_IS_ENTRY (entry), NULL);
+
+ priv = ST_ENTRY_PRIV (entry);
+ return priv->hint_actor;
+}
+
+/******************************************************************************/
+/*************************** ACCESSIBILITY SUPPORT ****************************/
+/******************************************************************************/
+
+#define ST_TYPE_ENTRY_ACCESSIBLE (st_entry_accessible_get_type ())
+#define ST_ENTRY_ACCESSIBLE(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), ST_TYPE_ENTRY_ACCESSIBLE, StEntryAccessible))
+#define ST_IS_ENTRY_ACCESSIBLE(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), ST_TYPE_ENTRY_ACCESSIBLE))
+#define ST_ENTRY_ACCESSIBLE_CLASS(c) (G_TYPE_CHECK_CLASS_CAST ((c), ST_TYPE_ENTRY_ACCESSIBLE, StEntryAccessibleClass))
+#define ST_IS_ENTRY_ACCESSIBLE_CLASS(c) (G_TYPE_CHECK_CLASS_TYPE ((c), ST_TYPE_ENTRY_ACCESSIBLE))
+#define ST_ENTRY_ACCESSIBLE_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), ST_TYPE_ENTRY_ACCESSIBLE, StEntryAccessibleClass))
+
+typedef struct _StEntryAccessible StEntryAccessible;
+typedef struct _StEntryAccessibleClass StEntryAccessibleClass;
+
+struct _StEntryAccessible
+{
+ StWidgetAccessible parent;
+};
+
+struct _StEntryAccessibleClass
+{
+ StWidgetAccessibleClass parent_class;
+};
+
+G_DEFINE_TYPE (StEntryAccessible, st_entry_accessible, ST_TYPE_WIDGET_ACCESSIBLE)
+
+static void
+st_entry_accessible_init (StEntryAccessible *self)
+{
+ /* initialization done on AtkObject->initialize */
+}
+
+static void
+st_entry_accessible_initialize (AtkObject *obj,
+ gpointer data)
+{
+ ATK_OBJECT_CLASS (st_entry_accessible_parent_class)->initialize (obj, data);
+
+ /* StEntry is behaving as a ClutterText container */
+ atk_object_set_role (obj, ATK_ROLE_PANEL);
+}
+
+static gint
+st_entry_accessible_get_n_children (AtkObject *obj)
+{
+ StEntry *entry = NULL;
+ StEntryPrivate *priv;
+
+ g_return_val_if_fail (ST_IS_ENTRY_ACCESSIBLE (obj), 0);
+
+ entry = ST_ENTRY (atk_gobject_accessible_get_object (ATK_GOBJECT_ACCESSIBLE (obj)));
+
+ if (entry == NULL)
+ return 0;
+
+ priv = st_entry_get_instance_private (entry);
+ if (priv->entry == NULL)
+ return 0;
+ else
+ return 1;
+}
+
+static AtkObject*
+st_entry_accessible_ref_child (AtkObject *obj,
+ gint i)
+{
+ StEntry *entry = NULL;
+ StEntryPrivate *priv;
+ AtkObject *result = NULL;
+
+ g_return_val_if_fail (ST_IS_ENTRY_ACCESSIBLE (obj), NULL);
+ g_return_val_if_fail (i == 0, NULL);
+
+ entry = ST_ENTRY (atk_gobject_accessible_get_object (ATK_GOBJECT_ACCESSIBLE (obj)));
+
+ if (entry == NULL)
+ return NULL;
+
+ priv = st_entry_get_instance_private (entry);
+ if (priv->entry == NULL)
+ return NULL;
+
+ result = clutter_actor_get_accessible (priv->entry);
+ g_object_ref (result);
+
+ return result;
+}
+
+
+static void
+st_entry_accessible_class_init (StEntryAccessibleClass *klass)
+{
+ AtkObjectClass *atk_class = ATK_OBJECT_CLASS (klass);
+
+ atk_class->initialize = st_entry_accessible_initialize;
+ atk_class->get_n_children = st_entry_accessible_get_n_children;
+ atk_class->ref_child= st_entry_accessible_ref_child;
+}
diff --git a/src/st/st-entry.h b/src/st/st-entry.h
new file mode 100644
index 0000000..2a05759
--- /dev/null
+++ b/src/st/st-entry.h
@@ -0,0 +1,79 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * st-entry.h: Plain entry actor
+ *
+ * Copyright 2008, 2009 Intel Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU Lesser General Public License,
+ * version 2.1, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#if !defined(ST_H_INSIDE) && !defined(ST_COMPILATION)
+#error "Only <st/st.h> can be included directly.h"
+#endif
+
+#ifndef __ST_ENTRY_H__
+#define __ST_ENTRY_H__
+
+G_BEGIN_DECLS
+
+#include <st/st-widget.h>
+
+#define ST_TYPE_ENTRY (st_entry_get_type ())
+G_DECLARE_DERIVABLE_TYPE (StEntry, st_entry, ST, ENTRY, StWidget)
+
+struct _StEntryClass
+{
+ StWidgetClass parent_class;
+
+ /* signals */
+ void (*primary_icon_clicked) (StEntry *entry);
+ void (*secondary_icon_clicked) (StEntry *entry);
+};
+
+StWidget *st_entry_new (const gchar *text);
+const gchar *st_entry_get_text (StEntry *entry);
+void st_entry_set_text (StEntry *entry,
+ const gchar *text);
+ClutterActor *st_entry_get_clutter_text (StEntry *entry);
+
+void st_entry_set_hint_text (StEntry *entry,
+ const gchar *text);
+const gchar *st_entry_get_hint_text (StEntry *entry);
+
+void st_entry_set_input_purpose (StEntry *entry,
+ ClutterInputContentPurpose purpose);
+void st_entry_set_input_hints (StEntry *entry,
+ ClutterInputContentHintFlags hints);
+
+ClutterInputContentPurpose st_entry_get_input_purpose (StEntry *entry);
+ClutterInputContentHintFlags st_entry_get_input_hints (StEntry *entry);
+
+void st_entry_set_primary_icon (StEntry *entry,
+ ClutterActor *icon);
+ClutterActor * st_entry_get_primary_icon (StEntry *entry);
+
+void st_entry_set_secondary_icon (StEntry *entry,
+ ClutterActor *icon);
+ClutterActor * st_entry_get_secondary_icon (StEntry *entry);
+
+void st_entry_set_hint_actor (StEntry *entry,
+ ClutterActor *hint_actor);
+ClutterActor * st_entry_get_hint_actor (StEntry *entry);
+
+typedef void (*StEntryCursorFunc) (StEntry *entry, gboolean use_ibeam, gpointer data);
+void st_entry_set_cursor_func (StEntryCursorFunc func,
+ gpointer user_data);
+
+G_END_DECLS
+
+#endif /* __ST_ENTRY_H__ */
diff --git a/src/st/st-focus-manager.c b/src/st/st-focus-manager.c
new file mode 100644
index 0000000..1ac6d28
--- /dev/null
+++ b/src/st/st-focus-manager.c
@@ -0,0 +1,256 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * st-focus-manager.c: Keyboard focus manager
+ *
+ * Copyright 2010 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation, either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * SECTION:st-focus-manager
+ * @short_description: Keyboard focus management
+ *
+ * #StFocusManager handles keyboard focus for all actors on the stage.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <clutter/clutter.h>
+
+#include "st-focus-manager.h"
+
+struct _StFocusManagerPrivate
+{
+ GHashTable *groups;
+};
+
+G_DEFINE_TYPE_WITH_PRIVATE (StFocusManager, st_focus_manager, G_TYPE_OBJECT)
+
+static void
+st_focus_manager_dispose (GObject *object)
+{
+ StFocusManager *manager = ST_FOCUS_MANAGER (object);
+
+ if (manager->priv->groups)
+ {
+ g_hash_table_destroy (manager->priv->groups);
+ manager->priv->groups = NULL;
+ }
+
+ G_OBJECT_CLASS (st_focus_manager_parent_class)->dispose (object);
+}
+
+static void
+st_focus_manager_class_init (StFocusManagerClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->dispose = st_focus_manager_dispose;
+}
+
+static void
+st_focus_manager_init (StFocusManager *manager)
+{
+ manager->priv = st_focus_manager_get_instance_private (manager);
+ manager->priv->groups = g_hash_table_new (NULL, NULL);
+}
+
+static gboolean
+st_focus_manager_stage_event (ClutterActor *stage,
+ ClutterEvent *event,
+ gpointer user_data)
+{
+ StFocusManager *manager = user_data;
+ StDirectionType direction;
+ gboolean wrap_around = FALSE;
+ ClutterActor *focused, *group;
+
+ if (event->type != CLUTTER_KEY_PRESS)
+ return FALSE;
+
+ switch (event->key.keyval)
+ {
+ case CLUTTER_KEY_Up:
+ direction = ST_DIR_UP;
+ break;
+ case CLUTTER_KEY_Down:
+ direction = ST_DIR_DOWN;
+ break;
+ case CLUTTER_KEY_Left:
+ direction = ST_DIR_LEFT;
+ break;
+ case CLUTTER_KEY_Right:
+ direction = ST_DIR_RIGHT;
+ break;
+ case CLUTTER_KEY_Tab:
+ if (event->key.modifier_state & CLUTTER_SHIFT_MASK)
+ direction = ST_DIR_TAB_BACKWARD;
+ else
+ direction = ST_DIR_TAB_FORWARD;
+ wrap_around = TRUE;
+ break;
+ case CLUTTER_KEY_ISO_Left_Tab:
+ direction = ST_DIR_TAB_BACKWARD;
+ wrap_around = TRUE;
+ break;
+
+ default:
+ return FALSE;
+ }
+
+ focused = clutter_stage_get_key_focus (CLUTTER_STAGE (stage));
+ if (!focused)
+ return FALSE;
+
+ for (group = focused; group != stage; group = clutter_actor_get_parent (group))
+ {
+ if (g_hash_table_lookup (manager->priv->groups, group))
+ {
+ return st_widget_navigate_focus (ST_WIDGET (group), focused,
+ direction, wrap_around);
+ }
+ }
+ return FALSE;
+}
+
+/**
+ * st_focus_manager_get_for_stage:
+ * @stage: a #ClutterStage
+ *
+ * Gets the #StFocusManager for @stage, creating it if necessary.
+ *
+ * Returns: (transfer none): the focus manager for @stage
+ */
+StFocusManager *
+st_focus_manager_get_for_stage (ClutterStage *stage)
+{
+ StFocusManager *manager;
+
+ manager = g_object_get_data (G_OBJECT (stage), "st-focus-manager");
+ if (!manager)
+ {
+ manager = g_object_new (ST_TYPE_FOCUS_MANAGER, NULL);
+ g_object_set_data_full (G_OBJECT (stage), "st-focus-manager",
+ manager, g_object_unref);
+
+ g_signal_connect (stage, "event",
+ G_CALLBACK (st_focus_manager_stage_event), manager);
+ }
+
+ return manager;
+}
+
+static void
+remove_destroyed_group (ClutterActor *actor,
+ gpointer user_data)
+{
+ StFocusManager *manager = user_data;
+
+ st_focus_manager_remove_group (manager, ST_WIDGET (actor));
+}
+
+/**
+ * st_focus_manager_add_group:
+ * @manager: the #StFocusManager
+ * @root: the root container of the group
+ *
+ * Adds a new focus group to @manager. When the focus is in an actor
+ * that is a descendant of @root, @manager will handle moving focus
+ * from one actor to another within @root based on keyboard events.
+ */
+void
+st_focus_manager_add_group (StFocusManager *manager,
+ StWidget *root)
+{
+ gpointer count_p = g_hash_table_lookup (manager->priv->groups, root);
+ int count = count_p ? GPOINTER_TO_INT (count_p) : 0;
+
+ g_signal_connect (root, "destroy",
+ G_CALLBACK (remove_destroyed_group),
+ manager);
+ g_hash_table_insert (manager->priv->groups, root, GINT_TO_POINTER (++count));
+}
+
+/**
+ * st_focus_manager_remove_group:
+ * @manager: the #StFocusManager
+ * @root: the root container of the group
+ *
+ * Removes the group rooted at @root from @manager
+ */
+void
+st_focus_manager_remove_group (StFocusManager *manager,
+ StWidget *root)
+{
+ gpointer count_p = g_hash_table_lookup (manager->priv->groups, root);
+ int count = count_p ? GPOINTER_TO_INT (count_p) : 0;
+
+ if (count == 0)
+ return;
+ if (count == 1)
+ g_hash_table_remove (manager->priv->groups, root);
+ else
+ g_hash_table_insert (manager->priv->groups, root, GINT_TO_POINTER(--count));
+}
+
+/**
+ * st_focus_manager_get_group:
+ * @manager: the #StFocusManager
+ * @widget: an #StWidget
+ *
+ * Checks if @widget is inside a focus group, and if so, returns
+ * the root of that group.
+ *
+ * Returns: (transfer none): the focus group root, or %NULL if
+ * @widget is not in a focus group
+ */
+StWidget *
+st_focus_manager_get_group (StFocusManager *manager,
+ StWidget *widget)
+{
+ ClutterActor *actor = CLUTTER_ACTOR (widget);
+
+ while (actor && !g_hash_table_lookup (manager->priv->groups, actor))
+ actor = clutter_actor_get_parent (actor);
+
+ return ST_WIDGET (actor);
+}
+
+/**
+ * st_focus_manager_navigate_from_event:
+ * @manager: the #StFocusManager
+ * @event: a #ClutterEvent
+ *
+ * Try to navigate from @event as if it bubbled all the way up to
+ * the stage. This is useful in complex event handling situations
+ * where you want key navigation, but a parent might be stopping
+ * the key navigation event from bubbling all the way up to the stage.
+ *
+ * Returns: Whether a new actor was navigated to
+ */
+gboolean
+st_focus_manager_navigate_from_event (StFocusManager *manager,
+ ClutterEvent *event)
+{
+ ClutterActor *stage;
+
+ if (event->type != CLUTTER_KEY_PRESS)
+ return FALSE;
+
+ stage = CLUTTER_ACTOR (event->key.stage);
+ return st_focus_manager_stage_event (stage, event, manager);
+}
diff --git a/src/st/st-focus-manager.h b/src/st/st-focus-manager.h
new file mode 100644
index 0000000..ba8442b
--- /dev/null
+++ b/src/st/st-focus-manager.h
@@ -0,0 +1,65 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * st-focus-manager.h: Keyboard focus manager
+ *
+ * Copyright 2010 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation, either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#if !defined(ST_H_INSIDE) && !defined(ST_COMPILATION)
+#error "Only <st/st.h> can be included directly.h"
+#endif
+
+#ifndef __ST_FOCUS_MANAGER_H__
+#define __ST_FOCUS_MANAGER_H__
+
+#include <st/st-types.h>
+#include <st/st-widget.h>
+
+G_BEGIN_DECLS
+
+#define ST_TYPE_FOCUS_MANAGER (st_focus_manager_get_type ())
+G_DECLARE_FINAL_TYPE (StFocusManager, st_focus_manager, ST, FOCUS_MANAGER, GObject)
+
+typedef struct _StFocusManager StFocusManager;
+typedef struct _StFocusManagerPrivate StFocusManagerPrivate;
+
+/**
+ * StFocusManager:
+ *
+ * The #StFocusManager struct contains only private data
+ */
+struct _StFocusManager
+{
+ /*< private >*/
+ GObject parent_instance;
+
+ StFocusManagerPrivate *priv;
+};
+
+StFocusManager *st_focus_manager_get_for_stage (ClutterStage *stage);
+
+void st_focus_manager_add_group (StFocusManager *manager,
+ StWidget *root);
+void st_focus_manager_remove_group (StFocusManager *manager,
+ StWidget *root);
+StWidget *st_focus_manager_get_group (StFocusManager *manager,
+ StWidget *widget);
+gboolean st_focus_manager_navigate_from_event (StFocusManager *manager,
+ ClutterEvent *event);
+
+G_END_DECLS
+
+#endif /* __ST_FOCUS_MANAGER_H__ */
diff --git a/src/st/st-generic-accessible.c b/src/st/st-generic-accessible.c
new file mode 100644
index 0000000..e6cb393
--- /dev/null
+++ b/src/st/st-generic-accessible.c
@@ -0,0 +1,246 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * st-generic-accessible.c: generic accessible
+ *
+ * Copyright 2013 Igalia, S.L.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU Lesser General Public License,
+ * version 2.1, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * SECTION:st-generic-accessible
+ * @short_description: An accessible class with signals for
+ * implementing specific Atk interfaces
+ *
+ * #StGenericAccessible is mainly a workaround for the current lack of
+ * of a proper support for GValue at javascript. See bug#703412 for
+ * more information. We implement the accessible interfaces, but proxy
+ * the virtual functions into signals, which gjs can catch.
+ *
+ * #StGenericAccessible is an #StWidgetAccessible
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "st-generic-accessible.h"
+
+static void atk_value_iface_init (AtkValueIface *iface);
+
+G_DEFINE_TYPE_WITH_CODE(StGenericAccessible,
+ st_generic_accessible,
+ ST_TYPE_WIDGET_ACCESSIBLE,
+ G_IMPLEMENT_INTERFACE (ATK_TYPE_VALUE,
+ atk_value_iface_init));
+/* Signals */
+enum
+{
+ GET_CURRENT_VALUE,
+ GET_MAXIMUM_VALUE,
+ GET_MINIMUM_VALUE,
+ SET_CURRENT_VALUE,
+ GET_MINIMUM_INCREMENT,
+ LAST_SIGNAL
+};
+
+static guint st_generic_accessible_signals [LAST_SIGNAL] = { 0 };
+
+static void
+st_generic_accessible_init (StGenericAccessible *accessible)
+{
+}
+
+static void
+st_generic_accessible_class_init (StGenericAccessibleClass *klass)
+{
+ /**
+ * StGenericAccessible::get-current-value:
+ * @self: the #StGenericAccessible
+ *
+ * Emitted when atk_value_get_current_value() is called on
+ * @self. Right now we only care about doubles, so the value is
+ * directly returned by the signal.
+ *
+ * Returns: value of the current element.
+ */
+ st_generic_accessible_signals[GET_CURRENT_VALUE] =
+ g_signal_new ("get-current-value",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL, NULL,
+ G_TYPE_DOUBLE, 0);
+
+ /**
+ * StGenericAccessible::get-maximum-value:
+ * @self: the #StGenericAccessible
+ *
+ * Emitted when atk_value_get_maximum_value() is called on
+ * @self. Right now we only care about doubles, so the value is
+ * directly returned by the signal.
+ *
+ * Returns: maximum value of the accessible.
+ */
+ st_generic_accessible_signals[GET_MAXIMUM_VALUE] =
+ g_signal_new ("get-maximum-value",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL, NULL,
+ G_TYPE_DOUBLE, 0);
+
+ /**
+ * StGenericAccessible::get-minimum-value:
+ * @self: the #StGenericAccessible
+ *
+ * Emitted when atk_value_get_current_value() is called on
+ * @self. Right now we only care about doubles, so the value is
+ * directly returned by the signal.
+ *
+ * Returns: minimum value of the accessible.
+ */
+ st_generic_accessible_signals[GET_MINIMUM_VALUE] =
+ g_signal_new ("get-minimum-value",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL, NULL,
+ G_TYPE_DOUBLE, 0);
+
+ /**
+ * StGenericAccessible::get-minimum-increment:
+ * @self: the #StGenericAccessible
+ *
+ * Emitted when atk_value_get_minimum_increment() is called on
+ * @self. Right now we only care about doubles, so the value is
+ * directly returned by the signal.
+ *
+ * Returns: value of the current element.
+ */
+ st_generic_accessible_signals[GET_MINIMUM_INCREMENT] =
+ g_signal_new ("get-minimum-increment",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL, NULL,
+ G_TYPE_DOUBLE, 0);
+
+ /**
+ * StGenericAccessible::set-current-value:
+ * @self: the #StGenericAccessible
+ * @new_value: the new value for the accessible
+ *
+ * Emitted when atk_value_set_current_value() is called on
+ * @self. Right now we only care about doubles, so the value is
+ * directly returned by the signal.
+ */
+ st_generic_accessible_signals[SET_CURRENT_VALUE] =
+ g_signal_new ("set-current-value",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL, NULL,
+ G_TYPE_NONE, 1, G_TYPE_DOUBLE);
+
+}
+
+static void
+st_generic_accessible_get_current_value (AtkValue *obj,
+ GValue *value)
+{
+ gdouble current_value = 0;
+
+ g_value_init (value, G_TYPE_DOUBLE);
+ g_signal_emit (G_OBJECT (obj), st_generic_accessible_signals[GET_CURRENT_VALUE], 0, &current_value);
+ g_value_set_double (value, current_value);
+}
+
+static void
+st_generic_accessible_get_maximum_value (AtkValue *obj,
+ GValue *value)
+{
+ gdouble current_value = 0;
+
+ g_value_init (value, G_TYPE_DOUBLE);
+ g_signal_emit (G_OBJECT (obj), st_generic_accessible_signals[GET_MAXIMUM_VALUE], 0, &current_value);
+ g_value_set_double (value, current_value);
+}
+
+static void
+st_generic_accessible_get_minimum_value (AtkValue *obj,
+ GValue *value)
+{
+ gdouble current_value = 0;
+
+ g_value_init (value, G_TYPE_DOUBLE);
+ g_signal_emit (G_OBJECT (obj), st_generic_accessible_signals[GET_MINIMUM_VALUE], 0, &current_value);
+ g_value_set_double (value, current_value);
+}
+
+static void
+st_generic_accessible_get_minimum_increment (AtkValue *obj,
+ GValue *value)
+{
+ gdouble current_value = 0;
+
+ g_value_init (value, G_TYPE_DOUBLE);
+ g_signal_emit (G_OBJECT (obj), st_generic_accessible_signals[GET_MINIMUM_INCREMENT], 0, &current_value);
+ g_value_set_double (value, current_value);
+}
+
+static gboolean
+st_generic_accessible_set_current_value (AtkValue *obj,
+ const GValue *value)
+{
+ gdouble current_value = 0;
+
+ current_value = g_value_get_double (value);
+ g_signal_emit (G_OBJECT (obj), st_generic_accessible_signals[SET_CURRENT_VALUE], 0, current_value);
+
+ return TRUE; // we assume that the value was properly set
+}
+
+static void
+atk_value_iface_init (AtkValueIface *iface)
+{
+ iface->get_current_value = st_generic_accessible_get_current_value;
+ iface->get_maximum_value = st_generic_accessible_get_maximum_value;
+ iface->get_minimum_value = st_generic_accessible_get_minimum_value;
+ iface->get_minimum_increment = st_generic_accessible_get_minimum_increment;
+ iface->set_current_value = st_generic_accessible_set_current_value;
+}
+
+/**
+ * st_generic_accessible_new_for_actor:
+ * @actor: a #Clutter Actor
+ *
+ * Create a new #StGenericAccessible for @actor.
+ *
+ * This is useful only for custom widgets that need a proxy for #AtkObject.
+ *
+ * Returns: (transfer full): a new #AtkObject
+ */
+AtkObject*
+st_generic_accessible_new_for_actor (ClutterActor *actor)
+{
+ AtkObject *accessible = NULL;
+
+ g_return_val_if_fail (CLUTTER_IS_ACTOR (actor), NULL);
+
+ accessible = ATK_OBJECT (g_object_new (ST_TYPE_GENERIC_ACCESSIBLE,
+ NULL));
+ atk_object_initialize (accessible, actor);
+
+ return accessible;
+}
diff --git a/src/st/st-generic-accessible.h b/src/st/st-generic-accessible.h
new file mode 100644
index 0000000..99a6a71
--- /dev/null
+++ b/src/st/st-generic-accessible.h
@@ -0,0 +1,62 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * st-generic-accessible.h: generic accessible
+ *
+ * Copyright 2013 Igalia, S.L.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU Lesser General Public License,
+ * version 2.1, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#if !defined(ST_H_INSIDE) && !defined(ST_COMPILATION)
+#error "Only <st/st.h> can be included directly.h"
+#endif
+
+#ifndef __ST_GENERIC_ACCESSIBLE_H__
+#define __ST_GENERIC_ACCESSIBLE_H__
+
+#include <clutter/clutter.h>
+#include <st/st-widget-accessible.h>
+
+G_BEGIN_DECLS
+
+#define ST_TYPE_GENERIC_ACCESSIBLE (st_generic_accessible_get_type ())
+#define ST_GENERIC_ACCESSIBLE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), ST_TYPE_GENERIC_ACCESSIBLE, StGenericAccessible))
+#define ST_GENERIC_ACCESSIBLE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), ST_TYPE_GENERIC_ACCESSIBLE, StGenericAccessibleClass))
+#define ST_IS_GENERIC_ACCESSIBLE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), ST_TYPE_GENERIC_ACCESSIBLE))
+#define ST_IS_GENERIC_ACCESSIBLE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), ST_TYPE_GENERIC_ACCESSIBLE))
+#define ST_GENERIC_ACCESSIBLE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), ST_TYPE_GENERIC_ACCESSIBLE, StGenericAccessibleClass))
+
+typedef struct _StGenericAccessible StGenericAccessible;
+typedef struct _StGenericAccessibleClass StGenericAccessibleClass;
+
+typedef struct _StGenericAccessiblePrivate StGenericAccessiblePrivate;
+
+struct _StGenericAccessible
+{
+ StWidgetAccessible parent;
+
+ StGenericAccessiblePrivate *priv;
+};
+
+struct _StGenericAccessibleClass
+{
+ StWidgetAccessibleClass parent_class;
+};
+
+GType st_generic_accessible_get_type (void) G_GNUC_CONST;
+
+AtkObject* st_generic_accessible_new_for_actor (ClutterActor *actor);
+
+G_END_DECLS
+
+#endif /* __ST_GENERIC_ACCESSIBLE_H__ */
diff --git a/src/st/st-icon-colors.c b/src/st/st-icon-colors.c
new file mode 100644
index 0000000..c6a082a
--- /dev/null
+++ b/src/st/st-icon-colors.c
@@ -0,0 +1,133 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * st-icon-colors.c: Colors for colorizing a symbolic icon
+ *
+ * Copyright 2010 Red Hat, Inc.
+ * Copyright 2010 Florian Müllner
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation, either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "st-icon-colors.h"
+
+/**
+ * st_icon_colors_new:
+ *
+ * Creates a new #StIconColors. All colors are initialized to transparent black.
+ *
+ * Returns: a newly created #StIconColors. Free with st_icon_colors_unref()
+ */
+StIconColors *
+st_icon_colors_new (void)
+{
+ StIconColors *colors;
+
+ colors = g_new0 (StIconColors, 1);
+ colors->ref_count = 1;
+
+ return colors;
+}
+
+/**
+ * st_icon_colors_ref:
+ * @colors: a #StIconColors
+ *
+ * Atomically increments the reference count of @colors by one.
+ *
+ * Returns: the passed in #StIconColors.
+ */
+StIconColors *
+st_icon_colors_ref (StIconColors *colors)
+{
+ g_return_val_if_fail (colors != NULL, NULL);
+ g_return_val_if_fail (colors->ref_count > 0, colors);
+
+ g_atomic_int_inc ((volatile int *)&colors->ref_count);
+ return colors;
+}
+
+/**
+ * st_icon_colors_unref:
+ * @colors: a #StIconColors
+ *
+ * Atomically decrements the reference count of @colors by one.
+ * If the reference count drops to 0, all memory allocated by the
+ * #StIconColors is released.
+ */
+void
+st_icon_colors_unref (StIconColors *colors)
+{
+ g_return_if_fail (colors != NULL);
+ g_return_if_fail (colors->ref_count > 0);
+
+ if (g_atomic_int_dec_and_test ((volatile int *)&colors->ref_count))
+ g_free (colors);
+}
+
+/**
+ * st_icon_colors_copy:
+ * @colors: a #StIconColors
+ *
+ * Creates a new StIconColors structure that is a copy of the passed
+ * in @colors. You would use this function instead of st_icon_colors_ref()
+ * if you were planning to change colors in the result.
+ *
+ * Returns: a newly created #StIconColors.
+ */
+StIconColors *
+st_icon_colors_copy (StIconColors *colors)
+{
+ StIconColors *copy;
+
+ g_return_val_if_fail (colors != NULL, NULL);
+
+ copy = st_icon_colors_new ();
+
+ copy->foreground = colors->foreground;
+ copy->warning = colors->warning;
+ copy->error = colors->error;
+ copy->success = colors->success;
+
+ return copy;
+}
+
+/**
+ * st_icon_colors_equal:
+ * @colors: a #StIconColors
+ * @other: another #StIconColors
+ *
+ * Check if two #StIconColors objects are identical.
+ *
+ * Returns: %TRUE if the #StIconColors are equal
+ */
+gboolean
+st_icon_colors_equal (StIconColors *colors,
+ StIconColors *other)
+{
+ if (colors == other)
+ return TRUE;
+
+ if (colors == NULL || other == NULL)
+ return FALSE;
+
+ return clutter_color_equal (&colors->foreground, &other->foreground) &&
+ clutter_color_equal (&colors->warning, &other->warning) &&
+ clutter_color_equal (&colors->error, &other->error) &&
+ clutter_color_equal (&colors->success, &other->success);
+}
+
+G_DEFINE_BOXED_TYPE (StIconColors,
+ st_icon_colors,
+ st_icon_colors_ref,
+ st_icon_colors_unref)
diff --git a/src/st/st-icon-colors.h b/src/st/st-icon-colors.h
new file mode 100644
index 0000000..e994a75
--- /dev/null
+++ b/src/st/st-icon-colors.h
@@ -0,0 +1,43 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+#ifndef __ST_ICON_COLORS__
+#define __ST_ICON_COLORS__
+
+#include <clutter/clutter.h>
+
+G_BEGIN_DECLS
+
+#define ST_TYPE_ICON_COLORS (st_icon_colors_get_type ())
+
+typedef struct _StIconColors StIconColors;
+
+/**
+ * StIconColors:
+ * @foreground: foreground color
+ * @warning: color indicating a warning state
+ * @error: color indicating an error state
+ * @success: color indicating a successful operation
+ *
+ * The #StIconColors structure encapsulates colors for colorizing a symbolic
+ * icon.
+ */
+struct _StIconColors {
+ volatile guint ref_count;
+
+ ClutterColor foreground;
+ ClutterColor warning;
+ ClutterColor error;
+ ClutterColor success;
+};
+
+GType st_icon_colors_get_type (void) G_GNUC_CONST;
+
+StIconColors *st_icon_colors_new (void);
+StIconColors *st_icon_colors_ref (StIconColors *colors);
+void st_icon_colors_unref (StIconColors *colors);
+StIconColors *st_icon_colors_copy (StIconColors *colors);
+gboolean st_icon_colors_equal (StIconColors *colors,
+ StIconColors *other);
+
+G_END_DECLS
+
+#endif /* __ST_ICON_COLORS__ */
diff --git a/src/st/st-icon.c b/src/st/st-icon.c
new file mode 100644
index 0000000..6009afe
--- /dev/null
+++ b/src/st/st-icon.c
@@ -0,0 +1,833 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * st-icon.c: icon widget
+ *
+ * Copyright 2009, 2010 Intel Corporation.
+ * Copyright 2010 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU Lesser General Public License,
+ * version 2.1, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * SECTION:st-icon
+ * @short_description: a simple styled icon actor
+ *
+ * #StIcon is a simple styled texture actor that displays an image from
+ * a stylesheet.
+ */
+
+#include "st-enum-types.h"
+#include "st-icon.h"
+#include "st-texture-cache.h"
+#include "st-theme-context.h"
+#include "st-private.h"
+
+enum
+{
+ PROP_0,
+
+ PROP_GICON,
+ PROP_FALLBACK_GICON,
+
+ PROP_ICON_NAME,
+ PROP_ICON_SIZE,
+ PROP_FALLBACK_ICON_NAME,
+
+ N_PROPS
+};
+
+static GParamSpec *props[N_PROPS] = { NULL, };
+
+struct _StIconPrivate
+{
+ ClutterActor *icon_texture;
+ ClutterActor *pending_texture;
+ gulong opacity_handler_id;
+
+ GIcon *gicon;
+ gint prop_icon_size; /* icon size set as property */
+ gint theme_icon_size; /* icon size from theme node */
+ gint icon_size; /* icon size we are using */
+ GIcon *fallback_gicon;
+ gboolean needs_update;
+
+ StIconColors *colors;
+
+ CoglPipeline *shadow_pipeline;
+ StShadow *shadow_spec;
+ graphene_size_t shadow_size;
+};
+
+G_DEFINE_TYPE_WITH_PRIVATE (StIcon, st_icon, ST_TYPE_WIDGET)
+
+static void st_icon_update (StIcon *icon);
+static gboolean st_icon_update_icon_size (StIcon *icon);
+static void st_icon_update_shadow_pipeline (StIcon *icon);
+static void st_icon_clear_shadow_pipeline (StIcon *icon);
+
+static GIcon *default_gicon = NULL;
+
+#define IMAGE_MISSING_ICON_NAME "image-missing"
+#define DEFAULT_ICON_SIZE 48
+
+static void
+st_icon_set_property (GObject *gobject,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ StIcon *icon = ST_ICON (gobject);
+
+ switch (prop_id)
+ {
+ case PROP_GICON:
+ st_icon_set_gicon (icon, g_value_get_object (value));
+ break;
+
+ case PROP_FALLBACK_GICON:
+ st_icon_set_fallback_gicon (icon, g_value_get_object (value));
+ break;
+
+ case PROP_ICON_NAME:
+ st_icon_set_icon_name (icon, g_value_get_string (value));
+ break;
+
+ case PROP_ICON_SIZE:
+ st_icon_set_icon_size (icon, g_value_get_int (value));
+ break;
+
+ case PROP_FALLBACK_ICON_NAME:
+ st_icon_set_fallback_icon_name (icon, g_value_get_string (value));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+st_icon_get_property (GObject *gobject,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ StIcon *icon = ST_ICON (gobject);
+
+ switch (prop_id)
+ {
+ case PROP_GICON:
+ g_value_set_object (value, st_icon_get_gicon (icon));
+ break;
+
+ case PROP_FALLBACK_GICON:
+ g_value_set_object (value, st_icon_get_fallback_gicon (icon));
+ break;
+
+ case PROP_ICON_NAME:
+ g_value_set_string (value, st_icon_get_icon_name (icon));
+ break;
+
+ case PROP_ICON_SIZE:
+ g_value_set_int (value, st_icon_get_icon_size (icon));
+ break;
+
+ case PROP_FALLBACK_ICON_NAME:
+ g_value_set_string (value, st_icon_get_fallback_icon_name (icon));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+st_icon_dispose (GObject *gobject)
+{
+ StIconPrivate *priv = ST_ICON (gobject)->priv;
+
+ if (priv->icon_texture)
+ {
+ clutter_actor_destroy (priv->icon_texture);
+ priv->icon_texture = NULL;
+ }
+
+ if (priv->pending_texture)
+ {
+ clutter_actor_destroy (priv->pending_texture);
+ g_object_unref (priv->pending_texture);
+ priv->pending_texture = NULL;
+ }
+
+ g_clear_object (&priv->gicon);
+ g_clear_object (&priv->fallback_gicon);
+ g_clear_pointer (&priv->colors, st_icon_colors_unref);
+ g_clear_pointer (&priv->shadow_pipeline, cogl_object_unref);
+ g_clear_pointer (&priv->shadow_spec, st_shadow_unref);
+
+ G_OBJECT_CLASS (st_icon_parent_class)->dispose (gobject);
+}
+
+static void
+st_icon_paint (ClutterActor *actor,
+ ClutterPaintContext *paint_context)
+{
+ StIcon *icon = ST_ICON (actor);
+ StIconPrivate *priv = icon->priv;
+
+ st_widget_paint_background (ST_WIDGET (actor), paint_context);
+
+ if (priv->icon_texture)
+ {
+ st_icon_update_shadow_pipeline (icon);
+
+ if (priv->shadow_pipeline)
+ {
+ ClutterActorBox allocation;
+ CoglFramebuffer *framebuffer;
+
+ clutter_actor_get_allocation_box (priv->icon_texture, &allocation);
+ framebuffer = clutter_paint_context_get_framebuffer (paint_context);
+ _st_paint_shadow_with_opacity (priv->shadow_spec,
+ framebuffer,
+ priv->shadow_pipeline,
+ &allocation,
+ clutter_actor_get_paint_opacity (priv->icon_texture));
+ }
+
+ clutter_actor_paint (priv->icon_texture, paint_context);
+ }
+}
+
+static void
+st_icon_style_changed (StWidget *widget)
+{
+ StIcon *self = ST_ICON (widget);
+ StThemeNode *theme_node = st_widget_get_theme_node (widget);
+ StIconPrivate *priv = self->priv;
+ gboolean should_update = FALSE;
+ g_autoptr(StShadow) shadow_spec = NULL;
+ StIconColors *colors;
+
+ shadow_spec = st_theme_node_get_shadow (theme_node, "icon-shadow");
+
+ if (shadow_spec && shadow_spec->inset)
+ {
+ g_warning ("The icon-shadow property does not support inset shadows");
+ g_clear_pointer (&shadow_spec, st_shadow_unref);
+ }
+
+ if ((shadow_spec && priv->shadow_spec && !st_shadow_equal (shadow_spec, priv->shadow_spec)) ||
+ (shadow_spec && !priv->shadow_spec) || (!shadow_spec && priv->shadow_spec))
+ {
+ st_icon_clear_shadow_pipeline (self);
+
+ g_clear_pointer (&priv->shadow_spec, st_shadow_unref);
+ priv->shadow_spec = g_steal_pointer (&shadow_spec);
+
+ should_update = TRUE;
+ }
+
+ colors = st_theme_node_get_icon_colors (theme_node);
+
+ if ((colors && priv->colors && !st_icon_colors_equal (colors, priv->colors)) ||
+ (colors && !priv->colors) || (!colors && priv->colors))
+ {
+ g_clear_pointer (&priv->colors, st_icon_colors_unref);
+ priv->colors = st_icon_colors_ref (colors);
+
+ should_update = TRUE;
+ }
+
+ priv->theme_icon_size = (int)(0.5 + st_theme_node_get_length (theme_node, "icon-size"));
+
+ should_update |= st_icon_update_icon_size (self);
+
+ if (priv->needs_update || should_update)
+ st_icon_update (self);
+
+ ST_WIDGET_CLASS (st_icon_parent_class)->style_changed (widget);
+}
+
+static void
+st_icon_resource_scale_changed (ClutterActor *actor)
+{
+ st_icon_update (ST_ICON (actor));
+
+ if (CLUTTER_ACTOR_CLASS (st_icon_parent_class)->resource_scale_changed)
+ CLUTTER_ACTOR_CLASS (st_icon_parent_class)->resource_scale_changed (actor);
+}
+
+static void
+st_icon_class_init (StIconClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ ClutterActorClass *actor_class = CLUTTER_ACTOR_CLASS (klass);
+ StWidgetClass *widget_class = ST_WIDGET_CLASS (klass);
+
+ object_class->get_property = st_icon_get_property;
+ object_class->set_property = st_icon_set_property;
+ object_class->dispose = st_icon_dispose;
+
+ actor_class->paint = st_icon_paint;
+
+ widget_class->style_changed = st_icon_style_changed;
+ actor_class->resource_scale_changed = st_icon_resource_scale_changed;
+
+ /**
+ * StIcon:gicon:
+ *
+ * The #GIcon being displayed by this #StIcon.
+ */
+ props[PROP_GICON] =
+ g_param_spec_object ("gicon",
+ "GIcon",
+ "The GIcon shown by this icon actor",
+ G_TYPE_ICON,
+ ST_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
+
+ /**
+ * StIcon:fallback-gicon:
+ *
+ * The fallback #GIcon to display if #StIcon:gicon fails to load.
+ */
+ props[PROP_FALLBACK_GICON] =
+ g_param_spec_object ("fallback-gicon",
+ "Fallback GIcon",
+ "The fallback GIcon shown if the normal icon fails to load",
+ G_TYPE_ICON,
+ ST_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
+
+ /**
+ * StIcon:icon-name:
+ *
+ * The name of the icon if the icon being displayed is a #GThemedIcon.
+ */
+ props[PROP_ICON_NAME] =
+ g_param_spec_string ("icon-name",
+ "Icon name",
+ "An icon name",
+ NULL,
+ ST_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
+
+ /**
+ * StIcon:icon-size:
+ *
+ * The size of the icon, if greater than `0`. Other the icon size is derived
+ * from the current style.
+ */
+ props[PROP_ICON_SIZE] =
+ g_param_spec_int ("icon-size",
+ "Icon size",
+ "The size if the icon, if positive. Otherwise the size will be derived from the current style",
+ -1, G_MAXINT, -1,
+ ST_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
+
+ /**
+ * StIcon:fallback-icon-name:
+ *
+ * The fallback icon name of the #StIcon. See st_icon_set_fallback_icon_name()
+ * for details.
+ */
+ props[PROP_FALLBACK_ICON_NAME] =
+ g_param_spec_string ("fallback-icon-name",
+ "Fallback icon name",
+ "A fallback icon name",
+ NULL,
+ ST_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
+
+ g_object_class_install_properties (object_class, N_PROPS, props);
+}
+
+static void
+st_icon_init (StIcon *self)
+{
+ ClutterLayoutManager *layout_manager;
+
+ if (G_UNLIKELY (default_gicon == NULL))
+ default_gicon = g_themed_icon_new (IMAGE_MISSING_ICON_NAME);
+
+ self->priv = st_icon_get_instance_private (self);
+
+ layout_manager = clutter_bin_layout_new (CLUTTER_BIN_ALIGNMENT_FILL,
+ CLUTTER_BIN_ALIGNMENT_FILL);
+ clutter_actor_set_layout_manager (CLUTTER_ACTOR (self), layout_manager);
+
+ /* Set the icon size to -1 here to make sure we apply the scale to the
+ * default size on the first "style-changed" signal. */
+ self->priv->icon_size = -1;
+ self->priv->prop_icon_size = -1;
+
+ self->priv->shadow_pipeline = NULL;
+}
+
+static void
+st_icon_clear_shadow_pipeline (StIcon *icon)
+{
+ StIconPrivate *priv = icon->priv;
+
+ g_clear_pointer (&priv->shadow_pipeline, cogl_object_unref);
+ graphene_size_init (&priv->shadow_size, 0, 0);
+}
+
+static void
+st_icon_update_shadow_pipeline (StIcon *icon)
+{
+ StIconPrivate *priv = icon->priv;
+
+ if (priv->icon_texture && priv->shadow_spec)
+ {
+ ClutterActorBox box;
+ float width, height;
+
+ clutter_actor_get_allocation_box (CLUTTER_ACTOR (priv->icon_texture),
+ &box);
+ clutter_actor_box_get_size (&box, &width, &height);
+
+ if (priv->shadow_pipeline == NULL ||
+ priv->shadow_size.width != width ||
+ priv->shadow_size.height != height)
+ {
+ st_icon_clear_shadow_pipeline (icon);
+
+ priv->shadow_pipeline =
+ _st_create_shadow_pipeline_from_actor (priv->shadow_spec,
+ priv->icon_texture);
+
+ if (priv->shadow_pipeline)
+ graphene_size_init (&priv->shadow_size, width, height);
+ }
+ }
+}
+
+static void
+on_content_changed (ClutterActor *actor,
+ GParamSpec *pspec,
+ StIcon *icon)
+{
+ st_icon_clear_shadow_pipeline (icon);
+}
+
+static void
+st_icon_finish_update (StIcon *icon)
+{
+ StIconPrivate *priv = icon->priv;
+
+ if (priv->icon_texture)
+ {
+ clutter_actor_destroy (priv->icon_texture);
+ priv->icon_texture = NULL;
+ }
+
+ if (priv->pending_texture)
+ {
+ priv->icon_texture = priv->pending_texture;
+ priv->pending_texture = NULL;
+ clutter_actor_set_x_align (priv->icon_texture, CLUTTER_ACTOR_ALIGN_CENTER);
+ clutter_actor_set_y_align (priv->icon_texture, CLUTTER_ACTOR_ALIGN_CENTER);
+ clutter_actor_add_child (CLUTTER_ACTOR (icon), priv->icon_texture);
+
+ /* Remove the temporary ref we added */
+ g_object_unref (priv->icon_texture);
+
+ st_icon_clear_shadow_pipeline (icon);
+
+ g_signal_connect_object (priv->icon_texture, "notify::content",
+ G_CALLBACK (on_content_changed), icon, 0);
+ }
+
+ clutter_actor_queue_relayout (CLUTTER_ACTOR (icon));
+}
+
+static void
+opacity_changed_cb (GObject *object,
+ GParamSpec *pspec,
+ gpointer user_data)
+{
+ StIcon *icon = user_data;
+ StIconPrivate *priv = icon->priv;
+
+ g_clear_signal_handler (&priv->opacity_handler_id, priv->pending_texture);
+
+ st_icon_finish_update (icon);
+}
+
+static void
+st_icon_update (StIcon *icon)
+{
+ StIconPrivate *priv = icon->priv;
+ StThemeNode *theme_node;
+ StTextureCache *cache;
+ gint paint_scale;
+ ClutterActor *stage;
+ StThemeContext *context;
+ float resource_scale;
+
+ if (priv->pending_texture)
+ {
+ clutter_actor_destroy (priv->pending_texture);
+ g_object_unref (priv->pending_texture);
+ priv->pending_texture = NULL;
+ priv->opacity_handler_id = 0;
+ }
+
+ if (priv->gicon == NULL && priv->fallback_gicon == NULL)
+ {
+ g_clear_pointer (&priv->icon_texture, clutter_actor_destroy);
+ return;
+ }
+
+ priv->needs_update = TRUE;
+
+ theme_node = st_widget_peek_theme_node (ST_WIDGET (icon));
+ if (theme_node == NULL)
+ return;
+
+ if (priv->icon_size <= 0)
+ return;
+
+ resource_scale = clutter_actor_get_resource_scale (CLUTTER_ACTOR (icon));
+
+ stage = clutter_actor_get_stage (CLUTTER_ACTOR (icon));
+ context = st_theme_context_get_for_stage (CLUTTER_STAGE (stage));
+ g_object_get (context, "scale-factor", &paint_scale, NULL);
+
+ cache = st_texture_cache_get_default ();
+
+ if (priv->gicon != NULL)
+ priv->pending_texture = st_texture_cache_load_gicon (cache,
+ theme_node,
+ priv->gicon,
+ priv->icon_size / paint_scale,
+ paint_scale,
+ resource_scale);
+
+ if (priv->pending_texture == NULL && priv->fallback_gicon != NULL)
+ priv->pending_texture = st_texture_cache_load_gicon (cache,
+ theme_node,
+ priv->fallback_gicon,
+ priv->icon_size / paint_scale,
+ paint_scale,
+ resource_scale);
+
+ if (priv->pending_texture == NULL)
+ priv->pending_texture = st_texture_cache_load_gicon (cache,
+ theme_node,
+ default_gicon,
+ priv->icon_size / paint_scale,
+ paint_scale,
+ resource_scale);
+ priv->needs_update = FALSE;
+
+ if (priv->pending_texture)
+ {
+ g_object_ref_sink (priv->pending_texture);
+
+ if (clutter_actor_get_opacity (priv->pending_texture) != 0 || priv->icon_texture == NULL)
+ {
+ /* This icon is ready for showing, or nothing else is already showing */
+ st_icon_finish_update (icon);
+ }
+ else
+ {
+ /* Will be shown when fully loaded */
+ priv->opacity_handler_id = g_signal_connect_object (priv->pending_texture, "notify::opacity", G_CALLBACK (opacity_changed_cb), icon, 0);
+ }
+ }
+ else if (priv->icon_texture)
+ {
+ clutter_actor_destroy (priv->icon_texture);
+ priv->icon_texture = NULL;
+ }
+}
+
+static gboolean
+st_icon_update_icon_size (StIcon *icon)
+{
+ StIconPrivate *priv = icon->priv;
+ int new_size;
+ gint scale = 1;
+ ClutterActor *stage;
+ StThemeContext *context;
+
+ stage = clutter_actor_get_stage (CLUTTER_ACTOR (icon));
+ if (stage != NULL)
+ {
+ context = st_theme_context_get_for_stage (CLUTTER_STAGE (stage));
+ g_object_get (context, "scale-factor", &scale, NULL);
+ }
+
+ if (priv->prop_icon_size > 0)
+ new_size = priv->prop_icon_size * scale;
+ else if (priv->theme_icon_size > 0)
+ new_size = priv->theme_icon_size;
+ else
+ new_size = DEFAULT_ICON_SIZE * scale;
+
+ if (new_size != priv->icon_size)
+ {
+ priv->icon_size = new_size;
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+/**
+ * st_icon_new:
+ *
+ * Create a newly allocated #StIcon.
+ *
+ * Returns: A newly allocated #StIcon
+ */
+ClutterActor *
+st_icon_new (void)
+{
+ return g_object_new (ST_TYPE_ICON, NULL);
+}
+
+/**
+ * st_icon_get_icon_name:
+ * @icon: an #StIcon
+ *
+ * This is a convenience method to get the icon name of the current icon, if it
+ * is currenyly a #GThemedIcon, or %NULL otherwise.
+ *
+ * Returns: (transfer none) (nullable): The name of the icon or %NULL
+ */
+const gchar *
+st_icon_get_icon_name (StIcon *icon)
+{
+ StIconPrivate *priv;
+
+ g_return_val_if_fail (ST_IS_ICON (icon), NULL);
+
+ priv = icon->priv;
+
+ if (priv->gicon && G_IS_THEMED_ICON (priv->gicon))
+ return g_themed_icon_get_names (G_THEMED_ICON (priv->gicon)) [0];
+ else
+ return NULL;
+}
+
+/**
+ * st_icon_set_icon_name:
+ * @icon: an #StIcon
+ * @icon_name: (nullable): the name of the icon
+ *
+ * This is a convenience method to set the #GIcon to a #GThemedIcon created
+ * using the given icon name. If @icon_name is an empty string, %NULL or
+ * fails to load, the fallback icon will be shown.
+ */
+void
+st_icon_set_icon_name (StIcon *icon,
+ const gchar *icon_name)
+{
+ g_autoptr(GIcon) gicon = NULL;
+
+ g_return_if_fail (ST_IS_ICON (icon));
+
+ if (g_strcmp0 (icon_name, st_icon_get_icon_name (icon)) == 0)
+ return;
+
+ if (icon_name && *icon_name)
+ gicon = g_themed_icon_new_with_default_fallbacks (icon_name);
+
+ g_object_freeze_notify (G_OBJECT (icon));
+
+ st_icon_set_gicon (icon, gicon);
+ g_object_notify_by_pspec (G_OBJECT (icon), props[PROP_ICON_NAME]);
+
+ g_object_thaw_notify (G_OBJECT (icon));
+}
+
+/**
+ * st_icon_get_gicon:
+ * @icon: an #StIcon
+ *
+ * Gets the current #GIcon in use.
+ *
+ * Returns: (nullable) (transfer none): The current #GIcon, if set, otherwise %NULL
+ */
+GIcon *
+st_icon_get_gicon (StIcon *icon)
+{
+ g_return_val_if_fail (ST_IS_ICON (icon), NULL);
+
+ return icon->priv->gicon;
+}
+
+/**
+ * st_icon_set_gicon:
+ * @icon: an #StIcon
+ * @gicon: (nullable): a #GIcon
+ *
+ * Sets a #GIcon to show for the icon. If @gicon is %NULL or fails to load,
+ * the fallback icon set using st_icon_set_fallback_icon() will be shown.
+ */
+void
+st_icon_set_gicon (StIcon *icon, GIcon *gicon)
+{
+ g_return_if_fail (ST_IS_ICON (icon));
+ g_return_if_fail (gicon == NULL || G_IS_ICON (gicon));
+
+ if (g_icon_equal (icon->priv->gicon, gicon)) /* do nothing */
+ return;
+
+ g_set_object (&icon->priv->gicon, gicon);
+ g_object_notify_by_pspec (G_OBJECT (icon), props[PROP_GICON]);
+
+ st_icon_update (icon);
+}
+
+/**
+ * st_icon_get_fallback_gicon:
+ * @icon: a #StIcon
+ *
+ * Gets the currently set fallback #GIcon.
+ *
+ * Returns: (transfer none): The fallback #GIcon, if set, otherwise %NULL
+ */
+GIcon *
+st_icon_get_fallback_gicon (StIcon *icon)
+{
+ g_return_val_if_fail (ST_IS_ICON (icon), NULL);
+
+ return icon->priv->fallback_gicon;
+}
+
+/**
+ * st_icon_set_fallback_gicon:
+ * @icon: a #StIcon
+ * @fallback_gicon: (nullable): the fallback #GIcon
+ *
+ * Sets a fallback #GIcon to show if the normal icon fails to load.
+ * If @fallback_gicon is %NULL or fails to load, the icon is unset and no
+ * texture will be visible for the fallback icon.
+ */
+void
+st_icon_set_fallback_gicon (StIcon *icon,
+ GIcon *fallback_gicon)
+{
+ g_return_if_fail (ST_IS_ICON (icon));
+ g_return_if_fail (fallback_gicon == NULL || G_IS_ICON (fallback_gicon));
+
+ if (g_icon_equal (icon->priv->fallback_gicon, fallback_gicon))
+ return;
+
+ g_set_object (&icon->priv->fallback_gicon, fallback_gicon);
+ g_object_notify_by_pspec (G_OBJECT (icon), props[PROP_FALLBACK_GICON]);
+
+ st_icon_update (icon);
+}
+
+/**
+ * st_icon_get_icon_size:
+ * @icon: an #StIcon
+ *
+ * Gets the explicit size set using st_icon_set_icon_size() for the icon.
+ * This is not necessarily the size that the icon will be displayed at.
+ *
+ * Returns: The explicitly set size, or -1 if no size has been set
+ */
+gint
+st_icon_get_icon_size (StIcon *icon)
+{
+ g_return_val_if_fail (ST_IS_ICON (icon), -1);
+
+ return icon->priv->prop_icon_size;
+}
+
+/**
+ * st_icon_set_icon_size:
+ * @icon: an #StIcon
+ * @size: if positive, the new size, otherwise the size will be
+ * derived from the current style
+ *
+ * Sets an explicit size for the icon. Setting @size to -1 will use the size
+ * defined by the current style or the default icon size.
+ */
+void
+st_icon_set_icon_size (StIcon *icon,
+ gint size)
+{
+ StIconPrivate *priv;
+
+ g_return_if_fail (ST_IS_ICON (icon));
+
+ priv = icon->priv;
+ if (priv->prop_icon_size != size)
+ {
+ priv->prop_icon_size = size;
+ if (st_icon_update_icon_size (icon))
+ st_icon_update (icon);
+ g_object_notify_by_pspec (G_OBJECT (icon), props[PROP_ICON_SIZE]);
+ }
+}
+
+/**
+ * st_icon_get_fallback_icon_name:
+ * @icon: an #StIcon
+ *
+ * This is a convenience method to get the icon name of the fallback
+ * #GThemedIcon that is currently set.
+ *
+ * Returns: (transfer none): The name of the icon or %NULL if no icon is set
+ */
+const gchar *
+st_icon_get_fallback_icon_name (StIcon *icon)
+{
+ StIconPrivate *priv;
+
+ g_return_val_if_fail (ST_IS_ICON (icon), NULL);
+
+ priv = icon->priv;
+
+ if (priv->fallback_gicon && G_IS_THEMED_ICON (priv->fallback_gicon))
+ return g_themed_icon_get_names (G_THEMED_ICON (priv->fallback_gicon)) [0];
+ else
+ return NULL;
+}
+
+/**
+ * st_icon_set_fallback_icon_name:
+ * @icon: an #StIcon
+ * @fallback_icon_name: (nullable): the name of the fallback icon
+ *
+ * This is a convenience method to set the fallback #GIcon to a #GThemedIcon
+ * created using the given icon name. If @fallback_icon_name is an empty
+ * string, %NULL or fails to load, the icon is unset and no texture will
+ * be visible for the fallback icon.
+ */
+void
+st_icon_set_fallback_icon_name (StIcon *icon,
+ const gchar *fallback_icon_name)
+{
+ g_autoptr(GIcon) gicon = NULL;
+
+ g_return_if_fail (ST_IS_ICON (icon));
+
+ if (g_strcmp0 (fallback_icon_name, st_icon_get_fallback_icon_name (icon)) == 0)
+ return;
+
+ if (fallback_icon_name && *fallback_icon_name)
+ gicon = g_themed_icon_new_with_default_fallbacks (fallback_icon_name);
+
+ g_object_freeze_notify (G_OBJECT (icon));
+
+ st_icon_set_fallback_gicon (icon, gicon);
+ g_object_notify_by_pspec (G_OBJECT (icon), props[PROP_FALLBACK_ICON_NAME]);
+
+ g_object_thaw_notify (G_OBJECT (icon));
+}
diff --git a/src/st/st-icon.h b/src/st/st-icon.h
new file mode 100644
index 0000000..8714ef9
--- /dev/null
+++ b/src/st/st-icon.h
@@ -0,0 +1,82 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * st-icon.h: icon widget
+ *
+ * Copyright 2009, 2010 Intel Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU Lesser General Public License,
+ * version 2.1, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Written by: Thomas Wood <thomas.wood@intel.com>
+ *
+ */
+
+#if !defined(ST_H_INSIDE) && !defined(ST_COMPILATION)
+#error "Only <st/st.h> can be included directly.h"
+#endif
+
+#ifndef _ST_ICON
+#define _ST_ICON
+
+#include <glib-object.h>
+#include <gio/gio.h>
+#include <st/st-widget.h>
+
+#include <st/st-types.h>
+
+G_BEGIN_DECLS
+
+#define ST_TYPE_ICON st_icon_get_type()
+G_DECLARE_FINAL_TYPE (StIcon, st_icon, ST, ICON, StWidget)
+
+typedef struct _StIconPrivate StIconPrivate;
+
+/**
+ * StIcon:
+ *
+ * The contents of this structure are private and should only be accessed
+ * through the public API.
+ */
+struct _StIcon {
+ /*< private >*/
+ StWidget parent;
+
+ StIconPrivate *priv;
+};
+
+ClutterActor* st_icon_new (void);
+
+GIcon *st_icon_get_gicon (StIcon *icon);
+void st_icon_set_gicon (StIcon *icon,
+ GIcon *gicon);
+
+GIcon *st_icon_get_fallback_gicon (StIcon *icon);
+void st_icon_set_fallback_gicon (StIcon *icon,
+ GIcon *fallback_gicon);
+
+const gchar *st_icon_get_icon_name (StIcon *icon);
+void st_icon_set_icon_name (StIcon *icon,
+ const gchar *icon_name);
+
+const gchar *st_icon_get_fallback_icon_name (StIcon *icon);
+void st_icon_set_fallback_icon_name (StIcon *icon,
+ const gchar *fallback_icon_name);
+
+gint st_icon_get_icon_size (StIcon *icon);
+void st_icon_set_icon_size (StIcon *icon,
+ gint size);
+
+G_END_DECLS
+
+#endif /* _ST_ICON */
+
diff --git a/src/st/st-image-content.c b/src/st/st-image-content.c
new file mode 100644
index 0000000..92f1c14
--- /dev/null
+++ b/src/st/st-image-content.c
@@ -0,0 +1,346 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * st-image-content.h: A content image with scaling support
+ *
+ * Copyright 2019 Canonical, Ltd
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation, either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "st-image-content.h"
+#include "st-private.h"
+
+struct _StImageContent
+{
+ /*< private >*/
+ ClutterImage parent_instance;
+};
+
+typedef struct _StImageContentPrivate StImageContentPrivate;
+struct _StImageContentPrivate
+{
+ int width;
+ int height;
+};
+
+enum
+{
+ PROP_0,
+ PROP_PREFERRED_WIDTH,
+ PROP_PREFERRED_HEIGHT,
+};
+
+static void clutter_content_interface_init (ClutterContentInterface *iface);
+static void g_icon_interface_init (GIconIface *iface);
+static void g_loadable_icon_interface_init (GLoadableIconIface *iface);
+
+G_DEFINE_TYPE_WITH_CODE (StImageContent, st_image_content, CLUTTER_TYPE_IMAGE,
+ G_ADD_PRIVATE (StImageContent)
+ G_IMPLEMENT_INTERFACE (CLUTTER_TYPE_CONTENT,
+ clutter_content_interface_init)
+ G_IMPLEMENT_INTERFACE (G_TYPE_ICON,
+ g_icon_interface_init)
+ G_IMPLEMENT_INTERFACE (G_TYPE_LOADABLE_ICON,
+ g_loadable_icon_interface_init))
+
+static void
+st_image_content_init (StImageContent *self)
+{
+}
+
+static void
+st_image_content_constructed (GObject *object)
+{
+ StImageContent *self = ST_IMAGE_CONTENT (object);
+ StImageContentPrivate *priv = st_image_content_get_instance_private (self);
+
+ if (priv->width < 0 || priv->height < 0)
+ g_warning ("StImageContent initialized with invalid preferred size: %dx%d\n",
+ priv->width, priv->height);
+}
+
+static void
+st_image_content_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ StImageContent *self = ST_IMAGE_CONTENT (object);
+ StImageContentPrivate *priv = st_image_content_get_instance_private (self);
+
+ switch (prop_id)
+ {
+ case PROP_PREFERRED_WIDTH:
+ g_value_set_int (value, priv->width);
+ break;
+
+ case PROP_PREFERRED_HEIGHT:
+ g_value_set_int (value, priv->height);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+st_image_content_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ StImageContent *self = ST_IMAGE_CONTENT (object);
+ StImageContentPrivate *priv = st_image_content_get_instance_private (self);
+
+ switch (prop_id)
+ {
+ case PROP_PREFERRED_WIDTH:
+ priv->width = g_value_get_int (value);
+ break;
+
+ case PROP_PREFERRED_HEIGHT:
+ priv->height = g_value_get_int (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+st_image_content_class_init (StImageContentClass *klass)
+{
+ GParamSpec *pspec;
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->constructed = st_image_content_constructed;
+ object_class->get_property = st_image_content_get_property;
+ object_class->set_property = st_image_content_set_property;
+
+ pspec = g_param_spec_int ("preferred-width",
+ "Preferred Width",
+ "Preferred Width of the Content when painted",
+ -1, G_MAXINT, -1,
+ G_PARAM_CONSTRUCT_ONLY | ST_PARAM_READWRITE);
+ g_object_class_install_property (object_class, PROP_PREFERRED_WIDTH, pspec);
+
+ pspec = g_param_spec_int ("preferred-height",
+ "Preferred Height",
+ "Preferred Height of the Content when painted",
+ -1, G_MAXINT, -1,
+ G_PARAM_CONSTRUCT_ONLY | ST_PARAM_READWRITE);
+ g_object_class_install_property (object_class, PROP_PREFERRED_HEIGHT, pspec);
+}
+
+static gboolean
+st_image_content_get_preferred_size (ClutterContent *content,
+ float *width,
+ float *height)
+{
+ StImageContent *self = ST_IMAGE_CONTENT (content);
+ StImageContentPrivate *priv = st_image_content_get_instance_private (self);
+ CoglTexture *texture;
+
+ texture = clutter_image_get_texture (CLUTTER_IMAGE (content));
+
+ if (texture == NULL)
+ return FALSE;
+
+ g_assert_cmpint (priv->width, >, -1);
+ g_assert_cmpint (priv->height, >, -1);
+
+ if (width != NULL)
+ *width = (float) priv->width;
+
+ if (height != NULL)
+ *height = (float) priv->height;
+
+ return TRUE;
+}
+
+static GdkPixbuf*
+pixbuf_from_image (StImageContent *image)
+{
+ CoglTexture *texture;
+ int width, height, rowstride;
+ uint8_t *data;
+
+ texture = clutter_image_get_texture (CLUTTER_IMAGE (image));
+ if (!texture || !cogl_texture_is_get_data_supported (texture))
+ return NULL;
+
+ width = cogl_texture_get_width (texture);
+ height = cogl_texture_get_width (texture);
+ rowstride = 4 * width;
+ data = g_new (uint8_t, rowstride * height);
+
+ cogl_texture_get_data (texture, COGL_PIXEL_FORMAT_RGBA_8888, rowstride, data);
+
+ return gdk_pixbuf_new_from_data ((const guchar *)data,
+ GDK_COLORSPACE_RGB,
+ TRUE, 8, width, height, rowstride,
+ (GdkPixbufDestroyNotify)g_free, NULL);
+}
+
+static void
+clutter_content_interface_init (ClutterContentInterface *iface)
+{
+ iface->get_preferred_size = st_image_content_get_preferred_size;
+}
+
+static guint
+st_image_content_hash (GIcon *icon)
+{
+ return g_direct_hash (icon);
+}
+
+static gboolean
+st_image_content_equal (GIcon *icon1,
+ GIcon *icon2)
+{
+ return g_direct_equal (icon1, icon2);
+}
+
+static GVariant *
+st_image_content_serialize (GIcon *icon)
+{
+ g_autoptr (GdkPixbuf) pixbuf = NULL;
+
+ pixbuf = pixbuf_from_image (ST_IMAGE_CONTENT (icon));
+ if (!pixbuf)
+ return NULL;
+
+ return g_icon_serialize (G_ICON (pixbuf));
+}
+
+static void
+g_icon_interface_init (GIconIface *iface)
+{
+ iface->hash = st_image_content_hash;
+ iface->equal = st_image_content_equal;
+ iface->serialize = st_image_content_serialize;
+}
+
+static GInputStream *
+st_image_load (GLoadableIcon *icon,
+ int size,
+ char **type,
+ GCancellable *cancellable,
+ GError **error)
+{
+ g_autoptr (GdkPixbuf) pixbuf = NULL;
+
+ pixbuf = pixbuf_from_image (ST_IMAGE_CONTENT (icon));
+ if (!pixbuf)
+ {
+ g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+ "Failed to read texture");
+ return NULL;
+ }
+
+ return g_loadable_icon_load (G_LOADABLE_ICON (pixbuf),
+ size, type, cancellable, error);
+}
+
+static void
+load_image_thread (GTask *task,
+ gpointer object,
+ gpointer task_data,
+ GCancellable *cancellable)
+{
+ GInputStream *stream;
+ GError *error = NULL;
+ char *type;
+
+ stream = st_image_load (G_LOADABLE_ICON (object),
+ GPOINTER_TO_INT (task_data),
+ &type,
+ cancellable,
+ &error);
+
+ if (error)
+ {
+ g_task_return_error (task, error);
+ }
+ else
+ {
+ g_task_set_task_data (task, type, g_free);
+ g_task_return_pointer (task, stream, g_object_unref);
+ }
+}
+
+static void
+st_image_load_async (GLoadableIcon *icon,
+ int size,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ g_autoptr (GTask) task = NULL;
+
+ task = g_task_new (icon, cancellable, callback, user_data);
+ g_task_set_task_data (task, GINT_TO_POINTER (size), NULL);
+ g_task_run_in_thread (task, load_image_thread);
+}
+
+static GInputStream *
+st_image_load_finish (GLoadableIcon *icon,
+ GAsyncResult *res,
+ char **type,
+ GError **error)
+{
+ GInputStream *stream;
+
+ stream = g_task_propagate_pointer (G_TASK (res), error);
+ if (!stream)
+ return NULL;
+
+ if (type)
+ *type = g_strdup (g_task_get_task_data (G_TASK (res)));
+
+ return stream;
+}
+
+static void
+g_loadable_icon_interface_init (GLoadableIconIface *iface)
+{
+ iface->load = st_image_load;
+ iface->load_async = st_image_load_async;
+ iface->load_finish = st_image_load_finish;
+}
+
+/**
+ * st_image_content_new_with_preferred_size:
+ * @width: The preferred width to be used when drawing the content
+ * @height: The preferred width to be used when drawing the content
+ *
+ * Creates a new #StImageContent, a simple content for sized images.
+ *
+ * See #ClutterImage for setting the actual image to display or #StIcon for
+ * displaying icons.
+ *
+ * Returns: (transfer full): the newly created #StImageContent content
+ * Use g_object_unref() when done.
+ */
+ClutterContent *
+st_image_content_new_with_preferred_size (int width,
+ int height)
+{
+ return g_object_new (ST_TYPE_IMAGE_CONTENT,
+ "preferred-width", width,
+ "preferred-height", height,
+ NULL);
+}
diff --git a/src/st/st-image-content.h b/src/st/st-image-content.h
new file mode 100644
index 0000000..0ebb0b7
--- /dev/null
+++ b/src/st/st-image-content.h
@@ -0,0 +1,33 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * st-image-content.h: A content image with scaling support
+ *
+ * Copyright 2019 Canonical, Ltd
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation, either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __ST_IMAGE_CONTENT_H__
+#define __ST_IMAGE_CONTENT_H__
+
+#include <clutter/clutter.h>
+
+#define ST_TYPE_IMAGE_CONTENT (st_image_content_get_type ())
+G_DECLARE_FINAL_TYPE (StImageContent, st_image_content,
+ ST, IMAGE_CONTENT, ClutterImage)
+
+ClutterContent *st_image_content_new_with_preferred_size (int width,
+ int height);
+
+#endif /* __ST_IMAGE_CONTENT_H__ */
diff --git a/src/st/st-label.c b/src/st/st-label.c
new file mode 100644
index 0000000..fe77743
--- /dev/null
+++ b/src/st/st-label.c
@@ -0,0 +1,549 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * st-label.c: Plain label actor
+ *
+ * Copyright 2008,2009 Intel Corporation
+ * Copyright 2009 Red Hat, Inc.
+ * Copyright 2010 Florian Müllner
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU Lesser General Public License,
+ * version 2.1, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * SECTION:st-label
+ * @short_description: Widget for displaying text
+ *
+ * #StLabel is a simple widget for displaying text. It derives from
+ * #StWidget to add extra style and placement functionality over
+ * #ClutterText. The internal #ClutterText is publicly accessibly to allow
+ * applications to set further properties.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <stdlib.h>
+#include <string.h>
+
+#include <glib.h>
+
+#include <clutter/clutter.h>
+
+#include "st-label.h"
+#include "st-private.h"
+#include "st-widget.h"
+
+#include <st/st-widget-accessible.h>
+
+enum
+{
+ PROP_0,
+
+ PROP_CLUTTER_TEXT,
+ PROP_TEXT,
+
+ N_PROPS
+};
+
+static GParamSpec *props[N_PROPS] = { NULL, };
+
+struct _StLabelPrivate
+{
+ ClutterActor *label;
+
+ StShadow *shadow_spec;
+
+ CoglPipeline *text_shadow_pipeline;
+ float shadow_width;
+ float shadow_height;
+};
+
+G_DEFINE_TYPE_WITH_PRIVATE (StLabel, st_label, ST_TYPE_WIDGET);
+
+static GType st_label_accessible_get_type (void) G_GNUC_CONST;
+
+static void
+st_label_set_property (GObject *gobject,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ StLabel *label = ST_LABEL (gobject);
+
+ switch (prop_id)
+ {
+ case PROP_TEXT:
+ st_label_set_text (label, g_value_get_string (value));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+st_label_get_property (GObject *gobject,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ StLabelPrivate *priv = ST_LABEL (gobject)->priv;
+
+ switch (prop_id)
+ {
+ case PROP_CLUTTER_TEXT:
+ g_value_set_object (value, priv->label);
+ break;
+
+ case PROP_TEXT:
+ g_value_set_string (value, clutter_text_get_text (CLUTTER_TEXT (priv->label)));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+st_label_style_changed (StWidget *self)
+{
+ StLabelPrivate *priv = ST_LABEL(self)->priv;
+ StThemeNode *theme_node;
+ StShadow *shadow_spec;
+
+ theme_node = st_widget_get_theme_node (self);
+
+ shadow_spec = st_theme_node_get_text_shadow (theme_node);
+ if (!priv->shadow_spec || !shadow_spec ||
+ !st_shadow_equal (shadow_spec, priv->shadow_spec))
+ {
+ g_clear_pointer (&priv->text_shadow_pipeline, cogl_object_unref);
+
+ g_clear_pointer (&priv->shadow_spec, st_shadow_unref);
+ if (shadow_spec)
+ priv->shadow_spec = st_shadow_ref (shadow_spec);
+ }
+
+ _st_set_text_from_style ((ClutterText *)priv->label, st_widget_get_theme_node (self));
+
+ ST_WIDGET_CLASS (st_label_parent_class)->style_changed (self);
+}
+
+static void
+st_label_get_preferred_width (ClutterActor *actor,
+ gfloat for_height,
+ gfloat *min_width_p,
+ gfloat *natural_width_p)
+{
+ StLabelPrivate *priv = ST_LABEL (actor)->priv;
+ StThemeNode *theme_node = st_widget_get_theme_node (ST_WIDGET (actor));
+
+ st_theme_node_adjust_for_height (theme_node, &for_height);
+
+ clutter_actor_get_preferred_width (priv->label, for_height,
+ min_width_p,
+ natural_width_p);
+
+ st_theme_node_adjust_preferred_width (theme_node, min_width_p, natural_width_p);
+}
+
+static void
+st_label_get_preferred_height (ClutterActor *actor,
+ gfloat for_width,
+ gfloat *min_height_p,
+ gfloat *natural_height_p)
+{
+ StLabelPrivate *priv = ST_LABEL (actor)->priv;
+ StThemeNode *theme_node = st_widget_get_theme_node (ST_WIDGET (actor));
+
+ st_theme_node_adjust_for_width (theme_node, &for_width);
+
+ clutter_actor_get_preferred_height (priv->label, for_width,
+ min_height_p,
+ natural_height_p);
+
+ st_theme_node_adjust_preferred_height (theme_node, min_height_p, natural_height_p);
+}
+
+static void
+st_label_allocate (ClutterActor *actor,
+ const ClutterActorBox *box)
+{
+ StLabelPrivate *priv = ST_LABEL (actor)->priv;
+ StThemeNode *theme_node = st_widget_get_theme_node (ST_WIDGET (actor));
+ ClutterActorBox content_box;
+
+ clutter_actor_set_allocation (actor, box);
+
+ st_theme_node_get_content_box (theme_node, box, &content_box);
+
+ clutter_actor_allocate (priv->label, &content_box);
+}
+
+static void
+st_label_dispose (GObject *object)
+{
+ StLabelPrivate *priv = ST_LABEL (object)->priv;
+
+ priv->label = NULL;
+ g_clear_pointer (&priv->text_shadow_pipeline, cogl_object_unref);
+
+ G_OBJECT_CLASS (st_label_parent_class)->dispose (object);
+}
+
+static void
+st_label_paint (ClutterActor *actor,
+ ClutterPaintContext *paint_context)
+{
+ StLabelPrivate *priv = ST_LABEL (actor)->priv;
+
+ st_widget_paint_background (ST_WIDGET (actor), paint_context);
+
+ if (priv->shadow_spec)
+ {
+ ClutterActorBox allocation;
+ float width, height;
+ float resource_scale;
+
+ clutter_actor_get_allocation_box (priv->label, &allocation);
+ clutter_actor_box_get_size (&allocation, &width, &height);
+
+ resource_scale = clutter_actor_get_resource_scale (priv->label);
+
+ width *= resource_scale;
+ height *= resource_scale;
+
+ if (priv->text_shadow_pipeline == NULL ||
+ width != priv->shadow_width ||
+ height != priv->shadow_height)
+ {
+ g_clear_pointer (&priv->text_shadow_pipeline, cogl_object_unref);
+
+ priv->shadow_width = width;
+ priv->shadow_height = height;
+ priv->text_shadow_pipeline =
+ _st_create_shadow_pipeline_from_actor (priv->shadow_spec,
+ priv->label);
+ }
+
+ if (priv->text_shadow_pipeline != NULL)
+ {
+ CoglFramebuffer *framebuffer;
+
+ framebuffer =
+ clutter_paint_context_get_framebuffer (paint_context);
+ _st_paint_shadow_with_opacity (priv->shadow_spec,
+ framebuffer,
+ priv->text_shadow_pipeline,
+ &allocation,
+ clutter_actor_get_paint_opacity (priv->label));
+ }
+ }
+
+ clutter_actor_paint (priv->label, paint_context);
+}
+
+static void
+st_label_resource_scale_changed (ClutterActor *actor)
+{
+ StLabelPrivate *priv = ST_LABEL (actor)->priv;
+
+ g_clear_pointer (&priv->text_shadow_pipeline, cogl_object_unref);
+
+ if (CLUTTER_ACTOR_CLASS (st_label_parent_class)->resource_scale_changed)
+ CLUTTER_ACTOR_CLASS (st_label_parent_class)->resource_scale_changed (actor);
+}
+
+static void
+st_label_class_init (StLabelClass *klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+ ClutterActorClass *actor_class = CLUTTER_ACTOR_CLASS (klass);
+ StWidgetClass *widget_class = ST_WIDGET_CLASS (klass);
+
+ gobject_class->set_property = st_label_set_property;
+ gobject_class->get_property = st_label_get_property;
+ gobject_class->dispose = st_label_dispose;
+
+ actor_class->paint = st_label_paint;
+ actor_class->allocate = st_label_allocate;
+ actor_class->get_preferred_width = st_label_get_preferred_width;
+ actor_class->get_preferred_height = st_label_get_preferred_height;
+ actor_class->resource_scale_changed = st_label_resource_scale_changed;
+
+ widget_class->style_changed = st_label_style_changed;
+ widget_class->get_accessible_type = st_label_accessible_get_type;
+
+ /**
+ * StLabel:clutter-text:
+ *
+ * The internal #ClutterText actor supporting the label
+ */
+ props[PROP_CLUTTER_TEXT] =
+ g_param_spec_object ("clutter-text",
+ "Clutter Text",
+ "Internal ClutterText actor",
+ CLUTTER_TYPE_TEXT,
+ ST_PARAM_READABLE);
+
+ /**
+ * StLabel:text:
+ *
+ * The current text being display in the #StLabel.
+ */
+ props[PROP_TEXT] =
+ g_param_spec_string ("text",
+ "Text",
+ "Text of the label",
+ NULL,
+ ST_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
+
+ g_object_class_install_properties (gobject_class, N_PROPS, props);
+}
+
+static void
+invalidate_shadow_pipeline (GObject *object,
+ GParamSpec *pspec,
+ StLabel *label)
+{
+ StLabelPrivate *priv = st_label_get_instance_private (label);
+
+ g_clear_pointer (&priv->text_shadow_pipeline, cogl_object_unref);
+}
+
+static void
+st_label_init (StLabel *label)
+{
+ ClutterActor *actor = CLUTTER_ACTOR (label);
+ StLabelPrivate *priv;
+
+ label->priv = priv = st_label_get_instance_private (label);
+
+ label->priv->label = g_object_new (CLUTTER_TYPE_TEXT,
+ "ellipsize", PANGO_ELLIPSIZE_END,
+ NULL);
+ label->priv->text_shadow_pipeline = NULL;
+ label->priv->shadow_width = -1.;
+ label->priv->shadow_height = -1.;
+
+ /* These properties might get set from CSS using _st_set_text_from_style */
+ g_signal_connect (priv->label, "notify::font-description",
+ G_CALLBACK (invalidate_shadow_pipeline), label);
+
+ g_signal_connect (priv->label, "notify::attributes",
+ G_CALLBACK (invalidate_shadow_pipeline), label);
+
+ g_signal_connect (priv->label, "notify::justify",
+ G_CALLBACK (invalidate_shadow_pipeline), label);
+
+ g_signal_connect (priv->label, "notify::line-alignment",
+ G_CALLBACK (invalidate_shadow_pipeline), label);
+
+ clutter_actor_add_child (actor, priv->label);
+
+ clutter_actor_set_offscreen_redirect (actor,
+ CLUTTER_OFFSCREEN_REDIRECT_ALWAYS);
+}
+
+/**
+ * st_label_new:
+ * @text: (nullable): text to set the label to
+ *
+ * Create a new #StLabel with the label specified by @text.
+ *
+ * Returns: a new #StLabel
+ */
+StWidget *
+st_label_new (const gchar *text)
+{
+ if (text == NULL || *text == '\0')
+ return g_object_new (ST_TYPE_LABEL, NULL);
+ else
+ return g_object_new (ST_TYPE_LABEL,
+ "text", text,
+ NULL);
+}
+
+/**
+ * st_label_get_text:
+ * @label: a #StLabel
+ *
+ * Get the text displayed on the label.
+ *
+ * Returns: (transfer none): the text for the label. This must not be freed by
+ * the application
+ */
+const gchar *
+st_label_get_text (StLabel *label)
+{
+ g_return_val_if_fail (ST_IS_LABEL (label), NULL);
+
+ return clutter_text_get_text (CLUTTER_TEXT (label->priv->label));
+}
+
+/**
+ * st_label_set_text:
+ * @label: a #StLabel
+ * @text: (nullable): text to set the label to
+ *
+ * Sets the text displayed by the label.
+ */
+void
+st_label_set_text (StLabel *label,
+ const gchar *text)
+{
+ StLabelPrivate *priv;
+ ClutterText *ctext;
+
+ g_return_if_fail (ST_IS_LABEL (label));
+
+ priv = label->priv;
+ ctext = CLUTTER_TEXT (priv->label);
+
+ if (clutter_text_get_editable (ctext) ||
+ g_strcmp0 (clutter_text_get_text (ctext), text) != 0)
+ {
+ g_clear_pointer (&priv->text_shadow_pipeline, cogl_object_unref);
+
+ clutter_text_set_text (ctext, text);
+
+ g_object_notify_by_pspec (G_OBJECT (label), props[PROP_TEXT]);
+ }
+}
+
+/**
+ * st_label_get_clutter_text:
+ * @label: a #StLabel
+ *
+ * Retrieve the internal #ClutterText used by @label so that extra parameters
+ * can be set.
+ *
+ * Returns: (transfer none): the #ClutterText used by #StLabel. The actor
+ * is owned by the #StLabel and should not be destroyed by the application.
+ */
+ClutterActor*
+st_label_get_clutter_text (StLabel *label)
+{
+ g_return_val_if_fail (ST_LABEL (label), NULL);
+
+ return label->priv->label;
+}
+
+
+/******************************************************************************/
+/*************************** ACCESSIBILITY SUPPORT ****************************/
+/******************************************************************************/
+
+#define ST_TYPE_LABEL_ACCESSIBLE st_label_accessible_get_type ()
+
+#define ST_LABEL_ACCESSIBLE(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), \
+ ST_TYPE_LABEL_ACCESSIBLE, StLabelAccessible))
+
+#define ST_IS_LABEL_ACCESSIBLE(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), \
+ ST_TYPE_LABEL_ACCESSIBLE))
+
+#define ST_LABEL_ACCESSIBLE_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), \
+ ST_TYPE_LABEL_ACCESSIBLE, StLabelAccessibleClass))
+
+#define ST_IS_LABEL_ACCESSIBLE_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), \
+ ST_TYPE_LABEL_ACCESSIBLE))
+
+#define ST_LABEL_ACCESSIBLE_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), \
+ ST_TYPE_LABEL_ACCESSIBLE, StLabelAccessibleClass))
+
+typedef struct _StLabelAccessible StLabelAccessible;
+typedef struct _StLabelAccessibleClass StLabelAccessibleClass;
+
+struct _StLabelAccessible
+{
+ StWidgetAccessible parent;
+};
+
+struct _StLabelAccessibleClass
+{
+ StWidgetAccessibleClass parent_class;
+};
+
+/* AtkObject */
+static void st_label_accessible_initialize (AtkObject *obj,
+ gpointer data);
+static const gchar * st_label_accessible_get_name (AtkObject *obj);
+
+G_DEFINE_TYPE (StLabelAccessible, st_label_accessible, ST_TYPE_WIDGET_ACCESSIBLE)
+
+static void
+st_label_accessible_class_init (StLabelAccessibleClass *klass)
+{
+ AtkObjectClass *atk_class = ATK_OBJECT_CLASS (klass);
+
+ atk_class->initialize = st_label_accessible_initialize;
+ atk_class->get_name = st_label_accessible_get_name;
+}
+
+static void
+st_label_accessible_init (StLabelAccessible *self)
+{
+ /* initialization done on AtkObject->initialize */
+}
+
+static void
+label_text_notify_cb (StLabel *label,
+ GParamSpec *pspec,
+ AtkObject *accessible)
+{
+ g_object_notify (G_OBJECT (accessible), "accessible-name");
+}
+
+static void
+st_label_accessible_initialize (AtkObject *obj,
+ gpointer data)
+{
+ ATK_OBJECT_CLASS (st_label_accessible_parent_class)->initialize (obj, data);
+
+ g_signal_connect (data, "notify::text",
+ G_CALLBACK (label_text_notify_cb),
+ obj);
+
+ obj->role = ATK_ROLE_LABEL;
+}
+
+static const gchar *
+st_label_accessible_get_name (AtkObject *obj)
+{
+ const gchar *name = NULL;
+
+ g_return_val_if_fail (ST_IS_LABEL_ACCESSIBLE (obj), NULL);
+
+ name = ATK_OBJECT_CLASS (st_label_accessible_parent_class)->get_name (obj);
+ if (name == NULL)
+ {
+ ClutterActor *actor = NULL;
+
+ actor = CLUTTER_ACTOR (atk_gobject_accessible_get_object (ATK_GOBJECT_ACCESSIBLE (obj)));
+
+ if (actor == NULL || st_widget_has_style_class_name (ST_WIDGET (actor), "hidden"))
+ name = NULL;
+ else
+ name = st_label_get_text (ST_LABEL (actor));
+ }
+
+ return name;
+}
diff --git a/src/st/st-label.h b/src/st/st-label.h
new file mode 100644
index 0000000..456ad31
--- /dev/null
+++ b/src/st/st-label.h
@@ -0,0 +1,58 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * st-label.h: Plain label actor
+ *
+ * Copyright 2008, 2009 Intel Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU Lesser General Public License,
+ * version 2.1, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#if !defined(ST_H_INSIDE) && !defined(ST_COMPILATION)
+#error "Only <st/st.h> can be included directly.h"
+#endif
+
+#ifndef __ST_LABEL_H__
+#define __ST_LABEL_H__
+
+G_BEGIN_DECLS
+
+#include <st/st-widget.h>
+
+#define ST_TYPE_LABEL (st_label_get_type ())
+G_DECLARE_FINAL_TYPE (StLabel, st_label, ST, LABEL, StWidget)
+
+typedef struct _StLabelPrivate StLabelPrivate;
+
+/**
+ * StLabel:
+ *
+ * The contents of this structure is private and should only be accessed using
+ * the provided API.
+ */
+struct _StLabel
+{
+ /*< private >*/
+ StWidget parent_instance;
+
+ StLabelPrivate *priv;
+};
+
+StWidget * st_label_new (const gchar *text);
+const gchar * st_label_get_text (StLabel *label);
+void st_label_set_text (StLabel *label,
+ const gchar *text);
+ClutterActor * st_label_get_clutter_text (StLabel *label);
+
+G_END_DECLS
+
+#endif /* __ST_LABEL_H__ */
diff --git a/src/st/st-password-entry.c b/src/st/st-password-entry.c
new file mode 100644
index 0000000..43f9b52
--- /dev/null
+++ b/src/st/st-password-entry.c
@@ -0,0 +1,363 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * st-password-entry.c: Password entry actor based on st-entry
+ *
+ * Copyright 2019 Endless Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU Lesser General Public License,
+ * version 2.1, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "st-private.h"
+#include "st-password-entry.h"
+#include "st-icon.h"
+#include "st-settings.h"
+
+#define BLACK_CIRCLE 9679
+
+#define ST_PASSWORD_ENTRY_PRIV(x) st_password_entry_get_instance_private ((StPasswordEntry *) x)
+
+typedef struct _StPasswordEntryPrivate StPasswordEntryPrivate;
+
+struct _StPasswordEntry
+{
+ /*< private >*/
+ StEntry parent_instance;
+};
+
+struct _StPasswordEntryPrivate
+{
+ ClutterActor *peek_password_icon;
+
+ gboolean password_visible;
+ gboolean show_peek_icon;
+};
+
+enum
+{
+ PROP_0,
+
+ PROP_PASSWORD_VISIBLE,
+ PROP_SHOW_PEEK_ICON,
+
+ N_PROPS
+};
+
+static GParamSpec *props[N_PROPS] = { NULL, };
+
+G_DEFINE_TYPE_WITH_PRIVATE (StPasswordEntry, st_password_entry, ST_TYPE_ENTRY);
+
+static gboolean
+show_password_locked_down (StPasswordEntry *entry)
+{
+ gboolean disable_show_password = FALSE;
+
+ g_object_get (st_settings_get (), "disable-show-password", &disable_show_password, NULL);
+
+ return disable_show_password;
+}
+
+static void
+st_password_entry_secondary_icon_clicked (StEntry *entry)
+{
+ StPasswordEntry *password_entry = ST_PASSWORD_ENTRY (entry);
+ StPasswordEntryPrivate *priv = ST_PASSWORD_ENTRY_PRIV (password_entry);
+
+ st_password_entry_set_password_visible (password_entry, !priv->password_visible);
+}
+
+static void
+st_password_entry_get_property (GObject *gobject,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ StPasswordEntry *entry = ST_PASSWORD_ENTRY (gobject);
+ StPasswordEntryPrivate *priv = ST_PASSWORD_ENTRY_PRIV (gobject);
+
+ switch (prop_id)
+ {
+ case PROP_PASSWORD_VISIBLE:
+ g_value_set_boolean (value, priv->password_visible);
+ break;
+
+ case PROP_SHOW_PEEK_ICON:
+ g_value_set_boolean (value, st_password_entry_get_show_peek_icon (entry));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+st_password_entry_set_property (GObject *gobject,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ StPasswordEntry *entry = ST_PASSWORD_ENTRY (gobject);
+
+ switch (prop_id)
+ {
+ case PROP_PASSWORD_VISIBLE:
+ st_password_entry_set_password_visible (entry, g_value_get_boolean (value));
+ break;
+
+ case PROP_SHOW_PEEK_ICON:
+ st_password_entry_set_show_peek_icon (entry, g_value_get_boolean (value));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+st_password_entry_dispose (GObject *gobject)
+{
+ StPasswordEntryPrivate *priv = ST_PASSWORD_ENTRY_PRIV (gobject);
+
+ g_clear_object (&priv->peek_password_icon);
+
+ G_OBJECT_CLASS(st_password_entry_parent_class)->dispose (gobject);
+}
+
+static void
+st_password_entry_class_init (StPasswordEntryClass *klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+ StEntryClass *st_entry_class = ST_ENTRY_CLASS (klass);
+
+ gobject_class->get_property = st_password_entry_get_property;
+ gobject_class->set_property = st_password_entry_set_property;
+ gobject_class->dispose = st_password_entry_dispose;
+
+ st_entry_class->secondary_icon_clicked = st_password_entry_secondary_icon_clicked;
+
+ /**
+ * StPasswordEntry:password-visible:
+ *
+ * Whether the text in the entry is masked for privacy.
+ */
+ props[PROP_PASSWORD_VISIBLE] = g_param_spec_boolean ("password-visible",
+ "Password visible",
+ "Whether the text in the entry is masked or not",
+ FALSE,
+ ST_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
+
+ /**
+ * StPasswordEntry:show-peek-icon:
+ *
+ * Whether to display an icon button to toggle the masking enabled by the
+ * #StPasswordEntry:password-visible property.
+ */
+ props[PROP_SHOW_PEEK_ICON] = g_param_spec_boolean ("show-peek-icon",
+ "Show peek icon",
+ "Whether to show the password peek icon",
+ TRUE,
+ ST_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
+
+ g_object_class_install_properties (gobject_class, N_PROPS, props);
+}
+
+static void
+update_peek_icon (StPasswordEntry *entry)
+{
+ StPasswordEntryPrivate *priv = ST_PASSWORD_ENTRY_PRIV (entry);
+ gboolean show_peek_icon;
+
+ show_peek_icon = st_password_entry_get_show_peek_icon (entry);
+
+ if (show_peek_icon)
+ st_entry_set_secondary_icon (ST_ENTRY (entry), priv->peek_password_icon);
+ else
+ st_entry_set_secondary_icon (ST_ENTRY (entry), NULL);
+}
+
+static void
+on_disable_show_password_changed (GObject *object,
+ GParamSpec *pspec,
+ gpointer user_data)
+{
+ StPasswordEntry *entry = ST_PASSWORD_ENTRY (user_data);
+
+ if (show_password_locked_down (entry))
+ st_password_entry_set_password_visible (entry, FALSE);
+
+ update_peek_icon (entry);
+
+ g_object_notify_by_pspec (G_OBJECT (entry), props[PROP_SHOW_PEEK_ICON]);
+}
+
+static void
+clutter_text_password_char_cb (GObject *object,
+ GParamSpec *pspec,
+ gpointer user_data)
+{
+ StPasswordEntry *entry = ST_PASSWORD_ENTRY (user_data);
+ ClutterActor *clutter_text;
+
+ clutter_text = st_entry_get_clutter_text (ST_ENTRY (entry));
+ if (clutter_text_get_password_char (CLUTTER_TEXT (clutter_text)) == 0)
+ st_password_entry_set_password_visible (entry, TRUE);
+ else
+ st_password_entry_set_password_visible (entry, FALSE);
+}
+
+static void
+st_password_entry_init (StPasswordEntry *entry)
+{
+ StPasswordEntryPrivate *priv = ST_PASSWORD_ENTRY_PRIV (entry);
+ ClutterActor *clutter_text;
+
+ priv->peek_password_icon = g_object_new (ST_TYPE_ICON,
+ "style-class", "peek-password",
+ "icon-name", "view-reveal-symbolic",
+ NULL);
+ st_entry_set_secondary_icon (ST_ENTRY (entry), priv->peek_password_icon);
+
+ st_password_entry_set_show_peek_icon (entry, TRUE);
+
+ g_signal_connect_object (st_settings_get (),
+ "notify::disable-show-password",
+ G_CALLBACK (on_disable_show_password_changed),
+ entry,
+ 0);
+
+ clutter_text = st_entry_get_clutter_text (ST_ENTRY (entry));
+ clutter_text_set_password_char (CLUTTER_TEXT (clutter_text), BLACK_CIRCLE);
+
+ st_entry_set_input_purpose (ST_ENTRY (entry), CLUTTER_INPUT_CONTENT_PURPOSE_PASSWORD);
+
+ g_signal_connect (clutter_text, "notify::password-char",
+ G_CALLBACK (clutter_text_password_char_cb), entry);
+}
+
+/**
+ * st_password_entry_new:
+ *
+ * Create a new #StPasswordEntry.
+ *
+ * Returns: a new #StEntry
+ */
+StEntry*
+st_password_entry_new (void)
+{
+ return ST_ENTRY (g_object_new (ST_TYPE_PASSWORD_ENTRY, NULL));
+}
+
+/**
+ * st_password_entry_set_show_peek_icon:
+ * @entry: a #StPasswordEntry
+ * @value: %TRUE to show the peek-icon in the entry
+ *
+ * Sets whether to show or hide the peek-icon in the password entry. If %TRUE,
+ * a icon button for temporarily unmasking the password will be shown at the
+ * end of the entry.
+ */
+void
+st_password_entry_set_show_peek_icon (StPasswordEntry *entry,
+ gboolean value)
+{
+ StPasswordEntryPrivate *priv;
+
+ g_return_if_fail (ST_IS_PASSWORD_ENTRY (entry));
+
+ priv = ST_PASSWORD_ENTRY_PRIV (entry);
+ if (priv->show_peek_icon == value)
+ return;
+
+ priv->show_peek_icon = value;
+
+ update_peek_icon (entry);
+
+ if (st_password_entry_get_show_peek_icon (entry) != value)
+ g_object_notify_by_pspec (G_OBJECT (entry), props[PROP_SHOW_PEEK_ICON]);
+}
+
+/**
+ * st_password_entry_get_show_peek_icon:
+ * @entry: a #StPasswordEntry
+ *
+ * Gets whether peek-icon is shown or hidden in the password entry.
+ *
+ * Returns: %TRUE if visible
+ */
+gboolean
+st_password_entry_get_show_peek_icon (StPasswordEntry *entry)
+{
+ StPasswordEntryPrivate *priv;
+
+ g_return_val_if_fail (ST_IS_PASSWORD_ENTRY (entry), TRUE);
+
+ priv = ST_PASSWORD_ENTRY_PRIV (entry);
+ return priv->show_peek_icon && !show_password_locked_down (entry);
+}
+
+/**
+ * st_password_entry_set_password_visible:
+ * @entry: a #StPasswordEntry
+ * @value: %TRUE to show the password in the entry, #FALSE otherwise
+ *
+ * Sets whether to show or hide text in the password entry.
+ */
+void
+st_password_entry_set_password_visible (StPasswordEntry *entry,
+ gboolean value)
+{
+ StPasswordEntryPrivate *priv;
+ ClutterActor *clutter_text;
+
+ g_return_if_fail (ST_IS_PASSWORD_ENTRY (entry));
+
+ priv = ST_PASSWORD_ENTRY_PRIV (entry);
+ if (priv->password_visible == value)
+ return;
+
+ priv->password_visible = value;
+
+ clutter_text = st_entry_get_clutter_text (ST_ENTRY (entry));
+ if (priv->password_visible)
+ {
+ clutter_text_set_password_char (CLUTTER_TEXT (clutter_text), 0);
+ st_icon_set_icon_name (ST_ICON (priv->peek_password_icon), "view-conceal-symbolic");
+ }
+ else
+ {
+ clutter_text_set_password_char (CLUTTER_TEXT (clutter_text), BLACK_CIRCLE);
+ st_icon_set_icon_name (ST_ICON (priv->peek_password_icon), "view-reveal-symbolic");
+ }
+
+ g_object_notify_by_pspec (G_OBJECT (entry), props[PROP_PASSWORD_VISIBLE]);
+}
+
+/**
+ * st_password_entry_get_password_visible:
+ * @entry: a #StPasswordEntry
+ *
+ * Gets whether the text is masked in the password entry.
+ *
+ * Returns: %TRUE if visible
+ */
+gboolean
+st_password_entry_get_password_visible (StPasswordEntry *entry)
+{
+ StPasswordEntryPrivate *priv;
+
+ g_return_val_if_fail (ST_IS_PASSWORD_ENTRY (entry), FALSE);
+
+ priv = ST_PASSWORD_ENTRY_PRIV (entry);
+ return priv->password_visible;
+}
diff --git a/src/st/st-password-entry.h b/src/st/st-password-entry.h
new file mode 100644
index 0000000..3998068
--- /dev/null
+++ b/src/st/st-password-entry.h
@@ -0,0 +1,46 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * st-password-entry.h: Password entry actor based on st-entry
+ *
+ * Copyright 2019 Endless Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU Lesser General Public License,
+ * version 2.1, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#if !defined(ST_H_INSIDE) && !defined(ST_COMPILATION)
+#error "Only <st/st.h> can be included directly.h"
+#endif
+
+#ifndef __ST_PASSWORD_ENTRY_H__
+#define __ST_PASSWORD_ENTRY_H__
+
+G_BEGIN_DECLS
+
+#include <st/st-entry.h>
+
+#define ST_TYPE_PASSWORD_ENTRY (st_password_entry_get_type ())
+
+G_DECLARE_FINAL_TYPE (StPasswordEntry, st_password_entry, ST, PASSWORD_ENTRY, StEntry)
+
+StEntry *st_password_entry_new (void);
+gboolean st_password_entry_get_password_visible (StPasswordEntry *entry);
+void st_password_entry_set_password_visible (StPasswordEntry *entry,
+ gboolean value);
+gboolean st_password_entry_get_show_peek_icon (StPasswordEntry *entry);
+void st_password_entry_set_show_peek_icon (StPasswordEntry *entry,
+ gboolean value);
+
+G_END_DECLS
+
+#endif /* __ST_PASSWORD_ENTRY_H__ */
+
diff --git a/src/st/st-private.c b/src/st/st-private.c
new file mode 100644
index 0000000..bb98151
--- /dev/null
+++ b/src/st/st-private.c
@@ -0,0 +1,804 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * st-private.h: Private declarations and functions
+ *
+ * Copyright 2009, 2010 Red Hat, Inc.
+ * Copyright 2010 Florian Müllner
+ * Copyright 2010 Intel Corporation
+ * Copyright 2010 Giovanni Campagna
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU Lesser General Public License,
+ * version 2.1, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+#include <math.h>
+#include <string.h>
+
+#include "st-private.h"
+
+/**
+ * _st_actor_get_preferred_width:
+ * @actor: a #ClutterActor
+ * @for_height: as with clutter_actor_get_preferred_width()
+ * @y_fill: %TRUE if @actor will fill its allocation vertically
+ * @min_width_p: as with clutter_actor_get_preferred_width()
+ * @natural_width_p: as with clutter_actor_get_preferred_width()
+ *
+ * Like clutter_actor_get_preferred_width(), but if @y_fill is %FALSE,
+ * then it will compute a width request based on the assumption that
+ * @actor will be given an allocation no taller than its natural
+ * height.
+ */
+void
+_st_actor_get_preferred_width (ClutterActor *actor,
+ gfloat for_height,
+ gboolean y_fill,
+ gfloat *min_width_p,
+ gfloat *natural_width_p)
+{
+ if (!y_fill && for_height != -1)
+ {
+ ClutterRequestMode mode;
+ gfloat natural_height;
+
+ mode = clutter_actor_get_request_mode (actor);
+ if (mode == CLUTTER_REQUEST_WIDTH_FOR_HEIGHT)
+ {
+ clutter_actor_get_preferred_height (actor, -1, NULL, &natural_height);
+ if (for_height > natural_height)
+ for_height = natural_height;
+ }
+ }
+
+ clutter_actor_get_preferred_width (actor, for_height, min_width_p, natural_width_p);
+}
+
+/**
+ * _st_actor_get_preferred_height:
+ * @actor: a #ClutterActor
+ * @for_width: as with clutter_actor_get_preferred_height()
+ * @x_fill: %TRUE if @actor will fill its allocation horizontally
+ * @min_height_p: as with clutter_actor_get_preferred_height()
+ * @natural_height_p: as with clutter_actor_get_preferred_height()
+ *
+ * Like clutter_actor_get_preferred_height(), but if @x_fill is
+ * %FALSE, then it will compute a height request based on the
+ * assumption that @actor will be given an allocation no wider than
+ * its natural width.
+ */
+void
+_st_actor_get_preferred_height (ClutterActor *actor,
+ gfloat for_width,
+ gboolean x_fill,
+ gfloat *min_height_p,
+ gfloat *natural_height_p)
+{
+ if (!x_fill && for_width != -1)
+ {
+ ClutterRequestMode mode;
+ gfloat natural_width;
+
+ mode = clutter_actor_get_request_mode (actor);
+ if (mode == CLUTTER_REQUEST_HEIGHT_FOR_WIDTH)
+ {
+ clutter_actor_get_preferred_width (actor, -1, NULL, &natural_width);
+ if (for_width > natural_width)
+ for_width = natural_width;
+ }
+ }
+
+ clutter_actor_get_preferred_height (actor, for_width, min_height_p, natural_height_p);
+}
+
+/**
+ * _st_set_text_from_style:
+ * @text: Target #ClutterText
+ * @theme_node: Source #StThemeNode
+ *
+ * Set various GObject properties of the @text object using
+ * CSS information from @theme_node.
+ */
+void
+_st_set_text_from_style (ClutterText *text,
+ StThemeNode *theme_node)
+{
+
+ ClutterColor color;
+ StTextDecoration decoration;
+ PangoAttrList *attribs = NULL;
+ const PangoFontDescription *font;
+ PangoAttribute *foreground;
+ StTextAlign align;
+ gdouble spacing;
+ gchar *font_features;
+
+ font = st_theme_node_get_font (theme_node);
+ clutter_text_set_font_description (text, (PangoFontDescription *) font);
+
+ attribs = pango_attr_list_new ();
+
+ st_theme_node_get_foreground_color (theme_node, &color);
+ clutter_text_set_cursor_color (text, &color);
+ foreground = pango_attr_foreground_new (color.red * 255,
+ color.green * 255,
+ color.blue * 255);
+ pango_attr_list_insert (attribs, foreground);
+
+ if (color.alpha != 255)
+ {
+ PangoAttribute *alpha;
+
+ /* An alpha value of 0 means "system inherited", so the
+ * minimum regular value is 1.
+ */
+ if (color.alpha == 0)
+ alpha = pango_attr_foreground_alpha_new (1);
+ else
+ alpha = pango_attr_foreground_alpha_new (color.alpha * 255);
+
+ pango_attr_list_insert (attribs, alpha);
+ }
+
+ decoration = st_theme_node_get_text_decoration (theme_node);
+ if (decoration)
+ {
+ if (decoration & ST_TEXT_DECORATION_UNDERLINE)
+ {
+ PangoAttribute *underline = pango_attr_underline_new (PANGO_UNDERLINE_SINGLE);
+ pango_attr_list_insert (attribs, underline);
+ }
+ if (decoration & ST_TEXT_DECORATION_LINE_THROUGH)
+ {
+ PangoAttribute *strikethrough = pango_attr_strikethrough_new (TRUE);
+ pango_attr_list_insert (attribs, strikethrough);
+ }
+ /* Pango doesn't have an equivalent attribute for _OVERLINE, and we deliberately
+ * skip BLINK (for now...)
+ */
+ }
+
+ spacing = st_theme_node_get_letter_spacing (theme_node);
+ if (spacing)
+ {
+ PangoAttribute *letter_spacing = pango_attr_letter_spacing_new ((int)(.5 + spacing) * PANGO_SCALE);
+ pango_attr_list_insert (attribs, letter_spacing);
+ }
+
+ font_features = st_theme_node_get_font_features (theme_node);
+ if (font_features)
+ {
+ pango_attr_list_insert (attribs, pango_attr_font_features_new (font_features));
+ g_free (font_features);
+ }
+
+ clutter_text_set_attributes (text, attribs);
+
+ if (attribs)
+ pango_attr_list_unref (attribs);
+
+ align = st_theme_node_get_text_align (theme_node);
+ if (align == ST_TEXT_ALIGN_JUSTIFY)
+ {
+ clutter_text_set_justify (text, TRUE);
+ clutter_text_set_line_alignment (text, PANGO_ALIGN_LEFT);
+ }
+ else
+ {
+ clutter_text_set_justify (text, FALSE);
+ clutter_text_set_line_alignment (text, (PangoAlignment) align);
+ }
+}
+
+/**
+ * _st_create_texture_pipeline:
+ * @src_texture: The CoglTexture for the pipeline
+ *
+ * Creates a simple pipeline which contains the given texture as a
+ * single layer.
+ */
+CoglPipeline *
+_st_create_texture_pipeline (CoglTexture *src_texture)
+{
+ static CoglPipeline *texture_pipeline_template = NULL;
+ CoglPipeline *pipeline;
+
+ g_return_val_if_fail (src_texture != NULL, NULL);
+
+ /* The only state used in the pipeline that would affect the shader
+ generation is the texture type on the layer. Therefore we create
+ a template pipeline which sets this state and all texture
+ pipelines are created as a copy of this. That way Cogl can find
+ the shader state for the pipeline more quickly by looking at the
+ pipeline ancestry instead of resorting to the shader cache. */
+ if (G_UNLIKELY (texture_pipeline_template == NULL))
+ {
+ CoglContext *ctx =
+ clutter_backend_get_cogl_context (clutter_get_default_backend ());
+
+ texture_pipeline_template = cogl_pipeline_new (ctx);
+ cogl_pipeline_set_layer_null_texture (texture_pipeline_template, 0);
+ }
+
+ pipeline = cogl_pipeline_copy (texture_pipeline_template);
+
+ if (src_texture != NULL)
+ cogl_pipeline_set_layer_texture (pipeline, 0, src_texture);
+
+ return pipeline;
+}
+
+/*****
+ * Shadows
+ *****/
+
+static gdouble *
+calculate_gaussian_kernel (gdouble sigma,
+ guint n_values)
+{
+ gdouble *ret, sum;
+ gdouble exp_divisor;
+ int half, i;
+
+ g_return_val_if_fail (sigma > 0, NULL);
+
+ half = n_values / 2;
+
+ ret = g_malloc (n_values * sizeof (gdouble));
+ sum = 0.0;
+
+ exp_divisor = 2 * sigma * sigma;
+
+ /* n_values of 1D Gauss function */
+ for (i = 0; i < (int)n_values; i++)
+ {
+ ret[i] = exp (-(i - half) * (i - half) / exp_divisor);
+ sum += ret[i];
+ }
+
+ /* normalize */
+ for (i = 0; i < (int)n_values; i++)
+ ret[i] /= sum;
+
+ return ret;
+}
+
+static guchar *
+blur_pixels (guchar *pixels_in,
+ gint width_in,
+ gint height_in,
+ gint rowstride_in,
+ gdouble blur,
+ gint *width_out,
+ gint *height_out,
+ size_t *rowstride_out)
+{
+ guchar *pixels_out;
+ gdouble sigma;
+
+ /* The CSS specification defines (or will define) the blur radius as twice
+ * the Gaussian standard deviation. See:
+ *
+ * http://lists.w3.org/Archives/Public/www-style/2010Sep/0002.html
+ */
+ sigma = blur / 2.;
+
+ if ((guint) blur == 0)
+ {
+ *width_out = width_in;
+ *height_out = height_in;
+ *rowstride_out = rowstride_in;
+ pixels_out = g_memdup2 (pixels_in, *rowstride_out * *height_out);
+ }
+ else
+ {
+ gdouble *kernel;
+ guchar *line;
+ gint n_values, half;
+ gint x_in, y_in, x_out, y_out, i;
+
+ n_values = (gint) 5 * sigma;
+ half = n_values / 2;
+
+ *width_out = width_in + 2 * half;
+ *height_out = height_in + 2 * half;
+ *rowstride_out = (*width_out + 3) & ~3;
+
+ pixels_out = g_malloc0 (*rowstride_out * *height_out);
+ line = g_malloc0 (*rowstride_out);
+
+ kernel = calculate_gaussian_kernel (sigma, n_values);
+
+ /* vertical blur */
+ for (x_in = 0; x_in < width_in; x_in++)
+ for (y_out = 0; y_out < *height_out; y_out++)
+ {
+ guchar *pixel_in, *pixel_out;
+ gint i0, i1;
+
+ y_in = y_out - half;
+
+ /* We read from the source at 'y = y_in + i - half'; clamp the
+ * full i range [0, n_values) so that y is in [0, height_in).
+ */
+ i0 = MAX (half - y_in, 0);
+ i1 = MIN (height_in + half - y_in, n_values);
+
+ pixel_in = pixels_in + (y_in + i0 - half) * rowstride_in + x_in;
+ pixel_out = pixels_out + y_out * *rowstride_out + (x_in + half);
+
+ for (i = i0; i < i1; i++)
+ {
+ *pixel_out += *pixel_in * kernel[i];
+ pixel_in += rowstride_in;
+ }
+ }
+
+ /* horizontal blur */
+ for (y_out = 0; y_out < *height_out; y_out++)
+ {
+ memcpy (line, pixels_out + y_out * *rowstride_out, *rowstride_out);
+
+ for (x_out = 0; x_out < *width_out; x_out++)
+ {
+ gint i0, i1;
+ guchar *pixel_out, *pixel_in;
+
+ /* We read from the source at 'x = x_out + i - half'; clamp the
+ * full i range [0, n_values) so that x is in [0, width_out).
+ */
+ i0 = MAX (half - x_out, 0);
+ i1 = MIN (*width_out + half - x_out, n_values);
+
+ pixel_in = line + x_out + i0 - half;
+ pixel_out = pixels_out + *rowstride_out * y_out + x_out;
+
+ *pixel_out = 0;
+ for (i = i0; i < i1; i++)
+ {
+ *pixel_out += *pixel_in * kernel[i];
+ pixel_in++;
+ }
+ }
+ }
+ g_free (kernel);
+ g_free (line);
+ }
+
+ return pixels_out;
+}
+
+CoglPipeline *
+_st_create_shadow_pipeline (StShadow *shadow_spec,
+ CoglTexture *src_texture,
+ float resource_scale)
+{
+ ClutterBackend *backend = clutter_get_default_backend ();
+ CoglContext *ctx = clutter_backend_get_cogl_context (backend);
+ g_autoptr (ClutterPaintNode) texture_node = NULL;
+ g_autoptr (ClutterPaintNode) blur_node = NULL;
+ g_autoptr (CoglOffscreen) offscreen = NULL;
+ g_autoptr (GError) error = NULL;
+ ClutterPaintContext *paint_context;
+ CoglFramebuffer *fb;
+ CoglPipeline *pipeline;
+ CoglTexture *texture;
+ float sampling_radius;
+ float sigma;
+ int src_height, dst_height;
+ int src_width, dst_width;
+ CoglPipeline *texture_pipeline;
+
+ static CoglPipelineKey texture_pipeline_key =
+ "st-create-shadow-pipeline-saturate-alpha";
+ static CoglPipeline *shadow_pipeline_template = NULL;
+
+ g_return_val_if_fail (shadow_spec != NULL, NULL);
+ g_return_val_if_fail (src_texture != NULL, NULL);
+
+ sampling_radius = resource_scale * shadow_spec->blur;
+ sigma = sampling_radius / 2.f;
+ sampling_radius = ceilf (sampling_radius);
+
+ src_width = cogl_texture_get_width (src_texture);
+ src_height = cogl_texture_get_height (src_texture);
+ dst_width = src_width + 2 * sampling_radius;
+ dst_height = src_height + 2 * sampling_radius;
+
+ texture = cogl_texture_2d_new_with_size (ctx, dst_width, dst_height);
+ if (!texture)
+ return NULL;
+
+ offscreen = cogl_offscreen_new_with_texture (texture);
+ fb = COGL_FRAMEBUFFER (offscreen);
+ if (!cogl_framebuffer_allocate (fb, &error))
+ {
+ cogl_clear_object (&texture);
+ return NULL;
+ }
+
+ cogl_framebuffer_clear4f (fb, COGL_BUFFER_BIT_COLOR, 0.f, 0.f, 0.f, 0.f);
+ cogl_framebuffer_orthographic (fb, 0, 0, dst_width, dst_height, 0, 1.0);
+
+ /* Blur */
+ blur_node = clutter_blur_node_new (dst_width, dst_height, sigma);
+ clutter_paint_node_add_rectangle (blur_node,
+ &(ClutterActorBox) {
+ 0.f, 0.f,
+ dst_width, dst_height,
+ });
+
+ /* Texture */
+ texture_pipeline = cogl_context_get_named_pipeline (ctx,
+ &texture_pipeline_key);
+
+ if (G_UNLIKELY (texture_pipeline == NULL))
+ {
+ CoglSnippet *snippet;
+
+ snippet = cogl_snippet_new (COGL_SNIPPET_HOOK_FRAGMENT,
+ "",
+ "if (cogl_color_out.a > 0.0)\n"
+ " cogl_color_out.a = 1.0;");
+
+ texture_pipeline = cogl_pipeline_new (ctx);
+ cogl_pipeline_add_snippet (texture_pipeline, snippet);
+ cogl_object_unref (snippet);
+
+ cogl_context_set_named_pipeline (ctx,
+ &texture_pipeline_key,
+ texture_pipeline);
+ }
+
+ /* No need to unref texture_pipeline since the named pipeline hash
+ * doesn't change its ref count from 1. Also no need to copy texture_pipeline
+ * since we'll be completely finished with it after clutter_paint_node_paint.
+ */
+
+ cogl_pipeline_set_layer_texture (texture_pipeline, 0, src_texture);
+ texture_node = clutter_pipeline_node_new (texture_pipeline);
+ clutter_paint_node_add_child (blur_node, texture_node);
+ clutter_paint_node_add_rectangle (texture_node,
+ &(ClutterActorBox) {
+ .x1 = sampling_radius,
+ .y1 = sampling_radius,
+ .x2 = src_width + sampling_radius,
+ .y2 = src_height + sampling_radius,
+ });
+
+ paint_context =
+ clutter_paint_context_new_for_framebuffer (fb, NULL, CLUTTER_PAINT_FLAG_NONE);
+ clutter_paint_node_paint (blur_node, paint_context);
+ clutter_paint_context_destroy (paint_context);
+
+ if (G_UNLIKELY (shadow_pipeline_template == NULL))
+ {
+ shadow_pipeline_template = cogl_pipeline_new (ctx);
+
+ /* We set up the pipeline to blend the shadow texture with the combine
+ * constant, but defer setting the latter until painting, so that we can
+ * take the actor's overall opacity into account. */
+ cogl_pipeline_set_layer_combine (shadow_pipeline_template, 0,
+ "RGBA = MODULATE (CONSTANT, TEXTURE[A])",
+ NULL);
+ }
+
+ pipeline = cogl_pipeline_copy (shadow_pipeline_template);
+ cogl_pipeline_set_layer_texture (pipeline, 0, texture);
+
+ cogl_clear_object (&texture);
+
+ return pipeline;
+}
+
+CoglPipeline *
+_st_create_shadow_pipeline_from_actor (StShadow *shadow_spec,
+ ClutterActor *actor)
+{
+ ClutterContent *image = NULL;
+ CoglPipeline *shadow_pipeline = NULL;
+ float resource_scale;
+ float width, height;
+ ClutterPaintContext *paint_context;
+
+ g_return_val_if_fail (clutter_actor_has_allocation (actor), NULL);
+
+ clutter_actor_get_size (actor, &width, &height);
+
+ if (width == 0 || height == 0)
+ return NULL;
+
+ resource_scale = clutter_actor_get_resource_scale (actor);
+
+ width = ceilf (width * resource_scale);
+ height = ceilf (height * resource_scale);
+
+ image = clutter_actor_get_content (actor);
+ if (image && CLUTTER_IS_IMAGE (image))
+ {
+ CoglTexture *texture;
+
+ texture = clutter_image_get_texture (CLUTTER_IMAGE (image));
+ if (texture &&
+ cogl_texture_get_width (texture) == width &&
+ cogl_texture_get_height (texture) == height)
+ shadow_pipeline = _st_create_shadow_pipeline (shadow_spec, texture,
+ resource_scale);
+ }
+
+ if (shadow_pipeline == NULL)
+ {
+ CoglTexture *buffer;
+ CoglOffscreen *offscreen;
+ CoglFramebuffer *fb;
+ CoglContext *ctx;
+ CoglColor clear_color;
+ GError *catch_error = NULL;
+ float x, y;
+
+ ctx = clutter_backend_get_cogl_context (clutter_get_default_backend ());
+ buffer = cogl_texture_2d_new_with_size (ctx, width, height);
+
+ if (buffer == NULL)
+ return NULL;
+
+ offscreen = cogl_offscreen_new_with_texture (buffer);
+ fb = COGL_FRAMEBUFFER (offscreen);
+
+ if (!cogl_framebuffer_allocate (fb, &catch_error))
+ {
+ g_error_free (catch_error);
+ g_object_unref (offscreen);
+ cogl_object_unref (buffer);
+ return NULL;
+ }
+
+ cogl_color_init_from_4ub (&clear_color, 0, 0, 0, 0);
+ clutter_actor_get_position (actor, &x, &y);
+ x *= resource_scale;
+ y *= resource_scale;
+
+ cogl_framebuffer_clear (fb, COGL_BUFFER_BIT_COLOR, &clear_color);
+ cogl_framebuffer_translate (fb, -x, -y, 0);
+ cogl_framebuffer_orthographic (fb, 0, 0, width, height, 0, 1.0);
+ cogl_framebuffer_scale (fb, resource_scale, resource_scale, 1);
+
+ clutter_actor_set_opacity_override (actor, 255);
+
+ paint_context =
+ clutter_paint_context_new_for_framebuffer (fb, NULL,
+ CLUTTER_PAINT_FLAG_NONE);
+ clutter_actor_paint (actor, paint_context);
+ clutter_paint_context_destroy (paint_context);
+
+ clutter_actor_set_opacity_override (actor, -1);
+
+ g_object_unref (fb);
+
+ shadow_pipeline = _st_create_shadow_pipeline (shadow_spec, buffer,
+ resource_scale);
+
+ cogl_object_unref (buffer);
+ }
+
+ return shadow_pipeline;
+}
+
+/**
+ * _st_create_shadow_cairo_pattern:
+ * @shadow_spec: the definition of the shadow
+ * @src_pattern: surface pattern for which we create the shadow
+ * (must be a surface pattern)
+ *
+ * This is a utility function for creating shadows used by
+ * st-theme-node.c; it's in this file to share the gaussian
+ * blur implementation. The usage of this function is quite different
+ * depending on whether shadow_spec->inset is %TRUE or not. If
+ * shadow_spec->inset is %TRUE, the caller should pass in a @src_pattern
+ * which is the <i>inverse</i> of what they want shadowed, and must take
+ * care of the spread and offset from the shadow spec themselves. If
+ * shadow_spec->inset is %FALSE then the caller should pass in what they
+ * want shadowed directly, and this function takes care of the spread and
+ * the offset.
+ */
+cairo_pattern_t *
+_st_create_shadow_cairo_pattern (StShadow *shadow_spec_in,
+ cairo_pattern_t *src_pattern)
+{
+ g_autoptr(StShadow) shadow_spec = NULL;
+ static cairo_user_data_key_t shadow_pattern_user_data;
+ cairo_t *cr;
+ cairo_surface_t *src_surface;
+ cairo_surface_t *surface_in;
+ cairo_surface_t *surface_out;
+ cairo_pattern_t *dst_pattern;
+ guchar *pixels_in, *pixels_out;
+ gint width_in, height_in, rowstride_in;
+ gint width_out, height_out;
+ size_t rowstride_out;
+ cairo_matrix_t shadow_matrix;
+ double xscale_in, yscale_in;
+ int i, j;
+
+ g_return_val_if_fail (shadow_spec_in != NULL, NULL);
+ g_return_val_if_fail (src_pattern != NULL, NULL);
+
+ if (cairo_pattern_get_surface (src_pattern, &src_surface) != CAIRO_STATUS_SUCCESS)
+ /* The most likely reason we can't get the pattern is that sizing went hairwire
+ * and the caller tried to create a surface too big for memory, leaving us with
+ * a pattern in an error state; we return a transparent pattern for the shadow.
+ */
+ return cairo_pattern_create_rgba(1.0, 1.0, 1.0, 0.0);
+
+ width_in = cairo_image_surface_get_width (src_surface);
+ height_in = cairo_image_surface_get_height (src_surface);
+
+ cairo_surface_get_device_scale (src_surface, &xscale_in, &yscale_in);
+
+ if (xscale_in != 1.0 || yscale_in != 1.0)
+ {
+ /* Scale the shadow specifications in a temporary copy so that
+ * we can work everywhere in absolute surface coordinates */
+ double scale = (xscale_in + yscale_in) / 2.0;
+ shadow_spec = st_shadow_new (&shadow_spec_in->color,
+ shadow_spec_in->xoffset * xscale_in,
+ shadow_spec_in->yoffset * yscale_in,
+ shadow_spec_in->blur * scale,
+ shadow_spec_in->spread * scale,
+ shadow_spec_in->inset);
+ }
+ else
+ {
+ shadow_spec = st_shadow_ref (shadow_spec_in);
+ }
+
+ /* We want the output to be a color agnostic alpha mask,
+ * so we need to strip the color channels from the input
+ */
+ if (cairo_image_surface_get_format (src_surface) != CAIRO_FORMAT_A8)
+ {
+ surface_in = cairo_image_surface_create (CAIRO_FORMAT_A8,
+ width_in, height_in);
+
+ cr = cairo_create (surface_in);
+ cairo_set_source_surface (cr, src_surface, 0, 0);
+ cairo_paint (cr);
+ cairo_destroy (cr);
+ }
+ else
+ {
+ surface_in = cairo_surface_reference (src_surface);
+ }
+
+ pixels_in = cairo_image_surface_get_data (surface_in);
+ rowstride_in = cairo_image_surface_get_stride (surface_in);
+
+ pixels_out = blur_pixels (pixels_in, width_in, height_in, rowstride_in,
+ shadow_spec->blur,
+ &width_out, &height_out, &rowstride_out);
+ cairo_surface_destroy (surface_in);
+
+ /* Invert pixels for inset shadows */
+ if (shadow_spec->inset)
+ {
+ for (j = 0; j < height_out; j++)
+ {
+ guchar *p = pixels_out + rowstride_out * j;
+ for (i = 0; i < width_out; i++, p++)
+ *p = ~*p;
+ }
+ }
+
+ surface_out = cairo_image_surface_create_for_data (pixels_out,
+ CAIRO_FORMAT_A8,
+ width_out,
+ height_out,
+ rowstride_out);
+ cairo_surface_set_device_scale (surface_out, xscale_in, yscale_in);
+ cairo_surface_set_user_data (surface_out, &shadow_pattern_user_data,
+ pixels_out, (cairo_destroy_func_t) g_free);
+
+ dst_pattern = cairo_pattern_create_for_surface (surface_out);
+ cairo_surface_destroy (surface_out);
+
+ cairo_pattern_get_matrix (src_pattern, &shadow_matrix);
+
+ if (shadow_spec->inset)
+ {
+ /* Scale the matrix in surface absolute coordinates */
+ cairo_matrix_scale (&shadow_matrix, 1.0 / xscale_in, 1.0 / yscale_in);
+
+ /* For inset shadows, offsets and spread radius have already been
+ * applied to the original pattern, so all left to do is shift the
+ * blurred image left, so that it aligns centered under the
+ * unblurred one
+ */
+ cairo_matrix_translate (&shadow_matrix,
+ (width_out - width_in) / 2.0,
+ (height_out - height_in) / 2.0);
+
+ /* Scale back the matrix in original coordinates */
+ cairo_matrix_scale (&shadow_matrix, xscale_in, yscale_in);
+
+ cairo_pattern_set_matrix (dst_pattern, &shadow_matrix);
+ return dst_pattern;
+ }
+
+ /* Read all the code from the cairo_pattern_set_matrix call
+ * at the end of this function to here from bottom to top,
+ * because each new affine transformation is applied in
+ * front of all the previous ones */
+
+ /* 6. Invert the matrix back */
+ cairo_matrix_invert (&shadow_matrix);
+
+ /* Scale the matrix in surface absolute coordinates */
+ cairo_matrix_scale (&shadow_matrix, 1.0 / xscale_in, 1.0 / yscale_in);
+
+ /* 5. Adjust based on specified offsets */
+ cairo_matrix_translate (&shadow_matrix,
+ shadow_spec->xoffset,
+ shadow_spec->yoffset);
+
+ /* 4. Recenter the newly scaled image */
+ cairo_matrix_translate (&shadow_matrix,
+ - shadow_spec->spread,
+ - shadow_spec->spread);
+
+ /* 3. Scale up the blurred image to fill the spread */
+ cairo_matrix_scale (&shadow_matrix,
+ (width_in + 2.0 * shadow_spec->spread) / width_in,
+ (height_in + 2.0 * shadow_spec->spread) / height_in);
+
+ /* 2. Shift the blurred image left, so that it aligns centered
+ * under the unblurred one */
+ cairo_matrix_translate (&shadow_matrix,
+ - (width_out - width_in) / 2.0,
+ - (height_out - height_in) / 2.0);
+
+ /* Scale back the matrix in scaled coordinates */
+ cairo_matrix_scale (&shadow_matrix, xscale_in, yscale_in);
+
+ /* 1. Invert the matrix so we can work with it in pattern space
+ */
+ cairo_matrix_invert (&shadow_matrix);
+
+ cairo_pattern_set_matrix (dst_pattern, &shadow_matrix);
+
+ return dst_pattern;
+}
+
+void
+_st_paint_shadow_with_opacity (StShadow *shadow_spec,
+ CoglFramebuffer *framebuffer,
+ CoglPipeline *shadow_pipeline,
+ ClutterActorBox *box,
+ guint8 paint_opacity)
+{
+ ClutterActorBox shadow_box;
+ CoglColor color;
+
+ g_return_if_fail (shadow_spec != NULL);
+ g_return_if_fail (shadow_pipeline != NULL);
+
+ st_shadow_get_box (shadow_spec, box, &shadow_box);
+
+ cogl_color_init_from_4ub (&color,
+ shadow_spec->color.red * paint_opacity / 255,
+ shadow_spec->color.green * paint_opacity / 255,
+ shadow_spec->color.blue * paint_opacity / 255,
+ shadow_spec->color.alpha * paint_opacity / 255);
+ cogl_color_premultiply (&color);
+ cogl_pipeline_set_layer_combine_constant (shadow_pipeline, 0, &color);
+ cogl_framebuffer_draw_rectangle (framebuffer,
+ shadow_pipeline,
+ shadow_box.x1, shadow_box.y1,
+ shadow_box.x2, shadow_box.y2);
+}
diff --git a/src/st/st-private.h b/src/st/st-private.h
new file mode 100644
index 0000000..3f1fd12
--- /dev/null
+++ b/src/st/st-private.h
@@ -0,0 +1,75 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * st-private.h: Private declarations and functions
+ *
+ * Copyright 2007 OpenedHand
+ * Copyright 2009 Intel Corporation.
+ * Copyright 2010 Red Hat, Inc.
+ * Copyright 2010 Florian Müllner
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU Lesser General Public License,
+ * version 2.1, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __ST_PRIVATE_H__
+#define __ST_PRIVATE_H__
+
+#include <glib.h>
+#include <cairo.h>
+#include "st-widget.h"
+#include "st-bin.h"
+#include "st-shadow.h"
+
+G_BEGIN_DECLS
+
+#define I_(str) (g_intern_static_string ((str)))
+
+#define ST_PARAM_READABLE (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)
+#define ST_PARAM_WRITABLE (G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS)
+#define ST_PARAM_READWRITE (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)
+
+G_END_DECLS
+
+ClutterActor *_st_widget_get_dnd_clone (StWidget *widget);
+
+void _st_actor_get_preferred_width (ClutterActor *actor,
+ gfloat for_height,
+ gboolean y_fill,
+ gfloat *min_width_p,
+ gfloat *natural_width_p);
+void _st_actor_get_preferred_height (ClutterActor *actor,
+ gfloat for_width,
+ gboolean x_fill,
+ gfloat *min_height_p,
+ gfloat *natural_height_p);
+
+void _st_set_text_from_style (ClutterText *text,
+ StThemeNode *theme_node);
+
+CoglPipeline * _st_create_texture_pipeline (CoglTexture *src_texture);
+
+/* Helper for widgets which need to draw additional shadows */
+CoglPipeline * _st_create_shadow_pipeline (StShadow *shadow_spec,
+ CoglTexture *src_texture,
+ float resource_scale);
+CoglPipeline * _st_create_shadow_pipeline_from_actor (StShadow *shadow_spec,
+ ClutterActor *actor);
+cairo_pattern_t *_st_create_shadow_cairo_pattern (StShadow *shadow_spec,
+ cairo_pattern_t *src_pattern);
+
+void _st_paint_shadow_with_opacity (StShadow *shadow_spec,
+ CoglFramebuffer *framebuffer,
+ CoglPipeline *shadow_pipeline,
+ ClutterActorBox *box,
+ guint8 paint_opacity);
+
+#endif /* __ST_PRIVATE_H__ */
diff --git a/src/st/st-scroll-bar.c b/src/st/st-scroll-bar.c
new file mode 100644
index 0000000..72bcd55
--- /dev/null
+++ b/src/st/st-scroll-bar.c
@@ -0,0 +1,1014 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * st-scroll-bar.c: Scroll bar actor
+ *
+ * Copyright 2008 OpenedHand
+ * Copyright 2008, 2009 Intel Corporation.
+ * Copyright 2009, 2010 Red Hat, Inc.
+ * Copyright 2010 Maxim Ermilov
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU Lesser General Public License,
+ * version 2.1, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * SECTION:st-scroll-bar
+ * @short_description: a user interface element to control scrollable areas.
+ *
+ * The #StScrollBar allows users to scroll scrollable actors, either by
+ * the step or page amount, or by manually dragging the handle.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <math.h>
+#include <clutter/clutter.h>
+
+#include "st-scroll-bar.h"
+#include "st-bin.h"
+#include "st-enum-types.h"
+#include "st-private.h"
+#include "st-button.h"
+#include "st-settings.h"
+
+#define PAGING_INITIAL_REPEAT_TIMEOUT 500
+#define PAGING_SUBSEQUENT_REPEAT_TIMEOUT 200
+
+typedef struct _StScrollBarPrivate StScrollBarPrivate;
+struct _StScrollBarPrivate
+{
+ StAdjustment *adjustment;
+
+ gfloat x_origin;
+ gfloat y_origin;
+
+ ClutterInputDevice *grab_device;
+ ClutterGrab *grab;
+
+ ClutterActor *trough;
+ ClutterActor *handle;
+
+ gfloat move_x;
+ gfloat move_y;
+
+ /* Trough-click handling. */
+ enum { NONE, UP, DOWN } paging_direction;
+ guint paging_source_id;
+ guint paging_event_no;
+
+ guint vertical : 1;
+};
+
+G_DEFINE_TYPE_WITH_PRIVATE (StScrollBar, st_scroll_bar, ST_TYPE_WIDGET)
+
+#define ST_SCROLL_BAR_PRIVATE(sb) st_scroll_bar_get_instance_private (ST_SCROLL_BAR (sb))
+
+enum
+{
+ PROP_0,
+
+ PROP_ADJUSTMENT,
+ PROP_VERTICAL,
+
+ N_PROPS
+};
+
+static GParamSpec *props[N_PROPS] = { NULL, };
+
+enum
+{
+ SCROLL_START,
+ SCROLL_STOP,
+
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = { 0, };
+
+static gboolean
+handle_button_press_event_cb (ClutterActor *actor,
+ ClutterButtonEvent *event,
+ StScrollBar *bar);
+
+static void stop_scrolling (StScrollBar *bar);
+
+static void
+st_scroll_bar_set_vertical (StScrollBar *bar,
+ gboolean vertical)
+{
+ StScrollBarPrivate *priv = ST_SCROLL_BAR_PRIVATE (bar);
+
+ if (priv->vertical == vertical)
+ return;
+
+ priv->vertical = vertical;
+
+ if (priv->vertical)
+ clutter_actor_set_name (CLUTTER_ACTOR (priv->handle),
+ "vhandle");
+ else
+ clutter_actor_set_name (CLUTTER_ACTOR (priv->handle),
+ "hhandle");
+ clutter_actor_queue_relayout (CLUTTER_ACTOR (bar));
+ g_object_notify_by_pspec (G_OBJECT (bar), props[PROP_VERTICAL]);
+}
+
+static void
+st_scroll_bar_get_property (GObject *gobject,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ StScrollBarPrivate *priv = ST_SCROLL_BAR_PRIVATE (gobject);
+
+ switch (prop_id)
+ {
+ case PROP_ADJUSTMENT:
+ g_value_set_object (value, priv->adjustment);
+ break;
+
+ case PROP_VERTICAL:
+ g_value_set_boolean (value, priv->vertical);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+st_scroll_bar_set_property (GObject *gobject,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ StScrollBar *bar = ST_SCROLL_BAR (gobject);
+
+ switch (prop_id)
+ {
+ case PROP_ADJUSTMENT:
+ st_scroll_bar_set_adjustment (bar, g_value_get_object (value));
+ break;
+
+ case PROP_VERTICAL:
+ st_scroll_bar_set_vertical (bar, g_value_get_boolean (value));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+st_scroll_bar_dispose (GObject *gobject)
+{
+ StScrollBar *bar = ST_SCROLL_BAR (gobject);
+ StScrollBarPrivate *priv = st_scroll_bar_get_instance_private (bar);
+
+ if (priv->adjustment)
+ st_scroll_bar_set_adjustment (bar, NULL);
+
+ if (priv->handle)
+ {
+ clutter_actor_destroy (priv->handle);
+ priv->handle = NULL;
+ }
+
+ if (priv->trough)
+ {
+ clutter_actor_destroy (priv->trough);
+ priv->trough = NULL;
+ }
+
+ G_OBJECT_CLASS (st_scroll_bar_parent_class)->dispose (gobject);
+}
+
+static void
+st_scroll_bar_unmap (ClutterActor *actor)
+{
+ CLUTTER_ACTOR_CLASS (st_scroll_bar_parent_class)->unmap (actor);
+
+ stop_scrolling (ST_SCROLL_BAR (actor));
+}
+
+static void
+scroll_bar_allocate_children (StScrollBar *bar,
+ const ClutterActorBox *box)
+{
+ StScrollBarPrivate *priv = st_scroll_bar_get_instance_private (bar);
+ StThemeNode *theme_node = st_widget_get_theme_node (ST_WIDGET (bar));
+ ClutterActorBox content_box, trough_box;
+
+ st_theme_node_get_content_box (theme_node, box, &content_box);
+
+ trough_box.x1 = content_box.x1;
+ trough_box.y1 = content_box.y1;
+ trough_box.x2 = content_box.x2;
+ trough_box.y2 = content_box.y2;
+ clutter_actor_allocate (priv->trough, &trough_box);
+
+ if (priv->adjustment)
+ {
+ float handle_size, position, avail_size;
+ gdouble value, lower, upper, page_size, increment, min_size, max_size;
+ ClutterActorBox handle_box = { 0, };
+
+ st_adjustment_get_values (priv->adjustment,
+ &value,
+ &lower,
+ &upper,
+ NULL,
+ NULL,
+ &page_size);
+
+ if ((upper == lower)
+ || (page_size >= (upper - lower)))
+ increment = 1.0;
+ else
+ increment = page_size / (upper - lower);
+
+ min_size = 32.;
+ st_theme_node_lookup_length (theme_node, "min-size", FALSE, &min_size);
+ max_size = G_MAXINT16;
+ st_theme_node_lookup_length (theme_node, "max-size", FALSE, &max_size);
+
+ if (upper - lower - page_size <= 0)
+ position = 0;
+ else
+ position = (value - lower) / (upper - lower - page_size);
+
+ if (priv->vertical)
+ {
+ avail_size = content_box.y2 - content_box.y1;
+ handle_size = increment * avail_size;
+ handle_size = CLAMP (handle_size, min_size, max_size);
+
+ handle_box.x1 = content_box.x1;
+ handle_box.y1 = content_box.y1 + position * (avail_size - handle_size);
+
+ handle_box.x2 = content_box.x2;
+ handle_box.y2 = handle_box.y1 + handle_size;
+ }
+ else
+ {
+ ClutterTextDirection direction;
+
+ avail_size = content_box.x2 - content_box.x1;
+ handle_size = increment * avail_size;
+ handle_size = CLAMP (handle_size, min_size, max_size);
+
+ direction = clutter_actor_get_text_direction (CLUTTER_ACTOR (bar));
+ if (direction == CLUTTER_TEXT_DIRECTION_RTL)
+ {
+ handle_box.x2 = content_box.x2 - position * (avail_size - handle_size);
+ handle_box.x1 = handle_box.x2 - handle_size;
+ }
+ else
+ {
+ handle_box.x1 = content_box.x1 + position * (avail_size - handle_size);
+ handle_box.x2 = handle_box.x1 + handle_size;
+ }
+
+ handle_box.y1 = content_box.y1;
+ handle_box.y2 = content_box.y2;
+ }
+
+ clutter_actor_allocate (priv->handle, &handle_box);
+ }
+}
+
+static void
+st_scroll_bar_get_preferred_width (ClutterActor *self,
+ gfloat for_height,
+ gfloat *min_width_p,
+ gfloat *natural_width_p)
+{
+ StScrollBar *bar = ST_SCROLL_BAR (self);
+ StScrollBarPrivate *priv = st_scroll_bar_get_instance_private (bar);
+ StThemeNode *theme_node = st_widget_get_theme_node (ST_WIDGET (self));
+ gfloat trough_min_width, trough_natural_width;
+ gfloat handle_min_width, handle_natural_width;
+
+ st_theme_node_adjust_for_height (theme_node, &for_height);
+
+ _st_actor_get_preferred_width (priv->trough, for_height, TRUE,
+ &trough_min_width, &trough_natural_width);
+
+ _st_actor_get_preferred_width (priv->handle, for_height, TRUE,
+ &handle_min_width, &handle_natural_width);
+
+ if (priv->vertical)
+ {
+ if (min_width_p)
+ *min_width_p = MAX (trough_min_width, handle_min_width);
+
+ if (natural_width_p)
+ *natural_width_p = MAX (trough_natural_width, handle_natural_width);
+ }
+ else
+ {
+ if (min_width_p)
+ *min_width_p = trough_min_width + handle_min_width;
+
+ if (natural_width_p)
+ *natural_width_p = trough_natural_width + handle_natural_width;
+ }
+
+ st_theme_node_adjust_preferred_width (theme_node, min_width_p, natural_width_p);
+}
+
+static void
+st_scroll_bar_get_preferred_height (ClutterActor *self,
+ gfloat for_width,
+ gfloat *min_height_p,
+ gfloat *natural_height_p)
+{
+ StScrollBar *bar = ST_SCROLL_BAR (self);
+ StScrollBarPrivate *priv = st_scroll_bar_get_instance_private (bar);
+ StThemeNode *theme_node = st_widget_get_theme_node (ST_WIDGET (self));
+ gfloat trough_min_height, trough_natural_height;
+ gfloat handle_min_height, handle_natural_height;
+
+ st_theme_node_adjust_for_width (theme_node, &for_width);
+
+ _st_actor_get_preferred_height (priv->trough, for_width, TRUE,
+ &trough_min_height, &trough_natural_height);
+
+ _st_actor_get_preferred_height (priv->handle, for_width, TRUE,
+ &handle_min_height, &handle_natural_height);
+
+ if (priv->vertical)
+ {
+ if (min_height_p)
+ *min_height_p = trough_min_height + handle_min_height;
+
+ if (natural_height_p)
+ *natural_height_p = trough_natural_height + handle_natural_height;
+ }
+ else
+ {
+ if (min_height_p)
+ *min_height_p = MAX (trough_min_height, handle_min_height);
+
+ if (natural_height_p)
+ *natural_height_p = MAX (trough_natural_height, handle_natural_height);
+ }
+
+ st_theme_node_adjust_preferred_height (theme_node, min_height_p, natural_height_p);
+}
+
+static void
+st_scroll_bar_allocate (ClutterActor *actor,
+ const ClutterActorBox *box)
+{
+ StScrollBar *bar = ST_SCROLL_BAR (actor);
+
+ clutter_actor_set_allocation (actor, box);
+
+ scroll_bar_allocate_children (bar, box);
+}
+
+static void
+scroll_bar_update_positions (StScrollBar *bar)
+{
+ ClutterActorBox box;
+
+ /* Due to a change in the adjustments, we need to reposition our
+ * children; since adjustments changes can come from allocation
+ * changes in the scrolled area, we can't just queue a new relayout -
+ * we may already be in a relayout cycle. On the other hand, if
+ * a relayout is already queued, we can't just go ahead and allocate
+ * our children, since we don't have a valid allocation, and calling
+ * clutter_actor_get_allocation_box() will trigger an immediate
+ * stage relayout. So what we do is go ahead and immediately
+ * allocate our children if we already have a valid allocation, and
+ * otherwise just wait for the queued relayout.
+ */
+ if (!clutter_actor_has_allocation (CLUTTER_ACTOR (bar)))
+ return;
+
+ clutter_actor_get_allocation_box (CLUTTER_ACTOR (bar), &box);
+ scroll_bar_allocate_children (bar, &box);
+}
+
+static void
+bar_reactive_notify_cb (GObject *gobject,
+ GParamSpec *arg1,
+ gpointer user_data)
+{
+ StScrollBar *bar = ST_SCROLL_BAR (gobject);
+ StScrollBarPrivate *priv = st_scroll_bar_get_instance_private (bar);
+
+ clutter_actor_set_reactive (priv->handle,
+ clutter_actor_get_reactive (CLUTTER_ACTOR (bar)));
+}
+
+static GObject*
+st_scroll_bar_constructor (GType type,
+ guint n_properties,
+ GObjectConstructParam *properties)
+{
+ GObjectClass *gobject_class;
+ GObject *obj;
+ StScrollBar *bar;
+
+ gobject_class = G_OBJECT_CLASS (st_scroll_bar_parent_class);
+ obj = gobject_class->constructor (type, n_properties, properties);
+
+ bar = ST_SCROLL_BAR (obj);
+
+ g_signal_connect (bar, "notify::reactive",
+ G_CALLBACK (bar_reactive_notify_cb), NULL);
+
+ return obj;
+}
+
+static void
+adjust_with_direction (StAdjustment *adj,
+ ClutterScrollDirection direction)
+{
+ gdouble delta;
+
+ switch (direction)
+ {
+ case CLUTTER_SCROLL_UP:
+ case CLUTTER_SCROLL_LEFT:
+ delta = -1.0;
+ break;
+ case CLUTTER_SCROLL_RIGHT:
+ case CLUTTER_SCROLL_DOWN:
+ delta = 1.0;
+ break;
+ case CLUTTER_SCROLL_SMOOTH:
+ default:
+ g_assert_not_reached ();
+ break;
+ }
+
+ st_adjustment_adjust_for_scroll_event (adj, delta);
+}
+
+static gboolean
+st_scroll_bar_scroll_event (ClutterActor *actor,
+ ClutterScrollEvent *event)
+{
+ StScrollBarPrivate *priv = ST_SCROLL_BAR_PRIVATE (actor);
+ ClutterTextDirection direction;
+ ClutterScrollDirection scroll_dir;
+
+ if (clutter_event_is_pointer_emulated ((ClutterEvent *) event))
+ return TRUE;
+
+ direction = clutter_actor_get_text_direction (actor);
+ scroll_dir = event->direction;
+
+ switch (scroll_dir)
+ {
+ case CLUTTER_SCROLL_SMOOTH:
+ {
+ gdouble delta_x, delta_y;
+ clutter_event_get_scroll_delta ((ClutterEvent *)event, &delta_x, &delta_y);
+
+ if (direction == CLUTTER_TEXT_DIRECTION_RTL)
+ delta_x *= -1;
+
+ if (priv->vertical)
+ st_adjustment_adjust_for_scroll_event (priv->adjustment, delta_y);
+ else
+ st_adjustment_adjust_for_scroll_event (priv->adjustment, delta_x);
+ }
+ break;
+ case CLUTTER_SCROLL_LEFT:
+ case CLUTTER_SCROLL_RIGHT:
+ if (direction == CLUTTER_TEXT_DIRECTION_RTL)
+ scroll_dir = scroll_dir == CLUTTER_SCROLL_LEFT ? CLUTTER_SCROLL_RIGHT
+ : CLUTTER_SCROLL_LEFT;
+ /* Fall through */
+ case CLUTTER_SCROLL_UP:
+ case CLUTTER_SCROLL_DOWN:
+ adjust_with_direction (priv->adjustment, scroll_dir);
+ break;
+ default:
+ g_return_val_if_reached (FALSE);
+ break;
+ }
+
+ return TRUE;
+}
+
+static void
+st_scroll_bar_class_init (StScrollBarClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ ClutterActorClass *actor_class = CLUTTER_ACTOR_CLASS (klass);
+
+ object_class->get_property = st_scroll_bar_get_property;
+ object_class->set_property = st_scroll_bar_set_property;
+ object_class->dispose = st_scroll_bar_dispose;
+ object_class->constructor = st_scroll_bar_constructor;
+
+ actor_class->get_preferred_width = st_scroll_bar_get_preferred_width;
+ actor_class->get_preferred_height = st_scroll_bar_get_preferred_height;
+ actor_class->allocate = st_scroll_bar_allocate;
+ actor_class->scroll_event = st_scroll_bar_scroll_event;
+ actor_class->unmap = st_scroll_bar_unmap;
+
+ /**
+ * StScrollBar:adjustment:
+ *
+ * The #StAdjustment controlling the #StScrollBar.
+ */
+ props[PROP_ADJUSTMENT] =
+ g_param_spec_object ("adjustment", "Adjustment", "The adjustment",
+ ST_TYPE_ADJUSTMENT,
+ ST_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
+
+ /**
+ * StScrollBar:vertical:
+ *
+ * Whether the #StScrollBar is vertical. If %FALSE it is horizontal.
+ */
+ props[PROP_VERTICAL] =
+ g_param_spec_boolean ("vertical",
+ "Vertical Orientation",
+ "Vertical Orientation",
+ FALSE,
+ ST_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
+
+ g_object_class_install_properties (object_class, N_PROPS, props);
+
+
+ /**
+ * StScrollBar::scroll-start:
+ * @bar: a #StScrollBar
+ *
+ * Emitted when the #StScrollBar begins scrolling.
+ */
+ signals[SCROLL_START] =
+ g_signal_new ("scroll-start",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (StScrollBarClass, scroll_start),
+ NULL, NULL, NULL,
+ G_TYPE_NONE, 0);
+
+ /**
+ * StScrollBar::scroll-stop:
+ * @bar: a #StScrollBar
+ *
+ * Emitted when the #StScrollBar finishes scrolling.
+ */
+ signals[SCROLL_STOP] =
+ g_signal_new ("scroll-stop",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (StScrollBarClass, scroll_stop),
+ NULL, NULL, NULL,
+ G_TYPE_NONE, 0);
+}
+
+static void
+move_slider (StScrollBar *bar,
+ gfloat x,
+ gfloat y)
+{
+ StScrollBarPrivate *priv = st_scroll_bar_get_instance_private (bar);
+ ClutterTextDirection direction;
+ gdouble position, lower, upper, page_size;
+ gfloat ux, uy, pos, size;
+
+ if (!priv->adjustment)
+ return;
+
+ if (!clutter_actor_transform_stage_point (priv->trough, x, y, &ux, &uy))
+ return;
+
+ if (priv->vertical)
+ size = clutter_actor_get_height (priv->trough)
+ - clutter_actor_get_height (priv->handle);
+ else
+ size = clutter_actor_get_width (priv->trough)
+ - clutter_actor_get_width (priv->handle);
+
+ if (size == 0)
+ return;
+
+ if (priv->vertical)
+ pos = uy - priv->y_origin;
+ else
+ pos = ux - priv->x_origin;
+ pos = CLAMP (pos, 0, size);
+
+ st_adjustment_get_values (priv->adjustment,
+ NULL,
+ &lower,
+ &upper,
+ NULL,
+ NULL,
+ &page_size);
+
+ direction = clutter_actor_get_text_direction (CLUTTER_ACTOR (bar));
+ if (!priv->vertical && direction == CLUTTER_TEXT_DIRECTION_RTL)
+ pos = size - pos;
+
+ position = ((pos / size)
+ * (upper - lower - page_size))
+ + lower;
+
+ st_adjustment_set_value (priv->adjustment, position);
+}
+
+static void
+stop_scrolling (StScrollBar *bar)
+{
+ StScrollBarPrivate *priv = st_scroll_bar_get_instance_private (bar);
+ if (!priv->grab_device)
+ return;
+
+ st_widget_remove_style_pseudo_class (ST_WIDGET (priv->handle), "active");
+
+ if (priv->grab)
+ {
+ clutter_grab_dismiss (priv->grab);
+ g_clear_pointer (&priv->grab, clutter_grab_unref);
+ }
+
+ priv->grab_device = NULL;
+ g_signal_emit (bar, signals[SCROLL_STOP], 0);
+}
+
+static gboolean
+handle_motion_event_cb (ClutterActor *trough,
+ ClutterMotionEvent *event,
+ StScrollBar *bar)
+{
+ StScrollBarPrivate *priv = st_scroll_bar_get_instance_private (bar);
+ if (!priv->grab_device)
+ return FALSE;
+
+ move_slider (bar, event->x, event->y);
+ return TRUE;
+}
+
+static gboolean
+handle_button_release_event_cb (ClutterActor *trough,
+ ClutterButtonEvent *event,
+ StScrollBar *bar)
+{
+ if (event->button != 1)
+ return FALSE;
+
+ stop_scrolling (bar);
+ return TRUE;
+}
+
+static gboolean
+handle_button_press_event_cb (ClutterActor *actor,
+ ClutterButtonEvent *event,
+ StScrollBar *bar)
+{
+ StScrollBarPrivate *priv = st_scroll_bar_get_instance_private (bar);
+ ClutterInputDevice *device = clutter_event_get_device ((ClutterEvent*) event);
+ ClutterActor *stage;
+
+ if (event->button != 1)
+ return FALSE;
+
+ if (!clutter_actor_transform_stage_point (priv->handle,
+ event->x,
+ event->y,
+ &priv->x_origin,
+ &priv->y_origin))
+ return FALSE;
+
+ st_widget_add_style_pseudo_class (ST_WIDGET (priv->handle), "active");
+
+ /* Account for the scrollbar-trough-handle nesting. */
+ priv->x_origin += clutter_actor_get_x (priv->trough);
+ priv->y_origin += clutter_actor_get_y (priv->trough);
+
+ g_assert (!priv->grab_device);
+
+ stage = clutter_actor_get_stage (actor);
+ priv->grab = clutter_stage_grab (CLUTTER_STAGE (stage), priv->handle);
+ priv->grab_device = device;
+ g_signal_emit (bar, signals[SCROLL_START], 0);
+
+ return TRUE;
+}
+
+static gboolean
+trough_paging_cb (StScrollBar *self)
+{
+ StScrollBarPrivate *priv = st_scroll_bar_get_instance_private (self);
+ ClutterTextDirection direction;
+ g_autoptr (ClutterTransition) transition = NULL;
+ StSettings *settings;
+ gfloat handle_pos, event_pos, tx, ty;
+ gdouble value, new_value;
+ gdouble page_increment;
+ gdouble slow_down_factor;
+ gboolean ret;
+
+ gulong mode;
+
+ if (priv->paging_event_no == 0)
+ {
+ /* Scroll on after initial timeout. */
+ mode = CLUTTER_EASE_OUT_CUBIC;
+ ret = FALSE;
+ priv->paging_event_no = 1;
+ priv->paging_source_id = g_timeout_add (
+ PAGING_INITIAL_REPEAT_TIMEOUT,
+ (GSourceFunc) trough_paging_cb,
+ self);
+ g_source_set_name_by_id (priv->paging_source_id, "[gnome-shell] trough_paging_cb");
+ }
+ else if (priv->paging_event_no == 1)
+ {
+ /* Scroll on after subsequent timeout. */
+ ret = FALSE;
+ mode = CLUTTER_EASE_IN_CUBIC;
+ priv->paging_event_no = 2;
+ priv->paging_source_id = g_timeout_add (
+ PAGING_SUBSEQUENT_REPEAT_TIMEOUT,
+ (GSourceFunc) trough_paging_cb,
+ self);
+ g_source_set_name_by_id (priv->paging_source_id, "[gnome-shell] trough_paging_cb");
+ }
+ else
+ {
+ /* Keep scrolling. */
+ ret = TRUE;
+ mode = CLUTTER_LINEAR;
+ priv->paging_event_no++;
+ }
+
+ /* Do the scrolling */
+ st_adjustment_get_values (priv->adjustment,
+ &value, NULL, NULL,
+ NULL, &page_increment, NULL);
+
+ if (priv->vertical)
+ handle_pos = clutter_actor_get_y (priv->handle);
+ else
+ handle_pos = clutter_actor_get_x (priv->handle);
+
+ clutter_actor_transform_stage_point (CLUTTER_ACTOR (priv->trough),
+ priv->move_x,
+ priv->move_y,
+ &tx, &ty);
+
+ direction = clutter_actor_get_text_direction (CLUTTER_ACTOR (self));
+ if (!priv->vertical && direction == CLUTTER_TEXT_DIRECTION_RTL)
+ page_increment *= -1;
+
+ if (priv->vertical)
+ event_pos = ty;
+ else
+ event_pos = tx;
+
+ if (event_pos > handle_pos)
+ {
+ if (priv->paging_direction == NONE)
+ {
+ /* Remember direction. */
+ priv->paging_direction = DOWN;
+ }
+ if (priv->paging_direction == UP)
+ {
+ /* Scrolled far enough. */
+ return FALSE;
+ }
+ new_value = value + page_increment;
+ }
+ else
+ {
+ if (priv->paging_direction == NONE)
+ {
+ /* Remember direction. */
+ priv->paging_direction = UP;
+ }
+ if (priv->paging_direction == DOWN)
+ {
+ /* Scrolled far enough. */
+ return FALSE;
+ }
+ new_value = value - page_increment;
+ }
+
+ /* Stop existing transition, if one exists */
+ st_adjustment_remove_transition (priv->adjustment, "value");
+
+ settings = st_settings_get ();
+ g_object_get (settings, "slow-down-factor", &slow_down_factor, NULL);
+
+ /* FIXME: Creating a new transition for each scroll is probably not the best
+ * idea, but it's a lot less involved than extending the current animation */
+ transition = g_object_new (CLUTTER_TYPE_PROPERTY_TRANSITION,
+ "property-name", "value",
+ "interval", clutter_interval_new (G_TYPE_DOUBLE, value, new_value),
+ "duration", (guint)(PAGING_SUBSEQUENT_REPEAT_TIMEOUT * slow_down_factor),
+ "progress-mode", mode,
+ "remove-on-complete", TRUE,
+ NULL);
+ st_adjustment_add_transition (priv->adjustment, "value", transition);
+
+ return ret;
+}
+
+static gboolean
+trough_button_press_event_cb (ClutterActor *actor,
+ ClutterButtonEvent *event,
+ StScrollBar *self)
+{
+ StScrollBarPrivate *priv;
+
+ g_return_val_if_fail (self, FALSE);
+
+ if (event->button != 1)
+ return FALSE;
+
+ priv = st_scroll_bar_get_instance_private (self);
+ if (priv->adjustment == NULL)
+ return FALSE;
+
+ priv->move_x = event->x;
+ priv->move_y = event->y;
+ priv->paging_direction = NONE;
+ priv->paging_event_no = 0;
+ trough_paging_cb (self);
+
+ return TRUE;
+}
+
+static gboolean
+trough_button_release_event_cb (ClutterActor *actor,
+ ClutterButtonEvent *event,
+ StScrollBar *self)
+{
+ StScrollBarPrivate *priv = st_scroll_bar_get_instance_private (self);
+
+ if (event->button != 1)
+ return FALSE;
+
+ g_clear_handle_id (&priv->paging_source_id, g_source_remove);
+
+ return TRUE;
+}
+
+static gboolean
+trough_leave_event_cb (ClutterActor *actor,
+ ClutterEvent *event,
+ StScrollBar *self)
+{
+ StScrollBarPrivate *priv = st_scroll_bar_get_instance_private (self);
+
+ if (priv->paging_source_id)
+ {
+ g_clear_handle_id (&priv->paging_source_id, g_source_remove);
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static void
+st_scroll_bar_notify_reactive (StScrollBar *self)
+{
+ StScrollBarPrivate *priv = st_scroll_bar_get_instance_private (self);
+
+ gboolean reactive = clutter_actor_get_reactive (CLUTTER_ACTOR (self));
+
+ clutter_actor_set_reactive (CLUTTER_ACTOR (priv->trough), reactive);
+ clutter_actor_set_reactive (CLUTTER_ACTOR (priv->handle), reactive);
+}
+
+static void
+st_scroll_bar_init (StScrollBar *self)
+{
+ StScrollBarPrivate *priv = st_scroll_bar_get_instance_private (self);
+
+ priv->trough = (ClutterActor *) st_bin_new ();
+ clutter_actor_set_reactive ((ClutterActor *) priv->trough, TRUE);
+ clutter_actor_set_name (CLUTTER_ACTOR (priv->trough), "trough");
+ clutter_actor_add_child (CLUTTER_ACTOR (self),
+ CLUTTER_ACTOR (priv->trough));
+ g_signal_connect (priv->trough, "button-press-event",
+ G_CALLBACK (trough_button_press_event_cb), self);
+ g_signal_connect (priv->trough, "button-release-event",
+ G_CALLBACK (trough_button_release_event_cb), self);
+ g_signal_connect (priv->trough, "leave-event",
+ G_CALLBACK (trough_leave_event_cb), self);
+
+ priv->handle = (ClutterActor *) st_button_new ();
+ clutter_actor_set_name (CLUTTER_ACTOR (priv->handle), "hhandle");
+ clutter_actor_add_child (CLUTTER_ACTOR (self),
+ CLUTTER_ACTOR (priv->handle));
+ g_signal_connect (priv->handle, "button-press-event",
+ G_CALLBACK (handle_button_press_event_cb), self);
+ g_signal_connect (priv->handle, "button-release-event",
+ G_CALLBACK (handle_button_release_event_cb), self);
+ g_signal_connect (priv->handle, "motion-event",
+ G_CALLBACK (handle_motion_event_cb), self);
+
+ clutter_actor_set_reactive (CLUTTER_ACTOR (self), TRUE);
+
+ g_signal_connect (self, "notify::reactive",
+ G_CALLBACK (st_scroll_bar_notify_reactive), NULL);
+}
+
+StWidget *
+st_scroll_bar_new (StAdjustment *adjustment)
+{
+ return g_object_new (ST_TYPE_SCROLL_BAR,
+ "adjustment", adjustment,
+ NULL);
+}
+
+static void
+on_notify_value (GObject *object,
+ GParamSpec *pspec,
+ StScrollBar *bar)
+{
+ scroll_bar_update_positions (bar);
+}
+
+static void
+on_changed (StAdjustment *adjustment,
+ StScrollBar *bar)
+{
+ scroll_bar_update_positions (bar);
+}
+
+void
+st_scroll_bar_set_adjustment (StScrollBar *bar,
+ StAdjustment *adjustment)
+{
+ StScrollBarPrivate *priv;
+
+ g_return_if_fail (ST_IS_SCROLL_BAR (bar));
+
+ priv = st_scroll_bar_get_instance_private (bar);
+
+ if (adjustment == priv->adjustment)
+ return;
+
+ if (priv->adjustment)
+ {
+ g_signal_handlers_disconnect_by_func (priv->adjustment,
+ on_notify_value,
+ bar);
+ g_signal_handlers_disconnect_by_func (priv->adjustment,
+ on_changed,
+ bar);
+ g_object_unref (priv->adjustment);
+ priv->adjustment = NULL;
+ }
+
+ if (adjustment)
+ {
+ priv->adjustment = g_object_ref (adjustment);
+
+ g_signal_connect (priv->adjustment, "notify::value",
+ G_CALLBACK (on_notify_value),
+ bar);
+ g_signal_connect (priv->adjustment, "changed",
+ G_CALLBACK (on_changed),
+ bar);
+
+ clutter_actor_queue_relayout (CLUTTER_ACTOR (bar));
+ }
+
+ g_object_notify_by_pspec (G_OBJECT (bar), props[PROP_ADJUSTMENT]);
+}
+
+/**
+ * st_scroll_bar_get_adjustment:
+ * @bar: a #StScrollbar
+ *
+ * Gets the #StAdjustment that controls the current position of @bar.
+ *
+ * Returns: (transfer none): an #StAdjustment
+ */
+StAdjustment *
+st_scroll_bar_get_adjustment (StScrollBar *bar)
+{
+ g_return_val_if_fail (ST_IS_SCROLL_BAR (bar), NULL);
+
+ return ((StScrollBarPrivate *)ST_SCROLL_BAR_PRIVATE (bar))->adjustment;
+}
+
diff --git a/src/st/st-scroll-bar.h b/src/st/st-scroll-bar.h
new file mode 100644
index 0000000..2c69fdd
--- /dev/null
+++ b/src/st/st-scroll-bar.h
@@ -0,0 +1,53 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * st-scroll-bar.h: Scroll bar actor
+ *
+ * Copyright 2008 OpenedHand
+ * Copyright 2009 Intel Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU Lesser General Public License,
+ * version 2.1, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#if !defined(ST_H_INSIDE) && !defined(ST_COMPILATION)
+#error "Only <st/st.h> can be included directly.h"
+#endif
+
+#ifndef __ST_SCROLL_BAR_H__
+#define __ST_SCROLL_BAR_H__
+
+#include <st/st-adjustment.h>
+#include <st/st-widget.h>
+
+G_BEGIN_DECLS
+
+#define ST_TYPE_SCROLL_BAR (st_scroll_bar_get_type())
+G_DECLARE_DERIVABLE_TYPE (StScrollBar, st_scroll_bar, ST, SCROLL_BAR, StWidget)
+
+struct _StScrollBarClass
+{
+ StWidgetClass parent_class;
+
+ /* signals */
+ void (*scroll_start) (StScrollBar *bar);
+ void (*scroll_stop) (StScrollBar *bar);
+};
+
+StWidget *st_scroll_bar_new (StAdjustment *adjustment);
+
+void st_scroll_bar_set_adjustment (StScrollBar *bar,
+ StAdjustment *adjustment);
+StAdjustment *st_scroll_bar_get_adjustment (StScrollBar *bar);
+
+G_END_DECLS
+
+#endif /* __ST_SCROLL_BAR_H__ */
diff --git a/src/st/st-scroll-view-fade.c b/src/st/st-scroll-view-fade.c
new file mode 100644
index 0000000..77b1d0b
--- /dev/null
+++ b/src/st/st-scroll-view-fade.c
@@ -0,0 +1,461 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * st-scroll-view-fade.h: Edge fade effect for StScrollView
+ *
+ * Copyright 2010 Intel Corporation.
+ * Copyright 2011 Adel Gadllah
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU Lesser General Public License,
+ * version 2.1, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#include "st-private.h"
+#include "st-scroll-view-fade.h"
+#include "st-scroll-view.h"
+#include "st-widget.h"
+#include "st-theme-node.h"
+#include "st-scroll-bar.h"
+#include "st-scrollable.h"
+
+#include <clutter/clutter.h>
+#include <cogl/cogl.h>
+
+#define DEFAULT_FADE_OFFSET 68.0f
+
+#include "st-scroll-view-fade-generated.h"
+
+struct _StScrollViewFade
+{
+ ClutterShaderEffect parent_instance;
+
+ /* a back pointer to our actor, so that we can query it */
+ ClutterActor *actor;
+
+ StAdjustment *vadjustment;
+ StAdjustment *hadjustment;
+
+ guint fade_edges : 1;
+ guint extend_fade_area: 1;
+
+ ClutterMargin fade_margins;
+};
+
+G_DEFINE_TYPE (StScrollViewFade,
+ st_scroll_view_fade,
+ CLUTTER_TYPE_SHADER_EFFECT);
+
+enum {
+ PROP_0,
+
+ PROP_FADE_MARGINS,
+ PROP_FADE_EDGES,
+ PROP_EXTEND_FADE_AREA,
+
+ N_PROPS
+};
+
+static GParamSpec *props[N_PROPS] = { NULL, };
+
+static CoglTexture *
+st_scroll_view_fade_create_texture (ClutterOffscreenEffect *effect,
+ gfloat min_width,
+ gfloat min_height)
+{
+ CoglContext *ctx =
+ clutter_backend_get_cogl_context (clutter_get_default_backend ());
+
+ return COGL_TEXTURE (cogl_texture_2d_new_with_size (ctx, min_width, min_height));
+}
+
+static char *
+st_scroll_view_fade_get_static_shader_source (ClutterShaderEffect *effect)
+{
+ return g_strdup (st_scroll_view_fade_glsl);
+}
+
+
+static void
+st_scroll_view_fade_paint_target (ClutterOffscreenEffect *effect,
+ ClutterPaintNode *node,
+ ClutterPaintContext *paint_context)
+{
+ StScrollViewFade *self = ST_SCROLL_VIEW_FADE (effect);
+ ClutterShaderEffect *shader = CLUTTER_SHADER_EFFECT (effect);
+ ClutterOffscreenEffectClass *parent;
+
+ gdouble value, lower, upper, page_size;
+ ClutterActor *vscroll = st_scroll_view_get_vscroll_bar (ST_SCROLL_VIEW (self->actor));
+ ClutterActor *hscroll = st_scroll_view_get_hscroll_bar (ST_SCROLL_VIEW (self->actor));
+ gboolean h_scroll_visible, v_scroll_visible, rtl;
+
+ ClutterActorBox allocation, content_box, paint_box;
+
+ float fade_area_topleft[2];
+ float fade_area_bottomright[2];
+ graphene_point3d_t verts[4];
+
+ clutter_actor_get_paint_box (self->actor, &paint_box);
+ clutter_actor_get_abs_allocation_vertices (self->actor, verts);
+
+ clutter_actor_get_allocation_box (self->actor, &allocation);
+ st_theme_node_get_content_box (st_widget_get_theme_node (ST_WIDGET (self->actor)),
+ (const ClutterActorBox *)&allocation, &content_box);
+
+ /*
+ * The FBO is based on the paint_volume's size which can be larger then the actual
+ * allocation, so we have to account for that when passing the positions
+ */
+ fade_area_topleft[0] = content_box.x1 + (verts[0].x - paint_box.x1);
+ fade_area_topleft[1] = content_box.y1 + (verts[0].y - paint_box.y1);
+ fade_area_bottomright[0] = content_box.x2 + (verts[3].x - paint_box.x2) + 1;
+ fade_area_bottomright[1] = content_box.y2 + (verts[3].y - paint_box.y2) + 1;
+
+ g_object_get (ST_SCROLL_VIEW (self->actor),
+ "hscrollbar-visible", &h_scroll_visible,
+ "vscrollbar-visible", &v_scroll_visible,
+ NULL);
+
+ if (v_scroll_visible)
+ {
+ if (clutter_actor_get_text_direction (self->actor) == CLUTTER_TEXT_DIRECTION_RTL)
+ fade_area_topleft[0] += clutter_actor_get_width (vscroll);
+
+ fade_area_bottomright[0] -= clutter_actor_get_width (vscroll);
+ }
+
+ if (h_scroll_visible)
+ fade_area_bottomright[1] -= clutter_actor_get_height (hscroll);
+
+ if (self->fade_margins.left < 0)
+ fade_area_topleft[0] -= ABS (self->fade_margins.left);
+ if (self->fade_margins.right < 0)
+ fade_area_bottomright[0] += ABS (self->fade_margins.right);
+ if (self->fade_margins.top < 0)
+ fade_area_topleft[1] -= ABS (self->fade_margins.top);
+ if (self->fade_margins.bottom < 0)
+ fade_area_bottomright[1] += ABS (self->fade_margins.bottom);
+
+ st_adjustment_get_values (self->vadjustment, &value, &lower, &upper, NULL, NULL, &page_size);
+ value = (value - lower) / (upper - page_size - lower);
+ clutter_shader_effect_set_uniform (shader, "fade_edges_top", G_TYPE_INT, 1, self->fade_edges ? value >= 0.0 : value > 0.0);
+ clutter_shader_effect_set_uniform (shader, "fade_edges_bottom", G_TYPE_INT, 1, self->fade_edges ? value <= 1.0 : value < 1.0);
+
+ st_adjustment_get_values (self->hadjustment, &value, &lower, &upper, NULL, NULL, &page_size);
+ value = (value - lower) / (upper - page_size - lower);
+ rtl = clutter_actor_get_text_direction (self->actor) == CLUTTER_TEXT_DIRECTION_RTL;
+ clutter_shader_effect_set_uniform (shader, "fade_edges_left", G_TYPE_INT, 1,
+ self->fade_edges ?
+ value >= 0.0 :
+ (rtl ? value < 1.0 : value > 0.0));
+ clutter_shader_effect_set_uniform (shader, "fade_edges_right", G_TYPE_INT, 1,
+ self->fade_edges ?
+ value <= 1.0 :
+ (rtl ? value > 0.0 : value < 1.0));
+
+ clutter_shader_effect_set_uniform (shader, "extend_fade_area", G_TYPE_INT, 1, self->extend_fade_area);
+ clutter_shader_effect_set_uniform (shader, "fade_offset_top", G_TYPE_FLOAT, 1, ABS (self->fade_margins.top));
+ clutter_shader_effect_set_uniform (shader, "fade_offset_bottom", G_TYPE_FLOAT, 1, ABS (self->fade_margins.bottom));
+ clutter_shader_effect_set_uniform (shader, "fade_offset_left", G_TYPE_FLOAT, 1, ABS (self->fade_margins.left));
+ clutter_shader_effect_set_uniform (shader, "fade_offset_right", G_TYPE_FLOAT, 1, ABS (self->fade_margins.right));
+ clutter_shader_effect_set_uniform (shader, "tex", G_TYPE_INT, 1, 0);
+ clutter_shader_effect_set_uniform (shader, "height", G_TYPE_FLOAT, 1, clutter_actor_get_height (self->actor));
+ clutter_shader_effect_set_uniform (shader, "width", G_TYPE_FLOAT, 1, clutter_actor_get_width (self->actor));
+ clutter_shader_effect_set_uniform (shader, "fade_area_topleft", CLUTTER_TYPE_SHADER_FLOAT, 2, fade_area_topleft);
+ clutter_shader_effect_set_uniform (shader, "fade_area_bottomright", CLUTTER_TYPE_SHADER_FLOAT, 2, fade_area_bottomright);
+
+ parent = CLUTTER_OFFSCREEN_EFFECT_CLASS (st_scroll_view_fade_parent_class);
+ parent->paint_target (effect, node, paint_context);
+}
+
+static void
+on_adjustment_changed (StAdjustment *adjustment,
+ ClutterEffect *effect)
+{
+ gdouble value, lower, upper, page_size;
+ gboolean needs_fade;
+ StScrollViewFade *self = ST_SCROLL_VIEW_FADE (effect);
+
+ st_adjustment_get_values (self->vadjustment, &value, &lower, &upper, NULL, NULL, &page_size);
+ needs_fade = (value > lower + 0.1) || (value < upper - page_size - 0.1);
+
+ if (!needs_fade)
+ {
+ st_adjustment_get_values (self->hadjustment, &value, &lower, &upper, NULL, NULL, &page_size);
+ needs_fade = (value > lower + 0.1) || (value < upper - page_size - 0.1);
+ }
+
+ clutter_actor_meta_set_enabled (CLUTTER_ACTOR_META (effect), needs_fade);
+}
+
+static void
+st_scroll_view_fade_set_actor (ClutterActorMeta *meta,
+ ClutterActor *actor)
+{
+ StScrollViewFade *self = ST_SCROLL_VIEW_FADE (meta);
+ ClutterActorMetaClass *parent;
+
+ g_return_if_fail (actor == NULL || ST_IS_SCROLL_VIEW (actor));
+
+ if (self->vadjustment)
+ {
+ g_signal_handlers_disconnect_by_func (self->vadjustment,
+ (gpointer)on_adjustment_changed,
+ self);
+ self->vadjustment = NULL;
+ }
+
+ if (self->hadjustment)
+ {
+ g_signal_handlers_disconnect_by_func (self->hadjustment,
+ (gpointer)on_adjustment_changed,
+ self);
+ self->hadjustment = NULL;
+ }
+
+
+ if (actor)
+ {
+ StScrollView *scroll_view = ST_SCROLL_VIEW (actor);
+ StScrollBar *vscroll = ST_SCROLL_BAR (st_scroll_view_get_vscroll_bar (scroll_view));
+ StScrollBar *hscroll = ST_SCROLL_BAR (st_scroll_view_get_hscroll_bar (scroll_view));
+ self->vadjustment = ST_ADJUSTMENT (st_scroll_bar_get_adjustment (vscroll));
+ self->hadjustment = ST_ADJUSTMENT (st_scroll_bar_get_adjustment (hscroll));
+
+ g_signal_connect (self->vadjustment, "changed",
+ G_CALLBACK (on_adjustment_changed),
+ self);
+
+ g_signal_connect (self->hadjustment, "changed",
+ G_CALLBACK (on_adjustment_changed),
+ self);
+
+ on_adjustment_changed (NULL, CLUTTER_EFFECT (self));
+ }
+
+ parent = CLUTTER_ACTOR_META_CLASS (st_scroll_view_fade_parent_class);
+ parent->set_actor (meta, actor);
+
+ /* we keep a back pointer here, to avoid going through the ActorMeta */
+ self->actor = clutter_actor_meta_get_actor (meta);
+}
+
+static void
+st_scroll_view_fade_dispose (GObject *gobject)
+{
+ StScrollViewFade *self = ST_SCROLL_VIEW_FADE (gobject);
+
+ if (self->vadjustment)
+ {
+ g_signal_handlers_disconnect_by_func (self->vadjustment,
+ (gpointer)on_adjustment_changed,
+ self);
+ self->vadjustment = NULL;
+ }
+
+ if (self->hadjustment)
+ {
+ g_signal_handlers_disconnect_by_func (self->hadjustment,
+ (gpointer)on_adjustment_changed,
+ self);
+ self->hadjustment = NULL;
+ }
+
+ self->actor = NULL;
+
+ G_OBJECT_CLASS (st_scroll_view_fade_parent_class)->dispose (gobject);
+}
+
+static void
+st_scroll_view_set_fade_margins (StScrollViewFade *self,
+ ClutterMargin *fade_margins)
+{
+ if (self->fade_margins.left == fade_margins->left &&
+ self->fade_margins.right == fade_margins->right &&
+ self->fade_margins.top == fade_margins->top &&
+ self->fade_margins.bottom == fade_margins->bottom)
+ return;
+
+ self->fade_margins = *fade_margins;
+
+ if (self->actor != NULL)
+ clutter_actor_queue_redraw (self->actor);
+
+ g_object_notify_by_pspec (G_OBJECT (self), props[PROP_FADE_MARGINS]);
+}
+
+static void
+st_scroll_view_fade_set_fade_edges (StScrollViewFade *self,
+ gboolean fade_edges)
+{
+ if (self->fade_edges == fade_edges)
+ return;
+
+ g_object_freeze_notify (G_OBJECT (self));
+
+ self->fade_edges = fade_edges;
+
+ if (self->actor != NULL)
+ clutter_actor_queue_redraw (self->actor);
+
+ g_object_notify_by_pspec (G_OBJECT (self), props[PROP_FADE_EDGES]);
+ g_object_thaw_notify (G_OBJECT (self));
+}
+
+static void
+st_scroll_view_fade_set_extend_fade_area (StScrollViewFade *self,
+ gboolean extend_fade_area)
+{
+ if (self->extend_fade_area == extend_fade_area)
+ return;
+
+ self->extend_fade_area = extend_fade_area;
+
+ if (self->actor != NULL)
+ clutter_actor_queue_redraw (self->actor);
+
+ g_object_notify_by_pspec (G_OBJECT (self), props[PROP_EXTEND_FADE_AREA]);
+}
+
+static void
+st_scroll_view_fade_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ StScrollViewFade *self = ST_SCROLL_VIEW_FADE (object);
+
+ switch (prop_id)
+ {
+ case PROP_FADE_MARGINS:
+ st_scroll_view_set_fade_margins (self, g_value_get_boxed (value));
+ break;
+ case PROP_FADE_EDGES:
+ st_scroll_view_fade_set_fade_edges (self, g_value_get_boolean (value));
+ break;
+ case PROP_EXTEND_FADE_AREA:
+ st_scroll_view_fade_set_extend_fade_area (self, g_value_get_boolean (value));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+st_scroll_view_fade_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ StScrollViewFade *self = ST_SCROLL_VIEW_FADE (object);
+
+ switch (prop_id)
+ {
+ case PROP_FADE_MARGINS:
+ g_value_set_boxed (value, &self->fade_margins);
+ break;
+ case PROP_FADE_EDGES:
+ g_value_set_boolean (value, self->fade_edges);
+ break;
+ case PROP_EXTEND_FADE_AREA:
+ g_value_set_boolean (value, self->extend_fade_area);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+st_scroll_view_fade_class_init (StScrollViewFadeClass *klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+ ClutterShaderEffectClass *shader_class;
+ ClutterOffscreenEffectClass *offscreen_class;
+ ClutterActorMetaClass *meta_class = CLUTTER_ACTOR_META_CLASS (klass);
+
+ gobject_class->dispose = st_scroll_view_fade_dispose;
+ gobject_class->get_property = st_scroll_view_fade_get_property;
+ gobject_class->set_property = st_scroll_view_fade_set_property;
+
+ meta_class->set_actor = st_scroll_view_fade_set_actor;
+
+ shader_class = CLUTTER_SHADER_EFFECT_CLASS (klass);
+ shader_class->get_static_shader_source = st_scroll_view_fade_get_static_shader_source;
+
+ offscreen_class = CLUTTER_OFFSCREEN_EFFECT_CLASS (klass);
+ offscreen_class->create_texture = st_scroll_view_fade_create_texture;
+ offscreen_class->paint_target = st_scroll_view_fade_paint_target;
+
+ /**
+ * StScrollViewFade:fade-margins:
+ *
+ * The margins widths that are faded.
+ */
+ props[PROP_FADE_MARGINS] =
+ g_param_spec_boxed ("fade-margins",
+ "Fade margins",
+ "The margin widths that are faded",
+ CLUTTER_TYPE_MARGIN,
+ ST_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
+
+ /**
+ * StScrollViewFade:fade-edges:
+ *
+ * Whether the faded area should extend to the edges of the #StScrollViewFade.
+ */
+ props[PROP_FADE_EDGES] =
+ g_param_spec_boolean ("fade-edges",
+ "Fade Edges",
+ "Whether the faded area should extend to the edges",
+ FALSE,
+ ST_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
+
+ /**
+ * StScrollViewFade:extend-fade-area:
+ *
+ * Whether faded edges should extend beyond the faded area of the #StScrollViewFade.
+ */
+ props[PROP_EXTEND_FADE_AREA] =
+ g_param_spec_boolean ("extend-fade-area",
+ "Extend Fade Area",
+ "Whether faded edges should extend beyond the faded area",
+ FALSE,
+ ST_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
+
+ g_object_class_install_properties (gobject_class, N_PROPS, props);
+}
+
+static void
+st_scroll_view_fade_init (StScrollViewFade *self)
+{
+ self->fade_margins = (ClutterMargin) {
+ DEFAULT_FADE_OFFSET,
+ DEFAULT_FADE_OFFSET,
+ DEFAULT_FADE_OFFSET,
+ DEFAULT_FADE_OFFSET,
+ };
+}
+
+/**
+ * st_scroll_view_fade_new:
+ *
+ * Create a new #StScrollViewFade.
+ *
+ * Returns: (transfer full): a new #StScrollViewFade
+ */
+ClutterEffect *
+st_scroll_view_fade_new (void)
+{
+ return g_object_new (ST_TYPE_SCROLL_VIEW_FADE, NULL);
+}
diff --git a/src/st/st-scroll-view-fade.glsl b/src/st/st-scroll-view-fade.glsl
new file mode 100644
index 0000000..ba6582f
--- /dev/null
+++ b/src/st/st-scroll-view-fade.glsl
@@ -0,0 +1,77 @@
+/*
+ * st-scroll-view-fade.glsl: Edge fade effect for StScrollView
+ *
+ * Copyright 2010 Intel Corporation.
+ * Copyright 2011 Adel Gadllah
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU Lesser General Public License,
+ * version 2.1, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+uniform sampler2D tex;
+uniform float height;
+uniform float width;
+uniform float fade_offset_top;
+uniform float fade_offset_bottom;
+uniform float fade_offset_left;
+uniform float fade_offset_right;
+uniform bool fade_edges_top;
+uniform bool fade_edges_right;
+uniform bool fade_edges_bottom;
+uniform bool fade_edges_left;
+uniform bool extend_fade_area;
+
+uniform vec2 fade_area_topleft;
+uniform vec2 fade_area_bottomright;
+
+void main ()
+{
+ cogl_color_out = cogl_color_in * texture2D (tex, vec2 (cogl_tex_coord_in[0].xy));
+
+ float y = height * cogl_tex_coord_in[0].y;
+ float x = width * cogl_tex_coord_in[0].x;
+ float ratio = 1.0;
+
+ if (x > fade_area_topleft[0] && x < fade_area_bottomright[0] &&
+ y > fade_area_topleft[1] && y < fade_area_bottomright[1])
+ {
+ float after_left = x - fade_area_topleft[0];
+ float before_right = fade_area_bottomright[0] - x;
+ float after_top = y - fade_area_topleft[1];
+ float before_bottom = fade_area_bottomright[1] - y;
+
+ if (after_top < fade_offset_top && fade_edges_top) {
+ ratio *= after_top / fade_offset_top;
+ }
+
+ if (before_bottom < fade_offset_bottom && fade_edges_bottom) {
+ ratio *= before_bottom / fade_offset_bottom;
+ }
+
+ if (after_left < fade_offset_left && fade_edges_left) {
+ ratio *= after_left / fade_offset_left;
+ }
+
+ if (before_right < fade_offset_right && fade_edges_right) {
+ ratio *= before_right / fade_offset_right;
+ }
+ } else if (extend_fade_area) {
+ if (x <= fade_area_topleft[0] && fade_edges_left ||
+ x >= fade_area_bottomright[0] && fade_edges_right ||
+ y <= fade_area_topleft[1] && fade_edges_top ||
+ y >= fade_area_bottomright[1] && fade_edges_bottom) {
+ ratio = 0.0;
+ }
+ }
+
+ cogl_color_out *= ratio;
+}
diff --git a/src/st/st-scroll-view-fade.h b/src/st/st-scroll-view-fade.h
new file mode 100644
index 0000000..2c65a77
--- /dev/null
+++ b/src/st/st-scroll-view-fade.h
@@ -0,0 +1,36 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * st-scroll-view-fade.h: Edge fade effect for StScrollView
+ *
+ * Copyright 2010 Intel Corporation.
+ * Copyright 2011 Adel Gadllah
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU Lesser General Public License,
+ * version 2.1, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __ST_SCROLL_VIEW_FADE_H__
+#define __ST_SCROLL_VIEW_FADE_H__
+
+#include <clutter/clutter.h>
+
+G_BEGIN_DECLS
+
+#define ST_TYPE_SCROLL_VIEW_FADE (st_scroll_view_fade_get_type ())
+G_DECLARE_FINAL_TYPE (StScrollViewFade, st_scroll_view_fade,
+ ST, SCROLL_VIEW_FADE, ClutterShaderEffect)
+
+ClutterEffect *st_scroll_view_fade_new (void);
+
+G_END_DECLS
+
+#endif /* __ST_SCROLL_VIEW_FADE_H__ */
diff --git a/src/st/st-scroll-view.c b/src/st/st-scroll-view.c
new file mode 100644
index 0000000..50de481
--- /dev/null
+++ b/src/st/st-scroll-view.c
@@ -0,0 +1,1327 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * st-scroll-view.h: Container with scroll-bars
+ *
+ * Copyright 2008 OpenedHand
+ * Copyright 2009 Intel Corporation.
+ * Copyright 2009, 2010 Red Hat, Inc.
+ * Copyright 2010 Maxim Ermilov
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU Lesser General Public License,
+ * version 2.1, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * SECTION:st-scroll-view
+ * @short_description: a container for scrollable children
+ *
+ * #StScrollView is a single child container for actors that implement
+ * #StScrollable. It provides scrollbars around the edge of the child to
+ * allow the user to move around the scrollable area.
+ */
+
+/* TODO: The code here currently only deals with height-for-width
+ * allocation; width-for-height allocation would need a second set of
+ * code paths through get_preferred_height()/get_preferred_width()/allocate()
+ * that reverse the roles of the horizontal and vertical scrollbars.
+ *
+ * TODO: The multiple layout passes with and without scrollbars when
+ * using the automatic policy causes considerable inefficiency because
+ * it breaks request caching; we should saved the last size passed
+ * into allocate() and if it's the same as previous size not repeat
+ * the determination of scrollbar visibility. This requires overriding
+ * queue_relayout() so we know when to discard the saved value.
+ *
+ * The size negotiation between the #StScrollView and the child is
+ * described in the documentation for #StScrollable; the significant
+ * part to note there is that reported minimum sizes for a scrolled
+ * child are the minimum sizes when no scrollbar is needed. This allows
+ * us to determine what scrollbars are visible without a need to look
+ * inside the #StAdjustment.
+ *
+ * The second simplification that we make that allows us to implement
+ * a straightforward height-for-width negotiation without multiple
+ * allocate passes is that when the scrollbar policy is
+ * AUTO, we always reserve space for the scrollbar in the
+ * reported minimum and natural size.
+ *
+ * See https://bugzilla.gnome.org/show_bug.cgi?id=611740 for a more
+ * detailed description of the considerations involved.
+ */
+
+#include "st-enum-types.h"
+#include "st-private.h"
+#include "st-scroll-view.h"
+#include "st-scroll-bar.h"
+#include "st-scrollable.h"
+#include "st-scroll-view-fade.h"
+#include <clutter/clutter.h>
+#include <math.h>
+
+static void clutter_container_iface_init (ClutterContainerIface *iface);
+
+static ClutterContainerIface *st_scroll_view_parent_iface = NULL;
+
+struct _StScrollViewPrivate
+{
+ /* a pointer to the child; this is actually stored
+ * inside StBin:child, but we keep it to avoid
+ * calling st_bin_get_child() every time we need it
+ */
+ ClutterActor *child;
+
+ StAdjustment *hadjustment;
+ ClutterActor *hscroll;
+ StAdjustment *vadjustment;
+ ClutterActor *vscroll;
+
+ StPolicyType hscrollbar_policy;
+ StPolicyType vscrollbar_policy;
+
+ gfloat row_size;
+ gfloat column_size;
+
+ StScrollViewFade *fade_effect;
+
+ guint row_size_set : 1;
+ guint column_size_set : 1;
+ guint mouse_scroll : 1;
+ guint overlay_scrollbars : 1;
+ guint hscrollbar_visible : 1;
+ guint vscrollbar_visible : 1;
+};
+
+G_DEFINE_TYPE_WITH_CODE (StScrollView, st_scroll_view, ST_TYPE_BIN,
+ G_ADD_PRIVATE (StScrollView)
+ G_IMPLEMENT_INTERFACE (CLUTTER_TYPE_CONTAINER,
+ clutter_container_iface_init))
+
+enum {
+ PROP_0,
+
+ PROP_HSCROLL,
+ PROP_VSCROLL,
+ PROP_HSCROLLBAR_POLICY,
+ PROP_VSCROLLBAR_POLICY,
+ PROP_HSCROLLBAR_VISIBLE,
+ PROP_VSCROLLBAR_VISIBLE,
+ PROP_MOUSE_SCROLL,
+ PROP_OVERLAY_SCROLLBARS,
+
+ N_PROPS
+};
+
+static GParamSpec *props[N_PROPS] = { NULL, };
+
+static void
+st_scroll_view_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ StScrollViewPrivate *priv = ((StScrollView *) object)->priv;
+
+ switch (property_id)
+ {
+ case PROP_HSCROLL:
+ g_value_set_object (value, priv->hscroll);
+ break;
+ case PROP_VSCROLL:
+ g_value_set_object (value, priv->vscroll);
+ break;
+ case PROP_HSCROLLBAR_POLICY:
+ g_value_set_enum (value, priv->hscrollbar_policy);
+ break;
+ case PROP_VSCROLLBAR_POLICY:
+ g_value_set_enum (value, priv->vscrollbar_policy);
+ break;
+ case PROP_HSCROLLBAR_VISIBLE:
+ g_value_set_boolean (value, priv->hscrollbar_visible);
+ break;
+ case PROP_VSCROLLBAR_VISIBLE:
+ g_value_set_boolean (value, priv->vscrollbar_visible);
+ break;
+ case PROP_MOUSE_SCROLL:
+ g_value_set_boolean (value, priv->mouse_scroll);
+ break;
+ case PROP_OVERLAY_SCROLLBARS:
+ g_value_set_boolean (value, priv->overlay_scrollbars);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ }
+}
+
+/**
+ * st_scroll_view_update_fade_effect:
+ * @scroll: a #StScrollView
+ * @fade_margins: a #ClutterMargin defining the vertical fade effects, in pixels.
+ *
+ * Sets the fade effects in all four edges of the view. A value of 0
+ * disables the effect.
+ */
+void
+st_scroll_view_update_fade_effect (StScrollView *scroll,
+ ClutterMargin *fade_margins)
+{
+ StScrollViewPrivate *priv = ST_SCROLL_VIEW (scroll)->priv;
+
+ /* A fade amount of other than 0 enables the effect. */
+ if (fade_margins->left != 0. || fade_margins->right != 0. ||
+ fade_margins->top != 0. || fade_margins->bottom != 0.)
+ {
+ if (priv->fade_effect == NULL)
+ {
+ priv->fade_effect = g_object_new (ST_TYPE_SCROLL_VIEW_FADE, NULL);
+
+ clutter_actor_add_effect_with_name (CLUTTER_ACTOR (scroll), "fade",
+ CLUTTER_EFFECT (priv->fade_effect));
+ }
+
+ g_object_set (priv->fade_effect,
+ "fade-margins", fade_margins,
+ NULL);
+ }
+ else
+ {
+ if (priv->fade_effect != NULL)
+ {
+ clutter_actor_remove_effect (CLUTTER_ACTOR (scroll),
+ CLUTTER_EFFECT (priv->fade_effect));
+ priv->fade_effect = NULL;
+ }
+ }
+}
+
+static void
+st_scroll_view_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ StScrollView *self = ST_SCROLL_VIEW (object);
+ StScrollViewPrivate *priv = self->priv;
+
+ switch (property_id)
+ {
+ case PROP_MOUSE_SCROLL:
+ st_scroll_view_set_mouse_scrolling (self,
+ g_value_get_boolean (value));
+ break;
+ case PROP_OVERLAY_SCROLLBARS:
+ st_scroll_view_set_overlay_scrollbars (self,
+ g_value_get_boolean (value));
+ break;
+ case PROP_HSCROLLBAR_POLICY:
+ st_scroll_view_set_policy (self,
+ g_value_get_enum (value),
+ priv->vscrollbar_policy);
+ break;
+ case PROP_VSCROLLBAR_POLICY:
+ st_scroll_view_set_policy (self,
+ priv->hscrollbar_policy,
+ g_value_get_enum (value));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ }
+}
+
+static void
+st_scroll_view_dispose (GObject *object)
+{
+ StScrollViewPrivate *priv = ST_SCROLL_VIEW (object)->priv;
+
+ if (priv->fade_effect)
+ {
+ clutter_actor_remove_effect (CLUTTER_ACTOR (object), CLUTTER_EFFECT (priv->fade_effect));
+ priv->fade_effect = NULL;
+ }
+
+ g_clear_pointer (&priv->vscroll, clutter_actor_destroy);
+ g_clear_pointer (&priv->hscroll, clutter_actor_destroy);
+
+ /* For most reliable freeing of memory, an object with signals
+ * like StAdjustment should be explicitly disposed. Since we own
+ * the adjustments, we take care of that. This also disconnects
+ * the signal handlers that we established on creation.
+ */
+ if (priv->hadjustment)
+ {
+ g_object_run_dispose (G_OBJECT (priv->hadjustment));
+ g_object_unref (priv->hadjustment);
+ priv->hadjustment = NULL;
+ }
+
+ if (priv->vadjustment)
+ {
+ g_object_run_dispose (G_OBJECT (priv->vadjustment));
+ g_object_unref (priv->vadjustment);
+ priv->vadjustment = NULL;
+ }
+
+ G_OBJECT_CLASS (st_scroll_view_parent_class)->dispose (object);
+}
+
+static void
+st_scroll_view_paint (ClutterActor *actor,
+ ClutterPaintContext *paint_context)
+{
+ StScrollViewPrivate *priv = ST_SCROLL_VIEW (actor)->priv;
+
+ st_widget_paint_background (ST_WIDGET (actor), paint_context);
+
+ if (priv->child)
+ clutter_actor_paint (priv->child, paint_context);
+ if (priv->hscrollbar_visible)
+ clutter_actor_paint (priv->hscroll, paint_context);
+ if (priv->vscrollbar_visible)
+ clutter_actor_paint (priv->vscroll, paint_context);
+}
+
+static void
+st_scroll_view_pick (ClutterActor *actor,
+ ClutterPickContext *pick_context)
+{
+ StScrollViewPrivate *priv = ST_SCROLL_VIEW (actor)->priv;
+
+ /* Chain up so we get a bounding box pained (if we are reactive) */
+ CLUTTER_ACTOR_CLASS (st_scroll_view_parent_class)->pick (actor, pick_context);
+
+ if (priv->child)
+ clutter_actor_pick (priv->child, pick_context);
+ if (priv->hscrollbar_visible)
+ clutter_actor_pick (priv->hscroll, pick_context);
+ if (priv->vscrollbar_visible)
+ clutter_actor_pick (priv->vscroll, pick_context);
+}
+
+static gboolean
+st_scroll_view_get_paint_volume (ClutterActor *actor,
+ ClutterPaintVolume *volume)
+{
+ return clutter_paint_volume_set_from_allocation (volume, actor);
+}
+
+static double
+get_scrollbar_width (StScrollView *scroll,
+ gfloat for_height)
+{
+ StScrollViewPrivate *priv = scroll->priv;
+
+ if (clutter_actor_is_visible (priv->vscroll))
+ {
+ gfloat min_size;
+
+ clutter_actor_get_preferred_width (CLUTTER_ACTOR (priv->vscroll), for_height,
+ &min_size, NULL);
+ return min_size;
+ }
+ else
+ return 0;
+}
+
+static double
+get_scrollbar_height (StScrollView *scroll,
+ gfloat for_width)
+{
+ StScrollViewPrivate *priv = scroll->priv;
+
+ if (clutter_actor_is_visible (priv->hscroll))
+ {
+ gfloat min_size;
+
+ clutter_actor_get_preferred_height (CLUTTER_ACTOR (priv->hscroll), for_width,
+ &min_size, NULL);
+
+ return min_size;
+ }
+ else
+ return 0;
+}
+
+static void
+st_scroll_view_get_preferred_width (ClutterActor *actor,
+ gfloat for_height,
+ gfloat *min_width_p,
+ gfloat *natural_width_p)
+{
+ StScrollViewPrivate *priv = ST_SCROLL_VIEW (actor)->priv;
+ StThemeNode *theme_node = st_widget_get_theme_node (ST_WIDGET (actor));
+ gboolean account_for_vscrollbar = FALSE;
+ gfloat min_width = 0, natural_width;
+ gfloat child_min_width, child_natural_width;
+
+ if (!priv->child)
+ return;
+
+ st_theme_node_adjust_for_height (theme_node, &for_height);
+
+ clutter_actor_get_preferred_width (priv->child, -1,
+ &child_min_width, &child_natural_width);
+
+ natural_width = child_natural_width;
+
+ switch (priv->hscrollbar_policy)
+ {
+ case ST_POLICY_NEVER:
+ min_width = child_min_width;
+ break;
+ case ST_POLICY_ALWAYS:
+ case ST_POLICY_AUTOMATIC:
+ case ST_POLICY_EXTERNAL:
+ /* Should theoretically use the min width of the hscrollbar,
+ * but that's not cleanly defined at the moment */
+ min_width = 0;
+ break;
+ default:
+ g_warn_if_reached();
+ break;
+ }
+
+ switch (priv->vscrollbar_policy)
+ {
+ case ST_POLICY_NEVER:
+ case ST_POLICY_EXTERNAL:
+ account_for_vscrollbar = FALSE;
+ break;
+ case ST_POLICY_ALWAYS:
+ account_for_vscrollbar = !priv->overlay_scrollbars;
+ break;
+ case ST_POLICY_AUTOMATIC:
+ /* For automatic scrollbars, we always request space for the vertical
+ * scrollbar; we won't know whether we actually need one until our
+ * height is assigned in allocate().
+ */
+ account_for_vscrollbar = !priv->overlay_scrollbars;
+ break;
+ default:
+ g_warn_if_reached();
+ break;
+ }
+
+ if (account_for_vscrollbar)
+ {
+ float sb_width = get_scrollbar_width (ST_SCROLL_VIEW (actor), for_height);
+
+ min_width += sb_width;
+ natural_width += sb_width;
+ }
+
+ if (min_width_p)
+ *min_width_p = min_width;
+
+ if (natural_width_p)
+ *natural_width_p = natural_width;
+
+ st_theme_node_adjust_preferred_width (theme_node, min_width_p, natural_width_p);
+}
+
+static void
+st_scroll_view_get_preferred_height (ClutterActor *actor,
+ gfloat for_width,
+ gfloat *min_height_p,
+ gfloat *natural_height_p)
+{
+ StScrollViewPrivate *priv = ST_SCROLL_VIEW (actor)->priv;
+ StThemeNode *theme_node = st_widget_get_theme_node (ST_WIDGET (actor));
+ gboolean account_for_hscrollbar = FALSE;
+ gfloat min_height = 0, natural_height;
+ gfloat child_min_height, child_natural_height;
+ gfloat child_min_width;
+ gfloat sb_width;
+
+ if (!priv->child)
+ return;
+
+ st_theme_node_adjust_for_width (theme_node, &for_width);
+
+ clutter_actor_get_preferred_width (priv->child, -1,
+ &child_min_width, NULL);
+
+ if (min_height_p)
+ *min_height_p = 0;
+
+ sb_width = get_scrollbar_width (ST_SCROLL_VIEW (actor), -1);
+
+ switch (priv->vscrollbar_policy)
+ {
+ case ST_POLICY_NEVER:
+ case ST_POLICY_EXTERNAL:
+ break;
+ case ST_POLICY_ALWAYS:
+ case ST_POLICY_AUTOMATIC:
+ /* We've requested space for the scrollbar, subtract it back out */
+ for_width -= sb_width;
+ break;
+ default:
+ g_warn_if_reached();
+ break;
+ }
+
+ switch (priv->hscrollbar_policy)
+ {
+ case ST_POLICY_NEVER:
+ case ST_POLICY_EXTERNAL:
+ account_for_hscrollbar = FALSE;
+ break;
+ case ST_POLICY_ALWAYS:
+ account_for_hscrollbar = !priv->overlay_scrollbars;
+ break;
+ case ST_POLICY_AUTOMATIC:
+ /* For automatic scrollbars, we always request space for the horizontal
+ * scrollbar; we won't know whether we actually need one until our
+ * width is assigned in allocate().
+ */
+ account_for_hscrollbar = !priv->overlay_scrollbars;
+ break;
+ default:
+ g_warn_if_reached();
+ break;
+ }
+
+ clutter_actor_get_preferred_height (priv->child, for_width,
+ &child_min_height, &child_natural_height);
+
+ natural_height = child_natural_height;
+
+ switch (priv->vscrollbar_policy)
+ {
+ case ST_POLICY_NEVER:
+ min_height = child_min_height;
+ break;
+ case ST_POLICY_ALWAYS:
+ case ST_POLICY_AUTOMATIC:
+ case ST_POLICY_EXTERNAL:
+ /* Should theoretically use the min height of the vscrollbar,
+ * but that's not cleanly defined at the moment */
+ min_height = 0;
+ break;
+ default:
+ g_warn_if_reached();
+ break;
+ }
+
+ if (account_for_hscrollbar)
+ {
+ float sb_height = get_scrollbar_height (ST_SCROLL_VIEW (actor), for_width);
+
+ min_height += sb_height;
+ natural_height += sb_height;
+ }
+
+ if (min_height_p)
+ *min_height_p = min_height;
+
+ if (natural_height_p)
+ *natural_height_p = natural_height;
+
+ st_theme_node_adjust_preferred_height (theme_node, min_height_p, natural_height_p);
+}
+
+static void
+st_scroll_view_allocate (ClutterActor *actor,
+ const ClutterActorBox *box)
+{
+ ClutterActorBox content_box, child_box;
+ gfloat avail_width, avail_height, sb_width, sb_height;
+ gboolean hscrollbar_visible, vscrollbar_visible;
+
+ StScrollViewPrivate *priv = ST_SCROLL_VIEW (actor)->priv;
+ StThemeNode *theme_node = st_widget_get_theme_node (ST_WIDGET (actor));
+
+ clutter_actor_set_allocation (actor, box);
+
+ st_theme_node_get_content_box (theme_node, box, &content_box);
+
+ avail_width = content_box.x2 - content_box.x1;
+ avail_height = content_box.y2 - content_box.y1;
+
+ if (clutter_actor_get_request_mode (actor) == CLUTTER_REQUEST_HEIGHT_FOR_WIDTH)
+ {
+ sb_width = get_scrollbar_width (ST_SCROLL_VIEW (actor), -1);
+ sb_height = get_scrollbar_height (ST_SCROLL_VIEW (actor), sb_width);
+ }
+ else
+ {
+ sb_height = get_scrollbar_height (ST_SCROLL_VIEW (actor), -1);
+ sb_width = get_scrollbar_width (ST_SCROLL_VIEW (actor), sb_height);
+ }
+
+ /* Determine what scrollbars are visible. The basic idea of the
+ * handling of an automatic scrollbars is that we start off with the
+ * assumption that we don't need any scrollbars, see if that works,
+ * and if not add horizontal and vertical scrollbars until we are no
+ * longer overflowing.
+ */
+ if (priv->child)
+ {
+ gfloat child_min_width;
+ gfloat child_min_height;
+
+ clutter_actor_get_preferred_width (priv->child, -1,
+ &child_min_width, NULL);
+
+ if (priv->vscrollbar_policy == ST_POLICY_AUTOMATIC)
+ {
+ if (priv->hscrollbar_policy == ST_POLICY_AUTOMATIC)
+ {
+ /* Pass one, try without a vertical scrollbar */
+ clutter_actor_get_preferred_height (priv->child, avail_width, &child_min_height, NULL);
+ vscrollbar_visible = child_min_height > avail_height;
+ hscrollbar_visible = child_min_width > avail_width - (vscrollbar_visible ? sb_width : 0);
+ vscrollbar_visible = child_min_height > avail_height - (hscrollbar_visible ? sb_height : 0);
+
+ /* Pass two - if we needed a vertical scrollbar, get a new preferred height */
+ if (vscrollbar_visible)
+ {
+ clutter_actor_get_preferred_height (priv->child, MAX (avail_width - sb_width, 0),
+ &child_min_height, NULL);
+ hscrollbar_visible = child_min_width > avail_width - sb_width;
+ }
+ }
+ else
+ {
+ hscrollbar_visible = priv->hscrollbar_policy == ST_POLICY_ALWAYS;
+
+ /* try without a vertical scrollbar */
+ clutter_actor_get_preferred_height (priv->child, avail_width, &child_min_height, NULL);
+ vscrollbar_visible = child_min_height > avail_height - (hscrollbar_visible ? sb_height : 0);
+ }
+ }
+ else
+ {
+ vscrollbar_visible = priv->vscrollbar_policy == ST_POLICY_ALWAYS;
+
+ if (priv->hscrollbar_policy == ST_POLICY_AUTOMATIC)
+ hscrollbar_visible = child_min_width > avail_height - (vscrollbar_visible ? 0 : sb_width);
+ else
+ hscrollbar_visible = priv->hscrollbar_policy == ST_POLICY_ALWAYS;
+ }
+ }
+ else
+ {
+ hscrollbar_visible = priv->hscrollbar_policy != ST_POLICY_NEVER &&
+ priv->hscrollbar_policy != ST_POLICY_EXTERNAL;
+ vscrollbar_visible = priv->vscrollbar_policy != ST_POLICY_NEVER &&
+ priv->vscrollbar_policy != ST_POLICY_EXTERNAL;
+ }
+
+ /* Whether or not we show the scrollbars, if the scrollbars are visible
+ * actors, we need to give them some allocation, so we unconditionally
+ * give them the "right" allocation; that might overlap the child when
+ * the scrollbars are not visible, but it doesn't matter because we
+ * don't include them in pick or paint.
+ */
+
+ /* Vertical scrollbar */
+ if (clutter_actor_get_text_direction (actor) == CLUTTER_TEXT_DIRECTION_RTL)
+ {
+ child_box.x1 = content_box.x1;
+ child_box.x2 = content_box.x1 + sb_width;
+ }
+ else
+ {
+ child_box.x1 = content_box.x2 - sb_width;
+ child_box.x2 = content_box.x2;
+ }
+ child_box.y1 = content_box.y1;
+ child_box.y2 = content_box.y2 - (hscrollbar_visible ? sb_height : 0);
+
+ clutter_actor_allocate (priv->vscroll, &child_box);
+
+ /* Horizontal scrollbar */
+ if (clutter_actor_get_text_direction (actor) == CLUTTER_TEXT_DIRECTION_RTL)
+ {
+ child_box.x1 = content_box.x1 + (vscrollbar_visible ? sb_width : 0);
+ child_box.x2 = content_box.x2;
+ }
+ else
+ {
+ child_box.x1 = content_box.x1;
+ child_box.x2 = content_box.x2 - (vscrollbar_visible ? sb_width : 0);
+ }
+ child_box.y1 = content_box.y2 - sb_height;
+ child_box.y2 = content_box.y2;
+
+ clutter_actor_allocate (priv->hscroll, &child_box);
+
+ /* In case the scrollbar policy is NEVER or EXTERNAL or scrollbars
+ * should be overlaid, we don't trim the content box allocation by
+ * the scrollbar size.
+ * Fold this into the scrollbar sizes to simplify the rest of the
+ * computations.
+ */
+ if (priv->hscrollbar_policy == ST_POLICY_NEVER ||
+ priv->hscrollbar_policy == ST_POLICY_EXTERNAL ||
+ priv->overlay_scrollbars)
+ sb_height = 0;
+ if (priv->vscrollbar_policy == ST_POLICY_NEVER ||
+ priv->vscrollbar_policy == ST_POLICY_EXTERNAL ||
+ priv->overlay_scrollbars)
+ sb_width = 0;
+
+ /* Child */
+ if (clutter_actor_get_text_direction (actor) == CLUTTER_TEXT_DIRECTION_RTL)
+ {
+ child_box.x1 = content_box.x1 + sb_width;
+ child_box.x2 = content_box.x2;
+ }
+ else
+ {
+ child_box.x1 = content_box.x1;
+ child_box.x2 = content_box.x2 - sb_width;
+ }
+ child_box.y1 = content_box.y1;
+ child_box.y2 = content_box.y2 - sb_height;
+
+ if (priv->child)
+ clutter_actor_allocate (priv->child, &child_box);
+
+ if (priv->hscrollbar_visible != hscrollbar_visible)
+ {
+ g_object_freeze_notify (G_OBJECT (actor));
+ priv->hscrollbar_visible = hscrollbar_visible;
+ g_object_notify_by_pspec (G_OBJECT (actor),
+ props[PROP_HSCROLLBAR_VISIBLE]);
+ g_object_thaw_notify (G_OBJECT (actor));
+ }
+
+ if (priv->vscrollbar_visible != vscrollbar_visible)
+ {
+ g_object_freeze_notify (G_OBJECT (actor));
+ priv->vscrollbar_visible = vscrollbar_visible;
+ g_object_notify_by_pspec (G_OBJECT (actor),
+ props[PROP_VSCROLLBAR_VISIBLE]);
+ g_object_thaw_notify (G_OBJECT (actor));
+ }
+
+}
+
+static void
+adjust_with_direction (StAdjustment *adj,
+ ClutterScrollDirection direction)
+{
+ gdouble delta;
+
+ switch (direction)
+ {
+ case CLUTTER_SCROLL_UP:
+ case CLUTTER_SCROLL_LEFT:
+ delta = -1.0;
+ break;
+ case CLUTTER_SCROLL_RIGHT:
+ case CLUTTER_SCROLL_DOWN:
+ delta = 1.0;
+ break;
+ case CLUTTER_SCROLL_SMOOTH:
+ default:
+ g_assert_not_reached ();
+ break;
+ }
+
+ st_adjustment_adjust_for_scroll_event (adj, delta);
+}
+
+static void
+st_scroll_view_style_changed (StWidget *widget)
+{
+ StScrollView *self = ST_SCROLL_VIEW (widget);
+ gboolean has_vfade, has_hfade;
+ double vfade_offset = 0.0;
+ double hfade_offset = 0.0;
+
+ StThemeNode *theme_node = st_widget_get_theme_node (widget);
+
+ has_vfade = st_theme_node_lookup_length (theme_node, "-st-vfade-offset", FALSE, &vfade_offset);
+ has_hfade = st_theme_node_lookup_length (theme_node, "-st-hfade-offset", FALSE, &hfade_offset);
+ if (has_vfade || has_hfade)
+ {
+ st_scroll_view_update_fade_effect (self,
+ &(ClutterMargin) {
+ .top = vfade_offset,
+ .bottom = vfade_offset,
+ .left = hfade_offset,
+ .right = hfade_offset,
+ });
+ }
+
+ ST_WIDGET_CLASS (st_scroll_view_parent_class)->style_changed (widget);
+}
+
+static gboolean
+st_scroll_view_scroll_event (ClutterActor *self,
+ ClutterScrollEvent *event)
+{
+ StScrollViewPrivate *priv = ST_SCROLL_VIEW (self)->priv;
+ ClutterTextDirection direction;
+
+ /* don't handle scroll events if requested not to */
+ if (!priv->mouse_scroll)
+ return FALSE;
+
+ if (clutter_event_is_pointer_emulated ((ClutterEvent *) event))
+ return TRUE;
+
+ direction = clutter_actor_get_text_direction (self);
+
+ switch (event->direction)
+ {
+ case CLUTTER_SCROLL_SMOOTH:
+ {
+ gdouble delta_x, delta_y;
+ clutter_event_get_scroll_delta ((ClutterEvent *)event, &delta_x, &delta_y);
+
+ if (direction == CLUTTER_TEXT_DIRECTION_RTL)
+ delta_x *= -1;
+
+ st_adjustment_adjust_for_scroll_event (priv->hadjustment, delta_x);
+ st_adjustment_adjust_for_scroll_event (priv->vadjustment, delta_y);
+ }
+ break;
+ case CLUTTER_SCROLL_UP:
+ case CLUTTER_SCROLL_DOWN:
+ adjust_with_direction (priv->vadjustment, event->direction);
+ break;
+ case CLUTTER_SCROLL_LEFT:
+ case CLUTTER_SCROLL_RIGHT:
+ if (direction == CLUTTER_TEXT_DIRECTION_RTL)
+ {
+ ClutterScrollDirection dir;
+
+ dir = event->direction == CLUTTER_SCROLL_LEFT ? CLUTTER_SCROLL_RIGHT
+ : CLUTTER_SCROLL_LEFT;
+ adjust_with_direction (priv->hadjustment, dir);
+ }
+ else
+ {
+ adjust_with_direction (priv->hadjustment, event->direction);
+ }
+ break;
+ default:
+ g_warn_if_reached();
+ break;
+ }
+
+ return TRUE;
+}
+
+static void
+st_scroll_view_class_init (StScrollViewClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ ClutterActorClass *actor_class = CLUTTER_ACTOR_CLASS (klass);
+ StWidgetClass *widget_class = ST_WIDGET_CLASS (klass);
+
+ object_class->get_property = st_scroll_view_get_property;
+ object_class->set_property = st_scroll_view_set_property;
+ object_class->dispose = st_scroll_view_dispose;
+
+ actor_class->paint = st_scroll_view_paint;
+ actor_class->pick = st_scroll_view_pick;
+ actor_class->get_paint_volume = st_scroll_view_get_paint_volume;
+ actor_class->get_preferred_width = st_scroll_view_get_preferred_width;
+ actor_class->get_preferred_height = st_scroll_view_get_preferred_height;
+ actor_class->allocate = st_scroll_view_allocate;
+ actor_class->scroll_event = st_scroll_view_scroll_event;
+
+ widget_class->style_changed = st_scroll_view_style_changed;
+
+ /**
+ * StScrollView:hscroll:
+ *
+ * The horizontal #StScrollBar for the #StScrollView.
+ */
+ props[PROP_HSCROLL] =
+ g_param_spec_object ("hscroll",
+ "StScrollBar",
+ "Horizontal scroll indicator",
+ ST_TYPE_SCROLL_BAR,
+ ST_PARAM_READABLE);
+
+ /**
+ * StScrollView:vscroll:
+ *
+ * The vertical #StScrollBar for the #StScrollView.
+ */
+ props[PROP_VSCROLL] =
+ g_param_spec_object ("vscroll",
+ "StScrollBar",
+ "Vertical scroll indicator",
+ ST_TYPE_SCROLL_BAR,
+ ST_PARAM_READABLE);
+
+ /**
+ * StScrollView:vscrollbar-policy:
+ *
+ * The #StPolicyType for when to show the vertical #StScrollBar.
+ */
+ props[PROP_VSCROLLBAR_POLICY] =
+ g_param_spec_enum ("vscrollbar-policy",
+ "Vertical Scrollbar Policy",
+ "When the vertical scrollbar is displayed",
+ ST_TYPE_POLICY_TYPE,
+ ST_POLICY_AUTOMATIC,
+ ST_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
+
+ /**
+ * StScrollView:hscrollbar-policy:
+ *
+ * The #StPolicyType for when to show the horizontal #StScrollBar.
+ */
+ props[PROP_HSCROLLBAR_POLICY] =
+ g_param_spec_enum ("hscrollbar-policy",
+ "Horizontal Scrollbar Policy",
+ "When the horizontal scrollbar is displayed",
+ ST_TYPE_POLICY_TYPE,
+ ST_POLICY_AUTOMATIC,
+ ST_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
+
+ /**
+ * StScrollView:hscrollbar-visible:
+ *
+ * Whether the horizontal #StScrollBar is visible.
+ */
+ props[PROP_HSCROLLBAR_VISIBLE] =
+ g_param_spec_boolean ("hscrollbar-visible",
+ "Horizontal Scrollbar Visibility",
+ "Whether the horizontal scrollbar is visible",
+ TRUE,
+ ST_PARAM_READABLE);
+
+ /**
+ * StScrollView:vscrollbar-visible:
+ *
+ * Whether the vertical #StScrollBar is visible.
+ */
+ props[PROP_VSCROLLBAR_VISIBLE] =
+ g_param_spec_boolean ("vscrollbar-visible",
+ "Vertical Scrollbar Visibility",
+ "Whether the vertical scrollbar is visible",
+ TRUE,
+ ST_PARAM_READABLE);
+
+ /**
+ * StScrollView:enable-mouse-scrolling:
+ *
+ * Whether to enable automatic mouse wheel scrolling.
+ */
+ props[PROP_MOUSE_SCROLL] =
+ g_param_spec_boolean ("enable-mouse-scrolling",
+ "Enable Mouse Scrolling",
+ "Enable automatic mouse wheel scrolling",
+ TRUE,
+ ST_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
+
+ /**
+ * StScrollView:overlay-scrollbars:
+ *
+ * Whether scrollbars are painted on top of the content.
+ */
+ props[PROP_OVERLAY_SCROLLBARS] =
+ g_param_spec_boolean ("overlay-scrollbars",
+ "Use Overlay Scrollbars",
+ "Overlay scrollbars over the content",
+ FALSE,
+ ST_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
+
+ g_object_class_install_properties (object_class, N_PROPS, props);
+}
+
+static void
+st_scroll_view_init (StScrollView *self)
+{
+ StScrollViewPrivate *priv = self->priv = st_scroll_view_get_instance_private (self);
+
+ priv->hscrollbar_policy = ST_POLICY_AUTOMATIC;
+ priv->vscrollbar_policy = ST_POLICY_AUTOMATIC;
+
+ priv->hadjustment = g_object_new (ST_TYPE_ADJUSTMENT,
+ "actor", self,
+ NULL);
+ priv->hscroll = g_object_new (ST_TYPE_SCROLL_BAR,
+ "adjustment", priv->hadjustment,
+ "vertical", FALSE,
+ NULL);
+
+ priv->vadjustment = g_object_new (ST_TYPE_ADJUSTMENT,
+ "actor", self,
+ NULL);
+ priv->vscroll = g_object_new (ST_TYPE_SCROLL_BAR,
+ "adjustment", priv->vadjustment,
+ "vertical", TRUE,
+ NULL);
+
+ clutter_actor_add_child (CLUTTER_ACTOR (self), priv->hscroll);
+ clutter_actor_add_child (CLUTTER_ACTOR (self), priv->vscroll);
+
+ /* mouse scroll is enabled by default, so we also need to be reactive */
+ priv->mouse_scroll = TRUE;
+ g_object_set (G_OBJECT (self), "reactive", TRUE, NULL);
+}
+
+static void
+st_scroll_view_add (ClutterContainer *container,
+ ClutterActor *actor)
+{
+ StScrollView *self = ST_SCROLL_VIEW (container);
+ StScrollViewPrivate *priv = self->priv;
+
+ if (ST_IS_SCROLLABLE (actor))
+ {
+ priv->child = actor;
+
+ /* chain up to StBin::add() */
+ st_scroll_view_parent_iface->add (container, actor);
+
+ st_scrollable_set_adjustments (ST_SCROLLABLE (actor),
+ priv->hadjustment, priv->vadjustment);
+ }
+ else
+ {
+ g_warning ("Attempting to add an actor of type %s to "
+ "a StScrollView, but the actor does "
+ "not implement StScrollable.",
+ g_type_name (G_OBJECT_TYPE (actor)));
+ }
+}
+
+static void
+st_scroll_view_remove (ClutterContainer *container,
+ ClutterActor *actor)
+{
+ StScrollView *self = ST_SCROLL_VIEW (container);
+ StScrollViewPrivate *priv = self->priv;
+
+ if (actor == priv->child)
+ {
+ g_object_ref (priv->child);
+
+ /* chain up to StBin::remove() */
+ st_scroll_view_parent_iface->remove (container, actor);
+
+ st_scrollable_set_adjustments (ST_SCROLLABLE (priv->child),
+ NULL, NULL);
+
+ g_object_unref (priv->child);
+ priv->child = NULL;
+ }
+ else
+ {
+ if (actor == priv->vscroll)
+ priv->vscroll = NULL;
+ else if (actor == priv->hscroll)
+ priv->hscroll = NULL;
+ else
+ g_assert ("Unknown child removed from StScrollView");
+
+ clutter_actor_remove_child (CLUTTER_ACTOR (container), actor);
+ }
+}
+
+static void
+clutter_container_iface_init (ClutterContainerIface *iface)
+{
+ /* store a pointer to the StBin implementation of
+ * ClutterContainer so that we can chain up when
+ * overriding the methods
+ */
+ st_scroll_view_parent_iface = g_type_interface_peek_parent (iface);
+
+ iface->add = st_scroll_view_add;
+ iface->remove = st_scroll_view_remove;
+}
+
+/**
+ * st_scroll_view_new:
+ *
+ * Create a new #StScrollView.
+ *
+ * Returns: (transfer full): a new #StScrollView
+ */
+StWidget *
+st_scroll_view_new (void)
+{
+ return g_object_new (ST_TYPE_SCROLL_VIEW, NULL);
+}
+
+/**
+ * st_scroll_view_get_hscroll_bar:
+ * @scroll: a #StScrollView
+ *
+ * Gets the horizontal #StScrollBar of the #StScrollView.
+ *
+ * Returns: (transfer none): the horizontal scrollbar
+ */
+ClutterActor *
+st_scroll_view_get_hscroll_bar (StScrollView *scroll)
+{
+ g_return_val_if_fail (ST_IS_SCROLL_VIEW (scroll), NULL);
+
+ return scroll->priv->hscroll;
+}
+
+/**
+ * st_scroll_view_get_vscroll_bar:
+ * @scroll: a #StScrollView
+ *
+ * Gets the vertical scrollbar of the #StScrollView.
+ *
+ * Returns: (transfer none): the vertical #StScrollBar
+ */
+ClutterActor *
+st_scroll_view_get_vscroll_bar (StScrollView *scroll)
+{
+ g_return_val_if_fail (ST_IS_SCROLL_VIEW (scroll), NULL);
+
+ return scroll->priv->vscroll;
+}
+
+/**
+ * st_scroll_view_get_column_size:
+ * @scroll: a #StScrollView
+ *
+ * Get the step increment of the horizontal plane.
+ *
+ * Returns: the horizontal step increment
+ */
+gfloat
+st_scroll_view_get_column_size (StScrollView *scroll)
+{
+ gdouble column_size;
+
+ g_return_val_if_fail (scroll, 0);
+
+ g_object_get (scroll->priv->hadjustment,
+ "step-increment", &column_size,
+ NULL);
+
+ return column_size;
+}
+
+/**
+ * st_scroll_view_set_column_size:
+ * @scroll: a #StScrollView
+ * @column_size: horizontal step increment
+ *
+ * Set the step increment of the horizontal plane to @column_size.
+ */
+void
+st_scroll_view_set_column_size (StScrollView *scroll,
+ gfloat column_size)
+{
+ g_return_if_fail (scroll);
+
+ if (column_size < 0)
+ {
+ scroll->priv->column_size_set = FALSE;
+ scroll->priv->column_size = -1;
+ }
+ else
+ {
+ scroll->priv->column_size_set = TRUE;
+ scroll->priv->column_size = column_size;
+
+ g_object_set (scroll->priv->hadjustment,
+ "step-increment", (gdouble) scroll->priv->column_size,
+ NULL);
+ }
+}
+
+/**
+ * st_scroll_view_get_row_size:
+ * @scroll: a #StScrollView
+ *
+ * Get the step increment of the vertical plane.
+ *
+ * Returns: the vertical step increment
+ */
+gfloat
+st_scroll_view_get_row_size (StScrollView *scroll)
+{
+ gdouble row_size;
+
+ g_return_val_if_fail (scroll, 0);
+
+ g_object_get (scroll->priv->vadjustment,
+ "step-increment", &row_size,
+ NULL);
+
+ return row_size;
+}
+
+/**
+ * st_scroll_view_set_row_size:
+ * @scroll: a #StScrollView
+ * @row_size: vertical step increment
+ *
+ * Set the step increment of the vertical plane to @row_size.
+ */
+void
+st_scroll_view_set_row_size (StScrollView *scroll,
+ gfloat row_size)
+{
+ g_return_if_fail (scroll);
+
+ if (row_size < 0)
+ {
+ scroll->priv->row_size_set = FALSE;
+ scroll->priv->row_size = -1;
+ }
+ else
+ {
+ scroll->priv->row_size_set = TRUE;
+ scroll->priv->row_size = row_size;
+
+ g_object_set (scroll->priv->vadjustment,
+ "step-increment", (gdouble) scroll->priv->row_size,
+ NULL);
+ }
+}
+
+/**
+ * st_scroll_view_set_mouse_scrolling:
+ * @scroll: a #StScrollView
+ * @enabled: %TRUE or %FALSE
+ *
+ * Sets automatic mouse wheel scrolling to enabled or disabled.
+ */
+void
+st_scroll_view_set_mouse_scrolling (StScrollView *scroll,
+ gboolean enabled)
+{
+ StScrollViewPrivate *priv;
+
+ g_return_if_fail (ST_IS_SCROLL_VIEW (scroll));
+
+ priv = ST_SCROLL_VIEW (scroll)->priv;
+
+ if (priv->mouse_scroll != enabled)
+ {
+ priv->mouse_scroll = enabled;
+
+ /* make sure we can receive mouse wheel events */
+ if (enabled)
+ clutter_actor_set_reactive ((ClutterActor *) scroll, TRUE);
+
+ g_object_notify_by_pspec (G_OBJECT (scroll), props[PROP_MOUSE_SCROLL]);
+ }
+}
+
+/**
+ * st_scroll_view_get_mouse_scrolling:
+ * @scroll: a #StScrollView
+ *
+ * Get whether automatic mouse wheel scrolling is enabled or disabled.
+ *
+ * Returns: %TRUE if enabled, %FALSE otherwise
+ */
+gboolean
+st_scroll_view_get_mouse_scrolling (StScrollView *scroll)
+{
+ StScrollViewPrivate *priv;
+
+ g_return_val_if_fail (ST_IS_SCROLL_VIEW (scroll), FALSE);
+
+ priv = ST_SCROLL_VIEW (scroll)->priv;
+
+ return priv->mouse_scroll;
+}
+
+/**
+ * st_scroll_view_set_overlay_scrollbars:
+ * @scroll: A #StScrollView
+ * @enabled: Whether to enable overlay scrollbars
+ *
+ * Sets whether scrollbars are painted on top of the content.
+ */
+void
+st_scroll_view_set_overlay_scrollbars (StScrollView *scroll,
+ gboolean enabled)
+{
+ StScrollViewPrivate *priv;
+
+ g_return_if_fail (ST_IS_SCROLL_VIEW (scroll));
+
+ priv = ST_SCROLL_VIEW (scroll)->priv;
+
+ if (priv->overlay_scrollbars != enabled)
+ {
+ priv->overlay_scrollbars = enabled;
+ g_object_notify_by_pspec (G_OBJECT (scroll),
+ props[PROP_OVERLAY_SCROLLBARS]);
+ clutter_actor_queue_relayout (CLUTTER_ACTOR (scroll));
+ }
+}
+
+/**
+ * st_scroll_view_get_overlay_scrollbars:
+ * @scroll: A #StScrollView
+ *
+ * Gets whether scrollbars are painted on top of the content.
+ *
+ * Returns: %TRUE if enabled, %FALSE otherwise
+ */
+gboolean
+st_scroll_view_get_overlay_scrollbars (StScrollView *scroll)
+{
+ StScrollViewPrivate *priv;
+
+ g_return_val_if_fail (ST_IS_SCROLL_VIEW (scroll), FALSE);
+
+ priv = ST_SCROLL_VIEW (scroll)->priv;
+
+ return priv->overlay_scrollbars;
+}
+
+/**
+ * st_scroll_view_set_policy:
+ * @scroll: A #StScrollView
+ * @hscroll: Whether to enable horizontal scrolling
+ * @vscroll: Whether to enable vertical scrolling
+ *
+ * Set the scroll policy.
+ */
+void
+st_scroll_view_set_policy (StScrollView *scroll,
+ StPolicyType hscroll,
+ StPolicyType vscroll)
+{
+ StScrollViewPrivate *priv;
+
+ g_return_if_fail (ST_IS_SCROLL_VIEW (scroll));
+
+ priv = ST_SCROLL_VIEW (scroll)->priv;
+
+ if (priv->hscrollbar_policy == hscroll && priv->vscrollbar_policy == vscroll)
+ return;
+
+ g_object_freeze_notify ((GObject *) scroll);
+
+ if (priv->hscrollbar_policy != hscroll)
+ {
+ priv->hscrollbar_policy = hscroll;
+ g_object_notify_by_pspec ((GObject *) scroll,
+ props[PROP_HSCROLLBAR_POLICY]);
+ }
+
+ if (priv->vscrollbar_policy != vscroll)
+ {
+ priv->vscrollbar_policy = vscroll;
+ g_object_notify_by_pspec ((GObject *) scroll,
+ props[PROP_VSCROLLBAR_POLICY]);
+ }
+
+ clutter_actor_queue_relayout (CLUTTER_ACTOR (scroll));
+
+ g_object_thaw_notify ((GObject *) scroll);
+}
diff --git a/src/st/st-scroll-view.h b/src/st/st-scroll-view.h
new file mode 100644
index 0000000..e2acaca
--- /dev/null
+++ b/src/st/st-scroll-view.h
@@ -0,0 +1,90 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * st-scroll-view.h: Container with scroll-bars
+ *
+ * Copyright 2008 OpenedHand
+ * Copyright 2009 Intel Corporation.
+ * Copyright 2010 Red Hat, Inc.
+ * Copyright 2010 Maxim Ermilov
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU Lesser General Public License,
+ * version 2.1, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#if !defined(ST_H_INSIDE) && !defined(ST_COMPILATION)
+#error "Only <st/st.h> can be included directly.h"
+#endif
+
+#ifndef __ST_SCROLL_VIEW_H__
+#define __ST_SCROLL_VIEW_H__
+
+#include <st/st-bin.h>
+
+G_BEGIN_DECLS
+
+#define ST_TYPE_SCROLL_VIEW (st_scroll_view_get_type())
+G_DECLARE_FINAL_TYPE (StScrollView, st_scroll_view, ST, SCROLL_VIEW, StBin)
+
+typedef enum
+{
+ ST_POLICY_ALWAYS,
+ ST_POLICY_AUTOMATIC,
+ ST_POLICY_NEVER,
+ ST_POLICY_EXTERNAL,
+} StPolicyType;
+
+typedef struct _StScrollViewPrivate StScrollViewPrivate;
+
+/**
+ * StScrollView:
+ *
+ * The contents of this structure are private and should only be accessed
+ * through the public API.
+ */
+struct _StScrollView
+{
+ /*< private >*/
+ StBin parent_instance;
+
+ StScrollViewPrivate *priv;
+};
+
+StWidget *st_scroll_view_new (void);
+
+ClutterActor *st_scroll_view_get_hscroll_bar (StScrollView *scroll);
+ClutterActor *st_scroll_view_get_vscroll_bar (StScrollView *scroll);
+
+gfloat st_scroll_view_get_column_size (StScrollView *scroll);
+void st_scroll_view_set_column_size (StScrollView *scroll,
+ gfloat column_size);
+
+gfloat st_scroll_view_get_row_size (StScrollView *scroll);
+void st_scroll_view_set_row_size (StScrollView *scroll,
+ gfloat row_size);
+
+void st_scroll_view_set_mouse_scrolling (StScrollView *scroll,
+ gboolean enabled);
+gboolean st_scroll_view_get_mouse_scrolling (StScrollView *scroll);
+
+void st_scroll_view_set_overlay_scrollbars (StScrollView *scroll,
+ gboolean enabled);
+gboolean st_scroll_view_get_overlay_scrollbars (StScrollView *scroll);
+
+void st_scroll_view_set_policy (StScrollView *scroll,
+ StPolicyType hscroll,
+ StPolicyType vscroll);
+void st_scroll_view_update_fade_effect (StScrollView *scroll,
+ ClutterMargin *fade_margins);
+
+G_END_DECLS
+
+#endif /* __ST_SCROLL_VIEW_H__ */
diff --git a/src/st/st-scrollable.c b/src/st/st-scrollable.c
new file mode 100644
index 0000000..3a77052
--- /dev/null
+++ b/src/st/st-scrollable.c
@@ -0,0 +1,196 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * st-scrollable.c: Scrollable interface
+ *
+ * Copyright 2008 OpenedHand
+ * Copyright 2009 Intel Corporation.
+ * Copyright 2010 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU Lesser General Public License,
+ * version 2.1, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "st-private.h"
+#include "st-scrollable.h"
+
+G_DEFINE_INTERFACE (StScrollable, st_scrollable, G_TYPE_OBJECT)
+
+/**
+ * SECTION:st-scrollable
+ * @short_description: A #ClutterActor that can be scrolled
+ *
+ * The #StScrollable interface is exposed by actors that support scrolling.
+ *
+ * The interface contains methods for getting and setting the adjustments
+ * for scrolling; these adjustments will be used to hook the scrolled
+ * position up to scrollbars or other external controls. When a #StScrollable
+ * is added to a parent container, the parent container is responsible
+ * for setting the adjustments. The parent container then sets the adjustments
+ * back to %NULL when the scrollable is removed.
+ *
+ * For #StScrollable supporting height-for-width size negotiation, size
+ * negotiation works as follows:
+ *
+ * In response to get_preferred_width(), the scrollable should report
+ * the minimum width at which horizontal scrolling is needed for the
+ * preferred width, and natural width of the actor when not
+ * horizontally scrolled as the natural width.
+ *
+ * The for_width passed into get_preferred_height() is the width at which
+ * the scrollable will be allocated; this will be smaller than the minimum
+ * width when scrolling horizontally, so the scrollable may want to adjust
+ * it up to the minimum width before computing a preferred height. (Other
+ * scrollables may want to fit as much content into the allocated area
+ * as possible and only scroll what absolutely needs to scroll - consider,
+ * for example, the line-wrapping behavior of a text editor where there
+ * is a long line without any spaces.) As for width, get_preferred_height()
+ * should return the minimum size at which no scrolling is needed for the
+ * minimum height, and the natural size of the actor when not vertically scrolled
+ * as the natural height.
+ *
+ * In allocate() the allocation box passed in will be actual allocated
+ * size of the actor so will be smaller than the reported minimum
+ * width and/or height when scrolling is present. Any scrollable actor
+ * must support being allocated at any size down to 0x0 without
+ * crashing, however if the actor has content around the scrolled area
+ * and has an absolute minimum size that's bigger than 0x0 its
+ * acceptable for it to misdraw between 0x0 and the absolute minimum
+ * size. It's up to the application author to avoid letting the user
+ * resize the scroll view small enough so that the scrolled area
+ * vanishes.
+ *
+ * In response to allocate, in addition to normal handling, the
+ * scrollable should also set the limits of the the horizontal and
+ * vertical adjustments that were set on it earlier. The standard
+ * settings are:
+ *
+ * lower: 0
+ * page_size: allocated size (width or height)
+ * upper: MAX (total size of the scrolled area,allocated_size)
+ * step_increment: natural row/column height or a fixed fraction of the page size
+ * page_increment: page_size - step_increment
+ */
+static void
+st_scrollable_default_init (StScrollableInterface *g_iface)
+{
+ static gboolean initialized = FALSE;
+
+ if (!initialized)
+ {
+ /**
+ * StScrollable:hadjustment:
+ *
+ * The horizontal #StAdjustment used by the #StScrollable.
+ *
+ * Implementations should override this property to provide read-write
+ * access to the #StAdjustment.
+ *
+ * JavaScript code may override this as demonstrated below:
+ *
+ * |[<!-- language="JavaScript" -->
+ * var MyScrollable = GObject.registerClass({
+ * Properties: {
+ * 'hadjustment': GObject.ParamSpec.override(
+ * 'hadjustment',
+ * St.Scrollable
+ * )
+ * }
+ * }, class MyScrollable extends St.Scrollable {
+ *
+ * get hadjustment() {
+ * return this._hadjustment || null;
+ * }
+ *
+ * set hadjustment(adjustment) {
+ * if (this.hadjustment === adjustment)
+ * return;
+ *
+ * this._hadjustment = adjustment;
+ * this.notify('hadjustment');
+ * }
+ * });
+ * ]|
+ */
+ g_object_interface_install_property (g_iface,
+ g_param_spec_object ("hadjustment",
+ "StAdjustment",
+ "Horizontal adjustment",
+ ST_TYPE_ADJUSTMENT,
+ ST_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY));
+
+ /**
+ * StScrollable:vadjustment:
+ *
+ * The vertical #StAdjustment used by the #StScrollable.
+ *
+ * Implementations should override this property to provide read-write
+ * access to the #StAdjustment.
+ *
+ * See #StScrollable:hadjustment for an example of how to override this
+ * property in JavaScript code.
+ */
+ g_object_interface_install_property (g_iface,
+ g_param_spec_object ("vadjustment",
+ "StAdjustment",
+ "Vertical adjustment",
+ ST_TYPE_ADJUSTMENT,
+ ST_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY));
+
+ initialized = TRUE;
+ }
+}
+
+/**
+ * st_scrollable_set_adjustments:
+ * @scrollable: a #StScrollable
+ * @hadjustment: the horizontal #StAdjustment
+ * @vadjustment: the vertical #StAdjustment
+ *
+ * This method should be implemented by classes implementing the #StScrollable
+ * interface.
+ *
+ * JavaScript code should do this by overriding the `vfunc_set_adjustments()`
+ * method.
+ */
+void
+st_scrollable_set_adjustments (StScrollable *scrollable,
+ StAdjustment *hadjustment,
+ StAdjustment *vadjustment)
+{
+ ST_SCROLLABLE_GET_IFACE (scrollable)->set_adjustments (scrollable,
+ hadjustment,
+ vadjustment);
+}
+
+/**
+ * st_scroll_bar_get_adjustments:
+ * @hadjustment: (transfer none) (out) (optional): location to store the horizontal adjustment, or %NULL
+ * @vadjustment: (transfer none) (out) (optional): location to store the vertical adjustment, or %NULL
+ *
+ * Gets the adjustment objects that store the offsets of the scrollable widget
+ * into its possible scrolling area.
+ *
+ * This method should be implemented by classes implementing the #StScrollable
+ * interface.
+ *
+ * JavaScript code should do this by overriding the `vfunc_get_adjustments()`
+ * method.
+ */
+void
+st_scrollable_get_adjustments (StScrollable *scrollable,
+ StAdjustment **hadjustment,
+ StAdjustment **vadjustment)
+{
+ ST_SCROLLABLE_GET_IFACE (scrollable)->get_adjustments (scrollable,
+ hadjustment,
+ vadjustment);
+}
diff --git a/src/st/st-scrollable.h b/src/st/st-scrollable.h
new file mode 100644
index 0000000..797ec7d
--- /dev/null
+++ b/src/st/st-scrollable.h
@@ -0,0 +1,59 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * st-scrollable.h: Scrollable interface
+ *
+ * Copyright 2008 OpenedHand
+ * Copyright 2009 Intel Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU Lesser General Public License,
+ * version 2.1, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#if !defined(ST_H_INSIDE) && !defined(ST_COMPILATION)
+#error "Only <st/st.h> can be included directly.h"
+#endif
+
+#ifndef __ST_SCROLLABLE_H__
+#define __ST_SCROLLABLE_H__
+
+#include <glib-object.h>
+#include <st/st-adjustment.h>
+
+G_BEGIN_DECLS
+
+#define ST_TYPE_SCROLLABLE (st_scrollable_get_type ())
+G_DECLARE_INTERFACE (StScrollable, st_scrollable, ST, SCROLLABLE, GObject)
+
+typedef struct _StScrollableInterface StScrollableInterface;
+
+struct _StScrollableInterface
+{
+ GTypeInterface parent;
+
+ void (* set_adjustments) (StScrollable *scrollable,
+ StAdjustment *hadjustment,
+ StAdjustment *vadjustment);
+ void (* get_adjustments) (StScrollable *scrollable,
+ StAdjustment **hadjustment,
+ StAdjustment **vadjustment);
+};
+
+void st_scrollable_set_adjustments (StScrollable *scrollable,
+ StAdjustment *hadjustment,
+ StAdjustment *vadjustment);
+void st_scrollable_get_adjustments (StScrollable *scrollable,
+ StAdjustment **hadjustment,
+ StAdjustment **vadjustment);
+
+G_END_DECLS
+
+#endif /* __ST_SCROLLABLE_H__ */
diff --git a/src/st/st-settings.c b/src/st/st-settings.c
new file mode 100644
index 0000000..04bf68f
--- /dev/null
+++ b/src/st/st-settings.c
@@ -0,0 +1,451 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * st-settings.c: Global settings
+ *
+ * Copyright 2019 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU Lesser General Public License,
+ * version 2.1, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <math.h>
+#include <gio/gio.h>
+
+#include "st-private.h"
+#include "st-settings.h"
+
+#define KEY_ENABLE_ANIMATIONS "enable-animations"
+#define KEY_PRIMARY_PASTE "gtk-enable-primary-paste"
+#define KEY_DRAG_THRESHOLD "drag-threshold"
+#define KEY_FONT_NAME "font-name"
+#define KEY_HIGH_CONTRAST "high-contrast"
+#define KEY_GTK_ICON_THEME "icon-theme"
+#define KEY_MAGNIFIER_ACTIVE "screen-magnifier-enabled"
+#define KEY_DISABLE_SHOW_PASSWORD "disable-show-password"
+
+enum {
+ PROP_0,
+ PROP_ENABLE_ANIMATIONS,
+ PROP_PRIMARY_PASTE,
+ PROP_DRAG_THRESHOLD,
+ PROP_FONT_NAME,
+ PROP_HIGH_CONTRAST,
+ PROP_GTK_ICON_THEME,
+ PROP_MAGNIFIER_ACTIVE,
+ PROP_SLOW_DOWN_FACTOR,
+ PROP_DISABLE_SHOW_PASSWORD,
+ N_PROPS
+};
+
+GParamSpec *props[N_PROPS] = { 0 };
+
+struct _StSettings
+{
+ GObject parent_object;
+ GSettings *interface_settings;
+ GSettings *mouse_settings;
+ GSettings *a11y_applications_settings;
+ GSettings *a11y_interface_settings;
+ GSettings *lockdown_settings;
+
+ gchar *font_name;
+ gboolean high_contrast;
+ gchar *gtk_icon_theme;
+ int inhibit_animations_count;
+ gboolean enable_animations;
+ gboolean primary_paste;
+ gboolean magnifier_active;
+ gboolean disable_show_password;
+ gint drag_threshold;
+ double slow_down_factor;
+};
+
+G_DEFINE_TYPE (StSettings, st_settings, G_TYPE_OBJECT)
+
+#define EPSILON (1e-10)
+
+static void
+st_settings_set_slow_down_factor (StSettings *settings,
+ double factor)
+{
+ if (fabs (settings->slow_down_factor - factor) < EPSILON)
+ return;
+
+ settings->slow_down_factor = factor;
+ g_object_notify_by_pspec (G_OBJECT (settings), props[PROP_SLOW_DOWN_FACTOR]);
+}
+
+static gboolean
+get_enable_animations (StSettings *settings)
+{
+ if (settings->inhibit_animations_count > 0)
+ return FALSE;
+ else
+ return settings->enable_animations;
+}
+
+void
+st_settings_inhibit_animations (StSettings *settings)
+{
+ gboolean enable_animations;
+
+ enable_animations = get_enable_animations (settings);
+ settings->inhibit_animations_count++;
+
+ if (enable_animations != get_enable_animations (settings))
+ g_object_notify_by_pspec (G_OBJECT (settings),
+ props[PROP_ENABLE_ANIMATIONS]);
+}
+
+void
+st_settings_uninhibit_animations (StSettings *settings)
+{
+ gboolean enable_animations;
+
+ enable_animations = get_enable_animations (settings);
+ settings->inhibit_animations_count--;
+
+ if (enable_animations != get_enable_animations (settings))
+ g_object_notify_by_pspec (G_OBJECT (settings),
+ props[PROP_ENABLE_ANIMATIONS]);
+}
+
+static void
+st_settings_finalize (GObject *object)
+{
+ StSettings *settings = ST_SETTINGS (object);
+
+ g_object_unref (settings->interface_settings);
+ g_object_unref (settings->mouse_settings);
+ g_object_unref (settings->a11y_applications_settings);
+ g_object_unref (settings->a11y_interface_settings);
+ g_object_unref (settings->lockdown_settings);
+ g_free (settings->font_name);
+ g_free (settings->gtk_icon_theme);
+
+ G_OBJECT_CLASS (st_settings_parent_class)->finalize (object);
+}
+
+static void
+st_settings_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ StSettings *settings = ST_SETTINGS (object);
+
+ switch (prop_id)
+ {
+ case PROP_SLOW_DOWN_FACTOR:
+ st_settings_set_slow_down_factor (settings, g_value_get_double (value));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+st_settings_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ StSettings *settings = ST_SETTINGS (object);
+
+ switch (prop_id)
+ {
+ case PROP_ENABLE_ANIMATIONS:
+ g_value_set_boolean (value, get_enable_animations (settings));
+ break;
+ case PROP_PRIMARY_PASTE:
+ g_value_set_boolean (value, settings->primary_paste);
+ break;
+ case PROP_DRAG_THRESHOLD:
+ g_value_set_int (value, settings->drag_threshold);
+ break;
+ case PROP_FONT_NAME:
+ g_value_set_string (value, settings->font_name);
+ break;
+ case PROP_HIGH_CONTRAST:
+ g_value_set_boolean (value, settings->high_contrast);
+ break;
+ case PROP_GTK_ICON_THEME:
+ g_value_set_string (value, settings->gtk_icon_theme);
+ break;
+ case PROP_MAGNIFIER_ACTIVE:
+ g_value_set_boolean (value, settings->magnifier_active);
+ break;
+ case PROP_SLOW_DOWN_FACTOR:
+ g_value_set_double (value, settings->slow_down_factor);
+ break;
+ case PROP_DISABLE_SHOW_PASSWORD:
+ g_value_set_boolean (value, settings->disable_show_password);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+st_settings_class_init (StSettingsClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->finalize = st_settings_finalize;
+ object_class->set_property = st_settings_set_property;
+ object_class->get_property = st_settings_get_property;
+
+ /**
+ * StSettings:enable-animations:
+ *
+ * Whether animations are enabled.
+ */
+ props[PROP_ENABLE_ANIMATIONS] = g_param_spec_boolean ("enable-animations",
+ "Enable animations",
+ "Enable animations",
+ TRUE,
+ ST_PARAM_READABLE);
+
+ /**
+ * StSettings:primary-paste:
+ *
+ * Whether pasting from the `PRIMARY` selection is supported (eg. middle-click
+ * paste).
+ */
+ props[PROP_PRIMARY_PASTE] = g_param_spec_boolean ("primary-paste",
+ "Primary paste",
+ "Primary paste",
+ TRUE,
+ ST_PARAM_READABLE);
+
+ /**
+ * StSettings:drag-threshold:
+ *
+ * The threshold before a drag operation begins.
+ */
+ props[PROP_DRAG_THRESHOLD] = g_param_spec_int ("drag-threshold",
+ "Drag threshold",
+ "Drag threshold",
+ 0, G_MAXINT, 8,
+ ST_PARAM_READABLE);
+
+ /**
+ * StSettings:font-name:
+ *
+ * The current font name.
+ */
+ props[PROP_FONT_NAME] = g_param_spec_string ("font-name",
+ "font name",
+ "font name",
+ "",
+ ST_PARAM_READABLE);
+
+ /**
+ * StSettings:high-contrast:
+ *
+ * Whether the accessibility high contrast mode is enabled.
+ */
+ props[PROP_HIGH_CONTRAST] = g_param_spec_boolean ("high-contrast",
+ "High contrast",
+ "High contrast",
+ FALSE,
+ ST_PARAM_READABLE);
+
+ /**
+ * StSettings:gtk-icon-theme:
+ *
+ * The current GTK icon theme
+ */
+ props[PROP_GTK_ICON_THEME] = g_param_spec_string ("gtk-icon-theme",
+ "GTK Icon Theme",
+ "GTK Icon Theme",
+ "",
+ ST_PARAM_READABLE);
+
+ /**
+ * StSettings:magnifier-active:
+ *
+ * Whether the accessibility magnifier is active.
+ */
+ props[PROP_MAGNIFIER_ACTIVE] = g_param_spec_boolean("magnifier-active",
+ "Magnifier is active",
+ "Whether the a11y magnifier is active",
+ FALSE,
+ ST_PARAM_READABLE);
+
+ /**
+ * StSettings:slow-down-factor:
+ *
+ * The slow-down factor applied to all animation durations.
+ */
+ props[PROP_SLOW_DOWN_FACTOR] = g_param_spec_double("slow-down-factor",
+ "Slow down factor",
+ "Factor applied to all animation durations",
+ EPSILON, G_MAXDOUBLE, 1.0,
+ ST_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
+
+ /**
+ * StSettings:disable-show-password:
+ *
+ * Whether password showing can be locked down
+ */
+ props[PROP_DISABLE_SHOW_PASSWORD] = g_param_spec_boolean("disable-show-password",
+ "'Show Password' is disabled",
+ "Whether user can request to see their password",
+ FALSE,
+ ST_PARAM_READABLE);
+
+ g_object_class_install_properties (object_class, N_PROPS, props);
+}
+
+static void
+on_interface_settings_changed (GSettings *g_settings,
+ const gchar *key,
+ StSettings *settings)
+{
+ if (g_str_equal (key, KEY_ENABLE_ANIMATIONS))
+ {
+ settings->enable_animations = g_settings_get_boolean (g_settings, key);
+ g_object_notify_by_pspec (G_OBJECT (settings), props[PROP_ENABLE_ANIMATIONS]);
+ }
+ else if (g_str_equal (key, KEY_PRIMARY_PASTE))
+ {
+ settings->primary_paste = g_settings_get_boolean (g_settings, key);
+ g_object_notify_by_pspec (G_OBJECT (settings), props[PROP_PRIMARY_PASTE]);
+ }
+ else if (g_str_equal (key, KEY_FONT_NAME))
+ {
+ g_free (settings->font_name);
+ settings->font_name = g_settings_get_string (g_settings, key);
+ g_object_notify_by_pspec (G_OBJECT (settings), props[PROP_FONT_NAME]);
+ }
+ else if (g_str_equal (key, KEY_GTK_ICON_THEME))
+ {
+ g_free (settings->gtk_icon_theme);
+ settings->gtk_icon_theme = g_settings_get_string (g_settings, key);
+ g_object_notify_by_pspec (G_OBJECT (settings),
+ props[PROP_GTK_ICON_THEME]);
+ }
+}
+
+static void
+on_mouse_settings_changed (GSettings *g_settings,
+ const gchar *key,
+ StSettings *settings)
+{
+ if (g_str_equal (key, KEY_DRAG_THRESHOLD))
+ {
+ settings->drag_threshold = g_settings_get_int (g_settings, key);
+ g_object_notify_by_pspec (G_OBJECT (settings), props[PROP_DRAG_THRESHOLD]);
+ }
+}
+
+static void
+on_a11y_applications_settings_changed (GSettings *g_settings,
+ const gchar *key,
+ StSettings *settings)
+{
+ if (g_str_equal (key, KEY_MAGNIFIER_ACTIVE))
+ {
+ settings->magnifier_active = g_settings_get_boolean (g_settings, key);
+ g_object_notify_by_pspec (G_OBJECT (settings), props[PROP_MAGNIFIER_ACTIVE]);
+ }
+}
+
+static void
+on_a11y_interface_settings_changed (GSettings *g_settings,
+ const gchar *key,
+ StSettings *settings)
+{
+ if (g_str_equal (key, KEY_HIGH_CONTRAST))
+ {
+ settings->high_contrast = g_settings_get_boolean (g_settings, key);
+ g_object_notify_by_pspec (G_OBJECT (settings), props[PROP_HIGH_CONTRAST]);
+
+ g_object_notify_by_pspec (G_OBJECT (settings), props[PROP_GTK_ICON_THEME]);
+ }
+}
+
+static void
+on_lockdown_settings_changed (GSettings *g_settings,
+ const gchar *key,
+ StSettings *settings)
+{
+ if (g_str_equal (key, KEY_DISABLE_SHOW_PASSWORD))
+ {
+ settings->disable_show_password = g_settings_get_boolean (g_settings, key);
+ g_object_notify_by_pspec (G_OBJECT (settings), props[PROP_DISABLE_SHOW_PASSWORD]);
+ }
+}
+
+static void
+st_settings_init (StSettings *settings)
+{
+ settings->interface_settings = g_settings_new ("org.gnome.desktop.interface");
+ g_signal_connect (settings->interface_settings, "changed",
+ G_CALLBACK (on_interface_settings_changed), settings);
+
+ settings->mouse_settings = g_settings_new ("org.gnome.desktop.peripherals.mouse");
+ g_signal_connect (settings->mouse_settings, "changed",
+ G_CALLBACK (on_mouse_settings_changed), settings);
+
+ settings->a11y_applications_settings = g_settings_new ("org.gnome.desktop.a11y.applications");
+ g_signal_connect (settings->a11y_applications_settings, "changed",
+ G_CALLBACK (on_a11y_applications_settings_changed), settings);
+
+ settings->a11y_interface_settings = g_settings_new ("org.gnome.desktop.a11y.interface");
+ g_signal_connect (settings->a11y_interface_settings, "changed",
+ G_CALLBACK (on_a11y_interface_settings_changed), settings);
+
+ settings->lockdown_settings = g_settings_new ("org.gnome.desktop.lockdown");
+ g_signal_connect (settings->lockdown_settings, "changed",
+ G_CALLBACK (on_lockdown_settings_changed), settings);
+
+ settings->enable_animations = g_settings_get_boolean (settings->interface_settings,
+ KEY_ENABLE_ANIMATIONS);
+ settings->primary_paste = g_settings_get_boolean (settings->interface_settings,
+ KEY_PRIMARY_PASTE);
+ settings->font_name = g_settings_get_string (settings->interface_settings,
+ KEY_FONT_NAME);
+ settings->gtk_icon_theme = g_settings_get_string (settings->interface_settings,
+ KEY_GTK_ICON_THEME);
+ settings->drag_threshold = g_settings_get_int (settings->mouse_settings,
+ KEY_DRAG_THRESHOLD);
+ settings->magnifier_active = g_settings_get_boolean (settings->a11y_applications_settings,
+ KEY_MAGNIFIER_ACTIVE);
+ settings->high_contrast = g_settings_get_boolean (settings->a11y_interface_settings,
+ KEY_HIGH_CONTRAST);
+ settings->slow_down_factor = 1.;
+ settings->disable_show_password = g_settings_get_boolean (settings->lockdown_settings, KEY_DISABLE_SHOW_PASSWORD);
+}
+
+/**
+ * st_settings_get:
+ *
+ * Gets the global #StSettings object.
+ *
+ * Returns: (transfer none): the global #StSettings object
+ **/
+StSettings *
+st_settings_get (void)
+{
+ static StSettings *settings = NULL;
+
+ if (!settings)
+ settings = g_object_new (ST_TYPE_SETTINGS, NULL);
+
+ return settings;
+}
diff --git a/src/st/st-settings.h b/src/st/st-settings.h
new file mode 100644
index 0000000..8b25494
--- /dev/null
+++ b/src/st/st-settings.h
@@ -0,0 +1,42 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * st-settings.h: Global settings
+ *
+ * Copyright 2019 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU Lesser General Public License,
+ * version 2.1, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#if !defined(ST_H_INSIDE) && !defined(ST_COMPILATION)
+#error "Only <st/st.h> can be included directly.h"
+#endif
+
+#ifndef __ST_SETTINGS_H__
+#define __ST_SETTINGS_H__
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define ST_TYPE_SETTINGS (st_settings_get_type ())
+G_DECLARE_FINAL_TYPE (StSettings, st_settings, ST, SETTINGS, GObject)
+
+StSettings * st_settings_get (void);
+
+void st_settings_inhibit_animations (StSettings *settings);
+
+void st_settings_uninhibit_animations (StSettings *settings);
+
+G_END_DECLS
+
+#endif /* __ST_SETTINGS_H__ */
diff --git a/src/st/st-shadow.c b/src/st/st-shadow.c
new file mode 100644
index 0000000..0a8e319
--- /dev/null
+++ b/src/st/st-shadow.c
@@ -0,0 +1,307 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * st-shadow.c: Boxed type holding for -st-shadow attributes
+ *
+ * Copyright 2009, 2010 Florian Müllner
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation, either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include "st-shadow.h"
+#include "st-private.h"
+
+G_DEFINE_BOXED_TYPE (StShadow, st_shadow, st_shadow_ref, st_shadow_unref)
+G_DEFINE_BOXED_TYPE (StShadowHelper, st_shadow_helper, st_shadow_helper_copy, st_shadow_helper_free)
+
+/**
+ * SECTION: st-shadow
+ * @short_description: Boxed type for -st-shadow attributes
+ *
+ * #StShadow is a boxed type for storing attributes of the -st-shadow
+ * property, modelled liberally after the CSS3 box-shadow property.
+ * See http://www.css3.info/preview/box-shadow/
+ *
+ */
+
+/**
+ * st_shadow_new:
+ * @color: shadow's color
+ * @xoffset: horizontal offset
+ * @yoffset: vertical offset
+ * @blur: blur radius
+ * @spread: spread radius
+ * @inset: whether the shadow should be inset
+ *
+ * Creates a new #StShadow
+ *
+ * Returns: the newly allocated shadow. Use st_shadow_free() when done
+ */
+StShadow *
+st_shadow_new (ClutterColor *color,
+ gdouble xoffset,
+ gdouble yoffset,
+ gdouble blur,
+ gdouble spread,
+ gboolean inset)
+{
+ StShadow *shadow;
+
+ shadow = g_new (StShadow, 1);
+
+ shadow->color = *color;
+ shadow->xoffset = xoffset;
+ shadow->yoffset = yoffset;
+ shadow->blur = blur;
+ shadow->spread = spread;
+ shadow->inset = inset;
+ shadow->ref_count = 1;
+
+ return shadow;
+}
+
+/**
+ * st_shadow_ref:
+ * @shadow: a #StShadow
+ *
+ * Atomically increments the reference count of @shadow by one.
+ *
+ * Returns: the passed in #StShadow.
+ */
+StShadow *
+st_shadow_ref (StShadow *shadow)
+{
+ g_return_val_if_fail (shadow != NULL, NULL);
+ g_return_val_if_fail (shadow->ref_count > 0, shadow);
+
+ g_atomic_int_inc (&shadow->ref_count);
+ return shadow;
+}
+
+/**
+ * st_shadow_unref:
+ * @shadow: a #StShadow
+ *
+ * Atomically decrements the reference count of @shadow by one.
+ * If the reference count drops to 0, all memory allocated by the
+ * #StShadow is released.
+ */
+void
+st_shadow_unref (StShadow *shadow)
+{
+ g_return_if_fail (shadow != NULL);
+ g_return_if_fail (shadow->ref_count > 0);
+
+ if (g_atomic_int_dec_and_test (&shadow->ref_count))
+ g_free (shadow);
+}
+
+/**
+ * st_shadow_equal:
+ * @shadow: a #StShadow
+ * @other: a different #StShadow
+ *
+ * Check if two shadow objects are identical. Note that two shadows may
+ * compare non-identically if they differ only by floating point rounding
+ * errors.
+ *
+ * Returns: %TRUE if the two shadows are identical
+ */
+gboolean
+st_shadow_equal (StShadow *shadow,
+ StShadow *other)
+{
+ g_return_val_if_fail (shadow != NULL, FALSE);
+ g_return_val_if_fail (other != NULL, FALSE);
+
+ if (shadow == other)
+ return TRUE;
+
+ /* We use strict equality to compare double quantities; this means
+ * that, for example, a shadow offset of 0.25in does not necessarily
+ * compare equal to a shadow offset of 18pt in this test. Assume
+ * that a few false negatives are mostly harmless.
+ */
+
+ return (clutter_color_equal (&shadow->color, &other->color) &&
+ shadow->xoffset == other->xoffset &&
+ shadow->yoffset == other->yoffset &&
+ shadow->blur == other->blur &&
+ shadow->spread == other->spread &&
+ shadow->inset == other->inset);
+}
+
+/**
+ * st_shadow_get_box:
+ * @shadow: a #StShadow
+ * @actor_box: the box allocated to a #ClutterAlctor
+ * @shadow_box: computed box occupied by @shadow
+ *
+ * Gets the box used to paint @shadow, which will be partly
+ * outside of @actor_box
+ */
+void
+st_shadow_get_box (StShadow *shadow,
+ const ClutterActorBox *actor_box,
+ ClutterActorBox *shadow_box)
+{
+ g_return_if_fail (shadow != NULL);
+ g_return_if_fail (actor_box != NULL);
+ g_return_if_fail (shadow_box != NULL);
+
+ /* Inset shadows are drawn below the border, so returning
+ * the original box is not actually correct; still, it's
+ * good enough for the purpose of determining additional space
+ * required outside the actor box.
+ */
+ if (shadow->inset)
+ {
+ *shadow_box = *actor_box;
+ return;
+ }
+
+ shadow_box->x1 = actor_box->x1 + shadow->xoffset
+ - shadow->blur - shadow->spread;
+ shadow_box->x2 = actor_box->x2 + shadow->xoffset
+ + shadow->blur + shadow->spread;
+ shadow_box->y1 = actor_box->y1 + shadow->yoffset
+ - shadow->blur - shadow->spread;
+ shadow_box->y2 = actor_box->y2 + shadow->yoffset
+ + shadow->blur + shadow->spread;
+}
+
+/**
+ * SECTION: st-shadow-helper
+ *
+ * An helper for implementing a drop shadow on a actor.
+ * The actor is expected to recreate the helper whenever its contents
+ * or size change. Then, it would call st_shadow_helper_paint() inside
+ * its paint() virtual function.
+ */
+
+struct _StShadowHelper {
+ StShadow *shadow;
+ CoglPipeline *pipeline;
+
+ gfloat width;
+ gfloat height;
+};
+
+/**
+ * st_shadow_helper_new:
+ * @shadow: a #StShadow representing the shadow properties
+ *
+ * Builds a #StShadowHelper that will build a drop shadow
+ * using @source as the mask.
+ *
+ * Returns: (transfer full): a new #StShadowHelper
+ */
+StShadowHelper *
+st_shadow_helper_new (StShadow *shadow)
+{
+ StShadowHelper *helper;
+
+ helper = g_new0 (StShadowHelper, 1);
+ helper->shadow = st_shadow_ref (shadow);
+
+ return helper;
+}
+
+/**
+ * st_shadow_helper_update:
+ * @helper: a #StShadowHelper
+ * @source: a #ClutterActor
+ *
+ * Update @helper from @source.
+ */
+void
+st_shadow_helper_update (StShadowHelper *helper,
+ ClutterActor *source)
+{
+ gfloat width, height;
+
+ clutter_actor_get_size (source, &width, &height);
+
+ if (helper->pipeline == NULL ||
+ helper->width != width ||
+ helper->height != height)
+ {
+ if (helper->pipeline)
+ cogl_object_unref (helper->pipeline);
+
+ helper->pipeline = _st_create_shadow_pipeline_from_actor (helper->shadow, source);
+ helper->width = width;
+ helper->height = height;
+ }
+}
+
+/**
+ * st_shadow_helper_copy:
+ * @helper: the #StShadowHelper to copy
+ *
+ * Returns: (transfer full): a copy of @helper
+ */
+StShadowHelper *
+st_shadow_helper_copy (StShadowHelper *helper)
+{
+ StShadowHelper *copy;
+
+ copy = g_new (StShadowHelper, 1);
+ *copy = *helper;
+ if (copy->pipeline)
+ cogl_object_ref (copy->pipeline);
+ st_shadow_ref (copy->shadow);
+
+ return copy;
+}
+
+/**
+ * st_shadow_helper_free:
+ * @helper: a #StShadowHelper
+ *
+ * Free resources associated with @helper.
+ */
+void
+st_shadow_helper_free (StShadowHelper *helper)
+{
+ if (helper->pipeline)
+ cogl_object_unref (helper->pipeline);
+ st_shadow_unref (helper->shadow);
+
+ g_free (helper);
+}
+
+/**
+ * st_shadow_helper_paint:
+ * @helper: a #StShadowHelper
+ * @framebuffer: a #CoglFramebuffer
+ * @actor_box: the bounding box of the shadow
+ * @paint_opacity: the opacity at which the shadow is painted
+ *
+ * Paints the shadow associated with @helper This must only
+ * be called from the implementation of ClutterActor::paint().
+ */
+void
+st_shadow_helper_paint (StShadowHelper *helper,
+ CoglFramebuffer *framebuffer,
+ ClutterActorBox *actor_box,
+ guint8 paint_opacity)
+{
+ _st_paint_shadow_with_opacity (helper->shadow,
+ framebuffer,
+ helper->pipeline,
+ actor_box,
+ paint_opacity);
+}
diff --git a/src/st/st-shadow.h b/src/st/st-shadow.h
new file mode 100644
index 0000000..267d48f
--- /dev/null
+++ b/src/st/st-shadow.h
@@ -0,0 +1,95 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * st-shadow.h: Boxed type holding for -st-shadow attributes
+ *
+ * Copyright 2009, 2010 Florian Müllner
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation, either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __ST_SHADOW__
+#define __ST_SHADOW__
+
+#include <clutter/clutter.h>
+
+G_BEGIN_DECLS
+
+#define ST_TYPE_SHADOW (st_shadow_get_type ())
+#define ST_TYPE_SHADOW_HELPER (st_shadow_get_type ())
+
+typedef struct _StShadow StShadow;
+typedef struct _StShadowHelper StShadowHelper;
+
+/**
+ * StShadow:
+ * @color: shadow's color
+ * @xoffset: horizontal offset - positive values mean placement to the right,
+ * negative values placement to the left of the element.
+ * @yoffset: vertical offset - positive values mean placement below, negative
+ * values placement above the element.
+ * @blur: shadow's blur radius - a value of 0.0 will result in a hard shadow.
+ * @spread: shadow's spread radius - grow the shadow without enlarging the
+ * blur.
+ *
+ * Attributes of the -st-shadow property.
+ */
+struct _StShadow {
+ ClutterColor color;
+ gdouble xoffset;
+ gdouble yoffset;
+ gdouble blur;
+ gdouble spread;
+ gboolean inset;
+ volatile int ref_count;
+};
+
+GType st_shadow_get_type (void) G_GNUC_CONST;
+
+StShadow *st_shadow_new (ClutterColor *color,
+ gdouble xoffset,
+ gdouble yoffset,
+ gdouble blur,
+ gdouble spread,
+ gboolean inset);
+StShadow *st_shadow_ref (StShadow *shadow);
+void st_shadow_unref (StShadow *shadow);
+
+gboolean st_shadow_equal (StShadow *shadow,
+ StShadow *other);
+
+void st_shadow_get_box (StShadow *shadow,
+ const ClutterActorBox *actor_box,
+ ClutterActorBox *shadow_box);
+
+G_DEFINE_AUTOPTR_CLEANUP_FUNC (StShadow, st_shadow_unref)
+
+
+GType st_shadow_helper_get_type (void) G_GNUC_CONST;
+
+StShadowHelper *st_shadow_helper_new (StShadow *shadow);
+
+StShadowHelper *st_shadow_helper_copy (StShadowHelper *helper);
+void st_shadow_helper_free (StShadowHelper *helper);
+
+void st_shadow_helper_update (StShadowHelper *helper,
+ ClutterActor *source);
+
+void st_shadow_helper_paint (StShadowHelper *helper,
+ CoglFramebuffer *framebuffer,
+ ClutterActorBox *actor_box,
+ guint8 paint_opacity);
+
+G_END_DECLS
+
+#endif /* __ST_SHADOW__ */
diff --git a/src/st/st-texture-cache.c b/src/st/st-texture-cache.c
new file mode 100644
index 0000000..7062221
--- /dev/null
+++ b/src/st/st-texture-cache.c
@@ -0,0 +1,1688 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * st-texture-cache.h: Object for loading and caching images as textures
+ *
+ * Copyright 2009, 2010 Red Hat, Inc.
+ * Copyright 2010, Maxim Ermilov
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation, either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include "st-image-content.h"
+#include "st-texture-cache.h"
+#include "st-private.h"
+#include "st-settings.h"
+#include <gtk/gtk.h>
+#include <math.h>
+#include <string.h>
+#include <glib.h>
+
+#define CACHE_PREFIX_ICON "icon:"
+#define CACHE_PREFIX_FILE "file:"
+#define CACHE_PREFIX_FILE_FOR_CAIRO "file-for-cairo:"
+
+struct _StTextureCachePrivate
+{
+ GtkIconTheme *icon_theme;
+ GSettings *settings;
+
+ /* Things that were loaded with a cache policy != NONE */
+ GHashTable *keyed_cache; /* char * -> ClutterImage* */
+ GHashTable *keyed_surface_cache; /* char * -> cairo_surface_t* */
+
+ GHashTable *used_scales; /* Set: double */
+
+ /* Presently this is used to de-duplicate requests for GIcons and async URIs. */
+ GHashTable *outstanding_requests; /* char * -> AsyncTextureLoadData * */
+
+ /* File monitors to evict cache data on changes */
+ GHashTable *file_monitors; /* char * -> GFileMonitor * */
+
+ GCancellable *cancellable;
+};
+
+static void st_texture_cache_dispose (GObject *object);
+static void st_texture_cache_finalize (GObject *object);
+
+enum
+{
+ ICON_THEME_CHANGED,
+ TEXTURE_FILE_CHANGED,
+
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = { 0, };
+G_DEFINE_TYPE(StTextureCache, st_texture_cache, G_TYPE_OBJECT);
+
+/* We want to preserve the aspect ratio by default, also the default
+ * pipeline for an empty texture is full opacity white, which we
+ * definitely don't want. Skip that by setting 0 opacity.
+ */
+static ClutterActor *
+create_invisible_actor (void)
+{
+ return g_object_new (CLUTTER_TYPE_ACTOR,
+ "opacity", 0,
+ "request-mode", CLUTTER_REQUEST_CONTENT_SIZE,
+ NULL);
+}
+
+/* Reverse the opacity we added while loading */
+static void
+set_content_from_image (ClutterActor *actor,
+ ClutterContent *image)
+{
+ g_assert (image && CLUTTER_IS_IMAGE (image));
+
+ clutter_actor_set_content (actor, image);
+ clutter_actor_set_opacity (actor, 255);
+}
+
+static void
+st_texture_cache_class_init (StTextureCacheClass *klass)
+{
+ GObjectClass *gobject_class = (GObjectClass *)klass;
+
+ gobject_class->dispose = st_texture_cache_dispose;
+ gobject_class->finalize = st_texture_cache_finalize;
+
+ /**
+ * StTextureCache::icon-theme-changed:
+ * @self: a #StTextureCache
+ *
+ * Emitted when the icon theme is changed.
+ */
+ signals[ICON_THEME_CHANGED] =
+ g_signal_new ("icon-theme-changed",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ 0, /* no default handler slot */
+ NULL, NULL, NULL,
+ G_TYPE_NONE, 0);
+
+ /**
+ * StTextureCache::texture-file-changed:
+ * @self: a #StTextureCache
+ * @file: a #GFile
+ *
+ * Emitted when the source file of a texture is changed.
+ */
+ signals[TEXTURE_FILE_CHANGED] =
+ g_signal_new ("texture-file-changed",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ 0, /* no default handler slot */
+ NULL, NULL, NULL,
+ G_TYPE_NONE, 1, G_TYPE_FILE);
+}
+
+/* Evicts all cached textures for named icons */
+static void
+st_texture_cache_evict_icons (StTextureCache *cache)
+{
+ GHashTableIter iter;
+ gpointer key;
+ gpointer value;
+
+ g_hash_table_iter_init (&iter, cache->priv->keyed_cache);
+ while (g_hash_table_iter_next (&iter, &key, &value))
+ {
+ const char *cache_key = key;
+
+ /* This is too conservative - it takes out all cached textures
+ * for GIcons even when they aren't named icons, but it's not
+ * worth the complexity of parsing the key and calling
+ * g_icon_new_for_string(); icon theme changes aren't normal */
+ if (g_str_has_prefix (cache_key, CACHE_PREFIX_ICON))
+ g_hash_table_iter_remove (&iter);
+ }
+}
+
+static void
+on_icon_theme_changed (StSettings *settings,
+ GParamSpec *pspec,
+ StTextureCache *cache)
+{
+ g_autofree gchar *theme = NULL;
+
+ g_cancellable_cancel (cache->priv->cancellable);
+ g_cancellable_reset (cache->priv->cancellable);
+
+ st_texture_cache_evict_icons (cache);
+
+ g_object_get (settings, "gtk-icon-theme", &theme, NULL);
+ gtk_icon_theme_set_custom_theme (cache->priv->icon_theme, theme);
+
+ g_signal_emit (cache, signals[ICON_THEME_CHANGED], 0);
+}
+
+static void
+on_gtk_icon_theme_changed (GtkIconTheme *icon_theme,
+ StTextureCache *self)
+{
+ st_texture_cache_evict_icons (self);
+ g_signal_emit (self, signals[ICON_THEME_CHANGED], 0);
+}
+
+static void
+st_texture_cache_init (StTextureCache *self)
+{
+ StSettings *settings;
+
+ self->priv = g_new0 (StTextureCachePrivate, 1);
+
+ self->priv->icon_theme = gtk_icon_theme_new ();
+ gtk_icon_theme_add_resource_path (self->priv->icon_theme,
+ "/org/gnome/shell/icons");
+ g_signal_connect (self->priv->icon_theme, "changed",
+ G_CALLBACK (on_gtk_icon_theme_changed), self);
+
+ settings = st_settings_get ();
+ g_signal_connect (settings, "notify::gtk-icon-theme",
+ G_CALLBACK (on_icon_theme_changed), self);
+
+ self->priv->keyed_cache = g_hash_table_new_full (g_str_hash, g_str_equal,
+ g_free, g_object_unref);
+ self->priv->keyed_surface_cache = g_hash_table_new_full (g_str_hash,
+ g_str_equal,
+ g_free,
+ (GDestroyNotify) cairo_surface_destroy);
+ self->priv->used_scales = g_hash_table_new_full (g_double_hash, g_double_equal,
+ g_free, NULL);
+ self->priv->outstanding_requests = g_hash_table_new_full (g_str_hash, g_str_equal,
+ g_free, NULL);
+ self->priv->file_monitors = g_hash_table_new_full (g_file_hash, (GEqualFunc) g_file_equal,
+ g_object_unref, g_object_unref);
+
+ self->priv->cancellable = g_cancellable_new ();
+
+ on_icon_theme_changed (settings, NULL, self);
+}
+
+static void
+st_texture_cache_dispose (GObject *object)
+{
+ StTextureCache *self = (StTextureCache*)object;
+
+ g_cancellable_cancel (self->priv->cancellable);
+
+ g_clear_object (&self->priv->settings);
+ g_clear_object (&self->priv->icon_theme);
+ g_clear_object (&self->priv->cancellable);
+
+ g_clear_pointer (&self->priv->keyed_cache, g_hash_table_destroy);
+ g_clear_pointer (&self->priv->keyed_surface_cache, g_hash_table_destroy);
+ g_clear_pointer (&self->priv->used_scales, g_hash_table_destroy);
+ g_clear_pointer (&self->priv->outstanding_requests, g_hash_table_destroy);
+ g_clear_pointer (&self->priv->file_monitors, g_hash_table_destroy);
+
+ G_OBJECT_CLASS (st_texture_cache_parent_class)->dispose (object);
+}
+
+static void
+st_texture_cache_finalize (GObject *object)
+{
+ G_OBJECT_CLASS (st_texture_cache_parent_class)->finalize (object);
+}
+
+static void
+compute_pixbuf_scale (gint width,
+ gint height,
+ gint available_width,
+ gint available_height,
+ gint *new_width,
+ gint *new_height)
+{
+ int scaled_width, scaled_height;
+
+ if (width == 0 || height == 0)
+ {
+ *new_width = *new_height = 0;
+ return;
+ }
+
+ if (available_width >= 0 && available_height >= 0)
+ {
+ /* This should keep the aspect ratio of the image intact, because if
+ * available_width < (available_height * width) / height
+ * then
+ * (available_width * height) / width < available_height
+ * So we are guaranteed to either scale the image to have an available_width
+ * for width and height scaled accordingly OR have the available_height
+ * for height and width scaled accordingly, whichever scaling results
+ * in the image that can fit both available dimensions.
+ */
+ scaled_width = MIN (available_width, (available_height * width) / height);
+ scaled_height = MIN (available_height, (available_width * height) / width);
+ }
+ else if (available_width >= 0)
+ {
+ scaled_width = available_width;
+ scaled_height = (available_width * height) / width;
+ }
+ else if (available_height >= 0)
+ {
+ scaled_width = (available_height * width) / height;
+ scaled_height = available_height;
+ }
+ else
+ {
+ scaled_width = scaled_height = 0;
+ }
+
+ /* Scale the image only if that will not increase its original dimensions. */
+ if (scaled_width > 0 && scaled_height > 0 && scaled_width < width && scaled_height < height)
+ {
+ *new_width = scaled_width;
+ *new_height = scaled_height;
+ }
+ else
+ {
+ *new_width = width;
+ *new_height = height;
+ }
+}
+
+static void
+rgba_from_clutter (GdkRGBA *rgba,
+ ClutterColor *color)
+{
+ rgba->red = color->red / 255.;
+ rgba->green = color->green / 255.;
+ rgba->blue = color->blue / 255.;
+ rgba->alpha = color->alpha / 255.;
+}
+
+/* A private structure for keeping width, height and scale. */
+typedef struct {
+ int width;
+ int height;
+ int scale;
+} Dimensions;
+
+/* This struct corresponds to a request for an texture.
+ * It's creasted when something needs a new texture,
+ * and destroyed when the texture data is loaded. */
+typedef struct {
+ StTextureCache *cache;
+ StTextureCachePolicy policy;
+ char *key;
+
+ guint width;
+ guint height;
+ guint paint_scale;
+ gfloat resource_scale;
+ GSList *actors;
+
+ GtkIconInfo *icon_info;
+ StIconColors *colors;
+ GFile *file;
+} AsyncTextureLoadData;
+
+static void
+texture_load_data_free (gpointer p)
+{
+ AsyncTextureLoadData *data = p;
+
+ if (data->icon_info)
+ {
+ g_object_unref (data->icon_info);
+ if (data->colors)
+ st_icon_colors_unref (data->colors);
+ }
+ else if (data->file)
+ g_object_unref (data->file);
+
+ if (data->key)
+ g_free (data->key);
+
+ if (data->actors)
+ g_slist_free_full (data->actors, (GDestroyNotify) g_object_unref);
+
+ g_free (data);
+}
+
+/**
+ * on_image_size_prepared:
+ * @pixbuf_loader: #GdkPixbufLoader loading the image
+ * @width: the original width of the image
+ * @height: the original height of the image
+ * @data: pointer to the #Dimensions structure containing available width and height for the image,
+ * available width or height can be -1 if the dimension is not limited
+ *
+ * Private function.
+ *
+ * Sets the size of the image being loaded to fit the available width and height dimensions,
+ * but never scales up the image beyond its actual size.
+ * Intended to be used as a callback for #GdkPixbufLoader "size-prepared" signal.
+ */
+static void
+on_image_size_prepared (GdkPixbufLoader *pixbuf_loader,
+ gint width,
+ gint height,
+ gpointer data)
+{
+ Dimensions *available_dimensions = data;
+ int available_width = available_dimensions->width;
+ int available_height = available_dimensions->height;
+ int scale_factor = available_dimensions->scale;
+ int scaled_width;
+ int scaled_height;
+
+ compute_pixbuf_scale (width, height, available_width, available_height,
+ &scaled_width, &scaled_height);
+
+ gdk_pixbuf_loader_set_size (pixbuf_loader,
+ scaled_width * scale_factor,
+ scaled_height * scale_factor);
+}
+
+static GdkPixbuf *
+impl_load_pixbuf_data (const guchar *data,
+ gsize size,
+ int available_width,
+ int available_height,
+ int scale,
+ GError **error)
+{
+ GdkPixbufLoader *pixbuf_loader = NULL;
+ GdkPixbuf *rotated_pixbuf = NULL;
+ GdkPixbuf *pixbuf;
+ gboolean success;
+ Dimensions available_dimensions;
+ int width_before_rotation, width_after_rotation;
+
+ pixbuf_loader = gdk_pixbuf_loader_new ();
+
+ available_dimensions.width = available_width;
+ available_dimensions.height = available_height;
+ available_dimensions.scale = scale;
+ g_signal_connect (pixbuf_loader, "size-prepared",
+ G_CALLBACK (on_image_size_prepared), &available_dimensions);
+
+ success = gdk_pixbuf_loader_write (pixbuf_loader, data, size, error);
+ if (!success)
+ goto out;
+ success = gdk_pixbuf_loader_close (pixbuf_loader, error);
+ if (!success)
+ goto out;
+
+ pixbuf = gdk_pixbuf_loader_get_pixbuf (pixbuf_loader);
+
+ width_before_rotation = gdk_pixbuf_get_width (pixbuf);
+
+ rotated_pixbuf = gdk_pixbuf_apply_embedded_orientation (pixbuf);
+ width_after_rotation = gdk_pixbuf_get_width (rotated_pixbuf);
+
+ /* There is currently no way to tell if the pixbuf will need to be rotated before it is loaded,
+ * so we only check that once it is loaded, and reload it again if it needs to be rotated in order
+ * to use the available width and height correctly.
+ * See http://bugzilla.gnome.org/show_bug.cgi?id=579003
+ */
+ if (width_before_rotation != width_after_rotation)
+ {
+ g_object_unref (pixbuf_loader);
+ g_object_unref (rotated_pixbuf);
+ rotated_pixbuf = NULL;
+
+ pixbuf_loader = gdk_pixbuf_loader_new ();
+
+ /* We know that the image will later be rotated, so we reverse the available dimensions. */
+ available_dimensions.width = available_height;
+ available_dimensions.height = available_width;
+ available_dimensions.scale = scale;
+ g_signal_connect (pixbuf_loader, "size-prepared",
+ G_CALLBACK (on_image_size_prepared), &available_dimensions);
+
+ success = gdk_pixbuf_loader_write (pixbuf_loader, data, size, error);
+ if (!success)
+ goto out;
+
+ success = gdk_pixbuf_loader_close (pixbuf_loader, error);
+ if (!success)
+ goto out;
+
+ pixbuf = gdk_pixbuf_loader_get_pixbuf (pixbuf_loader);
+
+ rotated_pixbuf = gdk_pixbuf_apply_embedded_orientation (pixbuf);
+ }
+
+out:
+ if (pixbuf_loader)
+ g_object_unref (pixbuf_loader);
+ return rotated_pixbuf;
+}
+
+static GdkPixbuf *
+impl_load_pixbuf_file (GFile *file,
+ int available_width,
+ int available_height,
+ int paint_scale,
+ float resource_scale,
+ GError **error)
+{
+ GdkPixbuf *pixbuf = NULL;
+ char *contents = NULL;
+ gsize size;
+
+ if (g_file_load_contents (file, NULL, &contents, &size, NULL, error))
+ {
+ int scale = ceilf (paint_scale * resource_scale);
+ pixbuf = impl_load_pixbuf_data ((const guchar *) contents, size,
+ available_width, available_height,
+ scale,
+ error);
+ }
+
+ g_free (contents);
+
+ return pixbuf;
+}
+
+static void
+load_pixbuf_thread (GTask *result,
+ gpointer source,
+ gpointer task_data,
+ GCancellable *cancellable)
+{
+ GdkPixbuf *pixbuf;
+ AsyncTextureLoadData *data = task_data;
+ GError *error = NULL;
+
+ g_assert (data != NULL);
+ g_assert (data->file != NULL);
+
+ pixbuf = impl_load_pixbuf_file (data->file, data->width, data->height,
+ data->paint_scale, data->resource_scale,
+ &error);
+
+ if (error != NULL)
+ g_task_return_error (result, error);
+ else if (pixbuf)
+ g_task_return_pointer (result, g_object_ref (pixbuf), g_object_unref);
+
+ g_clear_object (&pixbuf);
+}
+
+static GdkPixbuf *
+load_pixbuf_async_finish (StTextureCache *cache, GAsyncResult *result, GError **error)
+{
+ return g_task_propagate_pointer (G_TASK (result), error);
+}
+
+static ClutterContent *
+pixbuf_to_st_content_image (GdkPixbuf *pixbuf,
+ int width,
+ int height,
+ int paint_scale,
+ float resource_scale)
+{
+ ClutterContent *image;
+ g_autoptr(GError) error = NULL;
+
+ float native_width, native_height;
+
+ native_width = ceilf (gdk_pixbuf_get_width (pixbuf) / resource_scale);
+ native_height = ceilf (gdk_pixbuf_get_height (pixbuf) / resource_scale);
+
+ if (width < 0 && height < 0)
+ {
+ width = native_width;
+ height = native_height;
+ }
+ else if (width < 0)
+ {
+ height *= paint_scale;
+ width = native_width * (height / native_height);
+ }
+ else if (height < 0)
+ {
+ width *= paint_scale;
+ height = native_height * (width / native_width);
+ }
+ else
+ {
+ width *= paint_scale;
+ height *= paint_scale;
+ }
+
+ image = st_image_content_new_with_preferred_size (width, height);
+ clutter_image_set_data (CLUTTER_IMAGE (image),
+ gdk_pixbuf_get_pixels (pixbuf),
+ gdk_pixbuf_get_has_alpha (pixbuf) ?
+ COGL_PIXEL_FORMAT_RGBA_8888 : COGL_PIXEL_FORMAT_RGB_888,
+ gdk_pixbuf_get_width (pixbuf),
+ gdk_pixbuf_get_height (pixbuf),
+ gdk_pixbuf_get_rowstride (pixbuf),
+ &error);
+
+ if (error)
+ {
+ g_warning ("Failed to allocate texture: %s", error->message);
+ g_clear_object (&image);
+ }
+
+ return image;
+}
+
+static cairo_surface_t *
+pixbuf_to_cairo_surface (GdkPixbuf *pixbuf)
+{
+ cairo_surface_t *dummy_surface;
+ cairo_pattern_t *pattern;
+ cairo_surface_t *surface;
+ cairo_t *cr;
+
+ dummy_surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, 1, 1);
+
+ cr = cairo_create (dummy_surface);
+ gdk_cairo_set_source_pixbuf (cr, pixbuf, 0, 0);
+ pattern = cairo_get_source (cr);
+ cairo_pattern_get_surface (pattern, &surface);
+ cairo_surface_reference (surface);
+ cairo_destroy (cr);
+ cairo_surface_destroy (dummy_surface);
+
+ return surface;
+}
+
+static void
+finish_texture_load (AsyncTextureLoadData *data,
+ GdkPixbuf *pixbuf)
+{
+ g_autoptr(ClutterContent) image = NULL;
+ GSList *iter;
+ StTextureCache *cache;
+
+ cache = data->cache;
+
+ g_hash_table_remove (cache->priv->outstanding_requests, data->key);
+
+ if (pixbuf == NULL)
+ goto out;
+
+ if (data->policy != ST_TEXTURE_CACHE_POLICY_NONE)
+ {
+ gpointer orig_key = NULL, value = NULL;
+
+ if (!g_hash_table_lookup_extended (cache->priv->keyed_cache, data->key,
+ &orig_key, &value))
+ {
+ image = pixbuf_to_st_content_image (pixbuf,
+ data->width, data->height,
+ data->paint_scale,
+ data->resource_scale);
+ if (!image)
+ goto out;
+
+ g_hash_table_insert (cache->priv->keyed_cache, g_strdup (data->key),
+ g_object_ref (image));
+ }
+ else
+ {
+ image = g_object_ref (value);
+ }
+ }
+ else
+ {
+ image = pixbuf_to_st_content_image (pixbuf,
+ data->width, data->height,
+ data->paint_scale,
+ data->resource_scale);
+ if (!image)
+ goto out;
+ }
+
+ for (iter = data->actors; iter; iter = iter->next)
+ {
+ ClutterActor *actor = iter->data;
+ set_content_from_image (actor, image);
+ }
+
+out:
+ texture_load_data_free (data);
+}
+
+static void
+on_symbolic_icon_loaded (GObject *source,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ GdkPixbuf *pixbuf;
+ pixbuf = gtk_icon_info_load_symbolic_finish (GTK_ICON_INFO (source), result, NULL, NULL);
+ finish_texture_load (user_data, pixbuf);
+ g_clear_object (&pixbuf);
+}
+
+static void
+on_icon_loaded (GObject *source,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ GdkPixbuf *pixbuf;
+ pixbuf = gtk_icon_info_load_icon_finish (GTK_ICON_INFO (source), result, NULL);
+ finish_texture_load (user_data, pixbuf);
+ g_clear_object (&pixbuf);
+}
+
+static void
+on_pixbuf_loaded (GObject *source,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ GdkPixbuf *pixbuf;
+ pixbuf = load_pixbuf_async_finish (ST_TEXTURE_CACHE (source), result, NULL);
+ finish_texture_load (user_data, pixbuf);
+ g_clear_object (&pixbuf);
+}
+
+static void
+load_texture_async (StTextureCache *cache,
+ AsyncTextureLoadData *data)
+{
+ if (data->file)
+ {
+ GTask *task = g_task_new (cache, NULL, on_pixbuf_loaded, data);
+ g_task_set_task_data (task, data, NULL);
+ g_task_run_in_thread (task, load_pixbuf_thread);
+ g_object_unref (task);
+ }
+ else if (data->icon_info)
+ {
+ StIconColors *colors = data->colors;
+ if (colors)
+ {
+ GdkRGBA foreground_color;
+ GdkRGBA success_color;
+ GdkRGBA warning_color;
+ GdkRGBA error_color;
+
+ rgba_from_clutter (&foreground_color, &colors->foreground);
+ rgba_from_clutter (&success_color, &colors->success);
+ rgba_from_clutter (&warning_color, &colors->warning);
+ rgba_from_clutter (&error_color, &colors->error);
+
+ gtk_icon_info_load_symbolic_async (data->icon_info,
+ &foreground_color, &success_color,
+ &warning_color, &error_color,
+ cache->priv->cancellable,
+ on_symbolic_icon_loaded, data);
+ }
+ else
+ {
+ gtk_icon_info_load_icon_async (data->icon_info,
+ cache->priv->cancellable,
+ on_icon_loaded, data);
+ }
+ }
+ else
+ g_assert_not_reached ();
+}
+
+typedef struct {
+ StTextureCache *cache;
+ ClutterContent *image;
+ GObject *source;
+ gulong notify_signal_id;
+ gboolean weakref_active;
+} StTextureCachePropertyBind;
+
+static void
+st_texture_cache_load_surface (ClutterContent **image,
+ cairo_surface_t *surface)
+{
+ g_return_if_fail (image != NULL);
+
+ if (surface != NULL &&
+ cairo_surface_get_type (surface) == CAIRO_SURFACE_TYPE_IMAGE &&
+ (cairo_image_surface_get_format (surface) == CAIRO_FORMAT_ARGB32 ||
+ cairo_image_surface_get_format (surface) == CAIRO_FORMAT_RGB24))
+ {
+ g_autoptr(GError) error = NULL;
+ int width, height, size;
+
+ width = cairo_image_surface_get_width (surface);
+ height = cairo_image_surface_get_width (surface);
+ size = MAX(width, height);
+
+ if (*image == NULL)
+ *image = st_image_content_new_with_preferred_size (size, size);
+
+ clutter_image_set_data (CLUTTER_IMAGE (*image),
+ cairo_image_surface_get_data (surface),
+ cairo_image_surface_get_format (surface) == CAIRO_FORMAT_ARGB32 ?
+ COGL_PIXEL_FORMAT_BGRA_8888 : COGL_PIXEL_FORMAT_BGR_888,
+ width,
+ height,
+ cairo_image_surface_get_stride (surface),
+ &error);
+
+ if (error)
+ g_warning ("Failed to allocate texture: %s", error->message);
+ }
+ else if (*image == NULL)
+ {
+ *image = st_image_content_new_with_preferred_size (0, 0);
+ }
+}
+
+static void
+st_texture_cache_reset_texture (StTextureCachePropertyBind *bind,
+ const char *propname)
+{
+ cairo_surface_t *surface;
+
+ g_object_get (bind->source, propname, &surface, NULL);
+
+ st_texture_cache_load_surface (&bind->image, surface);
+}
+
+static void
+st_texture_cache_on_pixbuf_notify (GObject *object,
+ GParamSpec *paramspec,
+ gpointer data)
+{
+ StTextureCachePropertyBind *bind = data;
+ st_texture_cache_reset_texture (bind, paramspec->name);
+}
+
+static void
+st_texture_cache_bind_weak_notify (gpointer data,
+ GObject *source_location)
+{
+ StTextureCachePropertyBind *bind = data;
+ bind->weakref_active = FALSE;
+ g_signal_handler_disconnect (bind->source, bind->notify_signal_id);
+}
+
+static void
+st_texture_cache_free_bind (gpointer data)
+{
+ StTextureCachePropertyBind *bind = data;
+ if (bind->weakref_active)
+ g_object_weak_unref (G_OBJECT (bind->image), st_texture_cache_bind_weak_notify, bind);
+ g_free (bind);
+}
+
+/**
+ * st_texture_cache_bind_cairo_surface_property:
+ * @cache: A #StTextureCache
+ * @object: A #GObject with a property @property_name of type #cairo_surface_t
+ * @property_name: Name of a property
+ *
+ * Create a #GIcon which tracks the #cairo_surface_t value of a GObject property
+ * named by @property_name. Unlike other methods in StTextureCache, the underlying
+ * #CoglTexture is not shared by default with other invocations to this method.
+ *
+ * If the source object is destroyed, the texture will continue to show the last
+ * value of the property.
+ *
+ * Returns: (transfer full): A new #GIcon
+ */
+GIcon *
+st_texture_cache_bind_cairo_surface_property (StTextureCache *cache,
+ GObject *object,
+ const char *property_name)
+{
+ gchar *notify_key;
+ StTextureCachePropertyBind *bind;
+
+ bind = g_new0 (StTextureCachePropertyBind, 1);
+ bind->cache = cache;
+ bind->source = object;
+
+ st_texture_cache_reset_texture (bind, property_name);
+
+ g_object_weak_ref (G_OBJECT (bind->image), st_texture_cache_bind_weak_notify, bind);
+ bind->weakref_active = TRUE;
+
+ notify_key = g_strdup_printf ("notify::%s", property_name);
+ bind->notify_signal_id = g_signal_connect_data (object, notify_key, G_CALLBACK(st_texture_cache_on_pixbuf_notify),
+ bind, (GClosureNotify)st_texture_cache_free_bind, 0);
+ g_free (notify_key);
+
+ return G_ICON (bind->image);
+}
+
+/**
+ * st_texture_cache_load_cairo_surface_to_gicon:
+ * @cache: A #StTextureCache
+ * @surface: A #cairo_surface_t
+ *
+ * Create a #GIcon from @surface.
+ *
+ * Returns: (transfer full): A new #GIcon
+ */
+GIcon *
+st_texture_cache_load_cairo_surface_to_gicon (StTextureCache *cache,
+ cairo_surface_t *surface)
+{
+ ClutterContent *image = NULL;
+ st_texture_cache_load_surface (&image, surface);
+
+ return G_ICON (image);
+}
+
+/**
+ * st_texture_cache_load: (skip)
+ * @cache: A #StTextureCache
+ * @key: Arbitrary string used to refer to item
+ * @policy: Caching policy
+ * @load: Function to create the texture, if not already cached
+ * @data: User data passed to @load
+ * @error: A #GError
+ *
+ * Load an arbitrary texture, caching it. The string chosen for @key
+ * should be of the form "type-prefix:type-uuid". For example,
+ * "url:file:///usr/share/icons/hicolor/48x48/apps/firefox.png", or
+ * "stock-icon:gtk-ok".
+ *
+ * Returns: (transfer full): A newly-referenced handle to the texture
+ */
+CoglTexture *
+st_texture_cache_load (StTextureCache *cache,
+ const char *key,
+ StTextureCachePolicy policy,
+ StTextureCacheLoader load,
+ void *data,
+ GError **error)
+{
+ CoglTexture *texture;
+
+ texture = g_hash_table_lookup (cache->priv->keyed_cache, key);
+ if (!texture)
+ {
+ texture = load (cache, key, data, error);
+ if (texture && policy == ST_TEXTURE_CACHE_POLICY_FOREVER)
+ g_hash_table_insert (cache->priv->keyed_cache, g_strdup (key), texture);
+ }
+
+ if (texture && policy == ST_TEXTURE_CACHE_POLICY_FOREVER)
+ cogl_object_ref (texture);
+
+ return texture;
+}
+
+/**
+ * ensure_request:
+ * @cache: A #StTextureCache
+ * @key: A cache key
+ * @policy: Cache policy
+ * @request: (out): If no request is outstanding, one will be created and returned here
+ * @texture: A texture to be added to the request
+ *
+ * Check for any outstanding load for the data represented by @key. If there
+ * is already a request pending, append it to that request to avoid loading
+ * the data multiple times.
+ *
+ * Returns: %TRUE if there is already a request pending
+ */
+static gboolean
+ensure_request (StTextureCache *cache,
+ const char *key,
+ StTextureCachePolicy policy,
+ AsyncTextureLoadData **request,
+ ClutterActor *actor)
+{
+ ClutterContent *image;
+ AsyncTextureLoadData *pending;
+ gboolean had_pending;
+
+ image = g_hash_table_lookup (cache->priv->keyed_cache, key);
+
+ if (image != NULL)
+ {
+ /* We had this cached already, just set the texture and we're done. */
+ set_content_from_image (actor, image);
+ return TRUE;
+ }
+
+ pending = g_hash_table_lookup (cache->priv->outstanding_requests, key);
+ had_pending = pending != NULL;
+
+ if (pending == NULL)
+ {
+ /* Not cached and no pending request, create it */
+ *request = g_new0 (AsyncTextureLoadData, 1);
+ if (policy != ST_TEXTURE_CACHE_POLICY_NONE)
+ g_hash_table_insert (cache->priv->outstanding_requests, g_strdup (key), *request);
+ }
+ else
+ *request = pending;
+
+ /* Regardless of whether there was a pending request, prepend our texture here. */
+ (*request)->actors = g_slist_prepend ((*request)->actors, g_object_ref (actor));
+
+ return had_pending;
+}
+
+/**
+ * st_texture_cache_load_gicon:
+ * @cache: A #StTextureCache
+ * @theme_node: (nullable): The #StThemeNode to use for colors, or %NULL
+ * if the icon must not be recolored
+ * @icon: the #GIcon to load
+ * @size: Size of themed
+ * @paint_scale: Scale factor of display
+ * @resource_scale: Resource scale factor
+ *
+ * This method returns a new #ClutterActor for a given #GIcon. If the
+ * icon isn't loaded already, the texture will be filled
+ * asynchronously.
+ *
+ * Returns: (transfer none) (nullable): A new #ClutterActor for the icon, or %NULL if not found
+ */
+ClutterActor *
+st_texture_cache_load_gicon (StTextureCache *cache,
+ StThemeNode *theme_node,
+ GIcon *icon,
+ gint size,
+ gint paint_scale,
+ gfloat resource_scale)
+{
+ AsyncTextureLoadData *request;
+ ClutterActor *actor;
+ gint scale;
+ char *gicon_string;
+ g_autofree char *key = NULL;
+ float actor_size;
+ GtkIconTheme *theme;
+ StTextureCachePolicy policy;
+ StIconColors *colors = NULL;
+ StIconStyle icon_style = ST_ICON_STYLE_REQUESTED;
+ GtkIconLookupFlags lookup_flags;
+
+ actor_size = size * paint_scale;
+
+ if (ST_IS_IMAGE_CONTENT (icon))
+ {
+ int width, height;
+
+ g_object_get (G_OBJECT (icon),
+ "preferred-width", &width,
+ "preferred-height", &height,
+ NULL);
+ if (width == 0 && height == 0)
+ return NULL;
+
+ return g_object_new (CLUTTER_TYPE_ACTOR,
+ "content-gravity", CLUTTER_CONTENT_GRAVITY_RESIZE_ASPECT,
+ "width", actor_size,
+ "height", actor_size,
+ "content", CLUTTER_CONTENT (icon),
+ NULL);
+ }
+
+ if (theme_node)
+ {
+ colors = st_theme_node_get_icon_colors (theme_node);
+ icon_style = st_theme_node_get_icon_style (theme_node);
+ }
+
+ /* Do theme lookups in the main thread to avoid thread-unsafety */
+ theme = cache->priv->icon_theme;
+
+ lookup_flags = GTK_ICON_LOOKUP_USE_BUILTIN;
+
+ if (icon_style == ST_ICON_STYLE_REGULAR)
+ lookup_flags |= GTK_ICON_LOOKUP_FORCE_REGULAR;
+ else if (icon_style == ST_ICON_STYLE_SYMBOLIC)
+ lookup_flags |= GTK_ICON_LOOKUP_FORCE_SYMBOLIC;
+
+ if (clutter_get_default_text_direction () == CLUTTER_TEXT_DIRECTION_RTL)
+ lookup_flags |= GTK_ICON_LOOKUP_DIR_RTL;
+ else
+ lookup_flags |= GTK_ICON_LOOKUP_DIR_LTR;
+
+ scale = ceilf (paint_scale * resource_scale);
+
+ gicon_string = g_icon_to_string (icon);
+ /* A return value of NULL indicates that the icon can not be serialized,
+ * so don't have a unique identifier for it as a cache key, and thus can't
+ * be cached. If it is cacheable, we hardcode a policy of FOREVER here for
+ * now; we should actually blow this away on icon theme changes probably */
+ policy = gicon_string != NULL ? ST_TEXTURE_CACHE_POLICY_FOREVER
+ : ST_TEXTURE_CACHE_POLICY_NONE;
+ if (colors)
+ {
+ /* This raises some doubts about the practice of using string keys */
+ key = g_strdup_printf (CACHE_PREFIX_ICON "%s,size=%d,scale=%d,style=%d,colors=%2x%2x%2x%2x,%2x%2x%2x%2x,%2x%2x%2x%2x,%2x%2x%2x%2x",
+ gicon_string, size, scale, icon_style,
+ colors->foreground.red, colors->foreground.blue, colors->foreground.green, colors->foreground.alpha,
+ colors->warning.red, colors->warning.blue, colors->warning.green, colors->warning.alpha,
+ colors->error.red, colors->error.blue, colors->error.green, colors->error.alpha,
+ colors->success.red, colors->success.blue, colors->success.green, colors->success.alpha);
+ }
+ else
+ {
+ key = g_strdup_printf (CACHE_PREFIX_ICON "%s,size=%d,scale=%d,style=%d",
+ gicon_string, size, scale, icon_style);
+ }
+ g_free (gicon_string);
+
+ actor = create_invisible_actor ();
+ clutter_actor_set_content_gravity (actor, CLUTTER_CONTENT_GRAVITY_RESIZE_ASPECT);
+ clutter_actor_set_size (actor, actor_size, actor_size);
+ if (!ensure_request (cache, key, policy, &request, actor))
+ {
+ /* Else, make a new request */
+ GtkIconInfo *info;
+
+ info = gtk_icon_theme_lookup_by_gicon_for_scale (theme, icon,
+ size, scale,
+ lookup_flags);
+ if (info == NULL)
+ {
+ g_hash_table_remove (cache->priv->outstanding_requests, key);
+ texture_load_data_free (request);
+ g_object_unref (actor);
+ return NULL;
+ }
+
+ request->cache = cache;
+ /* Transfer ownership of key */
+ request->key = g_steal_pointer (&key);
+ request->policy = policy;
+ request->colors = colors ? st_icon_colors_ref (colors) : NULL;
+ request->icon_info = info;
+ request->width = request->height = size;
+ request->paint_scale = paint_scale;
+ request->resource_scale = resource_scale;
+
+ load_texture_async (cache, request);
+ }
+
+ return actor;
+}
+
+static ClutterActor *
+load_from_pixbuf (GdkPixbuf *pixbuf,
+ int paint_scale,
+ float resource_scale)
+{
+ g_autoptr(ClutterContent) image = NULL;
+ ClutterActor *actor;
+
+ image = pixbuf_to_st_content_image (pixbuf, -1, -1, paint_scale, resource_scale);
+
+ actor = g_object_new (CLUTTER_TYPE_ACTOR,
+ "request-mode", CLUTTER_REQUEST_CONTENT_SIZE,
+ NULL);
+ clutter_actor_set_content (actor, image);
+
+ return actor;
+}
+
+static void
+hash_table_remove_with_scales (GHashTable *hash,
+ GList *scales,
+ const char *base_key)
+{
+ GList *l;
+
+ for (l = scales; l; l = l->next)
+ {
+ double scale = *((double *)l->data);
+ g_autofree char *key = NULL;
+ key = g_strdup_printf ("%s%f", base_key, scale);
+ g_hash_table_remove (hash, key);
+ }
+}
+
+static void
+hash_table_insert_scale (GHashTable *hash,
+ double scale)
+{
+ double *saved_scale;
+
+ if (g_hash_table_contains (hash, &scale))
+ return;
+
+ saved_scale = g_new (double, 1);
+ *saved_scale = scale;
+
+ g_hash_table_add (hash, saved_scale);
+}
+
+static void
+file_changed_cb (GFileMonitor *monitor,
+ GFile *file,
+ GFile *other,
+ GFileMonitorEvent event_type,
+ gpointer user_data)
+{
+ StTextureCache *cache = user_data;
+ char *key;
+ guint file_hash;
+ g_autoptr (GList) scales = NULL;
+
+ if (event_type != G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT)
+ return;
+
+ file_hash = g_file_hash (file);
+ scales = g_hash_table_get_keys (cache->priv->used_scales);
+
+ key = g_strdup_printf (CACHE_PREFIX_FILE "%u", file_hash);
+ g_hash_table_remove (cache->priv->keyed_cache, key);
+ hash_table_remove_with_scales (cache->priv->keyed_cache, scales, key);
+ g_free (key);
+
+ key = g_strdup_printf (CACHE_PREFIX_FILE_FOR_CAIRO "%u", file_hash);
+ g_hash_table_remove (cache->priv->keyed_surface_cache, key);
+ hash_table_remove_with_scales (cache->priv->keyed_surface_cache, scales, key);
+ g_free (key);
+
+ g_signal_emit (cache, signals[TEXTURE_FILE_CHANGED], 0, file);
+}
+
+static void
+ensure_monitor_for_file (StTextureCache *cache,
+ GFile *file)
+{
+ StTextureCachePrivate *priv = cache->priv;
+
+ /* No point in trying to monitor files that are part of a
+ * GResource, since it does not support file monitoring.
+ */
+ if (g_file_has_uri_scheme (file, "resource"))
+ return;
+
+ if (g_hash_table_lookup (priv->file_monitors, file) == NULL)
+ {
+ GFileMonitor *monitor = g_file_monitor_file (file, G_FILE_MONITOR_NONE,
+ NULL, NULL);
+ g_signal_connect (monitor, "changed",
+ G_CALLBACK (file_changed_cb), cache);
+ g_hash_table_insert (priv->file_monitors, g_object_ref (file), monitor);
+ }
+}
+
+typedef struct {
+ GFile *gfile;
+ gint grid_width, grid_height;
+ gint paint_scale;
+ gfloat resource_scale;
+ ClutterActor *actor;
+ GCancellable *cancellable;
+ GFunc load_callback;
+ gpointer load_callback_data;
+} AsyncImageData;
+
+static void
+on_data_destroy (gpointer data)
+{
+ AsyncImageData *d = (AsyncImageData *)data;
+ g_object_unref (d->gfile);
+ g_object_unref (d->actor);
+ g_object_unref (d->cancellable);
+ g_free (d);
+}
+
+static void
+on_sliced_image_actor_destroyed (ClutterActor *actor,
+ gpointer data)
+{
+ GTask *task = data;
+ GCancellable *cancellable = g_task_get_cancellable (task);
+
+ g_cancellable_cancel (cancellable);
+}
+
+static void
+on_sliced_image_loaded (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ GObject *cache = source_object;
+ AsyncImageData *data = (AsyncImageData *)user_data;
+ GTask *task = G_TASK (res);
+ GList *list, *pixbufs;
+
+ if (g_task_had_error (task) || g_cancellable_is_cancelled (data->cancellable))
+ return;
+
+ pixbufs = g_task_propagate_pointer (task, NULL);
+
+ for (list = pixbufs; list; list = list->next)
+ {
+ ClutterActor *actor = load_from_pixbuf (GDK_PIXBUF (list->data),
+ data->paint_scale,
+ data->resource_scale);
+ clutter_actor_hide (actor);
+ clutter_actor_add_child (data->actor, actor);
+ }
+
+ g_list_free_full (pixbufs, g_object_unref);
+
+ g_signal_handlers_disconnect_by_func (data->actor,
+ on_sliced_image_actor_destroyed,
+ task);
+
+ if (data->load_callback != NULL)
+ data->load_callback (cache, data->load_callback_data);
+}
+
+static void
+free_glist_unref_gobjects (gpointer p)
+{
+ g_list_free_full (p, g_object_unref);
+}
+
+static void
+on_loader_size_prepared (GdkPixbufLoader *loader,
+ gint width,
+ gint height,
+ gpointer user_data)
+{
+ AsyncImageData *data = user_data;
+ int scale = ceilf (data->paint_scale * data->resource_scale);
+
+ gdk_pixbuf_loader_set_size (loader, width * scale, height * scale);
+}
+
+static void
+load_sliced_image (GTask *result,
+ gpointer object,
+ gpointer task_data,
+ GCancellable *cancellable)
+{
+ AsyncImageData *data;
+ GList *res = NULL;
+ GdkPixbuf *pix;
+ gint width, height, y, x;
+ gint scale_factor;
+ GdkPixbufLoader *loader;
+ GError *error = NULL;
+ gchar *buffer = NULL;
+ gsize length;
+
+ g_assert (cancellable);
+
+ data = task_data;
+ g_assert (data);
+
+ loader = gdk_pixbuf_loader_new ();
+ g_signal_connect (loader, "size-prepared", G_CALLBACK (on_loader_size_prepared), data);
+
+ if (!g_file_load_contents (data->gfile, cancellable, &buffer, &length, NULL, &error))
+ {
+ g_warning ("Failed to open sliced image: %s", error->message);
+ goto out;
+ }
+
+ if (!gdk_pixbuf_loader_write (loader, (const guchar *) buffer, length, &error))
+ {
+ g_warning ("Failed to load image: %s", error->message);
+ goto out;
+ }
+
+ if (!gdk_pixbuf_loader_close (loader, NULL))
+ goto out;
+
+ pix = gdk_pixbuf_loader_get_pixbuf (loader);
+ width = gdk_pixbuf_get_width (pix);
+ height = gdk_pixbuf_get_height (pix);
+ scale_factor = ceilf (data->paint_scale * data->resource_scale);
+ for (y = 0; y < height; y += data->grid_height * scale_factor)
+ {
+ for (x = 0; x < width; x += data->grid_width * scale_factor)
+ {
+ GdkPixbuf *pixbuf = gdk_pixbuf_new_subpixbuf (pix, x, y,
+ data->grid_width * scale_factor,
+ data->grid_height * scale_factor);
+ g_assert (pixbuf != NULL);
+ res = g_list_append (res, pixbuf);
+ }
+ }
+
+ out:
+ /* We don't need the original pixbuf anymore, which is owned by the loader,
+ * though the subpixbufs will hold a reference. */
+ g_object_unref (loader);
+ g_free (buffer);
+ g_clear_pointer (&error, g_error_free);
+ g_task_return_pointer (result, res, free_glist_unref_gobjects);
+}
+
+/**
+ * st_texture_cache_load_sliced_image:
+ * @cache: A #StTextureCache
+ * @file: A #GFile
+ * @grid_width: Width in pixels
+ * @grid_height: Height in pixels
+ * @paint_scale: Scale factor of the display
+ * @load_callback: (scope async) (nullable): Function called when the image is loaded, or %NULL
+ * @user_data: Data to pass to the load callback
+ *
+ * This function reads a single image file which contains multiple images internally.
+ * The image file will be divided using @grid_width and @grid_height;
+ * note that the dimensions of the image loaded from @path
+ * should be a multiple of the specified grid dimensions.
+ *
+ * Returns: (transfer none): A new #ClutterActor
+ */
+ClutterActor *
+st_texture_cache_load_sliced_image (StTextureCache *cache,
+ GFile *file,
+ gint grid_width,
+ gint grid_height,
+ gint paint_scale,
+ gfloat resource_scale,
+ GFunc load_callback,
+ gpointer user_data)
+{
+ AsyncImageData *data;
+ GTask *result;
+ ClutterActor *actor = clutter_actor_new ();
+ GCancellable *cancellable = g_cancellable_new ();
+
+ g_return_val_if_fail (G_IS_FILE (file), NULL);
+ g_assert (paint_scale > 0);
+ g_assert (resource_scale > 0);
+
+ data = g_new0 (AsyncImageData, 1);
+ data->grid_width = grid_width;
+ data->grid_height = grid_height;
+ data->paint_scale = paint_scale;
+ data->resource_scale = resource_scale;
+ data->gfile = g_object_ref (file);
+ data->actor = actor;
+ data->cancellable = cancellable;
+ data->load_callback = load_callback;
+ data->load_callback_data = user_data;
+ g_object_ref (G_OBJECT (actor));
+
+ result = g_task_new (cache, cancellable, on_sliced_image_loaded, data);
+
+ g_signal_connect (actor, "destroy",
+ G_CALLBACK (on_sliced_image_actor_destroyed), result);
+
+ g_task_set_task_data (result, data, on_data_destroy);
+ g_task_run_in_thread (result, load_sliced_image);
+
+ g_object_unref (result);
+
+ return actor;
+}
+
+/**
+ * st_texture_cache_load_file_async:
+ * @cache: A #StTextureCache
+ * @file: a #GFile of the image file from which to create a pixbuf
+ * @available_width: available width for the image, can be -1 if not limited
+ * @available_height: available height for the image, can be -1 if not limited
+ * @paint_scale: scale factor of the display
+ * @resource_scale: Resource scale factor
+ *
+ * Asynchronously load an image. Initially, the returned texture will have a natural
+ * size of zero. At some later point, either the image will be loaded successfully
+ * and at that point size will be negotiated, or upon an error, no image will be set.
+ *
+ * Returns: (transfer none): A new #ClutterActor with no image loaded initially.
+ */
+ClutterActor *
+st_texture_cache_load_file_async (StTextureCache *cache,
+ GFile *file,
+ int available_width,
+ int available_height,
+ int paint_scale,
+ gfloat resource_scale)
+{
+ ClutterActor *actor;
+ AsyncTextureLoadData *request;
+ StTextureCachePolicy policy;
+ gchar *key;
+ int scale;
+
+ scale = ceilf (paint_scale * resource_scale);
+ key = g_strdup_printf (CACHE_PREFIX_FILE "%u%d", g_file_hash (file), scale);
+
+ policy = ST_TEXTURE_CACHE_POLICY_NONE; /* XXX */
+
+ actor = create_invisible_actor ();
+
+ if (ensure_request (cache, key, policy, &request, actor))
+ {
+ /* If there's an outstanding request, we've just added ourselves to it */
+ g_free (key);
+ }
+ else
+ {
+ /* Else, make a new request */
+
+ request->cache = cache;
+ /* Transfer ownership of key */
+ request->key = key;
+ request->file = g_object_ref (file);
+ request->policy = policy;
+ request->width = available_width;
+ request->height = available_height;
+ request->paint_scale = paint_scale;
+ request->resource_scale = resource_scale;
+
+ load_texture_async (cache, request);
+ }
+
+ ensure_monitor_for_file (cache, file);
+
+ return actor;
+}
+
+static CoglTexture *
+st_texture_cache_load_file_sync_to_cogl_texture (StTextureCache *cache,
+ StTextureCachePolicy policy,
+ GFile *file,
+ int available_width,
+ int available_height,
+ int paint_scale,
+ gfloat resource_scale,
+ GError **error)
+{
+ ClutterContent *image;
+ CoglTexture *texdata;
+ GdkPixbuf *pixbuf;
+ char *key;
+
+ key = g_strdup_printf (CACHE_PREFIX_FILE "%u%f", g_file_hash (file), resource_scale);
+
+ texdata = NULL;
+ image = g_hash_table_lookup (cache->priv->keyed_cache, key);
+
+ if (image == NULL)
+ {
+ pixbuf = impl_load_pixbuf_file (file, available_width, available_height,
+ paint_scale, resource_scale, error);
+ if (!pixbuf)
+ goto out;
+
+ image = pixbuf_to_st_content_image (pixbuf,
+ available_height, available_width,
+ paint_scale, resource_scale);
+ g_object_unref (pixbuf);
+
+ if (!image)
+ goto out;
+
+ if (policy == ST_TEXTURE_CACHE_POLICY_FOREVER)
+ {
+ g_hash_table_insert (cache->priv->keyed_cache, g_strdup (key), image);
+ hash_table_insert_scale (cache->priv->used_scales, (double)resource_scale);
+ }
+ }
+
+ /* Because the texture is loaded synchronously, we won't call
+ * clutter_image_set_data(), so it's safe to use the texture
+ * of ClutterImage here. */
+ texdata = clutter_image_get_texture (CLUTTER_IMAGE (image));
+ cogl_object_ref (texdata);
+
+ ensure_monitor_for_file (cache, file);
+
+out:
+ g_free (key);
+ return texdata;
+}
+
+static cairo_surface_t *
+st_texture_cache_load_file_sync_to_cairo_surface (StTextureCache *cache,
+ StTextureCachePolicy policy,
+ GFile *file,
+ int available_width,
+ int available_height,
+ int paint_scale,
+ gfloat resource_scale,
+ GError **error)
+{
+ cairo_surface_t *surface;
+ GdkPixbuf *pixbuf;
+ char *key;
+
+ key = g_strdup_printf (CACHE_PREFIX_FILE_FOR_CAIRO "%u%f", g_file_hash (file), resource_scale);
+
+ surface = g_hash_table_lookup (cache->priv->keyed_surface_cache, key);
+
+ if (surface == NULL)
+ {
+ pixbuf = impl_load_pixbuf_file (file, available_width, available_height,
+ paint_scale, resource_scale, error);
+ if (!pixbuf)
+ goto out;
+
+ surface = pixbuf_to_cairo_surface (pixbuf);
+ g_object_unref (pixbuf);
+
+ if (policy == ST_TEXTURE_CACHE_POLICY_FOREVER)
+ {
+ cairo_surface_reference (surface);
+ g_hash_table_insert (cache->priv->keyed_surface_cache,
+ g_strdup (key), surface);
+ hash_table_insert_scale (cache->priv->used_scales, (double)resource_scale);
+ }
+ }
+ else
+ cairo_surface_reference (surface);
+
+ ensure_monitor_for_file (cache, file);
+
+out:
+ g_free (key);
+ return surface;
+}
+
+/**
+ * st_texture_cache_load_file_to_cogl_texture: (skip)
+ * @cache: A #StTextureCache
+ * @file: A #GFile in supported image format
+ * @paint_scale: Scale factor of the display
+ * @resource_scale: Resource scale factor
+ *
+ * This function synchronously loads the given file path
+ * into a COGL texture. On error, a warning is emitted
+ * and %NULL is returned.
+ *
+ * Returns: (transfer full): a new #CoglTexture
+ */
+CoglTexture *
+st_texture_cache_load_file_to_cogl_texture (StTextureCache *cache,
+ GFile *file,
+ gint paint_scale,
+ gfloat resource_scale)
+{
+ CoglTexture *texture;
+ GError *error = NULL;
+
+ texture = st_texture_cache_load_file_sync_to_cogl_texture (cache, ST_TEXTURE_CACHE_POLICY_FOREVER,
+ file, -1, -1, paint_scale, resource_scale,
+ &error);
+
+ if (texture == NULL)
+ {
+ char *uri = g_file_get_uri (file);
+ g_warning ("Failed to load %s: %s", uri, error->message);
+ g_clear_error (&error);
+ g_free (uri);
+ }
+
+ return texture;
+}
+
+/**
+ * st_texture_cache_load_file_to_cairo_surface:
+ * @cache: A #StTextureCache
+ * @file: A #GFile in supported image format
+ * @paint_scale: Scale factor of the display
+ * @resource_scale: Resource scale factor
+ *
+ * This function synchronously loads the given file path
+ * into a cairo surface. On error, a warning is emitted
+ * and %NULL is returned.
+ *
+ * Returns: (transfer full): a new #cairo_surface_t
+ */
+cairo_surface_t *
+st_texture_cache_load_file_to_cairo_surface (StTextureCache *cache,
+ GFile *file,
+ gint paint_scale,
+ gfloat resource_scale)
+{
+ cairo_surface_t *surface;
+ GError *error = NULL;
+
+ surface = st_texture_cache_load_file_sync_to_cairo_surface (cache, ST_TEXTURE_CACHE_POLICY_FOREVER,
+ file, -1, -1, paint_scale, resource_scale,
+ &error);
+
+ if (surface == NULL)
+ {
+ char *uri = g_file_get_uri (file);
+ g_warning ("Failed to load %s: %s", uri, error->message);
+ g_clear_error (&error);
+ g_free (uri);
+ }
+
+ return surface;
+}
+
+static StTextureCache *instance = NULL;
+
+/**
+ * st_texture_cache_get_default:
+ *
+ * Returns: (transfer none): The global texture cache
+ */
+StTextureCache*
+st_texture_cache_get_default (void)
+{
+ if (instance == NULL)
+ instance = g_object_new (ST_TYPE_TEXTURE_CACHE, NULL);
+ return instance;
+}
+
+/**
+ * st_texture_cache_rescan_icon_theme:
+ *
+ * Rescan the current icon theme, if necessary.
+ *
+ * Returns: %TRUE if the icon theme has changed and needed to be reloaded.
+ */
+gboolean
+st_texture_cache_rescan_icon_theme (StTextureCache *cache)
+{
+ StTextureCachePrivate *priv = cache->priv;
+
+ return gtk_icon_theme_rescan_if_needed (priv->icon_theme);
+}
diff --git a/src/st/st-texture-cache.h b/src/st/st-texture-cache.h
new file mode 100644
index 0000000..55d8495
--- /dev/null
+++ b/src/st/st-texture-cache.h
@@ -0,0 +1,120 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * st-texture-cache.h: Object for loading and caching images as textures
+ *
+ * Copyright 2009, 2010 Red Hat, Inc.
+ * Copyright 2010, Maxim Ermilov
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation, either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __ST_TEXTURE_CACHE_H__
+#define __ST_TEXTURE_CACHE_H__
+
+#if !defined(ST_H_INSIDE) && !defined(ST_COMPILATION)
+#error "Only <st/st.h> can be included directly.h"
+#endif
+
+#include <gio/gio.h>
+#include <gtk/gtk.h>
+#include <clutter/clutter.h>
+
+#include <st/st-types.h>
+#include <st/st-theme-node.h>
+#include <st/st-widget.h>
+
+#define ST_TYPE_TEXTURE_CACHE (st_texture_cache_get_type ())
+G_DECLARE_FINAL_TYPE (StTextureCache, st_texture_cache,
+ ST, TEXTURE_CACHE, GObject)
+
+typedef struct _StTextureCachePrivate StTextureCachePrivate;
+
+struct _StTextureCache
+{
+ GObject parent;
+
+ StTextureCachePrivate *priv;
+};
+
+typedef enum {
+ ST_TEXTURE_CACHE_POLICY_NONE,
+ ST_TEXTURE_CACHE_POLICY_FOREVER
+} StTextureCachePolicy;
+
+StTextureCache* st_texture_cache_get_default (void);
+
+ClutterActor *
+st_texture_cache_load_sliced_image (StTextureCache *cache,
+ GFile *file,
+ gint grid_width,
+ gint grid_height,
+ gint paint_scale,
+ gfloat resource_scale,
+ GFunc load_callback,
+ gpointer user_data);
+
+GIcon *st_texture_cache_bind_cairo_surface_property (StTextureCache *cache,
+ GObject *object,
+ const char *property_name);
+GIcon *
+st_texture_cache_load_cairo_surface_to_gicon (StTextureCache *cache,
+ cairo_surface_t *surface);
+
+ClutterActor *st_texture_cache_load_gicon (StTextureCache *cache,
+ StThemeNode *theme_node,
+ GIcon *icon,
+ gint size,
+ gint paint_scale,
+ gfloat resource_scale);
+
+ClutterActor *st_texture_cache_load_file_async (StTextureCache *cache,
+ GFile *file,
+ int available_width,
+ int available_height,
+ int paint_scale,
+ gfloat resource_scale);
+
+CoglTexture *st_texture_cache_load_file_to_cogl_texture (StTextureCache *cache,
+ GFile *file,
+ gint paint_scale,
+ gfloat resource_scale);
+
+cairo_surface_t *st_texture_cache_load_file_to_cairo_surface (StTextureCache *cache,
+ GFile *file,
+ gint paint_scale,
+ gfloat resource_scale);
+
+/**
+ * StTextureCacheLoader: (skip)
+ * @cache: a #StTextureCache
+ * @key: Unique identifier for this texture
+ * @data: Callback user data
+ * @error: A #GError
+ *
+ * See st_texture_cache_load(). Implementations should return a
+ * texture handle for the given key, or set @error.
+ *
+ */
+typedef CoglTexture * (*StTextureCacheLoader) (StTextureCache *cache, const char *key, void *data, GError **error);
+
+CoglTexture * st_texture_cache_load (StTextureCache *cache,
+ const char *key,
+ StTextureCachePolicy policy,
+ StTextureCacheLoader load,
+ void *data,
+ GError **error);
+
+gboolean st_texture_cache_rescan_icon_theme (StTextureCache *cache);
+
+#endif /* __ST_TEXTURE_CACHE_H__ */
diff --git a/src/st/st-theme-context.c b/src/st/st-theme-context.c
new file mode 100644
index 0000000..4055786
--- /dev/null
+++ b/src/st/st-theme-context.c
@@ -0,0 +1,492 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * st-theme-context.c: holds global information about a tree of styled objects
+ *
+ * Copyright 2009, 2010 Red Hat, Inc.
+ * Copyright 2009 Florian Müllner
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation, either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+
+#include "st-private.h"
+#include "st-settings.h"
+#include "st-texture-cache.h"
+#include "st-theme.h"
+#include "st-theme-context.h"
+#include "st-theme-node-private.h"
+
+struct _StThemeContext {
+ GObject parent;
+
+ PangoFontDescription *font;
+ StThemeNode *root_node;
+ StTheme *theme;
+
+ /* set of StThemeNode */
+ GHashTable *nodes;
+
+ gulong stylesheets_changed_id;
+
+ int scale_factor;
+};
+
+enum
+{
+ PROP_0,
+ PROP_SCALE_FACTOR,
+
+ N_PROPS
+};
+
+static GParamSpec *props[N_PROPS] = { NULL, };
+
+enum
+{
+ CHANGED,
+
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = { 0, };
+
+G_DEFINE_TYPE (StThemeContext, st_theme_context, G_TYPE_OBJECT)
+
+static PangoFontDescription *get_interface_font_description (void);
+static void on_font_name_changed (StSettings *settings,
+ GParamSpec *pspec,
+ StThemeContext *context);
+static void on_icon_theme_changed (StTextureCache *cache,
+ StThemeContext *context);
+static void st_theme_context_changed (StThemeContext *context);
+
+static void st_theme_context_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void st_theme_context_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec);
+
+static void
+st_theme_context_set_scale_factor (StThemeContext *context,
+ int scale_factor)
+{
+ if (scale_factor == context->scale_factor)
+ return;
+
+ context->scale_factor = scale_factor;
+ g_object_notify_by_pspec (G_OBJECT (context), props[PROP_SCALE_FACTOR]);
+ st_theme_context_changed (context);
+}
+
+
+static void
+st_theme_context_finalize (GObject *object)
+{
+ StThemeContext *context = ST_THEME_CONTEXT (object);
+
+ g_signal_handlers_disconnect_by_func (st_settings_get (),
+ (gpointer) on_font_name_changed,
+ context);
+ g_signal_handlers_disconnect_by_func (st_texture_cache_get_default (),
+ (gpointer) on_icon_theme_changed,
+ context);
+ g_signal_handlers_disconnect_by_func (clutter_get_default_backend (),
+ (gpointer) st_theme_context_changed,
+ context);
+
+ g_clear_signal_handler (&context->stylesheets_changed_id, context->theme);
+
+ if (context->nodes)
+ g_hash_table_unref (context->nodes);
+ if (context->root_node)
+ g_object_unref (context->root_node);
+ if (context->theme)
+ g_object_unref (context->theme);
+
+ pango_font_description_free (context->font);
+
+ G_OBJECT_CLASS (st_theme_context_parent_class)->finalize (object);
+}
+
+static void
+st_theme_context_class_init (StThemeContextClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->set_property = st_theme_context_set_property;
+ object_class->get_property = st_theme_context_get_property;
+ object_class->finalize = st_theme_context_finalize;
+
+ /**
+ * StThemeContext:scale-factor:
+ *
+ * The scaling factor used for HiDPI scaling.
+ */
+ props[PROP_SCALE_FACTOR] =
+ g_param_spec_int ("scale-factor",
+ "Scale factor",
+ "Integer scale factor used for HiDPI scaling",
+ 0, G_MAXINT, 1,
+ ST_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
+
+ g_object_class_install_properties (object_class, N_PROPS, props);
+
+ /**
+ * StThemeContext::changed:
+ * @self: a #StThemeContext
+ *
+ * Emitted when the icon theme, font, resolution, scale factor or the current
+ * theme's custom stylesheets change.
+ */
+ signals[CHANGED] =
+ g_signal_new ("changed",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ 0, /* no default handler slot */
+ NULL, NULL, NULL,
+ G_TYPE_NONE, 0);
+}
+
+static void
+st_theme_context_init (StThemeContext *context)
+{
+ context->font = get_interface_font_description ();
+
+ g_signal_connect (st_settings_get (),
+ "notify::font-name",
+ G_CALLBACK (on_font_name_changed),
+ context);
+ g_signal_connect (st_texture_cache_get_default (),
+ "icon-theme-changed",
+ G_CALLBACK (on_icon_theme_changed),
+ context);
+ g_signal_connect_swapped (clutter_get_default_backend (),
+ "resolution-changed",
+ G_CALLBACK (st_theme_context_changed),
+ context);
+
+ context->nodes = g_hash_table_new_full ((GHashFunc) st_theme_node_hash,
+ (GEqualFunc) st_theme_node_equal,
+ g_object_unref, NULL);
+ context->scale_factor = 1;
+}
+
+static void
+st_theme_context_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ StThemeContext *context = ST_THEME_CONTEXT (object);
+
+ switch (prop_id)
+ {
+ case PROP_SCALE_FACTOR:
+ st_theme_context_set_scale_factor (context, g_value_get_int (value));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+st_theme_context_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ StThemeContext *context = ST_THEME_CONTEXT (object);
+
+ switch (prop_id)
+ {
+ case PROP_SCALE_FACTOR:
+ g_value_set_int (value, context->scale_factor);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+/**
+ * st_theme_context_new:
+ *
+ * Create a new theme context not associated with any #ClutterStage.
+ * This can be useful in testing scenarios, or if using StThemeContext
+ * with something other than #ClutterActor objects, but you generally
+ * should use st_theme_context_get_for_stage() instead.
+ *
+ * Returns: (transfer full): a new #StThemeContext
+ */
+StThemeContext *
+st_theme_context_new (void)
+{
+ StThemeContext *context;
+
+ context = g_object_new (ST_TYPE_THEME_CONTEXT, NULL);
+
+ return context;
+}
+
+static PangoFontDescription *
+get_interface_font_description (void)
+{
+ StSettings *settings = st_settings_get ();
+ g_autofree char *font_name = NULL;
+
+ g_object_get (settings, "font-name", &font_name, NULL);
+ return pango_font_description_from_string (font_name);
+}
+
+static void
+on_stage_destroy (ClutterStage *stage)
+{
+ StThemeContext *context = st_theme_context_get_for_stage (stage);
+
+ g_object_set_data (G_OBJECT (stage), "st-theme-context", NULL);
+ g_object_unref (context);
+}
+
+static void
+st_theme_context_changed (StThemeContext *context)
+{
+ StThemeNode *old_root = context->root_node;
+ context->root_node = NULL;
+ g_hash_table_remove_all (context->nodes);
+
+ g_signal_emit (context, signals[CHANGED], 0);
+
+ if (old_root)
+ g_object_unref (old_root);
+}
+
+static void
+on_font_name_changed (StSettings *settings,
+ GParamSpec *pspect,
+ StThemeContext *context)
+{
+ PangoFontDescription *font_desc = get_interface_font_description ();
+ st_theme_context_set_font (context, font_desc);
+
+ pango_font_description_free (font_desc);
+}
+
+static gboolean
+changed_idle (gpointer userdata)
+{
+ st_theme_context_changed (userdata);
+ return FALSE;
+}
+
+static void
+on_icon_theme_changed (StTextureCache *cache,
+ StThemeContext *context)
+{
+ guint id;
+
+ /* Note that an icon theme change isn't really a change of the StThemeContext;
+ * the style information has changed. But since the style factors into the
+ * icon_name => icon lookup, faking a theme context change is a good way
+ * to force users such as StIcon to look up icons again.
+ */
+ id = g_idle_add ((GSourceFunc) changed_idle, context);
+ g_source_set_name_by_id (id, "[gnome-shell] changed_idle");
+}
+
+/**
+ * st_theme_context_get_for_stage:
+ * @stage: a #ClutterStage
+ *
+ * Gets a singleton theme context associated with the stage.
+ *
+ * Returns: (transfer none): the singleton theme context for the stage
+ */
+StThemeContext *
+st_theme_context_get_for_stage (ClutterStage *stage)
+{
+ StThemeContext *context;
+
+ g_return_val_if_fail (CLUTTER_IS_STAGE (stage), NULL);
+
+ context = g_object_get_data (G_OBJECT (stage), "st-theme-context");
+ if (context)
+ return context;
+
+ context = st_theme_context_new ();
+ g_object_set_data (G_OBJECT (stage), "st-theme-context", context);
+ g_signal_connect (stage, "destroy",
+ G_CALLBACK (on_stage_destroy), NULL);
+
+ return context;
+}
+
+/**
+ * st_theme_context_set_theme:
+ * @context: a #StThemeContext
+ * @theme: a #StTheme
+ *
+ * Sets the default set of theme stylesheets for the context. This theme will
+ * be used for the root node and for nodes descending from it, unless some other
+ * style is explicitly specified.
+ */
+void
+st_theme_context_set_theme (StThemeContext *context,
+ StTheme *theme)
+{
+ g_return_if_fail (ST_IS_THEME_CONTEXT (context));
+ g_return_if_fail (theme == NULL || ST_IS_THEME (theme));
+
+ if (context->theme != theme)
+ {
+ if (context->theme)
+ g_clear_signal_handler (&context->stylesheets_changed_id, context->theme);
+
+ g_set_object (&context->theme, theme);
+
+ if (context->theme)
+ {
+ context->stylesheets_changed_id =
+ g_signal_connect_swapped (context->theme,
+ "custom-stylesheets-changed",
+ G_CALLBACK (st_theme_context_changed),
+ context);
+ }
+
+ st_theme_context_changed (context);
+ }
+}
+
+/**
+ * st_theme_context_get_theme:
+ * @context: a #StThemeContext
+ *
+ * Gets the default theme for the context. See st_theme_context_set_theme()
+ *
+ * Returns: (transfer none): the default theme for the context
+ */
+StTheme *
+st_theme_context_get_theme (StThemeContext *context)
+{
+ g_return_val_if_fail (ST_IS_THEME_CONTEXT (context), NULL);
+
+ return context->theme;
+}
+
+/**
+ * st_theme_context_set_font:
+ * @context: a #StThemeContext
+ * @font: the default font for theme context
+ *
+ * Sets the default font for the theme context. This is the font that
+ * is inherited by the root node of the tree of theme nodes. If the
+ * font is not overridden, then this font will be used. If the font is
+ * partially modified (for example, with 'font-size: 110%'), then that
+ * modification is based on this font.
+ */
+void
+st_theme_context_set_font (StThemeContext *context,
+ const PangoFontDescription *font)
+{
+ g_return_if_fail (ST_IS_THEME_CONTEXT (context));
+ g_return_if_fail (font != NULL);
+
+ if (context->font == font ||
+ pango_font_description_equal (context->font, font))
+ return;
+
+ pango_font_description_free (context->font);
+ context->font = pango_font_description_copy (font);
+ st_theme_context_changed (context);
+}
+
+/**
+ * st_theme_context_get_font:
+ * @context: a #StThemeContext
+ *
+ * Gets the default font for the theme context. See st_theme_context_set_font().
+ *
+ * Returns: the default font for the theme context.
+ */
+const PangoFontDescription *
+st_theme_context_get_font (StThemeContext *context)
+{
+ g_return_val_if_fail (ST_IS_THEME_CONTEXT (context), NULL);
+
+ return context->font;
+}
+
+/**
+ * st_theme_context_get_root_node:
+ * @context: a #StThemeContext
+ *
+ * Gets the root node of the tree of theme style nodes that associated with this
+ * context. For the node tree associated with a stage, this node represents
+ * styles applied to the stage itself.
+ *
+ * Returns: (transfer none): the root node of the context's style tree
+ */
+StThemeNode *
+st_theme_context_get_root_node (StThemeContext *context)
+{
+ if (context->root_node == NULL)
+ context->root_node = st_theme_node_new (context, NULL, context->theme,
+ G_TYPE_NONE, NULL, NULL, NULL, NULL);
+
+ return context->root_node;
+}
+
+/**
+ * st_theme_context_intern_node:
+ * @context: a #StThemeContext
+ * @node: a #StThemeNode
+ *
+ * Return an existing node matching @node, or if that isn't possible,
+ * @node itself.
+ *
+ * Returns: (transfer none): a node with the same properties as @node
+ */
+StThemeNode *
+st_theme_context_intern_node (StThemeContext *context,
+ StThemeNode *node)
+{
+ StThemeNode *mine = g_hash_table_lookup (context->nodes, node);
+
+ /* this might be node or not - it doesn't actually matter */
+ if (mine != NULL)
+ return mine;
+
+ g_hash_table_add (context->nodes, g_object_ref (node));
+ return node;
+}
+
+/**
+ * st_theme_context_get_scale_factor:
+ * @context: a #StThemeContext
+ *
+ * Return the current scale factor of @context.
+ *
+ * Returns: an integer scale factor
+ */
+int
+st_theme_context_get_scale_factor (StThemeContext *context)
+{
+ g_return_val_if_fail (ST_IS_THEME_CONTEXT (context), -1);
+
+ return context->scale_factor;
+}
diff --git a/src/st/st-theme-context.h b/src/st/st-theme-context.h
new file mode 100644
index 0000000..165ce25
--- /dev/null
+++ b/src/st/st-theme-context.h
@@ -0,0 +1,65 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * st-theme-context.c: holds global information about a tree of styled objects
+ *
+ * Copyright 2009, 2010 Red Hat, Inc.
+ * Copyright 2009 Florian Müllner
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation, either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __ST_THEME_CONTEXT_H__
+#define __ST_THEME_CONTEXT_H__
+
+#include <clutter/clutter.h>
+#include <pango/pango.h>
+#include "st-theme-node.h"
+
+G_BEGIN_DECLS
+
+/**
+ * SECTION:st-theme-context
+ * @short_description: holds global information about a tree of styled objects
+ *
+ * #StThemeContext is responsible for managing information global to a tree of styled objects,
+ * such as the set of stylesheets or the default font. In normal usage, a #StThemeContext
+ * is bound to a #ClutterStage; a singleton #StThemeContext can be obtained for a #ClutterStage
+ * by using st_theme_context_get_for_stage().
+ */
+
+#define ST_TYPE_THEME_CONTEXT (st_theme_context_get_type ())
+G_DECLARE_FINAL_TYPE (StThemeContext, st_theme_context,
+ ST, THEME_CONTEXT, GObject)
+
+StThemeContext *st_theme_context_new (void);
+StThemeContext *st_theme_context_get_for_stage (ClutterStage *stage);
+
+void st_theme_context_set_theme (StThemeContext *context,
+ StTheme *theme);
+StTheme * st_theme_context_get_theme (StThemeContext *context);
+
+void st_theme_context_set_font (StThemeContext *context,
+ const PangoFontDescription *font);
+const PangoFontDescription *st_theme_context_get_font (StThemeContext *context);
+
+StThemeNode * st_theme_context_get_root_node (StThemeContext *context);
+
+StThemeNode * st_theme_context_intern_node (StThemeContext *context,
+ StThemeNode *node);
+
+int st_theme_context_get_scale_factor (StThemeContext *context);
+
+G_END_DECLS
+
+#endif /* __ST_THEME_CONTEXT_H__ */
diff --git a/src/st/st-theme-node-drawing.c b/src/st/st-theme-node-drawing.c
new file mode 100644
index 0000000..72745ed
--- /dev/null
+++ b/src/st/st-theme-node-drawing.c
@@ -0,0 +1,2864 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * st-theme-node-drawing.c: Code to draw themed elements
+ *
+ * Copyright 2009, 2010 Red Hat, Inc.
+ * Copyright 2009, 2010 Florian Müllner
+ * Copyright 2010 Intel Corporation.
+ * Copyright 2011 Quentin "Sardem FF7" Glidic
+ *
+ * Contains code derived from:
+ * rectangle.c: Rounded rectangle.
+ * Copyright 2008 litl, LLC.
+ * st-texture-frame.h: Expandible texture actor
+ * Copyright 2007 OpenedHand
+ * Copyright 2009 Intel Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU Lesser General Public License,
+ * version 2.1, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdlib.h>
+#include <math.h>
+
+#include "st-shadow.h"
+#include "st-private.h"
+#include "st-theme-private.h"
+#include "st-theme-context.h"
+#include "st-texture-cache.h"
+#include "st-theme-node-private.h"
+
+/****
+ * Rounded corners
+ ****/
+
+typedef struct {
+ ClutterColor color;
+ ClutterColor border_color_1;
+ ClutterColor border_color_2;
+ guint radius;
+ guint border_width_1;
+ guint border_width_2;
+ float resource_scale;
+} StCornerSpec;
+
+typedef enum {
+ ST_PAINT_BORDERS_MODE_COLOR,
+ ST_PAINT_BORDERS_MODE_SILHOUETTE
+} StPaintBordersMode;
+
+static void
+elliptical_arc (cairo_t *cr,
+ double x_center,
+ double y_center,
+ double x_radius,
+ double y_radius,
+ double angle1,
+ double angle2)
+{
+ cairo_save (cr);
+ cairo_translate (cr, x_center, y_center);
+ cairo_scale (cr, x_radius, y_radius);
+ cairo_arc (cr, 0, 0, 1.0, angle1, angle2);
+ cairo_restore (cr);
+}
+
+static CoglTexture *
+create_corner_material (StCornerSpec *corner)
+{
+ ClutterBackend *backend = clutter_get_default_backend ();
+ CoglContext *ctx = clutter_backend_get_cogl_context (backend);
+ GError *error = NULL;
+ CoglTexture *texture;
+ cairo_t *cr;
+ cairo_surface_t *surface;
+ guint rowstride;
+ guint8 *data;
+ guint size;
+ guint logical_size;
+ guint max_border_width;
+ double device_scaling;
+
+ max_border_width = MAX(corner->border_width_2, corner->border_width_1);
+ logical_size = 2 * MAX(max_border_width, corner->radius);
+ size = ceilf (logical_size * corner->resource_scale);
+ rowstride = size * 4;
+ data = g_new0 (guint8, size * rowstride);
+
+ surface = cairo_image_surface_create_for_data (data,
+ CAIRO_FORMAT_ARGB32,
+ size, size,
+ rowstride);
+ device_scaling = (double) size / logical_size;
+ cairo_surface_set_device_scale (surface, device_scaling, device_scaling);
+ cr = cairo_create (surface);
+ cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
+ cairo_scale (cr, logical_size, logical_size);
+
+ if (max_border_width <= corner->radius)
+ {
+ double x_radius, y_radius;
+
+ if (max_border_width != 0)
+ {
+ cairo_set_source_rgba (cr,
+ corner->border_color_1.red / 255.,
+ corner->border_color_1.green / 255.,
+ corner->border_color_1.blue / 255.,
+ corner->border_color_1.alpha / 255.);
+
+ cairo_arc (cr, 0.5, 0.5, 0.5, 0, 2 * M_PI);
+ cairo_fill (cr);
+ }
+
+ cairo_set_source_rgba (cr,
+ corner->color.red / 255.,
+ corner->color.green / 255.,
+ corner->color.blue / 255.,
+ corner->color.alpha / 255.);
+
+ x_radius = 0.5 * (1.0 - (double) corner->border_width_2 / corner->radius);
+ y_radius = 0.5 * (1.0 - (double) corner->border_width_1 / corner->radius);
+
+ /* TOPRIGHT */
+ elliptical_arc (cr,
+ 0.5, 0.5,
+ x_radius, y_radius,
+ 3 * M_PI / 2, 2 * M_PI);
+
+ /* BOTTOMRIGHT */
+ elliptical_arc (cr,
+ 0.5, 0.5,
+ x_radius, y_radius,
+ 0, M_PI / 2);
+
+ /* TOPLEFT */
+ elliptical_arc (cr,
+ 0.5, 0.5,
+ x_radius, y_radius,
+ M_PI, 3 * M_PI / 2);
+
+ /* BOTTOMLEFT */
+ elliptical_arc (cr,
+ 0.5, 0.5,
+ x_radius, y_radius,
+ M_PI / 2, M_PI);
+
+ cairo_fill (cr);
+ }
+ else
+ {
+ double radius;
+
+ radius = (gdouble)corner->radius / max_border_width;
+
+ cairo_set_source_rgba (cr,
+ corner->border_color_1.red / 255.,
+ corner->border_color_1.green / 255.,
+ corner->border_color_1.blue / 255.,
+ corner->border_color_1.alpha / 255.);
+
+ cairo_arc (cr, radius, radius, radius, M_PI, 3 * M_PI / 2);
+ cairo_line_to (cr, 1.0 - radius, 0.0);
+ cairo_arc (cr, 1.0 - radius, radius, radius, 3 * M_PI / 2, 2 * M_PI);
+ cairo_line_to (cr, 1.0, 1.0 - radius);
+ cairo_arc (cr, 1.0 - radius, 1.0 - radius, radius, 0, M_PI / 2);
+ cairo_line_to (cr, radius, 1.0);
+ cairo_arc (cr, radius, 1.0 - radius, radius, M_PI / 2, M_PI);
+ cairo_fill (cr);
+ }
+ cairo_destroy (cr);
+
+ cairo_surface_destroy (surface);
+
+ texture = COGL_TEXTURE (cogl_texture_2d_new_from_data (ctx, size, size,
+ CLUTTER_CAIRO_FORMAT_ARGB32,
+ rowstride,
+ data,
+ &error));
+
+ if (error)
+ {
+ g_warning ("Failed to allocate texture: %s", error->message);
+ g_error_free (error);
+ }
+
+ g_free (data);
+
+ return texture;
+}
+
+static char *
+corner_to_string (StCornerSpec *corner)
+{
+ return g_strdup_printf ("st-theme-node-corner:%02x%02x%02x%02x,%02x%02x%02x%02x,%02x%02x%02x%02x,%u,%u,%u,%.4f",
+ corner->color.red, corner->color.blue, corner->color.green, corner->color.alpha,
+ corner->border_color_1.red, corner->border_color_1.green, corner->border_color_1.blue, corner->border_color_1.alpha,
+ corner->border_color_2.red, corner->border_color_2.green, corner->border_color_2.blue, corner->border_color_2.alpha,
+ corner->radius,
+ corner->border_width_1,
+ corner->border_width_2,
+ corner->resource_scale);
+}
+
+static CoglTexture *
+load_corner (StTextureCache *cache,
+ const char *key,
+ void *datap,
+ GError **error)
+{
+ return create_corner_material ((StCornerSpec *) datap);
+}
+
+/* To match the CSS specification, we want the border to look like it was
+ * drawn over the background. But actually drawing the border over the
+ * background will produce slightly bad antialiasing at the edges, so
+ * compute the effective border color instead.
+ */
+#define NORM(x) (t = (x) + 127, (t + (t >> 8)) >> 8)
+#define MULT(c,a) NORM(c*a)
+
+static void
+premultiply (ClutterColor *color)
+{
+ guint t;
+ color->red = MULT (color->red, color->alpha);
+ color->green = MULT (color->green, color->alpha);
+ color->blue = MULT (color->blue, color->alpha);
+}
+
+static void
+unpremultiply (ClutterColor *color)
+{
+ if (color->alpha != 0)
+ {
+ color->red = MIN((color->red * 255 + 127) / color->alpha, 255);
+ color->green = MIN((color->green * 255 + 127) / color->alpha, 255);
+ color->blue = MIN((color->blue * 255 + 127) / color->alpha, 255);
+ }
+}
+
+static void
+over (const ClutterColor *source,
+ const ClutterColor *destination,
+ ClutterColor *result)
+{
+ guint t;
+ ClutterColor src = *source;
+ ClutterColor dst = *destination;
+
+ premultiply (&src);
+ premultiply (&dst);
+
+ result->alpha = src.alpha + NORM ((255 - src.alpha) * dst.alpha);
+ result->red = src.red + NORM ((255 - src.alpha) * dst.red);
+ result->green = src.green + NORM ((255 - src.alpha) * dst.green);
+ result->blue = src.blue + NORM ((255 - src.alpha) * dst.blue);
+
+ unpremultiply (result);
+}
+
+/*
+ * st_theme_node_reduce_border_radius:
+ * @node: a #StThemeNode
+ * @width: The width of the box
+ * @height: The height of the box
+ * @corners: (array length=4) (out): reduced corners
+ *
+ * Implements the corner overlap algorithm mentioned at
+ * http://www.w3.org/TR/css3-background/#corner-overlap
+ */
+static void
+st_theme_node_reduce_border_radius (StThemeNode *node,
+ float width,
+ float height,
+ guint *corners)
+{
+ gfloat scale;
+ guint sum;
+
+ scale = 1.0;
+
+ /* top */
+ sum = node->border_radius[ST_CORNER_TOPLEFT]
+ + node->border_radius[ST_CORNER_TOPRIGHT];
+
+ if (sum > 0)
+ scale = MIN (width / sum, scale);
+
+ /* right */
+ sum = node->border_radius[ST_CORNER_TOPRIGHT]
+ + node->border_radius[ST_CORNER_BOTTOMRIGHT];
+
+ if (sum > 0)
+ scale = MIN (height / sum, scale);
+
+ /* bottom */
+ sum = node->border_radius[ST_CORNER_BOTTOMLEFT]
+ + node->border_radius[ST_CORNER_BOTTOMRIGHT];
+
+ if (sum > 0)
+ scale = MIN (width / sum, scale);
+
+ /* left */
+ sum = node->border_radius[ST_CORNER_BOTTOMLEFT]
+ + node->border_radius[ST_CORNER_TOPLEFT];
+
+ if (sum > 0)
+ scale = MIN (height / sum, scale);
+
+ corners[ST_CORNER_TOPLEFT] = node->border_radius[ST_CORNER_TOPLEFT] * scale;
+ corners[ST_CORNER_TOPRIGHT] = node->border_radius[ST_CORNER_TOPRIGHT] * scale;
+ corners[ST_CORNER_BOTTOMLEFT] = node->border_radius[ST_CORNER_BOTTOMLEFT] * scale;
+ corners[ST_CORNER_BOTTOMRIGHT] = node->border_radius[ST_CORNER_BOTTOMRIGHT] * scale;
+}
+
+static void
+st_theme_node_get_corner_border_widths (StThemeNode *node,
+ StCorner corner_id,
+ guint *border_width_1,
+ guint *border_width_2)
+{
+ switch (corner_id)
+ {
+ case ST_CORNER_TOPLEFT:
+ if (border_width_1)
+ *border_width_1 = node->border_width[ST_SIDE_TOP];
+ if (border_width_2)
+ *border_width_2 = node->border_width[ST_SIDE_LEFT];
+ break;
+ case ST_CORNER_TOPRIGHT:
+ if (border_width_1)
+ *border_width_1 = node->border_width[ST_SIDE_TOP];
+ if (border_width_2)
+ *border_width_2 = node->border_width[ST_SIDE_RIGHT];
+ break;
+ case ST_CORNER_BOTTOMRIGHT:
+ if (border_width_1)
+ *border_width_1 = node->border_width[ST_SIDE_BOTTOM];
+ if (border_width_2)
+ *border_width_2 = node->border_width[ST_SIDE_RIGHT];
+ break;
+ case ST_CORNER_BOTTOMLEFT:
+ if (border_width_1)
+ *border_width_1 = node->border_width[ST_SIDE_BOTTOM];
+ if (border_width_2)
+ *border_width_2 = node->border_width[ST_SIDE_LEFT];
+ break;
+ default:
+ g_assert_not_reached();
+ break;
+ }
+}
+
+static CoglPipeline *
+st_theme_node_lookup_corner (StThemeNode *node,
+ float width,
+ float height,
+ float resource_scale,
+ StCorner corner_id)
+{
+ CoglTexture *texture = NULL;
+ CoglPipeline *material = NULL;
+ char *key;
+ StTextureCache *cache;
+ StCornerSpec corner;
+ guint radius[4];
+
+ cache = st_texture_cache_get_default ();
+
+ st_theme_node_reduce_border_radius (node, width, height, radius);
+
+ if (radius[corner_id] == 0)
+ return NULL;
+
+ corner.radius = radius[corner_id];
+ corner.color = node->background_color;
+ corner.resource_scale = resource_scale;
+ st_theme_node_get_corner_border_widths (node, corner_id,
+ &corner.border_width_1,
+ &corner.border_width_2);
+
+ switch (corner_id)
+ {
+ case ST_CORNER_TOPLEFT:
+ over (&node->border_color[ST_SIDE_TOP], &corner.color, &corner.border_color_1);
+ over (&node->border_color[ST_SIDE_LEFT], &corner.color, &corner.border_color_2);
+ break;
+ case ST_CORNER_TOPRIGHT:
+ over (&node->border_color[ST_SIDE_TOP], &corner.color, &corner.border_color_1);
+ over (&node->border_color[ST_SIDE_RIGHT], &corner.color, &corner.border_color_2);
+ break;
+ case ST_CORNER_BOTTOMRIGHT:
+ over (&node->border_color[ST_SIDE_BOTTOM], &corner.color, &corner.border_color_1);
+ over (&node->border_color[ST_SIDE_RIGHT], &corner.color, &corner.border_color_2);
+ break;
+ case ST_CORNER_BOTTOMLEFT:
+ over (&node->border_color[ST_SIDE_BOTTOM], &corner.color, &corner.border_color_1);
+ over (&node->border_color[ST_SIDE_LEFT], &corner.color, &corner.border_color_2);
+ break;
+ default:
+ g_assert_not_reached();
+ break;
+ }
+
+ if (corner.color.alpha == 0 &&
+ corner.border_color_1.alpha == 0 &&
+ corner.border_color_2.alpha == 0)
+ {
+ if (node->box_shadow == NULL)
+ return NULL;
+ else /* We still need a corner texture to render the box-shadow */
+ corner.color = (ClutterColor) {0, 0, 0, 255};
+ }
+
+ key = corner_to_string (&corner);
+ texture = st_texture_cache_load (cache, key, ST_TEXTURE_CACHE_POLICY_FOREVER, load_corner, &corner, NULL);
+
+ if (texture)
+ {
+ material = _st_create_texture_pipeline (texture);
+ cogl_object_unref (texture);
+ }
+
+ g_free (key);
+
+ return material;
+}
+
+static void
+get_background_scale (StThemeNode *node,
+ gdouble painting_area_width,
+ gdouble painting_area_height,
+ gdouble background_image_width,
+ gdouble background_image_height,
+ gdouble *scale_w,
+ gdouble *scale_h)
+{
+ *scale_w = -1.0;
+ *scale_h = -1.0;
+
+ switch (node->background_size)
+ {
+ case ST_BACKGROUND_SIZE_AUTO:
+ *scale_w = 1.0f;
+ break;
+ case ST_BACKGROUND_SIZE_CONTAIN:
+ *scale_w = MIN (painting_area_width / background_image_width,
+ painting_area_height / background_image_height);
+ break;
+ case ST_BACKGROUND_SIZE_COVER:
+ *scale_w = MAX (painting_area_width / background_image_width,
+ painting_area_height / background_image_height);
+ break;
+ case ST_BACKGROUND_SIZE_FIXED:
+ if (node->background_size_w > -1)
+ {
+ *scale_w = node->background_size_w / background_image_width;
+ if (node->background_size_h > -1)
+ *scale_h = node->background_size_h / background_image_height;
+ }
+ else if (node->background_size_h > -1)
+ *scale_w = node->background_size_h / background_image_height;
+ break;
+ default:
+ g_assert_not_reached();
+ break;
+ }
+ if (*scale_h < 0.0)
+ *scale_h = *scale_w;
+}
+
+static void
+get_background_coordinates (StThemeNode *node,
+ gdouble painting_area_width,
+ gdouble painting_area_height,
+ gdouble background_image_width,
+ gdouble background_image_height,
+ gdouble *x,
+ gdouble *y)
+{
+ /* honor the specified position if any */
+ if (node->background_position_set)
+ {
+ *x = node->background_position_x;
+ *y = node->background_position_y;
+ }
+ else
+ {
+ /* center the background on the widget */
+ *x = (painting_area_width / 2.0) - (background_image_width / 2.0);
+ *y = (painting_area_height / 2.0) - (background_image_height / 2.0);
+ }
+}
+
+static void
+get_background_position (StThemeNode *self,
+ const ClutterActorBox *allocation,
+ float resource_scale,
+ ClutterActorBox *result,
+ ClutterActorBox *texture_coords)
+{
+ gdouble painting_area_width, painting_area_height;
+ gdouble background_image_width, background_image_height;
+ gdouble x1, y1;
+ gdouble scale_w, scale_h;
+
+ /* get the background image size */
+ background_image_width = cogl_texture_get_width (self->background_texture);
+ background_image_height = cogl_texture_get_height (self->background_texture);
+
+ background_image_width /= resource_scale;
+ background_image_height /= resource_scale;
+
+ /* get the painting area size */
+ painting_area_width = allocation->x2 - allocation->x1;
+ painting_area_height = allocation->y2 - allocation->y1;
+
+ /* scale if requested */
+ get_background_scale (self,
+ painting_area_width, painting_area_height,
+ background_image_width, background_image_height,
+ &scale_w, &scale_h);
+
+ background_image_width *= scale_w;
+ background_image_height *= scale_h;
+
+ /* get coordinates */
+ get_background_coordinates (self,
+ painting_area_width, painting_area_height,
+ background_image_width, background_image_height,
+ &x1, &y1);
+
+ if (self->background_repeat)
+ {
+ gdouble width = allocation->x2 - allocation->x1 + x1;
+ gdouble height = allocation->y2 - allocation->y1 + y1;
+
+ *result = *allocation;
+
+ /* reference image is at x1, y1 */
+ texture_coords->x1 = x1 / background_image_width;
+ texture_coords->y1 = y1 / background_image_height;
+ texture_coords->x2 = width / background_image_width;
+ texture_coords->y2 = height / background_image_height;
+ }
+ else
+ {
+ result->x1 = x1;
+ result->y1 = y1;
+ result->x2 = x1 + background_image_width;
+ result->y2 = y1 + background_image_height;
+
+ texture_coords->x1 = texture_coords->y1 = 0;
+ texture_coords->x2 = texture_coords->y2 = 1;
+ }
+}
+
+/* Use of this function marks code which doesn't support
+ * non-uniform colors.
+ */
+static void
+get_arbitrary_border_color (StThemeNode *node,
+ ClutterColor *color)
+{
+ if (color)
+ st_theme_node_get_border_color (node, ST_SIDE_TOP, color);
+}
+
+static gboolean
+st_theme_node_has_visible_outline (StThemeNode *node)
+{
+ if (node->background_color.alpha > 0)
+ return TRUE;
+
+ if (node->background_gradient_end.alpha > 0)
+ return TRUE;
+
+ if (node->border_radius[ST_CORNER_TOPLEFT] > 0 ||
+ node->border_radius[ST_CORNER_TOPRIGHT] > 0 ||
+ node->border_radius[ST_CORNER_BOTTOMLEFT] > 0 ||
+ node->border_radius[ST_CORNER_BOTTOMRIGHT] > 0)
+ return TRUE;
+
+ if (node->border_width[ST_SIDE_TOP] > 0 ||
+ node->border_width[ST_SIDE_LEFT] > 0 ||
+ node->border_width[ST_SIDE_RIGHT] > 0 ||
+ node->border_width[ST_SIDE_BOTTOM] > 0)
+ return TRUE;
+
+ return FALSE;
+}
+
+static cairo_pattern_t *
+create_cairo_pattern_of_background_gradient (StThemeNode *node,
+ float width,
+ float height)
+{
+ cairo_pattern_t *pattern;
+
+ g_return_val_if_fail (node->background_gradient_type != ST_GRADIENT_NONE,
+ NULL);
+
+ if (node->background_gradient_type == ST_GRADIENT_VERTICAL)
+ pattern = cairo_pattern_create_linear (0, 0, 0, height);
+ else if (node->background_gradient_type == ST_GRADIENT_HORIZONTAL)
+ pattern = cairo_pattern_create_linear (0, 0, width, 0);
+ else
+ {
+ gdouble cx, cy;
+
+ cx = width / 2.;
+ cy = height / 2.;
+ pattern = cairo_pattern_create_radial (cx, cy, 0, cx, cy, MIN (cx, cy));
+ }
+
+ cairo_pattern_add_color_stop_rgba (pattern, 0,
+ node->background_color.red / 255.,
+ node->background_color.green / 255.,
+ node->background_color.blue / 255.,
+ node->background_color.alpha / 255.);
+ cairo_pattern_add_color_stop_rgba (pattern, 1,
+ node->background_gradient_end.red / 255.,
+ node->background_gradient_end.green / 255.,
+ node->background_gradient_end.blue / 255.,
+ node->background_gradient_end.alpha / 255.);
+ return pattern;
+}
+
+static cairo_pattern_t *
+create_cairo_pattern_of_background_image (StThemeNode *node,
+ float width,
+ float height,
+ float resource_scale,
+ gboolean *needs_background_fill)
+{
+ cairo_surface_t *surface;
+ cairo_pattern_t *pattern;
+ cairo_content_t content;
+ cairo_matrix_t matrix;
+ GFile *file;
+
+ StTextureCache *texture_cache;
+
+ gdouble background_image_width, background_image_height;
+ gdouble x, y;
+ gdouble scale_w, scale_h;
+
+ file = st_theme_node_get_background_image (node);
+
+ texture_cache = st_texture_cache_get_default ();
+
+ surface = st_texture_cache_load_file_to_cairo_surface (texture_cache, file,
+ node->cached_scale_factor,
+ resource_scale);
+
+ if (surface == NULL)
+ return NULL;
+
+ g_assert (cairo_surface_get_type (surface) == CAIRO_SURFACE_TYPE_IMAGE);
+
+ content = cairo_surface_get_content (surface);
+ pattern = cairo_pattern_create_for_surface (surface);
+
+ background_image_width = cairo_image_surface_get_width (surface);
+ background_image_height = cairo_image_surface_get_height (surface);
+
+ *needs_background_fill = TRUE;
+
+ cairo_matrix_init_identity (&matrix);
+
+ if (resource_scale != 1.0)
+ {
+ background_image_width /= resource_scale;
+ background_image_height /= resource_scale;
+
+ cairo_matrix_scale (&matrix, resource_scale, resource_scale);
+ }
+
+ get_background_scale (node,
+ width, height,
+ background_image_width, background_image_height,
+ &scale_w, &scale_h);
+
+ if ((scale_w != 1) || (scale_h != 1))
+ cairo_matrix_scale (&matrix, 1.0/scale_w, 1.0/scale_h);
+
+ background_image_width *= scale_w;
+ background_image_height *= scale_h;
+
+ get_background_coordinates (node,
+ width, height,
+ background_image_width, background_image_height,
+ &x, &y);
+ cairo_matrix_translate (&matrix, -x, -y);
+
+ if (node->background_repeat)
+ cairo_pattern_set_extend (pattern, CAIRO_EXTEND_REPEAT);
+
+ /* If it's opaque, fills up the entire allocated
+ * area, then don't bother doing a background fill first
+ */
+ if (content != CAIRO_CONTENT_COLOR_ALPHA)
+ {
+ if (node->background_repeat ||
+ (x >= 0 &&
+ y >= 0 &&
+ background_image_width - x >= width &&
+ background_image_height -y >= height))
+ *needs_background_fill = FALSE;
+ }
+
+ cairo_pattern_set_matrix (pattern, &matrix);
+
+ return pattern;
+}
+
+/* fill_exterior = TRUE means that pattern is a surface pattern and
+ * we should extend the pattern with a solid fill from its edges.
+ * This is a bit of a hack; the alternative would be to make the
+ * surface of the surface pattern 1 pixel bigger and use CAIRO_EXTEND_PAD.
+ */
+static void
+paint_shadow_pattern_to_cairo_context (StShadow *shadow_spec,
+ cairo_pattern_t *pattern,
+ gboolean fill_exterior,
+ cairo_t *cr,
+ cairo_path_t *interior_path,
+ cairo_path_t *outline_path)
+{
+ /* If there are borders, clip the shadow to the interior
+ * of the borders; if there is a visible outline, clip the shadow to
+ * that outline
+ */
+ cairo_path_t *path = (interior_path != NULL) ? interior_path : outline_path;
+ double x1, x2, y1, y2;
+
+ /* fill_exterior only makes sense if we're clipping the shadow - filling
+ * to the edges of the surface would be silly */
+ g_assert (!(fill_exterior && path == NULL));
+
+ cairo_save (cr);
+ if (path != NULL)
+ {
+ cairo_append_path (cr, path);
+
+ /* There's no way to invert a path in cairo, so we need bounds for
+ * the area we are drawing in order to create the "exterior" region.
+ * Pixel align to hit fast paths.
+ */
+ if (fill_exterior)
+ {
+ cairo_path_extents (cr, &x1, &y1, &x2, &y2);
+ x1 = floor (x1);
+ y1 = floor (y1);
+ x2 = ceil (x2);
+ y2 = ceil (y2);
+ }
+
+ cairo_clip (cr);
+ }
+
+ cairo_set_source_rgba (cr,
+ shadow_spec->color.red / 255.0,
+ shadow_spec->color.green / 255.0,
+ shadow_spec->color.blue / 255.0,
+ shadow_spec->color.alpha / 255.0);
+ if (fill_exterior)
+ {
+ cairo_surface_t *surface;
+ int width, height;
+ double xscale, yscale;
+ cairo_matrix_t matrix;
+
+ cairo_save (cr);
+
+ /* Start with a rectangle enclosing the bounds of the clipped
+ * region */
+ cairo_rectangle (cr, x1, y1, x2 - x1, y2 - y1);
+
+ /* Then subtract out the bounds of the surface in the surface
+ * pattern; we transform the context by the inverse of the
+ * pattern matrix to get to surface coordinates */
+
+ if (cairo_pattern_get_surface (pattern, &surface) != CAIRO_STATUS_SUCCESS)
+ /* Something went wrong previously */
+ goto no_surface;
+
+ cairo_surface_get_device_scale (surface, &xscale, &yscale);
+ width = cairo_image_surface_get_width (surface);
+ height = cairo_image_surface_get_height (surface);
+
+ cairo_pattern_get_matrix (pattern, &matrix);
+ cairo_matrix_invert (&matrix);
+ cairo_matrix_scale (&matrix, 1.0 / xscale, 1.0 / yscale);
+ cairo_transform (cr, &matrix);
+
+ cairo_rectangle (cr, 0, height, width, - height);
+ cairo_fill (cr);
+
+ no_surface:
+ cairo_restore (cr);
+ }
+
+ cairo_mask (cr, pattern);
+ cairo_restore (cr);
+}
+
+static void
+paint_background_image_shadow_to_cairo_context (StThemeNode *node,
+ StShadow *shadow_spec,
+ cairo_pattern_t *pattern,
+ cairo_t *cr,
+ cairo_path_t *interior_path,
+ cairo_path_t *outline_path,
+ int x,
+ int y,
+ int width,
+ int height,
+ float resource_scale)
+{
+ cairo_pattern_t *shadow_pattern;
+
+ g_assert (shadow_spec != NULL);
+ g_assert (pattern != NULL);
+
+ if (outline_path != NULL)
+ {
+ cairo_surface_t *clipped_surface;
+ cairo_pattern_t *clipped_pattern;
+ cairo_t *temp_cr;
+
+ /* Prerender the pattern to a temporary surface,
+ * so it's properly clipped before we create a shadow from it
+ */
+ width = ceilf (width * resource_scale);
+ height = ceilf (height * resource_scale);
+ clipped_surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, width, height);
+ cairo_surface_set_device_scale (clipped_surface, resource_scale, resource_scale);
+ temp_cr = cairo_create (clipped_surface);
+
+ cairo_set_operator (temp_cr, CAIRO_OPERATOR_CLEAR);
+ cairo_paint (temp_cr);
+ cairo_set_operator (temp_cr, CAIRO_OPERATOR_SOURCE);
+
+ if (interior_path != NULL)
+ {
+ cairo_append_path (temp_cr, interior_path);
+ cairo_clip (temp_cr);
+ }
+
+ cairo_append_path (temp_cr, outline_path);
+ cairo_translate (temp_cr, x, y);
+ cairo_set_source (temp_cr, pattern);
+ cairo_clip (temp_cr);
+ cairo_paint (temp_cr);
+ cairo_destroy (temp_cr);
+
+ clipped_pattern = cairo_pattern_create_for_surface (clipped_surface);
+ cairo_surface_destroy (clipped_surface);
+
+ shadow_pattern = _st_create_shadow_cairo_pattern (shadow_spec,
+ clipped_pattern);
+ cairo_pattern_destroy (clipped_pattern);
+ }
+ else
+ {
+ shadow_pattern = _st_create_shadow_cairo_pattern (shadow_spec,
+ pattern);
+ }
+
+ paint_shadow_pattern_to_cairo_context (shadow_spec,
+ shadow_pattern, FALSE,
+ cr,
+ interior_path,
+ outline_path);
+ cairo_pattern_destroy (shadow_pattern);
+}
+
+/* gets the extents of a cairo_path_t; slightly inefficient, but much simpler than
+ * computing from the raw path data */
+static void
+path_extents (cairo_path_t *path,
+ double *x1,
+ double *y1,
+ double *x2,
+ double *y2)
+
+{
+ cairo_surface_t *dummy = cairo_image_surface_create (CAIRO_FORMAT_A8, 1, 1);
+ cairo_t *cr = cairo_create (dummy);
+
+ cairo_append_path (cr, path);
+ cairo_path_extents (cr, x1, y1, x2, y2);
+
+ cairo_destroy (cr);
+ cairo_surface_destroy (dummy);
+}
+
+static void
+paint_inset_box_shadow_to_cairo_context (StThemeNode *node,
+ StShadow *shadow_spec,
+ float resource_scale,
+ cairo_t *cr,
+ cairo_path_t *shadow_outline)
+{
+ cairo_surface_t *shadow_surface;
+ cairo_pattern_t *shadow_pattern;
+ double extents_x1, extents_y1, extents_x2, extents_y2;
+ double shrunk_extents_x1, shrunk_extents_y1, shrunk_extents_x2, shrunk_extents_y2;
+ gboolean fill_exterior;
+
+ g_assert (shadow_spec != NULL);
+ g_assert (shadow_outline != NULL);
+
+ /* Create the pattern used to create the inset shadow; as the shadow
+ * should be drawn as if everything outside the outline was opaque,
+ * we use a temporary surface to draw the background as a solid shape,
+ * which is inverted when creating the shadow pattern.
+ */
+
+ /* First we need to find the size of the temporary surface
+ */
+ path_extents (shadow_outline,
+ &extents_x1, &extents_y1, &extents_x2, &extents_y2);
+
+ /* Shrink the extents by the spread, and offset */
+ shrunk_extents_x1 = extents_x1 + shadow_spec->xoffset + shadow_spec->spread;
+ shrunk_extents_y1 = extents_y1 + shadow_spec->yoffset + shadow_spec->spread;
+ shrunk_extents_x2 = extents_x2 + shadow_spec->xoffset - shadow_spec->spread;
+ shrunk_extents_y2 = extents_y2 + shadow_spec->yoffset - shadow_spec->spread;
+
+ if (shrunk_extents_x1 >= shrunk_extents_x2 || shrunk_extents_y1 >= shrunk_extents_y2)
+ {
+ /* Shadow occupies entire area within border */
+ shadow_pattern = cairo_pattern_create_rgb (0., 0., 0.);
+ fill_exterior = FALSE;
+ }
+ else
+ {
+ /* Bounds of temporary surface */
+ int surface_x = floor (shrunk_extents_x1);
+ int surface_y = floor (shrunk_extents_y1);
+ int surface_width = ceil ((shrunk_extents_x2 - surface_x) * resource_scale);
+ int surface_height = ceil ((shrunk_extents_y2 - surface_y) * resource_scale);
+
+ /* Center of the original path */
+ double x_center = (extents_x1 + extents_x2) / 2;
+ double y_center = (extents_y1 + extents_y2) / 2;
+
+ cairo_pattern_t *pattern;
+ cairo_t *temp_cr;
+ cairo_matrix_t matrix;
+
+ shadow_surface = cairo_image_surface_create (CAIRO_FORMAT_A8, surface_width, surface_height);
+ cairo_surface_set_device_scale (shadow_surface, resource_scale, resource_scale);
+ temp_cr = cairo_create (shadow_surface);
+
+ /* Match the coordinates in the temporary context to the parent context */
+ cairo_translate (temp_cr, - surface_x, - surface_y);
+
+ /* Shadow offset */
+ cairo_translate (temp_cr, shadow_spec->xoffset, shadow_spec->yoffset);
+
+ /* Scale the path around the center to match the shrunk bounds */
+ cairo_translate (temp_cr, x_center, y_center);
+ cairo_scale (temp_cr,
+ (shrunk_extents_x2 - shrunk_extents_x1) / (extents_x2 - extents_x1),
+ (shrunk_extents_y2 - shrunk_extents_y1) / (extents_y2 - extents_y1));
+ cairo_translate (temp_cr, - x_center, - y_center);
+
+ cairo_append_path (temp_cr, shadow_outline);
+ cairo_fill (temp_cr);
+ cairo_destroy (temp_cr);
+
+ pattern = cairo_pattern_create_for_surface (shadow_surface);
+ cairo_surface_destroy (shadow_surface);
+
+ /* The pattern needs to be offset back to coordinates in the parent context */
+ cairo_matrix_init_translate (&matrix, - surface_x, - surface_y);
+ cairo_pattern_set_matrix (pattern, &matrix);
+
+ shadow_pattern = _st_create_shadow_cairo_pattern (shadow_spec, pattern);
+ fill_exterior = TRUE;
+
+ cairo_pattern_destroy (pattern);
+ }
+
+ paint_shadow_pattern_to_cairo_context (shadow_spec,
+ shadow_pattern, fill_exterior,
+ cr,
+ shadow_outline,
+ NULL);
+
+ cairo_pattern_destroy (shadow_pattern);
+}
+
+/* In order for borders to be smoothly blended with non-solid backgrounds,
+ * we need to use cairo. This function is a slow fallback path for those
+ * cases (gradients, background images, etc).
+ */
+static CoglTexture *
+st_theme_node_prerender_background (StThemeNode *node,
+ float actor_width,
+ float actor_height,
+ float resource_scale)
+{
+ ClutterBackend *backend = clutter_get_default_backend ();
+ CoglContext *ctx = clutter_backend_get_cogl_context (backend);
+ GError *error = NULL;
+ StBorderImage *border_image;
+ CoglTexture *texture;
+ guint radius[4];
+ int i;
+ cairo_t *cr;
+ cairo_surface_t *surface;
+ StShadow *shadow_spec;
+ StShadow *box_shadow_spec;
+ cairo_pattern_t *pattern = NULL;
+ cairo_path_t *outline_path = NULL;
+ gboolean draw_solid_background = TRUE;
+ gboolean background_is_translucent;
+ gboolean interior_dirty;
+ gboolean draw_background_image_shadow = FALSE;
+ gboolean has_visible_outline;
+ ClutterColor border_color;
+ guint border_width[4];
+ guint rowstride;
+ guchar *data;
+ ClutterActorBox actor_box;
+ ClutterActorBox paint_box;
+ cairo_path_t *interior_path = NULL;
+ float width, height;
+ int texture_width;
+ int texture_height;
+
+ border_image = st_theme_node_get_border_image (node);
+
+ shadow_spec = st_theme_node_get_background_image_shadow (node);
+ box_shadow_spec = st_theme_node_get_box_shadow (node);
+
+ actor_box.x1 = 0;
+ actor_box.x2 = actor_width;
+ actor_box.y1 = 0;
+ actor_box.y2 = actor_height;
+
+ /* If there's a background image shadow, we
+ * may need to create an image bigger than the nodes
+ * allocation
+ */
+ st_theme_node_get_background_paint_box (node, &actor_box, &paint_box);
+
+ /* translate the boxes so the paint box is at 0,0
+ */
+ actor_box.x1 += - paint_box.x1;
+ actor_box.x2 += - paint_box.x1;
+ actor_box.y1 += - paint_box.y1;
+ actor_box.y2 += - paint_box.y1;
+
+ width = paint_box.x2 - paint_box.x1;
+ height = paint_box.y2 - paint_box.y1;
+
+ texture_width = ceilf (width * resource_scale);
+ texture_height = ceilf (height * resource_scale);
+
+ rowstride = cairo_format_stride_for_width (CAIRO_FORMAT_ARGB32, texture_width);
+ data = g_new0 (guchar, texture_height * rowstride);
+
+ /* We zero initialize the destination memory, so it's fully transparent
+ * by default.
+ */
+ interior_dirty = FALSE;
+
+ surface = cairo_image_surface_create_for_data (data,
+ CAIRO_FORMAT_ARGB32,
+ texture_width, texture_height,
+ rowstride);
+ cairo_surface_set_device_scale (surface, resource_scale, resource_scale);
+ cr = cairo_create (surface);
+
+ /* TODO - support non-uniform border colors */
+ get_arbitrary_border_color (node, &border_color);
+
+ st_theme_node_reduce_border_radius (node, width, height, radius);
+
+ for (i = 0; i < 4; i++)
+ border_width[i] = st_theme_node_get_border_width (node, i);
+
+ /* Note we don't support translucent background images on top
+ * of gradients. It's strictly either/or.
+ */
+ if (node->background_gradient_type != ST_GRADIENT_NONE)
+ {
+ pattern = create_cairo_pattern_of_background_gradient (node, width, height);
+ draw_solid_background = FALSE;
+
+ /* If the gradient has any translucent areas, we need to
+ * erase the interior region before drawing, so that we show
+ * what's actually under the gradient and not whatever is
+ * left over from filling the border, etc.
+ */
+ if (node->background_color.alpha < 255 ||
+ node->background_gradient_end.alpha < 255)
+ background_is_translucent = TRUE;
+ else
+ background_is_translucent = FALSE;
+ }
+ else
+ {
+ GFile *background_image;
+
+ background_image = st_theme_node_get_background_image (node);
+
+ if (background_image != NULL)
+ {
+ pattern = create_cairo_pattern_of_background_image (node,
+ width, height,
+ resource_scale,
+ &draw_solid_background);
+ if (shadow_spec && pattern != NULL)
+ draw_background_image_shadow = TRUE;
+ }
+
+ /* We never need to clear the interior region before drawing the
+ * background image, because it either always fills the entire area
+ * opaquely, or we draw the solid background behind it.
+ */
+ background_is_translucent = FALSE;
+ }
+
+ if (pattern == NULL)
+ draw_solid_background = TRUE;
+
+ /* drawing the solid background implicitly clears the interior
+ * region, so if we're going to draw a solid background before drawing
+ * the background pattern, then we don't need to bother also clearing the
+ * background region.
+ */
+ if (draw_solid_background)
+ background_is_translucent = FALSE;
+
+ has_visible_outline = st_theme_node_has_visible_outline (node);
+
+ /* Create a path for the background's outline first */
+ if (radius[ST_CORNER_TOPLEFT] > 0)
+ cairo_arc (cr,
+ actor_box.x1 + radius[ST_CORNER_TOPLEFT],
+ actor_box.y1 + radius[ST_CORNER_TOPLEFT],
+ radius[ST_CORNER_TOPLEFT], M_PI, 3 * M_PI / 2);
+ else
+ cairo_move_to (cr, actor_box.x1, actor_box.y1);
+ cairo_line_to (cr, actor_box.x2 - radius[ST_CORNER_TOPRIGHT], actor_box.x1);
+ if (radius[ST_CORNER_TOPRIGHT] > 0)
+ cairo_arc (cr,
+ actor_box.x2 - radius[ST_CORNER_TOPRIGHT],
+ actor_box.x1 + radius[ST_CORNER_TOPRIGHT],
+ radius[ST_CORNER_TOPRIGHT], 3 * M_PI / 2, 2 * M_PI);
+ cairo_line_to (cr, actor_box.x2, actor_box.y2 - radius[ST_CORNER_BOTTOMRIGHT]);
+ if (radius[ST_CORNER_BOTTOMRIGHT] > 0)
+ cairo_arc (cr,
+ actor_box.x2 - radius[ST_CORNER_BOTTOMRIGHT],
+ actor_box.y2 - radius[ST_CORNER_BOTTOMRIGHT],
+ radius[ST_CORNER_BOTTOMRIGHT], 0, M_PI / 2);
+ cairo_line_to (cr, actor_box.x1 + radius[ST_CORNER_BOTTOMLEFT], actor_box.y2);
+ if (radius[ST_CORNER_BOTTOMLEFT] > 0)
+ cairo_arc (cr,
+ actor_box.x1 + radius[ST_CORNER_BOTTOMLEFT],
+ actor_box.y2 - radius[ST_CORNER_BOTTOMLEFT],
+ radius[ST_CORNER_BOTTOMLEFT], M_PI / 2, M_PI);
+ cairo_close_path (cr);
+
+ outline_path = cairo_copy_path (cr);
+
+ /* If we have a solid border, we fill the outline shape with the border
+ * color and create the inline shape for the background;
+ * otherwise the outline shape is filled with the background
+ * directly
+ */
+ if (border_image == NULL &&
+ (border_width[ST_SIDE_TOP] > 0 ||
+ border_width[ST_SIDE_RIGHT] > 0 ||
+ border_width[ST_SIDE_BOTTOM] > 0 ||
+ border_width[ST_SIDE_LEFT] > 0))
+ {
+ cairo_set_source_rgba (cr,
+ border_color.red / 255.,
+ border_color.green / 255.,
+ border_color.blue / 255.,
+ border_color.alpha / 255.);
+ cairo_fill (cr);
+
+ /* We were sloppy when filling in the border, and now the interior
+ * is filled with the border color, too.
+ */
+ interior_dirty = TRUE;
+
+ if (radius[ST_CORNER_TOPLEFT] > MAX(border_width[ST_SIDE_TOP],
+ border_width[ST_SIDE_LEFT]))
+ elliptical_arc (cr,
+ actor_box.x1 + radius[ST_CORNER_TOPLEFT],
+ actor_box.y1 + radius[ST_CORNER_TOPLEFT],
+ radius[ST_CORNER_TOPLEFT] - border_width[ST_SIDE_LEFT],
+ radius[ST_CORNER_TOPLEFT] - border_width[ST_SIDE_TOP],
+ M_PI, 3 * M_PI / 2);
+ else
+ cairo_move_to (cr,
+ actor_box.x1 + border_width[ST_SIDE_LEFT],
+ actor_box.y1 + border_width[ST_SIDE_TOP]);
+
+ cairo_line_to (cr,
+ actor_box.x2 - MAX(radius[ST_CORNER_TOPRIGHT], border_width[ST_SIDE_RIGHT]),
+ actor_box.y1 + border_width[ST_SIDE_TOP]);
+
+ if (radius[ST_CORNER_TOPRIGHT] > MAX(border_width[ST_SIDE_TOP],
+ border_width[ST_SIDE_RIGHT]))
+ elliptical_arc (cr,
+ actor_box.x2 - radius[ST_CORNER_TOPRIGHT],
+ actor_box.y1 + radius[ST_CORNER_TOPRIGHT],
+ radius[ST_CORNER_TOPRIGHT] - border_width[ST_SIDE_RIGHT],
+ radius[ST_CORNER_TOPRIGHT] - border_width[ST_SIDE_TOP],
+ 3 * M_PI / 2, 2 * M_PI);
+ else
+ cairo_line_to (cr,
+ actor_box.x2 - border_width[ST_SIDE_RIGHT],
+ actor_box.y1 + border_width[ST_SIDE_TOP]);
+
+ cairo_line_to (cr,
+ actor_box.x2 - border_width[ST_SIDE_RIGHT],
+ actor_box.y2 - MAX(radius[ST_CORNER_BOTTOMRIGHT], border_width[ST_SIDE_BOTTOM]));
+
+ if (radius[ST_CORNER_BOTTOMRIGHT] > MAX(border_width[ST_SIDE_BOTTOM],
+ border_width[ST_SIDE_RIGHT]))
+ elliptical_arc (cr,
+ actor_box.x2 - radius[ST_CORNER_BOTTOMRIGHT],
+ actor_box.y2 - radius[ST_CORNER_BOTTOMRIGHT],
+ radius[ST_CORNER_BOTTOMRIGHT] - border_width[ST_SIDE_RIGHT],
+ radius[ST_CORNER_BOTTOMRIGHT] - border_width[ST_SIDE_BOTTOM],
+ 0, M_PI / 2);
+ else
+ cairo_line_to (cr,
+ actor_box.x2 - border_width[ST_SIDE_RIGHT],
+ actor_box.y2 - border_width[ST_SIDE_BOTTOM]);
+
+ cairo_line_to (cr,
+ MAX(radius[ST_CORNER_BOTTOMLEFT], border_width[ST_SIDE_LEFT]),
+ actor_box.y2 - border_width[ST_SIDE_BOTTOM]);
+
+ if (radius[ST_CORNER_BOTTOMLEFT] > MAX(border_width[ST_SIDE_BOTTOM],
+ border_width[ST_SIDE_LEFT]))
+ elliptical_arc (cr,
+ actor_box.x1 + radius[ST_CORNER_BOTTOMLEFT],
+ actor_box.y2 - radius[ST_CORNER_BOTTOMLEFT],
+ radius[ST_CORNER_BOTTOMLEFT] - border_width[ST_SIDE_LEFT],
+ radius[ST_CORNER_BOTTOMLEFT] - border_width[ST_SIDE_BOTTOM],
+ M_PI / 2, M_PI);
+ else
+ cairo_line_to (cr,
+ actor_box.x1 + border_width[ST_SIDE_LEFT],
+ actor_box.y2 - border_width[ST_SIDE_BOTTOM]);
+
+ cairo_close_path (cr);
+
+ interior_path = cairo_copy_path (cr);
+
+ /* clip drawing to the region inside of the borders
+ */
+ cairo_clip (cr);
+
+ /* But fill the pattern as if it started at the edge of outline,
+ * behind the borders. This is similar to
+ * background-clip: border-box; semantics.
+ */
+ cairo_append_path (cr, outline_path);
+ }
+
+ if (interior_dirty && background_is_translucent)
+ {
+ cairo_set_operator (cr, CAIRO_OPERATOR_CLEAR);
+ cairo_fill_preserve (cr);
+ cairo_set_operator (cr, CAIRO_OPERATOR_OVER);
+ }
+
+ if (draw_solid_background)
+ {
+ cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
+
+ cairo_set_source_rgba (cr,
+ node->background_color.red / 255.,
+ node->background_color.green / 255.,
+ node->background_color.blue / 255.,
+ node->background_color.alpha / 255.);
+ cairo_fill_preserve (cr);
+ cairo_set_operator (cr, CAIRO_OPERATOR_OVER);
+ }
+
+ if (draw_background_image_shadow)
+ {
+ paint_background_image_shadow_to_cairo_context (node,
+ shadow_spec,
+ pattern,
+ cr,
+ interior_path,
+ has_visible_outline? outline_path : NULL,
+ actor_box.x1,
+ actor_box.y1,
+ width, height,
+ resource_scale);
+ cairo_append_path (cr, outline_path);
+ }
+
+ cairo_translate (cr, actor_box.x1, actor_box.y1);
+
+ if (pattern != NULL)
+ {
+ cairo_set_source (cr, pattern);
+ cairo_fill (cr);
+ cairo_pattern_destroy (pattern);
+ }
+
+ if (box_shadow_spec && box_shadow_spec->inset)
+ {
+ paint_inset_box_shadow_to_cairo_context (node,
+ box_shadow_spec,
+ resource_scale,
+ cr,
+ interior_path ? interior_path
+ : outline_path);
+ }
+
+ if (outline_path != NULL)
+ cairo_path_destroy (outline_path);
+
+ if (interior_path != NULL)
+ cairo_path_destroy (interior_path);
+
+ texture = COGL_TEXTURE (cogl_texture_2d_new_from_data (ctx,
+ texture_width,
+ texture_height,
+ CLUTTER_CAIRO_FORMAT_ARGB32,
+ rowstride,
+ data,
+ &error));
+
+ if (error)
+ {
+ g_warning ("Failed to allocate texture: %s", error->message);
+ g_error_free (error);
+ }
+
+ cairo_destroy (cr);
+ cairo_surface_destroy (surface);
+ g_free (data);
+
+ return texture;
+}
+
+static void st_theme_node_paint_borders (StThemeNodePaintState *state,
+ CoglFramebuffer *framebuffer,
+ const ClutterActorBox *box,
+ StPaintBordersMode mode,
+ guint8 paint_opacity);
+
+void
+st_theme_node_invalidate_border_image (StThemeNode *node)
+{
+ cogl_clear_object (&node->border_slices_texture);
+ cogl_clear_object (&node->border_slices_pipeline);
+}
+
+static gboolean
+st_theme_node_load_border_image (StThemeNode *node,
+ gfloat resource_scale)
+{
+ if (node->border_slices_texture == NULL)
+ {
+ StBorderImage *border_image;
+ GFile *file;
+
+ border_image = st_theme_node_get_border_image (node);
+ if (border_image == NULL)
+ goto out;
+
+ file = st_border_image_get_file (border_image);
+
+ node->border_slices_texture = st_texture_cache_load_file_to_cogl_texture (st_texture_cache_get_default (),
+ file,
+ node->cached_scale_factor,
+ resource_scale);
+ if (node->border_slices_texture == NULL)
+ goto out;
+
+ node->border_slices_pipeline = _st_create_texture_pipeline (node->border_slices_texture);
+ }
+
+ out:
+ return node->border_slices_texture != NULL;
+}
+
+void
+st_theme_node_invalidate_background_image (StThemeNode *node)
+{
+ cogl_clear_object (&node->background_texture);
+ cogl_clear_object (&node->background_pipeline);
+ cogl_clear_object (&node->background_shadow_pipeline);
+}
+
+static gboolean
+st_theme_node_load_background_image (StThemeNode *node,
+ gfloat resource_scale)
+{
+ if (node->background_texture == NULL)
+ {
+ GFile *background_image;
+ StShadow *background_image_shadow_spec;
+
+ background_image = st_theme_node_get_background_image (node);
+ if (background_image == NULL)
+ goto out;
+
+ background_image_shadow_spec = st_theme_node_get_background_image_shadow (node);
+ node->background_texture = st_texture_cache_load_file_to_cogl_texture (st_texture_cache_get_default (),
+ background_image,
+ node->cached_scale_factor,
+ resource_scale);
+ if (node->background_texture == NULL)
+ goto out;
+
+ node->background_pipeline = _st_create_texture_pipeline (node->background_texture);
+
+ if (node->background_repeat)
+ cogl_pipeline_set_layer_wrap_mode (node->background_pipeline, 0,
+ COGL_PIPELINE_WRAP_MODE_REPEAT);
+
+ if (background_image_shadow_spec)
+ {
+ node->background_shadow_pipeline = _st_create_shadow_pipeline (background_image_shadow_spec,
+ node->background_texture,
+ resource_scale);
+ }
+ }
+
+ out:
+ return node->background_texture != NULL;
+}
+
+static gboolean
+st_theme_node_invalidate_resources_for_file (StThemeNode *node,
+ GFile *file)
+{
+ StBorderImage *border_image;
+ gboolean changed = FALSE;
+ GFile *theme_file;
+
+ theme_file = st_theme_node_get_background_image (node);
+ if ((theme_file != NULL) && g_file_equal (theme_file, file))
+ {
+ st_theme_node_invalidate_background_image (node);
+ changed = TRUE;
+ }
+
+ border_image = st_theme_node_get_border_image (node);
+ theme_file = border_image ? st_border_image_get_file (border_image) : NULL;
+ if ((theme_file != NULL) && g_file_equal (theme_file, file))
+ {
+ st_theme_node_invalidate_border_image (node);
+ changed = TRUE;
+ }
+
+ return changed;
+}
+
+static void st_theme_node_compute_maximum_borders (StThemeNodePaintState *state);
+static void st_theme_node_prerender_shadow (StThemeNodePaintState *state);
+
+static void
+st_theme_node_render_resources (StThemeNodePaintState *state,
+ StThemeNode *node,
+ float width,
+ float height,
+ float resource_scale)
+{
+ gboolean has_border;
+ gboolean has_border_radius;
+ gboolean has_inset_box_shadow;
+ gboolean has_large_corners;
+ StShadow *box_shadow_spec;
+
+ g_return_if_fail (width > 0 && height > 0);
+
+ /* FIXME - need to separate this into things that need to be recomputed on
+ * geometry change versus things that can be cached regardless, such as
+ * a background image.
+ */
+ st_theme_node_paint_state_free (state);
+
+ st_theme_node_paint_state_set_node (state, node);
+ state->alloc_width = width;
+ state->alloc_height = height;
+ state->resource_scale = resource_scale;
+
+ _st_theme_node_ensure_background (node);
+ _st_theme_node_ensure_geometry (node);
+
+ box_shadow_spec = st_theme_node_get_box_shadow (node);
+ has_inset_box_shadow = box_shadow_spec && box_shadow_spec->inset;
+
+ if (node->border_width[ST_SIDE_TOP] > 0 ||
+ node->border_width[ST_SIDE_LEFT] > 0 ||
+ node->border_width[ST_SIDE_RIGHT] > 0 ||
+ node->border_width[ST_SIDE_BOTTOM] > 0)
+ has_border = TRUE;
+ else
+ has_border = FALSE;
+
+ if (node->border_radius[ST_CORNER_TOPLEFT] > 0 ||
+ node->border_radius[ST_CORNER_TOPRIGHT] > 0 ||
+ node->border_radius[ST_CORNER_BOTTOMLEFT] > 0 ||
+ node->border_radius[ST_CORNER_BOTTOMRIGHT] > 0)
+ has_border_radius = TRUE;
+ else
+ has_border_radius = FALSE;
+
+ /* The cogl code pads each corner to the maximum border radius,
+ * which results in overlapping corner areas if the radius
+ * exceeds the actor's halfsize, causing rendering errors.
+ * Fall back to cairo in these cases. */
+ has_large_corners = FALSE;
+
+ if (has_border_radius) {
+ guint border_radius[4];
+ int corner;
+
+ st_theme_node_reduce_border_radius (node, width, height, border_radius);
+
+ for (corner = 0; corner < 4; corner ++) {
+ if (border_radius[corner] * 2 > height ||
+ border_radius[corner] * 2 > width) {
+ has_large_corners = TRUE;
+ break;
+ }
+ }
+ }
+
+ state->corner_material[ST_CORNER_TOPLEFT] =
+ st_theme_node_lookup_corner (node, width, height, resource_scale, ST_CORNER_TOPLEFT);
+ state->corner_material[ST_CORNER_TOPRIGHT] =
+ st_theme_node_lookup_corner (node, width, height, resource_scale, ST_CORNER_TOPRIGHT);
+ state->corner_material[ST_CORNER_BOTTOMRIGHT] =
+ st_theme_node_lookup_corner (node, width, height, resource_scale, ST_CORNER_BOTTOMRIGHT);
+ state->corner_material[ST_CORNER_BOTTOMLEFT] =
+ st_theme_node_lookup_corner (node, width, height, resource_scale, ST_CORNER_BOTTOMLEFT);
+
+ /* Use cairo to prerender the node if there is a gradient, or
+ * background image with borders and/or rounded corners,
+ * or large corners, since we can't do those things
+ * easily with cogl.
+ *
+ * FIXME: if we could figure out ahead of time that a
+ * background image won't overlap with the node borders,
+ * then we could use cogl for that case.
+ */
+ if ((node->background_gradient_type != ST_GRADIENT_NONE)
+ || (has_inset_box_shadow && (has_border || node->background_color.alpha > 0))
+ || (st_theme_node_get_background_image (node) && (has_border || has_border_radius))
+ || has_large_corners)
+ state->prerendered_texture = st_theme_node_prerender_background (node, width, height,
+ resource_scale);
+
+ if (state->prerendered_texture)
+ state->prerendered_pipeline = _st_create_texture_pipeline (state->prerendered_texture);
+ else
+ state->prerendered_pipeline = NULL;
+
+ if (box_shadow_spec && !has_inset_box_shadow)
+ {
+ st_theme_node_compute_maximum_borders (state);
+
+ if (st_theme_node_load_border_image (node, resource_scale))
+ state->box_shadow_pipeline = _st_create_shadow_pipeline (box_shadow_spec,
+ node->border_slices_texture,
+ state->resource_scale);
+ else if (state->prerendered_texture != NULL)
+ state->box_shadow_pipeline = _st_create_shadow_pipeline (box_shadow_spec,
+ state->prerendered_texture,
+ state->resource_scale);
+ else
+ st_theme_node_prerender_shadow (state);
+ }
+
+ /* If we don't have cached textures yet, check whether we can cache
+ them. */
+ if (!node->cached_textures)
+ {
+ if (state->prerendered_pipeline == NULL &&
+ width >= node->box_shadow_min_width &&
+ height >= node->box_shadow_min_height)
+ {
+ st_theme_node_paint_state_copy (&node->cached_state, state);
+ node->cached_textures = TRUE;
+ }
+ }
+}
+
+static void
+st_theme_node_update_resources (StThemeNodePaintState *state,
+ StThemeNode *node,
+ float width,
+ float height,
+ float resource_scale)
+{
+ gboolean had_prerendered_texture = FALSE;
+ gboolean had_box_shadow = FALSE;
+ StShadow *box_shadow_spec;
+
+ g_return_if_fail (width > 0 && height > 0);
+
+ /* Free handles we can't reuse */
+ had_prerendered_texture = (state->prerendered_texture != NULL);
+ cogl_clear_object (&state->prerendered_texture);
+
+ if (state->prerendered_pipeline != NULL)
+ {
+ cogl_clear_object (&state->prerendered_pipeline);
+
+ if (node->border_slices_texture == NULL &&
+ state->box_shadow_pipeline != NULL)
+ {
+ cogl_clear_object (&state->box_shadow_pipeline);
+ had_box_shadow = TRUE;
+ }
+ }
+
+ st_theme_node_paint_state_set_node (state, node);
+ state->alloc_width = width;
+ state->alloc_height = height;
+ state->resource_scale = resource_scale;
+
+ box_shadow_spec = st_theme_node_get_box_shadow (node);
+
+ if (had_prerendered_texture)
+ {
+ state->prerendered_texture = st_theme_node_prerender_background (node, width, height, resource_scale);
+ state->prerendered_pipeline = _st_create_texture_pipeline (state->prerendered_texture);
+ }
+ else
+ {
+ int corner_id;
+
+ for (corner_id = 0; corner_id < 4; corner_id++)
+ if (state->corner_material[corner_id] == NULL)
+ state->corner_material[corner_id] =
+ st_theme_node_lookup_corner (node, width, height, resource_scale, corner_id);
+ }
+
+ if (had_box_shadow)
+ state->box_shadow_pipeline = _st_create_shadow_pipeline (box_shadow_spec,
+ state->prerendered_texture,
+ state->resource_scale);
+}
+
+static void
+paint_material_with_opacity (CoglPipeline *material,
+ CoglFramebuffer *framebuffer,
+ ClutterActorBox *box,
+ ClutterActorBox *coords,
+ guint8 paint_opacity)
+{
+ cogl_pipeline_set_color4ub (material,
+ paint_opacity, paint_opacity, paint_opacity, paint_opacity);
+
+ if (coords)
+ cogl_framebuffer_draw_textured_rectangle (framebuffer, material,
+ box->x1, box->y1, box->x2, box->y2,
+ coords->x1, coords->y1, coords->x2, coords->y2);
+ else
+ cogl_framebuffer_draw_rectangle (framebuffer, material,
+ box->x1, box->y1, box->x2, box->y2);
+}
+
+static void
+st_theme_node_ensure_color_pipeline (StThemeNode *node)
+{
+ static CoglPipeline *color_pipeline_template = NULL;
+
+ if (node->color_pipeline != NULL)
+ return;
+
+ if (G_UNLIKELY (color_pipeline_template == NULL))
+ {
+ CoglContext *ctx =
+ clutter_backend_get_cogl_context (clutter_get_default_backend ());
+
+ color_pipeline_template = cogl_pipeline_new (ctx);
+ }
+
+ node->color_pipeline = cogl_pipeline_copy (color_pipeline_template);
+}
+
+static void
+st_theme_node_paint_borders (StThemeNodePaintState *state,
+ CoglFramebuffer *framebuffer,
+ const ClutterActorBox *box,
+ StPaintBordersMode mode,
+ guint8 paint_opacity)
+{
+ StThemeNode *node = state->node;
+ float width, height;
+ guint border_width[4];
+ guint border_radius[4];
+ guint max_border_radius = 0;
+ guint max_width_radius[4];
+ int corner_id, side_id;
+ ClutterColor border_color;
+ guint8 alpha;
+ gboolean corners_are_transparent;
+
+ width = box->x2 - box->x1;
+ height = box->y2 - box->y1;
+
+ /* TODO - support non-uniform border colors */
+ get_arbitrary_border_color (node, &border_color);
+
+ for (side_id = 0; side_id < 4; side_id++)
+ border_width[side_id] = st_theme_node_get_border_width(node, side_id);
+
+ st_theme_node_reduce_border_radius (node, width, height, border_radius);
+
+ for (corner_id = 0; corner_id < 4; corner_id++)
+ {
+ guint border_width_1, border_width_2;
+
+ st_theme_node_get_corner_border_widths (node, corner_id,
+ &border_width_1, &border_width_2);
+
+ if (border_radius[corner_id] > max_border_radius)
+ max_border_radius = border_radius[corner_id];
+ max_width_radius[corner_id] = MAX(MAX(border_width_1, border_width_2),
+ border_radius[corner_id]);
+ }
+
+ /* borders */
+ if (border_width[ST_SIDE_TOP] > 0 ||
+ border_width[ST_SIDE_RIGHT] > 0 ||
+ border_width[ST_SIDE_BOTTOM] > 0 ||
+ border_width[ST_SIDE_LEFT] > 0)
+ {
+ ClutterColor effective_border;
+ gboolean skip_corner_1, skip_corner_2;
+ float rects[16];
+
+ over (&border_color, &node->background_color, &effective_border);
+ alpha = paint_opacity * effective_border.alpha / 255;
+
+ if (alpha > 0)
+ {
+ st_theme_node_ensure_color_pipeline (node);
+ cogl_pipeline_set_color4ub (node->color_pipeline,
+ effective_border.red * alpha / 255,
+ effective_border.green * alpha / 255,
+ effective_border.blue * alpha / 255,
+ alpha);
+
+ /* NORTH */
+ skip_corner_1 = border_radius[ST_CORNER_TOPLEFT] > 0;
+ skip_corner_2 = border_radius[ST_CORNER_TOPRIGHT] > 0;
+
+ rects[0] = skip_corner_1 ? max_width_radius[ST_CORNER_TOPLEFT] : 0;
+ rects[1] = 0;
+ rects[2] = skip_corner_2 ? width - max_width_radius[ST_CORNER_TOPRIGHT] : width;
+ rects[3] = border_width[ST_SIDE_TOP];
+
+ /* EAST */
+ skip_corner_1 = border_radius[ST_CORNER_TOPRIGHT] > 0;
+ skip_corner_2 = border_radius[ST_CORNER_BOTTOMRIGHT] > 0;
+
+ rects[4] = width - border_width[ST_SIDE_RIGHT];
+ rects[5] = skip_corner_1 ? max_width_radius[ST_CORNER_TOPRIGHT]
+ : border_width[ST_SIDE_TOP];
+ rects[6] = width;
+ rects[7] = skip_corner_2 ? height - max_width_radius[ST_CORNER_BOTTOMRIGHT]
+ : height - border_width[ST_SIDE_BOTTOM];
+
+ /* SOUTH */
+ skip_corner_1 = border_radius[ST_CORNER_BOTTOMLEFT] > 0;
+ skip_corner_2 = border_radius[ST_CORNER_BOTTOMRIGHT] > 0;
+
+ rects[8] = skip_corner_1 ? max_width_radius[ST_CORNER_BOTTOMLEFT] : 0;
+ rects[9] = height - border_width[ST_SIDE_BOTTOM];
+ rects[10] = skip_corner_2 ? width - max_width_radius[ST_CORNER_BOTTOMRIGHT]
+ : width;
+ rects[11] = height;
+
+ /* WEST */
+ skip_corner_1 = border_radius[ST_CORNER_TOPLEFT] > 0;
+ skip_corner_2 = border_radius[ST_CORNER_BOTTOMLEFT] > 0;
+
+ rects[12] = 0;
+ rects[13] = skip_corner_1 ? max_width_radius[ST_CORNER_TOPLEFT]
+ : border_width[ST_SIDE_TOP];
+ rects[14] = border_width[ST_SIDE_LEFT];
+ rects[15] = skip_corner_2 ? height - max_width_radius[ST_CORNER_BOTTOMLEFT]
+ : height - border_width[ST_SIDE_BOTTOM];
+
+ cogl_framebuffer_draw_rectangles (framebuffer,
+ node->color_pipeline,
+ rects, 4);
+ }
+ }
+
+ corners_are_transparent = mode == ST_PAINT_BORDERS_MODE_COLOR &&
+ node->background_color.alpha == 0 &&
+ node->border_color[0].alpha == 0;
+
+ /* corners */
+ if (max_border_radius > 0 && paint_opacity > 0 && !corners_are_transparent)
+ {
+ for (corner_id = 0; corner_id < 4; corner_id++)
+ {
+ if (state->corner_material[corner_id] == NULL)
+ continue;
+
+ cogl_pipeline_set_color4ub (state->corner_material[corner_id],
+ paint_opacity, paint_opacity,
+ paint_opacity, paint_opacity);
+
+ switch (corner_id)
+ {
+ case ST_CORNER_TOPLEFT:
+ cogl_framebuffer_draw_textured_rectangle (framebuffer,
+ state->corner_material[corner_id], 0, 0,
+ max_width_radius[ST_CORNER_TOPLEFT], max_width_radius[ST_CORNER_TOPLEFT],
+ 0, 0, 0.5, 0.5);
+ break;
+ case ST_CORNER_TOPRIGHT:
+ cogl_framebuffer_draw_textured_rectangle (framebuffer,
+ state->corner_material[corner_id],
+ width - max_width_radius[ST_CORNER_TOPRIGHT], 0,
+ width, max_width_radius[ST_CORNER_TOPRIGHT],
+ 0.5, 0, 1, 0.5);
+ break;
+ case ST_CORNER_BOTTOMRIGHT:
+ cogl_framebuffer_draw_textured_rectangle (framebuffer,
+ state->corner_material[corner_id],
+ width - max_width_radius[ST_CORNER_BOTTOMRIGHT],
+ height - max_width_radius[ST_CORNER_BOTTOMRIGHT],
+ width, height,
+ 0.5, 0.5, 1, 1);
+ break;
+ case ST_CORNER_BOTTOMLEFT:
+ cogl_framebuffer_draw_textured_rectangle (framebuffer,
+ state->corner_material[corner_id],
+ 0, height - max_width_radius[ST_CORNER_BOTTOMLEFT],
+ max_width_radius[ST_CORNER_BOTTOMLEFT], height,
+ 0, 0.5, 0.5, 1);
+ break;
+ default:
+ g_assert_not_reached();
+ break;
+ }
+ }
+ }
+
+ /* background color */
+ alpha = mode == ST_PAINT_BORDERS_MODE_SILHOUETTE ?
+ 255 :
+ paint_opacity * node->background_color.alpha / 255;
+ if (alpha > 0)
+ {
+ st_theme_node_ensure_color_pipeline (node);
+ cogl_pipeline_set_color4ub (node->color_pipeline,
+ node->background_color.red * alpha / 255,
+ node->background_color.green * alpha / 255,
+ node->background_color.blue * alpha / 255,
+ alpha);
+
+ /* We add padding to each corner, so that all corners end up as if they
+ * had a border-radius of max_border_radius, which allows us to treat
+ * corners as uniform further on.
+ */
+ for (corner_id = 0; corner_id < 4; corner_id++)
+ {
+ float verts[8];
+ int n_rects;
+
+ /* corner texture does not need padding */
+ if (max_border_radius == border_radius[corner_id])
+ continue;
+
+ n_rects = border_radius[corner_id] == 0 ? 1 : 2;
+
+ switch (corner_id)
+ {
+ case ST_CORNER_TOPLEFT:
+ verts[0] = border_width[ST_SIDE_LEFT];
+ verts[1] = MAX(border_radius[corner_id],
+ border_width[ST_SIDE_TOP]);
+ verts[2] = max_border_radius;
+ verts[3] = max_border_radius;
+ if (n_rects == 2)
+ {
+ verts[4] = MAX(border_radius[corner_id],
+ border_width[ST_SIDE_LEFT]);
+ verts[5] = border_width[ST_SIDE_TOP];
+ verts[6] = max_border_radius;
+ verts[7] = MAX(border_radius[corner_id],
+ border_width[ST_SIDE_TOP]);
+ }
+ break;
+ case ST_CORNER_TOPRIGHT:
+ verts[0] = width - max_border_radius;
+ verts[1] = MAX(border_radius[corner_id],
+ border_width[ST_SIDE_TOP]);
+ verts[2] = width - border_width[ST_SIDE_RIGHT];
+ verts[3] = max_border_radius;
+ if (n_rects == 2)
+ {
+ verts[4] = width - max_border_radius;
+ verts[5] = border_width[ST_SIDE_TOP];
+ verts[6] = width - MAX(border_radius[corner_id],
+ border_width[ST_SIDE_RIGHT]);
+ verts[7] = MAX(border_radius[corner_id],
+ border_width[ST_SIDE_TOP]);
+ }
+ break;
+ case ST_CORNER_BOTTOMRIGHT:
+ verts[0] = width - max_border_radius;
+ verts[1] = height - max_border_radius;
+ verts[2] = width - border_width[ST_SIDE_RIGHT];
+ verts[3] = height - MAX(border_radius[corner_id],
+ border_width[ST_SIDE_BOTTOM]);
+ if (n_rects == 2)
+ {
+ verts[4] = width - max_border_radius;
+ verts[5] = height - MAX(border_radius[corner_id],
+ border_width[ST_SIDE_BOTTOM]);
+ verts[6] = width - MAX(border_radius[corner_id],
+ border_width[ST_SIDE_RIGHT]);
+ verts[7] = height - border_width[ST_SIDE_BOTTOM];
+ }
+ break;
+ case ST_CORNER_BOTTOMLEFT:
+ verts[0] = border_width[ST_SIDE_LEFT];
+ verts[1] = height - max_border_radius;
+ verts[2] = max_border_radius;
+ verts[3] = height - MAX(border_radius[corner_id],
+ border_width[ST_SIDE_BOTTOM]);
+ if (n_rects == 2)
+ {
+ verts[4] = MAX(border_radius[corner_id],
+ border_width[ST_SIDE_LEFT]);
+ verts[5] = height - MAX(border_radius[corner_id],
+ border_width[ST_SIDE_BOTTOM]);
+ verts[6] = max_border_radius;
+ verts[7] = height - border_width[ST_SIDE_BOTTOM];
+ }
+ break;
+ default:
+ g_assert_not_reached();
+ break;
+ }
+ cogl_framebuffer_draw_rectangles (framebuffer,
+ node->color_pipeline,
+ verts, n_rects);
+ }
+
+ /* Once we've drawn the borders and corners, if the corners are bigger
+ * then the border width, the remaining area is shaped like
+ *
+ * ########
+ * ##########
+ * ##########
+ * ########
+ *
+ * We draw it in at most 3 pieces - first the top and bottom if
+ * necessary, then the main rectangle
+ */
+ if (max_border_radius > border_width[ST_SIDE_TOP])
+ cogl_framebuffer_draw_rectangle (framebuffer, node->color_pipeline,
+ MAX(max_border_radius, border_width[ST_SIDE_LEFT]),
+ border_width[ST_SIDE_TOP],
+ width - MAX(max_border_radius, border_width[ST_SIDE_RIGHT]),
+ max_border_radius);
+ if (max_border_radius > border_width[ST_SIDE_BOTTOM])
+ cogl_framebuffer_draw_rectangle (framebuffer, node->color_pipeline,
+ MAX(max_border_radius, border_width[ST_SIDE_LEFT]),
+ height - max_border_radius,
+ width - MAX(max_border_radius, border_width[ST_SIDE_RIGHT]),
+ height - border_width[ST_SIDE_BOTTOM]);
+
+ cogl_framebuffer_draw_rectangle (framebuffer, node->color_pipeline,
+ border_width[ST_SIDE_LEFT],
+ MAX(border_width[ST_SIDE_TOP], max_border_radius),
+ width - border_width[ST_SIDE_RIGHT],
+ height - MAX(border_width[ST_SIDE_BOTTOM], max_border_radius));
+ }
+}
+
+static void
+st_theme_node_paint_sliced_shadow (StThemeNodePaintState *state,
+ CoglFramebuffer *framebuffer,
+ const ClutterActorBox *box,
+ guint8 paint_opacity)
+{
+ StThemeNode *node = state->node;
+ guint border_radius[4];
+ CoglColor color;
+ StShadow *box_shadow_spec;
+ gfloat xoffset, yoffset;
+ gfloat width, height;
+ gfloat shadow_width, shadow_height;
+ gfloat xend, yend, top, bottom, left, right;
+ gfloat s_top, s_bottom, s_left, s_right;
+ gfloat shadow_blur_radius, x_spread_factor, y_spread_factor;
+ float rectangles[8 * 9];
+ gint idx;
+ ClutterColor background_color;
+ static const ClutterColor invisible_occluded = {3, 2, 1, 0};
+
+ if (paint_opacity == 0)
+ return;
+
+ st_theme_node_reduce_border_radius (node, box->x2 - box->x1, box->y2 - box->y1, border_radius);
+
+ box_shadow_spec = st_theme_node_get_box_shadow (node);
+
+ /* Compute input & output areas :
+ *
+ * yoffset ----------------------------
+ * | | | |
+ * | | | |
+ * | | | |
+ * top ----------------------------
+ * | | | |
+ * | | | |
+ * | | | |
+ * bottom ----------------------------
+ * | | | |
+ * | | | |
+ * | | | |
+ * yend ----------------------------
+ * xoffset left right xend
+ *
+ * s_top = top in offscreen's coordinates (0.0 - 1.0)
+ * s_bottom = bottom in offscreen's coordinates (0.0 - 1.0)
+ * s_left = left in offscreen's coordinates (0.0 - 1.0)
+ * s_right = right in offscreen's coordinates (0.0 - 1.0)
+ */
+ if (box_shadow_spec->blur == 0)
+ shadow_blur_radius = 0;
+ else
+ shadow_blur_radius = ceilf (1.5 * box_shadow_spec->blur / 2.0) * 2.0;
+
+ shadow_width = state->box_shadow_width + 2 * shadow_blur_radius;
+ shadow_height = state->box_shadow_height + 2 * shadow_blur_radius;
+
+ /* Compute input regions parameters */
+ s_top = shadow_blur_radius + box_shadow_spec->blur +
+ MAX (node->border_radius[ST_CORNER_TOPLEFT],
+ node->border_radius[ST_CORNER_TOPRIGHT]);
+ s_bottom = shadow_blur_radius + box_shadow_spec->blur +
+ MAX (node->border_radius[ST_CORNER_BOTTOMLEFT],
+ node->border_radius[ST_CORNER_BOTTOMRIGHT]);
+ s_left = shadow_blur_radius + box_shadow_spec->blur +
+ MAX (node->border_radius[ST_CORNER_TOPLEFT],
+ node->border_radius[ST_CORNER_BOTTOMLEFT]);
+ s_right = shadow_blur_radius + box_shadow_spec->blur +
+ MAX (node->border_radius[ST_CORNER_TOPRIGHT],
+ node->border_radius[ST_CORNER_BOTTOMRIGHT]);
+
+ /* Compute output regions parameters */
+ xoffset = box->x1 + box_shadow_spec->xoffset - shadow_blur_radius - box_shadow_spec->spread;
+ yoffset = box->y1 + box_shadow_spec->yoffset - shadow_blur_radius - box_shadow_spec->spread;
+ width = box->x2 - box->x1 + 2 * shadow_blur_radius;
+ height = box->y2 - box->y1 + 2 * shadow_blur_radius;
+
+ x_spread_factor = (width + 2 * box_shadow_spec->spread) / width;
+ y_spread_factor = (height + 2 * box_shadow_spec->spread) / height;
+
+ width += 2 * box_shadow_spec->spread;
+ height += 2 * box_shadow_spec->spread;
+
+ xend = xoffset + width;
+ yend = yoffset + height;
+
+ top = s_top * y_spread_factor;
+ bottom = s_bottom * y_spread_factor;
+ left = s_left * x_spread_factor;
+ right = s_right * x_spread_factor;
+
+ bottom = height - bottom;
+ right = width - right;
+
+ /* Final adjustments */
+ s_top /= shadow_height;
+ s_bottom /= shadow_height;
+ s_left /= shadow_width;
+ s_right /= shadow_width;
+
+ s_bottom = 1.0 - s_bottom;
+ s_right = 1.0 - s_right;
+
+ top += yoffset;
+ bottom += yoffset;
+ left += xoffset;
+ right += xoffset;
+
+ /* Setup pipeline */
+ cogl_color_init_from_4ub (&color,
+ box_shadow_spec->color.red * paint_opacity / 255,
+ box_shadow_spec->color.green * paint_opacity / 255,
+ box_shadow_spec->color.blue * paint_opacity / 255,
+ box_shadow_spec->color.alpha * paint_opacity / 255);
+ cogl_color_premultiply (&color);
+
+ cogl_pipeline_set_layer_combine_constant (state->box_shadow_pipeline, 0, &color);
+
+ idx = 0;
+
+ if (yoffset < top)
+ {
+ if (xoffset < left)
+ {
+ /* Top left corner */
+ rectangles[idx++] = xoffset;
+ rectangles[idx++] = yoffset;
+ rectangles[idx++] = left;
+ rectangles[idx++] = top;
+
+ rectangles[idx++] = 0;
+ rectangles[idx++] = 0;
+ rectangles[idx++] = s_left;
+ rectangles[idx++] = s_top;
+ }
+
+ /* Top middle */
+ rectangles[idx++] = left;
+ rectangles[idx++] = yoffset;
+ rectangles[idx++] = right;
+ rectangles[idx++] = top;
+
+ rectangles[idx++] = s_left;
+ rectangles[idx++] = 0;
+ rectangles[idx++] = s_right;
+ rectangles[idx++] = s_top;
+
+ if (xend > right)
+ {
+ /* Top right corner */
+ rectangles[idx++] = right;
+ rectangles[idx++] = yoffset;
+ rectangles[idx++] = xend;
+ rectangles[idx++] = top;
+
+ rectangles[idx++] = s_right;
+ rectangles[idx++] = 0;
+ rectangles[idx++] = 1;
+ rectangles[idx++] = s_top;
+ }
+ }
+
+ if (xoffset < left)
+ {
+ /* Left middle */
+ rectangles[idx++] = xoffset;
+ rectangles[idx++] = top;
+ rectangles[idx++] = left;
+ rectangles[idx++] = bottom;
+
+ rectangles[idx++] = 0;
+ rectangles[idx++] = s_top;
+ rectangles[idx++] = s_left;
+ rectangles[idx++] = s_bottom;
+ }
+
+ /* Center middle is not definitely occluded? */
+ st_theme_node_get_background_color (node, &background_color);
+ if (!clutter_color_equal (&background_color, &invisible_occluded) ||
+ paint_opacity < 255 ||
+ xoffset > shadow_blur_radius || left < 0 ||
+ yoffset > shadow_blur_radius || top < 0)
+ {
+ rectangles[idx++] = left;
+ rectangles[idx++] = top;
+ rectangles[idx++] = right;
+ rectangles[idx++] = bottom;
+
+ rectangles[idx++] = s_left;
+ rectangles[idx++] = s_top;
+ rectangles[idx++] = s_right;
+ rectangles[idx++] = s_bottom;
+ }
+
+ if (xend > right)
+ {
+ /* Right middle */
+ rectangles[idx++] = right;
+ rectangles[idx++] = top;
+ rectangles[idx++] = xend;
+ rectangles[idx++] = bottom;
+
+ rectangles[idx++] = s_right;
+ rectangles[idx++] = s_top;
+ rectangles[idx++] = 1;
+ rectangles[idx++] = s_bottom;
+ }
+
+ if (yend > bottom)
+ {
+ if (xoffset < left)
+ {
+ /* Bottom left corner */
+ rectangles[idx++] = xoffset;
+ rectangles[idx++] = bottom;
+ rectangles[idx++] = left;
+ rectangles[idx++] = yend;
+
+ rectangles[idx++] = 0;
+ rectangles[idx++] = s_bottom;
+ rectangles[idx++] = s_left;
+ rectangles[idx++] = 1;
+ }
+
+ /* Bottom middle */
+ rectangles[idx++] = left;
+ rectangles[idx++] = bottom;
+ rectangles[idx++] = right;
+ rectangles[idx++] = yend;
+
+ rectangles[idx++] = s_left;
+ rectangles[idx++] = s_bottom;
+ rectangles[idx++] = s_right;
+ rectangles[idx++] = 1;
+
+ if (xend > right)
+ {
+ /* Bottom right corner */
+ rectangles[idx++] = right;
+ rectangles[idx++] = bottom;
+ rectangles[idx++] = xend;
+ rectangles[idx++] = yend;
+
+ rectangles[idx++] = s_right;
+ rectangles[idx++] = s_bottom;
+ rectangles[idx++] = 1;
+ rectangles[idx++] = 1;
+ }
+ }
+
+ cogl_framebuffer_draw_textured_rectangles (framebuffer, state->box_shadow_pipeline,
+ rectangles, idx / 8);
+
+#if 0
+ /* Visual feedback on shadow's 9-slice and original offscreen buffer,
+ for debug purposes */
+ cogl_framebuffer_draw_rectangle (framebuffer, state->box_shadow_pipeline,
+ xend, yoffset, xend + shadow_width, yoffset + shadow_height);
+
+ st_theme_node_ensure_color_pipeline (node);
+ cogl_pipeline_set_color4ub (node->color_pipeline, 0xff, 0x0, 0x0, 0xff);
+
+ cogl_framebuffer_draw_rectangle (framebuffer, node->color_pipeline,
+ xoffset, top, xend, top + 1);
+ cogl_framebuffer_draw_rectangle (framebuffer, node->color_pipeline,
+ xoffset, bottom, xend, bottom + 1);
+ cogl_framebuffer_draw_rectangle (framebuffer, node->color_pipeline,
+ left, yoffset, left + 1, yend);
+ cogl_framebuffer_draw_rectangle (framebuffer, node->color_pipeline,
+ right, yoffset, right + 1, yend);
+
+ cogl_framebuffer_draw_rectangle (framebuffer, node->color_pipeline,
+ xend, yoffset, xend + shadow_width, yoffset + 1);
+ cogl_framebuffer_draw_rectangle (framebuffer, node->color_pipeline,
+ xend, yoffset + shadow_height, xend + shadow_width, yoffset + shadow_height + 1);
+ cogl_framebuffer_draw_rectangle (framebuffer, node->color_pipeline,
+ xend, yoffset, xend + 1, yoffset + shadow_height);
+ cogl_framebuffer_draw_rectangle (framebuffer, node->color_pipeline,
+ xend + shadow_width, yoffset, xend + shadow_width + 1, yoffset + shadow_height);
+
+ s_top *= shadow_height;
+ s_bottom *= shadow_height;
+ s_left *= shadow_width;
+ s_right *= shadow_width;
+
+ cogl_framebuffer_draw_rectangle (framebuffer, node->color_pipeline,
+ xend, yoffset + s_top, xend + shadow_width, yoffset + s_top + 1);
+ cogl_framebuffer_draw_rectangle (framebuffer, node->color_pipeline,
+ xend, yoffset + s_bottom, xend + shadow_width, yoffset + s_bottom + 1);
+ cogl_framebuffer_draw_rectangle (framebuffer, node->color_pipeline,
+ xend + s_left, yoffset, xend + s_left + 1, yoffset + shadow_height);
+ cogl_framebuffer_draw_rectangle (framebuffer, node->color_pipeline,
+ xend + s_right, yoffset, xend + s_right + 1, yoffset + shadow_height);
+
+#endif
+}
+
+static void
+st_theme_node_prerender_shadow (StThemeNodePaintState *state)
+{
+ StThemeNode *node = state->node;
+ CoglContext *ctx;
+ int fb_width, fb_height;
+ CoglTexture *buffer;
+ CoglOffscreen *offscreen = NULL;
+ CoglFramebuffer *framebuffer;
+ GError *error = NULL;
+
+ ctx = clutter_backend_get_cogl_context (clutter_get_default_backend ());
+
+ /* Render offscreen */
+ fb_width = ceilf (state->box_shadow_width * state->resource_scale);
+ fb_height = ceilf (state->box_shadow_height * state->resource_scale);
+ buffer = COGL_TEXTURE (cogl_texture_2d_new_with_size (ctx, fb_width, fb_height));
+ if (buffer == NULL)
+ return;
+
+ offscreen = cogl_offscreen_new_with_texture (buffer);
+ framebuffer = COGL_FRAMEBUFFER (offscreen);
+
+ if (cogl_framebuffer_allocate (framebuffer, &error))
+ {
+ ClutterActorBox box = { 0, 0, state->box_shadow_width, state->box_shadow_height};
+
+ cogl_framebuffer_orthographic (framebuffer, 0, 0,
+ fb_width, fb_height, 0, 1.0);
+ cogl_framebuffer_scale (framebuffer,
+ state->resource_scale,
+ state->resource_scale, 1);
+ cogl_framebuffer_clear4f (framebuffer, COGL_BUFFER_BIT_COLOR, 0, 0, 0, 0);
+
+ st_theme_node_paint_borders (state, framebuffer, &box, ST_PAINT_BORDERS_MODE_SILHOUETTE, 0xFF);
+
+ state->box_shadow_pipeline = _st_create_shadow_pipeline (st_theme_node_get_box_shadow (node),
+ buffer, state->resource_scale);
+ }
+
+ g_clear_error (&error);
+ g_clear_object (&offscreen);
+ cogl_clear_object (&buffer);
+}
+
+static void
+st_theme_node_compute_maximum_borders (StThemeNodePaintState *state)
+{
+ int max_borders[4], center_radius;
+ StThemeNode * node = state->node;
+
+ /* Compute maximum borders sizes */
+ max_borders[ST_SIDE_TOP] = MAX (node->border_radius[ST_CORNER_TOPLEFT],
+ node->border_radius[ST_CORNER_TOPRIGHT]);
+ max_borders[ST_SIDE_BOTTOM] = MAX (node->border_radius[ST_CORNER_BOTTOMLEFT],
+ node->border_radius[ST_CORNER_BOTTOMRIGHT]);
+ max_borders[ST_SIDE_LEFT] = MAX (node->border_radius[ST_CORNER_TOPLEFT],
+ node->border_radius[ST_CORNER_BOTTOMLEFT]);
+ max_borders[ST_SIDE_RIGHT] = MAX (node->border_radius[ST_CORNER_TOPRIGHT],
+ node->border_radius[ST_CORNER_BOTTOMRIGHT]);
+
+ center_radius = (node->box_shadow->blur > 0) ? (2 * node->box_shadow->blur + 1) : 1;
+
+ node->box_shadow_min_width = max_borders[ST_SIDE_LEFT] + max_borders[ST_SIDE_RIGHT] + center_radius;
+ node->box_shadow_min_height = max_borders[ST_SIDE_TOP] + max_borders[ST_SIDE_BOTTOM] + center_radius;
+ if (state->alloc_width < node->box_shadow_min_width ||
+ state->alloc_height < node->box_shadow_min_height)
+ {
+ state->box_shadow_width = state->alloc_width;
+ state->box_shadow_height = state->alloc_height;
+ }
+ else
+ {
+ state->box_shadow_width = node->box_shadow_min_width;
+ state->box_shadow_height = node->box_shadow_min_height;
+ }
+}
+
+static void
+st_theme_node_paint_sliced_border_image (StThemeNode *node,
+ CoglFramebuffer *framebuffer,
+ float width,
+ float height,
+ guint8 paint_opacity)
+{
+ gfloat ex, ey;
+ gfloat tx1, ty1, tx2, ty2;
+ gint border_left, border_right, border_top, border_bottom;
+ float img_width, img_height;
+ StBorderImage *border_image;
+ CoglPipeline *pipeline;
+
+ border_image = st_theme_node_get_border_image (node);
+ g_assert (border_image != NULL);
+
+ st_border_image_get_borders (border_image,
+ &border_left, &border_right, &border_top, &border_bottom);
+
+ img_width = cogl_texture_get_width (node->border_slices_texture);
+ img_height = cogl_texture_get_height (node->border_slices_texture);
+
+ tx1 = border_left / img_width;
+ tx2 = (img_width - border_right) / img_width;
+ ty1 = border_top / img_height;
+ ty2 = (img_height - border_bottom) / img_height;
+
+ ex = width - border_right;
+ if (ex < 0)
+ ex = border_right; /* FIXME ? */
+
+ ey = height - border_bottom;
+ if (ey < 0)
+ ey = border_bottom; /* FIXME ? */
+
+ pipeline = node->border_slices_pipeline;
+ cogl_pipeline_set_color4ub (pipeline,
+ paint_opacity, paint_opacity, paint_opacity, paint_opacity);
+
+ {
+ float rectangles[] =
+ {
+ /* top left corner */
+ 0, 0, border_left, border_top,
+ 0.0, 0.0,
+ tx1, ty1,
+
+ /* top middle */
+ border_left, 0, ex, border_top,
+ tx1, 0.0,
+ tx2, ty1,
+
+ /* top right */
+ ex, 0, width, border_top,
+ tx2, 0.0,
+ 1.0, ty1,
+
+ /* mid left */
+ 0, border_top, border_left, ey,
+ 0.0, ty1,
+ tx1, ty2,
+
+ /* center */
+ border_left, border_top, ex, ey,
+ tx1, ty1,
+ tx2, ty2,
+
+ /* mid right */
+ ex, border_top, width, ey,
+ tx2, ty1,
+ 1.0, ty2,
+
+ /* bottom left */
+ 0, ey, border_left, height,
+ 0.0, ty2,
+ tx1, 1.0,
+
+ /* bottom center */
+ border_left, ey, ex, height,
+ tx1, ty2,
+ tx2, 1.0,
+
+ /* bottom right */
+ ex, ey, width, height,
+ tx2, ty2,
+ 1.0, 1.0
+ };
+
+ cogl_framebuffer_draw_textured_rectangles (framebuffer, pipeline, rectangles, 9);
+ }
+}
+
+static void
+st_theme_node_paint_outline (StThemeNode *node,
+ CoglFramebuffer *framebuffer,
+ const ClutterActorBox *box,
+ guint8 paint_opacity)
+
+{
+ float width, height;
+ int outline_width;
+ float rects[16];
+ ClutterColor outline_color, effective_outline;
+ guint8 alpha;
+
+ width = box->x2 - box->x1;
+ height = box->y2 - box->y1;
+
+ outline_width = st_theme_node_get_outline_width (node);
+ if (outline_width == 0)
+ return;
+
+ st_theme_node_get_outline_color (node, &outline_color);
+ over (&outline_color, &node->background_color, &effective_outline);
+
+ alpha = paint_opacity * outline_color.alpha / 255;
+
+ st_theme_node_ensure_color_pipeline (node);
+ cogl_pipeline_set_color4ub (node->color_pipeline,
+ effective_outline.red * alpha / 255,
+ effective_outline.green * alpha / 255,
+ effective_outline.blue * alpha / 255,
+ alpha);
+
+ /* The outline is drawn just outside the border, which means just
+ * outside the allocation box. This means that in some situations
+ * involving clip_to_allocation or the screen edges, you won't be
+ * able to see the outline. In practice, it works well enough.
+ */
+
+ /* NORTH */
+ rects[0] = -outline_width;
+ rects[1] = -outline_width;
+ rects[2] = width + outline_width;
+ rects[3] = 0;
+
+ /* EAST */
+ rects[4] = width;
+ rects[5] = 0;
+ rects[6] = width + outline_width;
+ rects[7] = height;
+
+ /* SOUTH */
+ rects[8] = -outline_width;
+ rects[9] = height;
+ rects[10] = width + outline_width;
+ rects[11] = height + outline_width;
+
+ /* WEST */
+ rects[12] = -outline_width;
+ rects[13] = 0;
+ rects[14] = 0;
+ rects[15] = height;
+
+ cogl_framebuffer_draw_rectangles (framebuffer, node->color_pipeline, rects, 4);
+}
+
+static gboolean
+st_theme_node_needs_new_box_shadow_for_size (StThemeNodePaintState *state,
+ StThemeNode *node,
+ float width,
+ float height,
+ float resource_scale)
+{
+ if (!node->rendered_once)
+ return TRUE;
+
+ /* The resource scale changed, so need to recompute a new box-shadow */
+ if (fabsf (state->resource_scale - resource_scale) > FLT_EPSILON)
+ return TRUE;
+
+ /* The allocation hasn't changed, no need to recompute a new
+ box-shadow. */
+ if (state->alloc_width == width &&
+ state->alloc_height == height)
+ return FALSE;
+
+ /* If there is no shadow, no need to recompute a new box-shadow. */
+ if (node->box_shadow_min_width == 0 ||
+ node->box_shadow_min_height == 0)
+ return FALSE;
+
+ /* If the new size is inferior to the box-shadow minimum size (we
+ already know the size has changed), we need to recompute the
+ box-shadow. */
+ if (width < node->box_shadow_min_width ||
+ height < node->box_shadow_min_height)
+ return TRUE;
+
+ /* Now checking whether the size of the node has crossed the minimum
+ box-shadow size boundary, from below to above the minimum size .
+ If that's the case, we need to recompute the box-shadow */
+ if (state->alloc_width < node->box_shadow_min_width ||
+ state->alloc_height < node->box_shadow_min_height)
+ return TRUE;
+
+ return FALSE;
+}
+
+void
+st_theme_node_paint (StThemeNode *node,
+ StThemeNodePaintState *state,
+ CoglFramebuffer *framebuffer,
+ const ClutterActorBox *box,
+ guint8 paint_opacity,
+ float resource_scale)
+{
+ float width, height;
+ ClutterActorBox allocation;
+
+ /* Some things take an ActorBox, some things just width/height */
+ width = box->x2 - box->x1;
+ height = box->y2 - box->y1;
+ allocation.x1 = allocation.y1 = 0;
+ allocation.x2 = width;
+ allocation.y2 = height;
+
+ if (width <= 0 || height <= 0 || resource_scale <= 0.0f)
+ return;
+
+ /* Check whether we need to recreate the textures of the paint
+ * state, either because :
+ * 1) the theme node associated to the paint state has changed
+ * 2) the allocation size change requires recreating textures
+ */
+ if (state->node != node ||
+ st_theme_node_needs_new_box_shadow_for_size (state, node, width, height,
+ resource_scale))
+ {
+ /* If we had the ability to cache textures on the node, then we
+ can just copy them over to the paint state and avoid all
+ rendering. We end up sharing textures a cross different
+ widgets. */
+ if (node->rendered_once && node->cached_textures &&
+ width >= node->box_shadow_min_width && height >= node->box_shadow_min_height &&
+ fabsf (resource_scale - state->resource_scale) < FLT_EPSILON)
+ st_theme_node_paint_state_copy (state, &node->cached_state);
+ else
+ st_theme_node_render_resources (state, node, width, height, resource_scale);
+
+ node->rendered_once = TRUE;
+ }
+ else if (state->alloc_width != width || state->alloc_height != height ||
+ fabsf (state->resource_scale - resource_scale) > FLT_EPSILON)
+ st_theme_node_update_resources (state, node, width, height, resource_scale);
+
+ /* Rough notes about the relationship of borders and backgrounds in CSS3;
+ * see http://www.w3.org/TR/css3-background/ for more accurate details.
+ *
+ * - Things are drawn in 4 layers, from the bottom:
+ * Background color
+ * Background image
+ * Border color or border image
+ * Content
+ * - The background color, gradient and image extend to and are clipped by
+ * the edge of the border area, so will be rounded if the border is
+ * rounded. (CSS3 background-clip property modifies this)
+ * - The border image replaces what would normally be drawn by the border
+ * - The border image is not clipped by a rounded border-radius
+ * - The border radius rounds the background even if the border is
+ * zero width or a border image is being used.
+ *
+ * Deviations from the above as implemented here:
+ * - The combination of border image and a non-zero border radius is
+ * not supported; the background color will be drawn with square
+ * corners.
+ * - The background image is drawn above the border color, not below it.
+ * - We clip the background image to the inside edges of the border
+ * instead of the outside edges of the border (but position the image
+ * such that it's aligned to the outside edges)
+ */
+
+ if (state->box_shadow_pipeline)
+ {
+ if (state->alloc_width < node->box_shadow_min_width ||
+ state->alloc_height < node->box_shadow_min_height)
+ _st_paint_shadow_with_opacity (node->box_shadow,
+ framebuffer,
+ state->box_shadow_pipeline,
+ &allocation,
+ paint_opacity);
+ else
+ st_theme_node_paint_sliced_shadow (state,
+ framebuffer,
+ &allocation,
+ paint_opacity);
+ }
+
+ if (state->prerendered_pipeline != NULL ||
+ st_theme_node_load_border_image (node, resource_scale))
+ {
+ if (state->prerendered_pipeline != NULL)
+ {
+ ClutterActorBox paint_box;
+
+ st_theme_node_get_background_paint_box (node,
+ &allocation,
+ &paint_box);
+
+ paint_material_with_opacity (state->prerendered_pipeline,
+ framebuffer,
+ &paint_box,
+ NULL,
+ paint_opacity);
+ }
+
+ if (node->border_slices_pipeline != NULL)
+ st_theme_node_paint_sliced_border_image (node, framebuffer, width, height, paint_opacity);
+ }
+ else
+ {
+ st_theme_node_paint_borders (state, framebuffer, box, ST_PAINT_BORDERS_MODE_COLOR, paint_opacity);
+ }
+
+ st_theme_node_paint_outline (node, framebuffer, box, paint_opacity);
+
+ if (state->prerendered_pipeline == NULL &&
+ st_theme_node_load_background_image (node, resource_scale))
+ {
+ ClutterActorBox background_box;
+ ClutterActorBox texture_coords;
+ gboolean has_visible_outline;
+
+ /* If the node doesn't have an opaque or repeating background or
+ * a border then we let its background image shadows leak out,
+ * but otherwise we clip it.
+ */
+ has_visible_outline = st_theme_node_has_visible_outline (node);
+
+ get_background_position (node, &allocation, resource_scale,
+ &background_box, &texture_coords);
+
+ if (has_visible_outline || node->background_repeat)
+ cogl_framebuffer_push_rectangle_clip (framebuffer,
+ allocation.x1, allocation.y1,
+ allocation.x2, allocation.y2);
+
+ /* CSS based drop shadows
+ *
+ * Drop shadows in ST are modelled after the CSS3 box-shadow property;
+ * see http://www.css3.info/preview/box-shadow/ for a detailed description.
+ *
+ * While the syntax of the property is mostly identical - we do not support
+ * multiple shadows and allow for a more liberal placement of the color
+ * parameter - its interpretation defers significantly in that the shadow's
+ * shape is not determined by the bounding box, but by the CSS background
+ * image. The drop shadows are allowed to escape the nodes allocation if
+ * there is nothing (like a border, or the edge of the background color)
+ * to logically confine it.
+ */
+ if (node->background_shadow_pipeline != NULL)
+ _st_paint_shadow_with_opacity (node->background_image_shadow,
+ framebuffer,
+ node->background_shadow_pipeline,
+ &background_box,
+ paint_opacity);
+
+ paint_material_with_opacity (node->background_pipeline,
+ framebuffer,
+ &background_box,
+ &texture_coords,
+ paint_opacity);
+
+ if (has_visible_outline || node->background_repeat)
+ cogl_framebuffer_pop_clip (framebuffer);
+ }
+}
+
+static void
+st_theme_node_paint_state_node_free_internal (StThemeNodePaintState *state,
+ gboolean unref_node)
+{
+ int corner_id;
+
+ cogl_clear_object (&state->prerendered_texture);
+ cogl_clear_object (&state->prerendered_pipeline);
+ cogl_clear_object (&state->box_shadow_pipeline);
+
+ for (corner_id = 0; corner_id < 4; corner_id++)
+ cogl_clear_object (&state->corner_material[corner_id]);
+
+ if (unref_node)
+ st_theme_node_paint_state_set_node (state, NULL);
+
+ st_theme_node_paint_state_init (state);
+}
+
+static void
+st_theme_node_paint_state_node_freed (StThemeNodePaintState *state)
+{
+ st_theme_node_paint_state_node_free_internal (state, FALSE);
+}
+
+void
+st_theme_node_paint_state_set_node (StThemeNodePaintState *state, StThemeNode *node)
+{
+ if (state->node)
+ g_object_weak_unref (G_OBJECT (state->node),
+ (GWeakNotify) st_theme_node_paint_state_node_freed,
+ state);
+
+ state->node = node;
+ if (state->node)
+ g_object_weak_ref (G_OBJECT (state->node),
+ (GWeakNotify) st_theme_node_paint_state_node_freed,
+ state);
+}
+
+void
+st_theme_node_paint_state_free (StThemeNodePaintState *state)
+{
+ st_theme_node_paint_state_node_free_internal (state, TRUE);
+}
+
+void
+st_theme_node_paint_state_init (StThemeNodePaintState *state)
+{
+ int corner_id;
+
+ state->alloc_width = 0;
+ state->alloc_height = 0;
+ state->resource_scale = -1;
+ state->node = NULL;
+ state->box_shadow_pipeline = NULL;
+ state->prerendered_texture = NULL;
+ state->prerendered_pipeline = NULL;
+
+ for (corner_id = 0; corner_id < 4; corner_id++)
+ state->corner_material[corner_id] = NULL;
+}
+
+void
+st_theme_node_paint_state_copy (StThemeNodePaintState *state,
+ StThemeNodePaintState *other)
+{
+ int corner_id;
+
+ if (state == other)
+ return;
+
+ st_theme_node_paint_state_free (state);
+
+ st_theme_node_paint_state_set_node (state, other->node);
+
+ state->alloc_width = other->alloc_width;
+ state->alloc_height = other->alloc_height;
+ state->resource_scale = other->resource_scale;
+ state->box_shadow_width = other->box_shadow_width;
+ state->box_shadow_height = other->box_shadow_height;
+
+ if (other->box_shadow_pipeline)
+ state->box_shadow_pipeline = cogl_object_ref (other->box_shadow_pipeline);
+ if (other->prerendered_texture)
+ state->prerendered_texture = cogl_object_ref (other->prerendered_texture);
+ if (other->prerendered_pipeline)
+ state->prerendered_pipeline = cogl_object_ref (other->prerendered_pipeline);
+ for (corner_id = 0; corner_id < 4; corner_id++)
+ if (other->corner_material[corner_id])
+ state->corner_material[corner_id] = cogl_object_ref (other->corner_material[corner_id]);
+}
+
+void
+st_theme_node_paint_state_invalidate (StThemeNodePaintState *state)
+{
+ state->alloc_width = 0;
+ state->alloc_height = 0;
+ state->resource_scale = -1.0f;
+}
+
+gboolean
+st_theme_node_paint_state_invalidate_for_file (StThemeNodePaintState *state,
+ GFile *file)
+{
+ if (state->node != NULL &&
+ st_theme_node_invalidate_resources_for_file (state->node, file))
+ {
+ st_theme_node_paint_state_invalidate (state);
+ return TRUE;
+ }
+
+ return FALSE;
+}
diff --git a/src/st/st-theme-node-private.h b/src/st/st-theme-node-private.h
new file mode 100644
index 0000000..7533482
--- /dev/null
+++ b/src/st/st-theme-node-private.h
@@ -0,0 +1,131 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * st-theme-node-private.h: private structures and functions for StThemeNode
+ *
+ * Copyright 2009, 2010 Red Hat, Inc.
+ * Copyright 2011 Quentin "Sardem FF7" Glidic
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation, either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __ST_THEME_NODE_PRIVATE_H__
+#define __ST_THEME_NODE_PRIVATE_H__
+
+#include <gdk/gdk.h>
+
+#include "st-theme-node.h"
+#include "croco/libcroco.h"
+#include "st-types.h"
+
+G_BEGIN_DECLS
+
+struct _StThemeNode {
+ GObject parent;
+
+ StThemeContext *context;
+ StThemeNode *parent_node;
+ StTheme *theme;
+
+ PangoFontDescription *font_desc;
+
+ ClutterColor background_color;
+ /* If gradient is set, then background_color is the gradient start */
+ StGradientType background_gradient_type;
+ ClutterColor background_gradient_end;
+
+ int background_position_x;
+ int background_position_y;
+
+ StBackgroundSize background_size;
+ gint background_size_w;
+ gint background_size_h;
+
+ ClutterColor foreground_color;
+ ClutterColor border_color[4];
+ ClutterColor outline_color;
+
+ int border_width[4];
+ int border_radius[4];
+ int outline_width;
+ guint padding[4];
+ guint margin[4];
+
+ int width;
+ int height;
+ int min_width;
+ int min_height;
+ int max_width;
+ int max_height;
+
+ int transition_duration;
+
+ GFile *background_image;
+ StBorderImage *border_image;
+ StShadow *box_shadow;
+ StShadow *background_image_shadow;
+ StShadow *text_shadow;
+ StIconColors *icon_colors;
+
+ GType element_type;
+ char *element_id;
+ GStrv element_classes;
+ GStrv pseudo_classes;
+ char *inline_style;
+
+ CRDeclaration **properties;
+ int n_properties;
+
+ /* We hold onto these separately so we can destroy them on finalize */
+ CRDeclaration *inline_properties;
+
+ guint background_position_set : 1;
+ guint background_repeat : 1;
+
+ guint properties_computed : 1;
+ guint geometry_computed : 1;
+ guint background_computed : 1;
+ guint foreground_computed : 1;
+ guint border_image_computed : 1;
+ guint box_shadow_computed : 1;
+ guint background_image_shadow_computed : 1;
+ guint text_shadow_computed : 1;
+ guint link_type : 2;
+ guint rendered_once : 1;
+ guint cached_textures : 1;
+
+ int box_shadow_min_width;
+ int box_shadow_min_height;
+
+ guint stylesheets_changed_id;
+
+ CoglPipeline *border_slices_texture;
+ CoglPipeline *border_slices_pipeline;
+ CoglPipeline *background_texture;
+ CoglPipeline *background_pipeline;
+ CoglPipeline *background_shadow_pipeline;
+ CoglPipeline *color_pipeline;
+
+ StThemeNodePaintState cached_state;
+
+ int cached_scale_factor;
+};
+
+void _st_theme_node_ensure_background (StThemeNode *node);
+void _st_theme_node_ensure_geometry (StThemeNode *node);
+void _st_theme_node_apply_margins (StThemeNode *node,
+ ClutterActor *actor);
+
+G_END_DECLS
+
+#endif /* __ST_THEME_NODE_PRIVATE_H__ */
diff --git a/src/st/st-theme-node-transition.c b/src/st/st-theme-node-transition.c
new file mode 100644
index 0000000..20b1476
--- /dev/null
+++ b/src/st/st-theme-node-transition.c
@@ -0,0 +1,470 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * st-theme-node-transition.c: Theme node transitions for StWidget.
+ *
+ * Copyright 2010 Florian Müllner
+ * Copyright 2010 Adel Gadllah
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation, either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <math.h>
+
+#include "st-theme-node-transition.h"
+
+enum {
+ COMPLETED,
+ NEW_FRAME,
+ LAST_SIGNAL
+};
+
+typedef struct _StThemeNodeTransitionPrivate StThemeNodeTransitionPrivate;
+
+struct _StThemeNodeTransition {
+ GObject parent;
+
+ StThemeNodeTransitionPrivate *priv;
+};
+
+struct _StThemeNodeTransitionPrivate {
+ StThemeNode *old_theme_node;
+ StThemeNode *new_theme_node;
+
+ StThemeNodePaintState old_paint_state;
+ StThemeNodePaintState new_paint_state;
+
+ CoglTexture *old_texture;
+ CoglTexture *new_texture;
+
+ CoglFramebuffer *old_offscreen;
+ CoglFramebuffer *new_offscreen;
+
+ CoglPipeline *material;
+
+ ClutterTimeline *timeline;
+
+ gulong timeline_completed_id;
+ gulong timeline_new_frame_id;
+
+ ClutterActorBox last_allocation;
+ ClutterActorBox offscreen_box;
+
+ gboolean needs_setup;
+};
+
+static guint signals[LAST_SIGNAL] = { 0 };
+
+G_DEFINE_TYPE_WITH_PRIVATE (StThemeNodeTransition, st_theme_node_transition, G_TYPE_OBJECT);
+
+
+static void
+on_timeline_completed (ClutterTimeline *timeline,
+ StThemeNodeTransition *transition)
+{
+ g_signal_emit (transition, signals[COMPLETED], 0);
+}
+
+static void
+on_timeline_new_frame (ClutterTimeline *timeline,
+ gint frame_num,
+ StThemeNodeTransition *transition)
+{
+ g_signal_emit (transition, signals[NEW_FRAME], 0);
+}
+
+StThemeNodeTransition *
+st_theme_node_transition_new (ClutterActor *actor,
+ StThemeNode *from_node,
+ StThemeNode *to_node,
+ StThemeNodePaintState *old_paint_state,
+ unsigned int duration)
+{
+ StThemeNodeTransition *transition;
+ g_return_val_if_fail (ST_IS_THEME_NODE (from_node), NULL);
+ g_return_val_if_fail (ST_IS_THEME_NODE (to_node), NULL);
+
+ duration = st_theme_node_get_transition_duration (to_node);
+
+ transition = g_object_new (ST_TYPE_THEME_NODE_TRANSITION, NULL);
+
+ transition->priv->old_theme_node = g_object_ref (from_node);
+ transition->priv->new_theme_node = g_object_ref (to_node);
+
+ st_theme_node_paint_state_copy (&transition->priv->old_paint_state,
+ old_paint_state);
+
+ transition->priv->timeline = clutter_timeline_new_for_actor (actor, duration);
+
+ transition->priv->timeline_completed_id =
+ g_signal_connect (transition->priv->timeline, "completed",
+ G_CALLBACK (on_timeline_completed), transition);
+ transition->priv->timeline_new_frame_id =
+ g_signal_connect (transition->priv->timeline, "new-frame",
+ G_CALLBACK (on_timeline_new_frame), transition);
+
+ clutter_timeline_set_progress_mode (transition->priv->timeline, CLUTTER_EASE_IN_OUT_QUAD);
+
+ clutter_timeline_start (transition->priv->timeline);
+
+ return transition;
+}
+
+/**
+ * st_theme_node_transition_get_new_paint_state: (skip)
+ *
+ */
+StThemeNodePaintState *
+st_theme_node_transition_get_new_paint_state (StThemeNodeTransition *transition)
+{
+ return &transition->priv->new_paint_state;
+}
+
+void
+st_theme_node_transition_update (StThemeNodeTransition *transition,
+ StThemeNode *new_node)
+{
+ StThemeNodeTransitionPrivate *priv;
+ StThemeNode *old_node;
+ ClutterTimelineDirection direction;
+
+ g_return_if_fail (ST_IS_THEME_NODE_TRANSITION (transition));
+ g_return_if_fail (ST_IS_THEME_NODE (new_node));
+
+ priv = transition->priv;
+ direction = clutter_timeline_get_direction (priv->timeline);
+ old_node = (direction == CLUTTER_TIMELINE_FORWARD) ? priv->old_theme_node
+ : priv->new_theme_node;
+
+ /* If the update is the reversal of the current transition,
+ * we reverse the timeline.
+ * Otherwise, we should initiate a new transition from the
+ * current state to the new one; this is hard to do if the
+ * transition is in an intermediate state, so we just cancel
+ * the ongoing transition in that case.
+ * Note that reversing a timeline before any time elapsed
+ * results in the timeline's time position being set to the
+ * full duration - this is not what we want, so we cancel the
+ * transition as well in that case.
+ */
+ if (st_theme_node_equal (new_node, old_node))
+ {
+ {
+ StThemeNodePaintState tmp;
+
+ st_theme_node_paint_state_init (&tmp);
+ st_theme_node_paint_state_copy (&tmp, &priv->old_paint_state);
+ st_theme_node_paint_state_copy (&priv->old_paint_state, &priv->new_paint_state);
+ st_theme_node_paint_state_copy (&priv->new_paint_state, &tmp);
+ st_theme_node_paint_state_free (&tmp);
+ }
+
+ if (clutter_timeline_get_elapsed_time (priv->timeline) > 0)
+ {
+ if (direction == CLUTTER_TIMELINE_FORWARD)
+ clutter_timeline_set_direction (priv->timeline,
+ CLUTTER_TIMELINE_BACKWARD);
+ else
+ clutter_timeline_set_direction (priv->timeline,
+ CLUTTER_TIMELINE_FORWARD);
+ }
+ else
+ {
+ clutter_timeline_stop (priv->timeline);
+ g_signal_emit (transition, signals[COMPLETED], 0);
+ }
+ }
+ else
+ {
+ if (clutter_timeline_get_elapsed_time (priv->timeline) > 0)
+ {
+ clutter_timeline_stop (priv->timeline);
+ g_signal_emit (transition, signals[COMPLETED], 0);
+ }
+ else
+ {
+ guint new_duration = st_theme_node_get_transition_duration (new_node);
+
+ clutter_timeline_set_duration (priv->timeline, new_duration);
+
+ g_object_unref (priv->new_theme_node);
+ priv->new_theme_node = g_object_ref (new_node);
+
+ st_theme_node_paint_state_invalidate (&priv->new_paint_state);
+ }
+ }
+}
+
+static void
+calculate_offscreen_box (StThemeNodeTransition *transition,
+ const ClutterActorBox *allocation)
+{
+ ClutterActorBox paint_box;
+
+ st_theme_node_transition_get_paint_box (transition,
+ allocation,
+ &paint_box);
+ transition->priv->offscreen_box.x1 = paint_box.x1 - allocation->x1;
+ transition->priv->offscreen_box.y1 = paint_box.y1 - allocation->y1;
+ transition->priv->offscreen_box.x2 = paint_box.x2 - allocation->x1;
+ transition->priv->offscreen_box.y2 = paint_box.y2 - allocation->y1;
+}
+
+void
+st_theme_node_transition_get_paint_box (StThemeNodeTransition *transition,
+ const ClutterActorBox *allocation,
+ ClutterActorBox *paint_box)
+{
+ StThemeNodeTransitionPrivate *priv = transition->priv;
+ ClutterActorBox old_node_box, new_node_box;
+
+ st_theme_node_get_paint_box (priv->old_theme_node,
+ allocation,
+ &old_node_box);
+
+ st_theme_node_get_paint_box (priv->new_theme_node,
+ allocation,
+ &new_node_box);
+
+ paint_box->x1 = MIN (old_node_box.x1, new_node_box.x1);
+ paint_box->y1 = MIN (old_node_box.y1, new_node_box.y1);
+ paint_box->x2 = MAX (old_node_box.x2, new_node_box.x2);
+ paint_box->y2 = MAX (old_node_box.y2, new_node_box.y2);
+}
+
+static gboolean
+setup_framebuffers (StThemeNodeTransition *transition,
+ const ClutterActorBox *allocation,
+ float resource_scale)
+{
+ StThemeNodeTransitionPrivate *priv = transition->priv;
+ CoglContext *ctx;
+ guint width, height;
+ GError *catch_error = NULL;
+
+ /* template material to avoid unnecessary shader compilation */
+ static CoglPipeline *material_template = NULL;
+
+ ctx = clutter_backend_get_cogl_context (clutter_get_default_backend ());
+ width = ceilf ((priv->offscreen_box.x2 - priv->offscreen_box.x1) * resource_scale);
+ height = ceilf ((priv->offscreen_box.y2 - priv->offscreen_box.y1) * resource_scale);
+
+ g_return_val_if_fail (width > 0, FALSE);
+ g_return_val_if_fail (height > 0, FALSE);
+
+ cogl_clear_object (&priv->old_texture);
+ priv->old_texture = COGL_TEXTURE (cogl_texture_2d_new_with_size (ctx, width, height));
+
+ cogl_clear_object (&priv->new_texture);
+ priv->new_texture = COGL_TEXTURE (cogl_texture_2d_new_with_size (ctx, width, height));
+
+ if (priv->old_texture == NULL)
+ return FALSE;
+
+ if (priv->new_texture == NULL)
+ return FALSE;
+
+ g_clear_object (&priv->old_offscreen);
+ priv->old_offscreen = COGL_FRAMEBUFFER (cogl_offscreen_new_with_texture (priv->old_texture));
+ if (!cogl_framebuffer_allocate (priv->old_offscreen, &catch_error))
+ {
+ g_error_free (catch_error);
+ g_clear_object (&priv->old_offscreen);
+ return FALSE;
+ }
+
+ g_clear_object (&priv->new_offscreen);
+ priv->new_offscreen = COGL_FRAMEBUFFER (cogl_offscreen_new_with_texture (priv->new_texture));
+ if (!cogl_framebuffer_allocate (priv->new_offscreen, &catch_error))
+ {
+ g_error_free (catch_error);
+ g_clear_object (&priv->new_offscreen);
+ return FALSE;
+ }
+
+ if (priv->material == NULL)
+ {
+ if (G_UNLIKELY (material_template == NULL))
+ {
+ CoglContext *ctx =
+ clutter_backend_get_cogl_context (clutter_get_default_backend ());
+ material_template = cogl_pipeline_new (ctx);
+
+ cogl_pipeline_set_layer_combine (material_template, 0,
+ "RGBA = REPLACE (TEXTURE)",
+ NULL);
+ cogl_pipeline_set_layer_combine (material_template, 1,
+ "RGBA = INTERPOLATE (PREVIOUS, "
+ "TEXTURE, "
+ "CONSTANT[A])",
+ NULL);
+ cogl_pipeline_set_layer_combine (material_template, 2,
+ "RGBA = MODULATE (PREVIOUS, "
+ "PRIMARY)",
+ NULL);
+ }
+ priv->material = cogl_pipeline_copy (material_template);
+ }
+
+ cogl_pipeline_set_layer_texture (priv->material, 0, priv->new_texture);
+ cogl_pipeline_set_layer_texture (priv->material, 1, priv->old_texture);
+
+ cogl_framebuffer_clear4f (priv->old_offscreen, COGL_BUFFER_BIT_COLOR,
+ 0, 0, 0, 0);
+ cogl_framebuffer_orthographic (priv->old_offscreen,
+ priv->offscreen_box.x1,
+ priv->offscreen_box.y1,
+ priv->offscreen_box.x2,
+ priv->offscreen_box.y2, 0.0, 1.0);
+
+ st_theme_node_paint (priv->old_theme_node, &priv->old_paint_state,
+ priv->old_offscreen, allocation, 255, resource_scale);
+
+ cogl_framebuffer_clear4f (priv->new_offscreen, COGL_BUFFER_BIT_COLOR,
+ 0, 0, 0, 0);
+ cogl_framebuffer_orthographic (priv->new_offscreen,
+ priv->offscreen_box.x1,
+ priv->offscreen_box.y1,
+ priv->offscreen_box.x2,
+ priv->offscreen_box.y2, 0.0, 1.0);
+ st_theme_node_paint (priv->new_theme_node, &priv->new_paint_state,
+ priv->new_offscreen, allocation, 255, resource_scale);
+
+ return TRUE;
+}
+
+void
+st_theme_node_transition_paint (StThemeNodeTransition *transition,
+ CoglFramebuffer *framebuffer,
+ ClutterActorBox *allocation,
+ guint8 paint_opacity,
+ float resource_scale)
+{
+ StThemeNodeTransitionPrivate *priv = transition->priv;
+
+ CoglColor constant;
+ float tex_coords[] = {
+ 0.0, 0.0, 1.0, 1.0,
+ 0.0, 0.0, 1.0, 1.0,
+ };
+
+ g_return_if_fail (ST_IS_THEME_NODE (priv->old_theme_node));
+ g_return_if_fail (ST_IS_THEME_NODE (priv->new_theme_node));
+
+ if (!clutter_actor_box_equal (allocation, &priv->last_allocation))
+ priv->needs_setup = TRUE;
+
+ if (priv->needs_setup)
+ {
+ priv->last_allocation = *allocation;
+
+ calculate_offscreen_box (transition, allocation);
+ priv->needs_setup = clutter_actor_box_get_area (&priv->offscreen_box) == 0 ||
+ !setup_framebuffers (transition, allocation,
+ resource_scale);
+
+ if (priv->needs_setup) /* setting up framebuffers failed */
+ return;
+ }
+
+ cogl_color_init_from_4f (&constant, 0., 0., 0.,
+ clutter_timeline_get_progress (priv->timeline));
+ cogl_pipeline_set_layer_combine_constant (priv->material, 1, &constant);
+
+ cogl_pipeline_set_color4ub (priv->material,
+ paint_opacity, paint_opacity,
+ paint_opacity, paint_opacity);
+
+ cogl_framebuffer_draw_multitextured_rectangle (framebuffer,
+ priv->material,
+ priv->offscreen_box.x1,
+ priv->offscreen_box.y1,
+ priv->offscreen_box.x2,
+ priv->offscreen_box.y2,
+ tex_coords, 8);
+}
+
+static void
+st_theme_node_transition_dispose (GObject *object)
+{
+ StThemeNodeTransitionPrivate *priv = ST_THEME_NODE_TRANSITION (object)->priv;
+
+ g_clear_object (&priv->old_theme_node);
+ g_clear_object (&priv->new_theme_node);
+
+ cogl_clear_object (&priv->old_texture);
+ cogl_clear_object (&priv->new_texture);
+
+ g_clear_object (&priv->old_offscreen);
+ g_clear_object (&priv->new_offscreen);
+
+ cogl_clear_object (&priv->material);
+
+ if (priv->timeline)
+ {
+ g_clear_signal_handler (&priv->timeline_completed_id, priv->timeline);
+ g_clear_signal_handler (&priv->timeline_new_frame_id, priv->timeline);
+
+ g_clear_object (&priv->timeline);
+ }
+
+ priv->timeline_completed_id = 0;
+ priv->timeline_new_frame_id = 0;
+
+ st_theme_node_paint_state_free (&priv->old_paint_state);
+ st_theme_node_paint_state_free (&priv->new_paint_state);
+
+ G_OBJECT_CLASS (st_theme_node_transition_parent_class)->dispose (object);
+}
+
+static void
+st_theme_node_transition_init (StThemeNodeTransition *transition)
+{
+ transition->priv = st_theme_node_transition_get_instance_private (transition);
+
+ transition->priv->old_theme_node = NULL;
+ transition->priv->new_theme_node = NULL;
+
+ transition->priv->old_texture = NULL;
+ transition->priv->new_texture = NULL;
+
+ transition->priv->old_offscreen = NULL;
+ transition->priv->new_offscreen = NULL;
+
+ st_theme_node_paint_state_init (&transition->priv->old_paint_state);
+ st_theme_node_paint_state_init (&transition->priv->new_paint_state);
+
+ transition->priv->needs_setup = TRUE;
+}
+
+static void
+st_theme_node_transition_class_init (StThemeNodeTransitionClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->dispose = st_theme_node_transition_dispose;
+
+ signals[COMPLETED] =
+ g_signal_new ("completed",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ 0, NULL, NULL, NULL,
+ G_TYPE_NONE, 0);
+
+ signals[NEW_FRAME] =
+ g_signal_new ("new-frame",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ 0, NULL, NULL, NULL,
+ G_TYPE_NONE, 0);
+}
diff --git a/src/st/st-theme-node-transition.h b/src/st/st-theme-node-transition.h
new file mode 100644
index 0000000..e7420e6
--- /dev/null
+++ b/src/st/st-theme-node-transition.h
@@ -0,0 +1,58 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * st-theme-node-transition.h: Theme node transitions for StWidget.
+ *
+ * Copyright 2010 Florian Müllner
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation, either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __ST_THEME_NODE_TRANSITION_H__
+#define __ST_THEME_NODE_TRANSITION_H__
+
+#include <clutter/clutter.h>
+
+#include "st-widget.h"
+#include "st-theme-node.h"
+
+G_BEGIN_DECLS
+
+#define ST_TYPE_THEME_NODE_TRANSITION (st_theme_node_transition_get_type ())
+G_DECLARE_FINAL_TYPE (StThemeNodeTransition, st_theme_node_transition,
+ ST, THEME_NODE_TRANSITION, GObject)
+
+StThemeNodeTransition *st_theme_node_transition_new (ClutterActor *actor,
+ StThemeNode *from_node,
+ StThemeNode *to_node,
+ StThemeNodePaintState *old_paint_state,
+ guint duration);
+
+void st_theme_node_transition_update (StThemeNodeTransition *transition,
+ StThemeNode *new_node);
+
+void st_theme_node_transition_paint (StThemeNodeTransition *transition,
+ CoglFramebuffer *framebuffer,
+ ClutterActorBox *allocation,
+ guint8 paint_opacity,
+ float resource_scale);
+
+void st_theme_node_transition_get_paint_box (StThemeNodeTransition *transition,
+ const ClutterActorBox *allocation,
+ ClutterActorBox *paint_box);
+
+StThemeNodePaintState * st_theme_node_transition_get_new_paint_state (StThemeNodeTransition *transition);
+
+G_END_DECLS
+
+#endif
diff --git a/src/st/st-theme-node.c b/src/st/st-theme-node.c
new file mode 100644
index 0000000..6e09c39
--- /dev/null
+++ b/src/st/st-theme-node.c
@@ -0,0 +1,4325 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * st-theme-node.c: style information for one node in a tree of themed objects
+ *
+ * Copyright 2008-2010 Red Hat, Inc.
+ * Copyright 2009 Steve Frécinaux
+ * Copyright 2009, 2010 Florian Müllner
+ * Copyright 2010 Adel Gadllah
+ * Copyright 2010 Giovanni Campagna
+ * Copyright 2011 Quentin "Sardem FF7" Glidic
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation, either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdlib.h>
+#include <string.h>
+
+#include "st-settings.h"
+#include "st-theme-private.h"
+#include "st-theme-context.h"
+#include "st-theme-node-private.h"
+
+static void st_theme_node_dispose (GObject *object);
+static void st_theme_node_finalize (GObject *object);
+
+static const ClutterColor BLACK_COLOR = { 0, 0, 0, 0xff };
+static const ClutterColor TRANSPARENT_COLOR = { 0, 0, 0, 0 };
+static const ClutterColor DEFAULT_SUCCESS_COLOR = { 0x4e, 0x9a, 0x06, 0xff };
+static const ClutterColor DEFAULT_WARNING_COLOR = { 0xf5, 0x79, 0x3e, 0xff };
+static const ClutterColor DEFAULT_ERROR_COLOR = { 0xcc, 0x00, 0x00, 0xff };
+
+G_DEFINE_TYPE (StThemeNode, st_theme_node, G_TYPE_OBJECT)
+
+static void
+st_theme_node_init (StThemeNode *node)
+{
+ node->transition_duration = -1;
+
+ st_theme_node_paint_state_init (&node->cached_state);
+}
+
+static void
+st_theme_node_class_init (StThemeNodeClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->dispose = st_theme_node_dispose;
+ object_class->finalize = st_theme_node_finalize;
+}
+
+static void
+maybe_free_properties (StThemeNode *node)
+{
+ if (node->properties)
+ {
+ g_free (node->properties);
+ node->properties = NULL;
+ node->n_properties = 0;
+ }
+
+ if (node->inline_properties)
+ {
+ /* This destroys the list, not just the head of the list */
+ cr_declaration_destroy (node->inline_properties);
+ node->inline_properties = NULL;
+ }
+}
+
+static void
+st_theme_node_dispose (GObject *gobject)
+{
+ StThemeNode *node = ST_THEME_NODE (gobject);
+
+ if (node->parent_node)
+ {
+ g_object_unref (node->parent_node);
+ node->parent_node = NULL;
+ }
+
+ if (node->border_image)
+ {
+ g_object_unref (node->border_image);
+ node->border_image = NULL;
+ }
+
+ if (node->icon_colors)
+ {
+ st_icon_colors_unref (node->icon_colors);
+ node->icon_colors = NULL;
+ }
+
+ st_theme_node_paint_state_free (&node->cached_state);
+
+ g_clear_object (&node->theme);
+
+ G_OBJECT_CLASS (st_theme_node_parent_class)->dispose (gobject);
+}
+
+static void
+st_theme_node_finalize (GObject *object)
+{
+ StThemeNode *node = ST_THEME_NODE (object);
+
+ g_free (node->element_id);
+ g_strfreev (node->element_classes);
+ g_strfreev (node->pseudo_classes);
+ g_free (node->inline_style);
+
+ maybe_free_properties (node);
+
+ g_clear_pointer (&node->font_desc, pango_font_description_free);
+
+ g_clear_pointer (&node->box_shadow, st_shadow_unref);
+ g_clear_pointer (&node->background_image_shadow, st_shadow_unref);
+ g_clear_pointer (&node->text_shadow, st_shadow_unref);
+
+ g_clear_object (&node->background_image);
+
+ cogl_clear_object (&node->background_texture);
+ cogl_clear_object (&node->background_pipeline);
+ cogl_clear_object (&node->background_shadow_pipeline);
+ cogl_clear_object (&node->border_slices_texture);
+ cogl_clear_object (&node->border_slices_pipeline);
+ cogl_clear_object (&node->color_pipeline);
+
+ G_OBJECT_CLASS (st_theme_node_parent_class)->finalize (object);
+}
+
+static GStrv
+split_on_whitespace (const gchar *s)
+{
+ gchar *cur;
+ gchar *l;
+ gchar *temp;
+ GPtrArray *arr;
+
+ if (s == NULL)
+ return NULL;
+
+ arr = g_ptr_array_new ();
+ l = g_strdup (s);
+
+ cur = strtok_r (l, " \t\f\r\n", &temp);
+
+ while (cur != NULL)
+ {
+ g_ptr_array_add (arr, g_strdup (cur));
+ cur = strtok_r (NULL, " \t\f\r\n", &temp);
+ }
+
+ g_free (l);
+ g_ptr_array_add (arr, NULL);
+ return (GStrv) g_ptr_array_free (arr, FALSE);
+}
+
+/**
+ * st_theme_node_new:
+ * @context: the context representing global state for this themed tree
+ * @parent_node: (nullable): the parent node of this node
+ * @theme: (nullable): a theme (stylesheet set) that overrides the
+ * theme inherited from the parent node
+ * @element_type: the type of the GObject represented by this node
+ * in the tree (corresponding to an element if we were theming an XML
+ * document. %G_TYPE_NONE means this style was created for the stage
+ * actor and matches a selector element name of 'stage'.
+ * @element_id: (nullable): the ID to match CSS rules against
+ * @element_class: (nullable): a whitespace-separated list of classes
+ * to match CSS rules against
+ * @pseudo_class: (nullable): a whitespace-separated list of pseudo-classes
+ * (like 'hover' or 'visited') to match CSS rules against
+ *
+ * Creates a new #StThemeNode. Once created, a node is immutable. If any
+ * of the attributes of the node (like the @element_class) change the node
+ * and its child nodes must be destroyed and recreated.
+ *
+ * Returns: (transfer full): a new #StThemeNode
+ */
+StThemeNode *
+st_theme_node_new (StThemeContext *context,
+ StThemeNode *parent_node,
+ StTheme *theme,
+ GType element_type,
+ const char *element_id,
+ const char *element_class,
+ const char *pseudo_class,
+ const char *inline_style)
+{
+ StThemeNode *node;
+
+ g_return_val_if_fail (ST_IS_THEME_CONTEXT (context), NULL);
+ g_return_val_if_fail (parent_node == NULL || ST_IS_THEME_NODE (parent_node), NULL);
+
+ node = g_object_new (ST_TYPE_THEME_NODE, NULL);
+
+ node->context = context;
+ if (parent_node != NULL)
+ node->parent_node = g_object_ref (parent_node);
+ else
+ node->parent_node = NULL;
+
+ if (theme == NULL && parent_node != NULL)
+ theme = parent_node->theme;
+
+ g_set_object (&node->theme, theme);
+ node->element_type = element_type;
+ node->element_id = g_strdup (element_id);
+ node->element_classes = split_on_whitespace (element_class);
+ node->pseudo_classes = split_on_whitespace (pseudo_class);
+ node->inline_style = g_strdup (inline_style);
+ node->cached_scale_factor = st_theme_context_get_scale_factor (context);
+
+ return node;
+}
+
+/**
+ * st_theme_node_get_parent:
+ * @node: a #StThemeNode
+ *
+ * Gets the parent themed element node.
+ *
+ * Returns: (nullable) (transfer none): the parent #StThemeNode, or %NULL if
+ * this is the root node of the tree of theme elements.
+ */
+StThemeNode *
+st_theme_node_get_parent (StThemeNode *node)
+{
+ g_return_val_if_fail (ST_IS_THEME_NODE (node), NULL);
+
+ return node->parent_node;
+}
+
+/**
+ * st_theme_node_get_theme:
+ * @node: a #StThemeNode
+ *
+ * Gets the theme stylesheet set that styles this node
+ *
+ * Returns: (transfer none): the theme stylesheet set
+ */
+StTheme *
+st_theme_node_get_theme (StThemeNode *node)
+{
+ g_return_val_if_fail (ST_IS_THEME_NODE (node), NULL);
+
+ return node->theme;
+}
+
+/**
+ * st_theme_node_get_element_type:
+ * @node: a #StThemeNode
+ *
+ * Get the element #GType for @node.
+ *
+ * Returns: the element type
+ */
+GType
+st_theme_node_get_element_type (StThemeNode *node)
+{
+ g_return_val_if_fail (ST_IS_THEME_NODE (node), G_TYPE_NONE);
+
+ return node->element_type;
+}
+
+/**
+ * st_theme_node_get_element_id:
+ * @node: a #StThemeNode
+ *
+ * Get the unique element ID for @node.
+ *
+ * Returns: (transfer none): the element's ID
+ */
+const char *
+st_theme_node_get_element_id (StThemeNode *node)
+{
+ g_return_val_if_fail (ST_IS_THEME_NODE (node), NULL);
+
+ return node->element_id;
+}
+
+/**
+ * st_theme_node_get_element_classes:
+ * @node: a #StThemeNode
+ *
+ * Get the list of element classes for @node.
+ *
+ * Returns: (transfer none): the element's classes
+ */
+GStrv
+st_theme_node_get_element_classes (StThemeNode *node)
+{
+ g_return_val_if_fail (ST_IS_THEME_NODE (node), NULL);
+
+ return node->element_classes;
+}
+
+/**
+ * st_theme_node_get_pseudo_classes:
+ * @node: a #StThemeNode
+ *
+ * Get the list of pseudo-classes for @node (eg. `:focused`).
+ *
+ * Returns: (transfer none): the element's pseudo-classes
+ */
+GStrv
+st_theme_node_get_pseudo_classes (StThemeNode *node)
+{
+ g_return_val_if_fail (ST_IS_THEME_NODE (node), NULL);
+
+ return node->pseudo_classes;
+}
+
+/**
+ * st_theme_node_equal:
+ * @node_a: first #StThemeNode
+ * @node_b: second #StThemeNode
+ *
+ * Compare two #StThemeNodes. Two nodes which compare equal will match
+ * the same CSS rules and have the same style properties. However, two
+ * nodes that have ended up with identical style properties do not
+ * necessarily compare equal.
+ *
+ * In detail, @node_a and @node_b are considered equal if and only if:
+ *
+ * - they share the same #StTheme and #StThemeContext
+ * - they have the same parent
+ * - they have the same element type
+ * - their id, class, pseudo-class and inline-style match
+ *
+ * Returns: %TRUE if @node_a equals @node_b
+ */
+gboolean
+st_theme_node_equal (StThemeNode *node_a, StThemeNode *node_b)
+{
+ g_return_val_if_fail (ST_IS_THEME_NODE (node_a), FALSE);
+
+ if (node_a == node_b)
+ return TRUE;
+
+ g_return_val_if_fail (ST_IS_THEME_NODE (node_b), FALSE);
+
+ if (node_a->parent_node != node_b->parent_node ||
+ node_a->context != node_b->context ||
+ node_a->theme != node_b->theme ||
+ node_a->element_type != node_b->element_type ||
+ node_a->cached_scale_factor != node_b->cached_scale_factor ||
+ g_strcmp0 (node_a->element_id, node_b->element_id) ||
+ g_strcmp0 (node_a->inline_style, node_b->inline_style))
+ return FALSE;
+
+ if ((node_a->element_classes == NULL) != (node_b->element_classes == NULL))
+ return FALSE;
+
+ if ((node_a->pseudo_classes == NULL) != (node_b->pseudo_classes == NULL))
+ return FALSE;
+
+ if (node_a->element_classes != NULL)
+ {
+ int i;
+
+ for (i = 0; ; i++)
+ {
+ if (g_strcmp0 (node_a->element_classes[i],
+ node_b->element_classes[i]))
+ return FALSE;
+
+ if (node_a->element_classes[i] == NULL)
+ break;
+ }
+ }
+
+ if (node_a->pseudo_classes != NULL)
+ {
+ int i;
+
+ for (i = 0; ; i++)
+ {
+ if (g_strcmp0 (node_a->pseudo_classes[i],
+ node_b->pseudo_classes[i]))
+ return FALSE;
+
+ if (node_a->pseudo_classes[i] == NULL)
+ break;
+ }
+ }
+
+ return TRUE;
+}
+
+/**
+ * st_theme_node_hash:
+ * @node: a #StThemeNode
+ *
+ * Converts @node to a hash value.
+ *
+ * Returns: a hash value corresponding to @node
+ */
+guint
+st_theme_node_hash (StThemeNode *node)
+{
+ guint hash;
+
+ g_return_val_if_fail (ST_IS_THEME_NODE(node), 0);
+
+ hash = GPOINTER_TO_UINT (node->parent_node);
+
+ hash = hash * 33 + GPOINTER_TO_UINT (node->context);
+ hash = hash * 33 + GPOINTER_TO_UINT (node->theme);
+ hash = hash * 33 + ((guint) node->element_type);
+ hash = hash * 33 + ((guint) node->cached_scale_factor);
+
+ if (node->element_id != NULL)
+ hash = hash * 33 + g_str_hash (node->element_id);
+
+ if (node->inline_style != NULL)
+ hash = hash * 33 + g_str_hash (node->inline_style);
+
+ if (node->element_classes != NULL)
+ {
+ gchar **it;
+
+ for (it = node->element_classes; *it != NULL; it++)
+ hash = hash * 33 + g_str_hash (*it) + 1;
+ }
+
+ if (node->pseudo_classes != NULL)
+ {
+ gchar **it;
+
+ for (it = node->pseudo_classes; *it != NULL; it++)
+ hash = hash * 33 + g_str_hash (*it) + 1;
+ }
+
+ return hash;
+}
+
+static void
+ensure_properties (StThemeNode *node)
+{
+ if (!node->properties_computed)
+ {
+ GPtrArray *properties = NULL;
+
+ node->properties_computed = TRUE;
+
+ if (node->theme)
+ properties = _st_theme_get_matched_properties (node->theme, node);
+
+ if (node->inline_style && *node->inline_style != '\0')
+ {
+ CRDeclaration *cur_decl;
+
+ if (!properties)
+ properties = g_ptr_array_new ();
+
+ node->inline_properties = _st_theme_parse_declaration_list (node->inline_style);
+ for (cur_decl = node->inline_properties; cur_decl; cur_decl = cur_decl->next)
+ g_ptr_array_add (properties, cur_decl);
+ }
+
+ if (properties)
+ {
+ node->n_properties = properties->len;
+ node->properties = (CRDeclaration **)g_ptr_array_free (properties, FALSE);
+ }
+ }
+}
+
+typedef enum {
+ VALUE_FOUND,
+ VALUE_NOT_FOUND,
+ VALUE_INHERIT
+} GetFromTermResult;
+
+static gboolean
+term_is_inherit (CRTerm *term)
+{
+ return (term->type == TERM_IDENT &&
+ strcmp (term->content.str->stryng->str, "inherit") == 0);
+}
+
+static gboolean
+term_is_none (CRTerm *term)
+{
+ return (term->type == TERM_IDENT &&
+ strcmp (term->content.str->stryng->str, "none") == 0);
+}
+
+static gboolean
+term_is_transparent (CRTerm *term)
+{
+ return (term->type == TERM_IDENT &&
+ strcmp (term->content.str->stryng->str, "transparent") == 0);
+}
+
+static int
+color_component_from_double (double component)
+{
+ /* We want to spread the range 0-1 equally over 0..255, but
+ * 1.0 should map to 255 not 256, so we need to special-case it.
+ * See http://people.redhat.com/otaylor/pixel-converting.html
+ * for (very) detailed discussion of related issues. */
+ if (component >= 1.0)
+ return 255;
+ else
+ return (int)(component * 256);
+}
+
+static GetFromTermResult
+get_color_from_rgba_term (CRTerm *term,
+ ClutterColor *color)
+{
+ CRTerm *arg = term->ext_content.func_param;
+ CRNum *num;
+ double r = 0, g = 0, b = 0, a = 0;
+ int i;
+
+ for (i = 0; i < 4; i++)
+ {
+ double value;
+
+ if (arg == NULL)
+ return VALUE_NOT_FOUND;
+
+ if ((i == 0 && arg->the_operator != NO_OP) ||
+ (i > 0 && arg->the_operator != COMMA))
+ return VALUE_NOT_FOUND;
+
+ if (arg->type != TERM_NUMBER)
+ return VALUE_NOT_FOUND;
+
+ num = arg->content.num;
+
+ /* For simplicity, we convert a,r,g,b to [0,1.0] floats and then
+ * convert them back below. Then when we set them on a cairo content
+ * we convert them back to floats, and then cairo converts them
+ * back to integers to pass them to X, and so forth...
+ */
+ if (i < 3)
+ {
+ if (num->type == NUM_PERCENTAGE)
+ value = num->val / 100;
+ else if (num->type == NUM_GENERIC)
+ value = num->val / 255;
+ else
+ return VALUE_NOT_FOUND;
+ }
+ else
+ {
+ if (num->type != NUM_GENERIC)
+ return VALUE_NOT_FOUND;
+
+ value = num->val;
+ }
+
+ value = CLAMP (value, 0, 1);
+
+ switch (i)
+ {
+ case 0:
+ r = value;
+ break;
+ case 1:
+ g = value;
+ break;
+ case 2:
+ b = value;
+ break;
+ case 3:
+ a = value;
+ break;
+ default:
+ g_assert_not_reached();
+ break;
+ }
+
+ arg = arg->next;
+ }
+
+ color->red = color_component_from_double (r);
+ color->green = color_component_from_double (g);
+ color->blue = color_component_from_double (b);
+ color->alpha = color_component_from_double (a);
+
+ return VALUE_FOUND;
+}
+
+static GetFromTermResult
+get_color_from_term (StThemeNode *node,
+ CRTerm *term,
+ ClutterColor *color)
+{
+ CRRgb rgb;
+ enum CRStatus status;
+
+ if (term_is_inherit (term))
+ {
+ return VALUE_INHERIT;
+ }
+ /* Since libcroco doesn't know about rgba colors, it can't handle
+ * the transparent keyword
+ */
+ else if (term_is_transparent (term))
+ {
+ *color = TRANSPARENT_COLOR;
+ return VALUE_FOUND;
+ }
+ /* rgba () colors - a CSS3 addition, are not supported by libcroco,
+ * but they are parsed as a "function", so we can emulate the
+ * functionality.
+ */
+ else if (term->type == TERM_FUNCTION &&
+ term->content.str &&
+ term->content.str->stryng &&
+ term->content.str->stryng->str &&
+ strcmp (term->content.str->stryng->str, "rgba") == 0)
+ {
+ return get_color_from_rgba_term (term, color);
+ }
+
+ status = cr_rgb_set_from_term (&rgb, term);
+ if (status != CR_OK)
+ return VALUE_NOT_FOUND;
+
+ if (rgb.is_percentage)
+ cr_rgb_compute_from_percentage (&rgb);
+
+ color->red = rgb.red;
+ color->green = rgb.green;
+ color->blue = rgb.blue;
+ color->alpha = 0xff;
+
+ return VALUE_FOUND;
+}
+
+/**
+ * st_theme_node_lookup_color:
+ * @node: a #StThemeNode
+ * @property_name: The name of the color property
+ * @inherit: if %TRUE, if a value is not found for the property on the
+ * node, then it will be looked up on the parent node, and then on the
+ * parent's parent, and so forth. Note that if the property has a
+ * value of 'inherit' it will be inherited even if %FALSE is passed
+ * in for @inherit; this only affects the default behavior for inheritance.
+ * @color: (out caller-allocates): location to store the color that was
+ * determined. If the property is not found, the value in this location
+ * will not be changed.
+ *
+ * Generically looks up a property containing a single color value. When
+ * specific getters (like st_theme_node_get_background_color()) exist, they
+ * should be used instead. They are cached, so more efficient, and have
+ * handling for shortcut properties and other details of CSS.
+ *
+ * See also st_theme_node_get_color(), which provides a simpler API.
+ *
+ * Returns: %TRUE if the property was found in the properties for this
+ * theme node (or in the properties of parent nodes when inheriting.)
+ */
+gboolean
+st_theme_node_lookup_color (StThemeNode *node,
+ const char *property_name,
+ gboolean inherit,
+ ClutterColor *color)
+{
+
+ int i;
+
+ g_return_val_if_fail (ST_IS_THEME_NODE(node), FALSE);
+ g_return_val_if_fail (property_name != NULL, FALSE);
+
+ ensure_properties (node);
+
+ for (i = node->n_properties - 1; i >= 0; i--)
+ {
+ CRDeclaration *decl = node->properties[i];
+
+ if (strcmp (decl->property->stryng->str, property_name) == 0)
+ {
+ GetFromTermResult result = get_color_from_term (node, decl->value, color);
+ if (result == VALUE_FOUND)
+ {
+ return TRUE;
+ }
+ else if (result == VALUE_INHERIT)
+ {
+ if (node->parent_node)
+ return st_theme_node_lookup_color (node->parent_node, property_name, inherit, color);
+ else
+ break;
+ }
+ }
+ }
+
+ if (inherit && node->parent_node)
+ return st_theme_node_lookup_color (node->parent_node, property_name, inherit, color);
+
+ return FALSE;
+}
+
+/**
+ * st_theme_node_get_color:
+ * @node: a #StThemeNode
+ * @property_name: The name of the color property
+ * @color: (out caller-allocates): location to store the color that
+ * was determined.
+ *
+ * Generically looks up a property containing a single color value. When
+ * specific getters (like st_theme_node_get_background_color()) exist, they
+ * should be used instead. They are cached, so more efficient, and have
+ * handling for shortcut properties and other details of CSS.
+ *
+ * If @property_name is not found, a warning will be logged and a
+ * default color returned.
+ *
+ * See also st_theme_node_lookup_color(), which provides more options,
+ * and lets you handle the case where the theme does not specify the
+ * indicated color.
+ */
+void
+st_theme_node_get_color (StThemeNode *node,
+ const char *property_name,
+ ClutterColor *color)
+{
+ if (!st_theme_node_lookup_color (node, property_name, FALSE, color))
+ {
+ g_warning ("Did not find color property '%s'", property_name);
+ memset (color, 0, sizeof (ClutterColor));
+ }
+}
+
+/**
+ * st_theme_node_lookup_double:
+ * @node: a #StThemeNode
+ * @property_name: The name of the numeric property
+ * @inherit: if %TRUE, if a value is not found for the property on the
+ * node, then it will be looked up on the parent node, and then on the
+ * parent's parent, and so forth. Note that if the property has a
+ * value of 'inherit' it will be inherited even if %FALSE is passed
+ * in for @inherit; this only affects the default behavior for inheritance.
+ * @value: (out): location to store the value that was determined.
+ * If the property is not found, the value in this location
+ * will not be changed.
+ *
+ * Generically looks up a property containing a single numeric value
+ * without units.
+ *
+ * See also st_theme_node_get_double(), which provides a simpler API.
+ *
+ * Returns: %TRUE if the property was found in the properties for this
+ * theme node (or in the properties of parent nodes when inheriting.)
+ */
+gboolean
+st_theme_node_lookup_double (StThemeNode *node,
+ const char *property_name,
+ gboolean inherit,
+ double *value)
+{
+ gboolean result = FALSE;
+ int i;
+
+ g_return_val_if_fail (ST_IS_THEME_NODE(node), FALSE);
+ g_return_val_if_fail (property_name != NULL, FALSE);
+
+ ensure_properties (node);
+
+ for (i = node->n_properties - 1; i >= 0; i--)
+ {
+ CRDeclaration *decl = node->properties[i];
+
+ if (strcmp (decl->property->stryng->str, property_name) == 0)
+ {
+ CRTerm *term = decl->value;
+
+ if (term->type != TERM_NUMBER || term->content.num->type != NUM_GENERIC)
+ continue;
+
+ *value = term->content.num->val;
+ result = TRUE;
+ break;
+ }
+ }
+
+ if (!result && inherit && node->parent_node)
+ result = st_theme_node_lookup_double (node->parent_node, property_name, inherit, value);
+
+ return result;
+}
+
+/**
+ * st_theme_node_lookup_time:
+ * @node: a #StThemeNode
+ * @property_name: The name of the time property
+ * @inherit: if %TRUE, if a value is not found for the property on the
+ * node, then it will be looked up on the parent node, and then on the
+ * parent's parent, and so forth. Note that if the property has a
+ * value of 'inherit' it will be inherited even if %FALSE is passed
+ * in for @inherit; this only affects the default behavior for inheritance.
+ * @value: (out): location to store the value that was determined.
+ * If the property is not found, the value in this location
+ * will not be changed.
+ *
+ * Generically looks up a property containing a single time value,
+ * which is converted to milliseconds.
+ *
+ * Returns: %TRUE if the property was found in the properties for this
+ * theme node (or in the properties of parent nodes when inheriting.)
+ */
+gboolean
+st_theme_node_lookup_time (StThemeNode *node,
+ const char *property_name,
+ gboolean inherit,
+ double *value)
+{
+ gboolean result = FALSE;
+ int i;
+
+ g_return_val_if_fail (ST_IS_THEME_NODE(node), FALSE);
+ g_return_val_if_fail (property_name != NULL, FALSE);
+
+ ensure_properties (node);
+
+ for (i = node->n_properties - 1; i >= 0; i--)
+ {
+ CRDeclaration *decl = node->properties[i];
+
+ if (strcmp (decl->property->stryng->str, property_name) == 0)
+ {
+ CRTerm *term = decl->value;
+ int factor = 1;
+
+ if (term->type != TERM_NUMBER)
+ continue;
+
+ if (term->content.num->type != NUM_TIME_S &&
+ term->content.num->type != NUM_TIME_MS)
+ continue;
+
+ if (term->content.num->type == NUM_TIME_S)
+ factor = 1000;
+
+ *value = factor * term->content.num->val;
+ result = TRUE;
+ break;
+ }
+ }
+
+ if (!result && inherit && node->parent_node)
+ result = st_theme_node_lookup_time (node->parent_node, property_name, inherit, value);
+
+ return result;
+}
+
+/**
+ * st_theme_node_get_double:
+ * @node: a #StThemeNode
+ * @property_name: The name of the numeric property
+ *
+ * Generically looks up a property containing a single numeric value
+ * without units.
+ *
+ * See also st_theme_node_lookup_double(), which provides more options,
+ * and lets you handle the case where the theme does not specify the
+ * indicated value.
+ *
+ * Returns: the value found. If @property_name is not
+ * found, a warning will be logged and 0 will be returned.
+ */
+gdouble
+st_theme_node_get_double (StThemeNode *node,
+ const char *property_name)
+{
+ gdouble value;
+
+ if (st_theme_node_lookup_double (node, property_name, FALSE, &value))
+ return value;
+ else
+ {
+ g_warning ("Did not find double property '%s'", property_name);
+ return 0.0;
+ }
+}
+
+/**
+ * st_theme_node_lookup_url:
+ * @node: a #StThemeNode
+ * @property_name: The name of the string property
+ * @inherit: if %TRUE, if a value is not found for the property on the
+ * node, then it will be looked up on the parent node, and then on the
+ * parent's parent, and so forth. Note that if the property has a
+ * value of 'inherit' it will be inherited even if %FALSE is passed
+ * in for @inherit; this only affects the default behavior for inheritance.
+ * @file: (out) (transfer full): location to store the newly allocated value that was
+ * determined. If the property is not found, the value in this location
+ * will not be changed.
+ *
+ * Looks up a property containing a single URL value.
+ *
+ * See also st_theme_node_get_url(), which provides a simpler API.
+ *
+ * Returns: %TRUE if the property was found in the properties for this
+ * theme node (or in the properties of parent nodes when inheriting.)
+ */
+gboolean
+st_theme_node_lookup_url (StThemeNode *node,
+ const char *property_name,
+ gboolean inherit,
+ GFile **file)
+{
+ gboolean result = FALSE;
+ int i;
+
+ g_return_val_if_fail (ST_IS_THEME_NODE(node), FALSE);
+ g_return_val_if_fail (property_name != NULL, FALSE);
+
+ ensure_properties (node);
+
+ for (i = node->n_properties - 1; i >= 0; i--)
+ {
+ CRDeclaration *decl = node->properties[i];
+
+ if (strcmp (decl->property->stryng->str, property_name) == 0)
+ {
+ CRTerm *term = decl->value;
+ CRStyleSheet *base_stylesheet;
+
+ if (term->type != TERM_URI && term->type != TERM_STRING)
+ continue;
+
+ if (decl->parent_statement != NULL)
+ base_stylesheet = decl->parent_statement->parent_sheet;
+ else
+ base_stylesheet = NULL;
+
+ *file = _st_theme_resolve_url (node->theme,
+ base_stylesheet,
+ decl->value->content.str->stryng->str);
+ result = TRUE;
+ break;
+ }
+ }
+
+ if (!result && inherit && node->parent_node)
+ result = st_theme_node_lookup_url (node->parent_node, property_name, inherit, file);
+
+ return result;
+}
+
+/**
+ * st_theme_node_get_url:
+ * @node: a #StThemeNode
+ * @property_name: The name of the string property
+ *
+ * Looks up a property containing a single URL value.
+ *
+ * See also st_theme_node_lookup_url(), which provides more options,
+ * and lets you handle the case where the theme does not specify the
+ * indicated value.
+ *
+ * Returns: (nullable) (transfer full): the newly allocated value if found.
+ * If @property_name is not found, a warning will be logged and %NULL
+ * will be returned.
+ */
+GFile *
+st_theme_node_get_url (StThemeNode *node,
+ const char *property_name)
+{
+ GFile *file;
+
+ if (st_theme_node_lookup_url (node, property_name, FALSE, &file))
+ return file;
+ else
+ {
+ g_warning ("Did not find string property '%s'", property_name);
+ return NULL;
+ }
+}
+
+static const PangoFontDescription *
+get_parent_font (StThemeNode *node)
+{
+ if (node->parent_node)
+ return st_theme_node_get_font (node->parent_node);
+ else
+ return st_theme_context_get_font (node->context);
+}
+
+static GetFromTermResult
+get_length_from_term (StThemeNode *node,
+ CRTerm *term,
+ gboolean use_parent_font,
+ gdouble *length)
+{
+ CRNum *num;
+
+ enum {
+ ABSOLUTE,
+ POINTS,
+ FONT_RELATIVE,
+ } type = ABSOLUTE;
+
+ double multiplier = 1.0;
+
+
+ if (term->type != TERM_NUMBER)
+ {
+ g_warning ("Ignoring length property that isn't a number at line %d, col %d",
+ term->location.line, term->location.column);
+ return VALUE_NOT_FOUND;
+ }
+
+ num = term->content.num;
+
+ switch (num->type)
+ {
+ case NUM_LENGTH_PX:
+ type = ABSOLUTE;
+ multiplier = 1 * node->cached_scale_factor;
+ break;
+ case NUM_LENGTH_PT:
+ type = POINTS;
+ multiplier = 1;
+ break;
+ case NUM_LENGTH_IN:
+ type = POINTS;
+ multiplier = 72;
+ break;
+ case NUM_LENGTH_CM:
+ type = POINTS;
+ multiplier = 72. / 2.54;
+ break;
+ case NUM_LENGTH_MM:
+ type = POINTS;
+ multiplier = 72. / 25.4;
+ break;
+ case NUM_LENGTH_PC:
+ type = POINTS;
+ multiplier = 12. / 25.4;
+ break;
+ case NUM_LENGTH_EM:
+ {
+ type = FONT_RELATIVE;
+ multiplier = 1;
+ break;
+ }
+ case NUM_LENGTH_EX:
+ {
+ /* Doing better would require actually resolving the font description
+ * to a specific font, and Pango doesn't have an ex metric anyways,
+ * so we'd have to try and synthesize it by complicated means.
+ *
+ * The 0.5em is the CSS spec suggested thing to use when nothing
+ * better is available.
+ */
+ type = FONT_RELATIVE;
+ multiplier = 0.5;
+ break;
+ }
+
+ case NUM_INHERIT:
+ return VALUE_INHERIT;
+
+ case NUM_AUTO:
+ g_warning ("'auto' not supported for lengths");
+ return VALUE_NOT_FOUND;
+
+ case NUM_GENERIC:
+ {
+ if (num->val != 0)
+ {
+ g_warning ("length values must specify a unit");
+ return VALUE_NOT_FOUND;
+ }
+ else
+ {
+ type = ABSOLUTE;
+ multiplier = 0;
+ }
+ break;
+ }
+
+ case NUM_PERCENTAGE:
+ g_warning ("percentage lengths not currently supported");
+ return VALUE_NOT_FOUND;
+
+ case NUM_ANGLE_DEG:
+ case NUM_ANGLE_RAD:
+ case NUM_ANGLE_GRAD:
+ case NUM_TIME_MS:
+ case NUM_TIME_S:
+ case NUM_FREQ_HZ:
+ case NUM_FREQ_KHZ:
+ case NUM_UNKNOWN_TYPE:
+ case NB_NUM_TYPE:
+ default:
+ g_warning ("Ignoring invalid type of number of length property");
+ return VALUE_NOT_FOUND;
+ }
+
+ switch (type)
+ {
+ case ABSOLUTE:
+ *length = num->val * multiplier;
+ break;
+ case POINTS:
+ {
+ double resolution = clutter_backend_get_resolution (clutter_get_default_backend ());
+ *length = num->val * multiplier * (resolution / 72.);
+ }
+ break;
+ case FONT_RELATIVE:
+ {
+ const PangoFontDescription *desc;
+ double font_size;
+
+ if (use_parent_font)
+ desc = get_parent_font (node);
+ else
+ desc = st_theme_node_get_font (node);
+
+ font_size = (double)pango_font_description_get_size (desc) / PANGO_SCALE;
+
+ if (pango_font_description_get_size_is_absolute (desc))
+ {
+ *length = num->val * multiplier * font_size;
+ }
+ else
+ {
+ double resolution = clutter_backend_get_resolution (clutter_get_default_backend ());
+ *length = num->val * multiplier * (resolution / 72.) * font_size;
+ }
+ }
+ break;
+ default:
+ g_assert_not_reached ();
+ }
+
+ return VALUE_FOUND;
+}
+
+static GetFromTermResult
+get_length_from_term_int (StThemeNode *node,
+ CRTerm *term,
+ gboolean use_parent_font,
+ gint *length)
+{
+ double value;
+ GetFromTermResult result;
+
+ result = get_length_from_term (node, term, use_parent_font, &value);
+ if (result == VALUE_FOUND)
+ *length = (int) ((value / node->cached_scale_factor) + 0.5) * node->cached_scale_factor;
+ return result;
+}
+
+static GetFromTermResult
+get_length_internal (StThemeNode *node,
+ const char *property_name,
+ gdouble *length)
+{
+ int i;
+
+ ensure_properties (node);
+
+ for (i = node->n_properties - 1; i >= 0; i--)
+ {
+ CRDeclaration *decl = node->properties[i];
+
+ if (strcmp (decl->property->stryng->str, property_name) == 0)
+ {
+ GetFromTermResult result = get_length_from_term (node, decl->value, FALSE, length);
+ if (result != VALUE_NOT_FOUND)
+ return result;
+ }
+ }
+
+ return VALUE_NOT_FOUND;
+}
+
+/**
+ * st_theme_node_lookup_length:
+ * @node: a #StThemeNode
+ * @property_name: The name of the length property
+ * @inherit: if %TRUE, if a value is not found for the property on the
+ * node, then it will be looked up on the parent node, and then on the
+ * parent's parent, and so forth. Note that if the property has a
+ * value of 'inherit' it will be inherited even if %FALSE is passed
+ * in for @inherit; this only affects the default behavior for inheritance.
+ * @length: (out): location to store the length that was determined.
+ * If the property is not found, the value in this location
+ * will not be changed. The returned length is resolved
+ * to pixels.
+ *
+ * Generically looks up a property containing a single length value. When
+ * specific getters (like st_theme_node_get_border_width()) exist, they
+ * should be used instead. They are cached, so more efficient, and have
+ * handling for shortcut properties and other details of CSS.
+ *
+ * See also st_theme_node_get_length(), which provides a simpler API.
+ *
+ * Returns: %TRUE if the property was found in the properties for this
+ * theme node (or in the properties of parent nodes when inheriting.)
+ */
+gboolean
+st_theme_node_lookup_length (StThemeNode *node,
+ const char *property_name,
+ gboolean inherit,
+ gdouble *length)
+{
+ GetFromTermResult result;
+
+ g_return_val_if_fail (ST_IS_THEME_NODE(node), FALSE);
+ g_return_val_if_fail (property_name != NULL, FALSE);
+
+ result = get_length_internal (node, property_name, length);
+
+ if (result == VALUE_FOUND)
+ return TRUE;
+ else if (result == VALUE_INHERIT)
+ inherit = TRUE;
+
+ if (inherit && node->parent_node)
+ return st_theme_node_lookup_length (node->parent_node, property_name, inherit, length);
+
+ return FALSE;
+}
+
+/**
+ * st_theme_node_get_length:
+ * @node: a #StThemeNode
+ * @property_name: The name of the length property
+ *
+ * Generically looks up a property containing a single length value. When
+ * specific getters (like st_theme_node_get_border_width()) exist, they
+ * should be used instead. They are cached, so more efficient, and have
+ * handling for shortcut properties and other details of CSS.
+ *
+ * Unlike st_theme_node_get_color() and st_theme_node_get_double(),
+ * this does not print a warning if the property is not found; it just
+ * returns 0.
+ *
+ * See also st_theme_node_lookup_length(), which provides more options. The
+ * returned value is in physical pixels, as opposed to logical pixels.
+ *
+ * Returns: the length, in pixels, or 0 if the property was not found.
+ */
+gdouble
+st_theme_node_get_length (StThemeNode *node,
+ const char *property_name)
+{
+ gdouble length;
+
+ if (st_theme_node_lookup_length (node, property_name, FALSE, &length))
+ return length;
+ else
+ return 0.0;
+}
+
+static void
+do_border_radius_term (StThemeNode *node,
+ CRTerm *term,
+ gboolean topleft,
+ gboolean topright,
+ gboolean bottomright,
+ gboolean bottomleft)
+{
+ int value;
+
+ if (get_length_from_term_int (node, term, FALSE, &value) != VALUE_FOUND)
+ return;
+
+ if (topleft)
+ node->border_radius[ST_CORNER_TOPLEFT] = value;
+ if (topright)
+ node->border_radius[ST_CORNER_TOPRIGHT] = value;
+ if (bottomright)
+ node->border_radius[ST_CORNER_BOTTOMRIGHT] = value;
+ if (bottomleft)
+ node->border_radius[ST_CORNER_BOTTOMLEFT] = value;
+}
+
+static void
+do_border_radius (StThemeNode *node,
+ CRDeclaration *decl)
+{
+ const char *property_name = decl->property->stryng->str + 13; /* Skip 'border-radius' */
+
+ if (strcmp (property_name, "") == 0)
+ {
+ /* Slight deviation ... if we don't understand some of the terms and understand others,
+ * then we set the ones we understand and ignore the others instead of ignoring the
+ * whole thing
+ */
+ if (decl->value == NULL) /* 0 values */
+ return;
+ else if (decl->value->next == NULL) /* 1 value */
+ {
+ do_border_radius_term (node, decl->value, TRUE, TRUE, TRUE, TRUE); /* all corners */
+ return;
+ }
+ else if (decl->value->next->next == NULL) /* 2 values */
+ {
+ do_border_radius_term (node, decl->value, TRUE, FALSE, TRUE, FALSE); /* topleft/bottomright */
+ do_border_radius_term (node, decl->value->next, FALSE, TRUE, FALSE, TRUE); /* topright/bottomleft */
+ }
+ else if (decl->value->next->next->next == NULL) /* 3 values */
+ {
+ do_border_radius_term (node, decl->value, TRUE, FALSE, FALSE, FALSE); /* topleft */
+ do_border_radius_term (node, decl->value->next, FALSE, TRUE, FALSE, TRUE); /* topright/bottomleft */
+ do_border_radius_term (node, decl->value->next->next, FALSE, FALSE, TRUE, FALSE); /* bottomright */
+ }
+ else if (decl->value->next->next->next->next == NULL) /* 4 values */
+ {
+ do_border_radius_term (node, decl->value, TRUE, FALSE, FALSE, FALSE); /* topleft */
+ do_border_radius_term (node, decl->value->next, FALSE, TRUE, FALSE, FALSE); /* topright */
+ do_border_radius_term (node, decl->value->next->next, FALSE, FALSE, TRUE, FALSE); /* bottomright */
+ do_border_radius_term (node, decl->value->next->next->next, FALSE, FALSE, FALSE, TRUE); /* bottomleft */
+ }
+ else
+ {
+ g_warning ("Too many values for border-radius property");
+ return;
+ }
+ }
+ else
+ {
+ if (decl->value == NULL || decl->value->next != NULL)
+ return;
+
+ if (strcmp (property_name, "-topleft") == 0)
+ do_border_radius_term (node, decl->value, TRUE, FALSE, FALSE, FALSE);
+ else if (strcmp (property_name, "-topright") == 0)
+ do_border_radius_term (node, decl->value, FALSE, TRUE, FALSE, FALSE);
+ else if (strcmp (property_name, "-bottomright") == 0)
+ do_border_radius_term (node, decl->value, FALSE, FALSE, TRUE, FALSE);
+ else if (strcmp (property_name, "-bottomleft") == 0)
+ do_border_radius_term (node, decl->value, FALSE, FALSE, FALSE, TRUE);
+ }
+}
+
+static void
+do_border_property (StThemeNode *node,
+ CRDeclaration *decl)
+{
+ const char *property_name = decl->property->stryng->str + 6; /* Skip 'border' */
+ StSide side = (StSide)-1;
+ ClutterColor color;
+ gboolean color_set = FALSE;
+ int width = 0; /* suppress warning */
+ gboolean width_set = FALSE;
+ int j;
+
+ if (g_str_has_prefix (property_name, "-radius"))
+ {
+ do_border_radius (node, decl);
+ return;
+ }
+
+ if (g_str_has_prefix (property_name, "-left"))
+ {
+ side = ST_SIDE_LEFT;
+ property_name += 5;
+ }
+ else if (g_str_has_prefix (property_name, "-right"))
+ {
+ side = ST_SIDE_RIGHT;
+ property_name += 6;
+ }
+ else if (g_str_has_prefix (property_name, "-top"))
+ {
+ side = ST_SIDE_TOP;
+ property_name += 4;
+ }
+ else if (g_str_has_prefix (property_name, "-bottom"))
+ {
+ side = ST_SIDE_BOTTOM;
+ property_name += 7;
+ }
+
+ if (strcmp (property_name, "") == 0)
+ {
+ /* Set value for width/color/style in any order */
+ CRTerm *term;
+
+ for (term = decl->value; term; term = term->next)
+ {
+ GetFromTermResult result;
+
+ if (term->type == TERM_IDENT)
+ {
+ const char *ident = term->content.str->stryng->str;
+ if (strcmp (ident, "none") == 0 || strcmp (ident, "hidden") == 0)
+ {
+ width = 0;
+ width_set = TRUE;
+ continue;
+ }
+ else if (strcmp (ident, "solid") == 0)
+ {
+ /* The only thing we support */
+ continue;
+ }
+ else if (strcmp (ident, "dotted") == 0 ||
+ strcmp (ident, "dashed") == 0 ||
+ strcmp (ident, "double") == 0 ||
+ strcmp (ident, "groove") == 0 ||
+ strcmp (ident, "ridge") == 0 ||
+ strcmp (ident, "inset") == 0 ||
+ strcmp (ident, "outset") == 0)
+ {
+ /* Treat the same as solid */
+ continue;
+ }
+
+ /* Presumably a color, fall through */
+ }
+
+ if (term->type == TERM_NUMBER)
+ {
+ result = get_length_from_term_int (node, term, FALSE, &width);
+ if (result != VALUE_NOT_FOUND)
+ {
+ width_set = result == VALUE_FOUND;
+ continue;
+ }
+ }
+
+ result = get_color_from_term (node, term, &color);
+ if (result != VALUE_NOT_FOUND)
+ {
+ color_set = result == VALUE_FOUND;
+ continue;
+ }
+ }
+
+ }
+ else if (strcmp (property_name, "-color") == 0)
+ {
+ if (decl->value == NULL || decl->value->next != NULL)
+ return;
+
+ if (get_color_from_term (node, decl->value, &color) == VALUE_FOUND)
+ /* Ignore inherit */
+ color_set = TRUE;
+ }
+ else if (strcmp (property_name, "-width") == 0)
+ {
+ if (decl->value == NULL || decl->value->next != NULL)
+ return;
+
+ if (get_length_from_term_int (node, decl->value, FALSE, &width) == VALUE_FOUND)
+ /* Ignore inherit */
+ width_set = TRUE;
+ }
+
+ if (side == (StSide)-1)
+ {
+ for (j = 0; j < 4; j++)
+ {
+ if (color_set)
+ node->border_color[j] = color;
+ if (width_set)
+ node->border_width[j] = width;
+ }
+ }
+ else
+ {
+ if (color_set)
+ node->border_color[side] = color;
+ if (width_set)
+ node->border_width[side] = width;
+ }
+}
+
+static void
+do_outline_property (StThemeNode *node,
+ CRDeclaration *decl)
+{
+ const char *property_name = decl->property->stryng->str + 7; /* Skip 'outline' */
+ ClutterColor color;
+ gboolean color_set = FALSE;
+ int width = 0; /* suppress warning */
+ gboolean width_set = FALSE;
+
+ if (strcmp (property_name, "") == 0)
+ {
+ /* Set value for width/color/style in any order */
+ CRTerm *term;
+
+ for (term = decl->value; term; term = term->next)
+ {
+ GetFromTermResult result;
+
+ if (term->type == TERM_IDENT)
+ {
+ const char *ident = term->content.str->stryng->str;
+ if (strcmp (ident, "none") == 0 || strcmp (ident, "hidden") == 0)
+ {
+ width = 0;
+ width_set = TRUE;
+ continue;
+ }
+ else if (strcmp (ident, "solid") == 0)
+ {
+ /* The only thing we support */
+ continue;
+ }
+ else if (strcmp (ident, "dotted") == 0 ||
+ strcmp (ident, "dashed") == 0 ||
+ strcmp (ident, "double") == 0 ||
+ strcmp (ident, "groove") == 0 ||
+ strcmp (ident, "ridge") == 0 ||
+ strcmp (ident, "inset") == 0 ||
+ strcmp (ident, "outset") == 0)
+ {
+ /* Treat the same as solid */
+ continue;
+ }
+
+ /* Presumably a color, fall through */
+ }
+
+ if (term->type == TERM_NUMBER)
+ {
+ result = get_length_from_term_int (node, term, FALSE, &width);
+ if (result != VALUE_NOT_FOUND)
+ {
+ width_set = result == VALUE_FOUND;
+ continue;
+ }
+ }
+
+ result = get_color_from_term (node, term, &color);
+ if (result != VALUE_NOT_FOUND)
+ {
+ color_set = result == VALUE_FOUND;
+ continue;
+ }
+ }
+
+ }
+ else if (strcmp (property_name, "-color") == 0)
+ {
+ if (decl->value == NULL || decl->value->next != NULL)
+ return;
+
+ if (get_color_from_term (node, decl->value, &color) == VALUE_FOUND)
+ /* Ignore inherit */
+ color_set = TRUE;
+ }
+ else if (strcmp (property_name, "-width") == 0)
+ {
+ if (decl->value == NULL || decl->value->next != NULL)
+ return;
+
+ if (get_length_from_term_int (node, decl->value, FALSE, &width) == VALUE_FOUND)
+ /* Ignore inherit */
+ width_set = TRUE;
+ }
+
+ if (color_set)
+ node->outline_color = color;
+ if (width_set)
+ node->outline_width = width;
+}
+
+static void
+do_padding_property_term (StThemeNode *node,
+ CRTerm *term,
+ gboolean left,
+ gboolean right,
+ gboolean top,
+ gboolean bottom)
+{
+ int value;
+
+ if (get_length_from_term_int (node, term, FALSE, &value) != VALUE_FOUND)
+ return;
+
+ if (left)
+ node->padding[ST_SIDE_LEFT] = value;
+ if (right)
+ node->padding[ST_SIDE_RIGHT] = value;
+ if (top)
+ node->padding[ST_SIDE_TOP] = value;
+ if (bottom)
+ node->padding[ST_SIDE_BOTTOM] = value;
+}
+
+static void
+do_padding_property (StThemeNode *node,
+ CRDeclaration *decl)
+{
+ const char *property_name = decl->property->stryng->str + 7; /* Skip 'padding' */
+
+ if (strcmp (property_name, "") == 0)
+ {
+ /* Slight deviation ... if we don't understand some of the terms and understand others,
+ * then we set the ones we understand and ignore the others instead of ignoring the
+ * whole thing
+ */
+ if (decl->value == NULL) /* 0 values */
+ return;
+ else if (decl->value->next == NULL) /* 1 value */
+ {
+ do_padding_property_term (node, decl->value, TRUE, TRUE, TRUE, TRUE); /* left/right/top/bottom */
+ return;
+ }
+ else if (decl->value->next->next == NULL) /* 2 values */
+ {
+ do_padding_property_term (node, decl->value, FALSE, FALSE, TRUE, TRUE); /* top/bottom */
+ do_padding_property_term (node, decl->value->next, TRUE, TRUE, FALSE, FALSE); /* left/right */
+ }
+ else if (decl->value->next->next->next == NULL) /* 3 values */
+ {
+ do_padding_property_term (node, decl->value, FALSE, FALSE, TRUE, FALSE); /* top */
+ do_padding_property_term (node, decl->value->next, TRUE, TRUE, FALSE, FALSE); /* left/right */
+ do_padding_property_term (node, decl->value->next->next, FALSE, FALSE, FALSE, TRUE); /* bottom */
+ }
+ else if (decl->value->next->next->next->next == NULL) /* 4 values */
+ {
+ do_padding_property_term (node, decl->value, FALSE, FALSE, TRUE, FALSE); /* top */
+ do_padding_property_term (node, decl->value->next, FALSE, TRUE, FALSE, FALSE); /* right */
+ do_padding_property_term (node, decl->value->next->next, FALSE, FALSE, FALSE, TRUE); /* bottom */
+ do_padding_property_term (node, decl->value->next->next->next, TRUE, FALSE, FALSE, FALSE); /* left */
+ }
+ else
+ {
+ g_warning ("Too many values for padding property");
+ return;
+ }
+ }
+ else
+ {
+ if (decl->value == NULL || decl->value->next != NULL)
+ return;
+
+ if (strcmp (property_name, "-left") == 0)
+ do_padding_property_term (node, decl->value, TRUE, FALSE, FALSE, FALSE);
+ else if (strcmp (property_name, "-right") == 0)
+ do_padding_property_term (node, decl->value, FALSE, TRUE, FALSE, FALSE);
+ else if (strcmp (property_name, "-top") == 0)
+ do_padding_property_term (node, decl->value, FALSE, FALSE, TRUE, FALSE);
+ else if (strcmp (property_name, "-bottom") == 0)
+ do_padding_property_term (node, decl->value, FALSE, FALSE, FALSE, TRUE);
+ }
+}
+
+static void
+do_margin_property_term (StThemeNode *node,
+ CRTerm *term,
+ gboolean left,
+ gboolean right,
+ gboolean top,
+ gboolean bottom)
+{
+ int value;
+
+ if (get_length_from_term_int (node, term, FALSE, &value) != VALUE_FOUND)
+ return;
+
+ if (left)
+ node->margin[ST_SIDE_LEFT] = value;
+ if (right)
+ node->margin[ST_SIDE_RIGHT] = value;
+ if (top)
+ node->margin[ST_SIDE_TOP] = value;
+ if (bottom)
+ node->margin[ST_SIDE_BOTTOM] = value;
+}
+
+static void
+do_margin_property (StThemeNode *node,
+ CRDeclaration *decl)
+{
+ const char *property_name = decl->property->stryng->str + 6; /* Skip 'margin' */
+
+ if (strcmp (property_name, "") == 0)
+ {
+ /* Slight deviation ... if we don't understand some of the terms and understand others,
+ * then we set the ones we understand and ignore the others instead of ignoring the
+ * whole thing
+ */
+ if (decl->value == NULL) /* 0 values */
+ return;
+ else if (decl->value->next == NULL) /* 1 value */
+ {
+ do_margin_property_term (node, decl->value, TRUE, TRUE, TRUE, TRUE); /* left/right/top/bottom */
+ return;
+ }
+ else if (decl->value->next->next == NULL) /* 2 values */
+ {
+ do_margin_property_term (node, decl->value, FALSE, FALSE, TRUE, TRUE); /* top/bottom */
+ do_margin_property_term (node, decl->value->next, TRUE, TRUE, FALSE, FALSE); /* left/right */
+ }
+ else if (decl->value->next->next->next == NULL) /* 3 values */
+ {
+ do_margin_property_term (node, decl->value, FALSE, FALSE, TRUE, FALSE); /* top */
+ do_margin_property_term (node, decl->value->next, TRUE, TRUE, FALSE, FALSE); /* left/right */
+ do_margin_property_term (node, decl->value->next->next, FALSE, FALSE, FALSE, TRUE); /* bottom */
+ }
+ else if (decl->value->next->next->next->next == NULL) /* 4 values */
+ {
+ do_margin_property_term (node, decl->value, FALSE, FALSE, TRUE, FALSE); /* top */
+ do_margin_property_term (node, decl->value->next, FALSE, TRUE, FALSE, FALSE); /* right */
+ do_margin_property_term (node, decl->value->next->next, FALSE, FALSE, FALSE, TRUE); /* bottom */
+ do_margin_property_term (node, decl->value->next->next->next, TRUE, FALSE, FALSE, FALSE); /* left */
+ }
+ else
+ {
+ g_warning ("Too many values for margin property");
+ return;
+ }
+ }
+ else
+ {
+ if (decl->value == NULL || decl->value->next != NULL)
+ return;
+
+ if (strcmp (property_name, "-left") == 0)
+ do_margin_property_term (node, decl->value, TRUE, FALSE, FALSE, FALSE);
+ else if (strcmp (property_name, "-right") == 0)
+ do_margin_property_term (node, decl->value, FALSE, TRUE, FALSE, FALSE);
+ else if (strcmp (property_name, "-top") == 0)
+ do_margin_property_term (node, decl->value, FALSE, FALSE, TRUE, FALSE);
+ else if (strcmp (property_name, "-bottom") == 0)
+ do_margin_property_term (node, decl->value, FALSE, FALSE, FALSE, TRUE);
+ }
+}
+
+static void
+do_size_property (StThemeNode *node,
+ CRDeclaration *decl,
+ int *node_value)
+{
+ CRTerm *term = decl->value;
+
+ if (term->type == TERM_IDENT &&
+ strcmp (term->content.str->stryng->str, "auto") == 0)
+ *node_value = -1;
+ else
+ get_length_from_term_int (node, term, FALSE, node_value);
+}
+
+void
+_st_theme_node_ensure_geometry (StThemeNode *node)
+{
+ int i, j;
+ int width, height;
+
+ if (node->geometry_computed)
+ return;
+
+ node->geometry_computed = TRUE;
+
+ ensure_properties (node);
+
+ for (j = 0; j < 4; j++)
+ {
+ node->border_width[j] = 0;
+ node->border_color[j] = TRANSPARENT_COLOR;
+ }
+
+ node->outline_width = 0;
+ node->outline_color = TRANSPARENT_COLOR;
+
+ width = -1;
+ height = -1;
+ node->width = -1;
+ node->height = -1;
+ node->min_width = -1;
+ node->min_height = -1;
+ node->max_width = -1;
+ node->max_height = -1;
+
+ for (i = 0; i < node->n_properties; i++)
+ {
+ CRDeclaration *decl = node->properties[i];
+ const char *property_name = decl->property->stryng->str;
+
+ if (g_str_has_prefix (property_name, "border"))
+ do_border_property (node, decl);
+ else if (g_str_has_prefix (property_name, "outline"))
+ do_outline_property (node, decl);
+ else if (g_str_has_prefix (property_name, "padding"))
+ do_padding_property (node, decl);
+ else if (g_str_has_prefix (property_name, "margin"))
+ do_margin_property (node, decl);
+ else if (strcmp (property_name, "width") == 0)
+ do_size_property (node, decl, &width);
+ else if (strcmp (property_name, "height") == 0)
+ do_size_property (node, decl, &height);
+ else if (strcmp (property_name, "-st-natural-width") == 0)
+ do_size_property (node, decl, &node->width);
+ else if (strcmp (property_name, "-st-natural-height") == 0)
+ do_size_property (node, decl, &node->height);
+ else if (strcmp (property_name, "min-width") == 0)
+ do_size_property (node, decl, &node->min_width);
+ else if (strcmp (property_name, "min-height") == 0)
+ do_size_property (node, decl, &node->min_height);
+ else if (strcmp (property_name, "max-width") == 0)
+ do_size_property (node, decl, &node->max_width);
+ else if (strcmp (property_name, "max-height") == 0)
+ do_size_property (node, decl, &node->max_height);
+ }
+
+ /*
+ * Setting width sets max-width, min-width and -st-natural-width,
+ * unless one of them is set individually.
+ * Setting min-width sets natural width too, so that the minimum
+ * width reported by get_preferred_width() is always not greater
+ * than the natural width.
+ * The natural width in node->width is actually a lower bound, the
+ * actor is allowed to request something greater than that, but
+ * not greater than max-width.
+ * We don't need to clamp node->width to be less than max_width,
+ * that's done by adjust_preferred_width.
+ */
+ if (width != -1)
+ {
+ if (node->width == -1)
+ node->width = width;
+ if (node->min_width == -1)
+ node->min_width = width;
+ if (node->max_width == -1)
+ node->max_width = width;
+ }
+
+ if (node->width < node->min_width)
+ node->width = node->min_width;
+
+ if (height != -1)
+ {
+ if (node->height == -1)
+ node->height = height;
+ if (node->min_height == -1)
+ node->min_height = height;
+ if (node->max_height == -1)
+ node->max_height = height;
+ }
+
+ if (node->height < node->min_height)
+ node->height = node->min_height;
+}
+
+/**
+ * st_theme_node_get_border_width:
+ * @node: a #StThemeNode
+ * @side: a #StCorner
+ *
+ * Get the border width for @node on @side, in physical pixels.
+ *
+ * Returns: the border width in physical pixels
+ */
+int
+st_theme_node_get_border_width (StThemeNode *node,
+ StSide side)
+{
+ g_return_val_if_fail (ST_IS_THEME_NODE (node), 0.);
+ g_return_val_if_fail (side >= ST_SIDE_TOP && side <= ST_SIDE_LEFT, 0.);
+
+ _st_theme_node_ensure_geometry (node);
+
+ return node->border_width[side];
+}
+
+/**
+ * st_theme_node_get_border_radius:
+ * @node: a #StThemeNode
+ * @corner: a #StCorner
+ *
+ * Get the border radius for @node at @corner, in physical pixels.
+ *
+ * Returns: the border radius in physical pixels
+ */
+int
+st_theme_node_get_border_radius (StThemeNode *node,
+ StCorner corner)
+{
+ g_return_val_if_fail (ST_IS_THEME_NODE (node), 0.);
+ g_return_val_if_fail (corner >= ST_CORNER_TOPLEFT && corner <= ST_CORNER_BOTTOMLEFT, 0.);
+
+ _st_theme_node_ensure_geometry (node);
+
+ return node->border_radius[corner];
+}
+
+/**
+ * st_theme_node_get_outline_width:
+ * @node: a #StThemeNode
+ *
+ * Get the width of the outline for @node, in physical pixels.
+ *
+ * Returns: the width in physical pixels
+ */
+int
+st_theme_node_get_outline_width (StThemeNode *node)
+{
+ g_return_val_if_fail (ST_IS_THEME_NODE (node), 0);
+
+ _st_theme_node_ensure_geometry (node);
+
+ return node->outline_width;
+}
+
+/**
+ * st_theme_node_get_outline_color:
+ * @node: a #StThemeNode
+ * @color: (out caller-allocates): location to store the color
+ *
+ * Gets the color of @node's outline.
+ */
+void
+st_theme_node_get_outline_color (StThemeNode *node,
+ ClutterColor *color)
+{
+ g_return_if_fail (ST_IS_THEME_NODE (node));
+
+ _st_theme_node_ensure_geometry (node);
+
+ *color = node->outline_color;
+}
+
+/**
+ * st_theme_node_get_width:
+ * @node: a #StThemeNode
+ *
+ * Get the width for @node, in physical pixels.
+ *
+ * Returns: the width in physical pixels
+ */
+int
+st_theme_node_get_width (StThemeNode *node)
+{
+ g_return_val_if_fail (ST_IS_THEME_NODE (node), -1);
+
+ _st_theme_node_ensure_geometry (node);
+ return node->width;
+}
+
+/**
+ * st_theme_node_get_height:
+ * @node: a #StThemeNode
+ *
+ * Get the height for @node, in physical pixels.
+ *
+ * Returns: the height in physical pixels
+ */
+int
+st_theme_node_get_height (StThemeNode *node)
+{
+ g_return_val_if_fail (ST_IS_THEME_NODE (node), -1);
+
+ _st_theme_node_ensure_geometry (node);
+ return node->height;
+}
+
+/**
+ * st_theme_node_get_min_width:
+ * @node: a #StThemeNode
+ *
+ * Get the minimum width for @node, in physical pixels.
+ *
+ * Returns: the minimum width in physical pixels
+ */
+int
+st_theme_node_get_min_width (StThemeNode *node)
+{
+ g_return_val_if_fail (ST_IS_THEME_NODE (node), -1);
+
+ _st_theme_node_ensure_geometry (node);
+ return node->min_width;
+}
+
+/**
+ * st_theme_node_get_min_height:
+ * @node: a #StThemeNode
+ *
+ * Get the minimum height for @node, in physical pixels.
+ *
+ * Returns: the minimum height in physical pixels
+ */
+int
+st_theme_node_get_min_height (StThemeNode *node)
+{
+ g_return_val_if_fail (ST_IS_THEME_NODE (node), -1);
+
+ _st_theme_node_ensure_geometry (node);
+ return node->min_height;
+}
+
+/**
+ * st_theme_node_get_max_width:
+ * @node: a #StThemeNode
+ *
+ * Get the maximum width for @node, in physical pixels.
+ *
+ * Returns: the maximum width in physical pixels
+ */
+int
+st_theme_node_get_max_width (StThemeNode *node)
+{
+ g_return_val_if_fail (ST_IS_THEME_NODE (node), -1);
+
+ _st_theme_node_ensure_geometry (node);
+ return node->max_width;
+}
+
+/**
+ * st_theme_node_get_max_height:
+ * @node: a #StThemeNode
+ *
+ * Get the maximum height for @node, in physical pixels.
+ *
+ * Returns: the maximum height in physical pixels
+ */
+int
+st_theme_node_get_max_height (StThemeNode *node)
+{
+ g_return_val_if_fail (ST_IS_THEME_NODE (node), -1);
+
+ _st_theme_node_ensure_geometry (node);
+ return node->max_height;
+}
+
+void
+_st_theme_node_ensure_background (StThemeNode *node)
+{
+ int i;
+
+ if (node->background_computed)
+ return;
+
+ node->background_repeat = FALSE;
+ node->background_computed = TRUE;
+ node->background_color = TRANSPARENT_COLOR;
+ node->background_gradient_type = ST_GRADIENT_NONE;
+ node->background_position_set = FALSE;
+ node->background_size = ST_BACKGROUND_SIZE_AUTO;
+
+ ensure_properties (node);
+
+ for (i = 0; i < node->n_properties; i++)
+ {
+ CRDeclaration *decl = node->properties[i];
+ const char *property_name = decl->property->stryng->str;
+
+ if (g_str_has_prefix (property_name, "background"))
+ property_name += 10;
+ else
+ continue;
+
+ if (strcmp (property_name, "") == 0)
+ {
+ /* We're very liberal here ... if we recognize any term in the expression we take it, and
+ * we ignore the rest. The actual specification is:
+ *
+ * background: [<'background-color'> || <'background-image'> || <'background-repeat'> || <'background-attachment'> || <'background-position'>] | inherit
+ */
+
+ CRTerm *term;
+ /* background: property sets all terms to specified or default values */
+ node->background_color = TRANSPARENT_COLOR;
+ g_clear_object (&node->background_image);
+ node->background_position_set = FALSE;
+ node->background_size = ST_BACKGROUND_SIZE_AUTO;
+
+ for (term = decl->value; term; term = term->next)
+ {
+ GetFromTermResult result = get_color_from_term (node, term, &node->background_color);
+ if (result == VALUE_FOUND)
+ {
+ /* color stored in node->background_color */
+ }
+ else if (result == VALUE_INHERIT)
+ {
+ if (node->parent_node)
+ {
+ st_theme_node_get_background_color (node->parent_node, &node->background_color);
+ node->background_image = g_object_ref (st_theme_node_get_background_image (node->parent_node));
+ }
+ }
+ else if (term_is_none (term))
+ {
+ /* leave node->background_color as transparent */
+ }
+ else if (term->type == TERM_URI)
+ {
+ CRStyleSheet *base_stylesheet;
+ GFile *file;
+
+ if (decl->parent_statement != NULL)
+ base_stylesheet = decl->parent_statement->parent_sheet;
+ else
+ base_stylesheet = NULL;
+
+ file = _st_theme_resolve_url (node->theme,
+ base_stylesheet,
+ term->content.str->stryng->str);
+
+ node->background_image = file;
+ }
+ }
+ }
+ else if (strcmp (property_name, "-position") == 0)
+ {
+ GetFromTermResult result = get_length_from_term_int (node, decl->value, FALSE, &node->background_position_x);
+ if (result == VALUE_NOT_FOUND)
+ {
+ node->background_position_set = FALSE;
+ continue;
+ }
+ else
+ node->background_position_set = TRUE;
+
+ result = get_length_from_term_int (node, decl->value->next, FALSE, &node->background_position_y);
+
+ if (result == VALUE_NOT_FOUND)
+ {
+ node->background_position_set = FALSE;
+ continue;
+ }
+ else
+ node->background_position_set = TRUE;
+ }
+ else if (strcmp (property_name, "-repeat") == 0)
+ {
+ if (decl->value->type == TERM_IDENT)
+ {
+ if (strcmp (decl->value->content.str->stryng->str, "repeat") == 0)
+ node->background_repeat = TRUE;
+ }
+ }
+ else if (strcmp (property_name, "-size") == 0)
+ {
+ if (decl->value->type == TERM_IDENT)
+ {
+ if (strcmp (decl->value->content.str->stryng->str, "contain") == 0)
+ node->background_size = ST_BACKGROUND_SIZE_CONTAIN;
+ else if (strcmp (decl->value->content.str->stryng->str, "cover") == 0)
+ node->background_size = ST_BACKGROUND_SIZE_COVER;
+ else if ((strcmp (decl->value->content.str->stryng->str, "auto") == 0) && (decl->value->next) && (decl->value->next->type == TERM_NUMBER))
+ {
+ GetFromTermResult result = get_length_from_term_int (node, decl->value->next, FALSE, &node->background_size_h);
+
+ node->background_size_w = -1;
+ node->background_size = (result == VALUE_FOUND) ? ST_BACKGROUND_SIZE_FIXED : ST_BACKGROUND_SIZE_AUTO;
+ }
+ else
+ node->background_size = ST_BACKGROUND_SIZE_AUTO;
+ }
+ else if (decl->value->type == TERM_NUMBER)
+ {
+ GetFromTermResult result = get_length_from_term_int (node, decl->value, FALSE, &node->background_size_w);
+ if (result == VALUE_NOT_FOUND)
+ continue;
+
+ node->background_size = ST_BACKGROUND_SIZE_FIXED;
+
+ if ((decl->value->next) && (decl->value->next->type == TERM_NUMBER))
+ {
+ result = get_length_from_term_int (node, decl->value->next, FALSE, &node->background_size_h);
+
+ if (result == VALUE_FOUND)
+ continue;
+ }
+ node->background_size_h = -1;
+ }
+ else
+ node->background_size = ST_BACKGROUND_SIZE_AUTO;
+ }
+ else if (strcmp (property_name, "-color") == 0)
+ {
+ GetFromTermResult result;
+
+ if (decl->value == NULL || decl->value->next != NULL)
+ continue;
+
+ result = get_color_from_term (node, decl->value, &node->background_color);
+ if (result == VALUE_FOUND)
+ {
+ /* color stored in node->background_color */
+ }
+ else if (result == VALUE_INHERIT)
+ {
+ if (node->parent_node)
+ st_theme_node_get_background_color (node->parent_node, &node->background_color);
+ }
+ }
+ else if (strcmp (property_name, "-image") == 0)
+ {
+ if (decl->value == NULL || decl->value->next != NULL)
+ continue;
+
+ if (decl->value->type == TERM_URI)
+ {
+ CRStyleSheet *base_stylesheet;
+
+ if (decl->parent_statement != NULL)
+ base_stylesheet = decl->parent_statement->parent_sheet;
+ else
+ base_stylesheet = NULL;
+
+ g_clear_object (&node->background_image);
+ node->background_image = _st_theme_resolve_url (node->theme,
+ base_stylesheet,
+ decl->value->content.str->stryng->str);
+ }
+ else if (term_is_inherit (decl->value))
+ {
+ g_clear_object (&node->background_image);
+ node->background_image = g_object_ref (st_theme_node_get_background_image (node->parent_node));
+ }
+ else if (term_is_none (decl->value))
+ {
+ g_clear_object (&node->background_image);
+ }
+ }
+ else if (strcmp (property_name, "-gradient-direction") == 0)
+ {
+ CRTerm *term = decl->value;
+ if (strcmp (term->content.str->stryng->str, "vertical") == 0)
+ {
+ node->background_gradient_type = ST_GRADIENT_VERTICAL;
+ }
+ else if (strcmp (term->content.str->stryng->str, "horizontal") == 0)
+ {
+ node->background_gradient_type = ST_GRADIENT_HORIZONTAL;
+ }
+ else if (strcmp (term->content.str->stryng->str, "radial") == 0)
+ {
+ node->background_gradient_type = ST_GRADIENT_RADIAL;
+ }
+ else if (strcmp (term->content.str->stryng->str, "none") == 0)
+ {
+ node->background_gradient_type = ST_GRADIENT_NONE;
+ }
+ else
+ {
+ g_warning ("Unrecognized background-gradient-direction \"%s\"",
+ term->content.str->stryng->str);
+ }
+ }
+ else if (strcmp (property_name, "-gradient-start") == 0)
+ {
+ get_color_from_term (node, decl->value, &node->background_color);
+ }
+ else if (strcmp (property_name, "-gradient-end") == 0)
+ {
+ get_color_from_term (node, decl->value, &node->background_gradient_end);
+ }
+ }
+}
+
+/**
+ * st_theme_node_get_background_color:
+ * @node: a #StThemeNode
+ * @color: (out caller-allocates): location to store the color
+ *
+ * Gets @node's background color.
+ */
+void
+st_theme_node_get_background_color (StThemeNode *node,
+ ClutterColor *color)
+{
+ g_return_if_fail (ST_IS_THEME_NODE (node));
+
+ _st_theme_node_ensure_background (node);
+
+ *color = node->background_color;
+}
+
+/**
+ * st_theme_node_get_background_image:
+ * @node: a #StThemeNode
+ *
+ * Returns: (transfer none): @node's background image.
+ */
+GFile *
+st_theme_node_get_background_image (StThemeNode *node)
+{
+ g_return_val_if_fail (ST_IS_THEME_NODE (node), NULL);
+
+ _st_theme_node_ensure_background (node);
+
+ return node->background_image;
+}
+
+/**
+ * st_theme_node_get_foreground_color:
+ * @node: a #StThemeNode
+ * @color: (out caller-allocates): location to store the color
+ *
+ * Gets @node's foreground color.
+ */
+void
+st_theme_node_get_foreground_color (StThemeNode *node,
+ ClutterColor *color)
+{
+ g_return_if_fail (ST_IS_THEME_NODE (node));
+
+ if (!node->foreground_computed)
+ {
+ int i;
+
+ node->foreground_computed = TRUE;
+
+ ensure_properties (node);
+
+ for (i = node->n_properties - 1; i >= 0; i--)
+ {
+ CRDeclaration *decl = node->properties[i];
+
+ if (strcmp (decl->property->stryng->str, "color") == 0)
+ {
+ GetFromTermResult result = get_color_from_term (node, decl->value, &node->foreground_color);
+ if (result == VALUE_FOUND)
+ goto out;
+ else if (result == VALUE_INHERIT)
+ break;
+ }
+ }
+
+ if (node->parent_node)
+ st_theme_node_get_foreground_color (node->parent_node, &node->foreground_color);
+ else
+ node->foreground_color = BLACK_COLOR; /* default to black */
+ }
+
+ out:
+ *color = node->foreground_color;
+}
+
+
+/**
+ * st_theme_node_get_background_gradient:
+ * @node: A #StThemeNode
+ * @type: (out): Type of gradient
+ * @start: (out caller-allocates): Color at start of gradient
+ * @end: (out caller-allocates): Color at end of gradient
+ *
+ * The @start and @end arguments will only be set if @type is not #ST_GRADIENT_NONE.
+ */
+void
+st_theme_node_get_background_gradient (StThemeNode *node,
+ StGradientType *type,
+ ClutterColor *start,
+ ClutterColor *end)
+{
+ g_return_if_fail (ST_IS_THEME_NODE (node));
+
+ _st_theme_node_ensure_background (node);
+
+ *type = node->background_gradient_type;
+ if (*type != ST_GRADIENT_NONE)
+ {
+ *start = node->background_color;
+ *end = node->background_gradient_end;
+ }
+}
+
+/**
+ * st_theme_node_get_border_color:
+ * @node: a #StThemeNode
+ * @side: a #StSide
+ * @color: (out caller-allocates): location to store the color
+ *
+ * Gets the color of @node's border on @side
+ */
+void
+st_theme_node_get_border_color (StThemeNode *node,
+ StSide side,
+ ClutterColor *color)
+{
+ g_return_if_fail (ST_IS_THEME_NODE (node));
+ g_return_if_fail (side >= ST_SIDE_TOP && side <= ST_SIDE_LEFT);
+
+ _st_theme_node_ensure_geometry (node);
+
+ *color = node->border_color[side];
+}
+
+/**
+ * st_theme_node_get_padding:
+ * @node: a #StThemeNode
+ * @side: a #StSide
+ *
+ * Get the padding for @node on @side, in physical pixels. This corresponds to
+ * the CSS properties such as `padding-top`.
+ *
+ * Returns: the padding size in physical pixels
+ */
+double
+st_theme_node_get_padding (StThemeNode *node,
+ StSide side)
+{
+ g_return_val_if_fail (ST_IS_THEME_NODE (node), 0.);
+ g_return_val_if_fail (side >= ST_SIDE_TOP && side <= ST_SIDE_LEFT, 0.);
+
+ _st_theme_node_ensure_geometry (node);
+
+ return node->padding[side];
+}
+
+/**
+ * st_theme_node_get_margin:
+ * @node: a #StThemeNode
+ * @side: a #StSide
+ *
+ * Get the margin for @node on @side, in physical pixels. This corresponds to
+ * the CSS properties such as `margin-top`.
+ *
+ * Returns: the margin size in physical pixels
+ */
+double
+st_theme_node_get_margin (StThemeNode *node,
+ StSide side)
+{
+ g_return_val_if_fail (ST_IS_THEME_NODE (node), 0.);
+ g_return_val_if_fail (side >= ST_SIDE_TOP && side <= ST_SIDE_LEFT, 0.);
+
+ _st_theme_node_ensure_geometry (node);
+
+ return node->margin[side];
+}
+
+/**
+ * st_theme_node_get_transition_duration:
+ * @node: an #StThemeNode
+ *
+ * Get the value of the transition-duration property, which
+ * specifies the transition time between the previous #StThemeNode
+ * and @node.
+ *
+ * Returns: the node's transition duration in milliseconds
+ */
+int
+st_theme_node_get_transition_duration (StThemeNode *node)
+{
+ StSettings *settings;
+ gdouble value = 0.0;
+ gdouble factor;
+
+ g_return_val_if_fail (ST_IS_THEME_NODE (node), 0);
+
+ settings = st_settings_get ();
+ g_object_get (settings, "slow-down-factor", &factor, NULL);
+
+ if (node->transition_duration > -1)
+ return factor * node->transition_duration;
+
+ st_theme_node_lookup_time (node, "transition-duration", FALSE, &value);
+
+ node->transition_duration = (int)value;
+
+ return factor * node->transition_duration;
+}
+
+/**
+ * st_theme_node_get_icon_style:
+ * @node: a #StThemeNode
+ *
+ * Get the icon style for @node (eg. symbolic, regular). This corresponds to the
+ * special `-st-icon-style` CSS property.
+ *
+ * Returns: the icon style for @node
+ */
+StIconStyle
+st_theme_node_get_icon_style (StThemeNode *node)
+{
+ int i;
+
+ g_return_val_if_fail (ST_IS_THEME_NODE(node), ST_ICON_STYLE_REQUESTED);
+
+ ensure_properties (node);
+
+ for (i = node->n_properties - 1; i >= 0; i--)
+ {
+ CRDeclaration *decl = node->properties[i];
+
+ if (strcmp (decl->property->stryng->str, "-st-icon-style") == 0)
+ {
+ CRTerm *term;
+
+ for (term = decl->value; term; term = term->next)
+ {
+ if (term->type != TERM_IDENT)
+ goto next_decl;
+
+ if (strcmp (term->content.str->stryng->str, "requested") == 0)
+ return ST_ICON_STYLE_REQUESTED;
+ else if (strcmp (term->content.str->stryng->str, "regular") == 0)
+ return ST_ICON_STYLE_REGULAR;
+ else if (strcmp (term->content.str->stryng->str, "symbolic") == 0)
+ return ST_ICON_STYLE_SYMBOLIC;
+ else
+ g_warning ("Unknown -st-icon-style \"%s\"",
+ term->content.str->stryng->str);
+ }
+ }
+
+ next_decl:
+ ;
+ }
+
+ if (node->parent_node)
+ return st_theme_node_get_icon_style (node->parent_node);
+
+ return ST_ICON_STYLE_REQUESTED;
+}
+
+/**
+ * st_theme_node_get_text_decoration
+ * @node: a #StThemeNode
+ *
+ * Get the text decoration for @node (eg. underline, line-through, etc).
+ *
+ * Returns: the text decoration for @node
+ */
+StTextDecoration
+st_theme_node_get_text_decoration (StThemeNode *node)
+{
+ int i;
+
+ g_return_val_if_fail (ST_IS_THEME_NODE(node), 0);
+
+ ensure_properties (node);
+
+ for (i = node->n_properties - 1; i >= 0; i--)
+ {
+ CRDeclaration *decl = node->properties[i];
+
+ if (strcmp (decl->property->stryng->str, "text-decoration") == 0)
+ {
+ CRTerm *term = decl->value;
+ StTextDecoration decoration = 0;
+
+ /* Specification is none | [ underline || overline || line-through || blink ] | inherit
+ *
+ * We're a bit more liberal, and for example treat 'underline none' as the same as
+ * none.
+ */
+ for (; term; term = term->next)
+ {
+ if (term->type != TERM_IDENT)
+ goto next_decl;
+
+ if (strcmp (term->content.str->stryng->str, "none") == 0)
+ {
+ return 0;
+ }
+ else if (strcmp (term->content.str->stryng->str, "inherit") == 0)
+ {
+ if (node->parent_node)
+ return st_theme_node_get_text_decoration (node->parent_node);
+ }
+ else if (strcmp (term->content.str->stryng->str, "underline") == 0)
+ {
+ decoration |= ST_TEXT_DECORATION_UNDERLINE;
+ }
+ else if (strcmp (term->content.str->stryng->str, "overline") == 0)
+ {
+ decoration |= ST_TEXT_DECORATION_OVERLINE;
+ }
+ else if (strcmp (term->content.str->stryng->str, "line-through") == 0)
+ {
+ decoration |= ST_TEXT_DECORATION_LINE_THROUGH;
+ }
+ else if (strcmp (term->content.str->stryng->str, "blink") == 0)
+ {
+ decoration |= ST_TEXT_DECORATION_BLINK;
+ }
+ else
+ {
+ goto next_decl;
+ }
+ }
+
+ return decoration;
+ }
+
+ next_decl:
+ ;
+ }
+
+ return 0;
+}
+
+/**
+ * st_theme_node_get_text_align:
+ * @node: a #StThemeNode
+ *
+ * Get the text alignment of @node.
+ *
+ * Returns: the alignment of text for @node
+ */
+StTextAlign
+st_theme_node_get_text_align(StThemeNode *node)
+{
+ int i;
+
+ g_return_val_if_fail (ST_IS_THEME_NODE(node), ST_TEXT_ALIGN_LEFT);
+
+ ensure_properties(node);
+
+ for (i = node->n_properties - 1; i >= 0; i--)
+ {
+ CRDeclaration *decl = node->properties[i];
+
+ if (strcmp(decl->property->stryng->str, "text-align") == 0)
+ {
+ CRTerm *term = decl->value;
+
+ if (term->type != TERM_IDENT || term->next)
+ continue;
+
+ if (strcmp(term->content.str->stryng->str, "inherit") == 0)
+ {
+ if (node->parent_node)
+ return st_theme_node_get_text_align(node->parent_node);
+ return ST_TEXT_ALIGN_LEFT;
+ }
+ else if (strcmp(term->content.str->stryng->str, "left") == 0)
+ {
+ return ST_TEXT_ALIGN_LEFT;
+ }
+ else if (strcmp(term->content.str->stryng->str, "right") == 0)
+ {
+ return ST_TEXT_ALIGN_RIGHT;
+ }
+ else if (strcmp(term->content.str->stryng->str, "center") == 0)
+ {
+ return ST_TEXT_ALIGN_CENTER;
+ }
+ else if (strcmp(term->content.str->stryng->str, "justify") == 0)
+ {
+ return ST_TEXT_ALIGN_JUSTIFY;
+ }
+ }
+ }
+ if(node->parent_node)
+ return st_theme_node_get_text_align(node->parent_node);
+
+ if (clutter_get_default_text_direction () == CLUTTER_TEXT_DIRECTION_RTL)
+ return ST_TEXT_ALIGN_RIGHT;
+ return ST_TEXT_ALIGN_LEFT;
+}
+
+/**
+ * st_theme_node_get_letter_spacing:
+ * @node: a #StThemeNode
+ *
+ * Gets the value for the letter-spacing style property, in physical pixels.
+ *
+ * Returns: the value of the letter-spacing property, if
+ * found, or zero if such property has not been found.
+ */
+gdouble
+st_theme_node_get_letter_spacing (StThemeNode *node)
+{
+ gdouble spacing = 0.;
+
+ g_return_val_if_fail (ST_IS_THEME_NODE (node), spacing);
+
+ ensure_properties (node);
+
+ st_theme_node_lookup_length (node, "letter-spacing", FALSE, &spacing);
+ return spacing;
+}
+
+static gboolean
+font_family_from_terms (CRTerm *term,
+ char **family)
+{
+ GString *family_string;
+ gboolean result = FALSE;
+ gboolean last_was_quoted = FALSE;
+
+ if (!term)
+ return FALSE;
+
+ family_string = g_string_new (NULL);
+
+ while (term)
+ {
+ if (term->type != TERM_STRING && term->type != TERM_IDENT)
+ {
+ goto out;
+ }
+
+ if (family_string->len > 0)
+ {
+ if (term->the_operator != COMMA && term->the_operator != NO_OP)
+ goto out;
+ /* Can concatenate two bare words, but not two quoted strings */
+ if ((term->the_operator == NO_OP && last_was_quoted) || term->type == TERM_STRING)
+ goto out;
+
+ if (term->the_operator == NO_OP)
+ g_string_append (family_string, " ");
+ else
+ g_string_append (family_string, ",");
+ }
+ else
+ {
+ if (term->the_operator != NO_OP)
+ goto out;
+ }
+
+ g_string_append (family_string, term->content.str->stryng->str);
+
+ term = term->next;
+ }
+
+ result = TRUE;
+
+ out:
+ if (result)
+ {
+ *family = g_string_free (family_string, FALSE);
+ return TRUE;
+ }
+ else
+ {
+ *family = g_string_free (family_string, TRUE);
+ return FALSE;
+ }
+}
+
+/* In points */
+static int font_sizes[] = {
+ 6 * 1024, /* xx-small */
+ 8 * 1024, /* x-small */
+ 10 * 1024, /* small */
+ 12 * 1024, /* medium */
+ 16 * 1024, /* large */
+ 20 * 1024, /* x-large */
+ 24 * 1024, /* xx-large */
+};
+
+static gboolean
+font_size_from_term (StThemeNode *node,
+ CRTerm *term,
+ double *size)
+{
+ if (term->type == TERM_IDENT)
+ {
+ double resolution = clutter_backend_get_resolution (clutter_get_default_backend ());
+ /* We work in integers to avoid double comparisons when converting back
+ * from a size in pixels to a logical size.
+ */
+ int size_points = (int)(0.5 + *size * (72. / resolution));
+
+ if (strcmp (term->content.str->stryng->str, "xx-small") == 0)
+ size_points = font_sizes[0];
+ else if (strcmp (term->content.str->stryng->str, "x-small") == 0)
+ size_points = font_sizes[1];
+ else if (strcmp (term->content.str->stryng->str, "small") == 0)
+ size_points = font_sizes[2];
+ else if (strcmp (term->content.str->stryng->str, "medium") == 0)
+ size_points = font_sizes[3];
+ else if (strcmp (term->content.str->stryng->str, "large") == 0)
+ size_points = font_sizes[4];
+ else if (strcmp (term->content.str->stryng->str, "x-large") == 0)
+ size_points = font_sizes[5];
+ else if (strcmp (term->content.str->stryng->str, "xx-large") == 0)
+ size_points = font_sizes[6];
+ else if (strcmp (term->content.str->stryng->str, "smaller") == 0)
+ {
+ /* Find the standard size equal to or smaller than the current size */
+ int i = 0;
+
+ while (i <= 6 && font_sizes[i] < size_points)
+ i++;
+
+ if (i > 6)
+ {
+ /* original size greater than any standard size */
+ size_points = (int)(0.5 + size_points / 1.2);
+ }
+ else
+ {
+ /* Go one smaller than that, if possible */
+ if (i > 0)
+ i--;
+
+ size_points = font_sizes[i];
+ }
+ }
+ else if (strcmp (term->content.str->stryng->str, "larger") == 0)
+ {
+ /* Find the standard size equal to or larger than the current size */
+ int i = 6;
+
+ while (i >= 0 && font_sizes[i] > size_points)
+ i--;
+
+ if (i < 0) /* original size smaller than any standard size */
+ i = 0;
+
+ /* Go one larger than that, if possible */
+ if (i < 6)
+ i++;
+
+ size_points = font_sizes[i];
+ }
+ else
+ {
+ return FALSE;
+ }
+
+ *size = size_points * (resolution / 72.);
+ return TRUE;
+
+ }
+ else if (term->type == TERM_NUMBER && term->content.num->type == NUM_PERCENTAGE)
+ {
+ *size *= term->content.num->val / 100.;
+ return TRUE;
+ }
+ else if (get_length_from_term (node, term, TRUE, size) == VALUE_FOUND)
+ {
+ /* Convert from pixels to Pango units */
+ *size *= 1024;
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static gboolean
+font_weight_from_term (CRTerm *term,
+ PangoWeight *weight,
+ gboolean *weight_absolute)
+{
+ if (term->type == TERM_NUMBER)
+ {
+ int weight_int;
+
+ /* The spec only allows numeric weights from 100-900, though Pango
+ * will handle any number. We just let anything through.
+ */
+ if (term->content.num->type != NUM_GENERIC)
+ return FALSE;
+
+ weight_int = (int)(0.5 + term->content.num->val);
+
+ *weight = weight_int;
+ *weight_absolute = TRUE;
+
+ }
+ else if (term->type == TERM_IDENT)
+ {
+ /* FIXME: handle INHERIT */
+
+ if (strcmp (term->content.str->stryng->str, "bold") == 0)
+ {
+ *weight = PANGO_WEIGHT_BOLD;
+ *weight_absolute = TRUE;
+ }
+ else if (strcmp (term->content.str->stryng->str, "normal") == 0)
+ {
+ *weight = PANGO_WEIGHT_NORMAL;
+ *weight_absolute = TRUE;
+ }
+ else if (strcmp (term->content.str->stryng->str, "bolder") == 0)
+ {
+ *weight = PANGO_WEIGHT_BOLD;
+ *weight_absolute = FALSE;
+ }
+ else if (strcmp (term->content.str->stryng->str, "lighter") == 0)
+ {
+ *weight = PANGO_WEIGHT_LIGHT;
+ *weight_absolute = FALSE;
+ }
+ else
+ {
+ return FALSE;
+ }
+
+ }
+ else
+ {
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static gboolean
+font_style_from_term (CRTerm *term,
+ PangoStyle *style)
+{
+ if (term->type != TERM_IDENT)
+ return FALSE;
+
+ /* FIXME: handle INHERIT */
+
+ if (strcmp (term->content.str->stryng->str, "normal") == 0)
+ *style = PANGO_STYLE_NORMAL;
+ else if (strcmp (term->content.str->stryng->str, "oblique") == 0)
+ *style = PANGO_STYLE_OBLIQUE;
+ else if (strcmp (term->content.str->stryng->str, "italic") == 0)
+ *style = PANGO_STYLE_ITALIC;
+ else
+ return FALSE;
+
+ return TRUE;
+}
+
+static gboolean
+font_variant_from_term (CRTerm *term,
+ PangoVariant *variant)
+{
+ if (term->type != TERM_IDENT)
+ return FALSE;
+
+ /* FIXME: handle INHERIT */
+
+ if (strcmp (term->content.str->stryng->str, "normal") == 0)
+ *variant = PANGO_VARIANT_NORMAL;
+ else if (strcmp (term->content.str->stryng->str, "small-caps") == 0)
+ *variant = PANGO_VARIANT_SMALL_CAPS;
+ else
+ return FALSE;
+
+ return TRUE;
+}
+
+/**
+ * st_theme_node_get_font:
+ * @node: a #StThemeNode
+ *
+ * Get the current font of @node as a #PangoFontDescription
+ *
+ * Returns: (transfer none): the current font
+ */
+const PangoFontDescription *
+st_theme_node_get_font (StThemeNode *node)
+{
+ /* Initialized despite _set flags to suppress compiler warnings */
+ PangoStyle font_style = PANGO_STYLE_NORMAL;
+ gboolean font_style_set = FALSE;
+ PangoVariant variant = PANGO_VARIANT_NORMAL;
+ gboolean variant_set = FALSE;
+ PangoWeight weight = PANGO_WEIGHT_NORMAL;
+ gboolean weight_absolute = TRUE;
+ gboolean weight_set = FALSE;
+ double size = 0.;
+ gboolean size_set = FALSE;
+
+ g_return_val_if_fail (ST_IS_THEME_NODE (node), NULL);
+
+ char *family = NULL;
+ double parent_size;
+ int i;
+
+ if (node->font_desc)
+ return node->font_desc;
+
+ node->font_desc = pango_font_description_copy (get_parent_font (node));
+ parent_size = pango_font_description_get_size (node->font_desc);
+ if (!pango_font_description_get_size_is_absolute (node->font_desc))
+ {
+ double resolution = clutter_backend_get_resolution (clutter_get_default_backend ());
+ parent_size *= (resolution / 72.);
+ }
+
+ ensure_properties (node);
+
+ for (i = 0; i < node->n_properties; i++)
+ {
+ CRDeclaration *decl = node->properties[i];
+
+ if (strcmp (decl->property->stryng->str, "font") == 0)
+ {
+ PangoStyle tmp_style = PANGO_STYLE_NORMAL;
+ PangoVariant tmp_variant = PANGO_VARIANT_NORMAL;
+ PangoWeight tmp_weight = PANGO_WEIGHT_NORMAL;
+ gboolean tmp_weight_absolute = TRUE;
+ double tmp_size;
+ CRTerm *term = decl->value;
+
+ /* A font specification starts with node/variant/weight
+ * in any order. Each is allowed to be specified only once,
+ * but we don't enforce that.
+ */
+ for (; term; term = term->next)
+ {
+ if (font_style_from_term (term, &tmp_style))
+ continue;
+ if (font_variant_from_term (term, &tmp_variant))
+ continue;
+ if (font_weight_from_term (term, &tmp_weight, &tmp_weight_absolute))
+ continue;
+
+ break;
+ }
+
+ /* The size is mandatory */
+
+ if (term == NULL || term->type != TERM_NUMBER)
+ {
+ g_warning ("Size missing from font property");
+ continue;
+ }
+
+ tmp_size = parent_size;
+ if (!font_size_from_term (node, term, &tmp_size))
+ {
+ g_warning ("Couldn't parse size in font property");
+ continue;
+ }
+
+ term = term->next;
+
+ if (term != NULL && term->type && TERM_NUMBER && term->the_operator == DIVIDE)
+ {
+ /* Ignore line-height specification */
+ term = term->next;
+ }
+
+ /* the font family is mandatory - it is a comma-separated list of
+ * names.
+ */
+ if (!font_family_from_terms (term, &family))
+ {
+ g_warning ("Couldn't parse family in font property");
+ continue;
+ }
+
+ font_style = tmp_style;
+ font_style_set = TRUE;
+ weight = tmp_weight;
+ weight_absolute = tmp_weight_absolute;
+ weight_set = TRUE;
+ variant = tmp_variant;
+ variant_set = TRUE;
+
+ size = tmp_size;
+ size_set = TRUE;
+
+ }
+ else if (strcmp (decl->property->stryng->str, "font-family") == 0)
+ {
+ if (!font_family_from_terms (decl->value, &family))
+ {
+ g_warning ("Couldn't parse family in font property");
+ continue;
+ }
+ }
+ else if (strcmp (decl->property->stryng->str, "font-weight") == 0)
+ {
+ if (decl->value == NULL || decl->value->next != NULL)
+ continue;
+
+ if (font_weight_from_term (decl->value, &weight, &weight_absolute))
+ weight_set = TRUE;
+ }
+ else if (strcmp (decl->property->stryng->str, "font-style") == 0)
+ {
+ if (decl->value == NULL || decl->value->next != NULL)
+ continue;
+
+ if (font_style_from_term (decl->value, &font_style))
+ font_style_set = TRUE;
+ }
+ else if (strcmp (decl->property->stryng->str, "font-variant") == 0)
+ {
+ if (decl->value == NULL || decl->value->next != NULL)
+ continue;
+
+ if (font_variant_from_term (decl->value, &variant))
+ variant_set = TRUE;
+ }
+ else if (strcmp (decl->property->stryng->str, "font-size") == 0)
+ {
+ gdouble tmp_size;
+ if (decl->value == NULL || decl->value->next != NULL)
+ continue;
+
+ tmp_size = parent_size;
+ if (font_size_from_term (node, decl->value, &tmp_size))
+ {
+ size = tmp_size;
+ size_set = TRUE;
+ }
+ }
+ }
+
+ if (family)
+ {
+ pango_font_description_set_family (node->font_desc, family);
+ g_free (family);
+ }
+
+ if (size_set)
+ pango_font_description_set_absolute_size (node->font_desc, size);
+
+ if (weight_set)
+ {
+ if (!weight_absolute)
+ {
+ /* bolder/lighter are supposed to switch between available styles, but with
+ * font substitution, that gets to be a pretty fuzzy concept. So we use
+ * a fixed step of 200. (The spec says 100, but that might not take us from
+ * normal to bold.
+ */
+
+ PangoWeight old_weight = pango_font_description_get_weight (node->font_desc);
+ if (weight == PANGO_WEIGHT_BOLD)
+ weight = old_weight + 200;
+ else
+ weight = old_weight - 200;
+
+ if (weight < 100)
+ weight = 100;
+ if (weight > 900)
+ weight = 900;
+ }
+
+ pango_font_description_set_weight (node->font_desc, weight);
+ }
+
+ if (font_style_set)
+ pango_font_description_set_style (node->font_desc, font_style);
+ if (variant_set)
+ pango_font_description_set_variant (node->font_desc, variant);
+
+ return node->font_desc;
+}
+
+/**
+ * st_theme_node_get_font_features:
+ * @node: a #StThemeNode
+ *
+ * Get the CSS font-features for @node.
+ *
+ * Returns: (transfer full): font-features as a string
+ */
+gchar *
+st_theme_node_get_font_features (StThemeNode *node)
+{
+ int i;
+
+ g_return_val_if_fail (ST_IS_THEME_NODE (node), NULL);
+
+ ensure_properties (node);
+
+ for (i = node->n_properties - 1; i >= 0; i--)
+ {
+ CRDeclaration *decl = node->properties[i];
+
+ if (strcmp (decl->property->stryng->str, "font-feature-settings") == 0)
+ {
+ CRTerm *term = decl->value;
+
+ if (!term->next && term->type == TERM_IDENT)
+ {
+ gchar *ident = term->content.str->stryng->str;
+
+ if (strcmp (ident, "inherit") == 0)
+ break;
+
+ if (strcmp (ident, "normal") == 0)
+ return NULL;
+ }
+
+ return (gchar *)cr_term_to_string (term);
+ }
+ }
+
+ return node->parent_node ? st_theme_node_get_font_features (node->parent_node) : NULL;
+}
+
+/**
+ * st_theme_node_get_border_image:
+ * @node: a #StThemeNode
+ *
+ * Gets the value for the border-image style property
+ *
+ * Returns: (transfer none): the border image, or %NULL
+ * if there is no border image.
+ */
+StBorderImage *
+st_theme_node_get_border_image (StThemeNode *node)
+{
+ int i;
+
+ g_return_val_if_fail (ST_IS_THEME_NODE (node), NULL);
+
+ if (node->border_image_computed)
+ return node->border_image;
+
+ node->border_image = NULL;
+ node->border_image_computed = TRUE;
+
+ ensure_properties (node);
+
+ for (i = node->n_properties - 1; i >= 0; i--)
+ {
+ CRDeclaration *decl = node->properties[i];
+
+ if (strcmp (decl->property->stryng->str, "border-image") == 0)
+ {
+ CRTerm *term = decl->value;
+ CRStyleSheet *base_stylesheet;
+ int borders[4];
+ int n_borders = 0;
+ int j;
+
+ const char *url;
+ int border_top;
+ int border_right;
+ int border_bottom;
+ int border_left;
+
+ GFile *file;
+
+ /* Support border-image: none; to suppress a previously specified border image */
+ if (term_is_none (term))
+ {
+ if (term->next == NULL)
+ return NULL;
+ else
+ goto next_property;
+ }
+
+ /* First term must be the URL to the image */
+ if (term->type != TERM_URI)
+ goto next_property;
+
+ url = term->content.str->stryng->str;
+
+ term = term->next;
+
+ /* Followed by 0 to 4 numbers or percentages. *Not lengths*. The interpretation
+ * of a number is supposed to be pixels if the image is pixel based, otherwise CSS pixels.
+ */
+ for (j = 0; j < 4; j++)
+ {
+ if (term == NULL)
+ break;
+
+ if (term->type != TERM_NUMBER)
+ goto next_property;
+
+ if (term->content.num->type == NUM_GENERIC)
+ {
+ borders[n_borders] = (int)(0.5 + term->content.num->val);
+ n_borders++;
+ }
+ else if (term->content.num->type == NUM_PERCENTAGE)
+ {
+ /* This would be easiest to support if we moved image handling into StBorderImage */
+ g_warning ("Percentages not supported for border-image");
+ goto next_property;
+ }
+ else
+ goto next_property;
+
+ term = term->next;
+ }
+
+ switch (n_borders)
+ {
+ case 0:
+ border_top = border_right = border_bottom = border_left = 0;
+ break;
+ case 1:
+ border_top = border_right = border_bottom = border_left = borders[0];
+ break;
+ case 2:
+ border_top = border_bottom = borders[0];
+ border_left = border_right = borders[1];
+ break;
+ case 3:
+ border_top = borders[0];
+ border_left = border_right = borders[1];
+ border_bottom = borders[2];
+ break;
+ case 4:
+ default:
+ border_top = borders[0];
+ border_right = borders[1];
+ border_bottom = borders[2];
+ border_left = borders[3];
+ break;
+ }
+
+ if (decl->parent_statement != NULL)
+ base_stylesheet = decl->parent_statement->parent_sheet;
+ else
+ base_stylesheet = NULL;
+
+ file = _st_theme_resolve_url (node->theme, base_stylesheet, url);
+
+ if (file == NULL)
+ goto next_property;
+
+ node->border_image = st_border_image_new (file,
+ border_top, border_right, border_bottom, border_left,
+ node->cached_scale_factor);
+
+ g_object_unref (file);
+
+ return node->border_image;
+ }
+
+ next_property:
+ ;
+ }
+
+ return NULL;
+}
+
+/**
+ * st_theme_node_get_horizontal_padding:
+ * @node: a #StThemeNode
+ *
+ * Gets the total horizontal padding (left + right padding), in physical pixels.
+ *
+ * Returns: the total horizontal padding in physical pixels
+ */
+double
+st_theme_node_get_horizontal_padding (StThemeNode *node)
+{
+ double padding = 0.0;
+
+ g_return_val_if_fail (ST_IS_THEME_NODE (node), padding);
+
+ padding += st_theme_node_get_padding (node, ST_SIDE_LEFT);
+ padding += st_theme_node_get_padding (node, ST_SIDE_RIGHT);
+
+ return padding;
+}
+
+/**
+ * st_theme_node_get_vertical_padding:
+ * @node: a #StThemeNode
+ *
+ * Gets the total vertical padding (top + bottom padding), in physical pixels.
+ *
+ * Returns: the total vertical padding in physical pixels
+ */
+double
+st_theme_node_get_vertical_padding (StThemeNode *node)
+{
+ double padding = 0.0;
+
+ g_return_val_if_fail (ST_IS_THEME_NODE (node), padding);
+
+ padding += st_theme_node_get_padding (node, ST_SIDE_TOP);
+ padding += st_theme_node_get_padding (node, ST_SIDE_BOTTOM);
+
+ return padding;
+}
+
+void
+_st_theme_node_apply_margins (StThemeNode *node,
+ ClutterActor *actor)
+{
+ g_return_if_fail (ST_IS_THEME_NODE (node));
+
+ _st_theme_node_ensure_geometry (node);
+
+ clutter_actor_set_margin_left (actor, st_theme_node_get_margin(node, ST_SIDE_LEFT));
+ clutter_actor_set_margin_right (actor, st_theme_node_get_margin(node, ST_SIDE_RIGHT));
+ clutter_actor_set_margin_top (actor, st_theme_node_get_margin(node, ST_SIDE_TOP));
+ clutter_actor_set_margin_bottom (actor, st_theme_node_get_margin(node, ST_SIDE_BOTTOM));
+}
+
+static GetFromTermResult
+parse_shadow_property (StThemeNode *node,
+ CRDeclaration *decl,
+ ClutterColor *color,
+ gdouble *xoffset,
+ gdouble *yoffset,
+ gdouble *blur,
+ gdouble *spread,
+ gboolean *inset,
+ gboolean *is_none)
+{
+ GetFromTermResult result;
+ CRTerm *term;
+ int n_offsets = 0;
+ *is_none = FALSE;
+
+ /* default values */
+ color->red = 0x0; color->green = 0x0; color->blue = 0x0; color->alpha = 0xff;
+ *xoffset = 0.;
+ *yoffset = 0.;
+ *blur = 0.;
+ *spread = 0.;
+ *inset = FALSE;
+
+ /* The CSS3 draft of the box-shadow property[0] is a lot stricter
+ * regarding the order of terms:
+ * If the 'inset' keyword is specified, it has to be first or last,
+ * and the color may not be mixed with the lengths; while we parse
+ * length values in the correct order, we allow for arbitrary
+ * placement of the color and 'inset' keyword.
+ *
+ * [0] http://www.w3.org/TR/css3-background/#box-shadow
+ */
+ for (term = decl->value; term; term = term->next)
+ {
+ /* if we found "none", we're all set with the default values */
+ if (term_is_none (term)) {
+ *is_none = TRUE;
+ return VALUE_FOUND;
+ }
+
+ if (term->type == TERM_NUMBER)
+ {
+ gdouble value;
+ gdouble multiplier;
+
+ multiplier = (term->unary_op == MINUS_UOP) ? -1. : 1.;
+ result = get_length_from_term (node, term, FALSE, &value);
+
+ if (result == VALUE_INHERIT)
+ {
+ /* we only allow inherit on the line by itself */
+ if (n_offsets > 0)
+ return VALUE_NOT_FOUND;
+ else
+ return VALUE_INHERIT;
+ }
+ else if (result == VALUE_FOUND)
+ {
+ switch (n_offsets++)
+ {
+ case 0:
+ *xoffset = multiplier * value;
+ break;
+ case 1:
+ *yoffset = multiplier * value;
+ break;
+ case 2:
+ if (multiplier < 0)
+ g_warning ("Negative blur values are "
+ "not allowed");
+ *blur = value;
+ break;
+ case 3:
+ if (multiplier < 0)
+ g_warning ("Negative spread values are "
+ "not allowed");
+ *spread = value;
+ break;
+ default:
+ g_warning ("Ignoring excess values in shadow definition");
+ break;
+ }
+ continue;
+ }
+ }
+ else if (term->type == TERM_IDENT &&
+ strcmp (term->content.str->stryng->str, "inset") == 0)
+ {
+ *inset = TRUE;
+ continue;
+ }
+
+ result = get_color_from_term (node, term, color);
+
+ if (result == VALUE_INHERIT)
+ {
+ if (n_offsets > 0)
+ return VALUE_NOT_FOUND;
+ else
+ return VALUE_INHERIT;
+ }
+ else if (result == VALUE_FOUND)
+ {
+ continue;
+ }
+ }
+
+ /* The only required terms are the x and y offsets
+ */
+ if (n_offsets >= 2)
+ return VALUE_FOUND;
+ else
+ return VALUE_NOT_FOUND;
+}
+
+/**
+ * st_theme_node_lookup_shadow:
+ * @node: a #StThemeNode
+ * @property_name: The name of the shadow property
+ * @inherit: if %TRUE, if a value is not found for the property on the
+ * node, then it will be looked up on the parent node, and then on the
+ * parent's parent, and so forth. Note that if the property has a
+ * value of 'inherit' it will be inherited even if %FALSE is passed
+ * in for @inherit; this only affects the default behavior for inheritance.
+ * @shadow: (out): location to store the shadow
+ *
+ * If the property is not found, the value in the shadow variable will not
+ * be changed.
+ *
+ * Generically looks up a property containing a set of shadow values. When
+ * specific getters (like st_theme_node_get_box_shadow ()) exist, they
+ * should be used instead. They are cached, so more efficient, and have
+ * handling for shortcut properties and other details of CSS.
+ *
+ * See also st_theme_node_get_shadow(), which provides a simpler API.
+ *
+ * Returns: %TRUE if the property was found in the properties for this
+ * theme node (or in the properties of parent nodes when inheriting.), %FALSE
+ * if the property was not found, or was explicitly set to 'none'.
+ */
+gboolean
+st_theme_node_lookup_shadow (StThemeNode *node,
+ const char *property_name,
+ gboolean inherit,
+ StShadow **shadow)
+{
+ ClutterColor color = { 0., };
+ gdouble xoffset = 0.;
+ gdouble yoffset = 0.;
+ gdouble blur = 0.;
+ gdouble spread = 0.;
+ gboolean inset = FALSE;
+ gboolean is_none = FALSE;
+
+ int i;
+
+ g_return_val_if_fail (ST_IS_THEME_NODE (node), FALSE);
+ g_return_val_if_fail (property_name != NULL, FALSE);
+
+ ensure_properties (node);
+
+ for (i = node->n_properties - 1; i >= 0; i--)
+ {
+ CRDeclaration *decl = node->properties[i];
+
+ if (strcmp (decl->property->stryng->str, property_name) == 0)
+ {
+ GetFromTermResult result = parse_shadow_property (node,
+ decl,
+ &color,
+ &xoffset,
+ &yoffset,
+ &blur,
+ &spread,
+ &inset,
+ &is_none);
+ if (result == VALUE_FOUND)
+ {
+ if (is_none)
+ return FALSE;
+
+ *shadow = st_shadow_new (&color,
+ xoffset, yoffset,
+ blur, spread,
+ inset);
+ return TRUE;
+ }
+ else if (result == VALUE_INHERIT)
+ {
+ if (node->parent_node)
+ return st_theme_node_lookup_shadow (node->parent_node,
+ property_name,
+ inherit,
+ shadow);
+ else
+ break;
+ }
+ }
+ }
+
+ if (inherit && node->parent_node)
+ return st_theme_node_lookup_shadow (node->parent_node,
+ property_name,
+ inherit,
+ shadow);
+
+ return FALSE;
+}
+
+/**
+ * st_theme_node_get_shadow:
+ * @node: a #StThemeNode
+ * @property_name: The name of the shadow property
+ *
+ * Generically looks up a property containing a set of shadow values. When
+ * specific getters (like st_theme_node_get_box_shadow()) exist, they
+ * should be used instead. They are cached, so more efficient, and have
+ * handling for shortcut properties and other details of CSS.
+ *
+ * Like st_theme_get_length(), this does not print a warning if the property is
+ * not found; it just returns %NULL
+ *
+ * See also st_theme_node_lookup_shadow (), which provides more options.
+ *
+ * Returns: (nullable) (transfer full): the shadow, or %NULL if the property was
+ * not found.
+ */
+StShadow *
+st_theme_node_get_shadow (StThemeNode *node,
+ const char *property_name)
+{
+ StShadow *shadow;
+
+ if (st_theme_node_lookup_shadow (node, property_name, FALSE, &shadow))
+ return shadow;
+ else
+ return NULL;
+}
+
+/**
+ * st_theme_node_get_box_shadow:
+ * @node: a #StThemeNode
+ *
+ * Gets the value for the box-shadow style property
+ *
+ * Returns: (nullable) (transfer none): the node's shadow, or %NULL
+ * if node has no shadow
+ */
+StShadow *
+st_theme_node_get_box_shadow (StThemeNode *node)
+{
+ StShadow *shadow;
+
+ g_return_val_if_fail (ST_IS_THEME_NODE (node), NULL);
+
+ if (node->box_shadow_computed)
+ return node->box_shadow;
+
+ node->box_shadow = NULL;
+ node->box_shadow_computed = TRUE;
+
+ if (st_theme_node_lookup_shadow (node,
+ "box-shadow",
+ FALSE,
+ &shadow))
+ {
+ node->box_shadow = shadow;
+
+ return node->box_shadow;
+ }
+
+ return NULL;
+}
+
+/**
+ * st_theme_node_get_background_image_shadow:
+ * @node: a #StThemeNode
+ *
+ * Gets the value for the -st-background-image-shadow style property
+ *
+ * Returns: (nullable) (transfer none): the node's background image shadow, or
+ * %NULL if node has no such shadow
+ */
+StShadow *
+st_theme_node_get_background_image_shadow (StThemeNode *node)
+{
+ StShadow *shadow;
+
+ g_return_val_if_fail (ST_IS_THEME_NODE (node), NULL);
+
+ if (node->background_image_shadow_computed)
+ return node->background_image_shadow;
+
+ node->background_image_shadow = NULL;
+ node->background_image_shadow_computed = TRUE;
+
+ if (st_theme_node_lookup_shadow (node,
+ "-st-background-image-shadow",
+ FALSE,
+ &shadow))
+ {
+ if (shadow->inset)
+ {
+ g_warning ("The -st-background-image-shadow property does not "
+ "support inset shadows");
+ st_shadow_unref (shadow);
+ shadow = NULL;
+ }
+
+ node->background_image_shadow = shadow;
+
+ return node->background_image_shadow;
+ }
+
+ return NULL;
+}
+
+/**
+ * st_theme_node_get_text_shadow:
+ * @node: a #StThemeNode
+ *
+ * Gets the value for the text-shadow style property
+ *
+ * Returns: (nullable) (transfer none): the node's text-shadow, or %NULL
+ * if node has no text-shadow
+ */
+StShadow *
+st_theme_node_get_text_shadow (StThemeNode *node)
+{
+ StShadow *result = NULL;
+
+ g_return_val_if_fail (ST_IS_THEME_NODE (node), NULL);
+
+ if (node->text_shadow_computed)
+ return node->text_shadow;
+
+ ensure_properties (node);
+
+ if (!st_theme_node_lookup_shadow (node,
+ "text-shadow",
+ FALSE,
+ &result))
+ {
+ if (node->parent_node)
+ {
+ result = st_theme_node_get_text_shadow (node->parent_node);
+ if (result)
+ st_shadow_ref (result);
+ }
+ }
+
+ if (result && result->inset)
+ {
+ g_warning ("The text-shadow property does not support inset shadows");
+ st_shadow_unref (result);
+ result = NULL;
+ }
+
+ node->text_shadow = result;
+ node->text_shadow_computed = TRUE;
+
+ return result;
+}
+
+/**
+ * st_theme_node_get_icon_colors:
+ * @node: a #StThemeNode
+ *
+ * Gets the colors that should be used for colorizing symbolic icons according
+ * the style of this node.
+ *
+ * Returns: (transfer none): the icon colors to use for this theme node
+ */
+StIconColors *
+st_theme_node_get_icon_colors (StThemeNode *node)
+{
+ /* Foreground here will always be the same as st_theme_node_get_foreground_color(),
+ * but there's a loss of symmetry and little efficiency win if we try to exploit
+ * that. */
+
+ enum {
+ FOREGROUND = 1 << 0,
+ WARNING = 1 << 1,
+ ERROR = 1 << 2,
+ SUCCESS = 1 << 3
+ };
+
+ gboolean shared_with_parent;
+ int i;
+ ClutterColor color = { 0, };
+
+ guint still_need = FOREGROUND | WARNING | ERROR | SUCCESS;
+
+ g_return_val_if_fail (ST_IS_THEME_NODE (node), NULL);
+
+ if (node->icon_colors)
+ return node->icon_colors;
+
+ if (node->parent_node)
+ {
+ node->icon_colors = st_theme_node_get_icon_colors (node->parent_node);
+ shared_with_parent = TRUE;
+ }
+ else
+ {
+ node->icon_colors = st_icon_colors_new ();
+ node->icon_colors->foreground = BLACK_COLOR;
+ node->icon_colors->warning = DEFAULT_WARNING_COLOR;
+ node->icon_colors->error = DEFAULT_ERROR_COLOR;
+ node->icon_colors->success = DEFAULT_SUCCESS_COLOR;
+ shared_with_parent = FALSE;
+ }
+
+ ensure_properties (node);
+
+ for (i = node->n_properties - 1; i >= 0 && still_need != 0; i--)
+ {
+ CRDeclaration *decl = node->properties[i];
+ GetFromTermResult result = VALUE_NOT_FOUND;
+ guint found = 0;
+
+ if ((still_need & FOREGROUND) != 0 &&
+ strcmp (decl->property->stryng->str, "color") == 0)
+ {
+ found = FOREGROUND;
+ result = get_color_from_term (node, decl->value, &color);
+ }
+ else if ((still_need & WARNING) != 0 &&
+ strcmp (decl->property->stryng->str, "warning-color") == 0)
+ {
+ found = WARNING;
+ result = get_color_from_term (node, decl->value, &color);
+ }
+ else if ((still_need & ERROR) != 0 &&
+ strcmp (decl->property->stryng->str, "error-color") == 0)
+ {
+ found = ERROR;
+ result = get_color_from_term (node, decl->value, &color);
+ }
+ else if ((still_need & SUCCESS) != 0 &&
+ strcmp (decl->property->stryng->str, "success-color") == 0)
+ {
+ found = SUCCESS;
+ result = get_color_from_term (node, decl->value, &color);
+ }
+
+ if (result == VALUE_INHERIT)
+ {
+ still_need &= ~found;
+ }
+ else if (result == VALUE_FOUND)
+ {
+ still_need &= ~found;
+ if (shared_with_parent)
+ {
+ node->icon_colors = st_icon_colors_copy (node->icon_colors);
+ shared_with_parent = FALSE;
+ }
+
+ switch (found)
+ {
+ case FOREGROUND:
+ node->icon_colors->foreground = color;
+ break;
+ case WARNING:
+ node->icon_colors->warning = color;
+ break;
+ case ERROR:
+ node->icon_colors->error = color;
+ break;
+ case SUCCESS:
+ node->icon_colors->success = color;
+ break;
+ default:
+ g_assert_not_reached();
+ break;
+ }
+ }
+ }
+
+ if (shared_with_parent)
+ st_icon_colors_ref (node->icon_colors);
+
+ return node->icon_colors;
+}
+
+static float
+get_width_inc (StThemeNode *node)
+{
+ return ((int)(0.5 + node->border_width[ST_SIDE_LEFT]) + node->padding[ST_SIDE_LEFT] +
+ (int)(0.5 + node->border_width[ST_SIDE_RIGHT]) + node->padding[ST_SIDE_RIGHT]);
+}
+
+static float
+get_height_inc (StThemeNode *node)
+{
+ return ((int)(0.5 + node->border_width[ST_SIDE_TOP]) + node->padding[ST_SIDE_TOP] +
+ (int)(0.5 + node->border_width[ST_SIDE_BOTTOM]) + node->padding[ST_SIDE_BOTTOM]);
+}
+
+/**
+ * st_theme_node_adjust_for_height:
+ * @node: a #StThemeNode
+ * @for_height: (inout): the "for height" to adjust
+ *
+ * Adjusts a "for height" passed to clutter_actor_get_preferred_width() to
+ * account for borders and padding. This is a convenience function meant
+ * to be called from a get_preferred_width() method of a #ClutterActor
+ * subclass. The value after adjustment is the height available for the actor's
+ * content.
+ */
+void
+st_theme_node_adjust_for_height (StThemeNode *node,
+ float *for_height)
+{
+ g_return_if_fail (ST_IS_THEME_NODE (node));
+ g_return_if_fail (for_height != NULL);
+
+ if (*for_height >= 0)
+ {
+ float height_inc = get_height_inc (node);
+ *for_height = MAX (0, *for_height - height_inc);
+ }
+}
+
+/**
+ * st_theme_node_adjust_preferred_width:
+ * @node: a #StThemeNode
+ * @min_width_p: (inout) (nullable): the minimum width to adjust
+ * @natural_width_p: (inout): the natural width to adjust
+ *
+ * Adjusts the minimum and natural width computed for an actor by
+ * adding on the necessary space for borders and padding and taking
+ * into account any minimum or maximum width. This is a convenience
+ * function meant to be called from the get_preferred_width() method
+ * of a #ClutterActor subclass
+ */
+void
+st_theme_node_adjust_preferred_width (StThemeNode *node,
+ float *min_width_p,
+ float *natural_width_p)
+{
+ float width_inc;
+
+ g_return_if_fail (ST_IS_THEME_NODE (node));
+
+ _st_theme_node_ensure_geometry (node);
+
+ width_inc = get_width_inc (node);
+
+ if (min_width_p)
+ {
+ if (node->min_width != -1)
+ *min_width_p = node->min_width;
+ *min_width_p += width_inc;
+ }
+
+ if (natural_width_p)
+ {
+ if (node->width != -1)
+ *natural_width_p = MAX (*natural_width_p, node->width);
+ if (node->max_width != -1)
+ *natural_width_p = MIN (*natural_width_p, node->max_width);
+ *natural_width_p += width_inc;
+ }
+}
+
+/**
+ * st_theme_node_adjust_for_width:
+ * @node: a #StThemeNode
+ * @for_width: (inout): the "for width" to adjust
+ *
+ * Adjusts a "for width" passed to clutter_actor_get_preferred_height() to
+ * account for borders and padding. This is a convenience function meant
+ * to be called from a get_preferred_height() method of a #ClutterActor
+ * subclass. The value after adjustment is the width available for the actor's
+ * content.
+ */
+void
+st_theme_node_adjust_for_width (StThemeNode *node,
+ float *for_width)
+{
+ g_return_if_fail (ST_IS_THEME_NODE (node));
+ g_return_if_fail (for_width != NULL);
+
+ if (*for_width >= 0)
+ {
+ float width_inc = get_width_inc (node);
+ *for_width = MAX (0, *for_width - width_inc);
+ }
+}
+
+/**
+ * st_theme_node_adjust_preferred_height:
+ * @node: a #StThemeNode
+ * @min_height_p: (inout) (nullable): the minimum height to adjust
+ * @natural_height_p: (inout): the natural height to adjust
+ *
+ * Adjusts the minimum and natural height computed for an actor by
+ * adding on the necessary space for borders and padding and taking
+ * into account any minimum or maximum height. This is a convenience
+ * function meant to be called from the get_preferred_height() method
+ * of a #ClutterActor subclass
+ */
+void
+st_theme_node_adjust_preferred_height (StThemeNode *node,
+ float *min_height_p,
+ float *natural_height_p)
+{
+ float height_inc;
+
+ g_return_if_fail (ST_IS_THEME_NODE (node));
+
+ _st_theme_node_ensure_geometry (node);
+
+ height_inc = get_height_inc (node);
+
+ if (min_height_p)
+ {
+ if (node->min_height != -1)
+ *min_height_p = node->min_height;
+ *min_height_p += height_inc;
+ }
+ if (natural_height_p)
+ {
+ if (node->height != -1)
+ *natural_height_p = MAX (*natural_height_p, node->height);
+ if (node->max_height != -1)
+ *natural_height_p = MIN (*natural_height_p, node->max_height);
+ *natural_height_p += height_inc;
+ }
+}
+
+/**
+ * st_theme_node_get_content_box:
+ * @node: a #StThemeNode
+ * @allocation: the box allocated to a #ClutterAlctor
+ * @content_box: (out caller-allocates): computed box occupied by the actor's content
+ *
+ * Gets the box within an actor's allocation that contents the content
+ * of an actor (excluding borders and padding). This is a convenience function
+ * meant to be used from the allocate() or paint() methods of a #ClutterActor
+ * subclass.
+ */
+void
+st_theme_node_get_content_box (StThemeNode *node,
+ const ClutterActorBox *allocation,
+ ClutterActorBox *content_box)
+{
+ double noncontent_left, noncontent_top, noncontent_right, noncontent_bottom;
+ double avail_width, avail_height, content_width, content_height;
+
+ g_return_if_fail (ST_IS_THEME_NODE (node));
+
+ _st_theme_node_ensure_geometry (node);
+
+ avail_width = allocation->x2 - allocation->x1;
+ avail_height = allocation->y2 - allocation->y1;
+
+ noncontent_left = node->border_width[ST_SIDE_LEFT] + node->padding[ST_SIDE_LEFT];
+ noncontent_top = node->border_width[ST_SIDE_TOP] + node->padding[ST_SIDE_TOP];
+ noncontent_right = node->border_width[ST_SIDE_RIGHT] + node->padding[ST_SIDE_RIGHT];
+ noncontent_bottom = node->border_width[ST_SIDE_BOTTOM] + node->padding[ST_SIDE_BOTTOM];
+
+ content_box->x1 = (int)(0.5 + noncontent_left);
+ content_box->y1 = (int)(0.5 + noncontent_top);
+
+ content_width = avail_width - noncontent_left - noncontent_right;
+ if (content_width < 0)
+ content_width = 0;
+ content_height = avail_height - noncontent_top - noncontent_bottom;
+ if (content_height < 0)
+ content_height = 0;
+
+ content_box->x2 = (int)(0.5 + content_box->x1 + content_width);
+ content_box->y2 = (int)(0.5 + content_box->y1 + content_height);
+}
+
+/**
+ * st_theme_node_get_background_paint_box:
+ * @node: a #StThemeNode
+ * @allocation: the box allocated to a #ClutterActor
+ * @paint_box: (out caller-allocates): computed box occupied when painting the actor's background
+ *
+ * Gets the box used to paint the actor's background, including the area
+ * occupied by properties which paint outside the actor's assigned allocation.
+ */
+void
+st_theme_node_get_background_paint_box (StThemeNode *node,
+ const ClutterActorBox *actor_box,
+ ClutterActorBox *paint_box)
+{
+ StShadow *background_image_shadow;
+ ClutterActorBox shadow_box;
+
+ g_return_if_fail (ST_IS_THEME_NODE (node));
+ g_return_if_fail (actor_box != NULL);
+ g_return_if_fail (paint_box != NULL);
+
+ background_image_shadow = st_theme_node_get_background_image_shadow (node);
+
+ *paint_box = *actor_box;
+
+ if (!background_image_shadow)
+ return;
+
+ st_shadow_get_box (background_image_shadow, actor_box, &shadow_box);
+
+ paint_box->x1 = MIN (paint_box->x1, shadow_box.x1);
+ paint_box->x2 = MAX (paint_box->x2, shadow_box.x2);
+ paint_box->y1 = MIN (paint_box->y1, shadow_box.y1);
+ paint_box->y2 = MAX (paint_box->y2, shadow_box.y2);
+}
+
+/**
+ * st_theme_node_get_paint_box:
+ * @node: a #StThemeNode
+ * @allocation: the box allocated to a #ClutterActor
+ * @paint_box: (out caller-allocates): computed box occupied when painting the actor
+ *
+ * Gets the box used to paint the actor, including the area occupied
+ * by properties which paint outside the actor's assigned allocation.
+ * When painting @node to an offscreen buffer, this function can be
+ * used to determine the necessary size of the buffer.
+ */
+void
+st_theme_node_get_paint_box (StThemeNode *node,
+ const ClutterActorBox *actor_box,
+ ClutterActorBox *paint_box)
+{
+ StShadow *box_shadow;
+ ClutterActorBox shadow_box;
+ int outline_width;
+
+ g_return_if_fail (ST_IS_THEME_NODE (node));
+ g_return_if_fail (actor_box != NULL);
+ g_return_if_fail (paint_box != NULL);
+
+ box_shadow = st_theme_node_get_box_shadow (node);
+ outline_width = st_theme_node_get_outline_width (node);
+
+ st_theme_node_get_background_paint_box (node, actor_box, paint_box);
+
+ if (!box_shadow && !outline_width)
+ return;
+
+ paint_box->x1 -= outline_width;
+ paint_box->x2 += outline_width;
+ paint_box->y1 -= outline_width;
+ paint_box->y2 += outline_width;
+
+ if (box_shadow)
+ {
+ st_shadow_get_box (box_shadow, actor_box, &shadow_box);
+
+ paint_box->x1 = MIN (paint_box->x1, shadow_box.x1);
+ paint_box->x2 = MAX (paint_box->x2, shadow_box.x2);
+ paint_box->y1 = MIN (paint_box->y1, shadow_box.y1);
+ paint_box->y2 = MAX (paint_box->y2, shadow_box.y2);
+ }
+}
+
+/**
+ * st_theme_node_geometry_equal:
+ * @node: a #StThemeNode
+ * @other: a different #StThemeNode
+ *
+ * Tests if two theme nodes have the same borders and padding; this can be
+ * used to optimize having to relayout when the style applied to a Clutter
+ * actor changes colors without changing the geometry.
+ *
+ * Returns: %TRUE if equal, %FALSE otherwise
+ */
+gboolean
+st_theme_node_geometry_equal (StThemeNode *node,
+ StThemeNode *other)
+{
+ StSide side;
+
+ g_return_val_if_fail (ST_IS_THEME_NODE (node), FALSE);
+
+ if (node == other)
+ return TRUE;
+
+ g_return_val_if_fail (ST_IS_THEME_NODE (other), FALSE);
+
+ if (node->cached_scale_factor != other->cached_scale_factor)
+ return FALSE;
+
+ _st_theme_node_ensure_geometry (node);
+ _st_theme_node_ensure_geometry (other);
+
+ for (side = ST_SIDE_TOP; side <= ST_SIDE_LEFT; side++)
+ {
+ if (node->border_width[side] != other->border_width[side])
+ return FALSE;
+ if (node->padding[side] != other->padding[side])
+ return FALSE;
+ }
+
+ if (node->width != other->width || node->height != other->height)
+ return FALSE;
+ if (node->min_width != other->min_width || node->min_height != other->min_height)
+ return FALSE;
+ if (node->max_width != other->max_width || node->max_height != other->max_height)
+ return FALSE;
+
+ return TRUE;
+}
+
+/**
+ * st_theme_node_paint_equal:
+ * @node: (nullable): a #StThemeNode
+ * @other: (nullable): a different #StThemeNode
+ *
+ * Check if st_theme_node_paint() will paint identically for @node as it does
+ * for @other. Note that in some cases this function may return %TRUE even
+ * if there is no visible difference in the painting.
+ *
+ * Returns: %TRUE if the two theme nodes paint identically. %FALSE if the
+ * two nodes potentially paint differently.
+ */
+gboolean
+st_theme_node_paint_equal (StThemeNode *node,
+ StThemeNode *other)
+{
+ StBorderImage *border_image, *other_border_image;
+ StShadow *shadow, *other_shadow;
+ int i;
+
+ /* Make sure NULL != NULL */
+ if (node == NULL || other == NULL)
+ return FALSE;
+
+ if (node == other)
+ return TRUE;
+
+ _st_theme_node_ensure_background (node);
+ _st_theme_node_ensure_background (other);
+
+ if (!clutter_color_equal (&node->background_color, &other->background_color))
+ return FALSE;
+
+ if (node->background_gradient_type != other->background_gradient_type)
+ return FALSE;
+
+ if (node->background_gradient_type != ST_GRADIENT_NONE &&
+ !clutter_color_equal (&node->background_gradient_end, &other->background_gradient_end))
+ return FALSE;
+
+ if ((node->background_image != NULL) &&
+ (other->background_image != NULL) &&
+ !g_file_equal (node->background_image, other->background_image))
+ return FALSE;
+
+ _st_theme_node_ensure_geometry (node);
+ _st_theme_node_ensure_geometry (other);
+
+ for (i = 0; i < 4; i++)
+ {
+ if (node->border_width[i] != other->border_width[i])
+ return FALSE;
+
+ if (node->border_width[i] > 0 &&
+ !clutter_color_equal (&node->border_color[i], &other->border_color[i]))
+ return FALSE;
+
+ if (node->border_radius[i] != other->border_radius[i])
+ return FALSE;
+ }
+
+ if (node->outline_width != other->outline_width)
+ return FALSE;
+
+ if (node->outline_width > 0 &&
+ !clutter_color_equal (&node->outline_color, &other->outline_color))
+ return FALSE;
+
+ border_image = st_theme_node_get_border_image (node);
+ other_border_image = st_theme_node_get_border_image (other);
+
+ if ((border_image == NULL) != (other_border_image == NULL))
+ return FALSE;
+
+ if (border_image != NULL && !st_border_image_equal (border_image, other_border_image))
+ return FALSE;
+
+ shadow = st_theme_node_get_box_shadow (node);
+ other_shadow = st_theme_node_get_box_shadow (other);
+
+ if ((shadow == NULL) != (other_shadow == NULL))
+ return FALSE;
+
+ if (shadow != NULL && !st_shadow_equal (shadow, other_shadow))
+ return FALSE;
+
+ shadow = st_theme_node_get_background_image_shadow (node);
+ other_shadow = st_theme_node_get_background_image_shadow (other);
+
+ if ((shadow == NULL) != (other_shadow == NULL))
+ return FALSE;
+
+ if (shadow != NULL && !st_shadow_equal (shadow, other_shadow))
+ return FALSE;
+
+ return TRUE;
+}
+
+/**
+ * st_theme_node_to_string:
+ * @node: a #StThemeNode
+ *
+ * Serialize @node to a string of its #GType name, CSS ID, classes and
+ * pseudo-classes.
+ *
+ * Returns: the serialized theme node
+ */
+gchar *
+st_theme_node_to_string (StThemeNode *node)
+{
+ GString *desc;
+ gchar **it;
+
+ if (!node)
+ return g_strdup ("[null]");
+
+ desc = g_string_new (NULL);
+ g_string_append_printf (desc,
+ "[%p %s#%s",
+ node,
+ g_type_name (node->element_type),
+ node->element_id);
+
+ for (it = node->element_classes; it && *it; it++)
+ g_string_append_printf (desc, ".%s", *it);
+
+ for (it = node->pseudo_classes; it && *it; it++)
+ g_string_append_printf (desc, ":%s", *it);
+
+ g_string_append_c (desc, ']');
+
+ return g_string_free (desc, FALSE);
+}
diff --git a/src/st/st-theme-node.h b/src/st/st-theme-node.h
new file mode 100644
index 0000000..520e29f
--- /dev/null
+++ b/src/st/st-theme-node.h
@@ -0,0 +1,368 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * st-theme-node.h: style information for one node in a tree of themed objects
+ *
+ * Copyright 2008-2010 Red Hat, Inc.
+ * Copyright 2009, 2010 Florian Müllner
+ * Copyright 2010 Giovanni Campagna
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation, either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __ST_THEME_NODE_H__
+#define __ST_THEME_NODE_H__
+
+#include <clutter/clutter.h>
+#include "st-border-image.h"
+#include "st-icon-colors.h"
+#include "st-shadow.h"
+
+G_BEGIN_DECLS
+
+/**
+ * SECTION:st-theme-node
+ * @short_description: style information for one node in a tree of themed objects
+ *
+ * A #StThemeNode represents the CSS style information (the set of CSS properties) for one
+ * node in a tree of themed objects. In typical usage, it represents the style information
+ * for a single #ClutterActor. A #StThemeNode is immutable: attributes such as the
+ * CSS classes for the node are passed in at construction. If the attributes of the node
+ * or any parent node change, the node should be discarded and a new node created.
+ * #StThemeNode has generic accessors to look up properties by name and specific
+ * accessors for standard CSS properties that add caching and handling of various
+ * details of the CSS specification. #StThemeNode also has convenience functions to help
+ * in implementing a #ClutterActor with borders and padding.
+ *
+ * Note that pixel measurements take the #StThemeContext:scale-factor into
+ * account so all values are in physical pixels, as opposed to logical pixels.
+ * Physical pixels correspond to actor sizes, not necessarily to pixels on
+ * display devices (eg. when `scale-monitor-framebuffer` is enabled).
+ */
+
+typedef struct _StTheme StTheme;
+typedef struct _StThemeContext StThemeContext;
+
+#define ST_TYPE_THEME_NODE (st_theme_node_get_type ())
+G_DECLARE_FINAL_TYPE (StThemeNode, st_theme_node, ST, THEME_NODE, GObject)
+
+/**
+ * StSide:
+ * @ST_SIDE_TOP: The top side.
+ * @ST_SIDE_RIGHT: The right side.
+ * @ST_SIDE_BOTTOM: The bottom side.
+ * @ST_SIDE_LEFT: The left side.
+ *
+ * Used to target a particular side of a #StThemeNode element.
+ */
+typedef enum {
+ ST_SIDE_TOP,
+ ST_SIDE_RIGHT,
+ ST_SIDE_BOTTOM,
+ ST_SIDE_LEFT
+} StSide;
+
+/**
+ * StCorner:
+ * @ST_CORNER_TOPLEFT: The top-right corner.
+ * @ST_CORNER_TOPRIGHT: The top-right corner.
+ * @ST_CORNER_BOTTOMRIGHT: The bottom-right corner.
+ * @ST_CORNER_BOTTOMLEFT: The bottom-left corner.
+ *
+ * Used to target a particular corner of a #StThemeNode element.
+ */
+typedef enum {
+ ST_CORNER_TOPLEFT,
+ ST_CORNER_TOPRIGHT,
+ ST_CORNER_BOTTOMRIGHT,
+ ST_CORNER_BOTTOMLEFT
+} StCorner;
+
+/* These are the CSS values; that doesn't mean we have to implement blink... */
+/**
+ * StTextDecoration:
+ * @ST_TEXT_DECORATION_: Text is underlined
+ * @ST_TEXT_DECORATION_OVERLINE: Text is overlined
+ * @ST_TEXT_DECORATION_LINE_THROUGH: Text is striked out
+ * @ST_TEXT_DECORATION_BLINK: Text blinks
+ *
+ * Flags used to determine the decoration of text.
+ *
+ * Not that neither %ST_TEXT_DECORATION_OVERLINE or %ST_TEXT_DECORATION_BLINK
+ * are implemented, currently.
+ */
+typedef enum {
+ ST_TEXT_DECORATION_UNDERLINE = 1 << 0,
+ ST_TEXT_DECORATION_OVERLINE = 1 << 1,
+ ST_TEXT_DECORATION_LINE_THROUGH = 1 << 2,
+ ST_TEXT_DECORATION_BLINK = 1 << 3
+} StTextDecoration;
+
+/**
+ * StTextAlign:
+ * @ST_TEXT_ALIGN_LEFT: Text is aligned at the beginning of the label.
+ * @ST_TEXT_ALIGN_CENTER: Text is aligned in the middle of the label.
+ * @ST_TEXT_ALIGN_RIGHT: Text is aligned at the end of the label.
+ * @ST_GRADIENT_JUSTIFY: Text is justified in the label.
+ *
+ * Used to align text in a label.
+ */
+typedef enum {
+ ST_TEXT_ALIGN_LEFT = PANGO_ALIGN_LEFT,
+ ST_TEXT_ALIGN_CENTER = PANGO_ALIGN_CENTER,
+ ST_TEXT_ALIGN_RIGHT = PANGO_ALIGN_RIGHT,
+ ST_TEXT_ALIGN_JUSTIFY
+} StTextAlign;
+
+/**
+ * StGradientType:
+ * @ST_GRADIENT_NONE: No gradient.
+ * @ST_GRADIENT_VERTICAL: A vertical gradient.
+ * @ST_GRADIENT_HORIZONTAL: A horizontal gradient.
+ * @ST_GRADIENT_RADIAL: Lookup the style requested in the icon name.
+ *
+ * Used to specify options when rendering gradients.
+ */
+typedef enum {
+ ST_GRADIENT_NONE,
+ ST_GRADIENT_VERTICAL,
+ ST_GRADIENT_HORIZONTAL,
+ ST_GRADIENT_RADIAL
+} StGradientType;
+
+/**
+ * StIconStyle:
+ * @ST_ICON_STYLE_REQUESTED: Lookup the style requested in the icon name.
+ * @ST_ICON_STYLE_REGULAR: Try to always load regular icons, even when symbolic
+ * icon names are given.
+ * @ST_ICON_STYLE_SYMBOLIC: Try to always load symbolic icons, even when regular
+ * icon names are given.
+ *
+ * Used to specify options when looking up icons.
+ */
+typedef enum {
+ ST_ICON_STYLE_REQUESTED,
+ ST_ICON_STYLE_REGULAR,
+ ST_ICON_STYLE_SYMBOLIC
+} StIconStyle;
+
+typedef struct _StThemeNodePaintState StThemeNodePaintState;
+
+struct _StThemeNodePaintState {
+ StThemeNode *node;
+
+ float alloc_width;
+ float alloc_height;
+
+ float box_shadow_width;
+ float box_shadow_height;
+
+ float resource_scale;
+
+ CoglPipeline *box_shadow_pipeline;
+ CoglPipeline *prerendered_texture;
+ CoglPipeline *prerendered_pipeline;
+ CoglPipeline *corner_material[4];
+};
+
+StThemeNode *st_theme_node_new (StThemeContext *context,
+ StThemeNode *parent_node, /* can be null */
+ StTheme *theme, /* can be null */
+ GType element_type,
+ const char *element_id,
+ const char *element_class,
+ const char *pseudo_class,
+ const char *inline_style);
+
+StThemeNode *st_theme_node_get_parent (StThemeNode *node);
+
+StTheme *st_theme_node_get_theme (StThemeNode *node);
+
+gboolean st_theme_node_equal (StThemeNode *node_a, StThemeNode *node_b);
+guint st_theme_node_hash (StThemeNode *node);
+
+GType st_theme_node_get_element_type (StThemeNode *node);
+const char *st_theme_node_get_element_id (StThemeNode *node);
+GStrv st_theme_node_get_element_classes (StThemeNode *node);
+GStrv st_theme_node_get_pseudo_classes (StThemeNode *node);
+
+/* Generic getters ... these are not cached so are less efficient. The other
+ * reason for adding the more specific version is that we can handle the
+ * details of the actual CSS rules, which can be complicated, especially
+ * for fonts
+ */
+gboolean st_theme_node_lookup_color (StThemeNode *node,
+ const char *property_name,
+ gboolean inherit,
+ ClutterColor *color);
+gboolean st_theme_node_lookup_double (StThemeNode *node,
+ const char *property_name,
+ gboolean inherit,
+ double *value);
+gboolean st_theme_node_lookup_length (StThemeNode *node,
+ const char *property_name,
+ gboolean inherit,
+ gdouble *length);
+gboolean st_theme_node_lookup_time (StThemeNode *node,
+ const char *property_name,
+ gboolean inherit,
+ gdouble *value);
+gboolean st_theme_node_lookup_shadow (StThemeNode *node,
+ const char *property_name,
+ gboolean inherit,
+ StShadow **shadow);
+gboolean st_theme_node_lookup_url (StThemeNode *node,
+ const char *property_name,
+ gboolean inherit,
+ GFile **file);
+
+/* Easier-to-use variants of the above, for application-level use */
+void st_theme_node_get_color (StThemeNode *node,
+ const char *property_name,
+ ClutterColor *color);
+gdouble st_theme_node_get_double (StThemeNode *node,
+ const char *property_name);
+gdouble st_theme_node_get_length (StThemeNode *node,
+ const char *property_name);
+StShadow *st_theme_node_get_shadow (StThemeNode *node,
+ const char *property_name);
+GFile *st_theme_node_get_url (StThemeNode *node,
+ const char *property_name);
+
+/* Specific getters for particular properties: cached
+ */
+void st_theme_node_get_background_color (StThemeNode *node,
+ ClutterColor *color);
+void st_theme_node_get_foreground_color (StThemeNode *node,
+ ClutterColor *color);
+void st_theme_node_get_background_gradient (StThemeNode *node,
+ StGradientType *type,
+ ClutterColor *start,
+ ClutterColor *end);
+
+GFile *st_theme_node_get_background_image (StThemeNode *node);
+
+int st_theme_node_get_border_width (StThemeNode *node,
+ StSide side);
+int st_theme_node_get_border_radius (StThemeNode *node,
+ StCorner corner);
+void st_theme_node_get_border_color (StThemeNode *node,
+ StSide side,
+ ClutterColor *color);
+
+int st_theme_node_get_outline_width (StThemeNode *node);
+void st_theme_node_get_outline_color (StThemeNode *node,
+ ClutterColor *color);
+
+double st_theme_node_get_padding (StThemeNode *node,
+ StSide side);
+
+double st_theme_node_get_horizontal_padding (StThemeNode *node);
+double st_theme_node_get_vertical_padding (StThemeNode *node);
+
+double st_theme_node_get_margin (StThemeNode *node,
+ StSide side);
+
+int st_theme_node_get_width (StThemeNode *node);
+int st_theme_node_get_height (StThemeNode *node);
+int st_theme_node_get_min_width (StThemeNode *node);
+int st_theme_node_get_min_height (StThemeNode *node);
+int st_theme_node_get_max_width (StThemeNode *node);
+int st_theme_node_get_max_height (StThemeNode *node);
+
+int st_theme_node_get_transition_duration (StThemeNode *node);
+
+StIconStyle st_theme_node_get_icon_style (StThemeNode *node);
+
+StTextDecoration st_theme_node_get_text_decoration (StThemeNode *node);
+
+StTextAlign st_theme_node_get_text_align (StThemeNode *node);
+
+double st_theme_node_get_letter_spacing (StThemeNode *node);
+
+/* Font rule processing is pretty complicated, so we just hardcode it
+ * under the standard font/font-family/font-size/etc names. This means
+ * you can't have multiple separate styled fonts for a single item,
+ * but that should be OK.
+ */
+const PangoFontDescription *st_theme_node_get_font (StThemeNode *node);
+
+gchar *st_theme_node_get_font_features (StThemeNode *node);
+
+StBorderImage *st_theme_node_get_border_image (StThemeNode *node);
+StShadow *st_theme_node_get_box_shadow (StThemeNode *node);
+StShadow *st_theme_node_get_text_shadow (StThemeNode *node);
+
+StShadow *st_theme_node_get_background_image_shadow (StThemeNode *node);
+
+StIconColors *st_theme_node_get_icon_colors (StThemeNode *node);
+
+/* Helpers for get_preferred_width()/get_preferred_height() ClutterActor vfuncs */
+void st_theme_node_adjust_for_height (StThemeNode *node,
+ float *for_height);
+void st_theme_node_adjust_preferred_width (StThemeNode *node,
+ float *min_width_p,
+ float *natural_width_p);
+void st_theme_node_adjust_for_width (StThemeNode *node,
+ float *for_width);
+void st_theme_node_adjust_preferred_height (StThemeNode *node,
+ float *min_height_p,
+ float *natural_height_p);
+
+/* Helper for allocate() ClutterActor vfunc */
+void st_theme_node_get_content_box (StThemeNode *node,
+ const ClutterActorBox *allocation,
+ ClutterActorBox *content_box);
+/* Helper for StThemeNodeTransition */
+void st_theme_node_get_paint_box (StThemeNode *node,
+ const ClutterActorBox *allocation,
+ ClutterActorBox *paint_box);
+/* Helper for background prerendering */
+void st_theme_node_get_background_paint_box (StThemeNode *node,
+ const ClutterActorBox *allocation,
+ ClutterActorBox *paint_box);
+
+gboolean st_theme_node_geometry_equal (StThemeNode *node,
+ StThemeNode *other);
+gboolean st_theme_node_paint_equal (StThemeNode *node,
+ StThemeNode *other);
+
+/**
+ * st_theme_node_paint: (skip)
+ */
+void st_theme_node_paint (StThemeNode *node,
+ StThemeNodePaintState *state,
+ CoglFramebuffer *framebuffer,
+ const ClutterActorBox *box,
+ guint8 paint_opacity,
+ float resource_scale);
+
+void st_theme_node_invalidate_background_image (StThemeNode *node);
+void st_theme_node_invalidate_border_image (StThemeNode *node);
+
+gchar * st_theme_node_to_string (StThemeNode *node);
+
+void st_theme_node_paint_state_init (StThemeNodePaintState *state);
+void st_theme_node_paint_state_free (StThemeNodePaintState *state);
+void st_theme_node_paint_state_copy (StThemeNodePaintState *state,
+ StThemeNodePaintState *other);
+void st_theme_node_paint_state_invalidate (StThemeNodePaintState *state);
+gboolean st_theme_node_paint_state_invalidate_for_file (StThemeNodePaintState *state,
+ GFile *file);
+
+void st_theme_node_paint_state_set_node (StThemeNodePaintState *state,
+ StThemeNode *node);
+
+G_END_DECLS
+
+#endif /* __ST_THEME_NODE_H__ */
diff --git a/src/st/st-theme-private.h b/src/st/st-theme-private.h
new file mode 100644
index 0000000..2083a7c
--- /dev/null
+++ b/src/st/st-theme-private.h
@@ -0,0 +1,41 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * st-theme-private.h: Private StThemeMethods
+ *
+ * Copyright 2008, 2009 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation, either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __ST_THEME_PRIVATE_H__
+#define __ST_THEME_PRIVATE_H__
+
+#include "croco/libcroco.h"
+#include "st-theme.h"
+
+G_BEGIN_DECLS
+
+GPtrArray *_st_theme_get_matched_properties (StTheme *theme,
+ StThemeNode *node);
+
+/* Resolve an URL from the stylesheet to a file */
+GFile *_st_theme_resolve_url (StTheme *theme,
+ CRStyleSheet *base_stylesheet,
+ const char *url);
+
+CRDeclaration *_st_theme_parse_declaration_list (const char *str);
+
+G_END_DECLS
+
+#endif /* __ST_THEME_PRIVATE_H__ */
diff --git a/src/st/st-theme.c b/src/st/st-theme.c
new file mode 100644
index 0000000..20d42fd
--- /dev/null
+++ b/src/st/st-theme.c
@@ -0,0 +1,1085 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * st-theme.c: A set of CSS stylesheets used for rule matching
+ *
+ * Copyright 2003-2004 Dodji Seketeli
+ * Copyright 2008, 2009 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU Lesser General Public License,
+ * version 2.1, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * This file started as a cut-and-paste of cr-sel-eng.c from libcroco.
+ *
+ * In moving it to hippo-canvas:
+ * - Reformatted and otherwise edited to match our coding style
+ * - Switched from handling xmlNode to handling HippoStyle
+ * - Simplified by removing things that we don't need or that don't
+ * make sense in our context.
+ * - The code to get a list of matching properties works quite differently;
+ * we order things in priority order, but we don't actually try to
+ * coalesce properties with the same name.
+ *
+ * In moving it to GNOME Shell:
+ * - Renamed again to StTheme
+ * - Reformatted to match the gnome-shell coding style
+ * - Removed notion of "theme engine" from hippo-canvas
+ * - pseudo-class matching changed from link enum to strings
+ * - Some code simplification
+ */
+
+
+#include <stdlib.h>
+#include <string.h>
+
+#include <gio/gio.h>
+
+#include "st-private.h"
+#include "st-theme-node.h"
+#include "st-theme-private.h"
+
+static void st_theme_constructed (GObject *object);
+static void st_theme_finalize (GObject *object);
+static void st_theme_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void st_theme_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec);
+
+struct _StTheme
+{
+ GObject parent;
+
+ GFile *application_stylesheet;
+ GFile *default_stylesheet;
+ GFile *theme_stylesheet;
+ GSList *custom_stylesheets;
+
+ GHashTable *stylesheets_by_file;
+ GHashTable *files_by_stylesheet;
+
+ CRCascade *cascade;
+};
+
+enum
+{
+ PROP_0,
+ PROP_APPLICATION_STYLESHEET,
+ PROP_THEME_STYLESHEET,
+ PROP_DEFAULT_STYLESHEET
+};
+
+enum
+{
+ STYLESHEETS_CHANGED,
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = { 0, };
+
+G_DEFINE_TYPE (StTheme, st_theme, G_TYPE_OBJECT)
+
+/* Quick strcmp. Test only for == 0 or != 0, not < 0 or > 0. */
+#define strqcmp(str,lit,lit_len) \
+ (strlen (str) != (lit_len) || memcmp (str, lit, lit_len))
+
+static gboolean
+file_equal0 (GFile *file1,
+ GFile *file2)
+{
+ if (file1 == file2)
+ return TRUE;
+
+ if ((file1 == NULL) || (file2 == NULL))
+ return FALSE;
+
+ return g_file_equal (file1, file2);
+}
+
+static void
+st_theme_init (StTheme *theme)
+{
+ theme->stylesheets_by_file = g_hash_table_new_full (g_file_hash, (GEqualFunc) g_file_equal,
+ (GDestroyNotify)g_object_unref, (GDestroyNotify)cr_stylesheet_unref);
+ theme->files_by_stylesheet = g_hash_table_new (g_direct_hash, g_direct_equal);
+}
+
+static void
+st_theme_class_init (StThemeClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->constructed = st_theme_constructed;
+ object_class->finalize = st_theme_finalize;
+ object_class->set_property = st_theme_set_property;
+ object_class->get_property = st_theme_get_property;
+
+ /**
+ * StTheme:application-stylesheet:
+ *
+ * The highest priority stylesheet, representing application-specific
+ * styling; this is associated with the CSS "author" stylesheet.
+ */
+ g_object_class_install_property (object_class,
+ PROP_APPLICATION_STYLESHEET,
+ g_param_spec_object ("application-stylesheet",
+ "Application Stylesheet",
+ "Stylesheet with application-specific styling",
+ G_TYPE_FILE,
+ ST_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+
+ /**
+ * StTheme:theme-stylesheet:
+ *
+ * The second priority stylesheet, representing theme-specific styling;
+ * this is associated with the CSS "user" stylesheet.
+ */
+ g_object_class_install_property (object_class,
+ PROP_THEME_STYLESHEET,
+ g_param_spec_object ("theme-stylesheet",
+ "Theme Stylesheet",
+ "Stylesheet with theme-specific styling",
+ G_TYPE_FILE,
+ ST_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+
+ /**
+ * StTheme:default-stylesheet:
+ *
+ * The lowest priority stylesheet, representing global default
+ * styling; this is associated with the CSS "user agent" stylesheet.
+ */
+ g_object_class_install_property (object_class,
+ PROP_DEFAULT_STYLESHEET,
+ g_param_spec_object ("default-stylesheet",
+ "Default Stylesheet",
+ "Stylesheet with global default styling",
+ G_TYPE_FILE,
+ ST_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+
+ signals[STYLESHEETS_CHANGED] =
+ g_signal_new ("custom-stylesheets-changed",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ 0, /* no default handler slot */
+ NULL, NULL, NULL,
+ G_TYPE_NONE, 0);
+}
+
+static CRStyleSheet *
+parse_stylesheet (GFile *file,
+ GError **error)
+{
+ enum CRStatus status;
+ CRStyleSheet *stylesheet;
+ char *contents;
+ gsize length;
+
+ if (file == NULL)
+ return NULL;
+
+ if (!g_file_load_contents (file, NULL, &contents, &length, NULL, error))
+ return NULL;
+
+ status = cr_om_parser_simply_parse_buf ((const guchar *) contents,
+ length,
+ CR_UTF_8,
+ &stylesheet);
+ g_free (contents);
+
+ if (status != CR_OK)
+ {
+ char *uri = g_file_get_uri (file);
+ g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+ "Error parsing stylesheet '%s'; errcode:%d", uri, status);
+ g_free (uri);
+ return NULL;
+ }
+
+ /* Extension stylesheet */
+ stylesheet->app_data = GUINT_TO_POINTER (FALSE);
+
+ return stylesheet;
+}
+
+CRDeclaration *
+_st_theme_parse_declaration_list (const char *str)
+{
+ return cr_declaration_parse_list_from_buf ((const guchar *)str,
+ CR_UTF_8);
+}
+
+/* Just g_warning for now until we have something nicer to do */
+static CRStyleSheet *
+parse_stylesheet_nofail (GFile *file)
+{
+ GError *error = NULL;
+ CRStyleSheet *result;
+
+ result = parse_stylesheet (file, &error);
+ if (error)
+ {
+ g_warning ("%s", error->message);
+ g_clear_error (&error);
+ }
+ return result;
+}
+
+static void
+insert_stylesheet (StTheme *theme,
+ GFile *file,
+ CRStyleSheet *stylesheet)
+{
+ if (stylesheet == NULL)
+ return;
+
+ g_object_ref (file);
+ cr_stylesheet_ref (stylesheet);
+
+ g_hash_table_insert (theme->stylesheets_by_file, file, stylesheet);
+ g_hash_table_insert (theme->files_by_stylesheet, stylesheet, file);
+}
+
+/**
+ * st_theme_load_stylesheet:
+ * @theme: a #StTheme
+ * @file: a #GFile
+ * @error: a #GError
+ *
+ * Load the stylesheet associated with @file.
+ *
+ * Returns: %TRUE if successful
+ */
+gboolean
+st_theme_load_stylesheet (StTheme *theme,
+ GFile *file,
+ GError **error)
+{
+ CRStyleSheet *stylesheet;
+
+ stylesheet = parse_stylesheet (file, error);
+ if (!stylesheet)
+ return FALSE;
+
+ stylesheet->app_data = GUINT_TO_POINTER (TRUE);
+
+ insert_stylesheet (theme, file, stylesheet);
+ cr_stylesheet_ref (stylesheet);
+ theme->custom_stylesheets = g_slist_prepend (theme->custom_stylesheets, stylesheet);
+ g_signal_emit (theme, signals[STYLESHEETS_CHANGED], 0);
+
+ return TRUE;
+}
+
+/**
+ * st_theme_unload_stylesheet:
+ * @theme: a #StTheme
+ * @file: a #GFile
+ *
+ * Unload the stylesheet associated with @file. If @file was not loaded this
+ * function does nothing.
+ */
+void
+st_theme_unload_stylesheet (StTheme *theme,
+ GFile *file)
+{
+ CRStyleSheet *stylesheet;
+
+ stylesheet = g_hash_table_lookup (theme->stylesheets_by_file, file);
+ if (!stylesheet)
+ return;
+
+ if (!g_slist_find (theme->custom_stylesheets, stylesheet))
+ return;
+
+ theme->custom_stylesheets = g_slist_remove (theme->custom_stylesheets, stylesheet);
+
+ g_signal_emit (theme, signals[STYLESHEETS_CHANGED], 0);
+
+ /* We need to remove the entry from the hashtable after emitting the signal
+ * since we might still access the files_by_stylesheet hashtable in
+ * _st_theme_resolve_url() during the signal emission.
+ */
+ g_hash_table_remove (theme->stylesheets_by_file, file);
+ g_hash_table_remove (theme->files_by_stylesheet, stylesheet);
+ cr_stylesheet_unref (stylesheet);
+}
+
+/**
+ * st_theme_get_custom_stylesheets:
+ * @theme: an #StTheme
+ *
+ * Get a list of the stylesheet files loaded with st_theme_load_stylesheet().
+ *
+ * Returns: (transfer full) (element-type GFile): the list of stylesheet files
+ * that were loaded with st_theme_load_stylesheet()
+ */
+GSList*
+st_theme_get_custom_stylesheets (StTheme *theme)
+{
+ GSList *result = NULL;
+ GSList *iter;
+
+ for (iter = theme->custom_stylesheets; iter; iter = iter->next)
+ {
+ CRStyleSheet *stylesheet = iter->data;
+ GFile *file = g_hash_table_lookup (theme->files_by_stylesheet, stylesheet);
+
+ result = g_slist_prepend (result, g_object_ref (file));
+ }
+
+ return result;
+}
+
+static void
+st_theme_constructed (GObject *object)
+{
+ StTheme *theme = ST_THEME (object);
+ CRStyleSheet *application_stylesheet;
+ CRStyleSheet *theme_stylesheet;
+ CRStyleSheet *default_stylesheet;
+
+ G_OBJECT_CLASS (st_theme_parent_class)->constructed (object);
+
+ application_stylesheet = parse_stylesheet_nofail (theme->application_stylesheet);
+ theme_stylesheet = parse_stylesheet_nofail (theme->theme_stylesheet);
+ default_stylesheet = parse_stylesheet_nofail (theme->default_stylesheet);
+
+ theme->cascade = cr_cascade_new (application_stylesheet,
+ theme_stylesheet,
+ default_stylesheet);
+
+ if (theme->cascade == NULL)
+ g_error ("Out of memory when creating cascade object");
+
+ insert_stylesheet (theme, theme->application_stylesheet, application_stylesheet);
+ insert_stylesheet (theme, theme->theme_stylesheet, theme_stylesheet);
+ insert_stylesheet (theme, theme->default_stylesheet, default_stylesheet);
+}
+
+static void
+st_theme_finalize (GObject * object)
+{
+ StTheme *theme = ST_THEME (object);
+
+ g_slist_foreach (theme->custom_stylesheets, (GFunc) cr_stylesheet_unref, NULL);
+ g_slist_free (theme->custom_stylesheets);
+ theme->custom_stylesheets = NULL;
+
+ g_hash_table_destroy (theme->stylesheets_by_file);
+ g_hash_table_destroy (theme->files_by_stylesheet);
+
+ g_clear_object (&theme->application_stylesheet);
+ g_clear_object (&theme->theme_stylesheet);
+ g_clear_object (&theme->default_stylesheet);
+
+ if (theme->cascade)
+ {
+ cr_cascade_unref (theme->cascade);
+ theme->cascade = NULL;
+ }
+
+ G_OBJECT_CLASS (st_theme_parent_class)->finalize (object);
+}
+
+static void
+st_theme_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ StTheme *theme = ST_THEME (object);
+
+ switch (prop_id)
+ {
+ case PROP_APPLICATION_STYLESHEET:
+ {
+ GFile *file = g_value_get_object (value);
+
+ if (!file_equal0 (file, theme->application_stylesheet))
+ {
+ g_clear_object (&theme->application_stylesheet);
+ if (file != NULL)
+ theme->application_stylesheet = g_object_ref (file);
+ }
+
+ break;
+ }
+ case PROP_THEME_STYLESHEET:
+ {
+ GFile *file = g_value_get_object (value);
+
+ if (!file_equal0 (file, theme->theme_stylesheet))
+ {
+ g_clear_object (&theme->theme_stylesheet);
+ if (file != NULL)
+ theme->theme_stylesheet = g_object_ref (file);
+ }
+
+ break;
+ }
+ case PROP_DEFAULT_STYLESHEET:
+ {
+ GFile *file = g_value_get_object (value);
+
+ if (!file_equal0 (file, theme->default_stylesheet))
+ {
+ g_clear_object (&theme->default_stylesheet);
+ if (file != NULL)
+ theme->default_stylesheet = g_object_ref (file);
+ }
+
+ break;
+ }
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+st_theme_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ StTheme *theme = ST_THEME (object);
+
+ switch (prop_id)
+ {
+ case PROP_APPLICATION_STYLESHEET:
+ g_value_set_object (value, theme->application_stylesheet);
+ break;
+ case PROP_THEME_STYLESHEET:
+ g_value_set_object (value, theme->theme_stylesheet);
+ break;
+ case PROP_DEFAULT_STYLESHEET:
+ g_value_set_object (value, theme->default_stylesheet);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+/**
+ * st_theme_new:
+ * @application_stylesheet: The highest priority stylesheet, representing application-specific
+ * styling; this is associated with the CSS "author" stylesheet, may be %NULL
+ * @theme_stylesheet: The second priority stylesheet, representing theme-specific styling ;
+ * this is associated with the CSS "user" stylesheet, may be %NULL
+ * @default_stylesheet: The lowest priority stylesheet, representing global default styling;
+ * this is associated with the CSS "user agent" stylesheet, may be %NULL
+ *
+ * Returns: the newly created theme object
+ **/
+StTheme *
+st_theme_new (GFile *application_stylesheet,
+ GFile *theme_stylesheet,
+ GFile *default_stylesheet)
+{
+ StTheme *theme = g_object_new (ST_TYPE_THEME,
+ "application-stylesheet", application_stylesheet,
+ "theme-stylesheet", theme_stylesheet,
+ "default-stylesheet", default_stylesheet,
+ NULL);
+
+ return theme;
+}
+
+static gboolean
+string_in_list (GString *stryng,
+ GStrv list)
+{
+ gchar **it;
+
+ if (list == NULL)
+ return FALSE;
+
+ for (it = list; *it != NULL; it++)
+ {
+ if (!strqcmp (*it, stryng->str, stryng->len))
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static gboolean
+pseudo_class_add_sel_matches_style (StTheme *a_this,
+ CRAdditionalSel *a_add_sel,
+ StThemeNode *a_node)
+{
+ GStrv node_pseudo_classes;
+
+ g_return_val_if_fail (a_this
+ && a_add_sel
+ && a_add_sel->content.pseudo
+ && a_add_sel->content.pseudo->name
+ && a_add_sel->content.pseudo->name->stryng
+ && a_add_sel->content.pseudo->name->stryng->str
+ && a_node, FALSE);
+
+ node_pseudo_classes = st_theme_node_get_pseudo_classes (a_node);
+
+ return string_in_list (a_add_sel->content.pseudo->name->stryng,
+ node_pseudo_classes);
+}
+
+/**
+ * class_add_sel_matches_style:
+ * @a_add_sel: The class additional selector to consider.
+ * @a_node: The style node to consider.
+ *
+ * Returns: %TRUE if the class additional selector matches
+ * the style node given in argument, %FALSE otherwise.
+ */
+static gboolean
+class_add_sel_matches_style (CRAdditionalSel *a_add_sel,
+ StThemeNode *a_node)
+{
+ GStrv element_classes;
+
+ g_return_val_if_fail (a_add_sel
+ && a_add_sel->type == CLASS_ADD_SELECTOR
+ && a_add_sel->content.class_name
+ && a_add_sel->content.class_name->stryng
+ && a_add_sel->content.class_name->stryng->str
+ && a_node, FALSE);
+
+ element_classes = st_theme_node_get_element_classes (a_node);
+
+ return string_in_list (a_add_sel->content.class_name->stryng,
+ element_classes);
+}
+
+/*
+ *@return TRUE if the additional attribute selector matches
+ *the current style node given in argument, FALSE otherwise.
+ *@param a_add_sel the additional attribute selector to consider.
+ *@param a_node the style node to consider.
+ */
+static gboolean
+id_add_sel_matches_style (CRAdditionalSel *a_add_sel,
+ StThemeNode *a_node)
+{
+ gboolean result = FALSE;
+ const char *id;
+
+ g_return_val_if_fail (a_add_sel
+ && a_add_sel->type == ID_ADD_SELECTOR
+ && a_add_sel->content.id_name
+ && a_add_sel->content.id_name->stryng
+ && a_add_sel->content.id_name->stryng->str
+ && a_node, FALSE);
+ g_return_val_if_fail (a_add_sel
+ && a_add_sel->type == ID_ADD_SELECTOR
+ && a_node, FALSE);
+
+ id = st_theme_node_get_element_id (a_node);
+
+ if (id != NULL)
+ {
+ if (!strqcmp (id, a_add_sel->content.id_name->stryng->str,
+ a_add_sel->content.id_name->stryng->len))
+ {
+ result = TRUE;
+ }
+ }
+
+ return result;
+}
+
+/**
+ *additional_selector_matches_style:
+ *Evaluates if a given additional selector matches an style node.
+ *@param a_add_sel the additional selector to consider.
+ *@param a_node the style node to consider.
+ *@return TRUE is a_add_sel matches a_node, FALSE otherwise.
+ */
+static gboolean
+additional_selector_matches_style (StTheme *a_this,
+ CRAdditionalSel *a_add_sel,
+ StThemeNode *a_node)
+{
+ CRAdditionalSel *cur_add_sel = NULL;
+
+ g_return_val_if_fail (a_add_sel, FALSE);
+
+ for (cur_add_sel = a_add_sel; cur_add_sel; cur_add_sel = cur_add_sel->next)
+ {
+ switch (cur_add_sel->type)
+ {
+ case NO_ADD_SELECTOR:
+ return FALSE;
+ case CLASS_ADD_SELECTOR:
+ if (!class_add_sel_matches_style (cur_add_sel, a_node))
+ return FALSE;
+ break;
+ case ID_ADD_SELECTOR:
+ if (!id_add_sel_matches_style (cur_add_sel, a_node))
+ return FALSE;
+ break;
+ case ATTRIBUTE_ADD_SELECTOR:
+ g_warning ("Attribute selectors not supported");
+ return FALSE;
+ case PSEUDO_CLASS_ADD_SELECTOR:
+ if (!pseudo_class_add_sel_matches_style (a_this, cur_add_sel, a_node))
+ return FALSE;
+ break;
+ default:
+ g_warning ("Unhandled selector type %d", cur_add_sel->type);
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+static gboolean
+element_name_matches_type (const char *element_name,
+ GType element_type)
+{
+ if (element_type == G_TYPE_NONE)
+ {
+ return strcmp (element_name, "stage") == 0;
+ }
+ else
+ {
+ GType match_type = g_type_from_name (element_name);
+ if (match_type == G_TYPE_INVALID)
+ return FALSE;
+
+ return g_type_is_a (element_type, match_type);
+ }
+}
+
+/*
+ *Evaluate a selector (a simple selectors list) and says
+ *if it matches the style node given in parameter.
+ *The algorithm used here is the following:
+ *Walk the combinator separated list of simple selectors backward, starting
+ *from the end of the list. For each simple selector, looks if
+ *if matches the current style.
+ *
+ *@param a_this the selection engine.
+ *@param a_sel the simple selection list.
+ *@param a_node the style node.
+ *@param a_result out parameter. Set to true if the
+ *selector matches the style node, FALSE otherwise.
+ *@param a_recurse if set to TRUE, the function will walk to
+ *the next simple selector (after the evaluation of the current one)
+ *and recursively evaluate it. Must be usually set to TRUE unless you
+ *know what you are doing.
+ */
+static enum CRStatus
+sel_matches_style_real (StTheme *a_this,
+ CRSimpleSel *a_sel,
+ StThemeNode *a_node,
+ gboolean *a_result,
+ gboolean a_eval_sel_list_from_end,
+ gboolean a_recurse)
+{
+ CRSimpleSel *cur_sel = NULL;
+ StThemeNode *cur_node = NULL;
+ GType cur_type;
+
+ *a_result = FALSE;
+
+ if (a_eval_sel_list_from_end)
+ {
+ /*go and get the last simple selector of the list */
+ for (cur_sel = a_sel; cur_sel && cur_sel->next; cur_sel = cur_sel->next)
+ ;
+ }
+ else
+ {
+ cur_sel = a_sel;
+ }
+
+ cur_node = a_node;
+ cur_type = st_theme_node_get_element_type (cur_node);
+
+ while (cur_sel)
+ {
+ if (((cur_sel->type_mask & TYPE_SELECTOR)
+ && (cur_sel->name
+ && cur_sel->name->stryng
+ && cur_sel->name->stryng->str)
+ &&
+ (element_name_matches_type (cur_sel->name->stryng->str, cur_type)))
+ || (cur_sel->type_mask & UNIVERSAL_SELECTOR))
+ {
+ /*
+ *this simple selector
+ *matches the current style node
+ *Let's see if the preceding
+ *simple selectors also match
+ *their style node counterpart.
+ */
+ if (cur_sel->add_sel)
+ {
+ if (additional_selector_matches_style (a_this, cur_sel->add_sel, cur_node))
+ goto walk_a_step_in_expr;
+ else
+ goto done;
+ }
+ else
+ goto walk_a_step_in_expr;
+ }
+ if (!(cur_sel->type_mask & TYPE_SELECTOR)
+ && !(cur_sel->type_mask & UNIVERSAL_SELECTOR))
+ {
+ if (!cur_sel->add_sel)
+ goto done;
+ if (additional_selector_matches_style (a_this, cur_sel->add_sel, cur_node))
+ goto walk_a_step_in_expr;
+ else
+ goto done;
+ }
+ else
+ {
+ goto done;
+ }
+
+ walk_a_step_in_expr:
+ if (a_recurse == FALSE)
+ {
+ *a_result = TRUE;
+ goto done;
+ }
+
+ /*
+ *here, depending on the combinator of cur_sel
+ *choose the axis of the element tree traversal
+ *and walk one step in the element tree.
+ */
+ if (!cur_sel->prev)
+ break;
+
+ switch (cur_sel->combinator)
+ {
+ case NO_COMBINATOR:
+ break;
+
+ case COMB_WS: /*descendant selector */
+ {
+ StThemeNode *n = NULL;
+
+ /*
+ *walk the element tree upward looking for a parent
+ *style that matches the preceding selector.
+ */
+ for (n = st_theme_node_get_parent (a_node); n; n = st_theme_node_get_parent (n))
+ {
+ enum CRStatus status;
+ gboolean matches = FALSE;
+
+ status = sel_matches_style_real (a_this, cur_sel->prev, n, &matches, FALSE, TRUE);
+
+ if (status != CR_OK)
+ goto done;
+
+ if (matches)
+ {
+ cur_node = n;
+ cur_type = st_theme_node_get_element_type (cur_node);
+ break;
+ }
+ }
+
+ if (!n)
+ {
+ /*
+ *didn't find any ancestor that matches
+ *the previous simple selector.
+ */
+ goto done;
+ }
+ /*
+ *in this case, the preceding simple sel
+ *will have been interpreted twice, which
+ *is a cpu and mem waste ... I need to find
+ *another way to do this. Anyway, this is
+ *my first attempt to write this function and
+ *I am a bit clueless.
+ */
+ break;
+ }
+
+ case COMB_PLUS:
+ g_warning ("+ combinators are not supported");
+ goto done;
+
+ case COMB_GT:
+ cur_node = st_theme_node_get_parent (cur_node);
+ if (!cur_node)
+ goto done;
+ cur_type = st_theme_node_get_element_type (cur_node);
+ break;
+
+ default:
+ goto done;
+ }
+
+ cur_sel = cur_sel->prev;
+ }
+
+ /*
+ *if we reached this point, it means the selector matches
+ *the style node.
+ */
+ *a_result = TRUE;
+
+done:
+ return CR_OK;
+}
+
+static void
+add_matched_properties (StTheme *a_this,
+ CRStyleSheet *a_nodesheet,
+ StThemeNode *a_node,
+ GPtrArray *props)
+{
+ CRStatement *cur_stmt = NULL;
+ CRSelector *sel_list = NULL;
+ CRSelector *cur_sel = NULL;
+ gboolean matches = FALSE;
+ enum CRStatus status = CR_OK;
+
+ /*
+ *walk through the list of statements and,
+ *get the selectors list inside the statements that
+ *contain some, and try to match our style node in these
+ *selectors lists.
+ */
+ for (cur_stmt = a_nodesheet->statements; cur_stmt; cur_stmt = cur_stmt->next)
+ {
+ /*
+ *initialize the selector list in which we will
+ *really perform the search.
+ */
+ sel_list = NULL;
+
+ /*
+ *get the the damn selector list in
+ *which we have to look
+ */
+ switch (cur_stmt->type)
+ {
+ case RULESET_STMT:
+ if (cur_stmt->kind.ruleset && cur_stmt->kind.ruleset->sel_list)
+ {
+ sel_list = cur_stmt->kind.ruleset->sel_list;
+ }
+ break;
+
+ case AT_IMPORT_RULE_STMT:
+ {
+ CRAtImportRule *import_rule = cur_stmt->kind.import_rule;
+
+ if (import_rule->sheet == NULL)
+ {
+ GFile *file = NULL;
+
+ if (import_rule->url->stryng && import_rule->url->stryng->str)
+ {
+ file = _st_theme_resolve_url (a_this,
+ a_nodesheet,
+ import_rule->url->stryng->str);
+ import_rule->sheet = parse_stylesheet (file, NULL);
+ }
+
+ if (import_rule->sheet)
+ {
+ insert_stylesheet (a_this, file, import_rule->sheet);
+ /* refcount of stylesheets starts off at zero, so we don't need to unref! */
+ }
+ else
+ {
+ /* Set a marker to avoid repeatedly trying to parse a non-existent or
+ * broken stylesheet
+ */
+ import_rule->sheet = (CRStyleSheet *) - 1;
+ }
+
+ if (file)
+ g_object_unref (file);
+ }
+
+ if (import_rule->sheet != (CRStyleSheet *) - 1)
+ {
+ add_matched_properties (a_this, import_rule->sheet,
+ a_node, props);
+ }
+ }
+ break;
+ case AT_MEDIA_RULE_STMT:
+ case AT_RULE_STMT:
+ case AT_PAGE_RULE_STMT:
+ case AT_CHARSET_RULE_STMT:
+ case AT_FONT_FACE_RULE_STMT:
+ default:
+ break;
+ }
+
+ if (!sel_list)
+ continue;
+
+ /*
+ *now, we have a comma separated selector list to look in.
+ *let's walk it and try to match the style node
+ *on each item of the list.
+ */
+ for (cur_sel = sel_list; cur_sel; cur_sel = cur_sel->next)
+ {
+ if (!cur_sel->simple_sel)
+ continue;
+
+ status = sel_matches_style_real (a_this, cur_sel->simple_sel, a_node, &matches, TRUE, TRUE);
+
+ if (status == CR_OK && matches)
+ {
+ CRDeclaration *cur_decl = NULL;
+
+ /* In order to sort the matching properties, we need to compute the
+ * specificity of the selector that actually matched this
+ * element. In a non-thread-safe fashion, we store it in the
+ * ruleset. (Fixing this would mean cut-and-pasting
+ * cr_simple_sel_compute_specificity(), and have no need for
+ * thread-safety anyways.)
+ *
+ * Once we've sorted the properties, the specificity no longer
+ * matters and it can be safely overridden.
+ */
+ cr_simple_sel_compute_specificity (cur_sel->simple_sel);
+
+ cur_stmt->specificity = cur_sel->simple_sel->specificity;
+
+ for (cur_decl = cur_stmt->kind.ruleset->decl_list; cur_decl; cur_decl = cur_decl->next)
+ g_ptr_array_add (props, cur_decl);
+ }
+ }
+ }
+}
+
+#define ORIGIN_OFFSET_IMPORTANT (NB_ORIGINS)
+#define ORIGIN_OFFSET_EXTENSION (NB_ORIGINS * 2)
+
+static inline int
+get_origin (const CRDeclaration * decl)
+{
+ enum CRStyleOrigin origin = decl->parent_statement->parent_sheet->origin;
+ gboolean is_extension_sheet = GPOINTER_TO_UINT (decl->parent_statement->parent_sheet->app_data);
+
+ if (decl->important)
+ origin += ORIGIN_OFFSET_IMPORTANT;
+
+ if (is_extension_sheet)
+ origin += ORIGIN_OFFSET_EXTENSION;
+
+ return origin;
+}
+
+/* Order of comparison is so that higher priority statements compare after
+ * lower priority statements */
+static int
+compare_declarations (gconstpointer a,
+ gconstpointer b)
+{
+ /* g_ptr_array_sort() is broooken */
+ CRDeclaration *decl_a = *(CRDeclaration **) a;
+ CRDeclaration *decl_b = *(CRDeclaration **) b;
+
+ int origin_a = get_origin (decl_a);
+ int origin_b = get_origin (decl_b);
+
+ if (origin_a != origin_b)
+ return origin_a - origin_b;
+
+ if (decl_a->parent_statement->specificity != decl_b->parent_statement->specificity)
+ return decl_a->parent_statement->specificity - decl_b->parent_statement->specificity;
+
+ return 0;
+}
+
+GPtrArray *
+_st_theme_get_matched_properties (StTheme *theme,
+ StThemeNode *node)
+{
+ enum CRStyleOrigin origin = 0;
+ CRStyleSheet *sheet = NULL;
+ GPtrArray *props = g_ptr_array_new ();
+ GSList *iter;
+
+ g_return_val_if_fail (ST_IS_THEME (theme), NULL);
+ g_return_val_if_fail (ST_IS_THEME_NODE (node), NULL);
+
+ for (origin = ORIGIN_UA; origin < NB_ORIGINS; origin++)
+ {
+ sheet = cr_cascade_get_sheet (theme->cascade, origin);
+ if (!sheet)
+ continue;
+
+ add_matched_properties (theme, sheet, node, props);
+ }
+
+ for (iter = theme->custom_stylesheets; iter; iter = iter->next)
+ add_matched_properties (theme, iter->data, node, props);
+
+ /* We count on a stable sort here so that later declarations come
+ * after earlier declarations */
+ g_ptr_array_sort (props, compare_declarations);
+
+ return props;
+}
+
+/* Resolve an url from an url() reference in a stylesheet into a GFile,
+ * if possible. The resolution here is distinctly lame and
+ * will fail on many examples.
+ */
+GFile *
+_st_theme_resolve_url (StTheme *theme,
+ CRStyleSheet *base_stylesheet,
+ const char *url)
+{
+ char *scheme;
+ GFile *resource;
+
+ if ((scheme = g_uri_parse_scheme (url)))
+ {
+ g_free (scheme);
+ resource = g_file_new_for_uri (url);
+ }
+ else if (base_stylesheet != NULL)
+ {
+ GFile *base_file = NULL, *parent;
+
+ base_file = g_hash_table_lookup (theme->files_by_stylesheet, base_stylesheet);
+
+ /* This is an internal function, if we get here with
+ a bad @base_stylesheet we have a problem. */
+ g_assert (base_file);
+
+ parent = g_file_get_parent (base_file);
+ resource = g_file_resolve_relative_path (parent, url);
+
+ g_object_unref (parent);
+ }
+ else
+ {
+ resource = g_file_new_for_path (url);
+ }
+
+ return resource;
+}
diff --git a/src/st/st-theme.h b/src/st/st-theme.h
new file mode 100644
index 0000000..d3f242c
--- /dev/null
+++ b/src/st/st-theme.h
@@ -0,0 +1,51 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * st-theme.h: A set of CSS stylesheets used for rule matching
+ *
+ * Copyright 2008, 2009 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation, either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+#ifndef __ST_THEME_H__
+#define __ST_THEME_H__
+
+#include <glib-object.h>
+
+#include "st-theme-node.h"
+
+G_BEGIN_DECLS
+
+/**
+ * SECTION:st-theme
+ * @short_description: a set of stylesheets
+ *
+ * #StTheme holds a set of stylesheets. (The "cascade" of the name
+ * Cascading Stylesheets.) A #StTheme can be set to apply to all the actors
+ * in a stage using st_theme_context_set_theme().
+ */
+
+#define ST_TYPE_THEME (st_theme_get_type ())
+G_DECLARE_FINAL_TYPE (StTheme, st_theme, ST, THEME, GObject)
+
+StTheme *st_theme_new (GFile *application_stylesheet,
+ GFile *theme_stylesheet,
+ GFile *default_stylesheet);
+
+gboolean st_theme_load_stylesheet (StTheme *theme, GFile *file, GError **error);
+void st_theme_unload_stylesheet (StTheme *theme, GFile *file);
+GSList *st_theme_get_custom_stylesheets (StTheme *theme);
+
+G_END_DECLS
+
+#endif /* __ST_THEME_H__ */
diff --git a/src/st/st-types.h b/src/st/st-types.h
new file mode 100644
index 0000000..d204041
--- /dev/null
+++ b/src/st/st-types.h
@@ -0,0 +1,52 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * Copyright 2009 Intel Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU Lesser General Public License,
+ * version 2.1, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+#ifndef __ST_TYPES_H__
+#define __ST_TYPES_H__
+
+#if !defined(ST_H_INSIDE) && !defined(ST_COMPILATION)
+#error "Only <st/st.h> can be included directly.h"
+#endif
+
+#include <glib-object.h>
+#include <clutter/clutter.h>
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+/**
+ * SECTION:st-types
+ * @short_description: type definitions used throughout St
+ *
+ * Common types for StWidgets.
+ */
+
+typedef enum {
+ ST_ALIGN_START,
+ ST_ALIGN_MIDDLE,
+ ST_ALIGN_END
+} StAlign;
+
+typedef enum {
+ ST_BACKGROUND_SIZE_AUTO,
+ ST_BACKGROUND_SIZE_CONTAIN,
+ ST_BACKGROUND_SIZE_COVER,
+ ST_BACKGROUND_SIZE_FIXED
+} StBackgroundSize;
+
+G_END_DECLS
+
+#endif /* __ST_TYPES_H__ */
diff --git a/src/st/st-viewport.c b/src/st/st-viewport.c
new file mode 100644
index 0000000..e6b9127
--- /dev/null
+++ b/src/st/st-viewport.c
@@ -0,0 +1,600 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * st-scrollable-wiget.c: a scrollable actor
+ *
+ * Copyright 2009 Intel Corporation.
+ * Copyright 2009 Abderrahim Kitouni
+ * Copyright 2009, 2010 Red Hat, Inc.
+ * Copyright 2010 Florian Muellner
+ * Copyright 2019 Endless, Inc
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU Lesser General Public License,
+ * version 2.1, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/* Portions copied from Clutter:
+ * Clutter.
+ *
+ * An OpenGL based 'interactive canvas' library.
+ *
+ * Authored By Matthew Allum <mallum@openedhand.com>
+ *
+ * Copyright (C) 2006 OpenedHand
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ */
+
+/**
+ * SECTION:st-viewport
+ * @short_description: a scrollable container
+ *
+ * The #StViewport is a generic #StScrollable implementation.
+ *
+ */
+
+#include <stdlib.h>
+
+#include "st-viewport.h"
+
+#include "st-private.h"
+#include "st-scrollable.h"
+
+
+static void st_viewport_scrollable_interface_init (StScrollableInterface *iface);
+
+enum {
+ PROP_0,
+
+ PROP_CLIP_TO_VIEW,
+
+ N_PROPS,
+
+ /* StScrollable */
+ PROP_HADJUST,
+ PROP_VADJUST
+};
+
+static GParamSpec *props[N_PROPS] = { NULL, };
+
+typedef struct
+{
+ StAdjustment *hadjustment;
+ StAdjustment *vadjustment;
+ gboolean clip_to_view;
+} StViewportPrivate;
+
+G_DEFINE_TYPE_WITH_CODE (StViewport, st_viewport, ST_TYPE_WIDGET,
+ G_ADD_PRIVATE (StViewport)
+ G_IMPLEMENT_INTERFACE (ST_TYPE_SCROLLABLE,
+ st_viewport_scrollable_interface_init));
+
+/*
+ * StScrollable Interface Implementation
+ */
+static void
+adjustment_value_notify_cb (StAdjustment *adjustment,
+ GParamSpec *pspec,
+ StViewport *viewport)
+{
+ clutter_actor_invalidate_transform (CLUTTER_ACTOR (viewport));
+ clutter_actor_invalidate_paint_volume (CLUTTER_ACTOR (viewport));
+ clutter_actor_queue_relayout (CLUTTER_ACTOR (viewport));
+}
+
+static void
+scrollable_set_adjustments (StScrollable *scrollable,
+ StAdjustment *hadjustment,
+ StAdjustment *vadjustment)
+{
+ StViewport *viewport = ST_VIEWPORT (scrollable);
+ StViewportPrivate *priv =
+ st_viewport_get_instance_private (viewport);
+
+ g_object_freeze_notify (G_OBJECT (scrollable));
+
+ if (hadjustment != priv->hadjustment)
+ {
+ if (priv->hadjustment)
+ {
+ g_signal_handlers_disconnect_by_func (priv->hadjustment,
+ adjustment_value_notify_cb,
+ scrollable);
+ g_object_unref (priv->hadjustment);
+ }
+
+ if (hadjustment)
+ {
+ g_object_ref (hadjustment);
+ g_signal_connect (hadjustment, "notify::value",
+ G_CALLBACK (adjustment_value_notify_cb),
+ scrollable);
+ }
+
+ priv->hadjustment = hadjustment;
+ g_object_notify (G_OBJECT (scrollable), "hadjustment");
+ }
+
+ if (vadjustment != priv->vadjustment)
+ {
+ if (priv->vadjustment)
+ {
+ g_signal_handlers_disconnect_by_func (priv->vadjustment,
+ adjustment_value_notify_cb,
+ scrollable);
+ g_object_unref (priv->vadjustment);
+ }
+
+ if (vadjustment)
+ {
+ g_object_ref (vadjustment);
+ g_signal_connect (vadjustment, "notify::value",
+ G_CALLBACK (adjustment_value_notify_cb),
+ scrollable);
+ }
+
+ priv->vadjustment = vadjustment;
+ g_object_notify (G_OBJECT (scrollable), "vadjustment");
+ }
+
+ g_object_thaw_notify (G_OBJECT (scrollable));
+}
+
+static void
+scrollable_get_adjustments (StScrollable *scrollable,
+ StAdjustment **hadjustment,
+ StAdjustment **vadjustment)
+{
+ StViewport *viewport = ST_VIEWPORT (scrollable);
+ StViewportPrivate *priv =
+ st_viewport_get_instance_private (viewport);
+
+ if (hadjustment)
+ *hadjustment = priv->hadjustment;
+
+ if (vadjustment)
+ *vadjustment = priv->vadjustment;
+}
+
+static void
+st_viewport_scrollable_interface_init (StScrollableInterface *iface)
+{
+ iface->set_adjustments = scrollable_set_adjustments;
+ iface->get_adjustments = scrollable_get_adjustments;
+}
+
+static void
+st_viewport_set_clip_to_view (StViewport *viewport,
+ gboolean clip_to_view)
+{
+ StViewportPrivate *priv =
+ st_viewport_get_instance_private (viewport);
+
+ if (!!priv->clip_to_view != !!clip_to_view)
+ {
+ priv->clip_to_view = clip_to_view;
+ clutter_actor_queue_redraw (CLUTTER_ACTOR (viewport));
+ g_object_notify_by_pspec (G_OBJECT (viewport), props[PROP_CLIP_TO_VIEW]);
+ }
+}
+
+static void
+st_viewport_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ StViewportPrivate *priv =
+ st_viewport_get_instance_private (ST_VIEWPORT (object));
+ StAdjustment *adjustment;
+
+ switch (property_id)
+ {
+ case PROP_HADJUST:
+ scrollable_get_adjustments (ST_SCROLLABLE (object), &adjustment, NULL);
+ g_value_set_object (value, adjustment);
+ break;
+
+ case PROP_VADJUST:
+ scrollable_get_adjustments (ST_SCROLLABLE (object), NULL, &adjustment);
+ g_value_set_object (value, adjustment);
+ break;
+
+ case PROP_CLIP_TO_VIEW:
+ g_value_set_boolean (value, priv->clip_to_view);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ }
+}
+
+static void
+st_viewport_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ StViewport *viewport = ST_VIEWPORT (object);
+ StViewportPrivate *priv =
+ st_viewport_get_instance_private (viewport);
+
+ switch (property_id)
+ {
+ case PROP_HADJUST:
+ scrollable_set_adjustments (ST_SCROLLABLE (object),
+ g_value_get_object (value),
+ priv->vadjustment);
+ break;
+
+ case PROP_VADJUST:
+ scrollable_set_adjustments (ST_SCROLLABLE (object),
+ priv->hadjustment,
+ g_value_get_object (value));
+ break;
+
+ case PROP_CLIP_TO_VIEW:
+ st_viewport_set_clip_to_view (viewport, g_value_get_boolean (value));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ }
+}
+
+static void
+st_viewport_dispose (GObject *object)
+{
+ StViewport *viewport = ST_VIEWPORT (object);
+ StViewportPrivate *priv =
+ st_viewport_get_instance_private (viewport);
+
+ g_clear_object (&priv->hadjustment);
+ g_clear_object (&priv->vadjustment);
+
+ G_OBJECT_CLASS (st_viewport_parent_class)->dispose (object);
+}
+
+static void
+st_viewport_allocate (ClutterActor *actor,
+ const ClutterActorBox *box)
+{
+ StViewport *viewport = ST_VIEWPORT (actor);
+ StViewportPrivate *priv =
+ st_viewport_get_instance_private (viewport);
+ StThemeNode *theme_node = st_widget_get_theme_node (ST_WIDGET (actor));
+ ClutterLayoutManager *layout = clutter_actor_get_layout_manager (actor);
+ ClutterActorBox viewport_box;
+ ClutterActorBox content_box;
+ float avail_width, avail_height;
+ float min_width, natural_width;
+ float min_height, natural_height;
+
+ st_theme_node_get_content_box (theme_node, box, &viewport_box);
+ clutter_actor_box_get_size (&viewport_box, &avail_width, &avail_height);
+
+ clutter_layout_manager_get_preferred_width (layout, CLUTTER_CONTAINER (actor),
+ avail_height,
+ &min_width, &natural_width);
+ clutter_layout_manager_get_preferred_height (layout, CLUTTER_CONTAINER (actor),
+ MAX (avail_width, min_width),
+ &min_height, &natural_height);
+
+ /* Because StViewport implements StScrollable, the allocation box passed here
+ * may not match the minimum sizes reported by the layout manager. When that
+ * happens, the content box needs to be adjusted to match the reported minimum
+ * sizes before being passed to clutter_layout_manager_allocate() */
+ clutter_actor_set_allocation (actor, box);
+
+ content_box = viewport_box;
+ if (priv->hadjustment)
+ content_box.x2 += MAX (0, min_width - avail_width);
+ if (priv->vadjustment)
+ content_box.y2 += MAX (0, min_height - avail_height);
+
+ clutter_layout_manager_allocate (layout, CLUTTER_CONTAINER (actor),
+ &content_box);
+
+ /* update adjustments for scrolling */
+ if (priv->vadjustment)
+ {
+ double prev_value;
+
+ g_object_set (G_OBJECT (priv->vadjustment),
+ "lower", 0.0,
+ "upper", MAX (min_height, avail_height),
+ "page-size", avail_height,
+ "step-increment", avail_height / 6,
+ "page-increment", avail_height - avail_height / 6,
+ NULL);
+
+ prev_value = st_adjustment_get_value (priv->vadjustment);
+ st_adjustment_set_value (priv->vadjustment, prev_value);
+ }
+
+ if (priv->hadjustment)
+ {
+ double prev_value;
+
+ g_object_set (G_OBJECT (priv->hadjustment),
+ "lower", 0.0,
+ "upper", MAX (min_width, avail_width),
+ "page-size", avail_width,
+ "step-increment", avail_width / 6,
+ "page-increment", avail_width - avail_width / 6,
+ NULL);
+
+ prev_value = st_adjustment_get_value (priv->hadjustment);
+ st_adjustment_set_value (priv->hadjustment, prev_value);
+ }
+}
+
+static double
+get_hadjustment_value (StViewport *viewport)
+{
+ StViewportPrivate *priv = st_viewport_get_instance_private (viewport);
+ ClutterTextDirection direction;
+ double x, upper, page_size;
+
+ if (!priv->hadjustment)
+ return 0;
+
+ st_adjustment_get_values (priv->hadjustment,
+ &x, NULL, &upper, NULL, NULL, &page_size);
+
+ direction = clutter_actor_get_text_direction (CLUTTER_ACTOR (viewport));
+ if (direction == CLUTTER_TEXT_DIRECTION_RTL)
+ return upper - page_size - x;
+
+ return x;
+}
+
+static void
+st_viewport_apply_transform (ClutterActor *actor,
+ graphene_matrix_t *matrix)
+{
+ StViewport *viewport = ST_VIEWPORT (actor);
+ StViewportPrivate *priv = st_viewport_get_instance_private (viewport);
+ ClutterActorClass *parent_class =
+ CLUTTER_ACTOR_CLASS (st_viewport_parent_class);
+ graphene_point3d_t p = GRAPHENE_POINT3D_INIT_ZERO;
+
+ if (priv->hadjustment)
+ p.x = -(int)get_hadjustment_value (viewport);
+
+ if (priv->vadjustment)
+ p.y = -(int)st_adjustment_get_value (priv->vadjustment);
+
+ graphene_matrix_translate (matrix, &p);
+
+ parent_class->apply_transform (actor, matrix);
+}
+
+/* If we are translated, then we need to translate back before chaining
+ * up or the background and borders will be drawn in the wrong place */
+static void
+get_border_paint_offsets (StViewport *viewport,
+ int *x,
+ int *y)
+{
+ StViewportPrivate *priv = st_viewport_get_instance_private (viewport);
+
+ if (priv->hadjustment)
+ *x = get_hadjustment_value (viewport);
+ else
+ *x = 0;
+
+ if (priv->vadjustment)
+ *y = st_adjustment_get_value (priv->vadjustment);
+ else
+ *y = 0;
+}
+
+
+static void
+st_viewport_paint (ClutterActor *actor,
+ ClutterPaintContext *paint_context)
+{
+ StViewport *viewport = ST_VIEWPORT (actor);
+ StViewportPrivate *priv = st_viewport_get_instance_private (viewport);
+ StThemeNode *theme_node = st_widget_get_theme_node (ST_WIDGET (actor));
+ int x, y;
+ ClutterActorBox allocation_box;
+ ClutterActorBox content_box;
+ ClutterActor *child;
+ CoglFramebuffer *fb = clutter_paint_context_get_framebuffer (paint_context);
+
+ get_border_paint_offsets (viewport, &x, &y);
+ if (x != 0 || y != 0)
+ {
+ cogl_framebuffer_push_matrix (fb);
+ cogl_framebuffer_translate (fb, x, y, 0);
+ }
+
+ st_widget_paint_background (ST_WIDGET (actor), paint_context);
+
+ if (x != 0 || y != 0)
+ cogl_framebuffer_pop_matrix (fb);
+
+ if (clutter_actor_get_n_children (actor) == 0)
+ return;
+
+ clutter_actor_get_allocation_box (actor, &allocation_box);
+ st_theme_node_get_content_box (theme_node, &allocation_box, &content_box);
+
+ content_box.x1 += x;
+ content_box.y1 += y;
+ content_box.x2 += x;
+ content_box.y2 += y;
+
+ /* The content area forms the viewport into the scrolled contents, while
+ * the borders and background stay in place; after drawing the borders and
+ * background, we clip to the content area */
+ if (priv->clip_to_view && (priv->hadjustment || priv->vadjustment))
+ {
+ cogl_framebuffer_push_rectangle_clip (fb,
+ (int)content_box.x1,
+ (int)content_box.y1,
+ (int)content_box.x2,
+ (int)content_box.y2);
+ }
+
+ for (child = clutter_actor_get_first_child (actor);
+ child != NULL;
+ child = clutter_actor_get_next_sibling (child))
+ clutter_actor_paint (child, paint_context);
+
+ if (priv->clip_to_view && (priv->hadjustment || priv->vadjustment))
+ cogl_framebuffer_pop_clip (fb);
+}
+
+static void
+st_viewport_pick (ClutterActor *actor,
+ ClutterPickContext *pick_context)
+{
+ StViewport *viewport = ST_VIEWPORT (actor);
+ StViewportPrivate *priv = st_viewport_get_instance_private (viewport);
+ StThemeNode *theme_node = st_widget_get_theme_node (ST_WIDGET (actor));
+ int x, y;
+ g_autoptr (ClutterActorBox) allocation_box = NULL;
+ ClutterActorBox content_box;
+ ClutterActor *child;
+
+ CLUTTER_ACTOR_CLASS (st_viewport_parent_class)->pick (actor, pick_context);
+
+ if (clutter_actor_get_n_children (actor) == 0)
+ return;
+
+ g_object_get (actor, "allocation", &allocation_box, NULL);
+ st_theme_node_get_content_box (theme_node, allocation_box, &content_box);
+
+ get_border_paint_offsets (viewport, &x, &y);
+
+ content_box.x1 += x;
+ content_box.y1 += y;
+ content_box.x2 += x;
+ content_box.y2 += y;
+
+ if (priv->hadjustment || priv->vadjustment)
+ clutter_pick_context_push_clip (pick_context, &content_box);
+
+ for (child = clutter_actor_get_first_child (actor);
+ child != NULL;
+ child = clutter_actor_get_next_sibling (child))
+ clutter_actor_pick (child, pick_context);
+
+ if (priv->hadjustment || priv->vadjustment)
+ clutter_pick_context_pop_clip (pick_context);
+}
+
+static gboolean
+st_viewport_get_paint_volume (ClutterActor *actor,
+ ClutterPaintVolume *volume)
+{
+ StViewport *viewport = ST_VIEWPORT (actor);
+ StViewportPrivate *priv = st_viewport_get_instance_private (viewport);
+ StThemeNode *theme_node = st_widget_get_theme_node (ST_WIDGET (actor));
+ ClutterActorBox allocation_box;
+ ClutterActorBox content_box;
+ int x, y;
+
+ /* Setting the paint volume does not make sense when we don't have any allocation */
+ if (!clutter_actor_has_allocation (actor))
+ return FALSE;
+
+ if (!priv->clip_to_view)
+ return CLUTTER_ACTOR_CLASS (st_viewport_parent_class)->get_paint_volume (actor, volume);
+
+ /* When have an adjustment we are clipped to the content box, so base
+ * our paint volume on that. */
+ if (priv->hadjustment || priv->vadjustment)
+ {
+ double width, height;
+
+ clutter_actor_get_allocation_box (actor, &allocation_box);
+ st_theme_node_get_content_box (theme_node, &allocation_box, &content_box);
+
+ width = content_box.x2 - content_box.x1;
+ height = content_box.y2 - content_box.y1;
+
+ clutter_paint_volume_set_width (volume, width);
+ clutter_paint_volume_set_height (volume, height);
+ }
+ else if (!CLUTTER_ACTOR_CLASS (st_viewport_parent_class)->get_paint_volume (actor, volume))
+ {
+ return FALSE;
+ }
+
+ /* When scrolled, st_viewport_apply_transform() includes the scroll offset
+ * and affects paint volumes. This is right for our children, but our paint volume
+ * is determined by our allocation and borders and doesn't scroll, so we need
+ * to reverse-compensate here, the same as we do when painting.
+ */
+ get_border_paint_offsets (viewport, &x, &y);
+ if (x != 0 || y != 0)
+ {
+ graphene_point3d_t origin;
+
+ clutter_paint_volume_get_origin (volume, &origin);
+ origin.x += x;
+ origin.y += y;
+ clutter_paint_volume_set_origin (volume, &origin);
+ }
+
+ return TRUE;
+}
+
+static void
+st_viewport_class_init (StViewportClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ ClutterActorClass *actor_class = CLUTTER_ACTOR_CLASS (klass);
+
+ object_class->get_property = st_viewport_get_property;
+ object_class->set_property = st_viewport_set_property;
+ object_class->dispose = st_viewport_dispose;
+
+ actor_class->allocate = st_viewport_allocate;
+ actor_class->apply_transform = st_viewport_apply_transform;
+
+ actor_class->paint = st_viewport_paint;
+ actor_class->get_paint_volume = st_viewport_get_paint_volume;
+ actor_class->pick = st_viewport_pick;
+
+ props[PROP_CLIP_TO_VIEW] =
+ g_param_spec_boolean ("clip-to-view",
+ "Clip to view",
+ "Clip to view",
+ TRUE,
+ ST_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
+
+ /* StScrollable properties */
+ g_object_class_override_property (object_class,
+ PROP_HADJUST,
+ "hadjustment");
+
+ g_object_class_override_property (object_class,
+ PROP_VADJUST,
+ "vadjustment");
+
+ g_object_class_install_properties (object_class, N_PROPS, props);
+}
+
+static void
+st_viewport_init (StViewport *self)
+{
+ StViewportPrivate *priv =
+ st_viewport_get_instance_private (self);
+
+ priv->clip_to_view = TRUE;
+}
diff --git a/src/st/st-viewport.h b/src/st/st-viewport.h
new file mode 100644
index 0000000..8f0b16b
--- /dev/null
+++ b/src/st/st-viewport.h
@@ -0,0 +1,40 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * st-viewport.h: viewport actor
+ *
+ * Copyright 2009 Intel Corporation.
+ * Copyright 2009, 2010 Red Hat, Inc.
+ * Copyright 2019 Endless, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU Lesser General Public License,
+ * version 2.1, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#if !defined(ST_H_INSIDE) && !defined(ST_COMPILATION)
+#error "Only <st/st.h> can be included directly.h"
+#endif
+
+#pragma once
+
+#include <st/st-widget.h>
+
+G_BEGIN_DECLS
+
+#define ST_TYPE_VIEWPORT (st_viewport_get_type())
+G_DECLARE_DERIVABLE_TYPE (StViewport, st_viewport, ST, VIEWPORT, StWidget)
+
+struct _StViewportClass
+{
+ StWidgetClass parent_class;
+};
+
+G_END_DECLS
diff --git a/src/st/st-widget-accessible.h b/src/st/st-widget-accessible.h
new file mode 100644
index 0000000..c60f778
--- /dev/null
+++ b/src/st/st-widget-accessible.h
@@ -0,0 +1,76 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * st-widget-accessible.h: Accessible object for StWidget
+ *
+ * Copyright 2010 Igalia, S.L.
+ * Author: Alejandro Piñeiro Iglesias <apinheiro@igalia.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU Lesser General Public License,
+ * version 2.1, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#if !defined(ST_H_INSIDE) && !defined(ST_COMPILATION)
+#error "Only <st/st.h> can be included directly.h"
+#endif
+
+#ifndef __ST_WIDGET_ACCESSIBLE_H__
+#define __ST_WIDGET_ACCESSIBLE_H__
+
+G_BEGIN_DECLS
+
+#include <st/st-widget.h>
+#include <cally/cally.h>
+
+#define ST_TYPE_WIDGET_ACCESSIBLE st_widget_accessible_get_type ()
+
+#define ST_WIDGET_ACCESSIBLE(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), \
+ ST_TYPE_WIDGET_ACCESSIBLE, StWidgetAccessible))
+
+#define ST_IS_WIDGET_ACCESSIBLE(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), \
+ ST_TYPE_WIDGET_ACCESSIBLE))
+
+#define ST_WIDGET_ACCESSIBLE_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), \
+ ST_TYPE_WIDGET_ACCESSIBLE, StWidgetAccessibleClass))
+
+#define ST_IS_WIDGET_ACCESSIBLE_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), \
+ ST_TYPE_WIDGET_ACCESSIBLE))
+
+#define ST_WIDGET_ACCESSIBLE_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), \
+ ST_TYPE_WIDGET_ACCESSIBLE, StWidgetAccessibleClass))
+
+typedef struct _StWidgetAccessible StWidgetAccessible;
+typedef struct _StWidgetAccessibleClass StWidgetAccessibleClass;
+typedef struct _StWidgetAccessiblePrivate StWidgetAccessiblePrivate;
+
+struct _StWidgetAccessible
+{
+ CallyActor parent;
+
+ /*< private >*/
+ StWidgetAccessiblePrivate *priv;
+};
+
+struct _StWidgetAccessibleClass
+{
+ CallyActorClass parent_class;
+};
+
+GType st_widget_accessible_get_type (void) G_GNUC_CONST;
+
+G_END_DECLS
+
+#endif /* __ST_WIDGET_ACCESSIBLE_H__ */
diff --git a/src/st/st-widget.c b/src/st/st-widget.c
new file mode 100644
index 0000000..31c400b
--- /dev/null
+++ b/src/st/st-widget.c
@@ -0,0 +1,3049 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * st-widget.c: Base class for St actors
+ *
+ * Copyright 2007 OpenedHand
+ * Copyright 2008, 2009 Intel Corporation.
+ * Copyright 2009, 2010 Red Hat, Inc.
+ * Copyright 2009 Abderrahim Kitouni
+ * Copyright 2009, 2010 Florian Müllner
+ * Copyright 2010 Adel Gadllah
+ * Copyright 2012 Igalia, S.L.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU Lesser General Public License,
+ * version 2.1, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <stdlib.h>
+#include <string.h>
+#include <math.h>
+
+#include <clutter/clutter.h>
+
+#include "st-widget.h"
+
+#include "st-label.h"
+#include "st-private.h"
+#include "st-settings.h"
+#include "st-texture-cache.h"
+#include "st-theme-context.h"
+#include "st-theme-node-transition.h"
+#include "st-theme-node-private.h"
+#include "st-drawing-area.h"
+
+#include "st-widget-accessible.h"
+
+#include <atk/atk-enum-types.h>
+
+/* This is set in stone and also hard-coded in GDK. */
+#define VIRTUAL_CORE_POINTER_ID 2
+
+/*
+ * Forward declaration for sake of StWidgetChild
+ */
+typedef struct _StWidgetPrivate StWidgetPrivate;
+struct _StWidgetPrivate
+{
+ StThemeNode *theme_node;
+ gchar *pseudo_class;
+ gchar *style_class;
+ gchar *inline_style;
+
+ StThemeNodeTransition *transition_animation;
+
+ guint is_style_dirty : 1;
+ guint first_child_dirty : 1;
+ guint last_child_dirty : 1;
+ guint draw_bg_color : 1;
+ guint draw_border_internal : 1;
+ guint track_hover : 1;
+ guint hover : 1;
+ guint can_focus : 1;
+
+ gulong texture_file_changed_id;
+ guint update_child_styles_id;
+
+ AtkObject *accessible;
+ AtkRole accessible_role;
+ AtkStateSet *local_state_set;
+
+ ClutterActor *label_actor;
+ gchar *accessible_name;
+
+ StWidget *last_visible_child;
+ StWidget *first_visible_child;
+
+ StThemeNodePaintState paint_states[2];
+ int current_paint_state : 2;
+};
+
+/**
+ * SECTION:st-widget
+ * @short_description: Base class for stylable actors
+ *
+ * #StWidget is a simple abstract class on top of #ClutterActor. It
+ * provides basic themeing properties.
+ *
+ * Actors in the St library should subclass #StWidget if they plan
+ * to obey to a certain #StStyle.
+ */
+
+enum
+{
+ PROP_0,
+
+ PROP_PSEUDO_CLASS,
+ PROP_STYLE_CLASS,
+ PROP_STYLE,
+ PROP_TRACK_HOVER,
+ PROP_HOVER,
+ PROP_CAN_FOCUS,
+ PROP_LABEL_ACTOR,
+ PROP_ACCESSIBLE_ROLE,
+ PROP_ACCESSIBLE_NAME,
+
+ N_PROPS
+};
+
+static GParamSpec *props[N_PROPS] = { NULL, };
+
+enum
+{
+ STYLE_CHANGED,
+ POPUP_MENU,
+
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = { 0, };
+
+G_DEFINE_TYPE_WITH_PRIVATE (StWidget, st_widget, CLUTTER_TYPE_ACTOR);
+#define ST_WIDGET_PRIVATE(w) ((StWidgetPrivate *)st_widget_get_instance_private (w))
+
+static void st_widget_recompute_style (StWidget *widget,
+ StThemeNode *old_theme_node);
+static gboolean st_widget_real_navigate_focus (StWidget *widget,
+ ClutterActor *from,
+ StDirectionType direction);
+
+static AtkObject * st_widget_get_accessible (ClutterActor *actor);
+static gboolean st_widget_has_accessible (ClutterActor *actor);
+
+static void
+st_widget_update_insensitive (StWidget *widget)
+{
+ if (clutter_actor_get_reactive (CLUTTER_ACTOR (widget)))
+ st_widget_remove_style_pseudo_class (widget, "insensitive");
+ else
+ st_widget_add_style_pseudo_class (widget, "insensitive");
+}
+
+static void
+st_widget_set_property (GObject *gobject,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ StWidget *actor = ST_WIDGET (gobject);
+
+ switch (prop_id)
+ {
+ case PROP_PSEUDO_CLASS:
+ st_widget_set_style_pseudo_class (actor, g_value_get_string (value));
+ break;
+
+ case PROP_STYLE_CLASS:
+ st_widget_set_style_class_name (actor, g_value_get_string (value));
+ break;
+
+ case PROP_STYLE:
+ st_widget_set_style (actor, g_value_get_string (value));
+ break;
+
+ case PROP_TRACK_HOVER:
+ st_widget_set_track_hover (actor, g_value_get_boolean (value));
+ break;
+
+ case PROP_HOVER:
+ st_widget_set_hover (actor, g_value_get_boolean (value));
+ break;
+
+ case PROP_CAN_FOCUS:
+ st_widget_set_can_focus (actor, g_value_get_boolean (value));
+ break;
+
+ case PROP_LABEL_ACTOR:
+ st_widget_set_label_actor (actor, g_value_get_object (value));
+ break;
+
+ case PROP_ACCESSIBLE_ROLE:
+ st_widget_set_accessible_role (actor, g_value_get_enum (value));
+ break;
+
+ case PROP_ACCESSIBLE_NAME:
+ st_widget_set_accessible_name (actor, g_value_get_string (value));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+st_widget_get_property (GObject *gobject,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ StWidget *actor = ST_WIDGET (gobject);
+ StWidgetPrivate *priv = st_widget_get_instance_private (ST_WIDGET (gobject));
+
+ switch (prop_id)
+ {
+ case PROP_PSEUDO_CLASS:
+ g_value_set_string (value, priv->pseudo_class);
+ break;
+
+ case PROP_STYLE_CLASS:
+ g_value_set_string (value, priv->style_class);
+ break;
+
+ case PROP_STYLE:
+ g_value_set_string (value, priv->inline_style);
+ break;
+
+ case PROP_TRACK_HOVER:
+ g_value_set_boolean (value, priv->track_hover);
+ break;
+
+ case PROP_HOVER:
+ g_value_set_boolean (value, priv->hover);
+ break;
+
+ case PROP_CAN_FOCUS:
+ g_value_set_boolean (value, priv->can_focus);
+ break;
+
+ case PROP_LABEL_ACTOR:
+ g_value_set_object (value, priv->label_actor);
+ break;
+
+ case PROP_ACCESSIBLE_ROLE:
+ g_value_set_enum (value, st_widget_get_accessible_role (actor));
+ break;
+
+ case PROP_ACCESSIBLE_NAME:
+ g_value_set_string (value, priv->accessible_name);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+st_widget_constructed (GObject *gobject)
+{
+ G_OBJECT_CLASS (st_widget_parent_class)->constructed (gobject);
+
+ st_widget_update_insensitive (ST_WIDGET (gobject));
+}
+
+static void
+st_widget_remove_transition (StWidget *widget)
+{
+ StWidgetPrivate *priv = st_widget_get_instance_private (widget);
+
+ if (priv->transition_animation)
+ {
+ g_object_run_dispose (G_OBJECT (priv->transition_animation));
+ g_object_unref (priv->transition_animation);
+ priv->transition_animation = NULL;
+ }
+}
+
+static void
+next_paint_state (StWidget *widget)
+{
+ StWidgetPrivate *priv = st_widget_get_instance_private (widget);
+
+ priv->current_paint_state = (priv->current_paint_state + 1) % G_N_ELEMENTS (priv->paint_states);
+}
+
+static StThemeNodePaintState *
+current_paint_state (StWidget *widget)
+{
+ StWidgetPrivate *priv = st_widget_get_instance_private (widget);
+
+ return &priv->paint_states[priv->current_paint_state];
+}
+
+static void
+st_widget_texture_cache_changed (StTextureCache *cache,
+ GFile *file,
+ gpointer user_data)
+{
+ StWidget *actor = ST_WIDGET (user_data);
+ StWidgetPrivate *priv = st_widget_get_instance_private (actor);
+ gboolean changed = FALSE;
+ int i;
+
+ for (i = 0; i < G_N_ELEMENTS (priv->paint_states); i++)
+ {
+ StThemeNodePaintState *paint_state = &priv->paint_states[i];
+ changed |= st_theme_node_paint_state_invalidate_for_file (paint_state, file);
+ }
+
+ if (changed && clutter_actor_is_mapped (CLUTTER_ACTOR (actor)))
+ clutter_actor_queue_redraw (CLUTTER_ACTOR (actor));
+}
+
+static void
+st_widget_dispose (GObject *gobject)
+{
+ StWidget *actor = ST_WIDGET (gobject);
+ StWidgetPrivate *priv = st_widget_get_instance_private (actor);
+
+ g_clear_pointer (&priv->theme_node, g_object_unref);
+
+ st_widget_remove_transition (actor);
+
+ g_clear_pointer (&priv->label_actor, g_object_unref);
+
+ g_clear_signal_handler (&priv->texture_file_changed_id,
+ st_texture_cache_get_default ());
+
+ g_clear_object (&priv->first_visible_child);
+ g_clear_object (&priv->last_visible_child);
+
+ G_OBJECT_CLASS (st_widget_parent_class)->dispose (gobject);
+
+ g_clear_handle_id (&priv->update_child_styles_id, g_source_remove);
+}
+
+static void
+st_widget_finalize (GObject *gobject)
+{
+ StWidgetPrivate *priv = st_widget_get_instance_private (ST_WIDGET (gobject));
+ guint i;
+
+ g_free (priv->style_class);
+ g_free (priv->pseudo_class);
+ g_object_unref (priv->local_state_set);
+ g_free (priv->accessible_name);
+ g_free (priv->inline_style);
+
+ for (i = 0; i < G_N_ELEMENTS (priv->paint_states); i++)
+ st_theme_node_paint_state_free (&priv->paint_states[i]);
+
+ G_OBJECT_CLASS (st_widget_parent_class)->finalize (gobject);
+}
+
+
+static void
+st_widget_get_preferred_width (ClutterActor *self,
+ gfloat for_height,
+ gfloat *min_width_p,
+ gfloat *natural_width_p)
+{
+ StThemeNode *theme_node = st_widget_get_theme_node (ST_WIDGET (self));
+
+ st_theme_node_adjust_for_width (theme_node, &for_height);
+
+ CLUTTER_ACTOR_CLASS (st_widget_parent_class)->get_preferred_width (self, for_height, min_width_p, natural_width_p);
+
+ st_theme_node_adjust_preferred_width (theme_node, min_width_p, natural_width_p);
+}
+
+static void
+st_widget_get_preferred_height (ClutterActor *self,
+ gfloat for_width,
+ gfloat *min_height_p,
+ gfloat *natural_height_p)
+{
+ StThemeNode *theme_node = st_widget_get_theme_node (ST_WIDGET (self));
+
+ st_theme_node_adjust_for_width (theme_node, &for_width);
+
+ CLUTTER_ACTOR_CLASS (st_widget_parent_class)->get_preferred_height (self, for_width, min_height_p, natural_height_p);
+
+ st_theme_node_adjust_preferred_height (theme_node, min_height_p, natural_height_p);
+}
+
+static void
+st_widget_allocate (ClutterActor *actor,
+ const ClutterActorBox *box)
+{
+ StThemeNode *theme_node = st_widget_get_theme_node (ST_WIDGET (actor));
+ ClutterActorBox content_box;
+
+ /* Note that we can't just chain up to clutter_actor_real_allocate --
+ * Clutter does some dirty tricks for backwards compatibility.
+ * Clutter also passes the actor's allocation directly to the layout
+ * manager, meaning that we can't modify it for children only.
+ */
+
+ clutter_actor_set_allocation (actor, box);
+
+ st_theme_node_get_content_box (theme_node, box, &content_box);
+
+ /* If we've chained up to here, we want to allocate the children using the
+ * currently installed layout manager */
+ clutter_layout_manager_allocate (clutter_actor_get_layout_manager (actor),
+ CLUTTER_CONTAINER (actor),
+ &content_box);
+}
+
+/**
+ * st_widget_paint_background:
+ * @widget: The #StWidget
+ *
+ * Paint the background of the widget. This is meant to be called by
+ * subclasses of StWidget that need to paint the background without
+ * painting children.
+ */
+void
+st_widget_paint_background (StWidget *widget,
+ ClutterPaintContext *paint_context)
+{
+ StWidgetPrivate *priv = st_widget_get_instance_private (widget);
+ CoglFramebuffer *framebuffer;
+ StThemeNode *theme_node;
+ ClutterActorBox allocation;
+ float resource_scale;
+ guint8 opacity;
+
+ resource_scale = clutter_actor_get_resource_scale (CLUTTER_ACTOR (widget));
+
+ framebuffer = clutter_paint_context_get_framebuffer (paint_context);
+ theme_node = st_widget_get_theme_node (widget);
+
+ clutter_actor_get_allocation_box (CLUTTER_ACTOR (widget), &allocation);
+
+ opacity = clutter_actor_get_paint_opacity (CLUTTER_ACTOR (widget));
+
+ if (priv->transition_animation)
+ st_theme_node_transition_paint (priv->transition_animation,
+ framebuffer,
+ &allocation,
+ opacity,
+ resource_scale);
+ else
+ st_theme_node_paint (theme_node,
+ current_paint_state (widget),
+ framebuffer,
+ &allocation,
+ opacity,
+ resource_scale);
+}
+
+static void
+st_widget_paint (ClutterActor *actor,
+ ClutterPaintContext *paint_context)
+{
+ st_widget_paint_background (ST_WIDGET (actor), paint_context);
+
+ /* Chain up so we paint children. */
+ CLUTTER_ACTOR_CLASS (st_widget_parent_class)->paint (actor, paint_context);
+}
+
+static void
+st_widget_parent_set (ClutterActor *widget,
+ ClutterActor *old_parent)
+{
+ StWidget *self = ST_WIDGET (widget);
+ ClutterActorClass *parent_class;
+
+ parent_class = CLUTTER_ACTOR_CLASS (st_widget_parent_class);
+ if (parent_class->parent_set)
+ parent_class->parent_set (widget, old_parent);
+
+ st_widget_style_changed (self);
+}
+
+static void
+st_widget_map (ClutterActor *actor)
+{
+ StWidget *self = ST_WIDGET (actor);
+
+ CLUTTER_ACTOR_CLASS (st_widget_parent_class)->map (actor);
+
+ st_widget_ensure_style (self);
+}
+
+static void
+st_widget_unmap (ClutterActor *actor)
+{
+ StWidget *self = ST_WIDGET (actor);
+ StWidgetPrivate *priv = st_widget_get_instance_private (self);
+
+ CLUTTER_ACTOR_CLASS (st_widget_parent_class)->unmap (actor);
+
+ st_widget_remove_transition (self);
+
+ if (priv->track_hover && priv->hover)
+ st_widget_set_hover (self, FALSE);
+}
+
+static void
+notify_children_of_style_change (ClutterActor *self)
+{
+ ClutterActorIter iter;
+ ClutterActor *actor;
+
+ clutter_actor_iter_init (&iter, self);
+ while (clutter_actor_iter_next (&iter, &actor))
+ {
+ if (ST_IS_WIDGET (actor))
+ st_widget_style_changed (ST_WIDGET (actor));
+ else
+ notify_children_of_style_change (actor);
+ }
+}
+
+static void
+st_widget_real_style_changed (StWidget *self)
+{
+ clutter_actor_queue_redraw ((ClutterActor *) self);
+}
+
+void
+st_widget_style_changed (StWidget *widget)
+{
+ StWidgetPrivate *priv = st_widget_get_instance_private (widget);
+ StThemeNode *old_theme_node = NULL;
+
+ priv->is_style_dirty = TRUE;
+ if (priv->theme_node)
+ {
+ old_theme_node = priv->theme_node;
+ priv->theme_node = NULL;
+ }
+
+ /* update the style only if we are mapped */
+ if (clutter_actor_is_mapped (CLUTTER_ACTOR (widget)))
+ st_widget_recompute_style (widget, old_theme_node);
+
+ /* Descend through all children. If the actor is not mapped,
+ * children will clear their theme node without recomputing style.
+ */
+ notify_children_of_style_change (CLUTTER_ACTOR (widget));
+
+ if (old_theme_node)
+ g_object_unref (old_theme_node);
+}
+
+static void
+on_theme_context_changed (StThemeContext *context,
+ ClutterStage *stage)
+{
+ notify_children_of_style_change (CLUTTER_ACTOR (stage));
+}
+
+static StThemeNode *
+get_root_theme_node (ClutterStage *stage)
+{
+ StThemeContext *context = st_theme_context_get_for_stage (stage);
+
+ if (!g_object_get_data (G_OBJECT (context), "st-theme-initialized"))
+ {
+ g_object_set_data (G_OBJECT (context), "st-theme-initialized", GUINT_TO_POINTER (1));
+ g_signal_connect (G_OBJECT (context), "changed",
+ G_CALLBACK (on_theme_context_changed), stage);
+ }
+
+ return st_theme_context_get_root_node (context);
+}
+
+/**
+ * st_widget_get_theme_node:
+ * @widget: a #StWidget
+ *
+ * Gets the theme node holding style information for the widget.
+ * The theme node is used to access standard and custom CSS
+ * properties of the widget.
+ *
+ * Note: it is a fatal error to call this on a widget that is
+ * not been added to a stage.
+ *
+ * Returns: (transfer none): the theme node for the widget.
+ * This is owned by the widget. When attributes of the widget
+ * or the environment that affect the styling change (for example
+ * the style_class property of the widget), it will be recreated,
+ * and the ::style-changed signal will be emitted on the widget.
+ */
+StThemeNode *
+st_widget_get_theme_node (StWidget *widget)
+{
+ StWidgetPrivate *priv;
+
+ g_return_val_if_fail (ST_IS_WIDGET (widget), NULL);
+
+ priv = st_widget_get_instance_private (widget);
+
+ if (priv->theme_node == NULL)
+ {
+ StThemeContext *context;
+ StThemeNode *tmp_node;
+ StThemeNode *parent_node = NULL;
+ ClutterStage *stage = NULL;
+ ClutterActor *parent;
+ char *pseudo_class, *direction_pseudo_class;
+
+ parent = clutter_actor_get_parent (CLUTTER_ACTOR (widget));
+ while (parent != NULL)
+ {
+ if (parent_node == NULL && ST_IS_WIDGET (parent))
+ parent_node = st_widget_get_theme_node (ST_WIDGET (parent));
+ else if (CLUTTER_IS_STAGE (parent))
+ stage = CLUTTER_STAGE (parent);
+
+ parent = clutter_actor_get_parent (parent);
+ }
+
+ if (stage == NULL)
+ {
+ g_autofree char *desc = st_describe_actor (CLUTTER_ACTOR (widget));
+
+ g_critical ("st_widget_get_theme_node called on the widget %s which is not in the stage.",
+ desc);
+
+ return g_object_new (ST_TYPE_THEME_NODE, NULL);
+ }
+
+ if (parent_node == NULL)
+ parent_node = get_root_theme_node (CLUTTER_STAGE (stage));
+
+ /* Always append a "magic" pseudo class indicating the text
+ * direction, to allow to adapt the CSS when necessary without
+ * requiring separate style sheets.
+ */
+ if (clutter_actor_get_text_direction (CLUTTER_ACTOR (widget)) == CLUTTER_TEXT_DIRECTION_RTL)
+ direction_pseudo_class = (char *)"rtl";
+ else
+ direction_pseudo_class = (char *)"ltr";
+
+ if (priv->pseudo_class)
+ pseudo_class = g_strconcat(priv->pseudo_class, " ",
+ direction_pseudo_class, NULL);
+ else
+ pseudo_class = direction_pseudo_class;
+
+ context = st_theme_context_get_for_stage (stage);
+ tmp_node = st_theme_node_new (context, parent_node, NULL,
+ G_OBJECT_TYPE (widget),
+ clutter_actor_get_name (CLUTTER_ACTOR (widget)),
+ priv->style_class,
+ pseudo_class,
+ priv->inline_style);
+
+ if (pseudo_class != direction_pseudo_class)
+ g_free (pseudo_class);
+
+ priv->theme_node = g_object_ref (st_theme_context_intern_node (context,
+ tmp_node));
+ g_object_unref (tmp_node);
+ }
+
+ return priv->theme_node;
+}
+
+/**
+ * st_widget_peek_theme_node:
+ * @widget: a #StWidget
+ *
+ * Returns the theme node for the widget if it has already been
+ * computed, %NULL if the widget hasn't been added to a stage or the theme
+ * node hasn't been computed. If %NULL is returned, then ::style-changed
+ * will be reliably emitted before the widget is allocated or painted.
+ *
+ * Returns: (transfer none): the theme node for the widget.
+ * This is owned by the widget. When attributes of the widget
+ * or the environment that affect the styling change (for example
+ * the style_class property of the widget), it will be recreated,
+ * and the ::style-changed signal will be emitted on the widget.
+ */
+StThemeNode *
+st_widget_peek_theme_node (StWidget *widget)
+{
+ g_return_val_if_fail (ST_IS_WIDGET (widget), NULL);
+
+ return ST_WIDGET_PRIVATE (widget)->theme_node;
+}
+
+static gboolean
+st_widget_enter (ClutterActor *actor,
+ ClutterCrossingEvent *event)
+{
+ StWidgetPrivate *priv = st_widget_get_instance_private (ST_WIDGET (actor));
+
+ if (priv->track_hover)
+ {
+ ClutterStage *stage;
+ ClutterActor *target;
+
+ stage = clutter_event_get_stage ((ClutterEvent *) event);
+ target = clutter_stage_get_event_actor (stage, (ClutterEvent *) event);
+
+ if (clutter_actor_contains (actor, target))
+ st_widget_set_hover (ST_WIDGET (actor), TRUE);
+ else
+ {
+ /* The widget has a grab and is being told about an
+ * enter-event outside its hierarchy. Hopefully we already
+ * got a leave-event, but if not, handle it now.
+ */
+ st_widget_set_hover (ST_WIDGET (actor), FALSE);
+ }
+ }
+
+ if (CLUTTER_ACTOR_CLASS (st_widget_parent_class)->enter_event)
+ return CLUTTER_ACTOR_CLASS (st_widget_parent_class)->enter_event (actor, event);
+ else
+ return FALSE;
+}
+
+static gboolean
+st_widget_leave (ClutterActor *actor,
+ ClutterCrossingEvent *event)
+{
+ StWidgetPrivate *priv = st_widget_get_instance_private (ST_WIDGET (actor));
+
+ if (priv->track_hover)
+ {
+ if (!event->related || !clutter_actor_contains (actor, event->related))
+ st_widget_set_hover (ST_WIDGET (actor), FALSE);
+ }
+
+ if (CLUTTER_ACTOR_CLASS (st_widget_parent_class)->leave_event)
+ return CLUTTER_ACTOR_CLASS (st_widget_parent_class)->leave_event (actor, event);
+ else
+ return FALSE;
+}
+
+static void
+st_widget_key_focus_in (ClutterActor *actor)
+{
+ StWidget *widget = ST_WIDGET (actor);
+
+ st_widget_add_style_pseudo_class (widget, "focus");
+}
+
+static void
+st_widget_key_focus_out (ClutterActor *actor)
+{
+ StWidget *widget = ST_WIDGET (actor);
+
+ st_widget_remove_style_pseudo_class (widget, "focus");
+}
+
+static gboolean
+st_widget_key_press_event (ClutterActor *actor,
+ ClutterKeyEvent *event)
+{
+ if (event->keyval == CLUTTER_KEY_Menu ||
+ (event->keyval == CLUTTER_KEY_F10 &&
+ (event->modifier_state & CLUTTER_SHIFT_MASK)))
+ {
+ st_widget_popup_menu (ST_WIDGET (actor));
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static gboolean
+st_widget_get_paint_volume (ClutterActor *self,
+ ClutterPaintVolume *volume)
+{
+ ClutterActorBox paint_box, alloc_box;
+ StThemeNode *theme_node;
+ StWidgetPrivate *priv;
+ graphene_point3d_t origin;
+
+ /* Setting the paint volume does not make sense when we don't have any allocation */
+ if (!clutter_actor_has_allocation (self))
+ return FALSE;
+
+ priv = st_widget_get_instance_private (ST_WIDGET (self));
+
+ theme_node = st_widget_get_theme_node (ST_WIDGET (self));
+ clutter_actor_get_allocation_box (self, &alloc_box);
+
+ if (priv->transition_animation)
+ st_theme_node_transition_get_paint_box (priv->transition_animation,
+ &alloc_box, &paint_box);
+ else
+ st_theme_node_get_paint_box (theme_node, &alloc_box, &paint_box);
+
+ origin.x = paint_box.x1 - alloc_box.x1;
+ origin.y = paint_box.y1 - alloc_box.y1;
+ origin.z = 0.0f;
+
+ clutter_paint_volume_set_origin (volume, &origin);
+ clutter_paint_volume_set_width (volume, paint_box.x2 - paint_box.x1);
+ clutter_paint_volume_set_height (volume, paint_box.y2 - paint_box.y1);
+
+ if (!clutter_actor_get_clip_to_allocation (self))
+ {
+ ClutterActor *child;
+ StShadow *shadow_spec = st_theme_node_get_text_shadow (theme_node);
+
+ if (shadow_spec)
+ {
+ ClutterActorBox shadow_box;
+
+ st_shadow_get_box (shadow_spec, &alloc_box, &shadow_box);
+ clutter_paint_volume_union_box (volume, &shadow_box);
+ }
+
+ /* Based on ClutterGroup/ClutterBox; include the children's
+ * paint volumes, since they may paint outside our allocation.
+ */
+ for (child = clutter_actor_get_first_child (self);
+ child != NULL;
+ child = clutter_actor_get_next_sibling (child))
+ {
+ const ClutterPaintVolume *child_volume;
+
+ if (!clutter_actor_is_visible (child))
+ continue;
+
+ child_volume = clutter_actor_get_transformed_paint_volume (child, self);
+ if (!child_volume)
+ return FALSE;
+
+ clutter_paint_volume_union (volume, child_volume);
+ }
+ }
+
+ return TRUE;
+}
+
+static GList *
+st_widget_real_get_focus_chain (StWidget *widget)
+{
+ GList *children, *l, *visible = NULL;
+
+ children = clutter_actor_get_children (CLUTTER_ACTOR (widget));
+
+ for (l = children; l; l = l->next)
+ {
+ if (clutter_actor_is_visible (CLUTTER_ACTOR (l->data)))
+ visible = g_list_prepend (visible, l->data);
+ }
+
+ g_list_free (children);
+
+ return g_list_reverse (visible);
+}
+
+static void
+st_widget_resource_scale_changed (ClutterActor *actor)
+{
+ StWidget *widget = ST_WIDGET (actor);
+ StWidgetPrivate *priv = st_widget_get_instance_private (widget);
+ int i;
+
+ for (i = 0; i < G_N_ELEMENTS (priv->paint_states); i++)
+ st_theme_node_paint_state_invalidate (&priv->paint_states[i]);
+
+ if (CLUTTER_ACTOR_CLASS (st_widget_parent_class)->resource_scale_changed)
+ CLUTTER_ACTOR_CLASS (st_widget_parent_class)->resource_scale_changed (actor);
+}
+
+static void
+st_widget_class_init (StWidgetClass *klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+ ClutterActorClass *actor_class = CLUTTER_ACTOR_CLASS (klass);
+
+ gobject_class->set_property = st_widget_set_property;
+ gobject_class->get_property = st_widget_get_property;
+ gobject_class->constructed = st_widget_constructed;
+ gobject_class->dispose = st_widget_dispose;
+ gobject_class->finalize = st_widget_finalize;
+
+ actor_class->get_preferred_width = st_widget_get_preferred_width;
+ actor_class->get_preferred_height = st_widget_get_preferred_height;
+ actor_class->allocate = st_widget_allocate;
+ actor_class->paint = st_widget_paint;
+ actor_class->get_paint_volume = st_widget_get_paint_volume;
+ actor_class->parent_set = st_widget_parent_set;
+ actor_class->map = st_widget_map;
+ actor_class->unmap = st_widget_unmap;
+
+ actor_class->enter_event = st_widget_enter;
+ actor_class->leave_event = st_widget_leave;
+ actor_class->key_focus_in = st_widget_key_focus_in;
+ actor_class->key_focus_out = st_widget_key_focus_out;
+ actor_class->key_press_event = st_widget_key_press_event;
+
+ actor_class->get_accessible = st_widget_get_accessible;
+ actor_class->has_accessible = st_widget_has_accessible;
+
+ actor_class->resource_scale_changed = st_widget_resource_scale_changed;
+
+ klass->style_changed = st_widget_real_style_changed;
+ klass->navigate_focus = st_widget_real_navigate_focus;
+ klass->get_accessible_type = st_widget_accessible_get_type;
+ klass->get_focus_chain = st_widget_real_get_focus_chain;
+
+ /**
+ * StWidget:pseudo-class:
+ *
+ * The pseudo-class of the actor. Typical values include "hover", "active",
+ * "focus".
+ */
+ props[PROP_PSEUDO_CLASS] =
+ g_param_spec_string ("pseudo-class",
+ "Pseudo Class",
+ "Pseudo class for styling",
+ "",
+ ST_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
+
+ /**
+ * StWidget:style-class:
+ *
+ * The style-class of the actor for use in styling.
+ */
+ props[PROP_STYLE_CLASS] =
+ g_param_spec_string ("style-class",
+ "Style Class",
+ "Style class for styling",
+ "",
+ ST_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
+
+ /**
+ * StWidget:style:
+ *
+ * Inline style information for the actor as a ';'-separated list of
+ * CSS properties.
+ */
+ props[PROP_STYLE] =
+ g_param_spec_string ("style",
+ "Style",
+ "Inline style string",
+ "",
+ ST_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
+
+ /**
+ * StWidget:track-hover:
+ *
+ * Determines whether the widget tracks pointer hover state. If
+ * %TRUE (and the widget is visible and reactive), the
+ * #StWidget:hover property and "hover" style pseudo class will be
+ * adjusted automatically as the pointer moves in and out of the
+ * widget.
+ */
+ props[PROP_TRACK_HOVER] =
+ g_param_spec_boolean ("track-hover",
+ "Track hover",
+ "Determines whether the widget tracks hover state",
+ FALSE,
+ ST_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
+
+ /**
+ * StWidget:hover:
+ *
+ * Whether or not the pointer is currently hovering over the widget. This is
+ * only tracked automatically if #StWidget:track-hover is %TRUE, but you can
+ * adjust it manually in any case.
+ */
+ props[PROP_HOVER] =
+ g_param_spec_boolean ("hover",
+ "Hover",
+ "Whether the pointer is hovering over the widget",
+ FALSE,
+ ST_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
+
+ /**
+ * StWidget:can-focus:
+ *
+ * Whether or not the widget can be focused via keyboard navigation.
+ */
+ props[PROP_CAN_FOCUS] =
+ g_param_spec_boolean ("can-focus",
+ "Can focus",
+ "Whether the widget can be focused via keyboard navigation",
+ FALSE,
+ ST_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
+
+ /**
+ * StWidget:label-actor:
+ *
+ * An actor that labels this widget.
+ */
+ props[PROP_LABEL_ACTOR] =
+ g_param_spec_object ("label-actor",
+ "Label",
+ "Label that identifies this widget",
+ CLUTTER_TYPE_ACTOR,
+ ST_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
+
+ /**
+ * StWidget:accessible-role:
+ *
+ * The accessible role of this object
+ */
+ props[PROP_ACCESSIBLE_ROLE] =
+ g_param_spec_enum ("accessible-role",
+ "Accessible Role",
+ "The accessible role of this object",
+ ATK_TYPE_ROLE,
+ ATK_ROLE_INVALID,
+ ST_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
+
+ /**
+ * StWidget:accessible-name:
+ *
+ * Object instance's name for assistive technology access.
+ */
+ props[PROP_ACCESSIBLE_NAME] =
+ g_param_spec_string ("accessible-name",
+ "Accessible name",
+ "Object instance's name for assistive technology access.",
+ NULL,
+ ST_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
+
+ g_object_class_install_properties (gobject_class, N_PROPS, props);
+
+ /**
+ * StWidget::style-changed:
+ * @widget: the #StWidget
+ *
+ * Emitted when the style information that the widget derives from the
+ * theme changes
+ */
+ signals[STYLE_CHANGED] =
+ g_signal_new ("style-changed",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (StWidgetClass, style_changed),
+ NULL, NULL, NULL,
+ G_TYPE_NONE, 0);
+
+ /**
+ * StWidget::popup-menu:
+ * @widget: the #StWidget
+ *
+ * Emitted when the user has requested a context menu (eg, via a keybinding)
+ */
+ signals[POPUP_MENU] =
+ g_signal_new ("popup-menu",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (StWidgetClass, popup_menu),
+ NULL, NULL, NULL,
+ G_TYPE_NONE, 0);
+}
+
+static const gchar *
+find_class_name (const gchar *class_list,
+ const gchar *class_name)
+{
+ gint len = strlen (class_name);
+ const gchar *match;
+
+ if (!class_list)
+ return NULL;
+
+ for (match = strstr (class_list, class_name); match; match = strstr (match + 1, class_name))
+ {
+ if ((match == class_list || g_ascii_isspace (match[-1])) &&
+ (match[len] == '\0' || g_ascii_isspace (match[len])))
+ return match;
+ }
+
+ return NULL;
+}
+
+static gboolean
+set_class_list (gchar **class_list,
+ const gchar *new_class_list)
+{
+ if (g_strcmp0 (*class_list, new_class_list) != 0)
+ {
+ g_free (*class_list);
+ *class_list = g_strdup (new_class_list);
+ return TRUE;
+ }
+ else
+ return FALSE;
+}
+
+static gboolean
+add_class_name (gchar **class_list,
+ const gchar *class_name)
+{
+ gchar *new_class_list;
+
+ if (*class_list)
+ {
+ if (find_class_name (*class_list, class_name))
+ return FALSE;
+
+ new_class_list = g_strdup_printf ("%s %s", *class_list, class_name);
+ g_free (*class_list);
+ *class_list = new_class_list;
+ }
+ else
+ *class_list = g_strdup (class_name);
+
+ return TRUE;
+}
+
+static gboolean
+remove_class_name (gchar **class_list,
+ const gchar *class_name)
+{
+ const gchar *match, *end;
+ gchar *new_class_list;
+
+ if (!*class_list)
+ return FALSE;
+
+ if (strcmp (*class_list, class_name) == 0)
+ {
+ g_free (*class_list);
+ *class_list = NULL;
+ return TRUE;
+ }
+
+ match = find_class_name (*class_list, class_name);
+ if (!match)
+ return FALSE;
+ end = match + strlen (class_name);
+
+ /* Adjust either match or end to include a space as well.
+ * (One or the other must be possible at this point.)
+ */
+ if (match != *class_list)
+ match--;
+ else
+ end++;
+
+ new_class_list = g_strdup_printf ("%.*s%s", (int)(match - *class_list),
+ *class_list, end);
+ g_free (*class_list);
+ *class_list = new_class_list;
+
+ return TRUE;
+}
+
+/**
+ * st_widget_set_style_class_name:
+ * @actor: a #StWidget
+ * @style_class_list: (nullable): a new style class list string
+ *
+ * Set the style class name list. @style_class_list can either be
+ * %NULL, for no classes, or a space-separated list of style class
+ * names. See also st_widget_add_style_class_name() and
+ * st_widget_remove_style_class_name().
+ */
+void
+st_widget_set_style_class_name (StWidget *actor,
+ const gchar *style_class_list)
+{
+ StWidgetPrivate *priv;
+
+ g_return_if_fail (ST_IS_WIDGET (actor));
+
+ priv = st_widget_get_instance_private (actor);
+
+ if (set_class_list (&priv->style_class, style_class_list))
+ {
+ st_widget_style_changed (actor);
+ g_object_notify_by_pspec (G_OBJECT (actor), props[PROP_STYLE_CLASS]);
+ }
+}
+
+/**
+ * st_widget_add_style_class_name:
+ * @actor: a #StWidget
+ * @style_class: a style class name string
+ *
+ * Adds @style_class to @actor's style class name list, if it is not
+ * already present.
+ */
+void
+st_widget_add_style_class_name (StWidget *actor,
+ const gchar *style_class)
+{
+ StWidgetPrivate *priv;
+
+ g_return_if_fail (ST_IS_WIDGET (actor));
+ g_return_if_fail (style_class != NULL);
+
+ priv = st_widget_get_instance_private (actor);
+
+ if (add_class_name (&priv->style_class, style_class))
+ {
+ st_widget_style_changed (actor);
+ g_object_notify_by_pspec (G_OBJECT (actor), props[PROP_STYLE_CLASS]);
+ }
+}
+
+/**
+ * st_widget_remove_style_class_name:
+ * @actor: a #StWidget
+ * @style_class: a style class name string
+ *
+ * Removes @style_class from @actor's style class name, if it is
+ * present.
+ */
+void
+st_widget_remove_style_class_name (StWidget *actor,
+ const gchar *style_class)
+{
+ StWidgetPrivate *priv;
+
+ g_return_if_fail (ST_IS_WIDGET (actor));
+ g_return_if_fail (style_class != NULL);
+
+ priv = st_widget_get_instance_private (actor);
+
+ if (remove_class_name (&priv->style_class, style_class))
+ {
+ st_widget_style_changed (actor);
+ g_object_notify_by_pspec (G_OBJECT (actor), props[PROP_STYLE_CLASS]);
+ }
+}
+
+/**
+ * st_widget_get_style_class_name:
+ * @actor: a #StWidget
+ *
+ * Get the current style class name
+ *
+ * Returns: the class name string. The string is owned by the #StWidget and
+ * should not be modified or freed.
+ */
+const gchar*
+st_widget_get_style_class_name (StWidget *actor)
+{
+ g_return_val_if_fail (ST_IS_WIDGET (actor), NULL);
+
+ return ST_WIDGET_PRIVATE (actor)->style_class;
+}
+
+/**
+ * st_widget_has_style_class_name:
+ * @actor: a #StWidget
+ * @style_class: a style class string
+ *
+ * Tests if @actor's style class list includes @style_class.
+ *
+ * Returns: whether or not @actor's style class list includes
+ * @style_class.
+ */
+gboolean
+st_widget_has_style_class_name (StWidget *actor,
+ const gchar *style_class)
+{
+ StWidgetPrivate *priv;
+
+ g_return_val_if_fail (ST_IS_WIDGET (actor), FALSE);
+
+ priv = st_widget_get_instance_private (actor);
+
+ return find_class_name (priv->style_class, style_class) != NULL;
+}
+
+/**
+ * st_widget_get_style_pseudo_class:
+ * @actor: a #StWidget
+ *
+ * Get the current style pseudo class list.
+ *
+ * Note that an actor can have multiple pseudo classes; if you just
+ * want to test for the presence of a specific pseudo class, use
+ * st_widget_has_style_pseudo_class().
+ *
+ * Returns: the pseudo class list string. The string is owned by the
+ * #StWidget and should not be modified or freed.
+ */
+const gchar*
+st_widget_get_style_pseudo_class (StWidget *actor)
+{
+ g_return_val_if_fail (ST_IS_WIDGET (actor), NULL);
+
+ return ST_WIDGET_PRIVATE (actor)->pseudo_class;
+}
+
+/**
+ * st_widget_has_style_pseudo_class:
+ * @actor: a #StWidget
+ * @pseudo_class: a pseudo class string
+ *
+ * Tests if @actor's pseudo class list includes @pseudo_class.
+ *
+ * Returns: whether or not @actor's pseudo class list includes
+ * @pseudo_class.
+ */
+gboolean
+st_widget_has_style_pseudo_class (StWidget *actor,
+ const gchar *pseudo_class)
+{
+ StWidgetPrivate *priv;
+
+ g_return_val_if_fail (ST_IS_WIDGET (actor), FALSE);
+
+ priv = st_widget_get_instance_private (actor);
+
+ return find_class_name (priv->pseudo_class, pseudo_class) != NULL;
+}
+
+/**
+ * st_widget_set_style_pseudo_class:
+ * @actor: a #StWidget
+ * @pseudo_class_list: (nullable): a new pseudo class list string
+ *
+ * Set the style pseudo class list. @pseudo_class_list can either be
+ * %NULL, for no classes, or a space-separated list of pseudo class
+ * names. See also st_widget_add_style_pseudo_class() and
+ * st_widget_remove_style_pseudo_class().
+ */
+void
+st_widget_set_style_pseudo_class (StWidget *actor,
+ const gchar *pseudo_class_list)
+{
+ StWidgetPrivate *priv;
+
+ g_return_if_fail (ST_IS_WIDGET (actor));
+
+ priv = st_widget_get_instance_private (actor);
+
+ if (set_class_list (&priv->pseudo_class, pseudo_class_list))
+ {
+ st_widget_style_changed (actor);
+ g_object_notify_by_pspec (G_OBJECT (actor), props[PROP_PSEUDO_CLASS]);
+ }
+}
+
+/**
+ * st_widget_add_style_pseudo_class:
+ * @actor: a #StWidget
+ * @pseudo_class: a pseudo class string
+ *
+ * Adds @pseudo_class to @actor's pseudo class list, if it is not
+ * already present.
+ */
+void
+st_widget_add_style_pseudo_class (StWidget *actor,
+ const gchar *pseudo_class)
+{
+ StWidgetPrivate *priv;
+
+ g_return_if_fail (ST_IS_WIDGET (actor));
+ g_return_if_fail (pseudo_class != NULL);
+
+ priv = st_widget_get_instance_private (actor);
+
+ if (add_class_name (&priv->pseudo_class, pseudo_class))
+ {
+ st_widget_style_changed (actor);
+ g_object_notify_by_pspec (G_OBJECT (actor), props[PROP_PSEUDO_CLASS]);
+ }
+}
+
+/**
+ * st_widget_remove_style_pseudo_class:
+ * @actor: a #StWidget
+ * @pseudo_class: a pseudo class string
+ *
+ * Removes @pseudo_class from @actor's pseudo class, if it is present.
+ */
+void
+st_widget_remove_style_pseudo_class (StWidget *actor,
+ const gchar *pseudo_class)
+{
+ StWidgetPrivate *priv;
+
+ g_return_if_fail (ST_IS_WIDGET (actor));
+ g_return_if_fail (pseudo_class != NULL);
+
+ priv = st_widget_get_instance_private (actor);
+
+ if (remove_class_name (&priv->pseudo_class, pseudo_class))
+ {
+ st_widget_style_changed (actor);
+ g_object_notify_by_pspec (G_OBJECT (actor), props[PROP_PSEUDO_CLASS]);
+ }
+}
+
+/**
+ * st_widget_set_style:
+ * @actor: a #StWidget
+ * @style: (nullable): a inline style string, or %NULL
+ *
+ * Set the inline style string for this widget. The inline style string is an
+ * optional ';'-separated list of CSS properties that override the style as
+ * determined from the stylesheets of the current theme.
+ */
+void
+st_widget_set_style (StWidget *actor,
+ const gchar *style)
+{
+ StWidgetPrivate *priv;
+
+ g_return_if_fail (ST_IS_WIDGET (actor));
+
+ priv = st_widget_get_instance_private (actor);
+
+ if (g_strcmp0 (style, priv->inline_style))
+ {
+ g_free (priv->inline_style);
+ priv->inline_style = g_strdup (style);
+
+ st_widget_style_changed (actor);
+
+ g_object_notify_by_pspec (G_OBJECT (actor), props[PROP_STYLE]);
+ }
+}
+
+/**
+ * st_widget_get_style:
+ * @actor: a #StWidget
+ *
+ * Get the current inline style string. See st_widget_set_style().
+ *
+ * Returns: (transfer none) (nullable): The inline style string, or %NULL. The
+ * string is owned by the #StWidget and should not be modified or freed.
+ */
+const gchar*
+st_widget_get_style (StWidget *actor)
+{
+ g_return_val_if_fail (ST_IS_WIDGET (actor), NULL);
+
+ return ST_WIDGET_PRIVATE (actor)->inline_style;
+}
+
+static void
+st_widget_set_first_visible_child (StWidget *widget,
+ ClutterActor *actor)
+{
+ StWidgetPrivate *priv = st_widget_get_instance_private (widget);
+
+ if (priv->first_visible_child == NULL && actor == NULL)
+ return;
+
+ if (priv->first_visible_child != NULL &&
+ CLUTTER_ACTOR (priv->first_visible_child) == actor)
+ return;
+
+ if (priv->first_visible_child != NULL)
+ {
+ st_widget_remove_style_pseudo_class (priv->first_visible_child, "first-child");
+ g_clear_object (&priv->first_visible_child);
+ }
+
+ if (actor == NULL)
+ return;
+
+ if (ST_IS_WIDGET (actor))
+ {
+ st_widget_add_style_pseudo_class (ST_WIDGET (actor), "first-child");
+ priv->first_visible_child = g_object_ref (ST_WIDGET (actor));
+ }
+}
+
+static void
+st_widget_set_last_visible_child (StWidget *widget,
+ ClutterActor *actor)
+{
+ StWidgetPrivate *priv = st_widget_get_instance_private (widget);
+
+ if (priv->last_visible_child == NULL && actor == NULL)
+ return;
+
+ if (priv->last_visible_child != NULL &&
+ CLUTTER_ACTOR (priv->last_visible_child) == actor)
+ return;
+
+ if (priv->last_visible_child != NULL)
+ {
+ st_widget_remove_style_pseudo_class (priv->last_visible_child, "last-child");
+ g_clear_object (&priv->last_visible_child);
+ }
+
+ if (actor == NULL)
+ return;
+
+ if (ST_IS_WIDGET (actor))
+ {
+ st_widget_add_style_pseudo_class (ST_WIDGET (actor), "last-child");
+ priv->last_visible_child = g_object_ref (ST_WIDGET (actor));
+ }
+}
+
+static void
+st_widget_name_notify (StWidget *widget,
+ GParamSpec *pspec,
+ gpointer data)
+{
+ st_widget_style_changed (widget);
+}
+
+static void
+st_widget_reactive_notify (StWidget *widget,
+ GParamSpec *pspec,
+ gpointer data)
+{
+ StWidgetPrivate *priv = st_widget_get_instance_private (widget);
+
+ st_widget_update_insensitive (widget);
+
+ if (priv->track_hover)
+ st_widget_sync_hover(widget);
+}
+
+static ClutterActor *
+find_nearest_visible_backwards (ClutterActor *actor)
+{
+ ClutterActor *prev = actor;
+
+ while (prev != NULL && !clutter_actor_is_visible (prev))
+ prev = clutter_actor_get_previous_sibling (prev);
+ return prev;
+}
+
+static ClutterActor *
+find_nearest_visible_forward (ClutterActor *actor)
+{
+ ClutterActor *next = actor;
+
+ while (next != NULL && !clutter_actor_is_visible (next))
+ next = clutter_actor_get_next_sibling (next);
+ return next;
+}
+
+static gboolean
+st_widget_update_child_styles (StWidget *widget)
+{
+ StWidgetPrivate *priv = st_widget_get_instance_private (widget);
+
+ if (priv->first_child_dirty)
+ {
+ ClutterActor *first_child;
+
+ priv->first_child_dirty = FALSE;
+
+ first_child = clutter_actor_get_first_child (CLUTTER_ACTOR (widget));
+ st_widget_set_first_visible_child (widget,
+ find_nearest_visible_forward (first_child));
+ }
+
+ if (priv->last_child_dirty)
+ {
+ ClutterActor *last_child;
+
+ priv->last_child_dirty = FALSE;
+
+ last_child = clutter_actor_get_last_child (CLUTTER_ACTOR (widget));
+ st_widget_set_last_visible_child (widget,
+ find_nearest_visible_backwards (last_child));
+ }
+
+ priv->update_child_styles_id = 0;
+ return G_SOURCE_REMOVE;
+}
+
+static void
+st_widget_queue_child_styles_update (StWidget *widget)
+{
+ StWidgetPrivate *priv = st_widget_get_instance_private (widget);
+
+ if (priv->update_child_styles_id != 0)
+ return;
+
+ priv->update_child_styles_id = g_idle_add ((GSourceFunc) st_widget_update_child_styles, widget);
+}
+
+static void
+st_widget_visible_notify (StWidget *widget,
+ GParamSpec *pspec,
+ gpointer data)
+{
+ StWidgetPrivate *parent_priv;
+ ClutterActor *actor = CLUTTER_ACTOR (widget);
+ ClutterActor *parent = clutter_actor_get_parent (actor);
+
+ if (parent == NULL || !ST_IS_WIDGET (parent))
+ return;
+
+ parent_priv = st_widget_get_instance_private (ST_WIDGET (parent));
+
+ if (clutter_actor_is_visible (actor))
+ {
+ ClutterActor *before, *after;
+
+ before = clutter_actor_get_previous_sibling (actor);
+ if (find_nearest_visible_backwards (before) == NULL)
+ parent_priv->first_child_dirty = TRUE;
+
+ after = clutter_actor_get_next_sibling (actor);
+ if (find_nearest_visible_forward (after) == NULL)
+ parent_priv->last_child_dirty = TRUE;
+ }
+ else
+ {
+ if (st_widget_has_style_pseudo_class (widget, "first-child"))
+ parent_priv->first_child_dirty = TRUE;
+
+ if (st_widget_has_style_pseudo_class (widget, "last-child"))
+ parent_priv->last_child_dirty = TRUE;
+ }
+
+ if (parent_priv->first_child_dirty || parent_priv->last_child_dirty)
+ st_widget_queue_child_styles_update (ST_WIDGET (parent));
+}
+
+static void
+st_widget_first_child_notify (StWidget *widget,
+ GParamSpec *pspec,
+ gpointer data)
+{
+ StWidgetPrivate *priv = st_widget_get_instance_private (widget);
+
+ priv->first_child_dirty = TRUE;
+ st_widget_queue_child_styles_update (widget);
+}
+
+static void
+st_widget_last_child_notify (StWidget *widget,
+ GParamSpec *pspec,
+ gpointer data)
+{
+ StWidgetPrivate *priv = st_widget_get_instance_private (widget);
+
+ priv->last_child_dirty = TRUE;
+ st_widget_queue_child_styles_update (widget);
+}
+
+static void
+st_widget_init (StWidget *actor)
+{
+ StWidgetPrivate *priv;
+ guint i;
+
+ priv = st_widget_get_instance_private (actor);
+ priv->transition_animation = NULL;
+ priv->local_state_set = atk_state_set_new ();
+
+ /* connect style changed */
+ g_signal_connect (actor, "notify::name", G_CALLBACK (st_widget_name_notify), NULL);
+ g_signal_connect (actor, "notify::reactive", G_CALLBACK (st_widget_reactive_notify), NULL);
+
+ g_signal_connect (actor, "notify::visible", G_CALLBACK (st_widget_visible_notify), NULL);
+ g_signal_connect (actor, "notify::first-child", G_CALLBACK (st_widget_first_child_notify), NULL);
+ g_signal_connect (actor, "notify::last-child", G_CALLBACK (st_widget_last_child_notify), NULL);
+ priv->texture_file_changed_id = g_signal_connect (st_texture_cache_get_default (), "texture-file-changed",
+ G_CALLBACK (st_widget_texture_cache_changed), actor);
+
+ for (i = 0; i < G_N_ELEMENTS (priv->paint_states); i++)
+ st_theme_node_paint_state_init (&priv->paint_states[i]);
+}
+
+static void
+on_transition_completed (StThemeNodeTransition *transition,
+ StWidget *widget)
+{
+ next_paint_state (widget);
+
+ st_theme_node_paint_state_copy (current_paint_state (widget),
+ st_theme_node_transition_get_new_paint_state (transition));
+
+ st_widget_remove_transition (widget);
+}
+
+static void
+st_widget_recompute_style (StWidget *widget,
+ StThemeNode *old_theme_node)
+{
+ StWidgetPrivate *priv = st_widget_get_instance_private (widget);
+ StThemeNode *new_theme_node = st_widget_get_theme_node (widget);
+ int transition_duration;
+ StSettings *settings;
+ gboolean paint_equal, geometry_equal = FALSE;
+ gboolean animations_enabled;
+
+ if (new_theme_node == old_theme_node)
+ {
+ priv->is_style_dirty = FALSE;
+ return;
+ }
+
+ _st_theme_node_apply_margins (new_theme_node, CLUTTER_ACTOR (widget));
+
+ if (old_theme_node)
+ geometry_equal = st_theme_node_geometry_equal (old_theme_node, new_theme_node);
+ if (!geometry_equal)
+ clutter_actor_queue_relayout ((ClutterActor *) widget);
+
+ transition_duration = st_theme_node_get_transition_duration (new_theme_node);
+
+ paint_equal = st_theme_node_paint_equal (old_theme_node, new_theme_node);
+
+ settings = st_settings_get ();
+ g_object_get (settings, "enable-animations", &animations_enabled, NULL);
+
+ if (animations_enabled && transition_duration > 0)
+ {
+ if (priv->transition_animation != NULL)
+ {
+ st_theme_node_transition_update (priv->transition_animation,
+ new_theme_node);
+ }
+ else if (old_theme_node && !paint_equal)
+ {
+ /* Since our transitions are only of the painting done by StThemeNode, we
+ * only want to start a transition when what is painted changes; if
+ * other visual aspects like the foreground color of a label change,
+ * we can't animate that anyways.
+ */
+
+ priv->transition_animation =
+ st_theme_node_transition_new (CLUTTER_ACTOR (widget),
+ old_theme_node,
+ new_theme_node,
+ current_paint_state (widget),
+ transition_duration);
+
+ g_signal_connect (priv->transition_animation, "completed",
+ G_CALLBACK (on_transition_completed), widget);
+ g_signal_connect_swapped (priv->transition_animation,
+ "new-frame",
+ G_CALLBACK (clutter_actor_queue_redraw),
+ widget);
+ }
+ }
+ else if (priv->transition_animation)
+ {
+ st_widget_remove_transition (widget);
+ }
+
+ if (!paint_equal)
+ {
+ clutter_actor_invalidate_paint_volume (CLUTTER_ACTOR (widget));
+
+ next_paint_state (widget);
+
+ if (!st_theme_node_paint_equal (new_theme_node, current_paint_state (widget)->node))
+ st_theme_node_paint_state_invalidate (current_paint_state (widget));
+ }
+
+ g_signal_emit (widget, signals[STYLE_CHANGED], 0);
+
+ priv->is_style_dirty = FALSE;
+}
+
+/**
+ * st_widget_ensure_style:
+ * @widget: A #StWidget
+ *
+ * Ensures that @widget has read its style information and propagated any
+ * changes to its children.
+ */
+void
+st_widget_ensure_style (StWidget *widget)
+{
+ StWidgetPrivate *priv;
+
+ g_return_if_fail (ST_IS_WIDGET (widget));
+
+ priv = st_widget_get_instance_private (widget);
+
+ if (priv->is_style_dirty)
+ {
+ st_widget_recompute_style (widget, NULL);
+ notify_children_of_style_change (CLUTTER_ACTOR (widget));
+ }
+}
+
+/**
+ * st_widget_set_track_hover:
+ * @widget: A #StWidget
+ * @track_hover: %TRUE if the widget should track the pointer hover state
+ *
+ * Enables hover tracking on the #StWidget.
+ *
+ * If hover tracking is enabled, and the widget is visible and
+ * reactive, then @widget's #StWidget:hover property will be updated
+ * automatically to reflect whether the pointer is in @widget (or one
+ * of its children), and @widget's #StWidget:pseudo-class will have
+ * the "hover" class added and removed from it accordingly.
+ *
+ * Note that currently it is not possible to correctly track the hover
+ * state when another actor has a pointer grab. You can use
+ * st_widget_sync_hover() to update the property manually in this
+ * case.
+ */
+void
+st_widget_set_track_hover (StWidget *widget,
+ gboolean track_hover)
+{
+ StWidgetPrivate *priv;
+
+ g_return_if_fail (ST_IS_WIDGET (widget));
+
+ priv = st_widget_get_instance_private (widget);
+
+ if (priv->track_hover != track_hover)
+ {
+ priv->track_hover = track_hover;
+ g_object_notify_by_pspec (G_OBJECT (widget), props[PROP_TRACK_HOVER]);
+
+ if (priv->track_hover)
+ st_widget_sync_hover (widget);
+ else
+ st_widget_set_hover (widget, FALSE);
+ }
+}
+
+/**
+ * st_widget_get_track_hover:
+ * @widget: A #StWidget
+ *
+ * Returns the current value of the #StWidget:track-hover property. See
+ * st_widget_set_track_hover() for more information.
+ *
+ * Returns: current value of track-hover on @widget
+ */
+gboolean
+st_widget_get_track_hover (StWidget *widget)
+{
+ g_return_val_if_fail (ST_IS_WIDGET (widget), FALSE);
+
+ return ST_WIDGET_PRIVATE (widget)->track_hover;
+}
+
+/**
+ * st_widget_set_hover:
+ * @widget: A #StWidget
+ * @hover: whether the pointer is hovering over the widget
+ *
+ * Sets @widget's hover property and adds or removes "hover" from its
+ * pseudo class accordingly.
+ *
+ * If you have set #StWidget:track-hover, you should not need to call
+ * this directly. You can call st_widget_sync_hover() if the hover
+ * state might be out of sync due to another actor's pointer grab.
+ */
+void
+st_widget_set_hover (StWidget *widget,
+ gboolean hover)
+{
+ StWidgetPrivate *priv;
+
+ g_return_if_fail (ST_IS_WIDGET (widget));
+
+ priv = st_widget_get_instance_private (widget);
+
+ if (priv->hover != hover)
+ {
+ priv->hover = hover;
+ if (priv->hover)
+ st_widget_add_style_pseudo_class (widget, "hover");
+ else
+ st_widget_remove_style_pseudo_class (widget, "hover");
+ g_object_notify_by_pspec (G_OBJECT (widget), props[PROP_HOVER]);
+ }
+}
+
+/**
+ * st_widget_sync_hover:
+ * @widget: A #StWidget
+ *
+ * Sets @widget's hover state according to the current pointer
+ * position. This can be used to ensure that it is correct after
+ * (or during) a pointer grab.
+ */
+void
+st_widget_sync_hover (StWidget *widget)
+{
+ ClutterInputDevice *pointer;
+ ClutterActor *stage;
+ ClutterActor *pointer_actor;
+ ClutterSeat *seat;
+
+ seat = clutter_backend_get_default_seat (clutter_get_default_backend ());
+ pointer = clutter_seat_get_pointer (seat);
+ stage = clutter_actor_get_stage (CLUTTER_ACTOR (widget));
+ if (!stage)
+ return;
+
+ pointer_actor = clutter_stage_get_device_actor (CLUTTER_STAGE (stage), pointer, NULL);
+ if (pointer_actor && clutter_actor_get_reactive (CLUTTER_ACTOR (widget)))
+ st_widget_set_hover (widget, clutter_actor_contains (CLUTTER_ACTOR (widget), pointer_actor));
+ else
+ st_widget_set_hover (widget, FALSE);
+}
+
+/**
+ * st_widget_get_hover:
+ * @widget: A #StWidget
+ *
+ * If #StWidget:track-hover is set, this returns whether the pointer
+ * is currently over the widget.
+ *
+ * Returns: current value of hover on @widget
+ */
+gboolean
+st_widget_get_hover (StWidget *widget)
+{
+ g_return_val_if_fail (ST_IS_WIDGET (widget), FALSE);
+
+ return ST_WIDGET_PRIVATE (widget)->hover;
+}
+
+/**
+ * st_widget_set_can_focus:
+ * @widget: A #StWidget
+ * @can_focus: %TRUE if the widget can receive keyboard focus
+ * via keyboard navigation
+ *
+ * Marks @widget as being able to receive keyboard focus via
+ * keyboard navigation.
+ */
+void
+st_widget_set_can_focus (StWidget *widget,
+ gboolean can_focus)
+{
+ StWidgetPrivate *priv;
+
+ g_return_if_fail (ST_IS_WIDGET (widget));
+
+ priv = st_widget_get_instance_private (widget);
+
+ if (priv->can_focus != can_focus)
+ {
+ priv->can_focus = can_focus;
+ g_object_notify_by_pspec (G_OBJECT (widget), props[PROP_CAN_FOCUS]);
+ }
+}
+
+/**
+ * st_widget_get_can_focus:
+ * @widget: A #StWidget
+ *
+ * Returns the current value of the can-focus property. See
+ * st_widget_set_can_focus() for more information.
+ *
+ * Returns: current value of can-focus on @widget
+ */
+gboolean
+st_widget_get_can_focus (StWidget *widget)
+{
+ g_return_val_if_fail (ST_IS_WIDGET (widget), FALSE);
+
+ return ST_WIDGET_PRIVATE (widget)->can_focus;
+}
+
+/**
+ * st_widget_popup_menu:
+ * @self: A #StWidget
+ *
+ * Asks the widget to pop-up a context menu by emitting #StWidget::popup-menu.
+ */
+void
+st_widget_popup_menu (StWidget *self)
+{
+ g_signal_emit (self, signals[POPUP_MENU], 0);
+}
+
+/* filter @children to contain only only actors that overlap @rbox
+ * when moving in @direction. (Assuming no transformations.)
+ */
+static GList *
+filter_by_position (GList *children,
+ ClutterActorBox *rbox,
+ StDirectionType direction)
+{
+ ClutterActorBox cbox;
+ graphene_point3d_t abs_vertices[4];
+ GList *l, *ret;
+ ClutterActor *child;
+
+ for (l = children, ret = NULL; l; l = l->next)
+ {
+ child = l->data;
+ clutter_actor_get_abs_allocation_vertices (child, abs_vertices);
+ clutter_actor_box_from_vertices (&cbox, abs_vertices);
+
+ /* Filter out children if they are in the wrong direction from
+ * @rbox, or if they don't overlap it. To account for floating-
+ * point imprecision, an actor is "down" (etc.) from an another
+ * actor even if it overlaps it by up to 0.1 pixels.
+ */
+ switch (direction)
+ {
+ case ST_DIR_UP:
+ if (cbox.y2 > rbox->y1 + 0.1)
+ continue;
+ break;
+
+ case ST_DIR_DOWN:
+ if (cbox.y1 < rbox->y2 - 0.1)
+ continue;
+ break;
+
+ case ST_DIR_LEFT:
+ if (cbox.x2 > rbox->x1 + 0.1)
+ continue;
+ break;
+
+ case ST_DIR_RIGHT:
+ if (cbox.x1 < rbox->x2 - 0.1)
+ continue;
+ break;
+
+ case ST_DIR_TAB_BACKWARD:
+ case ST_DIR_TAB_FORWARD:
+ default:
+ g_return_val_if_reached (NULL);
+ }
+
+ ret = g_list_prepend (ret, child);
+ }
+
+ g_list_free (children);
+ return ret;
+}
+
+
+static void
+get_midpoint (ClutterActorBox *box,
+ int *x,
+ int *y)
+{
+ *x = (box->x1 + box->x2) / 2;
+ *y = (box->y1 + box->y2) / 2;
+}
+
+static double
+get_distance (ClutterActor *actor,
+ ClutterActorBox *bbox)
+{
+ int ax, ay, bx, by, dx, dy;
+ ClutterActorBox abox;
+ graphene_point3d_t abs_vertices[4];
+
+ clutter_actor_get_abs_allocation_vertices (actor, abs_vertices);
+ clutter_actor_box_from_vertices (&abox, abs_vertices);
+
+ get_midpoint (&abox, &ax, &ay);
+ get_midpoint (bbox, &bx, &by);
+ dx = ax - bx;
+ dy = ay - by;
+
+ /* Not the exact distance, but good enough to sort by. */
+ return dx*dx + dy*dy;
+}
+
+static int
+sort_by_distance (gconstpointer a,
+ gconstpointer b,
+ gpointer user_data)
+{
+ ClutterActor *actor_a = (ClutterActor *)a;
+ ClutterActor *actor_b = (ClutterActor *)b;
+ ClutterActorBox *box = user_data;
+
+ return get_distance (actor_a, box) - get_distance (actor_b, box);
+}
+
+static gboolean
+st_widget_real_navigate_focus (StWidget *widget,
+ ClutterActor *from,
+ StDirectionType direction)
+{
+ StWidgetPrivate *priv = st_widget_get_instance_private (widget);
+ ClutterActor *widget_actor, *focus_child;
+ GList *children, *l;
+
+ widget_actor = CLUTTER_ACTOR (widget);
+ if (from == widget_actor)
+ return FALSE;
+
+ /* Figure out if @from is a descendant of @widget, and if so,
+ * set @focus_child to the immediate child of @widget that
+ * contains (or *is*) @from.
+ */
+ focus_child = from;
+ while (focus_child && clutter_actor_get_parent (focus_child) != widget_actor)
+ focus_child = clutter_actor_get_parent (focus_child);
+
+ if (priv->can_focus)
+ {
+ if (!focus_child)
+ {
+ if (clutter_actor_is_mapped (widget_actor))
+ {
+ /* Accept focus from outside */
+ clutter_actor_grab_key_focus (widget_actor);
+ return TRUE;
+ }
+ else
+ {
+ /* Refuse to set focus on hidden actors */
+ return FALSE;
+ }
+ }
+ else
+ {
+ /* Yield focus from within: since @widget itself is
+ * focusable we don't allow the focus to be navigated
+ * within @widget.
+ */
+ return FALSE;
+ }
+ }
+
+ /* See if we can navigate within @focus_child */
+ if (focus_child && ST_IS_WIDGET (focus_child))
+ {
+ if (st_widget_navigate_focus (ST_WIDGET (focus_child), from, direction, FALSE))
+ return TRUE;
+ }
+
+ children = st_widget_get_focus_chain (widget);
+ if (direction == ST_DIR_TAB_FORWARD ||
+ direction == ST_DIR_TAB_BACKWARD)
+ {
+ /* At this point we know that we want to navigate focus to one of
+ * @widget's immediate children; the next one after @focus_child, or the
+ * first one if @focus_child is %NULL. (With "next" and "first" being
+ * determined by @direction.)
+ */
+ if (direction == ST_DIR_TAB_BACKWARD)
+ children = g_list_reverse (children);
+
+ if (focus_child)
+ {
+ /* Remove focus_child and any earlier children */
+ while (children && children->data != focus_child)
+ children = g_list_delete_link (children, children);
+ if (children)
+ children = g_list_delete_link (children, children);
+ }
+ }
+ else /* direction is an arrow key, not tab */
+ {
+ ClutterActorBox sort_box;
+ graphene_point3d_t abs_vertices[4];
+
+ /* Compute the allocation box of the previous focused actor. If there
+ * was no previous focus, use the coordinates of the appropriate edge of
+ * @widget.
+ *
+ * Note that all of this code assumes the actors are not
+ * transformed (or at most, they are all scaled by the same
+ * amount). If @widget or any of its children is rotated, or
+ * any child is inconsistently scaled, then the focus chain will
+ * probably be unpredictable.
+ */
+ if (from)
+ {
+ clutter_actor_get_abs_allocation_vertices (from, abs_vertices);
+ clutter_actor_box_from_vertices (&sort_box, abs_vertices);
+ }
+ else
+ {
+ clutter_actor_get_abs_allocation_vertices (widget_actor, abs_vertices);
+ clutter_actor_box_from_vertices (&sort_box, abs_vertices);
+ switch (direction)
+ {
+ case ST_DIR_UP:
+ sort_box.y1 = sort_box.y2;
+ break;
+ case ST_DIR_DOWN:
+ sort_box.y2 = sort_box.y1;
+ break;
+ case ST_DIR_LEFT:
+ sort_box.x1 = sort_box.x2;
+ break;
+ case ST_DIR_RIGHT:
+ sort_box.x2 = sort_box.x1;
+ break;
+ case ST_DIR_TAB_FORWARD:
+ case ST_DIR_TAB_BACKWARD:
+ default:
+ g_warn_if_reached ();
+ }
+ }
+
+ if (from)
+ children = filter_by_position (children, &sort_box, direction);
+ if (children)
+ children = g_list_sort_with_data (children, sort_by_distance, &sort_box);
+ }
+
+ /* Now try each child in turn */
+ for (l = children; l; l = l->next)
+ {
+ if (ST_IS_WIDGET (l->data))
+ {
+ if (st_widget_navigate_focus (l->data, from, direction, FALSE))
+ {
+ g_list_free (children);
+ return TRUE;
+ }
+ }
+ }
+
+ g_list_free (children);
+ return FALSE;
+}
+
+
+/**
+ * st_widget_navigate_focus:
+ * @widget: the "top level" container
+ * @from: (nullable): the actor that the focus is coming from
+ * @direction: the direction focus is moving in
+ * @wrap_around: whether focus should wrap around
+ *
+ * Tries to update the keyboard focus within @widget in response to a
+ * keyboard event.
+ *
+ * If @from is a descendant of @widget, this attempts to move the
+ * keyboard focus to the next descendant of @widget (in the order
+ * implied by @direction) that has the #StWidget:can-focus property
+ * set. If @from is %NULL, this attempts to focus either @widget
+ * itself, or its first descendant in the order implied by
+ * @direction. If @from is outside of @widget, it behaves as if it was
+ * a descendant if @direction is one of the directional arrows and as
+ * if it was %NULL otherwise.
+ *
+ * If a container type is marked #StWidget:can-focus, the expected
+ * behavior is that it will only take up a single slot on the focus
+ * chain as a whole, rather than allowing navigation between its child
+ * actors (or having a distinction between itself being focused and
+ * one of its children being focused).
+ *
+ * Some widget classes might have slightly different behavior from the
+ * above, where that would make more sense.
+ *
+ * If @wrap_around is %TRUE and @from is a child of @widget, but the
+ * widget has no further children that can accept the focus in the
+ * given direction, then st_widget_navigate_focus() will try a second
+ * time, using a %NULL @from, which should cause it to reset the focus
+ * to the first available widget in the given direction.
+ *
+ * Returns: %TRUE if clutter_actor_grab_key_focus() has been
+ * called on an actor. %FALSE if not.
+ */
+gboolean
+st_widget_navigate_focus (StWidget *widget,
+ ClutterActor *from,
+ StDirectionType direction,
+ gboolean wrap_around)
+{
+ g_return_val_if_fail (ST_IS_WIDGET (widget), FALSE);
+
+ if (ST_WIDGET_GET_CLASS (widget)->navigate_focus (widget, from, direction))
+ return TRUE;
+ if (wrap_around && from && clutter_actor_contains (CLUTTER_ACTOR (widget), from))
+ return ST_WIDGET_GET_CLASS (widget)->navigate_focus (widget, NULL, direction);
+ return FALSE;
+}
+
+static gboolean
+append_actor_text (GString *desc,
+ ClutterActor *actor)
+{
+ if (CLUTTER_IS_TEXT (actor))
+ {
+ g_string_append_printf (desc, " (\"%s\")",
+ clutter_text_get_text (CLUTTER_TEXT (actor)));
+ return TRUE;
+ }
+ else if (ST_IS_LABEL (actor))
+ {
+ g_string_append_printf (desc, " (\"%s\")",
+ st_label_get_text (ST_LABEL (actor)));
+ return TRUE;
+ }
+ else
+ return FALSE;
+}
+
+/**
+ * st_describe_actor:
+ * @actor: a #ClutterActor
+ *
+ * Creates a string describing @actor, for use in debugging. This
+ * includes the class name and actor name (if any), plus if @actor
+ * is an #StWidget, its style class and pseudo class names.
+ *
+ * Returns: the debug name.
+ */
+char *
+st_describe_actor (ClutterActor *actor)
+{
+ GString *desc;
+ const char *name;
+ int i;
+
+ if (!actor)
+ return g_strdup ("[null]");
+
+ desc = g_string_new (NULL);
+ g_string_append_printf (desc, "[%p %s", actor,
+ G_OBJECT_TYPE_NAME (actor));
+
+ if (ST_IS_WIDGET (actor))
+ {
+ const char *style_class = st_widget_get_style_class_name (ST_WIDGET (actor));
+ const char *pseudo_class = st_widget_get_style_pseudo_class (ST_WIDGET (actor));
+ char **classes;
+
+ if (style_class)
+ {
+ classes = g_strsplit (style_class, ",", -1);
+ for (i = 0; classes[i]; i++)
+ {
+ g_strchug (classes[i]);
+ g_string_append_printf (desc, ".%s", classes[i]);
+ }
+ g_strfreev (classes);
+ }
+
+ if (pseudo_class)
+ {
+ classes = g_strsplit (pseudo_class, ",", -1);
+ for (i = 0; classes[i]; i++)
+ {
+ g_strchug (classes[i]);
+ g_string_append_printf (desc, ":%s", classes[i]);
+ }
+ g_strfreev (classes);
+ }
+ }
+
+ name = clutter_actor_get_name (actor);
+ if (name)
+ g_string_append_printf (desc, " \"%s\"", name);
+
+ if (!append_actor_text (desc, actor))
+ {
+ GList *children, *l;
+
+ /* Do a limited search of @actor's children looking for a label */
+ children = clutter_actor_get_children (actor);
+ for (l = children, i = 0; l && i < 20; l = l->next, i++)
+ {
+ if (append_actor_text (desc, l->data))
+ break;
+ children = g_list_concat (children, clutter_actor_get_children (l->data));
+ }
+ g_list_free (children);
+ }
+
+ g_string_append_c (desc, ']');
+
+ return g_string_free (desc, FALSE);
+}
+
+/**
+ * st_widget_get_label_actor:
+ * @widget: a #StWidget
+ *
+ * Gets the label that identifies @widget if it is defined
+ *
+ * Returns: (transfer none): the label that identifies the widget
+ */
+ClutterActor *
+st_widget_get_label_actor (StWidget *widget)
+{
+ g_return_val_if_fail (ST_IS_WIDGET (widget), NULL);
+
+ return ST_WIDGET_PRIVATE (widget)->label_actor;
+}
+
+/**
+ * st_widget_set_label_actor:
+ * @widget: a #StWidget
+ * @label: a #ClutterActor
+ *
+ * Sets @label as the #ClutterActor that identifies (labels)
+ * @widget. @label can be %NULL to indicate that @widget is not
+ * labelled any more
+ */
+
+void
+st_widget_set_label_actor (StWidget *widget,
+ ClutterActor *label)
+{
+ StWidgetPrivate *priv;
+
+ g_return_if_fail (ST_IS_WIDGET (widget));
+
+ priv = st_widget_get_instance_private (widget);
+
+ if (priv->label_actor != label)
+ {
+ if (priv->label_actor)
+ g_object_unref (priv->label_actor);
+
+ if (label != NULL)
+ priv->label_actor = g_object_ref (label);
+ else
+ priv->label_actor = NULL;
+
+ g_object_notify_by_pspec (G_OBJECT (widget), props[PROP_LABEL_ACTOR]);
+ }
+}
+
+/**
+ * st_widget_set_accessible_name:
+ * @widget: widget to set the accessible name for
+ * @name: (nullable): a character string to be set as the accessible name
+ *
+ * This method sets @name as the accessible name for @widget.
+ *
+ * Usually you will have no need to set the accessible name for an
+ * object, as usually there is a label for most of the interface
+ * elements. So in general it is better to just use
+ * @st_widget_set_label_actor. This method is only required when you
+ * need to set an accessible name and there is no available label
+ * object.
+ *
+ */
+void
+st_widget_set_accessible_name (StWidget *widget,
+ const gchar *name)
+{
+ StWidgetPrivate *priv;
+
+ g_return_if_fail (ST_IS_WIDGET (widget));
+
+ priv = st_widget_get_instance_private (widget);
+
+ if (g_strcmp0 (name, priv->accessible_name) == 0)
+ return;
+
+ if (priv->accessible_name != NULL)
+ g_free (priv->accessible_name);
+
+ priv->accessible_name = g_strdup (name);
+ g_object_notify_by_pspec (G_OBJECT (widget), props[PROP_ACCESSIBLE_NAME]);
+}
+
+/**
+ * st_widget_get_accessible_name:
+ * @widget: widget to get the accessible name for
+ *
+ * Gets the accessible name for this widget. See
+ * st_widget_set_accessible_name() for more information.
+ *
+ * Returns: a character string representing the accessible name
+ * of the widget.
+ */
+const gchar *
+st_widget_get_accessible_name (StWidget *widget)
+{
+ g_return_val_if_fail (ST_IS_WIDGET (widget), NULL);
+
+ return ST_WIDGET_PRIVATE (widget)->accessible_name;
+}
+
+/**
+ * st_widget_set_accessible_role:
+ * @widget: widget to set the accessible role for
+ * @role: The role to use
+ *
+ * This method sets @role as the accessible role for @widget. This
+ * role describes what kind of user interface element @widget is and
+ * is provided so that assistive technologies know how to present
+ * @widget to the user.
+ *
+ * Usually you will have no need to set the accessible role for an
+ * object, as this information is extracted from the context of the
+ * object (ie: a #StButton has by default a push button role). This
+ * method is only required when you need to redefine the role
+ * currently associated with the widget, for instance if it is being
+ * used in an unusual way (ie: a #StButton used as a togglebutton), or
+ * if a generic object is used directly (ie: a container as a menu
+ * item).
+ *
+ * If @role is #ATK_ROLE_INVALID, the role will not be changed
+ * and the accessible's default role will be used instead.
+ */
+void
+st_widget_set_accessible_role (StWidget *widget,
+ AtkRole role)
+{
+ StWidgetPrivate *priv;
+
+ g_return_if_fail (ST_IS_WIDGET (widget));
+
+ priv = st_widget_get_instance_private (widget);
+
+ if (priv->accessible_role == role)
+ return;
+
+ priv->accessible_role = role;
+
+ g_object_notify_by_pspec (G_OBJECT (widget), props[PROP_ACCESSIBLE_ROLE]);
+}
+
+
+/**
+ * st_widget_get_accessible_role:
+ * @widget: widget to get the accessible role for
+ *
+ * Gets the #AtkRole for this widget. See
+ * st_widget_set_accessible_role() for more information.
+ *
+ * Returns: accessible #AtkRole for this widget
+ */
+AtkRole
+st_widget_get_accessible_role (StWidget *widget)
+{
+ StWidgetPrivate *priv;
+ AtkRole role = ATK_ROLE_INVALID;
+
+ g_return_val_if_fail (ST_IS_WIDGET (widget), ATK_ROLE_INVALID);
+
+ priv = st_widget_get_instance_private (widget);
+
+ if (priv->accessible_role != ATK_ROLE_INVALID)
+ role = priv->accessible_role;
+ else if (priv->accessible != NULL)
+ role = atk_object_get_role (priv->accessible);
+
+ return role;
+}
+
+static void
+notify_accessible_state_change (StWidget *widget,
+ AtkStateType state,
+ gboolean value)
+{
+ StWidgetPrivate *priv = st_widget_get_instance_private (widget);
+
+ if (priv->accessible != NULL)
+ atk_object_notify_state_change (priv->accessible, state, value);
+}
+
+/**
+ * st_widget_add_accessible_state:
+ * @widget: A #StWidget
+ * @state: #AtkStateType state to add
+ *
+ * This method adds @state as one of the accessible states for
+ * @widget. The list of states of a widget describes the current state
+ * of user interface element @widget and is provided so that assistive
+ * technologies know how to present @widget to the user.
+ *
+ * Usually you will have no need to add accessible states for an
+ * object, as the accessible object can extract most of the states
+ * from the object itself (ie: a #StButton knows when it is pressed).
+ * This method is only required when one cannot extract the
+ * information automatically from the object itself (i.e.: a generic
+ * container used as a toggle menu item will not automatically include
+ * the toggled state).
+ *
+ */
+void
+st_widget_add_accessible_state (StWidget *widget,
+ AtkStateType state)
+{
+ StWidgetPrivate *priv;
+
+ g_return_if_fail (ST_IS_WIDGET (widget));
+
+ priv = st_widget_get_instance_private (widget);
+
+ if (atk_state_set_add_state (priv->local_state_set, state))
+ notify_accessible_state_change (widget, state, TRUE);
+}
+
+/**
+ * st_widget_remove_accessible_state:
+ * @widget: A #StWidget
+ * @state: #AtkState state to remove
+ *
+ * This method removes @state as on of the accessible states for
+ * @widget. See st_widget_add_accessible_state() for more information.
+ *
+ */
+void
+st_widget_remove_accessible_state (StWidget *widget,
+ AtkStateType state)
+{
+ StWidgetPrivate *priv;
+
+ g_return_if_fail (ST_IS_WIDGET (widget));
+
+ priv = st_widget_get_instance_private (widget);
+
+ if (atk_state_set_remove_state (priv->local_state_set, state))
+ notify_accessible_state_change (widget, state, FALSE);
+}
+
+/******************************************************************************/
+/*************************** ACCESSIBILITY SUPPORT ****************************/
+/******************************************************************************/
+
+/* GObject */
+
+static void st_widget_accessible_dispose (GObject *gobject);
+
+/* AtkObject */
+static AtkStateSet *st_widget_accessible_ref_state_set (AtkObject *obj);
+static void st_widget_accessible_initialize (AtkObject *obj,
+ gpointer data);
+static AtkRole st_widget_accessible_get_role (AtkObject *obj);
+
+/* Private methods */
+static void on_pseudo_class_notify (GObject *gobject,
+ GParamSpec *pspec,
+ gpointer data);
+static void on_can_focus_notify (GObject *gobject,
+ GParamSpec *pspec,
+ gpointer data);
+static void on_label_notify (GObject *gobject,
+ GParamSpec *pspec,
+ gpointer data);
+static void check_pseudo_class (StWidgetAccessible *self,
+ StWidget *widget);
+static void check_labels (StWidgetAccessible *self,
+ StWidget *widget);
+
+struct _StWidgetAccessiblePrivate
+{
+ /* Cached values (used to avoid extra notifications) */
+ gboolean selected;
+ gboolean checked;
+
+ /* The current_label. Right now there are the proper atk
+ * relationships between this object and the label
+ */
+ AtkObject *current_label;
+};
+
+G_DEFINE_TYPE_WITH_PRIVATE (StWidgetAccessible, st_widget_accessible, CALLY_TYPE_ACTOR)
+
+static gboolean
+st_widget_has_accessible (ClutterActor *actor)
+{
+ StWidget *widget;
+ StWidgetPrivate *priv;
+
+ g_return_val_if_fail (ST_IS_WIDGET (actor), FALSE);
+
+ widget = ST_WIDGET (actor);
+ priv = st_widget_get_instance_private (widget);
+
+ return priv->accessible != NULL;
+}
+
+static AtkObject *
+st_widget_get_accessible (ClutterActor *actor)
+{
+ StWidget *widget = NULL;
+ StWidgetPrivate *priv;
+
+ g_return_val_if_fail (ST_IS_WIDGET (actor), NULL);
+
+ widget = ST_WIDGET (actor);
+ priv = st_widget_get_instance_private (widget);
+
+ if (priv->accessible == NULL)
+ {
+ priv->accessible =
+ g_object_new (ST_WIDGET_GET_CLASS (widget)->get_accessible_type (),
+ NULL);
+
+ atk_object_initialize (priv->accessible, actor);
+
+ /* AtkGObjectAccessible, which StWidgetAccessible derives from, clears
+ * the back reference to the object in a weak notify for the object;
+ * weak-ref notification, which occurs during g_object_real_dispose(),
+ * is then the optimal time to clear the forward reference. We
+ * can't clear the reference in dispose() before chaining up, since
+ * clutter_actor_dispose() causes notifications to be sent out, which
+ * will result in a new accessible object being created.
+ */
+ g_object_add_weak_pointer (G_OBJECT (actor),
+ (gpointer *)&priv->accessible);
+ }
+
+ return priv->accessible;
+}
+
+/**
+ * st_widget_set_accessible:
+ * @widget: A #StWidget
+ * @accessible: an accessible (#AtkObject)
+ *
+ * This method allows to set a customly created accessible object to
+ * this widget. For example if you define a new subclass of
+ * #StWidgetAccessible at the javascript code.
+ *
+ * NULL is a valid value for @accessible. That contemplates the
+ * hypothetical case of not needing anymore a custom accessible object
+ * for the widget. Next call of st_widget_get_accessible() would
+ * create and return a default accessible.
+ *
+ * It assumes that the call to atk_object_initialize that bound the
+ * gobject with the custom accessible object was already called, so
+ * not a responsibility of this method.
+ *
+ */
+void
+st_widget_set_accessible (StWidget *widget,
+ AtkObject *accessible)
+{
+ StWidgetPrivate *priv;
+
+ g_return_if_fail (ST_IS_WIDGET (widget));
+ g_return_if_fail (accessible == NULL || ATK_IS_GOBJECT_ACCESSIBLE (accessible));
+
+ priv = st_widget_get_instance_private (widget);
+
+ if (priv->accessible != accessible)
+ {
+ if (priv->accessible)
+ {
+ g_object_remove_weak_pointer (G_OBJECT (widget),
+ (gpointer *)&priv->accessible);
+ g_object_unref (priv->accessible);
+ priv->accessible = NULL;
+ }
+
+ if (accessible)
+ {
+ priv->accessible = g_object_ref (accessible);
+ /* See note in st_widget_get_accessible() */
+ g_object_add_weak_pointer (G_OBJECT (widget),
+ (gpointer *)&priv->accessible);
+ }
+ else
+ priv->accessible = NULL;
+ }
+}
+
+static const gchar *
+st_widget_accessible_get_name (AtkObject *obj)
+{
+ const gchar* name = NULL;
+
+ g_return_val_if_fail (ST_IS_WIDGET_ACCESSIBLE (obj), NULL);
+
+ name = ATK_OBJECT_CLASS (st_widget_accessible_parent_class)->get_name (obj);
+ if (name == NULL)
+ {
+ StWidget *widget = NULL;
+
+ widget = ST_WIDGET (atk_gobject_accessible_get_object (ATK_GOBJECT_ACCESSIBLE (obj)));
+
+ if (widget == NULL)
+ name = NULL;
+ else
+ name = st_widget_get_accessible_name (widget);
+ }
+
+ return name;
+}
+
+static void
+st_widget_accessible_class_init (StWidgetAccessibleClass *klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+ AtkObjectClass *atk_class = ATK_OBJECT_CLASS (klass);
+
+ gobject_class->dispose = st_widget_accessible_dispose;
+
+ atk_class->ref_state_set = st_widget_accessible_ref_state_set;
+ atk_class->initialize = st_widget_accessible_initialize;
+ atk_class->get_role = st_widget_accessible_get_role;
+ atk_class->get_name = st_widget_accessible_get_name;
+}
+
+static void
+st_widget_accessible_init (StWidgetAccessible *self)
+{
+ StWidgetAccessiblePrivate *priv = st_widget_accessible_get_instance_private (self);
+
+ self->priv = priv;
+}
+
+static void
+st_widget_accessible_dispose (GObject *gobject)
+{
+ StWidgetAccessible *self = ST_WIDGET_ACCESSIBLE (gobject);
+
+ if (self->priv->current_label)
+ {
+ g_object_unref (self->priv->current_label);
+ self->priv->current_label = NULL;
+ }
+
+ G_OBJECT_CLASS (st_widget_accessible_parent_class)->dispose (gobject);
+}
+
+static void
+on_accessible_name_notify (GObject *gobject,
+ GParamSpec *pspec,
+ AtkObject *accessible)
+{
+ g_object_notify (G_OBJECT (accessible), "accessible-name");
+}
+
+static void
+st_widget_accessible_initialize (AtkObject *obj,
+ gpointer data)
+{
+ ATK_OBJECT_CLASS (st_widget_accessible_parent_class)->initialize (obj, data);
+
+ g_signal_connect (data, "notify::pseudo-class",
+ G_CALLBACK (on_pseudo_class_notify),
+ obj);
+
+ g_signal_connect (data, "notify::can-focus",
+ G_CALLBACK (on_can_focus_notify),
+ obj);
+
+ g_signal_connect (data, "notify::label-actor",
+ G_CALLBACK (on_label_notify),
+ obj);
+
+ g_signal_connect (data, "notify::accessible-name",
+ G_CALLBACK (on_accessible_name_notify),
+ obj);
+
+ /* Check the cached selected state and notify the first selection.
+ * Ie: it is required to ensure a first notification when Alt+Tab
+ * popup appears
+ */
+ check_pseudo_class (ST_WIDGET_ACCESSIBLE (obj), ST_WIDGET (data));
+ check_labels (ST_WIDGET_ACCESSIBLE (obj), ST_WIDGET (data));
+}
+
+static AtkStateSet *
+st_widget_accessible_ref_state_set (AtkObject *obj)
+{
+ AtkStateSet *result = NULL;
+ AtkStateSet *aux_set = NULL;
+ ClutterActor *actor = NULL;
+ StWidget *widget = NULL;
+ StWidgetPrivate *widget_priv;
+ StWidgetAccessible *self = NULL;
+
+ result = ATK_OBJECT_CLASS (st_widget_accessible_parent_class)->ref_state_set (obj);
+
+ actor = CLUTTER_ACTOR (atk_gobject_accessible_get_object (ATK_GOBJECT_ACCESSIBLE (obj)));
+
+ if (actor == NULL) /* State is defunct */
+ return result;
+
+ widget = ST_WIDGET (actor);
+ self = ST_WIDGET_ACCESSIBLE (obj);
+ widget_priv = st_widget_get_instance_private (widget);
+
+ /* priv->selected should be properly updated on the
+ * ATK_STATE_SELECTED notification callbacks
+ */
+ if (self->priv->selected)
+ atk_state_set_add_state (result, ATK_STATE_SELECTED);
+
+ if (self->priv->checked)
+ atk_state_set_add_state (result, ATK_STATE_CHECKED);
+
+ /* On clutter there isn't any tip to know if a actor is focusable or
+ * not, anyone can receive the key_focus. For this reason
+ * cally_actor sets any actor as FOCUSABLE. This is not the case on
+ * St, where we have can_focus. But this means that we need to
+ * remove the state FOCUSABLE if it is not focusable
+ */
+ if (st_widget_get_can_focus (widget))
+ atk_state_set_add_state (result, ATK_STATE_FOCUSABLE);
+ else
+ atk_state_set_remove_state (result, ATK_STATE_FOCUSABLE);
+
+ /* We add the states added externally if required */
+ if (!atk_state_set_is_empty (widget_priv->local_state_set))
+ {
+ aux_set = atk_state_set_or_sets (result, widget_priv->local_state_set);
+
+ g_object_unref (result); /* previous result will not be used */
+ result = aux_set;
+ }
+
+ return result;
+}
+
+static AtkRole
+st_widget_accessible_get_role (AtkObject *obj)
+{
+ StWidget *widget = NULL;
+ StWidgetPrivate *priv;
+
+ g_return_val_if_fail (ST_IS_WIDGET_ACCESSIBLE (obj), ATK_ROLE_INVALID);
+
+ widget = ST_WIDGET (atk_gobject_accessible_get_object (ATK_GOBJECT_ACCESSIBLE (obj)));
+
+ if (widget == NULL)
+ return ATK_ROLE_INVALID;
+
+ priv = st_widget_get_instance_private (widget);
+ if (priv->accessible_role != ATK_ROLE_INVALID)
+ return priv->accessible_role;
+
+ return ATK_OBJECT_CLASS (st_widget_accessible_parent_class)->get_role (obj);
+}
+
+static void
+on_pseudo_class_notify (GObject *gobject,
+ GParamSpec *pspec,
+ gpointer data)
+{
+ check_pseudo_class (ST_WIDGET_ACCESSIBLE (data),
+ ST_WIDGET (gobject));
+}
+
+/*
+ * In some cases the only way to check some states are checking the
+ * pseudo-class. Like if the object is selected (see bug 637830) or if
+ * the object is toggled. This method also notifies a state change if
+ * the value is different to the one cached.
+ *
+ * We also assume that if the object uses that pseudo-class, it makes
+ * sense to notify that state change. It would be possible to refine
+ * that behaviour checking the role (ie: notify CHECKED changes only
+ * for CHECK_BUTTON roles).
+ *
+ * In a ideal world we would have a more standard way to get the
+ * state, like the widget-context (as in the case of
+ * gtktreeview-cells), or something like the property "can-focus". But
+ * for the moment this is enough, and we can update that in the future
+ * if required.
+ */
+static void
+check_pseudo_class (StWidgetAccessible *self,
+ StWidget *widget)
+{
+ gboolean found = FALSE;
+
+ found = st_widget_has_style_pseudo_class (widget,
+ "selected");
+
+ if (found != self->priv->selected)
+ {
+ self->priv->selected = found;
+ atk_object_notify_state_change (ATK_OBJECT (self),
+ ATK_STATE_SELECTED,
+ found);
+ }
+
+ found = st_widget_has_style_pseudo_class (widget,
+ "checked");
+ if (found != self->priv->checked)
+ {
+ self->priv->checked = found;
+ atk_object_notify_state_change (ATK_OBJECT (self),
+ ATK_STATE_CHECKED,
+ found);
+ }
+}
+
+static void
+on_can_focus_notify (GObject *gobject,
+ GParamSpec *pspec,
+ gpointer data)
+{
+ gboolean can_focus = st_widget_get_can_focus (ST_WIDGET (gobject));
+
+ atk_object_notify_state_change (ATK_OBJECT (data),
+ ATK_STATE_FOCUSABLE, can_focus);
+}
+
+static void
+on_label_notify (GObject *gobject,
+ GParamSpec *pspec,
+ gpointer data)
+{
+ check_labels (ST_WIDGET_ACCESSIBLE (data), ST_WIDGET (gobject));
+}
+
+static void
+check_labels (StWidgetAccessible *widget_accessible,
+ StWidget *widget)
+{
+ ClutterActor *label = NULL;
+ AtkObject *label_accessible = NULL;
+
+ /* We only call this method at startup, and when the label changes,
+ * so it is fine to remove the previous relationships if we have the
+ * current_label by default
+ */
+ if (widget_accessible->priv->current_label != NULL)
+ {
+ AtkObject *previous_label = widget_accessible->priv->current_label;
+
+ atk_object_remove_relationship (ATK_OBJECT (widget_accessible),
+ ATK_RELATION_LABELLED_BY,
+ previous_label);
+
+ atk_object_remove_relationship (previous_label,
+ ATK_RELATION_LABEL_FOR,
+ ATK_OBJECT (widget_accessible));
+
+ g_object_unref (previous_label);
+ }
+
+ label = st_widget_get_label_actor (widget);
+ if (label == NULL)
+ {
+ widget_accessible->priv->current_label = NULL;
+ }
+ else
+ {
+ label_accessible = clutter_actor_get_accessible (label);
+ widget_accessible->priv->current_label = g_object_ref (label_accessible);
+
+ atk_object_add_relationship (ATK_OBJECT (widget_accessible),
+ ATK_RELATION_LABELLED_BY,
+ label_accessible);
+
+ atk_object_add_relationship (label_accessible,
+ ATK_RELATION_LABEL_FOR,
+ ATK_OBJECT (widget_accessible));
+ }
+}
+
+/**
+ * st_widget_get_focus_chain:
+ * @widget: An #StWidget
+ *
+ * Gets a list of the focusable children of @widget, in "Tab"
+ * order. By default, this returns all visible
+ * (as in clutter_actor_is_visible()) children of @widget.
+ *
+ * Returns: (element-type Clutter.Actor) (transfer container):
+ * @widget's focusable children
+ */
+GList *
+st_widget_get_focus_chain (StWidget *widget)
+{
+ return ST_WIDGET_GET_CLASS (widget)->get_focus_chain (widget);
+}
diff --git a/src/st/st-widget.h b/src/st/st-widget.h
new file mode 100644
index 0000000..f00c987
--- /dev/null
+++ b/src/st/st-widget.h
@@ -0,0 +1,167 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * st-widget.h: Base class for St actors
+ *
+ * Copyright 2007 OpenedHand
+ * Copyright 2008, 2009 Intel Corporation.
+ * Copyright 2009, 2010 Red Hat, Inc.
+ * Copyright 2009 Abderrahim Kitouni
+ * Copyright 2010 Florian Müllner
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU Lesser General Public License,
+ * version 2.1, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#if !defined(ST_H_INSIDE) && !defined(ST_COMPILATION)
+#error "Only <st/st.h> can be included directly.h"
+#endif
+
+#ifndef __ST_WIDGET_H__
+#define __ST_WIDGET_H__
+
+#include <clutter/clutter.h>
+#include <st/st-types.h>
+#include <st/st-theme.h>
+#include <st/st-theme-node.h>
+
+G_BEGIN_DECLS
+
+#define ST_TYPE_WIDGET (st_widget_get_type ())
+G_DECLARE_DERIVABLE_TYPE (StWidget, st_widget, ST, WIDGET, ClutterActor)
+
+/**
+ * StDirectionType:
+ * @ST_DIR_TAB_FORWARD: Move forward.
+ * @ST_DIR_TAB_BACKWARD: Move backward.
+ * @ST_DIR_UP: Move up.
+ * @ST_DIR_DOWN: Move down.
+ * @ST_DIR_LEFT: Move left.
+ * @ST_DIR_RIGHT: Move right.
+ *
+ * Enumeration for focus direction.
+ */
+typedef enum
+{
+ ST_DIR_TAB_FORWARD,
+ ST_DIR_TAB_BACKWARD,
+ ST_DIR_UP,
+ ST_DIR_DOWN,
+ ST_DIR_LEFT,
+ ST_DIR_RIGHT,
+} StDirectionType;
+
+typedef struct _StWidgetClass StWidgetClass;
+
+/**
+ * StWidgetClass:
+ *
+ * Base class for stylable actors.
+ */
+struct _StWidgetClass
+{
+ /*< private >*/
+ ClutterActorClass parent_class;
+
+ /* signals */
+ void (* style_changed) (StWidget *self);
+ void (* popup_menu) (StWidget *self);
+
+ /* vfuncs */
+
+ /**
+ * StWidgetClass::navigate_focus:
+ * @self: the "top level" container
+ * @from: (nullable): the actor that the focus is coming from
+ * @direction: the direction focus is moving in
+ */
+ gboolean (* navigate_focus) (StWidget *self,
+ ClutterActor *from,
+ StDirectionType direction);
+ GType (* get_accessible_type) (void);
+
+ GList * (* get_focus_chain) (StWidget *widget);
+};
+
+void st_widget_set_style_pseudo_class (StWidget *actor,
+ const gchar *pseudo_class_list);
+void st_widget_add_style_pseudo_class (StWidget *actor,
+ const gchar *pseudo_class);
+void st_widget_remove_style_pseudo_class (StWidget *actor,
+ const gchar *pseudo_class);
+const gchar * st_widget_get_style_pseudo_class (StWidget *actor);
+gboolean st_widget_has_style_pseudo_class (StWidget *actor,
+ const gchar *pseudo_class);
+
+void st_widget_set_style_class_name (StWidget *actor,
+ const gchar *style_class_list);
+void st_widget_add_style_class_name (StWidget *actor,
+ const gchar *style_class);
+void st_widget_remove_style_class_name (StWidget *actor,
+ const gchar *style_class);
+const gchar * st_widget_get_style_class_name (StWidget *actor);
+gboolean st_widget_has_style_class_name (StWidget *actor,
+ const gchar *style_class);
+
+void st_widget_set_style (StWidget *actor,
+ const gchar *style);
+const gchar * st_widget_get_style (StWidget *actor);
+void st_widget_set_track_hover (StWidget *widget,
+ gboolean track_hover);
+gboolean st_widget_get_track_hover (StWidget *widget);
+void st_widget_set_hover (StWidget *widget,
+ gboolean hover);
+void st_widget_sync_hover (StWidget *widget);
+gboolean st_widget_get_hover (StWidget *widget);
+void st_widget_popup_menu (StWidget *self);
+
+void st_widget_ensure_style (StWidget *widget);
+
+void st_widget_set_can_focus (StWidget *widget,
+ gboolean can_focus);
+gboolean st_widget_get_can_focus (StWidget *widget);
+gboolean st_widget_navigate_focus (StWidget *widget,
+ ClutterActor *from,
+ StDirectionType direction,
+ gboolean wrap_around);
+
+ClutterActor * st_widget_get_label_actor (StWidget *widget);
+void st_widget_set_label_actor (StWidget *widget,
+ ClutterActor *label);
+
+/* Only to be used by sub-classes of StWidget */
+void st_widget_style_changed (StWidget *widget);
+StThemeNode * st_widget_get_theme_node (StWidget *widget);
+StThemeNode * st_widget_peek_theme_node (StWidget *widget);
+
+GList * st_widget_get_focus_chain (StWidget *widget);
+void st_widget_paint_background (StWidget *widget,
+ ClutterPaintContext *paint_context);
+
+/* debug methods */
+char *st_describe_actor (ClutterActor *actor);
+
+/* accessibility methods */
+void st_widget_set_accessible_role (StWidget *widget,
+ AtkRole role);
+AtkRole st_widget_get_accessible_role (StWidget *widget);
+void st_widget_add_accessible_state (StWidget *widget,
+ AtkStateType state);
+void st_widget_remove_accessible_state (StWidget *widget,
+ AtkStateType state);
+void st_widget_set_accessible_name (StWidget *widget,
+ const gchar *name);
+const gchar * st_widget_get_accessible_name (StWidget *widget);
+void st_widget_set_accessible (StWidget *widget,
+ AtkObject *accessible);
+G_END_DECLS
+
+#endif /* __ST_WIDGET_H__ */
diff --git a/src/st/st.h.in b/src/st/st.h.in
new file mode 100644
index 0000000..825c820
--- /dev/null
+++ b/src/st/st.h.in
@@ -0,0 +1,3 @@
+#define ST_H_INSIDE 1
+@includes@
+#undef ST_H_INSIDE
diff --git a/src/st/test-theme.c b/src/st/test-theme.c
new file mode 100644
index 0000000..3fcbd99
--- /dev/null
+++ b/src/st/test-theme.c
@@ -0,0 +1,637 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * test-theme.c: test program for CSS styling code
+ *
+ * Copyright 2009, 2010 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation, either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <clutter/clutter.h>
+#include "st-theme.h"
+#include "st-theme-context.h"
+#include "st-label.h"
+#include "st-button.h"
+#include <math.h>
+#include <string.h>
+#include <meta-test/meta-context-test.h>
+#include <meta/meta-backend.h>
+
+static ClutterActor *stage;
+static StThemeNode *root;
+static StThemeNode *group1;
+static StThemeNode *text1;
+static StThemeNode *text2;
+static StThemeNode *group2;
+static StThemeNode *text3;
+static StThemeNode *text4;
+static StThemeNode *group3;
+static StThemeNode *group4;
+static StThemeNode *group5;
+static StThemeNode *group6;
+static StThemeNode *button;
+static gboolean fail;
+
+static const char *test;
+
+static void
+assert_font (StThemeNode *node,
+ const char *node_description,
+ const char *expected)
+{
+ char *value = pango_font_description_to_string (st_theme_node_get_font (node));
+
+ if (strcmp (expected, value) != 0)
+ {
+ g_print ("%s: %s.font: expected: %s, got: %s\n",
+ test, node_description, expected, value);
+ fail = TRUE;
+ }
+
+ g_free (value);
+}
+
+static void
+assert_font_features (StThemeNode *node,
+ const char *node_description,
+ const char *expected)
+{
+ char *value = st_theme_node_get_font_features (node);
+
+ if (g_strcmp0 (expected, value) != 0)
+ {
+ g_print ("%s: %s.font-feature-settings: expected: %s, got: %s\n",
+ test, node_description, expected, value);
+ fail = TRUE;
+ }
+
+ if (value)
+ g_free (value);
+}
+
+static char *
+text_decoration_to_string (StTextDecoration decoration)
+{
+ GString *result = g_string_new (NULL);
+
+ if (decoration & ST_TEXT_DECORATION_UNDERLINE)
+ g_string_append(result, " underline");
+ if (decoration & ST_TEXT_DECORATION_OVERLINE)
+ g_string_append(result, " overline");
+ if (decoration & ST_TEXT_DECORATION_LINE_THROUGH)
+ g_string_append(result, " line_through");
+ if (decoration & ST_TEXT_DECORATION_BLINK)
+ g_string_append(result, " blink");
+
+ if (result->len > 0)
+ g_string_erase (result, 0, 1);
+ else
+ g_string_append(result, "none");
+
+ return g_string_free (result, FALSE);
+}
+
+static void
+assert_text_decoration (StThemeNode *node,
+ const char *node_description,
+ StTextDecoration expected)
+{
+ StTextDecoration value = st_theme_node_get_text_decoration (node);
+ if (expected != value)
+ {
+ char *es = text_decoration_to_string (expected);
+ char *vs = text_decoration_to_string (value);
+
+ g_print ("%s: %s.text-decoration: expected: %s, got: %s\n",
+ test, node_description, es, vs);
+ fail = TRUE;
+
+ g_free (es);
+ g_free (vs);
+ }
+}
+
+static void
+assert_foreground_color (StThemeNode *node,
+ const char *node_description,
+ guint32 expected)
+{
+ ClutterColor color;
+ guint32 value;
+
+ st_theme_node_get_foreground_color (node, &color);
+ value = clutter_color_to_pixel (&color);
+
+ if (expected != value)
+ {
+ g_print ("%s: %s.color: expected: #%08x, got: #%08x\n",
+ test, node_description, expected, value);
+ fail = TRUE;
+ }
+}
+
+static void
+assert_background_color (StThemeNode *node,
+ const char *node_description,
+ guint32 expected)
+{
+ ClutterColor color;
+ guint32 value;
+
+ st_theme_node_get_background_color (node, &color);
+ value = clutter_color_to_pixel (&color);
+
+ if (expected != value)
+ {
+ g_print ("%s: %s.background-color: expected: #%08x, got: #%08x\n",
+ test, node_description, expected, value);
+ fail = TRUE;
+ }
+}
+
+static const char *
+side_to_string (StSide side)
+{
+ switch (side)
+ {
+ case ST_SIDE_TOP:
+ return "top";
+ case ST_SIDE_RIGHT:
+ return "right";
+ case ST_SIDE_BOTTOM:
+ return "bottom";
+ case ST_SIDE_LEFT:
+ return "left";
+ default:
+ return "<unknown>";
+ }
+}
+
+static void
+assert_border_color (StThemeNode *node,
+ const char *node_description,
+ StSide side,
+ guint32 expected)
+{
+ ClutterColor color;
+ guint32 value;
+
+ st_theme_node_get_border_color (node, side, &color);
+ value = clutter_color_to_pixel (&color);
+
+ if (expected != value)
+ {
+ g_print ("%s: %s.border-%s-color: expected: #%08x, got: #%08x\n",
+ test, node_description, side_to_string (side), expected, value);
+ fail = TRUE;
+ }
+}
+
+static void
+assert_background_image (StThemeNode *node,
+ const char *node_description,
+ const char *expected)
+{
+ GFile *value = st_theme_node_get_background_image (node);
+ GFile *expected_file;
+
+ if (expected != NULL && value != NULL)
+ {
+ expected_file = g_file_new_for_path (expected);
+
+ if (!g_file_equal (expected_file, value))
+ {
+ char *uri = g_file_get_uri (expected_file);
+ g_print ("%s: %s.background-image: expected: %s, got: %s\n",
+ test, node_description, expected, uri);
+ fail = TRUE;
+ g_free (uri);
+ }
+ }
+}
+
+#define LENGTH_EPSILON 0.001
+
+static void
+assert_length (const char *node_description,
+ const char *property_description,
+ double expected,
+ double value)
+{
+ if (fabs (expected - value) > LENGTH_EPSILON)
+ {
+ g_print ("%s %s.%s: expected: %3f, got: %3f\n",
+ test, node_description, property_description, expected, value);
+ fail = TRUE;
+ }
+}
+
+static void
+test_defaults (void)
+{
+ test = "defaults";
+ /* font comes from context */
+ assert_font (root, "stage", "sans-serif 12");
+ /* black is the default foreground color */
+ assert_foreground_color (root, "stage", 0x00000ff);
+}
+
+static void
+test_lengths (void)
+{
+ test = "lengths";
+ /* 12pt == 16px at 96dpi */
+ assert_length ("group1", "padding-top", 16.,
+ st_theme_node_get_padding (group1, ST_SIDE_TOP));
+ /* 12px == 12px */
+ assert_length ("group1", "padding-right", 12.,
+ st_theme_node_get_padding (group1, ST_SIDE_RIGHT));
+ /* 2em == 32px (with a 12pt font) */
+ assert_length ("group1", "padding-bottom", 32.,
+ st_theme_node_get_padding (group1, ST_SIDE_BOTTOM));
+ /* 1in == 72pt == 96px, at 96dpi */
+ assert_length ("group1", "padding-left", 96.,
+ st_theme_node_get_padding (group1, ST_SIDE_LEFT));
+
+ /* 12pt == 16px at 96dpi */
+ assert_length ("group1", "margin-top", 16.,
+ st_theme_node_get_margin (group1, ST_SIDE_TOP));
+ /* 12px == 12px */
+ assert_length ("group1", "margin-right", 12.,
+ st_theme_node_get_margin (group1, ST_SIDE_RIGHT));
+ /* 2em == 32px (with a 12pt font) */
+ assert_length ("group1", "margin-bottom", 32.,
+ st_theme_node_get_margin (group1, ST_SIDE_BOTTOM));
+ /* 1in == 72pt == 96px, at 96dpi */
+ assert_length ("group1", "margin-left", 96.,
+ st_theme_node_get_margin (group1, ST_SIDE_LEFT));
+}
+
+static void
+test_classes (void)
+{
+ test = "classes";
+ /* .special-text class overrides size and style;
+ * the StBin.special-text selector doesn't match */
+ assert_font (text1, "text1", "sans-serif Italic 32px");
+}
+
+static void
+test_type_inheritance (void)
+{
+ test = "type_inheritance";
+ /* From StBin element selector */
+ assert_length ("button", "padding-top", 10.,
+ st_theme_node_get_padding (button, ST_SIDE_TOP));
+ /* From StButton element selector */
+ assert_length ("button", "padding-right", 20.,
+ st_theme_node_get_padding (button, ST_SIDE_RIGHT));
+}
+
+static void
+test_adjacent_selector (void)
+{
+ test = "adjacent_selector";
+ /* #group1 > #text1 matches text1 */
+ assert_foreground_color (text1, "text1", 0x00ff00ff);
+ /* stage > #text2 doesn't match text2 */
+ assert_foreground_color (text2, "text2", 0x000000ff);
+}
+
+static void
+test_padding (void)
+{
+ test = "padding";
+ /* Test that a 4-sided padding property assigns the right paddings to
+ * all sides */
+ assert_length ("group2", "padding-top", 1.,
+ st_theme_node_get_padding (group2, ST_SIDE_TOP));
+ assert_length ("group2", "padding-right", 2.,
+ st_theme_node_get_padding (group2, ST_SIDE_RIGHT));
+ assert_length ("group2", "padding-bottom", 3.,
+ st_theme_node_get_padding (group2, ST_SIDE_BOTTOM));
+ assert_length ("group2", "padding-left", 4.,
+ st_theme_node_get_padding (group2, ST_SIDE_LEFT));
+}
+
+static void
+test_margin (void)
+{
+ test = "margin";
+ /* Test that a 4-sided margin property assigns the right margin to
+ * all sides */
+ assert_length ("group2", "margin-top", 1.,
+ st_theme_node_get_margin (group2, ST_SIDE_TOP));
+ assert_length ("group2", "margin-right", 2.,
+ st_theme_node_get_margin (group2, ST_SIDE_RIGHT));
+ assert_length ("group2", "margin-bottom", 3.,
+ st_theme_node_get_margin (group2, ST_SIDE_BOTTOM));
+ assert_length ("group2", "margin-left", 4.,
+ st_theme_node_get_margin (group2, ST_SIDE_LEFT));
+
+ /* Test that a 3-sided margin property assigns the right margin to
+ * all sides */
+ assert_length ("group4", "margin-top", 1.,
+ st_theme_node_get_margin (group4, ST_SIDE_TOP));
+ assert_length ("group4", "margin-right", 2.,
+ st_theme_node_get_margin (group4, ST_SIDE_RIGHT));
+ assert_length ("group4", "margin-bottom", 3.,
+ st_theme_node_get_margin (group4, ST_SIDE_BOTTOM));
+ assert_length ("group4", "margin-left", 2.,
+ st_theme_node_get_margin (group4, ST_SIDE_LEFT));
+
+ /* Test that a 2-sided margin property assigns the right margin to
+ * all sides */
+ assert_length ("group5", "margin-top", 1.,
+ st_theme_node_get_margin (group5, ST_SIDE_TOP));
+ assert_length ("group5", "margin-right", 2.,
+ st_theme_node_get_margin (group5, ST_SIDE_RIGHT));
+ assert_length ("group5", "margin-bottom", 1.,
+ st_theme_node_get_margin (group5, ST_SIDE_BOTTOM));
+ assert_length ("group5", "margin-left", 2.,
+ st_theme_node_get_margin (group5, ST_SIDE_LEFT));
+
+ /* Test that all sides have a margin of 0 when not specified */
+ assert_length ("group6", "margin-top", 0.,
+ st_theme_node_get_margin (group6, ST_SIDE_TOP));
+ assert_length ("group6", "margin-right", 0.,
+ st_theme_node_get_margin (group6, ST_SIDE_RIGHT));
+ assert_length ("group6", "margin-bottom", 0.,
+ st_theme_node_get_margin (group6, ST_SIDE_BOTTOM));
+ assert_length ("group6", "margin-left", 0.,
+ st_theme_node_get_margin (group6, ST_SIDE_LEFT));
+}
+
+static void
+test_border (void)
+{
+ test = "border";
+
+ /* group2 is defined as having a thin black border along the top three
+ * sides with rounded joins, then a square-joined green border at the
+ * bottom
+ */
+
+ assert_length ("group2", "border-top-width", 2.,
+ st_theme_node_get_border_width (group2, ST_SIDE_TOP));
+ assert_length ("group2", "border-right-width", 2.,
+ st_theme_node_get_border_width (group2, ST_SIDE_RIGHT));
+ assert_length ("group2", "border-bottom-width", 5.,
+ st_theme_node_get_border_width (group2, ST_SIDE_BOTTOM));
+ assert_length ("group2", "border-left-width", 2.,
+ st_theme_node_get_border_width (group2, ST_SIDE_LEFT));
+
+ assert_border_color (group2, "group2", ST_SIDE_TOP, 0x000000ff);
+ assert_border_color (group2, "group2", ST_SIDE_RIGHT, 0x000000ff);
+ assert_border_color (group2, "group2", ST_SIDE_BOTTOM, 0x0000ffff);
+ assert_border_color (group2, "group2", ST_SIDE_LEFT, 0x000000ff);
+
+ assert_length ("group2", "border-radius-topleft", 10.,
+ st_theme_node_get_border_radius (group2, ST_CORNER_TOPLEFT));
+ assert_length ("group2", "border-radius-topright", 10.,
+ st_theme_node_get_border_radius (group2, ST_CORNER_TOPRIGHT));
+ assert_length ("group2", "border-radius-bottomright", 0.,
+ st_theme_node_get_border_radius (group2, ST_CORNER_BOTTOMRIGHT));
+ assert_length ("group2", "border-radius-bottomleft", 0.,
+ st_theme_node_get_border_radius (group2, ST_CORNER_BOTTOMLEFT));
+}
+
+static void
+test_background (void)
+{
+ test = "background";
+ /* group1 has a background: shortcut property setting color and image */
+ assert_background_color (group1, "group1", 0xff0000ff);
+ assert_background_image (group1, "group1", "some-background.png");
+ /* text1 inherits the background image but not the color */
+ assert_background_color (text1, "text1", 0x00000000);
+ assert_background_image (text1, "text1", "some-background.png");
+ /* text2 inherits both, but then background: none overrides both */
+ assert_background_color (text2, "text2", 0x00000000);
+ assert_background_image (text2, "text2", NULL);
+ /* background-image property */
+ assert_background_image (group2, "group2", "other-background.png");
+}
+
+static void
+test_font (void)
+{
+ test = "font";
+ /* font specified with font: */
+ assert_font (group2, "group2", "serif Italic 12px");
+ /* text3 inherits and overrides individually properties */
+ assert_font (text3, "text3", "serif Bold Oblique Small-Caps 24px");
+}
+
+static void
+test_font_features (void)
+{
+ test = "font_features";
+ /* group1 has font-feature-settings: "tnum" */
+ assert_font_features (group1, "group1", "\"tnum\"");
+ /* text2 should inherit from group1 */
+ assert_font_features (text2, "text2", "\"tnum\"");
+ /* group2 has font-feature-settings: "tnum", "zero" */
+ assert_font_features (group2, "group2", "\"tnum\", \"zero\"");
+ /* text3 should inherit from group2 using the inherit keyword */
+ assert_font_features (text3, "text3", "\"tnum\", \"zero\"");
+ /* text4 has font-feature-settings: normal */
+ assert_font_features (text4, "text4", NULL);
+}
+
+static void
+test_pseudo_class (void)
+{
+ StWidget *label;
+ StThemeNode *labelNode;
+
+ test = "pseudo_class";
+ /* text4 has :visited and :hover pseudo-classes, so should pick up both of these */
+ assert_foreground_color (text4, "text4", 0x888888ff);
+ assert_text_decoration (text4, "text4", ST_TEXT_DECORATION_UNDERLINE);
+ /* :hover pseudo-class matches, but class doesn't match */
+ assert_text_decoration (group3, "group3", 0);
+
+ /* Test the StWidget add/remove pseudo_class interfaces */
+ label = st_label_new ("foo");
+ clutter_actor_add_child (stage, CLUTTER_ACTOR (label));
+
+ labelNode = st_widget_get_theme_node (label);
+ assert_foreground_color (labelNode, "label", 0x000000ff);
+ assert_text_decoration (labelNode, "label", 0);
+ assert_length ("label", "border-width", 0.,
+ st_theme_node_get_border_width (labelNode, ST_SIDE_TOP));
+
+ st_widget_add_style_pseudo_class (label, "visited");
+ g_assert (st_widget_has_style_pseudo_class (label, "visited"));
+ labelNode = st_widget_get_theme_node (label);
+ assert_foreground_color (labelNode, "label", 0x888888ff);
+ assert_text_decoration (labelNode, "label", 0);
+ assert_length ("label", "border-width", 0.,
+ st_theme_node_get_border_width (labelNode, ST_SIDE_TOP));
+
+ st_widget_add_style_pseudo_class (label, "hover");
+ g_assert (st_widget_has_style_pseudo_class (label, "hover"));
+ labelNode = st_widget_get_theme_node (label);
+ assert_foreground_color (labelNode, "label", 0x888888ff);
+ assert_text_decoration (labelNode, "label", ST_TEXT_DECORATION_UNDERLINE);
+ assert_length ("label", "border-width", 0.,
+ st_theme_node_get_border_width (labelNode, ST_SIDE_TOP));
+
+ st_widget_remove_style_pseudo_class (label, "visited");
+ g_assert (!st_widget_has_style_pseudo_class (label, "visited"));
+ g_assert (st_widget_has_style_pseudo_class (label, "hover"));
+ labelNode = st_widget_get_theme_node (label);
+ assert_foreground_color (labelNode, "label", 0x000000ff);
+ assert_text_decoration (labelNode, "label", ST_TEXT_DECORATION_UNDERLINE);
+ assert_length ("label", "border-width", 0.,
+ st_theme_node_get_border_width (labelNode, ST_SIDE_TOP));
+
+ st_widget_add_style_pseudo_class (label, "boxed");
+ labelNode = st_widget_get_theme_node (label);
+ assert_foreground_color (labelNode, "label", 0x000000ff);
+ assert_text_decoration (labelNode, "label", ST_TEXT_DECORATION_UNDERLINE);
+ assert_length ("label", "border-width", 1.,
+ st_theme_node_get_border_width (labelNode, ST_SIDE_TOP));
+
+ st_widget_remove_style_pseudo_class (label, "hover");
+ labelNode = st_widget_get_theme_node (label);
+ assert_foreground_color (labelNode, "label", 0x000000ff);
+ assert_text_decoration (labelNode, "label", 0);
+ assert_length ("label", "border-width", 1.,
+ st_theme_node_get_border_width (labelNode, ST_SIDE_TOP));
+
+ st_widget_remove_style_pseudo_class (label, "boxed");
+ g_assert (!st_widget_has_style_pseudo_class (label, "boxed"));
+ g_assert (st_widget_has_style_pseudo_class (label, "insensitive"));
+ labelNode = st_widget_get_theme_node (label);
+ assert_foreground_color (labelNode, "label", 0x000000ff);
+ assert_text_decoration (labelNode, "label", 0);
+ assert_length ("label", "border-width", 0.,
+ st_theme_node_get_border_width (labelNode, ST_SIDE_TOP));
+
+ clutter_actor_set_reactive (CLUTTER_ACTOR (label), TRUE);
+ g_assert (st_widget_get_style_pseudo_class (label) == NULL);
+}
+
+static void
+test_inline_style (void)
+{
+ test = "inline_style";
+ /* These properties come from the inline-style specified when creating the node */
+ assert_foreground_color (text3, "text3", 0x00000ffff);
+ assert_length ("text3", "padding-bottom", 12.,
+ st_theme_node_get_padding (text3, ST_SIDE_BOTTOM));
+}
+
+int
+main (int argc, char **argv)
+{
+ MetaContext *context;
+ g_autoptr (GError) error = NULL;
+ MetaBackend *backend;
+ StTheme *theme;
+ StThemeContext *theme_context;
+ PangoFontDescription *font_desc;
+ GFile *file;
+ g_autofree char *cwd = NULL;
+
+ gtk_init (&argc, &argv);
+
+ /* meta_init() cds to $HOME */
+ cwd = g_get_current_dir ();
+
+ context = meta_create_test_context (META_CONTEXT_TEST_TYPE_NESTED,
+ META_CONTEXT_TEST_FLAG_NONE);
+ if (!meta_context_configure (context, &argc, &argv, &error))
+ g_error ("Failed to configure: %s", error->message);
+
+ if (!meta_context_setup (context, &error))
+ g_error ("Failed to setup: %s", error->message);
+
+ if (chdir (cwd) < 0)
+ g_error ("chdir('%s') failed: %s", cwd, g_strerror (errno));
+
+ /* Make sure our assumptions about resolution are correct */
+ g_object_set (clutter_settings_get_default (), "font-dpi", -1, NULL);
+
+ file = g_file_new_for_path ("test-theme.css");
+ theme = st_theme_new (file, NULL, NULL);
+ g_object_unref (file);
+
+ backend = meta_get_backend ();
+ stage = meta_backend_get_stage (backend);
+ theme_context = st_theme_context_get_for_stage (CLUTTER_STAGE (stage));
+ st_theme_context_set_theme (theme_context, theme);
+
+ font_desc = pango_font_description_from_string ("sans-serif 12");
+ st_theme_context_set_font (theme_context, font_desc);
+ pango_font_description_free (font_desc);
+
+ root = st_theme_context_get_root_node (theme_context);
+ group1 = st_theme_node_new (theme_context, root, NULL,
+ CLUTTER_TYPE_ACTOR, "group1", NULL, NULL, NULL);
+ text1 = st_theme_node_new (theme_context, group1, NULL,
+ CLUTTER_TYPE_TEXT, "text1", "special-text", NULL, NULL);
+ text2 = st_theme_node_new (theme_context, group1, NULL,
+ CLUTTER_TYPE_TEXT, "text2", NULL, NULL, NULL);
+ group2 = st_theme_node_new (theme_context, root, NULL,
+ CLUTTER_TYPE_ACTOR, "group2", NULL, NULL, NULL);
+ group4 = st_theme_node_new (theme_context, root, NULL,
+ CLUTTER_TYPE_ACTOR, "group4", NULL, NULL, NULL);
+ group5 = st_theme_node_new (theme_context, root, NULL,
+ CLUTTER_TYPE_ACTOR, "group5", NULL, NULL, NULL);
+ group6 = st_theme_node_new (theme_context, root, NULL,
+ CLUTTER_TYPE_ACTOR, "group6", NULL, NULL, NULL);
+ text3 = st_theme_node_new (theme_context, group2, NULL,
+ CLUTTER_TYPE_TEXT, "text3", NULL, NULL,
+ "color: #0000ff; padding-bottom: 12px;");
+ text4 = st_theme_node_new (theme_context, group2, NULL,
+ CLUTTER_TYPE_TEXT, "text4", NULL, "visited hover", NULL);
+ group3 = st_theme_node_new (theme_context, group2, NULL,
+ CLUTTER_TYPE_ACTOR, "group3", NULL, "hover", NULL);
+ button = st_theme_node_new (theme_context, root, NULL,
+ ST_TYPE_BUTTON, "button", NULL, NULL, NULL);
+
+ test_defaults ();
+ test_lengths ();
+ test_classes ();
+ test_type_inheritance ();
+ test_adjacent_selector ();
+ test_padding ();
+ test_margin ();
+ test_border ();
+ test_background ();
+ test_font ();
+ test_font_features ();
+ test_pseudo_class ();
+ test_inline_style ();
+
+ g_object_unref (button);
+ g_object_unref (group1);
+ g_object_unref (group2);
+ g_object_unref (group3);
+ g_object_unref (group4);
+ g_object_unref (group5);
+ g_object_unref (group6);
+ g_object_unref (text1);
+ g_object_unref (text2);
+ g_object_unref (text3);
+ g_object_unref (text4);
+ g_object_unref (theme);
+
+ g_object_unref (context);
+
+ return fail ? 1 : 0;
+}
diff --git a/src/st/test-theme.css b/src/st/test-theme.css
new file mode 100644
index 0000000..d180255
--- /dev/null
+++ b/src/st/test-theme.css
@@ -0,0 +1,107 @@
+stage {
+}
+
+#group1 {
+ padding: 12pt;
+ padding-right: 12px;
+ padding-bottom: 2em;
+ padding-left: 1in;
+
+ margin: 12pt;
+ margin-right: 12px;
+ margin-bottom: 2em;
+ margin-left: 1in;
+
+ background: #ff0000 url('some-background.png');
+
+ font-feature-settings: "tnum";
+}
+
+#text1 {
+ background-image: inherit;
+}
+
+.special-text {
+ font-size: 24pt;
+ font-style: italic;
+}
+
+StBin.special-text {
+ font-weight: bold;
+}
+
+#text2 {
+ background: inherit;
+ background: none; /* also overrides the color */
+}
+
+#group2 {
+ font: italic 12px serif;
+ font-feature-settings: "tnum", "zero";
+}
+
+#text3 {
+ font-variant: small-caps;
+ font-weight: bold;
+ font-style: oblique;
+ font-size: 200%;
+ font-feature-settings: "pnum";
+}
+
+#text4 {
+ font-feature-settings: normal;
+}
+
+StBin {
+ padding: 10px;
+}
+
+StButton {
+ padding-right: 20px;
+}
+
+#group1 > #text1 {
+ color: #00ff00;
+}
+
+stage > #text2 {
+ color: #ff0000;
+}
+
+#group2 > #text3 {
+ font-feature-settings: inherit;
+}
+
+#group2 {
+ background-image: url('other-background.png');
+ padding: 1px 2px 3px 4px;
+ margin: 1px 2px 3px 4px;
+
+ border: 2px solid #000000;
+ border-bottom: 5px solid #0000ff;
+ border-radius: 10px 10px 0px 0px;
+}
+
+ClutterText:hover, StLabel:hover {
+ text-decoration: underline;
+}
+
+ClutterText:visited, StLabel:visited {
+ color: #888888;
+}
+
+StLabel:boxed {
+ border: 1px;
+}
+
+#group4 {
+ margin: 1px 2px 3px;
+}
+
+#group5 {
+ margin: 1px 2px;
+}
+
+#group6 {
+ padding: 5px;
+}
diff --git a/src/tray/meson.build b/src/tray/meson.build
new file mode 100644
index 0000000..139e3b2
--- /dev/null
+++ b/src/tray/meson.build
@@ -0,0 +1,12 @@
+tray_sources = [
+ 'na-tray-child.c',
+ 'na-tray-child.h',
+ 'na-tray-manager.c',
+ 'na-tray-manager.h'
+]
+
+libtray = static_library('tray', tray_sources,
+ c_args: ['-DG_LOG_DOMAIN="notification_area"'],
+ dependencies: [clutter_dep, gtk_dep],
+ include_directories: conf_inc
+)
diff --git a/src/tray/na-tray-child.c b/src/tray/na-tray-child.c
new file mode 100644
index 0000000..175ac8a
--- /dev/null
+++ b/src/tray/na-tray-child.c
@@ -0,0 +1,504 @@
+/* na-tray-child.c
+ * Copyright (C) 2002 Anders Carlsson <andersca@gnu.org>
+ * Copyright (C) 2003-2006 Vincent Untz
+ * Copyright (C) 2008 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <string.h>
+
+#include "na-tray-child.h"
+
+#include <gdk/gdk.h>
+#include <gdk/gdkx.h>
+#include <X11/Xatom.h>
+
+G_DEFINE_TYPE (NaTrayChild, na_tray_child, GTK_TYPE_SOCKET)
+
+static void
+na_tray_child_finalize (GObject *object)
+{
+ G_OBJECT_CLASS (na_tray_child_parent_class)->finalize (object);
+}
+
+static void
+na_tray_child_realize (GtkWidget *widget)
+{
+ NaTrayChild *child = NA_TRAY_CHILD (widget);
+ GdkVisual *visual = gtk_widget_get_visual (widget);
+ GdkWindow *window;
+
+ GTK_WIDGET_CLASS (na_tray_child_parent_class)->realize (widget);
+
+ window = gtk_widget_get_window (widget);
+
+ if (child->has_alpha)
+ {
+ /* We have real transparency with an ARGB visual and the Composite
+ * extension. */
+
+ /* Set a transparent background */
+ cairo_pattern_t *transparent = cairo_pattern_create_rgba (0, 0, 0, 0);
+G_GNUC_BEGIN_IGNORE_DEPRECATIONS
+ gdk_window_set_background_pattern (window, transparent);
+G_GNUC_END_IGNORE_DEPRECATIONS
+ cairo_pattern_destroy (transparent);
+
+ child->parent_relative_bg = FALSE;
+ }
+ else if (visual == gdk_window_get_visual (gdk_window_get_parent (window)))
+ {
+ /* Otherwise, if the visual matches the visual of the parent window, we
+ * can use a parent-relative background and fake transparency. */
+G_GNUC_BEGIN_IGNORE_DEPRECATIONS
+ gdk_window_set_background_pattern (window, NULL);
+G_GNUC_END_IGNORE_DEPRECATIONS
+
+ child->parent_relative_bg = TRUE;
+ }
+ else
+ {
+ /* Nothing to do; the icon will sit on top of an ugly gray box */
+ child->parent_relative_bg = FALSE;
+ }
+
+ gtk_widget_set_app_paintable (GTK_WIDGET (child),
+ child->parent_relative_bg || child->has_alpha);
+
+ /* Double-buffering will interfere with the parent-relative-background fake
+ * transparency, since the double-buffer code doesn't know how to fill in the
+ * background of the double-buffer correctly.
+ * The function is deprecated because it is only meaningful on X11 - the
+ * same is true for XEmbed of course, so just ignore the warning.
+ */
+G_GNUC_BEGIN_IGNORE_DEPRECATIONS
+ gtk_widget_set_double_buffered (GTK_WIDGET (child),
+ child->parent_relative_bg);
+G_GNUC_END_IGNORE_DEPRECATIONS
+}
+
+static void
+na_tray_child_style_set (GtkWidget *widget,
+ GtkStyle *previous_style)
+{
+ /* The default handler resets the background according to the new style.
+ * We either use a transparent background or a parent-relative background
+ * and ignore the style background. So, just don't chain up.
+ */
+}
+
+#if 0
+/* This is adapted from code that was commented out in na-tray-manager.c; the
+ * code in na-tray-manager.c wouldn't have worked reliably, this will. So maybe
+ * it can be re-enabled. On other hand, things seem to be working fine without
+ * it.
+ *
+ * If reenabling, you need to hook it up in na_tray_child_class_init().
+ */
+static void
+na_tray_child_size_request (GtkWidget *widget,
+ GtkRequisition *request)
+{
+ GTK_WIDGET_CLASS (na_tray_child_parent_class)->size_request (widget, request);
+
+ /*
+ * Make sure the icons have a meaningful size ..
+ */
+ if ((request->width < 16) || (request->height < 16))
+ {
+ gint nw = MAX (24, request->width);
+ gint nh = MAX (24, request->height);
+ g_warning ("Tray icon has requested a size of (%ix%i), resizing to (%ix%i)",
+ req.width, req.height, nw, nh);
+ request->width = nw;
+ request->height = nh;
+ }
+}
+#endif
+
+static void
+na_tray_child_size_allocate (GtkWidget *widget,
+ GtkAllocation *allocation)
+{
+ NaTrayChild *child = NA_TRAY_CHILD (widget);
+ GtkAllocation widget_allocation;
+ gboolean moved, resized;
+
+ gtk_widget_get_allocation (widget, &widget_allocation);
+
+ moved = (allocation->x != widget_allocation.x ||
+ allocation->y != widget_allocation.y);
+ resized = (allocation->width != widget_allocation.width ||
+ allocation->height != widget_allocation.height);
+
+ /* When we are allocating the widget while mapped we need special handling
+ * for both real and fake transparency.
+ *
+ * Real transparency: we need to invalidate and trigger a redraw of the old
+ * and new areas. (GDK really should handle this for us, but doesn't as of
+ * GTK+-2.14)
+ *
+ * Fake transparency: if the widget moved, we need to force the contents to
+ * be redrawn with the new offset for the parent-relative background.
+ */
+ if ((moved || resized) && gtk_widget_get_mapped (widget))
+ {
+ if (na_tray_child_has_alpha (child))
+ gdk_window_invalidate_rect (gdk_window_get_parent (gtk_widget_get_window (widget)),
+ &widget_allocation, FALSE);
+ }
+
+ GTK_WIDGET_CLASS (na_tray_child_parent_class)->size_allocate (widget,
+ allocation);
+
+ if ((moved || resized) && gtk_widget_get_mapped (widget))
+ {
+ if (na_tray_child_has_alpha (NA_TRAY_CHILD (widget)))
+ gdk_window_invalidate_rect (gdk_window_get_parent (gtk_widget_get_window (widget)),
+ &widget_allocation, FALSE);
+ else if (moved && child->parent_relative_bg)
+ na_tray_child_force_redraw (child);
+ }
+}
+
+/* The plug window should completely occupy the area of the child, so we won't
+ * get a draw event. But in case we do (the plug unmaps itself, say), this
+ * draw handler draws with real or fake transparency.
+ */
+static gboolean
+na_tray_child_draw (GtkWidget *widget,
+ cairo_t *cr)
+{
+ NaTrayChild *child = NA_TRAY_CHILD (widget);
+
+ if (na_tray_child_has_alpha (child))
+ {
+ /* Clear to transparent */
+ cairo_set_source_rgba (cr, 0, 0, 0, 0);
+ cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
+ cairo_paint (cr);
+ }
+ else if (child->parent_relative_bg)
+ {
+ GdkWindow *window;
+ cairo_surface_t *target;
+ GdkRectangle clip_rect;
+
+ window = gtk_widget_get_window (widget);
+ target = cairo_get_group_target (cr);
+
+ gdk_cairo_get_clip_rectangle (cr, &clip_rect);
+
+ /* Clear to parent-relative pixmap
+ * We need to use direct X access here because GDK doesn't know about
+ * the parent relative pixmap. */
+ cairo_surface_flush (target);
+
+ XClearArea (GDK_WINDOW_XDISPLAY (window),
+ GDK_WINDOW_XID (window),
+ clip_rect.x, clip_rect.y,
+ clip_rect.width, clip_rect.height,
+ False);
+ cairo_surface_mark_dirty_rectangle (target,
+ clip_rect.x, clip_rect.y,
+ clip_rect.width, clip_rect.height);
+ }
+
+ return FALSE;
+}
+
+static void
+na_tray_child_init (NaTrayChild *child)
+{
+}
+
+static void
+na_tray_child_class_init (NaTrayChildClass *klass)
+{
+ GObjectClass *gobject_class;
+ GtkWidgetClass *widget_class;
+
+ gobject_class = (GObjectClass *)klass;
+ widget_class = (GtkWidgetClass *)klass;
+
+ gobject_class->finalize = na_tray_child_finalize;
+ widget_class->style_set = na_tray_child_style_set;
+ widget_class->realize = na_tray_child_realize;
+ widget_class->size_allocate = na_tray_child_size_allocate;
+ widget_class->draw = na_tray_child_draw;
+}
+
+GtkWidget *
+na_tray_child_new (GdkScreen *screen,
+ Window icon_window)
+{
+ XWindowAttributes window_attributes;
+ GdkDisplay *display;
+ Display *xdisplay;
+ NaTrayChild *child;
+ GdkVisual *visual;
+ gboolean visual_has_alpha;
+ int red_prec, green_prec, blue_prec, depth;
+ int result;
+
+ g_return_val_if_fail (GDK_IS_SCREEN (screen), NULL);
+ g_return_val_if_fail (icon_window != None, NULL);
+
+ xdisplay = GDK_SCREEN_XDISPLAY (screen);
+ display = gdk_x11_lookup_xdisplay (xdisplay);
+
+ /* We need to determine the visual of the window we are embedding and create
+ * the socket in the same visual.
+ */
+
+ gdk_x11_display_error_trap_push (display);
+ result = XGetWindowAttributes (xdisplay, icon_window,
+ &window_attributes);
+ gdk_x11_display_error_trap_pop_ignored (display);
+
+ if (!result) /* Window already gone */
+ return NULL;
+
+ visual = gdk_x11_screen_lookup_visual (screen,
+ window_attributes.visual->visualid);
+ if (!visual) /* Icon window is on another screen? */
+ return NULL;
+
+ child = g_object_new (NA_TYPE_TRAY_CHILD, NULL);
+ child->icon_window = icon_window;
+
+ gtk_widget_set_visual (GTK_WIDGET (child), visual);
+
+ /* We have alpha if the visual has something other than red, green,
+ * and blue */
+ gdk_visual_get_red_pixel_details (visual, NULL, NULL, &red_prec);
+ gdk_visual_get_green_pixel_details (visual, NULL, NULL, &green_prec);
+ gdk_visual_get_blue_pixel_details (visual, NULL, NULL, &blue_prec);
+ depth = gdk_visual_get_depth (visual);
+
+ visual_has_alpha = red_prec + blue_prec + green_prec < depth;
+ child->has_alpha = visual_has_alpha;
+
+ return GTK_WIDGET (child);
+}
+
+char *
+na_tray_child_get_title (NaTrayChild *child)
+{
+ char *retval = NULL;
+ GdkDisplay *display;
+ Atom utf8_string, atom, type;
+ int result;
+ int format;
+ gulong nitems;
+ gulong bytes_after;
+ gchar *val;
+
+ g_return_val_if_fail (NA_IS_TRAY_CHILD (child), NULL);
+
+ display = gtk_widget_get_display (GTK_WIDGET (child));
+
+ utf8_string = gdk_x11_get_xatom_by_name_for_display (display, "UTF8_STRING");
+ atom = gdk_x11_get_xatom_by_name_for_display (display, "_NET_WM_NAME");
+
+ gdk_x11_display_error_trap_push (display);
+
+ result = XGetWindowProperty (GDK_DISPLAY_XDISPLAY (display),
+ child->icon_window,
+ atom,
+ 0, G_MAXLONG,
+ False, utf8_string,
+ &type, &format, &nitems,
+ &bytes_after, (guchar **)&val);
+
+ if (gdk_x11_display_error_trap_pop (display) || result != Success)
+ return NULL;
+
+ if (type != utf8_string ||
+ format != 8 ||
+ nitems == 0)
+ {
+ if (val)
+ XFree (val);
+ return NULL;
+ }
+
+ if (!g_utf8_validate (val, nitems, NULL))
+ {
+ XFree (val);
+ return NULL;
+ }
+
+ retval = g_strndup (val, nitems);
+
+ XFree (val);
+
+ return retval;
+}
+
+/**
+ * na_tray_child_has_alpha;
+ * @child: a #NaTrayChild
+ *
+ * Checks if the child has an ARGB visual and real alpha transparence.
+ * (as opposed to faked alpha transparency with an parent-relative
+ * background)
+ *
+ * Return value: %TRUE if the child has an alpha transparency
+ */
+gboolean
+na_tray_child_has_alpha (NaTrayChild *child)
+{
+ g_return_val_if_fail (NA_IS_TRAY_CHILD (child), FALSE);
+
+ return child->has_alpha;
+}
+
+/* If we are faking transparency with a window-relative background, force a
+ * redraw of the icon. This should be called if the background changes or if
+ * the child is shifted with respect to the background.
+ */
+void
+na_tray_child_force_redraw (NaTrayChild *child)
+{
+ GtkWidget *widget = GTK_WIDGET (child);
+
+ if (gtk_widget_get_mapped (widget) && child->parent_relative_bg)
+ {
+#if 1
+ /* Sending an ExposeEvent might cause redraw problems if the
+ * icon is expecting the server to clear-to-background before
+ * the redraw. It should be ok for GtkStatusIcon or EggTrayIcon.
+ */
+ GdkDisplay *display = gtk_widget_get_display (widget);
+ Display *xdisplay = GDK_DISPLAY_XDISPLAY (display);
+ XEvent xev;
+ GdkWindow *plug_window;
+ GtkAllocation allocation;
+
+ plug_window = gtk_socket_get_plug_window (GTK_SOCKET (child));
+ gtk_widget_get_allocation (widget, &allocation);
+
+ xev.xexpose.type = Expose;
+ xev.xexpose.window = GDK_WINDOW_XID (plug_window);
+ xev.xexpose.x = 0;
+ xev.xexpose.y = 0;
+ xev.xexpose.width = allocation.width;
+ xev.xexpose.height = allocation.height;
+ xev.xexpose.count = 0;
+
+ gdk_x11_display_error_trap_push (display);
+ XSendEvent (xdisplay,
+ xev.xexpose.window,
+ False, ExposureMask,
+ &xev);
+ gdk_x11_display_error_trap_pop_ignored (display);
+#else
+ /* Hiding and showing is the safe way to do it, but can result in more
+ * flickering.
+ */
+ gdk_window_hide (widget->window);
+ gdk_window_show (widget->window);
+#endif
+ }
+}
+
+/* from libwnck/xutils.c, comes as LGPLv2+ */
+static char *
+latin1_to_utf8 (const char *latin1)
+{
+ GString *str;
+ const char *p;
+
+ str = g_string_new (NULL);
+
+ p = latin1;
+ while (*p)
+ {
+ g_string_append_unichar (str, (gunichar) *p);
+ ++p;
+ }
+
+ return g_string_free (str, FALSE);
+}
+
+/* derived from libwnck/xutils.c, comes as LGPLv2+ */
+static void
+_get_wmclass (Display *xdisplay,
+ Window xwindow,
+ char **res_class,
+ char **res_name)
+{
+ GdkDisplay *display;
+ XClassHint ch;
+
+ ch.res_name = NULL;
+ ch.res_class = NULL;
+
+ display = gdk_x11_lookup_xdisplay (xdisplay);
+ gdk_x11_display_error_trap_push (display);
+ XGetClassHint (xdisplay, xwindow, &ch);
+ gdk_x11_display_error_trap_pop_ignored (display);
+
+ if (res_class)
+ *res_class = NULL;
+
+ if (res_name)
+ *res_name = NULL;
+
+ if (ch.res_name)
+ {
+ if (res_name)
+ *res_name = latin1_to_utf8 (ch.res_name);
+
+ XFree (ch.res_name);
+ }
+
+ if (ch.res_class)
+ {
+ if (res_class)
+ *res_class = latin1_to_utf8 (ch.res_class);
+
+ XFree (ch.res_class);
+ }
+}
+
+/**
+ * na_tray_child_get_wm_class;
+ * @child: a #NaTrayChild
+ * @res_name: return location for a string containing the application name of
+ * @child, or %NULL
+ * @res_class: return location for a string containing the application class of
+ * @child, or %NULL
+ *
+ * Fetches the resource associated with @child.
+ */
+void
+na_tray_child_get_wm_class (NaTrayChild *child,
+ char **res_name,
+ char **res_class)
+{
+ GdkDisplay *display;
+
+ g_return_if_fail (NA_IS_TRAY_CHILD (child));
+
+ display = gtk_widget_get_display (GTK_WIDGET (child));
+
+ _get_wmclass (GDK_DISPLAY_XDISPLAY (display),
+ child->icon_window,
+ res_class,
+ res_name);
+}
diff --git a/src/tray/na-tray-child.h b/src/tray/na-tray-child.h
new file mode 100644
index 0000000..d143676
--- /dev/null
+++ b/src/tray/na-tray-child.h
@@ -0,0 +1,66 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/* na-tray-child.h
+ * Copyright (C) 2002 Anders Carlsson <andersca@gnu.org>
+ * Copyright (C) 2003-2006 Vincent Untz
+ * Copyright (C) 2008 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __NA_TRAY_CHILD_H__
+#define __NA_TRAY_CHILD_H__
+
+#include <gtk/gtk.h>
+#include <gtk/gtkx.h>
+
+G_BEGIN_DECLS
+
+#define NA_TYPE_TRAY_CHILD (na_tray_child_get_type ())
+#define NA_TRAY_CHILD(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), NA_TYPE_TRAY_CHILD, NaTrayChild))
+#define NA_TRAY_CHILD_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), NA_TYPE_TRAY_CHILD, NaTrayChildClass))
+#define NA_IS_TRAY_CHILD(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NA_TYPE_TRAY_CHILD))
+#define NA_IS_TRAY_CHILD_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), NA_TYPE_TRAY_CHILD))
+#define NA_TRAY_CHILD_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), NA_TYPE_TRAY_CHILD, NaTrayChildClass))
+
+typedef struct _NaTrayChild NaTrayChild;
+typedef struct _NaTrayChildClass NaTrayChildClass;
+typedef struct _NaTrayChildChild NaTrayChildChild;
+
+struct _NaTrayChild
+{
+ GtkSocket parent_instance;
+ Window icon_window;
+ guint has_alpha : 1;
+ guint parent_relative_bg : 1;
+};
+
+struct _NaTrayChildClass
+{
+ GtkSocketClass parent_class;
+};
+
+GType na_tray_child_get_type (void);
+
+GtkWidget *na_tray_child_new (GdkScreen *screen,
+ Window icon_window);
+char *na_tray_child_get_title (NaTrayChild *child);
+gboolean na_tray_child_has_alpha (NaTrayChild *child);
+void na_tray_child_force_redraw (NaTrayChild *child);
+void na_tray_child_get_wm_class (NaTrayChild *child,
+ char **res_name,
+ char **res_class);
+
+G_END_DECLS
+
+#endif /* __NA_TRAY_CHILD_H__ */
diff --git a/src/tray/na-tray-manager.c b/src/tray/na-tray-manager.c
new file mode 100644
index 0000000..15e1d80
--- /dev/null
+++ b/src/tray/na-tray-manager.c
@@ -0,0 +1,888 @@
+/* na-tray-manager.c
+ * Copyright (C) 2002 Anders Carlsson <andersca@gnu.org>
+ * Copyright (C) 2003-2006 Vincent Untz
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * Used to be: eggtraymanager.c
+ */
+
+#include <config.h>
+#include <string.h>
+#include <libintl.h>
+
+#include "na-tray-manager.h"
+
+#if defined (GDK_WINDOWING_X11)
+#include <gdk/gdkx.h>
+#include <X11/Xatom.h>
+#elif defined (GDK_WINDOWING_WIN32)
+#include <gdk/gdkwin32.h>
+#endif
+#include <gtk/gtk.h>
+
+/* Signals */
+enum
+{
+ TRAY_ICON_ADDED,
+ TRAY_ICON_REMOVED,
+ MESSAGE_SENT,
+ MESSAGE_CANCELLED,
+ LOST_SELECTION,
+ LAST_SIGNAL
+};
+
+enum {
+ PROP_0,
+ PROP_ORIENTATION
+};
+
+typedef struct
+{
+ long id, len;
+ long remaining_len;
+
+ long timeout;
+ char *str;
+#ifdef GDK_WINDOWING_X11
+ Window window;
+#endif
+} PendingMessage;
+
+static guint manager_signals[LAST_SIGNAL];
+
+#define SYSTEM_TRAY_REQUEST_DOCK 0
+#define SYSTEM_TRAY_BEGIN_MESSAGE 1
+#define SYSTEM_TRAY_CANCEL_MESSAGE 2
+
+#define SYSTEM_TRAY_ORIENTATION_HORZ 0
+#define SYSTEM_TRAY_ORIENTATION_VERT 1
+
+#ifdef GDK_WINDOWING_X11
+static gboolean na_tray_manager_check_running_screen_x11 (void);
+#endif
+
+static void na_tray_manager_finalize (GObject *object);
+static void na_tray_manager_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void na_tray_manager_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec);
+
+static void na_tray_manager_unmanage (NaTrayManager *manager);
+
+G_DEFINE_TYPE (NaTrayManager, na_tray_manager, G_TYPE_OBJECT)
+
+static void
+na_tray_manager_init (NaTrayManager *manager)
+{
+ manager->invisible = NULL;
+ manager->socket_table = g_hash_table_new (NULL, NULL);
+
+ manager->fg.red = 0;
+ manager->fg.green = 0;
+ manager->fg.blue = 0;
+
+ manager->error.red = 0xff;
+ manager->error.green = 0;
+ manager->error.blue = 0;
+
+ manager->warning.red = 0xff;
+ manager->warning.green = 0xff;
+ manager->warning.blue = 0;
+
+ manager->success.red = 0;
+ manager->success.green = 0xff;
+ manager->success.blue = 0;
+}
+
+static void
+na_tray_manager_class_init (NaTrayManagerClass *klass)
+{
+ GObjectClass *gobject_class;
+
+ gobject_class = (GObjectClass *)klass;
+
+ gobject_class->finalize = na_tray_manager_finalize;
+ gobject_class->set_property = na_tray_manager_set_property;
+ gobject_class->get_property = na_tray_manager_get_property;
+
+ g_object_class_install_property (gobject_class,
+ PROP_ORIENTATION,
+ g_param_spec_enum ("orientation",
+ "orientation",
+ "orientation",
+ GTK_TYPE_ORIENTATION,
+ GTK_ORIENTATION_HORIZONTAL,
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS |
+ G_PARAM_CONSTRUCT));
+
+ manager_signals[TRAY_ICON_ADDED] =
+ g_signal_new ("tray_icon_added",
+ G_OBJECT_CLASS_TYPE (klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (NaTrayManagerClass, tray_icon_added),
+ NULL, NULL, NULL,
+ G_TYPE_NONE, 1,
+ GTK_TYPE_SOCKET);
+
+ manager_signals[TRAY_ICON_REMOVED] =
+ g_signal_new ("tray_icon_removed",
+ G_OBJECT_CLASS_TYPE (klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (NaTrayManagerClass, tray_icon_removed),
+ NULL, NULL, NULL,
+ G_TYPE_NONE, 1,
+ GTK_TYPE_SOCKET);
+ manager_signals[MESSAGE_SENT] =
+ g_signal_new ("message_sent",
+ G_OBJECT_CLASS_TYPE (klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (NaTrayManagerClass, message_sent),
+ NULL, NULL, NULL,
+ G_TYPE_NONE, 4,
+ GTK_TYPE_SOCKET,
+ G_TYPE_STRING,
+ G_TYPE_LONG,
+ G_TYPE_LONG);
+ manager_signals[MESSAGE_CANCELLED] =
+ g_signal_new ("message_cancelled",
+ G_OBJECT_CLASS_TYPE (klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (NaTrayManagerClass, message_cancelled),
+ NULL, NULL, NULL,
+ G_TYPE_NONE, 2,
+ GTK_TYPE_SOCKET,
+ G_TYPE_LONG);
+ manager_signals[LOST_SELECTION] =
+ g_signal_new ("lost_selection",
+ G_OBJECT_CLASS_TYPE (klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (NaTrayManagerClass, lost_selection),
+ NULL, NULL, NULL,
+ G_TYPE_NONE, 0);
+
+#if defined (GDK_WINDOWING_X11)
+ /* Nothing */
+#elif defined (GDK_WINDOWING_WIN32)
+ g_warning ("Port NaTrayManager to Win32");
+#else
+ g_warning ("Port NaTrayManager to this GTK+ backend");
+#endif
+}
+
+static void
+na_tray_manager_finalize (GObject *object)
+{
+ NaTrayManager *manager;
+
+ manager = NA_TRAY_MANAGER (object);
+
+ na_tray_manager_unmanage (manager);
+
+ g_list_free (manager->messages);
+ g_hash_table_destroy (manager->socket_table);
+
+ G_OBJECT_CLASS (na_tray_manager_parent_class)->finalize (object);
+}
+
+static void
+na_tray_manager_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ NaTrayManager *manager = NA_TRAY_MANAGER (object);
+
+ switch (prop_id)
+ {
+ case PROP_ORIENTATION:
+ na_tray_manager_set_orientation (manager, g_value_get_enum (value));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+na_tray_manager_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ NaTrayManager *manager = NA_TRAY_MANAGER (object);
+
+ switch (prop_id)
+ {
+ case PROP_ORIENTATION:
+ g_value_set_enum (value, manager->orientation);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+NaTrayManager *
+na_tray_manager_new (void)
+{
+ NaTrayManager *manager;
+
+ manager = g_object_new (NA_TYPE_TRAY_MANAGER, NULL);
+
+ return manager;
+}
+
+#ifdef GDK_WINDOWING_X11
+
+static gboolean
+na_tray_manager_plug_removed (GtkSocket *socket,
+ NaTrayManager *manager)
+{
+ NaTrayChild *child = NA_TRAY_CHILD (socket);
+
+ g_hash_table_remove (manager->socket_table,
+ GINT_TO_POINTER (child->icon_window));
+ g_signal_emit (manager, manager_signals[TRAY_ICON_REMOVED], 0, child);
+
+ /* This destroys the socket. */
+ return FALSE;
+}
+
+static void
+na_tray_manager_handle_dock_request (NaTrayManager *manager,
+ XClientMessageEvent *xevent)
+{
+ Window icon_window = xevent->data.l[2];
+ GtkWidget *child;
+
+ if (g_hash_table_lookup (manager->socket_table,
+ GINT_TO_POINTER (icon_window)))
+ {
+ /* We already got this notification earlier, ignore this one */
+ return;
+ }
+
+ child = na_tray_child_new (manager->screen, icon_window);
+ if (child == NULL) /* already gone or other error */
+ return;
+
+ g_signal_emit (manager, manager_signals[TRAY_ICON_ADDED], 0,
+ child);
+
+ /* If the child wasn't attached, then destroy it */
+
+ if (!GTK_IS_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (child))))
+ {
+ gtk_widget_destroy (child);
+ return;
+ }
+
+ g_signal_connect (child, "plug_removed",
+ G_CALLBACK (na_tray_manager_plug_removed), manager);
+
+ gtk_socket_add_id (GTK_SOCKET (child), icon_window);
+
+ if (!gtk_socket_get_plug_window (GTK_SOCKET (child)))
+ {
+ /* Embedding failed, we won't get a plug-removed signal */
+ /* This signal destroys the socket */
+ g_signal_emit (manager, manager_signals[TRAY_ICON_REMOVED], 0, child);
+ return;
+ }
+
+ g_hash_table_insert (manager->socket_table,
+ GINT_TO_POINTER (icon_window), child);
+ gtk_widget_show (child);
+}
+
+static void
+pending_message_free (PendingMessage *message)
+{
+ g_free (message->str);
+ g_free (message);
+}
+
+static void
+na_tray_manager_handle_message_data (NaTrayManager *manager,
+ XClientMessageEvent *xevent)
+{
+ GList *p;
+ int len;
+
+ /* Try to see if we can find the pending message in the list */
+ for (p = manager->messages; p; p = p->next)
+ {
+ PendingMessage *msg = p->data;
+
+ if (xevent->window == msg->window)
+ {
+ /* Append the message */
+ len = MIN (msg->remaining_len, 20);
+
+ memcpy ((msg->str + msg->len - msg->remaining_len),
+ &xevent->data, len);
+ msg->remaining_len -= len;
+
+ if (msg->remaining_len == 0)
+ {
+ GtkSocket *socket;
+
+ socket = g_hash_table_lookup (manager->socket_table,
+ GINT_TO_POINTER (msg->window));
+
+ if (socket)
+ g_signal_emit (manager, manager_signals[MESSAGE_SENT], 0,
+ socket, msg->str, msg->id, msg->timeout);
+
+ pending_message_free (msg);
+ manager->messages = g_list_remove_link (manager->messages, p);
+ g_list_free_1 (p);
+ }
+
+ break;
+ }
+ }
+}
+
+static void
+na_tray_manager_handle_begin_message (NaTrayManager *manager,
+ XClientMessageEvent *xevent)
+{
+ GtkSocket *socket;
+ GList *p;
+ PendingMessage *msg;
+ long timeout;
+ long len;
+ long id;
+
+ socket = g_hash_table_lookup (manager->socket_table,
+ GINT_TO_POINTER (xevent->window));
+ /* we don't know about this tray icon, so ignore the message */
+ if (!socket)
+ return;
+
+ timeout = xevent->data.l[2];
+ len = xevent->data.l[3];
+ id = xevent->data.l[4];
+
+ /* Check if the same message is already in the queue and remove it if so */
+ for (p = manager->messages; p; p = p->next)
+ {
+ PendingMessage *pmsg = p->data;
+
+ if (xevent->window == pmsg->window &&
+ id == pmsg->id)
+ {
+ /* Hmm, we found it, now remove it */
+ pending_message_free (pmsg);
+ manager->messages = g_list_remove_link (manager->messages, p);
+ g_list_free_1 (p);
+ break;
+ }
+ }
+
+ if (len == 0)
+ {
+ g_signal_emit (manager, manager_signals[MESSAGE_SENT], 0,
+ socket, "", id, timeout);
+ }
+ else
+ {
+ /* Now add the new message to the queue */
+ msg = g_new0 (PendingMessage, 1);
+ msg->window = xevent->window;
+ msg->timeout = timeout;
+ msg->len = len;
+ msg->id = id;
+ msg->remaining_len = msg->len;
+ msg->str = g_malloc (msg->len + 1);
+ msg->str[msg->len] = '\0';
+ manager->messages = g_list_prepend (manager->messages, msg);
+ }
+}
+
+static void
+na_tray_manager_handle_cancel_message (NaTrayManager *manager,
+ XClientMessageEvent *xevent)
+{
+ GList *p;
+ GtkSocket *socket;
+ long id;
+
+ id = xevent->data.l[2];
+
+ /* Check if the message is in the queue and remove it if so */
+ for (p = manager->messages; p; p = p->next)
+ {
+ PendingMessage *msg = p->data;
+
+ if (xevent->window == msg->window &&
+ id == msg->id)
+ {
+ pending_message_free (msg);
+ manager->messages = g_list_remove_link (manager->messages, p);
+ g_list_free_1 (p);
+ break;
+ }
+ }
+
+ socket = g_hash_table_lookup (manager->socket_table,
+ GINT_TO_POINTER (xevent->window));
+
+ if (socket)
+ {
+ g_signal_emit (manager, manager_signals[MESSAGE_CANCELLED], 0,
+ socket, xevent->data.l[2]);
+ }
+}
+
+static GdkFilterReturn
+na_tray_manager_window_filter (GdkXEvent *xev,
+ GdkEvent *event,
+ gpointer data)
+{
+ XEvent *xevent = (GdkXEvent *)xev;
+ NaTrayManager *manager = data;
+
+ if (xevent->type == ClientMessage)
+ {
+ /* _NET_SYSTEM_TRAY_OPCODE: SYSTEM_TRAY_REQUEST_DOCK */
+ if (xevent->xclient.message_type == manager->opcode_atom &&
+ xevent->xclient.data.l[1] == SYSTEM_TRAY_REQUEST_DOCK)
+ {
+ na_tray_manager_handle_dock_request (manager,
+ (XClientMessageEvent *) xevent);
+ return GDK_FILTER_REMOVE;
+ }
+ /* _NET_SYSTEM_TRAY_OPCODE: SYSTEM_TRAY_BEGIN_MESSAGE */
+ else if (xevent->xclient.message_type == manager->opcode_atom &&
+ xevent->xclient.data.l[1] == SYSTEM_TRAY_BEGIN_MESSAGE)
+ {
+ na_tray_manager_handle_begin_message (manager,
+ (XClientMessageEvent *) event);
+ return GDK_FILTER_REMOVE;
+ }
+ /* _NET_SYSTEM_TRAY_OPCODE: SYSTEM_TRAY_CANCEL_MESSAGE */
+ else if (xevent->xclient.message_type == manager->opcode_atom &&
+ xevent->xclient.data.l[1] == SYSTEM_TRAY_CANCEL_MESSAGE)
+ {
+ na_tray_manager_handle_cancel_message (manager,
+ (XClientMessageEvent *) event);
+ return GDK_FILTER_REMOVE;
+ }
+ /* _NET_SYSTEM_TRAY_MESSAGE_DATA */
+ else if (xevent->xclient.message_type == manager->message_data_atom)
+ {
+ na_tray_manager_handle_message_data (manager,
+ (XClientMessageEvent *) event);
+ return GDK_FILTER_REMOVE;
+ }
+ }
+ else if (xevent->type == SelectionClear)
+ {
+ g_signal_emit (manager, manager_signals[LOST_SELECTION], 0);
+ na_tray_manager_unmanage (manager);
+ }
+
+ return GDK_FILTER_CONTINUE;
+}
+
+#if 0
+//FIXME investigate why this doesn't work
+static gboolean
+na_tray_manager_selection_clear_event (GtkWidget *widget,
+ GdkEventSelection *event,
+ NaTrayManager *manager)
+{
+ g_signal_emit (manager, manager_signals[LOST_SELECTION], 0);
+ na_tray_manager_unmanage (manager);
+
+ return FALSE;
+}
+#endif
+#endif
+
+static void
+na_tray_manager_unmanage (NaTrayManager *manager)
+{
+#ifdef GDK_WINDOWING_X11
+ GdkDisplay *display;
+ guint32 timestamp;
+ GtkWidget *invisible;
+ GdkWindow *window;
+
+ if (manager->invisible == NULL)
+ return;
+
+ invisible = manager->invisible;
+ window = gtk_widget_get_window (invisible);
+
+ g_assert (GTK_IS_INVISIBLE (invisible));
+ g_assert (gtk_widget_get_realized (invisible));
+ g_assert (GDK_IS_WINDOW (window));
+
+ display = gtk_widget_get_display (invisible);
+
+ if (gdk_selection_owner_get_for_display (display, manager->selection_atom) ==
+ window)
+ {
+ timestamp = gdk_x11_get_server_time (window);
+ gdk_selection_owner_set_for_display (display,
+ NULL,
+ manager->selection_atom,
+ timestamp,
+ TRUE);
+ }
+
+ gdk_window_remove_filter (window,
+ na_tray_manager_window_filter, manager);
+
+ manager->invisible = NULL; /* prior to destroy for reentrancy paranoia */
+ gtk_widget_destroy (invisible);
+ g_object_unref (G_OBJECT (invisible));
+#endif
+}
+
+static void
+na_tray_manager_set_orientation_property (NaTrayManager *manager)
+{
+#ifdef GDK_WINDOWING_X11
+ GdkWindow *window;
+ GdkDisplay *display;
+ Atom orientation_atom;
+ gulong data[1];
+
+ g_return_if_fail (manager->invisible != NULL);
+ window = gtk_widget_get_window (manager->invisible);
+ g_return_if_fail (window != NULL);
+
+ display = gtk_widget_get_display (manager->invisible);
+ orientation_atom = gdk_x11_get_xatom_by_name_for_display (display,
+ "_NET_SYSTEM_TRAY_ORIENTATION");
+
+ data[0] = manager->orientation == GTK_ORIENTATION_HORIZONTAL ?
+ SYSTEM_TRAY_ORIENTATION_HORZ :
+ SYSTEM_TRAY_ORIENTATION_VERT;
+
+ XChangeProperty (GDK_DISPLAY_XDISPLAY (display),
+ GDK_WINDOW_XID (window),
+ orientation_atom,
+ XA_CARDINAL, 32,
+ PropModeReplace,
+ (guchar *) &data, 1);
+#endif
+}
+
+static void
+na_tray_manager_set_visual_property (NaTrayManager *manager)
+{
+#ifdef GDK_WINDOWING_X11
+ GdkWindow *window;
+ GdkDisplay *display;
+ Visual *xvisual;
+ Atom visual_atom;
+ gulong data[1];
+
+ g_return_if_fail (manager->invisible != NULL);
+ window = gtk_widget_get_window (manager->invisible);
+ g_return_if_fail (window != NULL);
+
+ /* The visual property is a hint to the tray icons as to what visual they
+ * should use for their windows. If the X server has RGBA colormaps, then
+ * we tell the tray icons to use a RGBA colormap and we'll composite the
+ * icon onto its parents with real transparency. Otherwise, we just tell
+ * the icon to use our colormap, and we'll do some hacks with parent
+ * relative backgrounds to simulate transparency.
+ */
+
+ display = gtk_widget_get_display (manager->invisible);
+ visual_atom = gdk_x11_get_xatom_by_name_for_display (display,
+ "_NET_SYSTEM_TRAY_VISUAL");
+
+ if (gdk_screen_get_rgba_visual (manager->screen) != NULL)
+ xvisual = GDK_VISUAL_XVISUAL (gdk_screen_get_rgba_visual (manager->screen));
+ else
+ {
+ /* We actually want the visual of the tray where the icons will
+ * be embedded. In almost all cases, this will be the same as the visual
+ * of the screen.
+ */
+ xvisual = GDK_VISUAL_XVISUAL (gdk_screen_get_system_visual (manager->screen));
+ }
+
+ data[0] = XVisualIDFromVisual (xvisual);
+
+ XChangeProperty (GDK_DISPLAY_XDISPLAY (display),
+ GDK_WINDOW_XID (window),
+ visual_atom,
+ XA_VISUALID, 32,
+ PropModeReplace,
+ (guchar *) &data, 1);
+#endif
+}
+
+static void
+na_tray_manager_set_colors_property (NaTrayManager *manager)
+{
+#ifdef GDK_WINDOWING_X11
+ GdkWindow *window;
+ GdkDisplay *display;
+ Atom atom;
+ gulong data[12];
+
+ g_return_if_fail (manager->invisible != NULL);
+ window = gtk_widget_get_window (manager->invisible);
+ g_return_if_fail (window != NULL);
+
+ display = gtk_widget_get_display (manager->invisible);
+ atom = gdk_x11_get_xatom_by_name_for_display (display,
+ "_NET_SYSTEM_TRAY_COLORS");
+
+ data[0] = manager->fg.red * 0x101;
+ data[1] = manager->fg.green * 0x101;
+ data[2] = manager->fg.blue * 0x101;
+ data[3] = manager->error.red * 0x101;
+ data[4] = manager->error.green * 0x101;
+ data[5] = manager->error.blue * 0x101;
+ data[6] = manager->warning.red * 0x101;
+ data[7] = manager->warning.green * 0x101;
+ data[8] = manager->warning.blue * 0x101;
+ data[9] = manager->success.red * 0x101;
+ data[10] = manager->success.green * 0x101;
+ data[11] = manager->success.blue * 0x101;
+
+ XChangeProperty (GDK_DISPLAY_XDISPLAY (display),
+ GDK_WINDOW_XID (window),
+ atom,
+ XA_CARDINAL, 32,
+ PropModeReplace,
+ (guchar *) &data, 12);
+#endif
+}
+
+#ifdef GDK_WINDOWING_X11
+
+static gboolean
+na_tray_manager_manage_screen_x11 (NaTrayManager *manager)
+{
+ GdkDisplay *display;
+ GdkScreen *screen;
+ Screen *xscreen;
+ GtkWidget *invisible;
+ GdkWindow *window;
+ char *selection_atom_name;
+ guint32 timestamp;
+
+ g_return_val_if_fail (NA_IS_TRAY_MANAGER (manager), FALSE);
+ g_return_val_if_fail (manager->screen == NULL, FALSE);
+
+ /* If there's already a manager running on the screen
+ * we can't create another one.
+ */
+#if 0
+ if (na_tray_manager_check_running_screen_x11 ())
+ return FALSE;
+#endif
+
+ screen = gdk_screen_get_default ();
+ manager->screen = screen;
+
+ display = gdk_screen_get_display (screen);
+ xscreen = GDK_SCREEN_XSCREEN (screen);
+
+ invisible = gtk_invisible_new_for_screen (screen);
+ gtk_widget_realize (invisible);
+
+ gtk_widget_add_events (invisible,
+ GDK_PROPERTY_CHANGE_MASK | GDK_STRUCTURE_MASK);
+
+ selection_atom_name = g_strdup_printf ("_NET_SYSTEM_TRAY_S%d",
+ gdk_x11_get_default_screen ());
+ manager->selection_atom = gdk_atom_intern (selection_atom_name, FALSE);
+ g_free (selection_atom_name);
+
+ manager->invisible = invisible;
+ g_object_ref (G_OBJECT (manager->invisible));
+
+ na_tray_manager_set_orientation_property (manager);
+ na_tray_manager_set_visual_property (manager);
+ na_tray_manager_set_colors_property (manager);
+
+ window = gtk_widget_get_window (invisible);
+
+ timestamp = gdk_x11_get_server_time (window);
+
+ /* Check if we could set the selection owner successfully */
+ if (gdk_selection_owner_set_for_display (display,
+ window,
+ manager->selection_atom,
+ timestamp,
+ TRUE))
+ {
+ XClientMessageEvent xev;
+ GdkAtom opcode_atom;
+ GdkAtom message_data_atom;
+
+ xev.type = ClientMessage;
+ xev.window = RootWindowOfScreen (xscreen);
+ xev.message_type = gdk_x11_get_xatom_by_name_for_display (display,
+ "MANAGER");
+
+ xev.format = 32;
+ xev.data.l[0] = timestamp;
+ xev.data.l[1] = gdk_x11_atom_to_xatom_for_display (display,
+ manager->selection_atom);
+ xev.data.l[2] = GDK_WINDOW_XID (window);
+ xev.data.l[3] = 0; /* manager specific data */
+ xev.data.l[4] = 0; /* manager specific data */
+
+ XSendEvent (GDK_DISPLAY_XDISPLAY (display),
+ RootWindowOfScreen (xscreen),
+ False, StructureNotifyMask, (XEvent *)&xev);
+
+ opcode_atom = gdk_atom_intern ("_NET_SYSTEM_TRAY_OPCODE", FALSE);
+ manager->opcode_atom = gdk_x11_atom_to_xatom_for_display (display,
+ opcode_atom);
+
+ message_data_atom = gdk_atom_intern ("_NET_SYSTEM_TRAY_MESSAGE_DATA",
+ FALSE);
+ manager->message_data_atom = gdk_x11_atom_to_xatom_for_display (display,
+ message_data_atom);
+
+ /* Add a window filter */
+#if 0
+ /* This is for when we lose the selection of _NET_SYSTEM_TRAY_Sx */
+ g_signal_connect (invisible, "selection-clear-event",
+ G_CALLBACK (na_tray_manager_selection_clear_event),
+ manager);
+#endif
+ gdk_window_add_filter (window,
+ na_tray_manager_window_filter, manager);
+ return TRUE;
+ }
+ else
+ {
+ gtk_widget_destroy (invisible);
+ g_object_unref (invisible);
+ manager->invisible = NULL;
+
+ manager->screen = NULL;
+
+ return FALSE;
+ }
+}
+
+#endif
+
+gboolean
+na_tray_manager_manage_screen (NaTrayManager *manager)
+{
+ g_return_val_if_fail (manager->screen == NULL, FALSE);
+
+#ifdef GDK_WINDOWING_X11
+ return na_tray_manager_manage_screen_x11 (manager);
+#else
+ return FALSE;
+#endif
+}
+
+#ifdef GDK_WINDOWING_X11
+
+static gboolean
+na_tray_manager_check_running_screen_x11 (void)
+{
+ GdkDisplay *display;
+ GdkScreen *screen;
+ Atom selection_atom;
+ char *selection_atom_name;
+
+ screen = gdk_screen_get_default ();
+ display = gdk_screen_get_display (screen);
+ selection_atom_name = g_strdup_printf ("_NET_SYSTEM_TRAY_S%d",
+ gdk_x11_get_default_screen ());
+ selection_atom = gdk_x11_get_xatom_by_name_for_display (display,
+ selection_atom_name);
+ g_free (selection_atom_name);
+
+ if (XGetSelectionOwner (GDK_DISPLAY_XDISPLAY (display),
+ selection_atom) != None)
+ return TRUE;
+ else
+ return FALSE;
+}
+
+#endif
+
+gboolean
+na_tray_manager_check_running (void)
+{
+#ifdef GDK_WINDOWING_X11
+ return na_tray_manager_check_running_screen_x11 ();
+#else
+ return FALSE;
+#endif
+}
+
+void
+na_tray_manager_set_orientation (NaTrayManager *manager,
+ GtkOrientation orientation)
+{
+ g_return_if_fail (NA_IS_TRAY_MANAGER (manager));
+
+ if (manager->orientation != orientation)
+ {
+ manager->orientation = orientation;
+
+ na_tray_manager_set_orientation_property (manager);
+
+ g_object_notify (G_OBJECT (manager), "orientation");
+ }
+}
+
+void
+na_tray_manager_set_colors (NaTrayManager *manager,
+ ClutterColor *fg,
+ ClutterColor *error,
+ ClutterColor *warning,
+ ClutterColor *success)
+{
+ g_return_if_fail (NA_IS_TRAY_MANAGER (manager));
+
+ if (!clutter_color_equal (&manager->fg, fg) ||
+ !clutter_color_equal (&manager->error, error) ||
+ !clutter_color_equal (&manager->warning, warning) ||
+ !clutter_color_equal (&manager->success, success))
+ {
+ manager->fg = *fg;
+ manager->error = *error;
+ manager->warning = *warning;
+ manager->success = *success;
+
+ na_tray_manager_set_colors_property (manager);
+ }
+}
+
+GtkOrientation
+na_tray_manager_get_orientation (NaTrayManager *manager)
+{
+ g_return_val_if_fail (NA_IS_TRAY_MANAGER (manager), GTK_ORIENTATION_HORIZONTAL);
+
+ return manager->orientation;
+}
diff --git a/src/tray/na-tray-manager.h b/src/tray/na-tray-manager.h
new file mode 100644
index 0000000..727b02d
--- /dev/null
+++ b/src/tray/na-tray-manager.h
@@ -0,0 +1,106 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/* na-tray-manager.h
+ * Copyright (C) 2002 Anders Carlsson <andersca@gnu.org>
+ * Copyright (C) 2003-2006 Vincent Untz
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * Used to be: eggtraymanager.h
+ */
+
+#ifndef __NA_TRAY_MANAGER_H__
+#define __NA_TRAY_MANAGER_H__
+
+#ifdef GDK_WINDOWING_X11
+#include <gdk/gdkx.h>
+#endif
+#include <gtk/gtk.h>
+#include <clutter/clutter.h>
+
+#include "na-tray-child.h"
+
+G_BEGIN_DECLS
+
+#define NA_TYPE_TRAY_MANAGER (na_tray_manager_get_type ())
+#define NA_TRAY_MANAGER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), NA_TYPE_TRAY_MANAGER, NaTrayManager))
+#define NA_TRAY_MANAGER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), NA_TYPE_TRAY_MANAGER, NaTrayManagerClass))
+#define NA_IS_TRAY_MANAGER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NA_TYPE_TRAY_MANAGER))
+#define NA_IS_TRAY_MANAGER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), NA_TYPE_TRAY_MANAGER))
+#define NA_TRAY_MANAGER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), NA_TYPE_TRAY_MANAGER, NaTrayManagerClass))
+
+typedef struct _NaTrayManager NaTrayManager;
+typedef struct _NaTrayManagerClass NaTrayManagerClass;
+
+struct _NaTrayManager
+{
+ GObject parent_instance;
+
+#ifdef GDK_WINDOWING_X11
+ GdkAtom selection_atom;
+ Atom opcode_atom;
+ Atom message_data_atom;
+#endif
+
+ GtkWidget *invisible;
+ GdkScreen *screen;
+ GtkOrientation orientation;
+ ClutterColor fg;
+ ClutterColor error;
+ ClutterColor warning;
+ ClutterColor success;
+
+ GList *messages;
+ GHashTable *socket_table;
+};
+
+struct _NaTrayManagerClass
+{
+ GObjectClass parent_class;
+
+ void (* tray_icon_added) (NaTrayManager *manager,
+ NaTrayChild *child);
+ void (* tray_icon_removed) (NaTrayManager *manager,
+ NaTrayChild *child);
+
+ void (* message_sent) (NaTrayManager *manager,
+ NaTrayChild *child,
+ const gchar *message,
+ glong id,
+ glong timeout);
+
+ void (* message_cancelled) (NaTrayManager *manager,
+ NaTrayChild *child,
+ glong id);
+
+ void (* lost_selection) (NaTrayManager *manager);
+};
+
+GType na_tray_manager_get_type (void);
+
+gboolean na_tray_manager_check_running (void);
+NaTrayManager *na_tray_manager_new (void);
+gboolean na_tray_manager_manage_screen (NaTrayManager *manager);
+void na_tray_manager_set_orientation (NaTrayManager *manager,
+ GtkOrientation orientation);
+GtkOrientation na_tray_manager_get_orientation (NaTrayManager *manager);
+void na_tray_manager_set_colors (NaTrayManager *manager,
+ ClutterColor *fg,
+ ClutterColor *error,
+ ClutterColor *warning,
+ ClutterColor *success);
+
+
+G_END_DECLS
+
+#endif /* __NA_TRAY_MANAGER_H__ */