From 092ff4289299fc0253bd72b5e62795f1a1ae589d Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Tue, 12 Mar 2024 21:31:28 -0400 Subject: split out and move some files --- src/windows/voice/voicewindow.hpp | 92 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 92 insertions(+) create mode 100644 src/windows/voice/voicewindow.hpp (limited to 'src/windows/voice/voicewindow.hpp') diff --git a/src/windows/voice/voicewindow.hpp b/src/windows/voice/voicewindow.hpp new file mode 100644 index 0000000..202a0ac --- /dev/null +++ b/src/windows/voice/voicewindow.hpp @@ -0,0 +1,92 @@ +#pragma once +#ifdef WITH_VOICE +// clang-format off + +#include "components/volumemeter.hpp" +#include "discord/snowflake.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +// clang-format on + +class VoiceWindowUserListEntry; + +class VoiceWindow : public Gtk::Window { +public: + VoiceWindow(Snowflake channel_id); + +private: + void SetUsers(const std::unordered_set &user_ids); + + Gtk::ListBoxRow *CreateRow(Snowflake id); + + void OnUserConnect(Snowflake user_id, Snowflake to_channel_id); + void OnUserDisconnect(Snowflake user_id, Snowflake from_channel_id); + + void OnMuteChanged(); + void OnDeafenChanged(); + + bool UpdateVoiceMeters(); + + void UpdateVADParamValue(); + + Gtk::Box m_main; + Gtk::Box m_controls; + + Gtk::CheckButton m_mute; + Gtk::CheckButton m_deafen; + + Gtk::ScrolledWindow m_scroll; + Gtk::ListBox m_user_list; + + // Shows volume for gate VAD method + // Shows probability for RNNoise VAD method + VolumeMeter m_vad_value; + // Volume threshold for gate VAD method + // VAD probability threshold for RNNoise VAD method + Gtk::Scale m_vad_param; + Gtk::Scale m_capture_gain; + + Gtk::CheckButton m_noise_suppression; + Gtk::CheckButton m_mix_mono; + + Gtk::ComboBoxText m_vad_combo; + Gtk::ComboBox m_playback_combo; + Gtk::ComboBox m_capture_combo; + + Snowflake m_channel_id; + + std::unordered_map m_rows; + + Gtk::MenuBar m_menu_bar; + Gtk::MenuItem m_menu_view; + Gtk::Menu m_menu_view_sub; + Gtk::MenuItem m_menu_view_settings; + + Gtk::Label m_TMP_stagelabel; + +public: + using type_signal_mute = sigc::signal; + using type_signal_deafen = sigc::signal; + using type_signal_mute_user_cs = sigc::signal; + using type_signal_user_volume_changed = sigc::signal; + + type_signal_mute signal_mute(); + type_signal_deafen signal_deafen(); + type_signal_mute_user_cs signal_mute_user_cs(); + type_signal_user_volume_changed signal_user_volume_changed(); + +private: + type_signal_mute m_signal_mute; + type_signal_deafen m_signal_deafen; + type_signal_mute_user_cs m_signal_mute_user_cs; + type_signal_user_volume_changed m_signal_user_volume_changed; +}; +#endif -- cgit v1.2.3 From 533157ece22c020dbf09d991b23d1cf4e5281e7a Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Thu, 14 Mar 2024 01:08:13 -0400 Subject: preliminary speaker checks --- .../channellist/cellrendererchannels.hpp | 2 +- src/components/channellist/channellisttree.cpp | 2 +- src/discord/discord.cpp | 9 +++++-- src/discord/discord.hpp | 7 +++--- src/discord/objects.cpp | 1 + src/discord/objects.hpp | 1 + src/discord/voicestate.cpp | 5 ++++ src/discord/voicestate.hpp | 29 ++++++++++++++++++++++ src/discord/voicestateflags.hpp | 19 -------------- src/misc/bitwise.hpp | 7 ++++++ src/windows/voice/voicewindow.cpp | 18 ++++++++++---- src/windows/voice/voicewindow.hpp | 1 + 12 files changed, 70 insertions(+), 31 deletions(-) create mode 100644 src/discord/voicestate.cpp create mode 100644 src/discord/voicestate.hpp delete mode 100644 src/discord/voicestateflags.hpp (limited to 'src/windows/voice/voicewindow.hpp') diff --git a/src/components/channellist/cellrendererchannels.hpp b/src/components/channellist/cellrendererchannels.hpp index a1c020b..813a996 100644 --- a/src/components/channellist/cellrendererchannels.hpp +++ b/src/components/channellist/cellrendererchannels.hpp @@ -6,7 +6,7 @@ #include #include #include "discord/snowflake.hpp" -#include "discord/voicestateflags.hpp" +#include "discord/voicestate.hpp" #include "misc/bitwise.hpp" enum class RenderType : uint8_t { diff --git a/src/components/channellist/channellisttree.cpp b/src/components/channellist/channellisttree.cpp index 8b313a3..9233941 100644 --- a/src/components/channellist/channellisttree.cpp +++ b/src/components/channellist/channellisttree.cpp @@ -1061,7 +1061,7 @@ Gtk::TreeModel::iterator ChannelListTree::CreateVoiceParticipantRow(const UserDa const auto voice_state = Abaddon::Get().GetDiscordClient().GetVoiceState(user.ID); if (voice_state.has_value()) { - row[m_columns.m_voice_flags] = voice_state->second; + row[m_columns.m_voice_flags] = voice_state->second.Flags; } auto &img = Abaddon::Get().GetImageManager(); diff --git a/src/discord/discord.cpp b/src/discord/discord.cpp index ab0374d..d997bc2 100644 --- a/src/discord/discord.cpp +++ b/src/discord/discord.cpp @@ -1290,13 +1290,18 @@ std::optional DiscordClient::GetSSRCOfUser(Snowflake id) const { return m_voice.GetSSRCOfUser(id); } -std::optional> DiscordClient::GetVoiceState(Snowflake user_id) const { +std::optional> DiscordClient::GetVoiceState(Snowflake user_id) const { if (const auto it = m_voice_states.find(user_id); it != m_voice_states.end()) { return it->second; } return std::nullopt; } +bool DiscordClient::IsUserSpeaker(Snowflake user_id) const { + const auto state = GetVoiceState(user_id); + return state.has_value() && state->second.IsSpeaker(); +} + DiscordVoiceClient &DiscordClient::GetVoiceClient() { return m_voice; } @@ -2962,7 +2967,7 @@ void DiscordClient::SetVoiceState(Snowflake user_id, const VoiceState &state) { if (state.IsSelfVideo) flags |= VoiceStateFlags::SelfVideo; if (state.IsSuppressed) flags |= VoiceStateFlags::Suppressed; - m_voice_states[user_id] = std::make_pair(*state.ChannelID, flags); + m_voice_states[user_id] = std::make_pair(*state.ChannelID, PackedVoiceState { flags, state.RequestToSpeakTimestamp }); m_voice_state_channel_users[*state.ChannelID].insert(user_id); m_signal_voice_state_set.emit(user_id, *state.ChannelID, flags); diff --git a/src/discord/discord.hpp b/src/discord/discord.hpp index a74b5f6..155079f 100644 --- a/src/discord/discord.hpp +++ b/src/discord/discord.hpp @@ -5,7 +5,7 @@ #include "objects.hpp" #include "store.hpp" #include "voiceclient.hpp" -#include "voicestateflags.hpp" +#include "voicestate.hpp" #include "websocket.hpp" #include #include @@ -202,7 +202,8 @@ public: [[nodiscard]] Snowflake GetVoiceChannelID() const noexcept; [[nodiscard]] std::unordered_set GetUsersInVoiceChannel(Snowflake channel_id); [[nodiscard]] std::optional GetSSRCOfUser(Snowflake id) const; - [[nodiscard]] std::optional> GetVoiceState(Snowflake user_id) const; + [[nodiscard]] std::optional> GetVoiceState(Snowflake user_id) const; + [[nodiscard]] bool IsUserSpeaker(Snowflake user_id) const; DiscordVoiceClient &GetVoiceClient(); @@ -380,7 +381,7 @@ private: Snowflake m_voice_channel_id; // todo sql i guess - std::unordered_map> m_voice_states; + std::unordered_map> m_voice_states; std::unordered_map> m_voice_state_channel_users; void SendVoiceStateUpdate(); diff --git a/src/discord/objects.cpp b/src/discord/objects.cpp index 804f10d..1c5dd39 100644 --- a/src/discord/objects.cpp +++ b/src/discord/objects.cpp @@ -714,4 +714,5 @@ void from_json(const nlohmann::json &j, VoiceState &m) { JS_D("user_id", m.UserID); JS_ON("member", m.Member); JS_D("session_id", m.SessionID); + JS_ON("request_to_speak_timestamp", m.RequestToSpeakTimestamp); } diff --git a/src/discord/objects.hpp b/src/discord/objects.hpp index 67474a3..e026311 100644 --- a/src/discord/objects.hpp +++ b/src/discord/objects.hpp @@ -921,6 +921,7 @@ struct VoiceState { std::string SessionID; bool IsSuppressed; Snowflake UserID; + std::optional RequestToSpeakTimestamp; friend void from_json(const nlohmann::json &j, VoiceState &m); }; diff --git a/src/discord/voicestate.cpp b/src/discord/voicestate.cpp new file mode 100644 index 0000000..05c050d --- /dev/null +++ b/src/discord/voicestate.cpp @@ -0,0 +1,5 @@ +#include "voicestate.hpp" + +bool PackedVoiceState::IsSpeaker() const noexcept { + return ((Flags & VoiceStateFlags::Suppressed) != VoiceStateFlags::Suppressed) && !RequestToSpeakTimestamp.has_value(); +} diff --git a/src/discord/voicestate.hpp b/src/discord/voicestate.hpp new file mode 100644 index 0000000..cc75b0c --- /dev/null +++ b/src/discord/voicestate.hpp @@ -0,0 +1,29 @@ +#pragma once +#include +#include +#include +#include "misc/bitwise.hpp" + +// this is packed into a enum cuz it makes implementing tree models easier +enum class VoiceStateFlags : uint8_t { + Clear = 0, + Deaf = 1 << 0, + Mute = 1 << 1, + SelfDeaf = 1 << 2, + SelfMute = 1 << 3, + SelfStream = 1 << 4, + SelfVideo = 1 << 5, + Suppressed = 1 << 6, +}; + +struct PackedVoiceState { + VoiceStateFlags Flags; + std::optional RequestToSpeakTimestamp; + + [[nodiscard]] bool IsSpeaker() const noexcept; +}; + +template<> +struct Bitwise { + static const bool enable = true; +}; diff --git a/src/discord/voicestateflags.hpp b/src/discord/voicestateflags.hpp deleted file mode 100644 index e725d9a..0000000 --- a/src/discord/voicestateflags.hpp +++ /dev/null @@ -1,19 +0,0 @@ -#pragma once -#include -#include "misc/bitwise.hpp" - -enum class VoiceStateFlags : uint8_t { - Clear = 0, - Deaf = 1 << 0, - Mute = 1 << 1, - SelfDeaf = 1 << 2, - SelfMute = 1 << 3, - SelfStream = 1 << 4, - SelfVideo = 1 << 5, - Suppressed = 1 << 6, -}; - -template<> -struct Bitwise { - static const bool enable = true; -}; diff --git a/src/misc/bitwise.hpp b/src/misc/bitwise.hpp index ecce333..4d4cf8f 100644 --- a/src/misc/bitwise.hpp +++ b/src/misc/bitwise.hpp @@ -1,6 +1,13 @@ #pragma once #include +namespace util { +template +bool FlagSet(T flags, T value) { + return (flags & value) == value; +} +} // namespace util + template struct Bitwise { static const bool enable = false; diff --git a/src/windows/voice/voicewindow.cpp b/src/windows/voice/voicewindow.cpp index 05b2ade..1ada8ee 100644 --- a/src/windows/voice/voicewindow.cpp +++ b/src/windows/voice/voicewindow.cpp @@ -29,14 +29,17 @@ VoiceWindow::VoiceWindow(Snowflake channel_id) auto &discord = Abaddon::Get().GetDiscordClient(); auto &audio = Abaddon::Get().GetAudio(); + const auto channel = discord.GetChannel(m_channel_id); + m_is_stage = channel.has_value() && channel->Type == ChannelType::GUILD_STAGE_VOICE; + SetUsers(discord.GetUsersInVoiceChannel(m_channel_id)); discord.signal_voice_user_disconnect().connect(sigc::mem_fun(*this, &VoiceWindow::OnUserDisconnect)); discord.signal_voice_user_connect().connect(sigc::mem_fun(*this, &VoiceWindow::OnUserConnect)); if (const auto self_state = discord.GetVoiceState(discord.GetUserData().ID); self_state.has_value()) { - m_mute.set_active((self_state->second & VoiceStateFlags::SelfMute) == VoiceStateFlags::SelfMute); - m_deafen.set_active((self_state->second & VoiceStateFlags::SelfDeaf) == VoiceStateFlags::SelfDeaf); + m_mute.set_active(util::FlagSet(self_state->second.Flags, VoiceStateFlags::SelfMute)); + m_deafen.set_active(util::FlagSet(self_state->second.Flags, VoiceStateFlags::SelfDeaf)); } m_mute.signal_toggled().connect(sigc::mem_fun(*this, &VoiceWindow::OnMuteChanged)); @@ -214,10 +217,13 @@ VoiceWindow::VoiceWindow(Snowflake channel_id) } void VoiceWindow::SetUsers(const std::unordered_set &user_ids) { - const auto me = Abaddon::Get().GetDiscordClient().GetUserData().ID; + auto &discord = Abaddon::Get().GetDiscordClient(); + const auto me = discord.GetUserData().ID; for (auto id : user_ids) { if (id == me) continue; - m_user_list.add(*CreateRow(id)); + if (discord.IsUserSpeaker(id)) { + m_user_list.add(*CreateRow(id)); + } } } @@ -283,7 +289,9 @@ void VoiceWindow::UpdateVADParamValue() { void VoiceWindow::OnUserConnect(Snowflake user_id, Snowflake to_channel_id) { if (m_channel_id == to_channel_id) { if (auto it = m_rows.find(user_id); it == m_rows.end()) { - m_user_list.add(*CreateRow(user_id)); + if (Abaddon::Get().GetDiscordClient().IsUserSpeaker(user_id)) { + m_user_list.add(*CreateRow(user_id)); + } } } } diff --git a/src/windows/voice/voicewindow.hpp b/src/windows/voice/voicewindow.hpp index 202a0ac..5d73e97 100644 --- a/src/windows/voice/voicewindow.hpp +++ b/src/windows/voice/voicewindow.hpp @@ -62,6 +62,7 @@ private: Gtk::ComboBox m_capture_combo; Snowflake m_channel_id; + bool m_is_stage; std::unordered_map m_rows; -- cgit v1.2.3 From eca1a9f0e07b9c942502e3b4ed797d61f23ccc15 Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Thu, 14 Mar 2024 01:38:36 -0400 Subject: track stage speakers only --- src/discord/discord.cpp | 15 ++++++++++++--- src/discord/discord.hpp | 3 +++ src/windows/voice/voicewindow.cpp | 15 +++++++++++++++ src/windows/voice/voicewindow.hpp | 1 + 4 files changed, 31 insertions(+), 3 deletions(-) (limited to 'src/windows/voice/voicewindow.hpp') diff --git a/src/discord/discord.cpp b/src/discord/discord.cpp index d997bc2..40af498 100644 --- a/src/discord/discord.cpp +++ b/src/discord/discord.cpp @@ -2426,9 +2426,14 @@ void DiscordClient::CheckVoiceState(const VoiceState &data) { if (data.ChannelID.has_value()) { const auto old_state = GetVoiceState(data.UserID); SetVoiceState(data.UserID, data); - if (old_state.has_value() && old_state->first != *data.ChannelID) { - m_signal_voice_user_disconnect.emit(data.UserID, old_state->first); - m_signal_voice_user_connect.emit(data.UserID, *data.ChannelID); + const auto new_state = GetVoiceState(data.UserID); + if (old_state.has_value()) { + if (old_state->first != *data.ChannelID) { + m_signal_voice_user_disconnect.emit(data.UserID, old_state->first); + m_signal_voice_user_connect.emit(data.UserID, *data.ChannelID); + } else if (old_state->second.IsSpeaker() != new_state.value().second.IsSpeaker()) { + m_signal_voice_speaker_state_changed.emit(*data.ChannelID, data.UserID, new_state->second.IsSpeaker()); + } } else if (!old_state.has_value()) { m_signal_voice_user_connect.emit(data.UserID, *data.ChannelID); } @@ -3308,4 +3313,8 @@ DiscordClient::type_signal_voice_channel_changed DiscordClient::signal_voice_cha DiscordClient::type_signal_voice_state_set DiscordClient::signal_voice_state_set() { return m_signal_voice_state_set; } + +DiscordClient::type_signal_voice_speaker_state_changed DiscordClient::signal_voice_speaker_state_changed() { + return m_signal_voice_speaker_state_changed; +} #endif diff --git a/src/discord/discord.hpp b/src/discord/discord.hpp index 155079f..ed7245e 100644 --- a/src/discord/discord.hpp +++ b/src/discord/discord.hpp @@ -480,6 +480,7 @@ public: using type_signal_voice_client_state_update = sigc::signal; using type_signal_voice_channel_changed = sigc::signal; using type_signal_voice_state_set = sigc::signal; + using type_signal_voice_speaker_state_changed = sigc::signal; #endif type_signal_gateway_ready signal_gateway_ready(); @@ -551,6 +552,7 @@ public: type_signal_voice_client_state_update signal_voice_client_state_update(); type_signal_voice_channel_changed signal_voice_channel_changed(); type_signal_voice_state_set signal_voice_state_set(); + type_signal_voice_speaker_state_changed signal_voice_speaker_state_changed(); #endif protected: @@ -623,5 +625,6 @@ protected: type_signal_voice_client_state_update m_signal_voice_client_state_update; type_signal_voice_channel_changed m_signal_voice_channel_changed; type_signal_voice_state_set m_signal_voice_state_set; + type_signal_voice_speaker_state_changed m_signal_voice_speaker_state_changed; #endif }; diff --git a/src/windows/voice/voicewindow.cpp b/src/windows/voice/voicewindow.cpp index 1ada8ee..079d0f7 100644 --- a/src/windows/voice/voicewindow.cpp +++ b/src/windows/voice/voicewindow.cpp @@ -36,6 +36,7 @@ VoiceWindow::VoiceWindow(Snowflake channel_id) discord.signal_voice_user_disconnect().connect(sigc::mem_fun(*this, &VoiceWindow::OnUserDisconnect)); discord.signal_voice_user_connect().connect(sigc::mem_fun(*this, &VoiceWindow::OnUserConnect)); + discord.signal_voice_speaker_state_changed().connect(sigc::mem_fun(*this, &VoiceWindow::OnSpeakerStateChanged)); if (const auto self_state = discord.GetVoiceState(discord.GetUserData().ID); self_state.has_value()) { m_mute.set_active(util::FlagSet(self_state->second.Flags, VoiceStateFlags::SelfMute)); @@ -305,6 +306,20 @@ void VoiceWindow::OnUserDisconnect(Snowflake user_id, Snowflake from_channel_id) } } +void VoiceWindow::OnSpeakerStateChanged(Snowflake channel_id, Snowflake user_id, bool is_speaker) { + if (m_channel_id != channel_id) return; + if (is_speaker) { + if (auto it = m_rows.find(user_id); it == m_rows.end()) { + m_user_list.add(*CreateRow(user_id)); + } + } else { + if (auto it = m_rows.find(user_id); it != m_rows.end()) { + delete it->second; + m_rows.erase(it); + } + } +} + VoiceWindow::type_signal_mute VoiceWindow::signal_mute() { return m_signal_mute; } diff --git a/src/windows/voice/voicewindow.hpp b/src/windows/voice/voicewindow.hpp index 5d73e97..fea998c 100644 --- a/src/windows/voice/voicewindow.hpp +++ b/src/windows/voice/voicewindow.hpp @@ -29,6 +29,7 @@ private: void OnUserConnect(Snowflake user_id, Snowflake to_channel_id); void OnUserDisconnect(Snowflake user_id, Snowflake from_channel_id); + void OnSpeakerStateChanged(Snowflake channel_id, Snowflake user_id, bool is_speaker); void OnMuteChanged(); void OnDeafenChanged(); -- cgit v1.2.3 From 1367e162c0581bdea7ec1d2129b18e70d7d43d2b Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Tue, 19 Mar 2024 21:59:50 -0400 Subject: separate audience section in voice --- src/windows/voice/voicewindow.cpp | 61 ++++++++++++++-------- src/windows/voice/voicewindow.hpp | 15 ++++-- src/windows/voice/voicewindowaudiencelistentry.cpp | 22 ++++++++ src/windows/voice/voicewindowaudiencelistentry.hpp | 18 +++++++ src/windows/voice/voicewindowspeakerlistentry.cpp | 58 ++++++++++++++++++++ src/windows/voice/voicewindowspeakerlistentry.hpp | 38 ++++++++++++++ src/windows/voice/voicewindowuserlistentry.cpp | 58 -------------------- src/windows/voice/voicewindowuserlistentry.hpp | 38 -------------- 8 files changed, 185 insertions(+), 123 deletions(-) create mode 100644 src/windows/voice/voicewindowaudiencelistentry.cpp create mode 100644 src/windows/voice/voicewindowaudiencelistentry.hpp create mode 100644 src/windows/voice/voicewindowspeakerlistentry.cpp create mode 100644 src/windows/voice/voicewindowspeakerlistentry.hpp delete mode 100644 src/windows/voice/voicewindowuserlistentry.cpp delete mode 100644 src/windows/voice/voicewindowuserlistentry.hpp (limited to 'src/windows/voice/voicewindow.hpp') diff --git a/src/windows/voice/voicewindow.cpp b/src/windows/voice/voicewindow.cpp index 079d0f7..5e91aad 100644 --- a/src/windows/voice/voicewindow.cpp +++ b/src/windows/voice/voicewindow.cpp @@ -7,7 +7,8 @@ #include "abaddon.hpp" #include "audio/manager.hpp" #include "components/lazyimage.hpp" -#include "voicewindowuserlistentry.hpp" +#include "voicewindowaudiencelistentry.hpp" +#include "voicewindowspeakerlistentry.hpp" #include "windows/voicesettingswindow.hpp" // clang-format on @@ -196,7 +197,13 @@ VoiceWindow::VoiceWindow(Snowflake channel_id) }, *this)); - m_scroll.add(m_user_list); + m_TMP_speakers_label.set_markup("Speakers"); + m_listing.pack_start(m_TMP_speakers_label, false, true); + m_listing.pack_start(m_speakers_list, false, true); + m_TMP_audience_label.set_markup("Audience"); + m_listing.pack_start(m_TMP_audience_label, false, true); + m_listing.pack_start(m_audience_list, false, true); + m_scroll.add(m_listing); m_controls.add(m_mute); m_controls.add(m_deafen); m_controls.add(m_noise_suppression); @@ -221,15 +228,16 @@ void VoiceWindow::SetUsers(const std::unordered_set &user_ids) { auto &discord = Abaddon::Get().GetDiscordClient(); const auto me = discord.GetUserData().ID; for (auto id : user_ids) { - if (id == me) continue; if (discord.IsUserSpeaker(id)) { - m_user_list.add(*CreateRow(id)); + if (id != me) m_speakers_list.add(*CreateSpeakerRow(id)); + } else { + m_audience_list.add(*CreateAudienceRow(id)); } } } -Gtk::ListBoxRow *VoiceWindow::CreateRow(Snowflake id) { - auto *row = Gtk::make_managed(id); +Gtk::ListBoxRow *VoiceWindow::CreateSpeakerRow(Snowflake id) { + auto *row = Gtk::make_managed(id); m_rows[id] = row; auto &vc = Abaddon::Get().GetDiscordClient().GetVoiceClient(); row->RestoreGain(vc.GetUserVolume(id)); @@ -239,7 +247,14 @@ Gtk::ListBoxRow *VoiceWindow::CreateRow(Snowflake id) { row->signal_volume().connect([this, id](double volume) { m_signal_user_volume_changed.emit(id, volume); }); - row->show_all(); + row->show(); + return row; +} + +Gtk::ListBoxRow *VoiceWindow::CreateAudienceRow(Snowflake id) { + auto *row = Gtk::make_managed(id); + m_rows[id] = row; + row->show(); return row; } @@ -251,6 +266,13 @@ void VoiceWindow::OnDeafenChanged() { m_signal_deafen.emit(m_deafen.get_active()); } +void VoiceWindow::TryDeleteRow(Snowflake id) { + if (auto it = m_rows.find(id); it != m_rows.end()) { + delete it->second; + m_rows.erase(it); + } +} + bool VoiceWindow::UpdateVoiceMeters() { auto &audio = Abaddon::Get().GetAudio(); switch (audio.GetVADMethod()) { @@ -267,7 +289,9 @@ bool VoiceWindow::UpdateVoiceMeters() { for (auto [id, row] : m_rows) { const auto ssrc = Abaddon::Get().GetDiscordClient().GetSSRCOfUser(id); if (ssrc.has_value()) { - row->SetVolumeMeter(audio.GetSSRCVolumeLevel(*ssrc)); + if (auto *speaker_row = dynamic_cast(row)) { + speaker_row->SetVolumeMeter(audio.GetSSRCVolumeLevel(*ssrc)); + } } } return true; @@ -291,32 +315,25 @@ void VoiceWindow::OnUserConnect(Snowflake user_id, Snowflake to_channel_id) { if (m_channel_id == to_channel_id) { if (auto it = m_rows.find(user_id); it == m_rows.end()) { if (Abaddon::Get().GetDiscordClient().IsUserSpeaker(user_id)) { - m_user_list.add(*CreateRow(user_id)); + m_speakers_list.add(*CreateSpeakerRow(user_id)); + } else { + m_audience_list.add(*CreateAudienceRow(user_id)); } } } } void VoiceWindow::OnUserDisconnect(Snowflake user_id, Snowflake from_channel_id) { - if (m_channel_id == from_channel_id) { - if (auto it = m_rows.find(user_id); it != m_rows.end()) { - delete it->second; - m_rows.erase(it); - } - } + if (m_channel_id == from_channel_id) TryDeleteRow(user_id); } void VoiceWindow::OnSpeakerStateChanged(Snowflake channel_id, Snowflake user_id, bool is_speaker) { if (m_channel_id != channel_id) return; + TryDeleteRow(user_id); if (is_speaker) { - if (auto it = m_rows.find(user_id); it == m_rows.end()) { - m_user_list.add(*CreateRow(user_id)); - } + m_speakers_list.add(*CreateSpeakerRow(user_id)); } else { - if (auto it = m_rows.find(user_id); it != m_rows.end()) { - delete it->second; - m_rows.erase(it); - } + m_audience_list.add(*CreateAudienceRow(user_id)); } } diff --git a/src/windows/voice/voicewindow.hpp b/src/windows/voice/voicewindow.hpp index fea998c..7008f9a 100644 --- a/src/windows/voice/voicewindow.hpp +++ b/src/windows/voice/voicewindow.hpp @@ -16,8 +16,6 @@ #include // clang-format on -class VoiceWindowUserListEntry; - class VoiceWindow : public Gtk::Window { public: VoiceWindow(Snowflake channel_id); @@ -25,7 +23,8 @@ public: private: void SetUsers(const std::unordered_set &user_ids); - Gtk::ListBoxRow *CreateRow(Snowflake id); + Gtk::ListBoxRow *CreateSpeakerRow(Snowflake id); + Gtk::ListBoxRow *CreateAudienceRow(Snowflake id); void OnUserConnect(Snowflake user_id, Snowflake to_channel_id); void OnUserDisconnect(Snowflake user_id, Snowflake from_channel_id); @@ -34,6 +33,8 @@ private: void OnMuteChanged(); void OnDeafenChanged(); + void TryDeleteRow(Snowflake id); + bool UpdateVoiceMeters(); void UpdateVADParamValue(); @@ -45,7 +46,9 @@ private: Gtk::CheckButton m_deafen; Gtk::ScrolledWindow m_scroll; - Gtk::ListBox m_user_list; + Gtk::VBox m_listing; + Gtk::ListBox m_speakers_list; + Gtk::ListBox m_audience_list; // Shows volume for gate VAD method // Shows probability for RNNoise VAD method @@ -65,7 +68,7 @@ private: Snowflake m_channel_id; bool m_is_stage; - std::unordered_map m_rows; + std::unordered_map m_rows; Gtk::MenuBar m_menu_bar; Gtk::MenuItem m_menu_view; @@ -73,6 +76,8 @@ private: Gtk::MenuItem m_menu_view_settings; Gtk::Label m_TMP_stagelabel; + Gtk::Label m_TMP_speakers_label; + Gtk::Label m_TMP_audience_label; public: using type_signal_mute = sigc::signal; diff --git a/src/windows/voice/voicewindowaudiencelistentry.cpp b/src/windows/voice/voicewindowaudiencelistentry.cpp new file mode 100644 index 0000000..aa7dad6 --- /dev/null +++ b/src/windows/voice/voicewindowaudiencelistentry.cpp @@ -0,0 +1,22 @@ +#include "voicewindowaudiencelistentry.hpp" + +VoiceWindowAudienceListEntry::VoiceWindowAudienceListEntry(Snowflake id) + : m_main(Gtk::ORIENTATION_HORIZONTAL) + , m_avatar(32, 32) { + m_name.set_halign(Gtk::ALIGN_START); + m_name.set_hexpand(true); + + m_main.add(m_avatar); + m_main.add(m_name); + 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->GetUsername()); + m_avatar.SetURL(user->GetAvatarURL("png", "32")); + } else { + m_name.set_text("Unknown user"); + } +} diff --git a/src/windows/voice/voicewindowaudiencelistentry.hpp b/src/windows/voice/voicewindowaudiencelistentry.hpp new file mode 100644 index 0000000..e7bdbb1 --- /dev/null +++ b/src/windows/voice/voicewindowaudiencelistentry.hpp @@ -0,0 +1,18 @@ +#pragma once + +#include "components/lazyimage.hpp" +#include "discord/snowflake.hpp" + +#include +#include +#include + +class VoiceWindowAudienceListEntry : public Gtk::ListBoxRow { +public: + VoiceWindowAudienceListEntry(Snowflake id); + +private: + Gtk::Box m_main; + LazyImage m_avatar; + Gtk::Label m_name; +}; diff --git a/src/windows/voice/voicewindowspeakerlistentry.cpp b/src/windows/voice/voicewindowspeakerlistentry.cpp new file mode 100644 index 0000000..a7bf2b8 --- /dev/null +++ b/src/windows/voice/voicewindowspeakerlistentry.cpp @@ -0,0 +1,58 @@ +#include "voicewindowspeakerlistentry.hpp" + +#include "abaddon.hpp" + +VoiceWindowSpeakerListEntry::VoiceWindowSpeakerListEntry(Snowflake id) + : m_main(Gtk::ORIENTATION_VERTICAL) + , m_horz(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_volume.set_range(0.0, 200.0); + m_volume.set_value_pos(Gtk::POS_LEFT); + m_volume.set_value(100.0); + m_volume.signal_value_changed().connect([this]() { + m_signal_volume.emit(m_volume.get_value() * 0.01); + }); + + m_horz.add(m_avatar); + m_horz.add(m_name); + m_horz.add(m_mute); + m_main.add(m_horz); + m_main.add(m_volume); + m_main.add(m_meter); + 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->GetUsername()); + m_avatar.SetURL(user->GetAvatarURL("png", "32")); + } else { + m_name.set_text("Unknown user"); + } + + m_mute.signal_toggled().connect([this]() { + m_signal_mute_cs.emit(m_mute.get_active()); + }); +} + +void VoiceWindowSpeakerListEntry::SetVolumeMeter(double frac) { + m_meter.SetVolume(frac); +} + +void VoiceWindowSpeakerListEntry::RestoreGain(double frac) { + m_volume.set_value(frac * 100.0); +} + +VoiceWindowSpeakerListEntry::type_signal_mute_cs VoiceWindowSpeakerListEntry::signal_mute_cs() { + return m_signal_mute_cs; +} + +VoiceWindowSpeakerListEntry::type_signal_volume VoiceWindowSpeakerListEntry::signal_volume() { + return m_signal_volume; +} diff --git a/src/windows/voice/voicewindowspeakerlistentry.hpp b/src/windows/voice/voicewindowspeakerlistentry.hpp new file mode 100644 index 0000000..a3b6429 --- /dev/null +++ b/src/windows/voice/voicewindowspeakerlistentry.hpp @@ -0,0 +1,38 @@ +#pragma once + +#include "components/lazyimage.hpp" +#include "components/volumemeter.hpp" +#include "discord/snowflake.hpp" + +#include +#include +#include +#include +#include + +class VoiceWindowSpeakerListEntry : public Gtk::ListBoxRow { +public: + VoiceWindowSpeakerListEntry(Snowflake id); + + void SetVolumeMeter(double frac); + void RestoreGain(double frac); + +private: + Gtk::Box m_main; + Gtk::Box m_horz; + LazyImage m_avatar; + Gtk::Label m_name; + Gtk::CheckButton m_mute; + Gtk::Scale m_volume; + VolumeMeter m_meter; + +public: + using type_signal_mute_cs = sigc::signal; + using type_signal_volume = sigc::signal; + type_signal_mute_cs signal_mute_cs(); + type_signal_volume signal_volume(); + +private: + type_signal_mute_cs m_signal_mute_cs; + type_signal_volume m_signal_volume; +}; diff --git a/src/windows/voice/voicewindowuserlistentry.cpp b/src/windows/voice/voicewindowuserlistentry.cpp deleted file mode 100644 index 97a3031..0000000 --- a/src/windows/voice/voicewindowuserlistentry.cpp +++ /dev/null @@ -1,58 +0,0 @@ -#include "voicewindowuserlistentry.hpp" - -#include "abaddon.hpp" - -VoiceWindowUserListEntry::VoiceWindowUserListEntry(Snowflake id) - : m_main(Gtk::ORIENTATION_VERTICAL) - , m_horz(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_volume.set_range(0.0, 200.0); - m_volume.set_value_pos(Gtk::POS_LEFT); - m_volume.set_value(100.0); - m_volume.signal_value_changed().connect([this]() { - m_signal_volume.emit(m_volume.get_value() * 0.01); - }); - - m_horz.add(m_avatar); - m_horz.add(m_name); - m_horz.add(m_mute); - m_main.add(m_horz); - m_main.add(m_volume); - m_main.add(m_meter); - 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->GetUsername()); - m_avatar.SetURL(user->GetAvatarURL("png", "32")); - } else { - m_name.set_text("Unknown user"); - } - - m_mute.signal_toggled().connect([this]() { - m_signal_mute_cs.emit(m_mute.get_active()); - }); -} - -void VoiceWindowUserListEntry::SetVolumeMeter(double frac) { - m_meter.SetVolume(frac); -} - -void VoiceWindowUserListEntry::RestoreGain(double frac) { - m_volume.set_value(frac * 100.0); -} - -VoiceWindowUserListEntry::type_signal_mute_cs VoiceWindowUserListEntry::signal_mute_cs() { - return m_signal_mute_cs; -} - -VoiceWindowUserListEntry::type_signal_volume VoiceWindowUserListEntry::signal_volume() { - return m_signal_volume; -} diff --git a/src/windows/voice/voicewindowuserlistentry.hpp b/src/windows/voice/voicewindowuserlistentry.hpp deleted file mode 100644 index 4e8c028..0000000 --- a/src/windows/voice/voicewindowuserlistentry.hpp +++ /dev/null @@ -1,38 +0,0 @@ -#pragma once - -#include "components/lazyimage.hpp" -#include "components/volumemeter.hpp" -#include "discord/snowflake.hpp" - -#include -#include -#include -#include -#include - -class VoiceWindowUserListEntry : public Gtk::ListBoxRow { -public: - VoiceWindowUserListEntry(Snowflake id); - - void SetVolumeMeter(double frac); - void RestoreGain(double frac); - -private: - Gtk::Box m_main; - Gtk::Box m_horz; - LazyImage m_avatar; - Gtk::Label m_name; - Gtk::CheckButton m_mute; - Gtk::Scale m_volume; - VolumeMeter m_meter; - -public: - using type_signal_mute_cs = sigc::signal; - using type_signal_volume = sigc::signal; - type_signal_mute_cs signal_mute_cs(); - type_signal_volume signal_volume(); - -private: - type_signal_mute_cs m_signal_mute_cs; - type_signal_volume m_signal_volume; -}; -- cgit v1.2.3 From af9f9ad803fab8e293e7d783f6e84fae6c5f21cb Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Wed, 26 Jun 2024 04:26:32 -0400 Subject: request to speak button --- src/discord/discord.cpp | 26 ++++++++++++++++++++++++++ src/discord/discord.hpp | 3 +++ src/discord/objects.cpp | 12 ++++++++++++ src/discord/objects.hpp | 8 ++++++++ src/windows/voice/voicewindow.cpp | 22 ++++++++++++++++++++-- src/windows/voice/voicewindow.hpp | 6 ++++++ 6 files changed, 75 insertions(+), 2 deletions(-) (limited to 'src/windows/voice/voicewindow.hpp') diff --git a/src/discord/discord.cpp b/src/discord/discord.cpp index 8f88a91..2ee8493 100644 --- a/src/discord/discord.cpp +++ b/src/discord/discord.cpp @@ -1301,6 +1301,32 @@ bool DiscordClient::IsUserSpeaker(Snowflake user_id) const { return state.has_value() && state->second.IsSpeaker(); } +bool DiscordClient::HasUserRequestedToSpeak(Snowflake user_id) const { + const auto state = GetVoiceState(user_id); + return state.has_value() && state->second.RequestToSpeakTimestamp.has_value() && util::FlagSet(state->second.Flags, VoiceStateFlags::Suppressed); +} + +void DiscordClient::RequestToSpeak(Snowflake channel_id, bool want, const sigc::slot &callback) { + if (want && !HasSelfChannelPermission(channel_id, Permission::REQUEST_TO_SPEAK)) return; + const auto channel = GetChannel(channel_id); + if (!channel.has_value() || !channel->GuildID.has_value()) return; + + ModifyCurrentUserVoiceStateObject d; + d.ChannelID = channel_id; + if (want) { + d.RequestToSpeakTimestamp = Glib::DateTime::create_now_utc().format_iso8601(); + } else { + d.RequestToSpeakTimestamp = ""; + } + m_http.MakePATCH("/guilds/" + std::to_string(*channel->GuildID) + "/voice-states/@me", nlohmann::json(d).dump(), [callback](const http::response_type &response) { + if (CheckCode(response, 204)) { + callback(DiscordError::NONE); + } else { + callback(GetCodeFromResponse(response)); + } + }); +} + DiscordVoiceClient &DiscordClient::GetVoiceClient() { return m_voice; } diff --git a/src/discord/discord.hpp b/src/discord/discord.hpp index ab051aa..55bd308 100644 --- a/src/discord/discord.hpp +++ b/src/discord/discord.hpp @@ -203,6 +203,9 @@ public: [[nodiscard]] Snowflake GetVoiceChannelID() const noexcept; [[nodiscard]] std::optional GetSSRCOfUser(Snowflake id) const; [[nodiscard]] bool IsUserSpeaker(Snowflake user_id) const; + [[nodiscard]] bool HasUserRequestedToSpeak(Snowflake user_id) const; + + void RequestToSpeak(Snowflake channel_id, bool want, const sigc::slot &callback); DiscordVoiceClient &GetVoiceClient(); diff --git a/src/discord/objects.cpp b/src/discord/objects.cpp index 1c5dd39..e6b7675 100644 --- a/src/discord/objects.cpp +++ b/src/discord/objects.cpp @@ -699,6 +699,18 @@ void from_json(const nlohmann::json &j, CallCreateData &m) { JS_D("channel_id", m.ChannelID); JS_ON("voice_states", m.VoiceStates); } + +void to_json(nlohmann::json &j, const ModifyCurrentUserVoiceStateObject &m) { + JS_IF("channel_id", m.ChannelID); + JS_IF("suppress", m.Suppress); + if (m.RequestToSpeakTimestamp.has_value()) { + if (m.RequestToSpeakTimestamp->empty()) { + j["request_to_speak_timestamp"] = nullptr; + } else { + j["request_to_speak_timestamp"] = *m.RequestToSpeakTimestamp; + } + } +} #endif void from_json(const nlohmann::json &j, VoiceState &m) { diff --git a/src/discord/objects.hpp b/src/discord/objects.hpp index e026311..44afe8d 100644 --- a/src/discord/objects.hpp +++ b/src/discord/objects.hpp @@ -957,4 +957,12 @@ struct CallCreateData { friend void from_json(const nlohmann::json &j, CallCreateData &m); }; + +struct ModifyCurrentUserVoiceStateObject { + std::optional ChannelID; + std::optional Suppress; + std::optional RequestToSpeakTimestamp; + + friend void to_json(nlohmann::json &j, const ModifyCurrentUserVoiceStateObject &m); +}; #endif diff --git a/src/windows/voice/voicewindow.cpp b/src/windows/voice/voicewindow.cpp index 7607a0f..bfea175 100644 --- a/src/windows/voice/voicewindow.cpp +++ b/src/windows/voice/voicewindow.cpp @@ -1,3 +1,4 @@ +#include "util.hpp" #ifdef WITH_VOICE // clang-format off @@ -20,6 +21,7 @@ VoiceWindow::VoiceWindow(Snowflake channel_id) , m_deafen("Deafen") , m_noise_suppression("Suppress Noise") , m_mix_mono("Mix Mono") + , m_request_to_speak("Request to Speak") , m_disconnect("Disconnect") , m_channel_id(channel_id) , m_menu_view("View") @@ -39,6 +41,7 @@ VoiceWindow::VoiceWindow(Snowflake channel_id) discord.signal_voice_user_disconnect().connect(sigc::mem_fun(*this, &VoiceWindow::OnUserDisconnect)); discord.signal_voice_user_connect().connect(sigc::mem_fun(*this, &VoiceWindow::OnUserConnect)); discord.signal_voice_speaker_state_changed().connect(sigc::mem_fun(*this, &VoiceWindow::OnSpeakerStateChanged)); + discord.signal_voice_state_set().connect(sigc::mem_fun(*this, &VoiceWindow::OnVoiceStateUpdate)); if (const auto self_state = discord.GetVoiceState(discord.GetUserData().ID); self_state.has_value()) { m_mute.set_active(util::FlagSet(self_state->second.Flags, VoiceStateFlags::SelfMute)); @@ -210,6 +213,12 @@ VoiceWindow::VoiceWindow(Snowflake channel_id) }, *this)); + m_request_to_speak.signal_clicked().connect([this]() { + auto &discord = Abaddon::Get().GetDiscordClient(); + const bool requested = discord.HasUserRequestedToSpeak(discord.GetUserData().ID); + Abaddon::Get().GetDiscordClient().RequestToSpeak(m_channel_id, !requested, NOOP_CALLBACK); + }); + m_TMP_speakers_label.set_markup("Speakers"); m_listing.pack_start(m_TMP_speakers_label, false, true); m_listing.pack_start(m_speakers_list, false, true); @@ -221,10 +230,13 @@ VoiceWindow::VoiceWindow(Snowflake channel_id) m_controls.add(m_deafen); m_controls.add(m_noise_suppression); m_controls.add(m_mix_mono); - m_controls.pack_end(m_disconnect, false, true); + m_buttons.set_halign(Gtk::ALIGN_CENTER); + m_buttons.pack_start(m_request_to_speak, false, true); + m_buttons.pack_start(m_disconnect, false, true); m_main.pack_start(m_menu_bar, false, true); m_main.pack_start(m_TMP_stagelabel, false, true); m_main.pack_start(m_controls, false, true); + m_main.pack_start(m_buttons, false, true); m_main.pack_start(m_vad_value, false, true); m_main.pack_start(*Gtk::make_managed("Input Settings"), false, true); m_main.pack_start(*sliders_container, false, true); @@ -232,7 +244,7 @@ VoiceWindow::VoiceWindow(Snowflake channel_id) m_main.pack_start(*combos_container, false, true, 2); add(m_main); show_all_children(); - + Glib::signal_timeout().connect(sigc::mem_fun(*this, &VoiceWindow::UpdateVoiceMeters), 40); } @@ -349,6 +361,12 @@ void VoiceWindow::OnSpeakerStateChanged(Snowflake channel_id, Snowflake user_id, } } +void VoiceWindow::OnVoiceStateUpdate(Snowflake user_id, Snowflake channel_id, VoiceStateFlags flags) { + auto &discord = Abaddon::Get().GetDiscordClient(); + m_has_requested_to_speak = discord.HasUserRequestedToSpeak(discord.GetUserData().ID); + m_request_to_speak.set_label(m_has_requested_to_speak ? "Cancel Request" : "Request to Speak"); +} + VoiceWindow::type_signal_mute VoiceWindow::signal_mute() { return m_signal_mute; } diff --git a/src/windows/voice/voicewindow.hpp b/src/windows/voice/voicewindow.hpp index 0df9fa8..7803f85 100644 --- a/src/windows/voice/voicewindow.hpp +++ b/src/windows/voice/voicewindow.hpp @@ -1,4 +1,5 @@ #pragma once +#include "discord/voicestate.hpp" #ifdef WITH_VOICE // clang-format off @@ -29,6 +30,7 @@ private: void OnUserConnect(Snowflake user_id, Snowflake to_channel_id); void OnUserDisconnect(Snowflake user_id, Snowflake from_channel_id); void OnSpeakerStateChanged(Snowflake channel_id, Snowflake user_id, bool is_speaker); + void OnVoiceStateUpdate(Snowflake user_id, Snowflake channel_id, VoiceStateFlags flags); void OnMuteChanged(); void OnDeafenChanged(); @@ -61,7 +63,11 @@ private: Gtk::CheckButton m_noise_suppression; Gtk::CheckButton m_mix_mono; + Gtk::HBox m_buttons; Gtk::Button m_disconnect; + Gtk::Button m_request_to_speak; + + bool m_has_requested_to_speak = false; Gtk::ComboBoxText m_vad_combo; Gtk::ComboBox m_playback_combo; -- cgit v1.2.3 From 7f709ce89cea9540b623b97df0a50a7cefc1f823 Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Sun, 30 Jun 2024 19:53:15 -0400 Subject: request/speak/leave stage and stuff --- src/discord/discord.cpp | 28 +++++++++++++++++++++++ src/discord/discord.hpp | 3 +++ src/windows/voice/voicewindow.cpp | 48 +++++++++++++++++++++++++++++++++------ src/windows/voice/voicewindow.hpp | 5 ++-- 4 files changed, 74 insertions(+), 10 deletions(-) (limited to 'src/windows/voice/voicewindow.hpp') diff --git a/src/discord/discord.cpp b/src/discord/discord.cpp index 2ee8493..d99e57a 100644 --- a/src/discord/discord.cpp +++ b/src/discord/discord.cpp @@ -470,6 +470,10 @@ bool DiscordClient::CanManageMember(Snowflake guild_id, Snowflake actor, Snowfla return actor_highest->Position > target_highest->Position; } +bool DiscordClient::IsStageModerator(Snowflake user_id, Snowflake channel_id) const { + return HasChannelPermission(user_id, channel_id, Permission::MANAGE_CHANNELS | Permission::MOVE_MEMBERS | Permission::MUTE_MEMBERS); +} + void DiscordClient::ChatMessageCallback(const std::string &nonce, const http::response_type &response, const sigc::slot &callback) { if (!CheckCode(response)) { if (response.status_code == http::TooManyRequests) { @@ -1306,6 +1310,11 @@ bool DiscordClient::HasUserRequestedToSpeak(Snowflake user_id) const { return state.has_value() && state->second.RequestToSpeakTimestamp.has_value() && util::FlagSet(state->second.Flags, VoiceStateFlags::Suppressed); } +bool DiscordClient::IsUserInvitedToSpeak(Snowflake user_id) const { + const auto state = GetVoiceState(user_id); + return state.has_value() && state->second.RequestToSpeakTimestamp.has_value() && !util::FlagSet(state->second.Flags, VoiceStateFlags::Suppressed); +} + void DiscordClient::RequestToSpeak(Snowflake channel_id, bool want, const sigc::slot &callback) { if (want && !HasSelfChannelPermission(channel_id, Permission::REQUEST_TO_SPEAK)) return; const auto channel = GetChannel(channel_id); @@ -1327,6 +1336,25 @@ void DiscordClient::RequestToSpeak(Snowflake channel_id, bool want, const sigc:: }); } +void DiscordClient::SetStageSpeaking(Snowflake channel_id, bool want, const sigc::slot &callback) { + const auto channel = GetChannel(channel_id); + if (!channel.has_value() || !channel->GuildID.has_value()) return; + + ModifyCurrentUserVoiceStateObject d; + d.ChannelID = channel_id; + d.Suppress = !want; + if (want) { + d.RequestToSpeakTimestamp = ""; + } + m_http.MakePATCH("/guilds/" + std::to_string(*channel->GuildID) + "/voice-states/@me", nlohmann::json(d).dump(), [callback](const http::response_type &response) { + if (CheckCode(response, 204)) { + callback(DiscordError::NONE); + } else { + callback(GetCodeFromResponse(response)); + } + }); +} + DiscordVoiceClient &DiscordClient::GetVoiceClient() { return m_voice; } diff --git a/src/discord/discord.hpp b/src/discord/discord.hpp index 55bd308..42fa1cb 100644 --- a/src/discord/discord.hpp +++ b/src/discord/discord.hpp @@ -87,6 +87,7 @@ public: Permission ComputePermissions(Snowflake member_id, Snowflake guild_id) const; Permission ComputeOverwrites(Permission base, Snowflake member_id, Snowflake channel_id) const; bool CanManageMember(Snowflake guild_id, Snowflake actor, Snowflake target) const; // kick, ban, edit nickname (cant think of a better name) + bool IsStageModerator(Snowflake user_id, Snowflake channel_id) const; void ChatMessageCallback(const std::string &nonce, const http::response_type &response, const sigc::slot &callback); void SendChatMessageNoAttachments(const ChatSubmitParams ¶ms, const sigc::slot &callback); @@ -204,8 +205,10 @@ public: [[nodiscard]] std::optional GetSSRCOfUser(Snowflake id) const; [[nodiscard]] bool IsUserSpeaker(Snowflake user_id) const; [[nodiscard]] bool HasUserRequestedToSpeak(Snowflake user_id) const; + [[nodiscard]] bool IsUserInvitedToSpeak(Snowflake user_id) const; void RequestToSpeak(Snowflake channel_id, bool want, const sigc::slot &callback); + void SetStageSpeaking(Snowflake channel_id, bool want, const sigc::slot &callback); DiscordVoiceClient &GetVoiceClient(); diff --git a/src/windows/voice/voicewindow.cpp b/src/windows/voice/voicewindow.cpp index bfea175..27dd734 100644 --- a/src/windows/voice/voicewindow.cpp +++ b/src/windows/voice/voicewindow.cpp @@ -21,7 +21,7 @@ VoiceWindow::VoiceWindow(Snowflake channel_id) , m_deafen("Deafen") , m_noise_suppression("Suppress Noise") , m_mix_mono("Mix Mono") - , m_request_to_speak("Request to Speak") + , m_stage_command("Request to Speak") , m_disconnect("Disconnect") , m_channel_id(channel_id) , m_menu_view("View") @@ -213,10 +213,21 @@ VoiceWindow::VoiceWindow(Snowflake channel_id) }, *this)); - m_request_to_speak.signal_clicked().connect([this]() { + m_stage_command.signal_clicked().connect([this]() { auto &discord = Abaddon::Get().GetDiscordClient(); - const bool requested = discord.HasUserRequestedToSpeak(discord.GetUserData().ID); - Abaddon::Get().GetDiscordClient().RequestToSpeak(m_channel_id, !requested, NOOP_CALLBACK); + const auto user_id = discord.GetUserData().ID; + const bool is_moderator = discord.IsStageModerator(user_id, m_channel_id); + const bool is_speaker = discord.IsUserSpeaker(user_id); + const bool is_invited_to_speak = discord.IsUserInvitedToSpeak(user_id); + + if (is_speaker) { + discord.SetStageSpeaking(m_channel_id, false, NOOP_CALLBACK); + } else if (is_moderator || is_invited_to_speak) { + discord.SetStageSpeaking(m_channel_id, true, NOOP_CALLBACK); + } else { + const bool requested = discord.HasUserRequestedToSpeak(user_id); + discord.RequestToSpeak(m_channel_id, !requested, NOOP_CALLBACK); + } }); m_TMP_speakers_label.set_markup("Speakers"); @@ -231,7 +242,7 @@ VoiceWindow::VoiceWindow(Snowflake channel_id) m_controls.add(m_noise_suppression); m_controls.add(m_mix_mono); m_buttons.set_halign(Gtk::ALIGN_CENTER); - m_buttons.pack_start(m_request_to_speak, false, true); + m_buttons.pack_start(m_stage_command, false, true); m_buttons.pack_start(m_disconnect, false, true); m_main.pack_start(m_menu_bar, false, true); m_main.pack_start(m_TMP_stagelabel, false, true); @@ -246,6 +257,8 @@ VoiceWindow::VoiceWindow(Snowflake channel_id) show_all_children(); Glib::signal_timeout().connect(sigc::mem_fun(*this, &VoiceWindow::UpdateVoiceMeters), 40); + + UpdateStageCommand(); } void VoiceWindow::SetUsers(const std::unordered_set &user_ids) { @@ -335,6 +348,26 @@ void VoiceWindow::UpdateVADParamValue() { } } +void VoiceWindow::UpdateStageCommand() { + auto &discord = Abaddon::Get().GetDiscordClient(); + const auto user_id = discord.GetUserData().ID; + + m_has_requested_to_speak = discord.HasUserRequestedToSpeak(user_id); + const bool is_moderator = discord.IsStageModerator(user_id, m_channel_id); + const bool is_speaker = discord.IsUserSpeaker(user_id); + const bool is_invited_to_speak = discord.IsUserInvitedToSpeak(user_id); + + if (is_speaker) { + m_stage_command.set_label("Leave the Stage"); + } else if (is_moderator || is_invited_to_speak) { + m_stage_command.set_label("Speak on Stage"); + } else if (m_has_requested_to_speak) { + m_stage_command.set_label("Cancel Request"); + } else { + m_stage_command.set_label("Request to Speak"); + } +} + void VoiceWindow::OnUserConnect(Snowflake user_id, Snowflake to_channel_id) { if (m_channel_id == to_channel_id) { if (auto it = m_rows.find(user_id); it == m_rows.end()) { @@ -363,8 +396,9 @@ void VoiceWindow::OnSpeakerStateChanged(Snowflake channel_id, Snowflake user_id, void VoiceWindow::OnVoiceStateUpdate(Snowflake user_id, Snowflake channel_id, VoiceStateFlags flags) { auto &discord = Abaddon::Get().GetDiscordClient(); - m_has_requested_to_speak = discord.HasUserRequestedToSpeak(discord.GetUserData().ID); - m_request_to_speak.set_label(m_has_requested_to_speak ? "Cancel Request" : "Request to Speak"); + if (user_id != discord.GetUserData().ID) return; + + UpdateStageCommand(); } VoiceWindow::type_signal_mute VoiceWindow::signal_mute() { diff --git a/src/windows/voice/voicewindow.hpp b/src/windows/voice/voicewindow.hpp index 7803f85..5005b90 100644 --- a/src/windows/voice/voicewindow.hpp +++ b/src/windows/voice/voicewindow.hpp @@ -36,10 +36,9 @@ private: void OnDeafenChanged(); void TryDeleteRow(Snowflake id); - bool UpdateVoiceMeters(); - void UpdateVADParamValue(); + void UpdateStageCommand(); Gtk::Box m_main; Gtk::Box m_controls; @@ -65,7 +64,7 @@ private: Gtk::HBox m_buttons; Gtk::Button m_disconnect; - Gtk::Button m_request_to_speak; + Gtk::Button m_stage_command; bool m_has_requested_to_speak = false; -- cgit v1.2.3 From 3109089e8c557c1385d3e6a34d9a097f905cec00 Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Sun, 30 Jun 2024 20:39:11 -0400 Subject: accept/decline stage invite --- src/discord/discord.cpp | 17 +++++++++++++++++ src/discord/discord.hpp | 1 + src/windows/voice/voicewindow.cpp | 27 +++++++++++++++++++++++++-- src/windows/voice/voicewindow.hpp | 6 ++++++ 4 files changed, 49 insertions(+), 2 deletions(-) (limited to 'src/windows/voice/voicewindow.hpp') diff --git a/src/discord/discord.cpp b/src/discord/discord.cpp index d99e57a..a7712d6 100644 --- a/src/discord/discord.cpp +++ b/src/discord/discord.cpp @@ -1355,6 +1355,23 @@ void DiscordClient::SetStageSpeaking(Snowflake channel_id, bool want, const sigc }); } +void DiscordClient::DeclineInviteToSpeak(Snowflake channel_id, const sigc::slot &callback) { + const auto channel = GetChannel(channel_id); + if (!channel.has_value() || !channel->GuildID.has_value()) return; + + ModifyCurrentUserVoiceStateObject d; + d.ChannelID = channel_id; + d.Suppress = true; + d.RequestToSpeakTimestamp = ""; + m_http.MakePATCH("/guilds/" + std::to_string(*channel->GuildID) + "/voice-states/@me", nlohmann::json(d).dump(), [callback](const http::response_type &response) { + if (CheckCode(response, 204)) { + callback(DiscordError::NONE); + } else { + callback(GetCodeFromResponse(response)); + } + }); +} + DiscordVoiceClient &DiscordClient::GetVoiceClient() { return m_voice; } diff --git a/src/discord/discord.hpp b/src/discord/discord.hpp index 42fa1cb..483abf0 100644 --- a/src/discord/discord.hpp +++ b/src/discord/discord.hpp @@ -209,6 +209,7 @@ public: void RequestToSpeak(Snowflake channel_id, bool want, const sigc::slot &callback); void SetStageSpeaking(Snowflake channel_id, bool want, const sigc::slot &callback); + void DeclineInviteToSpeak(Snowflake channel_id, const sigc::slot &callback); DiscordVoiceClient &GetVoiceClient(); diff --git a/src/windows/voice/voicewindow.cpp b/src/windows/voice/voicewindow.cpp index 27dd734..5930156 100644 --- a/src/windows/voice/voicewindow.cpp +++ b/src/windows/voice/voicewindow.cpp @@ -23,6 +23,9 @@ VoiceWindow::VoiceWindow(Snowflake channel_id) , m_mix_mono("Mix Mono") , m_stage_command("Request to Speak") , m_disconnect("Disconnect") + , m_stage_invite_lbl("You've been invited to speak") + , m_stage_accept("Accept") + , m_stage_decline("Decline") , m_channel_id(channel_id) , m_menu_view("View") , m_menu_view_settings("More _Settings", true) { @@ -222,14 +225,24 @@ VoiceWindow::VoiceWindow(Snowflake channel_id) if (is_speaker) { discord.SetStageSpeaking(m_channel_id, false, NOOP_CALLBACK); - } else if (is_moderator || is_invited_to_speak) { + } else if (is_moderator) { discord.SetStageSpeaking(m_channel_id, true, NOOP_CALLBACK); + } else if (is_invited_to_speak) { + discord.DeclineInviteToSpeak(m_channel_id, NOOP_CALLBACK); } else { const bool requested = discord.HasUserRequestedToSpeak(user_id); discord.RequestToSpeak(m_channel_id, !requested, NOOP_CALLBACK); } }); + m_stage_accept.signal_clicked().connect([this]() { + Abaddon::Get().GetDiscordClient().SetStageSpeaking(m_channel_id, true, NOOP_CALLBACK); + }); + + m_stage_decline.signal_clicked().connect([this]() { + Abaddon::Get().GetDiscordClient().DeclineInviteToSpeak(m_channel_id, NOOP_CALLBACK); + }); + m_TMP_speakers_label.set_markup("Speakers"); m_listing.pack_start(m_TMP_speakers_label, false, true); m_listing.pack_start(m_speakers_list, false, true); @@ -244,10 +257,16 @@ VoiceWindow::VoiceWindow(Snowflake channel_id) m_buttons.set_halign(Gtk::ALIGN_CENTER); m_buttons.pack_start(m_stage_command, false, true); m_buttons.pack_start(m_disconnect, false, true); + m_stage_invite_box.pack_start(m_stage_invite_lbl, false, true); + m_stage_invite_box.pack_start(m_stage_invite_btns); + m_stage_invite_btns.set_halign(Gtk::ALIGN_CENTER); + m_stage_invite_btns.pack_start(m_stage_accept, false, true); + m_stage_invite_btns.pack_start(m_stage_decline, false, true); m_main.pack_start(m_menu_bar, false, true); m_main.pack_start(m_TMP_stagelabel, false, true); m_main.pack_start(m_controls, false, true); m_main.pack_start(m_buttons, false, true); + m_main.pack_start(m_stage_invite_box, false, true); m_main.pack_start(m_vad_value, false, true); m_main.pack_start(*Gtk::make_managed("Input Settings"), false, true); m_main.pack_start(*sliders_container, false, true); @@ -357,12 +376,16 @@ void VoiceWindow::UpdateStageCommand() { const bool is_speaker = discord.IsUserSpeaker(user_id); const bool is_invited_to_speak = discord.IsUserInvitedToSpeak(user_id); + m_stage_invite_box.set_visible(is_invited_to_speak); + if (is_speaker) { m_stage_command.set_label("Leave the Stage"); - } else if (is_moderator || is_invited_to_speak) { + } else if (is_moderator) { m_stage_command.set_label("Speak on Stage"); } else if (m_has_requested_to_speak) { m_stage_command.set_label("Cancel Request"); + } else if (is_invited_to_speak) { + m_stage_command.set_label("Decline Invite"); } else { m_stage_command.set_label("Request to Speak"); } diff --git a/src/windows/voice/voicewindow.hpp b/src/windows/voice/voicewindow.hpp index 5005b90..7c5a137 100644 --- a/src/windows/voice/voicewindow.hpp +++ b/src/windows/voice/voicewindow.hpp @@ -66,6 +66,12 @@ private: Gtk::Button m_disconnect; Gtk::Button m_stage_command; + Gtk::VBox m_stage_invite_box; + Gtk::Label m_stage_invite_lbl; + Gtk::HBox m_stage_invite_btns; + Gtk::Button m_stage_accept; + Gtk::Button m_stage_decline; + bool m_has_requested_to_speak = false; Gtk::ComboBoxText m_vad_combo; -- cgit v1.2.3 From d685fdc48822c2604aebc34903ce348591cdddfb Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Thu, 4 Jul 2024 00:48:46 -0400 Subject: make non-stage voice ui normal again --- src/windows/voice/voicewindow.cpp | 14 +++++++------- src/windows/voice/voicewindow.hpp | 4 ++-- 2 files changed, 9 insertions(+), 9 deletions(-) (limited to 'src/windows/voice/voicewindow.hpp') diff --git a/src/windows/voice/voicewindow.cpp b/src/windows/voice/voicewindow.cpp index 5930156..944d848 100644 --- a/src/windows/voice/voicewindow.cpp +++ b/src/windows/voice/voicewindow.cpp @@ -243,19 +243,19 @@ VoiceWindow::VoiceWindow(Snowflake channel_id) Abaddon::Get().GetDiscordClient().DeclineInviteToSpeak(m_channel_id, NOOP_CALLBACK); }); - m_TMP_speakers_label.set_markup("Speakers"); - m_listing.pack_start(m_TMP_speakers_label, false, true); + m_speakers_label.set_markup("Speakers"); + if (m_is_stage) m_listing.pack_start(m_speakers_label, false, true); m_listing.pack_start(m_speakers_list, false, true); - m_TMP_audience_label.set_markup("Audience"); - m_listing.pack_start(m_TMP_audience_label, false, true); - m_listing.pack_start(m_audience_list, false, true); + m_audience_label.set_markup("Audience"); + if (m_is_stage) m_listing.pack_start(m_audience_label, false, true); + if (m_is_stage) m_listing.pack_start(m_audience_list, false, true); m_scroll.add(m_listing); m_controls.add(m_mute); m_controls.add(m_deafen); m_controls.add(m_noise_suppression); m_controls.add(m_mix_mono); m_buttons.set_halign(Gtk::ALIGN_CENTER); - m_buttons.pack_start(m_stage_command, false, true); + if (m_is_stage) m_buttons.pack_start(m_stage_command, false, true); m_buttons.pack_start(m_disconnect, false, true); m_stage_invite_box.pack_start(m_stage_invite_lbl, false, true); m_stage_invite_box.pack_start(m_stage_invite_btns); @@ -284,7 +284,7 @@ void VoiceWindow::SetUsers(const std::unordered_set &user_ids) { auto &discord = Abaddon::Get().GetDiscordClient(); const auto me = discord.GetUserData().ID; for (auto id : user_ids) { - if (discord.IsUserSpeaker(id)) { + if (!m_is_stage || discord.IsUserSpeaker(id)) { if (id != me) m_speakers_list.add(*CreateSpeakerRow(id)); } else { m_audience_list.add(*CreateAudienceRow(id)); diff --git a/src/windows/voice/voicewindow.hpp b/src/windows/voice/voicewindow.hpp index 7c5a137..64b14a9 100644 --- a/src/windows/voice/voicewindow.hpp +++ b/src/windows/voice/voicewindow.hpp @@ -89,8 +89,8 @@ private: Gtk::MenuItem m_menu_view_settings; Gtk::Label m_TMP_stagelabel; - Gtk::Label m_TMP_speakers_label; - Gtk::Label m_TMP_audience_label; + Gtk::Label m_speakers_label; + Gtk::Label m_audience_label; public: using type_signal_mute = sigc::signal; -- cgit v1.2.3 From b19782c16dffd38ac5651641131a48f8ff961b32 Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Thu, 4 Jul 2024 01:11:55 -0400 Subject: update how stage topic is shown --- src/windows/voice/voicewindow.cpp | 46 ++++++++++++++++++++++----------------- src/windows/voice/voicewindow.hpp | 7 +++++- 2 files changed, 32 insertions(+), 21 deletions(-) (limited to 'src/windows/voice/voicewindow.hpp') diff --git a/src/windows/voice/voicewindow.cpp b/src/windows/voice/voicewindow.cpp index 944d848..e59705a 100644 --- a/src/windows/voice/voicewindow.cpp +++ b/src/windows/voice/voicewindow.cpp @@ -193,28 +193,15 @@ VoiceWindow::VoiceWindow(Snowflake channel_id) combos_combos->pack_start(m_capture_combo); if (const auto instance = discord.GetStageInstanceFromChannel(channel_id); instance.has_value()) { - printf("%s\n", instance->Topic.c_str()); - m_TMP_stagelabel.show(); - m_TMP_stagelabel.set_markup("" + instance->Topic + ""); + m_stage_topic_label.show(); + UpdateStageTopicLabel(instance->Topic); } else { - m_TMP_stagelabel.hide(); + m_stage_topic_label.hide(); } - discord.signal_stage_instance_create().connect(sigc::track_obj([this](const StageInstance &instance) { - m_TMP_stagelabel.show(); - m_TMP_stagelabel.set_markup("" + instance.Topic + ""); - }, - *this)); - - discord.signal_stage_instance_update().connect(sigc::track_obj([this](const StageInstance &instance) { - m_TMP_stagelabel.set_markup("" + instance.Topic + ""); - }, - *this)); - - discord.signal_stage_instance_delete().connect(sigc::track_obj([this](const StageInstance &instance) { - m_TMP_stagelabel.hide(); - }, - *this)); + discord.signal_stage_instance_create().connect(sigc::mem_fun(*this, &VoiceWindow::OnStageInstanceCreate)); + discord.signal_stage_instance_update().connect(sigc::mem_fun(*this, &VoiceWindow::OnStageInstanceUpdate)); + discord.signal_stage_instance_delete().connect(sigc::mem_fun(*this, &VoiceWindow::OnStageInstanceDelete)); m_stage_command.signal_clicked().connect([this]() { auto &discord = Abaddon::Get().GetDiscordClient(); @@ -263,7 +250,6 @@ VoiceWindow::VoiceWindow(Snowflake channel_id) m_stage_invite_btns.pack_start(m_stage_accept, false, true); m_stage_invite_btns.pack_start(m_stage_decline, false, true); m_main.pack_start(m_menu_bar, false, true); - m_main.pack_start(m_TMP_stagelabel, false, true); m_main.pack_start(m_controls, false, true); m_main.pack_start(m_buttons, false, true); m_main.pack_start(m_stage_invite_box, false, true); @@ -271,6 +257,9 @@ VoiceWindow::VoiceWindow(Snowflake channel_id) m_main.pack_start(*Gtk::make_managed("Input Settings"), false, true); m_main.pack_start(*sliders_container, false, true); m_main.pack_start(m_scroll); + m_stage_topic_label.set_ellipsize(Pango::ELLIPSIZE_END); + m_stage_topic_label.set_halign(Gtk::ALIGN_CENTER); + m_main.pack_start(m_stage_topic_label, false, true); m_main.pack_start(*combos_container, false, true, 2); add(m_main); show_all_children(); @@ -391,6 +380,10 @@ void VoiceWindow::UpdateStageCommand() { } } +void VoiceWindow::UpdateStageTopicLabel(const std::string &topic) { + m_stage_topic_label.set_markup("Topic: " + topic); +} + void VoiceWindow::OnUserConnect(Snowflake user_id, Snowflake to_channel_id) { if (m_channel_id == to_channel_id) { if (auto it = m_rows.find(user_id); it == m_rows.end()) { @@ -424,6 +417,19 @@ void VoiceWindow::OnVoiceStateUpdate(Snowflake user_id, Snowflake channel_id, Vo UpdateStageCommand(); } +void VoiceWindow::OnStageInstanceCreate(const StageInstance &instance) { + m_stage_topic_label.show(); + UpdateStageTopicLabel(instance.Topic); +} + +void VoiceWindow::OnStageInstanceUpdate(const StageInstance &instance) { + UpdateStageTopicLabel(instance.Topic); +} + +void VoiceWindow::OnStageInstanceDelete(const StageInstance &instance) { + m_stage_topic_label.hide(); +} + VoiceWindow::type_signal_mute VoiceWindow::signal_mute() { return m_signal_mute; } diff --git a/src/windows/voice/voicewindow.hpp b/src/windows/voice/voicewindow.hpp index 64b14a9..05033d9 100644 --- a/src/windows/voice/voicewindow.hpp +++ b/src/windows/voice/voicewindow.hpp @@ -1,4 +1,5 @@ #pragma once +#include "discord/stage.hpp" #include "discord/voicestate.hpp" #ifdef WITH_VOICE // clang-format off @@ -31,6 +32,9 @@ private: void OnUserDisconnect(Snowflake user_id, Snowflake from_channel_id); void OnSpeakerStateChanged(Snowflake channel_id, Snowflake user_id, bool is_speaker); void OnVoiceStateUpdate(Snowflake user_id, Snowflake channel_id, VoiceStateFlags flags); + void OnStageInstanceCreate(const StageInstance &instance); + void OnStageInstanceUpdate(const StageInstance &instance); + void OnStageInstanceDelete(const StageInstance &instance); void OnMuteChanged(); void OnDeafenChanged(); @@ -39,6 +43,7 @@ private: bool UpdateVoiceMeters(); void UpdateVADParamValue(); void UpdateStageCommand(); + void UpdateStageTopicLabel(const std::string &topic); Gtk::Box m_main; Gtk::Box m_controls; @@ -88,7 +93,7 @@ private: Gtk::Menu m_menu_view_sub; Gtk::MenuItem m_menu_view_settings; - Gtk::Label m_TMP_stagelabel; + Gtk::Label m_stage_topic_label; Gtk::Label m_speakers_label; Gtk::Label m_audience_label; -- cgit v1.2.3