diff options
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 ¤t, 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 ¤t); 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; |