10c104529f
Initializing this synchronously means that we will possibly wait for the process to be auto-activated and answering to our call. If the process is already running it also might not answer immediately our request, as it might be doing sync I/O. The right thing to do is to initialize the proxy asynchronously; there are try/catch blocks in place for when the object is not available, or not properly initialized. https://bugzilla.gnome.org/show_bug.cgi?id=687491
212 lines
7.3 KiB
JavaScript
212 lines
7.3 KiB
JavaScript
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
|
|
|
|
const Gio = imports.gi.Gio;
|
|
const GLib = imports.gi.GLib;
|
|
const Lang = imports.lang;
|
|
const St = imports.gi.St;
|
|
|
|
const FileUtils = imports.misc.fileUtils;
|
|
const Search = imports.ui.search;
|
|
|
|
const KEY_FILE_GROUP = 'Shell Search Provider';
|
|
|
|
const SearchProviderIface = <interface name="org.gnome.Shell.SearchProvider">
|
|
<method name="GetInitialResultSet">
|
|
<arg type="as" direction="in" />
|
|
<arg type="as" direction="out" />
|
|
</method>
|
|
<method name="GetSubsearchResultSet">
|
|
<arg type="as" direction="in" />
|
|
<arg type="as" direction="in" />
|
|
<arg type="as" direction="out" />
|
|
</method>
|
|
<method name="GetResultMetas">
|
|
<arg type="as" direction="in" />
|
|
<arg type="aa{sv}" direction="out" />
|
|
</method>
|
|
<method name="ActivateResult">
|
|
<arg type="s" direction="in" />
|
|
</method>
|
|
</interface>;
|
|
|
|
var SearchProviderProxy = Gio.DBusProxy.makeProxyWrapper(SearchProviderIface);
|
|
|
|
|
|
function loadRemoteSearchProviders(addProviderCallback) {
|
|
let dataDirs = GLib.get_system_data_dirs();
|
|
let loadedProviders = {};
|
|
for (let i = 0; i < dataDirs.length; i++) {
|
|
let path = GLib.build_filenamev([dataDirs[i], 'gnome-shell', 'search-providers']);
|
|
let dir = Gio.file_new_for_path(path);
|
|
|
|
dir.query_info_async('standard:type', Gio.FileQueryInfoFlags.NONE,
|
|
GLib.PRIORITY_DEFAULT, null,
|
|
function(object, res) {
|
|
let exists = false;
|
|
try {
|
|
object.query_info_finish(res);
|
|
exists = true;
|
|
} catch (e) {
|
|
}
|
|
|
|
if (!exists)
|
|
return;
|
|
|
|
loadRemoteSearchProvidersFromDir(dir, loadedProviders, addProviderCallback);
|
|
});
|
|
}
|
|
};
|
|
|
|
function loadRemoteSearchProvidersFromDir(dir, loadedProviders, addProviderCallback) {
|
|
let dirPath = dir.get_path();
|
|
FileUtils.listDirAsync(dir, Lang.bind(this, function(files) {
|
|
for (let i = 0; i < files.length; i++) {
|
|
let keyfile = new GLib.KeyFile();
|
|
let path = GLib.build_filenamev([dirPath, files[i].get_name()]);
|
|
|
|
try {
|
|
keyfile.load_from_file(path, 0);
|
|
} catch(e) {
|
|
continue;
|
|
}
|
|
|
|
if (!keyfile.has_group(KEY_FILE_GROUP))
|
|
continue;
|
|
|
|
let remoteProvider;
|
|
try {
|
|
let group = KEY_FILE_GROUP;
|
|
let busName = keyfile.get_string(group, 'BusName');
|
|
let objectPath = keyfile.get_string(group, 'ObjectPath');
|
|
|
|
if (loadedProviders[objectPath])
|
|
continue;
|
|
|
|
let appInfo = null;
|
|
try {
|
|
let desktopId = keyfile.get_string(group, 'DesktopId');
|
|
appInfo = Gio.DesktopAppInfo.new(desktopId);
|
|
} catch (e) {
|
|
log('Ignoring search provider ' + path + ': missing DesktopId');
|
|
continue;
|
|
}
|
|
|
|
remoteProvider = new RemoteSearchProvider(appInfo.get_name(),
|
|
appInfo.get_icon(),
|
|
busName,
|
|
objectPath);
|
|
loadedProviders[objectPath] = remoteProvider;
|
|
} catch(e) {
|
|
log('Failed to add search provider %s: %s'.format(path, e.toString()));
|
|
continue;
|
|
}
|
|
|
|
addProviderCallback(remoteProvider);
|
|
}
|
|
}));
|
|
|
|
};
|
|
|
|
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, Lang.bind(this, this._onProxyConstructed));
|
|
|
|
this.parent(title.toUpperCase());
|
|
this._cancellable = new Gio.Cancellable();
|
|
},
|
|
|
|
_onProxyConstructed: function(proxy) {
|
|
// Do nothing
|
|
},
|
|
|
|
createIcon: function(size, meta) {
|
|
if (meta['gicon']) {
|
|
return new St.Icon({ gicon: Gio.icon_new_for_string(meta['gicon']),
|
|
icon_size: size });
|
|
} 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 });
|
|
},
|
|
|
|
_getResultsFinished: function(results, error) {
|
|
if (error)
|
|
return;
|
|
this.searchSystem.pushResults(this, results[0]);
|
|
},
|
|
|
|
getInitialResultSet: 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, []);
|
|
}
|
|
},
|
|
|
|
getSubsearchResultSet: 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);
|
|
},
|
|
|
|
getResultMetas: 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);
|
|
}
|
|
});
|
|
|
|
|