Blame js/ui/remoteSearch.js

Packit Service ed5168
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
Packit Service ed5168
Packit Service ed5168
const { GdkPixbuf, Gio, GLib, Shell, St } = imports.gi;
Packit Service ed5168
Packit Service ed5168
const FileUtils = imports.misc.fileUtils;
Packit Service ed5168
Packit Service ed5168
const KEY_FILE_GROUP = 'Shell Search Provider';
Packit Service ed5168
Packit Service ed5168
const SearchProviderIface = `
Packit Service ed5168
<node>
Packit Service ed5168
<interface name="org.gnome.Shell.SearchProvider">
Packit Service ed5168
<method name="GetInitialResultSet">
Packit Service ed5168
    <arg type="as" direction="in" />
Packit Service ed5168
    <arg type="as" direction="out" />
Packit Service ed5168
</method>
Packit Service ed5168
<method name="GetSubsearchResultSet">
Packit Service ed5168
    <arg type="as" direction="in" />
Packit Service ed5168
    <arg type="as" direction="in" />
Packit Service ed5168
    <arg type="as" direction="out" />
Packit Service ed5168
</method>
Packit Service ed5168
<method name="GetResultMetas">
Packit Service ed5168
    <arg type="as" direction="in" />
Packit Service ed5168
    <arg type="aa{sv}" direction="out" />
Packit Service ed5168
</method>
Packit Service ed5168
<method name="ActivateResult">
Packit Service ed5168
    <arg type="s" direction="in" />
Packit Service ed5168
</method>
Packit Service ed5168
</interface>
Packit Service ed5168
</node>`;
Packit Service ed5168
Packit Service ed5168
const SearchProvider2Iface = `
Packit Service ed5168
<node>
Packit Service ed5168
<interface name="org.gnome.Shell.SearchProvider2">
Packit Service ed5168
<method name="GetInitialResultSet">
Packit Service ed5168
    <arg type="as" direction="in" />
Packit Service ed5168
    <arg type="as" direction="out" />
Packit Service ed5168
</method>
Packit Service ed5168
<method name="GetSubsearchResultSet">
Packit Service ed5168
    <arg type="as" direction="in" />
Packit Service ed5168
    <arg type="as" direction="in" />
Packit Service ed5168
    <arg type="as" direction="out" />
Packit Service ed5168
</method>
Packit Service ed5168
<method name="GetResultMetas">
Packit Service ed5168
    <arg type="as" direction="in" />
Packit Service ed5168
    <arg type="aa{sv}" direction="out" />
Packit Service ed5168
</method>
Packit Service ed5168
<method name="ActivateResult">
Packit Service ed5168
    <arg type="s" direction="in" />
Packit Service ed5168
    <arg type="as" direction="in" />
Packit Service ed5168
    <arg type="u" direction="in" />
Packit Service ed5168
</method>
Packit Service ed5168
<method name="LaunchSearch">
Packit Service ed5168
    <arg type="as" direction="in" />
Packit Service ed5168
    <arg type="u" direction="in" />
Packit Service ed5168
</method>
Packit Service ed5168
</interface>
Packit Service ed5168
</node>`;
Packit Service ed5168
Packit Service ed5168
var SearchProviderProxyInfo = Gio.DBusInterfaceInfo.new_for_xml(SearchProviderIface);
Packit Service ed5168
var SearchProvider2ProxyInfo = Gio.DBusInterfaceInfo.new_for_xml(SearchProvider2Iface);
Packit Service ed5168
Packit Service ed5168
function loadRemoteSearchProviders(searchSettings, callback) {
Packit Service ed5168
    let objectPaths = {};
Packit Service ed5168
    let loadedProviders = [];
Packit Service ed5168
Packit Service ed5168
    function loadRemoteSearchProvider(file) {
Packit Service ed5168
        let keyfile = new GLib.KeyFile();
Packit Service ed5168
        let path = file.get_path();
Packit Service ed5168
Packit Service ed5168
        try {
Packit Service ed5168
            keyfile.load_from_file(path, 0);
Packit Service ed5168
        } catch(e) {
Packit Service ed5168
            return;
Packit Service ed5168
        }
Packit Service ed5168
Packit Service ed5168
        if (!keyfile.has_group(KEY_FILE_GROUP))
Packit Service ed5168
            return;
Packit Service ed5168
Packit Service ed5168
        let remoteProvider;
Packit Service ed5168
        try {
Packit Service ed5168
            let group = KEY_FILE_GROUP;
Packit Service ed5168
            let busName = keyfile.get_string(group, 'BusName');
Packit Service ed5168
            let objectPath = keyfile.get_string(group, 'ObjectPath');
Packit Service ed5168
Packit Service ed5168
            if (objectPaths[objectPath])
Packit Service ed5168
                return;
Packit Service ed5168
Packit Service ed5168
            let appInfo = null;
Packit Service ed5168
            try {
Packit Service ed5168
                let desktopId = keyfile.get_string(group, 'DesktopId');
Packit Service ed5168
                appInfo = Gio.DesktopAppInfo.new(desktopId);
Packit Service ed5168
            } catch (e) {
Packit Service ed5168
                log('Ignoring search provider ' + path + ': missing DesktopId');
Packit Service ed5168
                return;
Packit Service ed5168
            }
Packit Service ed5168
Packit Service ed5168
            let autoStart = true;
Packit Service ed5168
            try {
Packit Service ed5168
                autoStart = keyfile.get_boolean(group, 'AutoStart');
Packit Service ed5168
            } catch(e) {
Packit Service ed5168
                // ignore error
Packit Service ed5168
            }
Packit Service ed5168
Packit Service ed5168
            let version = '1';
Packit Service ed5168
            try {
Packit Service ed5168
                version = keyfile.get_string(group, 'Version');
Packit Service ed5168
            } catch (e) {
Packit Service ed5168
                // ignore error
Packit Service ed5168
            }
Packit Service ed5168
Packit Service ed5168
            if (version >= 2)
Packit Service ed5168
                remoteProvider = new RemoteSearchProvider2(appInfo, busName, objectPath, autoStart);
Packit Service ed5168
            else
Packit Service ed5168
                remoteProvider = new RemoteSearchProvider(appInfo, busName, objectPath, autoStart);
Packit Service ed5168
Packit Service ed5168
            remoteProvider.defaultEnabled = true;
Packit Service ed5168
            try {
Packit Service ed5168
                remoteProvider.defaultEnabled = !keyfile.get_boolean(group, 'DefaultDisabled');
Packit Service ed5168
            } catch(e) {
Packit Service ed5168
                // ignore error
Packit Service ed5168
            }
Packit Service ed5168
Packit Service ed5168
            objectPaths[objectPath] = remoteProvider;
Packit Service ed5168
            loadedProviders.push(remoteProvider);
Packit Service ed5168
        } catch(e) {
Packit Service ed5168
            log('Failed to add search provider %s: %s'.format(path, e.toString()));
Packit Service ed5168
        }
Packit Service ed5168
    }
Packit Service ed5168
Packit Service ed5168
    if (searchSettings.get_boolean('disable-external')) {
Packit Service ed5168
        callback([]);
Packit Service ed5168
        return;
Packit Service ed5168
    }
Packit Service ed5168
Packit Service ed5168
    FileUtils.collectFromDatadirs('search-providers', false, loadRemoteSearchProvider);
Packit Service ed5168
Packit Service ed5168
    let sortOrder = searchSettings.get_strv('sort-order');
Packit Service ed5168
Packit Service ed5168
    // Special case gnome-control-center to be always active and always first
Packit Service ed5168
    sortOrder.unshift('gnome-control-center.desktop');
Packit Service ed5168
Packit Service ed5168
    loadedProviders = loadedProviders.filter(provider => {
Packit Service ed5168
        let appId = provider.appInfo.get_id();
Packit Service ed5168
Packit Service ed5168
        if (provider.defaultEnabled) {
Packit Service ed5168
            let disabled = searchSettings.get_strv('disabled');
Packit Service ed5168
            return disabled.indexOf(appId) == -1;
Packit Service ed5168
        } else {
Packit Service ed5168
            let enabled = searchSettings.get_strv('enabled');
Packit Service ed5168
            return enabled.indexOf(appId) != -1;
Packit Service ed5168
        }
Packit Service ed5168
    });
Packit Service ed5168
Packit Service ed5168
    loadedProviders.sort((providerA, providerB) => {
Packit Service ed5168
        let idxA, idxB;
Packit Service ed5168
        let appIdA, appIdB;
Packit Service ed5168
Packit Service ed5168
        appIdA = providerA.appInfo.get_id();
Packit Service ed5168
        appIdB = providerB.appInfo.get_id();
Packit Service ed5168
Packit Service ed5168
        idxA = sortOrder.indexOf(appIdA);
Packit Service ed5168
        idxB = sortOrder.indexOf(appIdB);
Packit Service ed5168
Packit Service ed5168
        // if no provider is found in the order, use alphabetical order
Packit Service ed5168
        if ((idxA == -1) && (idxB == -1)) {
Packit Service ed5168
            let nameA = providerA.appInfo.get_name();
Packit Service ed5168
            let nameB = providerB.appInfo.get_name();
Packit Service ed5168
Packit Service ed5168
            return GLib.utf8_collate(nameA, nameB);
Packit Service ed5168
        }
Packit Service ed5168
Packit Service ed5168
        // if providerA isn't found, it's sorted after providerB
Packit Service ed5168
        if (idxA == -1)
Packit Service ed5168
            return 1;
Packit Service ed5168
Packit Service ed5168
        // if providerB isn't found, it's sorted after providerA
Packit Service ed5168
        if (idxB == -1)
Packit Service ed5168
            return -1;
Packit Service ed5168
Packit Service ed5168
        // finally, if both providers are found, return their order in the list
Packit Service ed5168
        return (idxA - idxB);
Packit Service ed5168
    });
Packit Service ed5168
Packit Service ed5168
    callback(loadedProviders);
Packit Service ed5168
}
Packit Service ed5168
Packit Service ed5168
var RemoteSearchProvider = class {
Packit Service ed5168
    constructor(appInfo, dbusName, dbusPath, autoStart, proxyInfo) {
Packit Service ed5168
        if (!proxyInfo)
Packit Service ed5168
            proxyInfo = SearchProviderProxyInfo;
Packit Service ed5168
Packit Service ed5168
        let g_flags = Gio.DBusProxyFlags.DO_NOT_LOAD_PROPERTIES;
Packit Service ed5168
        if (autoStart)
Packit Service ed5168
            g_flags |= Gio.DBusProxyFlags.DO_NOT_AUTO_START_AT_CONSTRUCTION;
Packit Service ed5168
        else
Packit Service ed5168
            g_flags |= Gio.DBusProxyFlags.DO_NOT_AUTO_START;
Packit Service ed5168
Packit Service ed5168
        this.proxy = new Gio.DBusProxy({ g_bus_type: Gio.BusType.SESSION,
Packit Service ed5168
                                         g_name: dbusName,
Packit Service ed5168
                                         g_object_path: dbusPath,
Packit Service ed5168
                                         g_interface_info: proxyInfo,
Packit Service ed5168
                                         g_interface_name: proxyInfo.name,
Packit Service ed5168
                                         g_flags });
Packit Service ed5168
        this.proxy.init_async(GLib.PRIORITY_DEFAULT, null, null);
Packit Service ed5168
Packit Service ed5168
        this.appInfo = appInfo;
Packit Service ed5168
        this.id = appInfo.get_id();
Packit Service ed5168
        this.isRemoteProvider = true;
Packit Service ed5168
        this.canLaunchSearch = false;
Packit Service ed5168
    }
Packit Service ed5168
Packit Service ed5168
    createIcon(size, meta) {
Packit Service ed5168
        let gicon = null;
Packit Service ed5168
        let icon = null;
Packit Service ed5168
Packit Service ed5168
        if (meta['icon']) {
Packit Service ed5168
            gicon = Gio.icon_deserialize(meta['icon']);
Packit Service ed5168
        } else if (meta['gicon']) {
Packit Service ed5168
            gicon = Gio.icon_new_for_string(meta['gicon']);
Packit Service ed5168
        } else if (meta['icon-data']) {
Packit Service ed5168
            let [width, height, rowStride, hasAlpha,
Packit Service ed5168
                 bitsPerSample, nChannels, data] = meta['icon-data'];
Packit Service ed5168
            gicon = Shell.util_create_pixbuf_from_data(data, GdkPixbuf.Colorspace.RGB, hasAlpha,
Packit Service ed5168
                                                       bitsPerSample, width, height, rowStride);
Packit Service ed5168
        }
Packit Service ed5168
Packit Service ed5168
        if (gicon)
Packit Service ed5168
            icon = new St.Icon({ gicon: gicon,
Packit Service ed5168
                                 icon_size: size });
Packit Service ed5168
        return icon;
Packit Service ed5168
    }
Packit Service ed5168
Packit Service ed5168
    filterResults(results, maxNumber) {
Packit Service ed5168
        if (results.length <= maxNumber)
Packit Service ed5168
            return results;
Packit Service ed5168
Packit Service ed5168
        let regularResults = results.filter(r => !r.startsWith('special:'));
Packit Service ed5168
        let specialResults = results.filter(r => r.startsWith('special:'));
Packit Service ed5168
Packit Service ed5168
        return regularResults.slice(0, maxNumber).concat(specialResults.slice(0, maxNumber));
Packit Service ed5168
    }
Packit Service ed5168
Packit Service ed5168
    _getResultsFinished(results, error, callback) {
Packit Service ed5168
        if (error) {
Packit Service ed5168
            if (error.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED))
Packit Service ed5168
                return;
Packit Service ed5168
Packit Service ed5168
            log('Received error from DBus search provider %s: %s'.format(this.id, String(error)));
Packit Service ed5168
            callback([]);
Packit Service ed5168
            return;
Packit Service ed5168
        }
Packit Service ed5168
Packit Service ed5168
        callback(results[0]);
Packit Service ed5168
    }
Packit Service ed5168
Packit Service ed5168
    getInitialResultSet(terms, callback, cancellable) {
Packit Service ed5168
        this.proxy.GetInitialResultSetRemote(terms,
Packit Service ed5168
                                             (results, error) => {
Packit Service ed5168
                                                 this._getResultsFinished(results, error, callback);
Packit Service ed5168
                                             },
Packit Service ed5168
                                             cancellable);
Packit Service ed5168
    }
Packit Service ed5168
Packit Service ed5168
    getSubsearchResultSet(previousResults, newTerms, callback, cancellable) {
Packit Service ed5168
        this.proxy.GetSubsearchResultSetRemote(previousResults, newTerms,
Packit Service ed5168
                                               (results, error) => {
Packit Service ed5168
                                                   this._getResultsFinished(results, error, callback);
Packit Service ed5168
                                               },
Packit Service ed5168
                                               cancellable);
Packit Service ed5168
    }
Packit Service ed5168
Packit Service ed5168
    _getResultMetasFinished(results, error, callback) {
Packit Service ed5168
        if (error) {
Packit Service ed5168
            if (!error.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED))
Packit Service ed5168
                log('Received error from DBus search provider %s during GetResultMetas: %s'.format(this.id, String(error)));
Packit Service ed5168
            callback([]);
Packit Service ed5168
            return;
Packit Service ed5168
        }
Packit Service ed5168
        let metas = results[0];
Packit Service ed5168
        let resultMetas = [];
Packit Service ed5168
        for (let i = 0; i < metas.length; i++) {
Packit Service ed5168
            for (let prop in metas[i]) {
Packit Service ed5168
                // we can use the serialized icon variant directly
Packit Service ed5168
                if (prop != 'icon')
Packit Service ed5168
                    metas[i][prop] = metas[i][prop].deep_unpack();
Packit Service ed5168
            }
Packit Service ed5168
Packit Service ed5168
            resultMetas.push({ id: metas[i]['id'],
Packit Service ed5168
                               name: metas[i]['name'],
Packit Service ed5168
                               description: metas[i]['description'],
Packit Service ed5168
                               createIcon: size => {
Packit Service ed5168
                                   return this.createIcon(size, metas[i]);
Packit Service ed5168
                               },
Packit Service ed5168
                               clipboardText: metas[i]['clipboardText'] });
Packit Service ed5168
        }
Packit Service ed5168
        callback(resultMetas);
Packit Service ed5168
    }
Packit Service ed5168
Packit Service ed5168
    getResultMetas(ids, callback, cancellable) {
Packit Service ed5168
        this.proxy.GetResultMetasRemote(ids,
Packit Service ed5168
                                        (results, error) => {
Packit Service ed5168
                                            this._getResultMetasFinished(results, error, callback);
Packit Service ed5168
                                        },
Packit Service ed5168
                                        cancellable);
Packit Service ed5168
    }
Packit Service ed5168
Packit Service ed5168
    activateResult(id) {
Packit Service ed5168
        this.proxy.ActivateResultRemote(id);
Packit Service ed5168
    }
Packit Service ed5168
Packit Service ed5168
    launchSearch(terms) {
Packit Service ed5168
        // the provider is not compatible with the new version of the interface, launch
Packit Service ed5168
        // the app itself but warn so we can catch the error in logs
Packit Service ed5168
        log('Search provider ' + this.appInfo.get_id() + ' does not implement LaunchSearch');
Packit Service ed5168
        this.appInfo.launch([], global.create_app_launch_context(0, -1));
Packit Service ed5168
    }
Packit Service ed5168
};
Packit Service ed5168
Packit Service ed5168
var RemoteSearchProvider2 = class extends RemoteSearchProvider {
Packit Service ed5168
    constructor(appInfo, dbusName, dbusPath, autoStart) {
Packit Service ed5168
        super(appInfo, dbusName, dbusPath, autoStart, SearchProvider2ProxyInfo);
Packit Service ed5168
Packit Service ed5168
        this.canLaunchSearch = true;
Packit Service ed5168
    }
Packit Service ed5168
Packit Service ed5168
    activateResult(id, terms) {
Packit Service ed5168
        this.proxy.ActivateResultRemote(id, terms, global.get_current_time());
Packit Service ed5168
    }
Packit Service ed5168
Packit Service ed5168
    launchSearch(terms) {
Packit Service ed5168
        this.proxy.LaunchSearchRemote(terms, global.get_current_time());
Packit Service ed5168
    }
Packit Service ed5168
};