mumble-voip_mumble/plugins/link/link.cpp

242 lines
6.0 KiB
C++

// Copyright 2007-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 <chrono>
#include <codecvt>
#include <cstdint>
#include <cstring>
#include <iostream>
#include <locale>
#include <string>
#include "LinkedMem.h"
#include "MumblePlugin.h"
#include "SharedMemory.h"
#define UNUSED(x) (void) x
constexpr const char *defaultName = "Link";
constexpr const char *defaultDescription = "Reads positional data from a linked game/software";
std::string pluginName(defaultName);
std::string applicationName;
std::string pluginDescription(defaultDescription);
std::string pluginContext;
std::string pluginIdentity;
SharedMemory sharedMem;
std::uint32_t last_tick = 0;
std::int64_t last_tick_time = 0;
/**
* @returns Time in ms since Epoch
*/
static std::uint64_t getTimeSinceEpoch() {
return std::chrono::system_clock::now().time_since_epoch() / std::chrono::milliseconds(1);
}
mumble_error_t mumble_init(mumble_plugin_id_t id) {
UNUSED(id);
if (!sharedMem.mapMemory(getLinkedMemoryName())) {
std::cerr << "Link plugin: Failed to setup shared memory: " << sharedMem.lastError() << std::endl;
return MUMBLE_EC_INTERNAL_ERROR;
}
return MUMBLE_STATUS_OK;
}
void mumble_shutdown() {
sharedMem.close();
}
MumbleStringWrapper mumble_getName() {
MumbleStringWrapper wrapper;
wrapper.data = pluginName.c_str();
wrapper.size = pluginName.size();
wrapper.needsReleasing = false;
return wrapper;
}
mumble_version_t mumble_getAPIVersion() {
return MUMBLE_PLUGIN_API_VERSION;
}
void mumble_registerAPIFunctions(void *apiStruct) {
UNUSED(apiStruct);
}
void mumble_releaseResource(const void *pointer) {
// This function should never be called
UNUSED(pointer);
std::terminate();
}
mumble_version_t mumble_getVersion() {
return { 1, 3, 0 };
}
MumbleStringWrapper mumble_getAuthor() {
static const char *author = "Mumble Developers";
MumbleStringWrapper wrapper;
wrapper.data = author;
wrapper.size = std::strlen(author);
wrapper.needsReleasing = false;
return wrapper;
}
MumbleStringWrapper mumble_getDescription() {
MumbleStringWrapper wrapper;
wrapper.data = pluginDescription.c_str();
wrapper.size = pluginDescription.size();
wrapper.needsReleasing = false;
return wrapper;
}
uint32_t mumble_getFeatures() {
return MUMBLE_FEATURE_POSITIONAL;
}
uint8_t mumble_initPositionalData(const char *const *programNames, const uint64_t *programPIDs, size_t programCount) {
UNUSED(programNames);
UNUSED(programPIDs);
UNUSED(programCount);
if (!sharedMem.isMemoryMapped()) {
return MUMBLE_PDEC_ERROR_TEMP;
}
LinkedMem lm = sharedMem.read();
if ((lm.uiVersion == 1) || (lm.uiVersion == 2)) {
if (lm.uiTick != last_tick) {
last_tick = lm.uiTick;
last_tick_time = getTimeSinceEpoch();
wchar_t buff[2048];
if (lm.name[0]) {
wcsncpy(buff, lm.name, 256);
buff[255] = 0;
applicationName = std::wstring_convert< std::codecvt_utf8< wchar_t > >().to_bytes(buff);
// Call the plugin itself "Link (<whatever>)"
pluginName += " (" + applicationName + ")";
}
if (lm.description[0]) {
wcsncpy(buff, lm.description, 2048);
buff[2047] = 0;
pluginDescription = std::wstring_convert< std::codecvt_utf8< wchar_t > >().to_bytes(buff);
}
return MUMBLE_PDEC_OK;
}
}
return MUMBLE_PDEC_ERROR_TEMP;
}
#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) {
SET_TO_ZERO(avatarPos);
SET_TO_ZERO(avatarDir);
SET_TO_ZERO(avatarAxis);
SET_TO_ZERO(cameraPos);
SET_TO_ZERO(cameraDir);
SET_TO_ZERO(cameraAxis);
LinkedMem lm = sharedMem.read();
if (lm.uiTick != last_tick) {
last_tick = lm.uiTick;
last_tick_time = getTimeSinceEpoch();
} else if ((getTimeSinceEpoch() - last_tick_time) > 5000) {
return false;
}
if ((lm.uiVersion != 1) && (lm.uiVersion != 2)) {
return false;
}
for (int i = 0; i < 3; ++i) {
avatarPos[i] = lm.fAvatarPosition[i];
avatarDir[i] = lm.fAvatarFront[i];
avatarAxis[i] = lm.fAvatarTop[i];
}
if (lm.uiVersion == 2) {
for (int i = 0; i < 3; ++i) {
cameraPos[i] = lm.fCameraPosition[i];
cameraDir[i] = lm.fCameraFront[i];
cameraAxis[i] = lm.fCameraTop[i];
}
if (lm.context_len > 255) {
lm.context_len = 255;
}
lm.identity[255] = 0;
pluginContext.assign(reinterpret_cast< const char * >(lm.context), lm.context_len);
pluginIdentity = std::wstring_convert< std::codecvt_utf8< wchar_t > >().to_bytes(lm.identity);
} else {
for (int i = 0; i < 3; ++i) {
cameraPos[i] = lm.fAvatarPosition[i];
cameraDir[i] = lm.fAvatarFront[i];
cameraAxis[i] = lm.fAvatarTop[i];
}
pluginContext.clear();
pluginIdentity.clear();
}
*context = pluginContext.c_str();
*identity = pluginIdentity.c_str();
return true;
}
#undef SET_TO_ZERO
void mumble_shutdownPositionalData() {
if (!applicationName.empty()) {
// We know that pluginName is in the format "Link (<whatever>)" where <whatever> is the applicationName
pluginName.erase(pluginName.size() - applicationName.size() - 3, std::string::npos);
} else if (applicationName.size() != std::strlen(defaultName)) {
// This code part should actually never run, since we expect the pluginName to be modified in the described way
// as soon as applicationName is defined.
pluginName.clear();
pluginName.append(defaultName);
}
applicationName.clear();
pluginDescription = std::string(defaultDescription);
pluginContext.clear();
pluginIdentity.clear();
sharedMem.reset();
}
MumbleStringWrapper mumble_getPositionalDataContextPrefix() {
MumbleStringWrapper wrapper;
wrapper.data = applicationName.c_str();
wrapper.size = applicationName.size();
wrapper.needsReleasing = false;
return wrapper;
}