diff options
Diffstat (limited to 'src/mumble/Plugin.h')
-rw-r--r-- | src/mumble/Plugin.h | 417 |
1 files changed, 417 insertions, 0 deletions
diff --git a/src/mumble/Plugin.h b/src/mumble/Plugin.h new file mode 100644 index 000000000..049e622f2 --- /dev/null +++ b/src/mumble/Plugin.h @@ -0,0 +1,417 @@ +// Copyright 2021 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_ + +#include "MumbleAPI_v_1_0_x.h" +#include "PluginComponents_v_1_0_x.h" +#include "PositionalData.h" +#include "MumblePlugin_v_1_0_x.h" + +#include <QObject> +#include <QReadWriteLock> +#include <QString> +#include <QLibrary> +#include <QMutex> +#include <QUrl> + +#include <stdexcept> +#include <memory> + +/// 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; + + // 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; + + // 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(); + /// 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 pluign 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 pluign 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 pluign 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 contructor arguments + /// @param args A list of args passed to the contructor 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 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 |