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: [PATCH 01/16] initial stages stuff --- .../channellist/cellrendererchannels.cpp | 12 +++-- .../channellist/cellrendererchannels.hpp | 4 +- .../channellist/channellisttree.cpp | 52 +++++++++++++++++-- .../channellist/channellisttree.hpp | 5 ++ src/discord/channel.hpp | 16 ------ src/discord/discord.cpp | 47 +++++++++++++++++ src/discord/discord.hpp | 17 +++++- src/discord/objects.hpp | 4 ++ src/discord/stage.cpp | 12 +++++ src/discord/stage.hpp | 32 ++++++++++++ src/discord/voiceclient.cpp | 7 +-- src/discord/voiceclient.hpp | 23 ++++++-- src/windows/voicewindow.cpp | 16 ++++++ src/windows/voicewindow.hpp | 2 + 14 files changed, 217 insertions(+), 32 deletions(-) create mode 100644 src/discord/stage.cpp create mode 100644 src/discord/stage.hpp diff --git a/src/components/channellist/cellrendererchannels.cpp b/src/components/channellist/cellrendererchannels.cpp index b0492528..edbe5bb0 100644 --- a/src/components/channellist/cellrendererchannels.cpp +++ b/src/components/channellist/cellrendererchannels.cpp @@ -122,6 +122,7 @@ void CellRendererChannels::get_preferred_width_vfunc(Gtk::Widget &widget, int &m return get_preferred_width_vfunc_thread(widget, minimum_width, natural_width); #ifdef WITH_VOICE case RenderType::VoiceChannel: + case RenderType::VoiceStage: return get_preferred_width_vfunc_voice_channel(widget, minimum_width, natural_width); case RenderType::VoiceParticipant: return get_preferred_width_vfunc_voice_participant(widget, minimum_width, natural_width); @@ -147,6 +148,7 @@ void CellRendererChannels::get_preferred_width_for_height_vfunc(Gtk::Widget &wid return get_preferred_width_for_height_vfunc_thread(widget, height, minimum_width, natural_width); #ifdef WITH_VOICE case RenderType::VoiceChannel: + case RenderType::VoiceStage: return get_preferred_width_for_height_vfunc_voice_channel(widget, height, minimum_width, natural_width); case RenderType::VoiceParticipant: return get_preferred_width_for_height_vfunc_voice_participant(widget, height, minimum_width, natural_width); @@ -172,6 +174,7 @@ void CellRendererChannels::get_preferred_height_vfunc(Gtk::Widget &widget, int & return get_preferred_height_vfunc_thread(widget, minimum_height, natural_height); #ifdef WITH_VOICE case RenderType::VoiceChannel: + case RenderType::VoiceStage: return get_preferred_height_vfunc_voice_channel(widget, minimum_height, natural_height); case RenderType::VoiceParticipant: return get_preferred_height_vfunc_voice_participant(widget, minimum_height, natural_height); @@ -197,6 +200,7 @@ void CellRendererChannels::get_preferred_height_for_width_vfunc(Gtk::Widget &wid return get_preferred_height_for_width_vfunc_thread(widget, width, minimum_height, natural_height); #ifdef WITH_VOICE case RenderType::VoiceChannel: + case RenderType::VoiceStage: return get_preferred_height_for_width_vfunc_voice_channel(widget, width, minimum_height, natural_height); case RenderType::VoiceParticipant: return get_preferred_height_for_width_vfunc_voice_participant(widget, width, minimum_height, natural_height); @@ -222,7 +226,9 @@ void CellRendererChannels::render_vfunc(const Cairo::RefPtr &cr, return render_vfunc_thread(cr, widget, background_area, cell_area, flags); #ifdef WITH_VOICE case RenderType::VoiceChannel: - return render_vfunc_voice_channel(cr, widget, background_area, cell_area, flags); + return render_vfunc_voice_channel(cr, widget, background_area, cell_area, flags, "\U0001F50A"); + case RenderType::VoiceStage: + return render_vfunc_voice_channel(cr, widget, background_area, cell_area, flags, "\U0001F4E1"); case RenderType::VoiceParticipant: return render_vfunc_voice_participant(cr, widget, background_area, cell_area, flags); #endif @@ -581,7 +587,7 @@ void CellRendererChannels::get_preferred_height_for_width_vfunc_voice_channel(Gt m_renderer_text.get_preferred_height_for_width(widget, width, minimum_height, natural_height); } -void CellRendererChannels::render_vfunc_voice_channel(const Cairo::RefPtr &cr, Gtk::Widget &widget, const Gdk::Rectangle &background_area, const Gdk::Rectangle &cell_area, Gtk::CellRendererState flags) { +void CellRendererChannels::render_vfunc_voice_channel(const Cairo::RefPtr &cr, Gtk::Widget &widget, const Gdk::Rectangle &background_area, const Gdk::Rectangle &cell_area, Gtk::CellRendererState flags, const char *emoji) { // channel name text Gtk::Requisition minimum_size, natural_size; m_renderer_text.get_preferred_size(widget, minimum_size, natural_size); @@ -598,7 +604,7 @@ void CellRendererChannels::render_vfunc_voice_channel(const Cairo::RefPtrset_font_description(font); layout->set_alignment(Pango::ALIGN_LEFT); cr->set_source_rgba(1.0, 1.0, 1.0, 1.0); diff --git a/src/components/channellist/cellrendererchannels.hpp b/src/components/channellist/cellrendererchannels.hpp index e142b2a2..a1c020ba 100644 --- a/src/components/channellist/cellrendererchannels.hpp +++ b/src/components/channellist/cellrendererchannels.hpp @@ -19,6 +19,7 @@ enum class RenderType : uint8_t { // TODO: maybe enable anyways but without ability to join if no voice support #ifdef WITH_VOICE VoiceChannel, + VoiceStage, // identical to non-stage except for icon VoiceParticipant, #endif @@ -117,7 +118,8 @@ class CellRendererChannels : public Gtk::CellRenderer { Gtk::Widget &widget, const Gdk::Rectangle &background_area, const Gdk::Rectangle &cell_area, - Gtk::CellRendererState flags); + Gtk::CellRendererState flags, + const char *emoji); // voice participant void get_preferred_width_vfunc_voice_participant(Gtk::Widget &widget, int &minimum_width, int &natural_width) const; diff --git a/src/components/channellist/channellisttree.cpp b/src/components/channellist/channellisttree.cpp index 4816b423..8b313a37 100644 --- a/src/components/channellist/channellisttree.cpp +++ b/src/components/channellist/channellisttree.cpp @@ -29,6 +29,8 @@ ChannelListTree::ChannelListTree() #ifdef WITH_VOICE , m_menu_voice_channel_join("_Join", true) , m_menu_voice_channel_disconnect("_Disconnect", true) + , m_menu_voice_stage_join("_Join", true) + , m_menu_voice_stage_disconnect("_Disconnect", true) #endif , m_menu_dm_copy_id("_Copy ID", true) , m_menu_dm_close("") // changes depending on if group or not @@ -212,6 +214,19 @@ ChannelListTree::ChannelListTree() m_menu_voice_channel.append(m_menu_voice_channel_join); m_menu_voice_channel.append(m_menu_voice_channel_disconnect); m_menu_voice_channel.show_all(); + + m_menu_voice_stage_join.signal_activate().connect([this]() { + const auto id = static_cast((*m_model->get_iter(m_path_for_menu))[m_columns.m_id]); + m_signal_action_join_voice_channel.emit(id); + }); + + m_menu_voice_stage_disconnect.signal_activate().connect([this]() { + m_signal_action_disconnect_voice.emit(); + }); + + m_menu_voice_stage.append(m_menu_voice_stage_join); + m_menu_voice_stage.append(m_menu_voice_stage_disconnect); + m_menu_voice_stage.show_all(); #endif m_menu_dm_copy_id.signal_activate().connect([this] { @@ -356,8 +371,8 @@ int ChannelListTree::SortFunc(const Gtk::TreeModel::iterator &a, const Gtk::Tree if (a_type == RenderType::DMHeader) return -1; if (b_type == RenderType::DMHeader) return 1; #ifdef WITH_VOICE - if (a_type == RenderType::TextChannel && b_type == RenderType::VoiceChannel) return -1; - if (b_type == RenderType::TextChannel && a_type == RenderType::VoiceChannel) return 1; + if (a_type == RenderType::TextChannel && (b_type == RenderType::VoiceChannel || b_type == RenderType::VoiceStage)) return -1; + if (b_type == RenderType::TextChannel && (a_type == RenderType::VoiceChannel || a_type == RenderType::VoiceStage)) return 1; #endif return static_cast(std::clamp(a_sort - b_sort, int64_t(-1), int64_t(1))); } @@ -624,6 +639,7 @@ void ChannelListTree::OnThreadListSync(const ThreadListSyncData &data) { #ifdef WITH_VOICE void ChannelListTree::OnVoiceUserConnect(Snowflake user_id, Snowflake channel_id) { auto parent_iter = GetIteratorForRowFromIDOfType(channel_id, RenderType::VoiceChannel); + if (!parent_iter) parent_iter = GetIteratorForRowFromIDOfType(channel_id, RenderType::VoiceStage); if (!parent_iter) parent_iter = GetIteratorForRowFromIDOfType(channel_id, RenderType::DM); if (!parent_iter) return; const auto user = Abaddon::Get().GetDiscordClient().GetUser(user_id); @@ -906,7 +922,7 @@ Gtk::TreeModel::iterator ChannelListTree::AddGuild(const GuildData &guild, const const auto channel = discord.GetChannel(channel_.ID); if (!channel.has_value()) continue; #ifdef WITH_VOICE - if (channel->Type == ChannelType::GUILD_TEXT || channel->Type == ChannelType::GUILD_NEWS || channel->Type == ChannelType::GUILD_VOICE) { + if (channel->Type == ChannelType::GUILD_TEXT || channel->Type == ChannelType::GUILD_NEWS || channel->Type == ChannelType::GUILD_VOICE || channel->Type == ChannelType::GUILD_STAGE_VOICE) { #else if (channel->Type == ChannelType::GUILD_TEXT || channel->Type == ChannelType::GUILD_NEWS) { #endif @@ -953,10 +969,14 @@ Gtk::TreeModel::iterator ChannelListTree::AddGuild(const GuildData &guild, const channel_row[m_columns.m_name] = "#" + Glib::Markup::escape_text(*channel.Name); } #ifdef WITH_VOICE - else { + else if (channel.Type == ChannelType::GUILD_VOICE) { channel_row[m_columns.m_type] = RenderType::VoiceChannel; channel_row[m_columns.m_name] = Glib::Markup::escape_text(*channel.Name); add_voice_participants(channel, channel_row->children()); + } else if (channel.Type == ChannelType::GUILD_STAGE_VOICE) { + channel_row[m_columns.m_type] = RenderType::VoiceStage; + channel_row[m_columns.m_name] = Glib::Markup::escape_text(*channel.Name); + add_voice_participants(channel, channel_row->children()); } #endif channel_row[m_columns.m_id] = channel.ID; @@ -985,10 +1005,14 @@ Gtk::TreeModel::iterator ChannelListTree::AddGuild(const GuildData &guild, const channel_row[m_columns.m_name] = "#" + Glib::Markup::escape_text(*channel.Name); } #ifdef WITH_VOICE - else { + else if (channel.Type == ChannelType::GUILD_VOICE) { channel_row[m_columns.m_type] = RenderType::VoiceChannel; channel_row[m_columns.m_name] = Glib::Markup::escape_text(*channel.Name); add_voice_participants(channel, channel_row->children()); + } else if (channel.Type == ChannelType::GUILD_STAGE_VOICE) { + channel_row[m_columns.m_type] = RenderType::VoiceStage; + channel_row[m_columns.m_name] = Glib::Markup::escape_text(*channel.Name); + add_voice_participants(channel, channel_row->children()); } #endif channel_row[m_columns.m_id] = channel.ID; @@ -1333,6 +1357,10 @@ bool ChannelListTree::OnButtonPressEvent(GdkEventButton *ev) { OnVoiceChannelSubmenuPopup(); m_menu_voice_channel.popup_at_pointer(reinterpret_cast(ev)); break; + case RenderType::VoiceStage: + OnVoiceStageSubmenuPopup(); + m_menu_voice_stage.popup_at_pointer(reinterpret_cast(ev)); + break; #endif case RenderType::DM: { OnDMSubmenuPopup(); @@ -1440,6 +1468,20 @@ void ChannelListTree::OnVoiceChannelSubmenuPopup() { m_menu_voice_channel_disconnect.set_sensitive(false); } } + +void ChannelListTree::OnVoiceStageSubmenuPopup() { + const auto iter = m_model->get_iter(m_path_for_menu); + if (!iter) return; + const auto id = static_cast((*iter)[m_columns.m_id]); + auto &discord = Abaddon::Get().GetDiscordClient(); + if (discord.IsVoiceConnected() || discord.IsVoiceConnecting()) { + m_menu_voice_stage_join.set_sensitive(false); + m_menu_voice_stage_disconnect.set_sensitive(discord.GetVoiceChannelID() == id); + } else { + m_menu_voice_stage_join.set_sensitive(true); + m_menu_voice_stage_disconnect.set_sensitive(false); + } +} #endif void ChannelListTree::OnDMSubmenuPopup() { diff --git a/src/components/channellist/channellisttree.hpp b/src/components/channellist/channellisttree.hpp index 136522b5..3841e3c4 100644 --- a/src/components/channellist/channellisttree.hpp +++ b/src/components/channellist/channellisttree.hpp @@ -165,6 +165,10 @@ class ChannelListTree : public Gtk::ScrolledWindow { Gtk::Menu m_menu_voice_channel; Gtk::MenuItem m_menu_voice_channel_join; Gtk::MenuItem m_menu_voice_channel_disconnect; + + Gtk::Menu m_menu_voice_stage; + Gtk::MenuItem m_menu_voice_stage_join; + Gtk::MenuItem m_menu_voice_stage_disconnect; #endif Gtk::Menu m_menu_dm; @@ -196,6 +200,7 @@ class ChannelListTree : public Gtk::ScrolledWindow { #ifdef WITH_VOICE void OnVoiceChannelSubmenuPopup(); + void OnVoiceStageSubmenuPopup(); #endif bool m_updating_listing = false; diff --git a/src/discord/channel.hpp b/src/discord/channel.hpp index cac8b4ca..ebf67b08 100644 --- a/src/discord/channel.hpp +++ b/src/discord/channel.hpp @@ -27,22 +27,6 @@ enum class ChannelType : int { GUILD_MEDIA = 16, }; -enum class StagePrivacy { - PUBLIC = 1, - GUILD_ONLY = 2, -}; - -constexpr const char *GetStagePrivacyDisplayString(StagePrivacy e) { - switch (e) { - case StagePrivacy::PUBLIC: - return "Public"; - case StagePrivacy::GUILD_ONLY: - return "Guild Only"; - default: - return "Unknown"; - } -} - // should be moved somewhere? struct ThreadMetadataData { diff --git a/src/discord/discord.cpp b/src/discord/discord.cpp index 385d6b7e..e99cb5f7 100644 --- a/src/discord/discord.cpp +++ b/src/discord/discord.cpp @@ -1646,6 +1646,15 @@ void DiscordClient::HandleGatewayMessage(std::string str) { case GatewayEvent::GUILD_MEMBERS_CHUNK: { HandleGatewayGuildMembersChunk(m); } break; + case GatewayEvent::STAGE_INSTANCE_CREATE: { + HandleGatewayStageInstanceCreate(m); + } break; + case GatewayEvent::STAGE_INSTANCE_UPDATE: { + HandleGatewayStageInstanceUpdate(m); + } break; + case GatewayEvent::STAGE_INSTANCE_DELETE: { + HandleGatewayStageInstanceDelete(m); + } break; #ifdef WITH_VOICE case GatewayEvent::VOICE_STATE_UPDATE: { HandleGatewayVoiceStateUpdate(m); @@ -2296,6 +2305,29 @@ void DiscordClient::HandleGatewayGuildMembersChunk(const GatewayMessage &msg) { m_store.EndTransaction(); } +void DiscordClient::HandleGatewayStageInstanceCreate(const GatewayMessage &msg) { + StageInstance data = msg.Data; + spdlog::get("discord")->debug("STAGE_INSTANCE_CREATE: {} in {}", data.ID, data.ChannelID); + m_stage_instances[data.ID] = data; + m_channel_to_stage_instance[data.ChannelID] = data.ID; + m_signal_stage_instance_create.emit(data); +} + +void DiscordClient::HandleGatewayStageInstanceUpdate(const GatewayMessage &msg) { + StageInstance data = msg.Data; + spdlog::get("discord")->debug("STAGE_INSTANCE_UPDATE: {} in {}", data.ID, data.ChannelID); + m_stage_instances[data.ID] = data; + m_signal_stage_instance_update.emit(data); +} + +void DiscordClient::HandleGatewayStageInstanceDelete(const GatewayMessage &msg) { + StageInstance data = msg.Data; + spdlog::get("discord")->debug("STAGE_INSTANCE_DELETE: {} in {}", data.ID, data.ChannelID); + m_stage_instances.erase(data.ID); + m_channel_to_stage_instance.erase(data.ChannelID); + m_signal_stage_instance_delete.emit(data); +} + #ifdef WITH_VOICE /* @@ -3001,6 +3033,9 @@ void DiscordClient::LoadEventMap() { m_event_map["VOICE_STATE_UPDATE"] = GatewayEvent::VOICE_STATE_UPDATE; m_event_map["VOICE_SERVER_UPDATE"] = GatewayEvent::VOICE_SERVER_UPDATE; m_event_map["CALL_CREATE"] = GatewayEvent::CALL_CREATE; + m_event_map["STAGE_INSTANCE_CREATE"] = GatewayEvent::STAGE_INSTANCE_CREATE; + m_event_map["STAGE_INSTANCE_UPDATE"] = GatewayEvent::STAGE_INSTANCE_UPDATE; + m_event_map["STAGE_INSTANCE_DELETE"] = GatewayEvent::STAGE_INSTANCE_DELETE; } DiscordClient::type_signal_gateway_ready DiscordClient::signal_gateway_ready() { @@ -3179,6 +3214,18 @@ DiscordClient::type_signal_guild_members_chunk DiscordClient::signal_guild_membe return m_signal_guild_members_chunk; } +DiscordClient::type_signal_stage_instance_create DiscordClient::signal_stage_instance_create() { + return m_signal_stage_instance_create; +} + +DiscordClient::type_signal_stage_instance_update DiscordClient::signal_stage_instance_update() { + return m_signal_stage_instance_update; +} + +DiscordClient::type_signal_stage_instance_delete DiscordClient::signal_stage_instance_delete() { + return m_signal_stage_instance_delete; +} + DiscordClient::type_signal_added_to_thread DiscordClient::signal_added_to_thread() { return m_signal_added_to_thread; } diff --git a/src/discord/discord.hpp b/src/discord/discord.hpp index 21eaa822..a74b5f63 100644 --- a/src/discord/discord.hpp +++ b/src/discord/discord.hpp @@ -294,12 +294,16 @@ class DiscordClient { void HandleGatewayMessageAck(const GatewayMessage &msg); void HandleGatewayUserGuildSettingsUpdate(const GatewayMessage &msg); void HandleGatewayGuildMembersChunk(const GatewayMessage &msg); + void HandleGatewayStageInstanceCreate(const GatewayMessage &msg); + void HandleGatewayStageInstanceUpdate(const GatewayMessage &msg); + void HandleGatewayStageInstanceDelete(const GatewayMessage &msg); void HandleGatewayReadySupplemental(const GatewayMessage &msg); void HandleGatewayReconnect(const GatewayMessage &msg); void HandleGatewayInvalidSession(const GatewayMessage &msg); #ifdef WITH_VOICE - void HandleGatewayVoiceStateUpdate(const GatewayMessage &msg); + void + HandleGatewayVoiceStateUpdate(const GatewayMessage &msg); void HandleGatewayVoiceServerUpdate(const GatewayMessage &msg); void HandleGatewayCallCreate(const GatewayMessage &msg); @@ -341,6 +345,8 @@ class DiscordClient { std::unordered_set m_muted_channels; std::unordered_map m_unread; std::unordered_set m_channel_muted_parent; + std::map m_stage_instances; + std::map m_channel_to_stage_instance; UserData m_user_data; UserSettings m_user_settings; @@ -441,6 +447,9 @@ class DiscordClient { typedef sigc::signal type_signal_thread_member_list_update; typedef sigc::signal type_signal_message_ack; typedef sigc::signal type_signal_guild_members_chunk; + typedef sigc::signal type_signal_stage_instance_create; + typedef sigc::signal type_signal_stage_instance_update; + typedef sigc::signal type_signal_stage_instance_delete; // not discord dispatch events typedef sigc::signal type_signal_added_to_thread; @@ -513,6 +522,9 @@ class DiscordClient { type_signal_thread_member_list_update signal_thread_member_list_update(); type_signal_message_ack signal_message_ack(); type_signal_guild_members_chunk signal_guild_members_chunk(); + type_signal_stage_instance_create signal_stage_instance_create(); + type_signal_stage_instance_update signal_stage_instance_update(); + type_signal_stage_instance_delete signal_stage_instance_delete(); type_signal_added_to_thread signal_added_to_thread(); type_signal_removed_from_thread signal_removed_from_thread(); @@ -582,6 +594,9 @@ class DiscordClient { type_signal_thread_member_list_update m_signal_thread_member_list_update; type_signal_message_ack m_signal_message_ack; type_signal_guild_members_chunk m_signal_guild_members_chunk; + type_signal_stage_instance_create m_signal_stage_instance_create; + type_signal_stage_instance_update m_signal_stage_instance_update; + type_signal_stage_instance_delete m_signal_stage_instance_delete; type_signal_removed_from_thread m_signal_removed_from_thread; type_signal_added_to_thread m_signal_added_to_thread; diff --git a/src/discord/objects.hpp b/src/discord/objects.hpp index dfe99f06..67474a31 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 { diff --git a/src/discord/stage.cpp b/src/discord/stage.cpp new file mode 100644 index 00000000..428e1f3b --- /dev/null +++ b/src/discord/stage.cpp @@ -0,0 +1,12 @@ +#include "stage.hpp" + +#include "json.hpp" + +void from_json(const nlohmann::json &j, StageInstance &m) { + JS_D("id", m.ID); + JS_D("guild_id", m.GuildID); + JS_D("channel_id", m.ChannelID); + JS_N("topic", m.Topic); + JS_N("privacy_level", m.PrivacyLevel); + JS_N("guild_scheduled_event_id", m.GuildScheduledEventID); +} diff --git a/src/discord/stage.hpp b/src/discord/stage.hpp new file mode 100644 index 00000000..3df44335 --- /dev/null +++ b/src/discord/stage.hpp @@ -0,0 +1,32 @@ +#pragma once + +#include + +#include "snowflake.hpp" + +enum class StagePrivacy { + PUBLIC = 1, + GUILD_ONLY = 2, +}; + +constexpr const char *GetStagePrivacyDisplayString(StagePrivacy e) { + switch (e) { + case StagePrivacy::PUBLIC: + return "Public"; + case StagePrivacy::GUILD_ONLY: + return "Guild Only"; + default: + return "Unknown"; + } +} + +struct StageInstance { + Snowflake ID; + Snowflake GuildID; + Snowflake ChannelID; + std::string Topic; + StagePrivacy PrivacyLevel; + Snowflake GuildScheduledEventID; + + friend void from_json(const nlohmann::json &j, StageInstance &m); +}; diff --git a/src/discord/voiceclient.cpp b/src/discord/voiceclient.cpp index e4b56bda..164033fa 100644 --- a/src/discord/voiceclient.cpp +++ b/src/discord/voiceclient.cpp @@ -96,11 +96,11 @@ std::vector UDPSocket::Receive() { } void UDPSocket::Stop() { - #ifdef _WIN32 +#ifdef _WIN32 closesocket(m_socket); - #else +#else close(m_socket); - #endif +#endif m_running = false; if (m_thread.joinable()) m_thread.join(); } @@ -250,6 +250,7 @@ bool DiscordVoiceClient::IsConnecting() const noexcept { } void DiscordVoiceClient::OnGatewayMessage(const std::string &str) { + m_log->trace("IN: {}", str); VoiceGatewayMessage msg = nlohmann::json::parse(str); switch (msg.Opcode) { case VoiceGatewayOp::Hello: diff --git a/src/discord/voiceclient.hpp b/src/discord/voiceclient.hpp index 0112749f..aa1014cf 100644 --- a/src/discord/voiceclient.hpp +++ b/src/discord/voiceclient.hpp @@ -43,6 +43,23 @@ enum class VoiceGatewayOp : int { Hello = 8, Resumed = 9, ClientDisconnect = 13, + SessionUpdate = 14, + MediaSinkWants = 15, + VoiceBackendVersion = 16, + ChannelOptionsUpdate = 17, + Flags = 18, + SpeedTest = 19, + Platform = 20, + SecureFramesPrepareProtocolTransition = 21, + SecureFramesExecuteTransition = 22, + SecureFramesReadyForTransition = 23, + SecureFramesPrepareEpoch = 24, + MlsExternalSenderPackage = 25, + MlsKeyPackage = 26, + MlsProposals = 27, + MlsCommitWelcome = 28, + MlsPrepareCommitTransition = 29, + MlsWelcome = 30, }; struct VoiceGatewayMessage { @@ -156,11 +173,11 @@ class UDPSocket { private: void ReadThread(); - #ifdef _WIN32 +#ifdef _WIN32 SOCKET m_socket; - #else +#else int m_socket; - #endif +#endif sockaddr_in m_server; std::atomic m_running = false; diff --git a/src/windows/voicewindow.cpp b/src/windows/voicewindow.cpp index 18f4a416..90338574 100644 --- a/src/windows/voicewindow.cpp +++ b/src/windows/voicewindow.cpp @@ -248,12 +248,26 @@ VoiceWindow::VoiceWindow(Snowflake channel_id) combos_combos->pack_start(m_playback_combo); combos_combos->pack_start(m_capture_combo); + discord.signal_stage_instance_create().connect([this](const StageInstance &instance) { + m_TMP_stagelabel.show(); + m_TMP_stagelabel.set_markup("" + instance.Topic + ""); + }); + + discord.signal_stage_instance_update().connect([this](const StageInstance &instance) { + m_TMP_stagelabel.set_markup("" + instance.Topic + ""); + }); + + discord.signal_stage_instance_delete().connect([this](const StageInstance &instance) { + m_TMP_stagelabel.hide(); + }); + m_scroll.add(m_user_list); m_controls.add(m_mute); m_controls.add(m_deafen); m_controls.add(m_noise_suppression); m_controls.add(m_mix_mono); 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_vad_value, false, true); m_main.pack_start(*Gtk::make_managed("Input Settings"), false, true); @@ -263,6 +277,8 @@ VoiceWindow::VoiceWindow(Snowflake channel_id) add(m_main); show_all_children(); + m_TMP_stagelabel.hide(); + Glib::signal_timeout().connect(sigc::mem_fun(*this, &VoiceWindow::UpdateVoiceMeters), 40); } diff --git a/src/windows/voicewindow.hpp b/src/windows/voicewindow.hpp index 018934b2..6d460849 100644 --- a/src/windows/voicewindow.hpp +++ b/src/windows/voicewindow.hpp @@ -69,6 +69,8 @@ class VoiceWindow : public Gtk::Window { 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; From fba1f568c23bd2e9a1cc7852dcb9b1b747611973 Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Tue, 12 Mar 2024 21:24:58 -0400 Subject: [PATCH 02/16] store suppressed state --- src/discord/discord.cpp | 1 + src/discord/voicestateflags.hpp | 1 + 2 files changed, 2 insertions(+) diff --git a/src/discord/discord.cpp b/src/discord/discord.cpp index e99cb5f7..ab0374da 100644 --- a/src/discord/discord.cpp +++ b/src/discord/discord.cpp @@ -2960,6 +2960,7 @@ void DiscordClient::SetVoiceState(Snowflake user_id, const VoiceState &state) { if (state.IsDeafened) flags |= VoiceStateFlags::Deaf; if (state.IsSelfStream) flags |= VoiceStateFlags::SelfStream; 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_state_channel_users[*state.ChannelID].insert(user_id); diff --git a/src/discord/voicestateflags.hpp b/src/discord/voicestateflags.hpp index 01fb7620..e725d9a9 100644 --- a/src/discord/voicestateflags.hpp +++ b/src/discord/voicestateflags.hpp @@ -10,6 +10,7 @@ enum class VoiceStateFlags : uint8_t { SelfMute = 1 << 3, SelfStream = 1 << 4, SelfVideo = 1 << 5, + Suppressed = 1 << 6, }; template<> 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: [PATCH 03/16] split out and move some files --- src/abaddon.cpp | 2 +- src/windows/{ => voice}/voicewindow.cpp | 80 +------------------ src/windows/{ => voice}/voicewindow.hpp | 1 + .../voice/voicewindowuserlistentry.cpp | 58 ++++++++++++++ .../voice/voicewindowuserlistentry.hpp | 38 +++++++++ 5 files changed, 102 insertions(+), 77 deletions(-) rename src/windows/{ => voice}/voicewindow.cpp (84%) rename src/windows/{ => voice}/voicewindow.hpp (99%) create mode 100644 src/windows/voice/voicewindowuserlistentry.cpp create mode 100644 src/windows/voice/voicewindowuserlistentry.hpp diff --git a/src/abaddon.cpp b/src/abaddon.cpp index 045b8a72..486d5269 100644 --- a/src/abaddon.cpp +++ b/src/abaddon.cpp @@ -19,7 +19,7 @@ #include "windows/profilewindow.hpp" #include "windows/pinnedwindow.hpp" #include "windows/threadswindow.hpp" -#include "windows/voicewindow.hpp" +#include "windows/voice/voicewindow.hpp" #include "startup.hpp" #include "notifications/notifications.hpp" #include "remoteauth/remoteauthdialog.hpp" diff --git a/src/windows/voicewindow.cpp b/src/windows/voice/voicewindow.cpp similarity index 84% rename from src/windows/voicewindow.cpp rename to src/windows/voice/voicewindow.cpp index 90338574..5e050cee 100644 --- a/src/windows/voicewindow.cpp +++ b/src/windows/voice/voicewindow.cpp @@ -2,88 +2,16 @@ // clang-format off +#include "voicewindow.hpp" + #include "abaddon.hpp" #include "audio/manager.hpp" #include "components/lazyimage.hpp" -#include "voicesettingswindow.hpp" -#include "voicewindow.hpp" +#include "voicewindowuserlistentry.hpp" +#include "windows/voicesettingswindow.hpp" // clang-format on -class VoiceWindowUserListEntry : public Gtk::ListBoxRow { -public: - 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 SetVolumeMeter(double frac) { - m_meter.SetVolume(frac); - } - - void RestoreGain(double frac) { - m_volume.set_value(frac * 100.0); - } - -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() { - return m_signal_mute_cs; - } - - type_signal_volume signal_volume() { - return m_signal_volume; - } - -private: - type_signal_mute_cs m_signal_mute_cs; - type_signal_volume m_signal_volume; -}; - VoiceWindow::VoiceWindow(Snowflake channel_id) : m_main(Gtk::ORIENTATION_VERTICAL) , m_controls(Gtk::ORIENTATION_HORIZONTAL) diff --git a/src/windows/voicewindow.hpp b/src/windows/voice/voicewindow.hpp similarity index 99% rename from src/windows/voicewindow.hpp rename to src/windows/voice/voicewindow.hpp index 6d460849..202a0ace 100644 --- a/src/windows/voicewindow.hpp +++ b/src/windows/voice/voicewindow.hpp @@ -17,6 +17,7 @@ // clang-format on class VoiceWindowUserListEntry; + class VoiceWindow : public Gtk::Window { public: VoiceWindow(Snowflake channel_id); diff --git a/src/windows/voice/voicewindowuserlistentry.cpp b/src/windows/voice/voicewindowuserlistentry.cpp new file mode 100644 index 00000000..97a30311 --- /dev/null +++ b/src/windows/voice/voicewindowuserlistentry.cpp @@ -0,0 +1,58 @@ +#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 new file mode 100644 index 00000000..4e8c028f --- /dev/null +++ b/src/windows/voice/voicewindowuserlistentry.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 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; +}; From 019ae0428bc3ad878dffa6218eb03b96457d7dcb Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Thu, 14 Mar 2024 01:06:39 -0400 Subject: [PATCH 04/16] fix crash in temp stuff --- src/windows/voice/voicewindow.cpp | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/windows/voice/voicewindow.cpp b/src/windows/voice/voicewindow.cpp index 5e050cee..05b2ade9 100644 --- a/src/windows/voice/voicewindow.cpp +++ b/src/windows/voice/voicewindow.cpp @@ -176,18 +176,21 @@ VoiceWindow::VoiceWindow(Snowflake channel_id) combos_combos->pack_start(m_playback_combo); combos_combos->pack_start(m_capture_combo); - discord.signal_stage_instance_create().connect([this](const StageInstance &instance) { + 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([this](const StageInstance &instance) { + 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([this](const StageInstance &instance) { + discord.signal_stage_instance_delete().connect(sigc::track_obj([this](const StageInstance &instance) { m_TMP_stagelabel.hide(); - }); + }, + *this)); m_scroll.add(m_user_list); m_controls.add(m_mute); 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: [PATCH 05/16] 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 +++++ .../{voicestateflags.hpp => voicestate.hpp} | 10 ++++++++++ src/misc/bitwise.hpp | 7 +++++++ src/windows/voice/voicewindow.cpp | 18 +++++++++++++----- src/windows/voice/voicewindow.hpp | 1 + 11 files changed, 51 insertions(+), 12 deletions(-) create mode 100644 src/discord/voicestate.cpp rename src/discord/{voicestateflags.hpp => voicestate.hpp} (56%) diff --git a/src/components/channellist/cellrendererchannels.hpp b/src/components/channellist/cellrendererchannels.hpp index a1c020ba..813a9963 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 8b313a37..92339410 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 ab0374da..d997bc22 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 a74b5f63..155079f5 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 @@ class DiscordClient { [[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 @@ class DiscordClient { 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 804f10d2..1c5dd395 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 67474a31..e026311f 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 00000000..05c050d6 --- /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/voicestateflags.hpp b/src/discord/voicestate.hpp similarity index 56% rename from src/discord/voicestateflags.hpp rename to src/discord/voicestate.hpp index e725d9a9..cc75b0cd 100644 --- a/src/discord/voicestateflags.hpp +++ b/src/discord/voicestate.hpp @@ -1,7 +1,10 @@ #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, @@ -13,6 +16,13 @@ enum class VoiceStateFlags : uint8_t { 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/misc/bitwise.hpp b/src/misc/bitwise.hpp index ecce3338..4d4cf8fc 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 05b2ade9..1ada8ee1 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 202a0ace..5d73e97a 100644 --- a/src/windows/voice/voicewindow.hpp +++ b/src/windows/voice/voicewindow.hpp @@ -62,6 +62,7 @@ class VoiceWindow : public Gtk::Window { Gtk::ComboBox m_capture_combo; Snowflake m_channel_id; + bool m_is_stage; std::unordered_map m_rows; 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: [PATCH 06/16] 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(-) diff --git a/src/discord/discord.cpp b/src/discord/discord.cpp index d997bc22..40af4984 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 155079f5..ed7245ee 100644 --- a/src/discord/discord.hpp +++ b/src/discord/discord.hpp @@ -480,6 +480,7 @@ class DiscordClient { 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 @@ class DiscordClient { 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 @@ class DiscordClient { 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 1ada8ee1..079d0f7a 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 5d73e97a..fea998ce 100644 --- a/src/windows/voice/voicewindow.hpp +++ b/src/windows/voice/voicewindow.hpp @@ -29,6 +29,7 @@ class VoiceWindow : public Gtk::Window { 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(); 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: [PATCH 07/16] separate audience section in voice --- src/windows/voice/voicewindow.cpp | 61 ++++++++++++------- src/windows/voice/voicewindow.hpp | 15 +++-- .../voice/voicewindowaudiencelistentry.cpp | 22 +++++++ .../voice/voicewindowaudiencelistentry.hpp | 18 ++++++ ...ry.cpp => voicewindowspeakerlistentry.cpp} | 12 ++-- ...ry.hpp => voicewindowspeakerlistentry.hpp} | 4 +- 6 files changed, 97 insertions(+), 35 deletions(-) create mode 100644 src/windows/voice/voicewindowaudiencelistentry.cpp create mode 100644 src/windows/voice/voicewindowaudiencelistentry.hpp rename src/windows/voice/{voicewindowuserlistentry.cpp => voicewindowspeakerlistentry.cpp} (73%) rename src/windows/voice/{voicewindowuserlistentry.hpp => voicewindowspeakerlistentry.hpp} (88%) diff --git a/src/windows/voice/voicewindow.cpp b/src/windows/voice/voicewindow.cpp index 079d0f7a..5e91aad6 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 fea998ce..7008f9a0 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 @@ class VoiceWindow : public Gtk::Window { 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 @@ class VoiceWindow : public Gtk::Window { void OnMuteChanged(); void OnDeafenChanged(); + void TryDeleteRow(Snowflake id); + bool UpdateVoiceMeters(); void UpdateVADParamValue(); @@ -45,7 +46,9 @@ class VoiceWindow : public Gtk::Window { 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 @@ class VoiceWindow : public Gtk::Window { 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 @@ class VoiceWindow : public Gtk::Window { 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 00000000..aa7dad6e --- /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 00000000..e7bdbb19 --- /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/voicewindowuserlistentry.cpp b/src/windows/voice/voicewindowspeakerlistentry.cpp similarity index 73% rename from src/windows/voice/voicewindowuserlistentry.cpp rename to src/windows/voice/voicewindowspeakerlistentry.cpp index 97a30311..a7bf2b83 100644 --- a/src/windows/voice/voicewindowuserlistentry.cpp +++ b/src/windows/voice/voicewindowspeakerlistentry.cpp @@ -1,8 +1,8 @@ -#include "voicewindowuserlistentry.hpp" +#include "voicewindowspeakerlistentry.hpp" #include "abaddon.hpp" -VoiceWindowUserListEntry::VoiceWindowUserListEntry(Snowflake id) +VoiceWindowSpeakerListEntry::VoiceWindowSpeakerListEntry(Snowflake id) : m_main(Gtk::ORIENTATION_VERTICAL) , m_horz(Gtk::ORIENTATION_HORIZONTAL) , m_avatar(32, 32) @@ -41,18 +41,18 @@ VoiceWindowUserListEntry::VoiceWindowUserListEntry(Snowflake id) }); } -void VoiceWindowUserListEntry::SetVolumeMeter(double frac) { +void VoiceWindowSpeakerListEntry::SetVolumeMeter(double frac) { m_meter.SetVolume(frac); } -void VoiceWindowUserListEntry::RestoreGain(double frac) { +void VoiceWindowSpeakerListEntry::RestoreGain(double frac) { m_volume.set_value(frac * 100.0); } -VoiceWindowUserListEntry::type_signal_mute_cs VoiceWindowUserListEntry::signal_mute_cs() { +VoiceWindowSpeakerListEntry::type_signal_mute_cs VoiceWindowSpeakerListEntry::signal_mute_cs() { return m_signal_mute_cs; } -VoiceWindowUserListEntry::type_signal_volume VoiceWindowUserListEntry::signal_volume() { +VoiceWindowSpeakerListEntry::type_signal_volume VoiceWindowSpeakerListEntry::signal_volume() { return m_signal_volume; } diff --git a/src/windows/voice/voicewindowuserlistentry.hpp b/src/windows/voice/voicewindowspeakerlistentry.hpp similarity index 88% rename from src/windows/voice/voicewindowuserlistentry.hpp rename to src/windows/voice/voicewindowspeakerlistentry.hpp index 4e8c028f..a3b6429f 100644 --- a/src/windows/voice/voicewindowuserlistentry.hpp +++ b/src/windows/voice/voicewindowspeakerlistentry.hpp @@ -10,9 +10,9 @@ #include #include -class VoiceWindowUserListEntry : public Gtk::ListBoxRow { +class VoiceWindowSpeakerListEntry : public Gtk::ListBoxRow { public: - VoiceWindowUserListEntry(Snowflake id); + VoiceWindowSpeakerListEntry(Snowflake id); void SetVolumeMeter(double frac); void RestoreGain(double frac); From 565213450a891d728b3b46102e3c485cdfa86844 Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Sun, 31 Mar 2024 22:03:25 -0400 Subject: [PATCH 08/16] include --- src/windows/voice/voicewindowaudiencelistentry.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/windows/voice/voicewindowaudiencelistentry.cpp b/src/windows/voice/voicewindowaudiencelistentry.cpp index aa7dad6e..cf93343d 100644 --- a/src/windows/voice/voicewindowaudiencelistentry.cpp +++ b/src/windows/voice/voicewindowaudiencelistentry.cpp @@ -1,4 +1,5 @@ #include "voicewindowaudiencelistentry.hpp" +#include "abaddon.hpp" VoiceWindowAudienceListEntry::VoiceWindowAudienceListEntry(Snowflake id) : m_main(Gtk::ORIENTATION_HORIZONTAL) From 870f05062a87444ee58436e8d12ba3702073eb8e Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Mon, 15 Apr 2024 20:28:11 -0400 Subject: [PATCH 09/16] store stage instances on connect --- src/discord/discord.cpp | 7 +++++++ src/discord/guild.cpp | 1 + src/discord/guild.hpp | 2 ++ 3 files changed, 10 insertions(+) diff --git a/src/discord/discord.cpp b/src/discord/discord.cpp index 40af4984..b3f8ca6a 100644 --- a/src/discord/discord.cpp +++ b/src/discord/discord.cpp @@ -1720,6 +1720,13 @@ void DiscordClient::ProcessNewGuild(GuildData &guild) { return; } + if (guild.StageInstances.has_value()) { + for (const auto &stage : *guild.StageInstances) { + m_stage_instances[stage.ID] = stage; + m_channel_to_stage_instance[stage.ChannelID] = stage.ID; + } + } + m_store.BeginTransaction(); m_store.SetGuild(guild.ID, guild); diff --git a/src/discord/guild.cpp b/src/discord/guild.cpp index 06c4acf7..9cf94c2b 100644 --- a/src/discord/guild.cpp +++ b/src/discord/guild.cpp @@ -54,6 +54,7 @@ void from_json(const nlohmann::json &j, GuildData &m) { JS_O("preferred_locale", m.PreferredLocale); JS_ON("public_updates_channel_id", m.PublicUpdatesChannelID); JS_O("max_video_channel_users", m.MaxVideoChannelUsers); + JS_ON("stage_instances", m.StageInstances); JS_O("approximate_member_count", tmp); if (tmp.has_value()) m.ApproximateMemberCount = std::stol(*tmp); diff --git a/src/discord/guild.hpp b/src/discord/guild.hpp index 4895d307..1ea858df 100644 --- a/src/discord/guild.hpp +++ b/src/discord/guild.hpp @@ -4,6 +4,7 @@ #include "role.hpp" #include "channel.hpp" #include "emoji.hpp" +#include "stage.hpp" #include #include #include @@ -90,6 +91,7 @@ struct GuildData { std::optional ApproximateMemberCount; std::optional ApproximatePresenceCount; std::optional> Threads; // only with permissions to view, id only + std::optional> StageInstances; // undocumented // std::map GuildHashes; From 3dc8fa8e65bdd1bd45dfa0171a06dfbb448baf0d Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Tue, 23 Apr 2024 22:14:14 -0400 Subject: [PATCH 10/16] show thingy topic right away --- src/discord/discord.cpp | 9 +++++++++ src/discord/discord.hpp | 1 + src/windows/voice/voicewindow.cpp | 12 +++++++++--- 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/src/discord/discord.cpp b/src/discord/discord.cpp index b3f8ca6a..c54583ac 100644 --- a/src/discord/discord.cpp +++ b/src/discord/discord.cpp @@ -358,6 +358,14 @@ std::optional DiscordClient::GetWebhookMessageData(Snowflake return m_store.GetWebhookMessage(message_id); } +std::optional DiscordClient::GetStageInstanceFromChannel(Snowflake channel_id) const { + const auto iter1 = m_channel_to_stage_instance.find(channel_id); + if (iter1 == m_channel_to_stage_instance.end()) return {}; + const auto iter2 = m_stage_instances.find(iter1->second); + if (iter2 == m_stage_instances.end()) return {}; + return iter2->second; +} + bool DiscordClient::IsThreadJoined(Snowflake thread_id) const { return std::find(m_joined_threads.begin(), m_joined_threads.end(), thread_id) != m_joined_threads.end(); } @@ -1722,6 +1730,7 @@ void DiscordClient::ProcessNewGuild(GuildData &guild) { if (guild.StageInstances.has_value()) { for (const auto &stage : *guild.StageInstances) { + spdlog::get("discord")->debug("storing stage {} in channel {}", stage.ID, stage.ChannelID); m_stage_instances[stage.ID] = stage; m_channel_to_stage_instance[stage.ChannelID] = stage.ID; } diff --git a/src/discord/discord.hpp b/src/discord/discord.hpp index 44a73288..eca25a67 100644 --- a/src/discord/discord.hpp +++ b/src/discord/discord.hpp @@ -65,6 +65,7 @@ class DiscordClient { void GetArchivedPrivateThreads(Snowflake channel_id, const sigc::slot &callback); std::vector GetChildChannelIDs(Snowflake parent_id) const; std::optional GetWebhookMessageData(Snowflake message_id) const; + std::optional GetStageInstanceFromChannel(Snowflake channel_id) const; // get ids of given list of members for who we do not have the member data template diff --git a/src/windows/voice/voicewindow.cpp b/src/windows/voice/voicewindow.cpp index 5e91aad6..8c4eb2ce 100644 --- a/src/windows/voice/voicewindow.cpp +++ b/src/windows/voice/voicewindow.cpp @@ -181,6 +181,14 @@ VoiceWindow::VoiceWindow(Snowflake channel_id) combos_combos->pack_start(m_playback_combo); 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 + ""); + } else { + m_TMP_stagelabel.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 + ""); @@ -218,9 +226,7 @@ VoiceWindow::VoiceWindow(Snowflake channel_id) m_main.pack_start(*combos_container, false, true, 2); add(m_main); show_all_children(); - - m_TMP_stagelabel.hide(); - + Glib::signal_timeout().connect(sigc::mem_fun(*this, &VoiceWindow::UpdateVoiceMeters), 40); } From 837e25a0cf28f3a132e6e675bb0e6d376f3d83f9 Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Tue, 25 Jun 2024 02:26:41 -0400 Subject: [PATCH 11/16] oops --- src/components/channellist/channellisttree.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/channellist/channellisttree.cpp b/src/components/channellist/channellisttree.cpp index e5f31fae..e824933c 100644 --- a/src/components/channellist/channellisttree.cpp +++ b/src/components/channellist/channellisttree.cpp @@ -1485,9 +1485,10 @@ void ChannelListTree::OnVoiceStageSubmenuPopup() { m_menu_voice_stage_join.set_sensitive(true); m_menu_voice_stage_disconnect.set_sensitive(false); } -#endif +#else m_menu_voice_stage_join.set_sensitive(false); m_menu_voice_stage_disconnect.set_sensitive(false); +#endif } void ChannelListTree::OnDMSubmenuPopup() { 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: [PATCH 12/16] 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(-) diff --git a/src/discord/discord.cpp b/src/discord/discord.cpp index 8f88a91a..2ee84936 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 ab051aa8..55bd3086 100644 --- a/src/discord/discord.hpp +++ b/src/discord/discord.hpp @@ -203,6 +203,9 @@ class DiscordClient { [[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 1c5dd395..e6b76753 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 e026311f..44afe8d1 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 7607a0f2..bfea175e 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 0df9fa88..7803f85c 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 @@ class VoiceWindow : public Gtk::Window { 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 @@ class VoiceWindow : public Gtk::Window { 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; 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: [PATCH 13/16] 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(-) diff --git a/src/discord/discord.cpp b/src/discord/discord.cpp index 2ee84936..d99e57a9 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 55bd3086..42fa1cb5 100644 --- a/src/discord/discord.hpp +++ b/src/discord/discord.hpp @@ -87,6 +87,7 @@ class DiscordClient { 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 @@ class DiscordClient { [[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 bfea175e..27dd734f 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 7803f85c..5005b90a 100644 --- a/src/windows/voice/voicewindow.hpp +++ b/src/windows/voice/voicewindow.hpp @@ -36,10 +36,9 @@ class VoiceWindow : public Gtk::Window { void OnDeafenChanged(); void TryDeleteRow(Snowflake id); - bool UpdateVoiceMeters(); - void UpdateVADParamValue(); + void UpdateStageCommand(); Gtk::Box m_main; Gtk::Box m_controls; @@ -65,7 +64,7 @@ class VoiceWindow : public Gtk::Window { 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; 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: [PATCH 14/16] 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(-) diff --git a/src/discord/discord.cpp b/src/discord/discord.cpp index d99e57a9..a7712d69 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 42fa1cb5..483abf0d 100644 --- a/src/discord/discord.hpp +++ b/src/discord/discord.hpp @@ -209,6 +209,7 @@ class DiscordClient { 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 27dd734f..5930156a 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 5005b90a..7c5a137e 100644 --- a/src/windows/voice/voicewindow.hpp +++ b/src/windows/voice/voicewindow.hpp @@ -66,6 +66,12 @@ class VoiceWindow : public Gtk::Window { 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; 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: [PATCH 15/16] 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(-) diff --git a/src/windows/voice/voicewindow.cpp b/src/windows/voice/voicewindow.cpp index 5930156a..944d8482 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 7c5a137e..64b14a9c 100644 --- a/src/windows/voice/voicewindow.hpp +++ b/src/windows/voice/voicewindow.hpp @@ -89,8 +89,8 @@ class VoiceWindow : public Gtk::Window { 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; 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: [PATCH 16/16] 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(-) diff --git a/src/windows/voice/voicewindow.cpp b/src/windows/voice/voicewindow.cpp index 944d8482..e59705a2 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 64b14a9c..05033d9b 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 @@ class VoiceWindow : public Gtk::Window { 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 @@ class VoiceWindow : public Gtk::Window { bool UpdateVoiceMeters(); void UpdateVADParamValue(); void UpdateStageCommand(); + void UpdateStageTopicLabel(const std::string &topic); Gtk::Box m_main; Gtk::Box m_controls; @@ -88,7 +93,7 @@ class VoiceWindow : public Gtk::Window { 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;