1 | /* |
2 | * Copyright 2010 Ryan Rix <ry@n.rix.si> |
3 | * |
4 | * This program is free software; you can redistribute it and/or modify |
5 | * it under the terms of the GNU Library General Public License as |
6 | * published by the Free Software Foundation; either version 2, or |
7 | * (at your option) any later version. |
8 | * |
9 | * This program is distributed in the hope that it will be useful, |
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
12 | * GNU General Public License for more details |
13 | * |
14 | * You should have received a copy of the GNU Library General Public |
15 | * License along with this program; if not, write to the |
16 | * Free Software Foundation, Inc., |
17 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
18 | */ |
19 | |
20 | #include "pluginloader.h" |
21 | |
22 | #include <kdebug.h> |
23 | #include <kglobal.h> |
24 | #include <kservice.h> |
25 | #include <kservicetypetrader.h> |
26 | #include <kstandarddirs.h> |
27 | #include <kplugininfo.h> |
28 | |
29 | #include "applet.h" |
30 | #include "abstractrunner.h" |
31 | #include "containment.h" |
32 | #include "packagestructure.h" |
33 | #include "popupapplet.h" |
34 | #include "private/applet_p.h" |
35 | #include "private/extenderapplet_p.h" |
36 | #include "private/service_p.h" // for NullService |
37 | #include "private/storage_p.h" |
38 | |
39 | namespace Plasma { |
40 | |
41 | static PluginLoader* s_pluginLoader = 0; |
42 | |
43 | class PluginLoaderPrivate |
44 | { |
45 | |
46 | }; |
47 | |
48 | PluginLoader::PluginLoader() |
49 | : d(0) |
50 | { |
51 | } |
52 | |
53 | PluginLoader::~PluginLoader() |
54 | { |
55 | delete d; |
56 | } |
57 | |
58 | void PluginLoader::setPluginLoader(PluginLoader* loader) |
59 | { |
60 | if (!s_pluginLoader) { |
61 | s_pluginLoader = loader; |
62 | } else { |
63 | kDebug() << "Cannot set pluginLoader, already set!" << s_pluginLoader; |
64 | } |
65 | } |
66 | |
67 | PluginLoader *PluginLoader::pluginLoader() |
68 | { |
69 | if (!s_pluginLoader) { |
70 | // we have been called before any PluginLoader was set, so just use the default |
71 | // implementation. this prevents plugins from nefariously injecting their own |
72 | // plugin loader if the app doesn't |
73 | s_pluginLoader = new PluginLoader; |
74 | } |
75 | |
76 | return s_pluginLoader; |
77 | } |
78 | |
79 | Applet *PluginLoader::loadApplet(const QString &name, uint appletId, const QVariantList &args) |
80 | { |
81 | // the application-specific appletLoader failed to create an applet, here we try with our own logic. |
82 | if (name.isEmpty()) { |
83 | return 0; |
84 | } |
85 | |
86 | Applet *applet = internalLoadApplet(name, appletId, args); |
87 | if (applet) { |
88 | return applet; |
89 | } |
90 | |
91 | const QString constraint = QString("[X-KDE-PluginInfo-Name] == '%1'" ).arg(name); |
92 | KService::List offers = KServiceTypeTrader::self()->query("Plasma/Applet" , constraint); |
93 | |
94 | bool isContainment = false; |
95 | if (offers.isEmpty()) { |
96 | //TODO: what would be -really- cool is offer to try and download the applet |
97 | // from the network at this point |
98 | offers = KServiceTypeTrader::self()->query("Plasma/Containment" , constraint); |
99 | if (offers.count() > 0) { |
100 | isContainment = true; |
101 | } |
102 | } |
103 | |
104 | /* if (offers.count() > 1) { |
105 | kDebug() << "hey! we got more than one! let's blindly take the first one"; |
106 | } */ |
107 | |
108 | AppletPrivate::filterOffers(offers); |
109 | if (offers.isEmpty()) { |
110 | kDebug() << "offers is empty for " << name; |
111 | return 0; |
112 | } |
113 | |
114 | KService::Ptr offer = offers.first(); |
115 | |
116 | if (appletId == 0) { |
117 | appletId = ++AppletPrivate::s_maxAppletId; |
118 | } |
119 | |
120 | QVariantList allArgs; |
121 | allArgs << offer->storageId() << appletId << args; |
122 | |
123 | if (!offer->property("X-Plasma-API" ).toString().isEmpty()) { |
124 | kDebug() << "we have a script using the" |
125 | << offer->property("X-Plasma-API" ).toString() << "API" ; |
126 | if (isContainment) { |
127 | return new Containment(0, allArgs); |
128 | } else { |
129 | if (offer->serviceTypes().contains("Plasma/Containment" )) { |
130 | return new Containment(0, allArgs); |
131 | } else if (offer->serviceTypes().contains("Plasma/PopupApplet" )) { |
132 | return new PopupApplet(0, allArgs); |
133 | } else { |
134 | return new Applet(0, allArgs); |
135 | } |
136 | } |
137 | } |
138 | |
139 | KPluginLoader plugin(*offer); |
140 | |
141 | if (!Plasma::isPluginVersionCompatible(plugin.pluginVersion()) && |
142 | (name != "internal:extender" )) { |
143 | return 0; |
144 | } |
145 | |
146 | |
147 | QString error; |
148 | if (name == "internal:extender" ) { |
149 | applet = new ExtenderApplet(0, allArgs); |
150 | } else { |
151 | applet = offer->createInstance<Plasma::Applet>(0, allArgs, &error); |
152 | } |
153 | |
154 | if (!applet) { |
155 | kWarning() << "Could not load applet" << name << "! reason given:" << error; |
156 | } |
157 | |
158 | return applet; |
159 | } |
160 | |
161 | DataEngine *PluginLoader::loadDataEngine(const QString &name) |
162 | { |
163 | DataEngine *engine = internalLoadDataEngine(name); |
164 | if (engine) { |
165 | return engine; |
166 | } |
167 | |
168 | // load the engine, add it to the engines |
169 | QString constraint = QString("[X-KDE-PluginInfo-Name] == '%1'" ).arg(name); |
170 | KService::List offers = KServiceTypeTrader::self()->query("Plasma/DataEngine" , |
171 | constraint); |
172 | QString error; |
173 | |
174 | if (offers.isEmpty()) { |
175 | kDebug() << "offers are empty for " << name << " with constraint " << constraint; |
176 | } else { |
177 | QVariantList allArgs; |
178 | allArgs << offers.first()->storageId(); |
179 | QString api = offers.first()->property("X-Plasma-API" ).toString(); |
180 | if (api.isEmpty()) { |
181 | if (offers.first()) { |
182 | KPluginLoader plugin(*offers.first()); |
183 | if (Plasma::isPluginVersionCompatible(plugin.pluginVersion())) { |
184 | engine = offers.first()->createInstance<Plasma::DataEngine>(0, allArgs, &error); |
185 | } |
186 | } |
187 | } else { |
188 | engine = new DataEngine(0, offers.first()); |
189 | } |
190 | } |
191 | |
192 | if (!engine) { |
193 | kDebug() << "Couldn't load engine \"" << name << "\". Error given: " << error; |
194 | } |
195 | |
196 | return engine; |
197 | } |
198 | |
199 | AbstractRunner *PluginLoader::loadRunner(const QString &name) |
200 | { |
201 | // FIXME: RunnerManager is all wrapped around runner loading; that should be sorted out |
202 | // and the actual plugin loading added here |
203 | return internalLoadRunner(name); |
204 | } |
205 | |
206 | Service *PluginLoader::loadService(const QString &name, const QVariantList &args, QObject *parent) |
207 | { |
208 | Service *service = internalLoadService(name, args, parent); |
209 | if (service) { |
210 | return service; |
211 | } |
212 | |
213 | //TODO: scripting API support |
214 | if (name.isEmpty()) { |
215 | return new NullService(QString(), parent); |
216 | } else if (name == "org.kde.servicestorage" ) { |
217 | return new Storage(parent); |
218 | } |
219 | |
220 | QString constraint = QString("[X-KDE-PluginInfo-Name] == '%1'" ).arg(name); |
221 | KService::List offers = KServiceTypeTrader::self()->query("Plasma/Service" , constraint); |
222 | |
223 | if (offers.isEmpty()) { |
224 | kDebug() << "offers is empty for " << name; |
225 | return new NullService(name, parent); |
226 | } |
227 | |
228 | KService::Ptr offer = offers.first(); |
229 | QString error; |
230 | |
231 | if (Plasma::isPluginVersionCompatible(KPluginLoader(*offer).pluginVersion())) { |
232 | service = offer->createInstance<Plasma::Service>(parent, args, &error); |
233 | } |
234 | |
235 | if (!service) { |
236 | kDebug() << "Couldn't load Service \"" << name << "\"! reason given: " << error; |
237 | return new NullService(name, parent); |
238 | } |
239 | |
240 | if (service->name().isEmpty()) { |
241 | service->setName(name); |
242 | } |
243 | |
244 | return service; |
245 | } |
246 | |
247 | KPluginInfo::List PluginLoader::listAppletInfo(const QString &category, const QString &parentApp) |
248 | { |
249 | KPluginInfo::List list; |
250 | |
251 | if (parentApp.isEmpty() || parentApp == KGlobal::mainComponent().componentName()) { |
252 | list = internalAppletInfo(category); |
253 | } |
254 | |
255 | QString constraint = AppletPrivate::parentAppConstraint(parentApp); |
256 | |
257 | //note: constraint guaranteed non-empty from here down |
258 | if (category.isEmpty()) { //use all but the excluded categories |
259 | KConfigGroup group(KGlobal::config(), "General" ); |
260 | QStringList excluded = group.readEntry("ExcludeCategories" , QStringList()); |
261 | foreach (const QString &category, excluded) { |
262 | constraint.append(" and [X-KDE-PluginInfo-Category] != '" ).append(category).append("'" ); |
263 | } |
264 | } else { //specific category (this could be an excluded one - is that bad?) |
265 | constraint.append(" and " ).append("[X-KDE-PluginInfo-Category] == '" ).append(category).append("'" ); |
266 | if (category == "Miscellaneous" ) { |
267 | constraint.append(" or (not exist [X-KDE-PluginInfo-Category] or [X-KDE-PluginInfo-Category] == '')" ); |
268 | } |
269 | } |
270 | |
271 | KService::List offers = KServiceTypeTrader::self()->query("Plasma/Applet" , constraint); |
272 | |
273 | //now we have to do some manual filtering because the constraint can't handle everything |
274 | AppletPrivate::filterOffers(offers); |
275 | |
276 | //kDebug() << "Applet::listAppletInfo constraint was '" << constraint |
277 | // << "' which got us " << offers.count() << " matches"; |
278 | return KPluginInfo::fromServices(offers); |
279 | } |
280 | |
281 | KPluginInfo::List PluginLoader::listDataEngineInfo(const QString &parentApp) |
282 | { |
283 | KPluginInfo::List list; |
284 | |
285 | if (parentApp.isEmpty() || parentApp == KGlobal::mainComponent().componentName()) { |
286 | list = internalDataEngineInfo(); |
287 | } |
288 | |
289 | QString constraint; |
290 | if (parentApp.isEmpty()) { |
291 | constraint.append("not exist [X-KDE-ParentApp]" ); |
292 | } else { |
293 | constraint.append("[X-KDE-ParentApp] == '" ).append(parentApp).append("'" ); |
294 | } |
295 | |
296 | KService::List offers = KServiceTypeTrader::self()->query("Plasma/DataEngine" , constraint); |
297 | return list + KPluginInfo::fromServices(offers); |
298 | } |
299 | |
300 | KPluginInfo::List PluginLoader::listRunnerInfo(const QString &parentApp) |
301 | { |
302 | KPluginInfo::List list; |
303 | |
304 | if (parentApp.isEmpty() || parentApp == KGlobal::mainComponent().componentName()) { |
305 | list = internalRunnerInfo(); |
306 | } |
307 | |
308 | QString constraint; |
309 | if (parentApp.isEmpty()) { |
310 | constraint.append("not exist [X-KDE-ParentApp]" ); |
311 | } else { |
312 | constraint.append("[X-KDE-ParentApp] == '" ).append(parentApp).append("'" ); |
313 | } |
314 | |
315 | KService::List offers = KServiceTypeTrader::self()->query("Plasma/Runner" , constraint); |
316 | return list + KPluginInfo::fromServices(offers); |
317 | } |
318 | |
319 | Applet* PluginLoader::internalLoadApplet(const QString &name, uint appletId, const QVariantList &args) |
320 | { |
321 | Q_UNUSED(name) |
322 | Q_UNUSED(appletId) |
323 | Q_UNUSED(args) |
324 | return 0; |
325 | } |
326 | |
327 | DataEngine* PluginLoader::internalLoadDataEngine(const QString &name) |
328 | { |
329 | Q_UNUSED(name) |
330 | return 0; |
331 | } |
332 | |
333 | AbstractRunner* PluginLoader::internalLoadRunner(const QString &name) |
334 | { |
335 | Q_UNUSED(name) |
336 | return 0; |
337 | } |
338 | |
339 | Service* PluginLoader::internalLoadService(const QString &name, const QVariantList &args, QObject *parent) |
340 | { |
341 | Q_UNUSED(name) |
342 | Q_UNUSED(args) |
343 | Q_UNUSED(parent) |
344 | return 0; |
345 | } |
346 | |
347 | KPluginInfo::List PluginLoader::internalAppletInfo(const QString &category) const |
348 | { |
349 | Q_UNUSED(category) |
350 | return KPluginInfo::List(); |
351 | } |
352 | |
353 | KPluginInfo::List PluginLoader::internalDataEngineInfo() const |
354 | { |
355 | return KPluginInfo::List(); |
356 | } |
357 | |
358 | KPluginInfo::List PluginLoader::internalRunnerInfo() const |
359 | { |
360 | return KPluginInfo::List(); |
361 | } |
362 | |
363 | KPluginInfo::List PluginLoader::internalServiceInfo() const |
364 | { |
365 | return KPluginInfo::List(); |
366 | } |
367 | |
368 | static KPluginInfo::List standardInternalInfo(const QString &type, const QString &category = QString()) |
369 | { |
370 | QStringList files = KGlobal::dirs()->findAllResources("appdata" , |
371 | "plasma/internal/" + type + "/*.desktop" , |
372 | KStandardDirs::NoDuplicates); |
373 | |
374 | KPluginInfo::List allInfo = KPluginInfo::fromFiles(files); |
375 | |
376 | if (category.isEmpty() || allInfo.isEmpty()) { |
377 | return allInfo; |
378 | } |
379 | |
380 | KPluginInfo::List matchingInfo; |
381 | foreach (const KPluginInfo &info, allInfo) { |
382 | if (info.category().compare(category, Qt::CaseInsensitive) == 0) { |
383 | matchingInfo << info; |
384 | } |
385 | } |
386 | |
387 | return matchingInfo; |
388 | } |
389 | |
390 | KPluginInfo::List PluginLoader::standardInternalAppletInfo(const QString &category) const |
391 | { |
392 | return standardInternalInfo("applets" , category); |
393 | } |
394 | |
395 | KPluginInfo::List PluginLoader::standardInternalDataEngineInfo() const |
396 | { |
397 | return standardInternalInfo("dataengines" ); |
398 | } |
399 | |
400 | KPluginInfo::List PluginLoader::standardInternalRunnerInfo() const |
401 | { |
402 | return standardInternalInfo("runners" ); |
403 | } |
404 | |
405 | KPluginInfo::List PluginLoader::standardInternalServiceInfo() const |
406 | { |
407 | return standardInternalInfo("services" ); |
408 | } |
409 | |
410 | } // Plasma Namespace |
411 | |
412 | |