Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Plugin API #1879

Draft
wants to merge 19 commits into
base: develop
Choose a base branch
from
Draft

Plugin API #1879

wants to merge 19 commits into from

Conversation

Mai-Lapyst
Copy link

This PR contains a first design and POC of a plugin API for PrismLauncher; related to #1240.

The current draft implements a few things:

  • configuration for a plugin directory
  • discovering & loading of plugin metadata in said directory + an basic UI in the launcher settings to manage plugins.
  • "contribution" based data-driven plugins
  • optional ability for plugins to specify native qt plugins
  • adding a extension point for plugins to provide cat packs as an basic example

To test things out i've created a test plugin over here: https://github.com/Mai-Lapyst/prism-test-plugin

The design

The basic idea behind this version of a plugin API is to make plugins as much as possible data driven. Here's an example plugin.json:

{
	// Basic metadata about the plugin; primarily used in the management UI
	"formatVersion": 1,
	"displayName": "Test plugin",
	"description": "A plugin that adds a catpack for test purposes",
	"homepage": "https://github.com/Mai-Lapyst/prism-test-plugin",
	"issues": "https://github.com/Mai-Lapyst/prism-test-plugin/issues",
	"icon": "./icon.png",
	"license": "GPL-3.0-only",
	"authors": [
		"Mai-Lapyst"
	],

	// Metadata about the files providing the native part of an plugin
	// This section is optional and if no contribution requires this
	// it ideally should neither be provided by plugins nor used
	// by the launcher (currently WIP)
	"natives": {
		"osx": "",
		"win32": "",
		"win64": "",
		"linux32": "",
		"linux64": "./libtestplugin.so"
	},

	// This section describes in a data-driven manner, what the plugin
	// wants to do / provide to the launcher.
	"contributions": {
		// Plugin contributes one or more cat packs; entries are simply
		// paths to an (json) cat pack as already defined in Prism.
		"cat_packs": [
			"./cats/my_cute_cat"
		]
	}
}

A data driven approach was picked to reduce developer burden for writing a complete C++ native plugin, compiling it for every platform (OS + Processor) only to do basic tasks. This also prevents bugs where plugins might not register / deregister everything correctly and thus become not completly unloadable without a restart (currently WIP). This also minimalizes entrypoints for malicious code an thus improving security for simple plugins.

The next steps

To further extend the API, there is unfortunatly a rather large set of edits needed to the core structure of the launcher. For example: modplatforms all extend a basic page but it would be against the data driven approach to requiring every plugin that wants to add a modplatform to implement it themself, potentially breaking the UI/UX design of the launcher too. Loaders are completly NOT extensible since it's internally represented trough a compile-time enum.

To further work on this, it needs to be decided how these and similar issues are solved, and if this should be done in this PR, making it potentially VERY huge, or to do it incrementally by reworking the internals with each plugin extension point added.

Comment on lines +85 to +89
if (!existingIds.isEmpty()) {
// TODO: we need to restart when plugins where removed from disk!
}

Check notice

Code scanning / CodeQL

Futile conditional

If-statement with an empty then-branch and no else-branch.
launcher/plugin/PluginList.h Fixed Show fixed Hide fixed
bool setEnabled(const QModelIndexList& indexes, Plugin::EnableAction action);

[[nodiscard]] QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override;
// bool setData(const QModelIndex& index, const QVariant& value, int role = Qt::EditRole) override;

Check notice

Code scanning / CodeQL

Commented-out code

This comment appears to contain commented-out code.
// connect(ui->actionRemoveItem, &QAction::triggered, this, &PluginPage::removeItem);
connect(ui->actionEnableItem, &QAction::triggered, this, &PluginPage::enableItem);
connect(ui->actionDisableItem, &QAction::triggered, this, &PluginPage::disableItem);
// connect(ui->actionViewConfigs, &QAction::triggered, this, &PluginPage::viewConfigs);

Check notice

Code scanning / CodeQL

Commented-out code

This comment appears to contain commented-out code.
Comment on lines 52 to 53
// connect(ui->actionAddItem, &QAction::triggered, this, &PluginPage::addItem);
// connect(ui->actionRemoveItem, &QAction::triggered, this, &PluginPage::removeItem);

Check notice

Code scanning / CodeQL

Commented-out code

This comment appears to contain commented-out code.
Comment on lines +84 to +96
switch (pluginFormatVersion) {
case 1: {
loadV1(root);
return true;
}
default: {
QString newName = "plugin-old.json";
qWarning() << "Unknown format version when loading plugin info. Existing one will be renamed to" << newName;
// Attempt to rename the old version.
file.rename(newName);
return false;
}
}

Check notice

Code scanning / CodeQL

No trivial switch statements

This switch statement should either handle more cases, or be rewritten as an if statement.
Comment on lines +179 to +185
switch (column) {
case ActiveColumn:
return m_plugins[row]->enabled() ? Qt::Checked : Qt::Unchecked;
default:
return {};
}

Check notice

Code scanning / CodeQL

No trivial switch statements

This switch statement should either handle more cases, or be rewritten as an if statement.
{
auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection());
if (m_model->setEnabled(selection.indexes(), Plugin::EnableAction::DISABLE)) {
auto response = CustomMessageBox::selectable(this, "Need Launcher restart",

Check notice

Code scanning / CodeQL

Unused local variable

Variable response is not used.
{
auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection());
if (m_model->setEnabled(selection.indexes(), Plugin::EnableAction::TOGGLE)) {
auto response = CustomMessageBox::selectable(this, "Need Launcher restart",

Check notice

Code scanning / CodeQL

Unused local variable

Variable response is not used.
Comment on lines +68 to +72
// protected:
// [[nodiscard]] bool filterAcceptsRow(int source_row, const QModelIndex& source_parent) const override;
// [[nodiscard]] bool lessThan(const QModelIndex& source_left, const QModelIndex& source_right) const override;

Check notice

Code scanning / CodeQL

Commented-out code

This comment appears to contain commented-out code.
Copy link
Member

@TheKodeToad TheKodeToad left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

😍 very cool - thank you for spending time on this!

launcher/plugin/Plugin.h Outdated Show resolved Hide resolved
launcher/plugin/Plugin.h Outdated Show resolved Hide resolved
@TheKodeToad
Copy link
Member

TheKodeToad commented Nov 21, 2023

Loaders are completly NOT extensible since it's internally represented trough a compile-time enum.

Hmm, that's hard. One thing that's unique to every loader (which are components) is a UID (usually reversed domain), so a QSet of component IDs could be a (less efficient) replacement. In future there may be a way to add loaders to menus without plugins (I think a repository system would be better) - I don't think you should need a plugin to register a new loader type if you went with that sort of system.

To further work on this, it needs to be decided how these and similar issues are solved, and if this should be done in this PR, making it potentially VERY huge, or to do it incrementally by reworking the internals with each plugin extension point added.

Yeah... probably best to have separate PRs to avoid a lot of merge conflicts

@Mai-Lapyst Mai-Lapyst force-pushed the plugin-api branch 2 times, most recently from 9f88158 to bc2fbca Compare November 29, 2023 13:22
@Mai-Lapyst
Copy link
Author

Loaders are completly NOT extensible since it's internally represented trough a compile-time enum.

Hmm, that's hard. One thing that's unique to every loader (which are components) is a UID (usually reversed domain), so a QSet of component IDs could be a (less efficient) replacement. In future there may be a way to add loaders to menus without plugins (I think a repository system would be better) - I don't think you should need a plugin to register a new loader type if you went with that sort of system.

While it's true that a (native) plugin might be a overhead, I still would like to add the ability for a plugin to add an loader type, so developers can reduce the steps for someone to install their stuff to a simple plugin install, which then configures / adds all the things neccessary.

To further work on this, it needs to be decided how these and similar issues are solved, and if this should be done in this PR, making it potentially VERY huge, or to do it incrementally by reworking the internals with each plugin extension point added.

Yeah... probably best to have separate PRs to avoid a lot of merge conflicts

Okay; will then polish this PR, maybe add a thing or two, and make it into a normal one so it can be reviewed properly and merged.

@DioEgizio
Copy link
Member

A first thing I noticed is that you put Win32 and win64 in natives but we also have an arm build and it should make the distinction between mingw and msvc too and for macOS It also needs both x64 and arm64

Signed-off-by: Mai-Lapyst <67418776+Mai-Lapyst@users.noreply.github.com>
Signed-off-by: Mai-Lapyst <67418776+Mai-Lapyst@users.noreply.github.com>
Signed-off-by: Mai-Lapyst <67418776+Mai-Lapyst@users.noreply.github.com>
Signed-off-by: Mai-Lapyst <67418776+Mai-Lapyst@users.noreply.github.com>
Signed-off-by: Mai-Lapyst <67418776+Mai-Lapyst@users.noreply.github.com>
Signed-off-by: Mai-Lapyst <67418776+Mai-Lapyst@users.noreply.github.com>
… active; just using the first remaining one instead

Signed-off-by: Mai-Lapyst <67418776+Mai-Lapyst@users.noreply.github.com>
Signed-off-by: Mai-Lapyst <67418776+Mai-Lapyst@users.noreply.github.com>
Signed-off-by: Mai-Lapyst <67418776+Mai-Lapyst@users.noreply.github.com>
Signed-off-by: Mai-Lapyst <67418776+Mai-Lapyst@users.noreply.github.com>
Signed-off-by: Mai-Lapyst <67418776+Mai-Lapyst@users.noreply.github.com>
Signed-off-by: Mai-Lapyst <67418776+Mai-Lapyst@users.noreply.github.com>
Signed-off-by: Mai-Lapyst <67418776+Mai-Lapyst@users.noreply.github.com>
Signed-off-by: Mai-Lapyst <67418776+Mai-Lapyst@users.noreply.github.com>
Signed-off-by: Mai-Lapyst <67418776+Mai-Lapyst@users.noreply.github.com>
Fixed by changing the plugin.json format to specify natives per qt version.

Signed-off-by: Mai-Lapyst <67418776+Mai-Lapyst@users.noreply.github.com>
Signed-off-by: Mai-Lapyst <67418776+Mai-Lapyst@users.noreply.github.com>
Signed-off-by: Mai-Lapyst <67418776+Mai-Lapyst@users.noreply.github.com>
Signed-off-by: Mai-Lapyst <67418776+Mai-Lapyst@users.noreply.github.com>
@Mai-Lapyst
Copy link
Author

A first thing I noticed is that you put Win32 and win64 in natives but we also have an arm build and it should make the distinction between mingw and msvc too and for macOS It also needs both x64 and arm64

Hmm is mingw and msvc that incompatible? I would like to keep down plugin-dev's burden and not require them to compile things more times than strictly neccessary.

It's generally a problem currently: since PrismLauncher is distributed twice, once with Qt5 and once with Qt6, it would be needed to compile the plugin once for each qt version, and that is without any custom rendering hook's or similar.

Because of that it might be a better way to map out what plugins should be able to accomplish. I.e. should we allow them to create their own ui elements, can we get away with ui custimisation by a declarative approach and only require a native library for API requests or file handling and so on.

@DioEgizio
Copy link
Member

Note that we don't have a x32/qt5 build anymore on windows and we're probably gonna drop qt5 soon-ish

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants