2011-11-16 18:11:05 -05:00
|
|
|
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
|
|
|
|
|
2023-07-10 05:53:00 -04:00
|
|
|
import GdkPixbuf from 'gi://GdkPixbuf';
|
|
|
|
import Gio from 'gi://Gio';
|
|
|
|
import GLib from 'gi://GLib';
|
|
|
|
import Shell from 'gi://Shell';
|
|
|
|
import St from 'gi://St';
|
2011-11-16 18:11:05 -05:00
|
|
|
|
2023-07-10 05:53:00 -04:00
|
|
|
import * as FileUtils from '../misc/fileUtils.js';
|
2011-11-16 18:11:05 -05:00
|
|
|
|
2011-12-15 08:46:12 -05:00
|
|
|
const KEY_FILE_GROUP = 'Shell Search Provider';
|
|
|
|
|
2018-08-22 20:55:02 -04:00
|
|
|
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>`;
|
2011-11-16 18:11:05 -05:00
|
|
|
|
2018-08-22 20:55:02 -04:00
|
|
|
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>`;
|
2012-12-05 17:20:22 -05:00
|
|
|
|
2023-07-10 05:53:00 -04:00
|
|
|
const SearchProviderProxyInfo = Gio.DBusInterfaceInfo.new_for_xml(SearchProviderIface);
|
|
|
|
const SearchProvider2ProxyInfo = Gio.DBusInterfaceInfo.new_for_xml(SearchProvider2Iface);
|
2011-11-16 18:11:05 -05:00
|
|
|
|
2022-06-23 12:17:57 -04:00
|
|
|
/**
|
|
|
|
* loadRemoteSearchProviders:
|
|
|
|
*
|
|
|
|
* @param {Gio.Settings} searchSettings - search settings
|
|
|
|
* @returns {RemoteSearchProvider[]} - the list of remote providers
|
|
|
|
*/
|
2023-07-10 05:53:00 -04:00
|
|
|
export function loadRemoteSearchProviders(searchSettings) {
|
2013-07-26 18:36:31 -04:00
|
|
|
let objectPaths = {};
|
|
|
|
let loadedProviders = [];
|
2012-06-22 11:44:15 -04:00
|
|
|
|
2013-07-26 18:36:31 -04:00
|
|
|
function loadRemoteSearchProvider(file) {
|
|
|
|
let keyfile = new GLib.KeyFile();
|
|
|
|
let path = file.get_path();
|
2012-11-30 10:36:12 -05:00
|
|
|
|
|
|
|
try {
|
2013-07-26 18:36:31 -04:00
|
|
|
keyfile.load_from_file(path, 0);
|
2019-01-28 20:26:39 -05:00
|
|
|
} catch (e) {
|
2012-11-30 10:36:12 -05:00
|
|
|
return;
|
2011-12-15 08:46:12 -05:00
|
|
|
}
|
2012-11-01 15:55:41 -04:00
|
|
|
|
2013-07-26 18:36:31 -04:00
|
|
|
if (!keyfile.has_group(KEY_FILE_GROUP))
|
|
|
|
return;
|
|
|
|
|
|
|
|
let remoteProvider;
|
2012-12-05 17:20:22 -05:00
|
|
|
try {
|
2013-07-26 18:36:31 -04:00
|
|
|
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);
|
2017-11-09 12:01:58 -05:00
|
|
|
if (!appInfo.should_show())
|
|
|
|
return;
|
2013-07-26 18:36:31 -04:00
|
|
|
} catch (e) {
|
2019-01-29 19:18:24 -05:00
|
|
|
log(`Ignoring search provider ${path}: missing DesktopId`);
|
2013-07-26 18:36:31 -04:00
|
|
|
return;
|
|
|
|
}
|
2012-12-05 17:20:22 -05:00
|
|
|
|
2017-09-19 17:58:29 -04:00
|
|
|
let autoStart = true;
|
|
|
|
try {
|
|
|
|
autoStart = keyfile.get_boolean(group, 'AutoStart');
|
2019-01-28 20:26:39 -05:00
|
|
|
} catch (e) {
|
2017-09-19 17:58:29 -04:00
|
|
|
// ignore error
|
|
|
|
}
|
|
|
|
|
2013-07-26 18:36:31 -04:00
|
|
|
let version = '1';
|
|
|
|
try {
|
|
|
|
version = keyfile.get_string(group, 'Version');
|
|
|
|
} catch (e) {
|
|
|
|
// ignore error
|
|
|
|
}
|
2012-12-05 17:20:22 -05:00
|
|
|
|
2013-07-26 18:36:31 -04:00
|
|
|
if (version >= 2)
|
2017-09-19 17:58:29 -04:00
|
|
|
remoteProvider = new RemoteSearchProvider2(appInfo, busName, objectPath, autoStart);
|
2013-07-26 18:36:31 -04:00
|
|
|
else
|
2017-09-19 17:58:29 -04:00
|
|
|
remoteProvider = new RemoteSearchProvider(appInfo, busName, objectPath, autoStart);
|
2013-07-26 18:36:31 -04:00
|
|
|
|
2014-08-01 07:35:31 -04:00
|
|
|
remoteProvider.defaultEnabled = true;
|
|
|
|
try {
|
|
|
|
remoteProvider.defaultEnabled = !keyfile.get_boolean(group, 'DefaultDisabled');
|
2019-01-28 20:26:39 -05:00
|
|
|
} catch (e) {
|
2014-08-01 07:35:31 -04:00
|
|
|
// ignore error
|
|
|
|
}
|
|
|
|
|
2013-07-26 18:36:31 -04:00
|
|
|
objectPaths[objectPath] = remoteProvider;
|
|
|
|
loadedProviders.push(remoteProvider);
|
2019-01-28 20:26:39 -05:00
|
|
|
} catch (e) {
|
2022-02-07 09:14:06 -05:00
|
|
|
log(`Failed to add search provider ${path}: ${e}`);
|
2013-07-26 18:36:31 -04:00
|
|
|
}
|
2012-11-30 10:36:12 -05:00
|
|
|
}
|
2012-11-01 15:55:41 -04:00
|
|
|
|
2022-06-23 12:17:57 -04:00
|
|
|
if (searchSettings.get_boolean('disable-external'))
|
|
|
|
return [];
|
2013-11-02 18:03:52 -04:00
|
|
|
|
2022-11-29 21:17:20 -05:00
|
|
|
for (const {dir} of FileUtils.collectFromDatadirs('search-providers', false))
|
|
|
|
loadRemoteSearchProvider(dir);
|
2013-07-26 18:36:31 -04:00
|
|
|
|
2012-11-01 15:57:18 -04:00
|
|
|
let sortOrder = searchSettings.get_strv('sort-order');
|
2012-12-28 12:03:13 -05:00
|
|
|
|
2021-04-01 23:39:31 -04:00
|
|
|
const disabled = searchSettings.get_strv('disabled');
|
|
|
|
const enabled = searchSettings.get_strv('enabled');
|
|
|
|
|
2017-10-30 20:38:18 -04:00
|
|
|
loadedProviders = loadedProviders.filter(provider => {
|
2013-11-02 18:03:52 -04:00
|
|
|
let appId = provider.appInfo.get_id();
|
2014-08-01 07:35:31 -04:00
|
|
|
|
2021-04-01 23:39:31 -04:00
|
|
|
if (provider.defaultEnabled)
|
2018-07-14 16:56:22 -04:00
|
|
|
return !disabled.includes(appId);
|
2021-04-01 23:39:31 -04:00
|
|
|
else
|
2018-07-14 16:56:22 -04:00
|
|
|
return enabled.includes(appId);
|
2013-11-02 18:03:52 -04:00
|
|
|
});
|
|
|
|
|
2017-10-30 20:38:18 -04:00
|
|
|
loadedProviders.sort((providerA, providerB) => {
|
2013-07-26 18:36:31 -04:00
|
|
|
let idxA, idxB;
|
|
|
|
let appIdA, appIdB;
|
2012-11-01 15:57:18 -04:00
|
|
|
|
2013-07-26 18:36:31 -04:00
|
|
|
appIdA = providerA.appInfo.get_id();
|
|
|
|
appIdB = providerB.appInfo.get_id();
|
2012-11-01 15:57:18 -04:00
|
|
|
|
2013-07-26 18:36:31 -04:00
|
|
|
idxA = sortOrder.indexOf(appIdA);
|
|
|
|
idxB = sortOrder.indexOf(appIdB);
|
2012-11-01 15:57:18 -04:00
|
|
|
|
2013-07-26 18:36:31 -04:00
|
|
|
// if no provider is found in the order, use alphabetical order
|
2023-08-06 20:51:19 -04:00
|
|
|
if ((idxA === -1) && (idxB === -1)) {
|
2013-07-26 18:36:31 -04:00
|
|
|
let nameA = providerA.appInfo.get_name();
|
|
|
|
let nameB = providerB.appInfo.get_name();
|
2013-01-28 12:04:59 -05:00
|
|
|
|
2013-07-26 18:36:31 -04:00
|
|
|
return GLib.utf8_collate(nameA, nameB);
|
|
|
|
}
|
2012-11-01 15:57:18 -04:00
|
|
|
|
2013-07-26 18:36:31 -04:00
|
|
|
// if providerA isn't found, it's sorted after providerB
|
2023-08-06 20:51:19 -04:00
|
|
|
if (idxA === -1)
|
2013-07-26 18:36:31 -04:00
|
|
|
return 1;
|
2012-11-01 15:57:18 -04:00
|
|
|
|
2013-07-26 18:36:31 -04:00
|
|
|
// if providerB isn't found, it's sorted after providerA
|
2023-08-06 20:51:19 -04:00
|
|
|
if (idxB === -1)
|
2013-07-26 18:36:31 -04:00
|
|
|
return -1;
|
2012-11-01 15:57:18 -04:00
|
|
|
|
2013-07-26 18:36:31 -04:00
|
|
|
// finally, if both providers are found, return their order in the list
|
2019-08-19 15:38:51 -04:00
|
|
|
return idxA - idxB;
|
2013-07-26 18:36:31 -04:00
|
|
|
});
|
2012-11-01 15:57:18 -04:00
|
|
|
|
2022-06-23 12:17:57 -04:00
|
|
|
return loadedProviders;
|
2012-11-01 15:55:41 -04:00
|
|
|
}
|
|
|
|
|
2023-07-10 05:53:00 -04:00
|
|
|
class RemoteSearchProvider {
|
2017-10-30 21:19:44 -04:00
|
|
|
constructor(appInfo, dbusName, dbusPath, autoStart, proxyInfo) {
|
2013-09-26 07:22:22 -04:00
|
|
|
if (!proxyInfo)
|
|
|
|
proxyInfo = SearchProviderProxyInfo;
|
2012-12-05 17:20:22 -05:00
|
|
|
|
2019-01-31 08:43:52 -05:00
|
|
|
let gFlags = Gio.DBusProxyFlags.DO_NOT_LOAD_PROPERTIES;
|
2017-09-19 17:58:29 -04:00
|
|
|
if (autoStart)
|
2019-01-31 08:43:52 -05:00
|
|
|
gFlags |= Gio.DBusProxyFlags.DO_NOT_AUTO_START_AT_CONSTRUCTION;
|
2017-08-24 07:06:45 -04:00
|
|
|
else
|
2019-01-31 08:43:52 -05:00
|
|
|
gFlags |= Gio.DBusProxyFlags.DO_NOT_AUTO_START;
|
2017-08-24 07:06:45 -04:00
|
|
|
|
2020-03-29 17:51:13 -04:00
|
|
|
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,
|
|
|
|
});
|
2019-12-19 14:50:37 -05:00
|
|
|
this.proxy.init_async(GLib.PRIORITY_DEFAULT, null);
|
2011-11-16 18:11:05 -05:00
|
|
|
|
2012-12-06 14:10:44 -05:00
|
|
|
this.appInfo = appInfo;
|
|
|
|
this.id = appInfo.get_id();
|
|
|
|
this.isRemoteProvider = true;
|
2017-06-12 22:24:12 -04:00
|
|
|
this.canLaunchSearch = false;
|
2017-10-30 21:19:44 -04:00
|
|
|
}
|
2011-11-16 18:11:05 -05:00
|
|
|
|
2017-10-30 20:03:21 -04:00
|
|
|
createIcon(size, meta) {
|
2013-12-06 06:56:43 -05:00
|
|
|
let gicon = null;
|
|
|
|
let icon = null;
|
|
|
|
|
2013-04-24 12:05:22 -04:00
|
|
|
if (meta['icon']) {
|
|
|
|
gicon = Gio.icon_deserialize(meta['icon']);
|
|
|
|
} else if (meta['gicon']) {
|
2013-01-02 09:31:50 -05:00
|
|
|
gicon = Gio.icon_new_for_string(meta['gicon']);
|
2011-11-16 18:11:05 -05:00
|
|
|
} else if (meta['icon-data']) {
|
2020-03-27 09:18:34 -04:00
|
|
|
const [
|
|
|
|
width, height, rowStride, hasAlpha,
|
|
|
|
bitsPerSample, nChannels_, data,
|
|
|
|
] = meta['icon-data'];
|
2023-08-06 19:45:22 -04:00
|
|
|
gicon = Shell.util_create_pixbuf_from_data(data,
|
|
|
|
GdkPixbuf.Colorspace.RGB,
|
|
|
|
hasAlpha,
|
|
|
|
bitsPerSample,
|
|
|
|
width,
|
|
|
|
height,
|
|
|
|
rowStride);
|
2011-11-16 18:11:05 -05:00
|
|
|
}
|
|
|
|
|
2013-12-06 06:56:43 -05:00
|
|
|
if (gicon)
|
2023-08-06 18:40:20 -04:00
|
|
|
icon = new St.Icon({gicon, icon_size: size});
|
2013-12-06 06:56:43 -05:00
|
|
|
return icon;
|
2017-10-30 21:19:44 -04:00
|
|
|
}
|
2011-11-16 18:11:05 -05:00
|
|
|
|
2017-10-30 20:03:21 -04:00
|
|
|
filterResults(results, maxNumber) {
|
2013-05-29 16:48:30 -04:00
|
|
|
if (results.length <= maxNumber)
|
|
|
|
return results;
|
|
|
|
|
2017-10-30 20:38:18 -04:00
|
|
|
let regularResults = results.filter(r => !r.startsWith('special:'));
|
|
|
|
let specialResults = results.filter(r => r.startsWith('special:'));
|
2013-05-29 16:48:30 -04:00
|
|
|
|
|
|
|
return regularResults.slice(0, maxNumber).concat(specialResults.slice(0, maxNumber));
|
2017-10-30 21:19:44 -04:00
|
|
|
}
|
2013-05-29 16:48:30 -04:00
|
|
|
|
2022-06-23 12:13:36 -04:00
|
|
|
async getInitialResultSet(terms, cancellable) {
|
|
|
|
try {
|
|
|
|
const [results] = await this.proxy.GetInitialResultSetAsync(terms, cancellable);
|
|
|
|
return results;
|
|
|
|
} catch (error) {
|
|
|
|
if (!error.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED))
|
|
|
|
log(`Received error from D-Bus search provider ${this.id}: ${error}`);
|
|
|
|
return [];
|
2013-09-26 07:22:22 -04:00
|
|
|
}
|
2017-10-30 21:19:44 -04:00
|
|
|
}
|
2011-11-16 18:11:05 -05:00
|
|
|
|
2022-06-23 12:13:36 -04:00
|
|
|
async getSubsearchResultSet(previousResults, newTerms, cancellable) {
|
|
|
|
try {
|
|
|
|
const [results] = await this.proxy.GetSubsearchResultSetAsync(previousResults, newTerms, cancellable);
|
|
|
|
return results;
|
|
|
|
} catch (error) {
|
|
|
|
if (!error.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED))
|
|
|
|
log(`Received error from D-Bus search provider ${this.id}: ${error}`);
|
|
|
|
return [];
|
|
|
|
}
|
2017-10-30 21:19:44 -04:00
|
|
|
}
|
2011-11-16 18:11:05 -05:00
|
|
|
|
2022-06-23 12:13:36 -04:00
|
|
|
async getResultMetas(ids, cancellable) {
|
|
|
|
let metas;
|
|
|
|
try {
|
|
|
|
[metas] = await this.proxy.GetResultMetasAsync(ids, cancellable);
|
|
|
|
} catch (error) {
|
2013-09-26 07:22:22 -04:00
|
|
|
if (!error.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED))
|
2022-02-07 09:14:06 -05:00
|
|
|
log(`Received error from D-Bus search provider ${this.id} during GetResultMetas: ${error}`);
|
2022-06-23 12:13:36 -04:00
|
|
|
return [];
|
2011-11-16 18:11:05 -05:00
|
|
|
}
|
2022-06-23 12:13:36 -04:00
|
|
|
|
2011-11-16 18:11:05 -05:00
|
|
|
let resultMetas = [];
|
|
|
|
for (let i = 0; i < metas.length; i++) {
|
2013-04-24 12:05:22 -04:00
|
|
|
for (let prop in metas[i]) {
|
|
|
|
// we can use the serialized icon variant directly
|
2022-06-23 12:13:36 -04:00
|
|
|
if (prop !== 'icon')
|
2022-08-10 05:56:14 -04:00
|
|
|
metas[i][prop] = metas[i][prop].deepUnpack();
|
2013-04-24 12:05:22 -04:00
|
|
|
}
|
|
|
|
|
2020-03-29 17:51:13 -04:00
|
|
|
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'],
|
|
|
|
});
|
2011-11-16 18:11:05 -05:00
|
|
|
}
|
2022-06-23 12:13:36 -04:00
|
|
|
return resultMetas;
|
2017-10-30 21:19:44 -04:00
|
|
|
}
|
2011-11-16 18:11:05 -05:00
|
|
|
|
2017-10-30 20:03:21 -04:00
|
|
|
activateResult(id) {
|
2022-06-23 08:53:29 -04:00
|
|
|
this.proxy.ActivateResultAsync(id).catch(logError);
|
2017-10-30 21:19:44 -04:00
|
|
|
}
|
2012-11-26 16:55:25 -05:00
|
|
|
|
2019-01-31 09:08:10 -05:00
|
|
|
launchSearch(_terms) {
|
2012-11-26 16:55:25 -05:00
|
|
|
// 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
|
2019-01-29 19:18:24 -05:00
|
|
|
log(`Search provider ${this.appInfo.get_id()} does not implement LaunchSearch`);
|
2017-07-14 21:42:06 -04:00
|
|
|
this.appInfo.launch([], global.create_app_launch_context(0, -1));
|
2011-11-16 18:11:05 -05:00
|
|
|
}
|
2023-07-10 05:53:00 -04:00
|
|
|
}
|
2011-11-16 18:11:05 -05:00
|
|
|
|
2023-07-10 05:53:00 -04:00
|
|
|
class RemoteSearchProvider2 extends RemoteSearchProvider {
|
2017-10-30 21:19:44 -04:00
|
|
|
constructor(appInfo, dbusName, dbusPath, autoStart) {
|
|
|
|
super(appInfo, dbusName, dbusPath, autoStart, SearchProvider2ProxyInfo);
|
2012-11-26 16:55:25 -05:00
|
|
|
|
|
|
|
this.canLaunchSearch = true;
|
2017-10-30 21:19:44 -04:00
|
|
|
}
|
2011-11-16 18:11:05 -05:00
|
|
|
|
2017-10-30 20:03:21 -04:00
|
|
|
activateResult(id, terms) {
|
2022-06-23 08:53:29 -04:00
|
|
|
this.proxy.ActivateResultAsync(
|
|
|
|
id, terms, global.get_current_time()).catch(logError);
|
2017-10-30 21:19:44 -04:00
|
|
|
}
|
2012-11-26 16:55:25 -05:00
|
|
|
|
2017-10-30 20:03:21 -04:00
|
|
|
launchSearch(terms) {
|
2022-06-23 08:53:29 -04:00
|
|
|
this.proxy.LaunchSearchAsync(
|
|
|
|
terms, global.get_current_time()).catch(logError);
|
2012-12-05 17:20:22 -05:00
|
|
|
}
|
2023-07-10 05:53:00 -04:00
|
|
|
}
|