diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/components/channels.cpp | 14 | ||||
-rw-r--r-- | src/components/channels.hpp | 2 | ||||
-rw-r--r-- | src/components/channelscellrenderer.cpp | 52 | ||||
-rw-r--r-- | src/components/channelscellrenderer.hpp | 7 | ||||
-rw-r--r-- | src/discord/discord.cpp | 47 | ||||
-rw-r--r-- | src/discord/discord.hpp | 10 | ||||
-rw-r--r-- | src/discord/voicestateflags.hpp | 17 |
7 files changed, 130 insertions, 19 deletions
diff --git a/src/components/channels.cpp b/src/components/channels.cpp index 5e3da86..0e97837 100644 --- a/src/components/channels.cpp +++ b/src/components/channels.cpp @@ -96,6 +96,7 @@ ChannelList::ChannelList() column->add_attribute(renderer->property_expanded(), m_columns.m_expanded); column->add_attribute(renderer->property_nsfw(), m_columns.m_nsfw); column->add_attribute(renderer->property_color(), m_columns.m_color); + column->add_attribute(renderer->property_voice_state(), m_columns.m_voice_flags); m_view.append_column(*column); m_menu_guild_copy_id.signal_activate().connect([this] { @@ -282,6 +283,7 @@ ChannelList::ChannelList() #if WITH_VOICE discord.signal_voice_user_connect().connect(sigc::mem_fun(*this, &ChannelList::OnVoiceUserConnect)); discord.signal_voice_user_disconnect().connect(sigc::mem_fun(*this, &ChannelList::OnVoiceUserDisconnect)); + discord.signal_voice_state_set().connect(sigc::mem_fun(*this, &ChannelList::OnVoiceStateSet)); #endif } @@ -537,6 +539,12 @@ void ChannelList::OnVoiceUserDisconnect(Snowflake user_id, Snowflake channel_id) m_model->erase(iter); } } + +void ChannelList::OnVoiceStateSet(Snowflake user_id, Snowflake channel_id, VoiceStateFlags flags) { + if (auto iter = GetIteratorForRowFromIDOfType(user_id, RenderType::VoiceParticipant)) { + (*iter)[m_columns.m_voice_flags] = flags; + } +} #endif void ChannelList::DeleteThreadRow(Snowflake id) { @@ -857,6 +865,11 @@ Gtk::TreeModel::iterator ChannelList::CreateVoiceParticipantRow(const UserData & row[m_columns.m_id] = user.ID; row[m_columns.m_name] = user.GetEscapedName(); + const auto voice_state = Abaddon::Get().GetDiscordClient().GetVoiceState(user.ID); + if (voice_state.has_value()) { + row[m_columns.m_voice_flags] = voice_state->second; + } + auto &img = Abaddon::Get().GetImageManager(); row[m_columns.m_icon] = img.GetPlaceholder(VoiceParticipantIconSize); const auto cb = [this, user_id = user.ID](const Glib::RefPtr<Gdk::Pixbuf> &pb) { @@ -1283,4 +1296,5 @@ ChannelList::ModelColumns::ModelColumns() { add(m_nsfw); add(m_expanded); add(m_color); + add(m_voice_flags); } diff --git a/src/components/channels.hpp b/src/components/channels.hpp index 2ad1a7c..7a23b3d 100644 --- a/src/components/channels.hpp +++ b/src/components/channels.hpp @@ -55,6 +55,7 @@ protected: void OnVoiceUserConnect(Snowflake user_id, Snowflake channel_id); void OnVoiceUserDisconnect(Snowflake user_id, Snowflake channel_id); + void OnVoiceStateSet(Snowflake user_id, Snowflake channel_id, VoiceStateFlags flags); Gtk::TreeView m_view; @@ -70,6 +71,7 @@ protected: Gtk::TreeModelColumn<int64_t> m_sort; Gtk::TreeModelColumn<bool> m_nsfw; Gtk::TreeModelColumn<std::optional<Gdk::RGBA>> m_color; // for folders right now + Gtk::TreeModelColumn<VoiceStateFlags> m_voice_flags; // Gtk::CellRenderer's property_is_expanded only works how i want it to if it has children // because otherwise it doesnt count as an "expander" (property_is_expander) // so this solution will have to do which i hate but the alternative is adding invisible children diff --git a/src/components/channelscellrenderer.cpp b/src/components/channelscellrenderer.cpp index ab3113c..6de7a00 100644 --- a/src/components/channelscellrenderer.cpp +++ b/src/components/channelscellrenderer.cpp @@ -17,7 +17,8 @@ CellRendererChannels::CellRendererChannels() , m_property_pixbuf_animation(*this, "pixbuf-animation") , m_property_expanded(*this, "expanded") , m_property_nsfw(*this, "nsfw") - , m_property_color(*this, "color") { + , m_property_color(*this, "color") + , m_property_voice_state(*this, "voice-state") { property_mode() = Gtk::CELL_RENDERER_MODE_ACTIVATABLE; property_xpad() = 2; property_ypad() = 2; @@ -58,6 +59,10 @@ Glib::PropertyProxy<std::optional<Gdk::RGBA>> CellRendererChannels::property_col return m_property_color.get_proxy(); } +Glib::PropertyProxy<VoiceStateFlags> CellRendererChannels::property_voice_state() { + return m_property_voice_state.get_proxy(); +} + void CellRendererChannels::get_preferred_width_vfunc(Gtk::Widget &widget, int &minimum_width, int &natural_width) const { switch (m_property_type.get_value()) { case RenderType::Folder: @@ -733,6 +738,51 @@ void CellRendererChannels::render_vfunc_voice_participant(const Cairo::RefPtr<Ca cr->rectangle(icon_x, icon_y, icon_w, icon_h); cr->fill(); } + + auto *paned = dynamic_cast<Gtk::Paned *>(widget.get_ancestor(Gtk::Paned::get_type())); + if (paned != nullptr) { + const auto edge = std::min(paned->get_position(), background_area.get_width()); + + const static std::array<std::pair<VoiceStateFlags, Glib::ustring>, 3> icon_order = { { + { VoiceStateFlags::SelfMute | VoiceStateFlags::Mute, "microphone-disabled-symbolic" }, + { VoiceStateFlags::SelfDeaf | VoiceStateFlags::Deaf, "audio-volume-muted-symbolic" }, + { VoiceStateFlags::SelfVideo, "camera-web-symbolic" }, + } }; + + constexpr static int IconSize = 18; + constexpr static int IconPad = 2; + + const VoiceStateFlags voice_flags = m_property_voice_state.get_value(); + + int offset = 0; + for (auto iter = icon_order.rbegin(); iter != icon_order.rend(); iter++) { + const auto &[flag, icon] = *iter; + if ((voice_flags & flag) == VoiceStateFlags::Clear) continue; + + const double icon_w = 18; + const double icon_h = 18; + const double icon_x = background_area.get_x() + edge - icon_w + offset; + const double icon_y = background_area.get_y() + background_area.get_height() / 2 - icon_h / 2; + Gdk::Rectangle icon_cell_area(icon_x, icon_y, icon_w, icon_h); + + offset -= (IconSize + IconPad); + + const bool is_server_mute = (voice_flags & VoiceStateFlags::Mute) == VoiceStateFlags::Mute; + const bool is_server_deaf = (voice_flags & VoiceStateFlags::Deaf) == VoiceStateFlags::Deaf; + auto context = widget.get_style_context(); + if (is_server_mute || is_server_deaf) { + context->context_save(); + context->add_class("voice-state-server"); + } + + m_renderer_pixbuf.property_icon_name() = icon; + m_renderer_pixbuf.render(cr, widget, background_area, icon_cell_area, flags); + + if (is_server_mute || is_server_deaf) { + context->context_restore(); + } + } + } } #endif diff --git a/src/components/channelscellrenderer.hpp b/src/components/channelscellrenderer.hpp index f6859dc..934ce5b 100644 --- a/src/components/channelscellrenderer.hpp +++ b/src/components/channelscellrenderer.hpp @@ -3,6 +3,8 @@ #include <glibmm/property.h> #include <map> #include "discord/snowflake.hpp" +#include "discord/voicestateflags.hpp" +#include "misc/bitwise.hpp" enum class RenderType : uint8_t { Folder, @@ -34,6 +36,7 @@ public: Glib::PropertyProxy<bool> property_expanded(); Glib::PropertyProxy<bool> property_nsfw(); Glib::PropertyProxy<std::optional<Gdk::RGBA>> property_color(); + Glib::PropertyProxy<VoiceStateFlags> property_voice_state(); protected: void get_preferred_width_vfunc(Gtk::Widget &widget, int &minimum_width, int &natural_width) const override; @@ -113,7 +116,7 @@ protected: const Gdk::Rectangle &cell_area, Gtk::CellRendererState flags); - // voice channel + // voice participant void get_preferred_width_vfunc_voice_participant(Gtk::Widget &widget, int &minimum_width, int &natural_width) const; void get_preferred_width_for_height_vfunc_voice_participant(Gtk::Widget &widget, int height, int &minimum_width, int &natural_width) const; void get_preferred_height_vfunc_voice_participant(Gtk::Widget &widget, int &minimum_height, int &natural_height) const; @@ -152,6 +155,7 @@ protected: private: Gtk::CellRendererText m_renderer_text; + Gtk::CellRendererPixbuf m_renderer_pixbuf; Glib::Property<RenderType> m_property_type; // all Glib::Property<Glib::ustring> m_property_name; // all @@ -161,6 +165,7 @@ private: Glib::Property<bool> m_property_expanded; // category Glib::Property<bool> m_property_nsfw; // channel Glib::Property<std::optional<Gdk::RGBA>> m_property_color; // folder + Glib::Property<VoiceStateFlags> m_property_voice_state; // same pitfalls as in https://github.com/uowuo/abaddon/blob/60404783bd4ce9be26233fe66fc3a74475d9eaa3/components/cellrendererpixbufanimation.hpp#L32-L39 // this will manifest though since guild icons can change diff --git a/src/discord/discord.cpp b/src/discord/discord.cpp index be939be..37d4f28 100644 --- a/src/discord/discord.cpp +++ b/src/discord/discord.cpp @@ -1227,8 +1227,8 @@ std::optional<uint32_t> DiscordClient::GetSSRCOfUser(Snowflake id) const { return m_voice.GetSSRCOfUser(id); } -std::optional<Snowflake> DiscordClient::GetVoiceState(Snowflake user_id) const { - if (const auto it = m_voice_state_user_channel.find(user_id); it != m_voice_state_user_channel.end()) { +std::optional<std::pair<Snowflake, VoiceStateFlags>> DiscordClient::GetVoiceState(Snowflake user_id) const { + if (const auto it = m_voice_states.find(user_id); it != m_voice_states.end()) { return it->second; } return std::nullopt; @@ -2267,9 +2267,9 @@ void DiscordClient::HandleGatewayVoiceStateUpdate(const GatewayMessage &msg) { if (data.ChannelID.has_value()) { const auto old_state = GetVoiceState(data.UserID); - SetVoiceState(data.UserID, *data.ChannelID); - if (old_state.has_value() && *old_state != *data.ChannelID) { - m_signal_voice_user_disconnect.emit(data.UserID, *old_state); + SetVoiceState(data.UserID, data); + if (old_state.has_value() && old_state->first != *data.ChannelID) { + m_signal_voice_user_disconnect.emit(data.UserID, old_state->first); m_signal_voice_user_connect.emit(data.UserID, *data.ChannelID); } else if (!old_state.has_value()) { m_signal_voice_user_connect.emit(data.UserID, *data.ChannelID); @@ -2278,7 +2278,7 @@ void DiscordClient::HandleGatewayVoiceStateUpdate(const GatewayMessage &msg) { const auto old_state = GetVoiceState(data.UserID); ClearVoiceState(data.UserID); if (old_state.has_value()) { - m_signal_voice_user_disconnect.emit(data.UserID, *old_state); + m_signal_voice_user_disconnect.emit(data.UserID, old_state->first); } } } @@ -2333,7 +2333,7 @@ void DiscordClient::HandleGatewayReadySupplemental(const GatewayMessage &msg) { for (const auto &g : data.Guilds) { for (const auto &s : g.VoiceStates) { if (s.ChannelID.has_value()) { - SetVoiceState(s.UserID, *s.ChannelID); + SetVoiceState(s.UserID, s); } } } @@ -2798,18 +2798,33 @@ void DiscordClient::SendVoiceStateUpdate() { m_websocket.Send(msg); } -void DiscordClient::SetVoiceState(Snowflake user_id, Snowflake channel_id) { - spdlog::get("discord")->debug("SetVoiceState: {} -> {}", user_id, channel_id); - m_voice_state_user_channel[user_id] = channel_id; - m_voice_state_channel_users[channel_id].insert(user_id); +void DiscordClient::SetVoiceState(Snowflake user_id, const VoiceState &state) { + if (!state.ChannelID.has_value()) { + spdlog::get("discord")->error("SetVoiceState called with missing channel ID"); + return; + } + spdlog::get("discord")->debug("SetVoiceState: {} -> {}", user_id, *state.ChannelID); + + auto flags = VoiceStateFlags::Clear; + if (state.IsSelfMuted) flags |= VoiceStateFlags::SelfMute; + if (state.IsSelfDeafened) flags |= VoiceStateFlags::SelfDeaf; + if (state.IsMuted) flags |= VoiceStateFlags::Mute; + if (state.IsDeafened) flags |= VoiceStateFlags::Deaf; + if (state.IsSelfStream) flags |= VoiceStateFlags::SelfStream; + if (state.IsSelfVideo) flags |= VoiceStateFlags::SelfVideo; + + m_voice_states[user_id] = std::make_pair(*state.ChannelID, flags); + m_voice_state_channel_users[*state.ChannelID].insert(user_id); + + m_signal_voice_state_set.emit(user_id, *state.ChannelID, flags); } void DiscordClient::ClearVoiceState(Snowflake user_id) { spdlog::get("discord")->debug("ClearVoiceState: {}", user_id); - if (const auto it = m_voice_state_user_channel.find(user_id); it != m_voice_state_user_channel.end()) { - m_voice_state_channel_users[it->second].erase(user_id); + if (const auto it = m_voice_states.find(user_id); it != m_voice_states.end()) { + m_voice_state_channel_users[it->second.first].erase(user_id); // invalidated - m_voice_state_user_channel.erase(user_id); + m_voice_states.erase(user_id); } } @@ -3119,4 +3134,8 @@ DiscordClient::type_signal_voice_client_state_update DiscordClient::signal_voice DiscordClient::type_signal_voice_channel_changed DiscordClient::signal_voice_channel_changed() { return m_signal_voice_channel_changed; } + +DiscordClient::type_signal_voice_state_set DiscordClient::signal_voice_state_set() { + return m_signal_voice_state_set; +} #endif diff --git a/src/discord/discord.hpp b/src/discord/discord.hpp index cb76ef6..7f7518c 100644 --- a/src/discord/discord.hpp +++ b/src/discord/discord.hpp @@ -5,6 +5,7 @@ #include "objects.hpp" #include "store.hpp" #include "voiceclient.hpp" +#include "voicestateflags.hpp" #include "websocket.hpp" #include <sigc++/sigc++.h> #include <nlohmann/json.hpp> @@ -192,7 +193,7 @@ public: [[nodiscard]] Snowflake GetVoiceChannelID() const noexcept; [[nodiscard]] std::unordered_set<Snowflake> GetUsersInVoiceChannel(Snowflake channel_id); [[nodiscard]] std::optional<uint32_t> GetSSRCOfUser(Snowflake id) const; - [[nodiscard]] std::optional<Snowflake> GetVoiceState(Snowflake user_id) const; + [[nodiscard]] std::optional<std::pair<Snowflake, VoiceStateFlags>> GetVoiceState(Snowflake user_id) const; DiscordVoiceClient &GetVoiceClient(); @@ -360,12 +361,12 @@ private: Snowflake m_voice_channel_id; // todo sql i guess - std::unordered_map<Snowflake, Snowflake> m_voice_state_user_channel; + std::unordered_map<Snowflake, std::pair<Snowflake, VoiceStateFlags>> m_voice_states; std::unordered_map<Snowflake, std::unordered_set<Snowflake>> m_voice_state_channel_users; void SendVoiceStateUpdate(); - void SetVoiceState(Snowflake user_id, Snowflake channel_id); + void SetVoiceState(Snowflake user_id, const VoiceState &state); void ClearVoiceState(Snowflake user_id); void OnVoiceConnected(); @@ -455,6 +456,7 @@ public: using type_signal_voice_requested_disconnect = sigc::signal<void()>; using type_signal_voice_client_state_update = sigc::signal<void(DiscordVoiceClient::State)>; using type_signal_voice_channel_changed = sigc::signal<void(Snowflake)>; + using type_signal_voice_state_set = sigc::signal<void(Snowflake, Snowflake, VoiceStateFlags)>; #endif type_signal_gateway_ready signal_gateway_ready(); @@ -522,6 +524,7 @@ public: type_signal_voice_requested_disconnect signal_voice_requested_disconnect(); type_signal_voice_client_state_update signal_voice_client_state_update(); type_signal_voice_channel_changed signal_voice_channel_changed(); + type_signal_voice_state_set signal_voice_state_set(); #endif protected: @@ -590,5 +593,6 @@ protected: type_signal_voice_requested_disconnect m_signal_voice_requested_disconnect; type_signal_voice_client_state_update m_signal_voice_client_state_update; type_signal_voice_channel_changed m_signal_voice_channel_changed; + type_signal_voice_state_set m_signal_voice_state_set; #endif }; diff --git a/src/discord/voicestateflags.hpp b/src/discord/voicestateflags.hpp new file mode 100644 index 0000000..e369001 --- /dev/null +++ b/src/discord/voicestateflags.hpp @@ -0,0 +1,17 @@ +#pragma once +#include <cstdint> + +enum class VoiceStateFlags : uint8_t { + Clear = 0, + Deaf = 1 << 0, + Mute = 1 << 1, + SelfDeaf = 1 << 2, + SelfMute = 1 << 3, + SelfStream = 1 << 4, + SelfVideo = 1 << 5, +}; + +template<> +struct Bitwise<VoiceStateFlags> { + static const bool enable = true; +}; |