mumble-voip_mumble/plugins/testPlugin/testPlugin.cpp

500 lines
18 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 the definitions of the plugin functions
// Not that this will also include ../PluginComponents.h
#include "../MumblePlugin.h"
#include <cstring>
#include <iostream>
// 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<void *>(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<ActualType *>(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() << "<Unable to fetch user name>" << std::endl;
continue;
}
const char *userHash;
if (mumAPI.getUserHash(ownID, connection, userIDs[i], &userHash) != MUMBLE_STATUS_OK) {
pluginLog("<Unable to get user-hash>");
}
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 - <b>hand over a cookie!</b>")
!= 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;
}