1 /*
2 For general Scribus (>=1.3.2) copyright and licensing information please refer
3 to the COPYING file provided with the program. Following this notice may exist
4 a copyright and/or license notice that predates the release of Scribus 1.3.2
5 for which a new license (GPL+exception) is in place.
6 */
7 #include "pluginmanager.h"
8 #include "scplugin.h"
9 #include "loadsaveplugin.h"
10 
11 #include <QDir>
12 #include <QEvent>
13 #include <QMessageBox>
14 
15 #include "scconfig.h"
16 
17 
18 #include "scribusdoc.h"
19 #include "scribuscore.h"
20 #include "ui/sctoolbar.h"
21 #include "selection.h"
22 #include "ui/scmwmenumanager.h"
23 #include "scraction.h"
24 #include "ui/splash.h"
25 #include "prefsmanager.h"
26 #include "prefsfile.h"
27 #include "scpaths.h"
28 #include "commonstrings.h"
29 #include "ui/storyeditor.h"
30 
31 #ifdef HAVE_DLFCN_H
32 #include <dlfcn.h>
33 #elif defined(DLL_USE_NATIVE_API) && defined(_WIN32)
34 #include <windows.h>
35 #else
36 #include <QLibrary>
37 #endif
38 
PluginManager()39 PluginManager::PluginManager() :
40 	QObject(nullptr),
41 	prefs(PrefsManager::instance().prefsFile->getPluginContext("pluginmanager"))
42 {
43 }
44 
~PluginManager()45 PluginManager::~PluginManager()
46 {
47 }
48 
loadDLL(const QString & plugin)49 void* PluginManager::loadDLL(const QString& plugin)
50 {
51 	void* lib = nullptr;
52 #ifdef HAVE_DLFCN_H
53 	QString libpath = QDir::toNativeSeparators(plugin);
54 	lib = dlopen(libpath.toLocal8Bit().data(), RTLD_LAZY | RTLD_GLOBAL);
55 	if (!lib)
56 	{
57 		const char* error = dlerror();
58 		qDebug() << tr("Error loading plugin", "plugin manager").toLocal8Bit().data();
59 		if (error)
60 			qDebug("%s", error);
61 		else
62 			qDebug() << tr("Unknown error","plugin manager").toLocal8Bit().data();
63 	}
64 #elif defined(DLL_USE_NATIVE_API) && defined(_WIN32)
65 	QString libpath = QDir::toNativeSeparators(plugin);
66 	HINSTANCE hdll = LoadLibraryW((const wchar_t*) libpath.utf16());
67 	lib = (void*) hdll;
68 #else
69 	if (QFile::exists(plugin))
70 		lib = (void*) new QLibrary(plugin);
71 	else
72 	{
73 		qDebug() << tr("Error loading plugin", "plugin manager").toLocal8Bit().data();
74 		qDebug("%s", plugin.toLocal8Bit().data());
75 	}
76 #endif
77 	return lib;
78 }
79 
resolveSym(void * plugin,const char * sym)80 void* PluginManager::resolveSym(void* plugin, const char* sym)
81 {
82 	void* symAddr = nullptr;
83 #ifdef HAVE_DLFCN_H
84 	const char* error;
85 	dlerror();
86 	symAddr = dlsym(plugin, sym);
87 	if ((error = dlerror()) != nullptr)
88 	{
89 		qDebug("%s", tr("Cannot find symbol (%1)", "plugin manager").arg(error).toLocal8Bit().data());
90 		symAddr = nullptr;
91 	}
92 #elif defined(DLL_USE_NATIVE_API) && defined(_WIN32)
93 	symAddr = (void* ) GetProcAddress((HMODULE) plugin, sym);
94 	if (symAddr == nullptr)
95 		qDebug("%s", tr("Cannot find symbol (%1)", "plugin manager").arg(sym).toLocal8Bit().data());
96 #else
97 	QLibrary* qlib = (QLibrary*) plugin;
98 	if (plugin)
99 		symAddr = qlib->resolve(sym);
100 	if (symAddr == nullptr)
101 		qDebug("%s", tr("Cannot find symbol (%1)", "plugin manager").arg(sym).toLocal8Bit().data());
102 #endif
103 	return symAddr;
104 }
105 
unloadDLL(void * plugin)106 void  PluginManager::unloadDLL(void* plugin)
107 {
108 #ifdef HAVE_DLFCN_H
109 	dlclose(plugin);
110 	dlerror();
111 #elif defined(DLL_USE_NATIVE_API) && defined(_WIN32)
112 	FreeLibrary((HMODULE) plugin);
113 #else
114 	delete ((QLibrary*) plugin);
115 #endif
116 }
117 
savePreferences()118 void PluginManager::savePreferences()
119 {
120 	// write configuration
121 	for (auto it = pluginMap.constBegin(); it != pluginMap.constEnd(); ++it)
122 	{
123 		const PluginData& pluginData = it.value();
124 		prefs->set(pluginData.pluginName, pluginData.enableOnStartup);
125 	}
126 }
127 
getPluginName(const QString & fileName)128 QString PluginManager::getPluginName(const QString& fileName)
129 {
130 	// Must return plugin name. Note that this may be platform dependent;
131 	// it's likely to need some adjustment for platform naming schemes.
132 	// It currently handles:
133 	//    (lib)?pluginname(\.pluginext)?
134 	QFileInfo fi(fileName);
135 	QString baseName(fi.baseName());
136 	if (baseName.startsWith("lib"))
137 		baseName = baseName.remove(0, 3);
138 	if (baseName.endsWith(platformDllExtension()))
139 		baseName.chop(1 + platformDllExtension().length());
140 	// check name
141 	for (int i = 0; i < (int) baseName.length(); i++)
142 	{
143 		if (! baseName[i].isLetterOrNumber() && baseName[i] != '_' )
144 		{
145 			qDebug("Invalid character in plugin name for %s; skipping",
146 					fileName.toLocal8Bit().data());
147 			return QString();
148 		}
149 	}
150 	return baseName.toLatin1();
151 }
152 
initPlugin(const QString & fileName)153 int PluginManager::initPlugin(const QString& fileName)
154 {
155 	PluginData pda;
156 	pda.pluginFile = QString("%1/%2").arg(ScPaths::instance().pluginDir(), fileName);
157 	pda.pluginName = getPluginName(pda.pluginFile);
158 	if (pda.pluginName.isNull())
159 		// Couldn't determine plugname from filename. We've already complained, so
160 		// move on to the next one.
161 		return 0;
162 	pda.plugin = nullptr;
163 	pda.pluginDLL = nullptr;
164 	pda.enabled = false;
165 	pda.enableOnStartup = prefs->getBool(pda.pluginName, false);
166 	ScCore->setSplashStatus( tr("Plugin: loading %1", "plugin manager").arg(pda.pluginName));
167 	if (loadPlugin(pda))
168 	{
169 		//HACK: Always enable our only persistent plugin, scripter
170 		if (pda.plugin->inherits("ScPersistentPlugin"))
171 			pda.enableOnStartup = true;
172 		if (pda.enableOnStartup)
173 			enablePlugin(pda);
174 		pluginMap.insert(pda.pluginName, pda);
175 		return 1;
176 	}
177 	return 0;
178 }
179 
initPlugs()180 void PluginManager::initPlugs()
181 {
182 	Q_ASSERT(!pluginMap.count());
183 	QString libPattern = QString("*.%1*").arg(platformDllExtension());
184 	QMap<QString, int> allPlugs;
185 	int loaded = 0;
186 	uint changes = 1;
187 	QStringList failedPlugs; // output string for warn dialog
188 
189 	/*! \note QDir::Reversed is there due the Mac plugin dependency.
190 	barcode depends on psimport. and load on that platform expect the
191 	psimp before barcode.You know, security by obscurity ;) PV */
192 	QDir dirList(ScPaths::instance().pluginDir(),
193 				 libPattern, QDir::Name | QDir::Reversed,
194 				 (QDir::Filter) PluginManager::platformDllSearchFlags());
195 
196 	if ((!dirList.exists()) || (dirList.count() == 0))
197 		return;
198 	for (uint i = 0; i < dirList.count(); ++i)
199 	{
200 		int res = initPlugin(dirList[i]);
201 		allPlugs[dirList[i]] = res;
202 		if (res != 0)
203 			++loaded;
204 		else
205 			failedPlugs.append(dirList[i]);
206 	}
207 	/* Re-try the failed plugins again and again until it promote
208 	any progress (changes variable is changing ;)) */
209 	QMap<QString, int>::Iterator it;
210 	while (loaded < allPlugs.count() && changes!=0)
211 	{
212 		changes = 0;
213 		for (it = allPlugs.begin(); it != allPlugs.end(); ++it)
214 		{
215 			if (it.value() != 0)
216 				continue;
217 			int res = initPlugin(it.key());
218 			allPlugs[it.key()] = res;
219 			if (res == 1)
220 			{
221 				++loaded;
222 				++changes;
223 				failedPlugs.removeAll(it.key());
224 			}
225 		}
226 	}
227 	if (loaded != allPlugs.count())
228 	{
229 		if (ScCore->usingGUI())
230 		{
231 			bool splashShown = ScCore->splashShowing();
232 			QString failedStr("<ul>");
233 			for (QStringList::Iterator it = failedPlugs.begin(); it != failedPlugs.end(); ++it)
234 				failedStr += "<li>" + *it + "</li>";
235 			failedStr += "</ul>";
236 			if (splashShown)
237 				ScCore->showSplash(false);
238 			ScMessageBox::warning(ScCore->primaryMainWindow(), CommonStrings::trWarning,
239 								 "<qt>" + tr("There is a problem loading %1 of %2 plugins. %3 This is probably caused by some kind of dependency issue or old plugins existing in your install directory. If you clean out your install directory and reinstall and this still occurs, please report it on bugs.scribus.net."
240 										).arg(allPlugs.count() - loaded).arg(allPlugs.count()).arg(failedStr)
241 									 + "</qt>");
242 			if (splashShown)
243 				ScCore->showSplash(true);
244 		}
245 	}
246 }
247 
248 // After a plugin has been initialized, this method calls its setup
249 // routines and connects it to the application.
enablePlugin(PluginData & pda)250 void PluginManager::enablePlugin(PluginData & pda)
251 {
252 	Q_ASSERT(pda.enabled == false);
253 	QString failReason;
254 	bool isActionPlugin = false;
255 	if (pda.plugin->inherits("ScActionPlugin"))
256 	{
257 // 		ScActionPlugin* plugin = dynamic_cast<ScActionPlugin*>(pda.plugin);
258 // 		Q_ASSERT(plugin);
259 		isActionPlugin = true;
260 		/*
261 		pda.enabled = setupPluginActions(plugin);
262 		if (!pda.enabled)
263 			failReason = tr("init failed", "plugin load error");
264 		*/
265 	}
266 	else if (pda.plugin->inherits("ScPersistentPlugin"))
267 	{
268 		ScPersistentPlugin* plugin = qobject_cast<ScPersistentPlugin*>(pda.plugin);
269 		assert(plugin);
270 		pda.enabled = plugin->initPlugin();
271 		if (!pda.enabled)
272 			failReason = tr("init failed", "plugin load error");
273 	}
274 /* temporary hack to enable the import plugins */
275 	else if (pda.plugin->inherits("LoadSavePlugin"))
276 		pda.enabled = true;
277 	else
278 		failReason = tr("unknown plugin type", "plugin load error");
279 
280 	if (pda.enabled || isActionPlugin)
281 		ScCore->setSplashStatus(tr("Plugin: %1 loaded", "plugin manager").arg(pda.plugin->fullTrName()));
282 	else
283 		ScCore->setSplashStatus(tr("Plugin: %1 failed to load: %2", "plugin manager").arg(pda.plugin->fullTrName(), failReason));
284 }
285 
setupPluginActions(ScribusMainWindow * mw)286 bool PluginManager::setupPluginActions(ScribusMainWindow *mw)
287 {
288 	Q_CHECK_PTR(mw);
289 	ScActionPlugin* plugin = nullptr;
290 
291 	//mw->scrMenuMgr->addMenuItemString("SEPARATOR", "Extras");
292 	for (PluginMap::Iterator it = pluginMap.begin(); it != pluginMap.end(); ++it)
293 	{
294 		if (!it.value().plugin->inherits("ScActionPlugin"))
295 		{
296 			it.value().plugin->addToMainWindowMenu(mw);
297 			continue;
298 		}
299 
300 		//Add in ScrAction based plugin linkage
301 		//Insert DLL Action into Dictionary with values from plugin interface
302 		plugin = qobject_cast<ScActionPlugin*>(it.value().plugin);
303 		assert(plugin);
304 		ScActionPlugin::ActionInfo ai(plugin->actionInfo());
305 		ScrAction* action = new ScrAction(ScrAction::ActionDLL, ai.iconPath1, ai.iconPath2, ai.text, ai.keySequence, mw);
306 		Q_CHECK_PTR(action);
307 		action->setStatusTip(ai.helpText);
308 		action->setToolTip(ai.helpText);
309 		mw->scrActions.insert(ai.name, action);
310 
311 		// then enable and connect up the action
312 		mw->scrActions[ai.name]->setEnabled(ai.enabledOnStartup);
313 
314 		// Connect action's activated signal with the plugin's run method
315 		it.value().enabled = connect(mw->scrActions[ai.name], SIGNAL(triggeredData(ScribusDoc*)), plugin, SLOT(run(ScribusDoc*)) );
316 
317 			//Get the menu manager to add the DLL's menu item to the right menu, after the chosen existing item
318 		if (ai.menuAfterName.isEmpty())
319 		{
320 			if (!ai.menu.isEmpty())
321 			{
322 				if ((!ai.subMenuName.isEmpty()) && (!ai.parentMenu.isEmpty()))
323 				{
324 					if (!mw->scrMenuMgr->menuExists(ai.menu))
325 					{
326 						mw->scrMenuMgr->createMenu(ai.menu, ai.subMenuName, ai.parentMenu);
327 					}
328 				}
329 				mw->scrMenuMgr->addMenuItemString(ai.name, ai.menu);
330 			}
331 		}
332 		else
333 		{
334 //			QString actionName(ai.menu.toLower()+ai.menuAfterName);
335 		//	QString actionName(ai.menuAfterName);
336 		//	ScrAction* afterAction = nullptr;
337 		//	if (mw->scrActions.contains(actionName))
338 		//		afterAction = mw->scrActions[actionName];
339 			if ((!ai.subMenuName.isEmpty()) && (!ai.parentMenu.isEmpty()))
340 			{
341 				if (!mw->scrMenuMgr->menuExists(ai.menu))
342 					mw->scrMenuMgr->createMenu(ai.menu, ai.subMenuName, ai.parentMenu);
343 			}
344 			mw->scrMenuMgr->addMenuItemStringAfter(ai.name, ai.menuAfterName, ai.menu);
345 		}
346 		if (!ai.toolbar.isEmpty())
347 		{
348 			QString tbName = ai.toolbar;
349 			if (mw->scrToolBars.contains(tbName))
350 				mw->scrToolBars[tbName]->addAction(mw->scrActions[ai.name]);
351 			else
352 			{
353 				ScToolBar *tb = new ScToolBar(ai.toolBarName, ai.toolbar, mw);
354 				tb->addAction(mw->scrActions[ai.name]);
355 				mw->addScToolBar(tb, tbName);
356 			}
357 		}
358 		if (it.value().enabled)
359 			ScCore->setSplashStatus( tr("Plugin: %1 initialized ok ", "plugin manager").arg(plugin->fullTrName()));
360 		else
361 			ScCore->setSplashStatus( tr("Plugin: %1 failed post initialization", "plugin manager").arg(plugin->fullTrName()));
362 	}
363 
364 	//CB maybe we should just call mw->createMenuBar() here instead...
365 	mw->scrMenuMgr->clearMenu("File");
366 	mw->scrMenuMgr->addMenuItemStringsToMenuBar("File", mw->scrActions);
367 	mw->scrMenuMgr->clearMenu("Edit");
368 	mw->scrMenuMgr->addMenuItemStringsToMenuBar("Edit", mw->scrActions);
369 	mw->scrMenuMgr->clearMenu("Insert");
370 	mw->scrMenuMgr->addMenuItemStringsToMenuBar("Insert", mw->scrActions);
371 	mw->scrMenuMgr->clearMenu("Item");
372 	mw->scrMenuMgr->addMenuItemStringsToMenuBar("Item", mw->scrActions);
373 	mw->scrMenuMgr->clearMenu("Page");
374 	mw->scrMenuMgr->addMenuItemStringsToMenuBar("Page", mw->scrActions);
375 	mw->scrMenuMgr->clearMenu("ItemTable");
376 	mw->scrMenuMgr->addMenuItemStringsToMenuBar("ItemTable", mw->scrActions);
377 	mw->scrMenuMgr->clearMenu("Extras");
378 	mw->scrMenuMgr->addMenuItemStringsToMenuBar("Extras", mw->scrActions);
379 	mw->scrMenuMgr->clearMenu("View");
380 	mw->scrMenuMgr->addMenuItemStringsToMenuBar("View", mw->scrActions);
381 	mw->scrMenuMgr->clearMenu("Help");
382 	mw->scrMenuMgr->addMenuItemStringsToMenuBar("Help", mw->scrActions);
383 
384 	return true;
385 }
386 
setupPluginActions(StoryEditor * sew)387 bool PluginManager::setupPluginActions(StoryEditor *sew)
388 {
389 	Q_CHECK_PTR(sew);
390 	ScActionPlugin* plugin = nullptr;
391 
392 	//sew->seMenuMgr->addMenuSeparator("Extras");
393 	for (PluginMap::Iterator it = pluginMap.begin(); it != pluginMap.end(); ++it)
394 	{
395 		if (!it.value().plugin->inherits("ScActionPlugin"))
396 			continue;
397 
398 		//Add in ScrAction based plugin linkage
399 		//Insert DLL Action into Dictionary with values from plugin interface
400 		plugin = qobject_cast<ScActionPlugin*>(it.value().plugin);
401 		assert(plugin);
402 		ScActionPlugin::ActionInfo ai(plugin->actionInfo());
403 		if (!ai.enabledForStoryEditor)
404 			continue;
405 
406 		ScrAction* action = new ScrAction(ScrAction::ActionDLLSE, ai.iconPath1, ai.iconPath2, ai.text, ai.keySequence, sew);
407 		Q_CHECK_PTR(action);
408 		sew->seActions.insert(ai.name, action);
409 
410 		// then enable and connect up the action
411 		sew->seActions[ai.name]->setEnabled(ai.enabledForStoryEditor);
412 
413 		// Connect action's activated signal with the plugin's run method
414 		it.value().enabled = connect(sew->seActions[ai.name], SIGNAL(triggeredData(QWidget*, ScribusDoc*)), plugin, SLOT(run(QWidget*, ScribusDoc*)));
415 
416 		//Get the menu manager to add the DLL's menu item to the right menu, after the chosen existing item
417 		if (ai.menuAfterName.isEmpty())
418 		{
419 			if (!ai.seMenu.isEmpty())
420 			{
421 				if ((!ai.subMenuName.isEmpty()) && (!ai.parentMenu.isEmpty()))
422 				{
423 					if (!sew->seMenuMgr->menuExists(ai.seMenu))
424 						sew->seMenuMgr->createMenu(ai.seMenu, ai.subMenuName, ai.parentMenu);
425 				}
426 //				sew->seMenuMgr->addMenuItem(sew->seActions[ai.name], ai.seMenu, true);
427 				sew->seMenuMgr->addMenuItemString(ai.name, ai.menu);
428 			}
429 		}
430 		else
431 		{
432 		//	QString actionName(ai.seMenu.toLower()+ai.menuAfterName);
433 		//	ScrAction* afterAction = nullptr;
434 		//	if (sew->seActions.contains(actionName))
435 		//		afterAction = sew->seActions[actionName];
436 			if ((!ai.subMenuName.isEmpty()) && (!ai.parentMenu.isEmpty()))
437 			{
438 				if (!sew->seMenuMgr->menuExists(ai.seMenu))
439 					sew->seMenuMgr->createMenu(ai.seMenu, ai.subMenuName, ai.parentMenu);
440 			}
441 //			sew->seMenuMgr->addMenuItemAfter(sew->seActions[ai.name], ai.seMenu, true, afterAction);
442 			sew->seMenuMgr->addMenuItemStringAfter(ai.name, ai.menuAfterName, ai.menu);
443 		}
444 	}
445 	return true;
446 }
447 
448 
enableOnlyStartupPluginActions(ScribusMainWindow * mw)449 void PluginManager::enableOnlyStartupPluginActions(ScribusMainWindow* mw)
450 {
451 	Q_CHECK_PTR(mw);
452 	ScActionPlugin* plugin = nullptr;
453 	for (PluginMap::Iterator it = pluginMap.begin(); it != pluginMap.end(); ++it)
454 	{
455 		if (!it.value().plugin->inherits("ScActionPlugin"))
456 			continue;
457 		plugin = qobject_cast<ScActionPlugin*>(it.value().plugin);
458 		assert(plugin);
459 		ScActionPlugin::ActionInfo ai(plugin->actionInfo());
460 		if (mw->scrActions.contains(ai.name))
461 			mw->scrActions[ai.name]->setEnabled(ai.enabledOnStartup);
462 	}
463 }
464 
enablePluginActionsForSelection(ScribusMainWindow * mw)465 void PluginManager::enablePluginActionsForSelection(ScribusMainWindow* mw)
466 {
467 	Q_CHECK_PTR(mw);
468 	ScribusDoc* doc = mw->doc;
469 	if (!doc)
470 		return;
471 
472 	int selectedType = -1;
473 	if (doc->m_Selection->count() > 0)
474 	{
475 		PageItem *currItem = doc->m_Selection->itemAt(0);
476 		selectedType = currItem->itemType();
477 	}
478 	bool isLayerLocked = doc->layerLocked(doc->activeLayer());
479 
480 	ScActionPlugin* actionPlug = nullptr;
481 	ScrAction* pluginAction = nullptr;
482 	for (PluginMap::Iterator it = pluginMap.begin(); it != pluginMap.end(); ++it)
483 	{
484 		if (!it.value().plugin->inherits("ScActionPlugin"))
485 			continue;
486 		actionPlug = qobject_cast<ScActionPlugin*>(it.value().plugin);
487 		if (!actionPlug)
488 			continue;
489 
490 		ScActionPlugin::ActionInfo actionInfo(actionPlug->actionInfo());
491 		pluginAction = mw->scrActions[actionInfo.name];
492 		if (pluginAction == nullptr)
493 			continue;
494 		if (isLayerLocked && !actionInfo.enabledOnStartup)
495 			pluginAction->setEnabled(false);
496 		else
497 			pluginAction->setEnabled(actionPlug->handleSelection(doc, selectedType));
498 	}
499 }
500 
DLLexists(const QString & name,bool includeDisabled) const501 bool PluginManager::DLLexists(const QString& name, bool includeDisabled) const
502 {
503 	// the plugin name must be known
504 	if (pluginMap.contains(name))
505 	{
506 		// the plugin must be loaded
507 		if (pluginMap[name].plugin)
508 		{
509 			// and the plugin must be enabled unless we were told otherwise
510 			if (pluginMap[name].enabled)
511 				return true;
512 			return includeDisabled;
513 		}
514 	}
515 	return false;
516 }
517 
loadPlugin(PluginData & pluginData)518 bool PluginManager::loadPlugin(PluginData& pluginData)
519 {
520 	typedef int (*getPluginAPIVersionPtr)();
521 	typedef ScPlugin* (*getPluginPtr)();
522 	getPluginAPIVersionPtr getPluginAPIVersion;
523 	getPluginPtr getPlugin;
524 
525 	Q_ASSERT(pluginData.plugin == nullptr);
526 	Q_ASSERT(pluginData.pluginDLL == nullptr);
527 	Q_ASSERT(!pluginData.enabled);
528 	pluginData.plugin = nullptr;
529 
530 	pluginData.pluginDLL = loadDLL(pluginData.pluginFile);
531 	if (!pluginData.pluginDLL)
532 		return false;
533 
534 	getPluginAPIVersion = (getPluginAPIVersionPtr)
535 		resolveSym(pluginData.pluginDLL, QString(pluginData.pluginName + "_getPluginAPIVersion").toLocal8Bit().data());
536 	if (getPluginAPIVersion)
537 	{
538 		int gotVersion = (*getPluginAPIVersion)();
539 		if (gotVersion != PLUGIN_API_VERSION)
540 		{
541 			qDebug("API version mismatch when loading %s: Got %i, expected %i",
542 					pluginData.pluginFile.toLocal8Bit().data(), gotVersion, PLUGIN_API_VERSION);
543 		}
544 		else
545 		{
546 			getPlugin = (getPluginPtr)
547 				resolveSym(pluginData.pluginDLL, QString(pluginData.pluginName + "_getPlugin").toLocal8Bit().data());
548 			if (getPlugin)
549 			{
550 				pluginData.plugin = (*getPlugin)();
551 				if (!pluginData.plugin)
552 				{
553 					qDebug("Unable to get ScPlugin when loading %s",
554 							pluginData.pluginFile.toLocal8Bit().data());
555 				}
556 				else
557 					return true;
558 			}
559 		}
560 	}
561 	unloadDLL(pluginData.pluginDLL);
562 	pluginData.pluginDLL = nullptr;
563 	Q_ASSERT(!pluginData.plugin);
564 	return false;
565 }
566 
cleanupPlugins()567 void PluginManager::cleanupPlugins()
568 {
569 	for (PluginMap::Iterator it = pluginMap.begin(); it != pluginMap.end(); ++it)
570 	{
571 		if (!it.value().enabled)
572 			continue;
573 		finalizePlug(it.value());
574 	}
575 }
576 
finalizePlug(PluginData & pluginData)577 void PluginManager::finalizePlug(PluginData & pluginData)
578 {
579 	typedef void (*freePluginPtr)(ScPlugin* plugin);
580 	if (pluginData.plugin)
581 	{
582 		if (pluginData.enabled)
583 			disablePlugin(pluginData);
584 		Q_ASSERT(!pluginData.enabled);
585 		freePluginPtr freePlugin = (freePluginPtr) resolveSym(pluginData.pluginDLL, QString(pluginData.pluginName + "_freePlugin").toLocal8Bit().data());
586 		if (freePlugin)
587 			(*freePlugin)(pluginData.plugin);
588 		pluginData.plugin = nullptr;
589 	}
590 	Q_ASSERT(!pluginData.enabled);
591 	if (pluginData.pluginDLL)
592 	{
593 		unloadDLL(pluginData.pluginDLL);
594 		pluginData.pluginDLL = nullptr;
595 	}
596 }
597 
disablePlugin(PluginData & pda)598 void PluginManager::disablePlugin(PluginData & pda)
599 {
600 	Q_ASSERT(pda.enabled);
601 	Q_ASSERT(pda.plugin);
602 	if (pda.plugin->inherits("ScActionPlugin"))
603 	{
604 		ScActionPlugin* plugin = qobject_cast<ScActionPlugin*>(pda.plugin);
605 		assert(plugin);
606 		plugin->cleanupPlugin();
607 		// FIXME: Correct way to delete action?
608 		delete ScCore->primaryMainWindow()->scrActions[plugin->actionInfo().name];
609 	}
610 	else if (pda.plugin->inherits("ScPersistentPlugin"))
611 	{
612 		ScPersistentPlugin* plugin = qobject_cast<ScPersistentPlugin*>(pda.plugin);
613 		assert(plugin);
614 		plugin->cleanupPlugin();
615 	}
616 /* temporary hack to enable the import plugins */
617 	else if (pda.plugin->inherits("LoadSavePlugin"))
618 		pda.enabled = false;
619 	else
620 		Q_ASSERT(false); // We shouldn't ever have enabled an unknown plugin type.
621 	pda.enabled = false;
622 }
623 
platformDllExtension()624 QString PluginManager::platformDllExtension()
625 {
626 #ifdef __hpux
627 	// HP/UX
628 	return "sl";
629 #elif defined(__APPLE__) && defined(__MACH__)
630 	// MacOS/X, Darwin
631 
632 	// MacOS/X may actually use both 'so' and 'dylib'. .so is usually used
633 	// for plugins etc, dylib for system and app libraries We need to
634 	// support this distinction in the plugin manager, but for now it's
635 	// most appropriate to return the extension used by plugins -- CR
636 
637 	//return "dylib";
638 	return "so";
639 #elif defined(_WIN32) || defined(_WIN64)
640 	return "dll";
641 #else
642 	// Generic *NIX
643 	return "so";
644 #endif
645 }
646 
platformDllSearchFlags()647 int PluginManager::platformDllSearchFlags()
648 {
649 #if defined(_WIN32) || defined(_WIN64)
650 	return (QDir::Files | QDir::NoSymLinks);
651 #else
652 	return (QDir::Files | QDir::Executable | QDir::NoSymLinks);
653 #endif
654 }
655 
languageChange()656 void PluginManager::languageChange()
657 {
658 	ScPlugin* plugin = nullptr;
659 	ScActionPlugin* ixplug = nullptr;
660 	ScrAction* pluginAction = nullptr;
661 	for (PluginMap::Iterator it = pluginMap.begin(); it != pluginMap.end(); ++it)
662 	{
663 		plugin = it.value().plugin;
664 		if (!plugin)
665 			continue;
666 		plugin->languageChange();
667 
668 		ixplug = qobject_cast<ScActionPlugin*>(plugin);
669 		if (!ixplug)
670 			continue;
671 
672 		ScActionPlugin::ActionInfo ai(ixplug->actionInfo());
673 		pluginAction = ScCore->primaryMainWindow()->scrActions[ai.name];
674 		if (pluginAction != nullptr)
675 			pluginAction->setText(ai.text);
676 		if ((!ai.menu.isEmpty()) && (!ai.subMenuName.isEmpty()))
677 			ScCore->primaryMainWindow()->scrMenuMgr->setText(ai.menu, ai.subMenuName);
678 	}
679 }
680 
getPlugin(const QString & pluginName,bool includeDisabled) const681 ScPlugin* PluginManager::getPlugin(const QString & pluginName, bool includeDisabled) const
682 {
683 	if (DLLexists(pluginName, includeDisabled))
684 		return pluginMap[pluginName].plugin;
685 	return nullptr;
686 }
687 
instance()688 PluginManager & PluginManager::instance()
689 {
690 	return (*ScCore->pluginManager);
691 }
692 
getPluginPath(const QString & pluginName) const693 QString PluginManager::getPluginPath(const QString & pluginName) const
694 {
695 	// It is not legal to call this function without a valid
696 	// plug in name.
697 	Q_ASSERT(pluginMap.contains(pluginName));
698 	return pluginMap[pluginName].pluginFile;
699 }
700 
enableOnStartup(const QString & pluginName)701 bool & PluginManager::enableOnStartup(const QString & pluginName)
702 {
703 	// It is not legal to call this function without a valid
704 	// plug in name.
705 	Q_ASSERT(pluginMap.contains(pluginName));
706 	return pluginMap[pluginName].enableOnStartup;
707 }
708 
enabled(const QString & pluginName)709 bool PluginManager::enabled(const QString & pluginName)
710 {
711 	// It is not legal to call this function without a valid
712 	// plug in name.
713 	Q_ASSERT(pluginMap.contains(pluginName));
714 	return pluginMap[pluginName].enabled;
715 }
716 
pluginNames(bool includeDisabled,const char * inherits) const717 QStringList PluginManager::pluginNames(bool includeDisabled, const char* inherits) const
718 {
719 	// Scan the plugin map for plugins...
720 	QStringList names;
721 	for (PluginMap::ConstIterator it = pluginMap.constBegin(); it != pluginMap.constEnd(); ++it)
722 	{
723 		if (!includeDisabled && !it.value().enabled)
724 			continue;
725 
726 		// Only including plugins that inherit a named parent (if
727 		// specified), using the QMetaObject system.
728 		if (!inherits || it.value().plugin->inherits(inherits))
729 			names.append(it.value().pluginName);
730 	}
731 	return names;
732 }
733