mumble-voip_mumble/src/mumble/PluginConfig.cpp

288 lines
9.5 KiB
C++

// Copyright 2021-2023 The Mumble Developers. All rights reserved.
// Use of this source code is governed by a BSD-style license
// that can be found in the LICENSE file at the root of the
// Mumble source tree or at <https://www.mumble.info/LICENSE>.
#include "PluginConfig.h"
#include "Log.h"
#include "MainWindow.h"
#include "MumbleApplication.h"
#include "PluginInstaller.h"
#include "PluginManager.h"
#include "ServerHandler.h"
#include "Utils.h"
#include "WebFetch.h"
#include <QtCore/QDir>
#include <QtCore/QStandardPaths>
#include <QtCore/QUrl>
#include <QtWidgets/QFileDialog>
#include <QtWidgets/QMessageBox>
#include "Global.h"
const QString PluginConfig::name = QLatin1String("PluginConfig");
static ConfigWidget *PluginConfigDialogNew(Settings &st) {
return new PluginConfig(st);
}
static ConfigRegistrar registrarPluginConfig(5000, PluginConfigDialogNew);
PluginConfig::PluginConfig(Settings &st) : ConfigWidget(st) {
setupUi(this);
qtwPlugins->header()->setSectionResizeMode(0, QHeaderView::Stretch);
qtwPlugins->header()->setSectionResizeMode(1, QHeaderView::ResizeToContents);
qtwPlugins->header()->setSectionResizeMode(2, QHeaderView::ResizeToContents);
qtwPlugins->header()->setSectionResizeMode(3, QHeaderView::ResizeToContents);
qtwPlugins->headerItem()->setData(0, Qt::AccessibleTextRole, tr("Plugin name"));
qtwPlugins->headerItem()->setData(1, Qt::AccessibleTextRole, tr("Plugin enabled checkbox"));
qtwPlugins->headerItem()->setData(2, Qt::AccessibleTextRole, tr("Plugin positional audio permission checkbox"));
qtwPlugins->headerItem()->setData(3, Qt::AccessibleTextRole,
tr("Plugin keyboard event listen permission checkbox"));
qpbUnload->setEnabled(false);
refillPluginList();
}
QString PluginConfig::title() const {
return tr("Plugins");
}
const QString &PluginConfig::getName() const {
return PluginConfig::name;
}
QIcon PluginConfig::icon() const {
return QIcon(QLatin1String("skin:config_plugin.png"));
}
void PluginConfig::load(const Settings &r) {
loadCheckBox(qcbTransmit, r.bTransmitPosition);
}
void PluginConfig::on_qpbInstallPlugin_clicked() {
QString pluginFile = QFileDialog::getOpenFileName(this, tr("Install plugin..."), QDir::homePath());
if (pluginFile.isEmpty()) {
return;
}
try {
PluginInstaller installer(pluginFile, this);
if (installer.exec() == QDialog::Accepted) {
// Reload plugins so the new one actually shows up
on_qpbReload_clicked();
QMessageBox::information(this, "Mumble", tr("The plugin was installed successfully"), QMessageBox::Ok,
QMessageBox::NoButton);
}
} catch (const PluginInstallException &e) {
QMessageBox::critical(this, "Mumble", e.getMessage(), QMessageBox::Ok, QMessageBox::NoButton);
}
}
void PluginConfig::save() const {
s.bTransmitPosition = qcbTransmit->isChecked();
s.qhPluginSettings.clear();
if (!s.bTransmitPosition) {
// Make sure that if posData is currently running, it gets reset
// The setting will prevent the system from reactivating
Global::get().pluginManager->unlinkPositionalData();
}
constexpr int ENABLE_COL = 1;
constexpr int POSITIONAL_DATA_COL = 2;
constexpr int KEYBOARD_MONITOR_COL = 3;
QList< QTreeWidgetItem * > list = qtwPlugins->findItems(QString(), Qt::MatchContains);
for (QTreeWidgetItem *i : list) {
bool enable = (i->checkState(ENABLE_COL) == Qt::Checked);
bool positionalDataEnabled = (i->checkState(POSITIONAL_DATA_COL) == Qt::Checked);
bool keyboardMonitoringEnabled = (i->checkState(KEYBOARD_MONITOR_COL) == Qt::Checked);
const_plugin_ptr_t plugin = pluginForItem(i);
if (plugin) {
// insert plugin to settings
Global::get().pluginManager->enablePositionalDataFor(plugin->getID(), positionalDataEnabled);
Global::get().pluginManager->allowKeyboardMonitoringFor(plugin->getID(), keyboardMonitoringEnabled);
if (enable) {
if (Global::get().pluginManager->loadPlugin(plugin->getID())) {
// potentially deactivate plugin features
// A plugin's feature is considered to be enabled by default after loading. Thus we only need to
// deactivate the ones we don't want
uint32_t featuresToDeactivate = MUMBLE_FEATURE_NONE;
const uint32_t pluginFeatures = plugin->getFeatures();
if (!positionalDataEnabled && (pluginFeatures & MUMBLE_FEATURE_POSITIONAL)) {
// deactivate this feature only if it is available in the first place
featuresToDeactivate |= MUMBLE_FEATURE_POSITIONAL;
}
if (featuresToDeactivate != MUMBLE_FEATURE_NONE) {
uint32_t remainingFeatures =
Global::get().pluginManager->deactivateFeaturesFor(plugin->getID(), featuresToDeactivate);
if (remainingFeatures != MUMBLE_FEATURE_NONE) {
Global::get().l->log(Log::Warning,
tr("Unable to deactivate all requested features for plugin \"%1\"")
.arg(plugin->getName()));
}
}
} else {
// loading failed
enable = false;
Global::get().l->log(Log::Warning, tr("Unable to load plugin \"%1\"").arg(plugin->getName()));
}
} else {
Global::get().pluginManager->unloadPlugin(plugin->getID());
}
QString pluginKey = QLatin1String(
QCryptographicHash::hash(plugin->getFilePath().toUtf8(), QCryptographicHash::Sha1).toHex());
s.qhPluginSettings.insert(
pluginKey, { plugin->getFilePath(), enable, positionalDataEnabled, keyboardMonitoringEnabled });
}
}
}
const_plugin_ptr_t PluginConfig::pluginForItem(QTreeWidgetItem *i) const {
if (i) {
return Global::get().pluginManager->getPlugin(i->data(0, Qt::UserRole).toUInt());
}
return nullptr;
}
void PluginConfig::on_qpbConfig_clicked() {
const_plugin_ptr_t plugin = pluginForItem(qtwPlugins->currentItem());
if (plugin) {
if (!plugin->showConfigDialog(this)) {
// if the plugin doesn't support showing such a dialog, we'll show a default one
QMessageBox::information(this, QLatin1String("Mumble"), tr("Plugin has no configure function."),
QMessageBox::Ok, QMessageBox::NoButton);
}
}
}
void PluginConfig::on_qpbAbout_clicked() {
const_plugin_ptr_t plugin = pluginForItem(qtwPlugins->currentItem());
if (plugin) {
if (!plugin->showAboutDialog(this)) {
// if the plugin doesn't support showing such a dialog, we'll show a default one
QMessageBox::information(this, QLatin1String("Mumble"), tr("Plugin has no about function."),
QMessageBox::Ok, QMessageBox::NoButton);
}
}
}
void PluginConfig::on_qpbReload_clicked() {
Global::get().pluginManager->rescanPlugins();
refillPluginList();
}
void PluginConfig::on_qpbUnload_clicked() {
QTreeWidgetItem *currentItem = qtwPlugins->currentItem();
if (!currentItem) {
return;
}
const_plugin_ptr_t plugin = pluginForItem(currentItem);
if (!plugin) {
return;
}
if (Global::get().pluginManager->clearPlugin(plugin->getID())) {
// Plugin was successfully cleared
currentItem = qtwPlugins->takeTopLevelItem(qtwPlugins->indexOfTopLevelItem(currentItem));
delete currentItem;
} else {
qWarning("PluginConfig.cpp: Failed to delete unloaded plugin entry");
}
}
void PluginConfig::refillPluginList() {
qtwPlugins->clear();
// get plugins already sorted according to their name
const QVector< const_plugin_ptr_t > plugins = Global::get().pluginManager->getPlugins(true);
foreach (const_plugin_ptr_t currentPlugin, plugins) {
QTreeWidgetItem *i = new QTreeWidgetItem(qtwPlugins);
i->setFlags(Qt::ItemIsUserCheckable | Qt::ItemIsEnabled | Qt::ItemIsSelectable);
i->setCheckState(1, currentPlugin->isLoaded() ? Qt::Checked : Qt::Unchecked);
if (currentPlugin->getFeatures() & MUMBLE_FEATURE_POSITIONAL) {
i->setCheckState(2, currentPlugin->isPositionalDataEnabled() ? Qt::Checked : Qt::Unchecked);
i->setToolTip(2, tr("Whether the positional audio feature of this plugin should be enabled"));
} else {
i->setToolTip(2, tr("This plugin does not provide support for positional audio"));
}
i->setCheckState(3, currentPlugin->isKeyboardMonitoringAllowed() ? Qt::Checked : Qt::Unchecked);
i->setToolTip(3, tr("Whether this plugin has the permission to be listening to all keyboard events that occur "
"while Mumble has focus"));
i->setText(0, currentPlugin->getName());
i->setToolTip(0, currentPlugin->getDescription().toHtmlEscaped());
i->setToolTip(1, tr("Whether this plugin should be enabled"));
i->setData(0, Qt::UserRole, currentPlugin->getID());
on_qtwPlugins_itemChanged(i, 1);
on_qtwPlugins_itemChanged(i, 2);
on_qtwPlugins_itemChanged(i, 3);
}
qtwPlugins->setCurrentItem(qtwPlugins->topLevelItem(0));
on_qtwPlugins_currentItemChanged(qtwPlugins->topLevelItem(0), NULL);
}
void PluginConfig::on_qtwPlugins_currentItemChanged(QTreeWidgetItem *current, QTreeWidgetItem *) {
const_plugin_ptr_t plugin = pluginForItem(current);
if (plugin) {
qpbAbout->setEnabled(plugin->providesAboutDialog());
qpbConfig->setEnabled(plugin->providesConfigDialog());
qpbUnload->setEnabled(true);
} else {
qpbAbout->setEnabled(false);
qpbConfig->setEnabled(false);
qpbUnload->setEnabled(false);
}
}
void PluginConfig::on_qtwPlugins_itemChanged(QTreeWidgetItem *item, int column) {
const_plugin_ptr_t plugin = pluginForItem(item);
if (!plugin) {
return;
}
switch (column) {
case 1:
case 3:
item->setData(column, Qt::AccessibleDescriptionRole,
item->checkState(column) == Qt::Checked ? tr("checked") : tr("unchecked"));
break;
case 2:
if (plugin->getFeatures() & MUMBLE_FEATURE_POSITIONAL) {
item->setData(column, Qt::AccessibleDescriptionRole,
item->checkState(column) == Qt::Checked ? tr("checked") : tr("unchecked"));
} else {
item->setData(column, Qt::AccessibleDescriptionRole, tr("Not available"));
}
break;
}
}