From 0fa33915da6255cf7460758197eaea7e43353543 Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Wed, 31 Aug 2022 01:51:02 -0400 Subject: rudimentary voice implementation --- src/discord/discord.cpp | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) (limited to 'src/discord/discord.cpp') diff --git a/src/discord/discord.cpp b/src/discord/discord.cpp index 561b25b..ed9b999 100644 --- a/src/discord/discord.cpp +++ b/src/discord/discord.cpp @@ -1169,6 +1169,16 @@ void DiscordClient::AcceptVerificationGate(Snowflake guild_id, VerificationGateI }); } +void DiscordClient::ConnectToVoice(Snowflake channel_id) { + auto channel = GetChannel(channel_id); + if (!channel.has_value() || !channel->GuildID.has_value()) return; + VoiceStateUpdateMessage m; + m.GuildID = *channel->GuildID; + m.ChannelID = channel_id; + m.PreferredRegion = "newark"; + m_websocket.Send(m); +} + void DiscordClient::SetReferringChannel(Snowflake id) { if (!id.IsValid()) { m_http.SetPersistentHeader("Referer", "https://discord.com/channels/@me"); @@ -1488,6 +1498,12 @@ void DiscordClient::HandleGatewayMessage(std::string str) { case GatewayEvent::GUILD_MEMBERS_CHUNK: { HandleGatewayGuildMembersChunk(m); } break; + case GatewayEvent::VOICE_STATE_UPDATE: { + HandleGatewayVoiceStateUpdate(m); + } break; + case GatewayEvent::VOICE_SERVER_UPDATE: { + HandleGatewayVoiceServerUpdate(m); + } break; } } break; default: @@ -2098,6 +2114,25 @@ void DiscordClient::HandleGatewayGuildMembersChunk(const GatewayMessage &msg) { m_store.EndTransaction(); } +void DiscordClient::HandleGatewayVoiceStateUpdate(const GatewayMessage &msg) { + VoiceStateUpdateData data = msg.Data; + if (data.UserID == m_user_data.ID) { + printf("voice session id: %s\n", data.SessionID.c_str()); + m_voice.SetSessionID(data.SessionID); + } +} + +void DiscordClient::HandleGatewayVoiceServerUpdate(const GatewayMessage &msg) { + VoiceServerUpdateData data = msg.Data; + printf("endpoint: %s\n", data.Endpoint.c_str()); + printf("token: %s\n", data.Token.c_str()); + m_voice.SetEndpoint(data.Endpoint); + m_voice.SetToken(data.Token); + m_voice.SetServerID(data.GuildID); + m_voice.SetUserID(m_user_data.ID); + m_voice.Start(); +} + void DiscordClient::HandleGatewayReadySupplemental(const GatewayMessage &msg) { ReadySupplementalData data = msg.Data; for (const auto &p : data.MergedPresences.Friends) { @@ -2589,6 +2624,8 @@ void DiscordClient::LoadEventMap() { m_event_map["MESSAGE_ACK"] = GatewayEvent::MESSAGE_ACK; m_event_map["USER_GUILD_SETTINGS_UPDATE"] = GatewayEvent::USER_GUILD_SETTINGS_UPDATE; m_event_map["GUILD_MEMBERS_CHUNK"] = GatewayEvent::GUILD_MEMBERS_CHUNK; + m_event_map["VOICE_STATE_UPDATE"] = GatewayEvent::VOICE_STATE_UPDATE; + m_event_map["VOICE_SERVER_UPDATE"] = GatewayEvent::VOICE_SERVER_UPDATE; } DiscordClient::type_signal_gateway_ready DiscordClient::signal_gateway_ready() { -- cgit v1.2.3 From 0a049856782397a7464d03c317ce3788559cdeda Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Fri, 2 Sep 2022 01:25:33 -0400 Subject: make compile work if voice support is disabled --- src/abaddon.cpp | 9 +++++++++ src/abaddon.hpp | 9 +++++++++ src/audio/manager.cpp | 4 ++++ src/audio/manager.hpp | 4 ++++ src/components/channels.cpp | 18 ++++++++++++++++++ src/components/channels.hpp | 7 ++++++- src/components/channelscellrenderer.cpp | 12 ++++++++++++ src/components/channelscellrenderer.hpp | 3 +++ src/discord/discord.cpp | 6 ++++++ src/discord/discord.hpp | 12 ++++++++++-- src/discord/objects.cpp | 2 ++ src/discord/objects.hpp | 2 ++ src/discord/voiceclient.cpp | 10 +++++++--- src/discord/voiceclient.hpp | 10 +++++++--- 14 files changed, 99 insertions(+), 9 deletions(-) (limited to 'src/discord/discord.cpp') diff --git a/src/abaddon.cpp b/src/abaddon.cpp index a3a228d..688aff4 100644 --- a/src/abaddon.cpp +++ b/src/abaddon.cpp @@ -220,6 +220,7 @@ int Abaddon::StartGTK() { return 1; } +#ifdef WITH_VOICE m_audio = std::make_unique(); if (!m_audio->OK()) { Gtk::MessageDialog dlg(*m_main_window, "The audio engine could not be initialized!", false, Gtk::MESSAGE_ERROR, Gtk::BUTTONS_OK, true); @@ -227,6 +228,7 @@ int Abaddon::StartGTK() { dlg.run(); return 1; } +#endif // store must be checked before this can be called m_main_window->UpdateComponents(); @@ -247,7 +249,10 @@ int Abaddon::StartGTK() { m_main_window->GetChannelList()->signal_action_channel_item_select().connect(sigc::bind(sigc::mem_fun(*this, &Abaddon::ActionChannelOpened), true)); m_main_window->GetChannelList()->signal_action_guild_leave().connect(sigc::mem_fun(*this, &Abaddon::ActionLeaveGuild)); m_main_window->GetChannelList()->signal_action_guild_settings().connect(sigc::mem_fun(*this, &Abaddon::ActionGuildSettings)); + +#ifdef WITH_VOICE m_main_window->GetChannelList()->signal_action_join_voice_channel().connect(sigc::mem_fun(*this, &Abaddon::ActionJoinVoiceChannel)); +#endif m_main_window->GetChatWindow()->signal_action_message_edit().connect(sigc::mem_fun(*this, &Abaddon::ActionChatEditMessage)); m_main_window->GetChatWindow()->signal_action_chat_submit().connect(sigc::mem_fun(*this, &Abaddon::ActionChatInputSubmit)); @@ -908,9 +913,11 @@ void Abaddon::ActionViewThreads(Snowflake channel_id) { window->show(); } +#ifdef WITH_VOICE void Abaddon::ActionJoinVoiceChannel(Snowflake channel_id) { m_discord.ConnectToVoice(channel_id); } +#endif std::optional Abaddon::ShowTextPrompt(const Glib::ustring &prompt, const Glib::ustring &title, const Glib::ustring &placeholder, Gtk::Window *window) { TextInputDialog dlg(prompt, title, placeholder, window != nullptr ? *window : *m_main_window); @@ -951,9 +958,11 @@ EmojiResource &Abaddon::GetEmojis() { return m_emojis; } +#ifdef WITH_VOICE AudioManager &Abaddon::GetAudio() { return *m_audio.get(); } +#endif int main(int argc, char **argv) { if (std::getenv("ABADDON_NO_FC") == nullptr) diff --git a/src/abaddon.hpp b/src/abaddon.hpp index d67f4ab..9a98c9e 100644 --- a/src/abaddon.hpp +++ b/src/abaddon.hpp @@ -53,7 +53,10 @@ public: void ActionAddRecipient(Snowflake channel_id); void ActionViewPins(Snowflake channel_id); void ActionViewThreads(Snowflake channel_id); + +#ifdef WITH_VOICE void ActionJoinVoiceChannel(Snowflake channel_id); +#endif std::optional ShowTextPrompt(const Glib::ustring &prompt, const Glib::ustring &title, const Glib::ustring &placeholder = "", Gtk::Window *window = nullptr); bool ShowConfirm(const Glib::ustring &prompt, Gtk::Window *window = nullptr); @@ -62,7 +65,10 @@ public: ImageManager &GetImageManager(); EmojiResource &GetEmojis(); + +#ifdef WITH_VOICE AudioManager &GetAudio(); +#endif std::string GetDiscordToken() const; bool IsDiscordActive() const; @@ -141,7 +147,10 @@ private: ImageManager m_img_mgr; EmojiResource m_emojis; + +#ifdef WITH_VOICE std::unique_ptr m_audio; +#endif mutable std::mutex m_mutex; Glib::RefPtr m_gtk_app; diff --git a/src/audio/manager.cpp b/src/audio/manager.cpp index 9ba8c51..d7d0bb8 100644 --- a/src/audio/manager.cpp +++ b/src/audio/manager.cpp @@ -1,3 +1,5 @@ +#ifdef WITH_VOICE +// clang-format off #ifdef _WIN32 #include #endif @@ -8,6 +10,7 @@ #include #include #include +// clang-format on const uint8_t *StripRTPExtensionHeader(const uint8_t *buf, int num_bytes, size_t &outlen) { if (buf[0] == 0xbe && buf[1] == 0xde && num_bytes > 4) { @@ -89,3 +92,4 @@ void AudioManager::FeedMeOpus(uint32_t ssrc, const std::vector &data) { bool AudioManager::OK() const { return m_ok; } +#endif diff --git a/src/audio/manager.hpp b/src/audio/manager.hpp index 55ecdc3..d0f3a21 100644 --- a/src/audio/manager.hpp +++ b/src/audio/manager.hpp @@ -1,4 +1,6 @@ #pragma once +#ifdef WITH_VOICE +// clang-format off #include #include #include @@ -8,6 +10,7 @@ #include #include #include +// clang-format on class AudioManager { public: @@ -31,3 +34,4 @@ private: std::mutex m_mutex; std::unordered_map, OpusDecoder *>> m_sources; }; +#endif diff --git a/src/components/channels.cpp b/src/components/channels.cpp index 566ebd1..201e7c9 100644 --- a/src/components/channels.cpp +++ b/src/components/channels.cpp @@ -36,7 +36,11 @@ ChannelList::ChannelList() const auto type = row[m_columns.m_type]; // text channels should not be allowed to be collapsed // maybe they should be but it seems a little difficult to handle expansion to permit this +#ifdef WITH_VOICE if (type != RenderType::TextChannel && type != RenderType::VoiceChannel) { +#else + if (type != RenderType::TextChannel) { +#endif if (row[m_columns.m_expanded]) { m_view.collapse_row(path); row[m_columns.m_expanded] = false; @@ -161,6 +165,7 @@ ChannelList::ChannelList() m_menu_channel.append(m_menu_channel_copy_id); m_menu_channel.show_all(); +#ifdef WITH_VOICE m_menu_voice_channel_join.signal_activate().connect([this]() { const auto id = static_cast((*m_model->get_iter(m_path_for_menu))[m_columns.m_id]); printf("join voice: %llu\n", static_cast(id)); @@ -169,6 +174,7 @@ ChannelList::ChannelList() m_menu_voice_channel.append(m_menu_voice_channel_join); m_menu_voice_channel.show_all(); +#endif m_menu_dm_copy_id.signal_activate().connect([this] { Gtk::Clipboard::get()->set_text(std::to_string((*m_model->get_iter(m_path_for_menu))[m_columns.m_id])); @@ -588,7 +594,11 @@ Gtk::TreeModel::iterator ChannelList::AddGuild(const GuildData &guild) { for (const auto &channel_ : *guild.Channels) { 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) { +#else + if (channel->Type == ChannelType::GUILD_TEXT || channel->Type == ChannelType::GUILD_NEWS) { +#endif if (channel->ParentID.has_value()) categories[*channel->ParentID].push_back(*channel); else @@ -618,8 +628,10 @@ Gtk::TreeModel::iterator ChannelList::AddGuild(const GuildData &guild) { auto channel_row = *m_model->append(guild_row.children()); if (IsTextChannel(channel.Type)) channel_row[m_columns.m_type] = RenderType::TextChannel; +#ifdef WITH_VOICE else channel_row[m_columns.m_type] = RenderType::VoiceChannel; +#endif channel_row[m_columns.m_id] = channel.ID; channel_row[m_columns.m_name] = "#" + Glib::Markup::escape_text(*channel.Name); channel_row[m_columns.m_sort] = *channel.Position + OrphanChannelSortOffset; @@ -644,8 +656,10 @@ Gtk::TreeModel::iterator ChannelList::AddGuild(const GuildData &guild) { auto channel_row = *m_model->append(cat_row.children()); if (IsTextChannel(channel.Type)) channel_row[m_columns.m_type] = RenderType::TextChannel; +#ifdef WITH_VOICE else channel_row[m_columns.m_type] = RenderType::VoiceChannel; +#endif channel_row[m_columns.m_id] = channel.ID; channel_row[m_columns.m_name] = "#" + Glib::Markup::escape_text(*channel.Name); channel_row[m_columns.m_sort] = *channel.Position; @@ -871,10 +885,12 @@ bool ChannelList::OnButtonPressEvent(GdkEventButton *ev) { OnChannelSubmenuPopup(); m_menu_channel.popup_at_pointer(reinterpret_cast(ev)); break; +#ifdef WITH_VOICE case RenderType::VoiceChannel: OnVoiceChannelSubmenuPopup(); m_menu_voice_channel.popup_at_pointer(reinterpret_cast(ev)); break; +#endif case RenderType::DM: { OnDMSubmenuPopup(); const auto channel = Abaddon::Get().GetDiscordClient().GetChannel(static_cast(row[m_columns.m_id])); @@ -966,8 +982,10 @@ void ChannelList::OnChannelSubmenuPopup() { m_menu_channel_toggle_mute.set_label("Mute"); } +#ifdef WITH_VOICE void ChannelList::OnVoiceChannelSubmenuPopup() { } +#endif void ChannelList::OnDMSubmenuPopup() { auto iter = m_model->get_iter(m_path_for_menu); diff --git a/src/components/channels.hpp b/src/components/channels.hpp index 53afbdc..8c34735 100644 --- a/src/components/channels.hpp +++ b/src/components/channels.hpp @@ -125,8 +125,10 @@ protected: Gtk::MenuItem m_menu_channel_open_tab; #endif +#ifdef WITH_VOICE Gtk::Menu m_menu_voice_channel; Gtk::MenuItem m_menu_voice_channel_join; +#endif Gtk::Menu m_menu_dm; Gtk::MenuItem m_menu_dm_copy_id; @@ -148,10 +150,13 @@ protected: void OnGuildSubmenuPopup(); void OnCategorySubmenuPopup(); void OnChannelSubmenuPopup(); - void OnVoiceChannelSubmenuPopup(); void OnDMSubmenuPopup(); void OnThreadSubmenuPopup(); +#ifdef WITH_VOICE + void OnVoiceChannelSubmenuPopup(); +#endif + bool m_updating_listing = false; Snowflake m_active_channel; diff --git a/src/components/channelscellrenderer.cpp b/src/components/channelscellrenderer.cpp index e9c43aa..23ee3f0 100644 --- a/src/components/channelscellrenderer.cpp +++ b/src/components/channelscellrenderer.cpp @@ -65,8 +65,10 @@ void CellRendererChannels::get_preferred_width_vfunc(Gtk::Widget &widget, int &m return get_preferred_width_vfunc_channel(widget, minimum_width, natural_width); case RenderType::Thread: return get_preferred_width_vfunc_thread(widget, minimum_width, natural_width); +#ifdef WITH_VOICE case RenderType::VoiceChannel: return get_preferred_width_vfunc_voice_channel(widget, minimum_width, natural_width); +#endif case RenderType::DMHeader: return get_preferred_width_vfunc_dmheader(widget, minimum_width, natural_width); case RenderType::DM: @@ -84,8 +86,10 @@ void CellRendererChannels::get_preferred_width_for_height_vfunc(Gtk::Widget &wid return get_preferred_width_for_height_vfunc_channel(widget, height, minimum_width, natural_width); case RenderType::Thread: return get_preferred_width_for_height_vfunc_thread(widget, height, minimum_width, natural_width); +#ifdef WITH_VOICE case RenderType::VoiceChannel: return get_preferred_width_for_height_vfunc_voice_channel(widget, height, minimum_width, natural_width); +#endif case RenderType::DMHeader: return get_preferred_width_for_height_vfunc_dmheader(widget, height, minimum_width, natural_width); case RenderType::DM: @@ -103,8 +107,10 @@ void CellRendererChannels::get_preferred_height_vfunc(Gtk::Widget &widget, int & return get_preferred_height_vfunc_channel(widget, minimum_height, natural_height); case RenderType::Thread: return get_preferred_height_vfunc_thread(widget, minimum_height, natural_height); +#ifdef WITH_VOICE case RenderType::VoiceChannel: return get_preferred_height_vfunc_voice_channel(widget, minimum_height, natural_height); +#endif case RenderType::DMHeader: return get_preferred_height_vfunc_dmheader(widget, minimum_height, natural_height); case RenderType::DM: @@ -122,8 +128,10 @@ void CellRendererChannels::get_preferred_height_for_width_vfunc(Gtk::Widget &wid return get_preferred_height_for_width_vfunc_channel(widget, width, minimum_height, natural_height); case RenderType::Thread: return get_preferred_height_for_width_vfunc_thread(widget, width, minimum_height, natural_height); +#ifdef WITH_VOICE case RenderType::VoiceChannel: return get_preferred_height_for_width_vfunc_voice_channel(widget, width, minimum_height, natural_height); +#endif case RenderType::DMHeader: return get_preferred_height_for_width_vfunc_dmheader(widget, width, minimum_height, natural_height); case RenderType::DM: @@ -141,8 +149,10 @@ void CellRendererChannels::render_vfunc(const Cairo::RefPtr &cr, return render_vfunc_channel(cr, widget, background_area, cell_area, flags); case RenderType::Thread: 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); +#endif case RenderType::DMHeader: return render_vfunc_dmheader(cr, widget, background_area, cell_area, flags); case RenderType::DM: @@ -511,6 +521,7 @@ void CellRendererChannels::render_vfunc_thread(const Cairo::RefPtrGuildID.has_value()) return; @@ -1178,6 +1179,7 @@ void DiscordClient::ConnectToVoice(Snowflake channel_id) { m.PreferredRegion = "newark"; m_websocket.Send(m); } +#endif void DiscordClient::SetReferringChannel(Snowflake id) { if (!id.IsValid()) { @@ -1498,12 +1500,14 @@ void DiscordClient::HandleGatewayMessage(std::string str) { case GatewayEvent::GUILD_MEMBERS_CHUNK: { HandleGatewayGuildMembersChunk(m); } break; +#ifdef WITH_VOICE case GatewayEvent::VOICE_STATE_UPDATE: { HandleGatewayVoiceStateUpdate(m); } break; case GatewayEvent::VOICE_SERVER_UPDATE: { HandleGatewayVoiceServerUpdate(m); } break; +#endif } } break; default: @@ -2114,6 +2118,7 @@ void DiscordClient::HandleGatewayGuildMembersChunk(const GatewayMessage &msg) { m_store.EndTransaction(); } +#ifdef WITH_VOICE void DiscordClient::HandleGatewayVoiceStateUpdate(const GatewayMessage &msg) { VoiceStateUpdateData data = msg.Data; if (data.UserID == m_user_data.ID) { @@ -2132,6 +2137,7 @@ void DiscordClient::HandleGatewayVoiceServerUpdate(const GatewayMessage &msg) { m_voice.SetUserID(m_user_data.ID); m_voice.Start(); } +#endif void DiscordClient::HandleGatewayReadySupplemental(const GatewayMessage &msg) { ReadySupplementalData data = msg.Data; diff --git a/src/discord/discord.hpp b/src/discord/discord.hpp index a6eabd9..907c3e6 100644 --- a/src/discord/discord.hpp +++ b/src/discord/discord.hpp @@ -181,7 +181,9 @@ public: void GetVerificationGateInfo(Snowflake guild_id, const sigc::slot)> &callback); void AcceptVerificationGate(Snowflake guild_id, VerificationGateInfoObject info, const sigc::slot &callback); +#ifdef WITH_VOICE void ConnectToVoice(Snowflake channel_id); +#endif void SetReferringChannel(Snowflake id); @@ -262,11 +264,15 @@ private: void HandleGatewayMessageAck(const GatewayMessage &msg); void HandleGatewayUserGuildSettingsUpdate(const GatewayMessage &msg); void HandleGatewayGuildMembersChunk(const GatewayMessage &msg); - void HandleGatewayVoiceStateUpdate(const GatewayMessage &msg); - void HandleGatewayVoiceServerUpdate(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 HandleGatewayVoiceServerUpdate(const GatewayMessage &msg); +#endif + void HeartbeatThread(); void SendIdentify(); void SendResume(); @@ -326,7 +332,9 @@ private: bool m_wants_resume = false; // reconnecting specifically to resume std::string m_session_id; +#ifdef WITH_VOICE DiscordVoiceClient m_voice; +#endif mutable std::mutex m_msg_mutex; Glib::Dispatcher m_msg_dispatch; diff --git a/src/discord/objects.cpp b/src/discord/objects.cpp index 5ca8718..a6a7cc6 100644 --- a/src/discord/objects.cpp +++ b/src/discord/objects.cpp @@ -641,6 +641,7 @@ void from_json(const nlohmann::json &j, GuildMembersChunkData &m) { JS_D("guild_id", m.GuildID); } +#ifdef WITH_VOICE void to_json(nlohmann::json &j, const VoiceStateUpdateMessage &m) { j["op"] = GatewayOp::VoiceStateUpdate; j["d"]["guild_id"] = m.GuildID; @@ -661,3 +662,4 @@ void from_json(const nlohmann::json &j, VoiceServerUpdateData &m) { JS_D("guild_id", m.GuildID); JS_D("endpoint", m.Endpoint); } +#endif diff --git a/src/discord/objects.hpp b/src/discord/objects.hpp index 240b4c5..c9c8aff 100644 --- a/src/discord/objects.hpp +++ b/src/discord/objects.hpp @@ -867,6 +867,7 @@ struct GuildMembersChunkData { friend void from_json(const nlohmann::json &j, GuildMembersChunkData &m); }; +#ifdef WITH_VOICE struct VoiceStateUpdateMessage { Snowflake GuildID; Snowflake ChannelID; @@ -892,3 +893,4 @@ struct VoiceServerUpdateData { friend void from_json(const nlohmann::json &j, VoiceServerUpdateData &m); }; +#endif diff --git a/src/discord/voiceclient.cpp b/src/discord/voiceclient.cpp index 7aaa4a5..d8855fd 100644 --- a/src/discord/voiceclient.cpp +++ b/src/discord/voiceclient.cpp @@ -1,3 +1,5 @@ +#ifdef WITH_VOICE + // clang-format off #include "voiceclient.hpp" #include "json.hpp" #include @@ -10,6 +12,7 @@ #else #define S_ADDR(var) (var).sin_addr.s_addr #endif +// clang-format on UDPSocket::UDPSocket() { m_socket = socket(AF_INET, SOCK_DGRAM, 0); @@ -88,11 +91,11 @@ std::vector UDPSocket::Receive() { void UDPSocket::Stop() { m_running = false; -#ifdef _WIN32 + #ifdef _WIN32 shutdown(m_socket, SD_BOTH); -#else + #else shutdown(m_socket, SHUT_RDWR); -#endif + #endif if (m_thread.joinable()) m_thread.join(); } @@ -385,3 +388,4 @@ void from_json(const nlohmann::json &j, VoiceSessionDescriptionData &m) { JS_D("mode", m.Mode); JS_D("secret_key", m.SecretKey); } +#endif diff --git a/src/discord/voiceclient.hpp b/src/discord/voiceclient.hpp index 615bbde..4b988d5 100644 --- a/src/discord/voiceclient.hpp +++ b/src/discord/voiceclient.hpp @@ -1,4 +1,6 @@ #pragma once +#ifdef WITH_VOICE +// clang-format off #include "snowflake.hpp" #include "waiter.hpp" #include "websocket.hpp" @@ -6,6 +8,7 @@ #include #include #include +// clang-format on enum class VoiceGatewayCloseCode : uint16_t { UnknownOpcode = 4001, @@ -124,11 +127,11 @@ public: 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; @@ -203,3 +206,4 @@ private: Waiter m_heartbeat_waiter; std::thread m_heartbeat_thread; }; +#endif -- cgit v1.2.3 From 90437de2c031f6cf0b58603d9cb5582064176374 Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Tue, 6 Sep 2022 03:29:13 -0400 Subject: make voice disconnect/reconnect work --- src/abaddon.cpp | 5 +++++ src/abaddon.hpp | 1 + src/components/channels.cpp | 25 ++++++++++++++++++++++++- src/components/channels.hpp | 5 +++++ src/discord/discord.cpp | 15 +++++++++++++++ src/discord/discord.hpp | 5 +++++ src/discord/objects.cpp | 10 ++++++++-- src/discord/objects.hpp | 4 ++-- src/discord/voiceclient.cpp | 35 ++++++++++++++++++++++++----------- src/discord/voiceclient.hpp | 4 ++++ 10 files changed, 93 insertions(+), 16 deletions(-) (limited to 'src/discord/discord.cpp') diff --git a/src/abaddon.cpp b/src/abaddon.cpp index 688aff4..82741be 100644 --- a/src/abaddon.cpp +++ b/src/abaddon.cpp @@ -252,6 +252,7 @@ int Abaddon::StartGTK() { #ifdef WITH_VOICE m_main_window->GetChannelList()->signal_action_join_voice_channel().connect(sigc::mem_fun(*this, &Abaddon::ActionJoinVoiceChannel)); + m_main_window->GetChannelList()->signal_action_disconnect_voice().connect(sigc::mem_fun(*this, &Abaddon::ActionDisconnectVoice)); #endif m_main_window->GetChatWindow()->signal_action_message_edit().connect(sigc::mem_fun(*this, &Abaddon::ActionChatEditMessage)); @@ -917,6 +918,10 @@ void Abaddon::ActionViewThreads(Snowflake channel_id) { void Abaddon::ActionJoinVoiceChannel(Snowflake channel_id) { m_discord.ConnectToVoice(channel_id); } + +void Abaddon::ActionDisconnectVoice() { + m_discord.DisconnectFromVoice(); +} #endif std::optional Abaddon::ShowTextPrompt(const Glib::ustring &prompt, const Glib::ustring &title, const Glib::ustring &placeholder, Gtk::Window *window) { diff --git a/src/abaddon.hpp b/src/abaddon.hpp index 9a98c9e..9b3199a 100644 --- a/src/abaddon.hpp +++ b/src/abaddon.hpp @@ -56,6 +56,7 @@ public: #ifdef WITH_VOICE void ActionJoinVoiceChannel(Snowflake channel_id); + void ActionDisconnectVoice(); #endif std::optional ShowTextPrompt(const Glib::ustring &prompt, const Glib::ustring &title, const Glib::ustring &placeholder = "", Gtk::Window *window = nullptr); diff --git a/src/components/channels.cpp b/src/components/channels.cpp index 201e7c9..eb9d688 100644 --- a/src/components/channels.cpp +++ b/src/components/channels.cpp @@ -20,6 +20,10 @@ ChannelList::ChannelList() #ifdef WITH_LIBHANDY , m_menu_channel_open_tab("Open in New _Tab", true) , m_menu_dm_open_tab("Open in New _Tab", true) +#endif +#ifdef WITH_VOICE + , m_menu_voice_channel_join("_Join", true) + , m_menu_voice_channel_disconnect("_Disconnect", true) #endif , m_menu_dm_copy_id("_Copy ID", true) , m_menu_dm_close("") // changes depending on if group or not @@ -168,11 +172,15 @@ ChannelList::ChannelList() #ifdef WITH_VOICE m_menu_voice_channel_join.signal_activate().connect([this]() { const auto id = static_cast((*m_model->get_iter(m_path_for_menu))[m_columns.m_id]); - printf("join voice: %llu\n", static_cast(id)); m_signal_action_join_voice_channel.emit(id); }); + m_menu_voice_channel_disconnect.signal_activate().connect([this]() { + m_signal_action_disconnect_voice.emit(); + }); + 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(); #endif @@ -984,6 +992,17 @@ void ChannelList::OnChannelSubmenuPopup() { #ifdef WITH_VOICE void ChannelList::OnVoiceChannelSubmenuPopup() { + 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.IsConnectedToVoice()) { + m_menu_voice_channel_join.set_sensitive(false); + m_menu_voice_channel_disconnect.set_sensitive(discord.GetVoiceChannelID() == id); + } else { + m_menu_voice_channel_join.set_sensitive(true); + m_menu_voice_channel_disconnect.set_sensitive(false); + } } #endif @@ -1041,6 +1060,10 @@ ChannelList::type_signal_action_open_new_tab ChannelList::signal_action_open_new ChannelList::type_signal_action_join_voice_channel ChannelList::signal_action_join_voice_channel() { return m_signal_action_join_voice_channel; } + +ChannelList::type_signal_action_disconnect_voice ChannelList::signal_action_disconnect_voice() { + return m_signal_action_disconnect_voice; +} #endif ChannelList::ModelColumns::ModelColumns() { diff --git a/src/components/channels.hpp b/src/components/channels.hpp index 8c34735..2d2b257 100644 --- a/src/components/channels.hpp +++ b/src/components/channels.hpp @@ -128,6 +128,7 @@ protected: #ifdef WITH_VOICE Gtk::Menu m_menu_voice_channel; Gtk::MenuItem m_menu_voice_channel_join; + Gtk::MenuItem m_menu_voice_channel_disconnect; #endif Gtk::Menu m_menu_dm; @@ -177,7 +178,10 @@ public: #ifdef WITH_VOICE using type_signal_action_join_voice_channel = sigc::signal; + using type_signal_action_disconnect_voice = sigc::signal; + type_signal_action_join_voice_channel signal_action_join_voice_channel(); + type_signal_action_disconnect_voice signal_action_disconnect_voice(); #endif type_signal_action_channel_item_select signal_action_channel_item_select(); @@ -195,5 +199,6 @@ private: #ifdef WITH_VOICE type_signal_action_join_voice_channel m_signal_action_join_voice_channel; + type_signal_action_disconnect_voice m_signal_action_disconnect_voice; #endif }; diff --git a/src/discord/discord.cpp b/src/discord/discord.cpp index 5670d4a..f44c1ed 100644 --- a/src/discord/discord.cpp +++ b/src/discord/discord.cpp @@ -1173,12 +1173,27 @@ void DiscordClient::AcceptVerificationGate(Snowflake guild_id, VerificationGateI void DiscordClient::ConnectToVoice(Snowflake channel_id) { auto channel = GetChannel(channel_id); if (!channel.has_value() || !channel->GuildID.has_value()) return; + m_voice_channel_id = channel_id; VoiceStateUpdateMessage m; m.GuildID = *channel->GuildID; m.ChannelID = channel_id; m.PreferredRegion = "newark"; m_websocket.Send(m); } + +void DiscordClient::DisconnectFromVoice() { + m_voice.Stop(); + VoiceStateUpdateMessage m; + m_websocket.Send(m); +} + +bool DiscordClient::IsConnectedToVoice() const noexcept { + return m_voice.IsConnected(); +} + +Snowflake DiscordClient::GetVoiceChannelID() const noexcept { + return m_voice_channel_id; +} #endif void DiscordClient::SetReferringChannel(Snowflake id) { diff --git a/src/discord/discord.hpp b/src/discord/discord.hpp index 907c3e6..0b88519 100644 --- a/src/discord/discord.hpp +++ b/src/discord/discord.hpp @@ -183,6 +183,9 @@ public: #ifdef WITH_VOICE void ConnectToVoice(Snowflake channel_id); + void DisconnectFromVoice(); + [[nodiscard]] bool IsConnectedToVoice() const noexcept; + [[nodiscard]] Snowflake GetVoiceChannelID() const noexcept; #endif void SetReferringChannel(Snowflake id); @@ -334,6 +337,8 @@ private: #ifdef WITH_VOICE DiscordVoiceClient m_voice; + + Snowflake m_voice_channel_id; #endif mutable std::mutex m_msg_mutex; diff --git a/src/discord/objects.cpp b/src/discord/objects.cpp index a6a7cc6..3cdc6b5 100644 --- a/src/discord/objects.cpp +++ b/src/discord/objects.cpp @@ -644,8 +644,14 @@ void from_json(const nlohmann::json &j, GuildMembersChunkData &m) { #ifdef WITH_VOICE void to_json(nlohmann::json &j, const VoiceStateUpdateMessage &m) { j["op"] = GatewayOp::VoiceStateUpdate; - j["d"]["guild_id"] = m.GuildID; - j["d"]["channel_id"] = m.ChannelID; + if (m.GuildID.has_value()) + j["d"]["guild_id"] = *m.GuildID; + else + j["d"]["guild_id"] = nullptr; + if (m.ChannelID.has_value()) + j["d"]["channel_id"] = *m.ChannelID; + else + j["d"]["channel_id"] = nullptr; j["d"]["self_mute"] = m.SelfMute; j["d"]["self_deaf"] = m.SelfDeaf; j["d"]["self_video"] = m.SelfVideo; diff --git a/src/discord/objects.hpp b/src/discord/objects.hpp index c9c8aff..0a947d4 100644 --- a/src/discord/objects.hpp +++ b/src/discord/objects.hpp @@ -869,8 +869,8 @@ struct GuildMembersChunkData { #ifdef WITH_VOICE struct VoiceStateUpdateMessage { - Snowflake GuildID; - Snowflake ChannelID; + std::optional GuildID; + std::optional ChannelID; bool SelfMute = false; bool SelfDeaf = false; bool SelfVideo = false; diff --git a/src/discord/voiceclient.cpp b/src/discord/voiceclient.cpp index b899638..6d45241 100644 --- a/src/discord/voiceclient.cpp +++ b/src/discord/voiceclient.cpp @@ -168,25 +168,34 @@ DiscordVoiceClient::DiscordVoiceClient() { m_udp_dispatch_mutex.unlock(); OnUDPData(data); }); + + Glib::signal_idle().connect_once([this]() { + // cant put in ctor or deadlock in singleton initialization + auto &aud = Abaddon::Get().GetAudio(); + aud.SetOpusBuffer(m_opus_buffer.data()); + aud.signal_opus_packet().connect([this](int payload_size) { + if (m_connected) + m_udp.SendEncrypted(m_opus_buffer.data(), payload_size); + }); + }); } DiscordVoiceClient::~DiscordVoiceClient() { - m_ws.Stop(); - m_udp.Stop(); - m_heartbeat_waiter.kill(); - if (m_heartbeat_thread.joinable()) m_heartbeat_thread.join(); + Stop(); } void DiscordVoiceClient::Start() { m_ws.StartConnection("wss://" + m_endpoint + "/?v=7"); +} - // cant put in ctor or deadlock in singleton initialization - auto &aud = Abaddon::Get().GetAudio(); - aud.SetOpusBuffer(m_opus_buffer.data()); - aud.signal_opus_packet().connect([this](int payload_size) { - if (m_connected) - m_udp.SendEncrypted(m_opus_buffer.data(), payload_size); - }); +void DiscordVoiceClient::Stop() { + if (m_connected) { + m_ws.Stop(); + m_udp.Stop(); + m_heartbeat_waiter.kill(); + if (m_heartbeat_thread.joinable()) m_heartbeat_thread.join(); + m_connected = false; + } } void DiscordVoiceClient::SetSessionID(std::string_view session_id) { @@ -209,6 +218,10 @@ void DiscordVoiceClient::SetUserID(Snowflake id) { m_user_id = id; } +bool DiscordVoiceClient::IsConnected() const noexcept { + return m_connected; +} + void DiscordVoiceClient::OnGatewayMessage(const std::string &str) { VoiceGatewayMessage msg = nlohmann::json::parse(str); puts(msg.Data.dump(4).c_str()); diff --git a/src/discord/voiceclient.hpp b/src/discord/voiceclient.hpp index 67919e5..f81763b 100644 --- a/src/discord/voiceclient.hpp +++ b/src/discord/voiceclient.hpp @@ -173,6 +173,7 @@ public: ~DiscordVoiceClient(); void Start(); + void Stop(); void SetSessionID(std::string_view session_id); void SetEndpoint(std::string_view endpoint); @@ -180,6 +181,8 @@ public: void SetServerID(Snowflake id); void SetUserID(Snowflake id); + [[nodiscard]] bool IsConnected() const noexcept; + private: void OnGatewayMessage(const std::string &str); void HandleGatewayHello(const VoiceGatewayMessage &m); @@ -198,6 +201,7 @@ private: std::string m_endpoint; std::string m_token; Snowflake m_server_id; + Snowflake m_channel_id; Snowflake m_user_id; std::string m_ip; -- cgit v1.2.3 From d57d822aa9f85ee023e2a50bd525d530b39a7186 Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Wed, 28 Sep 2022 20:44:52 -0400 Subject: manage decoders with ssrc updates --- src/abaddon.cpp | 19 +++++++++++++++ src/abaddon.hpp | 5 ++++ src/audio/manager.cpp | 45 +++++++++++++++++++++++----------- src/audio/manager.hpp | 4 +++ src/discord/discord.cpp | 59 ++++++++++++++++++++++++++++++++++++++++++++- src/discord/discord.hpp | 21 ++++++++++++++++ src/discord/objects.cpp | 25 +++++++++++++++---- src/discord/objects.hpp | 34 ++++++++++++++++++++------ src/discord/voiceclient.cpp | 33 +++++++++++++++++++++++-- src/discord/voiceclient.hpp | 38 +++++++++++++++++++++++------ 10 files changed, 246 insertions(+), 37 deletions(-) (limited to 'src/discord/discord.cpp') diff --git a/src/abaddon.cpp b/src/abaddon.cpp index bdedc7c..3245ad4 100644 --- a/src/abaddon.cpp +++ b/src/abaddon.cpp @@ -50,6 +50,16 @@ Abaddon::Abaddon() m_discord.signal_thread_update().connect(sigc::mem_fun(*this, &Abaddon::DiscordOnThreadUpdate)); m_discord.signal_message_sent().connect(sigc::mem_fun(*this, &Abaddon::DiscordOnMessageSent)); m_discord.signal_disconnected().connect(sigc::mem_fun(*this, &Abaddon::DiscordOnDisconnect)); + +#ifdef WITH_VOICE + m_discord.signal_voice_connected().connect(sigc::mem_fun(*this, &Abaddon::OnVoiceConnected)); + m_discord.signal_voice_disconnected().connect(sigc::mem_fun(*this, &Abaddon::OnVoiceDisconnected)); + m_discord.signal_voice_speaking().connect([this](const VoiceSpeakingData &m) { + printf("%llu has ssrc %u\n", (uint64_t)m.UserID, m.SSRC); + m_audio->AddSSRC(m.SSRC); + }); +#endif + m_discord.signal_channel_accessibility_changed().connect([this](Snowflake id, bool accessible) { if (!accessible) m_channels_requested.erase(id); @@ -406,6 +416,15 @@ void Abaddon::DiscordOnThreadUpdate(const ThreadUpdateData &data) { } } +#ifdef WITH_VOICE +void Abaddon::OnVoiceConnected() { +} + +void Abaddon::OnVoiceDisconnected() { + m_audio->RemoveAllSSRCs(); +} +#endif + SettingsManager::Settings &Abaddon::GetSettings() { return m_settings.GetSettings(); } diff --git a/src/abaddon.hpp b/src/abaddon.hpp index ca92370..a15670b 100644 --- a/src/abaddon.hpp +++ b/src/abaddon.hpp @@ -89,6 +89,11 @@ public: void DiscordOnDisconnect(bool is_reconnecting, GatewayCloseCode close_code); void DiscordOnThreadUpdate(const ThreadUpdateData &data); +#ifdef WITH_VOICE + void OnVoiceConnected(); + void OnVoiceDisconnected(); +#endif + SettingsManager::Settings &GetSettings(); Glib::RefPtr GetStyleProvider(); diff --git a/src/audio/manager.cpp b/src/audio/manager.cpp index b665c81..797d899 100644 --- a/src/audio/manager.cpp +++ b/src/audio/manager.cpp @@ -107,8 +107,28 @@ AudioManager::AudioManager() { AudioManager::~AudioManager() { ma_device_uninit(&m_device); ma_device_uninit(&m_capture_device); - for (auto &[ssrc, pair] : m_sources) { - opus_decoder_destroy(pair.second); + RemoveAllSSRCs(); +} + +void AudioManager::AddSSRC(uint32_t ssrc) { + int error; + auto *decoder = opus_decoder_create(48000, 2, &error); + m_sources.insert(std::make_pair(ssrc, std::make_pair(std::deque {}, decoder))); +} + +void AudioManager::RemoveSSRC(uint32_t ssrc) { + if (auto it = m_sources.find(ssrc); it != m_sources.end()) { + opus_decoder_destroy(it->second.second); + m_sources.erase(it); + } +} + +void AudioManager::RemoveAllSSRCs() { + puts("remove all ssrc"); + for (auto it = m_sources.begin(); it != m_sources.end();) { + opus_decoder_destroy(it->second.second); + m_sources.erase(it); + it++; } } @@ -120,18 +140,15 @@ void AudioManager::FeedMeOpus(uint32_t ssrc, const std::vector &data) { size_t payload_size = 0; const auto *opus_encoded = StripRTPExtensionHeader(data.data(), static_cast(data.size()), payload_size); static std::array pcm; - if (m_sources.find(ssrc) == m_sources.end()) { - int err; - auto *decoder = opus_decoder_create(48000, 2, &err); - m_sources.insert(std::make_pair(ssrc, std::make_pair(std::deque {}, decoder))); - } - int decoded = opus_decode(m_sources.at(ssrc).second, opus_encoded, static_cast(payload_size), pcm.data(), 120 * 48, 0); - if (decoded <= 0) { - } else { - m_mutex.lock(); - auto &buf = m_sources.at(ssrc).first; - buf.insert(buf.end(), pcm.begin(), pcm.begin() + decoded * 2); - m_mutex.unlock(); + if (auto it = m_sources.find(ssrc); it != m_sources.end()) { + int decoded = opus_decode(it->second.second, opus_encoded, static_cast(payload_size), pcm.data(), 120 * 48, 0); + if (decoded <= 0) { + } else { + m_mutex.lock(); + auto &buf = it->second.first; + buf.insert(buf.end(), pcm.begin(), pcm.begin() + decoded * 2); + m_mutex.unlock(); + } } } diff --git a/src/audio/manager.hpp b/src/audio/manager.hpp index 700fcc0..3ba6e29 100644 --- a/src/audio/manager.hpp +++ b/src/audio/manager.hpp @@ -18,6 +18,10 @@ public: AudioManager(); ~AudioManager(); + void AddSSRC(uint32_t ssrc); + void RemoveSSRC(uint32_t ssrc); + void RemoveAllSSRCs(); + void SetOpusBuffer(uint8_t *ptr); void FeedMeOpus(uint32_t ssrc, const std::vector &data); diff --git a/src/discord/discord.cpp b/src/discord/discord.cpp index 2fff2a1..4202f85 100644 --- a/src/discord/discord.cpp +++ b/src/discord/discord.cpp @@ -23,6 +23,14 @@ DiscordClient::DiscordClient(bool mem_store) m_websocket.signal_open().connect(sigc::mem_fun(*this, &DiscordClient::HandleSocketOpen)); m_websocket.signal_close().connect(sigc::mem_fun(*this, &DiscordClient::HandleSocketClose)); +#ifdef WITH_VOICE + m_voice.signal_connected().connect(sigc::mem_fun(*this, &DiscordClient::OnVoiceConnected)); + m_voice.signal_disconnected().connect(sigc::mem_fun(*this, &DiscordClient::OnVoiceDisconnected)); + m_voice.signal_speaking().connect([this](const VoiceSpeakingData &data) { + m_signal_voice_speaking.emit(data); + }); +#endif + LoadEventMap(); } @@ -2135,11 +2143,16 @@ void DiscordClient::HandleGatewayGuildMembersChunk(const GatewayMessage &msg) { #ifdef WITH_VOICE void DiscordClient::HandleGatewayVoiceStateUpdate(const GatewayMessage &msg) { - VoiceStateUpdateData data = msg.Data; + VoiceState data = msg.Data; if (data.UserID == m_user_data.ID) { printf("voice session id: %s\n", data.SessionID.c_str()); m_voice.SetSessionID(data.SessionID); } + if (data.ChannelID.has_value()) { + SetVoiceState(data.UserID, *data.ChannelID); + } else { + ClearVoiceState(data.UserID); + } } void DiscordClient::HandleGatewayVoiceServerUpdate(const GatewayMessage &msg) { @@ -2170,6 +2183,15 @@ void DiscordClient::HandleGatewayReadySupplemental(const GatewayMessage &msg) { m_user_to_status[p.UserID] = PresenceStatus::DND; m_signal_presence_update.emit(*user, m_user_to_status.at(p.UserID)); } +#ifdef WITH_VOICE + for (const auto &g : data.Guilds) { + for (const auto &s : g.VoiceStates) { + if (s.ChannelID.has_value()) { + SetVoiceState(s.UserID, *s.ChannelID); + } + } + } +#endif } void DiscordClient::HandleGatewayReconnect(const GatewayMessage &msg) { @@ -2602,6 +2624,29 @@ void DiscordClient::HandleReadyGuildSettings(const ReadyEventData &data) { } } +#ifdef WITH_VOICE +void DiscordClient::SetVoiceState(Snowflake user_id, Snowflake channel_id) { + m_voice_state_user_channel[user_id] = channel_id; + m_voice_state_channel_users[channel_id].insert(user_id); +} + +void DiscordClient::ClearVoiceState(Snowflake user_id) { + if (const auto it = m_voice_state_user_channel.find(user_id); it != m_voice_state_user_channel.end()) { + m_voice_state_channel_users[it->second].erase(user_id); + // invalidated + m_voice_state_user_channel.erase(user_id); + } +} + +void DiscordClient::OnVoiceConnected() { + m_signal_voice_connected.emit(); +} + +void DiscordClient::OnVoiceDisconnected() { + m_signal_voice_disconnected.emit(); +} +#endif + void DiscordClient::LoadEventMap() { m_event_map["READY"] = GatewayEvent::READY; m_event_map["MESSAGE_CREATE"] = GatewayEvent::MESSAGE_CREATE; @@ -2858,3 +2903,15 @@ DiscordClient::type_signal_channel_accessibility_changed DiscordClient::signal_c DiscordClient::type_signal_message_send_fail DiscordClient::signal_message_send_fail() { return m_signal_message_send_fail; } + +DiscordClient::type_signal_voice_connected DiscordClient::signal_voice_connected() { + return m_signal_voice_connected; +} + +DiscordClient::type_signal_voice_disconnected DiscordClient::signal_voice_disconnected() { + return m_signal_voice_disconnected; +} + +DiscordClient::type_signal_voice_speaking DiscordClient::signal_voice_speaking() { + return m_signal_voice_speaking; +} diff --git a/src/discord/discord.hpp b/src/discord/discord.hpp index 0b88519..fb0b46d 100644 --- a/src/discord/discord.hpp +++ b/src/discord/discord.hpp @@ -339,6 +339,15 @@ private: DiscordVoiceClient m_voice; Snowflake m_voice_channel_id; + // todo sql i guess + std::unordered_map m_voice_state_user_channel; + std::unordered_map> m_voice_state_channel_users; + + void SetVoiceState(Snowflake user_id, Snowflake channel_id); + void ClearVoiceState(Snowflake user_id); + + void OnVoiceConnected(); + void OnVoiceDisconnected(); #endif mutable std::mutex m_msg_mutex; @@ -413,6 +422,10 @@ public: typedef sigc::signal type_signal_connected; typedef sigc::signal type_signal_message_progress; + using type_signal_voice_connected = sigc::signal; + using type_signal_voice_disconnected = sigc::signal; + using type_signal_voice_speaking = sigc::signal; + type_signal_gateway_ready signal_gateway_ready(); type_signal_message_create signal_message_create(); type_signal_message_delete signal_message_delete(); @@ -467,6 +480,10 @@ public: type_signal_connected signal_connected(); type_signal_message_progress signal_message_progress(); + type_signal_voice_connected signal_voice_connected(); + type_signal_voice_disconnected signal_voice_disconnected(); + type_signal_voice_speaking signal_voice_speaking(); + protected: type_signal_gateway_ready m_signal_gateway_ready; type_signal_message_create m_signal_message_create; @@ -521,4 +538,8 @@ protected: type_signal_disconnected m_signal_disconnected; type_signal_connected m_signal_connected; type_signal_message_progress m_signal_message_progress; + + type_signal_voice_connected m_signal_voice_connected; + type_signal_voice_disconnected m_signal_voice_disconnected; + type_signal_voice_speaking m_signal_voice_speaking; }; diff --git a/src/discord/objects.cpp b/src/discord/objects.cpp index 3cdc6b5..21853a7 100644 --- a/src/discord/objects.cpp +++ b/src/discord/objects.cpp @@ -233,6 +233,11 @@ void from_json(const nlohmann::json &j, SupplementalMergedPresencesData &m) { JS_D("friends", m.Friends); } +void from_json(const nlohmann::json &j, SupplementalGuildEntry &m) { + JS_D("id", m.ID); + JS_D("voice_states", m.VoiceStates); +} + void from_json(const nlohmann::json &j, ReadySupplementalData &m) { JS_D("merged_presences", m.MergedPresences); } @@ -658,14 +663,24 @@ void to_json(nlohmann::json &j, const VoiceStateUpdateMessage &m) { // j["d"]["preferred_region"] = m.PreferredRegion; } -void from_json(const nlohmann::json &j, VoiceStateUpdateData &m) { - JS_ON("user_id", m.UserID); - JS_ON("session_id", m.SessionID); -} - void from_json(const nlohmann::json &j, VoiceServerUpdateData &m) { JS_D("token", m.Token); JS_D("guild_id", m.GuildID); JS_D("endpoint", m.Endpoint); } #endif + +void from_json(const nlohmann::json &j, VoiceState &m) { + JS_O("guild_id", m.GuildID); + JS_N("channel_id", m.ChannelID); + JS_D("deaf", m.IsDeafened); + JS_D("mute", m.IsMuted); + JS_D("self_deaf", m.IsSelfDeafened); + JS_D("self_mute", m.IsSelfMuted); + JS_D("self_video", m.IsSelfVideo); + JS_O("self_stream", m.IsSelfStream); + JS_D("suppress", m.IsSuppressed); + JS_D("user_id", m.UserID); + JS_N("member", m.Member); + JS_D("session_id", m.SessionID); +} diff --git a/src/discord/objects.hpp b/src/discord/objects.hpp index 0a947d4..14dd20c 100644 --- a/src/discord/objects.hpp +++ b/src/discord/objects.hpp @@ -354,8 +354,18 @@ struct SupplementalMergedPresencesData { friend void from_json(const nlohmann::json &j, SupplementalMergedPresencesData &m); }; +struct VoiceState; +struct SupplementalGuildEntry { + // std::vector EmbeddedActivities; + Snowflake ID; + std::vector VoiceStates; + + friend void from_json(const nlohmann::json &j, SupplementalGuildEntry &m); +}; + struct ReadySupplementalData { SupplementalMergedPresencesData MergedPresences; + std::vector Guilds; friend void from_json(const nlohmann::json &j, ReadySupplementalData &m); }; @@ -879,13 +889,6 @@ struct VoiceStateUpdateMessage { friend void to_json(nlohmann::json &j, const VoiceStateUpdateMessage &m); }; -struct VoiceStateUpdateData { - Snowflake UserID; - std::string SessionID; - - friend void from_json(const nlohmann::json &j, VoiceStateUpdateData &m); -}; - struct VoiceServerUpdateData { std::string Token; Snowflake GuildID; @@ -894,3 +897,20 @@ struct VoiceServerUpdateData { friend void from_json(const nlohmann::json &j, VoiceServerUpdateData &m); }; #endif + +struct VoiceState { + std::optional ChannelID; + bool IsDeafened; + bool IsMuted; + std::optional GuildID; + std::optional Member; + bool IsSelfDeafened; + bool IsSelfMuted; + bool IsSelfVideo; + bool IsSelfStream = false; + std::string SessionID; + bool IsSuppressed; + Snowflake UserID; + + friend void from_json(const nlohmann::json &j, VoiceState &m); +}; diff --git a/src/discord/voiceclient.cpp b/src/discord/voiceclient.cpp index 6d45241..b7fe21b 100644 --- a/src/discord/voiceclient.cpp +++ b/src/discord/voiceclient.cpp @@ -1,5 +1,5 @@ #ifdef WITH_VOICE - // clang-format off +// clang-format off #include "voiceclient.hpp" #include "json.hpp" #include @@ -186,6 +186,7 @@ DiscordVoiceClient::~DiscordVoiceClient() { void DiscordVoiceClient::Start() { m_ws.StartConnection("wss://" + m_endpoint + "/?v=7"); + m_heartbeat_waiter.revive(); } void DiscordVoiceClient::Stop() { @@ -195,6 +196,7 @@ void DiscordVoiceClient::Stop() { m_heartbeat_waiter.kill(); if (m_heartbeat_thread.joinable()) m_heartbeat_thread.join(); m_connected = false; + m_signal_disconnected.emit(); } } @@ -235,6 +237,9 @@ void DiscordVoiceClient::OnGatewayMessage(const std::string &str) { case VoiceGatewayOp::SessionDescription: { HandleGatewaySessionDescription(msg); } break; + case VoiceGatewayOp::Speaking: { + HandleGatewaySpeaking(msg); + } break; default: break; } } @@ -273,7 +278,7 @@ void DiscordVoiceClient::HandleGatewaySessionDescription(const VoiceGatewayMessa VoiceSpeakingMessage msg; msg.Delay = 0; msg.SSRC = m_ssrc; - msg.Speaking = VoiceSpeakingMessage::Microphone; + msg.Speaking = VoiceSpeakingType::Microphone; m_ws.Send(msg); m_secret_key = d.SecretKey; @@ -286,6 +291,12 @@ void DiscordVoiceClient::HandleGatewaySessionDescription(const VoiceGatewayMessa m_udp.SendEncrypted({ 0xF8, 0xFF, 0xFE }); m_udp.Run(); m_connected = true; + m_signal_connected.emit(); +} + +void DiscordVoiceClient::HandleGatewaySpeaking(const VoiceGatewayMessage &m) { + VoiceSpeakingData data = m.Data; + m_signal_speaking.emit(data); } void DiscordVoiceClient::Identify() { @@ -365,6 +376,18 @@ void DiscordVoiceClient::HeartbeatThread() { } } +DiscordVoiceClient::type_signal_disconnected DiscordVoiceClient::signal_connected() { + return m_signal_connected; +} + +DiscordVoiceClient::type_signal_disconnected DiscordVoiceClient::signal_disconnected() { + return m_signal_disconnected; +} + +DiscordVoiceClient::type_signal_speaking DiscordVoiceClient::signal_speaking() { + return m_signal_speaking; +} + void from_json(const nlohmann::json &j, VoiceGatewayMessage &m) { JS_D("op", m.Opcode); m.Data = j.at("d"); @@ -431,4 +454,10 @@ void to_json(nlohmann::json &j, const VoiceSpeakingMessage &m) { j["d"]["delay"] = m.Delay; j["d"]["ssrc"] = m.SSRC; } + +void from_json(const nlohmann::json &j, VoiceSpeakingData &m) { + JS_D("user_id", m.UserID); + JS_D("ssrc", m.SSRC); + JS_D("speaking", m.Speaking); +} #endif diff --git a/src/discord/voiceclient.hpp b/src/discord/voiceclient.hpp index f81763b..c052f28 100644 --- a/src/discord/voiceclient.hpp +++ b/src/discord/voiceclient.hpp @@ -1,6 +1,6 @@ #pragma once #ifdef WITH_VOICE -// clang-format off + // clang-format off #include "snowflake.hpp" #include "waiter.hpp" #include "websocket.hpp" @@ -8,6 +8,7 @@ #include #include #include +#include // clang-format on enum class VoiceGatewayCloseCode : uint16_t { @@ -110,20 +111,28 @@ struct VoiceSessionDescriptionData { friend void from_json(const nlohmann::json &j, VoiceSessionDescriptionData &m); }; -struct VoiceSpeakingMessage { - enum { - Microphone = 1 << 0, - Soundshare = 1 << 1, - Priority = 1 << 2, - }; +enum class VoiceSpeakingType { + Microphone = 1 << 0, + Soundshare = 1 << 1, + Priority = 1 << 2, +}; - int Speaking; +struct VoiceSpeakingMessage { + VoiceSpeakingType Speaking; int Delay; uint32_t SSRC; friend void to_json(nlohmann::json &j, const VoiceSpeakingMessage &m); }; +struct VoiceSpeakingData { + Snowflake UserID; + uint32_t SSRC; + VoiceSpeakingType Speaking; + + friend void from_json(const nlohmann::json &j, VoiceSpeakingData &m); +}; + class UDPSocket { public: UDPSocket(); @@ -188,6 +197,7 @@ private: void HandleGatewayHello(const VoiceGatewayMessage &m); void HandleGatewayReady(const VoiceGatewayMessage &m); void HandleGatewaySessionDescription(const VoiceGatewayMessage &m); + void HandleGatewaySpeaking(const VoiceGatewayMessage &m); void Identify(); void Discovery(); @@ -228,5 +238,17 @@ private: std::array m_opus_buffer; std::atomic m_connected = false; + + using type_signal_connected = sigc::signal; + using type_signal_disconnected = sigc::signal; + using type_signal_speaking = sigc::signal; + type_signal_connected m_signal_connected; + type_signal_disconnected m_signal_disconnected; + type_signal_speaking m_signal_speaking; + +public: + type_signal_connected signal_connected(); + type_signal_disconnected signal_disconnected(); + type_signal_speaking signal_speaking(); }; #endif -- cgit v1.2.3 From a96d96b3aa883c5ee5892e4ff94e3c539989c66a Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Wed, 28 Sep 2022 22:10:36 -0400 Subject: basic mute/deafen --- src/abaddon.cpp | 20 ++++++++++++++++++++ src/audio/manager.cpp | 12 +++++++++++- src/audio/manager.hpp | 7 +++++++ src/discord/discord.cpp | 27 +++++++++++++++++++++++++++ src/discord/discord.hpp | 14 ++++++++++++++ src/windows/voicewindow.cpp | 36 ++++++++++++++++++++++++++++++++++++ src/windows/voicewindow.hpp | 30 ++++++++++++++++++++++++++++++ 7 files changed, 145 insertions(+), 1 deletion(-) create mode 100644 src/windows/voicewindow.cpp create mode 100644 src/windows/voicewindow.hpp (limited to 'src/discord/discord.cpp') diff --git a/src/abaddon.cpp b/src/abaddon.cpp index 3245ad4..678353c 100644 --- a/src/abaddon.cpp +++ b/src/abaddon.cpp @@ -18,6 +18,7 @@ #include "windows/profilewindow.hpp" #include "windows/pinnedwindow.hpp" #include "windows/threadswindow.hpp" +#include "windows/voicewindow.hpp" #include "startup.hpp" #ifdef WITH_LIBHANDY @@ -418,6 +419,25 @@ void Abaddon::DiscordOnThreadUpdate(const ThreadUpdateData &data) { #ifdef WITH_VOICE void Abaddon::OnVoiceConnected() { + auto *wnd = new VoiceWindow; + + wnd->signal_mute().connect([this](bool is_mute) { + m_discord.SetVoiceMuted(is_mute); + m_audio->SetCapture(!is_mute); + }); + + wnd->signal_deafen().connect([this](bool is_deaf) { + m_discord.SetVoiceDeafened(is_deaf); + m_audio->SetPlayback(!is_deaf); + }); + + wnd->show(); + wnd->signal_hide().connect([this, wnd]() { + m_discord.DisconnectFromVoice(); + delete wnd; + delete m_user_menu; + SetupUserMenu(); + }); } void Abaddon::OnVoiceDisconnected() { diff --git a/src/audio/manager.cpp b/src/audio/manager.cpp index 797d899..35a9c76 100644 --- a/src/audio/manager.cpp +++ b/src/audio/manager.cpp @@ -137,6 +137,8 @@ void AudioManager::SetOpusBuffer(uint8_t *ptr) { } void AudioManager::FeedMeOpus(uint32_t ssrc, const std::vector &data) { + if (!m_should_playback) return; + size_t payload_size = 0; const auto *opus_encoded = StripRTPExtensionHeader(data.data(), static_cast(data.size()), payload_size); static std::array pcm; @@ -152,8 +154,16 @@ void AudioManager::FeedMeOpus(uint32_t ssrc, const std::vector &data) { } } +void AudioManager::SetCapture(bool capture) { + m_should_capture = capture; +} + +void AudioManager::SetPlayback(bool playback) { + m_should_playback = playback; +} + void AudioManager::OnCapturedPCM(const int16_t *pcm, ma_uint32 frames) { - if (m_opus_buffer == nullptr) return; + if (m_opus_buffer == nullptr || !m_should_capture) return; int payload_len = opus_encode(m_encoder, pcm, 480, static_cast(m_opus_buffer), 1275); if (payload_len < 0) { diff --git a/src/audio/manager.hpp b/src/audio/manager.hpp index 3ba6e29..4986da6 100644 --- a/src/audio/manager.hpp +++ b/src/audio/manager.hpp @@ -1,6 +1,7 @@ #pragma once #ifdef WITH_VOICE // clang-format off + #include #include #include @@ -25,6 +26,9 @@ public: void SetOpusBuffer(uint8_t *ptr); void FeedMeOpus(uint32_t ssrc, const std::vector &data); + void SetCapture(bool capture); + void SetPlayback(bool playback); + [[nodiscard]] bool OK() const; private: @@ -51,6 +55,9 @@ private: uint8_t *m_opus_buffer = nullptr; + std::atomic m_should_capture = true; + std::atomic m_should_playback = true; + public: using type_signal_opus_packet = sigc::signal; type_signal_opus_packet signal_opus_packet(); diff --git a/src/discord/discord.cpp b/src/discord/discord.cpp index 4202f85..f5a1e1b 100644 --- a/src/discord/discord.cpp +++ b/src/discord/discord.cpp @@ -1202,6 +1202,16 @@ bool DiscordClient::IsConnectedToVoice() const noexcept { Snowflake DiscordClient::GetVoiceChannelID() const noexcept { return m_voice_channel_id; } + +void DiscordClient::SetVoiceMuted(bool is_mute) { + m_mute_requested = is_mute; + SendVoiceStateUpdate(); +} + +void DiscordClient::SetVoiceDeafened(bool is_deaf) { + m_deaf_requested = is_deaf; + SendVoiceStateUpdate(); +} #endif void DiscordClient::SetReferringChannel(Snowflake id) { @@ -2625,6 +2635,21 @@ void DiscordClient::HandleReadyGuildSettings(const ReadyEventData &data) { } #ifdef WITH_VOICE +void DiscordClient::SendVoiceStateUpdate() { + VoiceStateUpdateMessage msg; + msg.ChannelID = m_voice_channel_id; + const auto channel = GetChannel(m_voice_channel_id); + if (channel.has_value() && channel->GuildID.has_value()) { + msg.GuildID = *channel->GuildID; + } + + msg.SelfMute = m_mute_requested; + msg.SelfDeaf = m_deaf_requested; + msg.SelfVideo = false; + + m_websocket.Send(msg); +} + void DiscordClient::SetVoiceState(Snowflake user_id, Snowflake channel_id) { m_voice_state_user_channel[user_id] = channel_id; m_voice_state_channel_users[channel_id].insert(user_id); @@ -2904,6 +2929,7 @@ DiscordClient::type_signal_message_send_fail DiscordClient::signal_message_send_ return m_signal_message_send_fail; } +#ifdef WITH_VOICE DiscordClient::type_signal_voice_connected DiscordClient::signal_voice_connected() { return m_signal_voice_connected; } @@ -2915,3 +2941,4 @@ DiscordClient::type_signal_voice_disconnected DiscordClient::signal_voice_discon DiscordClient::type_signal_voice_speaking DiscordClient::signal_voice_speaking() { return m_signal_voice_speaking; } +#endif diff --git a/src/discord/discord.hpp b/src/discord/discord.hpp index fb0b46d..b0fecab 100644 --- a/src/discord/discord.hpp +++ b/src/discord/discord.hpp @@ -186,6 +186,9 @@ public: void DisconnectFromVoice(); [[nodiscard]] bool IsConnectedToVoice() const noexcept; [[nodiscard]] Snowflake GetVoiceChannelID() const noexcept; + + void SetVoiceMuted(bool is_mute); + void SetVoiceDeafened(bool is_deaf); #endif void SetReferringChannel(Snowflake id); @@ -338,11 +341,16 @@ private: #ifdef WITH_VOICE DiscordVoiceClient m_voice; + bool m_mute_requested = false; + bool m_deaf_requested = false; + Snowflake m_voice_channel_id; // todo sql i guess std::unordered_map m_voice_state_user_channel; std::unordered_map> m_voice_state_channel_users; + void SendVoiceStateUpdate(); + void SetVoiceState(Snowflake user_id, Snowflake channel_id); void ClearVoiceState(Snowflake user_id); @@ -422,9 +430,11 @@ public: typedef sigc::signal type_signal_connected; typedef sigc::signal type_signal_message_progress; +#ifdef WITH_VOICE using type_signal_voice_connected = sigc::signal; using type_signal_voice_disconnected = sigc::signal; using type_signal_voice_speaking = sigc::signal; +#endif type_signal_gateway_ready signal_gateway_ready(); type_signal_message_create signal_message_create(); @@ -480,9 +490,11 @@ public: type_signal_connected signal_connected(); type_signal_message_progress signal_message_progress(); +#ifdef WITH_VOICE type_signal_voice_connected signal_voice_connected(); type_signal_voice_disconnected signal_voice_disconnected(); type_signal_voice_speaking signal_voice_speaking(); +#endif protected: type_signal_gateway_ready m_signal_gateway_ready; @@ -539,7 +551,9 @@ protected: type_signal_connected m_signal_connected; type_signal_message_progress m_signal_message_progress; +#ifdef WITH_VOICE type_signal_voice_connected m_signal_voice_connected; type_signal_voice_disconnected m_signal_voice_disconnected; type_signal_voice_speaking m_signal_voice_speaking; +#endif }; diff --git a/src/windows/voicewindow.cpp b/src/windows/voicewindow.cpp new file mode 100644 index 0000000..b352c86 --- /dev/null +++ b/src/windows/voicewindow.cpp @@ -0,0 +1,36 @@ +#include "voicewindow.hpp" + +VoiceWindow::VoiceWindow() + : m_main(Gtk::ORIENTATION_VERTICAL) + , m_controls(Gtk::ORIENTATION_HORIZONTAL) + , m_mute("Mute") + , m_deafen("Deafen") { + get_style_context()->add_class("app-window"); + + set_default_size(300, 300); + + m_mute.signal_toggled().connect(sigc::mem_fun(*this, &VoiceWindow::OnMuteChanged)); + m_deafen.signal_toggled().connect(sigc::mem_fun(*this, &VoiceWindow::OnDeafenChanged)); + + m_controls.add(m_mute); + m_controls.add(m_deafen); + m_main.add(m_controls); + add(m_main); + show_all_children(); +} + +void VoiceWindow::OnMuteChanged() { + m_signal_mute.emit(m_mute.get_active()); +} + +void VoiceWindow::OnDeafenChanged() { + m_signal_deafen.emit(m_deafen.get_active()); +} + +VoiceWindow::type_signal_mute VoiceWindow::signal_mute() { + return m_signal_mute; +} + +VoiceWindow::type_signal_deafen VoiceWindow::signal_deafen() { + return m_signal_deafen; +} diff --git a/src/windows/voicewindow.hpp b/src/windows/voicewindow.hpp new file mode 100644 index 0000000..bee0a84 --- /dev/null +++ b/src/windows/voicewindow.hpp @@ -0,0 +1,30 @@ +#pragma once +#include +#include +#include + +class VoiceWindow : public Gtk::Window { +public: + VoiceWindow(); + +private: + void OnMuteChanged(); + void OnDeafenChanged(); + + Gtk::Box m_main; + Gtk::Box m_controls; + + Gtk::CheckButton m_mute; + Gtk::CheckButton m_deafen; + +public: + using type_signal_mute = sigc::signal; + using type_signal_deafen = sigc::signal; + + type_signal_mute signal_mute(); + type_signal_deafen signal_deafen(); + +private: + type_signal_mute m_signal_mute; + type_signal_deafen m_signal_deafen; +}; -- cgit v1.2.3 From dc127d15fb49c108450e691c44ed930a11bb7e59 Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Thu, 29 Sep 2022 21:46:15 -0400 Subject: display user list, client side mute --- res/css/application-low-priority.css | 2 +- src/abaddon.cpp | 8 ++++- src/audio/manager.cpp | 16 ++++++++- src/audio/manager.hpp | 7 ++++ src/discord/discord.cpp | 8 +++++ src/discord/discord.hpp | 2 ++ src/discord/objects.cpp | 3 +- src/discord/voiceclient.cpp | 9 +++++ src/discord/voiceclient.hpp | 9 ++++- src/windows/voicewindow.cpp | 70 ++++++++++++++++++++++++++++++++++-- src/windows/voicewindow.hpp | 14 +++++++- 11 files changed, 140 insertions(+), 8 deletions(-) (limited to 'src/discord/discord.cpp') diff --git a/res/css/application-low-priority.css b/res/css/application-low-priority.css index cf108f4..5078d22 100644 --- a/res/css/application-low-priority.css +++ b/res/css/application-low-priority.css @@ -44,7 +44,7 @@ has to be separate to allow main.css to override certain things background: @secondary_color; } -.app-popup list { +.app-window list, .app-popup list { background: @secondary_color; } diff --git a/src/abaddon.cpp b/src/abaddon.cpp index 678353c..4c879ca 100644 --- a/src/abaddon.cpp +++ b/src/abaddon.cpp @@ -419,7 +419,7 @@ void Abaddon::DiscordOnThreadUpdate(const ThreadUpdateData &data) { #ifdef WITH_VOICE void Abaddon::OnVoiceConnected() { - auto *wnd = new VoiceWindow; + auto *wnd = new VoiceWindow(m_discord.GetVoiceChannelID()); wnd->signal_mute().connect([this](bool is_mute) { m_discord.SetVoiceMuted(is_mute); @@ -431,6 +431,12 @@ void Abaddon::OnVoiceConnected() { m_audio->SetPlayback(!is_deaf); }); + wnd->signal_mute_user_cs().connect([this](Snowflake id, bool is_mute) { + if (const auto ssrc = m_discord.GetSSRCOfUser(id); ssrc.has_value()) { + m_audio->SetMuteSSRC(*ssrc, is_mute); + } + }); + wnd->show(); wnd->signal_hide().connect([this, wnd]() { m_discord.DisconnectFromVoice(); diff --git a/src/audio/manager.cpp b/src/audio/manager.cpp index 35a9c76..e025390 100644 --- a/src/audio/manager.cpp +++ b/src/audio/manager.cpp @@ -1,5 +1,6 @@ #ifdef WITH_VOICE - // clang-format off +// clang-format off + #ifdef _WIN32 #include #endif @@ -138,6 +139,10 @@ void AudioManager::SetOpusBuffer(uint8_t *ptr) { void AudioManager::FeedMeOpus(uint32_t ssrc, const std::vector &data) { if (!m_should_playback) return; + { + std::lock_guard _(m_muted_ssrc_mutex); + if (m_muted_ssrcs.find(ssrc) != m_muted_ssrcs.end()) return; + } size_t payload_size = 0; const auto *opus_encoded = StripRTPExtensionHeader(data.data(), static_cast(data.size()), payload_size); @@ -162,6 +167,15 @@ void AudioManager::SetPlayback(bool playback) { m_should_playback = playback; } +void AudioManager::SetMuteSSRC(uint32_t ssrc, bool mute) { + std::lock_guard _(m_muted_ssrc_mutex); + if (mute) { + m_muted_ssrcs.insert(ssrc); + } else { + m_muted_ssrcs.erase(ssrc); + } +} + void AudioManager::OnCapturedPCM(const int16_t *pcm, ma_uint32 frames) { if (m_opus_buffer == nullptr || !m_should_capture) return; diff --git a/src/audio/manager.hpp b/src/audio/manager.hpp index 4986da6..277f2f3 100644 --- a/src/audio/manager.hpp +++ b/src/audio/manager.hpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -29,6 +30,8 @@ public: void SetCapture(bool capture); void SetPlayback(bool playback); + void SetMuteSSRC(uint32_t ssrc, bool mute); + [[nodiscard]] bool OK() const; private: @@ -58,6 +61,10 @@ private: std::atomic m_should_capture = true; std::atomic m_should_playback = true; + std::unordered_set m_muted_ssrcs; + + mutable std::mutex m_muted_ssrc_mutex; + public: using type_signal_opus_packet = sigc::signal; type_signal_opus_packet signal_opus_packet(); diff --git a/src/discord/discord.cpp b/src/discord/discord.cpp index f5a1e1b..b6bcc07 100644 --- a/src/discord/discord.cpp +++ b/src/discord/discord.cpp @@ -1203,6 +1203,14 @@ Snowflake DiscordClient::GetVoiceChannelID() const noexcept { return m_voice_channel_id; } +std::unordered_set DiscordClient::GetUsersInVoiceChannel(Snowflake channel_id) { + return m_voice_state_channel_users[channel_id]; +} + +std::optional DiscordClient::GetSSRCOfUser(Snowflake id) const { + return m_voice.GetSSRCOfUser(id); +} + void DiscordClient::SetVoiceMuted(bool is_mute) { m_mute_requested = is_mute; SendVoiceStateUpdate(); diff --git a/src/discord/discord.hpp b/src/discord/discord.hpp index b0fecab..9c6c748 100644 --- a/src/discord/discord.hpp +++ b/src/discord/discord.hpp @@ -186,6 +186,8 @@ public: void DisconnectFromVoice(); [[nodiscard]] bool IsConnectedToVoice() const noexcept; [[nodiscard]] Snowflake GetVoiceChannelID() const noexcept; + [[nodiscard]] std::unordered_set GetUsersInVoiceChannel(Snowflake channel_id); + [[nodiscard]] std::optional GetSSRCOfUser(Snowflake id) const; void SetVoiceMuted(bool is_mute); void SetVoiceDeafened(bool is_deaf); diff --git a/src/discord/objects.cpp b/src/discord/objects.cpp index 21853a7..3e87e43 100644 --- a/src/discord/objects.cpp +++ b/src/discord/objects.cpp @@ -240,6 +240,7 @@ void from_json(const nlohmann::json &j, SupplementalGuildEntry &m) { void from_json(const nlohmann::json &j, ReadySupplementalData &m) { JS_D("merged_presences", m.MergedPresences); + JS_D("guilds", m.Guilds); } void to_json(nlohmann::json &j, const IdentifyProperties &m) { @@ -681,6 +682,6 @@ void from_json(const nlohmann::json &j, VoiceState &m) { JS_O("self_stream", m.IsSelfStream); JS_D("suppress", m.IsSuppressed); JS_D("user_id", m.UserID); - JS_N("member", m.Member); + JS_ON("member", m.Member); JS_D("session_id", m.SessionID); } diff --git a/src/discord/voiceclient.cpp b/src/discord/voiceclient.cpp index b7fe21b..5f6ab31 100644 --- a/src/discord/voiceclient.cpp +++ b/src/discord/voiceclient.cpp @@ -1,5 +1,6 @@ #ifdef WITH_VOICE // clang-format off + #include "voiceclient.hpp" #include "json.hpp" #include @@ -220,6 +221,13 @@ void DiscordVoiceClient::SetUserID(Snowflake id) { m_user_id = id; } +std::optional DiscordVoiceClient::GetSSRCOfUser(Snowflake id) const { + if (const auto it = m_ssrc_map.find(id); it != m_ssrc_map.end()) { + return it->second; + } + return {}; +} + bool DiscordVoiceClient::IsConnected() const noexcept { return m_connected; } @@ -296,6 +304,7 @@ void DiscordVoiceClient::HandleGatewaySessionDescription(const VoiceGatewayMessa void DiscordVoiceClient::HandleGatewaySpeaking(const VoiceGatewayMessage &m) { VoiceSpeakingData data = m.Data; + m_ssrc_map[data.UserID] = data.SSRC; m_signal_speaking.emit(data); } diff --git a/src/discord/voiceclient.hpp b/src/discord/voiceclient.hpp index c052f28..1dc6e1a 100644 --- a/src/discord/voiceclient.hpp +++ b/src/discord/voiceclient.hpp @@ -1,14 +1,17 @@ #pragma once #ifdef WITH_VOICE - // clang-format off +// clang-format off + #include "snowflake.hpp" #include "waiter.hpp" #include "websocket.hpp" +#include #include #include #include #include #include +#include // clang-format on enum class VoiceGatewayCloseCode : uint16_t { @@ -190,6 +193,8 @@ public: void SetServerID(Snowflake id); void SetUserID(Snowflake id); + [[nodiscard]] std::optional GetSSRCOfUser(Snowflake id) const; + [[nodiscard]] bool IsConnected() const noexcept; private: @@ -218,6 +223,8 @@ private: uint16_t m_port; uint32_t m_ssrc; + std::unordered_map m_ssrc_map; + std::array m_secret_key; Websocket m_ws; diff --git a/src/windows/voicewindow.cpp b/src/windows/voicewindow.cpp index b352c86..17f8ec2 100644 --- a/src/windows/voicewindow.cpp +++ b/src/windows/voicewindow.cpp @@ -1,24 +1,86 @@ #include "voicewindow.hpp" +#include "components/lazyimage.hpp" +#include "abaddon.hpp" -VoiceWindow::VoiceWindow() +class VoiceWindowUserListEntry : public Gtk::ListBoxRow { +public: + VoiceWindowUserListEntry(Snowflake id) + : m_main(Gtk::ORIENTATION_HORIZONTAL) + , m_avatar(32, 32) + , m_mute("Mute") { + m_name.set_halign(Gtk::ALIGN_START); + m_name.set_hexpand(true); + m_mute.set_halign(Gtk::ALIGN_END); + + m_main.add(m_avatar); + m_main.add(m_name); + m_main.add(m_mute); + add(m_main); + show_all_children(); + + auto &discord = Abaddon::Get().GetDiscordClient(); + const auto user = discord.GetUser(id); + if (user.has_value()) { + m_name.set_text(user->Username); + } else { + m_name.set_text("Unknown user"); + } + + m_mute.signal_toggled().connect([this]() { + m_signal_mute_cs.emit(m_mute.get_active()); + }); + } + +private: + Gtk::Box m_main; + LazyImage m_avatar; + Gtk::Label m_name; + Gtk::CheckButton m_mute; + +public: + using type_signal_mute_cs = sigc::signal; + type_signal_mute_cs signal_mute_cs() { + return m_signal_mute_cs; + } + +private: + type_signal_mute_cs m_signal_mute_cs; +}; + +VoiceWindow::VoiceWindow(Snowflake channel_id) : m_main(Gtk::ORIENTATION_VERTICAL) , m_controls(Gtk::ORIENTATION_HORIZONTAL) , m_mute("Mute") - , m_deafen("Deafen") { + , m_deafen("Deafen") + , m_channel_id(channel_id) { get_style_context()->add_class("app-window"); set_default_size(300, 300); + auto &discord = Abaddon::Get().GetDiscordClient(); + SetUsers(discord.GetUsersInVoiceChannel(m_channel_id)); + m_mute.signal_toggled().connect(sigc::mem_fun(*this, &VoiceWindow::OnMuteChanged)); m_deafen.signal_toggled().connect(sigc::mem_fun(*this, &VoiceWindow::OnDeafenChanged)); m_controls.add(m_mute); m_controls.add(m_deafen); m_main.add(m_controls); + m_main.add(m_user_list); add(m_main); show_all_children(); } +void VoiceWindow::SetUsers(const std::unordered_set &user_ids) { + for (auto id : user_ids) { + auto *row = Gtk::make_managed(id); + row->signal_mute_cs().connect([this, id](bool is_muted) { + m_signal_mute_user_cs.emit(id, is_muted); + }); + m_user_list.add(*row); + } +} + void VoiceWindow::OnMuteChanged() { m_signal_mute.emit(m_mute.get_active()); } @@ -34,3 +96,7 @@ VoiceWindow::type_signal_mute VoiceWindow::signal_mute() { VoiceWindow::type_signal_deafen VoiceWindow::signal_deafen() { return m_signal_deafen; } + +VoiceWindow::type_signal_mute_user_cs VoiceWindow::signal_mute_user_cs() { + return m_signal_mute_user_cs; +} diff --git a/src/windows/voicewindow.hpp b/src/windows/voicewindow.hpp index bee0a84..a2162b4 100644 --- a/src/windows/voicewindow.hpp +++ b/src/windows/voicewindow.hpp @@ -1,13 +1,18 @@ #pragma once +#include "discord/snowflake.hpp" #include #include +#include #include +#include class VoiceWindow : public Gtk::Window { public: - VoiceWindow(); + VoiceWindow(Snowflake channel_id); private: + void SetUsers(const std::unordered_set &user_ids); + void OnMuteChanged(); void OnDeafenChanged(); @@ -17,14 +22,21 @@ private: Gtk::CheckButton m_mute; Gtk::CheckButton m_deafen; + Gtk::ListBox m_user_list; + + Snowflake m_channel_id; + public: using type_signal_mute = sigc::signal; using type_signal_deafen = sigc::signal; + using type_signal_mute_user_cs = sigc::signal; type_signal_mute signal_mute(); type_signal_deafen signal_deafen(); + type_signal_mute_user_cs signal_mute_user_cs(); private: type_signal_mute m_signal_mute; type_signal_deafen m_signal_deafen; + type_signal_mute_user_cs m_signal_mute_user_cs; }; -- cgit v1.2.3 From e08e3106d68da08c58f3a5bde968d4367bef78df Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Sat, 1 Oct 2022 17:46:10 -0400 Subject: rudimentary dm voice call support --- src/components/channels.cpp | 28 +++++++++++++++++++++++++++- src/components/channels.hpp | 4 ++++ src/discord/discord.cpp | 13 ++++++++++--- src/discord/objects.cpp | 5 +++-- src/discord/objects.hpp | 3 ++- 5 files changed, 46 insertions(+), 7 deletions(-) (limited to 'src/discord/discord.cpp') diff --git a/src/components/channels.cpp b/src/components/channels.cpp index eb9d688..dbd0aed 100644 --- a/src/components/channels.cpp +++ b/src/components/channels.cpp @@ -27,6 +27,10 @@ ChannelList::ChannelList() #endif , m_menu_dm_copy_id("_Copy ID", true) , m_menu_dm_close("") // changes depending on if group or not +#ifdef WITH_VOICE + , m_menu_dm_join_voice("Join _Voice", true) + , m_menu_dm_disconnect_voice("_Disconnect Voice", true) +#endif , m_menu_thread_copy_id("_Copy ID", true) , m_menu_thread_leave("_Leave", true) , m_menu_thread_archive("_Archive", true) @@ -215,6 +219,17 @@ ChannelList::ChannelList() #endif m_menu_dm.append(m_menu_dm_toggle_mute); m_menu_dm.append(m_menu_dm_close); +#ifdef WITH_VOICE + m_menu_dm_join_voice.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_dm_disconnect_voice.signal_activate().connect([this]() { + m_signal_action_disconnect_voice.emit(); + }); + m_menu_dm.append(m_menu_dm_join_voice); + m_menu_dm.append(m_menu_dm_disconnect_voice); +#endif m_menu_dm.append(m_menu_dm_copy_id); m_menu_dm.show_all(); @@ -1010,10 +1025,21 @@ void ChannelList::OnDMSubmenuPopup() { auto iter = m_model->get_iter(m_path_for_menu); if (!iter) return; const auto id = static_cast((*iter)[m_columns.m_id]); - if (Abaddon::Get().GetDiscordClient().IsChannelMuted(id)) + auto &discord = Abaddon::Get().GetDiscordClient(); + if (discord.IsChannelMuted(id)) m_menu_dm_toggle_mute.set_label("Unmute"); else m_menu_dm_toggle_mute.set_label("Mute"); + +#ifdef WITH_VOICE + if (discord.IsConnectedToVoice()) { + m_menu_dm_join_voice.set_sensitive(false); + m_menu_dm_disconnect_voice.set_sensitive(discord.GetVoiceChannelID() == id); + } else { + m_menu_dm_join_voice.set_sensitive(true); + m_menu_dm_disconnect_voice.set_sensitive(false); + } +#endif } void ChannelList::OnThreadSubmenuPopup() { diff --git a/src/components/channels.hpp b/src/components/channels.hpp index 2d2b257..d1986a0 100644 --- a/src/components/channels.hpp +++ b/src/components/channels.hpp @@ -135,6 +135,10 @@ protected: Gtk::MenuItem m_menu_dm_copy_id; Gtk::MenuItem m_menu_dm_close; Gtk::MenuItem m_menu_dm_toggle_mute; +#ifdef WITH_VOICE + Gtk::MenuItem m_menu_dm_join_voice; + Gtk::MenuItem m_menu_dm_disconnect_voice; +#endif #ifdef WITH_LIBHANDY Gtk::MenuItem m_menu_dm_open_tab; diff --git a/src/discord/discord.cpp b/src/discord/discord.cpp index b6bcc07..e0748fb 100644 --- a/src/discord/discord.cpp +++ b/src/discord/discord.cpp @@ -1180,10 +1180,11 @@ void DiscordClient::AcceptVerificationGate(Snowflake guild_id, VerificationGateI #ifdef WITH_VOICE void DiscordClient::ConnectToVoice(Snowflake channel_id) { auto channel = GetChannel(channel_id); - if (!channel.has_value() || !channel->GuildID.has_value()) return; + if (!channel.has_value()) return; m_voice_channel_id = channel_id; VoiceStateUpdateMessage m; - m.GuildID = *channel->GuildID; + if (channel->GuildID.has_value()) + m.GuildID = channel->GuildID; m.ChannelID = channel_id; m.PreferredRegion = "newark"; m_websocket.Send(m); @@ -2179,7 +2180,13 @@ void DiscordClient::HandleGatewayVoiceServerUpdate(const GatewayMessage &msg) { printf("token: %s\n", data.Token.c_str()); m_voice.SetEndpoint(data.Endpoint); m_voice.SetToken(data.Token); - m_voice.SetServerID(data.GuildID); + if (data.GuildID.has_value()) { + m_voice.SetServerID(*data.GuildID); + } else if (data.ChannelID.has_value()) { + m_voice.SetServerID(*data.ChannelID); + } else { + puts("no guild or channel id in voice server?"); + } m_voice.SetUserID(m_user_data.ID); m_voice.Start(); } diff --git a/src/discord/objects.cpp b/src/discord/objects.cpp index 3e87e43..37a9241 100644 --- a/src/discord/objects.cpp +++ b/src/discord/objects.cpp @@ -666,13 +666,14 @@ void to_json(nlohmann::json &j, const VoiceStateUpdateMessage &m) { void from_json(const nlohmann::json &j, VoiceServerUpdateData &m) { JS_D("token", m.Token); - JS_D("guild_id", m.GuildID); JS_D("endpoint", m.Endpoint); + JS_ON("guild_id", m.GuildID); + JS_ON("channel_id", m.ChannelID); } #endif void from_json(const nlohmann::json &j, VoiceState &m) { - JS_O("guild_id", m.GuildID); + JS_ON("guild_id", m.GuildID); JS_N("channel_id", m.ChannelID); JS_D("deaf", m.IsDeafened); JS_D("mute", m.IsMuted); diff --git a/src/discord/objects.hpp b/src/discord/objects.hpp index 14dd20c..120ffa2 100644 --- a/src/discord/objects.hpp +++ b/src/discord/objects.hpp @@ -891,8 +891,9 @@ struct VoiceStateUpdateMessage { struct VoiceServerUpdateData { std::string Token; - Snowflake GuildID; std::string Endpoint; + std::optional GuildID; + std::optional ChannelID; friend void from_json(const nlohmann::json &j, VoiceServerUpdateData &m); }; -- cgit v1.2.3 From 78a5b9599c900c5da56da0a15835f425e455b510 Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Mon, 10 Oct 2022 00:27:47 -0400 Subject: remove user from list on disconnect --- src/discord/discord.cpp | 19 +++++++++++++++++++ src/discord/discord.hpp | 4 ++++ src/windows/voicewindow.cpp | 12 ++++++++++++ src/windows/voicewindow.hpp | 4 ++++ 4 files changed, 39 insertions(+) (limited to 'src/discord/discord.cpp') diff --git a/src/discord/discord.cpp b/src/discord/discord.cpp index c62cd2a..91ba1e3 100644 --- a/src/discord/discord.cpp +++ b/src/discord/discord.cpp @@ -1208,6 +1208,13 @@ std::optional DiscordClient::GetSSRCOfUser(Snowflake id) const { return m_voice.GetSSRCOfUser(id); } +std::optional DiscordClient::GetVoiceState(Snowflake user_id) const { + if (const auto it = m_voice_state_user_channel.find(user_id); it != m_voice_state_user_channel.end()) { + return it->second; + } + return std::nullopt; +} + void DiscordClient::SetVoiceMuted(bool is_mute) { m_mute_requested = is_mute; SendVoiceStateUpdate(); @@ -2164,9 +2171,17 @@ void DiscordClient::HandleGatewayVoiceStateUpdate(const GatewayMessage &msg) { m_voice.SetSessionID(data.SessionID); } if (data.ChannelID.has_value()) { + const auto old_state = GetVoiceState(data.UserID); SetVoiceState(data.UserID, *data.ChannelID); + if (old_state.has_value() && *old_state != *data.ChannelID) { + m_signal_voice_user_disconnect.emit(data.UserID, *old_state); + } } else { + const auto old_state = GetVoiceState(data.UserID); ClearVoiceState(data.UserID); + if (old_state.has_value()) { + m_signal_voice_user_disconnect.emit(data.UserID, *old_state); + } } } @@ -2952,4 +2967,8 @@ DiscordClient::type_signal_voice_disconnected DiscordClient::signal_voice_discon DiscordClient::type_signal_voice_speaking DiscordClient::signal_voice_speaking() { return m_signal_voice_speaking; } + +DiscordClient::type_signal_voice_user_disconnect DiscordClient::signal_voice_user_disconnect() { + return m_signal_voice_user_disconnect; +} #endif diff --git a/src/discord/discord.hpp b/src/discord/discord.hpp index a3a2ba7..d0f5be0 100644 --- a/src/discord/discord.hpp +++ b/src/discord/discord.hpp @@ -187,6 +187,7 @@ public: [[nodiscard]] Snowflake GetVoiceChannelID() const noexcept; [[nodiscard]] std::unordered_set GetUsersInVoiceChannel(Snowflake channel_id); [[nodiscard]] std::optional GetSSRCOfUser(Snowflake id) const; + [[nodiscard]] std::optional GetVoiceState(Snowflake user_id) const; void SetVoiceMuted(bool is_mute); void SetVoiceDeafened(bool is_deaf); @@ -435,6 +436,7 @@ public: using type_signal_voice_connected = sigc::signal; using type_signal_voice_disconnected = sigc::signal; using type_signal_voice_speaking = sigc::signal; + using type_signal_voice_user_disconnect = sigc::signal; #endif type_signal_gateway_ready signal_gateway_ready(); @@ -495,6 +497,7 @@ public: type_signal_voice_connected signal_voice_connected(); type_signal_voice_disconnected signal_voice_disconnected(); type_signal_voice_speaking signal_voice_speaking(); + type_signal_voice_user_disconnect signal_voice_user_disconnect(); #endif protected: @@ -556,5 +559,6 @@ protected: type_signal_voice_connected m_signal_voice_connected; type_signal_voice_disconnected m_signal_voice_disconnected; type_signal_voice_speaking m_signal_voice_speaking; + type_signal_voice_user_disconnect m_signal_voice_user_disconnect; #endif }; diff --git a/src/windows/voicewindow.cpp b/src/windows/voicewindow.cpp index b8ade8f..7019140 100644 --- a/src/windows/voicewindow.cpp +++ b/src/windows/voicewindow.cpp @@ -84,6 +84,8 @@ VoiceWindow::VoiceWindow(Snowflake channel_id) auto &discord = Abaddon::Get().GetDiscordClient(); SetUsers(discord.GetUsersInVoiceChannel(m_channel_id)); + discord.signal_voice_user_disconnect().connect(sigc::mem_fun(*this, &VoiceWindow::OnUserDisconnect)); + m_mute.signal_toggled().connect(sigc::mem_fun(*this, &VoiceWindow::OnMuteChanged)); m_deafen.signal_toggled().connect(sigc::mem_fun(*this, &VoiceWindow::OnDeafenChanged)); @@ -103,6 +105,7 @@ VoiceWindow::VoiceWindow(Snowflake channel_id) void VoiceWindow::SetUsers(const std::unordered_set &user_ids) { for (auto id : user_ids) { auto *row = Gtk::make_managed(id); + m_rows[id] = row; row->signal_mute_cs().connect([this, id](bool is_muted) { m_signal_mute_user_cs.emit(id, is_muted); }); @@ -121,6 +124,15 @@ void VoiceWindow::OnDeafenChanged() { m_signal_deafen.emit(m_deafen.get_active()); } +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); + } + } +} + VoiceWindow::type_signal_mute VoiceWindow::signal_mute() { return m_signal_mute; } diff --git a/src/windows/voicewindow.hpp b/src/windows/voicewindow.hpp index b3edf46..c968acb 100644 --- a/src/windows/voicewindow.hpp +++ b/src/windows/voicewindow.hpp @@ -18,6 +18,8 @@ public: private: void SetUsers(const std::unordered_set &user_ids); + void OnUserDisconnect(Snowflake user_id, Snowflake from_channel_id); + void OnMuteChanged(); void OnDeafenChanged(); @@ -32,6 +34,8 @@ private: Snowflake m_channel_id; + std::unordered_map m_rows; + public: using type_signal_mute = sigc::signal; using type_signal_deafen = sigc::signal; -- cgit v1.2.3 From 17e7478bb4827e094a06faca6c5f2d5f4e5a45cc Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Wed, 12 Oct 2022 01:51:32 -0400 Subject: add user row on voice connect --- src/discord/discord.cpp | 5 +++++ src/discord/discord.hpp | 3 +++ src/windows/voicewindow.cpp | 32 +++++++++++++++++++++++--------- src/windows/voicewindow.hpp | 3 +++ 4 files changed, 34 insertions(+), 9 deletions(-) (limited to 'src/discord/discord.cpp') diff --git a/src/discord/discord.cpp b/src/discord/discord.cpp index 91ba1e3..7b1c7b9 100644 --- a/src/discord/discord.cpp +++ b/src/discord/discord.cpp @@ -2176,6 +2176,7 @@ void DiscordClient::HandleGatewayVoiceStateUpdate(const GatewayMessage &msg) { if (old_state.has_value() && *old_state != *data.ChannelID) { m_signal_voice_user_disconnect.emit(data.UserID, *old_state); } + m_signal_voice_user_connect.emit(data.UserID, *data.ChannelID); } else { const auto old_state = GetVoiceState(data.UserID); ClearVoiceState(data.UserID); @@ -2971,4 +2972,8 @@ DiscordClient::type_signal_voice_speaking DiscordClient::signal_voice_speaking() DiscordClient::type_signal_voice_user_disconnect DiscordClient::signal_voice_user_disconnect() { return m_signal_voice_user_disconnect; } + +DiscordClient::type_signal_voice_user_connect DiscordClient::signal_voice_user_connect() { + return m_signal_voice_user_connect; +} #endif diff --git a/src/discord/discord.hpp b/src/discord/discord.hpp index d0f5be0..fc714e6 100644 --- a/src/discord/discord.hpp +++ b/src/discord/discord.hpp @@ -437,6 +437,7 @@ public: using type_signal_voice_disconnected = sigc::signal; using type_signal_voice_speaking = sigc::signal; using type_signal_voice_user_disconnect = sigc::signal; + using type_signal_voice_user_connect = sigc::signal; #endif type_signal_gateway_ready signal_gateway_ready(); @@ -498,6 +499,7 @@ public: type_signal_voice_disconnected signal_voice_disconnected(); type_signal_voice_speaking signal_voice_speaking(); type_signal_voice_user_disconnect signal_voice_user_disconnect(); + type_signal_voice_user_connect signal_voice_user_connect(); #endif protected: @@ -560,5 +562,6 @@ protected: type_signal_voice_disconnected m_signal_voice_disconnected; type_signal_voice_speaking m_signal_voice_speaking; type_signal_voice_user_disconnect m_signal_voice_user_disconnect; + type_signal_voice_user_connect m_signal_voice_user_connect; #endif }; diff --git a/src/windows/voicewindow.cpp b/src/windows/voicewindow.cpp index 7019140..0af8176 100644 --- a/src/windows/voicewindow.cpp +++ b/src/windows/voicewindow.cpp @@ -85,6 +85,7 @@ VoiceWindow::VoiceWindow(Snowflake channel_id) 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)); m_mute.signal_toggled().connect(sigc::mem_fun(*this, &VoiceWindow::OnMuteChanged)); m_deafen.signal_toggled().connect(sigc::mem_fun(*this, &VoiceWindow::OnDeafenChanged)); @@ -104,18 +105,23 @@ VoiceWindow::VoiceWindow(Snowflake channel_id) void VoiceWindow::SetUsers(const std::unordered_set &user_ids) { for (auto id : user_ids) { - auto *row = Gtk::make_managed(id); - m_rows[id] = row; - row->signal_mute_cs().connect([this, id](bool is_muted) { - m_signal_mute_user_cs.emit(id, is_muted); - }); - row->signal_volume().connect([this, id](double volume) { - m_signal_user_volume_changed.emit(id, volume); - }); - m_user_list.add(*row); + m_user_list.add(*CreateRow(id)); } } +Gtk::ListBoxRow *VoiceWindow::CreateRow(Snowflake id) { + auto *row = Gtk::make_managed(id); + m_rows[id] = row; + row->signal_mute_cs().connect([this, id](bool is_muted) { + m_signal_mute_user_cs.emit(id, is_muted); + }); + row->signal_volume().connect([this, id](double volume) { + m_signal_user_volume_changed.emit(id, volume); + }); + row->show_all(); + return row; +} + void VoiceWindow::OnMuteChanged() { m_signal_mute.emit(m_mute.get_active()); } @@ -124,6 +130,14 @@ void VoiceWindow::OnDeafenChanged() { m_signal_deafen.emit(m_deafen.get_active()); } +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)); + } + } +} + 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()) { diff --git a/src/windows/voicewindow.hpp b/src/windows/voicewindow.hpp index c968acb..aaf77d7 100644 --- a/src/windows/voicewindow.hpp +++ b/src/windows/voicewindow.hpp @@ -18,6 +18,9 @@ public: private: void SetUsers(const std::unordered_set &user_ids); + Gtk::ListBoxRow *CreateRow(Snowflake id); + + void OnUserConnect(Snowflake user_id, Snowflake to_channel_id); void OnUserDisconnect(Snowflake user_id, Snowflake from_channel_id); void OnMuteChanged(); -- cgit v1.2.3 From e2110c22eefa899bf5d588eb34c477a3cc9c365e Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Tue, 18 Oct 2022 18:34:14 -0400 Subject: store user data from voice state updates --- src/discord/discord.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) (limited to 'src/discord/discord.cpp') diff --git a/src/discord/discord.cpp b/src/discord/discord.cpp index 7b1c7b9..cc066cc 100644 --- a/src/discord/discord.cpp +++ b/src/discord/discord.cpp @@ -2166,10 +2166,19 @@ void DiscordClient::HandleGatewayGuildMembersChunk(const GatewayMessage &msg) { #ifdef WITH_VOICE void DiscordClient::HandleGatewayVoiceStateUpdate(const GatewayMessage &msg) { VoiceState data = msg.Data; + if (data.UserID == m_user_data.ID) { printf("voice session id: %s\n", data.SessionID.c_str()); m_voice.SetSessionID(data.SessionID); + } else { + if (data.GuildID.has_value() && data.Member.has_value()) { + if (data.Member->User.has_value()) { + m_store.SetUser(data.UserID, *data.Member->User); + } + m_store.SetGuildMember(*data.GuildID, data.UserID, *data.Member); + } } + if (data.ChannelID.has_value()) { const auto old_state = GetVoiceState(data.UserID); SetVoiceState(data.UserID, *data.ChannelID); -- cgit v1.2.3 From 047168873235c3d835aaf9836f549de08bd96a47 Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Tue, 8 Nov 2022 04:01:54 -0500 Subject: add ability to set capture device --- src/abaddon.cpp | 1 + src/audio/devices.cpp | 31 +++++++++++++++++++++++++++++-- src/audio/devices.hpp | 14 +++++++++++++- src/audio/manager.cpp | 36 +++++++++++++++++++++++++++++++++++- src/audio/manager.hpp | 2 ++ src/discord/discord.cpp | 9 +++++---- src/windows/voicewindow.cpp | 18 +++++++++++++++--- src/windows/voicewindow.hpp | 1 + 8 files changed, 101 insertions(+), 11 deletions(-) (limited to 'src/discord/discord.cpp') diff --git a/src/abaddon.cpp b/src/abaddon.cpp index a0e73f2..67fd2bd 100644 --- a/src/abaddon.cpp +++ b/src/abaddon.cpp @@ -1105,6 +1105,7 @@ int main(int argc, char **argv) { spdlog::cfg::load_env_levels(); auto log_audio = spdlog::stdout_color_mt("audio"); auto log_voice = spdlog::stdout_color_mt("voice"); + auto log_discord = spdlog::stdout_color_mt("discord"); Gtk::Main::init_gtkmm_internals(); // why??? return Abaddon::Get().StartGTK(); diff --git a/src/audio/devices.cpp b/src/audio/devices.cpp index c9636b7..5e7720b 100644 --- a/src/audio/devices.cpp +++ b/src/audio/devices.cpp @@ -7,13 +7,18 @@ // clang-format on AudioDevices::AudioDevices() - : m_playback(Gtk::ListStore::create(m_playback_columns)) { + : m_playback(Gtk::ListStore::create(m_playback_columns)) + , m_capture(Gtk::ListStore::create(m_capture_columns)) { } Glib::RefPtr AudioDevices::GetPlaybackDeviceModel() { return m_playback; } +Glib::RefPtr AudioDevices::GetCaptureDeviceModel() { + return m_capture; +} + void AudioDevices::SetDevices(ma_device_info *pPlayback, ma_uint32 playback_count, ma_device_info *pCapture, ma_uint32 capture_count) { m_playback->clear(); @@ -23,9 +28,18 @@ void AudioDevices::SetDevices(ma_device_info *pPlayback, ma_uint32 playback_coun row[m_playback_columns.Name] = d.name; row[m_playback_columns.DeviceID] = d.id; } + + m_capture->clear(); + + for (ma_uint32 i = 0; i < capture_count; i++) { + auto &d = pCapture[i]; + auto row = *m_capture->append(); + row[m_capture_columns.Name] = d.name; + row[m_capture_columns.DeviceID] = d.id; + } } -std::optional AudioDevices::GetDeviceIDFromModel(const Gtk::TreeModel::iterator &iter) { +std::optional AudioDevices::GetPlaybackDeviceIDFromModel(const Gtk::TreeModel::iterator &iter) const { if (iter) { return static_cast((*iter)[m_playback_columns.DeviceID]); } else { @@ -33,8 +47,21 @@ std::optional AudioDevices::GetDeviceIDFromModel(const Gtk::TreeMo } } +std::optional AudioDevices::GetCaptureDeviceIDFromModel(const Gtk::TreeModel::iterator &iter) const { + if (iter) { + return static_cast((*iter)[m_capture_columns.DeviceID]); + } else { + return std::nullopt; + } +} + AudioDevices::PlaybackColumns::PlaybackColumns() { add(Name); add(DeviceID); } + +AudioDevices::CaptureColumns::CaptureColumns() { + add(Name); + add(DeviceID); +} #endif diff --git a/src/audio/devices.hpp b/src/audio/devices.hpp index c11744b..9d3de7d 100644 --- a/src/audio/devices.hpp +++ b/src/audio/devices.hpp @@ -14,9 +14,11 @@ public: AudioDevices(); Glib::RefPtr GetPlaybackDeviceModel(); + Glib::RefPtr GetCaptureDeviceModel(); void SetDevices(ma_device_info *pPlayback, ma_uint32 playback_count, ma_device_info *pCapture, ma_uint32 capture_count); - std::optional GetDeviceIDFromModel(const Gtk::TreeModel::iterator &iter); + std::optional GetPlaybackDeviceIDFromModel(const Gtk::TreeModel::iterator &iter) const; + std::optional GetCaptureDeviceIDFromModel(const Gtk::TreeModel::iterator &iter) const; private: class PlaybackColumns : public Gtk::TreeModel::ColumnRecord { @@ -28,5 +30,15 @@ private: }; PlaybackColumns m_playback_columns; Glib::RefPtr m_playback; + + class CaptureColumns : public Gtk::TreeModel::ColumnRecord { + public: + CaptureColumns(); + + Gtk::TreeModelColumn Name; + Gtk::TreeModelColumn DeviceID; + }; + CaptureColumns m_capture_columns; + Glib::RefPtr m_capture; }; #endif diff --git a/src/audio/manager.cpp b/src/audio/manager.cpp index c965658..839bfc8 100644 --- a/src/audio/manager.cpp +++ b/src/audio/manager.cpp @@ -186,7 +186,7 @@ void AudioManager::StopCaptureDevice() { void AudioManager::SetPlaybackDevice(const Gtk::TreeModel::iterator &iter) { spdlog::get("audio")->debug("Setting new playback device"); - const auto device_id = m_devices.GetDeviceIDFromModel(iter); + const auto device_id = m_devices.GetPlaybackDeviceIDFromModel(iter); if (!device_id) { spdlog::get("audio")->error("Requested ID from iterator is invalid"); return; @@ -215,6 +215,40 @@ void AudioManager::SetPlaybackDevice(const Gtk::TreeModel::iterator &iter) { } } +void AudioManager::SetCaptureDevice(const Gtk::TreeModel::iterator &iter) { + spdlog::get("audio")->debug("Setting new capture device"); + + const auto device_id = m_devices.GetCaptureDeviceIDFromModel(iter); + if (!device_id) { + spdlog::get("audio")->error("Requested ID from iterator is invalid"); + return; + } + + m_capture_id = *device_id; + + ma_device_uninit(&m_capture_device); + + m_capture_config = ma_device_config_init(ma_device_type_capture); + m_capture_config.capture.format = ma_format_s16; + m_capture_config.capture.channels = 2; + m_capture_config.capture.pDeviceID = &m_capture_id; + m_capture_config.sampleRate = 48000; + m_capture_config.periodSizeInFrames = 480; + m_capture_config.dataCallback = capture_data_callback; + m_capture_config.pUserData = this; + + if (ma_device_init(&m_context, &m_capture_config, &m_capture_device) != MA_SUCCESS) { + spdlog::get("audio")->error("Failed to initialize new device"); + return; + } + + // technically this should probably try and check old state but if you are in the window to change it then you are connected + if (ma_device_start(&m_capture_device) != MA_SUCCESS) { + spdlog::get("audio")->error("Failed to start new device"); + return; + } +} + void AudioManager::SetCapture(bool capture) { m_should_capture = capture; } diff --git a/src/audio/manager.hpp b/src/audio/manager.hpp index ff50390..7805b30 100644 --- a/src/audio/manager.hpp +++ b/src/audio/manager.hpp @@ -33,6 +33,7 @@ public: void StopCaptureDevice(); void SetPlaybackDevice(const Gtk::TreeModel::iterator &iter); + void SetCaptureDevice(const Gtk::TreeModel::iterator &iter); void SetCapture(bool capture); void SetPlayback(bool playback); @@ -75,6 +76,7 @@ private: // capture ma_device m_capture_device; ma_device_config m_capture_config; + ma_device_id m_capture_id; ma_context m_context; diff --git a/src/discord/discord.cpp b/src/discord/discord.cpp index cc066cc..2a08574 100644 --- a/src/discord/discord.cpp +++ b/src/discord/discord.cpp @@ -1,6 +1,7 @@ #include "abaddon.hpp" #include "discord.hpp" #include "util.hpp" +#include #include #include @@ -2168,7 +2169,7 @@ void DiscordClient::HandleGatewayVoiceStateUpdate(const GatewayMessage &msg) { VoiceState data = msg.Data; if (data.UserID == m_user_data.ID) { - printf("voice session id: %s\n", data.SessionID.c_str()); + spdlog::get("discord")->debug("Voice session ID: {}", data.SessionID); m_voice.SetSessionID(data.SessionID); } else { if (data.GuildID.has_value() && data.Member.has_value()) { @@ -2197,8 +2198,8 @@ void DiscordClient::HandleGatewayVoiceStateUpdate(const GatewayMessage &msg) { void DiscordClient::HandleGatewayVoiceServerUpdate(const GatewayMessage &msg) { VoiceServerUpdateData data = msg.Data; - printf("endpoint: %s\n", data.Endpoint.c_str()); - printf("token: %s\n", data.Token.c_str()); + spdlog::get("discord")->debug("Voice server endpoint: {}", data.Endpoint); + spdlog::get("discord")->debug("Voice token: {}", data.Token); m_voice.SetEndpoint(data.Endpoint); m_voice.SetToken(data.Token); if (data.GuildID.has_value()) { @@ -2206,7 +2207,7 @@ void DiscordClient::HandleGatewayVoiceServerUpdate(const GatewayMessage &msg) { } else if (data.ChannelID.has_value()) { m_voice.SetServerID(*data.ChannelID); } else { - puts("no guild or channel id in voice server?"); + spdlog::get("discord")->error("No guild or channel ID in voice server?"); } m_voice.SetUserID(m_user_data.ID); m_voice.Start(); diff --git a/src/windows/voicewindow.cpp b/src/windows/voicewindow.cpp index 95d1036..19d9863 100644 --- a/src/windows/voicewindow.cpp +++ b/src/windows/voicewindow.cpp @@ -121,17 +121,28 @@ VoiceWindow::VoiceWindow(Snowflake channel_id) m_signal_gain.emit(val / 100.0); }); - auto *renderer = Gtk::make_managed(); + auto *playback_renderer = Gtk::make_managed(); m_playback_combo.set_valign(Gtk::ALIGN_END); m_playback_combo.set_hexpand(true); m_playback_combo.set_halign(Gtk::ALIGN_FILL); m_playback_combo.set_model(Abaddon::Get().GetAudio().GetDevices().GetPlaybackDeviceModel()); - m_playback_combo.pack_start(*renderer); - m_playback_combo.add_attribute(*renderer, "text", 0); + m_playback_combo.pack_start(*playback_renderer); + m_playback_combo.add_attribute(*playback_renderer, "text", 0); m_playback_combo.signal_changed().connect([this]() { Abaddon::Get().GetAudio().SetPlaybackDevice(m_playback_combo.get_active()); }); + auto *capture_renderer = Gtk::make_managed(); + m_capture_combo.set_valign(Gtk::ALIGN_END); + m_capture_combo.set_hexpand(true); + m_capture_combo.set_halign(Gtk::ALIGN_FILL); + m_capture_combo.set_model(Abaddon::Get().GetAudio().GetDevices().GetCaptureDeviceModel()); + m_capture_combo.pack_start(*capture_renderer); + m_capture_combo.add_attribute(*capture_renderer, "text", 0); + m_capture_combo.signal_changed().connect([this]() { + Abaddon::Get().GetAudio().SetCaptureDevice(m_capture_combo.get_active()); + }); + m_scroll.add(m_user_list); m_controls.add(m_mute); m_controls.add(m_deafen); @@ -141,6 +152,7 @@ VoiceWindow::VoiceWindow(Snowflake channel_id) m_main.add(m_capture_gain); m_main.add(m_scroll); m_main.add(m_playback_combo); + m_main.add(m_capture_combo); add(m_main); show_all_children(); diff --git a/src/windows/voicewindow.hpp b/src/windows/voicewindow.hpp index e36846c..f9ea23f 100644 --- a/src/windows/voicewindow.hpp +++ b/src/windows/voicewindow.hpp @@ -47,6 +47,7 @@ private: Gtk::Scale m_capture_gain; Gtk::ComboBox m_playback_combo; + Gtk::ComboBox m_capture_combo; Snowflake m_channel_id; -- cgit v1.2.3 From 929ebf1a6008d861473e3ceffd11cd4eca90d620 Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Tue, 15 Nov 2022 02:14:49 -0500 Subject: mess with some websocket stuff to try and fix things to be honest, im not sure what ive done here. whatever memory i have of the issue i was trying to fix has long disappeared by the time im committing this. theres still some issues with being force disconnected and i really dont understand it ill figure it out eventually... maybe :/ --- src/discord/discord.cpp | 13 ++++-- src/discord/discord.hpp | 2 +- src/discord/voiceclient.cpp | 103 +++++++++++++++++++++++++++++++++++--------- src/discord/voiceclient.hpp | 24 ++++++++++- src/discord/websocket.cpp | 20 ++++++--- src/discord/websocket.hpp | 9 ++-- 6 files changed, 137 insertions(+), 34 deletions(-) (limited to 'src/discord/discord.cpp') diff --git a/src/discord/discord.cpp b/src/discord/discord.cpp index 2a08574..cd16aa8 100644 --- a/src/discord/discord.cpp +++ b/src/discord/discord.cpp @@ -9,7 +9,8 @@ using namespace std::string_literals; DiscordClient::DiscordClient(bool mem_store) : m_decompress_buf(InflateChunkSize) - , m_store(mem_store) { + , m_store(mem_store) + , m_websocket("gateway-ws") { m_msg_dispatch.connect(sigc::mem_fun(*this, &DiscordClient::MessageDispatch)); auto dispatch_cb = [this]() { m_generic_mutex.lock(); @@ -2171,6 +2172,11 @@ void DiscordClient::HandleGatewayVoiceStateUpdate(const GatewayMessage &msg) { if (data.UserID == m_user_data.ID) { spdlog::get("discord")->debug("Voice session ID: {}", data.SessionID); m_voice.SetSessionID(data.SessionID); + + // channel_id = null means disconnect. stop cuz out of order maybe + if (!data.ChannelID.has_value()) { + m_voice.Stop(); + } } else { if (data.GuildID.has_value() && data.Member.has_value()) { if (data.Member->User.has_value()) { @@ -2504,9 +2510,8 @@ void DiscordClient::SetSuperPropertiesFromIdentity(const IdentifyMessage &identi void DiscordClient::HandleSocketOpen() { } -void DiscordClient::HandleSocketClose(uint16_t code) { - printf("got socket close code: %d\n", code); - auto close_code = static_cast(code); +void DiscordClient::HandleSocketClose(const ix::WebSocketCloseInfo &info) { + auto close_code = static_cast(info.code); auto cb = [this, close_code]() { m_heartbeat_waiter.kill(); if (m_heartbeat_thread.joinable()) m_heartbeat_thread.join(); diff --git a/src/discord/discord.hpp b/src/discord/discord.hpp index fc714e6..2d8db91 100644 --- a/src/discord/discord.hpp +++ b/src/discord/discord.hpp @@ -289,7 +289,7 @@ private: void SetSuperPropertiesFromIdentity(const IdentifyMessage &identity); void HandleSocketOpen(); - void HandleSocketClose(uint16_t code); + void HandleSocketClose(const ix::WebSocketCloseInfo &info); static bool CheckCode(const http::response_type &r); static bool CheckCode(const http::response_type &r, int expected); diff --git a/src/discord/voiceclient.cpp b/src/discord/voiceclient.cpp index 291b975..3118cec 100644 --- a/src/discord/voiceclient.cpp +++ b/src/discord/voiceclient.cpp @@ -126,18 +126,26 @@ UDPSocket::type_signal_data UDPSocket::signal_data() { } DiscordVoiceClient::DiscordVoiceClient() { - sodium_init(); + if (sodium_init() == -1) { + spdlog::get("voice")->critical("sodium_init() failed"); + } + + m_ws = std::make_unique("voice-ws"); - m_ws.signal_open().connect([this]() { + m_ws->signal_open().connect([this]() { spdlog::get("voice")->info("Websocket open"); + SetState(State::Opened); }); - m_ws.signal_close().connect([this](uint16_t code) { - spdlog::get("voice")->info("Websocket closed with code {}", code); + m_ws->signal_close().connect([this](const ix::WebSocketCloseInfo &info) { + if (info.remote) { + SetState(State::ClosingByServer); + } Stop(); }); - m_ws.signal_message().connect([this](const std::string &str) { + m_ws->signal_message().connect([this](const std::string &str) { + spdlog::get("voice-ws")->trace("Recv: {}", str); std::lock_guard _(m_dispatch_mutex); m_message_queue.push(str); m_dispatcher.emit(); @@ -175,7 +183,8 @@ DiscordVoiceClient::~DiscordVoiceClient() { } void DiscordVoiceClient::Start() { - m_ws.StartConnection("wss://" + m_endpoint + "/?v=7"); + SetState(State::Opening); + m_ws->StartConnection("wss://" + m_endpoint + "/?v=7"); m_heartbeat_waiter.revive(); m_keepalive_waiter.revive(); m_connected = true; @@ -183,15 +192,29 @@ void DiscordVoiceClient::Start() { } void DiscordVoiceClient::Stop() { - m_ws.Stop(); - m_udp.Stop(); - m_heartbeat_waiter.kill(); - if (m_heartbeat_thread.joinable()) m_heartbeat_thread.join(); - m_keepalive_waiter.kill(); - if (m_keepalive_thread.joinable()) m_keepalive_thread.join(); - if (m_connected) { - m_connected = false; - m_signal_disconnected.emit(); + if (IsOpening() || IsOpened()) { + spdlog::get("voice")->debug("Requested voice client stop"); + SetState(State::ClosingByClient); + m_ws->Stop(); + } else if (IsClosing()) { + spdlog::get("voice")->debug("Completing stop in closing"); + if (m_state == State::ClosingByClient) { + SetState(State::ClosedByClient); + } else if (m_state == State::ClosingByServer) { + SetState(State::ClosedByServer); + } + m_ws->Stop(); + m_udp.Stop(); + m_heartbeat_waiter.kill(); + if (m_heartbeat_thread.joinable()) m_heartbeat_thread.join(); + m_keepalive_waiter.kill(); + if (m_keepalive_thread.joinable()) m_keepalive_thread.join(); + if (m_connected) { + m_connected = false; + m_signal_disconnected.emit(); + } + } else { + spdlog::get("voice")->debug("Stop called, but already stopped"); } } @@ -228,7 +251,6 @@ bool DiscordVoiceClient::IsConnected() const noexcept { void DiscordVoiceClient::OnGatewayMessage(const std::string &str) { VoiceGatewayMessage msg = nlohmann::json::parse(str); - puts(msg.Data.dump(4).c_str()); switch (msg.Opcode) { case VoiceGatewayOp::Hello: { HandleGatewayHello(msg); @@ -278,7 +300,7 @@ void DiscordVoiceClient::HandleGatewaySessionDescription(const VoiceGatewayMessa msg.Delay = 0; msg.SSRC = m_ssrc; msg.Speaking = VoiceSpeakingType::Microphone; - m_ws.Send(msg); + m_ws->Send(msg); m_secret_key = d.SecretKey; m_udp.SetSSRC(m_ssrc); @@ -304,7 +326,7 @@ void DiscordVoiceClient::Identify() { msg.SessionID = m_session_id; msg.Token = m_token; msg.Video = true; - m_ws.Send(msg); + m_ws->Send(msg); } void DiscordVoiceClient::Discovery() { @@ -341,7 +363,7 @@ void DiscordVoiceClient::SelectProtocol(std::string_view ip, uint16_t port) { msg.Address = ip; msg.Port = port; msg.Protocol = "udp"; - m_ws.Send(msg); + m_ws->Send(msg); } void DiscordVoiceClient::OnUDPData(std::vector data) { @@ -370,7 +392,7 @@ void DiscordVoiceClient::HeartbeatThread() { VoiceHeartbeatMessage msg; msg.Nonce = static_cast(ms); - m_ws.Send(msg); + m_ws->Send(msg); } } @@ -386,6 +408,47 @@ void DiscordVoiceClient::KeepaliveThread() { } } +void DiscordVoiceClient::SetState(State state) { + m_state = state; + + switch (state) { + case State::Opening: + spdlog::get("voice")->debug("WS state: Opening"); + break; + case State::Opened: + spdlog::get("voice")->debug("WS state: Opened"); + break; + case State::ClosingByClient: + spdlog::get("voice")->debug("WS state: Closing (Client)"); + break; + case State::ClosingByServer: + spdlog::get("voice")->debug("WS state: Closing (Server)"); + break; + case State::ClosedByClient: + spdlog::get("voice")->debug("WS state: Closed (Client)"); + break; + case State::ClosedByServer: + spdlog::get("voice")->debug("WS state: Closed (Server)"); + break; + } +} + +bool DiscordVoiceClient::IsOpening() const noexcept { + return m_state == State::Opening; +} + +bool DiscordVoiceClient::IsOpened() const noexcept { + return m_state == State::Opened; +} + +bool DiscordVoiceClient::IsClosing() const noexcept { + return m_state == State::ClosingByClient || m_state == State::ClosingByServer; +} + +bool DiscordVoiceClient::IsClosed() const noexcept { + return m_state == State::ClosedByClient || m_state == State::ClosedByServer; +} + DiscordVoiceClient::type_signal_disconnected DiscordVoiceClient::signal_connected() { return m_signal_connected; } diff --git a/src/discord/voiceclient.hpp b/src/discord/voiceclient.hpp index 1d8b952..61b329c 100644 --- a/src/discord/voiceclient.hpp +++ b/src/discord/voiceclient.hpp @@ -15,6 +15,7 @@ // clang-format on enum class VoiceGatewayCloseCode : uint16_t { + Normal = 4000, UnknownOpcode = 4001, InvalidPayload = 4002, NotAuthenticated = 4003, @@ -228,7 +229,10 @@ private: std::array m_secret_key; - Websocket m_ws; + // this is a unique_ptr because Websocket/ixwebsocket seems to have some strange behavior + // and quite frankly i do not feel like figuring out what is wrong + // so using a unique_ptr will just let me nuke the whole thing and make a new one + std::unique_ptr m_ws; UDPSocket m_udp; Glib::Dispatcher m_dispatcher; @@ -246,6 +250,24 @@ private: std::atomic m_connected = false; + enum class State { + Opening, + Opened, + ClosingByClient, + ClosedByClient, + ClosingByServer, + ClosedByServer, + }; + + void SetState(State state); + + [[nodiscard]] bool IsOpening() const noexcept; + [[nodiscard]] bool IsOpened() const noexcept; + [[nodiscard]] bool IsClosing() const noexcept; + [[nodiscard]] bool IsClosed() const noexcept; + + std::atomic m_state; + using type_signal_connected = sigc::signal; using type_signal_disconnected = sigc::signal; using type_signal_speaking = sigc::signal; diff --git a/src/discord/websocket.cpp b/src/discord/websocket.cpp index cf0111c..349913a 100644 --- a/src/discord/websocket.cpp +++ b/src/discord/websocket.cpp @@ -1,19 +1,25 @@ #include "websocket.hpp" +#include #include -Websocket::Websocket() - : m_close_code(ix::WebSocketCloseConstants::kNormalClosureCode) { +Websocket::Websocket(const std::string &id) + : m_close_info { 1000, "Normal", false } { + if (m_log = spdlog::get(id); !m_log) { + m_log = spdlog::stdout_color_mt(id); + } + m_open_dispatcher.connect([this]() { m_signal_open.emit(); }); m_close_dispatcher.connect([this]() { Stop(); - m_signal_close.emit(m_close_code); + m_signal_close.emit(m_close_info); }); } void Websocket::StartConnection(const std::string &url) { + m_log->debug("Starting connection to {}", url); m_websocket.disableAutomaticReconnection(); m_websocket.setUrl(url); m_websocket.setOnMessageCallback([this](auto &&msg) { OnMessage(std::forward(msg)); }); @@ -34,16 +40,18 @@ void Websocket::SetPrintMessages(bool show) noexcept { } void Websocket::Stop() { + m_log->debug("Stopping with default close code"); Stop(ix::WebSocketCloseConstants::kNormalClosureCode); } void Websocket::Stop(uint16_t code) { + m_log->debug("Stopping with close code {}", code); m_websocket.stop(code); } void Websocket::Send(const std::string &str) { if (m_print_messages) - printf("sending %s\n", str.c_str()); + m_log->trace("Send: {}", str); m_websocket.sendText(str); } @@ -54,10 +62,12 @@ void Websocket::Send(const nlohmann::json &j) { void Websocket::OnMessage(const ix::WebSocketMessagePtr &msg) { switch (msg->type) { case ix::WebSocketMessageType::Open: { + m_log->debug("Received open frame, dispatching"); m_open_dispatcher.emit(); } break; case ix::WebSocketMessageType::Close: { - m_close_code = msg->closeInfo.code; + m_log->debug("Received close frame, dispatching. {} ({}){}", msg->closeInfo.code, msg->closeInfo.reason, msg->closeInfo.remote ? " Remote" : ""); + m_close_info = msg->closeInfo; m_close_dispatcher.emit(); } break; case ix::WebSocketMessageType::Message: { diff --git a/src/discord/websocket.hpp b/src/discord/websocket.hpp index ba55254..768121e 100644 --- a/src/discord/websocket.hpp +++ b/src/discord/websocket.hpp @@ -6,10 +6,11 @@ #include #include #include +#include class Websocket { public: - Websocket(); + Websocket(const std::string &id); void StartConnection(const std::string &url); void SetUserAgent(std::string agent); @@ -30,7 +31,7 @@ private: public: using type_signal_open = sigc::signal; - using type_signal_close = sigc::signal; + using type_signal_close = sigc::signal; using type_signal_message = sigc::signal; type_signal_open signal_open(); @@ -46,5 +47,7 @@ private: Glib::Dispatcher m_open_dispatcher; Glib::Dispatcher m_close_dispatcher; - std::atomic m_close_code; + ix::WebSocketCloseInfo m_close_info; + + std::shared_ptr m_log; }; -- cgit v1.2.3 From 4740965f4c1ee879a733b4068f4026a0dfa626fd Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Tue, 3 Jan 2023 19:01:33 -0500 Subject: rewrite DiscordVoiceClient and stuff --- src/components/channels.cpp | 4 +- src/discord/discord.cpp | 8 +- src/discord/discord.hpp | 4 +- src/discord/voiceclient.cpp | 326 ++++++++++++++++++++++---------------------- src/discord/voiceclient.hpp | 63 +++++---- src/discord/websocket.cpp | 17 ++- src/discord/websocket.hpp | 2 +- 7 files changed, 214 insertions(+), 210 deletions(-) (limited to 'src/discord/discord.cpp') diff --git a/src/components/channels.cpp b/src/components/channels.cpp index a36b215..09e40c3 100644 --- a/src/components/channels.cpp +++ b/src/components/channels.cpp @@ -1019,7 +1019,7 @@ void ChannelList::OnVoiceChannelSubmenuPopup() { if (!iter) return; const auto id = static_cast((*iter)[m_columns.m_id]); auto &discord = Abaddon::Get().GetDiscordClient(); - if (discord.IsConnectedToVoice()) { + if (discord.IsVoiceConnected() || discord.IsVoiceConnecting()) { m_menu_voice_channel_join.set_sensitive(false); m_menu_voice_channel_disconnect.set_sensitive(discord.GetVoiceChannelID() == id); } else { @@ -1040,7 +1040,7 @@ void ChannelList::OnDMSubmenuPopup() { m_menu_dm_toggle_mute.set_label("Mute"); #ifdef WITH_VOICE - if (discord.IsConnectedToVoice()) { + if (discord.IsVoiceConnected() || discord.IsVoiceConnecting()) { m_menu_dm_join_voice.set_sensitive(false); m_menu_dm_disconnect_voice.set_sensitive(discord.GetVoiceChannelID() == id); } else { diff --git a/src/discord/discord.cpp b/src/discord/discord.cpp index cd16aa8..c068243 100644 --- a/src/discord/discord.cpp +++ b/src/discord/discord.cpp @@ -1194,10 +1194,14 @@ void DiscordClient::DisconnectFromVoice() { m_websocket.Send(m); } -bool DiscordClient::IsConnectedToVoice() const noexcept { +bool DiscordClient::IsVoiceConnected() const noexcept { return m_voice.IsConnected(); } +bool DiscordClient::IsVoiceConnecting() const noexcept { + return m_voice.IsConnecting(); +} + Snowflake DiscordClient::GetVoiceChannelID() const noexcept { return m_voice_channel_id; } @@ -2174,7 +2178,7 @@ void DiscordClient::HandleGatewayVoiceStateUpdate(const GatewayMessage &msg) { m_voice.SetSessionID(data.SessionID); // channel_id = null means disconnect. stop cuz out of order maybe - if (!data.ChannelID.has_value()) { + if (!data.ChannelID.has_value() && (m_voice.IsConnected() || m_voice.IsConnecting())) { m_voice.Stop(); } } else { diff --git a/src/discord/discord.hpp b/src/discord/discord.hpp index 2d8db91..1e500e9 100644 --- a/src/discord/discord.hpp +++ b/src/discord/discord.hpp @@ -183,7 +183,9 @@ public: #ifdef WITH_VOICE void ConnectToVoice(Snowflake channel_id); void DisconnectFromVoice(); - [[nodiscard]] bool IsConnectedToVoice() const noexcept; + // Is fully connected + [[nodiscard]] bool IsVoiceConnected() const noexcept; + [[nodiscard]] bool IsVoiceConnecting() const noexcept; [[nodiscard]] Snowflake GetVoiceChannelID() const noexcept; [[nodiscard]] std::unordered_set GetUsersInVoiceChannel(Snowflake channel_id); [[nodiscard]] std::optional GetSSRCOfUser(Snowflake id) const; diff --git a/src/discord/voiceclient.cpp b/src/discord/voiceclient.cpp index 765c2af..fe72f87 100644 --- a/src/discord/voiceclient.cpp +++ b/src/discord/voiceclient.cpp @@ -17,8 +17,8 @@ #endif // clang-format on -UDPSocket::UDPSocket() { - m_socket = socket(AF_INET, SOCK_DGRAM, 0); +UDPSocket::UDPSocket() + : m_socket(INVALID_SOCKET) { } UDPSocket::~UDPSocket() { @@ -30,6 +30,7 @@ void UDPSocket::Connect(std::string_view ip, uint16_t port) { m_server.sin_family = AF_INET; S_ADDR(m_server) = inet_addr(ip.data()); m_server.sin_port = htons(port); + m_socket = socket(AF_INET, SOCK_DGRAM, 0); bind(m_socket, reinterpret_cast(&m_server), sizeof(m_server)); } @@ -94,6 +95,11 @@ std::vector UDPSocket::Receive() { } void UDPSocket::Stop() { + #ifdef _WIN32 + closesocket(m_socket); + #else + close(m_socket); + #endif m_running = false; if (m_thread.joinable()) m_thread.join(); } @@ -125,97 +131,62 @@ UDPSocket::type_signal_data UDPSocket::signal_data() { return m_signal_data; } -DiscordVoiceClient::DiscordVoiceClient() { +DiscordVoiceClient::DiscordVoiceClient() + : m_state(State::DisconnectedByClient) + , m_ws("voice-ws") + , m_log(spdlog::get("voice")) { if (sodium_init() == -1) { - spdlog::get("voice")->critical("sodium_init() failed"); + m_log->critical("sodium_init() failed"); } m_udp.signal_data().connect([this](const std::vector &data) { OnUDPData(data); }); - m_dispatcher.connect([this]() { - m_dispatch_mutex.lock(); - if (m_message_queue.empty()) { - m_dispatch_mutex.unlock(); - return; - } - auto msg = std::move(m_message_queue.front()); - m_message_queue.pop(); - m_dispatch_mutex.unlock(); - OnGatewayMessage(msg); - }); + m_ws.signal_open().connect(sigc::mem_fun(*this, &DiscordVoiceClient::OnWebsocketOpen)); + m_ws.signal_close().connect(sigc::mem_fun(*this, &DiscordVoiceClient::OnWebsocketClose)); + m_ws.signal_message().connect(sigc::mem_fun(*this, &DiscordVoiceClient::OnWebsocketMessage)); + + m_dispatcher.connect(sigc::mem_fun(*this, &DiscordVoiceClient::OnDispatch)); + // idle or else singleton deadlock Glib::signal_idle().connect_once([this]() { - // cant put in ctor or deadlock in singleton initialization - auto &aud = Abaddon::Get().GetAudio(); - aud.SetOpusBuffer(m_opus_buffer.data()); - aud.signal_opus_packet().connect([this](int payload_size) { - if (m_connected) - m_udp.SendEncrypted(m_opus_buffer.data(), payload_size); + auto &audio = Abaddon::Get().GetAudio(); + audio.SetOpusBuffer(m_opus_buffer.data()); + audio.signal_opus_packet().connect([this](int payload_size) { + // SendEncrypted if udp is connected }); }); } DiscordVoiceClient::~DiscordVoiceClient() { - Stop(); + if (IsConnected() || IsConnecting()) Stop(); } void DiscordVoiceClient::Start() { - m_ws = std::make_unique("voice-ws"); - - m_ws->signal_open().connect([this]() { - spdlog::get("voice")->info("Websocket open"); - SetState(State::Opened); - }); - - m_ws->signal_close().connect([this](const ix::WebSocketCloseInfo &info) { - if (info.remote) { - SetState(State::ClosingByServer); - } - Stop(); - }); - - m_ws->signal_message().connect([this](const std::string &str) { - spdlog::get("voice-ws")->trace("Recv: {}", str); - std::lock_guard _(m_dispatch_mutex); - m_message_queue.push(str); - m_dispatcher.emit(); - }); - - SetState(State::Opening); - m_ws->StartConnection("wss://" + m_endpoint + "/?v=7"); + SetState(State::ConnectingToWebsocket); m_heartbeat_waiter.revive(); m_keepalive_waiter.revive(); - m_connected = true; + m_ws.StartConnection("wss://" + m_endpoint + "/?v=7"); + m_signal_connected.emit(); } void DiscordVoiceClient::Stop() { - if (IsOpening() || IsOpened()) { - spdlog::get("voice")->debug("Requested voice client stop"); - SetState(State::ClosingByClient); - m_ws->Stop(); - } else if (IsClosing()) { - spdlog::get("voice")->debug("Completing stop in closing"); - if (m_state == State::ClosingByClient) { - SetState(State::ClosedByClient); - } else if (m_state == State::ClosingByServer) { - SetState(State::ClosedByServer); - } - m_ws->Stop(); - m_udp.Stop(); - m_heartbeat_waiter.kill(); - if (m_heartbeat_thread.joinable()) m_heartbeat_thread.join(); - m_keepalive_waiter.kill(); - if (m_keepalive_thread.joinable()) m_keepalive_thread.join(); - if (m_connected) { - m_connected = false; - m_signal_disconnected.emit(); - } - } else { - spdlog::get("voice")->debug("Stop called, but already stopped"); + if (!IsConnected() && !IsConnecting()) { + m_log->warn("Requested stop while not connected (from {})", GetStateName(m_state)); + return; } + + SetState(State::DisconnectedByClient); + m_ws.Stop(4014); + m_udp.Stop(); + m_heartbeat_waiter.kill(); + if (m_heartbeat_thread.joinable()) m_heartbeat_thread.join(); + m_keepalive_waiter.kill(); + if (m_keepalive_thread.joinable()) m_keepalive_thread.join(); + + m_signal_disconnected.emit(); } void DiscordVoiceClient::SetSessionID(std::string_view session_id) { @@ -239,37 +210,59 @@ void DiscordVoiceClient::SetUserID(Snowflake id) { } std::optional DiscordVoiceClient::GetSSRCOfUser(Snowflake id) const { - if (const auto it = m_ssrc_map.find(id); it != m_ssrc_map.end()) { - return it->second; - } - return {}; + return std::nullopt; } bool DiscordVoiceClient::IsConnected() const noexcept { - return m_connected; + return m_state == State::Connected; +} + +bool DiscordVoiceClient::IsConnecting() const noexcept { + return m_state == State::ConnectingToWebsocket || m_state == State::EstablishingConnection; } void DiscordVoiceClient::OnGatewayMessage(const std::string &str) { VoiceGatewayMessage msg = nlohmann::json::parse(str); switch (msg.Opcode) { - case VoiceGatewayOp::Hello: { + case VoiceGatewayOp::Hello: HandleGatewayHello(msg); - } break; - case VoiceGatewayOp::Ready: { + break; + case VoiceGatewayOp::Ready: HandleGatewayReady(msg); - } break; - case VoiceGatewayOp::SessionDescription: { + break; + case VoiceGatewayOp::SessionDescription: HandleGatewaySessionDescription(msg); - } break; - case VoiceGatewayOp::Speaking: { + break; + case VoiceGatewayOp::Speaking: HandleGatewaySpeaking(msg); - } break; - default: break; + break; + default: + m_log->warn("Unhandled opcode: {}", static_cast(msg.Opcode)); + } +} + +const char *DiscordVoiceClient::GetStateName(State state) { + switch (state) { + case State::DisconnectedByClient: + return "DisconnectedByClient"; + case State::DisconnectedByServer: + return "DisconnectedByServer"; + case State::ConnectingToWebsocket: + return "ConnectingToWebsocket"; + case State::EstablishingConnection: + return "EstablishingConnection"; + case State::Connected: + return "Connected"; + default: + return "Unknown"; } } void DiscordVoiceClient::HandleGatewayHello(const VoiceGatewayMessage &m) { VoiceHelloData d = m.Data; + + m_log->debug("Received hello: {}ms", d.HeartbeatInterval); + m_heartbeat_msec = d.HeartbeatInterval; m_heartbeat_thread = std::thread(&DiscordVoiceClient::HeartbeatThread, this); @@ -278,13 +271,16 @@ void DiscordVoiceClient::HandleGatewayHello(const VoiceGatewayMessage &m) { void DiscordVoiceClient::HandleGatewayReady(const VoiceGatewayMessage &m) { VoiceReadyData d = m.Data; + + m_log->debug("Received ready: {}:{} (ssrc: {})", d.IP, d.Port, d.SSRC); + m_ip = d.IP; m_port = d.Port; m_ssrc = d.SSRC; + if (std::find(d.Modes.begin(), d.Modes.end(), "xsalsa20_poly1305") == d.Modes.end()) { - spdlog::get("voice")->error("xsalsa20_poly1305 not in encryption modes"); + m_log->warn("xsalsa20_poly1305 not in modes"); } - spdlog::get("voice")->info("connect to {}:{} ssrc {}", m_ip, m_port, m_ssrc); m_udp.Connect(m_ip, m_port); m_keepalive_thread = std::thread(&DiscordVoiceClient::KeepaliveThread, this); @@ -294,29 +290,26 @@ void DiscordVoiceClient::HandleGatewayReady(const VoiceGatewayMessage &m) { void DiscordVoiceClient::HandleGatewaySessionDescription(const VoiceGatewayMessage &m) { VoiceSessionDescriptionData d = m.Data; - spdlog::get("voice")->debug("receiving with {}, secret key: {:ns}", d.Mode, spdlog::to_hex(std::begin(d.SecretKey), std::end(d.SecretKey))); + + m_log->debug("Received session description (mode: {}) (key: {:ns}) ", d.Mode, spdlog::to_hex(d.SecretKey.begin(), d.SecretKey.end())); VoiceSpeakingMessage msg; msg.Delay = 0; msg.SSRC = m_ssrc; msg.Speaking = VoiceSpeakingType::Microphone; - m_ws->Send(msg); + m_ws.Send(msg); m_secret_key = d.SecretKey; m_udp.SetSSRC(m_ssrc); m_udp.SetSecretKey(m_secret_key); m_udp.SendEncrypted({ 0xF8, 0xFF, 0xFE }); - m_udp.SendEncrypted({ 0xF8, 0xFF, 0xFE }); - m_udp.SendEncrypted({ 0xF8, 0xFF, 0xFE }); - m_udp.SendEncrypted({ 0xF8, 0xFF, 0xFE }); - m_udp.SendEncrypted({ 0xF8, 0xFF, 0xFE }); m_udp.Run(); } void DiscordVoiceClient::HandleGatewaySpeaking(const VoiceGatewayMessage &m) { - VoiceSpeakingData data = m.Data; - m_ssrc_map[data.UserID] = data.SSRC; - m_signal_speaking.emit(data); + VoiceSpeakingData d = m.Data; + // ssrc map + m_signal_speaking.emit(d); } void DiscordVoiceClient::Identify() { @@ -326,80 +319,92 @@ void DiscordVoiceClient::Identify() { msg.SessionID = m_session_id; msg.Token = m_token; msg.Video = true; - m_ws->Send(msg); + + m_ws.Send(msg); } void DiscordVoiceClient::Discovery() { std::vector payload; - // 2 bytes = 1, request + // request payload.push_back(0x00); payload.push_back(0x01); - // 2 bytes = 70, pl length + // payload length (70) payload.push_back(0x00); - payload.push_back(70); - // 4 bytes = ssrc + payload.push_back(0x46); + // ssrc payload.push_back((m_ssrc >> 24) & 0xFF); payload.push_back((m_ssrc >> 16) & 0xFF); payload.push_back((m_ssrc >> 8) & 0xFF); payload.push_back((m_ssrc >> 0) & 0xFF); - // address and port - for (int i = 0; i < 66; i++) - payload.push_back(0); + // space for address and port + for (int i = 0; i < 66; i++) payload.push_back(0x00); + m_udp.Send(payload.data(), payload.size()); - auto response = m_udp.Receive(); - if (response.size() >= 74 && response[0] == 0x00 && response[1] == 0x02) { - const char *our_ip = reinterpret_cast(&response[8]); - uint16_t our_port = (response[73] << 8) | response[74]; - spdlog::get("voice")->debug("IP address discovered: {}:{}\n", our_ip, our_port); - SelectProtocol(our_ip, our_port); - } else { - spdlog::get("voice")->error("Received non-discovery packet after discovery"); + + constexpr int MAX_TRIES = 100; + for (int i = 0; i < MAX_TRIES; i++) { + const auto response = m_udp.Receive(); + if (response.size() >= 74 && response[0] == 0x00 && response[1] == 0x02) { + const char *ip = reinterpret_cast(response.data() + 8); + uint16_t port = (response[73] << 8) | response[74]; + m_log->info("Discovered IP and port: {}:{}", ip, port); + SelectProtocol(ip, port); + break; + } else { + m_log->error("Received non-discovery packet after sending request (try {}/{})", i + 1, MAX_TRIES); + } } } -void DiscordVoiceClient::SelectProtocol(std::string_view ip, uint16_t port) { +void DiscordVoiceClient::SelectProtocol(const char *ip, uint16_t port) { VoiceSelectProtocolMessage msg; msg.Mode = "xsalsa20_poly1305"; msg.Address = ip; msg.Port = port; msg.Protocol = "udp"; - m_ws->Send(msg); + + m_ws.Send(msg); } -void DiscordVoiceClient::OnUDPData(std::vector data) { - uint8_t *payload = data.data() + 12; - uint32_t ssrc = (data[8] << 24) | - (data[9] << 16) | - (data[10] << 8) | - (data[11] << 0); - static std::array nonce = {}; - std::memcpy(nonce.data(), data.data(), 12); - if (crypto_secretbox_open_easy(payload, payload, data.size() - 12, nonce.data(), m_secret_key.data())) { - // spdlog::get("voice")->trace("UDP payload decryption failure"); +void DiscordVoiceClient::OnWebsocketOpen() { + m_log->info("Websocket opened"); + SetState(State::EstablishingConnection); +} + +void DiscordVoiceClient::OnWebsocketClose(const ix::WebSocketCloseInfo &info) { + if (info.remote) { + m_log->debug("Websocket closed (remote): {} ({})", info.code, info.reason); } else { - Abaddon::Get().GetAudio().FeedMeOpus(ssrc, { payload, payload + data.size() - 12 - crypto_box_MACBYTES }); + m_log->debug("Websocket closed (local): {} ({})", info.code, info.reason); } } +void DiscordVoiceClient::OnWebsocketMessage(const std::string &data) { + m_dispatch_mutex.lock(); + m_dispatch_queue.push(data); + m_dispatcher.emit(); + m_dispatch_mutex.unlock(); +} + void DiscordVoiceClient::HeartbeatThread() { while (true) { - if (!m_heartbeat_waiter.wait_for(std::chrono::milliseconds(m_heartbeat_msec))) - break; + if (!m_heartbeat_waiter.wait_for(std::chrono::milliseconds(m_heartbeat_msec))) break; + + const auto ms = static_cast(std::chrono::duration_cast( + std::chrono::system_clock::now().time_since_epoch()) + .count()); - const auto ms = std::chrono::duration_cast( - std::chrono::system_clock::now().time_since_epoch()) - .count(); + m_log->trace("Heartbeat: {}", ms); VoiceHeartbeatMessage msg; - msg.Nonce = static_cast(ms); - m_ws->Send(msg); + msg.Nonce = ms; + m_ws.Send(msg); } } void DiscordVoiceClient::KeepaliveThread() { while (true) { - if (!m_keepalive_waiter.wait_for(std::chrono::seconds(10))) - break; + if (!m_heartbeat_waiter.wait_for(std::chrono::seconds(10))) break; if (IsConnected()) { const static uint8_t KEEPALIVE[] = { 0x13, 0x37 }; @@ -409,44 +414,35 @@ void DiscordVoiceClient::KeepaliveThread() { } void DiscordVoiceClient::SetState(State state) { + m_log->debug("Changing state to {}", GetStateName(state)); m_state = state; - - switch (state) { - case State::Opening: - spdlog::get("voice")->debug("WS state: Opening"); - break; - case State::Opened: - spdlog::get("voice")->debug("WS state: Opened"); - break; - case State::ClosingByClient: - spdlog::get("voice")->debug("WS state: Closing (Client)"); - break; - case State::ClosingByServer: - spdlog::get("voice")->debug("WS state: Closing (Server)"); - break; - case State::ClosedByClient: - spdlog::get("voice")->debug("WS state: Closed (Client)"); - break; - case State::ClosedByServer: - spdlog::get("voice")->debug("WS state: Closed (Server)"); - break; - } -} - -bool DiscordVoiceClient::IsOpening() const noexcept { - return m_state == State::Opening; } -bool DiscordVoiceClient::IsOpened() const noexcept { - return m_state == State::Opened; -} - -bool DiscordVoiceClient::IsClosing() const noexcept { - return m_state == State::ClosingByClient || m_state == State::ClosingByServer; +void DiscordVoiceClient::OnUDPData(std::vector data) { + uint8_t *payload = data.data() + 12; + uint32_t ssrc = (data[8] << 24) | + (data[9] << 16) | + (data[10] << 8) | + (data[11] << 0); + static std::array nonce = {}; + std::memcpy(nonce.data(), data.data(), 12); + if (crypto_secretbox_open_easy(payload, payload, data.size() - 12, nonce.data(), m_secret_key.data())) { + // spdlog::get("voice")->trace("UDP payload decryption failure"); + } else { + Abaddon::Get().GetAudio().FeedMeOpus(ssrc, { payload, payload + data.size() - 12 - crypto_box_MACBYTES }); + } } -bool DiscordVoiceClient::IsClosed() const noexcept { - return m_state == State::ClosedByClient || m_state == State::ClosedByServer; +void DiscordVoiceClient::OnDispatch() { + m_dispatch_mutex.lock(); + if (m_dispatch_queue.empty()) { + m_dispatch_mutex.unlock(); + return; + } + auto msg = std::move(m_dispatch_queue.front()); + m_dispatch_queue.pop(); + m_dispatch_mutex.unlock(); + OnGatewayMessage(msg); } DiscordVoiceClient::type_signal_disconnected DiscordVoiceClient::signal_connected() { diff --git a/src/discord/voiceclient.hpp b/src/discord/voiceclient.hpp index 61b329c..47caec6 100644 --- a/src/discord/voiceclient.hpp +++ b/src/discord/voiceclient.hpp @@ -11,6 +11,7 @@ #include #include #include +#include #include // clang-format on @@ -196,10 +197,21 @@ public: [[nodiscard]] std::optional GetSSRCOfUser(Snowflake id) const; + // Is a websocket and udp connection fully established [[nodiscard]] bool IsConnected() const noexcept; + [[nodiscard]] bool IsConnecting() const noexcept; private: - void OnGatewayMessage(const std::string &str); + enum class State { + ConnectingToWebsocket, + EstablishingConnection, + Connected, + DisconnectedByClient, + DisconnectedByServer, + }; + static const char *GetStateName(State state); + + void OnGatewayMessage(const std::string &msg); void HandleGatewayHello(const VoiceGatewayMessage &m); void HandleGatewayReady(const VoiceGatewayMessage &m); void HandleGatewaySessionDescription(const VoiceGatewayMessage &m); @@ -207,13 +219,19 @@ private: void Identify(); void Discovery(); - void SelectProtocol(std::string_view ip, uint16_t port); + void SelectProtocol(const char *ip, uint16_t port); - void OnUDPData(std::vector data); + void OnWebsocketOpen(); + void OnWebsocketClose(const ix::WebSocketCloseInfo &info); + void OnWebsocketMessage(const std::string &str); void HeartbeatThread(); void KeepaliveThread(); + void SetState(State state); + + void OnUDPData(std::vector data); + std::string m_session_id; std::string m_endpoint; std::string m_token; @@ -221,24 +239,12 @@ private: Snowflake m_channel_id; Snowflake m_user_id; + std::array m_secret_key; + std::string m_ip; uint16_t m_port; uint32_t m_ssrc; - std::unordered_map m_ssrc_map; - - std::array m_secret_key; - - // this is a unique_ptr because Websocket/ixwebsocket seems to have some strange behavior - // and quite frankly i do not feel like figuring out what is wrong - // so using a unique_ptr will just let me nuke the whole thing and make a new one - std::unique_ptr m_ws; - UDPSocket m_udp; - - Glib::Dispatcher m_dispatcher; - std::queue m_message_queue; - std::mutex m_dispatch_mutex; - int m_heartbeat_msec; Waiter m_heartbeat_waiter; std::thread m_heartbeat_thread; @@ -246,25 +252,18 @@ private: Waiter m_keepalive_waiter; std::thread m_keepalive_thread; - std::array m_opus_buffer; + Websocket m_ws; + UDPSocket m_udp; - std::atomic m_connected = false; + Glib::Dispatcher m_dispatcher; + std::queue m_dispatch_queue; + std::mutex m_dispatch_mutex; - enum class State { - Opening, - Opened, - ClosingByClient, - ClosedByClient, - ClosingByServer, - ClosedByServer, - }; + void OnDispatch(); - void SetState(State state); + std::array m_opus_buffer; - [[nodiscard]] bool IsOpening() const noexcept; - [[nodiscard]] bool IsOpened() const noexcept; - [[nodiscard]] bool IsClosing() const noexcept; - [[nodiscard]] bool IsClosed() const noexcept; + std::shared_ptr m_log; std::atomic m_state; diff --git a/src/discord/websocket.cpp b/src/discord/websocket.cpp index 349913a..d40d057 100644 --- a/src/discord/websocket.cpp +++ b/src/discord/websocket.cpp @@ -20,11 +20,14 @@ Websocket::Websocket(const std::string &id) void Websocket::StartConnection(const std::string &url) { m_log->debug("Starting connection to {}", url); - m_websocket.disableAutomaticReconnection(); - m_websocket.setUrl(url); - m_websocket.setOnMessageCallback([this](auto &&msg) { OnMessage(std::forward(msg)); }); - m_websocket.setExtraHeaders(ix::WebSocketHttpHeaders { { "User-Agent", m_agent } }); // idk if this actually works - m_websocket.start(); + + m_websocket = std::make_unique(); + + m_websocket->disableAutomaticReconnection(); + m_websocket->setUrl(url); + m_websocket->setOnMessageCallback([this](auto &&msg) { OnMessage(std::forward(msg)); }); + m_websocket->setExtraHeaders(ix::WebSocketHttpHeaders { { "User-Agent", m_agent } }); // idk if this actually works + m_websocket->start(); } void Websocket::SetUserAgent(std::string agent) { @@ -46,13 +49,13 @@ void Websocket::Stop() { void Websocket::Stop(uint16_t code) { m_log->debug("Stopping with close code {}", code); - m_websocket.stop(code); + m_websocket-> stop(code); } void Websocket::Send(const std::string &str) { if (m_print_messages) m_log->trace("Send: {}", str); - m_websocket.sendText(str); + m_websocket->sendText(str); } void Websocket::Send(const nlohmann::json &j) { diff --git a/src/discord/websocket.hpp b/src/discord/websocket.hpp index 768121e..a77bf55 100644 --- a/src/discord/websocket.hpp +++ b/src/discord/websocket.hpp @@ -26,7 +26,7 @@ public: private: void OnMessage(const ix::WebSocketMessagePtr &msg); - ix::WebSocket m_websocket; + std::unique_ptr m_websocket; std::string m_agent; public: -- cgit v1.2.3 From c4590f8b2382fb23a733d2cf2f6c645f02e2fc48 Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Tue, 3 Jan 2023 22:52:41 -0500 Subject: start voice info box --- res/css/main.css | 18 +++++++++ src/components/voiceinfobox.cpp | 89 +++++++++++++++++++++++++++++++++++++++++ src/components/voiceinfobox.hpp | 19 +++++++++ src/discord/discord.cpp | 17 ++++++++ src/discord/discord.hpp | 9 +++++ src/discord/voiceclient.cpp | 5 +++ src/discord/voiceclient.hpp | 6 ++- src/windows/mainwindow.cpp | 13 ++++-- src/windows/mainwindow.hpp | 4 ++ 9 files changed, 176 insertions(+), 4 deletions(-) create mode 100644 src/components/voiceinfobox.cpp create mode 100644 src/components/voiceinfobox.hpp (limited to 'src/discord/discord.cpp') diff --git a/res/css/main.css b/res/css/main.css index ace3b0b..4b76bb4 100644 --- a/res/css/main.css +++ b/res/css/main.css @@ -360,3 +360,21 @@ background-color: #dd3300; margin-left: 1px; } + +.voice-info { + background-color: #0B0B0B; + padding: 5px; + border: 1px solid #202020; +} + +.voice-info-disconnect-image { + color: #DDDDDD; +} + +.voice-info-status { + font-weight: bold; +} + +.voice-info-location { + +} diff --git a/src/components/voiceinfobox.cpp b/src/components/voiceinfobox.cpp new file mode 100644 index 0000000..f4dc6ed --- /dev/null +++ b/src/components/voiceinfobox.cpp @@ -0,0 +1,89 @@ +#include "voiceinfobox.hpp" +#include "abaddon.hpp" +#include "util.hpp" + +VoiceInfoBox::VoiceInfoBox() + : Gtk::Box(Gtk::ORIENTATION_HORIZONTAL) + , m_left(Gtk::ORIENTATION_VERTICAL) { + m_disconnect_ev.signal_button_press_event().connect([this](GdkEventButton *ev) -> bool { + if (ev->type == GDK_BUTTON_PRESS && ev->button == GDK_BUTTON_PRIMARY) { + spdlog::get("discord")->debug("Request disconnect from info box"); + Abaddon::Get().GetDiscordClient().DisconnectFromVoice(); + return true; + } + + return false; + }); + + AddPointerCursor(m_disconnect_ev); + + get_style_context()->add_class("voice-info"); + m_status.get_style_context()->add_class("voice-info-status"); + m_location.get_style_context()->add_class("voice-info-location"); + m_disconnect_img.get_style_context()->add_class("voice-info-disconnect-image"); + + m_status.set_label("You shouldn't see me"); + m_location.set_label("You shouldn't see me"); + + Abaddon::Get().GetDiscordClient().signal_voice_requested_connect().connect([this](Snowflake channel_id) { + show(); + + if (const auto channel = Abaddon::Get().GetDiscordClient().GetChannel(channel_id); channel.has_value() && channel->Name.has_value()) { + if (channel->GuildID.has_value()) { + if (const auto guild = Abaddon::Get().GetDiscordClient().GetGuild(*channel->GuildID); guild.has_value()) { + m_location.set_label(*channel->Name + " / " + guild->Name); + return; + } + } + + m_location.set_label(*channel->Name); + return; + } + + m_location.set_label("Unknown"); + }); + + Abaddon::Get().GetDiscordClient().signal_voice_requested_disconnect().connect([this]() { + hide(); + }); + + Abaddon::Get().GetDiscordClient().signal_voice_client_state_update().connect([this](DiscordVoiceClient::State state) { + Glib::ustring label; + switch (state) { + case DiscordVoiceClient::State::ConnectingToWebsocket: + label = "Connecting"; + break; + case DiscordVoiceClient::State::EstablishingConnection: + label = "Establishing connection"; + break; + case DiscordVoiceClient::State::Connected: + label = "Connected"; + break; + case DiscordVoiceClient::State::DisconnectedByServer: + case DiscordVoiceClient::State::DisconnectedByClient: + label = "Disconnected"; + break; + default: + label = "Unknown"; + break; + } + m_status.set_label(label); + }); + + m_status.set_ellipsize(Pango::ELLIPSIZE_END); + m_location.set_ellipsize(Pango::ELLIPSIZE_END); + + m_disconnect_ev.add(m_disconnect_img); + m_disconnect_img.property_icon_name() = "call-stop-symbolic"; + m_disconnect_img.property_icon_size() = 5; + m_disconnect_img.set_hexpand(true); + m_disconnect_img.set_halign(Gtk::ALIGN_END); + + m_left.add(m_status); + m_left.add(m_location); + + add(m_left); + add(m_disconnect_ev); + + show_all_children(); +} diff --git a/src/components/voiceinfobox.hpp b/src/components/voiceinfobox.hpp new file mode 100644 index 0000000..e0bc4e8 --- /dev/null +++ b/src/components/voiceinfobox.hpp @@ -0,0 +1,19 @@ +#pragma once + +#include +#include +#include +#include + +class VoiceInfoBox : public Gtk::Box { +public: + VoiceInfoBox(); + +private: + Gtk::Box m_left; + Gtk::Label m_status; + Gtk::Label m_location; + + Gtk::EventBox m_disconnect_ev; + Gtk::Image m_disconnect_img; +}; diff --git a/src/discord/discord.cpp b/src/discord/discord.cpp index c068243..44f64a9 100644 --- a/src/discord/discord.cpp +++ b/src/discord/discord.cpp @@ -31,6 +31,9 @@ DiscordClient::DiscordClient(bool mem_store) m_voice.signal_speaking().connect([this](const VoiceSpeakingData &data) { m_signal_voice_speaking.emit(data); }); + m_voice.signal_state_update().connect([this](DiscordVoiceClient::State state) { + m_signal_voice_client_state_update.emit(state); + }); #endif LoadEventMap(); @@ -1186,12 +1189,14 @@ void DiscordClient::ConnectToVoice(Snowflake channel_id) { m.ChannelID = channel_id; m.PreferredRegion = "newark"; m_websocket.Send(m); + m_signal_voice_requested_connect.emit(channel_id); } void DiscordClient::DisconnectFromVoice() { m_voice.Stop(); VoiceStateUpdateMessage m; m_websocket.Send(m); + m_signal_voice_requested_disconnect.emit(); } bool DiscordClient::IsVoiceConnected() const noexcept { @@ -2995,4 +3000,16 @@ DiscordClient::type_signal_voice_user_disconnect DiscordClient::signal_voice_use DiscordClient::type_signal_voice_user_connect DiscordClient::signal_voice_user_connect() { return m_signal_voice_user_connect; } + +DiscordClient::type_signal_voice_requested_connect DiscordClient::signal_voice_requested_connect() { + return m_signal_voice_requested_connect; +} + +DiscordClient::type_signal_voice_requested_disconnect DiscordClient::signal_voice_requested_disconnect() { + return m_signal_voice_requested_disconnect; +} + +DiscordClient::type_signal_voice_client_state_update DiscordClient::signal_voice_client_state_update() { + return m_signal_voice_client_state_update; +} #endif diff --git a/src/discord/discord.hpp b/src/discord/discord.hpp index 1e500e9..a7e3f79 100644 --- a/src/discord/discord.hpp +++ b/src/discord/discord.hpp @@ -440,6 +440,9 @@ public: using type_signal_voice_speaking = sigc::signal; using type_signal_voice_user_disconnect = sigc::signal; using type_signal_voice_user_connect = sigc::signal; + using type_signal_voice_requested_connect = sigc::signal; + using type_signal_voice_requested_disconnect = sigc::signal; + using type_signal_voice_client_state_update = sigc::signal; #endif type_signal_gateway_ready signal_gateway_ready(); @@ -502,6 +505,9 @@ public: type_signal_voice_speaking signal_voice_speaking(); type_signal_voice_user_disconnect signal_voice_user_disconnect(); type_signal_voice_user_connect signal_voice_user_connect(); + type_signal_voice_requested_connect signal_voice_requested_connect(); + type_signal_voice_requested_disconnect signal_voice_requested_disconnect(); + type_signal_voice_client_state_update signal_voice_client_state_update(); #endif protected: @@ -565,5 +571,8 @@ protected: type_signal_voice_speaking m_signal_voice_speaking; type_signal_voice_user_disconnect m_signal_voice_user_disconnect; type_signal_voice_user_connect m_signal_voice_user_connect; + type_signal_voice_requested_connect m_signal_voice_requested_connect; + type_signal_voice_requested_disconnect m_signal_voice_requested_disconnect; + type_signal_voice_client_state_update m_signal_voice_client_state_update; #endif }; diff --git a/src/discord/voiceclient.cpp b/src/discord/voiceclient.cpp index 379a65c..ed83e3c 100644 --- a/src/discord/voiceclient.cpp +++ b/src/discord/voiceclient.cpp @@ -423,6 +423,7 @@ void DiscordVoiceClient::KeepaliveThread() { void DiscordVoiceClient::SetState(State state) { m_log->debug("Changing state to {}", GetStateName(state)); m_state = state; + m_signal_state_update.emit(state); } void DiscordVoiceClient::OnUDPData(std::vector data) { @@ -464,6 +465,10 @@ DiscordVoiceClient::type_signal_speaking DiscordVoiceClient::signal_speaking() { return m_signal_speaking; } +DiscordVoiceClient::type_signal_state_update DiscordVoiceClient::signal_state_update() { + return m_signal_state_update; +} + void from_json(const nlohmann::json &j, VoiceGatewayMessage &m) { JS_D("op", m.Opcode); m.Data = j.at("d"); diff --git a/src/discord/voiceclient.hpp b/src/discord/voiceclient.hpp index 7e4dee3..916d070 100644 --- a/src/discord/voiceclient.hpp +++ b/src/discord/voiceclient.hpp @@ -201,7 +201,6 @@ public: [[nodiscard]] bool IsConnected() const noexcept; [[nodiscard]] bool IsConnecting() const noexcept; -private: enum class State { ConnectingToWebsocket, EstablishingConnection, @@ -209,6 +208,8 @@ private: DisconnectedByClient, DisconnectedByServer, }; + +private: static const char *GetStateName(State state); void OnGatewayMessage(const std::string &msg); @@ -272,13 +273,16 @@ private: using type_signal_connected = sigc::signal; using type_signal_disconnected = sigc::signal; using type_signal_speaking = sigc::signal; + using type_signal_state_update = sigc::signal; type_signal_connected m_signal_connected; type_signal_disconnected m_signal_disconnected; type_signal_speaking m_signal_speaking; + type_signal_state_update m_signal_state_update; public: type_signal_connected signal_connected(); type_signal_disconnected signal_disconnected(); type_signal_speaking signal_speaking(); + type_signal_state_update signal_state_update(); }; #endif diff --git a/src/windows/mainwindow.cpp b/src/windows/mainwindow.cpp index 20da46b..c80dfda 100644 --- a/src/windows/mainwindow.cpp +++ b/src/windows/mainwindow.cpp @@ -6,6 +6,7 @@ MainWindow::MainWindow() , m_content_box(Gtk::ORIENTATION_HORIZONTAL) , m_chan_content_paned(Gtk::ORIENTATION_HORIZONTAL) , m_content_members_paned(Gtk::ORIENTATION_HORIZONTAL) + , m_left_pane(Gtk::ORIENTATION_VERTICAL) , m_accels(Gtk::AccelGroup::create()) { set_default_size(1200, 800); get_style_context()->add_class("app-window"); @@ -51,12 +52,18 @@ MainWindow::MainWindow() m_content_stack.set_visible_child("chat"); m_content_stack.show(); - m_chan_content_paned.pack1(m_channel_list); + m_voice_info.show(); + + m_left_pane.add(m_channel_list); + m_left_pane.add(m_voice_info); + m_left_pane.show(); + + m_chan_content_paned.pack1(m_left_pane); m_chan_content_paned.pack2(m_content_members_paned); m_chan_content_paned.child_property_shrink(m_content_members_paned) = true; m_chan_content_paned.child_property_resize(m_content_members_paned) = true; - m_chan_content_paned.child_property_shrink(m_channel_list) = true; - m_chan_content_paned.child_property_resize(m_channel_list) = true; + m_chan_content_paned.child_property_shrink(m_left_pane) = true; + m_chan_content_paned.child_property_resize(m_left_pane) = true; m_chan_content_paned.set_position(200); m_chan_content_paned.show(); m_content_box.add(m_chan_content_paned); diff --git a/src/windows/mainwindow.hpp b/src/windows/mainwindow.hpp index 6e95b72..d4013dc 100644 --- a/src/windows/mainwindow.hpp +++ b/src/windows/mainwindow.hpp @@ -3,6 +3,7 @@ #include "components/chatwindow.hpp" #include "components/memberlist.hpp" #include "components/friendslist.hpp" +#include "components/voiceinfobox.hpp" #include class MainWindow : public Gtk::Window { @@ -53,6 +54,9 @@ private: ChatWindow m_chat; MemberList m_members; FriendsList m_friends; + VoiceInfoBox m_voice_info; + + Gtk::Box m_left_pane; Gtk::Stack m_content_stack; -- cgit v1.2.3 From 67e924e53879409733f1f462d865ac44762c21dc Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Fri, 6 Jan 2023 18:40:11 -0500 Subject: display users in voice in channel list --- src/abaddon.cpp | 3 ++- src/components/channels.cpp | 23 ++++++++++++++-- src/components/channelscellrenderer.cpp | 47 ++++++++++++++++++++++++++++++++- src/components/channelscellrenderer.hpp | 13 +++++++++ src/discord/discord.cpp | 6 +++++ src/discord/discord.hpp | 3 +++ 6 files changed, 91 insertions(+), 4 deletions(-) (limited to 'src/discord/discord.cpp') diff --git a/src/abaddon.cpp b/src/abaddon.cpp index 67fd2bd..8b760fd 100644 --- a/src/abaddon.cpp +++ b/src/abaddon.cpp @@ -41,7 +41,8 @@ Abaddon::Abaddon() std::string ua = GetSettings().UserAgent; m_discord.SetUserAgent(ua); - m_discord.signal_gateway_ready().connect(sigc::mem_fun(*this, &Abaddon::DiscordOnReady)); + // todo rename funcs + m_discord.signal_gateway_ready_supplemental().connect(sigc::mem_fun(*this, &Abaddon::DiscordOnReady)); m_discord.signal_message_create().connect(sigc::mem_fun(*this, &Abaddon::DiscordOnMessageCreate)); m_discord.signal_message_delete().connect(sigc::mem_fun(*this, &Abaddon::DiscordOnMessageDelete)); m_discord.signal_message_update().connect(sigc::mem_fun(*this, &Abaddon::DiscordOnMessageUpdate)); diff --git a/src/components/channels.cpp b/src/components/channels.cpp index 09e40c3..dc14688 100644 --- a/src/components/channels.cpp +++ b/src/components/channels.cpp @@ -651,13 +651,30 @@ Gtk::TreeModel::iterator ChannelList::AddGuild(const GuildData &guild) { m_tmp_channel_map[thread.ID] = CreateThreadRow(row.children(), thread); }; + auto add_voice_participants = [this, &discord](const ChannelData &channel, const Gtk::TreeNodeChildren &root) { + for (auto user_id : discord.GetUsersInVoiceChannel(channel.ID)) { + const auto user = discord.GetUser(user_id); + + auto user_row = *m_model->append(root); + user_row[m_columns.m_type] = RenderType::VoiceParticipant; + user_row[m_columns.m_id] = user_id; + if (user.has_value()) { + user_row[m_columns.m_name] = user->GetEscapedName(); + } else { + user_row[m_columns.m_name] = "Unknown"; + } + } + }; + for (const auto &channel : orphan_channels) { auto channel_row = *m_model->append(guild_row.children()); if (IsTextChannel(channel.Type)) channel_row[m_columns.m_type] = RenderType::TextChannel; #ifdef WITH_VOICE - else + else { channel_row[m_columns.m_type] = RenderType::VoiceChannel; + add_voice_participants(channel, channel_row->children()); + } #endif channel_row[m_columns.m_id] = channel.ID; channel_row[m_columns.m_name] = "#" + Glib::Markup::escape_text(*channel.Name); @@ -684,8 +701,10 @@ Gtk::TreeModel::iterator ChannelList::AddGuild(const GuildData &guild) { if (IsTextChannel(channel.Type)) channel_row[m_columns.m_type] = RenderType::TextChannel; #ifdef WITH_VOICE - else + else { channel_row[m_columns.m_type] = RenderType::VoiceChannel; + add_voice_participants(channel, channel_row->children()); + } #endif channel_row[m_columns.m_id] = channel.ID; channel_row[m_columns.m_name] = "#" + Glib::Markup::escape_text(*channel.Name); diff --git a/src/components/channelscellrenderer.cpp b/src/components/channelscellrenderer.cpp index 23ee3f0..47fca4e 100644 --- a/src/components/channelscellrenderer.cpp +++ b/src/components/channelscellrenderer.cpp @@ -68,6 +68,8 @@ void CellRendererChannels::get_preferred_width_vfunc(Gtk::Widget &widget, int &m #ifdef WITH_VOICE case RenderType::VoiceChannel: 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); #endif case RenderType::DMHeader: return get_preferred_width_vfunc_dmheader(widget, minimum_width, natural_width); @@ -89,6 +91,8 @@ void CellRendererChannels::get_preferred_width_for_height_vfunc(Gtk::Widget &wid #ifdef WITH_VOICE case RenderType::VoiceChannel: 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); #endif case RenderType::DMHeader: return get_preferred_width_for_height_vfunc_dmheader(widget, height, minimum_width, natural_width); @@ -110,6 +114,8 @@ void CellRendererChannels::get_preferred_height_vfunc(Gtk::Widget &widget, int & #ifdef WITH_VOICE case RenderType::VoiceChannel: 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); #endif case RenderType::DMHeader: return get_preferred_height_vfunc_dmheader(widget, minimum_height, natural_height); @@ -131,6 +137,8 @@ void CellRendererChannels::get_preferred_height_for_width_vfunc(Gtk::Widget &wid #ifdef WITH_VOICE case RenderType::VoiceChannel: 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); #endif case RenderType::DMHeader: return get_preferred_height_for_width_vfunc_dmheader(widget, width, minimum_height, natural_height); @@ -152,6 +160,8 @@ void CellRendererChannels::render_vfunc(const Cairo::RefPtr &cr, #ifdef WITH_VOICE case RenderType::VoiceChannel: return render_vfunc_voice_channel(cr, widget, background_area, cell_area, flags); + case RenderType::VoiceParticipant: + return render_vfunc_voice_participant(cr, widget, background_area, cell_area, flags); #endif case RenderType::DMHeader: return render_vfunc_dmheader(cr, widget, background_area, cell_area, flags); @@ -519,9 +529,10 @@ void CellRendererChannels::render_vfunc_thread(const Cairo::RefPtr &cr, Gtk::Widget &widget, const Gdk::Rectangle &background_area, const Gdk::Rectangle &cell_area, Gtk::CellRendererState flags) { + Gtk::Requisition minimum_size, natural_size; + m_renderer_text.get_preferred_size(widget, minimum_size, natural_size); + + const int text_x = background_area.get_x() + 27; + const int text_y = background_area.get_y() + background_area.get_height() / 2 - natural_size.height / 2; + const int text_w = natural_size.width; + const int text_h = natural_size.height; + + Gdk::Rectangle text_cell_area(text_x, text_y, text_w, text_h); + m_renderer_text.property_foreground_rgba() = Gdk::RGBA("#f00"); + m_renderer_text.render(cr, widget, background_area, text_cell_area, flags); + m_renderer_text.property_foreground_set() = false; +} + #endif // dm header diff --git a/src/components/channelscellrenderer.hpp b/src/components/channelscellrenderer.hpp index 8e4025a..e8ba5f5 100644 --- a/src/components/channelscellrenderer.hpp +++ b/src/components/channelscellrenderer.hpp @@ -11,8 +11,10 @@ enum class RenderType : uint8_t { TextChannel, Thread, +// TODO: maybe enable anyways but without ability to join if no voice support #ifdef WITH_VOICE VoiceChannel, + VoiceParticipant, #endif DMHeader, @@ -98,6 +100,17 @@ protected: const Gdk::Rectangle &background_area, const Gdk::Rectangle &cell_area, Gtk::CellRendererState flags); + + // voice channel + void get_preferred_width_vfunc_voice_participant(Gtk::Widget &widget, int &minimum_width, int &natural_width) const; + void get_preferred_width_for_height_vfunc_voice_participant(Gtk::Widget &widget, int height, int &minimum_width, int &natural_width) const; + void get_preferred_height_vfunc_voice_participant(Gtk::Widget &widget, int &minimum_height, int &natural_height) const; + void get_preferred_height_for_width_vfunc_voice_participant(Gtk::Widget &widget, int width, int &minimum_height, int &natural_height) const; + void render_vfunc_voice_participant(const Cairo::RefPtr &cr, + Gtk::Widget &widget, + const Gdk::Rectangle &background_area, + const Gdk::Rectangle &cell_area, + Gtk::CellRendererState flags); #endif // dm header diff --git a/src/discord/discord.cpp b/src/discord/discord.cpp index 44f64a9..12ce4e8 100644 --- a/src/discord/discord.cpp +++ b/src/discord/discord.cpp @@ -2254,6 +2254,8 @@ void DiscordClient::HandleGatewayReadySupplemental(const GatewayMessage &msg) { } } #endif + + m_signal_gateway_ready_supplemental.emit(); } void DiscordClient::HandleGatewayReconnect(const GatewayMessage &msg) { @@ -2776,6 +2778,10 @@ DiscordClient::type_signal_gateway_ready DiscordClient::signal_gateway_ready() { return m_signal_gateway_ready; } +DiscordClient::type_signal_gateway_ready_supplemental DiscordClient::signal_gateway_ready_supplemental() { + return m_signal_gateway_ready_supplemental; +} + DiscordClient::type_signal_message_create DiscordClient::signal_message_create() { return m_signal_message_create; } diff --git a/src/discord/discord.hpp b/src/discord/discord.hpp index a7e3f79..fa399cc 100644 --- a/src/discord/discord.hpp +++ b/src/discord/discord.hpp @@ -379,6 +379,7 @@ private: // signals public: typedef sigc::signal type_signal_gateway_ready; + typedef sigc::signal type_signal_gateway_ready_supplemental; typedef sigc::signal type_signal_message_create; typedef sigc::signal type_signal_message_delete; typedef sigc::signal type_signal_message_update; @@ -446,6 +447,7 @@ public: #endif type_signal_gateway_ready signal_gateway_ready(); + type_signal_gateway_ready_supplemental signal_gateway_ready_supplemental(); type_signal_message_create signal_message_create(); type_signal_message_delete signal_message_delete(); type_signal_message_update signal_message_update(); @@ -512,6 +514,7 @@ public: protected: type_signal_gateway_ready m_signal_gateway_ready; + type_signal_gateway_ready_supplemental m_signal_gateway_ready_supplemental; type_signal_message_create m_signal_message_create; type_signal_message_delete m_signal_message_delete; type_signal_message_update m_signal_message_update; -- cgit v1.2.3 From 78adcf2f5c9858d241dcf6f6e48f2f64bbc63f4e Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Sat, 7 Jan 2023 20:13:57 -0500 Subject: fix voice_user_connect signal --- src/discord/discord.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'src/discord/discord.cpp') diff --git a/src/discord/discord.cpp b/src/discord/discord.cpp index 12ce4e8..2c7358b 100644 --- a/src/discord/discord.cpp +++ b/src/discord/discord.cpp @@ -2200,8 +2200,9 @@ void DiscordClient::HandleGatewayVoiceStateUpdate(const GatewayMessage &msg) { SetVoiceState(data.UserID, *data.ChannelID); if (old_state.has_value() && *old_state != *data.ChannelID) { m_signal_voice_user_disconnect.emit(data.UserID, *old_state); + } else if (!old_state.has_value()) { + m_signal_voice_user_connect.emit(data.UserID, *data.ChannelID); } - m_signal_voice_user_connect.emit(data.UserID, *data.ChannelID); } else { const auto old_state = GetVoiceState(data.UserID); ClearVoiceState(data.UserID); -- cgit v1.2.3 From ea04035f0db8fa990dd7ca8dd1a64f56bceb82e2 Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Mon, 16 Jan 2023 22:52:39 -0500 Subject: persist voice settings, handle volume w/ no ssrc --- src/abaddon.cpp | 5 ++--- src/audio/manager.cpp | 23 +++++++++++++++++++---- src/audio/manager.hpp | 3 +++ src/discord/discord.cpp | 4 ++++ src/discord/discord.hpp | 2 ++ src/discord/voiceclient.cpp | 25 +++++++++++++++++++++++++ src/discord/voiceclient.hpp | 4 ++++ src/windows/voicewindow.cpp | 21 +++++++++++++++------ 8 files changed, 74 insertions(+), 13 deletions(-) (limited to 'src/discord/discord.cpp') diff --git a/src/abaddon.cpp b/src/abaddon.cpp index 5ed17be..417d7af 100644 --- a/src/abaddon.cpp +++ b/src/abaddon.cpp @@ -474,9 +474,8 @@ void Abaddon::ShowVoiceWindow() { }); wnd->signal_user_volume_changed().connect([this](Snowflake id, double volume) { - if (const auto ssrc = m_discord.GetSSRCOfUser(id); ssrc.has_value()) { - m_audio->SetVolumeSSRC(*ssrc, volume); - } + auto &vc = m_discord.GetVoiceClient(); + vc.SetUserVolume(id, volume); }); wnd->set_position(Gtk::WIN_POS_CENTER); diff --git a/src/audio/manager.cpp b/src/audio/manager.cpp index 1a7bda5..618773d 100644 --- a/src/audio/manager.cpp +++ b/src/audio/manager.cpp @@ -278,13 +278,21 @@ void AudioManager::SetPlayback(bool playback) { } void AudioManager::SetCaptureGate(double gate) { - m_capture_gate = gate * 0.01; + m_capture_gate = gate; } void AudioManager::SetCaptureGain(double gain) { m_capture_gain = gain; } +double AudioManager::GetCaptureGate() const noexcept { + return m_capture_gate; +} + +double AudioManager::GetCaptureGain() const noexcept { + return m_capture_gain; +} + void AudioManager::SetMuteSSRC(uint32_t ssrc, bool mute) { std::lock_guard _(m_mutex); if (mute) { @@ -296,9 +304,15 @@ void AudioManager::SetMuteSSRC(uint32_t ssrc, bool mute) { void AudioManager::SetVolumeSSRC(uint32_t ssrc, double volume) { std::lock_guard _(m_mutex); - volume *= 0.01; - constexpr const double E = 2.71828182845904523536; - m_volume_ssrc[ssrc] = (std::exp(volume) - 1) / (E - 1); + m_volume_ssrc[ssrc] = volume; +} + +double AudioManager::GetVolumeSSRC(uint32_t ssrc) const { + std::lock_guard _(m_mutex); + if (const auto iter = m_volume_ssrc.find(ssrc); iter != m_volume_ssrc.end()) { + return iter->second; + } + return 1.0; } void AudioManager::SetEncodingApplication(int application) { @@ -463,4 +477,5 @@ AudioDevices &AudioManager::GetDevices() { AudioManager::type_signal_opus_packet AudioManager::signal_opus_packet() { return m_signal_opus_packet; } + #endif diff --git a/src/audio/manager.hpp b/src/audio/manager.hpp index dbb0b6e..9cd7f42 100644 --- a/src/audio/manager.hpp +++ b/src/audio/manager.hpp @@ -40,9 +40,12 @@ public: void SetCaptureGate(double gate); void SetCaptureGain(double gain); + [[nodiscard]] double GetCaptureGate() const noexcept; + [[nodiscard]] double GetCaptureGain() const noexcept; void SetMuteSSRC(uint32_t ssrc, bool mute); void SetVolumeSSRC(uint32_t ssrc, double volume); + [[nodiscard]] double GetVolumeSSRC(uint32_t ssrc) const; void SetEncodingApplication(int application); [[nodiscard]] int GetEncodingApplication(); diff --git a/src/discord/discord.cpp b/src/discord/discord.cpp index 2c7358b..45e8919 100644 --- a/src/discord/discord.cpp +++ b/src/discord/discord.cpp @@ -1226,6 +1226,10 @@ std::optional DiscordClient::GetVoiceState(Snowflake user_id) const { return std::nullopt; } +DiscordVoiceClient &DiscordClient::GetVoiceClient() { + return m_voice; +} + void DiscordClient::SetVoiceMuted(bool is_mute) { m_mute_requested = is_mute; SendVoiceStateUpdate(); diff --git a/src/discord/discord.hpp b/src/discord/discord.hpp index fa399cc..10b1d30 100644 --- a/src/discord/discord.hpp +++ b/src/discord/discord.hpp @@ -191,6 +191,8 @@ public: [[nodiscard]] std::optional GetSSRCOfUser(Snowflake id) const; [[nodiscard]] std::optional GetVoiceState(Snowflake user_id) const; + DiscordVoiceClient &GetVoiceClient(); + void SetVoiceMuted(bool is_mute); void SetVoiceDeafened(bool is_deaf); #endif diff --git a/src/discord/voiceclient.cpp b/src/discord/voiceclient.cpp index ed83e3c..3d246b1 100644 --- a/src/discord/voiceclient.cpp +++ b/src/discord/voiceclient.cpp @@ -167,6 +167,7 @@ DiscordVoiceClient::~DiscordVoiceClient() { void DiscordVoiceClient::Start() { SetState(State::ConnectingToWebsocket); + m_ssrc_map.clear(); m_heartbeat_waiter.revive(); m_keepalive_waiter.revive(); m_ws.StartConnection("wss://" + m_endpoint + "/?v=7"); @@ -188,6 +189,8 @@ void DiscordVoiceClient::Stop() { m_keepalive_waiter.kill(); if (m_keepalive_thread.joinable()) m_keepalive_thread.join(); + m_ssrc_map.clear(); + m_signal_disconnected.emit(); } @@ -211,6 +214,20 @@ void DiscordVoiceClient::SetUserID(Snowflake id) { m_user_id = id; } +void DiscordVoiceClient::SetUserVolume(Snowflake id, float volume) { + m_user_volumes[id] = volume; + if (const auto ssrc = GetSSRCOfUser(id); ssrc.has_value()) { + Abaddon::Get().GetAudio().SetVolumeSSRC(*ssrc, volume); + } +} + +[[nodiscard]] float DiscordVoiceClient::GetUserVolume(Snowflake id) const { + if (const auto it = m_user_volumes.find(id); it != m_user_volumes.end()) { + return it->second; + } + return 1.0f; +} + std::optional DiscordVoiceClient::GetSSRCOfUser(Snowflake id) const { if (const auto it = m_ssrc_map.find(id); it != m_ssrc_map.end()) { return it->second; @@ -315,6 +332,14 @@ void DiscordVoiceClient::HandleGatewaySessionDescription(const VoiceGatewayMessa void DiscordVoiceClient::HandleGatewaySpeaking(const VoiceGatewayMessage &m) { VoiceSpeakingData d = m.Data; + + // set volume if already set but ssrc just found + if (const auto iter = m_user_volumes.find(d.UserID); iter != m_user_volumes.end()) { + if (m_ssrc_map.find(d.UserID) == m_ssrc_map.end()) { + Abaddon::Get().GetAudio().SetVolumeSSRC(d.SSRC, iter->second); + } + } + m_ssrc_map[d.UserID] = d.SSRC; m_signal_speaking.emit(d); } diff --git a/src/discord/voiceclient.hpp b/src/discord/voiceclient.hpp index 916d070..7bf4295 100644 --- a/src/discord/voiceclient.hpp +++ b/src/discord/voiceclient.hpp @@ -195,6 +195,9 @@ public: void SetServerID(Snowflake id); void SetUserID(Snowflake id); + // todo serialize + void SetUserVolume(Snowflake id, float volume); + [[nodiscard]] float GetUserVolume(Snowflake id) const; [[nodiscard]] std::optional GetSSRCOfUser(Snowflake id) const; // Is a websocket and udp connection fully established @@ -241,6 +244,7 @@ private: Snowflake m_user_id; std::unordered_map m_ssrc_map; + std::unordered_map m_user_volumes; std::array m_secret_key; diff --git a/src/windows/voicewindow.cpp b/src/windows/voicewindow.cpp index 7c90e96..c82a0aa 100644 --- a/src/windows/voicewindow.cpp +++ b/src/windows/voicewindow.cpp @@ -24,7 +24,7 @@ public: 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()); + m_signal_volume.emit(m_volume.get_value() * 0.01); }); m_horz.add(m_avatar); @@ -54,6 +54,10 @@ public: m_meter.SetVolume(frac); } + void RestoreGain(double frac) { + m_volume.set_value(frac * 100.0); + } + private: Gtk::Box m_main; Gtk::Box m_horz; @@ -92,6 +96,8 @@ VoiceWindow::VoiceWindow(Snowflake channel_id) set_default_size(300, 300); auto &discord = Abaddon::Get().GetDiscordClient(); + auto &audio = Abaddon::Get().GetAudio(); + SetUsers(discord.GetUsersInVoiceChannel(m_channel_id)); discord.signal_voice_user_disconnect().connect(sigc::mem_fun(*this, &VoiceWindow::OnUserDisconnect)); @@ -108,17 +114,16 @@ VoiceWindow::VoiceWindow(Snowflake channel_id) m_capture_gate.set_range(0.0, 100.0); m_capture_gate.set_value_pos(Gtk::POS_LEFT); - m_capture_gate.set_value(0.0); + m_capture_gate.set_value(audio.GetCaptureGate() * 100.0); m_capture_gate.signal_value_changed().connect([this]() { - // todo this should probably emit 0-1 i dont think the mgr should be responsible for scaling down - const double val = m_capture_gate.get_value(); + const double val = m_capture_gate.get_value() * 0.01; m_signal_gate.emit(val); - m_capture_volume.SetTick(val / 100.0); + m_capture_volume.SetTick(val); }); m_capture_gain.set_range(0.0, 200.0); m_capture_gain.set_value_pos(Gtk::POS_LEFT); - m_capture_gain.set_value(100.0); + m_capture_gain.set_value(audio.GetCaptureGain() * 100.0); m_capture_gain.signal_value_changed().connect([this]() { const double val = m_capture_gain.get_value(); m_signal_gain.emit(val / 100.0); @@ -174,7 +179,9 @@ VoiceWindow::VoiceWindow(Snowflake channel_id) } void VoiceWindow::SetUsers(const std::unordered_set &user_ids) { + const auto me = Abaddon::Get().GetDiscordClient().GetUserData().ID; for (auto id : user_ids) { + if (id == me) continue; m_user_list.add(*CreateRow(id)); } } @@ -182,6 +189,8 @@ void VoiceWindow::SetUsers(const std::unordered_set &user_ids) { Gtk::ListBoxRow *VoiceWindow::CreateRow(Snowflake id) { auto *row = Gtk::make_managed(id); m_rows[id] = row; + auto &vc = Abaddon::Get().GetDiscordClient().GetVoiceClient(); + row->RestoreGain(vc.GetUserVolume(id)); row->signal_mute_cs().connect([this, id](bool is_muted) { m_signal_mute_user_cs.emit(id, is_muted); }); -- cgit v1.2.3 From 75158f2c295bdc52ab4743b3529ce9092ccfd502 Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Mon, 8 May 2023 00:28:59 -0400 Subject: handle channel moves + region change --- src/components/voiceinfobox.cpp | 39 +++++++++++++++++++++++------------- src/components/voiceinfobox.hpp | 2 ++ src/discord/discord.cpp | 44 +++++++++++++++++++++++++++++++++++++++++ src/discord/discord.hpp | 3 +++ src/discord/voiceclient.cpp | 4 ++++ src/discord/websocket.cpp | 7 ++++++- 6 files changed, 84 insertions(+), 15 deletions(-) (limited to 'src/discord/discord.cpp') diff --git a/src/components/voiceinfobox.cpp b/src/components/voiceinfobox.cpp index b870794..4c5a127 100644 --- a/src/components/voiceinfobox.cpp +++ b/src/components/voiceinfobox.cpp @@ -29,20 +29,7 @@ VoiceInfoBox::VoiceInfoBox() Abaddon::Get().GetDiscordClient().signal_voice_requested_connect().connect([this](Snowflake channel_id) { show(); - - if (const auto channel = Abaddon::Get().GetDiscordClient().GetChannel(channel_id); channel.has_value() && channel->Name.has_value()) { - if (channel->GuildID.has_value()) { - if (const auto guild = Abaddon::Get().GetDiscordClient().GetGuild(*channel->GuildID); guild.has_value()) { - m_location.set_label(*channel->Name + " / " + guild->Name); - return; - } - } - - m_location.set_label(*channel->Name); - return; - } - - m_location.set_label("Unknown"); + UpdateLocation(); }); Abaddon::Get().GetDiscordClient().signal_voice_requested_disconnect().connect([this]() { @@ -72,6 +59,10 @@ VoiceInfoBox::VoiceInfoBox() m_status.set_label(label); }); + Abaddon::Get().GetDiscordClient().signal_voice_channel_changed().connect([this](Snowflake channel_id) { + UpdateLocation(); + }); + AddPointerCursor(m_status_ev); m_status_ev.signal_button_press_event().connect([this](GdkEventButton *ev) -> bool { if (ev->type == GDK_BUTTON_PRESS && ev->button == GDK_BUTTON_PRIMARY) { @@ -100,4 +91,24 @@ VoiceInfoBox::VoiceInfoBox() show_all_children(); } +void VoiceInfoBox::UpdateLocation() { + auto &discord = Abaddon::Get().GetDiscordClient(); + + const auto channel_id = discord.GetVoiceChannelID(); + + if (const auto channel = discord.GetChannel(channel_id); channel.has_value() && channel->Name.has_value()) { + if (channel->GuildID.has_value()) { + if (const auto guild = discord.GetGuild(*channel->GuildID); guild.has_value()) { + m_location.set_label(*channel->Name + " / " + guild->Name); + return; + } + } + + m_location.set_label(*channel->Name); + return; + } + + m_location.set_label("Unknown"); +} + #endif diff --git a/src/components/voiceinfobox.hpp b/src/components/voiceinfobox.hpp index 74aad27..9988c63 100644 --- a/src/components/voiceinfobox.hpp +++ b/src/components/voiceinfobox.hpp @@ -12,6 +12,8 @@ public: VoiceInfoBox(); private: + void UpdateLocation(); + Gtk::Box m_left; Gtk::EventBox m_status_ev; Gtk::Label m_status; diff --git a/src/discord/discord.cpp b/src/discord/discord.cpp index 64988d2..675b640 100644 --- a/src/discord/discord.cpp +++ b/src/discord/discord.cpp @@ -2207,7 +2207,42 @@ void DiscordClient::HandleGatewayGuildMembersChunk(const GatewayMessage &msg) { } #ifdef WITH_VOICE + +/* + * When you connect to a voice channel: + * C->S: VOICE_STATE_UPDATE + * S->C: VOICE_STATE_UPDATE + * S->C: VOICE_SERVER_UPDATE + * + * A new websocket is opened to the ws specified by VOICE_SERVER_UPDATE then: + * S->C: HELLO + * C->S: IDENTIFY + * S->C: READY + * C->U: discover + * U->C: discover result + * C->S: SELECT_PROTOCOL + * S->C: SESSION_DESCRIPTION + * Done!!! + * + * When you get disconnected (no particular order): + * S->C: 4014 Disconnected (Server to voice gateway) + * S->C: VOICE_STATE_UPDATE (Server to main gateway) + * + * When you get moved: + * S->C: VOICE_STATE_UPDATE + * S->C: VOICE_SERVER_UPDATE (usually) + * S->C: 4014 Disconnected (Server to voice gateway) + * + * Key thing: 4014 Disconnected can come before or after or in between main gateway messages + * + * Region change: + * Same thing but close code 4000 + * + */ + void DiscordClient::HandleGatewayVoiceStateUpdate(const GatewayMessage &msg) { + spdlog::get("discord")->trace("VOICE_STATE_UPDATE"); + VoiceState data = msg.Data; if (data.UserID == m_user_data.ID) { @@ -2217,6 +2252,9 @@ void DiscordClient::HandleGatewayVoiceStateUpdate(const GatewayMessage &msg) { // channel_id = null means disconnect. stop cuz out of order maybe if (!data.ChannelID.has_value() && (m_voice.IsConnected() || m_voice.IsConnecting())) { m_voice.Stop(); + } else if (data.ChannelID.has_value()) { + m_voice_channel_id = *data.ChannelID; + m_signal_voice_channel_changed.emit(m_voice_channel_id); } } else { if (data.GuildID.has_value() && data.Member.has_value()) { @@ -2245,6 +2283,8 @@ void DiscordClient::HandleGatewayVoiceStateUpdate(const GatewayMessage &msg) { } void DiscordClient::HandleGatewayVoiceServerUpdate(const GatewayMessage &msg) { + spdlog::get("discord")->trace("VOICE_SERVER_UPDATE"); + VoiceServerUpdateData data = msg.Data; spdlog::get("discord")->debug("Voice server endpoint: {}", data.Endpoint); spdlog::get("discord")->debug("Voice token: {}", data.Token); @@ -3055,4 +3095,8 @@ DiscordClient::type_signal_voice_requested_disconnect DiscordClient::signal_voic DiscordClient::type_signal_voice_client_state_update DiscordClient::signal_voice_client_state_update() { return m_signal_voice_client_state_update; } + +DiscordClient::type_signal_voice_channel_changed DiscordClient::signal_voice_channel_changed() { + return m_signal_voice_channel_changed; +} #endif diff --git a/src/discord/discord.hpp b/src/discord/discord.hpp index aadb72d..cb76ef6 100644 --- a/src/discord/discord.hpp +++ b/src/discord/discord.hpp @@ -454,6 +454,7 @@ public: using type_signal_voice_requested_connect = sigc::signal; using type_signal_voice_requested_disconnect = sigc::signal; using type_signal_voice_client_state_update = sigc::signal; + using type_signal_voice_channel_changed = sigc::signal; #endif type_signal_gateway_ready signal_gateway_ready(); @@ -520,6 +521,7 @@ public: type_signal_voice_requested_connect signal_voice_requested_connect(); type_signal_voice_requested_disconnect signal_voice_requested_disconnect(); type_signal_voice_client_state_update signal_voice_client_state_update(); + type_signal_voice_channel_changed signal_voice_channel_changed(); #endif protected: @@ -587,5 +589,6 @@ protected: type_signal_voice_requested_connect m_signal_voice_requested_connect; type_signal_voice_requested_disconnect m_signal_voice_requested_disconnect; type_signal_voice_client_state_update m_signal_voice_client_state_update; + type_signal_voice_channel_changed m_signal_voice_channel_changed; #endif }; diff --git a/src/discord/voiceclient.cpp b/src/discord/voiceclient.cpp index c58d4b8..c37ba7b 100644 --- a/src/discord/voiceclient.cpp +++ b/src/discord/voiceclient.cpp @@ -166,6 +166,10 @@ DiscordVoiceClient::~DiscordVoiceClient() { } void DiscordVoiceClient::Start() { + if (IsConnected() || IsConnecting()) { + Stop(); + } + SetState(State::ConnectingToWebsocket); m_ssrc_map.clear(); m_heartbeat_waiter.revive(); diff --git a/src/discord/websocket.cpp b/src/discord/websocket.cpp index d40d057..f886e69 100644 --- a/src/discord/websocket.cpp +++ b/src/discord/websocket.cpp @@ -49,7 +49,12 @@ void Websocket::Stop() { void Websocket::Stop(uint16_t code) { m_log->debug("Stopping with close code {}", code); - m_websocket-> stop(code); + m_websocket->stop(code); + m_log->trace("Socket::stop complete"); + while (Gtk::Main::events_pending()) { + Gtk::Main::iteration(); + } + m_log->trace("No events pending"); } void Websocket::Send(const std::string &str) { -- cgit v1.2.3 From f6dd890775f29f3031747b9d076359f219d492b4 Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Thu, 11 May 2023 20:31:34 -0400 Subject: emit user voice connect more consistently (?) --- src/discord/discord.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'src/discord/discord.cpp') diff --git a/src/discord/discord.cpp b/src/discord/discord.cpp index 675b640..bcbf048 100644 --- a/src/discord/discord.cpp +++ b/src/discord/discord.cpp @@ -2270,9 +2270,8 @@ void DiscordClient::HandleGatewayVoiceStateUpdate(const GatewayMessage &msg) { SetVoiceState(data.UserID, *data.ChannelID); if (old_state.has_value() && *old_state != *data.ChannelID) { m_signal_voice_user_disconnect.emit(data.UserID, *old_state); - } else if (!old_state.has_value()) { - m_signal_voice_user_connect.emit(data.UserID, *data.ChannelID); } + m_signal_voice_user_connect.emit(data.UserID, *data.ChannelID); } else { const auto old_state = GetVoiceState(data.UserID); ClearVoiceState(data.UserID); @@ -2781,11 +2780,13 @@ void DiscordClient::SendVoiceStateUpdate() { } void DiscordClient::SetVoiceState(Snowflake user_id, Snowflake channel_id) { + spdlog::get("discord")->debug("SetVoiceState: {} -> {}", user_id, channel_id); m_voice_state_user_channel[user_id] = channel_id; m_voice_state_channel_users[channel_id].insert(user_id); } void DiscordClient::ClearVoiceState(Snowflake user_id) { + spdlog::get("discord")->debug("ClearVoiceState: {}", user_id); if (const auto it = m_voice_state_user_channel.find(user_id); it != m_voice_state_user_channel.end()) { m_voice_state_channel_users[it->second].erase(user_id); // invalidated -- cgit v1.2.3 From 5d3e5eec5e32c49247a0a1fbae1817a4b183f6cd Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Fri, 12 May 2023 20:28:44 -0400 Subject: try to fix voice connect signal again --- src/discord/discord.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'src/discord/discord.cpp') diff --git a/src/discord/discord.cpp b/src/discord/discord.cpp index bcbf048..3c311ed 100644 --- a/src/discord/discord.cpp +++ b/src/discord/discord.cpp @@ -2270,8 +2270,10 @@ void DiscordClient::HandleGatewayVoiceStateUpdate(const GatewayMessage &msg) { SetVoiceState(data.UserID, *data.ChannelID); if (old_state.has_value() && *old_state != *data.ChannelID) { m_signal_voice_user_disconnect.emit(data.UserID, *old_state); + m_signal_voice_user_connect.emit(data.UserID, *data.ChannelID); + } else if (!old_state.has_value()) { + m_signal_voice_user_connect.emit(data.UserID, *data.ChannelID); } - m_signal_voice_user_connect.emit(data.UserID, *data.ChannelID); } else { const auto old_state = GetVoiceState(data.UserID); ClearVoiceState(data.UserID); -- cgit v1.2.3 From 49f6cd021beb70a2f9e8c359275af69922bffb83 Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Sun, 14 May 2023 21:12:59 -0400 Subject: fix duplicate guild appearances --- src/discord/discord.cpp | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'src/discord/discord.cpp') diff --git a/src/discord/discord.cpp b/src/discord/discord.cpp index 3c311ed..40c900b 100644 --- a/src/discord/discord.cpp +++ b/src/discord/discord.cpp @@ -2457,6 +2457,13 @@ void DiscordClient::HandleGatewayGuildMemberListUpdate(const GatewayMessage &msg void DiscordClient::HandleGatewayGuildCreate(const GatewayMessage &msg) { GuildData data = msg.Data; + + // TODO: figure out why this is even happening... maybe? + // ignore guild if already stored + if (m_store.GetGuild(data.ID).has_value()) { + return; + } + ProcessNewGuild(data); m_signal_guild_create.emit(data); -- cgit v1.2.3 From 3e67e8a7aeb265914d538314b17f36a988017081 Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Fri, 19 May 2023 20:20:33 -0400 Subject: add voice state icons to participant rows --- .github/workflows/ci.yml | 14 ++++----- ci/used-icons.txt | 7 +++-- res/css/main.css | 4 +++ src/components/channels.cpp | 14 +++++++++ src/components/channels.hpp | 2 ++ src/components/channelscellrenderer.cpp | 52 ++++++++++++++++++++++++++++++++- src/components/channelscellrenderer.hpp | 7 ++++- src/discord/discord.cpp | 47 ++++++++++++++++++++--------- src/discord/discord.hpp | 10 +++++-- src/discord/voicestateflags.hpp | 17 +++++++++++ 10 files changed, 146 insertions(+), 28 deletions(-) create mode 100644 src/discord/voicestateflags.hpp (limited to 'src/discord/discord.cpp') diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 63511cc..2a1bd25 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -93,13 +93,13 @@ jobs: cd ${artifact_dir}/share/icons/Adwaita mkdir -p 16x16/actions 24x24/actions 32x32/actions 48x48/actions 64x64/actions 96x96/actions scalable/actions cd ${GITHUB_WORKSPACE} - cat "ci/used-icons.txt" | sed 's/\r$//' | xargs -I % cp ci/gtk-for-windows/gtk-nsis-pack/share/icons/Adwaita/16x16/actions/%.symbolic.png ${artifact_dir}/share/icons/Adwaita/16x16/actions || : - cat "ci/used-icons.txt" | sed 's/\r$//' | xargs -I % cp ci/gtk-for-windows/gtk-nsis-pack/share/icons/Adwaita/24x24/actions/%.symbolic.png ${artifact_dir}/share/icons/Adwaita/24x24/actions || : - cat "ci/used-icons.txt" | sed 's/\r$//' | xargs -I % cp ci/gtk-for-windows/gtk-nsis-pack/share/icons/Adwaita/32x32/actions/%.symbolic.png ${artifact_dir}/share/icons/Adwaita/32x32/actions || : - cat "ci/used-icons.txt" | sed 's/\r$//' | xargs -I % cp ci/gtk-for-windows/gtk-nsis-pack/share/icons/Adwaita/48x48/actions/%.symbolic.png ${artifact_dir}/share/icons/Adwaita/48x48/actions || : - cat "ci/used-icons.txt" | sed 's/\r$//' | xargs -I % cp ci/gtk-for-windows/gtk-nsis-pack/share/icons/Adwaita/64x64/actions/%.symbolic.png ${artifact_dir}/share/icons/Adwaita/64x64/actions || : - cat "ci/used-icons.txt" | sed 's/\r$//' | xargs -I % cp ci/gtk-for-windows/gtk-nsis-pack/share/icons/Adwaita/96x96/actions/%.symbolic.png ${artifact_dir}/share/icons/Adwaita/96x96/actions || : - cat "ci/used-icons.txt" | sed 's/\r$//' | xargs -I % cp ci/gtk-for-windows/gtk-nsis-pack/share/icons/Adwaita/scalable/actions/%.svg ${artifact_dir}/share/icons/Adwaita/scalable/actions || : + cat "ci/used-icons.txt" | sed 's/\r$//' | xargs -I % cp ci/gtk-for-windows/gtk-nsis-pack/share/icons/Adwaita/16x16/%.symbolic.png ${artifact_dir}/share/icons/Adwaita/16x16/%.symbolic.png || : + cat "ci/used-icons.txt" | sed 's/\r$//' | xargs -I % cp ci/gtk-for-windows/gtk-nsis-pack/share/icons/Adwaita/24x24/%.symbolic.png ${artifact_dir}/share/icons/Adwaita/24x24/%.symbolic.png || : + cat "ci/used-icons.txt" | sed 's/\r$//' | xargs -I % cp ci/gtk-for-windows/gtk-nsis-pack/share/icons/Adwaita/32x32/%.symbolic.png ${artifact_dir}/share/icons/Adwaita/32x32/%.symbolic.png || : + cat "ci/used-icons.txt" | sed 's/\r$//' | xargs -I % cp ci/gtk-for-windows/gtk-nsis-pack/share/icons/Adwaita/48x48/%.symbolic.png ${artifact_dir}/share/icons/Adwaita/48x48/%.symbolic.png || : + cat "ci/used-icons.txt" | sed 's/\r$//' | xargs -I % cp ci/gtk-for-windows/gtk-nsis-pack/share/icons/Adwaita/64x64/%.symbolic.png ${artifact_dir}/share/icons/Adwaita/64x64/%.symbolic.png || : + cat "ci/used-icons.txt" | sed 's/\r$//' | xargs -I % cp ci/gtk-for-windows/gtk-nsis-pack/share/icons/Adwaita/96x96/%.symbolic.png ${artifact_dir}/share/icons/Adwaita/96x96/%.symbolic.png || : + cat "ci/used-icons.txt" | sed 's/\r$//' | xargs -I % cp ci/gtk-for-windows/gtk-nsis-pack/share/icons/Adwaita/scalable/%.svg ${artifact_dir}/share/icons/Adwaita/scalable/%.svg || : - name: Upload build (1) uses: haya14busa/action-cond@v1 diff --git a/ci/used-icons.txt b/ci/used-icons.txt index 7e00c12..0ea18ae 100644 --- a/ci/used-icons.txt +++ b/ci/used-icons.txt @@ -1,2 +1,5 @@ -document-send-symbolic -call-stop-symbolic +actions/document-send-symbolic +actions/call-stop-symbolic +status/microphone-disabled-symbolic +status/audio-volume-muted-symbolic +devices/camera-web-symbolic diff --git a/res/css/main.css b/res/css/main.css index 4b76bb4..dcbdf4f 100644 --- a/res/css/main.css +++ b/res/css/main.css @@ -378,3 +378,7 @@ .voice-info-location { } + +.voice-state-server { + color: red; +} diff --git a/src/components/channels.cpp b/src/components/channels.cpp index 5e3da86..0e97837 100644 --- a/src/components/channels.cpp +++ b/src/components/channels.cpp @@ -96,6 +96,7 @@ ChannelList::ChannelList() column->add_attribute(renderer->property_expanded(), m_columns.m_expanded); column->add_attribute(renderer->property_nsfw(), m_columns.m_nsfw); column->add_attribute(renderer->property_color(), m_columns.m_color); + column->add_attribute(renderer->property_voice_state(), m_columns.m_voice_flags); m_view.append_column(*column); m_menu_guild_copy_id.signal_activate().connect([this] { @@ -282,6 +283,7 @@ ChannelList::ChannelList() #if WITH_VOICE discord.signal_voice_user_connect().connect(sigc::mem_fun(*this, &ChannelList::OnVoiceUserConnect)); discord.signal_voice_user_disconnect().connect(sigc::mem_fun(*this, &ChannelList::OnVoiceUserDisconnect)); + discord.signal_voice_state_set().connect(sigc::mem_fun(*this, &ChannelList::OnVoiceStateSet)); #endif } @@ -537,6 +539,12 @@ void ChannelList::OnVoiceUserDisconnect(Snowflake user_id, Snowflake channel_id) m_model->erase(iter); } } + +void ChannelList::OnVoiceStateSet(Snowflake user_id, Snowflake channel_id, VoiceStateFlags flags) { + if (auto iter = GetIteratorForRowFromIDOfType(user_id, RenderType::VoiceParticipant)) { + (*iter)[m_columns.m_voice_flags] = flags; + } +} #endif void ChannelList::DeleteThreadRow(Snowflake id) { @@ -857,6 +865,11 @@ Gtk::TreeModel::iterator ChannelList::CreateVoiceParticipantRow(const UserData & row[m_columns.m_id] = user.ID; row[m_columns.m_name] = user.GetEscapedName(); + const auto voice_state = Abaddon::Get().GetDiscordClient().GetVoiceState(user.ID); + if (voice_state.has_value()) { + row[m_columns.m_voice_flags] = voice_state->second; + } + auto &img = Abaddon::Get().GetImageManager(); row[m_columns.m_icon] = img.GetPlaceholder(VoiceParticipantIconSize); const auto cb = [this, user_id = user.ID](const Glib::RefPtr &pb) { @@ -1283,4 +1296,5 @@ ChannelList::ModelColumns::ModelColumns() { add(m_nsfw); add(m_expanded); add(m_color); + add(m_voice_flags); } diff --git a/src/components/channels.hpp b/src/components/channels.hpp index 2ad1a7c..7a23b3d 100644 --- a/src/components/channels.hpp +++ b/src/components/channels.hpp @@ -55,6 +55,7 @@ protected: void OnVoiceUserConnect(Snowflake user_id, Snowflake channel_id); void OnVoiceUserDisconnect(Snowflake user_id, Snowflake channel_id); + void OnVoiceStateSet(Snowflake user_id, Snowflake channel_id, VoiceStateFlags flags); Gtk::TreeView m_view; @@ -70,6 +71,7 @@ protected: Gtk::TreeModelColumn m_sort; Gtk::TreeModelColumn m_nsfw; Gtk::TreeModelColumn> m_color; // for folders right now + Gtk::TreeModelColumn m_voice_flags; // Gtk::CellRenderer's property_is_expanded only works how i want it to if it has children // because otherwise it doesnt count as an "expander" (property_is_expander) // so this solution will have to do which i hate but the alternative is adding invisible children diff --git a/src/components/channelscellrenderer.cpp b/src/components/channelscellrenderer.cpp index ab3113c..6de7a00 100644 --- a/src/components/channelscellrenderer.cpp +++ b/src/components/channelscellrenderer.cpp @@ -17,7 +17,8 @@ CellRendererChannels::CellRendererChannels() , m_property_pixbuf_animation(*this, "pixbuf-animation") , m_property_expanded(*this, "expanded") , m_property_nsfw(*this, "nsfw") - , m_property_color(*this, "color") { + , m_property_color(*this, "color") + , m_property_voice_state(*this, "voice-state") { property_mode() = Gtk::CELL_RENDERER_MODE_ACTIVATABLE; property_xpad() = 2; property_ypad() = 2; @@ -58,6 +59,10 @@ Glib::PropertyProxy> CellRendererChannels::property_col return m_property_color.get_proxy(); } +Glib::PropertyProxy CellRendererChannels::property_voice_state() { + return m_property_voice_state.get_proxy(); +} + void CellRendererChannels::get_preferred_width_vfunc(Gtk::Widget &widget, int &minimum_width, int &natural_width) const { switch (m_property_type.get_value()) { case RenderType::Folder: @@ -733,6 +738,51 @@ void CellRendererChannels::render_vfunc_voice_participant(const Cairo::RefPtrrectangle(icon_x, icon_y, icon_w, icon_h); cr->fill(); } + + auto *paned = dynamic_cast(widget.get_ancestor(Gtk::Paned::get_type())); + if (paned != nullptr) { + const auto edge = std::min(paned->get_position(), background_area.get_width()); + + const static std::array, 3> icon_order = { { + { VoiceStateFlags::SelfMute | VoiceStateFlags::Mute, "microphone-disabled-symbolic" }, + { VoiceStateFlags::SelfDeaf | VoiceStateFlags::Deaf, "audio-volume-muted-symbolic" }, + { VoiceStateFlags::SelfVideo, "camera-web-symbolic" }, + } }; + + constexpr static int IconSize = 18; + constexpr static int IconPad = 2; + + const VoiceStateFlags voice_flags = m_property_voice_state.get_value(); + + int offset = 0; + for (auto iter = icon_order.rbegin(); iter != icon_order.rend(); iter++) { + const auto &[flag, icon] = *iter; + if ((voice_flags & flag) == VoiceStateFlags::Clear) continue; + + const double icon_w = 18; + const double icon_h = 18; + const double icon_x = background_area.get_x() + edge - icon_w + offset; + const double icon_y = background_area.get_y() + background_area.get_height() / 2 - icon_h / 2; + Gdk::Rectangle icon_cell_area(icon_x, icon_y, icon_w, icon_h); + + offset -= (IconSize + IconPad); + + const bool is_server_mute = (voice_flags & VoiceStateFlags::Mute) == VoiceStateFlags::Mute; + const bool is_server_deaf = (voice_flags & VoiceStateFlags::Deaf) == VoiceStateFlags::Deaf; + auto context = widget.get_style_context(); + if (is_server_mute || is_server_deaf) { + context->context_save(); + context->add_class("voice-state-server"); + } + + m_renderer_pixbuf.property_icon_name() = icon; + m_renderer_pixbuf.render(cr, widget, background_area, icon_cell_area, flags); + + if (is_server_mute || is_server_deaf) { + context->context_restore(); + } + } + } } #endif diff --git a/src/components/channelscellrenderer.hpp b/src/components/channelscellrenderer.hpp index f6859dc..934ce5b 100644 --- a/src/components/channelscellrenderer.hpp +++ b/src/components/channelscellrenderer.hpp @@ -3,6 +3,8 @@ #include #include #include "discord/snowflake.hpp" +#include "discord/voicestateflags.hpp" +#include "misc/bitwise.hpp" enum class RenderType : uint8_t { Folder, @@ -34,6 +36,7 @@ public: Glib::PropertyProxy property_expanded(); Glib::PropertyProxy property_nsfw(); Glib::PropertyProxy> property_color(); + Glib::PropertyProxy property_voice_state(); protected: void get_preferred_width_vfunc(Gtk::Widget &widget, int &minimum_width, int &natural_width) const override; @@ -113,7 +116,7 @@ protected: const Gdk::Rectangle &cell_area, Gtk::CellRendererState flags); - // voice channel + // voice participant void get_preferred_width_vfunc_voice_participant(Gtk::Widget &widget, int &minimum_width, int &natural_width) const; void get_preferred_width_for_height_vfunc_voice_participant(Gtk::Widget &widget, int height, int &minimum_width, int &natural_width) const; void get_preferred_height_vfunc_voice_participant(Gtk::Widget &widget, int &minimum_height, int &natural_height) const; @@ -152,6 +155,7 @@ protected: private: Gtk::CellRendererText m_renderer_text; + Gtk::CellRendererPixbuf m_renderer_pixbuf; Glib::Property m_property_type; // all Glib::Property m_property_name; // all @@ -161,6 +165,7 @@ private: Glib::Property m_property_expanded; // category Glib::Property m_property_nsfw; // channel Glib::Property> m_property_color; // folder + Glib::Property m_property_voice_state; // same pitfalls as in https://github.com/uowuo/abaddon/blob/60404783bd4ce9be26233fe66fc3a74475d9eaa3/components/cellrendererpixbufanimation.hpp#L32-L39 // this will manifest though since guild icons can change diff --git a/src/discord/discord.cpp b/src/discord/discord.cpp index be939be..37d4f28 100644 --- a/src/discord/discord.cpp +++ b/src/discord/discord.cpp @@ -1227,8 +1227,8 @@ std::optional DiscordClient::GetSSRCOfUser(Snowflake id) const { return m_voice.GetSSRCOfUser(id); } -std::optional DiscordClient::GetVoiceState(Snowflake user_id) const { - if (const auto it = m_voice_state_user_channel.find(user_id); it != m_voice_state_user_channel.end()) { +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; @@ -2267,9 +2267,9 @@ void DiscordClient::HandleGatewayVoiceStateUpdate(const GatewayMessage &msg) { if (data.ChannelID.has_value()) { const auto old_state = GetVoiceState(data.UserID); - SetVoiceState(data.UserID, *data.ChannelID); - if (old_state.has_value() && *old_state != *data.ChannelID) { - m_signal_voice_user_disconnect.emit(data.UserID, *old_state); + 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); } else if (!old_state.has_value()) { m_signal_voice_user_connect.emit(data.UserID, *data.ChannelID); @@ -2278,7 +2278,7 @@ void DiscordClient::HandleGatewayVoiceStateUpdate(const GatewayMessage &msg) { const auto old_state = GetVoiceState(data.UserID); ClearVoiceState(data.UserID); if (old_state.has_value()) { - m_signal_voice_user_disconnect.emit(data.UserID, *old_state); + m_signal_voice_user_disconnect.emit(data.UserID, old_state->first); } } } @@ -2333,7 +2333,7 @@ void DiscordClient::HandleGatewayReadySupplemental(const GatewayMessage &msg) { for (const auto &g : data.Guilds) { for (const auto &s : g.VoiceStates) { if (s.ChannelID.has_value()) { - SetVoiceState(s.UserID, *s.ChannelID); + SetVoiceState(s.UserID, s); } } } @@ -2798,18 +2798,33 @@ void DiscordClient::SendVoiceStateUpdate() { m_websocket.Send(msg); } -void DiscordClient::SetVoiceState(Snowflake user_id, Snowflake channel_id) { - spdlog::get("discord")->debug("SetVoiceState: {} -> {}", user_id, channel_id); - m_voice_state_user_channel[user_id] = channel_id; - m_voice_state_channel_users[channel_id].insert(user_id); +void DiscordClient::SetVoiceState(Snowflake user_id, const VoiceState &state) { + if (!state.ChannelID.has_value()) { + spdlog::get("discord")->error("SetVoiceState called with missing channel ID"); + return; + } + spdlog::get("discord")->debug("SetVoiceState: {} -> {}", user_id, *state.ChannelID); + + auto flags = VoiceStateFlags::Clear; + if (state.IsSelfMuted) flags |= VoiceStateFlags::SelfMute; + if (state.IsSelfDeafened) flags |= VoiceStateFlags::SelfDeaf; + if (state.IsMuted) flags |= VoiceStateFlags::Mute; + if (state.IsDeafened) flags |= VoiceStateFlags::Deaf; + if (state.IsSelfStream) flags |= VoiceStateFlags::SelfStream; + if (state.IsSelfVideo) flags |= VoiceStateFlags::SelfVideo; + + m_voice_states[user_id] = std::make_pair(*state.ChannelID, flags); + m_voice_state_channel_users[*state.ChannelID].insert(user_id); + + m_signal_voice_state_set.emit(user_id, *state.ChannelID, flags); } void DiscordClient::ClearVoiceState(Snowflake user_id) { spdlog::get("discord")->debug("ClearVoiceState: {}", user_id); - if (const auto it = m_voice_state_user_channel.find(user_id); it != m_voice_state_user_channel.end()) { - m_voice_state_channel_users[it->second].erase(user_id); + if (const auto it = m_voice_states.find(user_id); it != m_voice_states.end()) { + m_voice_state_channel_users[it->second.first].erase(user_id); // invalidated - m_voice_state_user_channel.erase(user_id); + m_voice_states.erase(user_id); } } @@ -3119,4 +3134,8 @@ DiscordClient::type_signal_voice_client_state_update DiscordClient::signal_voice DiscordClient::type_signal_voice_channel_changed DiscordClient::signal_voice_channel_changed() { return m_signal_voice_channel_changed; } + +DiscordClient::type_signal_voice_state_set DiscordClient::signal_voice_state_set() { + return m_signal_voice_state_set; +} #endif diff --git a/src/discord/discord.hpp b/src/discord/discord.hpp index cb76ef6..7f7518c 100644 --- a/src/discord/discord.hpp +++ b/src/discord/discord.hpp @@ -5,6 +5,7 @@ #include "objects.hpp" #include "store.hpp" #include "voiceclient.hpp" +#include "voicestateflags.hpp" #include "websocket.hpp" #include #include @@ -192,7 +193,7 @@ public: [[nodiscard]] Snowflake GetVoiceChannelID() const noexcept; [[nodiscard]] std::unordered_set GetUsersInVoiceChannel(Snowflake channel_id); [[nodiscard]] std::optional GetSSRCOfUser(Snowflake id) const; - [[nodiscard]] std::optional GetVoiceState(Snowflake user_id) const; + [[nodiscard]] std::optional> GetVoiceState(Snowflake user_id) const; DiscordVoiceClient &GetVoiceClient(); @@ -360,12 +361,12 @@ private: Snowflake m_voice_channel_id; // todo sql i guess - std::unordered_map m_voice_state_user_channel; + std::unordered_map> m_voice_states; std::unordered_map> m_voice_state_channel_users; void SendVoiceStateUpdate(); - void SetVoiceState(Snowflake user_id, Snowflake channel_id); + void SetVoiceState(Snowflake user_id, const VoiceState &state); void ClearVoiceState(Snowflake user_id); void OnVoiceConnected(); @@ -455,6 +456,7 @@ public: using type_signal_voice_requested_disconnect = sigc::signal; 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; #endif type_signal_gateway_ready signal_gateway_ready(); @@ -522,6 +524,7 @@ public: type_signal_voice_requested_disconnect signal_voice_requested_disconnect(); 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(); #endif protected: @@ -590,5 +593,6 @@ protected: type_signal_voice_requested_disconnect m_signal_voice_requested_disconnect; 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; #endif }; diff --git a/src/discord/voicestateflags.hpp b/src/discord/voicestateflags.hpp new file mode 100644 index 0000000..e369001 --- /dev/null +++ b/src/discord/voicestateflags.hpp @@ -0,0 +1,17 @@ +#pragma once +#include + +enum class VoiceStateFlags : uint8_t { + Clear = 0, + Deaf = 1 << 0, + Mute = 1 << 1, + SelfDeaf = 1 << 2, + SelfMute = 1 << 3, + SelfStream = 1 << 4, + SelfVideo = 1 << 5, +}; + +template<> +struct Bitwise { + static const bool enable = true; +}; -- cgit v1.2.3