diff --git a/configure.ac b/configure.ac index 3248f2d93..35a132375 100644 --- a/configure.ac +++ b/configure.ac @@ -69,7 +69,7 @@ GIO_MIN_VERSION=2.25.9 # Collect more than 20 libraries for a prize! PKG_CHECK_MODULES(MUTTER_PLUGIN, gio-2.0 >= $GIO_MIN_VERSION - gio-unix-2.0 dbus-glib-1 + gio-unix-2.0 dbus-glib-1 libxml-2.0 gtk+-3.0 >= $GTK_MIN_VERSION mutter-plugins >= $MUTTER_MIN_VERSION gjs-internals-1.0 >= $GJS_MIN_VERSION diff --git a/data/Makefile.am b/data/Makefile.am index ece423d24..4e73e037a 100644 --- a/data/Makefile.am +++ b/data/Makefile.am @@ -12,6 +12,11 @@ desktop_DATA = gnome-shell.desktop %.desktop:%.desktop.in $(AM_V_GEN) sed s/^_// < $< > $@ || rm $@ +searchprovidersdir = $(pkgdatadir)/search_providers +dist_searchproviders_DATA = \ + search_providers/google.xml \ + search_providers/wikipedia.xml + imagesdir = $(pkgdatadir)/images dist_images_DATA = \ close-black.svg \ diff --git a/data/org.gnome.shell.gschema.xml.in b/data/org.gnome.shell.gschema.xml.in index 78f301d66..c19463099 100644 --- a/data/org.gnome.shell.gschema.xml.in +++ b/data/org.gnome.shell.gschema.xml.in @@ -37,6 +37,10 @@ will be displayed in the favorites area. + + [] + <_summary>disabled OpenSearch providers + [] <_summary>History for command (Alt-F2) dialog diff --git a/data/search_providers/google.xml b/data/search_providers/google.xml new file mode 100644 index 000000000..daaa254b7 --- /dev/null +++ b/data/search_providers/google.xml @@ -0,0 +1,7 @@ + +Google +Google Search +UTF-8 +data:image/x-icon;base64,AAABAAEAEBAAAAEAGABoAwAAFgAAACgAAAAQAAAAIAAAAAEAGAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADs9Pt8xetPtu9FsfFNtu%2BTzvb2%2B%2Fne4dFJeBw0egA%2FfAJAfAA8ewBBegAAAAD%2B%2FPtft98Mp%2BwWsfAVsvEbs%2FQeqvF8xO7%2F%2F%2F63yqkxdgM7gwE%2FggM%2BfQA%2BegBDeQDe7PIbotgQufcMufEPtfIPsvAbs%2FQvq%2Bfz%2Bf%2F%2B%2B%2FZKhR05hgBBhQI8hgBAgAI9ewD0%2B%2Fg3pswAtO8Cxf4Kw%2FsJvvYAqupKsNv%2B%2Fv7%2F%2FP5VkSU0iQA7jQA9hgBDgQU%2BfQH%2F%2Ff%2FQ6fM4sM4KsN8AteMCruIqqdbZ7PH8%2Fv%2Fg6Nc%2Fhg05kAA8jAM9iQI%2BhQA%2BgQDQu6b97uv%2F%2F%2F7V8Pqw3eiWz97q8%2Ff%2F%2F%2F%2F7%2FPptpkkqjQE4kwA7kAA5iwI8iAA8hQCOSSKdXjiyflbAkG7u2s%2F%2B%2F%2F39%2F%2F7r8utrqEYtjQE8lgA7kwA7kwA9jwA9igA9hACiWSekVRyeSgiYSBHx6N%2F%2B%2Fv7k7OFRmiYtlAA5lwI7lwI4lAA7kgI9jwE9iwI4iQCoVhWcTxCmb0K%2BooT8%2Fv%2F7%2F%2F%2FJ2r8fdwI1mwA3mQA3mgA8lAE8lAE4jwA9iwE%2BhwGfXifWvqz%2B%2Ff%2F58u%2Fev6Dt4tr%2B%2F%2F2ZuIUsggA7mgM6mAM3lgA5lgA6kQE%2FkwBChwHt4dv%2F%2F%2F728ei1bCi7VAC5XQ7kz7n%2F%2F%2F6bsZkgcB03lQA9lgM7kwA2iQktZToPK4r9%2F%2F%2F9%2F%2F%2FSqYK5UwDKZAS9WALIkFn%2B%2F%2F3%2F%2BP8oKccGGcIRJrERILYFEMwAAuEAAdX%2F%2Ff7%2F%2FP%2B%2BfDvGXQLIZgLEWgLOjlf7%2F%2F%2F%2F%2F%2F9QU90EAPQAAf8DAP0AAfMAAOUDAtr%2F%2F%2F%2F7%2B%2Fu2bCTIYwDPZgDBWQDSr4P%2F%2Fv%2F%2F%2FP5GRuABAPkAA%2FwBAfkDAPAAAesAAN%2F%2F%2B%2Fz%2F%2F%2F64g1C5VwDMYwK8Yg7y5tz8%2Fv%2FV1PYKDOcAAP0DAf4AAf0AAfYEAOwAAuAAAAD%2F%2FPvi28ymXyChTATRrIb8%2F%2F3v8fk6P8MAAdUCAvoAAP0CAP0AAfYAAO4AAACAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAQAA + + diff --git a/data/search_providers/wikipedia.xml b/data/search_providers/wikipedia.xml new file mode 100644 index 000000000..4c61256a9 --- /dev/null +++ b/data/search_providers/wikipedia.xml @@ -0,0 +1,44 @@ + +Wikipedia +Wikipedia, the free encyclopedia +UTF-8 +data:image/x-icon;base64,AAABAAEAEBAQAAEABAAoAQAAFgAAACgAAAAQAAAAIAAAAAEABAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAEAgQAhIOEAMjHyABIR0gA6ejpAGlqaQCpqKkAKCgoAPz9%2FAAZGBkAmJiYANjZ2ABXWFcAent6ALm6uQA8OjwAiIiIiIiIiIiIiI4oiL6IiIiIgzuIV4iIiIhndo53KIiIiB%2FWvXoYiIiIfEZfWBSIiIEGi%2FfoqoiIgzuL84i9iIjpGIoMiEHoiMkos3FojmiLlUipYliEWIF%2BiDe0GoRa7D6GPbjcu1yIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + + +ar +bg +ca +cs +da +de +en +eo +es +fa +fi +fr +he +hu +id +it +ja +ko +lt +nl +no +pl +pt +ro +ru +sk +sl +sr +sv +tr +uk +vi +vo +war +zh + diff --git a/data/theme/gnome-shell.css b/data/theme/gnome-shell.css index 05c9ce263..d7c0b6c13 100644 --- a/data/theme/gnome-shell.css +++ b/data/theme/gnome-shell.css @@ -441,6 +441,32 @@ StTooltip StLabel { spacing: 4px; } +.search-providers-box { + spacing: 4px; +} + +.dash-search-button { + background-gradient-direction: vertical; + background-gradient-start: rgba(255, 255, 255, 0.2); + background-gradient-end: rgba(255, 255, 255, 0); +/* border: 1px solid #808080;*/ + border-radius: 10px; + height: 32px; + width: 300px; +} + +.dash-search-button:selected, +.dash-search-button:hover { + background-gradient-direction: vertical; + background-gradient-start: rgba(255, 255, 255, 0.4); + background-gradient-end: rgba(255, 255, 255, 0.2); +} + +.dash-search-button-label { + color: #cccccc; + font: 16px sans-serif; +} + /* Apps */ .icon-grid { diff --git a/js/ui/search.js b/js/ui/search.js index adc561198..926abb72c 100644 --- a/js/ui/search.js +++ b/js/ui/search.js @@ -1,6 +1,18 @@ /* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */ +const Gio = imports.gi.Gio; +const GLib = imports.gi.GLib; +const Lang = imports.lang; const Signals = imports.signals; +const Shell = imports.gi.Shell; + +const Gettext = imports.gettext.domain('gnome-shell'); +const _ = Gettext.gettext; + +const FileUtils = imports.misc.fileUtils; +const Main = imports.ui.main; + +const DISABLED_OPEN_SEARCH_PROVIDERS_KEY = 'disabled-open-search-providers'; const RESULT_ICON_SIZE = 48; @@ -211,6 +223,108 @@ SearchProvider.prototype = { }; Signals.addSignalMethods(SearchProvider.prototype); +function OpenSearchSystem(title) { + this._init(title); +} + +OpenSearchSystem.prototype = { + _init: function() { + this._providers = []; + global.settings.connect('changed::' + DISABLED_OPEN_SEARCH_PROVIDERS_KEY, Lang.bind(this, this._refresh)); + this._refresh(); + }, + + getProviders: function() { + let res = []; + for (let i = 0; i < this._providers.length; i++) + res.push({ id: i, name: this._providers[i].name }); + + return res; + }, + + setSearchTerms: function(terms) { + this._terms = terms; + }, + + _checkSupportedProviderLanguage: function(provider) { + if (provider.url.search(/{language}/) == -1) + return true; + + let langs = GLib.get_language_names(); + + langs.push('en'); + let lang = null; + for (let i = 0; i < langs.length; i++) { + for (let k = 0; k < provider.langs.length; k++) { + if (langs[i] == provider.langs[k]) + lang = langs[i]; + } + if (lang) + break; + } + provider.lang = lang; + return lang != null; + }, + + activateResult: function(id) { + let searchTerms = this._terms.join(' '); + + let url = this._providers[id].url.replace('{searchTerms}', encodeURIComponent(searchTerms)); + if (url.match('{language}')) + url = url.replace('{language}', this._providers[id].lang); + + try { + Gio.app_info_launch_default_for_uri(url, global.create_app_launch_context()); + } catch (e) { + // TODO: remove this after glib will be removed from moduleset + // In the default jhbuild, gio is in our prefix but gvfs is not + let p = new Shell.Process({ 'args' : ['gvfs-open', url] }); + p.run(); + } + + Main.overview.hide(); + }, + + _addProvider: function(fileName) { + let file = Gio.file_new_for_path(global.datadir + '/search_providers/' + fileName); + let source = ''; + + file.load_contents_async(null, Lang.bind(this, function (obj, res) { + let [success, source] = file.load_contents_finish(res); + if (source) { + let [success, name, url, langs, icon_uri] = global.parse_search_provider(source); + let provider ={ name: name, + url: url, + id: this._providers.length, + icon_uri: icon_uri, + langs: langs }; + if (this._checkSupportedProviderLanguage(provider)) { + this._providers.push(provider); + this.emit('changed'); + } + } + })); + }, + + _refresh: function() { + this._providers = []; + let names = global.settings.get_strv(DISABLED_OPEN_SEARCH_PROVIDERS_KEY); + let file = Gio.file_new_for_path(global.datadir + '/search_providers'); + FileUtils.listDirAsync(file, Lang.bind(this, function(files) { + for (let i = 0; i < files.length; i++) { + let enabled = true; + let name = files[i].get_name(); + for (let k = 0; k < names.length; k++) + if (names[k] == name) + enabled = false; + if (enabled) + this._addProvider(name); + } + })); + } +} +Signals.addSignalMethods(OpenSearchSystem.prototype); + function SearchSystem() { this._init(); } diff --git a/js/ui/searchDisplay.js b/js/ui/searchDisplay.js index 3f094546f..fe6c76072 100644 --- a/js/ui/searchDisplay.js +++ b/js/ui/searchDisplay.js @@ -149,18 +149,18 @@ GridSearchResults.prototype = { }; -function SearchResults(searchSystem) { - this._init(searchSystem); +function SearchResults(searchSystem, openSearchSystem) { + this._init(searchSystem, openSearchSystem); } SearchResults.prototype = { - _init: function(searchSystem) { + _init: function(searchSystem, openSearchSystem) { this._searchSystem = searchSystem; + this._openSearchSystem = openSearchSystem; + + this.actor = new St.BoxLayout({ name: 'searchResults', + vertical: true }); - this.actor = new St.Bin({ name: 'searchResults', - y_align: St.Align.START, - x_align: St.Align.START, - x_fill: true }); this._content = new St.BoxLayout({ name: 'searchResultsContent', vertical: true }); @@ -170,7 +170,11 @@ SearchResults.prototype = { scrollView.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC); scrollView.add_actor(this._content); - this.actor.set_child(scrollView); + this.actor.add(scrollView, { x_fill: true, + y_fill: false, + expand: true, + x_align: St.Align.START, + y_align: St.Align.START }); this._statusText = new St.Label({ style_class: 'search-statustext' }); this._content.add(this._statusText); @@ -179,6 +183,51 @@ SearchResults.prototype = { this._providerMeta = []; for (let i = 0; i < this._providers.length; i++) this.createProviderMeta(this._providers[i]); + + this._searchProvidersBox = new St.BoxLayout({ style_class: 'search-providers-box' }); + this.actor.add(this._searchProvidersBox); + + this._openSearchProviders = []; + this._openSearchSystem.connect('changed', Lang.bind(this, this._updateOpenSearchProviderButtons)); + this._updateOpenSearchProviderButtons(); + }, + + _updateOpenSearchProviderButtons: function() { + this._selectedOpenSearchButton = -1; + for (let i = 0; i < this._openSearchProviders.length; i++) + this._openSearchProviders[i].actor.destroy(); + this._openSearchProviders = this._openSearchSystem.getProviders(); + for (let i = 0; i < this._openSearchProviders.length; i++) + this._createOpenSearchProviderButton(this._openSearchProviders[i]); + }, + + _updateOpenSearchButtonState: function() { + for (let i = 0; i < this._openSearchProviders.length; i++) { + if (i == this._selectedOpenSearchButton) + this._openSearchProviders[i].actor.add_style_pseudo_class('selected'); + else + this._openSearchProviders[i].actor.remove_style_pseudo_class('selected'); + } + }, + + _createOpenSearchProviderButton: function(provider) { + let clickable = new St.Clickable({ style_class: 'dash-search-button', + reactive: true, + x_fill: true, + y_align: St.Align.MIDDLE }); + let bin = new St.Bin({ x_fill: false, + x_align:St.Align.MIDDLE }); + clickable.connect('clicked', Lang.bind(this, function() { + this._openSearchSystem.activateResult(provider.id); + })); + let title = new St.Label({ text: provider.name, + style_class: 'dash-search-button-label' }); + + bin.set_child(title); + clickable.set_child(bin); + provider.actor = clickable; + + this._searchProvidersBox.add(clickable); }, createProviderMeta: function(provider) { @@ -227,6 +276,8 @@ SearchResults.prototype = { this._searchSystem.reset(); this._statusText.hide(); this._clearDisplay(); + this._selectedOpenSearchButton = -1; + this._updateOpenSearchButtonState(); }, startingSearch: function() { @@ -247,12 +298,12 @@ SearchResults.prototype = { if (results.length == 0) { this._statusText.set_text(_("No matching results.")); this._statusText.show(); - return true; } else { this._statusText.hide(); } let terms = this._searchSystem.getTerms(); + this._openSearchSystem.setSearchTerms(terms); for (let i = 0; i < results.length; i++) { let [provider, providerResults] = results[i]; @@ -262,7 +313,8 @@ SearchResults.prototype = { meta.count.set_text('' + providerResults.length); } - this.selectDown(false); + if (this._selectedOpenSearchButton == -1) + this.selectDown(false); return true; }, @@ -284,16 +336,26 @@ SearchResults.prototype = { }, selectUp: function(recursing) { - for (let i = this._selectedProvider; i >= 0; i--) { - let meta = this._providerMeta[i]; - if (!meta.actor.visible) - continue; - let success = this._modifyActorSelection(meta.resultDisplay, true); - if (success) { - this._selectedProvider = i; - return; + if (this._selectedOpenSearchButton == -1) { + for (let i = this._selectedProvider; i >= 0; i--) { + let meta = this._providerMeta[i]; + if (!meta.actor.visible) + continue; + let success = this._modifyActorSelection(meta.resultDisplay, true); + if (success) { + this._selectedProvider = i; + return; + } } } + + if (this._selectedOpenSearchButton == -1) + this._selectedOpenSearchButton = this._openSearchProviders.length; + this._selectedOpenSearchButton--; + this._updateOpenSearchButtonState(); + if (this._selectedOpenSearchButton >= 0) + return; + if (this._providerMeta.length > 0 && !recursing) { this._selectedProvider = this._providerMeta.length - 1; this.selectUp(true); @@ -302,18 +364,30 @@ SearchResults.prototype = { selectDown: function(recursing) { let current = this._selectedProvider; - if (current == -1) - current = 0; - for (let i = current; i < this._providerMeta.length; i++) { - let meta = this._providerMeta[i]; - if (!meta.actor.visible) - continue; - let success = this._modifyActorSelection(meta.resultDisplay, false); - if (success) { - this._selectedProvider = i; - return; + if (this._selectedOpenSearchButton == -1) { + if (current == -1) + current = 0; + for (let i = current; i < this._providerMeta.length; i++) { + let meta = this._providerMeta[i]; + if (!meta.actor.visible) + continue; + let success = this._modifyActorSelection(meta.resultDisplay, false); + if (success) { + this._selectedProvider = i; + return; + } } } + this._selectedOpenSearchButton++; + + if (this._selectedOpenSearchButton < this._openSearchProviders.length) { + this._updateOpenSearchButtonState(); + return; + } + + this._selectedOpenSearchButton = -1; + this._updateOpenSearchButtonState(); + if (this._providerMeta.length > 0 && !recursing) { this._selectedProvider = 0; this.selectDown(true); @@ -321,6 +395,13 @@ SearchResults.prototype = { }, activateSelected: function() { + if (this._selectedOpenSearchButton != -1) { + let provider = this._openSearchProviders[this._selectedOpenSearchButton]; + this._openSearchSystem.activateResult(provider.id); + Main.overview.hide(); + return; + } + let current = this._selectedProvider; if (current < 0) return; diff --git a/js/ui/viewSelector.js b/js/ui/viewSelector.js index 96d7a9717..9575e296e 100644 --- a/js/ui/viewSelector.js +++ b/js/ui/viewSelector.js @@ -247,9 +247,10 @@ SearchTab.prototype = { this._focusBase = focusBase; this._searchSystem = new Search.SearchSystem(); + this._openSearchSystem = new Search.OpenSearchSystem(); this._searchEntry = new SearchEntry(focusBase); - this._searchResults = new SearchDisplay.SearchResults(this._searchSystem); + this._searchResults = new SearchDisplay.SearchResults(this._searchSystem, this._openSearchSystem); BaseTab.prototype._init.call(this, this._searchEntry.actor, this._searchResults.actor); diff --git a/src/shell-global.c b/src/shell-global.c index 4c3246f20..8e0463ddd 100644 --- a/src/shell-global.c +++ b/src/shell-global.c @@ -27,6 +27,9 @@ #include #include #include +#include +#include +#include #ifdef HAVE_SYS_RESOURCE_H #include #endif @@ -1070,6 +1073,136 @@ shell_global_breakpoint (ShellGlobal *global) G_BREAKPOINT (); } +/** + * shell_global_parse_search_provider: + * @global: A #ShellGlobal + * @data: description of provider + * @name: (out): location to store a display name + * @url: (out): location to store template of url + * @langs: (out) (transfer full) (element-type utf8): list of supported languages + * @icon_data_uri: (out): location to store uri + * @error: location to store GError + * + * Returns: %TRUE on success + */ +gboolean +shell_global_parse_search_provider (ShellGlobal *global, + const char *data, + char **name, + char **url, + GList **langs, + char **icon_data_uri, + GError **error) +{ + xmlDocPtr doc = xmlParseMemory (data, strlen(data)); + xmlNode *root; + + *name = NULL; + *url = NULL; + *icon_data_uri = NULL; + *langs = NULL; + + if (!doc) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Malformed xml"); + return FALSE; + } + + root = xmlDocGetRootElement (doc); + if (root && root->name && xmlStrcmp (root->name, (const xmlChar *)"OpenSearchDescription") == 0) + { + xmlNode *child; + for (child = root->children; child; child = child->next) + { + if (!child->name) + continue; + if (xmlStrcmp (child->name, (const xmlChar *)"Language") == 0) + { + xmlChar *val = xmlNodeListGetString(doc, child->xmlChildrenNode, 1); + if (!val) + continue; + *langs = g_list_append (*langs, g_strdup ((char *)val)); + xmlFree (val); + } + if (!*name && xmlStrcmp (child->name, (const xmlChar *)"ShortName") == 0) + { + xmlChar *val = xmlNodeListGetString(doc, child->xmlChildrenNode, 1); + *name = g_strdup ((char *)val); + xmlFree (val); + } + if (!*icon_data_uri && xmlStrcmp (child->name, (const xmlChar *)"Image") == 0) + { + xmlChar *val = xmlNodeListGetString(doc, child->xmlChildrenNode, 1); + if (val) + *icon_data_uri = g_strdup ((char *)val); + xmlFree (val); + } + if (!*url && xmlStrcmp (child->name, (const xmlChar *)"Url") == 0) + { + xmlChar *template; + xmlChar *type; + + type = xmlGetProp(child, (const xmlChar *)"type"); + if (!type) + continue; + + if (xmlStrcmp (type, (const xmlChar *)"text/html") != 0) + { + xmlFree (type); + continue; + } + xmlFree (type); + + template = xmlGetProp(child, (const xmlChar *)"template"); + if (!template) + continue; + *url = g_strdup ((char *)template); + xmlFree (template); + } + } + } + else + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Invalid OpenSearch document"); + xmlFreeDoc (doc); + return FALSE; + } + xmlFreeDoc (doc); + if (*icon_data_uri && *name && *url) + return TRUE; + + if (*icon_data_uri) + g_free (*icon_data_uri); + else + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "search provider doesn't have icon"); + + if (*name) + g_free (*name); + else if (error && !*error) + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "search provider doesn't have ShortName"); + + if (*url) + g_free (*url); + else if (error && !*error) + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "search provider doesn't have template for url"); + + if (*langs) + { + g_list_foreach (*langs, (GFunc)g_free, NULL); + g_list_free (*langs); + } + + *url = NULL; + *name = NULL; + *icon_data_uri = NULL; + *langs = NULL; + + return FALSE; +} + /** * shell_global_gc: * @global: A #ShellGlobal diff --git a/src/shell-global.h b/src/shell-global.h index 4a40bcd53..1afbdc465 100644 --- a/src/shell-global.h +++ b/src/shell-global.h @@ -84,6 +84,14 @@ void shell_global_reexec_self (ShellGlobal *global); void shell_global_breakpoint (ShellGlobal *global); +gboolean shell_global_parse_search_provider (ShellGlobal *global, + const char *data, + char **name, + char **url, + GList **langs, + char **icon_data_uri, + GError **error); + void shell_global_gc (ShellGlobal *global); void shell_global_maybe_gc (ShellGlobal *global); diff --git a/src/st/st-texture-cache.c b/src/st/st-texture-cache.c index 520b7d750..346ac4a8a 100644 --- a/src/st/st-texture-cache.c +++ b/src/st/st-texture-cache.c @@ -437,6 +437,67 @@ out: return rotated_pixbuf; } +static GdkPixbuf* +decode_image (const char *val) +{ + int i; + GError *error = NULL; + GdkPixbuf *res = NULL; + struct { + const char *prefix; + const char *mime_type; + } formats[] = { + { "data:image/x-icon;base64,", "image/x-icon" }, + { "data:image/png;base64,", "image/png" } + }; + + g_return_val_if_fail (val, NULL); + + for (i = 0; i < G_N_ELEMENTS (formats); i++) + { + if (g_str_has_prefix (val, formats[i].prefix)) + { + gsize len; + guchar *data = NULL; + char *unescaped; + + unescaped = g_uri_unescape_string (val + strlen (formats[i].prefix), NULL); + if (unescaped) + { + data = g_base64_decode (unescaped, &len); + g_free (unescaped); + } + + if (data) + { + GdkPixbufLoader *loader; + + loader = gdk_pixbuf_loader_new_with_mime_type (formats[i].mime_type, &error); + if (loader && + gdk_pixbuf_loader_write (loader, data, len, &error) && + gdk_pixbuf_loader_close (loader, &error)) + { + res = gdk_pixbuf_loader_get_pixbuf (loader); + g_object_ref (res); + } + g_object_unref (loader); + g_free (data); + } + } + } + if (!res) + { + if (error) + { + g_warning (error->message); + g_error_free (error); + } + else + g_warning ("incorrect data uri"); + } + return res; +} + static GdkPixbuf * impl_load_pixbuf_file (const char *uri, int available_width, @@ -448,6 +509,9 @@ impl_load_pixbuf_file (const char *uri, char *contents = NULL; gsize size; + if (g_str_has_prefix (uri, "data:")) + return decode_image (uri); + file = g_file_new_for_uri (uri); if (g_file_load_contents (file, NULL, &contents, &size, NULL, error)) {