summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.github/workflows/ci.yml14
-rw-r--r--ci/used-icons.txt7
-rw-r--r--res/css/main.css4
-rw-r--r--src/components/channels.cpp14
-rw-r--r--src/components/channels.hpp2
-rw-r--r--src/components/channelscellrenderer.cpp52
-rw-r--r--src/components/channelscellrenderer.hpp7
-rw-r--r--src/discord/discord.cpp47
-rw-r--r--src/discord/discord.hpp10
-rw-r--r--src/discord/voicestateflags.hpp17
10 files changed, 146 insertions, 28 deletions
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 63511cc..2a1bd25 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -93,13 +93,13 @@ jobs:
cd ${artifact_dir}/share/icons/Adwaita
mkdir -p 16x16/actions 24x24/actions 32x32/actions 48x48/actions 64x64/actions 96x96/actions scalable/actions
cd ${GITHUB_WORKSPACE}
- cat "ci/used-icons.txt" | sed 's/\r$//' | xargs -I % cp ci/gtk-for-windows/gtk-nsis-pack/share/icons/Adwaita/16x16/actions/%.symbolic.png ${artifact_dir}/share/icons/Adwaita/16x16/actions || :
- cat "ci/used-icons.txt" | sed 's/\r$//' | xargs -I % cp ci/gtk-for-windows/gtk-nsis-pack/share/icons/Adwaita/24x24/actions/%.symbolic.png ${artifact_dir}/share/icons/Adwaita/24x24/actions || :
- cat "ci/used-icons.txt" | sed 's/\r$//' | xargs -I % cp ci/gtk-for-windows/gtk-nsis-pack/share/icons/Adwaita/32x32/actions/%.symbolic.png ${artifact_dir}/share/icons/Adwaita/32x32/actions || :
- cat "ci/used-icons.txt" | sed 's/\r$//' | xargs -I % cp ci/gtk-for-windows/gtk-nsis-pack/share/icons/Adwaita/48x48/actions/%.symbolic.png ${artifact_dir}/share/icons/Adwaita/48x48/actions || :
- cat "ci/used-icons.txt" | sed 's/\r$//' | xargs -I % cp ci/gtk-for-windows/gtk-nsis-pack/share/icons/Adwaita/64x64/actions/%.symbolic.png ${artifact_dir}/share/icons/Adwaita/64x64/actions || :
- cat "ci/used-icons.txt" | sed 's/\r$//' | xargs -I % cp ci/gtk-for-windows/gtk-nsis-pack/share/icons/Adwaita/96x96/actions/%.symbolic.png ${artifact_dir}/share/icons/Adwaita/96x96/actions || :
- cat "ci/used-icons.txt" | sed 's/\r$//' | xargs -I % cp ci/gtk-for-windows/gtk-nsis-pack/share/icons/Adwaita/scalable/actions/%.svg ${artifact_dir}/share/icons/Adwaita/scalable/actions || :
+ cat "ci/used-icons.txt" | sed 's/\r$//' | xargs -I % cp ci/gtk-for-windows/gtk-nsis-pack/share/icons/Adwaita/16x16/%.symbolic.png ${artifact_dir}/share/icons/Adwaita/16x16/%.symbolic.png || :
+ cat "ci/used-icons.txt" | sed 's/\r$//' | xargs -I % cp ci/gtk-for-windows/gtk-nsis-pack/share/icons/Adwaita/24x24/%.symbolic.png ${artifact_dir}/share/icons/Adwaita/24x24/%.symbolic.png || :
+ cat "ci/used-icons.txt" | sed 's/\r$//' | xargs -I % cp ci/gtk-for-windows/gtk-nsis-pack/share/icons/Adwaita/32x32/%.symbolic.png ${artifact_dir}/share/icons/Adwaita/32x32/%.symbolic.png || :
+ cat "ci/used-icons.txt" | sed 's/\r$//' | xargs -I % cp ci/gtk-for-windows/gtk-nsis-pack/share/icons/Adwaita/48x48/%.symbolic.png ${artifact_dir}/share/icons/Adwaita/48x48/%.symbolic.png || :
+ cat "ci/used-icons.txt" | sed 's/\r$//' | xargs -I % cp ci/gtk-for-windows/gtk-nsis-pack/share/icons/Adwaita/64x64/%.symbolic.png ${artifact_dir}/share/icons/Adwaita/64x64/%.symbolic.png || :
+ cat "ci/used-icons.txt" | sed 's/\r$//' | xargs -I % cp ci/gtk-for-windows/gtk-nsis-pack/share/icons/Adwaita/96x96/%.symbolic.png ${artifact_dir}/share/icons/Adwaita/96x96/%.symbolic.png || :
+ cat "ci/used-icons.txt" | sed 's/\r$//' | xargs -I % cp ci/gtk-for-windows/gtk-nsis-pack/share/icons/Adwaita/scalable/%.svg ${artifact_dir}/share/icons/Adwaita/scalable/%.svg || :
- name: Upload build (1)
uses: haya14busa/action-cond@v1
diff --git a/ci/used-icons.txt b/ci/used-icons.txt
index 7e00c12..0ea18ae 100644
--- a/ci/used-icons.txt
+++ b/ci/used-icons.txt
@@ -1,2 +1,5 @@
-document-send-symbolic
-call-stop-symbolic
+actions/document-send-symbolic
+actions/call-stop-symbolic
+status/microphone-disabled-symbolic
+status/audio-volume-muted-symbolic
+devices/camera-web-symbolic
diff --git a/res/css/main.css b/res/css/main.css
index 4b76bb4..dcbdf4f 100644
--- a/res/css/main.css
+++ b/res/css/main.css
@@ -378,3 +378,7 @@
.voice-info-location {
}
+
+.voice-state-server {
+ color: red;
+}
diff --git a/src/components/channels.cpp b/src/components/channels.cpp
index 5e3da86..0e97837 100644
--- a/src/components/channels.cpp
+++ b/src/components/channels.cpp
@@ -96,6 +96,7 @@ ChannelList::ChannelList()
column->add_attribute(renderer->property_expanded(), m_columns.m_expanded);
column->add_attribute(renderer->property_nsfw(), m_columns.m_nsfw);
column->add_attribute(renderer->property_color(), m_columns.m_color);
+ column->add_attribute(renderer->property_voice_state(), m_columns.m_voice_flags);
m_view.append_column(*column);
m_menu_guild_copy_id.signal_activate().connect([this] {
@@ -282,6 +283,7 @@ ChannelList::ChannelList()
#if WITH_VOICE
discord.signal_voice_user_connect().connect(sigc::mem_fun(*this, &ChannelList::OnVoiceUserConnect));
discord.signal_voice_user_disconnect().connect(sigc::mem_fun(*this, &ChannelList::OnVoiceUserDisconnect));
+ discord.signal_voice_state_set().connect(sigc::mem_fun(*this, &ChannelList::OnVoiceStateSet));
#endif
}
@@ -537,6 +539,12 @@ void ChannelList::OnVoiceUserDisconnect(Snowflake user_id, Snowflake channel_id)
m_model->erase(iter);
}
}
+
+void ChannelList::OnVoiceStateSet(Snowflake user_id, Snowflake channel_id, VoiceStateFlags flags) {
+ if (auto iter = GetIteratorForRowFromIDOfType(user_id, RenderType::VoiceParticipant)) {
+ (*iter)[m_columns.m_voice_flags] = flags;
+ }
+}
#endif
void ChannelList::DeleteThreadRow(Snowflake id) {
@@ -857,6 +865,11 @@ Gtk::TreeModel::iterator ChannelList::CreateVoiceParticipantRow(const UserData &
row[m_columns.m_id] = user.ID;
row[m_columns.m_name] = user.GetEscapedName();
+ const auto voice_state = Abaddon::Get().GetDiscordClient().GetVoiceState(user.ID);
+ if (voice_state.has_value()) {
+ row[m_columns.m_voice_flags] = voice_state->second;
+ }
+
auto &img = Abaddon::Get().GetImageManager();
row[m_columns.m_icon] = img.GetPlaceholder(VoiceParticipantIconSize);
const auto cb = [this, user_id = user.ID](const Glib::RefPtr<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;
+};