summaryrefslogtreecommitdiff
path: root/src/ui/theme-parser.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/ui/theme-parser.c')
-rw-r--r--src/ui/theme-parser.c270
1 files changed, 243 insertions, 27 deletions
diff --git a/src/ui/theme-parser.c b/src/ui/theme-parser.c
index 2a01fd1d..a2225427 100644
--- a/src/ui/theme-parser.c
+++ b/src/ui/theme-parser.c
@@ -27,6 +27,22 @@
#include <string.h>
#include <stdlib.h>
+/* We were intending to put the version number
+ * in the subdirectory name, but we ended up
+ * using the filename instead. The "-1" survives
+ * as a fossil.
+ */
+#define THEME_SUBDIR "metacity-1"
+
+/* Highest version of the theme format to
+ * look out for.
+ */
+#define THEME_MAJOR_VERSION 3
+#define THEME_MINOR_VERSION 0
+#define THEME_VERSION (1000 * THEME_MAJOR_VERSION + THEME_MINOR_VERSION)
+
+#define MARCO_THEME_FILENAME_FORMAT "metacity-theme-%d.xml"
+
typedef enum
{
STATE_START,
@@ -81,11 +97,15 @@ typedef enum
typedef struct
{
+ /* This two lists contain stacks of state and required version
+ * (cast to pointers.) There is one list item for each currently
+ * open element. */
GSList *states;
+ GSList *required_versions;
const char *theme_name; /* name of theme (directory it's in) */
- const char *theme_file; /* theme filename */
- const char *theme_dir; /* dir the theme is inside */
+ const char *theme_file; /* theme filename */
+ const char *theme_dir; /* dir the theme is inside */
MetaTheme *theme; /* theme being parsed */
guint format_version; /* version of format of theme file */
char *name; /* name of named thing being parsed */
@@ -97,8 +117,22 @@ typedef struct
MetaFramePiece piece; /* position of piece being parsed */
MetaButtonType button_type; /* type of button/menuitem being parsed */
MetaButtonState button_state; /* state of button being parsed */
+ int skip_level; /* depth of elements that we're ignoring */
} ParseInfo;
+typedef enum {
+ THEME_PARSE_ERROR_TOO_OLD,
+ THEME_PARSE_ERROR_TOO_FAILED
+ } ThemeParseError;
+
+static GQuark
+theme_parse_error_quark (void)
+{
+ return g_quark_from_static_string ("theme-parse-error-quark");
+}
+
+#define THEME_PARSE_ERROR (theme_parse_error_quark ())
+
static void set_error (GError **err,
GMarkupParseContext *context,
int error_domain,
@@ -273,6 +307,7 @@ parse_info_init (ParseInfo *info)
{
info->theme_file = NULL;
info->states = g_slist_prepend (NULL, GINT_TO_POINTER (STATE_START));
+ info->required_versions = NULL;
info->theme = NULL;
info->name = NULL;
info->layout = NULL;
@@ -283,12 +318,14 @@ parse_info_init (ParseInfo *info)
info->piece = META_FRAME_PIECE_LAST;
info->button_type = META_BUTTON_TYPE_LAST;
info->button_state = META_BUTTON_STATE_LAST;
+ info->skip_level = 0;
}
static void
parse_info_free (ParseInfo *info)
{
g_slist_free (info->states);
+ g_slist_free (info->required_versions);
if (info->theme)
meta_theme_free (info->theme);
@@ -332,6 +369,31 @@ peek_state (ParseInfo *info)
return GPOINTER_TO_INT (info->states->data);
}
+static void
+push_required_version (ParseInfo *info,
+ int version)
+{
+ info->required_versions = g_slist_prepend (info->required_versions,
+ GINT_TO_POINTER (version));
+}
+
+static void
+pop_required_version (ParseInfo *info)
+{
+ g_return_if_fail (info->required_versions != NULL);
+
+ info->required_versions = g_slist_delete_link (info->required_versions, info->required_versions);
+}
+
+static int
+peek_required_version (ParseInfo *info)
+{
+ if (info->required_versions)
+ return GPOINTER_TO_INT (info->required_versions->data);
+ else
+ return info->format_version;
+}
+
#define ELEMENT_IS(name) (strcmp (element_name, (name)) == 0)
typedef struct
@@ -413,6 +475,13 @@ locate_attributes (GMarkupParseContext *context,
int j;
gboolean found;
+ /* Can be present anywhere */
+ if (strcmp (attribute_names[i], "version") == 0)
+ {
+ ++i;
+ continue;
+ }
+
found = FALSE;
j = 0;
while (j < n_attrs)
@@ -489,7 +558,13 @@ check_no_attributes (GMarkupParseContext *context,
const char **attribute_values,
GError **error)
{
- if (attribute_names[0] != NULL)
+ int i = 0;
+
+ /* Can be present anywhere */
+ if (attribute_names[0] && strcmp (attribute_names[i], "version") == 0)
+ i++;
+
+ if (attribute_names[i] != NULL)
{
set_error (error, context,
G_MARKUP_ERROR,
@@ -3354,6 +3429,89 @@ parse_menu_icon_element (GMarkupParseContext *context,
}
}
+static const char *
+find_version (const char **attribute_names,
+ const char **attribute_values)
+{
+ int i;
+
+ for (i = 0; attribute_names[i]; i++)
+ {
+ if (strcmp (attribute_names[i], "version") == 0)
+ return attribute_values[i];
+ }
+
+ return NULL;
+}
+
+/* Returns whether the version element was successfully parsed.
+ * If successfully parsed, then two additional items are returned:
+ *
+ * satisfied: whether this version of Mutter meets the version check
+ * minimum_required: minimum version of theme format required by version check
+ */
+static gboolean
+check_version (GMarkupParseContext *context,
+ const char *version_str,
+ gboolean *satisfied,
+ guint *minimum_required,
+ GError **error)
+{
+ static GRegex *version_regex;
+ GMatchInfo *info;
+ char *comparison_str, *major_str, *minor_str;
+ guint version;
+
+ *minimum_required = 0;
+
+ if (!version_regex)
+ version_regex = g_regex_new ("^\\s*([<>]=?)\\s*(\\d+)(\\.\\d+)?\\s*$", 0, 0, NULL);
+
+ if (!g_regex_match (version_regex, version_str, 0, &info))
+ {
+ g_match_info_free (info);
+ set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("Bad version specification '%s'"), version_str);
+ return FALSE;
+ }
+
+ comparison_str = g_match_info_fetch (info, 1);
+ major_str = g_match_info_fetch (info, 2);
+ minor_str = g_match_info_fetch (info, 3);
+
+ version = 1000 * atoi (major_str);
+ /* might get NULL, see: https://bugzilla.gnome.org/review?bug=588217 */
+ if (minor_str && minor_str[0])
+ version += atoi (minor_str + 1);
+
+ if (comparison_str[0] == '<')
+ {
+ if (comparison_str[1] == '=')
+ *satisfied = THEME_VERSION <= version;
+ else
+ *satisfied = THEME_VERSION < version;
+ }
+ else
+ {
+ if (comparison_str[1] == '=')
+ {
+ *satisfied = THEME_VERSION >= version;
+ *minimum_required = version;
+ }
+ else
+ {
+ *satisfied = THEME_VERSION > version;
+ *minimum_required = version + 1;
+ }
+ }
+
+ g_free (comparison_str);
+ g_free (major_str);
+ g_free (minor_str);
+ g_match_info_free (info);
+
+ return TRUE;
+}
static void
start_element_handler (GMarkupParseContext *context,
@@ -3364,6 +3522,65 @@ start_element_handler (GMarkupParseContext *context,
GError **error)
{
ParseInfo *info = user_data;
+ const char *version;
+ guint required_version = 0;
+
+ if (info->skip_level > 0)
+ {
+ info->skip_level++;
+ return;
+ }
+
+ required_version = peek_required_version (info);
+
+ version = find_version (attribute_names, attribute_values);
+ if (version != NULL)
+ {
+ gboolean satisfied;
+ guint element_required;
+
+ if (required_version < 3000)
+ {
+ set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("\"version\" attribute cannot be used in metacity-theme-1.xml or metacity-theme-2.xml"));
+ return;
+ }
+
+ if (!check_version (context, version, &satisfied, &element_required, error))
+ return;
+
+ /* Two different ways of handling an unsatisfied version check:
+ * for the toplevel element of a file, we throw an error back so
+ * that the controlling code can go ahead and look for an
+ * alternate metacity-theme-1.xml or metacity-theme-2.xml; for
+ * other elements we just silently skip the element and children.
+ */
+ if (peek_state (info) == STATE_START)
+ {
+ if (satisfied)
+ {
+ if (element_required > info->format_version)
+ info->format_version = element_required;
+ }
+ else
+ {
+ set_error (error, context, THEME_PARSE_ERROR, THEME_PARSE_ERROR_TOO_OLD,
+ _("Theme requires version %s but latest supported theme version is %d.%d"),
+ version, THEME_VERSION, THEME_MINOR_VERSION);
+ return;
+ }
+ }
+ else if (!satisfied)
+ {
+ info->skip_level = 1;
+ return;
+ }
+
+ if (element_required > required_version)
+ required_version = element_required;
+ }
+
+ push_required_version (info, required_version);
switch (peek_state (info))
{
@@ -3513,6 +3730,12 @@ end_element_handler (GMarkupParseContext *context,
{
ParseInfo *info = user_data;
+ if (info->skip_level > 0)
+ {
+ info->skip_level--;
+ return;
+ }
+
switch (peek_state (info))
{
case STATE_START:
@@ -3794,6 +4017,8 @@ end_element_handler (GMarkupParseContext *context,
g_assert (peek_state (info) == STATE_THEME);
break;
}
+
+ pop_required_version (info);
}
#define NO_TEXT(element_name) set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, _("No text is allowed inside element <%s>"), element_name)
@@ -3828,6 +4053,9 @@ text_handler (GMarkupParseContext *context,
{
ParseInfo *info = user_data;
+ if (info->skip_level > 0)
+ return;
+
if (all_whitespace (text, text_len))
return;
@@ -4002,27 +4230,15 @@ text_handler (GMarkupParseContext *context,
}
}
-/* We were intending to put the version number
- * in the subdirectory name, but we ended up
- * using the filename instead. The "-1" survives
- * as a fossil.
- */
-#define THEME_SUBDIR "metacity-1"
-
-/* Highest version of the theme format to
- * look out for.
- */
-#define THEME_VERSION 2
-
-#define MARCO_THEME_FILENAME_FORMAT "metacity-theme-%d.xml"
-
/* If the theme is not-corrupt, keep looking for alternate versions
* in other locations we might be compatible with
*/
static gboolean
theme_error_is_fatal (GError *error)
{
- return error->domain != G_FILE_ERROR;
+ return !(error->domain == G_FILE_ERROR ||
+ (error->domain == THEME_PARSE_ERROR &&
+ error->code == THEME_PARSE_ERROR_TOO_OLD));
}
static MetaTheme*
@@ -4059,7 +4275,7 @@ load_theme (const char *theme_dir,
info.theme_file = theme_file;
info.theme_dir = theme_dir;
- info.format_version = major_version;
+ info.format_version = 1000 * major_version;
context = g_markup_parse_context_new (&marco_theme_parser, 0, &info, NULL);
@@ -4110,7 +4326,7 @@ meta_theme_load (const char *theme_name,
char *theme_dir;
MetaTheme *retval;
const gchar* const* xdg_data_dirs;
- int version;
+ int major_version;
int i;
retval = NULL;
@@ -4118,10 +4334,10 @@ meta_theme_load (const char *theme_name,
if (meta_is_debugging ())
{
/* We try all supported major versions from current to oldest */
- for (version = THEME_VERSION; (version > 0); version--)
+ for (major_version = THEME_MAJOR_VERSION; (major_version > 0); major_version--)
{
theme_dir = g_build_filename ("./themes", theme_name, NULL);
- retval = load_theme (theme_dir, theme_name, version, &error);
+ retval = load_theme (theme_dir, theme_name, major_version, &error);
if (!keep_trying (&error))
goto out;
@@ -4129,7 +4345,7 @@ meta_theme_load (const char *theme_name,
}
/* We try all supported major versions from current to oldest */
- for (version = THEME_VERSION; (version > 0); version--)
+ for (major_version = THEME_MAJOR_VERSION; (major_version > 0); major_version--)
{
/* We try first in home dir, XDG_USER_DATA_DIR, XDG_DATA_DIRS,
* then system dir for themes */
@@ -4141,7 +4357,7 @@ meta_theme_load (const char *theme_name,
THEME_SUBDIR,
NULL);
- retval = load_theme (theme_dir, theme_name, version, &error);
+ retval = load_theme (theme_dir, theme_name, major_version, &error);
g_free (theme_dir);
if (!keep_trying (&error))
goto out;
@@ -4153,7 +4369,7 @@ meta_theme_load (const char *theme_name,
THEME_SUBDIR,
NULL);
- retval = load_theme (theme_dir, theme_name, version, &error);
+ retval = load_theme (theme_dir, theme_name, major_version, &error);
g_free (theme_dir);
if (!keep_trying (&error))
@@ -4169,7 +4385,7 @@ meta_theme_load (const char *theme_name,
THEME_SUBDIR,
NULL);
- retval = load_theme (theme_dir, theme_name, version, &error);
+ retval = load_theme (theme_dir, theme_name, major_version, &error);
g_free (theme_dir);
if (!keep_trying (&error))
goto out;
@@ -4180,7 +4396,7 @@ meta_theme_load (const char *theme_name,
theme_name,
THEME_SUBDIR,
NULL);
- retval = load_theme (theme_dir, theme_name, version, &error);
+ retval = load_theme (theme_dir, theme_name, major_version, &error);
g_free (theme_dir);
if (!keep_trying (&error))
goto out;