2b45a01517
We have made good progress on object literals as well, although there are still a lot that use the old style, given how ubiquitous object literals are. But the needed reindentation isn't overly intrusive, as changes are limited to the object literals themselves (i.e. they don't affect surrounding code). And given that object literals account for quite a bit of the remaining differences between regular and legacy rules, doing the transition now is still worthwhile. Part-of: <https://gitlab.gnome.org/GNOME/gnome-shell/-/merge_requests/2200>
340 lines
11 KiB
JavaScript
340 lines
11 KiB
JavaScript
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
|
|
/* exported loadRemoteSearchProviders */
|
|
|
|
const { GdkPixbuf, Gio, GLib, Shell, St } = imports.gi;
|
|
|
|
const FileUtils = imports.misc.fileUtils;
|
|
|
|
const KEY_FILE_GROUP = 'Shell Search Provider';
|
|
|
|
const SearchProviderIface = `
|
|
<node>
|
|
<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>
|
|
</node>`;
|
|
|
|
const SearchProvider2Iface = `
|
|
<node>
|
|
<interface name="org.gnome.Shell.SearchProvider2">
|
|
<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" />
|
|
<arg type="as" direction="in" />
|
|
<arg type="u" direction="in" />
|
|
</method>
|
|
<method name="LaunchSearch">
|
|
<arg type="as" direction="in" />
|
|
<arg type="u" direction="in" />
|
|
</method>
|
|
</interface>
|
|
</node>`;
|
|
|
|
var SearchProviderProxyInfo = Gio.DBusInterfaceInfo.new_for_xml(SearchProviderIface);
|
|
var SearchProvider2ProxyInfo = Gio.DBusInterfaceInfo.new_for_xml(SearchProvider2Iface);
|
|
|
|
function loadRemoteSearchProviders(searchSettings, callback) {
|
|
let objectPaths = {};
|
|
let loadedProviders = [];
|
|
|
|
function loadRemoteSearchProvider(file) {
|
|
let keyfile = new GLib.KeyFile();
|
|
let path = file.get_path();
|
|
|
|
try {
|
|
keyfile.load_from_file(path, 0);
|
|
} catch (e) {
|
|
return;
|
|
}
|
|
|
|
if (!keyfile.has_group(KEY_FILE_GROUP))
|
|
return;
|
|
|
|
let remoteProvider;
|
|
try {
|
|
let group = KEY_FILE_GROUP;
|
|
let busName = keyfile.get_string(group, 'BusName');
|
|
let objectPath = keyfile.get_string(group, 'ObjectPath');
|
|
|
|
if (objectPaths[objectPath])
|
|
return;
|
|
|
|
let appInfo = null;
|
|
try {
|
|
let desktopId = keyfile.get_string(group, 'DesktopId');
|
|
appInfo = Gio.DesktopAppInfo.new(desktopId);
|
|
if (!appInfo.should_show())
|
|
return;
|
|
} catch (e) {
|
|
log(`Ignoring search provider ${path}: missing DesktopId`);
|
|
return;
|
|
}
|
|
|
|
let autoStart = true;
|
|
try {
|
|
autoStart = keyfile.get_boolean(group, 'AutoStart');
|
|
} catch (e) {
|
|
// ignore error
|
|
}
|
|
|
|
let version = '1';
|
|
try {
|
|
version = keyfile.get_string(group, 'Version');
|
|
} catch (e) {
|
|
// ignore error
|
|
}
|
|
|
|
if (version >= 2)
|
|
remoteProvider = new RemoteSearchProvider2(appInfo, busName, objectPath, autoStart);
|
|
else
|
|
remoteProvider = new RemoteSearchProvider(appInfo, busName, objectPath, autoStart);
|
|
|
|
remoteProvider.defaultEnabled = true;
|
|
try {
|
|
remoteProvider.defaultEnabled = !keyfile.get_boolean(group, 'DefaultDisabled');
|
|
} catch (e) {
|
|
// ignore error
|
|
}
|
|
|
|
objectPaths[objectPath] = remoteProvider;
|
|
loadedProviders.push(remoteProvider);
|
|
} catch (e) {
|
|
log(`Failed to add search provider ${path}: ${e}`);
|
|
}
|
|
}
|
|
|
|
if (searchSettings.get_boolean('disable-external')) {
|
|
callback([]);
|
|
return;
|
|
}
|
|
|
|
FileUtils.collectFromDatadirs('search-providers', false, loadRemoteSearchProvider);
|
|
|
|
let sortOrder = searchSettings.get_strv('sort-order');
|
|
|
|
// Special case gnome-control-center to be always active and always first
|
|
sortOrder.unshift('org.gnome.Settings.desktop');
|
|
|
|
const disabled = searchSettings.get_strv('disabled');
|
|
const enabled = searchSettings.get_strv('enabled');
|
|
|
|
loadedProviders = loadedProviders.filter(provider => {
|
|
let appId = provider.appInfo.get_id();
|
|
|
|
if (provider.defaultEnabled)
|
|
return !disabled.includes(appId);
|
|
else
|
|
return enabled.includes(appId);
|
|
});
|
|
|
|
loadedProviders.sort((providerA, providerB) => {
|
|
let idxA, idxB;
|
|
let appIdA, appIdB;
|
|
|
|
appIdA = providerA.appInfo.get_id();
|
|
appIdB = providerB.appInfo.get_id();
|
|
|
|
idxA = sortOrder.indexOf(appIdA);
|
|
idxB = sortOrder.indexOf(appIdB);
|
|
|
|
// if no provider is found in the order, use alphabetical order
|
|
if ((idxA == -1) && (idxB == -1)) {
|
|
let nameA = providerA.appInfo.get_name();
|
|
let nameB = providerB.appInfo.get_name();
|
|
|
|
return GLib.utf8_collate(nameA, nameB);
|
|
}
|
|
|
|
// if providerA isn't found, it's sorted after providerB
|
|
if (idxA == -1)
|
|
return 1;
|
|
|
|
// if providerB isn't found, it's sorted after providerA
|
|
if (idxB == -1)
|
|
return -1;
|
|
|
|
// finally, if both providers are found, return their order in the list
|
|
return idxA - idxB;
|
|
});
|
|
|
|
callback(loadedProviders);
|
|
}
|
|
|
|
var RemoteSearchProvider = class {
|
|
constructor(appInfo, dbusName, dbusPath, autoStart, proxyInfo) {
|
|
if (!proxyInfo)
|
|
proxyInfo = SearchProviderProxyInfo;
|
|
|
|
let gFlags = Gio.DBusProxyFlags.DO_NOT_LOAD_PROPERTIES;
|
|
if (autoStart)
|
|
gFlags |= Gio.DBusProxyFlags.DO_NOT_AUTO_START_AT_CONSTRUCTION;
|
|
else
|
|
gFlags |= Gio.DBusProxyFlags.DO_NOT_AUTO_START;
|
|
|
|
this.proxy = new Gio.DBusProxy({
|
|
g_bus_type: Gio.BusType.SESSION,
|
|
g_name: dbusName,
|
|
g_object_path: dbusPath,
|
|
g_interface_info: proxyInfo,
|
|
g_interface_name: proxyInfo.name,
|
|
gFlags,
|
|
});
|
|
this.proxy.init_async(GLib.PRIORITY_DEFAULT, null);
|
|
|
|
this.appInfo = appInfo;
|
|
this.id = appInfo.get_id();
|
|
this.isRemoteProvider = true;
|
|
this.canLaunchSearch = false;
|
|
}
|
|
|
|
createIcon(size, meta) {
|
|
let gicon = null;
|
|
let icon = null;
|
|
|
|
if (meta['icon']) {
|
|
gicon = Gio.icon_deserialize(meta['icon']);
|
|
} else if (meta['gicon']) {
|
|
gicon = Gio.icon_new_for_string(meta['gicon']);
|
|
} else if (meta['icon-data']) {
|
|
const [
|
|
width, height, rowStride, hasAlpha,
|
|
bitsPerSample, nChannels_, data,
|
|
] = meta['icon-data'];
|
|
gicon = Shell.util_create_pixbuf_from_data(data, GdkPixbuf.Colorspace.RGB, hasAlpha,
|
|
bitsPerSample, width, height, rowStride);
|
|
}
|
|
|
|
if (gicon)
|
|
icon = new St.Icon({ gicon, icon_size: size });
|
|
return icon;
|
|
}
|
|
|
|
filterResults(results, maxNumber) {
|
|
if (results.length <= maxNumber)
|
|
return results;
|
|
|
|
let regularResults = results.filter(r => !r.startsWith('special:'));
|
|
let specialResults = results.filter(r => r.startsWith('special:'));
|
|
|
|
return regularResults.slice(0, maxNumber).concat(specialResults.slice(0, maxNumber));
|
|
}
|
|
|
|
_getResultsFinished(results, error, callback) {
|
|
if (error) {
|
|
if (error.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED))
|
|
return;
|
|
|
|
log(`Received error from D-Bus search provider ${this.id}: ${error}`);
|
|
callback([]);
|
|
return;
|
|
}
|
|
|
|
callback(results[0]);
|
|
}
|
|
|
|
getInitialResultSet(terms, callback, cancellable) {
|
|
this.proxy.GetInitialResultSetRemote(terms,
|
|
(results, error) => {
|
|
this._getResultsFinished(results, error, callback);
|
|
},
|
|
cancellable);
|
|
}
|
|
|
|
getSubsearchResultSet(previousResults, newTerms, callback, cancellable) {
|
|
this.proxy.GetSubsearchResultSetRemote(previousResults, newTerms,
|
|
(results, error) => {
|
|
this._getResultsFinished(results, error, callback);
|
|
},
|
|
cancellable);
|
|
}
|
|
|
|
_getResultMetasFinished(results, error, callback) {
|
|
if (error) {
|
|
if (!error.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED))
|
|
log(`Received error from D-Bus search provider ${this.id} during GetResultMetas: ${error}`);
|
|
callback([]);
|
|
return;
|
|
}
|
|
let metas = results[0];
|
|
let resultMetas = [];
|
|
for (let i = 0; i < metas.length; i++) {
|
|
for (let prop in metas[i]) {
|
|
// we can use the serialized icon variant directly
|
|
if (prop != 'icon')
|
|
metas[i][prop] = metas[i][prop].deep_unpack();
|
|
}
|
|
|
|
resultMetas.push({
|
|
id: metas[i]['id'],
|
|
name: metas[i]['name'],
|
|
description: metas[i]['description'],
|
|
createIcon: size => this.createIcon(size, metas[i]),
|
|
clipboardText: metas[i]['clipboardText'],
|
|
});
|
|
}
|
|
callback(resultMetas);
|
|
}
|
|
|
|
getResultMetas(ids, callback, cancellable) {
|
|
this.proxy.GetResultMetasRemote(ids,
|
|
(results, error) => {
|
|
this._getResultMetasFinished(results, error, callback);
|
|
},
|
|
cancellable);
|
|
}
|
|
|
|
activateResult(id) {
|
|
this.proxy.ActivateResultRemote(id);
|
|
}
|
|
|
|
launchSearch(_terms) {
|
|
// the provider is not compatible with the new version of the interface, launch
|
|
// the app itself but warn so we can catch the error in logs
|
|
log(`Search provider ${this.appInfo.get_id()} does not implement LaunchSearch`);
|
|
this.appInfo.launch([], global.create_app_launch_context(0, -1));
|
|
}
|
|
};
|
|
|
|
var RemoteSearchProvider2 = class extends RemoteSearchProvider {
|
|
constructor(appInfo, dbusName, dbusPath, autoStart) {
|
|
super(appInfo, dbusName, dbusPath, autoStart, SearchProvider2ProxyInfo);
|
|
|
|
this.canLaunchSearch = true;
|
|
}
|
|
|
|
activateResult(id, terms) {
|
|
this.proxy.ActivateResultRemote(id, terms, global.get_current_time());
|
|
}
|
|
|
|
launchSearch(terms) {
|
|
this.proxy.LaunchSearchRemote(terms, global.get_current_time());
|
|
}
|
|
};
|