Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/mumble-voip/mumble.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRobert Adam <dev@robert-adam.de>2021-12-13 10:53:38 +0300
committerRobert Adam <dev@robert-adam.de>2022-09-12 13:42:01 +0300
commit3f4b00a425be622eb8ce9867f3490ddaff17472f (patch)
treeb016f3ed1b0b63e281b68aef74de851ed1563f30
parentac2e5bccb58a8c3dbd2fe7bc116ddd0f77d6befc (diff)
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.
-rw-r--r--src/ChannelListenerManager.cpp86
-rw-r--r--src/ChannelListenerManager.h44
-rw-r--r--src/Mumble.proto7
-rw-r--r--src/mumble/AudioOutput.cpp17
-rw-r--r--src/mumble/CMakeLists.txt4
-rw-r--r--src/mumble/ListenerLocalVolumeSlider.cpp30
-rw-r--r--src/mumble/ListenerVolumeSlider.cpp56
-rw-r--r--src/mumble/ListenerVolumeSlider.h (renamed from src/mumble/ListenerLocalVolumeSlider.h)7
-rw-r--r--src/mumble/MainWindow.cpp8
-rw-r--r--src/mumble/MainWindow.h4
-rw-r--r--src/mumble/Messages.cpp12
-rw-r--r--src/mumble/TalkingUI.cpp26
-rw-r--r--src/mumble/UserModel.cpp4
-rw-r--r--src/mumble/VolumeSliderWidgetAction.h2
-rw-r--r--src/mumble/main.cpp7
-rw-r--r--src/murmur/Messages.cpp103
-rw-r--r--src/murmur/Meta.cpp4
-rw-r--r--src/murmur/Meta.h2
-rw-r--r--src/murmur/MumbleServer.ice14
-rw-r--r--src/murmur/MumbleServerI.h6
-rw-r--r--src/murmur/MumbleServerIce.cpp22
-rw-r--r--src/murmur/RPC.cpp46
-rw-r--r--src/murmur/Server.cpp163
-rw-r--r--src/murmur/Server.h18
-rw-r--r--src/murmur/ServerDB.cpp165
-rw-r--r--src/murmur/ServerDB.h2
-rw-r--r--src/murmur/ServerUser.h2
27 files changed, 678 insertions, 183 deletions
diff --git a/src/ChannelListenerManager.cpp b/src/ChannelListenerManager.cpp
index 4954bb0ff..7151d90c1 100644
--- a/src/ChannelListenerManager.cpp
+++ b/src/ChannelListenerManager.cpp
@@ -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;
+
+ auto it = m_listenerVolumeAdjustments.find(key);
+ if (it != m_listenerVolumeAdjustments.end()) {
+ oldValue = it->second.factor;
+ }
+
+ m_listenerVolumeAdjustments[key] = volumeAdjustment;
}
- if (oldValue != volumeAdjustment) {
- emit localVolumeAdjustmentsChanged(channelID, volumeAdjustment, oldValue);
+ if (oldValue != volumeAdjustment.factor) {
+ emit localVolumeAdjustmentsChanged(channelID, volumeAdjustment.factor, oldValue);
}
}
-float ChannelListenerManager::getListenerLocalVolumeAdjustment(int channelID) const {
+const VolumeAdjustment &ChannelListenerManager::getListenerVolumeAdjustment(unsigned int userSession,
+ int channelID) const {
+ static VolumeAdjustment fallbackObj = VolumeAdjustment::fromFactor(1.0f);
+
QReadLocker lock(&m_volumeLock);
- return m_listenerVolumeAdjustments.value(channelID, 1.0f);
-}
+ ChannelListener key = {};
+ key.channelID = channelID;
+ key.userSession = userSession;
-QHash< int, float > ChannelListenerManager::getAllListenerLocalVolumeAdjustments(bool filter) const {
- QReadLocker lock(&m_volumeLock);
+ auto it = m_listenerVolumeAdjustments.find(key);
- if (!filter) {
- return m_listenerVolumeAdjustments;
+ if (it == m_listenerVolumeAdjustments.end()) {
+ return fallbackObj;
} else {
- QHash< int, float > volumeMap;
+ return it->second;
+ }
+}
- QHashIterator< int, float > it(m_listenerVolumeAdjustments);
+std::unordered_map< int, VolumeAdjustment >
+ ChannelListenerManager::getAllListenerVolumeAdjustments(unsigned int userSession) const {
+ QReadLocker lock1(&m_volumeLock);
+ QReadLocker lock2(&m_listenerLock);
- while (it.hasNext()) {
- it.next();
+ std::unordered_map< int, VolumeAdjustment > adjustments;
- if (it.value() != 1.0f) {
- volumeMap.insert(it.key(), it.value());
- }
- }
+ for (int channelID : m_listeningUsers.value(userSession)) {
+ ChannelListener listener = {};
+ listener.channelID = channelID;
+ listener.userSession = userSession;
- return volumeMap;
+ auto it = m_listenerVolumeAdjustments.find(listener);
+
+ if (it != m_listenerVolumeAdjustments.end() && it->second.factor != 1.0f) {
+ adjustments[channelID] = it->second;
+ }
}
+
+ return adjustments;
}
-#endif
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
}
diff --git a/src/ChannelListenerManager.h b/src/ChannelListenerManager.h
index a7beaf685..2cf401ea8 100644
--- a/src/ChannelListenerManager.h
+++ b/src/ChannelListenerManager.h
@@ -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_
diff --git a/src/Mumble.proto b/src/Mumble.proto
index 8f8b7c9a4..8561e21c5 100644
--- a/src/Mumble.proto
+++ b/src/Mumble.proto
@@ -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
diff --git a/src/mumble/AudioOutput.cpp b/src/mumble/AudioOutput.cpp
index 07a5d7dae..838e0b367 100644
--- a/src/mumble/AudioOutput.cpp
+++ b/src/mumble/AudioOutput.cpp
@@ -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) {
diff --git a/src/mumble/CMakeLists.txt b/src/mumble/CMakeLists.txt
index 585f09f47..018a1d3a1 100644
--- a/src/mumble/CMakeLists.txt
+++ b/src/mumble/CMakeLists.txt
@@ -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"
diff --git a/src/mumble/ListenerLocalVolumeSlider.cpp b/src/mumble/ListenerLocalVolumeSlider.cpp
deleted file mode 100644
index f3ccc0897..000000000
--- a/src/mumble/ListenerLocalVolumeSlider.cpp
+++ /dev/null
@@ -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);
- }
-}
diff --git a/src/mumble/ListenerVolumeSlider.cpp b/src/mumble/ListenerVolumeSlider.cpp
new file mode 100644
index 000000000..4fab69286
--- /dev/null
+++ b/src/mumble/ListenerVolumeSlider.cpp
@@ -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);
+ }
+}
diff --git a/src/mumble/ListenerLocalVolumeSlider.h b/src/mumble/ListenerVolumeSlider.h
index fb9ff539e..b41294419 100644
--- a/src/mumble/ListenerLocalVolumeSlider.h
+++ b/src/mumble/ListenerVolumeSlider.h
@@ -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
diff --git a/src/mumble/MainWindow.cpp b/src/mumble/MainWindow.cpp
index 02db85f04..4ba554ec7 100644
--- a/src/mumble/MainWindow.cpp
+++ b/src/mumble/MainWindow.cpp
@@ -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();
}
diff --git a/src/mumble/MainWindow.h b/src/mumble/MainWindow.h
index 880b33239..2390b8bbd 100644
--- a/src/mumble/MainWindow.h
+++ b/src/mumble/MainWindow.h
@@ -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();
diff --git a/src/mumble/Messages.cpp b/src/mumble/Messages.cpp
index cf4a4e819..5881029ca 100644
--- a/src/mumble/Messages.cpp
+++ b/src/mumble/Messages.cpp
@@ -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;
diff --git a/src/mumble/TalkingUI.cpp b/src/mumble/TalkingUI.cpp
index 7f00b328c..7c7d3119d 100644
--- a/src/mumble/TalkingUI.cpp
+++ b/src/mumble/TalkingUI.cpp
@@ -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() {
diff --git a/src/mumble/UserModel.cpp b/src/mumble/UserModel.cpp
index d39d2f830..0145f3b72 100644
--- a/src/mumble/UserModel.cpp
+++ b/src/mumble/UserModel.cpp
@@ -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();
diff --git a/src/mumble/VolumeSliderWidgetAction.h b/src/mumble/VolumeSliderWidgetAction.h
index e661427bb..3a53c3566 100644
--- a/src/mumble/VolumeSliderWidgetAction.h
+++ b/src/mumble/VolumeSliderWidgetAction.h
@@ -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(){};
};
diff --git a/src/mumble/main.cpp b/src/mumble/main.cpp
index 90b8e5edc..8d76c92b7 100644
--- a/src/mumble/main.cpp
+++ b/src/mumble/main.cpp
@@ -701,6 +701,13 @@ int main(int argc, char **argv) {
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,
+ &TalkingUI::on_channelListenerRemoved);
+ QObject::connect(Global::get().channelListenerManager.get(), &ChannelListenerManager::localVolumeAdjustmentsChanged,
+ Global::get().talkingUI, &TalkingUI::on_channelListenerLocalVolumeAdjustmentChanged);
+
QObject::connect(Global::get().mw, &MainWindow::serverSynchronized, Global::get().talkingUI,
&TalkingUI::on_serverSynchronized);
diff --git a/src/murmur/Messages.cpp b/src/murmur/Messages.cpp
index 486a22946..329d99c68 100644
--- a/src/murmur/Messages.cpp
+++ b/src/murmur/Messages.cpp
@@ -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();
diff --git a/src/murmur/Meta.cpp b/src/murmur/Meta.cpp
index 88cb9cb3a..7bbb379fb 100644
--- a/src/murmur/Meta.cpp
+++ b/src/murmur/Meta.cpp
@@ -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.");
diff --git a/src/murmur/Meta.h b/src/murmur/Meta.h
index 655c841a5..c9ef82dfa 100644
--- a/src/murmur/Meta.h
+++ b/src/murmur/Meta.h
@@ -104,6 +104,8 @@ public:
unsigned int iPluginMessageLimit;
unsigned int iPluginMessageBurst;
+ bool broadcastListenerVolumeAdjustments;
+
QSslCertificate qscCert;
QSslKey qskKey;
diff --git a/src/murmur/MumbleServer.ice b/src/murmur/MumbleServer.ice
index 3f7d8ccca..280b0214b 100644
--- a/src/murmur/MumbleServer.ice
+++ b/src/murmur/MumbleServer.ice
@@ -827,6 +827,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
*/
idempotent void sendWelcomeMessage(IdList receiverUserIDs);
diff --git a/src/murmur/MumbleServerI.h b/src/murmur/MumbleServerI.h
index e1ddb2d3c..1cc88e317 100644
--- a/src/murmur/MumbleServerI.h
+++ b/src/murmur/MumbleServerI.h
@@ -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);
diff --git a/src/murmur/MumbleServerIce.cpp b/src/murmur/MumbleServerIce.cpp
index 69a098b4c..e2e0ddcdb 100644
--- a/src/murmur/MumbleServerIce.cpp
+++ b/src/murmur/MumbleServerIce.cpp
@@ -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;
diff --git a/src/murmur/RPC.cpp b/src/murmur/RPC.cpp
index d04e29c04..2cc62da66 100644
--- a/src/murmur/RPC.cpp
+++ b/src/murmur/RPC.cpp
@@ -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());
diff --git a/src/murmur/Server.cpp b/src/murmur/Server.cpp
index c98972543..705d199dd 100644
--- a/src/murmur/Server.cpp
+++ b/src/murmur/Server.cpp
@@ -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."));
}
}
}
diff --git a/src/murmur/Server.h b/src/murmur/Server.h
index 31a0de69d..c78186f29 100644
--- a/src/murmur/Server.h
+++ b/src/murmur/Server.h
@@ -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
diff --git a/src/murmur/ServerDB.cpp b/src/murmur/ServerDB.cpp
index 354dd041e..d8924c858 100644
--- a/src/murmur/ServerDB.cpp
+++ b/src/murmur/ServerDB.cpp
@@ -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;
diff --git a/src/murmur/ServerDB.h b/src/murmur/ServerDB.h
index 2815ede5d..b3790a555 100644
--- a/src/murmur/ServerDB.h
+++ b/src/murmur/ServerDB.h
@@ -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 {
diff --git a/src/murmur/ServerUser.h b/src/murmur/ServerUser.h
index 4b9fcef17..6bcf201eb 100644
--- a/src/murmur/ServerUser.h
+++ b/src/murmur/ServerUser.h
@@ -65,7 +65,7 @@ class ServerUser;
struct WhisperTargetCache {
QSet< ServerUser * > channelTargets;
QSet< ServerUser * > directTargets;
- QSet< ServerUser * > listeningTargets;
+ QHash< ServerUser *, VolumeAdjustment > listeningTargets;
};
class Server;