diff options
-rw-r--r-- | res/css/application-low-priority.css | 2 | ||||
-rw-r--r-- | src/abaddon.cpp | 8 | ||||
-rw-r--r-- | src/audio/manager.cpp | 16 | ||||
-rw-r--r-- | src/audio/manager.hpp | 7 | ||||
-rw-r--r-- | src/discord/discord.cpp | 8 | ||||
-rw-r--r-- | src/discord/discord.hpp | 2 | ||||
-rw-r--r-- | src/discord/objects.cpp | 3 | ||||
-rw-r--r-- | src/discord/voiceclient.cpp | 9 | ||||
-rw-r--r-- | src/discord/voiceclient.hpp | 9 | ||||
-rw-r--r-- | src/windows/voicewindow.cpp | 70 | ||||
-rw-r--r-- | src/windows/voicewindow.hpp | 14 |
11 files changed, 140 insertions, 8 deletions
diff --git a/res/css/application-low-priority.css b/res/css/application-low-priority.css index cf108f4..5078d22 100644 --- a/res/css/application-low-priority.css +++ b/res/css/application-low-priority.css @@ -44,7 +44,7 @@ has to be separate to allow main.css to override certain things background: @secondary_color; } -.app-popup list { +.app-window list, .app-popup list { background: @secondary_color; } diff --git a/src/abaddon.cpp b/src/abaddon.cpp index 678353c..4c879ca 100644 --- a/src/abaddon.cpp +++ b/src/abaddon.cpp @@ -419,7 +419,7 @@ void Abaddon::DiscordOnThreadUpdate(const ThreadUpdateData &data) { #ifdef WITH_VOICE void Abaddon::OnVoiceConnected() { - auto *wnd = new VoiceWindow; + auto *wnd = new VoiceWindow(m_discord.GetVoiceChannelID()); wnd->signal_mute().connect([this](bool is_mute) { m_discord.SetVoiceMuted(is_mute); @@ -431,6 +431,12 @@ void Abaddon::OnVoiceConnected() { m_audio->SetPlayback(!is_deaf); }); + wnd->signal_mute_user_cs().connect([this](Snowflake id, bool is_mute) { + if (const auto ssrc = m_discord.GetSSRCOfUser(id); ssrc.has_value()) { + m_audio->SetMuteSSRC(*ssrc, is_mute); + } + }); + wnd->show(); wnd->signal_hide().connect([this, wnd]() { m_discord.DisconnectFromVoice(); diff --git a/src/audio/manager.cpp b/src/audio/manager.cpp index 35a9c76..e025390 100644 --- a/src/audio/manager.cpp +++ b/src/audio/manager.cpp @@ -1,5 +1,6 @@ #ifdef WITH_VOICE - // clang-format off +// clang-format off + #ifdef _WIN32 #include <winsock2.h> #endif @@ -138,6 +139,10 @@ void AudioManager::SetOpusBuffer(uint8_t *ptr) { void AudioManager::FeedMeOpus(uint32_t ssrc, const std::vector<uint8_t> &data) { if (!m_should_playback) return; + { + std::lock_guard<std::mutex> _(m_muted_ssrc_mutex); + if (m_muted_ssrcs.find(ssrc) != m_muted_ssrcs.end()) return; + } size_t payload_size = 0; const auto *opus_encoded = StripRTPExtensionHeader(data.data(), static_cast<int>(data.size()), payload_size); @@ -162,6 +167,15 @@ void AudioManager::SetPlayback(bool playback) { m_should_playback = playback; } +void AudioManager::SetMuteSSRC(uint32_t ssrc, bool mute) { + std::lock_guard<std::mutex> _(m_muted_ssrc_mutex); + if (mute) { + m_muted_ssrcs.insert(ssrc); + } else { + m_muted_ssrcs.erase(ssrc); + } +} + void AudioManager::OnCapturedPCM(const int16_t *pcm, ma_uint32 frames) { if (m_opus_buffer == nullptr || !m_should_capture) return; diff --git a/src/audio/manager.hpp b/src/audio/manager.hpp index 4986da6..277f2f3 100644 --- a/src/audio/manager.hpp +++ b/src/audio/manager.hpp @@ -8,6 +8,7 @@ #include <mutex> #include <thread> #include <unordered_map> +#include <unordered_set> #include <vector> #include <miniaudio.h> #include <opus.h> @@ -29,6 +30,8 @@ public: void SetCapture(bool capture); void SetPlayback(bool playback); + void SetMuteSSRC(uint32_t ssrc, bool mute); + [[nodiscard]] bool OK() const; private: @@ -58,6 +61,10 @@ private: std::atomic<bool> m_should_capture = true; std::atomic<bool> m_should_playback = true; + std::unordered_set<uint32_t> m_muted_ssrcs; + + mutable std::mutex m_muted_ssrc_mutex; + public: using type_signal_opus_packet = sigc::signal<void(int payload_size)>; type_signal_opus_packet signal_opus_packet(); diff --git a/src/discord/discord.cpp b/src/discord/discord.cpp index f5a1e1b..b6bcc07 100644 --- a/src/discord/discord.cpp +++ b/src/discord/discord.cpp @@ -1203,6 +1203,14 @@ Snowflake DiscordClient::GetVoiceChannelID() const noexcept { return m_voice_channel_id; } +std::unordered_set<Snowflake> DiscordClient::GetUsersInVoiceChannel(Snowflake channel_id) { + return m_voice_state_channel_users[channel_id]; +} + +std::optional<uint32_t> DiscordClient::GetSSRCOfUser(Snowflake id) const { + return m_voice.GetSSRCOfUser(id); +} + void DiscordClient::SetVoiceMuted(bool is_mute) { m_mute_requested = is_mute; SendVoiceStateUpdate(); diff --git a/src/discord/discord.hpp b/src/discord/discord.hpp index b0fecab..9c6c748 100644 --- a/src/discord/discord.hpp +++ b/src/discord/discord.hpp @@ -186,6 +186,8 @@ public: void DisconnectFromVoice(); [[nodiscard]] bool IsConnectedToVoice() const noexcept; [[nodiscard]] Snowflake GetVoiceChannelID() const noexcept; + [[nodiscard]] std::unordered_set<Snowflake> GetUsersInVoiceChannel(Snowflake channel_id); + [[nodiscard]] std::optional<uint32_t> GetSSRCOfUser(Snowflake id) const; void SetVoiceMuted(bool is_mute); void SetVoiceDeafened(bool is_deaf); diff --git a/src/discord/objects.cpp b/src/discord/objects.cpp index 21853a7..3e87e43 100644 --- a/src/discord/objects.cpp +++ b/src/discord/objects.cpp @@ -240,6 +240,7 @@ void from_json(const nlohmann::json &j, SupplementalGuildEntry &m) { void from_json(const nlohmann::json &j, ReadySupplementalData &m) { JS_D("merged_presences", m.MergedPresences); + JS_D("guilds", m.Guilds); } void to_json(nlohmann::json &j, const IdentifyProperties &m) { @@ -681,6 +682,6 @@ void from_json(const nlohmann::json &j, VoiceState &m) { JS_O("self_stream", m.IsSelfStream); JS_D("suppress", m.IsSuppressed); JS_D("user_id", m.UserID); - JS_N("member", m.Member); + JS_ON("member", m.Member); JS_D("session_id", m.SessionID); } diff --git a/src/discord/voiceclient.cpp b/src/discord/voiceclient.cpp index b7fe21b..5f6ab31 100644 --- a/src/discord/voiceclient.cpp +++ b/src/discord/voiceclient.cpp @@ -1,5 +1,6 @@ #ifdef WITH_VOICE // clang-format off + #include "voiceclient.hpp" #include "json.hpp" #include <sodium.h> @@ -220,6 +221,13 @@ void DiscordVoiceClient::SetUserID(Snowflake id) { m_user_id = id; } +std::optional<uint32_t> DiscordVoiceClient::GetSSRCOfUser(Snowflake id) const { + if (const auto it = m_ssrc_map.find(id); it != m_ssrc_map.end()) { + return it->second; + } + return {}; +} + bool DiscordVoiceClient::IsConnected() const noexcept { return m_connected; } @@ -296,6 +304,7 @@ void DiscordVoiceClient::HandleGatewaySessionDescription(const VoiceGatewayMessa void DiscordVoiceClient::HandleGatewaySpeaking(const VoiceGatewayMessage &m) { VoiceSpeakingData data = m.Data; + m_ssrc_map[data.UserID] = data.SSRC; m_signal_speaking.emit(data); } diff --git a/src/discord/voiceclient.hpp b/src/discord/voiceclient.hpp index c052f28..1dc6e1a 100644 --- a/src/discord/voiceclient.hpp +++ b/src/discord/voiceclient.hpp @@ -1,14 +1,17 @@ #pragma once #ifdef WITH_VOICE - // clang-format off +// clang-format off + #include "snowflake.hpp" #include "waiter.hpp" #include "websocket.hpp" +#include <optional> #include <mutex> #include <queue> #include <string> #include <glibmm/dispatcher.h> #include <sigc++/sigc++.h> +#include <unordered_map> // clang-format on enum class VoiceGatewayCloseCode : uint16_t { @@ -190,6 +193,8 @@ public: void SetServerID(Snowflake id); void SetUserID(Snowflake id); + [[nodiscard]] std::optional<uint32_t> GetSSRCOfUser(Snowflake id) const; + [[nodiscard]] bool IsConnected() const noexcept; private: @@ -218,6 +223,8 @@ private: uint16_t m_port; uint32_t m_ssrc; + std::unordered_map<Snowflake, uint32_t> m_ssrc_map; + std::array<uint8_t, 32> m_secret_key; Websocket m_ws; diff --git a/src/windows/voicewindow.cpp b/src/windows/voicewindow.cpp index b352c86..17f8ec2 100644 --- a/src/windows/voicewindow.cpp +++ b/src/windows/voicewindow.cpp @@ -1,24 +1,86 @@ #include "voicewindow.hpp" +#include "components/lazyimage.hpp" +#include "abaddon.hpp" -VoiceWindow::VoiceWindow() +class VoiceWindowUserListEntry : public Gtk::ListBoxRow { +public: + VoiceWindowUserListEntry(Snowflake id) + : m_main(Gtk::ORIENTATION_HORIZONTAL) + , m_avatar(32, 32) + , m_mute("Mute") { + m_name.set_halign(Gtk::ALIGN_START); + m_name.set_hexpand(true); + m_mute.set_halign(Gtk::ALIGN_END); + + m_main.add(m_avatar); + m_main.add(m_name); + m_main.add(m_mute); + add(m_main); + show_all_children(); + + auto &discord = Abaddon::Get().GetDiscordClient(); + const auto user = discord.GetUser(id); + if (user.has_value()) { + m_name.set_text(user->Username); + } else { + m_name.set_text("Unknown user"); + } + + m_mute.signal_toggled().connect([this]() { + m_signal_mute_cs.emit(m_mute.get_active()); + }); + } + +private: + Gtk::Box m_main; + LazyImage m_avatar; + Gtk::Label m_name; + Gtk::CheckButton m_mute; + +public: + using type_signal_mute_cs = sigc::signal<void(bool)>; + type_signal_mute_cs signal_mute_cs() { + return m_signal_mute_cs; + } + +private: + type_signal_mute_cs m_signal_mute_cs; +}; + +VoiceWindow::VoiceWindow(Snowflake channel_id) : m_main(Gtk::ORIENTATION_VERTICAL) , m_controls(Gtk::ORIENTATION_HORIZONTAL) , m_mute("Mute") - , m_deafen("Deafen") { + , m_deafen("Deafen") + , m_channel_id(channel_id) { get_style_context()->add_class("app-window"); set_default_size(300, 300); + auto &discord = Abaddon::Get().GetDiscordClient(); + SetUsers(discord.GetUsersInVoiceChannel(m_channel_id)); + m_mute.signal_toggled().connect(sigc::mem_fun(*this, &VoiceWindow::OnMuteChanged)); m_deafen.signal_toggled().connect(sigc::mem_fun(*this, &VoiceWindow::OnDeafenChanged)); m_controls.add(m_mute); m_controls.add(m_deafen); m_main.add(m_controls); + m_main.add(m_user_list); add(m_main); show_all_children(); } +void VoiceWindow::SetUsers(const std::unordered_set<Snowflake> &user_ids) { + for (auto id : user_ids) { + auto *row = Gtk::make_managed<VoiceWindowUserListEntry>(id); + row->signal_mute_cs().connect([this, id](bool is_muted) { + m_signal_mute_user_cs.emit(id, is_muted); + }); + m_user_list.add(*row); + } +} + void VoiceWindow::OnMuteChanged() { m_signal_mute.emit(m_mute.get_active()); } @@ -34,3 +96,7 @@ VoiceWindow::type_signal_mute VoiceWindow::signal_mute() { VoiceWindow::type_signal_deafen VoiceWindow::signal_deafen() { return m_signal_deafen; } + +VoiceWindow::type_signal_mute_user_cs VoiceWindow::signal_mute_user_cs() { + return m_signal_mute_user_cs; +} diff --git a/src/windows/voicewindow.hpp b/src/windows/voicewindow.hpp index bee0a84..a2162b4 100644 --- a/src/windows/voicewindow.hpp +++ b/src/windows/voicewindow.hpp @@ -1,13 +1,18 @@ #pragma once +#include "discord/snowflake.hpp" #include <gtkmm/box.h> #include <gtkmm/checkbutton.h> +#include <gtkmm/listbox.h> #include <gtkmm/window.h> +#include <unordered_set> class VoiceWindow : public Gtk::Window { public: - VoiceWindow(); + VoiceWindow(Snowflake channel_id); private: + void SetUsers(const std::unordered_set<Snowflake> &user_ids); + void OnMuteChanged(); void OnDeafenChanged(); @@ -17,14 +22,21 @@ private: Gtk::CheckButton m_mute; Gtk::CheckButton m_deafen; + Gtk::ListBox m_user_list; + + Snowflake m_channel_id; + public: using type_signal_mute = sigc::signal<void(bool)>; using type_signal_deafen = sigc::signal<void(bool)>; + using type_signal_mute_user_cs = sigc::signal<void(Snowflake, bool)>; type_signal_mute signal_mute(); type_signal_deafen signal_deafen(); + type_signal_mute_user_cs signal_mute_user_cs(); private: type_signal_mute m_signal_mute; type_signal_deafen m_signal_deafen; + type_signal_mute_user_cs m_signal_mute_user_cs; }; |