From f6749fb2045efd6c94719cbf62353225a3b26216 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20M=C3=BCllner?= Date: Thu, 17 Nov 2011 00:11:05 +0100 Subject: [PATCH] search: Add RemoteSearchProvider Add an asynchronous search provider for results from a DBus service implementing the org.gnome.Shell.SearchProvider interface; this will allow applications to hook into the Shell's search without implementing it in Shell itself or requiring an extension. https://bugzilla.gnome.org/show_bug.cgi?id=663125 --- data/Makefile.am | 4 + data/org.gnome.ShellSearchProvider.xml | 147 +++++++++++++++++++++++++ js/Makefile.am | 1 + js/ui/remoteSearch.js | 131 ++++++++++++++++++++++ 4 files changed, 283 insertions(+) create mode 100644 data/org.gnome.ShellSearchProvider.xml create mode 100644 js/ui/remoteSearch.js diff --git a/data/Makefile.am b/data/Makefile.am index cbc7b796d..4803081e8 100644 --- a/data/Makefile.am +++ b/data/Makefile.am @@ -17,6 +17,9 @@ dist_searchproviders_DATA = \ open-search-providers/google.xml \ open-search-providers/wikipedia.xml +introspectiondir = $(datadir)/dbus-1/interfaces +introspection_DATA = org.gnome.ShellSearchProvider.xml + themedir = $(pkgdatadir)/theme dist_theme_DATA = \ theme/calendar-arrow-left.svg \ @@ -73,6 +76,7 @@ shaders_DATA = \ EXTRA_DIST = \ gnome-shell.desktop.in.in \ gnome-shell-extension-prefs.desktop.in.in \ + $(introspection_DATA) \ $(menu_DATA) \ $(shaders_DATA) \ $(convert_DATA) \ diff --git a/data/org.gnome.ShellSearchProvider.xml b/data/org.gnome.ShellSearchProvider.xml new file mode 100644 index 000000000..16fa9ad0a --- /dev/null +++ b/data/org.gnome.ShellSearchProvider.xml @@ -0,0 +1,147 @@ + + + + + + + The interface used for integrating into GNOME Shell's search + interface. + + + + + + + + + Called when the user first begins a search. + + + + + + + + Array of search terms, which the provider should treat as + logical AND. + + + + + + + + + An array of result identifier strings representing items which + match the given search terms. Identifiers must be unique within + the provider's domain, but other than that may be chosen freely + by the provider. + + + + + + + + + + + Called when a search is performed which is a "subsearch" of + the previous search, e.g. the method may return less results, but + not more or different results. + + This allows search providers to only search through the previous + result set, rather than possibly performing a full re-query. + + + + + + + + Array of item identifiers + + + + + + + + + Array of updated search terms, which the provider should treat as + logical AND. + + + + + + + + + An array of result identifier strings representing items which + match the given search terms. Identifiers must be unique within + the provider's domain, but other than that may be chosen freely + by the provider. + + + + + + + + + + + Return an array of meta data used to display each given result + + + + + + + + An array of result identifiers as returned by + GetInitialResultSet() or GetSubsearchResultSet() + + + + + + + + + A dictionary describing the given search result, containing + 'id', 'name' (both strings) and either 'icon' (a serialized + GIcon) or 'icon-data' (raw image data as (iiibiiay) - width, + height, rowstride, has-alpha, bits per sample, channels, data) + + + + + + + + + + + Called when the users chooses a given result. The result should + be displayed in the application associated with the corresponding + provider. + + + + + + + + A result identifier as returned by GetInitialResultSet() or + GetSubsearchResultSet() + + + + + + + diff --git a/js/Makefile.am b/js/Makefile.am index 98f7a4858..67806178e 100644 --- a/js/Makefile.am +++ b/js/Makefile.am @@ -75,6 +75,7 @@ nobase_dist_js_DATA = \ ui/placeDisplay.js \ ui/polkitAuthenticationAgent.js \ ui/popupMenu.js \ + ui/remoteSearch.js \ ui/runDialog.js \ ui/scripting.js \ ui/search.js \ diff --git a/js/ui/remoteSearch.js b/js/ui/remoteSearch.js new file mode 100644 index 000000000..18e57497a --- /dev/null +++ b/js/ui/remoteSearch.js @@ -0,0 +1,131 @@ +// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- + +const Gio = imports.gi.Gio; +const Lang = imports.lang; +const St = imports.gi.St; + +const Search = imports.ui.search; + +const SearchProviderIface = + + + + + + + + + + + + + + + + +; + +var SearchProviderProxy = Gio.DBusProxy.makeProxyWrapper(SearchProviderIface); + + +const RemoteSearchProvider = new Lang.Class({ + Name: 'RemoteSearchProvider', + Extends: Search.SearchProvider, + + _init: function(title, icon, dbusName, dbusPath) { + this._proxy = new SearchProviderProxy(Gio.DBus.session, + dbusName, dbusPath); + + this.parent(title.toUpperCase()); + this.async = true; + this._cancellable = new Gio.Cancellable(); + }, + + createIcon: function(size, meta) { + if (meta['gicon']) { + return new St.Icon({ gicon: Gio.icon_new_for_string(meta['gicon']), + icon_size: size, + icon_type: St.IconType.FULLCOLOR }); + } else if (meta['icon-data']) { + let [width, height, rowStride, hasAlpha, + bitsPerSample, nChannels, data] = meta['icon-data']; + let textureCache = St.TextureCache.get_default(); + return textureCache.load_from_raw(data, hasAlpha, + width, height, rowStride, size); + } + + // Ugh, but we want to fall back to something ... + return new St.Icon({ icon_name: 'text-x-generic', + icon_size: size, + icon_type: St.IconType.FULLCOLOR }); + }, + + _getResultsFinished: function(results, error) { + if (error) + return; + this.searchSystem.pushResults(this, results[0]); + }, + + getInitialResultSetAsync: function(terms) { + this._cancellable.cancel(); + this._cancellable.reset(); + try { + this._proxy.GetInitialResultSetRemote(terms, + Lang.bind(this, this._getResultsFinished), + this._cancellable); + } catch(e) { + log('Error calling GetInitialResultSet for provider %s: %s'.format( this.title, e.toString())); + this.searchSystem.pushResults(this, []); + } + }, + + getSubsearchResultSetAsync: function(previousResults, newTerms) { + this._cancellable.cancel(); + this._cancellable.reset(); + try { + this._proxy.GetSubsearchResultSetRemote(previousResults, newTerms, + Lang.bind(this, this._getResultsFinished), + this._cancellable); + } catch(e) { + log('Error calling GetSubsearchResultSet for provider %s: %s'.format(this.title, e.toString())); + this.searchSystem.pushResults(this, []); + } + }, + + _getResultMetasFinished: function(results, error, callback) { + if (error) { + callback([]); + return; + } + let metas = results[0]; + let resultMetas = []; + for (let i = 0; i < metas.length; i++) { + for (let prop in metas[i]) + metas[i][prop] = metas[i][prop].deep_unpack(); + resultMetas.push({ id: metas[i]['id'], + name: metas[i]['name'], + createIcon: Lang.bind(this, + this.createIcon, metas[i]) }); + } + callback(resultMetas); + }, + + getResultMetasAsync: function(ids, callback) { + this._cancellable.cancel(); + this._cancellable.reset(); + try { + this._proxy.GetResultMetasRemote(ids, + Lang.bind(this, this._getResultMetasFinished, callback), + this._cancellable); + } catch(e) { + log('Error calling GetResultMetas for provider %s: %s'.format(this.title, e.toString())); + callback([]); + } + }, + + activateResult: function(id) { + this._proxy.ActivateResultRemote(id); + } +}); + +