[gnome-shell] add ability to search in web from search view



commit b0c6cf3fc541e117ca3d4b00b77bf92182225b75
Author: Maxim Ermilov <zaspire rambler ru>
Date:   Tue Jan 18 00:38:47 2011 +0300

    add ability to search in web from search view
    
    It use OpenSearch to define the search engines.
    https://bugzilla.gnome.org/show_bug.cgi?id=623708

 configure.ac                        |    2 +-
 data/Makefile.am                    |    5 ++
 data/org.gnome.shell.gschema.xml.in |    4 +
 data/search_providers/google.xml    |    7 ++
 data/search_providers/wikipedia.xml |   44 +++++++++++
 data/theme/gnome-shell.css          |   26 +++++++
 js/ui/search.js                     |  114 +++++++++++++++++++++++++++++
 js/ui/searchDisplay.js              |  137 ++++++++++++++++++++++++++++-------
 js/ui/viewSelector.js               |    3 +-
 src/shell-global.c                  |  133 ++++++++++++++++++++++++++++++++++
 src/shell-global.h                  |    8 ++
 src/st/st-texture-cache.c           |   64 ++++++++++++++++
 12 files changed, 517 insertions(+), 30 deletions(-)
---
diff --git a/configure.ac b/configure.ac
index 3248f2d..35a1323 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 ece423d..4e73e03 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 78f301d..c194630 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.
       </_description>
     </key>
+    <key name="disabled-open-search-providers" type="as">
+      <default>[]</default>
+      <_summary>disabled OpenSearch providers</_summary>
+    </key>
     <key name="command-history" type="as">
       <default>[]</default>
       <_summary>History for command (Alt-F2) dialog</_summary>
diff --git a/data/search_providers/google.xml b/data/search_providers/google.xml
new file mode 100644
index 0000000..daaa254
--- /dev/null
+++ b/data/search_providers/google.xml
@@ -0,0 +1,7 @@
+<OpenSearchDescription xmlns="http://a9.com/-/spec/opensearch/1.1/";>
+<ShortName>Google</ShortName>
+<Description>Google Search</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">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%2F6bsZkgcB03lQA9lgM7kwA2iQktZT
 oPK4r9%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</Image>
+<Url type="text/html" method="GET" template="http://www.google.com/search?q={searchTerms}"/>
+</OpenSearchDescription>
diff --git a/data/search_providers/wikipedia.xml b/data/search_providers/wikipedia.xml
new file mode 100644
index 0000000..4c61256
--- /dev/null
+++ b/data/search_providers/wikipedia.xml
@@ -0,0 +1,44 @@
+<OpenSearchDescription xmlns="http://a9.com/-/spec/opensearch/1.1/";>
+<ShortName>Wikipedia</ShortName>
+<Description>Wikipedia, the free encyclopedia</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,AAABAAEAEBAQAAEABAAoAQAAFgAAACgAAAAQAAAAIAAAAAEABAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAEAgQAhIOEAMjHyABIR0gA6ejpAGlqaQCpqKkAKCgoAPz9%2FAAZGBkAmJiYANjZ2ABXWFcAent6ALm6uQA8OjwAiIiIiIiIiIiIiI4oiL6IiIiIgzuIV4iIiIhndo53KIiIiB%2FWvXoYiIiIfEZfWBSIiIEGi%2FfoqoiIgzuL84i9iIjpGIoMiEHoiMkos3FojmiLlUipYliEWIF%2BiDe0GoRa7D6GPbjcu1yIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA</Image>
+<Url type="text/html" method="GET" template="http://{language}.wikipedia.org/wiki/Special:Search?search={searchTerms}"/>
+<!-- The criterion for being below is being listed with more than 100,000
+articles on http://meta.wikimedia.org/wiki/List_of_Wikipedias -->
+<Language>ar</Language>
+<Language>bg</Language>
+<Language>ca</Language>
+<Language>cs</Language>
+<Language>da</Language>
+<Language>de</Language>
+<Language>en</Language>
+<Language>eo</Language>
+<Language>es</Language>
+<Language>fa</Language>
+<Language>fi</Language>
+<Language>fr</Language>
+<Language>he</Language>
+<Language>hu</Language>
+<Language>id</Language>
+<Language>it</Language>
+<Language>ja</Language>
+<Language>ko</Language>
+<Language>lt</Language>
+<Language>nl</Language>
+<Language>no</Language>
+<Language>pl</Language>
+<Language>pt</Language>
+<Language>ro</Language>
+<Language>ru</Language>
+<Language>sk</Language>
+<Language>sl</Language>
+<Language>sr</Language>
+<Language>sv</Language>
+<Language>tr</Language>
+<Language>uk</Language>
+<Language>vi</Language>
+<Language>vo</Language>
+<Language>war</Language>
+<Language>zh</Language>
+</OpenSearchDescription>
diff --git a/data/theme/gnome-shell.css b/data/theme/gnome-shell.css
index 05c9ce2..d7c0b6c 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 adc5611..926abb7 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 3f09454..fe6c760 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 96d7a97..9575e29 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 4c3246f..8e0463d 100644
--- a/src/shell-global.c
+++ b/src/shell-global.c
@@ -27,6 +27,9 @@
 #include <X11/extensions/Xfixes.h>
 #include <gjs/gjs-module.h>
 #include <canberra.h>
+#include <libxml/xmlmemory.h>
+#include <libxml/parser.h>
+#include <libxml/tree.h>
 #ifdef HAVE_SYS_RESOURCE_H
 #include <sys/resource.h>
 #endif
@@ -1071,6 +1074,136 @@ shell_global_breakpoint (ShellGlobal *global)
 }
 
 /**
+ * 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 4a40bcd..1afbdc4 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 520b7d7..346ac4a 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))
     {



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]