From 98218dfde37ce595401a0c1446d9e40418cc15d6 Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Tue, 12 Mar 2024 04:15:21 -0400 Subject: initial stages stuff --- src/discord/objects.hpp | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'src/discord/objects.hpp') diff --git a/src/discord/objects.hpp b/src/discord/objects.hpp index dfe99f0..67474a3 100644 --- a/src/discord/objects.hpp +++ b/src/discord/objects.hpp @@ -20,6 +20,7 @@ #include "auditlog.hpp" #include "relationship.hpp" #include "errors.hpp" +#include "stage.hpp" // most stuff below should just be objects that get processed and thrown away immediately @@ -110,6 +111,9 @@ enum class GatewayEvent : int { VOICE_STATE_UPDATE, VOICE_SERVER_UPDATE, CALL_CREATE, + STAGE_INSTANCE_CREATE, + STAGE_INSTANCE_UPDATE, + STAGE_INSTANCE_DELETE, }; enum class GatewayCloseCode : uint16_t { -- 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/discord/objects.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 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/discord/objects.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