34
const SearchProvider2Iface = <interface name="org.gnome.Shell.SearchProvider2">
35
<method name="GetInitialResultSet">
36
<arg type="as" direction="in" />
37
<arg type="as" direction="out" />
39
<method name="GetSubsearchResultSet">
40
<arg type="as" direction="in" />
41
<arg type="as" direction="in" />
42
<arg type="as" direction="out" />
44
<method name="GetResultMetas">
45
<arg type="as" direction="in" />
46
<arg type="aa{sv}" direction="out" />
48
<method name="ActivateResult">
49
<arg type="s" direction="in" />
50
<arg type="as" direction="in" />
51
<arg type="u" direction="in" />
53
<method name="LaunchSearch">
54
<arg type="as" direction="in" />
55
<arg type="u" direction="in" />
32
59
var SearchProviderProxy = Gio.DBusProxy.makeProxyWrapper(SearchProviderIface);
60
var SearchProvider2Proxy = Gio.DBusProxy.makeProxyWrapper(SearchProvider2Iface);
35
62
function loadRemoteSearchProviders(addProviderCallback) {
36
let dataDirs = GLib.get_system_data_dirs();
37
let loadedProviders = {};
38
for (let i = 0; i < dataDirs.length; i++) {
39
let path = GLib.build_filenamev([dataDirs[i], 'gnome-shell', 'search-providers']);
40
let dir = Gio.file_new_for_path(path);
41
if (!dir.query_exists(null))
43
loadRemoteSearchProvidersFromDir(dir, loadedProviders, addProviderCallback);
47
function loadRemoteSearchProvidersFromDir(dir, loadedProviders, addProviderCallback) {
48
let dirPath = dir.get_path();
49
FileUtils.listDirAsync(dir, Lang.bind(this, function(files) {
50
for (let i = 0; i < files.length; i++) {
51
let keyfile = new GLib.KeyFile();
52
let path = GLib.build_filenamev([dirPath, files[i].get_name()]);
55
keyfile.load_from_file(path, 0);
60
if (!keyfile.has_group(KEY_FILE_GROUP))
63
let remoteProvider, title;
65
let group = KEY_FILE_GROUP;
66
let busName = keyfile.get_string(group, 'BusName');
67
let objectPath = keyfile.get_string(group, 'ObjectPath');
69
if (loadedProviders[objectPath])
74
let desktopId = keyfile.get_string(group, 'DesktopId');
75
appInfo = Gio.DesktopAppInfo.new(desktopId);
81
icon = appInfo.get_icon();
82
title = appInfo.get_name();
84
let iconName = keyfile.get_string(group, 'Icon');
85
icon = new Gio.ThemedIcon({ name: iconName });
86
title = keyfile.get_locale_string(group, 'Title', null);
89
remoteProvider = new RemoteSearchProvider(title,
93
loadedProviders[objectPath] = remoteProvider;
95
log('Failed to add search provider "%s": %s'.format(title, e.toString()));
99
addProviderCallback(remoteProvider);
63
let data = { loadedProviders: [],
65
addProviderCallback: addProviderCallback };
66
FileUtils.collectFromDatadirsAsync('search-providers',
67
{ loadedCallback: remoteProvidersLoaded,
68
processFile: loadRemoteSearchProvider,
73
function loadRemoteSearchProvider(file, info, data) {
74
let keyfile = new GLib.KeyFile();
75
let path = file.get_path();
78
keyfile.load_from_file(path, 0);
83
if (!keyfile.has_group(KEY_FILE_GROUP))
88
let group = KEY_FILE_GROUP;
89
let busName = keyfile.get_string(group, 'BusName');
90
let objectPath = keyfile.get_string(group, 'ObjectPath');
92
if (data.objectPaths[objectPath])
97
let desktopId = keyfile.get_string(group, 'DesktopId');
98
appInfo = Gio.DesktopAppInfo.new(desktopId);
100
log('Ignoring search provider ' + path + ': missing DesktopId');
106
version = keyfile.get_string(group, 'Version');
112
remoteProvider = new RemoteSearchProvider2(appInfo, busName, objectPath);
114
remoteProvider = new RemoteSearchProvider(appInfo, busName, objectPath);
116
data.objectPaths[objectPath] = remoteProvider;
117
data.loadedProviders.push(remoteProvider);
119
log('Failed to add search provider %s: %s'.format(path, e.toString()));
123
function remoteProvidersLoaded(loadState) {
124
let searchSettings = new Gio.Settings({ schema: Search.SEARCH_PROVIDERS_SCHEMA });
125
let sortOrder = searchSettings.get_strv('sort-order');
127
// Special case gnome-control-center to be always active and always first
128
sortOrder.unshift('gnome-control-center.desktop');
130
loadState.loadedProviders.sort(
131
function(providerA, providerB) {
135
appIdA = providerA.appInfo.get_id();
136
appIdB = providerB.appInfo.get_id();
138
idxA = sortOrder.indexOf(appIdA);
139
idxB = sortOrder.indexOf(appIdB);
141
// if no provider is found in the order, use alphabetical order
142
if ((idxA == -1) && (idxB == -1)) {
143
let nameA = providerA.appInfo.get_name();
144
let nameB = providerB.appInfo.get_name();
146
return GLib.utf8_collate(nameA, nameB);
149
// if providerA isn't found, it's sorted after providerB
153
// if providerB isn't found, it's sorted after providerA
157
// finally, if both providers are found, return their order in the list
158
return (idxA - idxB);
161
loadState.loadedProviders.forEach(
163
loadState.addProviderCallback(provider);
105
167
const RemoteSearchProvider = new Lang.Class({
106
168
Name: 'RemoteSearchProvider',
107
Extends: Search.SearchProvider,
109
_init: function(title, icon, dbusName, dbusPath) {
110
this._proxy = new SearchProviderProxy(Gio.DBus.session,
113
this.parent(title.toUpperCase());
170
_init: function(appInfo, dbusName, dbusPath, proxyType) {
172
proxyType = SearchProviderProxy;
174
this.proxy = new proxyType(Gio.DBus.session,
175
dbusName, dbusPath, Lang.bind(this, this._onProxyConstructed));
177
this.appInfo = appInfo;
178
this.id = appInfo.get_id();
179
this.isRemoteProvider = true;
114
181
this._cancellable = new Gio.Cancellable();
184
_onProxyConstructed: function(proxy) {
117
188
createIcon: function(size, meta) {
118
190
if (meta['gicon']) {
119
return new St.Icon({ gicon: Gio.icon_new_for_string(meta['gicon']),
191
gicon = Gio.icon_new_for_string(meta['gicon']);
121
192
} else if (meta['icon-data']) {
122
193
let [width, height, rowStride, hasAlpha,
123
194
bitsPerSample, nChannels, data] = meta['icon-data'];
124
let textureCache = St.TextureCache.get_default();
125
return textureCache.load_from_raw(data, hasAlpha,
126
width, height, rowStride, size);
195
gicon = Shell.util_create_pixbuf_from_data(data, GdkPixbuf.Colorspace.RGB, hasAlpha,
196
bitsPerSample, width, height, rowStride);
129
// Ugh, but we want to fall back to something ...
130
return new St.Icon({ icon_name: 'text-x-generic',
199
return new St.Icon({ gicon: gicon,
131
200
icon_size: size });
185
255
this._cancellable.cancel();
186
256
this._cancellable.reset();
188
this._proxy.GetResultMetasRemote(ids,
189
Lang.bind(this, this._getResultMetasFinished, callback),
258
this.proxy.GetResultMetasRemote(ids,
259
Lang.bind(this, this._getResultMetasFinished, callback),
192
log('Error calling GetResultMetas for provider %s: %s'.format(this.title, e.toString()));
262
log('Error calling GetResultMetas for provider %s: %s'.format(this.id, e.toString()));
197
267
activateResult: function(id) {
198
this._proxy.ActivateResultRemote(id);
268
this.proxy.ActivateResultRemote(id);
271
launchSearch: function(terms) {
272
// the provider is not compatible with the new version of the interface, launch
273
// the app itself but warn so we can catch the error in logs
274
log('Search provider ' + this.appInfo.get_id() + ' does not implement LaunchSearch');
275
this.appInfo.launch([], global.create_app_launch_context());
279
const RemoteSearchProvider2 = new Lang.Class({
280
Name: 'RemoteSearchProvider2',
281
Extends: RemoteSearchProvider,
283
_init: function(appInfo, dbusName, dbusPath) {
284
this.parent(appInfo, dbusName, dbusPath, SearchProvider2Proxy);
286
this.canLaunchSearch = true;
289
activateResult: function(id, terms) {
290
this.proxy.ActivateResultRemote(id, terms, global.get_current_time());
293
launchSearch: function(terms) {
294
this.proxy.LaunchSearchRemote(terms, global.get_current_time());