mirror of
https://github.com/brl/mutter.git
synced 2024-11-21 07:30:42 -05:00
metacity-theme-3.xml: Add a flexible version mechanism
The current mechanism of metacity-theme-1.xml and metacity-theme-2.xml is not flexible for allowing small-scale additions. With this patch we bump the major version version once more to metacity-theme-3.xml and add a single feature: Any element in the DTD can have an attribute: version="[<|<=|=>|>] MAJOR.MINOR" And it will be ignored unless the predicate is met. (< and > should be to be entity escaped as < and >) This allows having alternate sections of the theme file for older and newer version. * Required GLib version is bumped to 2.14 so we can parse versions with a regular expression. * We switch internal version numbers to be "1000 * major + minor" * We keep a stack of the maximum required version for the current portion the XML tree so that the "cannot use versions you don't require" stricture of the old code can be made local to a subpart of the tree. * A version on the top metacity_theme element causes the entire file to be ignored; this allows having one metacity-theme-3.xml for version 3.2 and newer (say) and a metacity-1.xml for everything old. Actual new features will be added starting with 3.1 - 3.0 is just the version="" feature. http://bugzilla.gnome.org/show_bug.cgi?id=592503
This commit is contained in:
parent
0ac46316af
commit
020aea033c
@ -181,8 +181,8 @@ AC_CHECK_HEADERS(execinfo.h, [AC_CHECK_FUNCS(backtrace)])
|
||||
AM_GLIB_GNU_GETTEXT
|
||||
|
||||
## here we get the flags we'll actually use
|
||||
# GOptionEntry requires glib-2.6.0
|
||||
PKG_CHECK_MODULES(ALL, glib-2.0 >= 2.6.0)
|
||||
# GRegex requires Glib-2.14.0
|
||||
PKG_CHECK_MODULES(ALL, glib-2.0 >= 2.14.0)
|
||||
# gtk_window_set_icon_name requires gtk2+-2.6.0
|
||||
PKG_CHECK_MODULES(MUTTER_MESSAGE, gtk+-2.0 >= 2.6.0)
|
||||
PKG_CHECK_MODULES(MUTTER_WINDOW_DEMO, gtk+-2.0 >= 2.6.0)
|
||||
|
@ -4,6 +4,7 @@ of the theme format, and a given theme can support more than one format.
|
||||
Version 1: THEMEDIR/metacity-1/metacity-theme-1.xml
|
||||
(original metacity format)
|
||||
Version 2: THEMEDIR/metacity-1/metacity-theme-2.xml
|
||||
Version 3: THEMEDIR/metacity-1/metacity-theme-3.xml
|
||||
|
||||
The subdirectory name is "metacity-1" in all versions.
|
||||
|
||||
@ -21,6 +22,27 @@ This document has separate sections for each format version. You may
|
||||
want to read the document in reverse order, since the base features
|
||||
are discussed under version 1.
|
||||
|
||||
New Features in Theme Format Version 3
|
||||
======================================
|
||||
|
||||
Format version 3 has exactly one new feature; any element in the file
|
||||
can now have a version attribute:
|
||||
|
||||
version="[<|<=|=>|>] MAJOR.MINOR"
|
||||
|
||||
(< and > should be to be entity escaped as < and >). If this
|
||||
version check is not met, then the element and its children will be
|
||||
ignored. This allows having alternate sections of the theme file for
|
||||
older and newer version of the Metacity theme format.
|
||||
|
||||
When placed on the toplevel <metacity_theme> element, an unsatisfied
|
||||
version check will not just cause the contents of the file to be
|
||||
ignored, it will also cause the lookup of a theme file to proceed on
|
||||
and look for an older format 2 or format 1 file. This allows making a
|
||||
metacity-theme-3.xml file that is only used the format version 3.2 or
|
||||
newer is supported, and using metacity-theme-1.xml for older window
|
||||
managers.
|
||||
|
||||
New Features in Theme Format Version 2
|
||||
======================================
|
||||
|
||||
|
@ -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 METACITY_THEME_FILENAME_FORMAT "metacity-theme-%d.xml"
|
||||
|
||||
typedef enum
|
||||
{
|
||||
STATE_START,
|
||||
@ -79,7 +95,11 @@ 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 */
|
||||
@ -95,8 +115,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,
|
||||
@ -257,6 +291,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;
|
||||
@ -267,12 +302,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);
|
||||
@ -316,6 +353,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
|
||||
@ -393,6 +455,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)
|
||||
@ -469,7 +538,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,
|
||||
@ -3239,6 +3314,91 @@ 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,
|
||||
@ -3249,6 +3409,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))
|
||||
{
|
||||
@ -3388,6 +3607,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:
|
||||
@ -3661,6 +3886,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)
|
||||
@ -3695,6 +3922,9 @@ text_handler (GMarkupParseContext *context,
|
||||
{
|
||||
ParseInfo *info = user_data;
|
||||
|
||||
if (info->skip_level > 0)
|
||||
return;
|
||||
|
||||
if (all_whitespace (text, text_len))
|
||||
return;
|
||||
|
||||
@ -3863,27 +4093,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 METACITY_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 *
|
||||
@ -3923,7 +4141,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 (&metacity_theme_parser,
|
||||
0, &info, NULL);
|
||||
@ -3980,7 +4198,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;
|
||||
@ -3989,10 +4207,10 @@ meta_theme_load (const char *theme_name,
|
||||
{
|
||||
/* Try in themes in our source tree */
|
||||
/* 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);
|
||||
g_free (theme_dir);
|
||||
if (!keep_trying (&error))
|
||||
goto out;
|
||||
@ -4000,7 +4218,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_DATA_DIRS, then system dir for themes */
|
||||
|
||||
@ -4011,7 +4229,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;
|
||||
@ -4026,7 +4244,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;
|
||||
@ -4039,7 +4257,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;
|
||||
|
@ -6617,7 +6617,7 @@ meta_theme_earliest_version_with_button (MetaButtonType type)
|
||||
case META_BUTTON_TYPE_RIGHT_LEFT_BACKGROUND:
|
||||
case META_BUTTON_TYPE_RIGHT_MIDDLE_BACKGROUND:
|
||||
case META_BUTTON_TYPE_RIGHT_RIGHT_BACKGROUND:
|
||||
return 1;
|
||||
return 1000;
|
||||
|
||||
case META_BUTTON_TYPE_SHADE:
|
||||
case META_BUTTON_TYPE_ABOVE:
|
||||
@ -6625,10 +6625,10 @@ meta_theme_earliest_version_with_button (MetaButtonType type)
|
||||
case META_BUTTON_TYPE_UNSHADE:
|
||||
case META_BUTTON_TYPE_UNABOVE:
|
||||
case META_BUTTON_TYPE_UNSTICK:
|
||||
return 2;
|
||||
return 2000;
|
||||
|
||||
default:
|
||||
meta_warning("Unknown button %d\n", type);
|
||||
return 1;
|
||||
return 1000;
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user