mumble-voip_mumble/src/mumble/Plugin.h

442 lines
22 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>.
#ifndef MUMBLE_MUMBLE_PLUGIN_H_
#define MUMBLE_MUMBLE_PLUGIN_H_
#define MUMBLE_PLUGIN_NO_DEFAULT_FUNCTION_DEFINITIONS
#include "MumblePlugin.h"
#undef MUMBLE_PLUGIN_NO_DEFAULT_FUNCTION_DEFINITIONS
#include "MumbleAPI_structs.h"
#include "PositionalData.h"
#include <QLibrary>
#include <QMutex>
#include <QObject>
#include <QReadWriteLock>
#include <QString>
#include <QUrl>
#include <memory>
#include <stdexcept>
/// A struct for holding the function pointers to the functions inside the plugin's library
/// For the documentation of those functions, see the plugin's header file (the one used when developing a plugin)
struct MumblePluginFunctions {
decltype(&mumble_init) init;
decltype(&mumble_shutdown) shutdown;
decltype(&mumble_getName) getName;
decltype(&mumble_getAPIVersion) getAPIVersion;
decltype(&mumble_registerAPIFunctions) registerAPIFunctions;
decltype(&mumble_releaseResource) releaseResource;
decltype(&mumble_getPluginFunctionsVersion) getPluginFunctionsVersion;
// Further utility functions the plugin may implement
decltype(&mumble_setMumbleInfo) setMumbleInfo;
decltype(&mumble_getVersion) getVersion;
decltype(&mumble_getAuthor) getAuthor;
decltype(&mumble_getDescription) getDescription;
decltype(&mumble_getFeatures) getFeatures;
decltype(&mumble_deactivateFeatures) deactivateFeatures;
// Functions for dealing with positional audio (or rather the fetching of the needed data)
decltype(&mumble_initPositionalData) initPositionalData;
decltype(&mumble_fetchPositionalData) fetchPositionalData;
decltype(&mumble_shutdownPositionalData) shutdownPositionalData;
decltype(&mumble_getPositionalDataContextPrefix) getPositionalDataContextPrefix;
// Callback functions and EventHandlers
decltype(&mumble_onServerConnected) onServerConnected;
decltype(&mumble_onServerDisconnected) onServerDisconnected;
decltype(&mumble_onChannelEntered) onChannelEntered;
decltype(&mumble_onChannelExited) onChannelExited;
decltype(&mumble_onUserTalkingStateChanged) onUserTalkingStateChanged;
decltype(&mumble_onReceiveData) onReceiveData;
decltype(&mumble_onAudioInput) onAudioInput;
decltype(&mumble_onAudioSourceFetched) onAudioSourceFetched;
decltype(&mumble_onAudioOutputAboutToPlay) onAudioOutputAboutToPlay;
decltype(&mumble_onServerSynchronized) onServerSynchronized;
decltype(&mumble_onUserAdded) onUserAdded;
decltype(&mumble_onUserRemoved) onUserRemoved;
decltype(&mumble_onChannelAdded) onChannelAdded;
decltype(&mumble_onChannelRemoved) onChannelRemoved;
decltype(&mumble_onChannelRenamed) onChannelRenamed;
decltype(&mumble_onKeyEvent) onKeyEvent;
// Plugin updates
decltype(&mumble_hasUpdate) hasUpdate;
decltype(&mumble_getUpdateDownloadURL) getUpdateDownloadURL;
};
/// An exception that is being thrown by a plugin whenever it encounters an error
class PluginError : public std::runtime_error {
public:
// inherit constructors of runtime_error
using std::runtime_error::runtime_error;
};
/// An implementation similar to QReadLocker except that this one allows to lock on a lock the same thread is already
/// holding a write-lock on. This could also result in obtaining a write-lock though so it shouldn't be used for code
/// regions that take quite some time and rely on other readers still having access to the locked object.
class PluginReadLocker {
protected:
/// The lock this lock-guard is acting upon
QReadWriteLock *m_lock;
/// A flag indicating whether the lock has been unlocked (manually) and thus doesn't have to be unlocked
/// in the destructor.
bool m_unlocked;
public:
/// Constructor of the PluginReadLocker. If the passed lock-pointer is not nullptr, the constructor will
/// already lock the provided lock.
///
/// @param lock A pointer to the QReadWriteLock that shall be managed by this object. May be nullptr
PluginReadLocker(QReadWriteLock *lock);
/// Locks this lock again after it has been unlocked before (Locking a locked lock results in a runtime error)
void relock();
/// Unlocks this lock
void unlock();
~PluginReadLocker();
};
class Plugin;
/// Typedef for the plugin ID
typedef uint32_t plugin_id_t;
/// Typedef for a plugin pointer
typedef std::shared_ptr< Plugin > plugin_ptr_t;
/// Typedef for a const plugin pointer
typedef std::shared_ptr< const Plugin > const_plugin_ptr_t;
/// A class representing a plugin library attached to Mumble. It can be used to manage (load/unload) and access plugin
/// libraries.
class Plugin : public QObject {
friend class PluginManager;
friend class PluginConfig;
private:
Q_OBJECT
Q_DISABLE_COPY(Plugin)
protected:
/// A mutex guarding Plugin::nextID
static QMutex s_idLock;
/// The ID of the plugin that will be loaded next. Whenever accessing this field, Plugin::idLock should be locked.
static plugin_id_t s_nextID;
/// Constructor of the Plugin.
///
/// @param path The path to the plugin's shared library file. This path has to exist unless isBuiltIn is true
/// @param isBuiltIn A flag indicating that this is a plugin built into Mumble itself and is does not backed by a
/// shared library
/// @param p A pointer to a QObject representing the parent of this object or nullptr if there is no parent
Plugin(QString path, bool isBuiltIn = false, QObject *p = nullptr);
/// A flag indicating whether this plugin is valid. It is mainly used throughout the plugin's initialization.
bool m_pluginIsValid;
/// The QLibrary representing the shared library of this plugin
QLibrary m_lib;
/// The path to the shared library file in the host's filesystem
QString m_pluginPath;
/// The unique ID of this plugin. Note though that this ID is not suitable for uniquely identifying this plugin
/// between restarts of Mumble (not even between rescans of the plugins) let alone across clients.
plugin_id_t m_pluginID;
// a flag indicating whether this plugin has been loaded by calling its init function.
bool m_pluginIsLoaded;
/// The lock guarding this plugin object. Every time a member is accessed this lock should be locked accordingly.
/// After successful construction and initialization (doInitilize()), this member variable is effectively const
/// and therefore no locking is required in order to read from it!
/// In fact protecting read-accesses by a non-recursive lock can introduce deadlocks by plugins using certain
/// API functions.
mutable QReadWriteLock m_pluginLock;
/// The struct holding the function pointers to the functions in the shared library.
MumblePluginFunctions m_pluginFnc;
/// A flag indicating whether this plugin is built into Mumble and is thus not represented by a shared library.
bool m_isBuiltIn;
/// A flag indicating whether positional data gathering is enabled for this plugin (Enabled as in allowed via
/// preferences).
bool m_positionalDataIsEnabled;
/// A flag indicating whether positional data gathering is currently active (Active as in running)
bool m_positionalDataIsActive;
/// A flag indicating whether this plugin has permission to monitor keyboard events that occur while
/// Mumble has the keyboard focus.
bool m_mayMonitorKeyboard;
QString extractWrappedString(MumbleStringWrapper wrapper) const;
// Most of this class's functions are protected in order to only allow access to them via the PluginManager
// as some require additional handling before/after calling them.
/// Initializes this plugin. This function must be called directly after construction. This is guaranteed when the
/// plugin is created via Plugin::createNew
virtual bool doInitialize();
/// Resolves the function pointers in the shared library and sets the respective fields in Plugin::apiFnc
virtual void resolveFunctionPointers();
/// Enables positional data gathering for this plugin (as in allowing)
///
/// @param enable Whether to enable the data gathering
virtual void enablePositionalData(bool enable = true);
/// Allows or forbids the monitoring of keyboard events for this plugin.
///
/// @param allow Whether to allow or forbid it
virtual void allowKeyboardMonitoring(bool allow);
/// Initializes this plugin
virtual mumble_error_t init();
/// Shuts this plugin down
virtual void shutdown();
/// Delegates the struct of API function pointers to the plugin backend
///
/// @param api The pointer to the API struct
virtual void registerAPIFunctions(void *api) const;
/// Asks the plugin to release (free/delete) the resource pointed to by the given pointer
///
/// @param pointer Pointer to the resource
virtual void releaseResource(const void *pointer) const;
/// Provides the plugin backend with some version information about Mumble
///
/// @param mumbleVersion The version of the Mumble client
/// @param mumbleAPIVersion The API version used by the Mumble client
/// @param minimalExpectedAPIVersion The minimal API version expected to be used by the plugin backend
virtual void setMumbleInfo(mumble_version_t mumbleVersion, mumble_version_t mumbleAPIVersion,
mumble_version_t minimalExpectedAPIVersion) const;
/// Asks the plugin to deactivate certain features
///
/// @param features The feature list or'ed together
/// @returns The list of features that couldn't be deactivated or'ed together
virtual uint32_t deactivateFeatures(uint32_t features) const;
/// Shows an about-dialog
///
/// @parent A pointer to the QWidget that should be used as a parent
/// @returns Whether the dialog could be shown successfully
virtual bool showAboutDialog(QWidget *parent) const;
/// Shows a config-dialog
///
/// @parent A pointer to the QWidget that should be used as a parent
/// @returns Whether the dialog could be shown successfully
virtual bool showConfigDialog(QWidget *parent) const;
/// Initializes the positional data gathering
///
/// @params programNames A pointer to an array of const char* representing the program names
/// @params programCount A pointer to an array of PIDs corresponding to the program names
/// @params programCount The length of the two previous arrays
virtual uint8_t initPositionalData(const char *const *programNames, const uint64_t *programPIDs,
size_t programCount);
/// Fetches the positional data
///
/// @param[out] avatarPos The position of the ingame avatar (player)
/// @param[out] avatarDir The directiion in which the avatar (player) is looking/facing
/// @param[out] avatarAxis The vector from the avatar's toes to its head
/// @param[out] cameraPos The position of the ingame camera
/// @param[out] cameraDir The direction in which the camera is looking/facing
/// @param[out] cameraAxis The vector from the camera's bottom to its top
/// @param[out] context The context of the current game-session (includes server/squad info)
/// @param[out] identity The ingame identity of the player (name)
virtual bool fetchPositionalData(Position3D &avatarPos, Vector3D &avatarDir, Vector3D &avatarAxis,
Position3D &cameraPos, Vector3D &cameraDir, Vector3D &cameraAxis, QString &context,
QString &identity) const;
/// Shuts down positional data gathering
virtual void shutdownPositionalData();
/// @returns The positional data context prefix to be used for this plugin
virtual QString getPositionalDataContextPrefix() const;
/// Called to indicate that the client has connected to a server
///
/// @param connection An object used to identify the current connection
virtual void onServerConnected(mumble_connection_t connection) const;
/// Called to indicate that the client disconnected from a server
///
/// @param connection An object used to identify the connection that has been disconnected
virtual void onServerDisconnected(mumble_connection_t connection) const;
/// Called to indicate that a user has switched its channel
///
/// @param connection An object used to identify the current connection
/// @param userID The ID of the user that switched channel
/// @param previousChannelID The ID of the channel the user came from (-1 if there is no previous channel)
/// æparam newChannelID The ID of the channel the user has switched to
virtual void onChannelEntered(mumble_connection_t connection, mumble_userid_t userID,
mumble_channelid_t previousChannelID, mumble_channelid_t newChannelID) const;
/// Called to indicate that a user exited a channel.
///
/// @param connection An object used to identify the current connection
/// @param userID The ID of the user that switched channel
/// @param channelID The ID of the channel the user exited
virtual void onChannelExited(mumble_connection_t connection, mumble_userid_t userID,
mumble_channelid_t channelID) const;
/// Called to indicate that a user has changed its talking state
///
/// @param connection An object used to identify the current connection
/// @param userID The ID of the user that switched channel
/// @param talkingState The new talking state of the user
virtual void onUserTalkingStateChanged(mumble_connection_t connection, mumble_userid_t userID,
mumble_talking_state_t talkingState) const;
/// Called to indicate that a data packet has been received
///
/// @param connection An object used to identify the current connection
/// @param sender The ID of the user whose client sent the data
/// @param data The actual data
/// @param dataLength The length of the data array
/// @param datID The ID of the data used to determine whether this plugin handles this data or not
/// @returns Whether this plugin handled the data
virtual bool onReceiveData(mumble_connection_t connection, mumble_userid_t sender, const uint8_t *data,
size_t dataLength, const char *dataID) const;
/// Called to indicate that there is audio input
///
/// @param inputPCM A pointer to a short array representing the input PCM
/// @param sampleCount The amount of samples per channel
/// @param channelCount The amount of channels in the PCM
/// @param sampleRate The used sample rate in Hz
/// @param isSpeech Whether Mumble considers the input as speech
/// @returns Whether this plugin has modified the audio
virtual bool onAudioInput(short *inputPCM, uint32_t sampleCount, uint16_t channelCount, uint32_t sampleRate,
bool isSpeech) const;
/// Called to indicate that an audio source has been fetched
///
/// @param outputPCM A pointer to a short array representing the output PCM
/// @param sampleCount The amount of samples per channel
/// @param channelCount The amount of channels in the PCM
/// @param sampleRate The used sample rate in Hz
/// @param isSpeech Whether Mumble considers the output as speech
/// @param userID The ID of the user responsible for the output (only relevant if isSpeech == true)
/// @returns Whether this plugin has modified the audio
virtual bool onAudioSourceFetched(float *outputPCM, uint32_t sampleCount, uint16_t channelCount,
uint32_t sampleRate, bool isSpeech, mumble_userid_t userID) const;
/// Called to indicate that audio is about to be played
///
/// @param outputPCM A pointer to a short array representing the output PCM
/// @param sampleCount The amount of samples per channel
/// @param channelCount The amount of channels in the PCM
/// @param sampleRate The used sample rate in Hz
/// @returns Whether this plugin has modified the audio
virtual bool onAudioOutputAboutToPlay(float *outputPCM, uint32_t sampleCount, uint16_t channelCount,
uint32_t sampleRate) const;
/// Called when the server has synchronized with the client
///
/// @param connection An object used to identify the current connection
virtual void onServerSynchronized(mumble_connection_t connection) const;
/// Called when a new user gets added to the user model. This is the case when that new user freshly connects to the
/// server the local user is on but also when the local user connects to a server other clients are already
/// connected to (in this case this method will be called for every client already on that server).
///
/// @param connection An object used to identify the current connection
/// @param userID The ID of the user that has been added
virtual void onUserAdded(mumble_connection_t connection, mumble_userid_t userID) const;
/// Called when a user gets removed from the user model. This is the case when that user disconnects from the server
/// the local user is on but also when the local user disconnects from a server other clients are connected to (in
/// this case this method will be called for every client on that server).
///
/// @param connection An object used to identify the current connection
/// @param userID The ID of the user that has been removed
virtual void onUserRemoved(mumble_connection_t connection, mumble_userid_t userID) const;
/// Called when a new channel gets added to the user model. This is the case when a new channel is created on the
/// server the local user is on but also when the local user connects to a server that contains channels other than
/// the root-channel (in this case this method will be called for ever non-root channel on that server).
///
/// @param connection An object used to identify the current connection
/// @param channelID The ID of the channel that has been added
virtual void onChannelAdded(mumble_connection_t connection, mumble_channelid_t channelID) const;
/// Called when a channel gets removed from the user model. This is the case when a channel is removed on the server
/// the local user is on but also when the local user disconnects from a server that contains channels other than
/// the root-channel (in this case this method will be called for ever non-root channel on that server).
///
/// @param connection An object used to identify the current connection
/// @param channelID The ID of the channel that has been removed
virtual void onChannelRemoved(mumble_connection_t connection, mumble_channelid_t channelID) const;
/// Called when a channel gets renamed. This also applies when a new channel is created (thus assigning it an
/// initial name is also considered renaming).
///
/// @param connection An object used to identify the current connection
/// @param channelID The ID of the channel that has been renamed
virtual void onChannelRenamed(mumble_connection_t connection, mumble_channelid_t channelID) const;
/// Called when a key has been pressed or released while Mumble has keyboard focus.
///
/// @param keyCode The key code of the respective key. The character codes are defined
/// via the KeyCode enum. For printable 7-bit ASCII characters these codes conform
/// to the ASCII code-page with the only difference that case is not distinguished. Therefore
/// always the upper-case letter code will be used for letters.
/// @param wasPress Whether the key has been pressed (instead of released)
virtual void onKeyEvent(mumble_keycode_t keyCode, bool wasPress) const;
public:
/// A template function for instantiating new plugin objects and initializing them. The plugin will be allocated on
/// the heap and has thus to be deleted via the delete instruction.
///
/// @tparam T The type of the plugin to be instantiated
/// @tparam Ts The types of the constructor arguments
/// @param args A list of args passed to the constructor of the plugin object
/// @returns A pointer to the allocated plugin
///
/// @throws PluginError if the plugin could not be loaded
template< typename T, typename... Ts > static T *createNew(Ts &&... args) {
static_assert(std::is_base_of< Plugin, T >::value,
"The Plugin::create() can only be used to instantiate objects of base-type Plugin");
static_assert(!std::is_pointer< T >::value,
"Plugin::create() can't be used to instantiate pointers. It will return a pointer automatically");
T *instancePtr = new T(std::forward< Ts >(args)...);
// call the initialize-method and throw an exception of it doesn't succeed
if (!instancePtr->doInitialize()) {
delete instancePtr;
// Delete the constructed object to prevent a memory leak
throw PluginError("Failed to initialize plugin");
}
return instancePtr;
}
/// Destructor
virtual ~Plugin() Q_DECL_OVERRIDE;
/// @returns Whether this plugin is in a valid state
virtual bool isValid() const;
/// @returns Whether this plugin is loaded (has been initialized via Plugin::init())
virtual bool isLoaded() const Q_DECL_FINAL;
/// @returns The unique ID of this plugin. This ID holds only as long as this plugin isn't "reconstructed".
virtual plugin_id_t getID() const Q_DECL_FINAL;
/// @returns Whether this plugin is built into Mumble (thus not backed by a shared library)
virtual bool isBuiltInPlugin() const Q_DECL_FINAL;
/// @returns The path to the shared library in the host's filesystem
virtual QString getFilePath() const;
/// @returns Whether positional data gathering is enabled (as in allowed via preferences)
virtual bool isPositionalDataEnabled() const Q_DECL_FINAL;
/// @returns Whether positional data gathering is currently active (as in running)
virtual bool isPositionalDataActive() const Q_DECL_FINAL;
/// @returns Whether this plugin is currently allowed to monitor keyboard events
virtual bool isKeyboardMonitoringAllowed() const Q_DECL_FINAL;
/// @returns Whether this plugin provides an about-dialog
virtual bool providesAboutDialog() const;
/// @returns Whether this plugin provides an config-dialog
virtual bool providesConfigDialog() const;
/// @returns The name of this plugin
virtual QString getName() const;
/// @returns The API version this plugin intends to use
virtual mumble_version_t getAPIVersion() const;
/// @returns The version of the plugins function scheme this plugin is using
virtual mumble_version_t getPluginFunctionsVersion() const;
/// @returns The version of this plugin
virtual mumble_version_t getVersion() const;
/// @returns The author of this plugin
virtual QString getAuthor() const;
/// @returns The plugin's description
virtual QString getDescription() const;
/// @returns The plugin's features or'ed together (See the PluginFeature enum in MumblePlugin.h for what features
/// are available)
virtual uint32_t getFeatures() const;
/// @return Whether the plugin has found a new/updated version of itself available for download
virtual bool hasUpdate() const;
/// @return The URL to download the updated plugin. May be empty
virtual QUrl getUpdateDownloadURL() const;
};
#endif