FEAT: Make channel listeners persistent on server

For registered users, the server will now remember their channel
listeners (and their volume adjustments) and will restore them once the
user rejoins this server.
This commit is contained in:
Robert Adam 2021-12-13 08:53:38 +01:00
parent ac2e5bccb5
commit 3f4b00a425
27 changed files with 689 additions and 194 deletions

View File

@ -10,13 +10,17 @@
#include <QReadLocker>
#include <QWriteLocker>
std::size_t qHash(const ChannelListener &listener) {
return std::hash< ChannelListener >()(listener);
};
bool operator==(const ChannelListener &lhs, const ChannelListener &rhs) {
return lhs.channelID == rhs.channelID && lhs.userSession == rhs.userSession;
}
ChannelListenerManager::ChannelListenerManager()
: QObject(nullptr), m_listenerLock(), m_listeningUsers(), m_listenedChannels()
#ifdef MUMBLE
,
m_volumeLock(), m_listenerVolumeAdjustments()
#endif
{
: QObject(nullptr), m_listenerLock(), m_listeningUsers(), m_listenedChannels(), m_volumeLock(),
m_listenerVolumeAdjustments() {
}
void ChannelListenerManager::addListener(unsigned int userSession, int channelID) {
@ -75,49 +79,69 @@ int ChannelListenerManager::getListenedChannelCountForUser(unsigned int userSess
return m_listeningUsers[userSession].size();
}
#ifdef MUMBLE
void ChannelListenerManager::setListenerLocalVolumeAdjustment(int channelID, float volumeAdjustment) {
float oldValue;
void ChannelListenerManager::setListenerVolumeAdjustment(unsigned int userSession, int channelID,
const VolumeAdjustment &volumeAdjustment) {
float oldValue = 1.0f;
{
QWriteLocker lock(&m_volumeLock);
oldValue = m_listenerVolumeAdjustments.value(channelID, 1.0f);
m_listenerVolumeAdjustments.insert(channelID, volumeAdjustment);
}
ChannelListener key = {};
key.channelID = channelID;
key.userSession = userSession;
if (oldValue != volumeAdjustment) {
emit localVolumeAdjustmentsChanged(channelID, volumeAdjustment, oldValue);
}
}
float ChannelListenerManager::getListenerLocalVolumeAdjustment(int channelID) const {
QReadLocker lock(&m_volumeLock);
return m_listenerVolumeAdjustments.value(channelID, 1.0f);
}
QHash< int, float > ChannelListenerManager::getAllListenerLocalVolumeAdjustments(bool filter) const {
QReadLocker lock(&m_volumeLock);
if (!filter) {
return m_listenerVolumeAdjustments;
} else {
QHash< int, float > volumeMap;
QHashIterator< int, float > it(m_listenerVolumeAdjustments);
while (it.hasNext()) {
it.next();
if (it.value() != 1.0f) {
volumeMap.insert(it.key(), it.value());
}
auto it = m_listenerVolumeAdjustments.find(key);
if (it != m_listenerVolumeAdjustments.end()) {
oldValue = it->second.factor;
}
return volumeMap;
m_listenerVolumeAdjustments[key] = volumeAdjustment;
}
if (oldValue != volumeAdjustment.factor) {
emit localVolumeAdjustmentsChanged(channelID, volumeAdjustment.factor, oldValue);
}
}
#endif
const VolumeAdjustment &ChannelListenerManager::getListenerVolumeAdjustment(unsigned int userSession,
int channelID) const {
static VolumeAdjustment fallbackObj = VolumeAdjustment::fromFactor(1.0f);
QReadLocker lock(&m_volumeLock);
ChannelListener key = {};
key.channelID = channelID;
key.userSession = userSession;
auto it = m_listenerVolumeAdjustments.find(key);
if (it == m_listenerVolumeAdjustments.end()) {
return fallbackObj;
} else {
return it->second;
}
}
std::unordered_map< int, VolumeAdjustment >
ChannelListenerManager::getAllListenerVolumeAdjustments(unsigned int userSession) const {
QReadLocker lock1(&m_volumeLock);
QReadLocker lock2(&m_listenerLock);
std::unordered_map< int, VolumeAdjustment > adjustments;
for (int channelID : m_listeningUsers.value(userSession)) {
ChannelListener listener = {};
listener.channelID = channelID;
listener.userSession = userSession;
auto it = m_listenerVolumeAdjustments.find(listener);
if (it != m_listenerVolumeAdjustments.end() && it->second.factor != 1.0f) {
adjustments[channelID] = it->second;
}
}
return adjustments;
}
void ChannelListenerManager::clear() {
{
@ -125,10 +149,8 @@ void ChannelListenerManager::clear() {
m_listeningUsers.clear();
m_listenedChannels.clear();
}
#ifdef MUMBLE
{
QWriteLocker lock(&m_volumeLock);
m_listenerVolumeAdjustments.clear();
}
#endif
}

View File

@ -6,16 +6,35 @@
#ifndef MUMBLE_CHANNELLISTENERMANAGER_H_
#define MUMBLE_CHANNELLISTENERMANAGER_H_
#include "VolumeAdjustment.h"
#include <QtCore/QHash>
#include <QtCore/QObject>
#include <QtCore/QReadWriteLock>
#include <QtCore/QSet>
#include <atomic>
#include <unordered_map>
class User;
class Channel;
struct ChannelListener {
/// The session ID of the owning user
unsigned int userSession;
/// The ID of the channel this listener is placed in
int channelID;
};
// Make ChannelListener hashable and comparable
template<> struct std::hash< ChannelListener > {
std::size_t operator()(const ChannelListener &val) const {
return std::hash< unsigned int >()(val.userSession) ^ (std::hash< int >()(val.channelID) << 2);
}
};
std::size_t qHash(const ChannelListener &listener);
bool operator==(const ChannelListener &lhs, const ChannelListener &rhs);
/// This class serves as a namespace for storing information about ChannelListeners. This is a feature
/// that allows a user to listen to a channel without being in it. Kinda similar to linked channels
/// except that this is something each user can do individually.
@ -31,13 +50,11 @@ protected:
QHash< unsigned int, QSet< int > > m_listeningUsers;
/// A map between a channel's ID and a list of all user-sessions of users listening to that channel
QHash< int, QSet< unsigned int > > m_listenedChannels;
#ifdef MUMBLE
/// A lock for guarding m_listenerVolumeAdjustments
mutable QReadWriteLock m_volumeLock;
/// A map between channel IDs and local volume adjustments to be made for ChannelListeners
/// in that channel
QHash< int, float > m_listenerVolumeAdjustments;
#endif
std::unordered_map< ChannelListener, VolumeAdjustment > m_listenerVolumeAdjustments;
public:
/// Constructor
@ -84,29 +101,26 @@ public:
/// @returns The amount of channels the given user is listening to
int getListenedChannelCountForUser(unsigned int userSession) const;
#ifdef MUMBLE
/// Sets the local volume adjustment for any channelListener in the given channel.
/// Sets the volume adjustment for the channelListener of the given user in the given channel.
///
/// @param userSession The session ID of the user
/// @param channelID The ID of the channel
/// @param volumeAdjustment The volume adjustment to apply
void setListenerLocalVolumeAdjustment(int channelID, float volumeAdjustment);
void setListenerVolumeAdjustment(unsigned int userSession, int channelID, const VolumeAdjustment &volumeAdjustment);
/// @param userSession The session ID of the user
/// @param channelID The ID of the channel
/// @param The local volume adjustment for the given channel. If none has been set,
/// 1.0f is being returned.
float getListenerLocalVolumeAdjustment(int channelID) const;
/// @returns The volume adjustment for the listener of the given user in the given channel.
const VolumeAdjustment &getListenerVolumeAdjustment(unsigned int userSession, int channelID) const;
/// @param filter Whether to filter out adjustments of 1 (which have no effect)
/// @param userSession The session ID of the user whose listener's volume adjustments to obtain
/// @returns A map between channel IDs and the currently set volume adjustment
QHash< int, float > getAllListenerLocalVolumeAdjustments(bool filter = false) const;
#endif
std::unordered_map< int, VolumeAdjustment > getAllListenerVolumeAdjustments(unsigned int userSession) const;
/// Clears all ChannelListeners and volume adjustments
void clear();
signals:
#ifdef MUMBLE
void localVolumeAdjustmentsChanged(int channelID, float newAdjustment, float oldAdjustment);
#endif
};
#endif // MUMBLE_CHANNELLISTENERMANAGER_H_

View File

@ -179,6 +179,11 @@ message UserRemove {
// First seen during login procedure. May be sent by the client when it wishes
// to alter its state.
message UserState {
message VolumeAdjustment {
optional uint32 listening_channel = 1;
optional float volume_adjustment = 2;
}
// Unique user session ID of the user whose state this is, may change on
// reconnect.
optional uint32 session = 1;
@ -230,6 +235,8 @@ message UserState {
repeated uint32 listening_channel_add = 21;
// a list of channels the user does no longer want to listen to.
repeated uint32 listening_channel_remove = 22;
// A list of volume adjustments the user has applied to listeners
repeated VolumeAdjustment listening_volume_adjustment = 23;
}
// Relays information on the bans. The client may send the BanList message to

View File

@ -506,14 +506,21 @@ bool AudioOutput::mix(void *outbuff, unsigned int frameCount) {
volumeAdjustment *= user->getLocalVolumeAdjustments();
if (user->cChannel
&& Global::get().channelListenerManager->isListening(Global::get().uiSession, user->cChannel->iId)
&& (speech->m_audioContext == Mumble::Protocol::AudioContext::LISTEN)) {
if (sh && sh->m_version >= Mumble::Protocol::PROTOBUF_INTRODUCTION_VERSION) {
// The new protocol supports sending volume adjustments which is used to figure out the correct
// volume adjustment for listeners on the server. Thus, we only have to apply that here.
volumeAdjustment *= speech->m_suggestedVolumeAdjustment;
} else if (user->cChannel
&& Global::get().channelListenerManager->isListening(Global::get().uiSession,
user->cChannel->iId)
&& (speech->m_audioContext == Mumble::Protocol::AudioContext::LISTEN)) {
// We are receiving this audio packet only because we are listening to the channel
// the speaking user is in. Thus we receive the audio via our "listener proxy".
// Thus we'll apply the volume adjustment for our listener proxy as well
volumeAdjustment *=
Global::get().channelListenerManager->getListenerLocalVolumeAdjustment(user->cChannel->iId);
volumeAdjustment *= Global::get()
.channelListenerManager
->getListenerVolumeAdjustment(Global::get().uiSession, user->cChannel->iId)
.factor;
}
if (prioritySpeakerActive) {

View File

@ -161,8 +161,8 @@ set(MUMBLE_SOURCES
"LCD.ui"
"LegacyPlugin.cpp"
"LegacyPlugin.h"
"ListenerLocalVolumeSlider.cpp"
"ListenerLocalVolumeSlider.h"
"ListenerVolumeSlider.cpp"
"ListenerVolumeSlider.h"
"Log.cpp"
"Log.h"
"Log.ui"

View File

@ -1,30 +0,0 @@
// Copyright 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 <https://www.mumble.info/LICENSE>.
#include "ListenerLocalVolumeSlider.h"
#include "Channel.h"
#include "ChannelListenerManager.h"
#include "VolumeAdjustment.h"
#include "Global.h"
ListenerLocalVolumeSlider::ListenerLocalVolumeSlider(QWidget *parent) : VolumeSliderWidgetAction(parent) {
}
void ListenerLocalVolumeSlider::setListenedChannel(const Channel &channel) {
m_channel = &channel;
float initialAdjustment = Global::get().channelListenerManager->getListenerLocalVolumeAdjustment(m_channel->iId);
updateSliderValue(initialAdjustment);
}
void ListenerLocalVolumeSlider::on_VolumeSlider_valueChanged(int value) {
updateTooltip(value);
displayTooltip(value);
if (m_channel) {
float factor = VolumeAdjustment::toFactor(value);
Global::get().channelListenerManager->setListenerLocalVolumeAdjustment(m_channel->iId, factor);
}
}

View File

@ -0,0 +1,56 @@
// Copyright 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 <https://www.mumble.info/LICENSE>.
#include "ListenerVolumeSlider.h"
#include "Channel.h"
#include "ChannelListenerManager.h"
#include "ServerHandler.h"
#include "VolumeAdjustment.h"
#include "Global.h"
ListenerVolumeSlider::ListenerVolumeSlider(QWidget *parent) : VolumeSliderWidgetAction(parent) {
}
void ListenerVolumeSlider::setListenedChannel(const Channel &channel) {
m_channel = &channel;
float initialAdjustment =
Global::get()
.channelListenerManager->getListenerVolumeAdjustment(Global::get().uiSession, m_channel->iId)
.factor;
updateSliderValue(initialAdjustment);
}
void ListenerVolumeSlider::on_VolumeSlider_valueChanged(int value) {
updateTooltip(value);
displayTooltip(value);
}
void ListenerVolumeSlider::on_VolumeSlider_sliderReleased() {
ServerHandlerPtr handler = Global::get().sh;
if (!handler || !m_channel || !m_volumeSlider) {
return;
}
VolumeAdjustment adjustment = VolumeAdjustment::fromDBAdjustment(m_volumeSlider->value());
if (handler->m_version >= Mumble::Protocol::PROTOBUF_INTRODUCTION_VERSION) {
// With the new audio protocol, volume adjustments for listeners are handled on the server and thus we want
// to avoid spamming updates to the adjustments, which is why we only update them once the slider is released.
MumbleProto::UserState mpus;
mpus.set_session(Global::get().uiSession);
MumbleProto::UserState::VolumeAdjustment *adjustmentMsg = mpus.add_listening_volume_adjustment();
adjustmentMsg->set_listening_channel(m_channel->iId);
adjustmentMsg->set_volume_adjustment(adjustment.factor);
handler->sendMessage(mpus);
} else {
// Before the new audio protocol, volume adjustments for listeners are handled locally
Global::get().channelListenerManager->setListenerVolumeAdjustment(Global::get().uiSession, m_channel->iId,
adjustment);
}
}

View File

@ -10,11 +10,11 @@
class Channel;
class ListenerLocalVolumeSlider : public VolumeSliderWidgetAction {
class ListenerVolumeSlider : public VolumeSliderWidgetAction {
Q_OBJECT
public:
ListenerLocalVolumeSlider(QWidget *parent = nullptr);
ListenerVolumeSlider(QWidget *parent = nullptr);
/// Must be called before adding this object as an action
void setListenedChannel(const Channel &channel);
@ -24,7 +24,8 @@ private:
const Channel *m_channel;
private slots:
void on_VolumeSlider_valueChanged(int value);
void on_VolumeSlider_valueChanged(int value) override;
void on_VolumeSlider_sliderReleased() override;
};
#endif

View File

@ -29,7 +29,7 @@
#endif
#include "../SignalCurry.h"
#include "ChannelListenerManager.h"
#include "ListenerLocalVolumeSlider.h"
#include "ListenerVolumeSlider.h"
#include "Markdown.h"
#include "MenuLabel.h"
#include "PTTButtonWidget.h"
@ -98,7 +98,7 @@ OpenURLEvent::OpenURLEvent(QUrl u) : QEvent(static_cast< QEvent::Type >(OU_QEVEN
MainWindow::MainWindow(QWidget *p)
: QMainWindow(p), m_localVolumeLabel(make_qt_unique< MenuLabel >(tr("Local Volume Adjustment:"), this)),
m_userLocalVolumeSlider(make_qt_unique< UserLocalVolumeSlider >(this)),
m_listenerLocalVolumeSlider(make_qt_unique< ListenerLocalVolumeSlider >(this)) {
m_listenerVolumeSlider(make_qt_unique< ListenerVolumeSlider >(this)) {
SvgIcon::addSvgPixmapsToIcon(qiIconMuteSelf, QLatin1String("skin:muted_self.svg"));
SvgIcon::addSvgPixmapsToIcon(qiIconMuteServer, QLatin1String("skin:muted_server.svg"));
SvgIcon::addSvgPixmapsToIcon(qiIconMuteSuppressed, QLatin1String("skin:muted_suppressed.svg"));
@ -1759,8 +1759,8 @@ void MainWindow::qmListener_aboutToShow() {
qmListener->addAction(m_localVolumeLabel.get());
Channel *channel = getContextMenuChannel();
if (channel) {
m_listenerLocalVolumeSlider->setListenedChannel(*channel);
qmListener->addAction(m_listenerLocalVolumeSlider.get());
m_listenerVolumeSlider->setListenedChannel(*channel);
qmListener->addAction(m_listenerVolumeSlider.get());
qmListener->addSeparator();
}

View File

@ -44,7 +44,7 @@ class SearchDialog;
};
class MenuLabel;
class ListenerLocalVolumeSlider;
class ListenerVolumeSlider;
class UserLocalVolumeSlider;
struct ShortcutTarget;
@ -183,7 +183,7 @@ protected:
qt_unique_ptr< MenuLabel > m_localVolumeLabel;
qt_unique_ptr< UserLocalVolumeSlider > m_userLocalVolumeSlider;
qt_unique_ptr< ListenerLocalVolumeSlider > m_listenerLocalVolumeSlider;
qt_unique_ptr< ListenerVolumeSlider > m_listenerVolumeSlider;
void createActions();
void setupGui();

View File

@ -500,6 +500,18 @@ void MainWindow::msgUserState(const MumbleProto::UserState &msg) {
Global::get().l->log(Log::ChannelListeningRemove, logMsg);
}
}
for (int i = 0; i < msg.listening_volume_adjustment_size(); i++) {
int channelID = msg.listening_volume_adjustment(i).listening_channel();
float adjustment = msg.listening_volume_adjustment(i).volume_adjustment();
const Channel *channel = Channel::get(channelID);
if (channel && pSelf && pSelf->uiSession == pDst->uiSession) {
Global::get().channelListenerManager->setListenerVolumeAdjustment(pDst->uiSession, channel->iId,
VolumeAdjustment::fromFactor(adjustment));
} else if (!channel) {
qWarning("msgUserState(): Invalid channel ID encountered in volume adjustment");
}
}
if (msg.has_name()) {
QString oldName = pDst->qsName;

View File

@ -31,6 +31,7 @@
#include <QtGui/QPixmap>
#include <algorithm>
#include <cassert>
TalkingUI::TalkingUI(QWidget *parent) : QWidget(parent), m_containers(), m_currentSelection(nullptr) {
setupUI();
@ -666,13 +667,36 @@ void TalkingUI::on_mainWindowSelectionChanged(const QModelIndex &current, const
}
void TalkingUI::on_serverSynchronized() {
ClientUser *self = ClientUser::get(Global::get().uiSession);
assert(self);
if (Global::get().s.bTalkingUI_LocalUserStaysVisible) {
// According to the settings the local user should always be visible and as we
// can't count on it to change its talking state right after it has connected to
// a server, we have to add it manually.
ClientUser *self = ClientUser::get(Global::get().uiSession);
findOrAddUser(self);
}
// The client may have received add listener messages for the user before the
// sync was complete. So we do this to ensure that they appear in the
// TalkingUI. Removing all listeners is probably not necessary but could
// prevent duplicates appearing in the case of a race.
removeAllListeners();
if (Global::get().s.bTalkingUI_ShowLocalListeners) {
if (self) {
const QSet< int > channels =
Global::get().channelListenerManager->getListenedChannelsForUser(self->uiSession);
for (int currentChannelID : channels) {
const Channel *channel = Channel::get(currentChannelID);
if (channel) {
addListener(self, channel);
}
}
}
}
}
void TalkingUI::on_serverDisconnected() {

View File

@ -1971,7 +1971,9 @@ QString UserModel::createDisplayString(const ClientUser &user, bool isChannelLis
if (parentChannel && user.uiSession == Global::get().uiSession) {
// Only the listener of the local user can have a volume adjustment
volumeAdjustment =
Global::get().channelListenerManager->getListenerLocalVolumeAdjustment(parentChannel->iId);
Global::get()
.channelListenerManager->getListenerVolumeAdjustment(user.uiSession, parentChannel->iId)
.factor;
}
} else {
volumeAdjustment = user.getLocalVolumeAdjustments();

View File

@ -26,7 +26,7 @@ protected:
void updateTooltip(int value);
protected slots:
virtual void on_VolumeSlider_valueChanged(int value) = 0;
virtual void on_VolumeSlider_valueChanged(int){};
virtual void on_VolumeSlider_sliderReleased(){};
};

View File

@ -694,6 +694,13 @@ int main(int argc, char **argv) {
// window's frame to be included in the positioning calculation on X11 (at least using KDE Plasma)
Global::get().talkingUI->setVisible(Global::get().s.bShowTalkingUI);
QObject::connect(Global::get().mw, &MainWindow::userAddedChannelListener, Global::get().talkingUI,
&TalkingUI::on_channelListenerAdded);
QObject::connect(Global::get().mw, &MainWindow::userRemovedChannelListener, Global::get().talkingUI,
&TalkingUI::on_channelListenerRemoved);
QObject::connect(Global::get().channelListenerManager.get(), &ChannelListenerManager::localVolumeAdjustmentsChanged,
Global::get().talkingUI, &TalkingUI::on_channelListenerLocalVolumeAdjustmentChanged);
QObject::connect(Global::get().mw, &MainWindow::userAddedChannelListener, Global::get().talkingUI,
&TalkingUI::on_channelListenerAdded);
QObject::connect(Global::get().mw, &MainWindow::userRemovedChannelListener, Global::get().talkingUI,

View File

@ -5,6 +5,7 @@
#include "ACL.h"
#include "Channel.h"
#include "ChannelListenerManager.h"
#include "Connection.h"
#include "Group.h"
#include "Meta.h"
@ -22,6 +23,7 @@
#include <QtCore/QtEndian>
#include <cassert>
#include <unordered_map>
#include <Tracy.hpp>
@ -390,6 +392,8 @@ void Server::msgAuthenticate(ServerUser *uSource, MumbleProto::Authenticate &msg
}
}
loadChannelListenersOf(*uSource);
// Transmit user profile
MumbleProto::UserState mpus;
@ -483,8 +487,16 @@ void Server::msgAuthenticate(ServerUser *uSource, MumbleProto::Authenticate &msg
if (!u->qsHash.isEmpty())
mpus.set_hash(u8(u->qsHash));
foreach (int channelID, m_channelListenerManager.getListenedChannelsForUser(u->uiSession)) {
for (int channelID : m_channelListenerManager.getListenedChannelsForUser(u->uiSession)) {
mpus.add_listening_channel_add(channelID);
if (broadcastListenerVolumeAdjustments) {
VolumeAdjustment volume = m_channelListenerManager.getListenerVolumeAdjustment(u->uiSession, channelID);
MumbleProto::UserState::VolumeAdjustment *adjustment = mpus.add_listening_volume_adjustment();
adjustment->set_listening_channel(channelID);
adjustment->set_volume_adjustment(volume.factor);
}
}
sendMessage(uSource, mpus);
@ -507,6 +519,39 @@ void Server::msgAuthenticate(ServerUser *uSource, MumbleProto::Authenticate &msg
sendMessage(uSource, mpss);
// Transmit user's listeners - this has to be done AFTER the server-sync message has been sent to uSource as the
// client may require its own session ID for processing the listeners properly.
mpus.Clear();
mpus.set_session(uSource->uiSession);
for (int channelID : m_channelListenerManager.getListenedChannelsForUser(uSource->uiSession)) {
mpus.add_listening_channel_add(channelID);
}
// If we are not intending to broadcast the volume adjustments to everyone, we have to send the message to all but
// uSource without the volume adjustments. Then append the adjustments, but only send them to uSource. If we are in
// fact broadcasting, just append the adjustments and send to everyone.
if (!broadcastListenerVolumeAdjustments && mpus.listening_channel_add_size() > 0) {
sendExcept(uSource, mpus, Version::fromComponents(1, 2, 2), Version::CompareMode::AtLeast);
}
std::unordered_map< int, VolumeAdjustment > volumeAdjustments =
m_channelListenerManager.getAllListenerVolumeAdjustments(uSource->uiSession);
for (auto it = volumeAdjustments.begin(); it != volumeAdjustments.end(); ++it) {
MumbleProto::UserState::VolumeAdjustment *adjustment = mpus.add_listening_volume_adjustment();
adjustment->set_listening_channel(it->first);
adjustment->set_volume_adjustment(it->second.factor);
}
if (mpus.listening_channel_add_size() > 0 || mpus.listening_volume_adjustment_size() > 0) {
if (!broadcastListenerVolumeAdjustments) {
if (uSource->m_version >= Version::fromComponents(1, 2, 2)) {
sendMessage(uSource, mpus);
}
} else {
sendAll(mpus, Version::fromComponents(1, 2, 2), Version::CompareMode::AtLeast);
}
}
MumbleProto::ServerConfig mpsc;
mpsc.set_allow_html(bAllowHTML);
mpsc.set_message_length(iMaxTextMessageLength);
@ -959,18 +1004,42 @@ void Server::msgUserState(ServerUser *uSource, MumbleProto::UserState &msg) {
// Handle channel listening
// Note that it is important to handle the listening channels after channel-joins
foreach (Channel *c, listeningChannelsAdd) {
m_channelListenerManager.addListener(pDstServerUser->uiSession, c->iId);
QSet< int > volumeAdjustedChannels;
for (int i = 0; i < msg.listening_volume_adjustment_size(); i++) {
const MumbleProto::UserState::VolumeAdjustment &adjustment = msg.listening_volume_adjustment(i);
const Channel *channel = qhChannels.value(adjustment.listening_channel());
if (channel) {
setChannelListenerVolume(*pDstServerUser, *channel, adjustment.volume_adjustment());
volumeAdjustedChannels << channel->iId;
} else {
log(uSource, QString::fromLatin1("Invalid channel ID \"%1\" in volume adjustment")
.arg(adjustment.listening_channel()));
}
}
for (Channel *c : listeningChannelsAdd) {
addChannelListener(*pDstServerUser, *c);
log(QString::fromLatin1("\"%1\" is now listening to channel \"%2\"")
.arg(QString(*pDstServerUser))
.arg(QString(*c)));
float volumeAdjustment =
m_channelListenerManager.getListenerVolumeAdjustment(pDstServerUser->uiSession, c->iId).factor;
if (volumeAdjustment != 1.0f && !volumeAdjustedChannels.contains(c->iId)) {
MumbleProto::UserState::VolumeAdjustment *adjustment = msg.add_listening_volume_adjustment();
adjustment->set_listening_channel(c->iId);
adjustment->set_volume_adjustment(volumeAdjustment);
}
}
for (int i = 0; i < msg.listening_channel_remove_size(); i++) {
Channel *c = qhChannels.value(msg.listening_channel_remove(i));
if (c) {
m_channelListenerManager.removeListener(pDstServerUser->uiSession, c->iId);
disableChannelListener(*pDstServerUser, *c);
log(QString::fromLatin1("\"%1\" is no longer listening to \"%2\"")
.arg(QString(*pDstServerUser))
@ -978,9 +1047,17 @@ void Server::msgUserState(ServerUser *uSource, MumbleProto::UserState &msg) {
}
}
bool listenerChanged = !listeningChannelsAdd.isEmpty() || msg.listening_channel_remove_size() > 0;
bool listenerVolumeChanged = msg.listening_volume_adjustment_size() > 0;
bool listenerChanged = !listeningChannelsAdd.isEmpty() || msg.listening_channel_remove_size() > 0;
bBroadcast = bBroadcast || listenerChanged;
bool broadcastingBecauseOfVolumeChange = !bBroadcast && listenerVolumeChanged;
bBroadcast = bBroadcast || listenerChanged || listenerVolumeChanged;
if (listenerChanged || listenerVolumeChanged) {
// As whisper targets also contain information about ChannelListeners and
// their associated volume adjustment, we have to clear the target cache
clearWhisperTargetCache();
}
bool bDstAclChanged = false;
@ -1033,11 +1110,21 @@ void Server::msgUserState(ServerUser *uSource, MumbleProto::UserState &msg) {
msg.set_comment_hash(blob(pDstServerUser->qbaCommentHash));
}
sendAll(msg, Version::fromComponents(1, 2, 2), Version::CompareMode::AtLeast);
if (uSource->m_version >= Version::fromComponents(1, 2, 2)) {
sendMessage(uSource, msg);
}
if (!broadcastListenerVolumeAdjustments) {
// Don't broadcast the volume adjustments to everyone
msg.clear_listening_volume_adjustment();
}
if (broadcastListenerVolumeAdjustments || !broadcastingBecauseOfVolumeChange) {
sendExcept(uSource, msg, Version::fromComponents(1, 2, 2), Version::CompareMode::AtLeast);
}
if (bDstAclChanged) {
clearACLCache(pDstServerUser);
} else if (listenerChanged) {
} else if (listenerChanged || listenerVolumeChanged) {
// We only have to do this if the ACLs didn't change as
// clearACLCache calls clearWhisperTargetChache anyways
clearWhisperTargetCache();

View File

@ -106,6 +106,8 @@ MetaParams::MetaParams() {
iPluginMessageLimit = 4;
iPluginMessageBurst = 15;
broadcastListenerVolumeAdjustments = false;
qsCiphers = MumbleSSL::defaultOpenSSLCipherString();
bLogGroupChanges = false;
@ -370,6 +372,8 @@ void MetaParams::read(QString fname) {
iPluginMessageLimit = typeCheckedFromSettings("pluginmessagelimit", 4);
iPluginMessageBurst = typeCheckedFromSettings("pluginmessageburst", 15);
broadcastListenerVolumeAdjustments = typeCheckedFromSettings("broadcastlistenervolumeadjustments", false);
bool bObfuscate = typeCheckedFromSettings("obfuscate", false);
if (bObfuscate) {
qWarning("IP address obfuscation enabled.");

View File

@ -104,6 +104,8 @@ public:
unsigned int iPluginMessageLimit;
unsigned int iPluginMessageBurst;
bool broadcastListenerVolumeAdjustments;
QSslCertificate qscCert;
QSslKey qskKey;

View File

@ -826,6 +826,20 @@ module MumbleServer
*/
idempotent IntList getListeningUsers(int channelid);
/**
* @param channelid The ID of the channel
* @param userid The ID of the user
* @returns The volume adjustment set for a listener of the given user in the given channel
*/
idempotent float getListenerVolumeAdjustment(int channelid, int userid);
/**
* Sets the volume adjustment set for a listener of the given user in the given channel
* @param channelid The ID of the channel
* @param userid The ID of the user
*/
idempotent void setListenerVolumeAdjustment(int channelid, int userid, float volumeAdjustment);
/**
* @param receiverUserIDs list of IDs of the users the message shall be sent to
*/

View File

@ -161,6 +161,12 @@ public:
virtual void getListeningUsers_async(const ::MumbleServer::AMD_Server_getListeningUsersPtr &, ::Ice::Int,
const Ice::Current &);
virtual void getListenerVolumeAdjustment_async(const ::MumbleServer::AMD_Server_getListenerVolumeAdjustmentPtr &,
::Ice::Int, ::Ice::Int, const Ice::Current &);
virtual void setListenerVolumeAdjustment_async(const ::MumbleServer::AMD_Server_setListenerVolumeAdjustmentPtr &,
::Ice::Int, ::Ice::Int, ::Ice::Float, const Ice::Current &);
virtual void sendWelcomeMessage_async(const ::MumbleServer::AMD_Server_sendWelcomeMessagePtr &,
const ::MumbleServer::IdList &p1, const ::Ice::Current &current);

View File

@ -7,6 +7,7 @@
#include "Ban.h"
#include "Channel.h"
#include "ChannelListenerManager.h"
#include "Group.h"
#include "Meta.h"
#include "MumbleServer.h"
@ -1809,6 +1810,27 @@ static void impl_Server_sendWelcomeMessage(const ::MumbleServer::AMD_Server_send
cb->ice_response();
}
static void impl_Server_getListenerVolumeAdjustment(const ::MumbleServer::AMD_Server_getListenerVolumeAdjustmentPtr cb,
int server_id, int channelid, int session) {
NEED_SERVER;
NEED_CHANNEL;
NEED_PLAYER;
cb->ice_response(
server->m_channelListenerManager.getListenerVolumeAdjustment(user->uiSession, channel->iId).factor);
}
static void impl_Server_setListenerVolumeAdjustment(const ::MumbleServer::AMD_Server_setListenerVolumeAdjustmentPtr cb,
int server_id, int channelid, int session, float volumeAdjustment) {
NEED_SERVER;
NEED_CHANNEL;
NEED_PLAYER;
server->setListenerVolumeAdjustment(user, channel, VolumeAdjustment::fromFactor(volumeAdjustment));
cb->ice_response();
}
static void impl_Server_addUserToGroup(const ::MumbleServer::AMD_Server_addUserToGroupPtr cb, int server_id,
::Ice::Int channelid, ::Ice::Int session, const ::std::string &group) {
NEED_SERVER;

View File

@ -10,6 +10,7 @@
#endif
#include "Channel.h"
#include "ChannelListenerManager.h"
#include "Group.h"
#include "Meta.h"
#include "QtUtils.h"
@ -357,14 +358,30 @@ void Server::startListeningToChannel(ServerUser *user, Channel *cChannel) {
return;
}
m_channelListenerManager.addListener(user->uiSession, cChannel->iId);
addChannelListener(*user, *cChannel);
MumbleProto::UserState mpus;
mpus.set_session(user->uiSession);
mpus.add_listening_channel_add(cChannel->iId);
sendAll(mpus);
if (!broadcastListenerVolumeAdjustments) {
sendExcept(user, mpus);
}
// Adding a listener might resurrect an old volume adjustment, so we need to
// inform the (all) client(s) about this volume adjustment.
float volumeAdjustment =
m_channelListenerManager.getListenerVolumeAdjustment(user->uiSession, cChannel->iId).factor;
MumbleProto::UserState::VolumeAdjustment *volume_adjustment = mpus.add_listening_volume_adjustment();
volume_adjustment->set_listening_channel(cChannel->iId);
volume_adjustment->set_volume_adjustment(volumeAdjustment);
if (broadcastListenerVolumeAdjustments) {
sendAll(mpus);
} else {
sendMessage(user, mpus);
}
}
void Server::stopListeningToChannel(ServerUser *user, Channel *cChannel) {
@ -373,7 +390,7 @@ void Server::stopListeningToChannel(ServerUser *user, Channel *cChannel) {
return;
}
m_channelListenerManager.removeListener(user->uiSession, cChannel->iId);
disableChannelListener(*user, *cChannel);
MumbleProto::UserState mpus;
mpus.set_session(user->uiSession);
@ -383,6 +400,29 @@ void Server::stopListeningToChannel(ServerUser *user, Channel *cChannel) {
sendAll(mpus);
}
void Server::setListenerVolumeAdjustment(ServerUser *user, const Channel *cChannel,
const VolumeAdjustment &volumeAdjustment) {
setChannelListenerVolume(*user, *cChannel, volumeAdjustment.factor);
// Inform clients about this change
MumbleProto::UserState mpus;
mpus.set_session(user->uiSession);
if (!broadcastListenerVolumeAdjustments) {
sendExcept(user, mpus);
}
MumbleProto::UserState::VolumeAdjustment *volume_adjustment = mpus.add_listening_volume_adjustment();
volume_adjustment->set_listening_channel(cChannel->iId);
volume_adjustment->set_volume_adjustment(volumeAdjustment.factor);
if (broadcastListenerVolumeAdjustments) {
sendAll(mpus);
} else {
sendMessage(user, mpus);
}
}
void Server::sendWelcomeMessageTo(ServerUser *user) {
MumbleProto::ServerConfig mpsc;
mpsc.set_welcome_text(qsWelcomeText.toUtf8().data());

View File

@ -315,43 +315,44 @@ Server::~Server() {
}
void Server::readParams() {
qsPassword = Meta::mp.qsPassword;
usPort = static_cast< unsigned short >(Meta::mp.usPort + iServerNum - 1);
iTimeout = Meta::mp.iTimeout;
iMaxBandwidth = Meta::mp.iMaxBandwidth;
iMaxUsers = Meta::mp.iMaxUsers;
iMaxUsersPerChannel = Meta::mp.iMaxUsersPerChannel;
iMaxTextMessageLength = Meta::mp.iMaxTextMessageLength;
iMaxImageMessageLength = Meta::mp.iMaxImageMessageLength;
bAllowHTML = Meta::mp.bAllowHTML;
iDefaultChan = Meta::mp.iDefaultChan;
bRememberChan = Meta::mp.bRememberChan;
iRememberChanDuration = Meta::mp.iRememberChanDuration;
qsWelcomeText = Meta::mp.qsWelcomeText;
qsWelcomeTextFile = Meta::mp.qsWelcomeTextFile;
qlBind = Meta::mp.qlBind;
qsRegName = Meta::mp.qsRegName;
qsRegPassword = Meta::mp.qsRegPassword;
qsRegHost = Meta::mp.qsRegHost;
qsRegLocation = Meta::mp.qsRegLocation;
qurlRegWeb = Meta::mp.qurlRegWeb;
bBonjour = Meta::mp.bBonjour;
bAllowPing = Meta::mp.bAllowPing;
allowRecording = Meta::mp.allowRecording;
bCertRequired = Meta::mp.bCertRequired;
bForceExternalAuth = Meta::mp.bForceExternalAuth;
qrUserName = Meta::mp.qrUserName;
qrChannelName = Meta::mp.qrChannelName;
iMessageLimit = Meta::mp.iMessageLimit;
iMessageBurst = Meta::mp.iMessageBurst;
iPluginMessageLimit = Meta::mp.iPluginMessageLimit;
iPluginMessageBurst = Meta::mp.iPluginMessageBurst;
m_suggestVersion = Meta::mp.m_suggestVersion;
qvSuggestPositional = Meta::mp.qvSuggestPositional;
qvSuggestPushToTalk = Meta::mp.qvSuggestPushToTalk;
iOpusThreshold = Meta::mp.iOpusThreshold;
iChannelNestingLimit = Meta::mp.iChannelNestingLimit;
iChannelCountLimit = Meta::mp.iChannelCountLimit;
qsPassword = Meta::mp.qsPassword;
usPort = static_cast< unsigned short >(Meta::mp.usPort + iServerNum - 1);
iTimeout = Meta::mp.iTimeout;
iMaxBandwidth = Meta::mp.iMaxBandwidth;
iMaxUsers = Meta::mp.iMaxUsers;
iMaxUsersPerChannel = Meta::mp.iMaxUsersPerChannel;
iMaxTextMessageLength = Meta::mp.iMaxTextMessageLength;
iMaxImageMessageLength = Meta::mp.iMaxImageMessageLength;
bAllowHTML = Meta::mp.bAllowHTML;
iDefaultChan = Meta::mp.iDefaultChan;
bRememberChan = Meta::mp.bRememberChan;
iRememberChanDuration = Meta::mp.iRememberChanDuration;
qsWelcomeText = Meta::mp.qsWelcomeText;
qsWelcomeTextFile = Meta::mp.qsWelcomeTextFile;
qlBind = Meta::mp.qlBind;
qsRegName = Meta::mp.qsRegName;
qsRegPassword = Meta::mp.qsRegPassword;
qsRegHost = Meta::mp.qsRegHost;
qsRegLocation = Meta::mp.qsRegLocation;
qurlRegWeb = Meta::mp.qurlRegWeb;
bBonjour = Meta::mp.bBonjour;
bAllowPing = Meta::mp.bAllowPing;
allowRecording = Meta::mp.allowRecording;
bCertRequired = Meta::mp.bCertRequired;
bForceExternalAuth = Meta::mp.bForceExternalAuth;
qrUserName = Meta::mp.qrUserName;
qrChannelName = Meta::mp.qrChannelName;
iMessageLimit = Meta::mp.iMessageLimit;
iMessageBurst = Meta::mp.iMessageBurst;
iPluginMessageLimit = Meta::mp.iPluginMessageLimit;
iPluginMessageBurst = Meta::mp.iPluginMessageBurst;
broadcastListenerVolumeAdjustments = Meta::mp.broadcastListenerVolumeAdjustments;
m_suggestVersion = Meta::mp.m_suggestVersion;
qvSuggestPositional = Meta::mp.qvSuggestPositional;
qvSuggestPushToTalk = Meta::mp.qvSuggestPushToTalk;
iOpusThreshold = Meta::mp.iOpusThreshold;
iChannelNestingLimit = Meta::mp.iChannelNestingLimit;
iChannelCountLimit = Meta::mp.iChannelCountLimit;
QString qsHost = getConf("host", QString()).toString();
if (!qsHost.isEmpty()) {
@ -462,6 +463,8 @@ void Server::readParams() {
if (iPluginMessageBurst < 1) { // Prevent disabling messages entirely
iPluginMessageBurst = 1;
}
broadcastListenerVolumeAdjustments =
getConf("broadcastlistenervolumeadjustments", broadcastListenerVolumeAdjustments).toBool();
}
void Server::setLiveConf(const QString &key, const QString &value) {
@ -595,6 +598,9 @@ void Server::setLiveConf(const QString &key, const QString &value) {
if (iMessageBurst < 1) {
iMessageBurst = 1;
}
} else if (key == "broadcastlistenervolumeadjustments") {
broadcastListenerVolumeAdjustments =
(!v.isNull() ? QVariant(v).toBool() : Meta::mp.broadcastListenerVolumeAdjustments);
}
}
@ -1087,6 +1093,16 @@ void Server::sendMessage(ServerUser &u, const unsigned char *data, int len, QByt
}
}
void Server::addListener(QHash< ServerUser *, VolumeAdjustment > &listeners, ServerUser &user, const Channel &channel) {
const VolumeAdjustment &volumeAdjustment =
m_channelListenerManager.getListenerVolumeAdjustment(user.uiSession, channel.iId);
auto it = listeners.find(&user);
if (it == listeners.end() || it->factor < volumeAdjustment.factor) {
listeners[&user] = volumeAdjustment;
}
}
void Server::processMsg(ServerUser *u, Mumble::Protocol::AudioData audioData, AudioReceiverBuffer &buffer,
Mumble::Protocol::UDPAudioEncoder< Mumble::Protocol::Role::Server > &encoder) {
@ -1124,7 +1140,8 @@ void Server::processMsg(ServerUser *u, Mumble::Protocol::AudioData audioData, Au
foreach (unsigned int currentSession, m_channelListenerManager.getListenersForChannel(c->iId)) {
ServerUser *pDst = static_cast< ServerUser * >(qhUsers.value(currentSession));
if (pDst) {
buffer.addReceiver(*u, *pDst, Mumble::Protocol::AudioContext::LISTEN, audioData.containsPositionalData);
buffer.addReceiver(*u, *pDst, Mumble::Protocol::AudioContext::LISTEN, audioData.containsPositionalData,
m_channelListenerManager.getListenerVolumeAdjustment(pDst->uiSession, c->iId));
}
}
@ -1148,8 +1165,9 @@ void Server::processMsg(ServerUser *u, Mumble::Protocol::AudioData audioData, Au
for (unsigned int currentSession : m_channelListenerManager.getListenersForChannel(l->iId)) {
ServerUser *pDst = static_cast< ServerUser * >(qhUsers.value(currentSession));
if (pDst) {
buffer.addReceiver(*u, *pDst, Mumble::Protocol::AudioContext::LISTEN,
audioData.containsPositionalData);
buffer.addReceiver(
*u, *pDst, Mumble::Protocol::AudioContext::LISTEN, audioData.containsPositionalData,
m_channelListenerManager.getListenerVolumeAdjustment(pDst->uiSession, l->iId));
}
}
@ -1166,7 +1184,7 @@ void Server::processMsg(ServerUser *u, Mumble::Protocol::AudioData audioData, Au
} else if (u->qmTargets.contains(audioData.targetOrContext)) { // Whisper/Shout
QSet< ServerUser * > channel;
QSet< ServerUser * > direct;
QSet< ServerUser * > listener;
QHash< ServerUser *, VolumeAdjustment > cachedListeners;
if (u->qmTargetCache.contains(audioData.targetOrContext)) {
ZoneScopedN(TracyConstants::AUDIO_WHISPER_CACHE_STORE);
@ -1174,7 +1192,7 @@ void Server::processMsg(ServerUser *u, Mumble::Protocol::AudioData audioData, Au
const WhisperTargetCache &cache = u->qmTargetCache.value(audioData.targetOrContext);
channel = cache.channelTargets;
direct = cache.directTargets;
listener = cache.listeningTargets;
cachedListeners = cache.listeningTargets;
} else {
ZoneScopedN(TracyConstants::AUDIO_WHISPER_CACHE_CREATE);
@ -1198,7 +1216,7 @@ void Server::processMsg(ServerUser *u, Mumble::Protocol::AudioData audioData, Au
ServerUser *pDst = static_cast< ServerUser * >(qhUsers.value(currentSession));
if (pDst) {
listener << pDst;
addListener(cachedListeners, *pDst, *wc);
}
}
}
@ -1229,7 +1247,7 @@ void Server::processMsg(ServerUser *u, Mumble::Protocol::AudioData audioData, Au
if (pDst && (!group || Group::isMember(tc, tc, qsg, pDst))) {
// Only send audio to listener if the user exists and it is in the group the
// speech is directed at (if any)
listener << pDst;
addListener(cachedListeners, *pDst, *tc);
}
}
}
@ -1237,10 +1255,6 @@ void Server::processMsg(ServerUser *u, Mumble::Protocol::AudioData audioData, Au
}
}
}
// If a user receives the audio through this shout anyways, we won't send it through the
// listening channel again (and thus sending the audio twice)
listener -= channel;
}
{
@ -1259,7 +1273,7 @@ void Server::processMsg(ServerUser *u, Mumble::Protocol::AudioData audioData, Au
qrwlVoiceThread.lockForWrite();
if (qhUsers.contains(uiSession))
u->qmTargetCache.insert(audioData.targetOrContext, { channel, direct, listener });
u->qmTargetCache.insert(audioData.targetOrContext, { channel, direct, cachedListeners });
qrwlVoiceThread.unlock();
qrwlVoiceThread.lockForRead();
if (!qhUsers.contains(uiSession))
@ -1274,8 +1288,14 @@ void Server::processMsg(ServerUser *u, Mumble::Protocol::AudioData audioData, Au
buffer.addReceiver(*u, *pDst, Mumble::Protocol::AudioContext::WHISPER, audioData.containsPositionalData);
}
// These users receive audio because someone is sending audio to one of their listeners
for (ServerUser *current : listener) {
buffer.addReceiver(*u, *current, Mumble::Protocol::AudioContext::LISTEN, audioData.containsPositionalData);
QHashIterator< ServerUser *, VolumeAdjustment > it(cachedListeners);
while (it.hasNext()) {
it.next();
ServerUser *user = it.key();
const VolumeAdjustment &volumeAdjustment = it.value();
buffer.addReceiver(*u, *user, Mumble::Protocol::AudioContext::LISTEN, audioData.containsPositionalData,
volumeAdjustment);
}
}
@ -1318,7 +1338,8 @@ void Server::processMsg(ServerUser *u, Mumble::Protocol::AudioData audioData, Au
isFirstIteration = false;
}
audioData.targetOrContext = currentRange.begin->getContext();
audioData.targetOrContext = currentRange.begin->getContext();
audioData.volumeAdjustment = currentRange.begin->getVolumeAdjustment();
// Update data
TracyCZoneN(__tracy_zone, TracyConstants::AUDIO_UPDATE, true);
@ -1605,8 +1626,8 @@ void Server::sslError(const QList< QSslError > &errors) {
void Server::connectionClosed(QAbstractSocket::SocketError err, const QString &reason) {
if (reason.contains(QLatin1String("140E0197"))) {
// A severe bug was introduced in qt/qtbase@93a803a6de27d9eb57931c431b5f3d074914f693.
// q_SSL_shutdown() causes Qt to emit "error()" from unrelated QSslSocket(s), in addition to the correct one.
// The issue causes this function to disconnect random authenticated clients.
// q_SSL_shutdown() causes Qt to emit "error()" from unrelated QSslSocket(s), in addition to the correct
// one. The issue causes this function to disconnect random authenticated clients.
//
// The workaround consists in ignoring a specific OpenSSL error:
// "Error while reading: error:140E0197:SSL routines:SSL_shutdown:shutdown while in init [20]"
@ -1631,19 +1652,10 @@ void Server::connectionClosed(QAbstractSocket::SocketError err, const QString &r
if (u->sState == ServerUser::Authenticated) {
if (m_channelListenerManager.isListeningToAny(u->uiSession)) {
// Send nessage to all other clients that this particular user won't be listening
// to any channel anymore
MumbleProto::UserState mpus;
mpus.set_session(u->uiSession);
foreach (int channelID, m_channelListenerManager.getListenedChannelsForUser(u->uiSession)) {
mpus.add_listening_channel_remove(channelID);
// Also remove the client from the list on the server
// Remove the client from the list on the server
m_channelListenerManager.removeListener(u->uiSession, channelID);
}
sendExcept(u, mpus);
}
MumbleProto::UserRemove mpur;
@ -1878,11 +1890,16 @@ void Server::removeChannel(Channel *chan, Channel *dest) {
}
foreach (unsigned int userSession, m_channelListenerManager.getListenersForChannel(chan->iId)) {
m_channelListenerManager.removeListener(userSession, chan->iId);
const ServerUser *user = qhUsers.value(userSession);
if (!user) {
continue;
}
deleteChannelListener(*user, *chan);
// Notify that all clients that have been listening to this channel, will do so no more
MumbleProto::UserState mpus;
mpus.set_session(userSession);
mpus.set_session(user->uiSession);
mpus.add_listening_channel_remove(chan->iId);
sendAll(mpus);
@ -2230,14 +2247,14 @@ void Server::recheckCodecVersions(ServerUser *connectingUser) {
if (bOpus) {
foreach (ServerUser *u, qhUsers) {
// Prevent connected users that could not yet declare their opus capability during msgAuthenticate from
// being spammed. Only authenticated users and the currently connecting user (if recheck is called in that
// context) have a reliable u->bOpus.
// being spammed. Only authenticated users and the currently connecting user (if recheck is called in
// that context) have a reliable u->bOpus.
if ((u->sState == ServerUser::Authenticated || u == connectingUser) && !u->bOpus) {
sendTextMessage(
nullptr, u, false,
QLatin1String(
"<strong>WARNING:</strong> Your client doesn't support the Opus codec the server is switching "
"to, you won't be able to talk or hear anyone. Please upgrade to a client with Opus support."));
sendTextMessage(nullptr, u, false,
QLatin1String("<strong>WARNING:</strong> Your client doesn't support the Opus "
"codec the server is switching "
"to, you won't be able to talk or hear anyone. Please upgrade to a "
"client with Opus support."));
}
}
}

View File

@ -22,6 +22,7 @@
#include "Timer.h"
#include "User.h"
#include "Version.h"
#include "VolumeAdjustment.h"
#ifndef Q_MOC_RUN
# include <boost/function.hpp>
@ -144,7 +145,10 @@ public:
unsigned int iPluginMessageLimit;
unsigned int iPluginMessageBurst;
bool broadcastListenerVolumeAdjustments;
Version::full_t m_suggestVersion;
QVariant qvSuggestPositional;
QVariant qvSuggestPushToTalk;
@ -309,6 +313,7 @@ public:
QList< Ban > qlBans;
void addListener(QHash< ServerUser *, VolumeAdjustment > &listeners, ServerUser &user, const Channel &channel);
void processMsg(ServerUser *u, Mumble::Protocol::AudioData audioData, AudioReceiverBuffer &buffer,
Mumble::Protocol::UDPAudioEncoder< Mumble::Protocol::Role::Server > &encoder);
void sendMessage(ServerUser &u, const unsigned char *data, int len, QByteArray &cache, bool force = false);
@ -380,6 +385,8 @@ public:
void clearTempGroups(User *user, Channel *cChannel = nullptr, bool recurse = true);
void startListeningToChannel(ServerUser *user, Channel *cChannel);
void stopListeningToChannel(ServerUser *user, Channel *cChannel);
void setListenerVolumeAdjustment(ServerUser *user, const Channel *cChannel,
const VolumeAdjustment &volumeAdjustment);
void sendWelcomeMessageTo(ServerUser *user);
signals:
void registerUserSig(int &, const QMap< int, QString > &);
@ -456,7 +463,16 @@ public:
void setConf(const QString &key, const QVariant &value);
void dblog(const QString &str) const;
// From msgHandler. Implementation in Messages.cpp
// These functions perform both the necessary changes to ChannelListeners as
// well as persisting the changed listeners state to the DB. You should use
// these unless you have a good reason not to
void loadChannelListenersOf(const ServerUser &user);
void addChannelListener(const ServerUser &user, const Channel &channel);
void disableChannelListener(const ServerUser &user, const Channel &channel);
void deleteChannelListener(const ServerUser &user, const Channel &channel);
void setChannelListenerVolume(const ServerUser &user, const Channel &channel, float volumeAdjustment);
// Implementation in Messages.cpp
#define PROCESS_MUMBLE_TCP_MESSAGE(name, value) void msg##name(ServerUser *, MumbleProto::name &);
MUMBLE_ALL_TCP_MESSAGES
#undef PROCESS_MUMBLE_TCP_MESSAGE

View File

@ -339,6 +339,9 @@ ServerDB::ServerDB() {
SQLQUERY("ALTER TABLE `%1user_info` RENAME TO `%1user_info%2`");
SQLQUERY("ALTER TABLE `%1channel_info` RENAME TO `%1channel_info%2`");
}
if (version >= 9) {
SQLQUERY("ALTER TABLE `%1channel_listeners` RENAME TO `%1channel_listeners%2`");
}
}
// Now we generate new tables that conform to the state-of-the-art structure
@ -367,6 +370,9 @@ ServerDB::ServerDB() {
SQLDO("DROP TRIGGER IF EXISTS `%1acl_del_user`");
SQLDO("DROP TRIGGER IF EXISTS `%1channel_links_del_channel`");
SQLDO("DROP TRIGGER IF EXISTS `%1bans_del_server`");
SQLDO("DROP TRIGGER IF EXISTS `%1channel_listeners_del_server`");
SQLDO("DROP TRIGGER IF EXISTS `%1channel_listeners_del_channel`");
SQLDO("DROP TRIGGER IF EXISTS `%1channel_listeners_del_user`");
SQLDO("DROP INDEX IF EXISTS `%1log_time`");
SQLDO("DROP INDEX IF EXISTS `%1slog_time`");
@ -463,6 +469,17 @@ ServerDB::ServerDB() {
"`hash` TEXT, `reason` TEXT, `start` DATE, `duration` INTEGER)");
SQLDO("CREATE TRIGGER `%1bans_del_server` AFTER DELETE ON `%1servers` FOR EACH ROW BEGIN DELETE FROM "
"`%1bans` WHERE `server_id` = old.`server_id`; END;");
SQLDO("CREATE TABLE `%1channel_listeners` (`server_id` INTEGER NOT NULL, `user_id` INTEGER NOT NULL, "
"`channel_id` INTEGER NOT NULL, `volume_adjustment` FLOAT DEFAULT 1, `enabled` SMALLINT DEFAULT 1)");
SQLDO("CREATE TRIGGER `%1channel_listeners_del_server` AFTER DELETE ON `%1servers` FOR EACH ROW BEGIN "
"DELETE FROM `%1channel_listeners` WHERE `server_id` = old.`server_id`; END;");
SQLDO("CREATE TRIGGER `%1channel_listeners_del_channel` AFTER DELETE ON `%1channels` FOR EACH ROW BEGIN "
"DELETE FROM `%1channel_listeners` WHERE `server_id` = old.`server_id` AND `channel_id` = "
"old.`channel_id`; END;");
SQLDO("CREATE TRIGGER `%1channel_listeners_del_user` AFTER DELETE ON `%1users` FOR EACH ROW BEGIN "
"DELETE FROM `%1channel_listeners` WHERE `server_id` = old.`server_id` AND `user_id` = "
"old.`user_id`; END;");
} else if (Meta::mp.qsDBDriver == "QPSQL") {
if (version > 0) {
typedef QPair< QString, QString > qsp;
@ -594,6 +611,18 @@ ServerDB::ServerDB() {
"varchar(255), `hash` CHAR(40), `reason` TEXT, `start` TIMESTAMP, `duration` INTEGER)");
SQLQUERY("ALTER TABLE `%1bans` ADD CONSTRAINT `%1bans_del_server` FOREIGN KEY(`server_id`) REFERENCES "
"`%1servers`(`server_id`) ON DELETE CASCADE");
SQLQUERY(
"CREATE TABLE `%1channel_listeners` (`server_id` INTEGER NOT NULL, `user_id` INTEGER NOT NULL, "
"`channel_id` INTEGER NOT NULL, `volume_adjustment` FLOAT DEFAULT 1, `enabled` SMALLINT DEFAULT 1)");
SQLQUERY("ALTER TABLE `%1channel_listeners` ADD CONSTRAINT `%1channel_listeners_del_server` FOREIGN "
"KEY(`server_id`) REFERENCES `%1servers`(`server_id`) ON DELETE CASCADE");
SQLQUERY("ALTER TABLE `%1channel_listeners` ADD CONSTRAINT `%1channel_listeners_del_channel` FOREIGN KEY "
"(`server_id`, "
"`channel_id`) REFERENCES `%1channels`(`server_id`, `channel_id`) ON DELETE CASCADE");
SQLQUERY("ALTER TABLE `%1channel_listeners` ADD CONSTRAINT `%1channel_listeners_del_user` FOREIGN KEY "
"(`server_id`, "
"`user_id`) REFERENCES `%1users`(`server_id`, `user_id`) ON DELETE CASCADE");
} else {
// MySQL
if (version > 0) {
@ -694,6 +723,15 @@ ServerDB::ServerDB() {
"varchar(255), `hash` CHAR(40), `reason` TEXT, `start` DATETIME, `duration` INTEGER) ENGINE=InnoDB");
SQLDO("ALTER TABLE `%1bans` ADD CONSTRAINT `%1bans_del_server` FOREIGN KEY(`server_id`) REFERENCES "
"`%1servers`(`server_id`) ON DELETE CASCADE");
SQLDO("CREATE TABLE `%1channel_listeners` (`server_id` INTEGER NOT NULL, `user_id` INTEGER NOT NULL, "
"`channel_id` INTEGER NOT NULL, `volume_adjustment` FLOAT DEFAULT 1, `enabled` SMALLINT DEFAULT 1)");
SQLDO("ALTER TABLE `%1channel_listeners` ADD CONSTRAINT `%1channel_listeners_del_server` FOREIGN "
"KEY(`server_id`) REFERENCES `%1servers`(`server_id`) ON DELETE CASCADE");
SQLDO("ALTER TABLE `%1channel_listeners` ADD CONSTRAINT `%1channel_listeners_del_channel` FOREIGN KEY "
"(`server_id`, `channel_id`) REFERENCES `%1channels`(`server_id`, `channel_id`) ON DELETE CASCADE");
SQLDO("ALTER TABLE `%1channel_listeners` ADD CONSTRAINT `%1channel_listeners_del_user` FOREIGN KEY "
"(`server_id`, `user_id`) REFERENCES `%1users`(`server_id`, `user_id`) ON DELETE CASCADE");
}
if (version == 0) {
@ -809,6 +847,9 @@ ServerDB::ServerDB() {
SQLDO("INSERT INTO `%1channel_info` SELECT * FROM `%1channel_info%2`");
}
if (version >= 9) {
SQLDO("INSERT INTO `%1channel_listeners` SELECT * FROM `%1channel_listeners%2`");
}
if (Meta::mp.qsDBDriver == "QMYSQL")
SQLDO("SET FOREIGN_KEY_CHECKS = 1;");
@ -826,6 +867,7 @@ ServerDB::ServerDB() {
SQLQUERY("DROP TABLE IF EXISTS `%1channels%2`");
SQLQUERY("DROP TABLE IF EXISTS `%1bans%2`");
SQLQUERY("DROP TABLE IF EXISTS `%1servers%2`");
SQLQUERY("DROP TABLE IF EXISTS `%1channel_listeners%2`");
SQLDO_NO_CONVERSION(QLatin1String("UPDATE `%1meta` SET `value` = ")
+ QString::fromLatin1("'%1' WHERE `keystring` = 'version'").arg(DB_STRUCTURE_VERSION));
@ -2371,6 +2413,129 @@ void Server::dblog(const QString &str) const {
SQLEXEC();
}
void Server::loadChannelListenersOf(const ServerUser &user) {
if (user.iId < 0) {
// Not registered
return;
}
TransactionHolder th;
QSqlQuery &query = *th.qsqQuery;
SQLPREP("SELECT `channel_id`, `volume_adjustment`, `enabled` FROM `%1channel_listeners` WHERE `server_id` = ? AND "
"`user_id` = ?");
query.addBindValue(iServerNum);
query.addBindValue(user.iId);
SQLEXEC();
while (query.next()) {
int channelID = query.value(0).toInt();
float volume = query.value(1).toFloat();
bool enabled = query.value(2).toUInt() == 1;
if (enabled) {
m_channelListenerManager.addListener(user.uiSession, channelID);
}
// We load the volume adjustment regardless of whether the listener is currently enabled in case the listener
// gets re-activated
m_channelListenerManager.setListenerVolumeAdjustment(user.uiSession, channelID,
VolumeAdjustment::fromFactor(volume));
}
}
void Server::addChannelListener(const ServerUser &user, const Channel &channel) {
if (user.iId >= 0) {
TransactionHolder th;
QSqlQuery &query = *th.qsqQuery;
// Update or insert entry
SQLPREP(
"SELECT COUNT(*) FROM `%1channel_listeners` WHERE `server_id` = ? AND `user_id` = ? AND `channel_id` = ?");
query.addBindValue(iServerNum);
query.addBindValue(user.iId);
query.addBindValue(channel.iId);
SQLEXEC();
bool entryAlreadyExists = query.next() && query.value(0).toInt() > 0;
if (entryAlreadyExists) {
SQLPREP("UPDATE `%1channel_listeners` SET `enabled` = 1 WHERE `server_id` = ? AND `user_id`= ? AND "
"`channel_id` = ?");
} else {
SQLPREP("INSERT INTO `%1channel_listeners` (`server_id`, `user_id`, `channel_id`) VALUES (?, ?, ?)");
}
query.addBindValue(iServerNum);
query.addBindValue(user.iId);
query.addBindValue(channel.iId);
SQLEXEC();
}
m_channelListenerManager.addListener(user.uiSession, channel.iId);
}
void Server::disableChannelListener(const ServerUser &user, const Channel &channel) {
if (!m_channelListenerManager.isListening(user.uiSession, channel.iId)) {
return;
}
if (user.iId >= 0) {
TransactionHolder th;
QSqlQuery &query = *th.qsqQuery;
SQLPREP("UPDATE `%1channel_listeners` SET `enabled` = ? WHERE `server_id` = ? AND `user_id` = ? AND "
"`channel_id` = ?");
// Explicit cast to int is required for Postgresql
query.addBindValue(static_cast< int >(false));
query.addBindValue(iServerNum);
query.addBindValue(user.iId);
query.addBindValue(channel.iId);
SQLEXEC();
}
m_channelListenerManager.removeListener(user.uiSession, channel.iId);
}
void Server::deleteChannelListener(const ServerUser &user, const Channel &channel) {
if (!m_channelListenerManager.isListening(user.uiSession, channel.iId)) {
return;
}
if (user.iId >= 0) {
TransactionHolder th;
QSqlQuery &query = *th.qsqQuery;
SQLPREP("DELETE FROM `%1channel_listeners` WHERE `server_id` = ? AND `user_id` = ? AND `channel_id` = ?");
query.addBindValue(iServerNum);
query.addBindValue(user.iId);
query.addBindValue(channel.iId);
SQLEXEC();
}
m_channelListenerManager.removeListener(user.uiSession, channel.iId);
}
void Server::setChannelListenerVolume(const ServerUser &user, const Channel &channel, float volumeAdjustment) {
if (user.iId >= 0) {
TransactionHolder th;
QSqlQuery &query = *th.qsqQuery;
SQLPREP("UPDATE `%1channel_listeners` SET `volume_adjustment` = ? WHERE `server_id` = ? AND `user_id` = ? AND "
"`channel_id` = ?");
query.addBindValue(volumeAdjustment);
query.addBindValue(iServerNum);
query.addBindValue(user.iId);
query.addBindValue(channel.iId);
SQLEXEC();
}
m_channelListenerManager.setListenerVolumeAdjustment(user.uiSession, channel.iId,
VolumeAdjustment::fromFactor(volumeAdjustment));
}
void ServerDB::wipeLogs() {
TransactionHolder th;
QSqlQuery &query = *th.qsqQuery;

View File

@ -26,7 +26,7 @@ public:
/// Whenever you change the DB structure (add a new table, added a new column in a table, etc.)
/// you have to increase this version number by one and add the respective "backwards compatibility
/// code" into the ServerDB code.
static const int DB_STRUCTURE_VERSION = 8;
static const int DB_STRUCTURE_VERSION = 9;
enum ChannelInfo { Channel_Description, Channel_Position, Channel_Max_Users };
enum UserInfo {

View File

@ -65,7 +65,7 @@ class ServerUser;
struct WhisperTargetCache {
QSet< ServerUser * > channelTargets;
QSet< ServerUser * > directTargets;
QSet< ServerUser * > listeningTargets;
QHash< ServerUser *, VolumeAdjustment > listeningTargets;
};
class Server;