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/abaddon.cpp | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) (limited to 'src/abaddon.cpp') diff --git a/src/abaddon.cpp b/src/abaddon.cpp index 343dff7..a3a228d 100644 --- a/src/abaddon.cpp +++ b/src/abaddon.cpp @@ -3,6 +3,7 @@ #include #include #include "platform.hpp" +#include "audio/manager.hpp" #include "discord/discord.hpp" #include "dialogs/token.hpp" #include "dialogs/editmessage.hpp" @@ -219,6 +220,14 @@ int Abaddon::StartGTK() { return 1; } + 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); + dlg.set_position(Gtk::WIN_POS_CENTER); + dlg.run(); + return 1; + } + // store must be checked before this can be called m_main_window->UpdateComponents(); @@ -238,6 +247,7 @@ 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)); + m_main_window->GetChannelList()->signal_action_join_voice_channel().connect(sigc::mem_fun(*this, &Abaddon::ActionJoinVoiceChannel)); 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)); @@ -898,6 +908,10 @@ void Abaddon::ActionViewThreads(Snowflake channel_id) { window->show(); } +void Abaddon::ActionJoinVoiceChannel(Snowflake channel_id) { + m_discord.ConnectToVoice(channel_id); +} + 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); const auto code = dlg.run(); @@ -937,6 +951,10 @@ EmojiResource &Abaddon::GetEmojis() { return m_emojis; } +AudioManager &Abaddon::GetAudio() { + return *m_audio.get(); +} + int main(int argc, char **argv) { if (std::getenv("ABADDON_NO_FC") == nullptr) Platform::SetupFonts(); -- 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/abaddon.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/abaddon.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/abaddon.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/abaddon.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/abaddon.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 d8d9f1b857da6e7beaf8b998f50b20ea295ab92a Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Sun, 2 Oct 2022 02:50:48 -0400 Subject: close voice window on context menu disconnect --- src/abaddon.cpp | 5 +++++ src/abaddon.hpp | 1 + 2 files changed, 6 insertions(+) (limited to 'src/abaddon.cpp') diff --git a/src/abaddon.cpp b/src/abaddon.cpp index 4c879ca..6dc5fd1 100644 --- a/src/abaddon.cpp +++ b/src/abaddon.cpp @@ -420,6 +420,7 @@ void Abaddon::DiscordOnThreadUpdate(const ThreadUpdateData &data) { #ifdef WITH_VOICE void Abaddon::OnVoiceConnected() { auto *wnd = new VoiceWindow(m_discord.GetVoiceChannelID()); + m_voice_window = wnd; wnd->signal_mute().connect([this](bool is_mute) { m_discord.SetVoiceMuted(is_mute); @@ -440,6 +441,7 @@ void Abaddon::OnVoiceConnected() { wnd->show(); wnd->signal_hide().connect([this, wnd]() { m_discord.DisconnectFromVoice(); + m_voice_window = nullptr; delete wnd; delete m_user_menu; SetupUserMenu(); @@ -448,6 +450,9 @@ void Abaddon::OnVoiceConnected() { void Abaddon::OnVoiceDisconnected() { m_audio->RemoveAllSSRCs(); + if (m_voice_window != nullptr) { + m_voice_window->close(); + } } #endif diff --git a/src/abaddon.hpp b/src/abaddon.hpp index a15670b..f45f435 100644 --- a/src/abaddon.hpp +++ b/src/abaddon.hpp @@ -162,6 +162,7 @@ private: #ifdef WITH_VOICE std::unique_ptr m_audio; + Gtk::Window *m_voice_window = nullptr; #endif mutable std::mutex m_mutex; -- cgit v1.2.3 From 92c70bda08472d880e23df30661376d526406230 Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Wed, 5 Oct 2022 18:43:44 -0400 Subject: add per user volume slider --- res/css/application-low-priority.css | 8 ++++++++ src/abaddon.cpp | 6 ++++++ src/audio/manager.cpp | 14 +++++++++++++- src/audio/manager.hpp | 3 +++ src/windows/voicewindow.cpp | 33 +++++++++++++++++++++++++++++---- src/windows/voicewindow.hpp | 3 +++ 6 files changed, 62 insertions(+), 5 deletions(-) (limited to 'src/abaddon.cpp') diff --git a/res/css/application-low-priority.css b/res/css/application-low-priority.css index 5078d22..130033f 100644 --- a/res/css/application-low-priority.css +++ b/res/css/application-low-priority.css @@ -87,3 +87,11 @@ has to be separate to allow main.css to override certain things .app-window colorswatch { box-shadow: 0 1px rgba(0, 0, 0, 0); } + +.app-window scale { + padding-top: 0px; + padding-bottom: 0px; + margin-top: 0px; + margin-bottom: 0px; + color: @text_color; +} diff --git a/src/abaddon.cpp b/src/abaddon.cpp index 6dc5fd1..8a92de7 100644 --- a/src/abaddon.cpp +++ b/src/abaddon.cpp @@ -438,6 +438,12 @@ void Abaddon::OnVoiceConnected() { } }); + 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); + } + }); + wnd->show(); wnd->signal_hide().connect([this, wnd]() { m_discord.DisconnectFromVoice(); diff --git a/src/audio/manager.cpp b/src/audio/manager.cpp index 8321c3e..37f696e 100644 --- a/src/audio/manager.cpp +++ b/src/audio/manager.cpp @@ -28,13 +28,18 @@ void data_callback(ma_device *pDevice, void *pOutput, const void *pInput, ma_uin AudioManager *mgr = reinterpret_cast(pDevice->pUserData); if (mgr == nullptr) return; std::lock_guard _(mgr->m_mutex); + std::lock_guard _2(mgr->m_volume_ssrc_mutex); auto *pOutputF32 = static_cast(pOutput); for (auto &[ssrc, pair] : mgr->m_sources) { + double volume = 1.0; + if (const auto vol_it = mgr->m_volume_ssrc.find(ssrc); vol_it != mgr->m_volume_ssrc.end()) { + volume = vol_it->second; + } auto &buf = pair.first; const size_t n = std::min(static_cast(buf.size()), static_cast(frameCount * 2ULL)); for (size_t i = 0; i < n; i++) { - pOutputF32[i] += buf[i] / 32768.F; + pOutputF32[i] += volume * buf[i] / 32768.F; } buf.erase(buf.begin(), buf.begin() + n); } @@ -180,6 +185,13 @@ void AudioManager::SetMuteSSRC(uint32_t ssrc, bool mute) { } } +void AudioManager::SetVolumeSSRC(uint32_t ssrc, double volume) { + std::lock_guard _(m_volume_ssrc_mutex); + volume *= 0.01; + constexpr const double E = 2.71828182845904523536; + m_volume_ssrc[ssrc] = (std::exp(volume) - 1) / (E - 1); +} + 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 277f2f3..4d2299e 100644 --- a/src/audio/manager.hpp +++ b/src/audio/manager.hpp @@ -31,6 +31,7 @@ public: void SetPlayback(bool playback); void SetMuteSSRC(uint32_t ssrc, bool mute); + void SetVolumeSSRC(uint32_t ssrc, double volume); [[nodiscard]] bool OK() const; @@ -62,8 +63,10 @@ private: std::atomic m_should_playback = true; std::unordered_set m_muted_ssrcs; + std::unordered_map m_volume_ssrc; mutable std::mutex m_muted_ssrc_mutex; + mutable std::mutex m_volume_ssrc_mutex; public: using type_signal_opus_packet = sigc::signal; diff --git a/src/windows/voicewindow.cpp b/src/windows/voicewindow.cpp index 847ba81..ed9971e 100644 --- a/src/windows/voicewindow.cpp +++ b/src/windows/voicewindow.cpp @@ -10,16 +10,26 @@ class VoiceWindowUserListEntry : public Gtk::ListBoxRow { public: VoiceWindowUserListEntry(Snowflake id) - : m_main(Gtk::ORIENTATION_HORIZONTAL) + : m_main(Gtk::ORIENTATION_VERTICAL) + , m_horz(Gtk::ORIENTATION_HORIZONTAL) , m_avatar(32, 32) , m_mute("Mute") { m_name.set_halign(Gtk::ALIGN_START); m_name.set_hexpand(true); m_mute.set_halign(Gtk::ALIGN_END); - m_main.add(m_avatar); - m_main.add(m_name); - m_main.add(m_mute); + m_volume.set_range(0.0, 200.0); + m_volume.set_value_pos(Gtk::POS_LEFT); + m_volume.set_value(100.0); + m_volume.signal_value_changed().connect([this]() { + m_signal_volume.emit(m_volume.get_value()); + }); + + m_horz.add(m_avatar); + m_horz.add(m_name); + m_horz.add(m_mute); + m_main.add(m_horz); + m_main.add(m_volume); add(m_main); show_all_children(); @@ -38,18 +48,26 @@ public: private: Gtk::Box m_main; + Gtk::Box m_horz; LazyImage m_avatar; Gtk::Label m_name; Gtk::CheckButton m_mute; + Gtk::Scale m_volume; public: using type_signal_mute_cs = sigc::signal; + using type_signal_volume = sigc::signal; type_signal_mute_cs signal_mute_cs() { return m_signal_mute_cs; } + type_signal_volume signal_volume() { + return m_signal_volume; + } + private: type_signal_mute_cs m_signal_mute_cs; + type_signal_volume m_signal_volume; }; VoiceWindow::VoiceWindow(Snowflake channel_id) @@ -82,6 +100,9 @@ void VoiceWindow::SetUsers(const std::unordered_set &user_ids) { 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); } } @@ -105,4 +126,8 @@ VoiceWindow::type_signal_deafen VoiceWindow::signal_deafen() { VoiceWindow::type_signal_mute_user_cs VoiceWindow::signal_mute_user_cs() { return m_signal_mute_user_cs; } + +VoiceWindow::type_signal_user_volume_changed VoiceWindow::signal_user_volume_changed() { + return m_signal_user_volume_changed; +} #endif diff --git a/src/windows/voicewindow.hpp b/src/windows/voicewindow.hpp index 50943f4..da4987e 100644 --- a/src/windows/voicewindow.hpp +++ b/src/windows/voicewindow.hpp @@ -34,14 +34,17 @@ public: using type_signal_mute = sigc::signal; using type_signal_deafen = sigc::signal; using type_signal_mute_user_cs = sigc::signal; + using type_signal_user_volume_changed = sigc::signal; type_signal_mute signal_mute(); type_signal_deafen signal_deafen(); type_signal_mute_user_cs signal_mute_user_cs(); + type_signal_user_volume_changed signal_user_volume_changed(); private: type_signal_mute m_signal_mute; type_signal_deafen m_signal_deafen; type_signal_mute_user_cs m_signal_mute_user_cs; + type_signal_user_volume_changed m_signal_user_volume_changed; }; #endif -- cgit v1.2.3 From dfcfe4353ae40e11bd4bc779bc1f04f74c9f925f Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Wed, 5 Oct 2022 22:01:23 -0400 Subject: center voice window --- src/abaddon.cpp | 2 ++ 1 file changed, 2 insertions(+) (limited to 'src/abaddon.cpp') diff --git a/src/abaddon.cpp b/src/abaddon.cpp index 8a92de7..be62f69 100644 --- a/src/abaddon.cpp +++ b/src/abaddon.cpp @@ -444,6 +444,8 @@ void Abaddon::OnVoiceConnected() { } }); + wnd->set_position(Gtk::WIN_POS_CENTER); + wnd->show(); wnd->signal_hide().connect([this, wnd]() { m_discord.DisconnectFromVoice(); -- cgit v1.2.3 From 5a3bce7498aea568cb6eb83d04313f8d777e8c94 Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Tue, 18 Oct 2022 01:47:43 -0400 Subject: basic voice gate --- src/abaddon.cpp | 4 ++++ src/audio/manager.cpp | 6 ++++++ src/audio/manager.hpp | 4 ++++ src/windows/voicewindow.cpp | 12 ++++++++++++ src/windows/voicewindow.hpp | 5 +++++ 5 files changed, 31 insertions(+) (limited to 'src/abaddon.cpp') diff --git a/src/abaddon.cpp b/src/abaddon.cpp index 701a3f1..45fca80 100644 --- a/src/abaddon.cpp +++ b/src/abaddon.cpp @@ -430,6 +430,10 @@ void Abaddon::OnVoiceConnected() { m_audio->SetPlayback(!is_deaf); }); + wnd->signal_gate().connect([this](double gate) { + m_audio->SetCaptureGate(gate); + }); + 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); diff --git a/src/audio/manager.cpp b/src/audio/manager.cpp index 5bcc3bb..7416ff7 100644 --- a/src/audio/manager.cpp +++ b/src/audio/manager.cpp @@ -176,6 +176,10 @@ void AudioManager::SetPlayback(bool playback) { m_should_playback = playback; } +void AudioManager::SetCaptureGate(double gate) { + m_capture_gate = gate * 0.01; +} + void AudioManager::SetMuteSSRC(uint32_t ssrc, bool mute) { std::lock_guard _(m_mutex); if (mute) { @@ -197,6 +201,8 @@ void AudioManager::OnCapturedPCM(const int16_t *pcm, ma_uint32 frames) { UpdateCaptureVolume(pcm, frames); + if (m_capture_peak_meter / 32768.0 < m_capture_gate) return; + int payload_len = opus_encode(m_encoder, pcm, 480, static_cast(m_opus_buffer), 1275); if (payload_len < 0) { printf("encoding error: %d\n", payload_len); diff --git a/src/audio/manager.hpp b/src/audio/manager.hpp index 43d8a1e..67241fb 100644 --- a/src/audio/manager.hpp +++ b/src/audio/manager.hpp @@ -30,6 +30,8 @@ public: void SetCapture(bool capture); void SetPlayback(bool playback); + void SetCaptureGate(double gate); + void SetMuteSSRC(uint32_t ssrc, bool mute); void SetVolumeSSRC(uint32_t ssrc, double volume); @@ -71,6 +73,8 @@ private: std::atomic m_should_capture = true; std::atomic m_should_playback = true; + std::atomic m_capture_gate = 0.0; + std::unordered_set m_muted_ssrcs; std::unordered_map m_volume_ssrc; diff --git a/src/windows/voicewindow.cpp b/src/windows/voicewindow.cpp index 0e42f32..596f502 100644 --- a/src/windows/voicewindow.cpp +++ b/src/windows/voicewindow.cpp @@ -101,11 +101,19 @@ VoiceWindow::VoiceWindow(Snowflake channel_id) m_scroll.set_hexpand(true); m_scroll.set_vexpand(true); + 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.signal_value_changed().connect([this]() { + m_signal_gate.emit(m_capture_gate.get_value()); + }); + m_scroll.add(m_user_list); m_controls.add(m_mute); m_controls.add(m_deafen); m_main.add(m_controls); m_main.add(m_capture_volume); + m_main.add(m_capture_gate); m_main.add(m_scroll); add(m_main); show_all_children(); @@ -176,6 +184,10 @@ VoiceWindow::type_signal_deafen VoiceWindow::signal_deafen() { return m_signal_deafen; } +VoiceWindow::type_signal_gate VoiceWindow::signal_gate() { + return m_signal_gate; +} + 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 d28a0cd..5f5436b 100644 --- a/src/windows/voicewindow.hpp +++ b/src/windows/voicewindow.hpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -40,6 +41,7 @@ private: Gtk::ListBox m_user_list; Gtk::ProgressBar m_capture_volume; + Gtk::Scale m_capture_gate; Snowflake m_channel_id; @@ -48,17 +50,20 @@ private: public: using type_signal_mute = sigc::signal; using type_signal_deafen = sigc::signal; + using type_signal_gate = sigc::signal; using type_signal_mute_user_cs = sigc::signal; using type_signal_user_volume_changed = sigc::signal; type_signal_mute signal_mute(); type_signal_deafen signal_deafen(); + type_signal_gate signal_gate(); type_signal_mute_user_cs signal_mute_user_cs(); type_signal_user_volume_changed signal_user_volume_changed(); private: type_signal_mute m_signal_mute; type_signal_deafen m_signal_deafen; + type_signal_gate m_signal_gate; type_signal_mute_user_cs m_signal_mute_user_cs; type_signal_user_volume_changed m_signal_user_volume_changed; }; -- cgit v1.2.3 From e888306272304224ac8edd37b3b2bd24ad8c0765 Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Fri, 21 Oct 2022 01:23:37 -0400 Subject: add gain slider (how 2 loudmic? 🤓) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/abaddon.cpp | 4 ++++ src/audio/manager.cpp | 16 ++++++++++++++-- src/audio/manager.hpp | 2 ++ src/windows/voicewindow.cpp | 13 +++++++++++++ src/windows/voicewindow.hpp | 4 ++++ 5 files changed, 37 insertions(+), 2 deletions(-) (limited to 'src/abaddon.cpp') diff --git a/src/abaddon.cpp b/src/abaddon.cpp index 45fca80..664c0bd 100644 --- a/src/abaddon.cpp +++ b/src/abaddon.cpp @@ -434,6 +434,10 @@ void Abaddon::OnVoiceConnected() { m_audio->SetCaptureGate(gate); }); + wnd->signal_gain().connect([this](double gain) { + m_audio->SetCaptureGain(gain); + }); + 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); diff --git a/src/audio/manager.cpp b/src/audio/manager.cpp index 60969cc..0461ee8 100644 --- a/src/audio/manager.cpp +++ b/src/audio/manager.cpp @@ -180,6 +180,10 @@ void AudioManager::SetCaptureGate(double gate) { m_capture_gate = gate * 0.01; } +void AudioManager::SetCaptureGain(double gain) { + m_capture_gain = gain; +} + void AudioManager::SetMuteSSRC(uint32_t ssrc, bool mute) { std::lock_guard _(m_mutex); if (mute) { @@ -199,11 +203,19 @@ void AudioManager::SetVolumeSSRC(uint32_t ssrc, double volume) { void AudioManager::OnCapturedPCM(const int16_t *pcm, ma_uint32 frames) { if (m_opus_buffer == nullptr || !m_should_capture) return; - UpdateCaptureVolume(pcm, frames); + const double gain = m_capture_gain; + // i have a suspicion i can cast the const away... but i wont + std::vector new_pcm(pcm, pcm + frames * 2); + for (auto &val : new_pcm) { + const int32_t unclamped = static_cast(val * gain); + val = std::clamp(unclamped, INT16_MIN, INT16_MAX); + } + + UpdateCaptureVolume(new_pcm.data(), frames); if (m_capture_peak_meter / 32768.0 < m_capture_gate) return; - int payload_len = opus_encode(m_encoder, pcm, 480, static_cast(m_opus_buffer), 1275); + int payload_len = opus_encode(m_encoder, new_pcm.data(), 480, static_cast(m_opus_buffer), 1275); if (payload_len < 0) { printf("encoding error: %d\n", payload_len); } else { diff --git a/src/audio/manager.hpp b/src/audio/manager.hpp index 67241fb..13dbfa4 100644 --- a/src/audio/manager.hpp +++ b/src/audio/manager.hpp @@ -31,6 +31,7 @@ public: void SetPlayback(bool playback); void SetCaptureGate(double gate); + void SetCaptureGain(double gain); void SetMuteSSRC(uint32_t ssrc, bool mute); void SetVolumeSSRC(uint32_t ssrc, double volume); @@ -74,6 +75,7 @@ private: std::atomic m_should_playback = true; std::atomic m_capture_gate = 0.0; + std::atomic m_capture_gain = 1.0; std::unordered_set m_muted_ssrcs; std::unordered_map m_volume_ssrc; diff --git a/src/windows/voicewindow.cpp b/src/windows/voicewindow.cpp index 80a388e..9f53638 100644 --- a/src/windows/voicewindow.cpp +++ b/src/windows/voicewindow.cpp @@ -113,12 +113,21 @@ VoiceWindow::VoiceWindow(Snowflake channel_id) m_capture_volume.SetTick(val / 100.0); }); + 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.signal_value_changed().connect([this]() { + const double val = m_capture_gain.get_value(); + m_signal_gain.emit(val / 100.0); + }); + m_scroll.add(m_user_list); m_controls.add(m_mute); m_controls.add(m_deafen); m_main.add(m_controls); m_main.add(m_capture_volume); m_main.add(m_capture_gate); + m_main.add(m_capture_gain); m_main.add(m_scroll); add(m_main); show_all_children(); @@ -193,6 +202,10 @@ VoiceWindow::type_signal_gate VoiceWindow::signal_gate() { return m_signal_gate; } +VoiceWindow::type_signal_gate VoiceWindow::signal_gain() { + return m_signal_gain; +} + 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 86cedb8..8eb02f3 100644 --- a/src/windows/voicewindow.hpp +++ b/src/windows/voicewindow.hpp @@ -43,6 +43,7 @@ private: VolumeMeter m_capture_volume; Gtk::Scale m_capture_gate; + Gtk::Scale m_capture_gain; Snowflake m_channel_id; @@ -52,12 +53,14 @@ public: using type_signal_mute = sigc::signal; using type_signal_deafen = sigc::signal; using type_signal_gate = sigc::signal; + using type_signal_gain = sigc::signal; using type_signal_mute_user_cs = sigc::signal; using type_signal_user_volume_changed = sigc::signal; type_signal_mute signal_mute(); type_signal_deafen signal_deafen(); type_signal_gate signal_gate(); + type_signal_gain signal_gain(); type_signal_mute_user_cs signal_mute_user_cs(); type_signal_user_volume_changed signal_user_volume_changed(); @@ -65,6 +68,7 @@ private: type_signal_mute m_signal_mute; type_signal_deafen m_signal_deafen; type_signal_gate m_signal_gate; + type_signal_gain m_signal_gain; type_signal_mute_user_cs m_signal_mute_user_cs; type_signal_user_volume_changed m_signal_user_volume_changed; }; -- cgit v1.2.3 From cb690b6defde4851889d04a68efa4f06d7e38847 Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Mon, 24 Oct 2022 22:10:50 -0400 Subject: only enable microphone when in a voice channel --- src/abaddon.cpp | 3 +++ src/audio/manager.cpp | 19 ++++++++++++------- src/audio/manager.hpp | 3 +++ 3 files changed, 18 insertions(+), 7 deletions(-) (limited to 'src/abaddon.cpp') diff --git a/src/abaddon.cpp b/src/abaddon.cpp index 692526c..b39b854 100644 --- a/src/abaddon.cpp +++ b/src/abaddon.cpp @@ -427,6 +427,8 @@ void Abaddon::DiscordOnThreadUpdate(const ThreadUpdateData &data) { #ifdef WITH_VOICE void Abaddon::OnVoiceConnected() { + m_audio->StartCaptureDevice(); + auto *wnd = new VoiceWindow(m_discord.GetVoiceChannelID()); m_voice_window = wnd; @@ -473,6 +475,7 @@ void Abaddon::OnVoiceConnected() { } void Abaddon::OnVoiceDisconnected() { + m_audio->StopCaptureDevice(); m_audio->RemoveAllSSRCs(); if (m_voice_window != nullptr) { m_voice_window->close(); diff --git a/src/audio/manager.cpp b/src/audio/manager.cpp index 0461ee8..21c522e 100644 --- a/src/audio/manager.cpp +++ b/src/audio/manager.cpp @@ -98,13 +98,6 @@ AudioManager::AudioManager() { return; } - if (ma_device_start(&m_capture_device) != MA_SUCCESS) { - puts("failed to start capture"); - ma_device_uninit(&m_capture_device); - m_ok = false; - return; - } - char device_name[MA_MAX_DEVICE_NAME_LENGTH + 1]; ma_device_get_name(&m_capture_device, ma_device_type_capture, device_name, sizeof(device_name), nullptr); printf("using %s for capture\n", device_name); @@ -168,6 +161,18 @@ void AudioManager::FeedMeOpus(uint32_t ssrc, const std::vector &data) { } } +void AudioManager::StartCaptureDevice() { + if (ma_device_start(&m_capture_device) != MA_SUCCESS) { + puts("failed to start capture"); + } +} + +void AudioManager::StopCaptureDevice() { + if (ma_device_stop(&m_capture_device) != MA_SUCCESS) { + puts("failed to stop capture"); + } +} + void AudioManager::SetCapture(bool capture) { m_should_capture = capture; } diff --git a/src/audio/manager.hpp b/src/audio/manager.hpp index 13dbfa4..7d8f479 100644 --- a/src/audio/manager.hpp +++ b/src/audio/manager.hpp @@ -27,6 +27,9 @@ public: void SetOpusBuffer(uint8_t *ptr); void FeedMeOpus(uint32_t ssrc, const std::vector &data); + void StartCaptureDevice(); + void StopCaptureDevice(); + void SetCapture(bool capture); void SetPlayback(bool playback); -- cgit v1.2.3 From f8f9a907c931623b8ef43d7af45b10c49d41afaa Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Sat, 5 Nov 2022 02:32:43 -0400 Subject: add basic combobox to choose output device, start using spdlog --- CMakeLists.txt | 3 ++ src/abaddon.cpp | 8 +++++ src/audio/devices.cpp | 33 +++++++++++++++++ src/audio/devices.hpp | 26 ++++++++++++++ src/audio/manager.cpp | 87 ++++++++++++++++++++++++++++++++++++++++----- src/audio/manager.hpp | 13 +++++++ src/discord/voiceclient.cpp | 22 ++++++------ src/windows/voicewindow.cpp | 12 +++++++ src/windows/voicewindow.hpp | 3 ++ 9 files changed, 186 insertions(+), 21 deletions(-) create mode 100644 src/audio/devices.cpp create mode 100644 src/audio/devices.hpp (limited to 'src/abaddon.cpp') diff --git a/CMakeLists.txt b/CMakeLists.txt index 3ff2a2b..d4c499e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -90,6 +90,9 @@ if (Fontconfig_FOUND) target_link_libraries(abaddon Fontconfig::Fontconfig) endif () +find_package(spdlog REQUIRED) +target_link_libraries(abaddon spdlog::spdlog) + target_link_libraries(abaddon ${SQLite3_LIBRARIES}) target_link_libraries(abaddon ${GTKMM_LIBRARIES}) target_link_libraries(abaddon ${CURL_LIBRARIES}) diff --git a/src/abaddon.cpp b/src/abaddon.cpp index b39b854..a0e73f2 100644 --- a/src/abaddon.cpp +++ b/src/abaddon.cpp @@ -1,5 +1,8 @@ #include #include +#include +#include +#include #include #include #include "platform.hpp" @@ -1098,6 +1101,11 @@ int main(int argc, char **argv) { if (buf[0] != '1') SetEnvironmentVariableA("GTK_CSD", "0"); #endif + + spdlog::cfg::load_env_levels(); + auto log_audio = spdlog::stdout_color_mt("audio"); + auto log_voice = spdlog::stdout_color_mt("voice"); + Gtk::Main::init_gtkmm_internals(); // why??? return Abaddon::Get().StartGTK(); } diff --git a/src/audio/devices.cpp b/src/audio/devices.cpp new file mode 100644 index 0000000..f315a17 --- /dev/null +++ b/src/audio/devices.cpp @@ -0,0 +1,33 @@ +#include "devices.hpp" + +AudioDevices::AudioDevices() + : m_playback(Gtk::ListStore::create(m_playback_columns)) { +} + +Glib::RefPtr AudioDevices::GetPlaybackDeviceModel() { + return m_playback; +} + +void AudioDevices::SetDevices(ma_device_info *pPlayback, ma_uint32 playback_count, ma_device_info *pCapture, ma_uint32 capture_count) { + m_playback->clear(); + + for (ma_uint32 i = 0; i < playback_count; i++) { + auto &d = pPlayback[i]; + auto row = *m_playback->append(); + row[m_playback_columns.Name] = d.name; + row[m_playback_columns.DeviceID] = d.id; + } +} + +std::optional AudioDevices::GetDeviceIDFromModel(const Gtk::TreeModel::iterator &iter) { + if (iter) { + return static_cast((*iter)[m_playback_columns.DeviceID]); + } else { + return std::nullopt; + } +} + +AudioDevices::PlaybackColumns::PlaybackColumns() { + add(Name); + add(DeviceID); +} diff --git a/src/audio/devices.hpp b/src/audio/devices.hpp new file mode 100644 index 0000000..0a88873 --- /dev/null +++ b/src/audio/devices.hpp @@ -0,0 +1,26 @@ +#pragma once + +#include +#include +#include + +class AudioDevices { +public: + AudioDevices(); + + Glib::RefPtr GetPlaybackDeviceModel(); + + 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); + +private: + class PlaybackColumns : public Gtk::TreeModel::ColumnRecord { + public: + PlaybackColumns(); + + Gtk::TreeModelColumn Name; + Gtk::TreeModelColumn DeviceID; + }; + PlaybackColumns m_playback_columns; + Glib::RefPtr m_playback; +}; diff --git a/src/audio/manager.cpp b/src/audio/manager.cpp index 21c522e..c965658 100644 --- a/src/audio/manager.cpp +++ b/src/audio/manager.cpp @@ -8,6 +8,7 @@ #include "manager.hpp" #include #include +#include #define MINIAUDIO_IMPLEMENTATION #include #include @@ -58,12 +59,20 @@ AudioManager::AudioManager() { int err; m_encoder = opus_encoder_create(48000, 2, OPUS_APPLICATION_VOIP, &err); if (err != OPUS_OK) { - printf("failed to initialize opus encoder: %d\n", err); + spdlog::get("audio")->error("failed to initialize opus encoder: {}", err); m_ok = false; return; } opus_encoder_ctl(m_encoder, OPUS_SET_BITRATE(64000)); + if (ma_context_init(nullptr, 0, nullptr, &m_context) != MA_SUCCESS) { + spdlog::get("audio")->error("failed to initialize context"); + m_ok = false; + return; + } + + Enumerate(); + m_device_config = ma_device_config_init(ma_device_type_playback); m_device_config.playback.format = ma_format_f32; m_device_config.playback.channels = 2; @@ -72,13 +81,13 @@ AudioManager::AudioManager() { m_device_config.pUserData = this; if (ma_device_init(nullptr, &m_device_config, &m_device) != MA_SUCCESS) { - puts("open playback fail"); + spdlog::get("audio")->error("failed to initialize playback device"); m_ok = false; return; } if (ma_device_start(&m_device) != MA_SUCCESS) { - puts("failed to start playback"); + spdlog::get("audio")->error("failed to start playback"); ma_device_uninit(&m_device); m_ok = false; return; @@ -93,14 +102,14 @@ AudioManager::AudioManager() { m_capture_config.pUserData = this; if (ma_device_init(nullptr, &m_capture_config, &m_capture_device) != MA_SUCCESS) { - puts("open capture fail"); + spdlog::get("audio")->error("failed to initialize capture device"); m_ok = false; return; } char device_name[MA_MAX_DEVICE_NAME_LENGTH + 1]; ma_device_get_name(&m_capture_device, ma_device_type_capture, device_name, sizeof(device_name), nullptr); - printf("using %s for capture\n", device_name); + spdlog::get("audio")->info("using {} as capture device", device_name); Glib::signal_timeout().connect(sigc::mem_fun(*this, &AudioManager::DecayVolumeMeters), 40); } @@ -108,6 +117,7 @@ AudioManager::AudioManager() { AudioManager::~AudioManager() { ma_device_uninit(&m_device); ma_device_uninit(&m_capture_device); + ma_context_uninit(&m_context); RemoveAllSSRCs(); } @@ -129,7 +139,7 @@ void AudioManager::RemoveSSRC(uint32_t ssrc) { } void AudioManager::RemoveAllSSRCs() { - puts("remove all ssrc"); + spdlog::get("audio")->info("removing all ssrc"); std::lock_guard _(m_mutex); for (auto &[ssrc, pair] : m_sources) { opus_decoder_destroy(pair.second); @@ -163,13 +173,45 @@ void AudioManager::FeedMeOpus(uint32_t ssrc, const std::vector &data) { void AudioManager::StartCaptureDevice() { if (ma_device_start(&m_capture_device) != MA_SUCCESS) { - puts("failed to start capture"); + spdlog::get("audio")->error("Failed to start capture device"); } } void AudioManager::StopCaptureDevice() { if (ma_device_stop(&m_capture_device) != MA_SUCCESS) { - puts("failed to stop capture"); + spdlog::get("audio")->error("Failed to stop capture device"); + } +} + +void AudioManager::SetPlaybackDevice(const Gtk::TreeModel::iterator &iter) { + spdlog::get("audio")->debug("Setting new playback device"); + + const auto device_id = m_devices.GetDeviceIDFromModel(iter); + if (!device_id) { + spdlog::get("audio")->error("Requested ID from iterator is invalid"); + return; + } + + m_playback_id = *device_id; + + ma_device_uninit(&m_device); + + m_device_config = ma_device_config_init(ma_device_type_playback); + m_device_config.playback.format = ma_format_f32; + m_device_config.playback.channels = 2; + m_device_config.playback.pDeviceID = &m_playback_id; + m_device_config.sampleRate = 48000; + m_device_config.dataCallback = data_callback; + m_device_config.pUserData = this; + + if (ma_device_init(&m_context, &m_device_config, &m_device) != MA_SUCCESS) { + spdlog::get("audio")->error("Failed to initialize new device"); + return; + } + + if (ma_device_start(&m_device) != MA_SUCCESS) { + spdlog::get("audio")->error("Failed to start new device"); + return; } } @@ -205,6 +247,29 @@ void AudioManager::SetVolumeSSRC(uint32_t ssrc, double volume) { m_volume_ssrc[ssrc] = (std::exp(volume) - 1) / (E - 1); } +void AudioManager::Enumerate() { + ma_device_info *pPlaybackDeviceInfo; + ma_uint32 playbackDeviceCount; + ma_device_info *pCaptureDeviceInfo; + ma_uint32 captureDeviceCount; + + spdlog::get("audio")->debug("Enumerating devices"); + + if (ma_context_get_devices( + &m_context, + &pPlaybackDeviceInfo, + &playbackDeviceCount, + &pCaptureDeviceInfo, + &captureDeviceCount) != MA_SUCCESS) { + spdlog::get("audio")->error("Failed to enumerate devices"); + return; + } + + spdlog::get("audio")->debug("Found {} playback devices and {} capture devices", playbackDeviceCount, captureDeviceCount); + + m_devices.SetDevices(pPlaybackDeviceInfo, playbackDeviceCount, pCaptureDeviceInfo, captureDeviceCount); +} + void AudioManager::OnCapturedPCM(const int16_t *pcm, ma_uint32 frames) { if (m_opus_buffer == nullptr || !m_should_capture) return; @@ -222,7 +287,7 @@ void AudioManager::OnCapturedPCM(const int16_t *pcm, ma_uint32 frames) { int payload_len = opus_encode(m_encoder, new_pcm.data(), 480, static_cast(m_opus_buffer), 1275); if (payload_len < 0) { - printf("encoding error: %d\n", payload_len); + spdlog::get("audio")->error("encoding error: {}", payload_len); } else { m_signal_opus_packet.emit(payload_len); } @@ -275,6 +340,10 @@ double AudioManager::GetSSRCVolumeLevel(uint32_t ssrc) const noexcept { return 0.0; } +AudioDevices &AudioManager::GetDevices() { + return m_devices; +} + AudioManager::type_signal_opus_packet AudioManager::signal_opus_packet() { return m_signal_opus_packet; } diff --git a/src/audio/manager.hpp b/src/audio/manager.hpp index 7d8f479..ff50390 100644 --- a/src/audio/manager.hpp +++ b/src/audio/manager.hpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include @@ -13,6 +14,7 @@ #include #include #include +#include "devices.hpp" // clang-format on class AudioManager { @@ -30,6 +32,8 @@ public: void StartCaptureDevice(); void StopCaptureDevice(); + void SetPlaybackDevice(const Gtk::TreeModel::iterator &iter); + void SetCapture(bool capture); void SetPlayback(bool playback); @@ -39,11 +43,15 @@ public: void SetMuteSSRC(uint32_t ssrc, bool mute); void SetVolumeSSRC(uint32_t ssrc, double volume); + void Enumerate(); + [[nodiscard]] bool OK() const; [[nodiscard]] double GetCaptureVolumeLevel() const noexcept; [[nodiscard]] double GetSSRCVolumeLevel(uint32_t ssrc) const noexcept; + [[nodiscard]] AudioDevices &GetDevices(); + private: void OnCapturedPCM(const int16_t *pcm, ma_uint32 frames); @@ -63,10 +71,13 @@ private: // playback ma_device m_device; ma_device_config m_device_config; + ma_device_id m_playback_id; // capture ma_device m_capture_device; ma_device_config m_capture_config; + ma_context m_context; + mutable std::mutex m_mutex; std::unordered_map, OpusDecoder *>> m_sources; @@ -86,6 +97,8 @@ private: mutable std::mutex m_vol_mtx; std::unordered_map m_volumes; + AudioDevices m_devices; + public: using type_signal_opus_packet = sigc::signal; type_signal_opus_packet signal_opus_packet(); diff --git a/src/discord/voiceclient.cpp b/src/discord/voiceclient.cpp index c0679ce..291b975 100644 --- a/src/discord/voiceclient.cpp +++ b/src/discord/voiceclient.cpp @@ -4,6 +4,8 @@ #include "voiceclient.hpp" #include "json.hpp" #include +#include +#include #include "abaddon.hpp" #include "audio/manager.hpp" @@ -127,11 +129,11 @@ DiscordVoiceClient::DiscordVoiceClient() { sodium_init(); m_ws.signal_open().connect([this]() { - puts("vws open"); + spdlog::get("voice")->info("Websocket open"); }); m_ws.signal_close().connect([this](uint16_t code) { - printf("vws close %u\n", code); + spdlog::get("voice")->info("Websocket closed with code {}", code); Stop(); }); @@ -258,9 +260,9 @@ void DiscordVoiceClient::HandleGatewayReady(const VoiceGatewayMessage &m) { m_port = d.Port; m_ssrc = d.SSRC; if (std::find(d.Modes.begin(), d.Modes.end(), "xsalsa20_poly1305") == d.Modes.end()) { - puts("xsalsa20_poly1305 not in encryption modes"); + spdlog::get("voice")->error("xsalsa20_poly1305 not in encryption modes"); } - printf("connect to %s:%u ssrc %u\n", m_ip.c_str(), m_port, m_ssrc); + 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); @@ -270,11 +272,7 @@ void DiscordVoiceClient::HandleGatewayReady(const VoiceGatewayMessage &m) { void DiscordVoiceClient::HandleGatewaySessionDescription(const VoiceGatewayMessage &m) { VoiceSessionDescriptionData d = m.Data; - printf("receiving with %s secret key: ", d.Mode.c_str()); - for (auto b : d.SecretKey) { - printf("%02X", b); - } - printf("\n"); + spdlog::get("voice")->debug("receiving with {}, secret key: {:ns}", d.Mode, spdlog::to_hex(std::begin(d.SecretKey), std::end(d.SecretKey))); VoiceSpeakingMessage msg; msg.Delay = 0; @@ -330,10 +328,10 @@ void DiscordVoiceClient::Discovery() { 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]; - printf("we are %s:%u\n", our_ip, our_port); + spdlog::get("voice")->debug("IP address discovered: {}:{}\n", our_ip, our_port); SelectProtocol(our_ip, our_port); } else { - puts("received non-discovery packet after discovery"); + spdlog::get("voice")->error("Received non-discovery packet after discovery"); } } @@ -355,7 +353,7 @@ void DiscordVoiceClient::OnUDPData(std::vector data) { 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())) { - puts("decrypt fail"); + // spdlog::get("voice")->trace("UDP payload decryption failure"); } else { Abaddon::Get().GetAudio().FeedMeOpus(ssrc, { payload, payload + data.size() - 12 - crypto_box_MACBYTES }); } diff --git a/src/windows/voicewindow.cpp b/src/windows/voicewindow.cpp index 9f53638..95d1036 100644 --- a/src/windows/voicewindow.cpp +++ b/src/windows/voicewindow.cpp @@ -121,6 +121,17 @@ VoiceWindow::VoiceWindow(Snowflake channel_id) m_signal_gain.emit(val / 100.0); }); + auto *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.signal_changed().connect([this]() { + Abaddon::Get().GetAudio().SetPlaybackDevice(m_playback_combo.get_active()); + }); + m_scroll.add(m_user_list); m_controls.add(m_mute); m_controls.add(m_deafen); @@ -129,6 +140,7 @@ VoiceWindow::VoiceWindow(Snowflake channel_id) m_main.add(m_capture_gate); m_main.add(m_capture_gain); m_main.add(m_scroll); + m_main.add(m_playback_combo); add(m_main); show_all_children(); diff --git a/src/windows/voicewindow.hpp b/src/windows/voicewindow.hpp index 8eb02f3..e36846c 100644 --- a/src/windows/voicewindow.hpp +++ b/src/windows/voicewindow.hpp @@ -6,6 +6,7 @@ #include "discord/snowflake.hpp" #include #include +#include #include #include #include @@ -45,6 +46,8 @@ private: Gtk::Scale m_capture_gate; Gtk::Scale m_capture_gain; + Gtk::ComboBox m_playback_combo; + Snowflake m_channel_id; std::unordered_map m_rows; -- 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/abaddon.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 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/abaddon.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 5e85d16cd61a9f28bd046d4c7921be8f6cc2b2fc Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Sat, 7 Jan 2023 20:14:16 -0500 Subject: dont disconnect on voice window close, add ability to reopen --- src/abaddon.cpp | 22 +++++++++++++--------- src/abaddon.hpp | 2 ++ src/components/voiceinfobox.cpp | 12 +++++++++++- src/components/voiceinfobox.hpp | 1 + 4 files changed, 27 insertions(+), 10 deletions(-) (limited to 'src/abaddon.cpp') diff --git a/src/abaddon.cpp b/src/abaddon.cpp index 8b760fd..5ed17be 100644 --- a/src/abaddon.cpp +++ b/src/abaddon.cpp @@ -432,6 +432,19 @@ void Abaddon::DiscordOnThreadUpdate(const ThreadUpdateData &data) { #ifdef WITH_VOICE void Abaddon::OnVoiceConnected() { m_audio->StartCaptureDevice(); + ShowVoiceWindow(); +} + +void Abaddon::OnVoiceDisconnected() { + m_audio->StopCaptureDevice(); + m_audio->RemoveAllSSRCs(); + if (m_voice_window != nullptr) { + m_voice_window->close(); + } +} + +void Abaddon::ShowVoiceWindow() { + if (m_voice_window != nullptr) return; auto *wnd = new VoiceWindow(m_discord.GetVoiceChannelID()); m_voice_window = wnd; @@ -470,21 +483,12 @@ void Abaddon::OnVoiceConnected() { wnd->show(); wnd->signal_hide().connect([this, wnd]() { - m_discord.DisconnectFromVoice(); m_voice_window = nullptr; delete wnd; delete m_user_menu; SetupUserMenu(); }); } - -void Abaddon::OnVoiceDisconnected() { - m_audio->StopCaptureDevice(); - m_audio->RemoveAllSSRCs(); - if (m_voice_window != nullptr) { - m_voice_window->close(); - } -} #endif SettingsManager::Settings &Abaddon::GetSettings() { diff --git a/src/abaddon.hpp b/src/abaddon.hpp index f45f435..5540c5a 100644 --- a/src/abaddon.hpp +++ b/src/abaddon.hpp @@ -92,6 +92,8 @@ public: #ifdef WITH_VOICE void OnVoiceConnected(); void OnVoiceDisconnected(); + + void ShowVoiceWindow(); #endif SettingsManager::Settings &GetSettings(); diff --git a/src/components/voiceinfobox.cpp b/src/components/voiceinfobox.cpp index f4dc6ed..456c5f0 100644 --- a/src/components/voiceinfobox.cpp +++ b/src/components/voiceinfobox.cpp @@ -70,6 +70,15 @@ VoiceInfoBox::VoiceInfoBox() m_status.set_label(label); }); + 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) { + Abaddon::Get().ShowVoiceWindow(); + return true; + } + return false; + }); + m_status.set_ellipsize(Pango::ELLIPSIZE_END); m_location.set_ellipsize(Pango::ELLIPSIZE_END); @@ -79,7 +88,8 @@ VoiceInfoBox::VoiceInfoBox() m_disconnect_img.set_hexpand(true); m_disconnect_img.set_halign(Gtk::ALIGN_END); - m_left.add(m_status); + m_status_ev.add(m_status); + m_left.add(m_status_ev); m_left.add(m_location); add(m_left); diff --git a/src/components/voiceinfobox.hpp b/src/components/voiceinfobox.hpp index e0bc4e8..0117c0d 100644 --- a/src/components/voiceinfobox.hpp +++ b/src/components/voiceinfobox.hpp @@ -11,6 +11,7 @@ public: private: Gtk::Box m_left; + Gtk::EventBox m_status_ev; Gtk::Label m_status; Gtk::Label m_location; -- 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/abaddon.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 1f765e1ddf0991084843a90446b6b4aec59ffb48 Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Sun, 28 May 2023 19:48:23 -0400 Subject: fix state saving --- src/abaddon.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src/abaddon.cpp') diff --git a/src/abaddon.cpp b/src/abaddon.cpp index 5c6f136..bbca8df 100644 --- a/src/abaddon.cpp +++ b/src/abaddon.cpp @@ -341,8 +341,8 @@ void Abaddon::StartDiscord() { } void Abaddon::StopDiscord() { - if (m_discord.Stop()) - SaveState(); + if (m_discord.IsStarted()) SaveState(); + m_discord.Stop(); m_main_window->UpdateMenus(); } -- cgit v1.2.3 From 7da37a2fa90cebf43631c74aa1abfb9842502291 Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Mon, 29 May 2023 01:20:41 -0400 Subject: printf to log --- src/abaddon.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/abaddon.cpp') diff --git a/src/abaddon.cpp b/src/abaddon.cpp index bbca8df..e8911f9 100644 --- a/src/abaddon.cpp +++ b/src/abaddon.cpp @@ -58,7 +58,7 @@ Abaddon::Abaddon() 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); + spdlog::get("voice")->debug("{} SSRC: {}", m.UserID, m.SSRC); m_audio->AddSSRC(m.SSRC); }); #endif -- cgit v1.2.3