mirror of
https://github.com/uowuo/abaddon.git
synced 2025-03-04 03:03:16 -05:00
Merge branch 'master' into stages
This commit is contained in:
commit
e65174f5aa
@ -66,7 +66,9 @@ if (ENABLE_QRCODE_LOGIN)
|
||||
target_compile_definitions(abaddon PRIVATE WITH_QRLOGIN)
|
||||
endif ()
|
||||
|
||||
if (NOT (APPLE AND CMAKE_CXX_COMPILER_ID STREQUAL "GNU"))
|
||||
target_precompile_headers(abaddon PRIVATE <gtkmm.h> src/abaddon.hpp src/util.hpp)
|
||||
endif ()
|
||||
|
||||
if ((CMAKE_CXX_COMPILER_ID STREQUAL "GNU") OR
|
||||
(CMAKE_CXX_COMPILER_ID STREQUAL "Clang" AND
|
||||
@ -113,11 +115,12 @@ target_link_libraries(abaddon spdlog::spdlog)
|
||||
|
||||
target_link_libraries(abaddon ${SQLite3_LIBRARIES})
|
||||
target_link_libraries(abaddon ${GTKMM_LIBRARIES})
|
||||
target_link_libraries(abaddon ${CURL_LIBRARIES})
|
||||
target_link_libraries(abaddon ${ZLIB_LIBRARY})
|
||||
target_link_libraries(abaddon ${NLOHMANN_JSON_LIBRARIES})
|
||||
target_link_libraries(abaddon ${CMAKE_DL_LIBS})
|
||||
|
||||
target_link_libraries(abaddon CURL::libcurl)
|
||||
|
||||
include(CheckAtomic)
|
||||
if (NOT HAVE_CXX_ATOMICS_WITHOUT_LIB OR NOT HAVE_CXX_ATOMICS64_WITHOUT_LIB)
|
||||
target_link_libraries(abaddon atomic)
|
||||
@ -146,6 +149,7 @@ if (APPLE)
|
||||
target_link_libraries(abaddon "-framework CoreFoundation")
|
||||
target_link_libraries(abaddon "-framework CoreAudio")
|
||||
target_link_libraries(abaddon "-framework AudioToolbox")
|
||||
target_link_libraries(abaddon "-framework AudioUnit")
|
||||
endif ()
|
||||
|
||||
if (ENABLE_VOICE)
|
||||
|
@ -347,7 +347,7 @@ For example, memory_db would be set by adding `memory_db = true` under the line
|
||||
|
||||
| Setting | Type | Default | Description |
|
||||
|---------------|---------|---------|-------------------------|
|
||||
| `hideconsole` | boolean | true | Hide console on startup |
|
||||
| `hideconsole` | boolean | false | Hide console on startup |
|
||||
|
||||
### Environment variables
|
||||
|
||||
|
@ -40,6 +40,7 @@
|
||||
/bin/libjpeg-8.dll
|
||||
/bin/liblzma-5.dll
|
||||
/bin/libnghttp2-14.dll
|
||||
/bin/libnghttp3-9.dll
|
||||
/bin/libopus-0.dll
|
||||
/bin/libpango-1.0-0.dll
|
||||
/bin/libpangocairo-1.0-0.dll
|
||||
|
@ -157,6 +157,11 @@
|
||||
min-height: 0px;
|
||||
}
|
||||
|
||||
.classic-mention-overlay:selected {
|
||||
color: @theme_selected_color;
|
||||
background-color: @theme_selected_bg_color;
|
||||
}
|
||||
|
||||
.channel-list .view:selected {
|
||||
background-color: @theme_selected_bg_color;
|
||||
}
|
||||
@ -176,6 +181,10 @@
|
||||
background: radial-gradient(7px circle at left, @theme_selected_bg_color 50%, transparent 20%);
|
||||
}
|
||||
|
||||
.classic-guild-list-folder.has-unread stack {
|
||||
background: radial-gradient(7px circle at left, @theme_selected_bg_color 50%, transparent 20%);
|
||||
}
|
||||
|
||||
.classic-guild-list-guild box, .classic-guild-list-folder stack {
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
#include "abaddon.hpp"
|
||||
#include <cstdlib>
|
||||
#include <memory>
|
||||
#include <spdlog/spdlog.h>
|
||||
#include <spdlog/cfg/env.h>
|
||||
@ -1153,7 +1154,32 @@ void Abaddon::on_window_hide() {
|
||||
}
|
||||
}
|
||||
|
||||
// clang-format off
|
||||
|
||||
#ifdef __GLIBC__
|
||||
#ifndef _GNU_SOURCE
|
||||
#define _GNU_SOURCE
|
||||
#include <features.h>
|
||||
#ifndef __USE_GNU
|
||||
#define __MUSL__
|
||||
#endif
|
||||
#undef _GNU_SOURCE
|
||||
#else
|
||||
#include <features.h>
|
||||
#ifndef __USE_GNU
|
||||
#define __MUSL__
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// clang-format on
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
#ifdef __MUSL__
|
||||
char env[] = "LANG=C";
|
||||
putenv(env);
|
||||
#endif
|
||||
|
||||
if (std::getenv("ABADDON_NO_FC") == nullptr)
|
||||
Platform::SetupFonts();
|
||||
|
||||
|
@ -1,13 +1,15 @@
|
||||
#include "cellrendererchannels.hpp"
|
||||
|
||||
#include <gdkmm/general.h>
|
||||
|
||||
#include "misc/cairo.hpp"
|
||||
|
||||
#include "abaddon.hpp"
|
||||
|
||||
constexpr static int MentionsRightPad = 7;
|
||||
#ifndef M_PI
|
||||
constexpr static double M_PI = 3.14159265358979;
|
||||
#endif
|
||||
constexpr static double M_PI_H = M_PI / 2.0;
|
||||
constexpr static double M_PI_3_2 = M_PI * 3.0 / 2.0;
|
||||
|
||||
void AddUnreadIndicator(const Cairo::RefPtr<Cairo::Context> &cr, Gtk::Widget &widget, const Gdk::Rectangle &background_area) {
|
||||
static const auto color_setting = Gdk::RGBA(Abaddon::Get().GetSettings().UnreadIndicatorColor);
|
||||
@ -120,13 +122,11 @@ 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:
|
||||
case RenderType::VoiceStage:
|
||||
return get_preferred_width_vfunc_voice_channel(widget, minimum_width, natural_width);
|
||||
case RenderType::VoiceParticipant:
|
||||
return get_preferred_width_vfunc_voice_participant(widget, minimum_width, natural_width);
|
||||
#endif
|
||||
case RenderType::DMHeader:
|
||||
return get_preferred_width_vfunc_dmheader(widget, minimum_width, natural_width);
|
||||
case RenderType::DM:
|
||||
@ -146,13 +146,11 @@ 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:
|
||||
case RenderType::VoiceStage:
|
||||
return get_preferred_width_for_height_vfunc_voice_channel(widget, height, minimum_width, natural_width);
|
||||
case RenderType::VoiceParticipant:
|
||||
return get_preferred_width_for_height_vfunc_voice_participant(widget, height, minimum_width, natural_width);
|
||||
#endif
|
||||
case RenderType::DMHeader:
|
||||
return get_preferred_width_for_height_vfunc_dmheader(widget, height, minimum_width, natural_width);
|
||||
case RenderType::DM:
|
||||
@ -172,13 +170,11 @@ 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:
|
||||
case RenderType::VoiceStage:
|
||||
return get_preferred_height_vfunc_voice_channel(widget, minimum_height, natural_height);
|
||||
case RenderType::VoiceParticipant:
|
||||
return get_preferred_height_vfunc_voice_participant(widget, minimum_height, natural_height);
|
||||
#endif
|
||||
case RenderType::DMHeader:
|
||||
return get_preferred_height_vfunc_dmheader(widget, minimum_height, natural_height);
|
||||
case RenderType::DM:
|
||||
@ -198,13 +194,11 @@ 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:
|
||||
case RenderType::VoiceStage:
|
||||
return get_preferred_height_for_width_vfunc_voice_channel(widget, width, minimum_height, natural_height);
|
||||
case RenderType::VoiceParticipant:
|
||||
return get_preferred_height_for_width_vfunc_voice_participant(widget, width, minimum_height, natural_height);
|
||||
#endif
|
||||
case RenderType::DMHeader:
|
||||
return get_preferred_height_for_width_vfunc_dmheader(widget, width, minimum_height, natural_height);
|
||||
case RenderType::DM:
|
||||
@ -224,14 +218,12 @@ void CellRendererChannels::render_vfunc(const Cairo::RefPtr<Cairo::Context> &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, "\U0001F50A");
|
||||
case RenderType::VoiceStage:
|
||||
return render_vfunc_voice_channel(cr, widget, background_area, cell_area, flags, "\U0001F4E1");
|
||||
case RenderType::VoiceParticipant:
|
||||
return render_vfunc_voice_participant(cr, widget, background_area, cell_area, flags);
|
||||
#endif
|
||||
case RenderType::DMHeader:
|
||||
return render_vfunc_dmheader(cr, widget, background_area, cell_area, flags);
|
||||
case RenderType::DM:
|
||||
@ -567,8 +559,6 @@ void CellRendererChannels::render_vfunc_thread(const Cairo::RefPtr<Cairo::Contex
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef WITH_VOICE
|
||||
|
||||
// voice channel
|
||||
|
||||
void CellRendererChannels::get_preferred_width_vfunc_voice_channel(Gtk::Widget &widget, int &minimum_width, int &natural_width) const {
|
||||
@ -616,6 +606,21 @@ void CellRendererChannels::render_vfunc_voice_channel(const Cairo::RefPtr<Cairo:
|
||||
layout->show_in_cairo_context(cr);
|
||||
|
||||
RenderExpander(24, cr, widget, background_area, property_expanded());
|
||||
|
||||
// unread
|
||||
if (!Abaddon::Get().GetSettings().Unreads) return;
|
||||
|
||||
const auto id = m_property_id.get_value();
|
||||
const auto unread_state = Abaddon::Get().GetDiscordClient().GetUnreadStateForChannel(id);
|
||||
|
||||
if (unread_state < 1) return;
|
||||
|
||||
auto *paned = dynamic_cast<Gtk::Paned *>(widget.get_ancestor(Gtk::Paned::get_type()));
|
||||
if (paned != nullptr) {
|
||||
const auto edge = std::min(paned->get_position(), cell_area.get_width());
|
||||
|
||||
unread_render_mentions(cr, widget, unread_state, edge, cell_area);
|
||||
}
|
||||
}
|
||||
|
||||
// voice participant
|
||||
@ -718,8 +723,6 @@ void CellRendererChannels::render_vfunc_voice_participant(const Cairo::RefPtr<Ca
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
// dm header
|
||||
|
||||
void CellRendererChannels::get_preferred_width_vfunc_dmheader(Gtk::Widget &widget, int &minimum_width, int &natural_width) const {
|
||||
@ -838,17 +841,6 @@ void CellRendererChannels::render_vfunc_dm(const Cairo::RefPtr<Cairo::Context> &
|
||||
}
|
||||
}
|
||||
|
||||
void CellRendererChannels::cairo_path_rounded_rect(const Cairo::RefPtr<Cairo::Context> &cr, double x, double y, double w, double h, double r) {
|
||||
const double degrees = M_PI / 180.0;
|
||||
|
||||
cr->begin_new_sub_path();
|
||||
cr->arc(x + w - r, y + r, r, -M_PI_H, 0);
|
||||
cr->arc(x + w - r, y + h - r, r, 0, M_PI_H);
|
||||
cr->arc(x + r, y + h - r, r, M_PI_H, M_PI);
|
||||
cr->arc(x + r, y + r, r, M_PI, M_PI_3_2);
|
||||
cr->close_path();
|
||||
}
|
||||
|
||||
void CellRendererChannels::unread_render_mentions(const Cairo::RefPtr<Cairo::Context> &cr, Gtk::Widget &widget, int mentions, int edge, const Gdk::Rectangle &cell_area) {
|
||||
Pango::FontDescription font;
|
||||
font.set_family("sans 14");
|
||||
@ -869,7 +861,7 @@ void CellRendererChannels::unread_render_mentions(const Cairo::RefPtr<Cairo::Con
|
||||
|
||||
const auto x = cell_area.get_x() + edge - width - MentionsRightPad;
|
||||
const auto y = cell_area.get_y() + cell_area.get_height() / 2.0 - height / 2.0 - 1;
|
||||
cairo_path_rounded_rect(cr, x - 4, y + 2, width + 8, height, 5);
|
||||
CairoUtil::PathRoundedRect(cr, x - 4, y + 2, width + 8, height, 5);
|
||||
cr->set_source_rgb(bg.get_red(), bg.get_green(), bg.get_blue());
|
||||
cr->fill();
|
||||
cr->set_source_rgb(text.get_red(), text.get_green(), text.get_blue());
|
||||
|
@ -15,13 +15,9 @@ enum class RenderType : uint8_t {
|
||||
Category,
|
||||
TextChannel,
|
||||
Thread,
|
||||
|
||||
// TODO: maybe enable anyways but without ability to join if no voice support
|
||||
#ifdef WITH_VOICE
|
||||
VoiceChannel,
|
||||
VoiceStage, // identical to non-stage except for icon
|
||||
VoiceParticipant,
|
||||
#endif
|
||||
|
||||
DMHeader,
|
||||
DM,
|
||||
@ -108,7 +104,6 @@ protected:
|
||||
const Gdk::Rectangle &cell_area,
|
||||
Gtk::CellRendererState flags);
|
||||
|
||||
#ifdef WITH_VOICE
|
||||
// voice channel
|
||||
void get_preferred_width_vfunc_voice_channel(Gtk::Widget &widget, int &minimum_width, int &natural_width) const;
|
||||
void get_preferred_width_for_height_vfunc_voice_channel(Gtk::Widget &widget, int height, int &minimum_width, int &natural_width) const;
|
||||
@ -131,7 +126,6 @@ protected:
|
||||
const Gdk::Rectangle &background_area,
|
||||
const Gdk::Rectangle &cell_area,
|
||||
Gtk::CellRendererState flags);
|
||||
#endif
|
||||
|
||||
// dm header
|
||||
void get_preferred_width_vfunc_dmheader(Gtk::Widget &widget, int &minimum_width, int &natural_width) const;
|
||||
@ -155,7 +149,6 @@ protected:
|
||||
const Gdk::Rectangle &cell_area,
|
||||
Gtk::CellRendererState flags);
|
||||
|
||||
static void cairo_path_rounded_rect(const Cairo::RefPtr<Cairo::Context> &cr, double x, double y, double w, double h, double r);
|
||||
static void unread_render_mentions(const Cairo::RefPtr<Cairo::Context> &cr, Gtk::Widget &widget, int mentions, int edge, const Gdk::Rectangle &cell_area);
|
||||
|
||||
private:
|
||||
|
@ -26,12 +26,12 @@ ChannelListTree::ChannelListTree()
|
||||
, 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)
|
||||
, m_menu_voice_stage_join("_Join", true)
|
||||
, m_menu_voice_stage_disconnect("_Disconnect", true)
|
||||
#endif
|
||||
, m_menu_voice_channel_mark_as_read("Mark as _Read", true)
|
||||
, m_menu_voice_open_chat("Open _Chat", true)
|
||||
, m_menu_dm_copy_id("_Copy ID", true)
|
||||
, m_menu_dm_close("") // changes depending on if group or not
|
||||
#ifdef WITH_VOICE
|
||||
@ -210,11 +210,24 @@ ChannelListTree::ChannelListTree()
|
||||
m_menu_voice_channel_disconnect.signal_activate().connect([this]() {
|
||||
m_signal_action_disconnect_voice.emit();
|
||||
});
|
||||
#endif
|
||||
|
||||
m_menu_voice_channel_mark_as_read.signal_activate().connect([this]() {
|
||||
Abaddon::Get().GetDiscordClient().MarkChannelAsRead(static_cast<Snowflake>((*m_model->get_iter(m_path_for_menu))[m_columns.m_id]), NOOP_CALLBACK);
|
||||
});
|
||||
|
||||
m_menu_voice_open_chat.signal_activate().connect([this]() {
|
||||
const auto id = static_cast<Snowflake>((*m_model->get_iter(m_path_for_menu))[m_columns.m_id]);
|
||||
m_signal_action_channel_item_select.emit(id);
|
||||
});
|
||||
|
||||
m_menu_voice_channel.append(m_menu_voice_channel_join);
|
||||
m_menu_voice_channel.append(m_menu_voice_channel_disconnect);
|
||||
m_menu_voice_channel.append(m_menu_voice_channel_mark_as_read);
|
||||
m_menu_voice_channel.append(m_menu_voice_open_chat);
|
||||
m_menu_voice_channel.show_all();
|
||||
|
||||
#ifdef WITH_VOICE
|
||||
m_menu_voice_stage_join.signal_activate().connect([this]() {
|
||||
const auto id = static_cast<Snowflake>((*m_model->get_iter(m_path_for_menu))[m_columns.m_id]);
|
||||
m_signal_action_join_voice_channel.emit(id);
|
||||
@ -223,11 +236,11 @@ ChannelListTree::ChannelListTree()
|
||||
m_menu_voice_stage_disconnect.signal_activate().connect([this]() {
|
||||
m_signal_action_disconnect_voice.emit();
|
||||
});
|
||||
#endif
|
||||
|
||||
m_menu_voice_stage.append(m_menu_voice_stage_join);
|
||||
m_menu_voice_stage.append(m_menu_voice_stage_disconnect);
|
||||
m_menu_voice_stage.show_all();
|
||||
#endif
|
||||
|
||||
m_menu_dm_copy_id.signal_activate().connect([this] {
|
||||
Gtk::Clipboard::get()->set_text(std::to_string((*m_model->get_iter(m_path_for_menu))[m_columns.m_id]));
|
||||
@ -370,10 +383,8 @@ int ChannelListTree::SortFunc(const Gtk::TreeModel::iterator &a, const Gtk::Tree
|
||||
const int64_t b_sort = (*b)[m_columns.m_sort];
|
||||
if (a_type == RenderType::DMHeader) return -1;
|
||||
if (b_type == RenderType::DMHeader) return 1;
|
||||
#ifdef WITH_VOICE
|
||||
if (a_type == RenderType::TextChannel && (b_type == RenderType::VoiceChannel || b_type == RenderType::VoiceStage)) return -1;
|
||||
if (b_type == RenderType::TextChannel && (a_type == RenderType::VoiceChannel || a_type == RenderType::VoiceStage)) return 1;
|
||||
#endif
|
||||
return static_cast<int>(std::clamp(a_sort - b_sort, int64_t(-1), int64_t(1)));
|
||||
}
|
||||
|
||||
@ -505,7 +516,9 @@ void ChannelListTree::UpdateChannel(Snowflake id) {
|
||||
auto channel = Abaddon::Get().GetDiscordClient().GetChannel(id);
|
||||
if (!iter || !channel.has_value()) return;
|
||||
if (channel->Type == ChannelType::GUILD_CATEGORY) return UpdateChannelCategory(*channel);
|
||||
if (!channel->IsText()) return;
|
||||
// TODO: theres like 4 different fucking ways of checking if somethin g is text or voice can i fix that How stupid .
|
||||
// fun fact clang-format is indenting me right now i wonder why ,,,,,,,,,,,
|
||||
if (!channel->IsText() && channel->Type != ChannelType::GUILD_VOICE) return;
|
||||
|
||||
// refresh stuff that might have changed
|
||||
const bool is_orphan_TMP = !channel->ParentID.has_value();
|
||||
@ -527,7 +540,7 @@ void ChannelListTree::UpdateChannel(Snowflake id) {
|
||||
void ChannelListTree::UpdateCreateChannel(const ChannelData &channel) {
|
||||
if (channel.Type == ChannelType::GUILD_CATEGORY) return (void)UpdateCreateChannelCategory(channel);
|
||||
if (channel.Type == ChannelType::DM || channel.Type == ChannelType::GROUP_DM) return UpdateCreateDMChannel(channel);
|
||||
if (channel.Type != ChannelType::GUILD_TEXT && channel.Type != ChannelType::GUILD_NEWS) return;
|
||||
if (channel.Type != ChannelType::GUILD_TEXT && channel.Type != ChannelType::GUILD_NEWS && channel.Type != ChannelType::GUILD_VOICE) return;
|
||||
|
||||
Gtk::TreeRow channel_row;
|
||||
bool orphan;
|
||||
@ -540,7 +553,7 @@ void ChannelListTree::UpdateCreateChannel(const ChannelData &channel) {
|
||||
auto iter = GetIteratorForGuildFromID(*channel.GuildID);
|
||||
channel_row = *m_model->append(iter->children());
|
||||
}
|
||||
channel_row[m_columns.m_type] = RenderType::TextChannel;
|
||||
channel_row[m_columns.m_type] = IsTextChannel(channel.Type) ? RenderType::TextChannel : RenderType::VoiceChannel;
|
||||
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_nsfw] = channel.NSFW();
|
||||
@ -636,7 +649,6 @@ void ChannelListTree::OnThreadListSync(const ThreadListSyncData &data) {
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef WITH_VOICE
|
||||
void ChannelListTree::OnVoiceUserConnect(Snowflake user_id, Snowflake channel_id) {
|
||||
auto parent_iter = GetIteratorForRowFromIDOfType(channel_id, RenderType::VoiceChannel);
|
||||
if (!parent_iter) parent_iter = GetIteratorForRowFromIDOfType(channel_id, RenderType::VoiceStage);
|
||||
@ -659,7 +671,6 @@ void ChannelListTree::OnVoiceStateSet(Snowflake user_id, Snowflake channel_id, V
|
||||
(*iter)[m_columns.m_voice_flags] = flags;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
void ChannelListTree::DeleteThreadRow(Snowflake id) {
|
||||
auto iter = GetIteratorForRowFromID(id);
|
||||
@ -921,11 +932,7 @@ Gtk::TreeModel::iterator ChannelListTree::AddGuild(const GuildData &guild, const
|
||||
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 || channel->Type == ChannelType::GUILD_STAGE_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
|
||||
@ -952,7 +959,6 @@ Gtk::TreeModel::iterator ChannelListTree::AddGuild(const GuildData &guild, const
|
||||
}
|
||||
};
|
||||
|
||||
#ifdef WITH_VOICE
|
||||
auto add_voice_participants = [this, &discord](const ChannelData &channel, const Gtk::TreeNodeChildren &root) {
|
||||
for (auto user_id : discord.GetUsersInVoiceChannel(channel.ID)) {
|
||||
if (const auto user = discord.GetUser(user_id); user.has_value()) {
|
||||
@ -960,25 +966,21 @@ Gtk::TreeModel::iterator ChannelListTree::AddGuild(const GuildData &guild, const
|
||||
}
|
||||
}
|
||||
};
|
||||
#endif
|
||||
|
||||
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;
|
||||
channel_row[m_columns.m_name] = "#" + Glib::Markup::escape_text(*channel.Name);
|
||||
}
|
||||
#ifdef WITH_VOICE
|
||||
else if (channel.Type == ChannelType::GUILD_VOICE) {
|
||||
channel_row[m_columns.m_type] = RenderType::VoiceChannel;
|
||||
channel_row[m_columns.m_name] = Glib::Markup::escape_text(*channel.Name);
|
||||
add_voice_participants(channel, channel_row->children());
|
||||
} else if (channel.Type == ChannelType::GUILD_STAGE_VOICE) {
|
||||
channel_row[m_columns.m_type] = RenderType::VoiceStage;
|
||||
channel_row[m_columns.m_name] = Glib::Markup::escape_text(*channel.Name);
|
||||
add_voice_participants(channel, channel_row->children());
|
||||
} else {
|
||||
channel_row[m_columns.m_type] = RenderType::VoiceChannel;
|
||||
channel_row[m_columns.m_name] = Glib::Markup::escape_text(*channel.Name);
|
||||
add_voice_participants(channel, channel_row->children());
|
||||
}
|
||||
#endif
|
||||
channel_row[m_columns.m_id] = channel.ID;
|
||||
channel_row[m_columns.m_sort] = *channel.Position + OrphanChannelSortOffset;
|
||||
channel_row[m_columns.m_nsfw] = channel.NSFW();
|
||||
@ -1003,18 +1005,15 @@ Gtk::TreeModel::iterator ChannelListTree::AddGuild(const GuildData &guild, const
|
||||
if (IsTextChannel(channel.Type)) {
|
||||
channel_row[m_columns.m_type] = RenderType::TextChannel;
|
||||
channel_row[m_columns.m_name] = "#" + Glib::Markup::escape_text(*channel.Name);
|
||||
}
|
||||
#ifdef WITH_VOICE
|
||||
else if (channel.Type == ChannelType::GUILD_VOICE) {
|
||||
channel_row[m_columns.m_type] = RenderType::VoiceChannel;
|
||||
channel_row[m_columns.m_name] = Glib::Markup::escape_text(*channel.Name);
|
||||
add_voice_participants(channel, channel_row->children());
|
||||
} else if (channel.Type == ChannelType::GUILD_STAGE_VOICE) {
|
||||
channel_row[m_columns.m_type] = RenderType::VoiceStage;
|
||||
channel_row[m_columns.m_name] = Glib::Markup::escape_text(*channel.Name);
|
||||
add_voice_participants(channel, channel_row->children());
|
||||
} else {
|
||||
channel_row[m_columns.m_type] = RenderType::VoiceChannel;
|
||||
channel_row[m_columns.m_name] = Glib::Markup::escape_text(*channel.Name);
|
||||
add_voice_participants(channel, channel_row->children());
|
||||
}
|
||||
#endif
|
||||
channel_row[m_columns.m_id] = channel.ID;
|
||||
channel_row[m_columns.m_sort] = *channel.Position;
|
||||
channel_row[m_columns.m_nsfw] = channel.NSFW();
|
||||
@ -1052,7 +1051,6 @@ Gtk::TreeModel::iterator ChannelListTree::CreateThreadRow(const Gtk::TreeNodeChi
|
||||
return thread_iter;
|
||||
}
|
||||
|
||||
#ifdef WITH_VOICE
|
||||
Gtk::TreeModel::iterator ChannelListTree::CreateVoiceParticipantRow(const UserData &user, const Gtk::TreeNodeChildren &parent) {
|
||||
auto row = *m_model->append(parent);
|
||||
row[m_columns.m_type] = RenderType::VoiceParticipant;
|
||||
@ -1074,7 +1072,6 @@ Gtk::TreeModel::iterator ChannelListTree::CreateVoiceParticipantRow(const UserDa
|
||||
|
||||
return row;
|
||||
}
|
||||
#endif
|
||||
|
||||
void ChannelListTree::UpdateChannelCategory(const ChannelData &channel) {
|
||||
auto iter = GetIteratorForRowFromID(channel.ID);
|
||||
@ -1182,8 +1179,11 @@ bool ChannelListTree::SelectionFunc(const Glib::RefPtr<Gtk::TreeModel> &model, c
|
||||
}
|
||||
}
|
||||
|
||||
auto type = (*model->get_iter(path))[m_columns.m_type];
|
||||
return type == RenderType::TextChannel || type == RenderType::DM || type == RenderType::Thread;
|
||||
const auto type = (*model->get_iter(path))[m_columns.m_type];
|
||||
const auto id = static_cast<Snowflake>((*model->get_iter(path))[m_columns.m_id]);
|
||||
// todo maybe just keep this last check?
|
||||
if (type == RenderType::TextChannel || type == RenderType::DM || type == RenderType::Thread || (id == m_active_channel)) return true;
|
||||
return is_currently_selected;
|
||||
}
|
||||
|
||||
void ChannelListTree::AddPrivateChannels() {
|
||||
@ -1215,13 +1215,11 @@ void ChannelListTree::AddPrivateChannels() {
|
||||
row[m_columns.m_icon] = img.GetPlaceholder(DMIconSize);
|
||||
row[m_columns.m_expanded] = true;
|
||||
|
||||
#ifdef WITH_VOICE
|
||||
for (auto user_id : discord.GetUsersInVoiceChannel(dm_id)) {
|
||||
if (const auto user = discord.GetUser(user_id); user.has_value()) {
|
||||
CreateVoiceParticipantRow(*user, row->children());
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
SetDMChannelIcon(iter, *dm);
|
||||
}
|
||||
@ -1352,7 +1350,6 @@ bool ChannelListTree::OnButtonPressEvent(GdkEventButton *ev) {
|
||||
OnChannelSubmenuPopup();
|
||||
m_menu_channel.popup_at_pointer(reinterpret_cast<GdkEvent *>(ev));
|
||||
break;
|
||||
#ifdef WITH_VOICE
|
||||
case RenderType::VoiceChannel:
|
||||
OnVoiceChannelSubmenuPopup();
|
||||
m_menu_voice_channel.popup_at_pointer(reinterpret_cast<GdkEvent *>(ev));
|
||||
@ -1361,7 +1358,6 @@ bool ChannelListTree::OnButtonPressEvent(GdkEventButton *ev) {
|
||||
OnVoiceStageSubmenuPopup();
|
||||
m_menu_voice_stage.popup_at_pointer(reinterpret_cast<GdkEvent *>(ev));
|
||||
break;
|
||||
#endif
|
||||
case RenderType::DM: {
|
||||
OnDMSubmenuPopup();
|
||||
const auto channel = Abaddon::Get().GetDiscordClient().GetChannel(static_cast<Snowflake>(row[m_columns.m_id]));
|
||||
@ -1454,8 +1450,8 @@ void ChannelListTree::OnChannelSubmenuPopup() {
|
||||
m_menu_channel_toggle_mute.set_label("Mute");
|
||||
}
|
||||
|
||||
#ifdef WITH_VOICE
|
||||
void ChannelListTree::OnVoiceChannelSubmenuPopup() {
|
||||
#ifdef WITH_VOICE
|
||||
const auto iter = m_model->get_iter(m_path_for_menu);
|
||||
if (!iter) return;
|
||||
const auto id = static_cast<Snowflake>((*iter)[m_columns.m_id]);
|
||||
@ -1467,9 +1463,14 @@ void ChannelListTree::OnVoiceChannelSubmenuPopup() {
|
||||
m_menu_voice_channel_join.set_sensitive(true);
|
||||
m_menu_voice_channel_disconnect.set_sensitive(false);
|
||||
}
|
||||
#else
|
||||
m_menu_voice_channel_join.set_sensitive(false);
|
||||
m_menu_voice_channel_disconnect.set_sensitive(false);
|
||||
#endif
|
||||
}
|
||||
|
||||
void ChannelListTree::OnVoiceStageSubmenuPopup() {
|
||||
#ifdef WITH_VOICE
|
||||
const auto iter = m_model->get_iter(m_path_for_menu);
|
||||
if (!iter) return;
|
||||
const auto id = static_cast<Snowflake>((*iter)[m_columns.m_id]);
|
||||
@ -1481,8 +1482,10 @@ void ChannelListTree::OnVoiceStageSubmenuPopup() {
|
||||
m_menu_voice_stage_join.set_sensitive(true);
|
||||
m_menu_voice_stage_disconnect.set_sensitive(false);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
m_menu_voice_stage_join.set_sensitive(false);
|
||||
m_menu_voice_stage_disconnect.set_sensitive(false);
|
||||
}
|
||||
|
||||
void ChannelListTree::OnDMSubmenuPopup() {
|
||||
auto iter = m_model->get_iter(m_path_for_menu);
|
||||
|
@ -104,10 +104,7 @@ protected:
|
||||
Gtk::TreeModel::iterator AddGuild(const GuildData &guild, const Gtk::TreeNodeChildren &root);
|
||||
Gtk::TreeModel::iterator UpdateCreateChannelCategory(const ChannelData &channel);
|
||||
Gtk::TreeModel::iterator CreateThreadRow(const Gtk::TreeNodeChildren &children, const ChannelData &channel);
|
||||
|
||||
#ifdef WITH_VOICE
|
||||
Gtk::TreeModel::iterator CreateVoiceParticipantRow(const UserData &user, const Gtk::TreeNodeChildren &parent);
|
||||
#endif
|
||||
|
||||
void UpdateChannelCategory(const ChannelData &channel);
|
||||
|
||||
@ -162,7 +159,6 @@ 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;
|
||||
Gtk::MenuItem m_menu_voice_channel_disconnect;
|
||||
@ -170,7 +166,8 @@ protected:
|
||||
Gtk::Menu m_menu_voice_stage;
|
||||
Gtk::MenuItem m_menu_voice_stage_join;
|
||||
Gtk::MenuItem m_menu_voice_stage_disconnect;
|
||||
#endif
|
||||
Gtk::MenuItem m_menu_voice_channel_mark_as_read;
|
||||
Gtk::MenuItem m_menu_voice_open_chat;
|
||||
|
||||
Gtk::Menu m_menu_dm;
|
||||
Gtk::MenuItem m_menu_dm_copy_id;
|
||||
@ -198,11 +195,8 @@ protected:
|
||||
void OnChannelSubmenuPopup();
|
||||
void OnDMSubmenuPopup();
|
||||
void OnThreadSubmenuPopup();
|
||||
|
||||
#ifdef WITH_VOICE
|
||||
void OnVoiceChannelSubmenuPopup();
|
||||
void OnVoiceStageSubmenuPopup();
|
||||
#endif
|
||||
|
||||
bool m_updating_listing = false;
|
||||
|
||||
|
@ -1,8 +1,11 @@
|
||||
#include <gtkmm/overlay.h>
|
||||
|
||||
#include "guildlist.hpp"
|
||||
|
||||
#include "abaddon.hpp"
|
||||
#include "util.hpp"
|
||||
#include "guildlistfolderitem.hpp"
|
||||
#include "mentionoverlay.hpp"
|
||||
|
||||
class GuildListDMsButton : public Gtk::EventBox {
|
||||
public:
|
||||
@ -93,10 +96,38 @@ void GuildList::UpdateListing() {
|
||||
}
|
||||
}
|
||||
|
||||
static Gtk::Widget *AddMentionOverlay(Gtk::Widget *widget, Snowflake guild_id) {
|
||||
auto *overlay = Gtk::make_managed<Gtk::Overlay>();
|
||||
overlay->add(*widget);
|
||||
auto *mention_overlay = Gtk::make_managed<MentionOverlay>(guild_id);
|
||||
overlay->add_overlay(*mention_overlay);
|
||||
overlay->set_overlay_pass_through(*mention_overlay, true);
|
||||
mention_overlay->signal_realize().connect([mention_overlay]() {
|
||||
mention_overlay->get_window()->set_pass_through(true);
|
||||
});
|
||||
mention_overlay->show();
|
||||
overlay->show();
|
||||
return overlay;
|
||||
}
|
||||
|
||||
static Gtk::Widget *AddMentionOverlay(Gtk::Widget *widget, const UserSettingsGuildFoldersEntry &folder) {
|
||||
auto *overlay = Gtk::make_managed<Gtk::Overlay>();
|
||||
overlay->add(*widget);
|
||||
auto *mention_overlay = Gtk::make_managed<MentionOverlay>(folder);
|
||||
overlay->add_overlay(*mention_overlay);
|
||||
overlay->set_overlay_pass_through(*mention_overlay, true);
|
||||
mention_overlay->signal_realize().connect([mention_overlay]() {
|
||||
mention_overlay->get_window()->set_pass_through(true);
|
||||
});
|
||||
mention_overlay->show();
|
||||
overlay->show();
|
||||
return overlay;
|
||||
}
|
||||
|
||||
void GuildList::AddGuild(Snowflake id) {
|
||||
if (auto item = CreateGuildWidget(id)) {
|
||||
item->show();
|
||||
add(*item);
|
||||
add(*AddMentionOverlay(item, id));
|
||||
}
|
||||
}
|
||||
|
||||
@ -132,12 +163,12 @@ void GuildList::AddFolder(const UserSettingsGuildFoldersEntry &folder) {
|
||||
for (const auto guild_id : folder.GuildIDs) {
|
||||
if (auto *guild_widget = CreateGuildWidget(guild_id)) {
|
||||
guild_widget->show();
|
||||
folder_widget->AddGuildWidget(guild_widget);
|
||||
folder_widget->AddGuildWidget(AddMentionOverlay(guild_widget, guild_id));
|
||||
}
|
||||
}
|
||||
|
||||
folder_widget->show();
|
||||
add(*folder_widget);
|
||||
add(*AddMentionOverlay(folder_widget, folder));
|
||||
}
|
||||
|
||||
void GuildList::Clear() {
|
||||
|
@ -95,7 +95,7 @@ GuildListFolderItem::GuildListFolderItem(const UserSettingsGuildFoldersEntry &fo
|
||||
CheckUnreadStatus();
|
||||
}
|
||||
|
||||
void GuildListFolderItem::AddGuildWidget(GuildListGuildItem *widget) {
|
||||
void GuildListFolderItem::AddGuildWidget(Gtk::Widget *widget) {
|
||||
m_box.add(*widget);
|
||||
}
|
||||
|
||||
|
@ -24,7 +24,7 @@ class GuildListFolderItem : public Gtk::VBox {
|
||||
public:
|
||||
GuildListFolderItem(const UserSettingsGuildFoldersEntry &folder);
|
||||
|
||||
void AddGuildWidget(GuildListGuildItem *widget);
|
||||
void AddGuildWidget(Gtk::Widget *widget);
|
||||
|
||||
private:
|
||||
void OnMessageCreate(const Message &msg);
|
||||
|
75
src/components/channellist/classic/mentionoverlay.cpp
Normal file
75
src/components/channellist/classic/mentionoverlay.cpp
Normal file
@ -0,0 +1,75 @@
|
||||
#include "mentionoverlay.hpp"
|
||||
|
||||
#include "misc/cairo.hpp"
|
||||
|
||||
#include "abaddon.hpp"
|
||||
|
||||
MentionOverlay::MentionOverlay(Snowflake guild_id)
|
||||
: m_guild_ids({ guild_id }) {
|
||||
Init();
|
||||
}
|
||||
|
||||
MentionOverlay::MentionOverlay(const UserSettingsGuildFoldersEntry &folder)
|
||||
: m_guild_ids({ folder.GuildIDs.begin(), folder.GuildIDs.end() }) {
|
||||
Init();
|
||||
}
|
||||
|
||||
void MentionOverlay::Init() {
|
||||
m_font.set_family("sans 14");
|
||||
m_layout = create_pango_layout("12");
|
||||
m_layout->set_font_description(m_font);
|
||||
m_layout->set_alignment(Pango::ALIGN_RIGHT);
|
||||
|
||||
get_style_context()->add_class("classic-mention-overlay"); // fuck you
|
||||
|
||||
set_hexpand(false);
|
||||
set_vexpand(false);
|
||||
|
||||
signal_draw().connect(sigc::mem_fun(*this, &MentionOverlay::OnDraw));
|
||||
|
||||
Abaddon::Get().GetDiscordClient().signal_message_ack().connect([this](const MessageAckData &data) {
|
||||
// fetching and checking guild id is probably more expensive than just forcing a redraw anyways
|
||||
queue_draw();
|
||||
});
|
||||
|
||||
Abaddon::Get().GetDiscordClient().signal_message_create().connect([this](const Message &msg) {
|
||||
if (msg.GuildID.has_value() && m_guild_ids.find(*msg.GuildID) == m_guild_ids.end()) return;
|
||||
if (!msg.DoesMentionEveryone && msg.Mentions.empty() && msg.MentionRoles.empty()) return;
|
||||
queue_draw();
|
||||
});
|
||||
}
|
||||
|
||||
bool MentionOverlay::OnDraw(const Cairo::RefPtr<Cairo::Context> &cr) {
|
||||
int total_mentions = 0;
|
||||
for (auto guild_id : m_guild_ids) {
|
||||
int mentions;
|
||||
Abaddon::Get().GetDiscordClient().GetUnreadStateForGuild(guild_id, mentions);
|
||||
total_mentions += mentions;
|
||||
}
|
||||
if (total_mentions == 0) return true;
|
||||
m_layout->set_text(std::to_string(total_mentions));
|
||||
|
||||
const int width = get_allocated_width();
|
||||
const int height = std::min(get_allocated_height(), 48); // cope
|
||||
|
||||
int lw, lh;
|
||||
m_layout->get_pixel_size(lw, lh);
|
||||
{
|
||||
static const auto badge_setting = Gdk::RGBA(Abaddon::Get().GetSettings().MentionBadgeColor);
|
||||
static const auto text_setting = Gdk::RGBA(Abaddon::Get().GetSettings().MentionBadgeTextColor);
|
||||
|
||||
auto bg = badge_setting.get_alpha_u() > 0 ? badge_setting : get_style_context()->get_background_color(Gtk::STATE_FLAG_SELECTED);
|
||||
auto text = text_setting.get_alpha_u() > 0 ? text_setting : get_style_context()->get_color(Gtk::STATE_FLAG_SELECTED);
|
||||
|
||||
const auto x = width - lw - 5;
|
||||
const auto y = height - lh - 1;
|
||||
CairoUtil::PathRoundedRect(cr, x - 4, y + 2, lw + 8, lh, 5);
|
||||
cr->set_source_rgb(bg.get_red(), bg.get_green(), bg.get_blue());
|
||||
cr->fill();
|
||||
cr->set_source_rgb(text.get_red(), text.get_green(), text.get_blue());
|
||||
cr->move_to(x, y);
|
||||
m_layout->show_in_cairo_context(cr);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
25
src/components/channellist/classic/mentionoverlay.hpp
Normal file
25
src/components/channellist/classic/mentionoverlay.hpp
Normal file
@ -0,0 +1,25 @@
|
||||
#pragma once
|
||||
|
||||
#include <set>
|
||||
|
||||
#include <gtkmm/drawingarea.h>
|
||||
#include <pangomm/fontdescription.h>
|
||||
|
||||
#include "discord/snowflake.hpp"
|
||||
#include "discord/usersettings.hpp"
|
||||
|
||||
class MentionOverlay : public Gtk::DrawingArea {
|
||||
public:
|
||||
MentionOverlay(Snowflake guild_id);
|
||||
MentionOverlay(const UserSettingsGuildFoldersEntry &folder);
|
||||
|
||||
private:
|
||||
void Init();
|
||||
|
||||
bool OnDraw(const Cairo::RefPtr<Cairo::Context> &cr);
|
||||
|
||||
std::set<Snowflake> m_guild_ids;
|
||||
|
||||
Pango::FontDescription m_font;
|
||||
Glib::RefPtr<Pango::Layout> m_layout;
|
||||
};
|
@ -351,7 +351,9 @@ void DiscordClient::GetArchivedPrivateThreads(Snowflake channel_id, const sigc::
|
||||
}
|
||||
|
||||
std::vector<Snowflake> DiscordClient::GetChildChannelIDs(Snowflake parent_id) const {
|
||||
return m_store.GetChannelIDsWithParentID(parent_id);
|
||||
std::vector<Snowflake> ids;
|
||||
for (auto [id, type] : m_store.GetChannelIDsWithParentID(parent_id)) ids.push_back(id);
|
||||
return ids;
|
||||
}
|
||||
|
||||
std::optional<WebhookMessageData> DiscordClient::GetWebhookMessageData(Snowflake message_id) const {
|
||||
@ -1290,21 +1292,10 @@ Snowflake DiscordClient::GetVoiceChannelID() const noexcept {
|
||||
return m_voice_channel_id;
|
||||
}
|
||||
|
||||
std::unordered_set<Snowflake> DiscordClient::GetUsersInVoiceChannel(Snowflake channel_id) {
|
||||
return m_voice_state_channel_users[channel_id];
|
||||
}
|
||||
|
||||
std::optional<uint32_t> DiscordClient::GetSSRCOfUser(Snowflake id) const {
|
||||
return m_voice.GetSSRCOfUser(id);
|
||||
}
|
||||
|
||||
std::optional<std::pair<Snowflake, PackedVoiceState>> DiscordClient::GetVoiceState(Snowflake user_id) const {
|
||||
if (const auto it = m_voice_states.find(user_id); it != m_voice_states.end()) {
|
||||
return it->second;
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
bool DiscordClient::IsUserSpeaker(Snowflake user_id) const {
|
||||
const auto state = GetVoiceState(user_id);
|
||||
return state.has_value() && state->second.IsSpeaker();
|
||||
@ -1325,6 +1316,17 @@ void DiscordClient::SetVoiceDeafened(bool is_deaf) {
|
||||
}
|
||||
#endif
|
||||
|
||||
std::optional<std::pair<Snowflake, PackedVoiceState>> 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;
|
||||
}
|
||||
|
||||
std::unordered_set<Snowflake> DiscordClient::GetUsersInVoiceChannel(Snowflake channel_id) {
|
||||
return m_voice_state_channel_users[channel_id];
|
||||
}
|
||||
|
||||
void DiscordClient::SetReferringChannel(Snowflake id) {
|
||||
if (!id.IsValid()) {
|
||||
m_http.SetPersistentHeader("Referer", "https://discord.com/channels/@me");
|
||||
@ -1384,7 +1386,8 @@ int DiscordClient::GetUnreadStateForChannel(Snowflake id) const noexcept {
|
||||
|
||||
int DiscordClient::GetUnreadChannelsCountForCategory(Snowflake id) const noexcept {
|
||||
int result = 0;
|
||||
for (Snowflake channel_id : m_store.GetChannelIDsWithParentID(id)) {
|
||||
for (auto [channel_id, channel_type] : m_store.GetChannelIDsWithParentID(id)) {
|
||||
if (!ShouldChannelTypeCountInUnread(channel_type)) continue;
|
||||
if (IsChannelMuted(channel_id)) continue;
|
||||
const auto iter = m_unread.find(channel_id);
|
||||
if (iter == m_unread.end()) continue;
|
||||
@ -1406,6 +1409,9 @@ bool DiscordClient::GetUnreadStateForGuild(Snowflake id, int &total_mentions) co
|
||||
if (const auto iter = m_channel_muted_parent.find(channel_id); iter != m_channel_muted_parent.end())
|
||||
continue;
|
||||
|
||||
const auto channel = GetChannel(channel_id);
|
||||
if (channel.has_value() && !ShouldChannelTypeCountInUnread(channel->Type)) continue;
|
||||
|
||||
if (!has_any_unread && channel_unread > -1 && !IsChannelMuted(channel_id))
|
||||
has_any_unread = true;
|
||||
}
|
||||
@ -1668,10 +1674,10 @@ void DiscordClient::HandleGatewayMessage(std::string str) {
|
||||
case GatewayEvent::STAGE_INSTANCE_DELETE: {
|
||||
HandleGatewayStageInstanceDelete(m);
|
||||
} break;
|
||||
#ifdef WITH_VOICE
|
||||
case GatewayEvent::VOICE_STATE_UPDATE: {
|
||||
HandleGatewayVoiceStateUpdate(m);
|
||||
} break;
|
||||
#ifdef WITH_VOICE
|
||||
case GatewayEvent::VOICE_SERVER_UPDATE: {
|
||||
HandleGatewayVoiceServerUpdate(m);
|
||||
} break;
|
||||
@ -2383,12 +2389,6 @@ void DiscordClient::HandleGatewayStageInstanceDelete(const GatewayMessage &msg)
|
||||
*
|
||||
*/
|
||||
|
||||
void DiscordClient::HandleGatewayVoiceStateUpdate(const GatewayMessage &msg) {
|
||||
spdlog::get("discord")->trace("VOICE_STATE_UPDATE");
|
||||
|
||||
CheckVoiceState(msg.Data);
|
||||
}
|
||||
|
||||
void DiscordClient::HandleGatewayVoiceServerUpdate(const GatewayMessage &msg) {
|
||||
spdlog::get("discord")->trace("VOICE_SERVER_UPDATE");
|
||||
|
||||
@ -2418,8 +2418,17 @@ void DiscordClient::HandleGatewayCallCreate(const GatewayMessage &msg) {
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
void DiscordClient::HandleGatewayVoiceStateUpdate(const GatewayMessage &msg) {
|
||||
spdlog::get("discord")->trace("VOICE_STATE_UPDATE");
|
||||
|
||||
CheckVoiceState(msg.Data);
|
||||
}
|
||||
|
||||
void DiscordClient::CheckVoiceState(const VoiceState &data) {
|
||||
if (data.UserID == m_user_data.ID) {
|
||||
#ifdef WITH_VOICE
|
||||
spdlog::get("discord")->debug("Voice session ID: {}", data.SessionID);
|
||||
m_voice.SetSessionID(data.SessionID);
|
||||
|
||||
@ -2430,6 +2439,7 @@ void DiscordClient::CheckVoiceState(const VoiceState &data) {
|
||||
m_voice_channel_id = *data.ChannelID;
|
||||
m_signal_voice_channel_changed.emit(m_voice_channel_id);
|
||||
}
|
||||
#endif
|
||||
} else {
|
||||
if (data.GuildID.has_value() && data.Member.has_value()) {
|
||||
if (data.Member->User.has_value()) {
|
||||
@ -2461,7 +2471,6 @@ void DiscordClient::CheckVoiceState(const VoiceState &data) {
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
void DiscordClient::HandleGatewayReadySupplemental(const GatewayMessage &msg) {
|
||||
ReadySupplementalData data = msg.Data;
|
||||
@ -2811,6 +2820,14 @@ bool DiscordClient::CheckCode(const http::response_type &r, int expected) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DiscordClient::ShouldChannelTypeCountInUnread(ChannelType type) {
|
||||
return type != ChannelType::GUILD_VOICE &&
|
||||
type != ChannelType::GUILD_FORUM &&
|
||||
type != ChannelType::GUILD_MEDIA &&
|
||||
type != ChannelType::GUILD_STORE &&
|
||||
type != ChannelType::GUILD_DIRECTORY;
|
||||
}
|
||||
|
||||
void DiscordClient::StoreMessageData(Message &msg) {
|
||||
const auto chan = m_store.GetChannel(msg.ChannelID);
|
||||
if (chan.has_value() && chan->GuildID.has_value())
|
||||
@ -2973,6 +2990,15 @@ void DiscordClient::SendVoiceStateUpdate() {
|
||||
m_websocket.Send(msg);
|
||||
}
|
||||
|
||||
void DiscordClient::OnVoiceConnected() {
|
||||
m_signal_voice_connected.emit();
|
||||
}
|
||||
|
||||
void DiscordClient::OnVoiceDisconnected() {
|
||||
m_signal_voice_disconnected.emit();
|
||||
}
|
||||
#endif
|
||||
|
||||
void DiscordClient::SetVoiceState(Snowflake user_id, const VoiceState &state) {
|
||||
if (!state.ChannelID.has_value()) {
|
||||
spdlog::get("discord")->error("SetVoiceState called with missing channel ID");
|
||||
@ -3003,15 +3029,6 @@ void DiscordClient::ClearVoiceState(Snowflake 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;
|
||||
@ -3302,14 +3319,6 @@ DiscordClient::type_signal_voice_speaking DiscordClient::signal_voice_speaking()
|
||||
return m_signal_voice_speaking;
|
||||
}
|
||||
|
||||
DiscordClient::type_signal_voice_user_disconnect DiscordClient::signal_voice_user_disconnect() {
|
||||
return m_signal_voice_user_disconnect;
|
||||
}
|
||||
|
||||
DiscordClient::type_signal_voice_user_connect DiscordClient::signal_voice_user_connect() {
|
||||
return m_signal_voice_user_connect;
|
||||
}
|
||||
|
||||
DiscordClient::type_signal_voice_requested_connect DiscordClient::signal_voice_requested_connect() {
|
||||
return m_signal_voice_requested_connect;
|
||||
}
|
||||
@ -3325,6 +3334,15 @@ 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;
|
||||
}
|
||||
#endif
|
||||
|
||||
DiscordClient::type_signal_voice_user_disconnect DiscordClient::signal_voice_user_disconnect() {
|
||||
return m_signal_voice_user_disconnect;
|
||||
}
|
||||
|
||||
DiscordClient::type_signal_voice_user_connect DiscordClient::signal_voice_user_connect() {
|
||||
return m_signal_voice_user_connect;
|
||||
}
|
||||
|
||||
DiscordClient::type_signal_voice_state_set DiscordClient::signal_voice_state_set() {
|
||||
return m_signal_voice_state_set;
|
||||
@ -3333,4 +3351,3 @@ DiscordClient::type_signal_voice_state_set DiscordClient::signal_voice_state_set
|
||||
DiscordClient::type_signal_voice_speaker_state_changed DiscordClient::signal_voice_speaker_state_changed() {
|
||||
return m_signal_voice_speaker_state_changed;
|
||||
}
|
||||
#endif
|
||||
|
@ -201,9 +201,7 @@ public:
|
||||
[[nodiscard]] bool IsVoiceConnected() const noexcept;
|
||||
[[nodiscard]] bool IsVoiceConnecting() const noexcept;
|
||||
[[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<std::pair<Snowflake, PackedVoiceState>> GetVoiceState(Snowflake user_id) const;
|
||||
[[nodiscard]] bool IsUserSpeaker(Snowflake user_id) const;
|
||||
|
||||
DiscordVoiceClient &GetVoiceClient();
|
||||
@ -212,6 +210,9 @@ public:
|
||||
void SetVoiceDeafened(bool is_deaf);
|
||||
#endif
|
||||
|
||||
[[nodiscard]] std::optional<std::pair<Snowflake, PackedVoiceState>> GetVoiceState(Snowflake user_id) const;
|
||||
[[nodiscard]] std::unordered_set<Snowflake> GetUsersInVoiceChannel(Snowflake channel_id);
|
||||
|
||||
void SetReferringChannel(Snowflake id);
|
||||
|
||||
void SetBuildNumber(uint32_t build_number);
|
||||
@ -304,13 +305,13 @@ private:
|
||||
void HandleGatewayInvalidSession(const GatewayMessage &msg);
|
||||
|
||||
#ifdef WITH_VOICE
|
||||
void
|
||||
HandleGatewayVoiceStateUpdate(const GatewayMessage &msg);
|
||||
void HandleGatewayVoiceServerUpdate(const GatewayMessage &msg);
|
||||
void HandleGatewayCallCreate(const GatewayMessage &msg);
|
||||
#endif
|
||||
|
||||
void HandleGatewayVoiceStateUpdate(const GatewayMessage &msg);
|
||||
|
||||
void CheckVoiceState(const VoiceState &data);
|
||||
#endif
|
||||
|
||||
void HeartbeatThread();
|
||||
void SendIdentify();
|
||||
@ -327,6 +328,8 @@ private:
|
||||
|
||||
void StoreMessageData(Message &msg);
|
||||
|
||||
static bool ShouldChannelTypeCountInUnread(ChannelType type);
|
||||
|
||||
void HandleReadyReadState(const ReadyEventData &data);
|
||||
void HandleReadyGuildSettings(const ReadyEventData &data);
|
||||
|
||||
@ -381,19 +384,20 @@ private:
|
||||
bool m_deaf_requested = false;
|
||||
|
||||
Snowflake m_voice_channel_id;
|
||||
// todo sql i guess
|
||||
std::unordered_map<Snowflake, std::pair<Snowflake, PackedVoiceState>> m_voice_states;
|
||||
std::unordered_map<Snowflake, std::unordered_set<Snowflake>> m_voice_state_channel_users;
|
||||
|
||||
void SendVoiceStateUpdate();
|
||||
|
||||
void SetVoiceState(Snowflake user_id, const VoiceState &state);
|
||||
void ClearVoiceState(Snowflake user_id);
|
||||
|
||||
void OnVoiceConnected();
|
||||
void OnVoiceDisconnected();
|
||||
#endif
|
||||
|
||||
void SetVoiceState(Snowflake user_id, const VoiceState &state);
|
||||
void ClearVoiceState(Snowflake user_id);
|
||||
|
||||
// todo sql i guess
|
||||
std::unordered_map<Snowflake, std::pair<Snowflake, PackedVoiceState>> m_voice_states;
|
||||
std::unordered_map<Snowflake, std::unordered_set<Snowflake>> m_voice_state_channel_users;
|
||||
|
||||
mutable std::mutex m_msg_mutex;
|
||||
Glib::Dispatcher m_msg_dispatch;
|
||||
std::queue<std::string> m_msg_queue;
|
||||
@ -474,15 +478,16 @@ public:
|
||||
using type_signal_voice_connected = sigc::signal<void()>;
|
||||
using type_signal_voice_disconnected = sigc::signal<void()>;
|
||||
using type_signal_voice_speaking = sigc::signal<void(VoiceSpeakingData)>;
|
||||
using type_signal_voice_user_disconnect = sigc::signal<void(Snowflake, Snowflake)>;
|
||||
using type_signal_voice_user_connect = sigc::signal<void(Snowflake, Snowflake)>;
|
||||
using type_signal_voice_requested_connect = sigc::signal<void(Snowflake)>;
|
||||
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)>;
|
||||
#endif
|
||||
|
||||
using type_signal_voice_user_disconnect = sigc::signal<void(Snowflake, Snowflake)>;
|
||||
using type_signal_voice_user_connect = sigc::signal<void(Snowflake, Snowflake)>;
|
||||
using type_signal_voice_state_set = sigc::signal<void(Snowflake, Snowflake, VoiceStateFlags)>;
|
||||
using type_signal_voice_speaker_state_changed = sigc::signal<void(Snowflake /* channel_id */, Snowflake /* user_id */, bool /* is_speaker */)>;
|
||||
#endif
|
||||
|
||||
type_signal_gateway_ready signal_gateway_ready();
|
||||
type_signal_gateway_ready_supplemental signal_gateway_ready_supplemental();
|
||||
@ -546,15 +551,16 @@ public:
|
||||
type_signal_voice_connected signal_voice_connected();
|
||||
type_signal_voice_disconnected signal_voice_disconnected();
|
||||
type_signal_voice_speaking signal_voice_speaking();
|
||||
type_signal_voice_user_disconnect signal_voice_user_disconnect();
|
||||
type_signal_voice_user_connect signal_voice_user_connect();
|
||||
type_signal_voice_requested_connect signal_voice_requested_connect();
|
||||
type_signal_voice_requested_disconnect signal_voice_requested_disconnect();
|
||||
type_signal_voice_client_state_update signal_voice_client_state_update();
|
||||
type_signal_voice_channel_changed signal_voice_channel_changed();
|
||||
#endif
|
||||
|
||||
type_signal_voice_user_disconnect signal_voice_user_disconnect();
|
||||
type_signal_voice_user_connect signal_voice_user_connect();
|
||||
type_signal_voice_state_set signal_voice_state_set();
|
||||
type_signal_voice_speaker_state_changed signal_voice_speaker_state_changed();
|
||||
#endif
|
||||
|
||||
protected:
|
||||
type_signal_gateway_ready m_signal_gateway_ready;
|
||||
@ -619,13 +625,14 @@ protected:
|
||||
type_signal_voice_connected m_signal_voice_connected;
|
||||
type_signal_voice_disconnected m_signal_voice_disconnected;
|
||||
type_signal_voice_speaking m_signal_voice_speaking;
|
||||
type_signal_voice_user_disconnect m_signal_voice_user_disconnect;
|
||||
type_signal_voice_user_connect m_signal_voice_user_connect;
|
||||
type_signal_voice_requested_connect m_signal_voice_requested_connect;
|
||||
type_signal_voice_requested_disconnect m_signal_voice_requested_disconnect;
|
||||
type_signal_voice_client_state_update m_signal_voice_client_state_update;
|
||||
type_signal_voice_channel_changed m_signal_voice_channel_changed;
|
||||
#endif
|
||||
|
||||
type_signal_voice_user_disconnect m_signal_voice_user_disconnect;
|
||||
type_signal_voice_user_connect m_signal_voice_user_connect;
|
||||
type_signal_voice_state_set m_signal_voice_state_set;
|
||||
type_signal_voice_speaker_state_changed m_signal_voice_speaker_state_changed;
|
||||
#endif
|
||||
};
|
||||
|
@ -638,16 +638,16 @@ std::vector<ChannelData> Store::GetActiveThreads(Snowflake channel_id) const {
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::vector<Snowflake> Store::GetChannelIDsWithParentID(Snowflake channel_id) const {
|
||||
std::vector<std::pair<Snowflake, ChannelType>> Store::GetChannelIDsWithParentID(Snowflake channel_id) const {
|
||||
auto &s = m_stmt_get_chan_ids_parent;
|
||||
|
||||
s->Bind(1, channel_id);
|
||||
|
||||
std::vector<Snowflake> ret;
|
||||
std::vector<std::pair<Snowflake, ChannelType>> ret;
|
||||
while (s->FetchOne()) {
|
||||
Snowflake x;
|
||||
s->Get(0, x);
|
||||
ret.push_back(x);
|
||||
auto &p = ret.emplace_back();
|
||||
s->Get(0, p.first);
|
||||
s->Get(1, p.second);
|
||||
}
|
||||
|
||||
s->Reset();
|
||||
@ -2315,7 +2315,7 @@ bool Store::CreateStatements() {
|
||||
}
|
||||
|
||||
m_stmt_get_chan_ids_parent = std::make_unique<Statement>(m_db, R"(
|
||||
SELECT id FROM channels WHERE parent_id = ?
|
||||
SELECT id, type FROM channels WHERE parent_id = ?
|
||||
)");
|
||||
if (!m_stmt_get_chan_ids_parent->OK()) {
|
||||
fprintf(stderr, "failed to prepare get channel ids for parent statement: %s\n", m_db.ErrStr());
|
||||
|
@ -50,7 +50,7 @@ public:
|
||||
std::vector<Message> GetMessagesBefore(Snowflake channel_id, Snowflake message_id, size_t limit) const;
|
||||
std::vector<Message> GetPinnedMessages(Snowflake channel_id) const;
|
||||
std::vector<ChannelData> GetActiveThreads(Snowflake channel_id) const; // public
|
||||
std::vector<Snowflake> GetChannelIDsWithParentID(Snowflake channel_id) const;
|
||||
std::vector<std::pair<Snowflake, ChannelType>> GetChannelIDsWithParentID(Snowflake channel_id) const;
|
||||
std::unordered_set<Snowflake> GetMembersInGuild(Snowflake guild_id) const;
|
||||
// ^ not the same as GetUsersInGuild since users in a guild may include users who do not have retrieved member data
|
||||
|
||||
|
@ -220,34 +220,34 @@ void UserData::update_from_json(const nlohmann::json &j) {
|
||||
}
|
||||
|
||||
const char *UserData::GetFlagName(uint64_t flag) {
|
||||
switch (flag) {
|
||||
case DiscordEmployee:
|
||||
switch (static_cast<UserData::EFlags>(flag)) {
|
||||
case UserData::EFlags::DiscordEmployee:
|
||||
return "discordstaff";
|
||||
case PartneredServerOwner:
|
||||
case UserData::EFlags::PartneredServerOwner:
|
||||
return "partneredowner";
|
||||
case HypeSquadEvents:
|
||||
case UserData::EFlags::HypeSquadEvents:
|
||||
return "hypesquadevents";
|
||||
case BugHunterLevel1:
|
||||
case UserData::EFlags::BugHunterLevel1:
|
||||
return "discordbughunter";
|
||||
case HouseBravery:
|
||||
case UserData::EFlags::HouseBravery:
|
||||
return "hypesquadbravery";
|
||||
case HouseBrilliance:
|
||||
case UserData::EFlags::HouseBrilliance:
|
||||
return "hypesquadbrilliance";
|
||||
case HouseBalance:
|
||||
case UserData::EFlags::HouseBalance:
|
||||
return "hypesquadbalance";
|
||||
case EarlySupporter:
|
||||
case UserData::EFlags::EarlySupporter:
|
||||
return "earlysupporter";
|
||||
case TeamUser:
|
||||
case UserData::EFlags::TeamUser:
|
||||
return "teamuser";
|
||||
case System:
|
||||
case UserData::EFlags::System:
|
||||
return "system";
|
||||
case BugHunterLevel2:
|
||||
case UserData::EFlags::BugHunterLevel2:
|
||||
return "discordbughunter2";
|
||||
case VerifiedBot:
|
||||
case UserData::EFlags::VerifiedBot:
|
||||
return "verifiedbot";
|
||||
case EarlyVerifiedBotDeveloper:
|
||||
case UserData::EFlags::EarlyVerifiedBotDeveloper:
|
||||
return "earlyverifiedbotdeveloper";
|
||||
case CertifiedModerator:
|
||||
case UserData::EFlags::CertifiedModerator:
|
||||
return "certifiedmoderator";
|
||||
default:
|
||||
return "unknown";
|
||||
@ -255,34 +255,34 @@ const char *UserData::GetFlagName(uint64_t flag) {
|
||||
}
|
||||
|
||||
const char *UserData::GetFlagReadableName(uint64_t flag) {
|
||||
switch (flag) {
|
||||
case DiscordEmployee:
|
||||
switch (static_cast<UserData::EFlags>(flag)) {
|
||||
case UserData::EFlags::DiscordEmployee:
|
||||
return "Discord Staff";
|
||||
case PartneredServerOwner:
|
||||
case UserData::EFlags::PartneredServerOwner:
|
||||
return "Partnered Server Owner";
|
||||
case HypeSquadEvents:
|
||||
case UserData::EFlags::HypeSquadEvents:
|
||||
return "HypeSquad Events";
|
||||
case BugHunterLevel1:
|
||||
case UserData::EFlags::BugHunterLevel1:
|
||||
return "Discord Bug Hunter";
|
||||
case HouseBravery:
|
||||
case UserData::EFlags::HouseBravery:
|
||||
return "HypeSquad Bravery";
|
||||
case HouseBrilliance:
|
||||
case UserData::EFlags::HouseBrilliance:
|
||||
return "HypeSquad Brilliance";
|
||||
case HouseBalance:
|
||||
case UserData::EFlags::HouseBalance:
|
||||
return "HypeSquad Balance";
|
||||
case EarlySupporter:
|
||||
case UserData::EFlags::EarlySupporter:
|
||||
return "Early Supporter";
|
||||
case TeamUser:
|
||||
case UserData::EFlags::TeamUser:
|
||||
return "Team User"; // ???
|
||||
case System:
|
||||
case UserData::EFlags::System:
|
||||
return "System";
|
||||
case BugHunterLevel2:
|
||||
case UserData::EFlags::BugHunterLevel2:
|
||||
return "Discord Bug Hunter Level 2";
|
||||
case VerifiedBot:
|
||||
case UserData::EFlags::VerifiedBot:
|
||||
return "Verified Bot";
|
||||
case EarlyVerifiedBotDeveloper:
|
||||
case UserData::EFlags::EarlyVerifiedBotDeveloper:
|
||||
return "Early Verified Bot Developer";
|
||||
case CertifiedModerator:
|
||||
case UserData::EFlags::CertifiedModerator:
|
||||
return "Discord Certified Moderator";
|
||||
default:
|
||||
return "";
|
||||
|
@ -11,8 +11,7 @@ enum class EPremiumType {
|
||||
};
|
||||
|
||||
struct UserData {
|
||||
// todo: enum class? (for consistencys sake)
|
||||
enum {
|
||||
enum class EFlags : uint64_t {
|
||||
DiscordEmployee = 1 << 0,
|
||||
PartneredServerOwner = 1 << 1,
|
||||
HypeSquadEvents = 1 << 2,
|
||||
|
15
src/misc/cairo.cpp
Normal file
15
src/misc/cairo.cpp
Normal file
@ -0,0 +1,15 @@
|
||||
#include "cairo.hpp"
|
||||
|
||||
constexpr static double M_PI_H = M_PI / 2.0;
|
||||
constexpr static double M_PI_3_2 = M_PI * 3.0 / 2.0;
|
||||
|
||||
void CairoUtil::PathRoundedRect(const Cairo::RefPtr<Cairo::Context> &cr, double x, double y, double w, double h, double r) {
|
||||
const double degrees = M_PI / 180.0;
|
||||
|
||||
cr->begin_new_sub_path();
|
||||
cr->arc(x + w - r, y + r, r, -M_PI_H, 0);
|
||||
cr->arc(x + w - r, y + h - r, r, 0, M_PI_H);
|
||||
cr->arc(x + r, y + h - r, r, M_PI_H, M_PI);
|
||||
cr->arc(x + r, y + r, r, M_PI, M_PI_3_2);
|
||||
cr->close_path();
|
||||
}
|
7
src/misc/cairo.hpp
Normal file
7
src/misc/cairo.hpp
Normal file
@ -0,0 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <cairomm/context.h>
|
||||
|
||||
namespace CairoUtil {
|
||||
void PathRoundedRect(const Cairo::RefPtr<Cairo::Context> &cr, double x, double y, double w, double h, double r);
|
||||
} // namespace CairoUtil
|
@ -4,6 +4,15 @@
|
||||
#include <fstream>
|
||||
#include <string>
|
||||
|
||||
#ifdef __APPLE__
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
#ifdef __linux__
|
||||
#include "util.hpp"
|
||||
#endif
|
||||
|
||||
#include <spdlog/spdlog.h>
|
||||
|
||||
using namespace std::literals::string_literals;
|
||||
|
||||
#if defined(_WIN32)
|
||||
|
@ -75,7 +75,7 @@ void RemoteAuthDialog::OnFingerprint(const std::string &fingerprint) {
|
||||
int size = qr.getSize();
|
||||
const int border = 4;
|
||||
|
||||
const auto module_set = "192 0 255";
|
||||
const auto module_set = "0 0 0";
|
||||
const auto module_clr = "255 255 255";
|
||||
|
||||
std::ostringstream sb;
|
||||
|
@ -96,7 +96,7 @@ void SettingsManager::DefineSettings() {
|
||||
AddSetting("gui", "stock_emojis", false, &Settings::ShowStockEmojis);
|
||||
AddSetting("gui", "unreads", true, &Settings::Unreads);
|
||||
AddSetting("gui", "alt_menu", false, &Settings::AltMenu);
|
||||
AddSetting("gui", "hide_to_try", false, &Settings::HideToTray);
|
||||
AddSetting("gui", "hide_to_tray", false, &Settings::HideToTray);
|
||||
AddSetting("gui", "show_deleted_indicator", true, &Settings::ShowDeletedIndicator);
|
||||
AddSetting("gui", "font_scale", -1.0, &Settings::FontScale);
|
||||
AddSetting("gui", "folder_icon_only", false, &Settings::FolderIconOnly);
|
||||
|
@ -46,8 +46,8 @@ std::optional<Glib::ustring> GetJavascriptFileFromAppPage(const Glib::ustring &c
|
||||
start_position += str.size();
|
||||
}
|
||||
|
||||
if (matches.size() >= 7) {
|
||||
return matches[matches.size() - 7];
|
||||
if (matches.size() >= 9) {
|
||||
return matches[matches.size() - 9];
|
||||
}
|
||||
|
||||
return {};
|
||||
|
@ -1,6 +1,7 @@
|
||||
#include "profilewindow.hpp"
|
||||
|
||||
#include "abaddon.hpp"
|
||||
#include "discord/user.hpp"
|
||||
#include "util.hpp"
|
||||
|
||||
ProfileWindow::ProfileWindow(Snowflake user_id)
|
||||
@ -116,13 +117,13 @@ void ProfileWindow::OnFetchProfile(const UserProfileData &data) {
|
||||
|
||||
if (!data.User.PublicFlags.has_value()) return;
|
||||
const auto x = *data.User.PublicFlags;
|
||||
for (uint64_t i = 1; i <= UserData::MaxFlag; i <<= 1) {
|
||||
for (uint64_t i = 1; i <= static_cast<uint64_t>(UserData::EFlags::MaxFlag); i <<= 1) {
|
||||
if (!(x & i)) continue;
|
||||
const std::string name = UserData::GetFlagName(i);
|
||||
if (name == "unknown") continue;
|
||||
Glib::RefPtr<Gdk::Pixbuf> pixbuf;
|
||||
try {
|
||||
if (name == "verifiedbot")
|
||||
if (i == static_cast<uint64_t>(UserData::EFlags::VerifiedBot))
|
||||
pixbuf = Gdk::Pixbuf::create_from_file(Abaddon::GetResPath("/checkmark.png"), 24, 24);
|
||||
else
|
||||
pixbuf = Gdk::Pixbuf::create_from_file(Abaddon::GetResPath("/" + name + ".png"), 24, 24);
|
||||
|
@ -20,12 +20,13 @@ VoiceWindow::VoiceWindow(Snowflake channel_id)
|
||||
, m_deafen("Deafen")
|
||||
, m_noise_suppression("Suppress Noise")
|
||||
, m_mix_mono("Mix Mono")
|
||||
, m_disconnect("Disconnect")
|
||||
, m_channel_id(channel_id)
|
||||
, m_menu_view("View")
|
||||
, m_menu_view_settings("More _Settings", true) {
|
||||
get_style_context()->add_class("app-window");
|
||||
|
||||
set_default_size(300, 300);
|
||||
set_default_size(300, 400);
|
||||
|
||||
auto &discord = Abaddon::Get().GetDiscordClient();
|
||||
auto &audio = Abaddon::Get().GetAudio();
|
||||
@ -118,6 +119,10 @@ VoiceWindow::VoiceWindow(Snowflake channel_id)
|
||||
Abaddon::Get().GetAudio().SetMixMono(m_mix_mono.get_active());
|
||||
});
|
||||
|
||||
m_disconnect.signal_clicked().connect([this]() {
|
||||
Abaddon::Get().GetDiscordClient().DisconnectFromVoice();
|
||||
});
|
||||
|
||||
auto *playback_renderer = Gtk::make_managed<Gtk::CellRendererText>();
|
||||
m_playback_combo.set_valign(Gtk::ALIGN_END);
|
||||
m_playback_combo.set_hexpand(true);
|
||||
@ -216,6 +221,7 @@ VoiceWindow::VoiceWindow(Snowflake channel_id)
|
||||
m_controls.add(m_deafen);
|
||||
m_controls.add(m_noise_suppression);
|
||||
m_controls.add(m_mix_mono);
|
||||
m_controls.pack_end(m_disconnect, false, true);
|
||||
m_main.pack_start(m_menu_bar, false, true);
|
||||
m_main.pack_start(m_TMP_stagelabel, false, true);
|
||||
m_main.pack_start(m_controls, false, true);
|
||||
|
@ -61,6 +61,8 @@ private:
|
||||
Gtk::CheckButton m_noise_suppression;
|
||||
Gtk::CheckButton m_mix_mono;
|
||||
|
||||
Gtk::Button m_disconnect;
|
||||
|
||||
Gtk::ComboBoxText m_vad_combo;
|
||||
Gtk::ComboBox m_playback_combo;
|
||||
Gtk::ComboBox m_capture_combo;
|
||||
|
Loading…
Reference in New Issue
Block a user