mumble-voip_mumble/src/mumble/Plugin.cpp

776 lines
24 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 "Plugin.h"
#include "API.h"
#include "Version.h"
#include <QMutexLocker>
#include <QWriteLocker>
#include <cstring>
// initialize the static ID counter
plugin_id_t Plugin::s_nextID = 1;
QMutex Plugin::s_idLock;
void assertPluginLoaded(const Plugin *plugin) {
// don't throw and exception in release build
if (!plugin->isLoaded()) {
#ifdef QT_DEBUG
throw std::runtime_error("Attempting to access plugin but it is not loaded!");
#else
qWarning("Plugin assertion failed: Assumed plugin with ID %d to be loaded but it wasn't!", plugin->getID());
#endif
}
}
Plugin::Plugin(QString path, bool isBuiltIn, QObject *p)
: QObject(p), m_lib(path), m_pluginPath(path), m_pluginIsLoaded(false), m_pluginLock(QReadWriteLock::NonRecursive),
m_pluginFnc(), m_isBuiltIn(isBuiltIn), m_positionalDataIsEnabled(true), m_positionalDataIsActive(false),
m_mayMonitorKeyboard(false) {
// See if the plugin is loadable in the first place unless it is a built-in plugin
m_pluginIsValid = isBuiltIn || m_lib.load();
if (!m_pluginIsValid) {
// throw an exception to indicate that the plugin isn't valid
throw PluginError(
QString::fromLatin1("Unable to load the specified library (%1)").arg(m_lib.errorString()).toStdString());
}
// acquire id-lock in order to assign an ID to this plugin
QMutexLocker lock(&Plugin::s_idLock);
m_pluginID = Plugin::s_nextID;
Plugin::s_nextID++;
}
Plugin::~Plugin() {
if (isLoaded()) {
shutdown();
}
if (m_lib.isLoaded()) {
m_lib.unload();
}
}
QString Plugin::extractWrappedString(MumbleStringWrapper wrapper) const {
QString wrappedString = QString::fromUtf8(wrapper.data, static_cast< int >(wrapper.size));
if (wrapper.needsReleasing) {
releaseResource(static_cast< const void * >(wrapper.data));
}
return wrappedString;
}
bool Plugin::doInitialize() {
resolveFunctionPointers();
return m_pluginIsValid;
}
void Plugin::resolveFunctionPointers() {
if (isValid()) {
// The corresponding library was loaded -> try to locate all API functions and provide defaults for
// the missing ones
QWriteLocker lock(&m_pluginLock);
// resolve the mandatory functions first
m_pluginFnc.init = reinterpret_cast< decltype(MumblePluginFunctions::init) >(m_lib.resolve("mumble_init"));
m_pluginFnc.shutdown =
reinterpret_cast< decltype(MumblePluginFunctions::shutdown) >(m_lib.resolve("mumble_shutdown"));
m_pluginFnc.getName =
reinterpret_cast< decltype(MumblePluginFunctions::getName) >(m_lib.resolve("mumble_getName"));
m_pluginFnc.getAPIVersion =
reinterpret_cast< decltype(MumblePluginFunctions::getAPIVersion) >(m_lib.resolve("mumble_getAPIVersion"));
m_pluginFnc.registerAPIFunctions = reinterpret_cast< decltype(MumblePluginFunctions::registerAPIFunctions) >(
m_lib.resolve("mumble_registerAPIFunctions"));
m_pluginFnc.releaseResource = reinterpret_cast< decltype(MumblePluginFunctions::releaseResource) >(
m_lib.resolve("mumble_releaseResource"));
// validate that all those functions are available in the loaded lib
m_pluginIsValid = m_pluginFnc.init && m_pluginFnc.shutdown && m_pluginFnc.getName && m_pluginFnc.getAPIVersion
&& m_pluginFnc.registerAPIFunctions && m_pluginFnc.releaseResource;
if (!m_pluginIsValid) {
// Don't bother trying to resolve any other functions
#ifdef MUMBLE_PLUGIN_DEBUG
# define CHECK_AND_LOG(name) \
if (!m_pluginFnc.name) { \
qDebug("\t\"%s\" is missing the %s() function", qPrintable(m_pluginPath), "mumble_" #name); \
}
CHECK_AND_LOG(init);
CHECK_AND_LOG(shutdown);
CHECK_AND_LOG(getName);
CHECK_AND_LOG(getAPIVersion);
CHECK_AND_LOG(registerAPIFunctions);
CHECK_AND_LOG(releaseResource);
# undef CHECK_AND_LOG
#endif
return;
}
m_pluginFnc.getPluginFunctionsVersion =
reinterpret_cast< decltype(MumblePluginFunctions::getPluginFunctionsVersion) >(
m_lib.resolve("mumble_getPluginFunctionsVersion"));
const mumble_version_t pluginFunctionsVersion = getPluginFunctionsVersion();
// The mandatory functions are there, now see if any optional functions are implemented as well
m_pluginFnc.setMumbleInfo =
reinterpret_cast< decltype(MumblePluginFunctions::setMumbleInfo) >(m_lib.resolve("mumble_setMumbleInfo"));
m_pluginFnc.getVersion =
reinterpret_cast< decltype(MumblePluginFunctions::getVersion) >(m_lib.resolve("mumble_getVersion"));
m_pluginFnc.getAuthor =
reinterpret_cast< decltype(MumblePluginFunctions::getAuthor) >(m_lib.resolve("mumble_getAuthor"));
m_pluginFnc.getDescription =
reinterpret_cast< decltype(MumblePluginFunctions::getDescription) >(m_lib.resolve("mumble_getDescription"));
m_pluginFnc.getFeatures =
reinterpret_cast< decltype(MumblePluginFunctions::getFeatures) >(m_lib.resolve("mumble_getFeatures"));
m_pluginFnc.deactivateFeatures = reinterpret_cast< decltype(MumblePluginFunctions::deactivateFeatures) >(
m_lib.resolve("mumble_deactivateFeatures"));
m_pluginFnc.initPositionalData = reinterpret_cast< decltype(MumblePluginFunctions::initPositionalData) >(
m_lib.resolve("mumble_initPositionalData"));
m_pluginFnc.fetchPositionalData = reinterpret_cast< decltype(MumblePluginFunctions::fetchPositionalData) >(
m_lib.resolve("mumble_fetchPositionalData"));
m_pluginFnc.shutdownPositionalData =
reinterpret_cast< decltype(MumblePluginFunctions::shutdownPositionalData) >(
m_lib.resolve("mumble_shutdownPositionalData"));
m_pluginFnc.onServerConnected = reinterpret_cast< decltype(MumblePluginFunctions::onServerConnected) >(
m_lib.resolve("mumble_onServerConnected"));
m_pluginFnc.onServerDisconnected = reinterpret_cast< decltype(MumblePluginFunctions::onServerDisconnected) >(
m_lib.resolve("mumble_onServerDisconnected"));
m_pluginFnc.onChannelEntered = reinterpret_cast< decltype(MumblePluginFunctions::onChannelEntered) >(
m_lib.resolve("mumble_onChannelEntered"));
m_pluginFnc.onChannelExited = reinterpret_cast< decltype(MumblePluginFunctions::onChannelExited) >(
m_lib.resolve("mumble_onChannelExited"));
m_pluginFnc.onUserTalkingStateChanged =
reinterpret_cast< decltype(MumblePluginFunctions::onUserTalkingStateChanged) >(
m_lib.resolve("mumble_onUserTalkingStateChanged"));
m_pluginFnc.onReceiveData =
reinterpret_cast< decltype(MumblePluginFunctions::onReceiveData) >(m_lib.resolve("mumble_onReceiveData"));
m_pluginFnc.onAudioInput =
reinterpret_cast< decltype(MumblePluginFunctions::onAudioInput) >(m_lib.resolve("mumble_onAudioInput"));
m_pluginFnc.onAudioSourceFetched = reinterpret_cast< decltype(MumblePluginFunctions::onAudioSourceFetched) >(
m_lib.resolve("mumble_onAudioSourceFetched"));
m_pluginFnc.onAudioOutputAboutToPlay =
reinterpret_cast< decltype(MumblePluginFunctions::onAudioOutputAboutToPlay) >(
m_lib.resolve("mumble_onAudioOutputAboutToPlay"));
m_pluginFnc.onServerSynchronized = reinterpret_cast< decltype(MumblePluginFunctions::onServerSynchronized) >(
m_lib.resolve("mumble_onServerSynchronized"));
m_pluginFnc.onUserAdded =
reinterpret_cast< decltype(MumblePluginFunctions::onUserAdded) >(m_lib.resolve("mumble_onUserAdded"));
m_pluginFnc.onUserRemoved =
reinterpret_cast< decltype(MumblePluginFunctions::onUserRemoved) >(m_lib.resolve("mumble_onUserRemoved"));
m_pluginFnc.onChannelAdded =
reinterpret_cast< decltype(MumblePluginFunctions::onChannelAdded) >(m_lib.resolve("mumble_onChannelAdded"));
m_pluginFnc.onChannelRemoved = reinterpret_cast< decltype(MumblePluginFunctions::onChannelRemoved) >(
m_lib.resolve("mumble_onChannelRemoved"));
m_pluginFnc.onChannelRenamed = reinterpret_cast< decltype(MumblePluginFunctions::onChannelRenamed) >(
m_lib.resolve("mumble_onChannelRenamed"));
m_pluginFnc.onKeyEvent =
reinterpret_cast< decltype(MumblePluginFunctions::onKeyEvent) >(m_lib.resolve("mumble_onKeyEvent"));
m_pluginFnc.hasUpdate =
reinterpret_cast< decltype(MumblePluginFunctions::hasUpdate) >(m_lib.resolve("mumble_hasUpdate"));
m_pluginFnc.getUpdateDownloadURL = reinterpret_cast< decltype(MumblePluginFunctions::getUpdateDownloadURL) >(
m_lib.resolve("mumble_getUpdateDownloadURL"));
if (pluginFunctionsVersion >= mumble_version_t({ 1, 1, 0 })) {
// Functions introduced with plugin functions scheme v1.1.0
m_pluginFnc.getPositionalDataContextPrefix =
reinterpret_cast< decltype(MumblePluginFunctions::getPositionalDataContextPrefix) >(
m_lib.resolve("mumble_getPositionalDataContextPrefix"));
}
#ifdef MUMBLE_PLUGIN_DEBUG
# define CHECK_AND_LOG(name) \
qDebug("\t" \
"mumble_" #name ": %s", \
(m_pluginFnc.name == nullptr ? "no" : "yes"))
qDebug(">>>> Found optional functions for plugin \"%s\"", qUtf8Printable(m_pluginPath));
CHECK_AND_LOG(setMumbleInfo);
CHECK_AND_LOG(getVersion);
CHECK_AND_LOG(getAuthor);
CHECK_AND_LOG(getDescription);
CHECK_AND_LOG(getFeatures);
CHECK_AND_LOG(deactivateFeatures);
CHECK_AND_LOG(initPositionalData);
CHECK_AND_LOG(fetchPositionalData);
CHECK_AND_LOG(shutdownPositionalData);
CHECK_AND_LOG(getPositionalDataContextPrefix);
CHECK_AND_LOG(onServerConnected);
CHECK_AND_LOG(onServerDisconnected);
CHECK_AND_LOG(onChannelEntered);
CHECK_AND_LOG(onChannelExited);
CHECK_AND_LOG(onUserTalkingStateChanged);
CHECK_AND_LOG(onReceiveData);
CHECK_AND_LOG(onAudioInput);
CHECK_AND_LOG(onAudioSourceFetched);
CHECK_AND_LOG(onAudioOutputAboutToPlay);
CHECK_AND_LOG(onServerSynchronized);
CHECK_AND_LOG(onUserAdded);
CHECK_AND_LOG(onUserRemoved);
CHECK_AND_LOG(onChannelAdded);
CHECK_AND_LOG(onChannelRemoved);
CHECK_AND_LOG(onChannelRenamed);
CHECK_AND_LOG(onKeyEvent);
CHECK_AND_LOG(hasUpdate);
CHECK_AND_LOG(getUpdateDownloadURL);
qDebug("<<<<");
#endif
// If positional audio is to be supported, all three functions (init, fetch, shutdown) have to be implemented
// For PA it is all or nothing
if (!(m_pluginFnc.initPositionalData && m_pluginFnc.fetchPositionalData && m_pluginFnc.shutdownPositionalData)
&& (m_pluginFnc.initPositionalData || m_pluginFnc.fetchPositionalData
|| m_pluginFnc.shutdownPositionalData)) {
m_pluginFnc.initPositionalData = nullptr;
m_pluginFnc.fetchPositionalData = nullptr;
m_pluginFnc.shutdownPositionalData = nullptr;
#ifdef MUMBLE_PLUGIN_DEBUG
qDebug("\t\"%s\" has only partially implemented positional data functions -> deactivating all of them",
qPrintable(m_pluginPath));
#endif
}
}
}
bool Plugin::isValid() const {
PluginReadLocker lock(&m_pluginLock);
return m_pluginIsValid;
}
bool Plugin::isLoaded() const {
PluginReadLocker lock(&m_pluginLock);
return m_pluginIsLoaded;
}
plugin_id_t Plugin::getID() const {
PluginReadLocker lock(&m_pluginLock);
return m_pluginID;
}
bool Plugin::isBuiltInPlugin() const {
PluginReadLocker lock(&m_pluginLock);
return m_isBuiltIn;
}
QString Plugin::getFilePath() const {
PluginReadLocker lock(&m_pluginLock);
return m_pluginPath;
}
bool Plugin::isPositionalDataEnabled() const {
PluginReadLocker lock(&m_pluginLock);
return m_positionalDataIsEnabled;
}
void Plugin::enablePositionalData(bool enable) {
QWriteLocker lock(&m_pluginLock);
m_positionalDataIsEnabled = enable;
}
bool Plugin::isPositionalDataActive() const {
PluginReadLocker lock(&m_pluginLock);
return m_positionalDataIsActive;
}
void Plugin::allowKeyboardMonitoring(bool allow) {
QWriteLocker lock(&m_pluginLock);
m_mayMonitorKeyboard = allow;
}
bool Plugin::isKeyboardMonitoringAllowed() const {
PluginReadLocker lock(&m_pluginLock);
return m_mayMonitorKeyboard;
}
mumble_error_t Plugin::init() {
{
QReadLocker lock(&m_pluginLock);
if (m_pluginIsLoaded) {
return MUMBLE_STATUS_OK;
}
}
//////////////////////////////
// Step 1: Introduce ourselves (inform the plugin about Mumble's (API) version
// Get Mumble version
Version::component_t mumbleMajor, mumbleMinor, mumblePatch;
Version::getComponents(mumbleMajor, mumbleMinor, mumblePatch);
// Require API version 1.0.0 as the minimal supported one
setMumbleInfo({ mumbleMajor, mumbleMinor, mumblePatch }, MUMBLE_PLUGIN_API_VERSION, { 1, 0, 0 });
//////////////////////////////
// Step 2: Provide the API functions to the plugin
const mumble_version_t apiVersion = getAPIVersion();
if (apiVersion >= mumble_version_t({ 1, 0, 0 }) && apiVersion < mumble_version_t({ 1, 2, 0 })) {
MumbleAPI_v_1_0_x api = API::getMumbleAPI_v_1_0_x();
registerAPIFunctions(&api);
} else if (apiVersion >= mumble_version_t({ 1, 2, 0 }) && apiVersion < mumble_version_t({ 1, 3, 0 })) {
MumbleAPI_v_1_2_x api = API::getMumbleAPI_v_1_2_x();
registerAPIFunctions(&api);
} else {
// The API version could not be obtained -> this is an invalid plugin that shouldn't have been loaded in the
// first place
qWarning("Unable to obtain requested MumbleAPI version");
return MUMBLE_EC_INVALID_API_VERSION;
}
//////////////////////////////
// Step 3: Actually try to load the plugin
mumble_error_t retStatus;
if (m_pluginFnc.init) {
retStatus = m_pluginFnc.init(m_pluginID);
} else {
retStatus = MUMBLE_EC_GENERIC_ERROR;
}
{
QWriteLocker lock(&m_pluginLock);
m_pluginIsLoaded = retStatus == MUMBLE_STATUS_OK;
}
return retStatus;
}
void Plugin::shutdown() {
bool posDataActive;
{
QReadLocker rLock(&m_pluginLock);
if (!m_pluginIsLoaded) {
return;
}
posDataActive = m_positionalDataIsActive;
}
if (posDataActive) {
shutdownPositionalData();
}
if (m_pluginFnc.shutdown) {
m_pluginFnc.shutdown();
}
{
QWriteLocker lock(&m_pluginLock);
m_pluginIsLoaded = false;
}
}
QString Plugin::getName() const {
if (m_pluginFnc.getName) {
return extractWrappedString(m_pluginFnc.getName());
} else {
return QString::fromLatin1("Unknown plugin");
}
}
mumble_version_t Plugin::getAPIVersion() const {
if (m_pluginFnc.getAPIVersion) {
return m_pluginFnc.getAPIVersion();
} else {
return MUMBLE_VERSION_UNKNOWN;
}
}
mumble_version_t Plugin::getPluginFunctionsVersion() const {
if (m_pluginFnc.getPluginFunctionsVersion) {
return m_pluginFnc.getPluginFunctionsVersion();
} else {
return mumble_version_t({ 1, 0, 0 });
}
}
void Plugin::registerAPIFunctions(void *api) const {
if (m_pluginFnc.registerAPIFunctions) {
m_pluginFnc.registerAPIFunctions(api);
}
}
void Plugin::releaseResource(const void *pointer) const {
if (m_pluginFnc.releaseResource) {
m_pluginFnc.releaseResource(pointer);
}
}
void Plugin::setMumbleInfo(mumble_version_t mumbleVersion, mumble_version_t mumbleAPIVersion,
mumble_version_t minimalExpectedAPIVersion) const {
if (m_pluginFnc.setMumbleInfo) {
m_pluginFnc.setMumbleInfo(mumbleVersion, mumbleAPIVersion, minimalExpectedAPIVersion);
}
}
mumble_version_t Plugin::getVersion() const {
if (m_pluginFnc.getVersion) {
return m_pluginFnc.getVersion();
} else {
return MUMBLE_VERSION_UNKNOWN;
}
}
QString Plugin::getAuthor() const {
if (m_pluginFnc.getAuthor) {
return extractWrappedString(m_pluginFnc.getAuthor());
} else {
return QString::fromLatin1("Unknown");
}
}
QString Plugin::getDescription() const {
if (m_pluginFnc.getDescription) {
return extractWrappedString(m_pluginFnc.getDescription());
} else {
return QString::fromLatin1("No description provided");
}
}
uint32_t Plugin::getFeatures() const {
if (m_pluginFnc.getFeatures) {
return m_pluginFnc.getFeatures();
} else {
return MUMBLE_FEATURE_NONE;
}
}
uint32_t Plugin::deactivateFeatures(uint32_t features) const {
assertPluginLoaded(this);
if (m_pluginFnc.deactivateFeatures) {
return m_pluginFnc.deactivateFeatures(features);
} else {
return features;
}
}
bool Plugin::showAboutDialog(QWidget *parent) const {
assertPluginLoaded(this);
Q_UNUSED(parent);
return false;
}
bool Plugin::showConfigDialog(QWidget *parent) const {
assertPluginLoaded(this);
Q_UNUSED(parent);
return false;
}
uint8_t Plugin::initPositionalData(const char *const *programNames, const uint64_t *programPIDs, size_t programCount) {
assertPluginLoaded(this);
if (m_pluginFnc.initPositionalData) {
uint8_t returnCode = m_pluginFnc.initPositionalData(programNames, programPIDs, programCount);
{
QWriteLocker lock(&m_pluginLock);
m_positionalDataIsActive = returnCode == MUMBLE_PDEC_OK;
}
return returnCode;
} else {
return MUMBLE_PDEC_ERROR_PERM;
}
}
bool Plugin::fetchPositionalData(Position3D &avatarPos, Vector3D &avatarDir, Vector3D &avatarAxis,
Position3D &cameraPos, Vector3D &cameraDir, Vector3D &cameraAxis, QString &context,
QString &identity) const {
assertPluginLoaded(this);
if (m_pluginFnc.fetchPositionalData) {
const char *contextPtr = "";
const char *identityPtr = "";
bool retStatus = m_pluginFnc.fetchPositionalData(
static_cast< float * >(avatarPos), static_cast< float * >(avatarDir), static_cast< float * >(avatarAxis),
static_cast< float * >(cameraPos), static_cast< float * >(cameraDir), static_cast< float * >(cameraAxis),
&contextPtr, &identityPtr);
context = QString::fromUtf8(contextPtr);
identity = QString::fromUtf8(identityPtr);
return retStatus;
} else {
avatarPos.toZero();
avatarDir.toZero();
avatarAxis.toZero();
cameraPos.toZero();
cameraDir.toZero();
cameraAxis.toZero();
context = QString();
identity = QString();
return false;
}
}
void Plugin::shutdownPositionalData() {
assertPluginLoaded(this);
if (m_pluginFnc.shutdownPositionalData) {
m_positionalDataIsActive = false;
m_pluginFnc.shutdownPositionalData();
}
}
QString Plugin::getPositionalDataContextPrefix() const {
assertPluginLoaded(this);
if (m_pluginFnc.getPositionalDataContextPrefix) {
return extractWrappedString(m_pluginFnc.getPositionalDataContextPrefix());
} else {
// Use plugin's name as default context prefix
return getName();
}
}
void Plugin::onServerConnected(mumble_connection_t connection) const {
assertPluginLoaded(this);
if (m_pluginFnc.onServerConnected) {
m_pluginFnc.onServerConnected(connection);
}
}
void Plugin::onServerDisconnected(mumble_connection_t connection) const {
assertPluginLoaded(this);
if (m_pluginFnc.onServerDisconnected) {
m_pluginFnc.onServerDisconnected(connection);
}
}
void Plugin::onChannelEntered(mumble_connection_t connection, mumble_userid_t userID,
mumble_channelid_t previousChannelID, mumble_channelid_t newChannelID) const {
assertPluginLoaded(this);
if (m_pluginFnc.onChannelEntered) {
m_pluginFnc.onChannelEntered(connection, userID, previousChannelID, newChannelID);
}
}
void Plugin::onChannelExited(mumble_connection_t connection, mumble_userid_t userID,
mumble_channelid_t channelID) const {
assertPluginLoaded(this);
if (m_pluginFnc.onChannelExited) {
m_pluginFnc.onChannelExited(connection, userID, channelID);
}
}
void Plugin::onUserTalkingStateChanged(mumble_connection_t connection, mumble_userid_t userID,
mumble_talking_state_t talkingState) const {
assertPluginLoaded(this);
if (m_pluginFnc.onUserTalkingStateChanged) {
m_pluginFnc.onUserTalkingStateChanged(connection, userID, talkingState);
}
}
bool Plugin::onReceiveData(mumble_connection_t connection, mumble_userid_t sender, const uint8_t *data,
size_t dataLength, const char *dataID) const {
assertPluginLoaded(this);
if (m_pluginFnc.onReceiveData) {
return m_pluginFnc.onReceiveData(connection, sender, data, dataLength, dataID);
} else {
return false;
}
}
bool Plugin::onAudioInput(short *inputPCM, uint32_t sampleCount, uint16_t channelCount, uint32_t sampleRate,
bool isSpeech) const {
assertPluginLoaded(this);
if (m_pluginFnc.onAudioInput) {
return m_pluginFnc.onAudioInput(inputPCM, sampleCount, channelCount, sampleRate, isSpeech);
} else {
return false;
}
}
bool Plugin::onAudioSourceFetched(float *outputPCM, uint32_t sampleCount, uint16_t channelCount, uint32_t sampleRate,
bool isSpeech, mumble_userid_t userID) const {
assertPluginLoaded(this);
if (m_pluginFnc.onAudioSourceFetched) {
return m_pluginFnc.onAudioSourceFetched(outputPCM, sampleCount, channelCount, sampleRate, isSpeech, userID);
} else {
return false;
}
}
bool Plugin::onAudioOutputAboutToPlay(float *outputPCM, uint32_t sampleCount, uint16_t channelCount,
uint32_t sampleRate) const {
assertPluginLoaded(this);
if (m_pluginFnc.onAudioOutputAboutToPlay) {
return m_pluginFnc.onAudioOutputAboutToPlay(outputPCM, sampleCount, channelCount, sampleRate);
} else {
return false;
}
}
void Plugin::onServerSynchronized(mumble_connection_t connection) const {
assertPluginLoaded(this);
if (m_pluginFnc.onServerSynchronized) {
m_pluginFnc.onServerSynchronized(connection);
}
}
void Plugin::onUserAdded(mumble_connection_t connection, mumble_userid_t userID) const {
assertPluginLoaded(this);
if (m_pluginFnc.onUserAdded) {
m_pluginFnc.onUserAdded(connection, userID);
}
}
void Plugin::onUserRemoved(mumble_connection_t connection, mumble_userid_t userID) const {
assertPluginLoaded(this);
if (m_pluginFnc.onUserRemoved) {
m_pluginFnc.onUserRemoved(connection, userID);
}
}
void Plugin::onChannelAdded(mumble_connection_t connection, mumble_channelid_t channelID) const {
assertPluginLoaded(this);
if (m_pluginFnc.onChannelAdded) {
m_pluginFnc.onChannelAdded(connection, channelID);
}
}
void Plugin::onChannelRemoved(mumble_connection_t connection, mumble_channelid_t channelID) const {
assertPluginLoaded(this);
if (m_pluginFnc.onChannelRemoved) {
m_pluginFnc.onChannelRemoved(connection, channelID);
}
}
void Plugin::onChannelRenamed(mumble_connection_t connection, mumble_channelid_t channelID) const {
assertPluginLoaded(this);
if (m_pluginFnc.onChannelRenamed) {
m_pluginFnc.onChannelRenamed(connection, channelID);
}
}
void Plugin::onKeyEvent(mumble_keycode_t keyCode, bool wasPress) const {
assertPluginLoaded(this);
if (!m_mayMonitorKeyboard) {
// Keyboard monitoring is forbidden for this plugin
return;
}
if (m_pluginFnc.onKeyEvent) {
m_pluginFnc.onKeyEvent(static_cast< std::uint32_t >(keyCode), wasPress);
}
}
bool Plugin::hasUpdate() const {
if (m_pluginFnc.hasUpdate) {
return m_pluginFnc.hasUpdate();
} else {
// A plugin that doesn't implement this function is assumed to never know about
// any potential updates
return false;
}
}
QUrl Plugin::getUpdateDownloadURL() const {
if (m_pluginFnc.getUpdateDownloadURL) {
return QUrl(extractWrappedString(m_pluginFnc.getUpdateDownloadURL()));
} else {
// Return an empty URL as a fallback
return QUrl();
}
}
bool Plugin::providesAboutDialog() const {
return false;
}
bool Plugin::providesConfigDialog() const {
return false;
}
/////////////////// Implementation of the PluginReadLocker /////////////////////////
PluginReadLocker::PluginReadLocker(QReadWriteLock *lock) : m_lock(lock), m_unlocked(false) {
relock();
}
void PluginReadLocker::unlock() {
if (!m_lock) {
// do nothgin for nullptr
return;
}
m_unlocked = true;
m_lock->unlock();
}
void PluginReadLocker::relock() {
if (!m_lock) {
// do nothing for a nullptr
return;
}
// First try to lock for read-access
if (!m_lock->tryLockForRead()) {
// if that fails, we'll try to lock for write-access
// That will only succeed in the case that the current thread holds the write-access to this lock already which
// caused the previous attempt to lock for reading to fail (by design of the QtReadWriteLock). As we are in the
// thread with the write-access, it means that this threads has asked for read-access on top of it which we will
// grant (in contrast of QtReadLocker) because if you have the permission to change something you surely should
// have permission to read it. This assumes that the thread won't try to read data it temporarily has corrupted.
if (!m_lock->tryLockForWrite()) {
// If we couldn't lock for write at this point, it means another thread has write-access granted by the lock
// so we'll have to wait in order to gain regular read-access as would be with QtReadLocker
m_lock->lockForRead();
}
}
m_unlocked = false;
}
PluginReadLocker::~PluginReadLocker() {
if (m_lock && !m_unlocked) {
// unlock the lock if it isn't nullptr
m_lock->unlock();
}
}