// Copyright 2021-2022 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 . // Include the definitions of the plugin functions // Not that this will also include ../PluginComponents.h #include "../MumbleAPI_v_1_0_x.h" #include "../MumblePlugin_v_1_0_x.h" #include #include // These are just some utility functions facilitating writing logs and the like // The actual implementation of the plugin is further down std::ostream &pLog() { std::cout << "TestPlugin: "; return std::cout; } template< typename T > void pluginLog(T log) { pLog() << log << std::endl; } std::ostream &operator<<(std::ostream &stream, const mumble_version_t version) { stream << "v" << version.major << "." << version.minor << "." << version.patch; return stream; } ////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////// //////////////////// PLUGIN IMPLEMENTATION /////////////////// ////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////// mumble_api_t mumAPI; mumble_connection_t activeConnection; mumble_plugin_id_t ownID; ////////////////////////////////////////////////////////////// //////////////////// OBLIGATORY FUNCTIONS //////////////////// ////////////////////////////////////////////////////////////// // All of the following function must be implemented in order for Mumble to load the plugin mumble_error_t mumble_init(uint32_t id) { pluginLog("Initialized plugin"); ownID = id; // Print the connection ID at initialization. If not connected to a server it should be -1. pLog() << "Plugin ID is " << id << std::endl; mumAPI.log(ownID, "Intitialized"); // Little showcase for how to retrieve a setting from Mumble int64_t voiceHold; mumble_error_t error = mumAPI.getMumbleSetting_int(ownID, MUMBLE_SK_AUDIO_INPUT_VOICE_HOLD, &voiceHold); if (error == MUMBLE_STATUS_OK) { pLog() << "The voice hold is set to " << voiceHold << std::endl; } else { pluginLog("Failed to retrieve voice hold"); pLog() << mumble_errorMessage(error) << std::endl; } // MUMBLE_STATUS_OK is a macro set to the appropriate status flag (ErrorCode) // If you need to return any other status have a look at the ErrorCode enum // inside PluginComponents.h and use one of its values return MUMBLE_STATUS_OK; } void mumble_shutdown() { pluginLog("Shutdown plugin"); mumAPI.log(ownID, "Shutdown"); } MumbleStringWrapper mumble_getName() { static const char *name = "TestPlugin"; MumbleStringWrapper wrapper; wrapper.data = name; wrapper.size = strlen(name); // It's a static String and therefore doesn't need releasing wrapper.needsReleasing = false; return wrapper; } mumble_version_t mumble_getAPIVersion() { // MUMBLE_PLUGIN_API_VERSION will always contain the API version of the used header file (the one used to build // this plugin against). Thus you should always return that here in order to no have to worry about it. return MUMBLE_PLUGIN_API_VERSION; } void mumble_registerAPIFunctions(void *api) { // In this function the plugin is presented with a struct of function pointers that can be used // to interact with Mumble. Thus you should store it somewhere safe for later usage. // The pointer has to be cast to the respective API struct. You always have to cast to the same API version // as this plugin itself is using. Thus if this plugin is compiled using the API version 1.0.x (where x is an // arbitrary version) the pointer has to be cast to MumbleAPI_v_1_0_x (where x is a literal "x"). Furthermore the // struct HAS TO BE COPIED!!! Storing the pointer is not an option as it will become invalid quickly! // **If** you are using the same API version that is specified in the included header file (as you should), you // can simply use the MUMBLE_API_CAST to cast the pointer to the correct type and automatically dereferencing it. mumAPI = MUMBLE_API_CAST(api); pluginLog("Registered Mumble's API functions"); } void mumble_releaseResource(const void *pointer) { std::cerr << "[ERROR]: Unexpected call to mumble_releaseResources" << std::endl; std::terminate(); // This plugin doesn't use resources that are explicitly allocated (only static Strings are used). Therefore // we don't have to implement this function. // // If you allocated resources using malloc(), you're implementation for releasing that would be // free(const_cast(pointer)); // // If however you allocated a resource using the new operator (C++ only), you have figure out the pointer's // original type and then invoke // delete static_cast(pointer); // Mark as unused (void) pointer; } ////////////////////////////////////////////////////////////// ///////////////////// OPTIONAL FUNCTIONS ///////////////////// ////////////////////////////////////////////////////////////// // The implementation of below functions is optional. If you don't need them, don't include them in your // plugin void mumble_setMumbleInfo(mumble_version_t mumbleVersion, mumble_version_t mumbleAPIVersion, mumble_version_t minimumExpectedAPIVersion) { // this function will always be the first one to be called. Even before init() // In here you can get info about the Mumble version this plugin is about to run in. pLog() << "Mumble version: " << mumbleVersion << "; Mumble API-Version: " << mumbleAPIVersion << "; Minimal expected API-Version: " << minimumExpectedAPIVersion << std::endl; } mumble_version_t mumble_getVersion() { // Mumble uses semantic versioning (see https://semver.org/) // { major, minor, patch } return { 1, 0, 0 }; } MumbleStringWrapper mumble_getAuthor() { static const char *author = "MumbleDevelopers"; MumbleStringWrapper wrapper; wrapper.data = author; wrapper.size = strlen(author); // It's a static String and therefore doesn't need releasing wrapper.needsReleasing = false; return wrapper; } MumbleStringWrapper mumble_getDescription() { static const char *description = "This plugin is merely a reference implementation without any real functionality. " "It shouldn't be included in the release build of Mumble."; MumbleStringWrapper wrapper; wrapper.data = description; wrapper.size = strlen(description); // It's a static String and therefore doesn't need releasing wrapper.needsReleasing = false; return wrapper; } uint32_t mumble_getFeatures() { // Tells Mumble whether this plugin delivers some known common functionality. See the PluginFeature enum in // PluginComponents.h for what is available. // If you want your plugin to deliver positional data, you'll want to return FEATURE_POSITIONAL return MUMBLE_FEATURE_NONE; } uint32_t mumble_deactivateFeatures(uint32_t features) { pLog() << "Asked to deactivate feature set " << features << std::endl; // All features that can't be deactivated should be returned return features; } uint8_t mumble_initPositionalData(const char *const *programNames, const uint64_t *programPIDs, size_t programCount) { std::ostream &stream = pLog() << "Got " << programCount << " programs to init positional data."; if (programCount > 0) { stream << " The first name is " << programNames[0] << " and has PID " << programPIDs[0]; } stream << std::endl; // As this plugin doesn't provide PD, we return PDEC_ERROR_PERM to indicate that even in the future we won't do so // If your plugin is indeed delivering positional data but is only temporarily unable to do so, return // PDEC_ERROR_TEMP. and if you deliver PD and succeeded initializing return PDEC_OK. return MUMBLE_PDEC_ERROR_PERM; } #define SET_TO_ZERO(name) \ name[0] = 0.0f; \ name[1] = 0.0f; \ name[2] = 0.0f bool mumble_fetchPositionalData(float *avatarPos, float *avatarDir, float *avatarAxis, float *cameraPos, float *cameraDir, float *cameraAxis, const char **context, const char **identity) { pluginLog("Has been asked to deliver positional data"); // If unable to provide positional data, this function should return false and reset all given values to 0 / empty // Strings SET_TO_ZERO(avatarPos); SET_TO_ZERO(avatarDir); SET_TO_ZERO(avatarAxis); SET_TO_ZERO(cameraPos); SET_TO_ZERO(cameraDir); SET_TO_ZERO(cameraAxis); *context = ""; *identity = ""; // This function returns whether it can continue to deliver positional data return false; } #undef SET_TO_ZERO void mumble_shutdownPositionalData() { pluginLog("Shutting down positional data"); } void mumble_onServerConnected(mumble_connection_t connection) { activeConnection = connection; pLog() << "Established server-connection with ID " << connection << std::endl; // Use API function that'll block mumAPI.log(ownID, "Connected to a server"); } void mumble_onServerDisconnected(mumble_connection_t connection) { activeConnection = -1; const char *serverHash; if (mumAPI.getServerHash(ownID, connection, &serverHash) == MUMBLE_STATUS_OK) { pLog() << "Disconnected from server-connection with ID " << connection << "(hash: " << serverHash << ")" << std::endl; mumAPI.freeMemory(ownID, serverHash); } else { pluginLog("[ERROR]: mumble_onServerDisconnected - Unable to fetch server-hash"); } } void mumble_onServerSynchronized(mumble_connection_t connection) { // The client has finished synchronizing with the server. Thus we can now obtain a list of all users on this server const char *serverHash; if (mumAPI.getServerHash(ownID, connection, &serverHash) == MUMBLE_STATUS_OK) { pLog() << "Server has finished synchronizing (ServerConnection: " << connection << "; hash: " << serverHash << ")" << std::endl; mumAPI.freeMemory(ownID, serverHash); } else { pluginLog("[ERROR]: mumble_onServerSynchronized - Unable to fetch server-hash"); } size_t userCount; mumble_userid_t *userIDs; if (mumAPI.getAllUsers(ownID, activeConnection, &userIDs, &userCount) != MUMBLE_STATUS_OK) { pluginLog("[ERROR]: Can't obtain user list"); return; } mumble_userid_t localUserID; if (mumAPI.getLocalUserID(ownID, connection, &localUserID) != MUMBLE_STATUS_OK) { pluginLog("[ERROR]: Can't obtain ID of local user"); return; } pLog() << "There are " << userCount << " users on this server. Their names are:" << std::endl; for (size_t i = 0; i < userCount; i++) { const char *userName; if (mumAPI.getUserName(ownID, connection, userIDs[i], &userName) != MUMBLE_STATUS_OK) { pLog() << "" << std::endl; continue; } const char *userHash; if (mumAPI.getUserHash(ownID, connection, userIDs[i], &userHash) != MUMBLE_STATUS_OK) { pluginLog(""); } pLog() << "\t" << userName << " (" << userHash << ")" << std::endl; // Mute the user "MuteMe" if this is not the name of the local user (in which case it'd fail) if (userIDs[i] != localUserID && std::strcmp(userName, "MuteMe") == 0) { if (mumAPI.requestLocalMute(ownID, connection, userIDs[i], true) != MUMBLE_STATUS_OK) { pluginLog("[ERROR]: Failed at muting user \"MuteMe\"!"); } } mumAPI.freeMemory(ownID, userName); mumAPI.freeMemory(ownID, userHash); } mumAPI.freeMemory(ownID, userIDs); size_t channelCount; mumble_channelid_t *channelIDs; if (mumAPI.getAllChannels(ownID, activeConnection, &channelIDs, &channelCount) != MUMBLE_STATUS_OK) { pluginLog("[ERROR]: Failed to fetch channel list!"); return; } pLog() << "There are " << channelCount << " channels on this server" << std::endl; mumAPI.freeMemory(ownID, channelIDs); mumble_userid_t localUser; if (mumAPI.getLocalUserID(ownID, activeConnection, &localUser) != MUMBLE_STATUS_OK) { pluginLog("Failed to retrieve local user ID"); return; } if (mumAPI.sendData(ownID, activeConnection, &localUser, 1, reinterpret_cast< const uint8_t * >("Just a test"), 12, "testMsg") == MUMBLE_STATUS_OK) { pluginLog("Successfully sent plugin message"); // Try break the rate-limiter for plugin messages for (int i = 0; i < 40; i++) { std::string data = "Rate-limit message #" + std::to_string(i); mumAPI.sendData(ownID, activeConnection, &localUser, 1, reinterpret_cast< const uint8_t * >(data.c_str()), data.size(), "testMsg"); } } else { pluginLog("Failed at sending message"); } if (mumAPI.requestSetLocalUserComment(ownID, connection, "This user has the TestPlugin enabled - hand over a cookie!") != MUMBLE_STATUS_OK) { pluginLog("Failed at setting the local user's comment"); } } void mumble_onChannelEntered(mumble_connection_t connection, mumble_userid_t userID, mumble_channelid_t previousChannelID, mumble_channelid_t newChannelID) { std::ostream &stream = pLog() << "User with ID " << userID << " entered channel with ID " << newChannelID << "."; // negative ID means that there was no previous channel (e.g. because the user just connected) if (previousChannelID >= 0) { stream << " Came from channel with ID " << previousChannelID << "."; } stream << " (ServerConnection: " << connection << ")" << std::endl; } void mumble_onChannelExited(mumble_connection_t connection, mumble_userid_t userID, mumble_channelid_t channelID) { pLog() << "User with ID " << userID << " has left channel with ID " << channelID << ". (ServerConnection: " << connection << ")" << std::endl; } void mumble_onUserTalkingStateChanged(mumble_connection_t connection, mumble_userid_t userID, mumble_talking_state_t talkingState) { std::ostream &stream = pLog() << "User with ID " << userID << " changed his talking state to "; // The possible values are contained in the TalkingState enum inside PluginComponent.h switch (talkingState) { case MUMBLE_TS_INVALID: stream << "Invalid"; break; case MUMBLE_TS_PASSIVE: stream << "Passive"; break; case MUMBLE_TS_TALKING: stream << "Talking"; break; case MUMBLE_TS_WHISPERING: stream << "Whispering"; break; case MUMBLE_TS_SHOUTING: stream << "Shouting"; break; default: stream << "Unknown (" << talkingState << ")"; } stream << ". (ServerConnection: " << connection << ")" << std::endl; } bool mumble_onAudioInput(short *inputPCM, uint32_t sampleCount, uint16_t channelCount, uint32_t sampleRate, bool isSpeech) { // pLog() << "Audio input with " << channelCount << " channels and " << sampleCount << " samples per channel // encountered. IsSpeech: " // << isSpeech << " Sample rate is " << sampleRate << "Hz" << std::endl; // mark variables as unused (void) inputPCM; (void) sampleCount; (void) channelCount; (void) sampleRate; (void) isSpeech; // This function returns whether it has modified the audio stream return false; } bool mumble_onAudioSourceFetched(float *outputPCM, uint32_t sampleCount, uint16_t channelCount, uint32_t sampleRate, bool isSpeech, mumble_userid_t userID) { std::ostream &stream = pLog() << "Audio output source with " << channelCount << " channels and " << sampleCount << " samples per channel " << "(" << sampleRate << " Hz) fetched."; if (isSpeech) { stream << " The output is speech from user with ID " << userID << "."; } stream << std::endl; // Mark ouputPCM as unused (void) outputPCM; // This function returns whether it has modified the audio stream return false; } bool mumble_onAudioOutputAboutToPlay(float *outputPCM, uint32_t sampleCount, uint16_t channelCount, uint32_t sampleRate) { // pLog() << "The resulting audio output has " << channelCount << " channels with " << sampleCount << " samples per // channel (" sampleRate << " Hz)" << std::endl; // mark variables as unused (void) outputPCM; (void) sampleCount; (void) channelCount; (void) sampleRate; // This function returns whether it has modified the audio stream return false; } bool mumble_onReceiveData(mumble_connection_t connection, mumble_userid_t sender, const uint8_t *data, size_t dataLength, const char *dataID) { pLog() << "Received data with ID \"" << dataID << "\" from user with ID " << sender << ". Its length is " << dataLength << ". (ServerConnection:" << connection << ")" << std::endl; if (std::strcmp(dataID, "testMsg") == 0) { // We know that data is only a normal C-encoded String, so the reinterpret_cast is safe pLog() << "The received data: " << reinterpret_cast< const char * >(data) << std::endl; } // This function returns whether it has processed the data (preventing further plugins from seeing it) return false; } void mumble_onUserAdded(mumble_connection_t connection, mumble_userid_t userID) { pLog() << "Added user with ID " << userID << " (ServerConnection: " << connection << ")" << std::endl; } void mumble_onUserRemoved(mumble_connection_t connection, mumble_userid_t userID) { pLog() << "Removed user with ID " << userID << " (ServerConnection: " << connection << ")" << std::endl; } void mumble_onChannelAdded(mumble_connection_t connection, mumble_channelid_t channelID) { pLog() << "Added channel with ID " << channelID << " (ServerConnection: " << connection << ")" << std::endl; } void mumble_onChannelRemoved(mumble_connection_t connection, mumble_channelid_t channelID) { pLog() << "Removed channel with ID " << channelID << " (ServerConnection: " << connection << ")" << std::endl; } void mumble_onChannelRenamed(mumble_connection_t connection, mumble_channelid_t channelID) { pLog() << "Renamed channel with ID " << channelID << " (ServerConnection: " << connection << ")" << std::endl; } void mumble_onKeyEvent(uint32_t keyCode, bool wasPress) { pLog() << "Encountered key " << (wasPress ? "press" : "release") << " of key with code " << keyCode << std::endl; } bool mumble_hasUpdate() { // This plugin never has an update return false; } MumbleStringWrapper mumble_getUpdateDownloadURL() { static const char *url = "https://i.dont.exist/testplugin.zip"; MumbleStringWrapper wrapper; wrapper.data = url; wrapper.size = strlen(url); // It's a static String and therefore doesn't need releasing wrapper.needsReleasing = false; return wrapper; }