6 Commits

Author SHA1 Message Date
ouwou
ad0d4e7d4d merge 2021-05-06 20:11:15 -04:00
ouwou
1ca6235e09 Merge branch 'master' into mobile 2021-05-02 00:45:00 -04:00
ouwou
c04fc2e60c Merge branch 'master' into mobile 2021-04-24 04:26:06 -04:00
ouwou
a7bf9a2404 mobile layout (#24) 2021-04-18 23:51:43 -04:00
ouwou
2065ef4940 Merge branch 'alt-channels' into mobile 2021-04-18 23:39:09 -04:00
ouwou
3aefab652e add option to use labels for channel list (#24) 2021-04-15 15:45:49 -04:00
124 changed files with 2155 additions and 7773 deletions

View File

@@ -8,7 +8,7 @@ jobs:
runs-on: windows-latest
strategy:
matrix:
buildtype: [Debug, RelWithDebInfo, MinSizeRel]
buildtype: [Debug, RelWithDebInfo]
steps:
- uses: actions/checkout@v1
with:
@@ -43,7 +43,6 @@ jobs:
del /f /s /q "${{ runner.workspace }}\build\CMakeCache.txt"
xcopy /E /I "${{ github.workspace }}\css" "${{ runner.workspace }}\build\css"
xcopy /E /I "${{ github.workspace }}\res" "${{ runner.workspace }}\build\res"
xcopy /E /I "${{ github.workspace }}\fonts" "${{ runner.workspace }}\build\fonts"
mkdir "${{ runner.workspace }}\build\share"
xcopy /E /I "${{ github.workspace }}\ci\gtk-for-windows\gtk-nsis-pack\share\glib-2.0" "${{ runner.workspace }}\build\share\glib-2.0"
copy "${{ github.workspace }}\ci\vcpkg\installed\x64-windows\tools\glib\gspawn-win64-helper.exe" "${{ runner.workspace }}\build\gspawn-win64-helper.exe"

2
.gitignore vendored
View File

@@ -354,5 +354,3 @@ testdata/
build/
out/
fonts/fonts.conf

View File

@@ -2,8 +2,6 @@ cmake_minimum_required(VERSION 3.16)
project(abaddon)
set(ABADDON_RESOURCE_DIR "/usr/share/abaddon" CACHE PATH "Fallback directory for resources on Linux")
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake/")
@@ -32,13 +30,8 @@ endif()
if(WIN32)
add_compile_definitions(_CRT_SECURE_NO_WARNINGS)
add_compile_definitions(NOMINMAX)
find_package(Fontconfig REQUIRED)
link_libraries(${Fontconfig_LIBRARIES})
endif()
configure_file(${PROJECT_SOURCE_DIR}/config.h.in ${PROJECT_BINARY_DIR}/config.h)
file(GLOB ABADDON_SOURCES
"*.h"
"*.hpp"
@@ -58,7 +51,6 @@ file(GLOB ABADDON_SOURCES
)
add_executable(abaddon ${ABADDON_SOURCES})
target_include_directories(abaddon PUBLIC ${PROJECT_BINARY_DIR})
target_include_directories(abaddon PUBLIC ${GTKMM_INCLUDE_DIRS})
target_include_directories(abaddon PUBLIC ${ZLIB_INCLUDE_DIRS})
target_include_directories(abaddon PUBLIC ${SQLite3_INCLUDE_DIRS})

View File

@@ -4,8 +4,6 @@ Alternative Discord client made in C++ with GTK
<img src="/.readme/s3.png">
[😎Discord Server](https://discord.gg/wkCU3vuzG5)
Current features:
* Not Electron
* Handles most types of chat messages including embeds, images, and replies
@@ -54,11 +52,7 @@ Or, do steps 1 and 2, and open CMakeLists.txt in Visual Studio if `vcpkg integra
- MacOS: [here](https://nightly.link/uowuo/abaddon/workflows/ci/master/build-macos-RelWithDebInfo.zip) unsigned, unpackaged, requires gtkmm3 (e.g. from homebrew)
- Linux: [here](https://nightly.link/uowuo/abaddon/workflows/ci/master/build-linux-MinSizeRel.zip) unpackaged (for now), requires gtkmm3. built on Ubuntu 18.04 + gcc9
⚠️ If you use Windows, make sure to start from the directory containing `css` and `res`
If you don't use Windows, `css` and `res` can be loaded from `/usr/share/abaddon`
`abaddon.ini` will also be automatically used if located at `~/.config/abaddon/abaddon.ini` and there is no `abaddon.ini` in the working directory
⚠️ Make sure you start from the directory where `css` and `res` are or else stuff will be broken
#### Dependencies:
* [gtkmm](https://www.gtkmm.org/en/)
@@ -190,21 +184,10 @@ For example, memory_db would be set by adding `memory_db = true` under the line
#### gui
* member_list_discriminator (true or false, default true) - show user discriminators in the member list
* stock_emojis (true or false, default true) - allow abaddon to substitute unicode emojis with images from emojis.bin, must be false to allow GTK to render emojis itself
* custom_emojis (true or false, default true) - download and use custom Discord emojis
* emojis (true or false, default true) - resolve unicode and custom emojis to images. this needs to be false to allow GTK to render emojis by itself
* css (string) - path to the main CSS file
* animations (true or false, default true) - use animated images where available (e.g. server icons, emojis, avatars). false means static images will be used
* animated_guild_hover_only (true or false, default true) - only animate guild icons when the guild is being hovered over
* owner_crown (true or false, default true) - show a crown next to the owner
* gateway (string) - override url for Discord gateway. must be json format and use zlib stream compression
* api_base (string) - override base url for Discord API
#### style
#### misc
* linkcolor (string) - color to use for links in messages
* expandercolor (string) - color to use for the expander in the channel list
* nsfwchannelcolor (string) - color to use for NSFW channels in the channel list
### Environment variables
* ABADDON_NO_FC (Windows only) - don't use custom font config
* ABADDON_CONFIG - change path of configuration file to use. relative to cwd or can be absolute

View File

@@ -2,7 +2,6 @@
#include <memory>
#include <string>
#include <algorithm>
#include "platform.hpp"
#include "discord/discord.hpp"
#include "dialogs/token.hpp"
#include "dialogs/editmessage.hpp"
@@ -14,16 +13,14 @@
#include "abaddon.hpp"
#include "windows/guildsettingswindow.hpp"
#include "windows/profilewindow.hpp"
#include "windows/pinnedwindow.hpp"
#include "windows/threadswindow.hpp"
#ifdef _WIN32
#pragma comment(lib, "crypt32.lib")
#endif
Abaddon::Abaddon()
: m_settings(Platform::FindConfigFile())
, m_emojis(GetResPath("/emojis.bin"))
: m_settings("abaddon.ini")
, m_emojis("res/emojis.bin")
, m_discord(m_settings.GetUseMemoryDB()) { // stupid but easy
LoadFromSettings();
@@ -36,11 +33,15 @@ Abaddon::Abaddon()
m_discord.signal_message_delete().connect(sigc::mem_fun(*this, &Abaddon::DiscordOnMessageDelete));
m_discord.signal_message_update().connect(sigc::mem_fun(*this, &Abaddon::DiscordOnMessageUpdate));
m_discord.signal_guild_member_list_update().connect(sigc::mem_fun(*this, &Abaddon::DiscordOnGuildMemberListUpdate));
m_discord.signal_thread_member_list_update().connect(sigc::mem_fun(*this, &Abaddon::DiscordOnThreadMemberListUpdate));
m_discord.signal_guild_create().connect(sigc::mem_fun(*this, &Abaddon::DiscordOnGuildCreate));
m_discord.signal_guild_delete().connect(sigc::mem_fun(*this, &Abaddon::DiscordOnGuildDelete));
m_discord.signal_channel_delete().connect(sigc::mem_fun(*this, &Abaddon::DiscordOnChannelDelete));
m_discord.signal_channel_update().connect(sigc::mem_fun(*this, &Abaddon::DiscordOnChannelUpdate));
m_discord.signal_channel_create().connect(sigc::mem_fun(*this, &Abaddon::DiscordOnChannelCreate));
m_discord.signal_guild_update().connect(sigc::mem_fun(*this, &Abaddon::DiscordOnGuildUpdate));
m_discord.signal_reaction_add().connect(sigc::mem_fun(*this, &Abaddon::DiscordOnReactionAdd));
m_discord.signal_reaction_remove().connect(sigc::mem_fun(*this, &Abaddon::DiscordOnReactionRemove));
m_discord.signal_guild_join_request_create().connect(sigc::mem_fun(*this, &Abaddon::DiscordOnGuildJoinRequestCreate));
m_discord.signal_thread_update().connect(sigc::mem_fun(*this, &Abaddon::DiscordOnThreadUpdate));
m_discord.signal_message_sent().connect(sigc::mem_fun(*this, &Abaddon::DiscordOnMessageSent));
m_discord.signal_disconnected().connect(sigc::mem_fun(*this, &Abaddon::DiscordOnDisconnect));
if (m_settings.GetPrefetch())
@@ -67,6 +68,7 @@ Abaddon &Abaddon::Get() {
int Abaddon::StartGTK() {
m_gtk_app = Gtk::Application::create("com.github.uowuo.abaddon");
// tmp css stuff
m_css_provider = Gtk::CssProvider::create();
m_css_provider->signal_parsing_error().connect([this](const Glib::RefPtr<const Gtk::CssSection> &section, const Glib::Error &error) {
Gtk::MessageDialog dlg(*m_main_window, "css failed parsing (" + error.what() + ")", false, Gtk::MESSAGE_ERROR, Gtk::BUTTONS_OK, true);
@@ -74,13 +76,6 @@ int Abaddon::StartGTK() {
dlg.run();
});
m_css_low_provider = Gtk::CssProvider::create();
m_css_low_provider->signal_parsing_error().connect([this](const Glib::RefPtr<const Gtk::CssSection> &section, const Glib::Error &error) {
Gtk::MessageDialog dlg(*m_main_window, "low-priority css failed parsing (" + error.what() + ")", false, Gtk::MESSAGE_ERROR, Gtk::BUTTONS_OK, true);
dlg.set_position(Gtk::WIN_POS_CENTER);
dlg.run();
});
m_main_window = std::make_unique<MainWindow>();
m_main_window->set_title(APP_TITLE);
m_main_window->UpdateComponents();
@@ -95,14 +90,16 @@ int Abaddon::StartGTK() {
m_main_window->signal_action_reload_css().connect(sigc::mem_fun(*this, &Abaddon::ActionReloadCSS));
m_main_window->signal_action_join_guild().connect(sigc::mem_fun(*this, &Abaddon::ActionJoinGuildDialog));
m_main_window->signal_action_set_status().connect(sigc::mem_fun(*this, &Abaddon::ActionSetStatus));
m_main_window->signal_action_reload_settings().connect(sigc::mem_fun(*this, &Abaddon::ActionReloadSettings));
m_main_window->signal_action_add_recipient().connect(sigc::mem_fun(*this, &Abaddon::ActionAddRecipient));
m_main_window->signal_action_view_pins().connect(sigc::mem_fun(*this, &Abaddon::ActionViewPins));
m_main_window->signal_action_view_threads().connect(sigc::mem_fun(*this, &Abaddon::ActionViewThreads));
m_main_window->signal_action_show_user_menu().connect(sigc::mem_fun(*this, &Abaddon::ShowUserMenu));
m_main_window->GetChannelList()->signal_action_channel_item_select().connect(sigc::mem_fun(*this, &Abaddon::ActionChannelOpened));
m_main_window->GetChannelList()->signal_action_guild_leave().connect(sigc::mem_fun(*this, &Abaddon::ActionLeaveGuild));
m_main_window->GetChannelList()->signal_action_guild_settings().connect(sigc::mem_fun(*this, &Abaddon::ActionGuildSettings));
m_main_window->GetChatWindow()->signal_action_message_delete().connect(sigc::mem_fun(*this, &Abaddon::ActionChatDeleteMessage));
m_main_window->GetChatWindow()->signal_action_message_edit().connect(sigc::mem_fun(*this, &Abaddon::ActionChatEditMessage));
m_main_window->GetChatWindow()->signal_action_chat_submit().connect(sigc::mem_fun(*this, &Abaddon::ActionChatInputSubmit));
m_main_window->GetChatWindow()->signal_action_chat_load_history().connect(sigc::mem_fun(*this, &Abaddon::ActionChatLoadHistory));
@@ -179,7 +176,7 @@ void Abaddon::DiscordOnReady() {
}
void Abaddon::DiscordOnMessageCreate(const Message &message) {
m_main_window->UpdateChatNewMessage(message);
m_main_window->UpdateChatNewMessage(message.ID); // todo ill fix you later :^)
}
void Abaddon::DiscordOnMessageDelete(Snowflake id, Snowflake channel_id) {
@@ -194,8 +191,28 @@ void Abaddon::DiscordOnGuildMemberListUpdate(Snowflake guild_id) {
m_main_window->UpdateMembers();
}
void Abaddon::DiscordOnThreadMemberListUpdate(const ThreadMemberListUpdateData &data) {
m_main_window->UpdateMembers();
void Abaddon::DiscordOnGuildCreate(const GuildData &guild) {
m_main_window->UpdateChannelsNewGuild(guild.ID);
}
void Abaddon::DiscordOnGuildDelete(Snowflake guild_id) {
m_main_window->UpdateChannelsRemoveGuild(guild_id);
}
void Abaddon::DiscordOnChannelDelete(Snowflake channel_id) {
m_main_window->UpdateChannelsRemoveChannel(channel_id);
}
void Abaddon::DiscordOnChannelUpdate(Snowflake channel_id) {
m_main_window->UpdateChannelsUpdateChannel(channel_id);
}
void Abaddon::DiscordOnChannelCreate(Snowflake channel_id) {
m_main_window->UpdateChannelsCreateChannel(channel_id);
}
void Abaddon::DiscordOnGuildUpdate(Snowflake guild_id) {
m_main_window->UpdateChannelsUpdateGuild(guild_id);
}
void Abaddon::DiscordOnReactionAdd(Snowflake message_id, const Glib::ustring &param) {
@@ -214,7 +231,7 @@ void Abaddon::DiscordOnGuildJoinRequestCreate(const GuildJoinRequestCreateData &
}
void Abaddon::DiscordOnMessageSent(const Message &data) {
m_main_window->UpdateChatNewMessage(data);
m_main_window->UpdateChatNewMessage(data.ID);
}
void Abaddon::DiscordOnDisconnect(bool is_reconnecting, GatewayCloseCode close_code) {
@@ -238,15 +255,6 @@ void Abaddon::DiscordOnDisconnect(bool is_reconnecting, GatewayCloseCode close_c
}
}
void Abaddon::DiscordOnThreadUpdate(const ThreadUpdateData &data) {
if (data.Thread.ID == m_main_window->GetChatActiveChannel()) {
if (data.Thread.ThreadMetadata->IsArchived)
m_main_window->GetChatWindow()->SetTopic("This thread is archived. Sending a message will unarchive it");
else
m_main_window->GetChatWindow()->SetTopic("");
}
}
const SettingsManager &Abaddon::GetSettings() const {
return m_settings;
}
@@ -312,8 +320,8 @@ void Abaddon::ShowUserMenu(const GdkEvent *event, Snowflake id, Snowflake guild_
void Abaddon::ShowGuildVerificationGateDialog(Snowflake guild_id) {
VerificationGateDialog dlg(*m_main_window, guild_id);
if (dlg.run() == Gtk::RESPONSE_OK) {
const auto cb = [this](DiscordError code) {
if (code != DiscordError::NONE) {
const auto cb = [this](bool success) {
if (!success) {
Gtk::MessageDialog dlg(*m_main_window, "Failed to accept the verification gate.", false, Gtk::MESSAGE_ERROR, Gtk::BUTTONS_OK, true);
dlg.set_position(Gtk::WIN_POS_CENTER);
dlg.run();
@@ -397,8 +405,8 @@ void Abaddon::on_user_menu_open_dm() {
if (existing.has_value())
ActionChannelOpened(*existing);
else
m_discord.CreateDM(m_shown_user_menu_id, [this](DiscordError code, Snowflake channel_id) {
if (code == DiscordError::NONE) {
m_discord.CreateDM(m_shown_user_menu_id, [this](bool success, Snowflake channel_id) {
if (success) {
// give the gateway a little window to send CHANNEL_CREATE
auto cb = [this, channel_id] {
ActionChannelOpened(channel_id);
@@ -412,24 +420,6 @@ void Abaddon::on_user_menu_remove_recipient() {
m_discord.RemoveGroupDMRecipient(m_main_window->GetChatActiveChannel(), m_shown_user_menu_id);
}
std::string Abaddon::GetCSSPath() {
const static auto path = Platform::FindResourceFolder() + "/css";
return path;
}
std::string Abaddon::GetResPath() {
const static auto path = Platform::FindResourceFolder() + "/res";
return path;
}
std::string Abaddon::GetCSSPath(const std::string &path) {
return GetCSSPath() + path;
}
std::string Abaddon::GetResPath(const std::string &path) {
return GetResPath() + path;
}
void Abaddon::ActionConnect() {
if (!m_discord.IsStarted())
StartDiscord();
@@ -437,7 +427,8 @@ void Abaddon::ActionConnect() {
}
void Abaddon::ActionDisconnect() {
StopDiscord();
if (m_discord.IsStarted())
StopDiscord();
}
void Abaddon::ActionSetToken() {
@@ -463,10 +454,7 @@ void Abaddon::ActionJoinGuildDialog() {
void Abaddon::ActionChannelOpened(Snowflake id) {
if (id == m_main_window->GetChatActiveChannel()) return;
m_main_window->GetChatWindow()->SetTopic("");
const auto channel = m_discord.GetChannel(id);
if (!channel.has_value()) return;
if (channel->Type == ChannelType::GUILD_TEXT || channel->Type == ChannelType::GUILD_NEWS)
m_main_window->set_title(std::string(APP_TITLE) + " - #" + *channel->Name);
else {
@@ -482,7 +470,7 @@ void Abaddon::ActionChannelOpened(Snowflake id) {
}
m_main_window->UpdateChatActiveChannel(id);
if (m_channels_requested.find(id) == m_channels_requested.end()) {
m_discord.FetchMessagesInChannel(id, [this, id](const std::vector<Message> &msgs) {
m_discord.FetchMessagesInChannel(id, [this, id](const std::vector<Snowflake> &msgs) {
m_main_window->UpdateChatWindowContents();
m_channels_requested.insert(id);
});
@@ -490,14 +478,11 @@ void Abaddon::ActionChannelOpened(Snowflake id) {
m_main_window->UpdateChatWindowContents();
}
if (channel->IsThread()) {
m_discord.SendThreadLazyLoad(id);
if (channel->ThreadMetadata->IsArchived)
m_main_window->GetChatWindow()->SetTopic("This thread is archived. Sending a message will unarchive it");
} else if (channel->Type != ChannelType::DM && channel->Type != ChannelType::GROUP_DM && channel->GuildID.has_value()) {
if (channel->Type != ChannelType::DM && channel->Type != ChannelType::GROUP_DM) {
m_discord.SendLazyLoad(id);
if (m_discord.IsVerificationRequired(*channel->GuildID))
const auto request = m_discord.GetGuildApplication(*channel->GuildID);
if (request.has_value() && request->ApplicationStatus == GuildApplicationStatus::STARTED)
ShowGuildVerificationGateDialog(*channel->GuildID);
}
}
@@ -510,23 +495,20 @@ void Abaddon::ActionChatLoadHistory(Snowflake id) {
return;
Snowflake before_id = m_main_window->GetChatOldestListedMessage();
auto knownset = m_discord.GetMessageIDsForChannel(id);
auto knownset = m_discord.GetMessagesForChannel(id);
std::vector<Snowflake> knownvec(knownset.begin(), knownset.end());
std::sort(knownvec.begin(), knownvec.end());
auto latest = std::find_if(knownvec.begin(), knownvec.end(), [&before_id](Snowflake x) -> bool { return x == before_id; });
int distance = std::distance(knownvec.begin(), latest);
if (distance >= 50) {
std::vector<Message> msgs;
for (auto it = knownvec.begin() + distance - 50; it != knownvec.begin() + distance; it++)
msgs.push_back(*m_discord.GetMessage(*it));
m_main_window->UpdateChatPrependHistory(msgs);
m_main_window->UpdateChatPrependHistory(std::vector<Snowflake>(knownvec.begin() + distance - 50, knownvec.begin() + distance));
return;
}
m_channels_history_loading.insert(id);
m_discord.FetchMessagesInChannelBefore(id, before_id, [this, id](const std::vector<Message> &msgs) {
m_discord.FetchMessagesInChannelBefore(id, before_id, [this, id](const std::vector<Snowflake> &msgs) {
m_channels_history_loading.erase(id);
if (msgs.size() == 0) {
@@ -546,9 +528,12 @@ void Abaddon::ActionChatInputSubmit(std::string msg, Snowflake channel, Snowflak
m_discord.SendChatMessage(msg, channel);
}
void Abaddon::ActionChatDeleteMessage(Snowflake channel_id, Snowflake id) {
m_discord.DeleteMessage(channel_id, id);
}
void Abaddon::ActionChatEditMessage(Snowflake channel_id, Snowflake id) {
const auto msg = m_discord.GetMessage(id);
if (!msg.has_value()) return;
EditMessageDialog dlg(*m_main_window);
dlg.SetContent(msg->Content);
auto response = dlg.run();
@@ -632,41 +617,21 @@ void Abaddon::ActionAddRecipient(Snowflake channel_id) {
}
}
void Abaddon::ActionViewPins(Snowflake channel_id) {
const auto data = m_discord.GetChannel(channel_id);
if (!data.has_value()) return;
auto window = new PinnedWindow(*data);
ManageHeapWindow(window);
window->show();
}
void Abaddon::ActionViewThreads(Snowflake channel_id) {
auto data = m_discord.GetChannel(channel_id);
if (!data.has_value()) return;
if (data->IsThread()) {
data = m_discord.GetChannel(*data->ParentID);
if (!data.has_value()) return;
}
auto window = new ThreadsWindow(*data);
ManageHeapWindow(window);
window->show();
}
bool Abaddon::ShowConfirm(const Glib::ustring &prompt, Gtk::Window *window) {
ConfirmDialog dlg(window != nullptr ? *window : *m_main_window);
dlg.SetConfirmText(prompt);
return dlg.run() == Gtk::RESPONSE_OK;
}
void Abaddon::ActionReloadSettings() {
m_settings.Reload();
}
void Abaddon::ActionReloadCSS() {
try {
Gtk::StyleContext::remove_provider_for_screen(Gdk::Screen::get_default(), m_css_provider);
m_css_provider->load_from_path(GetCSSPath("/" + m_settings.GetMainCSS()));
m_css_provider->load_from_path(m_settings.GetMainCSS());
Gtk::StyleContext::add_provider_for_screen(Gdk::Screen::get_default(), m_css_provider, GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
Gtk::StyleContext::remove_provider_for_screen(Gdk::Screen::get_default(), m_css_low_provider);
m_css_low_provider->load_from_path(GetCSSPath("/application-low-priority.css"));
Gtk::StyleContext::add_provider_for_screen(Gdk::Screen::get_default(), m_css_low_provider, GTK_STYLE_PROVIDER_PRIORITY_APPLICATION - 1);
} catch (Glib::Error &e) {
Gtk::MessageDialog dlg(*m_main_window, "css failed to load (" + e.what() + ")", false, Gtk::MESSAGE_ERROR, Gtk::BUTTONS_OK, true);
dlg.set_position(Gtk::WIN_POS_CENTER);
@@ -683,8 +648,6 @@ EmojiResource &Abaddon::GetEmojis() {
}
int main(int argc, char **argv) {
if (std::getenv("ABADDON_NO_FC") == nullptr)
Platform::SetupFonts();
#if defined(_WIN32) && defined(_MSC_VER)
TCHAR buf[2] { 0 };
GetEnvironmentVariableA("GTK_CSD", buf, sizeof(buf));

View File

@@ -36,6 +36,7 @@ public:
void ActionChannelOpened(Snowflake id);
void ActionChatInputSubmit(std::string msg, Snowflake channel, Snowflake referenced_message);
void ActionChatLoadHistory(Snowflake id);
void ActionChatDeleteMessage(Snowflake channel_id, Snowflake id);
void ActionChatEditMessage(Snowflake channel_id, Snowflake id);
void ActionInsertMention(Snowflake id);
void ActionLeaveGuild(Snowflake id);
@@ -46,11 +47,10 @@ public:
void ActionReactionRemove(Snowflake id, const Glib::ustring &param);
void ActionGuildSettings(Snowflake id);
void ActionAddRecipient(Snowflake channel_id);
void ActionViewPins(Snowflake channel_id);
void ActionViewThreads(Snowflake channel_id);
bool ShowConfirm(const Glib::ustring &prompt, Gtk::Window *window = nullptr);
void ActionReloadSettings();
void ActionReloadCSS();
ImageManager &GetImageManager();
@@ -66,13 +66,17 @@ public:
void DiscordOnMessageDelete(Snowflake id, Snowflake channel_id);
void DiscordOnMessageUpdate(Snowflake id, Snowflake channel_id);
void DiscordOnGuildMemberListUpdate(Snowflake guild_id);
void DiscordOnThreadMemberListUpdate(const ThreadMemberListUpdateData &data);
void DiscordOnGuildCreate(const GuildData &guild);
void DiscordOnGuildDelete(Snowflake guild_id);
void DiscordOnChannelDelete(Snowflake channel_id);
void DiscordOnChannelUpdate(Snowflake channel_id);
void DiscordOnChannelCreate(Snowflake channel_id);
void DiscordOnGuildUpdate(Snowflake guild_id);
void DiscordOnReactionAdd(Snowflake message_id, const Glib::ustring &param);
void DiscordOnReactionRemove(Snowflake message_id, const Glib::ustring &param);
void DiscordOnGuildJoinRequestCreate(const GuildJoinRequestCreateData &data);
void DiscordOnMessageSent(const Message &data);
void DiscordOnDisconnect(bool is_reconnecting, GatewayCloseCode close_code);
void DiscordOnThreadUpdate(const ThreadUpdateData &data);
const SettingsManager &GetSettings() const;
@@ -80,18 +84,13 @@ public:
void ShowUserMenu(const GdkEvent *event, Snowflake id, Snowflake guild_id);
void ManageHeapWindow(Gtk::Window *window);
static std::string GetCSSPath();
static std::string GetResPath();
static std::string GetCSSPath(const std::string &path);
static std::string GetResPath(const std::string &path);
protected:
void ShowGuildVerificationGateDialog(Snowflake guild_id);
void SetupUserMenu();
void ManageHeapWindow(Gtk::Window *window);
Snowflake m_shown_user_menu_id;
Snowflake m_shown_user_menu_guild_id;
@@ -129,6 +128,5 @@ private:
mutable std::mutex m_mutex;
Glib::RefPtr<Gtk::Application> m_gtk_app;
Glib::RefPtr<Gtk::CssProvider> m_css_provider;
Glib::RefPtr<Gtk::CssProvider> m_css_low_provider; // registered with a lower priority to allow better customization
std::unique_ptr<MainWindow> m_main_window; // wah wah cant create a gtkstylecontext fuck you
std::unique_ptr<MainWindow> m_main_window; // wah wah cant create a gtkstylecontext fuck you
};

View File

@@ -1,6 +1,4 @@
set(PANGO_LIBRARY_NAME pango-1.0)
set(PANGOCAIRO_LIBRARY_NAME pangocairo-1.0)
set(PANGOFT2_LIBRARY_NAME pangoft2-1.0)
find_package(HarfBuzz)
find_package(cairo)
@@ -44,27 +42,7 @@ find_library(PANGO_LIBRARY
PATH_SUFFIXES ${PANGO_LIBRARY_NAME}
${PANGO_LIBRARY_NAME}/include)
find_library(PANGOCAIRO_LIBRARY
NAMES ${PANGOCAIRO_LIBRARY_NAME}
pangocairo
HINTS ${PANGO_LIBRARY_HINTS}
/usr/lib
/usr/local/lib
/opt/local/lib
PATH_SUFFIXES ${PANGO_LIBRARY_NAME}
${PANGO_LIBRARY_NAME}/include)
find_library(PANGOFT2_LIBRARY
NAMES ${PANGOFT2_LIBRARY_NAME}
pangoft2
HINTS ${PANGO_LIBRARY_HINTS}
/usr/lib
/usr/local/lib
/opt/local/lib
PATH_SUFFIXES ${PANGO_LIBRARY_NAME}
${PANGO_LIBRARY_NAME}/include)
set(PANGO_LIBRARIES ${PANGO_LIBRARY};${HARFBUZZ_LIBRARIES};${CAIRO_LIBRARIES};${FREETYPE_LIBRARIES};${PANGOCAIRO_LIBRARY};${PANGOFT2_LIBRARY})
set(PANGO_LIBRARIES ${PANGO_LIBRARY};${HARFBUZZ_LIBRARIES};${CAIRO_LIBRARIES};${FREETYPE_LIBRARIES})
set(PANGO_INCLUDE_DIRS ${PANGO_INCLUDE_DIR};${PANGO_CONFIG_INCLUDE_DIRS};${HARFBUZZ_INCLUDE_DIR};${CAIRO_INCLUDE_DIRS};${FREETYPE_INCLUDE_DIRS})
include(FindPackageHandleStandardArgs)

File diff suppressed because it is too large Load Diff

View File

@@ -8,226 +8,172 @@
#include <sigc++/sigc++.h>
#include "../discord/discord.hpp"
constexpr static int GuildIconSize = 24;
constexpr static int DMIconSize = 20;
constexpr static int OrphanChannelSortOffset = -100; // forces orphan channels to the top of the list
static const constexpr int ChannelEmojiSize = 16;
enum class RenderType : uint8_t {
Guild,
Category,
TextChannel,
Thread,
class ChannelListRow : public Gtk::ListBoxRow {
public:
bool IsUserCollapsed;
Snowflake ID;
std::unordered_set<ChannelListRow *> Children;
ChannelListRow *Parent = nullptr;
DMHeader,
DM,
virtual void Collapse();
virtual void Expand();
static void MakeReadOnly(Gtk::TextView *tv);
};
class CellRendererChannels : public Gtk::CellRenderer {
class ChannelListRowDMHeader : public ChannelListRow {
public:
CellRendererChannels();
virtual ~CellRendererChannels();
Glib::PropertyProxy<RenderType> property_type();
Glib::PropertyProxy<Glib::ustring> property_name();
Glib::PropertyProxy<Glib::RefPtr<Gdk::Pixbuf>> property_icon();
Glib::PropertyProxy<Glib::RefPtr<Gdk::PixbufAnimation>> property_icon_animation();
Glib::PropertyProxy<bool> property_expanded();
Glib::PropertyProxy<bool> property_nsfw();
ChannelListRowDMHeader();
protected:
void get_preferred_width_vfunc(Gtk::Widget &widget, int &minimum_width, int &natural_width) const override;
void get_preferred_width_for_height_vfunc(Gtk::Widget &widget, int height, int &minimum_width, int &natural_width) const override;
void get_preferred_height_vfunc(Gtk::Widget &widget, int &minimum_height, int &natural_height) const override;
void get_preferred_height_for_width_vfunc(Gtk::Widget &widget, int width, int &minimum_height, int &natural_height) const override;
void render_vfunc(const Cairo::RefPtr<Cairo::Context> &cr,
Gtk::Widget &widget,
const Gdk::Rectangle &background_area,
const Gdk::Rectangle &cell_area,
Gtk::CellRendererState flags) override;
Gtk::EventBox *m_ev;
Gtk::Box *m_box;
Gtk::Label *m_lbl;
};
// guild functions
void get_preferred_width_vfunc_guild(Gtk::Widget &widget, int &minimum_width, int &natural_width) const;
void get_preferred_width_for_height_vfunc_guild(Gtk::Widget &widget, int height, int &minimum_width, int &natural_width) const;
void get_preferred_height_vfunc_guild(Gtk::Widget &widget, int &minimum_height, int &natural_height) const;
void get_preferred_height_for_width_vfunc_guild(Gtk::Widget &widget, int width, int &minimum_height, int &natural_height) const;
void render_vfunc_guild(const Cairo::RefPtr<Cairo::Context> &cr,
Gtk::Widget &widget,
const Gdk::Rectangle &background_area,
const Gdk::Rectangle &cell_area,
Gtk::CellRendererState flags);
class StatusIndicator;
class ChannelListRowDMChannel : public ChannelListRow {
public:
ChannelListRowDMChannel(const ChannelData *data);
// category
void get_preferred_width_vfunc_category(Gtk::Widget &widget, int &minimum_width, int &natural_width) const;
void get_preferred_width_for_height_vfunc_category(Gtk::Widget &widget, int height, int &minimum_width, int &natural_width) const;
void get_preferred_height_vfunc_category(Gtk::Widget &widget, int &minimum_height, int &natural_height) const;
void get_preferred_height_for_width_vfunc_category(Gtk::Widget &widget, int width, int &minimum_height, int &natural_height) const;
void render_vfunc_category(const Cairo::RefPtr<Cairo::Context> &cr,
Gtk::Widget &widget,
const Gdk::Rectangle &background_area,
const Gdk::Rectangle &cell_area,
Gtk::CellRendererState flags);
protected:
Gtk::EventBox *m_ev;
Gtk::Box *m_box;
StatusIndicator *m_status = nullptr;
Gtk::Widget *m_lbl;
Gtk::Image *m_icon = nullptr;
// text channel
void get_preferred_width_vfunc_channel(Gtk::Widget &widget, int &minimum_width, int &natural_width) const;
void get_preferred_width_for_height_vfunc_channel(Gtk::Widget &widget, int height, int &minimum_width, int &natural_width) const;
void get_preferred_height_vfunc_channel(Gtk::Widget &widget, int &minimum_height, int &natural_height) const;
void get_preferred_height_for_width_vfunc_channel(Gtk::Widget &widget, int width, int &minimum_height, int &natural_height) const;
void render_vfunc_channel(const Cairo::RefPtr<Cairo::Context> &cr,
Gtk::Widget &widget,
const Gdk::Rectangle &background_area,
const Gdk::Rectangle &cell_area,
Gtk::CellRendererState flags);
Gtk::Menu m_menu;
Gtk::MenuItem *m_menu_close; // leave if group
Gtk::MenuItem *m_menu_copy_id;
};
// thread
void get_preferred_width_vfunc_thread(Gtk::Widget &widget, int &minimum_width, int &natural_width) const;
void get_preferred_width_for_height_vfunc_thread(Gtk::Widget &widget, int height, int &minimum_width, int &natural_width) const;
void get_preferred_height_vfunc_thread(Gtk::Widget &widget, int &minimum_height, int &natural_height) const;
void get_preferred_height_for_width_vfunc_thread(Gtk::Widget &widget, int width, int &minimum_height, int &natural_height) const;
void render_vfunc_thread(const Cairo::RefPtr<Cairo::Context> &cr,
Gtk::Widget &widget,
const Gdk::Rectangle &background_area,
const Gdk::Rectangle &cell_area,
Gtk::CellRendererState flags);
class ChannelListRowGuild : public ChannelListRow {
public:
ChannelListRowGuild(const GuildData *data);
// dm header
void get_preferred_width_vfunc_dmheader(Gtk::Widget &widget, int &minimum_width, int &natural_width) const;
void get_preferred_width_for_height_vfunc_dmheader(Gtk::Widget &widget, int height, int &minimum_width, int &natural_width) const;
void get_preferred_height_vfunc_dmheader(Gtk::Widget &widget, int &minimum_height, int &natural_height) const;
void get_preferred_height_for_width_vfunc_dmheader(Gtk::Widget &widget, int width, int &minimum_height, int &natural_height) const;
void render_vfunc_dmheader(const Cairo::RefPtr<Cairo::Context> &cr,
Gtk::Widget &widget,
const Gdk::Rectangle &background_area,
const Gdk::Rectangle &cell_area,
Gtk::CellRendererState flags);
int GuildIndex;
// dm
void get_preferred_width_vfunc_dm(Gtk::Widget &widget, int &minimum_width, int &natural_width) const;
void get_preferred_width_for_height_vfunc_dm(Gtk::Widget &widget, int height, int &minimum_width, int &natural_width) const;
void get_preferred_height_vfunc_dm(Gtk::Widget &widget, int &minimum_height, int &natural_height) const;
void get_preferred_height_for_width_vfunc_dm(Gtk::Widget &widget, int width, int &minimum_height, int &natural_height) const;
void render_vfunc_dm(const Cairo::RefPtr<Cairo::Context> &cr,
Gtk::Widget &widget,
const Gdk::Rectangle &background_area,
const Gdk::Rectangle &cell_area,
Gtk::CellRendererState flags);
protected:
Gtk::EventBox *m_ev;
Gtk::Box *m_box;
Gtk::Widget *m_lbl;
Gtk::Image *m_icon;
Gtk::Menu m_menu;
Gtk::MenuItem *m_menu_copyid;
Gtk::MenuItem *m_menu_leave;
Gtk::MenuItem *m_menu_settings;
private:
Gtk::CellRendererText m_renderer_text;
typedef sigc::signal<void> type_signal_copy_id;
typedef sigc::signal<void> type_signal_leave;
typedef sigc::signal<void> type_signal_settings;
Glib::Property<RenderType> m_property_type; // all
Glib::Property<Glib::ustring> m_property_name; // all
Glib::Property<Glib::RefPtr<Gdk::Pixbuf>> m_property_pixbuf; // guild, dm
Glib::Property<Glib::RefPtr<Gdk::PixbufAnimation>> m_property_pixbuf_animation; // guild
Glib::Property<bool> m_property_expanded; // category
Glib::Property<bool> m_property_nsfw; // channel
type_signal_copy_id m_signal_copy_id;
type_signal_leave m_signal_leave;
type_signal_settings m_signal_settings;
// 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
// an animation or two wont be the end of the world though
std::map<Glib::RefPtr<Gdk::PixbufAnimation>, Glib::RefPtr<Gdk::PixbufAnimationIter>> m_pixbuf_anim_iters;
public:
type_signal_copy_id signal_copy_id();
type_signal_leave signal_leave();
type_signal_settings signal_settings();
};
class ChannelList : public Gtk::ScrolledWindow {
class ChannelListRowCategory : public ChannelListRow {
public:
ChannelList();
ChannelListRowCategory(const ChannelData *data);
void UpdateListing();
void SetActiveChannel(Snowflake id);
virtual void Collapse();
virtual void Expand();
protected:
void UpdateNewGuild(const GuildData &guild);
Gtk::EventBox *m_ev;
Gtk::Box *m_box;
Gtk::Widget *m_lbl;
Gtk::Arrow *m_arrow;
Gtk::Menu m_menu;
Gtk::MenuItem *m_menu_copyid;
private:
typedef sigc::signal<void> type_signal_copy_id;
type_signal_copy_id m_signal_copy_id;
public:
type_signal_copy_id signal_copy_id();
};
class ChannelListRowChannel : public ChannelListRow {
public:
ChannelListRowChannel(const ChannelData *data);
protected:
Gtk::EventBox *m_ev;
Gtk::Box *m_box;
Gtk::Widget *m_lbl;
Gtk::Menu m_menu;
Gtk::MenuItem *m_menu_copyid;
private:
typedef sigc::signal<void> type_signal_copy_id;
type_signal_copy_id m_signal_copy_id;
public:
type_signal_copy_id signal_copy_id();
};
class ChannelList {
public:
ChannelList();
Gtk::Widget *GetRoot() const;
void UpdateListing();
void UpdateNewGuild(Snowflake id);
void UpdateRemoveGuild(Snowflake id);
void UpdateRemoveChannel(Snowflake id);
void UpdateChannel(Snowflake id);
void UpdateCreateChannel(const ChannelData &channel);
void UpdateCreateDMChannel(Snowflake id);
void UpdateCreateChannel(Snowflake id);
void UpdateGuild(Snowflake id);
void DeleteThreadRow(Snowflake id);
void OnThreadJoined(Snowflake id);
void OnThreadRemoved(Snowflake id);
void OnThreadDelete(const ThreadDeleteData &data);
void OnThreadUpdate(const ThreadUpdateData &data);
void OnThreadListSync(const ThreadListSyncData &data);
void SetActiveChannel(Snowflake id);
Gtk::TreeView m_view;
protected:
Gtk::ListBox *m_list;
Gtk::ScrolledWindow *m_main;
class ModelColumns : public Gtk::TreeModel::ColumnRecord {
public:
ModelColumns();
ChannelListRowDMHeader *m_dm_header_row = nullptr;
Gtk::TreeModelColumn<RenderType> m_type;
Gtk::TreeModelColumn<uint64_t> m_id;
Gtk::TreeModelColumn<Glib::ustring> m_name;
Gtk::TreeModelColumn<Glib::RefPtr<Gdk::Pixbuf>> m_icon;
Gtk::TreeModelColumn<Glib::RefPtr<Gdk::PixbufAnimation>> m_icon_anim;
Gtk::TreeModelColumn<int64_t> m_sort;
Gtk::TreeModelColumn<bool> m_nsfw;
// 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
// to all categories without children and having a filter model but that sounds worse
// of course its a lot better than the absolute travesty i had before
Gtk::TreeModelColumn<bool> m_expanded;
};
void CollapseRow(ChannelListRow *row);
void ExpandRow(ChannelListRow *row);
void DeleteRow(ChannelListRow *row);
ModelColumns m_columns;
Glib::RefPtr<Gtk::TreeStore> m_model;
void UpdateChannelCategory(Snowflake id);
Gtk::TreeModel::iterator AddGuild(const GuildData &guild);
Gtk::TreeModel::iterator UpdateCreateChannelCategory(const ChannelData &channel);
Gtk::TreeModel::iterator CreateThreadRow(const Gtk::TreeNodeChildren &children, const ChannelData &channel);
void on_row_activated(Gtk::ListBoxRow *row);
void UpdateChannelCategory(const ChannelData &channel);
int m_guild_count;
void OnMenuCopyID(Snowflake id);
void OnGuildMenuLeave(Snowflake id);
void OnGuildMenuSettings(Snowflake id);
// separation necessary because a channel and guild can share the same id
Gtk::TreeModel::iterator GetIteratorForGuildFromID(Snowflake id);
Gtk::TreeModel::iterator GetIteratorForChannelFromID(Snowflake id);
Gtk::Menu m_channel_menu;
Gtk::MenuItem *m_channel_menu_copyid;
bool IsTextChannel(ChannelType type);
// i would use one map but in really old guilds there can be a channel w/ same id as the guild so this hacky shit has to do
std::unordered_map<Snowflake, ChannelListRow *> m_guild_id_to_row;
std::unordered_map<Snowflake, ChannelListRow *> m_id_to_row;
void OnRowCollapsed(const Gtk::TreeModel::iterator &iter, const Gtk::TreeModel::Path &path);
void OnRowExpanded(const Gtk::TreeModel::iterator &iter, const Gtk::TreeModel::Path &path);
bool SelectionFunc(const Glib::RefPtr<Gtk::TreeModel> &model, const Gtk::TreeModel::Path &path, bool is_currently_selected);
bool OnButtonPressEvent(GdkEventButton *ev);
void MoveRow(const Gtk::TreeModel::iterator &iter, const Gtk::TreeModel::iterator &new_parent);
Gtk::TreeModel::Path m_last_selected;
Gtk::TreeModel::Path m_dm_header;
void InsertGuildAt(Snowflake id, int pos);
void AddPrivateChannels();
void UpdateCreateDMChannel(const ChannelData &channel);
void OnMessageCreate(const Message &msg);
Gtk::TreeModel::Path m_path_for_menu;
// cant be recovered through selection
Gtk::TreeModel::iterator m_temporary_thread_row;
Gtk::Menu m_menu_guild;
Gtk::MenuItem m_menu_guild_copy_id;
Gtk::MenuItem m_menu_guild_settings;
Gtk::MenuItem m_menu_guild_leave;
Gtk::Menu m_menu_category;
Gtk::MenuItem m_menu_category_copy_id;
Gtk::Menu m_menu_channel;
Gtk::MenuItem m_menu_channel_copy_id;
Gtk::Menu m_menu_dm;
Gtk::MenuItem m_menu_dm_copy_id;
Gtk::MenuItem m_menu_dm_close;
Gtk::Menu m_menu_thread;
Gtk::MenuItem m_menu_thread_copy_id;
Gtk::MenuItem m_menu_thread_leave;
Gtk::MenuItem m_menu_thread_archive;
Gtk::MenuItem m_menu_thread_unarchive;
void OnThreadSubmenuPopup(const Gdk::Rectangle *flipped_rect, const Gdk::Rectangle *final_rect, bool flipped_x, bool flipped_y);
bool m_updating_listing = false;
void CheckBumpDM(Snowflake channel_id);
public:
typedef sigc::signal<void, Snowflake> type_signal_action_channel_item_select;

View File

@@ -21,9 +21,8 @@ ChatInputIndicator::ChatInputIndicator()
m_label.show();
// try loading gif
const static auto path = Abaddon::GetResPath("/typing_indicator.gif");
if (!std::filesystem::exists(path)) return;
auto gif_data = ReadWholeFile(path);
if (!std::filesystem::exists("./res/typing_indicator.gif")) return;
auto gif_data = ReadWholeFile("./res/typing_indicator.gif");
auto loader = Gdk::PixbufLoader::create();
loader->signal_size_prepared().connect([&](int inw, int inh) {
int w, h;

View File

@@ -1,361 +0,0 @@
#include "chatmessage.hpp"
#include "chatlist.hpp"
#include "../abaddon.hpp"
#include "../constants.hpp"
ChatList::ChatList() {
m_list.get_style_context()->add_class("messages");
set_can_focus(false);
set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_ALWAYS);
signal_edge_reached().connect(sigc::mem_fun(*this, &ChatList::OnScrollEdgeOvershot));
auto v = get_vadjustment();
v->signal_value_changed().connect([this, v] {
m_should_scroll_to_bottom = v->get_upper() - v->get_page_size() <= v->get_value();
});
m_list.signal_size_allocate().connect([this](Gtk::Allocation &) {
if (m_should_scroll_to_bottom)
ScrollToBottom();
});
m_list.set_focus_hadjustment(get_hadjustment());
m_list.set_focus_vadjustment(get_vadjustment());
m_list.set_selection_mode(Gtk::SELECTION_NONE);
m_list.set_hexpand(true);
m_list.set_vexpand(true);
add(m_list);
m_list.show();
m_menu_copy_id = Gtk::manage(new Gtk::MenuItem("Copy ID"));
m_menu_copy_id->signal_activate().connect([this] {
Gtk::Clipboard::get()->set_text(std::to_string(m_menu_selected_message));
});
m_menu_copy_id->show();
m_menu.append(*m_menu_copy_id);
m_menu_delete_message = Gtk::manage(new Gtk::MenuItem("Delete Message"));
m_menu_delete_message->signal_activate().connect([this] {
Abaddon::Get().GetDiscordClient().DeleteMessage(m_active_channel, m_menu_selected_message);
});
m_menu_delete_message->show();
m_menu.append(*m_menu_delete_message);
m_menu_edit_message = Gtk::manage(new Gtk::MenuItem("Edit Message"));
m_menu_edit_message->signal_activate().connect([this] {
m_signal_action_message_edit.emit(m_active_channel, m_menu_selected_message);
});
m_menu_edit_message->show();
m_menu.append(*m_menu_edit_message);
m_menu_copy_content = Gtk::manage(new Gtk::MenuItem("Copy Content"));
m_menu_copy_content->signal_activate().connect([this] {
const auto msg = Abaddon::Get().GetDiscordClient().GetMessage(m_menu_selected_message);
if (msg.has_value())
Gtk::Clipboard::get()->set_text(msg->Content);
});
m_menu_copy_content->show();
m_menu.append(*m_menu_copy_content);
m_menu_reply_to = Gtk::manage(new Gtk::MenuItem("Reply To"));
m_menu_reply_to->signal_activate().connect([this] {
m_signal_action_reply_to.emit(m_menu_selected_message);
});
m_menu_reply_to->show();
m_menu.append(*m_menu_reply_to);
m_menu_unpin = Gtk::manage(new Gtk::MenuItem("Unpin"));
m_menu_unpin->signal_activate().connect([this] {
Abaddon::Get().GetDiscordClient().Unpin(m_active_channel, m_menu_selected_message, [](...) {});
});
m_menu.append(*m_menu_unpin);
m_menu_pin = Gtk::manage(new Gtk::MenuItem("Pin"));
m_menu_pin->signal_activate().connect([this] {
Abaddon::Get().GetDiscordClient().Pin(m_active_channel, m_menu_selected_message, [](...) {});
});
m_menu.append(*m_menu_pin);
m_menu.show();
}
void ChatList::Clear() {
auto children = m_list.get_children();
auto it = children.begin();
while (it != children.end()) {
delete *it;
it++;
}
}
void ChatList::SetActiveChannel(Snowflake id) {
m_active_channel = id;
}
void ChatList::ProcessNewMessage(const Message &data, bool prepend) {
auto &discord = Abaddon::Get().GetDiscordClient();
if (!discord.IsStarted()) return;
// delete preview message when gateway sends it back
if (!data.IsPending && data.Nonce.has_value() && data.Author.ID == discord.GetUserData().ID) {
for (auto [id, widget] : m_id_to_widget) {
if (dynamic_cast<ChatMessageItemContainer *>(widget)->Nonce == *data.Nonce) {
RemoveMessageAndHeader(widget);
m_id_to_widget.erase(id);
break;
}
}
}
ChatMessageHeader *last_row = nullptr;
bool should_attach = false;
if (!m_separate_all && m_num_rows > 0) {
if (prepend)
last_row = dynamic_cast<ChatMessageHeader *>(m_list.get_row_at_index(0));
else
last_row = dynamic_cast<ChatMessageHeader *>(m_list.get_row_at_index(m_num_rows - 1));
if (last_row != nullptr) {
const uint64_t diff = std::max(data.ID, last_row->NewestID) - std::min(data.ID, last_row->NewestID);
if (last_row->UserID == data.Author.ID && (prepend || (diff < SnowflakeSplitDifference * Snowflake::SecondsInterval)))
should_attach = true;
}
}
m_num_messages++;
if (m_should_scroll_to_bottom && !prepend) {
while (m_num_messages > MaxMessagesForChatCull) {
auto first_it = m_id_to_widget.begin();
RemoveMessageAndHeader(first_it->second);
m_id_to_widget.erase(first_it);
}
}
ChatMessageHeader *header;
if (should_attach) {
header = last_row;
} else {
const auto guild_id = *discord.GetChannel(m_active_channel)->GuildID;
const auto user_id = data.Author.ID;
const auto user = discord.GetUser(user_id);
if (!user.has_value()) return;
header = Gtk::manage(new ChatMessageHeader(data));
header->signal_action_insert_mention().connect([this, user_id]() {
m_signal_action_insert_mention.emit(user_id);
});
header->signal_action_open_user_menu().connect([this, user_id, guild_id](const GdkEvent *event) {
m_signal_action_open_user_menu.emit(event, user_id, guild_id);
});
m_num_rows++;
}
auto *content = ChatMessageItemContainer::FromMessage(data);
if (content != nullptr) {
header->AddContent(content, prepend);
m_id_to_widget[data.ID] = content;
const auto cb = [this, id = data.ID](GdkEventButton *ev) -> bool {
if (ev->type == GDK_BUTTON_PRESS && ev->button == GDK_BUTTON_SECONDARY) {
m_menu_selected_message = id;
const auto &client = Abaddon::Get().GetDiscordClient();
const auto data = client.GetMessage(id);
if (!data.has_value()) return false;
const auto channel = client.GetChannel(m_active_channel);
bool is_dm = channel.has_value() && (channel->Type == ChannelType::DM || channel->Type == ChannelType::GROUP_DM);
const bool has_manage = client.HasChannelPermission(client.GetUserData().ID, m_active_channel, Permission::MANAGE_MESSAGES);
m_menu_edit_message->set_visible(!m_use_pinned_menu);
m_menu_reply_to->set_visible(!m_use_pinned_menu);
m_menu_unpin->set_visible((is_dm || has_manage) && data->IsPinned);
m_menu_pin->set_visible((is_dm || has_manage) && !data->IsPinned);
if (data->IsDeleted()) {
m_menu_delete_message->set_sensitive(false);
m_menu_edit_message->set_sensitive(false);
} else {
const bool can_edit = client.GetUserData().ID == data->Author.ID;
const bool can_delete = can_edit || has_manage;
m_menu_delete_message->set_sensitive(can_delete);
m_menu_edit_message->set_sensitive(can_edit);
}
m_menu.popup_at_pointer(reinterpret_cast<GdkEvent *>(ev));
}
return false;
};
content->signal_button_press_event().connect(cb);
if (!data.IsPending) {
content->signal_action_reaction_add().connect([this, id = data.ID](const Glib::ustring &param) {
m_signal_action_reaction_add.emit(id, param);
});
content->signal_action_reaction_remove().connect([this, id = data.ID](const Glib::ustring &param) {
m_signal_action_reaction_remove.emit(id, param);
});
content->signal_action_channel_click().connect([this](const Snowflake &id) {
m_signal_action_channel_click.emit(id);
});
}
}
header->set_margin_left(5);
header->show_all();
if (!should_attach) {
if (prepend)
m_list.prepend(*header);
else
m_list.add(*header);
}
}
void ChatList::DeleteMessage(Snowflake id) {
auto widget = m_id_to_widget.find(id);
if (widget == m_id_to_widget.end()) return;
auto *x = dynamic_cast<ChatMessageItemContainer *>(widget->second);
if (x != nullptr)
x->UpdateAttributes();
}
void ChatList::RefetchMessage(Snowflake id) {
auto widget = m_id_to_widget.find(id);
if (widget == m_id_to_widget.end()) return;
auto *x = dynamic_cast<ChatMessageItemContainer *>(widget->second);
if (x != nullptr) {
x->UpdateContent();
x->UpdateAttributes();
}
}
Snowflake ChatList::GetOldestListedMessage() {
return m_id_to_widget.begin()->first;
}
void ChatList::UpdateMessageReactions(Snowflake id) {
auto it = m_id_to_widget.find(id);
if (it == m_id_to_widget.end()) return;
auto *widget = dynamic_cast<ChatMessageItemContainer *>(it->second);
if (widget == nullptr) return;
widget->UpdateReactions();
}
void ChatList::SetFailedByNonce(const std::string &nonce) {
for (auto [id, widget] : m_id_to_widget) {
if (auto *container = dynamic_cast<ChatMessageItemContainer *>(widget); container->Nonce == nonce) {
container->SetFailed();
break;
}
}
}
std::vector<Snowflake> ChatList::GetRecentAuthors() {
const auto &discord = Abaddon::Get().GetDiscordClient();
std::vector<Snowflake> ret;
std::map<Snowflake, Gtk::Widget *> ordered(m_id_to_widget.begin(), m_id_to_widget.end());
for (auto it = ordered.crbegin(); it != ordered.crend(); it++) {
const auto *widget = dynamic_cast<ChatMessageItemContainer *>(it->second);
if (widget == nullptr) continue;
const auto msg = discord.GetMessage(widget->ID);
if (!msg.has_value()) continue;
if (std::find(ret.begin(), ret.end(), msg->Author.ID) == ret.end())
ret.push_back(msg->Author.ID);
}
const auto chan = discord.GetChannel(m_active_channel);
if (chan->GuildID.has_value()) {
const auto others = discord.GetUsersInGuild(*chan->GuildID);
for (const auto id : others)
if (std::find(ret.begin(), ret.end(), id) == ret.end())
ret.push_back(id);
}
return ret;
}
void ChatList::SetSeparateAll(bool separate) {
m_separate_all = true;
}
void ChatList::SetUsePinnedMenu() {
m_use_pinned_menu = true;
}
void ChatList::ActuallyRemoveMessage(Snowflake id) {
auto it = m_id_to_widget.find(id);
if (it != m_id_to_widget.end())
RemoveMessageAndHeader(it->second);
}
void ChatList::OnScrollEdgeOvershot(Gtk::PositionType pos) {
if (pos == Gtk::POS_TOP)
m_signal_action_chat_load_history.emit(m_active_channel);
}
void ChatList::ScrollToBottom() {
auto x = get_vadjustment();
x->set_value(x->get_upper());
}
void ChatList::RemoveMessageAndHeader(Gtk::Widget *widget) {
auto *header = dynamic_cast<ChatMessageHeader *>(widget->get_ancestor(Gtk::ListBoxRow::get_type()));
if (header != nullptr) {
if (header->GetChildContent().size() == 1) {
m_num_rows--;
delete header;
} else {
delete widget;
}
} else {
delete widget;
}
m_num_messages--;
}
ChatList::type_signal_action_message_edit ChatList::signal_action_message_edit() {
return m_signal_action_message_edit;
}
ChatList::type_signal_action_chat_submit ChatList::signal_action_chat_submit() {
return m_signal_action_chat_submit;
}
ChatList::type_signal_action_chat_load_history ChatList::signal_action_chat_load_history() {
return m_signal_action_chat_load_history;
}
ChatList::type_signal_action_channel_click ChatList::signal_action_channel_click() {
return m_signal_action_channel_click;
}
ChatList::type_signal_action_insert_mention ChatList::signal_action_insert_mention() {
return m_signal_action_insert_mention;
}
ChatList::type_signal_action_open_user_menu ChatList::signal_action_open_user_menu() {
return m_signal_action_open_user_menu;
}
ChatList::type_signal_action_reaction_add ChatList::signal_action_reaction_add() {
return m_signal_action_reaction_add;
}
ChatList::type_signal_action_reaction_remove ChatList::signal_action_reaction_remove() {
return m_signal_action_reaction_remove;
}
ChatList::type_signal_action_reply_to ChatList::signal_action_reply_to() {
return m_signal_action_reply_to;
}

View File

@@ -1,106 +0,0 @@
#pragma once
#include <gtkmm.h>
#include <map>
#include <vector>
#include "../discord/snowflake.hpp"
class ChatList : public Gtk::ScrolledWindow {
public:
ChatList();
void Clear();
void SetActiveChannel(Snowflake id);
template<typename Iter>
void SetMessages(Iter begin, Iter end);
template<typename Iter>
void PrependMessages(Iter begin, Iter end);
void ProcessNewMessage(const Message &data, bool prepend);
void DeleteMessage(Snowflake id);
void RefetchMessage(Snowflake id);
Snowflake GetOldestListedMessage();
void UpdateMessageReactions(Snowflake id);
void SetFailedByNonce(const std::string &nonce);
std::vector<Snowflake> GetRecentAuthors();
void SetSeparateAll(bool separate);
void SetUsePinnedMenu(); // i think i need a better way to do menus
void ActuallyRemoveMessage(Snowflake id); // perhaps not the best method name
private:
void OnScrollEdgeOvershot(Gtk::PositionType pos);
void ScrollToBottom();
void RemoveMessageAndHeader(Gtk::Widget *widget);
bool m_use_pinned_menu = false;
Gtk::Menu m_menu;
Gtk::MenuItem *m_menu_copy_id;
Gtk::MenuItem *m_menu_copy_content;
Gtk::MenuItem *m_menu_delete_message;
Gtk::MenuItem *m_menu_edit_message;
Gtk::MenuItem *m_menu_reply_to;
Gtk::MenuItem *m_menu_unpin;
Gtk::MenuItem *m_menu_pin;
Snowflake m_menu_selected_message;
Snowflake m_active_channel;
int m_num_messages = 0;
int m_num_rows = 0;
std::map<Snowflake, Gtk::Widget *> m_id_to_widget;
bool m_should_scroll_to_bottom = true;
Gtk::ListBox m_list;
bool m_separate_all = false;
public:
// these are all forwarded by the parent
using type_signal_action_message_edit = sigc::signal<void, Snowflake, Snowflake>;
using type_signal_action_chat_submit = sigc::signal<void, std::string, Snowflake, Snowflake>;
using type_signal_action_chat_load_history = sigc::signal<void, Snowflake>;
using type_signal_action_channel_click = sigc::signal<void, Snowflake>;
using type_signal_action_insert_mention = sigc::signal<void, Snowflake>;
using type_signal_action_open_user_menu = sigc::signal<void, const GdkEvent *, Snowflake, Snowflake>;
using type_signal_action_reaction_add = sigc::signal<void, Snowflake, Glib::ustring>;
using type_signal_action_reaction_remove = sigc::signal<void, Snowflake, Glib::ustring>;
using type_signal_action_reply_to = sigc::signal<void, Snowflake>;
type_signal_action_message_edit signal_action_message_edit();
type_signal_action_chat_submit signal_action_chat_submit();
type_signal_action_chat_load_history signal_action_chat_load_history();
type_signal_action_channel_click signal_action_channel_click();
type_signal_action_insert_mention signal_action_insert_mention();
type_signal_action_open_user_menu signal_action_open_user_menu();
type_signal_action_reaction_add signal_action_reaction_add();
type_signal_action_reaction_remove signal_action_reaction_remove();
type_signal_action_reply_to signal_action_reply_to();
private:
type_signal_action_message_edit m_signal_action_message_edit;
type_signal_action_chat_submit m_signal_action_chat_submit;
type_signal_action_chat_load_history m_signal_action_chat_load_history;
type_signal_action_channel_click m_signal_action_channel_click;
type_signal_action_insert_mention m_signal_action_insert_mention;
type_signal_action_open_user_menu m_signal_action_open_user_menu;
type_signal_action_reaction_add m_signal_action_reaction_add;
type_signal_action_reaction_remove m_signal_action_reaction_remove;
type_signal_action_reply_to m_signal_action_reply_to;
};
template<typename Iter>
inline void ChatList::SetMessages(Iter begin, Iter end) {
Clear();
m_num_rows = 0;
m_num_messages = 0;
m_id_to_widget.clear();
for (Iter it = begin; it != end; it++)
ProcessNewMessage(*it, false);
ScrollToBottom();
}
template<typename Iter>
inline void ChatList::PrependMessages(Iter begin, Iter end) {
for (Iter it = begin; it != end; it++)
ProcessNewMessage(*it, true);
}

View File

@@ -9,11 +9,32 @@ constexpr static int AvatarSize = 32;
constexpr static int EmbedImageWidth = 400;
constexpr static int EmbedImageHeight = 300;
constexpr static int ThumbnailSize = 100;
constexpr static int StickerComponentSize = 160;
ChatMessageItemContainer::ChatMessageItemContainer()
: m_main(Gtk::ORIENTATION_VERTICAL) {
add(m_main);
ChatMessageItemContainer::ChatMessageItemContainer() {
m_main = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_VERTICAL));
add(*m_main);
m_menu_copy_id = Gtk::manage(new Gtk::MenuItem("Copy ID"));
m_menu_copy_id->signal_activate().connect(sigc::mem_fun(*this, &ChatMessageItemContainer::on_menu_copy_id));
m_menu.append(*m_menu_copy_id);
m_menu_delete_message = Gtk::manage(new Gtk::MenuItem("Delete Message"));
m_menu_delete_message->signal_activate().connect(sigc::mem_fun(*this, &ChatMessageItemContainer::on_menu_delete_message));
m_menu.append(*m_menu_delete_message);
m_menu_edit_message = Gtk::manage(new Gtk::MenuItem("Edit Message"));
m_menu_edit_message->signal_activate().connect(sigc::mem_fun(*this, &ChatMessageItemContainer::on_menu_edit_message));
m_menu.append(*m_menu_edit_message);
m_menu_copy_content = Gtk::manage(new Gtk::MenuItem("Copy Content"));
m_menu_copy_content->signal_activate().connect(sigc::mem_fun(*this, &ChatMessageItemContainer::on_menu_copy_content));
m_menu.append(*m_menu_copy_content);
m_menu_reply_to = Gtk::manage(new Gtk::MenuItem("Reply To"));
m_menu_reply_to->signal_activate().connect(sigc::mem_fun(*this, &ChatMessageItemContainer::on_menu_reply_to));
m_menu.append(*m_menu_reply_to);
m_menu.show_all();
m_link_menu_copy = Gtk::manage(new Gtk::MenuItem("Copy Link"));
m_link_menu_copy->signal_activate().connect(sigc::mem_fun(*this, &ChatMessageItemContainer::on_link_menu_copy));
@@ -22,74 +43,68 @@ ChatMessageItemContainer::ChatMessageItemContainer()
m_link_menu.show_all();
}
ChatMessageItemContainer *ChatMessageItemContainer::FromMessage(const Message &data) {
ChatMessageItemContainer *ChatMessageItemContainer::FromMessage(Snowflake id) {
const auto data = Abaddon::Get().GetDiscordClient().GetMessage(id);
if (!data.has_value()) return nullptr;
auto *container = Gtk::manage(new ChatMessageItemContainer);
container->ID = data.ID;
container->ChannelID = data.ChannelID;
container->ID = data->ID;
container->ChannelID = data->ChannelID;
if (data.Nonce.has_value())
container->Nonce = *data.Nonce;
if (data->Nonce.has_value())
container->Nonce = *data->Nonce;
if (data.Content.size() > 0 || data.Type != MessageType::DEFAULT) {
container->m_text_component = container->CreateTextComponent(data);
if (data->Content.size() > 0 || data->Type != MessageType::DEFAULT) {
container->m_text_component = container->CreateTextComponent(&*data);
container->AttachEventHandlers(*container->m_text_component);
container->m_main.add(*container->m_text_component);
container->m_main->add(*container->m_text_component);
}
if ((data.MessageReference.has_value() || data.Interaction.has_value()) && data.Type != MessageType::CHANNEL_FOLLOW_ADD) {
auto *widget = container->CreateReplyComponent(data);
if (widget != nullptr) {
container->m_main.add(*widget);
container->m_main.child_property_position(*widget) = 0; // eek
}
if ((data->MessageReference.has_value() || data->Interaction.has_value()) && data->Type != MessageType::CHANNEL_FOLLOW_ADD) {
auto *widget = container->CreateReplyComponent(*data);
container->m_main->add(*widget);
container->m_main->child_property_position(*widget) = 0; // eek
}
// there should only ever be 1 embed (i think?)
if (data.Embeds.size() == 1) {
const auto &embed = data.Embeds[0];
if (data->Embeds.size() == 1) {
const auto &embed = data->Embeds[0];
if (IsEmbedImageOnly(embed)) {
auto *widget = container->CreateImageComponent(*embed.Thumbnail->ProxyURL, *embed.Thumbnail->URL, *embed.Thumbnail->Width, *embed.Thumbnail->Height);
container->AttachEventHandlers(*widget);
container->m_main.add(*widget);
container->m_main->add(*widget);
} else {
container->m_embed_component = container->CreateEmbedComponent(embed);
container->AttachEventHandlers(*container->m_embed_component);
container->m_main.add(*container->m_embed_component);
container->m_main->add(*container->m_embed_component);
}
}
// i dont think attachments can be edited
// also this can definitely be done much better holy shit
for (const auto &a : data.Attachments) {
for (const auto &a : data->Attachments) {
if (IsURLViewableImage(a.ProxyURL) && a.Width.has_value() && a.Height.has_value()) {
auto *widget = container->CreateImageComponent(a.ProxyURL, a.URL, *a.Width, *a.Height);
container->m_main.add(*widget);
container->m_main->add(*widget);
} else {
auto *widget = container->CreateAttachmentComponent(a);
container->m_main.add(*widget);
container->m_main->add(*widget);
}
}
// only 1?
/*
DEPRECATED
if (data.Stickers.has_value()) {
const auto &sticker = data.Stickers.value()[0];
if (data->Stickers.has_value()) {
const auto &sticker = data->Stickers.value()[0];
// todo: lottie, proper apng
if (sticker.FormatType == StickerFormatType::PNG || sticker.FormatType == StickerFormatType::APNG) {
auto *widget = container->CreateStickerComponent(sticker);
container->m_main->add(*widget);
}
}*/
if (data.StickerItems.has_value()) {
auto *widget = container->CreateStickersComponent(*data.StickerItems);
container->m_main.add(*widget);
}
if (data.Reactions.has_value() && data.Reactions->size() > 0) {
container->m_reactions_component = container->CreateReactionsComponent(data);
container->m_main.add(*container->m_reactions_component);
if (data->Reactions.has_value() && data->Reactions->size() > 0) {
container->m_reactions_component = container->CreateReactionsComponent(*data);
container->m_main->add(*container->m_reactions_component);
}
container->UpdateAttributes();
@@ -111,7 +126,7 @@ void ChatMessageItemContainer::UpdateContent() {
if (data->Embeds.size() == 1) {
m_embed_component = CreateEmbedComponent(data->Embeds[0]);
AttachEventHandlers(*m_embed_component);
m_main.add(*m_embed_component);
m_main->add(*m_embed_component);
}
}
@@ -123,15 +138,13 @@ void ChatMessageItemContainer::UpdateReactions() {
if (data->Reactions.has_value() && data->Reactions->size() > 0) {
m_reactions_component = CreateReactionsComponent(*data);
m_reactions_component->show_all();
m_main.add(*m_reactions_component);
m_main->add(*m_reactions_component);
}
}
void ChatMessageItemContainer::SetFailed() {
if (m_text_component != nullptr) {
m_text_component->get_style_context()->remove_class("pending");
m_text_component->get_style_context()->add_class("failed");
}
m_text_component->get_style_context()->remove_class("pending");
m_text_component->get_style_context()->add_class("failed");
}
void ChatMessageItemContainer::UpdateAttributes() {
@@ -147,7 +160,7 @@ void ChatMessageItemContainer::UpdateAttributes() {
m_attrib_label = Gtk::manage(new Gtk::Label);
m_attrib_label->set_halign(Gtk::ALIGN_START);
m_attrib_label->show();
m_main.add(*m_attrib_label); // todo: maybe insert markup into existing text widget's buffer if the circumstances are right (or pack horizontally)
m_main->add(*m_attrib_label); // todo: maybe insert markup into existing text widget's buffer if the circumstances are right (or pack horizontally)
}
if (deleted)
@@ -168,10 +181,10 @@ void ChatMessageItemContainer::AddClickHandler(Gtk::Widget *widget, std::string
// clang-format on
}
Gtk::TextView *ChatMessageItemContainer::CreateTextComponent(const Message &data) {
Gtk::TextView *ChatMessageItemContainer::CreateTextComponent(const Message *data) {
auto *tv = Gtk::manage(new Gtk::TextView);
if (data.IsPending)
if (data->IsPending)
tv->get_style_context()->add_class("pending");
tv->get_style_context()->add_class("message-text");
tv->set_can_focus(false);
@@ -276,20 +289,6 @@ void ChatMessageItemContainer::UpdateTextComponent(Gtk::TextView *tv) {
case MessageType::GUILD_DISCOVERY_GRACE_PERIOD_FINAL_WARNING: {
b->insert_markup(s, "<i><span color='#999999'>This server has failed Discovery activity requirements for 3 weeks in a row. If this server fails for 1 more week, it will be removed from Discovery.</span></i>");
} break;
case MessageType::THREAD_CREATED: {
const auto author = Abaddon::Get().GetDiscordClient().GetUser(data->Author.ID);
if (data->MessageReference.has_value() && data->MessageReference->ChannelID.has_value()) {
auto iter = b->insert_markup(s, "<i><span color='#999999'>" + author->GetEscapedBoldName() + " started a thread: </span></i>");
auto tag = b->create_tag();
tag->property_weight() = Pango::WEIGHT_BOLD;
m_channel_tagmap[tag] = *data->MessageReference->ChannelID;
b->insert_with_tag(iter, data->Content, tag);
tv->signal_button_press_event().connect(sigc::mem_fun(*this, &ChatMessageItemContainer::OnClickChannel), false);
} else {
b->insert_markup(s, "<i><span color='#999999'>" + author->GetEscapedBoldName() + " started a thread: </span><b>" + Glib::Markup::escape_text(data->Content) + "</b></i>");
}
} break;
default: break;
}
}
@@ -503,7 +502,7 @@ Gtk::Widget *ChatMessageItemContainer::CreateAttachmentComponent(const Attachmen
return ev;
}
Gtk::Widget *ChatMessageItemContainer::CreateStickerComponentDeprecated(const StickerData &data) {
Gtk::Widget *ChatMessageItemContainer::CreateStickerComponent(const StickerData &data) {
auto *box = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL));
auto *imgw = Gtk::manage(new Gtk::Image);
box->add(*imgw);
@@ -520,27 +519,6 @@ Gtk::Widget *ChatMessageItemContainer::CreateStickerComponentDeprecated(const St
return box;
}
Gtk::Widget *ChatMessageItemContainer::CreateStickersComponent(const std::vector<StickerItem> &data) {
auto *box = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_VERTICAL));
for (const auto &sticker : data) {
// no lottie
if (sticker.FormatType != StickerFormatType::PNG && sticker.FormatType != StickerFormatType::APNG) continue;
auto *ev = Gtk::manage(new Gtk::EventBox);
auto *img = Gtk::manage(new LazyImage(sticker.GetURL(), StickerComponentSize, StickerComponentSize, false));
img->set_size_request(StickerComponentSize, StickerComponentSize); // should this go in LazyImage ?
img->show();
ev->show();
ev->add(*img);
box->add(*ev);
}
box->show();
AttachEventHandlers(*box);
return box;
}
Gtk::Widget *ChatMessageItemContainer::CreateReactionsComponent(const Message &data) {
auto *flow = Gtk::manage(new Gtk::FlowBox);
flow->set_orientation(Gtk::ORIENTATION_HORIZONTAL);
@@ -623,8 +601,6 @@ Gtk::Widget *ChatMessageItemContainer::CreateReactionsComponent(const Message &d
}
Gtk::Widget *ChatMessageItemContainer::CreateReplyComponent(const Message &data) {
if (data.Type == MessageType::THREAD_CREATED) return nullptr;
auto *box = Gtk::manage(new Gtk::Box);
auto *lbl = Gtk::manage(new Gtk::Label);
lbl->set_single_line_mode(true);
@@ -832,11 +808,11 @@ void ChatMessageItemContainer::HandleCustomEmojis(Gtk::TextView &tv) {
}
void ChatMessageItemContainer::HandleEmojis(Gtk::TextView &tv) {
static const bool stock_emojis = Abaddon::Get().GetSettings().GetShowStockEmojis();
static const bool custom_emojis = Abaddon::Get().GetSettings().GetShowCustomEmojis();
if (stock_emojis) HandleStockEmojis(tv);
if (custom_emojis) HandleCustomEmojis(tv);
static bool emojis = Abaddon::Get().GetSettings().GetShowEmojis();
if (emojis) {
HandleStockEmojis(tv);
HandleCustomEmojis(tv);
}
}
void ChatMessageItemContainer::CleanupEmojis(Glib::RefPtr<Gtk::TextBuffer> buf) {
@@ -890,10 +866,8 @@ void ChatMessageItemContainer::HandleChannelMentions(Glib::RefPtr<Gtk::TextBuffe
}
auto tag = buf->create_tag();
if (chan->Type == ChannelType::GUILD_TEXT) {
m_channel_tagmap[tag] = channel_id;
tag->property_weight() = Pango::WEIGHT_BOLD;
}
m_channel_tagmap[tag] = channel_id;
tag->property_weight() = Pango::WEIGHT_BOLD;
const auto chars_start = g_utf8_pointer_to_offset(text.c_str(), text.c_str() + mstart);
const auto chars_end = g_utf8_pointer_to_offset(text.c_str(), text.c_str() + mend);
@@ -1015,6 +989,52 @@ bool ChatMessageItemContainer::OnLinkClick(GdkEventButton *ev) {
return false;
}
void ChatMessageItemContainer::ShowMenu(GdkEvent *event) {
const auto &client = Abaddon::Get().GetDiscordClient();
const auto data = client.GetMessage(ID);
if (data->IsDeleted()) {
m_menu_delete_message->set_sensitive(false);
m_menu_edit_message->set_sensitive(false);
} else {
const bool can_edit = client.GetUserData().ID == data->Author.ID;
const bool can_delete = can_edit || client.HasChannelPermission(client.GetUserData().ID, ChannelID, Permission::MANAGE_MESSAGES);
m_menu_delete_message->set_sensitive(can_delete);
m_menu_edit_message->set_sensitive(can_edit);
}
m_menu.popup_at_pointer(event);
}
void ChatMessageItemContainer::on_menu_copy_id() {
Gtk::Clipboard::get()->set_text(std::to_string(ID));
}
void ChatMessageItemContainer::on_menu_delete_message() {
m_signal_action_delete.emit();
}
void ChatMessageItemContainer::on_menu_edit_message() {
m_signal_action_edit.emit();
}
void ChatMessageItemContainer::on_menu_copy_content() {
const auto msg = Abaddon::Get().GetDiscordClient().GetMessage(ID);
if (msg.has_value())
Gtk::Clipboard::get()->set_text(msg->Content);
}
void ChatMessageItemContainer::on_menu_reply_to() {
m_signal_action_reply_to.emit(ID);
}
ChatMessageItemContainer::type_signal_action_delete ChatMessageItemContainer::signal_action_delete() {
return m_signal_action_delete;
}
ChatMessageItemContainer::type_signal_action_edit ChatMessageItemContainer::signal_action_edit() {
return m_signal_action_edit;
}
ChatMessageItemContainer::type_signal_channel_click ChatMessageItemContainer::signal_action_channel_click() {
return m_signal_action_channel_click;
}
@@ -1027,10 +1047,14 @@ ChatMessageItemContainer::type_signal_action_reaction_remove ChatMessageItemCont
return m_signal_action_reaction_remove;
}
ChatMessageItemContainer::type_signal_action_reply_to ChatMessageItemContainer::signal_action_reply_to() {
return m_signal_action_reply_to;
}
void ChatMessageItemContainer::AttachEventHandlers(Gtk::Widget &widget) {
const auto on_button_press_event = [this](GdkEventButton *e) -> bool {
if (e->type == GDK_BUTTON_PRESS && e->button == GDK_BUTTON_SECONDARY) {
event(reinterpret_cast<GdkEvent *>(e)); // illegal ooooooh
const auto on_button_press_event = [this](GdkEventButton *event) -> bool {
if (event->type == GDK_BUTTON_PRESS && event->button == GDK_BUTTON_SECONDARY) {
ShowMenu(reinterpret_cast<GdkEvent *>(event));
return true;
}
@@ -1039,22 +1063,28 @@ void ChatMessageItemContainer::AttachEventHandlers(Gtk::Widget &widget) {
widget.signal_button_press_event().connect(on_button_press_event, false);
}
ChatMessageHeader::ChatMessageHeader(const Message &data)
: m_main_box(Gtk::ORIENTATION_HORIZONTAL)
, m_content_box(Gtk::ORIENTATION_VERTICAL)
, m_meta_box(Gtk::ORIENTATION_HORIZONTAL)
, m_avatar(Abaddon::Get().GetImageManager().GetPlaceholder(AvatarSize)) {
UserID = data.Author.ID;
ChannelID = data.ChannelID;
ChatMessageHeader::ChatMessageHeader(const Message *data) {
UserID = data->Author.ID;
ChannelID = data->ChannelID;
m_main_box = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL));
m_content_box = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_VERTICAL));
m_content_box_ev = Gtk::manage(new Gtk::EventBox);
m_meta_box = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL));
m_meta_ev = Gtk::manage(new Gtk::EventBox);
m_author = Gtk::manage(new Gtk::Label);
m_timestamp = Gtk::manage(new Gtk::Label);
m_avatar_ev = Gtk::manage(new Gtk::EventBox);
const auto author = Abaddon::Get().GetDiscordClient().GetUser(UserID);
auto &img = Abaddon::Get().GetImageManager();
m_avatar = Gtk::manage(new Gtk::Image(img.GetPlaceholder(AvatarSize)));
auto cb = [this](const Glib::RefPtr<Gdk::Pixbuf> &pb) {
m_static_avatar = pb->scale_simple(AvatarSize, AvatarSize, Gdk::INTERP_BILINEAR);
m_avatar.property_pixbuf() = m_static_avatar;
m_avatar->property_pixbuf() = m_static_avatar;
};
img.LoadFromURL(author->GetAvatarURL(data.GuildID), sigc::track_obj(cb, *this));
img.LoadFromURL(author->GetAvatarURL(), sigc::track_obj(cb, *this));
if (author->HasAnimatedAvatar()) {
auto cb = [this](const Glib::RefPtr<Gdk::PixbufAnimation> &pb) {
@@ -1064,23 +1094,23 @@ ChatMessageHeader::ChatMessageHeader(const Message &data)
}
get_style_context()->add_class("message-container");
m_author.get_style_context()->add_class("message-container-author");
m_timestamp.get_style_context()->add_class("message-container-timestamp");
m_avatar.get_style_context()->add_class("message-container-avatar");
m_author->get_style_context()->add_class("message-container-author");
m_timestamp->get_style_context()->add_class("message-container-timestamp");
m_avatar->get_style_context()->add_class("message-container-avatar");
m_avatar.set_valign(Gtk::ALIGN_START);
m_avatar.set_margin_right(10);
m_avatar->set_valign(Gtk::ALIGN_START);
m_avatar->set_margin_right(10);
m_author.set_markup(data.Author.GetEscapedBoldName());
m_author.set_single_line_mode(true);
m_author.set_line_wrap(false);
m_author.set_ellipsize(Pango::ELLIPSIZE_END);
m_author.set_xalign(0.f);
m_author.set_can_focus(false);
m_author->set_markup(data->Author.GetEscapedBoldName());
m_author->set_single_line_mode(true);
m_author->set_line_wrap(false);
m_author->set_ellipsize(Pango::ELLIPSIZE_END);
m_author->set_xalign(0.f);
m_author->set_can_focus(false);
m_meta_ev.signal_button_press_event().connect(sigc::mem_fun(*this, &ChatMessageHeader::on_author_button_press));
m_meta_ev->signal_button_press_event().connect(sigc::mem_fun(*this, &ChatMessageHeader::on_author_button_press));
if (author->IsBot || data.WebhookID.has_value()) {
if (author->IsBot || data->WebhookID.has_value()) {
m_extra = Gtk::manage(new Gtk::Label);
m_extra->get_style_context()->add_class("message-container-extra");
m_extra->set_single_line_mode(true);
@@ -1090,62 +1120,62 @@ ChatMessageHeader::ChatMessageHeader(const Message &data)
}
if (author->IsBot)
m_extra->set_markup("<b>BOT</b>");
else if (data.WebhookID.has_value())
else if (data->WebhookID.has_value())
m_extra->set_markup("<b>Webhook</b>");
m_timestamp.set_text(data.ID.GetLocalTimestamp());
m_timestamp.set_hexpand(true);
m_timestamp.set_halign(Gtk::ALIGN_END);
m_timestamp.set_ellipsize(Pango::ELLIPSIZE_END);
m_timestamp.set_opacity(0.5);
m_timestamp.set_single_line_mode(true);
m_timestamp.set_margin_start(12);
m_timestamp.set_can_focus(false);
m_timestamp->set_text(data->ID.GetLocalTimestamp());
m_timestamp->set_hexpand(true);
m_timestamp->set_halign(Gtk::ALIGN_END);
m_timestamp->set_ellipsize(Pango::ELLIPSIZE_END);
m_timestamp->set_opacity(0.5);
m_timestamp->set_single_line_mode(true);
m_timestamp->set_margin_start(12);
m_timestamp->set_can_focus(false);
m_main_box.set_hexpand(true);
m_main_box.set_vexpand(true);
m_main_box.set_can_focus(true);
m_main_box->set_hexpand(true);
m_main_box->set_vexpand(true);
m_main_box->set_can_focus(true);
m_meta_box.set_hexpand(true);
m_meta_box.set_can_focus(false);
m_meta_box->set_hexpand(true);
m_meta_box->set_can_focus(false);
m_content_box.set_can_focus(false);
m_content_box->set_can_focus(false);
const auto on_enter_cb = [this](const GdkEventCrossing *event) -> bool {
if (m_anim_avatar)
m_avatar.property_pixbuf_animation() = m_anim_avatar;
m_avatar->property_pixbuf_animation() = m_anim_avatar;
return false;
};
const auto on_leave_cb = [this](const GdkEventCrossing *event) -> bool {
if (m_anim_avatar)
m_avatar.property_pixbuf() = m_static_avatar;
m_avatar->property_pixbuf() = m_static_avatar;
return false;
};
m_content_box_ev.add_events(Gdk::ENTER_NOTIFY_MASK | Gdk::LEAVE_NOTIFY_MASK);
m_meta_ev.add_events(Gdk::ENTER_NOTIFY_MASK | Gdk::LEAVE_NOTIFY_MASK);
m_avatar_ev.add_events(Gdk::ENTER_NOTIFY_MASK | Gdk::LEAVE_NOTIFY_MASK);
m_content_box_ev->add_events(Gdk::ENTER_NOTIFY_MASK | Gdk::LEAVE_NOTIFY_MASK);
m_meta_ev->add_events(Gdk::ENTER_NOTIFY_MASK | Gdk::LEAVE_NOTIFY_MASK);
m_avatar_ev->add_events(Gdk::ENTER_NOTIFY_MASK | Gdk::LEAVE_NOTIFY_MASK);
if (Abaddon::Get().GetSettings().GetShowAnimations()) {
m_content_box_ev.signal_enter_notify_event().connect(on_enter_cb);
m_content_box_ev.signal_leave_notify_event().connect(on_leave_cb);
m_meta_ev.signal_enter_notify_event().connect(on_enter_cb);
m_meta_ev.signal_leave_notify_event().connect(on_leave_cb);
m_avatar_ev.signal_enter_notify_event().connect(on_enter_cb);
m_avatar_ev.signal_leave_notify_event().connect(on_leave_cb);
m_content_box_ev->signal_enter_notify_event().connect(on_enter_cb);
m_content_box_ev->signal_leave_notify_event().connect(on_leave_cb);
m_meta_ev->signal_enter_notify_event().connect(on_enter_cb);
m_meta_ev->signal_leave_notify_event().connect(on_leave_cb);
m_avatar_ev->signal_enter_notify_event().connect(on_enter_cb);
m_avatar_ev->signal_leave_notify_event().connect(on_leave_cb);
}
m_meta_box.add(m_author);
m_meta_box->add(*m_author);
if (m_extra != nullptr)
m_meta_box.add(*m_extra);
m_meta_box->add(*m_extra);
m_meta_box.add(m_timestamp);
m_meta_ev.add(m_meta_box);
m_content_box.add(m_meta_ev);
m_avatar_ev.add(m_avatar);
m_main_box.add(m_avatar_ev);
m_content_box_ev.add(m_content_box);
m_main_box.add(m_content_box_ev);
add(m_main_box);
m_meta_box->add(*m_timestamp);
m_meta_ev->add(*m_meta_box);
m_content_box->add(*m_meta_ev);
m_avatar_ev->add(*m_avatar);
m_main_box->add(*m_avatar_ev);
m_content_box_ev->add(*m_content_box);
m_main_box->add(*m_content_box_ev);
add(*m_main_box);
set_margin_bottom(8);
@@ -1157,8 +1187,8 @@ ChatMessageHeader::ChatMessageHeader(const Message &data)
auto guild_member_update_cb = [this](const auto &, const auto &) { UpdateNameColor(); };
discord.signal_guild_member_update().connect(sigc::track_obj(guild_member_update_cb, *this));
UpdateNameColor();
AttachUserMenuHandler(m_meta_ev);
AttachUserMenuHandler(m_avatar_ev);
AttachUserMenuHandler(*m_meta_ev);
AttachUserMenuHandler(*m_avatar_ev);
}
void ChatMessageHeader::UpdateNameColor() {
@@ -1175,7 +1205,7 @@ void ChatMessageHeader::UpdateNameColor() {
else
md = "<span weight='bold'>" + user->GetEscapedName() + "</span>";
m_author.set_markup(md);
m_author->set_markup(md);
}
std::vector<Gtk::Widget *> ChatMessageHeader::GetChildContent() {
@@ -1185,11 +1215,7 @@ std::vector<Gtk::Widget *> ChatMessageHeader::GetChildContent() {
void ChatMessageHeader::AttachUserMenuHandler(Gtk::Widget &widget) {
widget.signal_button_press_event().connect([this](GdkEventButton *ev) -> bool {
if (ev->type == GDK_BUTTON_PRESS && ev->button == GDK_BUTTON_SECONDARY) {
auto info = Abaddon::Get().GetDiscordClient().GetChannel(ChannelID);
Snowflake guild_id;
if (info.has_value() && info->GuildID.has_value())
guild_id = *info->GuildID;
Abaddon::Get().ShowUserMenu(reinterpret_cast<GdkEvent *>(ev), UserID, guild_id);
m_signal_action_open_user_menu.emit(reinterpret_cast<GdkEvent *>(ev));
return true;
}
@@ -1216,13 +1242,12 @@ ChatMessageHeader::type_signal_action_open_user_menu ChatMessageHeader::signal_a
void ChatMessageHeader::AddContent(Gtk::Widget *widget, bool prepend) {
m_content_widgets.push_back(widget);
const auto cb = [this, widget]() {
widget->signal_unmap().connect([this, widget]() {
m_content_widgets.erase(std::remove(m_content_widgets.begin(), m_content_widgets.end(), widget), m_content_widgets.end());
};
widget->signal_unmap().connect(sigc::track_obj(cb, *this, *widget), false);
m_content_box.add(*widget);
});
m_content_box->add(*widget);
if (prepend)
m_content_box.reorder_child(*widget, 1);
m_content_box->reorder_child(*widget, 1);
if (auto *x = dynamic_cast<ChatMessageItemContainer *>(widget)) {
if (x->ID > NewestID)
NewestID = x->ID;

View File

@@ -10,7 +10,7 @@ public:
std::string Nonce;
ChatMessageItemContainer();
static ChatMessageItemContainer *FromMessage(const Message &data);
static ChatMessageItemContainer *FromMessage(Snowflake id);
// attributes = edited, deleted
void UpdateAttributes();
@@ -20,13 +20,12 @@ public:
protected:
void AddClickHandler(Gtk::Widget *widget, std::string);
Gtk::TextView *CreateTextComponent(const Message &data); // Message.Content
Gtk::TextView *CreateTextComponent(const Message *data); // Message.Content
void UpdateTextComponent(Gtk::TextView *tv);
Gtk::Widget *CreateEmbedComponent(const EmbedData &data); // Message.Embeds[0]
Gtk::Widget *CreateImageComponent(const std::string &proxy_url, const std::string &url, int inw, int inh);
Gtk::Widget *CreateAttachmentComponent(const AttachmentData &data); // non-image attachments
Gtk::Widget *CreateStickerComponentDeprecated(const StickerData &data);
Gtk::Widget *CreateStickersComponent(const std::vector<StickerItem> &data);
Gtk::Widget *CreateStickerComponent(const StickerData &data);
Gtk::Widget *CreateReactionsComponent(const Message &data);
Gtk::Widget *CreateReplyComponent(const Message &data);
@@ -57,9 +56,23 @@ protected:
std::map<Glib::RefPtr<Gtk::TextTag>, Snowflake> m_channel_tagmap;
void AttachEventHandlers(Gtk::Widget &widget);
void ShowMenu(GdkEvent *event);
Gtk::EventBox *_ev;
Gtk::Box m_main;
Gtk::Menu m_menu;
Gtk::MenuItem *m_menu_copy_id;
Gtk::MenuItem *m_menu_copy_content;
Gtk::MenuItem *m_menu_delete_message;
Gtk::MenuItem *m_menu_edit_message;
Gtk::MenuItem *m_menu_reply_to;
void on_menu_copy_id();
void on_menu_delete_message();
void on_menu_edit_message();
void on_menu_copy_content();
void on_menu_reply_to();
Gtk::EventBox *m_ev;
Gtk::Box *m_main;
Gtk::Label *m_attrib_label = nullptr;
Gtk::TextView *m_text_component = nullptr;
@@ -67,18 +80,29 @@ protected:
Gtk::Widget *m_reactions_component = nullptr;
public:
typedef sigc::signal<void> type_signal_action_delete;
typedef sigc::signal<void> type_signal_action_edit;
typedef sigc::signal<void, Snowflake> type_signal_channel_click;
typedef sigc::signal<void, Glib::ustring> type_signal_action_reaction_add;
typedef sigc::signal<void, Glib::ustring> type_signal_action_reaction_remove;
typedef sigc::signal<void, Snowflake> type_signal_action_reply_to;
typedef sigc::signal<void> type_signal_enter;
typedef sigc::signal<void> type_signal_leave;
type_signal_action_delete signal_action_delete();
type_signal_action_edit signal_action_edit();
type_signal_channel_click signal_action_channel_click();
type_signal_action_reaction_add signal_action_reaction_add();
type_signal_action_reaction_remove signal_action_reaction_remove();
type_signal_action_reply_to signal_action_reply_to();
private:
type_signal_action_delete m_signal_action_delete;
type_signal_action_edit m_signal_action_edit;
type_signal_channel_click m_signal_action_channel_click;
type_signal_action_reaction_add m_signal_action_reaction_add;
type_signal_action_reaction_remove m_signal_action_reaction_remove;
type_signal_action_reply_to m_signal_action_reply_to;
};
class ChatMessageHeader : public Gtk::ListBoxRow {
@@ -87,28 +111,28 @@ public:
Snowflake ChannelID;
Snowflake NewestID = 0;
ChatMessageHeader(const Message &data);
ChatMessageHeader(const Message *data);
void AddContent(Gtk::Widget *widget, bool prepend);
void UpdateNameColor();
std::vector<Gtk::Widget *> GetChildContent();
std::vector<Gtk::Widget*> GetChildContent();
protected:
void AttachUserMenuHandler(Gtk::Widget &widget);
bool on_author_button_press(GdkEventButton *ev);
std::vector<Gtk::Widget *> m_content_widgets;
std::vector<Gtk::Widget*> m_content_widgets;
Gtk::Box m_main_box;
Gtk::Box m_content_box;
Gtk::EventBox m_content_box_ev;
Gtk::Box m_meta_box;
Gtk::EventBox m_meta_ev;
Gtk::Label m_author;
Gtk::Label m_timestamp;
Gtk::Box *m_main_box;
Gtk::Box *m_content_box;
Gtk::EventBox *m_content_box_ev;
Gtk::Box *m_meta_box;
Gtk::EventBox *m_meta_ev;
Gtk::Label *m_author;
Gtk::Label *m_timestamp;
Gtk::Label *m_extra = nullptr;
Gtk::Image m_avatar;
Gtk::EventBox m_avatar_ev;
Gtk::Image *m_avatar;
Gtk::EventBox *m_avatar_ev;
Glib::RefPtr<Gdk::Pixbuf> m_static_avatar;
Glib::RefPtr<Gdk::PixbufAnimation> m_anim_avatar;

View File

@@ -4,13 +4,15 @@
#include "chatinputindicator.hpp"
#include "ratelimitindicator.hpp"
#include "chatinput.hpp"
#include "chatlist.hpp"
constexpr static uint64_t SnowflakeSplitDifference = 600;
ChatWindow::ChatWindow() {
Abaddon::Get().GetDiscordClient().signal_message_send_fail().connect(sigc::mem_fun(*this, &ChatWindow::OnMessageSendFail));
m_main = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_VERTICAL));
m_chat = Gtk::manage(new ChatList);
m_list = Gtk::manage(new Gtk::ListBox);
m_scroll = Gtk::manage(new Gtk::ScrolledWindow);
m_input = Gtk::manage(new ChatInput);
m_input_indicator = Gtk::manage(new ChatInputIndicator);
m_rate_limit_indicator = Gtk::manage(new RateLimitIndicator);
@@ -27,14 +29,33 @@ ChatWindow::ChatWindow() {
m_input_indicator->show();
m_main->get_style_context()->add_class("messages");
m_list->get_style_context()->add_class("messages");
m_main->set_hexpand(true);
m_main->set_vexpand(true);
m_topic.get_style_context()->add_class("channel-topic");
m_topic.add(m_topic_text);
m_topic_text.set_halign(Gtk::ALIGN_START);
m_topic_text.show();
m_scroll->signal_edge_reached().connect(sigc::mem_fun(*this, &ChatWindow::OnScrollEdgeOvershot));
auto v = m_scroll->get_vadjustment();
v->signal_value_changed().connect([this, v] {
m_should_scroll_to_bottom = v->get_upper() - v->get_page_size() <= v->get_value();
});
m_scroll->set_can_focus(false);
m_scroll->set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_ALWAYS);
m_scroll->show();
m_list->signal_size_allocate().connect([this](Gtk::Allocation &) {
if (m_should_scroll_to_bottom)
ScrollToBottom();
});
m_list->set_selection_mode(Gtk::SELECTION_NONE);
m_list->set_hexpand(true);
m_list->set_vexpand(true);
m_list->set_focus_hadjustment(m_scroll->get_hadjustment());
m_list->set_focus_vadjustment(m_scroll->get_vadjustment());
m_list->show();
m_input->signal_submit().connect(sigc::mem_fun(*this, &ChatWindow::OnInputSubmit));
m_input->signal_escape().connect([this]() {
@@ -50,47 +71,41 @@ ChatWindow::ChatWindow() {
});
m_completer.SetGetRecentAuthors([this]() -> auto {
return m_chat->GetRecentAuthors();
const auto &discord = Abaddon::Get().GetDiscordClient();
std::vector<Snowflake> ret;
std::map<Snowflake, Gtk::Widget *> ordered(m_id_to_widget.begin(), m_id_to_widget.end());
for (auto it = ordered.crbegin(); it != ordered.crend(); it++) {
const auto *widget = dynamic_cast<ChatMessageItemContainer *>(it->second);
if (widget == nullptr) continue;
const auto msg = discord.GetMessage(widget->ID);
if (!msg.has_value()) continue;
if (std::find(ret.begin(), ret.end(), msg->Author.ID) == ret.end())
ret.push_back(msg->Author.ID);
}
const auto chan = discord.GetChannel(m_active_channel);
if (chan->GuildID.has_value()) {
const auto others = discord.GetUsersInGuild(*chan->GuildID);
for (const auto id : others)
if (std::find(ret.begin(), ret.end(), id) == ret.end())
ret.push_back(id);
}
return ret;
});
m_completer.show();
m_chat->signal_action_channel_click().connect([this](Snowflake id) {
m_signal_action_channel_click.emit(id);
});
m_chat->signal_action_chat_load_history().connect([this](Snowflake id) {
m_signal_action_chat_load_history.emit(id);
});
m_chat->signal_action_chat_submit().connect([this](const std::string &str, Snowflake channel_id, Snowflake referenced_id) {
m_signal_action_chat_submit.emit(str, channel_id, referenced_id);
});
m_chat->signal_action_insert_mention().connect([this](Snowflake id) {
// lowkey gross
m_signal_action_insert_mention.emit(id);
});
m_chat->signal_action_message_edit().connect([this](Snowflake channel_id, Snowflake message_id) {
m_signal_action_message_edit.emit(channel_id, message_id);
});
m_chat->signal_action_reaction_add().connect([this](Snowflake id, const Glib::ustring &param) {
m_signal_action_reaction_add.emit(id, param);
});
m_chat->signal_action_reaction_remove().connect([this](Snowflake id, const Glib::ustring &param) {
m_signal_action_reaction_remove.emit(id, param);
});
m_chat->signal_action_reply_to().connect([this](Snowflake id) {
StartReplying(id);
});
m_chat->show();
m_meta->set_hexpand(true);
m_meta->set_halign(Gtk::ALIGN_FILL);
m_meta->show();
m_meta->add(*m_input_indicator);
m_meta->add(*m_rate_limit_indicator);
//m_scroll->add(*m_list);
m_main->add(m_topic);
m_main->add(*m_chat);
m_scroll->add(*m_list);
m_main->add(*m_scroll);
m_main->add(m_completer);
m_main->add(*m_input);
m_main->add(*m_meta);
@@ -102,36 +117,63 @@ Gtk::Widget *ChatWindow::GetRoot() const {
}
void ChatWindow::Clear() {
m_chat->Clear();
SetMessages(std::set<Snowflake>());
}
void ChatWindow::SetMessages(const std::vector<Message> &msgs) {
m_chat->SetMessages(msgs.begin(), msgs.end());
void ChatWindow::SetMessages(const std::set<Snowflake> &msgs) {
// empty the listbox
auto children = m_list->get_children();
auto it = children.begin();
while (it != children.end()) {
delete *it;
it++;
}
m_num_rows = 0;
m_num_messages = 0;
m_id_to_widget.clear();
for (const auto &id : msgs) {
ProcessNewMessage(id, false);
}
}
void ChatWindow::SetActiveChannel(Snowflake id) {
m_active_channel = id;
m_chat->SetActiveChannel(id);
m_input_indicator->SetActiveChannel(id);
m_rate_limit_indicator->SetActiveChannel(id);
if (m_is_replying)
StopReplying();
}
void ChatWindow::AddNewMessage(const Message &data) {
m_chat->ProcessNewMessage(data, false);
void ChatWindow::AddNewMessage(Snowflake id) {
ProcessNewMessage(id, false);
}
void ChatWindow::DeleteMessage(Snowflake id) {
m_chat->DeleteMessage(id);
auto widget = m_id_to_widget.find(id);
if (widget == m_id_to_widget.end()) return;
auto *x = dynamic_cast<ChatMessageItemContainer *>(widget->second);
if (x != nullptr)
x->UpdateAttributes();
}
void ChatWindow::UpdateMessage(Snowflake id) {
m_chat->RefetchMessage(id);
auto widget = m_id_to_widget.find(id);
if (widget == m_id_to_widget.end()) return;
auto *x = dynamic_cast<ChatMessageItemContainer *>(widget->second);
if (x != nullptr) {
x->UpdateContent();
x->UpdateAttributes();
}
}
void ChatWindow::AddNewHistory(const std::vector<Message> &msgs) {
m_chat->PrependMessages(msgs.crbegin(), msgs.crend());
void ChatWindow::AddNewHistory(const std::vector<Snowflake> &id) {
std::set<Snowflake> ids(id.begin(), id.end());
for (auto it = ids.rbegin(); it != ids.rend(); it++)
ProcessNewMessage(*it, true);
}
void ChatWindow::InsertChatInput(std::string text) {
@@ -139,16 +181,15 @@ void ChatWindow::InsertChatInput(std::string text) {
}
Snowflake ChatWindow::GetOldestListedMessage() {
return m_chat->GetOldestListedMessage();
return m_id_to_widget.begin()->first;
}
void ChatWindow::UpdateReactions(Snowflake id) {
m_chat->UpdateMessageReactions(id);
}
void ChatWindow::SetTopic(const std::string &text) {
m_topic_text.set_text(text);
m_topic.set_visible(text.length() > 0);
auto it = m_id_to_widget.find(id);
if (it == m_id_to_widget.end()) return;
auto *widget = dynamic_cast<ChatMessageItemContainer *>(it->second);
if (widget == nullptr) return;
widget->UpdateReactions();
}
Snowflake ChatWindow::GetActiveChannel() const {
@@ -159,9 +200,6 @@ bool ChatWindow::OnInputSubmit(const Glib::ustring &text) {
if (!m_rate_limit_indicator->CanSpeak())
return false;
if (text.size() == 0)
return false;
if (m_active_channel.IsValid())
m_signal_action_chat_submit.emit(text, m_active_channel, m_replying_to); // m_replying_to is checked for invalid in the handler
if (m_is_replying)
@@ -180,6 +218,122 @@ bool ChatWindow::OnKeyPressEvent(GdkEventKey *e) {
return false;
}
ChatMessageItemContainer *ChatWindow::CreateMessageComponent(Snowflake id) {
auto *container = ChatMessageItemContainer::FromMessage(id);
return container;
}
void ChatWindow::RemoveMessageAndHeader(Gtk::Widget *widget) {
ChatMessageHeader *header = dynamic_cast<ChatMessageHeader *>(widget->get_ancestor(Gtk::ListBoxRow::get_type()));
if (header != nullptr) {
if (header->GetChildContent().size() == 1) {
m_num_rows--;
delete header;
} else
delete widget;
} else
delete widget;
m_num_messages--;
}
constexpr static int MaxMessagesForCull = 50; // this has to be 50 cuz that magic number is used in a couple other places and i dont feel like replacing them
void ChatWindow::ProcessNewMessage(Snowflake id, bool prepend) {
const auto &client = Abaddon::Get().GetDiscordClient();
if (!client.IsStarted()) return; // e.g. load channel and then dc
const auto data = client.GetMessage(id);
if (!data.has_value()) return;
if (!data->IsPending && data->Nonce.has_value() && data->Author.ID == client.GetUserData().ID) {
for (auto [id, widget] : m_id_to_widget) {
if (dynamic_cast<ChatMessageItemContainer *>(widget)->Nonce == *data->Nonce) {
RemoveMessageAndHeader(widget);
m_id_to_widget.erase(id);
break;
}
}
}
ChatMessageHeader *last_row = nullptr;
bool should_attach = false;
if (m_num_rows > 0) {
if (prepend)
last_row = dynamic_cast<ChatMessageHeader *>(m_list->get_row_at_index(0));
else
last_row = dynamic_cast<ChatMessageHeader *>(m_list->get_row_at_index(m_num_rows - 1));
if (last_row != nullptr) {
const uint64_t diff = std::max(id, last_row->NewestID) - std::min(id, last_row->NewestID);
if (last_row->UserID == data->Author.ID && (prepend || (diff < SnowflakeSplitDifference * Snowflake::SecondsInterval)))
should_attach = true;
}
}
m_num_messages++;
if (m_should_scroll_to_bottom && !prepend)
while (m_num_messages > MaxMessagesForCull) {
auto first_it = m_id_to_widget.begin();
RemoveMessageAndHeader(first_it->second);
m_id_to_widget.erase(first_it);
}
ChatMessageHeader *header;
if (should_attach) {
header = last_row;
} else {
const auto guild_id = *client.GetChannel(m_active_channel)->GuildID;
const auto user_id = data->Author.ID;
const auto user = client.GetUser(user_id);
if (!user.has_value()) return;
header = Gtk::manage(new ChatMessageHeader(&*data));
header->signal_action_insert_mention().connect([this, user_id]() {
m_signal_action_insert_mention.emit(user_id);
});
header->signal_action_open_user_menu().connect([this, user_id, guild_id](const GdkEvent *event) {
m_signal_action_open_user_menu.emit(event, user_id, guild_id);
});
m_num_rows++;
}
auto *content = CreateMessageComponent(id);
if (content != nullptr) {
header->AddContent(content, prepend);
m_id_to_widget[id] = content;
if (!data->IsPending) {
content->signal_action_delete().connect([this, id] {
m_signal_action_message_delete.emit(m_active_channel, id);
});
content->signal_action_edit().connect([this, id] {
m_signal_action_message_edit.emit(m_active_channel, id);
});
content->signal_action_reaction_add().connect([this, id](const Glib::ustring &param) {
m_signal_action_reaction_add.emit(id, param);
});
content->signal_action_reaction_remove().connect([this, id](const Glib::ustring &param) {
m_signal_action_reaction_remove.emit(id, param);
});
content->signal_action_channel_click().connect([this](const Snowflake &id) {
m_signal_action_channel_click.emit(id);
});
content->signal_action_reply_to().connect(sigc::mem_fun(*this, &ChatWindow::StartReplying));
}
}
header->set_margin_left(5);
header->show_all();
if (!should_attach) {
if (prepend)
m_list->prepend(*header);
else
m_list->add(*header);
}
}
void ChatWindow::StartReplying(Snowflake message_id) {
const auto &discord = Abaddon::Get().GetDiscordClient();
const auto message = *discord.GetMessage(message_id);
@@ -206,8 +360,22 @@ void ChatWindow::OnScrollEdgeOvershot(Gtk::PositionType pos) {
m_signal_action_chat_load_history.emit(m_active_channel);
}
void ChatWindow::ScrollToBottom() {
auto x = m_scroll->get_vadjustment();
x->set_value(x->get_upper());
}
void ChatWindow::OnMessageSendFail(const std::string &nonce, float retry_after) {
m_chat->SetFailedByNonce(nonce);
for (auto [id, widget] : m_id_to_widget) {
if (auto *container = dynamic_cast<ChatMessageItemContainer *>(widget); container->Nonce == nonce) {
container->SetFailed();
break;
}
}
}
ChatWindow::type_signal_action_message_delete ChatWindow::signal_action_message_delete() {
return m_signal_action_message_delete;
}
ChatWindow::type_signal_action_message_edit ChatWindow::signal_action_message_edit() {
@@ -230,6 +398,10 @@ ChatWindow::type_signal_action_insert_mention ChatWindow::signal_action_insert_m
return m_signal_action_insert_mention;
}
ChatWindow::type_signal_action_open_user_menu ChatWindow::signal_action_open_user_menu() {
return m_signal_action_open_user_menu;
}
ChatWindow::type_signal_action_reaction_add ChatWindow::signal_action_reaction_add() {
return m_signal_action_reaction_add;
}

View File

@@ -10,7 +10,6 @@ class ChatMessageItemContainer;
class ChatInput;
class ChatInputIndicator;
class RateLimitIndicator;
class ChatList;
class ChatWindow {
public:
ChatWindow();
@@ -19,24 +18,30 @@ public:
Snowflake GetActiveChannel() const;
void Clear();
void SetMessages(const std::vector<Message> &msgs); // clear contents and replace with given set
void SetMessages(const std::set<Snowflake> &msgs); // clear contents and replace with given set
void SetActiveChannel(Snowflake id);
void AddNewMessage(const Message &data); // append new message to bottom
void AddNewMessage(Snowflake id); // append new message to bottom
void DeleteMessage(Snowflake id); // add [deleted] indicator
void UpdateMessage(Snowflake id); // add [edited] indicator
void AddNewHistory(const std::vector<Message> &msgs); // prepend messages
void AddNewHistory(const std::vector<Snowflake> &id); // prepend messages
void InsertChatInput(std::string text);
Snowflake GetOldestListedMessage(); // oldest message that is currently in the ListBox
void UpdateReactions(Snowflake id);
void SetTopic(const std::string &text);
protected:
ChatMessageItemContainer *CreateMessageComponent(Snowflake id); // to be inserted into header's content box
void ProcessNewMessage(Snowflake id, bool prepend); // creates and adds components
bool m_is_replying = false;
Snowflake m_replying_to;
void StartReplying(Snowflake message_id);
void StopReplying();
int m_num_messages = 0;
int m_num_rows = 0;
std::map<Snowflake, Gtk::Widget *> m_id_to_widget;
Snowflake m_active_channel;
bool OnInputSubmit(const Glib::ustring &text);
@@ -44,16 +49,16 @@ protected:
bool OnKeyPressEvent(GdkEventKey *e);
void OnScrollEdgeOvershot(Gtk::PositionType pos);
void RemoveMessageAndHeader(Gtk::Widget *widget);
void ScrollToBottom();
bool m_should_scroll_to_bottom = true;
void OnMessageSendFail(const std::string &nonce, float retry_after);
Gtk::Box *m_main;
//Gtk::ListBox *m_list;
//Gtk::ScrolledWindow *m_scroll;
Gtk::EventBox m_topic; // todo probably make everything else go on the stack
Gtk::Label m_topic_text;
ChatList *m_chat;
Gtk::ListBox *m_list;
Gtk::ScrolledWindow *m_scroll;
ChatInput *m_input;
@@ -63,28 +68,34 @@ protected:
Gtk::Box *m_meta;
public:
typedef sigc::signal<void, Snowflake, Snowflake> type_signal_action_message_delete;
typedef sigc::signal<void, Snowflake, Snowflake> type_signal_action_message_edit;
typedef sigc::signal<void, std::string, Snowflake, Snowflake> type_signal_action_chat_submit;
typedef sigc::signal<void, Snowflake> type_signal_action_chat_load_history;
typedef sigc::signal<void, Snowflake> type_signal_action_channel_click;
typedef sigc::signal<void, Snowflake> type_signal_action_insert_mention;
typedef sigc::signal<void, const GdkEvent *, Snowflake, Snowflake> type_signal_action_open_user_menu;
typedef sigc::signal<void, Snowflake, Glib::ustring> type_signal_action_reaction_add;
typedef sigc::signal<void, Snowflake, Glib::ustring> type_signal_action_reaction_remove;
type_signal_action_message_delete signal_action_message_delete();
type_signal_action_message_edit signal_action_message_edit();
type_signal_action_chat_submit signal_action_chat_submit();
type_signal_action_chat_load_history signal_action_chat_load_history();
type_signal_action_channel_click signal_action_channel_click();
type_signal_action_insert_mention signal_action_insert_mention();
type_signal_action_open_user_menu signal_action_open_user_menu();
type_signal_action_reaction_add signal_action_reaction_add();
type_signal_action_reaction_remove signal_action_reaction_remove();
private:
type_signal_action_message_delete m_signal_action_message_delete;
type_signal_action_message_edit m_signal_action_message_edit;
type_signal_action_chat_submit m_signal_action_chat_submit;
type_signal_action_chat_load_history m_signal_action_chat_load_history;
type_signal_action_channel_click m_signal_action_channel_click;
type_signal_action_insert_mention m_signal_action_insert_mention;
type_signal_action_open_user_menu m_signal_action_open_user_menu;
type_signal_action_reaction_add m_signal_action_reaction_add;
type_signal_action_reaction_remove m_signal_action_reaction_remove;
};

View File

@@ -291,7 +291,7 @@ bool MultiBackwardSearch(const Gtk::TextIter &iter, const Glib::ustring &chars,
if (!iter.backward_search(tmp, flags, tstart, tend)) continue;
// if previous found, compare to see if closer to out iter
if (any) {
if (tstart.get_offset() > out.get_offset())
if (tstart > out)
out = tstart;
} else
out = tstart;
@@ -308,7 +308,7 @@ bool MultiForwardSearch(const Gtk::TextIter &iter, const Glib::ustring &chars, G
if (!iter.forward_search(tmp, flags, tstart, tend)) continue;
// if previous found, compare to see if closer to out iter
if (any) {
if (tstart.get_offset() < out.get_offset())
if (tstart < out)
out = tstart;
} else
out = tstart;

View File

@@ -1,354 +0,0 @@
#include "friendslist.hpp"
#include "../abaddon.hpp"
#include "lazyimage.hpp"
using namespace std::string_literals;
FriendsList::FriendsList()
: Gtk::Box(Gtk::ORIENTATION_VERTICAL)
, m_filter_mode(FILTER_FRIENDS) {
get_style_context()->add_class("friends-list");
auto &discord = Abaddon::Get().GetDiscordClient();
discord.signal_relationship_add().connect(sigc::mem_fun(*this, &FriendsList::OnRelationshipAdd));
discord.signal_relationship_remove().connect(sigc::mem_fun(*this, &FriendsList::OnRelationshipRemove));
PopulateRelationships();
signal_map().connect(sigc::mem_fun(*this, &FriendsList::PopulateRelationships));
constexpr static std::array<const char *, 4> strs = {
"Friends",
"Online",
"Pending",
"Blocked",
};
for (const auto &x : strs) {
auto *btn = Gtk::manage(new Gtk::RadioButton(m_group, x));
m_buttons.add(*btn);
btn->show();
btn->signal_toggled().connect([this, btn, str = x] {
if (!btn->get_active()) return;
switch (str[0]) { // hehe
case 'F':
m_filter_mode = FILTER_FRIENDS;
break;
case 'O':
m_filter_mode = FILTER_ONLINE;
break;
case 'P':
m_filter_mode = FILTER_PENDING;
break;
case 'B':
m_filter_mode = FILTER_BLOCKED;
break;
}
m_list.invalidate_filter();
});
}
m_buttons.set_homogeneous(true);
m_buttons.set_halign(Gtk::ALIGN_CENTER);
m_add.set_halign(Gtk::ALIGN_CENTER);
m_add.set_margin_top(5);
m_add.set_margin_bottom(5);
m_scroll.set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC);
m_list.set_sort_func(sigc::mem_fun(*this, &FriendsList::ListSortFunc));
m_list.set_filter_func(sigc::mem_fun(*this, &FriendsList::ListFilterFunc));
m_list.set_selection_mode(Gtk::SELECTION_NONE);
m_list.set_hexpand(true);
m_list.set_vexpand(true);
m_scroll.add(m_list);
add(m_add);
add(m_buttons);
add(m_scroll);
m_add.show();
m_scroll.show();
m_buttons.show();
m_list.show();
}
FriendsListFriendRow *FriendsList::MakeRow(const UserData &user, RelationshipType type) {
auto *row = Gtk::manage(new FriendsListFriendRow(type, user));
row->signal_action_remove().connect(sigc::bind(sigc::mem_fun(*this, &FriendsList::OnActionRemove), user.ID));
row->signal_action_accept().connect(sigc::bind(sigc::mem_fun(*this, &FriendsList::OnActionAccept), user.ID));
return row;
}
void FriendsList::OnRelationshipAdd(const RelationshipAddData &data) {
for (auto *row_ : m_list.get_children()) {
auto *row = dynamic_cast<FriendsListFriendRow *>(row_);
if (row == nullptr || row->ID != data.ID) continue;
delete row;
break;
}
auto *row = MakeRow(data.User, data.Type);
m_list.add(*row);
row->show();
}
void FriendsList::OnRelationshipRemove(Snowflake id, RelationshipType type) {
for (auto *row_ : m_list.get_children()) {
auto *row = dynamic_cast<FriendsListFriendRow *>(row_);
if (row == nullptr || row->ID != id) continue;
delete row;
return;
}
}
void FriendsList::OnActionAccept(Snowflake id) {
const auto cb = [this](DiscordError code) {
if (code != DiscordError::NONE) {
Gtk::MessageDialog dlg(*dynamic_cast<Gtk::Window *>(get_toplevel()), "Failed to accept", false, Gtk::MESSAGE_ERROR, Gtk::BUTTONS_OK, true);
dlg.set_position(Gtk::WIN_POS_CENTER);
dlg.run();
}
};
Abaddon::Get().GetDiscordClient().PutRelationship(id, sigc::track_obj(cb, *this));
}
void FriendsList::OnActionRemove(Snowflake id) {
auto &discord = Abaddon::Get().GetDiscordClient();
const auto user = discord.GetUser(id);
if (auto *window = dynamic_cast<Gtk::Window *>(get_toplevel())) {
Glib::ustring str;
switch (*discord.GetRelationship(id)) {
case RelationshipType::Blocked:
str = "Are you sure you want to unblock " + user->Username + "#" + user->Discriminator + "?";
break;
case RelationshipType::Friend:
str = "Are you sure you want to remove " + user->Username + "#" + user->Discriminator + "?";
break;
case RelationshipType::PendingIncoming:
str = "Are you sure you want to ignore " + user->Username + "#" + user->Discriminator + "?";
break;
case RelationshipType::PendingOutgoing:
str = "Are you sure you want to cancel your request to " + user->Username + "#" + user->Discriminator + "?";
break;
default:
break;
}
if (Abaddon::Get().ShowConfirm(str, window)) {
const auto cb = [this, window](DiscordError code) {
if (code == DiscordError::NONE) return;
Gtk::MessageDialog dlg(*window, "Failed to remove user", false, Gtk::MESSAGE_ERROR, Gtk::BUTTONS_OK, true);
dlg.set_position(Gtk::WIN_POS_CENTER);
dlg.run();
};
discord.RemoveRelationship(id, sigc::track_obj(cb, *this));
}
}
}
void FriendsList::PopulateRelationships() {
for (auto child : m_list.get_children())
delete child;
auto &discord = Abaddon::Get().GetDiscordClient();
for (const auto &[id, type] : discord.GetRelationships()) {
const auto user = discord.GetUser(id);
if (!user.has_value()) continue;
auto *row = MakeRow(*user, type);
m_list.add(*row);
row->show();
}
}
int FriendsList::ListSortFunc(Gtk::ListBoxRow *a_, Gtk::ListBoxRow *b_) {
auto *a = dynamic_cast<FriendsListFriendRow *>(a_);
auto *b = dynamic_cast<FriendsListFriendRow *>(b_);
if (a == nullptr || b == nullptr) return 0;
return a->Name.compare(b->Name);
}
bool FriendsList::ListFilterFunc(Gtk::ListBoxRow *row_) {
auto *row = dynamic_cast<FriendsListFriendRow *>(row_);
if (row == nullptr) return false;
switch (m_filter_mode) {
case FILTER_FRIENDS:
return row->Type == RelationshipType::Friend;
case FILTER_ONLINE:
return row->Type == RelationshipType::Friend && row->Status != PresenceStatus::Offline;
case FILTER_PENDING:
return row->Type == RelationshipType::PendingIncoming || row->Type == RelationshipType::PendingOutgoing;
case FILTER_BLOCKED:
return row->Type == RelationshipType::Blocked;
default:
return false;
}
}
FriendsListAddComponent::FriendsListAddComponent()
: Gtk::Box(Gtk::ORIENTATION_VERTICAL)
, m_label("Add a Friend", Gtk::ALIGN_START)
, m_box(Gtk::ORIENTATION_HORIZONTAL)
, m_add("Add")
, m_status("", Gtk::ALIGN_START) {
m_box.add(m_entry);
m_box.add(m_add);
m_box.add(m_status);
m_add.signal_clicked().connect(sigc::mem_fun(*this, &FriendsListAddComponent::Submit));
m_label.set_halign(Gtk::ALIGN_CENTER);
m_entry.set_placeholder_text("Enter a Username#1234");
m_entry.signal_key_press_event().connect(sigc::mem_fun(*this, &FriendsListAddComponent::OnKeyPress), false);
add(m_label);
add(m_box);
show_all_children();
}
void FriendsListAddComponent::Submit() {
if (m_requesting) return;
auto text = m_entry.get_text();
m_label.set_text("Invalid input"); // cheeky !!
m_entry.set_text("");
const auto hashpos = text.find("#");
if (hashpos == Glib::ustring::npos) return;
const auto username = text.substr(0, hashpos);
const auto discriminator = text.substr(hashpos + 1);
if (username.size() == 0 || discriminator.size() != 4) return;
if (discriminator.find_first_not_of("0123456789") != Glib::ustring::npos) return;
m_requesting = true;
m_label.set_text("Hang on...");
const auto cb = [this](DiscordError code) {
m_requesting = false;
if (code == DiscordError::NONE) {
m_label.set_text("Success!");
} else {
m_label.set_text("Failed: "s + GetDiscordErrorDisplayString(code));
}
};
Abaddon::Get().GetDiscordClient().SendFriendRequest(username, std::stoul(discriminator), sigc::track_obj(cb, *this));
}
bool FriendsListAddComponent::OnKeyPress(GdkEventKey *e) {
if (e->keyval == GDK_KEY_Return) {
Submit();
return true;
}
return false;
}
FriendsListFriendRow::FriendsListFriendRow(RelationshipType type, const UserData &data)
: Name(data.Username + "#" + data.Discriminator)
, Type(type)
, ID(data.ID)
, Status(Abaddon::Get().GetDiscordClient().GetUserStatus(data.ID))
, m_accept("Accept") {
auto *ev = Gtk::manage(new Gtk::EventBox);
auto *box = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL));
auto *img = Gtk::manage(new LazyImage(32, 32, true));
auto *namebox = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL));
auto *namelbl = Gtk::manage(new Gtk::Label("", Gtk::ALIGN_START));
m_status_lbl = Gtk::manage(new Gtk::Label("", Gtk::ALIGN_START));
auto *lblbox = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_VERTICAL));
auto &discord = Abaddon::Get().GetDiscordClient();
discord.signal_presence_update().connect(sigc::mem_fun(*this, &FriendsListFriendRow::OnPresenceUpdate));
static bool show_animations = Abaddon::Get().GetSettings().GetShowAnimations();
if (data.HasAnimatedAvatar() && show_animations) {
img->SetAnimated(true);
img->SetURL(data.GetAvatarURL("gif", "32"));
} else {
img->SetURL(data.GetAvatarURL("png", "32"));
}
namelbl->set_markup(data.GetEscapedBoldName());
UpdatePresenceLabel();
AddWidgetMenuHandler(ev, m_menu, [this] {
m_accept.set_visible(Type == RelationshipType::PendingIncoming);
switch (Type) {
case RelationshipType::Blocked:
case RelationshipType::Friend:
m_remove.set_label("Remove");
break;
case RelationshipType::PendingIncoming:
m_remove.set_label("Ignore");
break;
case RelationshipType::PendingOutgoing:
m_remove.set_label("Cancel");
break;
default:
break;
}
});
m_remove.signal_activate().connect([this] {
m_signal_remove.emit();
});
m_accept.signal_activate().connect([this] {
m_signal_accept.emit();
});
m_menu.append(m_accept);
m_menu.append(m_remove);
m_menu.show_all();
lblbox->set_valign(Gtk::ALIGN_CENTER);
img->set_margin_end(5);
namebox->add(*namelbl);
lblbox->add(*namebox);
lblbox->add(*m_status_lbl);
box->add(*img);
box->add(*lblbox);
ev->add(*box);
add(*ev);
show_all_children();
}
void FriendsListFriendRow::UpdatePresenceLabel() {
switch (Type) {
case RelationshipType::PendingIncoming:
m_status_lbl->set_text("Incoming Friend Request");
break;
case RelationshipType::PendingOutgoing:
m_status_lbl->set_text("Outgoing Friend Request");
break;
default:
m_status_lbl->set_text(GetPresenceDisplayString(Status));
break;
}
}
void FriendsListFriendRow::OnPresenceUpdate(const UserData &user, PresenceStatus status) {
if (user.ID != ID) return;
Status = status;
UpdatePresenceLabel();
changed();
}
FriendsListFriendRow::type_signal_remove FriendsListFriendRow::signal_action_remove() {
return m_signal_remove;
}
FriendsListFriendRow::type_signal_accept FriendsListFriendRow::signal_action_accept() {
return m_signal_accept;
}
FriendsListWindow::FriendsListWindow() {
add(m_friends);
set_default_size(500, 500);
get_style_context()->add_class("app-window");
get_style_context()->add_class("app-popup");
m_friends.show();
}

View File

@@ -1,92 +0,0 @@
#pragma once
#include <gtkmm.h>
#include "../discord/objects.hpp"
class FriendsListAddComponent : public Gtk::Box {
public:
FriendsListAddComponent();
private:
void Submit();
bool OnKeyPress(GdkEventKey *e);
Gtk::Label m_label;
Gtk::Label m_status;
Gtk::Entry m_entry;
Gtk::Button m_add;
Gtk::Box m_box;
bool m_requesting = false;
};
class FriendsListFriendRow;
class FriendsList : public Gtk::Box {
public:
FriendsList();
private:
FriendsListFriendRow *MakeRow(const UserData &user, RelationshipType type);
void OnRelationshipAdd(const RelationshipAddData &data);
void OnRelationshipRemove(Snowflake id, RelationshipType type);
void OnActionAccept(Snowflake id);
void OnActionRemove(Snowflake id);
void PopulateRelationships();
enum FilterMode {
FILTER_FRIENDS,
FILTER_ONLINE,
FILTER_PENDING,
FILTER_BLOCKED,
};
FilterMode m_filter_mode;
int ListSortFunc(Gtk::ListBoxRow *a, Gtk::ListBoxRow *b);
bool ListFilterFunc(Gtk::ListBoxRow *row);
FriendsListAddComponent m_add;
Gtk::RadioButtonGroup m_group;
Gtk::ButtonBox m_buttons;
Gtk::ScrolledWindow m_scroll;
Gtk::ListBox m_list;
};
class FriendsListFriendRow : public Gtk::ListBoxRow {
public:
FriendsListFriendRow(RelationshipType type, const UserData &str);
Snowflake ID;
RelationshipType Type;
Glib::ustring Name;
PresenceStatus Status;
private:
void UpdatePresenceLabel();
void OnPresenceUpdate(const UserData &user, PresenceStatus status);
Gtk::Label *m_status_lbl;
Gtk::Menu m_menu;
Gtk::MenuItem m_remove; // or cancel or ignore
Gtk::MenuItem m_accept; // incoming
using type_signal_remove = sigc::signal<void>;
using type_signal_accept = sigc::signal<void>;
type_signal_remove m_signal_remove;
type_signal_accept m_signal_accept;
public:
type_signal_remove signal_action_remove();
type_signal_accept signal_action_accept();
};
class FriendsListWindow : public Gtk::Window {
public:
FriendsListWindow();
private:
FriendsList m_friends;
};

View File

@@ -0,0 +1,8 @@
#pragma once
// for things that are used in stackswitchers to be able to be told when they're switched to
class INotifySwitched {
public:
virtual void on_switched_to() {};
};

View File

@@ -6,7 +6,7 @@
constexpr static const int MaxMemberListRows = 200;
MemberListUserRow::MemberListUserRow(const std::optional<GuildData> &guild, const UserData &data) {
MemberListUserRow::MemberListUserRow(const GuildData *guild, const UserData &data) {
ID = data.ID;
m_ev = Gtk::manage(new Gtk::EventBox);
m_box = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL));
@@ -15,10 +15,9 @@ MemberListUserRow::MemberListUserRow(const std::optional<GuildData> &guild, cons
m_status_indicator = Gtk::manage(new StatusIndicator(ID));
static bool crown = Abaddon::Get().GetSettings().GetShowOwnerCrown();
if (crown && guild.has_value() && guild->OwnerID == data.ID) {
if (crown && guild != nullptr && guild->OwnerID == data.ID) {
try {
const static auto crown_path = Abaddon::GetResPath("/crown.png");
auto pixbuf = Gdk::Pixbuf::create_from_file(crown_path, 12, 12);
auto pixbuf = Gdk::Pixbuf::create_from_file("./res/crown.png", 12, 12);
m_crown = Gtk::manage(new Gtk::Image(pixbuf));
m_crown->set_valign(Gtk::ALIGN_CENTER);
m_crown->set_margin_end(8);
@@ -27,10 +26,7 @@ MemberListUserRow::MemberListUserRow(const std::optional<GuildData> &guild, cons
m_status_indicator->set_margin_start(3);
if (guild.has_value())
m_avatar->SetURL(data.GetAvatarURL(guild->ID, "png"));
else
m_avatar->SetURL(data.GetAvatarURL("png"));
m_avatar->SetURL(data.GetAvatarURL("png"));
get_style_context()->add_class("members-row");
get_style_context()->add_class("members-row-member");
@@ -44,7 +40,7 @@ MemberListUserRow::MemberListUserRow(const std::optional<GuildData> &guild, cons
std::string display = data.Username;
if (show_discriminator)
display += "#" + data.Discriminator;
if (guild.has_value()) {
if (guild != nullptr) {
if (const auto col_id = data.GetHoistedRole(guild->ID, true); col_id.IsValid()) {
auto color = Abaddon::Get().GetDiscordClient().GetRole(col_id)->Color;
m_label->set_use_markup(true);
@@ -118,7 +114,7 @@ void MemberList::UpdateMemberList() {
int num_rows = 0;
for (const auto &user : chan->GetDMRecipients()) {
if (num_rows++ > MaxMemberListRows) break;
auto *row = Gtk::manage(new MemberListUserRow(std::nullopt, user));
auto *row = Gtk::manage(new MemberListUserRow(nullptr, user));
m_id_to_row[user.ID] = row;
AttachUserMenuHandler(row, user.ID);
m_listbox->add(*row);
@@ -127,12 +123,7 @@ void MemberList::UpdateMemberList() {
return;
}
std::set<Snowflake> ids;
if (chan->IsThread()) {
const auto x = discord.GetUsersInThread(m_chan_id);
ids = { x.begin(), x.end() };
} else
ids = discord.GetUsersInGuild(m_guild_id);
auto ids = discord.GetUsersInGuild(m_guild_id);
// process all the shit first so its in proper order
std::map<int, RoleData> pos_to_role;
@@ -165,7 +156,7 @@ void MemberList::UpdateMemberList() {
const auto guild = *discord.GetGuild(m_guild_id);
auto add_user = [this, &user_to_color, &num_rows, guild](const UserData &data) -> bool {
if (num_rows++ > MaxMemberListRows) return false;
auto *row = Gtk::manage(new MemberListUserRow(guild, data));
auto *row = Gtk::manage(new MemberListUserRow(&guild, data));
m_id_to_row[data.ID] = row;
AttachUserMenuHandler(row, data.ID);
m_listbox->add(*row);
@@ -219,10 +210,14 @@ void MemberList::UpdateMemberList() {
void MemberList::AttachUserMenuHandler(Gtk::ListBoxRow *row, Snowflake id) {
row->signal_button_press_event().connect([this, row, id](GdkEventButton *e) -> bool {
if (e->type == GDK_BUTTON_PRESS && e->button == GDK_BUTTON_SECONDARY) {
Abaddon::Get().ShowUserMenu(reinterpret_cast<const GdkEvent *>(e), id, m_guild_id);
m_signal_action_show_user_menu.emit(reinterpret_cast<const GdkEvent *>(e), id, m_guild_id);
return true;
}
return false;
});
}
MemberList::type_signal_action_show_user_menu MemberList::signal_action_show_user_menu() {
return m_signal_action_show_user_menu;
}

View File

@@ -9,7 +9,7 @@ class LazyImage;
class StatusIndicator;
class MemberListUserRow : public Gtk::ListBoxRow {
public:
MemberListUserRow(const std::optional<GuildData> &guild, const UserData &data);
MemberListUserRow(const GuildData *guild, const UserData &data);
Snowflake ID;
@@ -41,4 +41,12 @@ private:
Snowflake m_chan_id;
std::unordered_map<Snowflake, Gtk::ListBoxRow *> m_id_to_row;
public:
typedef sigc::signal<void, const GdkEvent *, Snowflake, Snowflake> type_signal_action_show_user_menu;
type_signal_action_show_user_menu signal_action_show_user_menu();
private:
type_signal_action_show_user_menu m_signal_action_show_user_menu;
};

View File

@@ -15,10 +15,9 @@ RateLimitIndicator::RateLimitIndicator()
add(m_img);
m_label.show();
const static auto clock_path = Abaddon::GetResPath("/clock.png");
if (std::filesystem::exists(clock_path)) {
if (std::filesystem::exists("./res/clock.png")) {
try {
const auto pixbuf = Gdk::Pixbuf::create_from_file(clock_path);
const auto pixbuf = Gdk::Pixbuf::create_from_file("./res/clock.png");
int w, h;
GetImageDimensions(pixbuf->get_width(), pixbuf->get_height(), w, h, 20, 10);
m_img.property_pixbuf() = pixbuf->scale_simple(w, h, Gdk::INTERP_BILINEAR);
@@ -32,9 +31,9 @@ RateLimitIndicator::RateLimitIndicator()
void RateLimitIndicator::SetActiveChannel(Snowflake id) {
m_active_channel = id;
const auto channel = Abaddon::Get().GetDiscordClient().GetChannel(m_active_channel);
if (channel.has_value() && channel->RateLimitPerUser.has_value())
m_rate_limit = *channel->RateLimitPerUser;
const auto channel = *Abaddon::Get().GetDiscordClient().GetChannel(m_active_channel);
if (channel.RateLimitPerUser.has_value())
m_rate_limit = *channel.RateLimitPerUser;
else
m_rate_limit = 0;
@@ -124,10 +123,7 @@ void RateLimitIndicator::OnMessageSendFail(const std::string &nonce, float retry
}
void RateLimitIndicator::OnChannelUpdate(Snowflake channel_id) {
if (channel_id != m_active_channel) return;
const auto chan = Abaddon::Get().GetDiscordClient().GetChannel(m_active_channel);
if (!chan.has_value()) return;
const auto r = chan->RateLimitPerUser;
const auto r = Abaddon::Get().GetDiscordClient().GetChannel(channel_id)->RateLimitPerUser;
if (r.has_value())
m_rate_limit = *r;
else

View File

@@ -18,8 +18,8 @@ StatusIndicator::StatusIndicator(Snowflake user_id)
get_style_context()->add_class("status-indicator");
Abaddon::Get().GetDiscordClient().signal_guild_member_list_update().connect(sigc::hide(sigc::mem_fun(*this, &StatusIndicator::CheckStatus)));
auto cb = [this](const UserData &user, PresenceStatus status) {
if (user.ID == m_id) CheckStatus();
auto cb = [this](Snowflake id, PresenceStatus status) {
if (id == m_id) CheckStatus();
};
Abaddon::Get().GetDiscordClient().signal_presence_update().connect(sigc::track_obj(cb, *this));

View File

@@ -1 +0,0 @@
#define ABADDON_DEFAULT_RESOURCE_DIR "@ABADDON_RESOURCE_DIR@"

View File

@@ -1,4 +0,0 @@
#include <cstdint>
constexpr static uint64_t SnowflakeSplitDifference = 600;
constexpr static int MaxMessagesForChatCull = 50; // this has to be 50 (for now) cuz that magic number is used in a couple other places and i dont feel like replacing them

View File

@@ -1,89 +0,0 @@
/*
application wide stuff
has to be separate to allow main.css to override certain things
*/
.app-window label:not(:disabled) {
color: @text_color;
}
.app-window entry {
background: @secondary_color;
color: @text_color;
border: 1px solid #1c2e40;
}
.app-window button {
background: @secondary_color;
color: @text_color;
text-shadow: none;
box-shadow: none;
}
.app-window button:checked {
border-top: 0px;
border-left: 0px;
border-right: 0px;
border-bottom: 3px solid #39a2ed;
color: #ffffff;
}
.app-window button:not(:checked) {
border: 3px #0000ff;
}
.app-window.background {
background: @background_color;
}
.app-window treeview {
color: @text_color;
}
.app-window treeview:not(:selected) {
background: @secondary_color;
}
.app-popup list {
background: @secondary_color;
}
.app-window paned separator {
background: @background_color;
}
.app-window scrollbar {
background: @background_color;
border-left: 1px solid transparent;
}
.app-window menubar, menu {
background: @background_color;
color: #cccccc;
}
.app-window textview text {
caret-color: #ababab;
}
.app-window check,
.app-window radio {
background-clip: padding-box;
background: @secondary_color;
border-color: #070707;
box-shadow: 0 1px rgba(0, 0, 0, 0);
color: #dddddd;
}
.app-window check:checked,
.app-window radio:checked {
background-clip: border-box;
background: #0b4285;
border-color: #092444;
box-shadow: 0 1px rgba(0, 0, 0, 0);
color: #dddddd;
}
.app-window colorswatch {
box-shadow: 0 1px rgba(0, 0, 0, 0);
}

View File

@@ -57,8 +57,14 @@
padding: 15px;
}
.message-container + .message-container {
margin-top: 10px;
}
.message-container-extra {
color: #78909c;
margin-left: -5px;
margin-right: -5px;
}
.message-container-timestamp {
@@ -66,8 +72,7 @@
}
.message-text {
/* this isnt stricly necessary but it fixes emoji clipping */
padding-bottom: 5px;
padding-top: 5px;
}
.message-text:not(.failed) text, .message-reply {
@@ -136,26 +141,6 @@
margin: 5px;
}
.message-component {
margin: 5px;
}
.message-component.primary {
background: #5865F2;
}
.message-component.secondary, .message-component.link {
background: #4F545C;
}
.message-component.success {
background: #43B581;
}
.message-component.danger {
background: #F04747;
}
.reaction-box {
padding: 2px 5px 2px 5px;
margin: 0px 0px 0px 0px;
@@ -191,6 +176,62 @@
color: @text_color;
}
.app-window label:not(:disabled) {
color: @text_color;
}
.app-window entry {
background: @secondary_color;
color: @text_color;
border: 1px solid #1c2e40;
}
.app-window button {
background: @secondary_color;
color: @text_color;
text-shadow: none;
box-shadow: none;
}
.app-window button:checked {
border-top: 0px;
border-left: 0px;
border-right: 0px;
border-bottom: 3px solid #39a2ed;
color: #ffffff;
}
.app-window button:not(:checked) {
border: 3px #0000ff;
}
.app-window.background {
background: @background_color;
}
.app-window treeview {
color: @text_color;
background: @secondary_color;
}
.app-popup list {
background: @secondary_color;
}
.app-window paned separator {
background: @background_color;
}
.app-window scrollbar {
background: @background_color;
border-left: 1px solid transparent;
}
.app-window menubar, menu {
background: @background_color;
color: #cccccc;
}
.status-indicator.dnd {
color: #982929;
}
@@ -262,10 +303,37 @@
padding-left: 5px;
}
.app-window textview text {
caret-color: #ababab;
}
.guild-members-pane-info {
padding: 10px;
}
.app-window check,
.app-window radio {
background-clip: padding-box;
background: @secondary_color;
border-color: #070707;
box-shadow: 0 1px rgba(0, 0, 0, 0);
color: #dddddd;
}
.app-window check:checked,
.app-window radio:checked {
background-clip: border-box;
background: #0b4285;
border-color: #092444;
box-shadow: 0 1px rgba(0, 0, 0, 0);
color: #dddddd;
}
.app-window colorswatch {
box-shadow: 0 1px rgba(0, 0, 0, 0);
}
.drag-hover-top {
background: linear-gradient(to bottom, rgba(255, 66, 66, 0.65) 0%, rgba(0, 0, 0, 0) 35%);
}
@@ -273,12 +341,3 @@
.drag-hover-bottom {
background: linear-gradient(to bottom, rgba(0, 0, 0, 0) 65%, rgba(255, 66, 66, 0.65) 100%);
}
.friends-list list {
background: @background_color;
padding-left: 10px;
}
.friends-list-row-bot {
color: #ff0000;
}

View File

@@ -1,12 +1,5 @@
#include "token.hpp"
std::string trim(const std::string& str) {
const auto first = str.find_first_not_of(' ');
if (first == std::string::npos) return str;
const auto last = str.find_last_not_of(' ');
return str.substr(first, last - first + 1);
}
TokenDialog::TokenDialog(Gtk::Window &parent)
: Gtk::Dialog("Set Token", parent, true)
, m_layout(Gtk::ORIENTATION_VERTICAL)
@@ -18,7 +11,7 @@ TokenDialog::TokenDialog(Gtk::Window &parent)
get_style_context()->add_class("app-popup");
m_ok.signal_clicked().connect([&]() {
m_token = trim(m_entry.get_text());
m_token = m_entry.get_text();
response(Gtk::RESPONSE_OK);
});

View File

@@ -26,20 +26,6 @@ constexpr inline const char *GetPresenceString(PresenceStatus s) {
return "";
}
constexpr inline const char* GetPresenceDisplayString(PresenceStatus s) {
switch (s) {
case PresenceStatus::Online:
return "Online";
case PresenceStatus::Offline:
return "Offline";
case PresenceStatus::Idle:
return "Away";
case PresenceStatus::DND:
return "Do Not Disturb";
}
return "";
}
enum class ActivityType : int {
Game = 0,
Streaming = 1,

View File

@@ -20,7 +20,7 @@ void from_json(const nlohmann::json &j, AuditLogOptions &m) {
void from_json(const nlohmann::json &j, AuditLogEntry &m) {
JS_N("target_id", m.TargetID);
JS_O("changes", m.Changes);
JS_N("user_id", m.UserID);
JS_D("user_id", m.UserID);
JS_D("id", m.ID);
JS_D("action_type", m.Type);
JS_O("options", m.Options);

View File

@@ -40,15 +40,6 @@ enum class AuditLogActionType {
INTEGRATION_CREATE = 80,
INTEGRATION_UPDATE = 81,
INTEGRATION_DELETE = 82,
STAGE_INSTANCE_CREATE = 83,
STAGE_INSTANCE_UPDATE = 84,
STAGE_INSTANCE_DELETE = 85,
STICKER_CREATE = 90,
STICKER_UPDATE = 91,
STICKER_DELETE = 92,
THREAD_CREATE = 110,
THREAD_UPDATE = 111,
THREAD_DELETE = 112,
};
struct AuditLogChange {
@@ -75,7 +66,7 @@ struct AuditLogOptions {
struct AuditLogEntry {
Snowflake ID;
std::string TargetID; // null
std::optional<Snowflake> UserID;
Snowflake UserID;
AuditLogActionType Type;
std::optional<std::string> Reason;
std::optional<std::vector<AuditLogChange>> Changes;

View File

@@ -1,20 +1,6 @@
#include "../abaddon.hpp"
#include "channel.hpp"
void from_json(const nlohmann::json &j, ThreadMetadataData &m) {
JS_D("archived", m.IsArchived);
JS_D("auto_archive_duration", m.AutoArchiveDuration);
JS_D("archive_timestamp", m.ArchiveTimestamp);
JS_O("locked", m.IsLocked);
}
void from_json(const nlohmann::json &j, ThreadMemberObject &m) {
JS_O("id", m.ThreadID);
JS_O("user_id", m.UserID);
JS_D("join_timestamp", m.JoinTimestamp);
JS_D("flags", m.Flags);
}
void from_json(const nlohmann::json &j, ChannelData &m) {
JS_D("id", m.ID);
JS_D("type", m.Type);
@@ -35,8 +21,6 @@ void from_json(const nlohmann::json &j, ChannelData &m) {
JS_O("application_id", m.ApplicationID);
JS_ON("parent_id", m.ParentID);
JS_ON("last_pin_timestamp", m.LastPinTimestamp);
JS_O("thread_metadata", m.ThreadMetadata);
JS_O("member", m.ThreadMember);
}
void ChannelData::update_from_json(const nlohmann::json &j) {
@@ -59,20 +43,6 @@ void ChannelData::update_from_json(const nlohmann::json &j) {
JS_RD("last_pin_timestamp", LastPinTimestamp);
}
bool ChannelData::NSFW() const {
return IsNSFW.has_value() && *IsNSFW;
}
bool ChannelData::IsThread() const noexcept {
return Type == ChannelType::GUILD_PUBLIC_THREAD ||
Type == ChannelType::GUILD_PRIVATE_THREAD ||
Type == ChannelType::GUILD_NEWS_THREAD;
}
bool ChannelData::IsJoinedThread() const {
return Abaddon::Get().GetDiscordClient().IsThreadJoined(ID);
}
std::optional<PermissionOverwrite> ChannelData::GetOverwrite(Snowflake id) const {
return Abaddon::Get().GetDiscordClient().GetPermissionOverwrite(ID, id);
}

View File

@@ -15,49 +15,12 @@ enum class ChannelType : int {
GUILD_NEWS = 5,
GUILD_STORE = 6,
/* 7 and 8 were used for LFG */
/* 9 was used for threads */
GUILD_NEWS_THREAD = 10,
GUILD_PUBLIC_THREAD = 11,
GUILD_PRIVATE_THREAD = 12,
/* 9 and 10 were used for threads */
PUBLIC_THREAD = 11,
PRIVATE_THREAD = 12,
GUILD_STAGE_VOICE = 13,
};
enum class StagePrivacy {
PUBLIC = 1,
GUILD_ONLY = 2,
};
constexpr const char *GetStagePrivacyDisplayString(StagePrivacy e) {
switch (e) {
case StagePrivacy::PUBLIC:
return "Public";
case StagePrivacy::GUILD_ONLY:
return "Guild Only";
default:
return "Unknown";
}
}
// should be moved somewhere?
struct ThreadMetadataData {
bool IsArchived;
int AutoArchiveDuration;
std::string ArchiveTimestamp;
std::optional<bool> IsLocked;
friend void from_json(const nlohmann::json &j, ThreadMetadataData &m);
};
struct ThreadMemberObject {
std::optional<Snowflake> ThreadID;
std::optional<Snowflake> UserID;
std::string JoinTimestamp;
int Flags;
friend void from_json(const nlohmann::json &j, ThreadMemberObject &m);
};
struct ChannelData {
Snowflake ID;
ChannelType Type;
@@ -78,15 +41,10 @@ struct ChannelData {
std::optional<Snowflake> ApplicationID;
std::optional<Snowflake> ParentID; // null
std::optional<std::string> LastPinTimestamp; // null
std::optional<ThreadMetadataData> ThreadMetadata;
std::optional<ThreadMemberObject> ThreadMember;
friend void from_json(const nlohmann::json &j, ChannelData &m);
void update_from_json(const nlohmann::json &j);
bool NSFW() const;
bool IsThread() const noexcept;
bool IsJoinedThread() const;
std::optional<PermissionOverwrite> GetOverwrite(Snowflake id) const;
std::vector<UserData> GetDMRecipients() const;
};

File diff suppressed because it is too large Load Diff

View File

@@ -6,17 +6,20 @@
#include <sigc++/sigc++.h>
#include <nlohmann/json.hpp>
#include <thread>
#include <map>
#include <unordered_map>
#include <set>
#include <unordered_set>
#include <mutex>
#include <zlib.h>
#include <glibmm.h>
#include <queue>
// bruh
#ifdef GetMessage
#undef GetMessage
#endif
// https://stackoverflow.com/questions/29775153/stopping-long-sleep-threads/29775639#29775639
class HeartbeatWaiter {
public:
template<class R, class P>
@@ -46,6 +49,10 @@ class Abaddon;
class DiscordClient {
friend class Abaddon;
public:
static const constexpr char *DiscordGateway = "wss://gateway.discord.gg/?v=9&encoding=json&compress=zlib-stream";
static const constexpr char *DiscordAPI = "https://discord.com/api/v9";
public:
DiscordClient(bool mem_store = false);
void Start();
@@ -65,14 +72,13 @@ public:
const UserData &GetUserData() const;
const UserSettings &GetUserSettings() const;
std::vector<Snowflake> GetUserSortedGuilds() const;
std::vector<Message> GetMessagesForChannel(Snowflake id, size_t limit = 50) const;
std::vector<Snowflake> GetMessageIDsForChannel(Snowflake id) const;
std::set<Snowflake> GetMessagesForChannel(Snowflake id) const;
std::set<Snowflake> GetPrivateChannels() const;
EPremiumType GetSelfPremiumType() const;
void FetchMessagesInChannel(Snowflake id, sigc::slot<void(const std::vector<Message> &)> cb);
void FetchMessagesInChannelBefore(Snowflake channel_id, Snowflake before_id, sigc::slot<void(const std::vector<Message> &)> cb);
void FetchMessagesInChannel(Snowflake id, std::function<void(const std::vector<Snowflake> &)> cb);
void FetchMessagesInChannelBefore(Snowflake channel_id, Snowflake before_id, std::function<void(const std::vector<Snowflake> &)> cb);
std::optional<Message> GetMessage(Snowflake id) const;
std::optional<ChannelData> GetChannel(Snowflake id) const;
std::optional<EmojiData> GetEmoji(Snowflake id) const;
@@ -84,13 +90,9 @@ public:
std::optional<BanData> GetBan(Snowflake guild_id, Snowflake user_id) const;
Snowflake GetMemberHoistedRole(Snowflake guild_id, Snowflake user_id, bool with_color = false) const;
std::optional<RoleData> GetMemberHighestRole(Snowflake guild_id, Snowflake user_id) const;
std::set<Snowflake> GetUsersInGuild(Snowflake id) const;
std::set<Snowflake> GetChannelsInGuild(Snowflake id) const;
std::vector<Snowflake> GetUsersInThread(Snowflake id) const;
std::vector<ChannelData> GetActiveThreads(Snowflake channel_id) const;
void GetArchivedPublicThreads(Snowflake channel_id, sigc::slot<void(DiscordError, const ArchivedThreadsResponseData &)> callback);
std::unordered_set<Snowflake> GetUsersInGuild(Snowflake id) const;
std::unordered_set<Snowflake> GetChannelsInGuild(Snowflake id) const;
bool IsThreadJoined(Snowflake thread_id) const;
bool HasGuildPermission(Snowflake user_id, Snowflake guild_id, Permission perm) const;
bool HasAnyChannelPermission(Snowflake user_id, Snowflake channel_id, Permission perm) const;
@@ -106,7 +108,6 @@ public:
void DeleteMessage(Snowflake channel_id, Snowflake id);
void EditMessage(Snowflake channel_id, Snowflake id, std::string content);
void SendLazyLoad(Snowflake id);
void SendThreadLazyLoad(Snowflake id);
void JoinGuild(std::string code);
void LeaveGuild(Snowflake id);
void KickUser(Snowflake user_id, Snowflake guild_id);
@@ -114,51 +115,40 @@ public:
void UpdateStatus(PresenceStatus status, bool is_afk);
void UpdateStatus(PresenceStatus status, bool is_afk, const ActivityData &obj);
void CreateDM(Snowflake user_id);
void CreateDM(Snowflake user_id, sigc::slot<void(DiscordError code, Snowflake channel_id)> callback);
void CreateDM(Snowflake user_id, sigc::slot<void(bool success, Snowflake channel_id)> callback);
void CloseDM(Snowflake channel_id);
std::optional<Snowflake> FindDM(Snowflake user_id); // wont find group dms
void AddReaction(Snowflake id, Glib::ustring param);
void RemoveReaction(Snowflake id, Glib::ustring param);
void SetGuildName(Snowflake id, const Glib::ustring &name);
void SetGuildName(Snowflake id, const Glib::ustring &name, sigc::slot<void(DiscordError code)> callback);
void SetGuildName(Snowflake id, const Glib::ustring &name, sigc::slot<void(bool success)> callback);
void SetGuildIcon(Snowflake id, const std::string &data);
void SetGuildIcon(Snowflake id, const std::string &data, sigc::slot<void(DiscordError code)> callback);
void SetGuildIcon(Snowflake id, const std::string &data, sigc::slot<void(bool success)> callback);
void UnbanUser(Snowflake guild_id, Snowflake user_id);
void UnbanUser(Snowflake guild_id, Snowflake user_id, sigc::slot<void(DiscordError code)> callback);
void UnbanUser(Snowflake guild_id, Snowflake user_id, sigc::slot<void(bool success)> callback);
void DeleteInvite(const std::string &code);
void DeleteInvite(const std::string &code, sigc::slot<void(DiscordError code)> callback);
void DeleteInvite(const std::string &code, sigc::slot<void(bool success)> callback);
void AddGroupDMRecipient(Snowflake channel_id, Snowflake user_id);
void RemoveGroupDMRecipient(Snowflake channel_id, Snowflake user_id);
void ModifyRolePermissions(Snowflake guild_id, Snowflake role_id, Permission permissions, sigc::slot<void(DiscordError code)> callback);
void ModifyRoleName(Snowflake guild_id, Snowflake role_id, const Glib::ustring &name, sigc::slot<void(DiscordError code)> callback);
void ModifyRoleColor(Snowflake guild_id, Snowflake role_id, uint32_t color, sigc::slot<void(DiscordError code)> callback);
void ModifyRoleColor(Snowflake guild_id, Snowflake role_id, Gdk::RGBA color, sigc::slot<void(DiscordError code)> callback);
void ModifyRolePosition(Snowflake guild_id, Snowflake role_id, int position, sigc::slot<void(DiscordError code)> callback);
void ModifyEmojiName(Snowflake guild_id, Snowflake emoji_id, const Glib::ustring &name, sigc::slot<void(DiscordError code)> callback);
void DeleteEmoji(Snowflake guild_id, Snowflake emoji_id, sigc::slot<void(DiscordError code)> callback);
void ModifyRolePermissions(Snowflake guild_id, Snowflake role_id, Permission permissions, sigc::slot<void(bool success)> callback);
void ModifyRoleName(Snowflake guild_id, Snowflake role_id, const Glib::ustring &name, sigc::slot<void(bool success)> callback);
void ModifyRoleColor(Snowflake guild_id, Snowflake role_id, uint32_t color, sigc::slot<void(bool success)> callback);
void ModifyRoleColor(Snowflake guild_id, Snowflake role_id, Gdk::RGBA color, sigc::slot<void(bool success)> callback);
void ModifyRolePosition(Snowflake guild_id, Snowflake role_id, int position, sigc::slot<void(bool success)> callback);
void ModifyEmojiName(Snowflake guild_id, Snowflake emoji_id, const Glib::ustring &name, sigc::slot<void(bool success)> callback);
void DeleteEmoji(Snowflake guild_id, Snowflake emoji_id, sigc::slot<void(bool success)> callback);
std::optional<GuildApplicationData> GetGuildApplication(Snowflake guild_id) const;
void RemoveRelationship(Snowflake id, sigc::slot<void(DiscordError code)> callback);
void SendFriendRequest(const Glib::ustring &username, int discriminator, sigc::slot<void(DiscordError code)> callback);
void PutRelationship(Snowflake id, sigc::slot<void(DiscordError code)> callback); // send fr by id, accept incoming
void Pin(Snowflake channel_id, Snowflake message_id, sigc::slot<void(DiscordError code)> callback);
void Unpin(Snowflake channel_id, Snowflake message_id, sigc::slot<void(DiscordError code)> callback);
void LeaveThread(Snowflake channel_id, const std::string &location, sigc::slot<void(DiscordError code)> callback);
void ArchiveThread(Snowflake channel_id, sigc::slot<void(DiscordError code)> callback);
void UnArchiveThread(Snowflake channel_id, sigc::slot<void(DiscordError code)> callback);
bool CanModifyRole(Snowflake guild_id, Snowflake role_id) const;
bool CanModifyRole(Snowflake guild_id, Snowflake role_id, Snowflake user_id) const;
// real client doesn't seem to use the single role endpoints so neither do we
template<typename Iter>
auto SetMemberRoles(Snowflake guild_id, Snowflake user_id, Iter begin, Iter end, sigc::slot<void(DiscordError code)> callback) {
auto SetMemberRoles(Snowflake guild_id, Snowflake user_id, Iter begin, Iter end, sigc::slot<void(bool success)> callback) {
ModifyGuildMemberObject obj;
obj.Roles = { begin, end };
m_http.MakePATCH("/guilds/" + std::to_string(guild_id) + "/members/" + std::to_string(user_id), nlohmann::json(obj).dump(), [this, callback](const http::response_type &response) {
if (CheckCode(response))
callback(DiscordError::NONE);
else
callback(GetCodeFromResponse(response));
callback(CheckCode(response, 200));
});
}
@@ -177,23 +167,18 @@ public:
void FetchUserProfile(Snowflake user_id, sigc::slot<void(UserProfileData)> callback);
void FetchUserNote(Snowflake user_id, sigc::slot<void(std::string note)> callback);
void SetUserNote(Snowflake user_id, std::string note);
void SetUserNote(Snowflake user_id, std::string note, sigc::slot<void(DiscordError code)> callback);
void SetUserNote(Snowflake user_id, std::string note, sigc::slot<void(bool success)> callback);
void FetchUserRelationships(Snowflake user_id, sigc::slot<void(std::vector<UserData>)> callback);
void FetchPinned(Snowflake id, sigc::slot<void(std::vector<Message>, DiscordError code)> callback);
bool IsVerificationRequired(Snowflake guild_id);
void GetVerificationGateInfo(Snowflake guild_id, sigc::slot<void(std::optional<VerificationGateInfoObject>)> callback);
void AcceptVerificationGate(Snowflake guild_id, VerificationGateInfoObject info, sigc::slot<void(DiscordError code)> callback);
void AcceptVerificationGate(Snowflake guild_id, VerificationGateInfoObject info, sigc::slot<void(bool success)> callback);
void UpdateToken(std::string token);
void SetUserAgent(std::string agent);
PresenceStatus GetUserStatus(Snowflake id) const;
std::map<Snowflake, RelationshipType> GetRelationships() const;
std::set<Snowflake> GetRelationships(RelationshipType type) const;
std::optional<RelationshipType> GetRelationship(Snowflake id) const;
std::unordered_set<Snowflake> GetRelationships(RelationshipType type) const;
private:
static const constexpr int InflateChunkSize = 0x10000;
@@ -201,11 +186,6 @@ private:
std::vector<uint8_t> m_decompress_buf;
z_stream m_zstream;
std::string GetAPIURL();
std::string GetGatewayURL();
static DiscordError GetCodeFromResponse(const http::response_type &response);
void ProcessNewGuild(GuildData &guild);
void HandleGatewayMessageRaw(std::string str);
@@ -242,15 +222,6 @@ private:
void HandleGatewayGuildJoinRequestCreate(const GatewayMessage &msg);
void HandleGatewayGuildJoinRequestUpdate(const GatewayMessage &msg);
void HandleGatewayGuildJoinRequestDelete(const GatewayMessage &msg);
void HandleGatewayRelationshipRemove(const GatewayMessage &msg);
void HandleGatewayRelationshipAdd(const GatewayMessage &msg);
void HandleGatewayThreadCreate(const GatewayMessage &msg);
void HandleGatewayThreadDelete(const GatewayMessage &msg);
void HandleGatewayThreadListSync(const GatewayMessage &msg);
void HandleGatewayThreadMembersUpdate(const GatewayMessage &msg);
void HandleGatewayThreadMemberUpdate(const GatewayMessage &msg);
void HandleGatewayThreadUpdate(const GatewayMessage &msg);
void HandleGatewayThreadMemberListUpdate(const GatewayMessage &msg);
void HandleGatewayReadySupplemental(const GatewayMessage &msg);
void HandleGatewayReconnect(const GatewayMessage &msg);
void HandleGatewayInvalidSession(const GatewayMessage &msg);
@@ -268,14 +239,18 @@ private:
std::string m_token;
void AddMessageToChannel(Snowflake msg_id, Snowflake channel_id);
std::unordered_map<Snowflake, std::unordered_set<Snowflake>> m_chan_to_message_map;
void AddUserToGuild(Snowflake user_id, Snowflake guild_id);
std::map<Snowflake, std::set<Snowflake>> m_guild_to_users;
std::map<Snowflake, std::set<Snowflake>> m_guild_to_channels;
std::map<Snowflake, GuildApplicationData> m_guild_join_requests;
std::map<Snowflake, PresenceStatus> m_user_to_status;
std::map<Snowflake, RelationshipType> m_user_relationships;
std::set<Snowflake> m_joined_threads;
std::map<Snowflake, std::vector<Snowflake>> m_thread_members;
std::unordered_map<Snowflake, std::unordered_set<Snowflake>> m_guild_to_users;
std::unordered_map<Snowflake, std::unordered_set<Snowflake>> m_guild_to_channels;
std::unordered_map<Snowflake, GuildApplicationData> m_guild_join_requests;
std::unordered_map<Snowflake, PresenceStatus> m_user_to_status;
std::unordered_map<Snowflake, RelationshipType> m_user_relationships;
UserData m_user_data;
UserSettings m_user_settings;
@@ -285,9 +260,8 @@ private:
Websocket m_websocket;
std::atomic<bool> m_client_connected = false;
std::atomic<bool> m_ready_received = false;
bool m_client_started = false;
std::map<std::string, GatewayEvent> m_event_map;
std::unordered_map<std::string, GatewayEvent> m_event_map;
void LoadEventMap();
std::thread m_heartbeat_thread;
@@ -309,9 +283,6 @@ private:
Glib::Dispatcher m_generic_dispatch;
std::queue<std::function<void()>> m_generic_queue;
std::set<Snowflake> m_channels_pinned_requested;
std::set<Snowflake> m_channels_lazy_loaded;
// signals
public:
typedef sigc::signal<void> type_signal_gateway_ready;
@@ -323,7 +294,7 @@ public:
typedef sigc::signal<void, Snowflake> type_signal_guild_delete;
typedef sigc::signal<void, Snowflake> type_signal_channel_delete;
typedef sigc::signal<void, Snowflake> type_signal_channel_update;
typedef sigc::signal<void, ChannelData> type_signal_channel_create;
typedef sigc::signal<void, Snowflake> type_signal_channel_create;
typedef sigc::signal<void, Snowflake> type_signal_guild_update;
typedef sigc::signal<void, Snowflake, Snowflake> type_signal_role_update; // guild id, role id
typedef sigc::signal<void, Snowflake, Snowflake> type_signal_role_create; // guild id, role id
@@ -336,30 +307,15 @@ public:
typedef sigc::signal<void, Snowflake, Snowflake> type_signal_guild_ban_add; // guild id, user id
typedef sigc::signal<void, InviteData> type_signal_invite_create;
typedef sigc::signal<void, InviteDeleteObject> type_signal_invite_delete;
typedef sigc::signal<void, UserData, PresenceStatus> type_signal_presence_update;
typedef sigc::signal<void, Snowflake, PresenceStatus> type_signal_presence_update;
typedef sigc::signal<void, Snowflake, std::string> type_signal_note_update;
typedef sigc::signal<void, Snowflake, std::vector<EmojiData>> type_signal_guild_emojis_update; // guild id
typedef sigc::signal<void, GuildJoinRequestCreateData> type_signal_guild_join_request_create;
typedef sigc::signal<void, GuildJoinRequestUpdateData> type_signal_guild_join_request_update;
typedef sigc::signal<void, GuildJoinRequestDeleteData> type_signal_guild_join_request_delete;
typedef sigc::signal<void, Snowflake, RelationshipType> type_signal_relationship_remove;
typedef sigc::signal<void, RelationshipAddData> type_signal_relationship_add;
typedef sigc::signal<void, ChannelData> type_signal_thread_create;
typedef sigc::signal<void, ThreadDeleteData> type_signal_thread_delete;
typedef sigc::signal<void, ThreadListSyncData> type_signal_thread_list_sync;
typedef sigc::signal<void, ThreadMembersUpdateData> type_signal_thread_members_update;
typedef sigc::signal<void, ThreadUpdateData> type_signal_thread_update;
typedef sigc::signal<void, ThreadMemberListUpdateData> type_signal_thread_member_list_update;
// not discord dispatch events
typedef sigc::signal<void, Snowflake> type_signal_added_to_thread;
typedef sigc::signal<void, Snowflake> type_signal_removed_from_thread;
typedef sigc::signal<void, Message> type_signal_message_unpinned;
typedef sigc::signal<void, Message> type_signal_message_pinned;
typedef sigc::signal<void, Message> type_signal_message_sent;
typedef sigc::signal<void, std::string /* nonce */, float /* retry_after */> type_signal_message_send_fail; // retry after param will be 0 if it failed for a reason that isnt slowmode
typedef sigc::signal<void, bool, GatewayCloseCode> type_signal_disconnected; // bool true if reconnecting
typedef sigc::signal<void, bool, GatewayCloseCode> type_signal_disconnected; // bool true if reconnecting
typedef sigc::signal<void> type_signal_connected;
type_signal_gateway_ready signal_gateway_ready();
@@ -390,19 +346,6 @@ public:
type_signal_guild_join_request_create signal_guild_join_request_create();
type_signal_guild_join_request_update signal_guild_join_request_update();
type_signal_guild_join_request_delete signal_guild_join_request_delete();
type_signal_relationship_remove signal_relationship_remove();
type_signal_relationship_add signal_relationship_add();
type_signal_message_unpinned signal_message_unpinned();
type_signal_message_pinned signal_message_pinned();
type_signal_thread_create signal_thread_create();
type_signal_thread_delete signal_thread_delete();
type_signal_thread_list_sync signal_thread_list_sync();
type_signal_thread_members_update signal_thread_members_update();
type_signal_thread_update signal_thread_update();
type_signal_thread_member_list_update signal_thread_member_list_update();
type_signal_added_to_thread signal_added_to_thread();
type_signal_removed_from_thread signal_removed_from_thread();
type_signal_message_sent signal_message_sent();
type_signal_message_send_fail signal_message_send_fail();
type_signal_disconnected signal_disconnected();
@@ -437,19 +380,6 @@ protected:
type_signal_guild_join_request_create m_signal_guild_join_request_create;
type_signal_guild_join_request_update m_signal_guild_join_request_update;
type_signal_guild_join_request_delete m_signal_guild_join_request_delete;
type_signal_relationship_remove m_signal_relationship_remove;
type_signal_relationship_add m_signal_relationship_add;
type_signal_message_unpinned m_signal_message_unpinned;
type_signal_message_pinned m_signal_message_pinned;
type_signal_thread_create m_signal_thread_create;
type_signal_thread_delete m_signal_thread_delete;
type_signal_thread_list_sync m_signal_thread_list_sync;
type_signal_thread_members_update m_signal_thread_members_update;
type_signal_thread_update m_signal_thread_update;
type_signal_thread_member_list_update m_signal_thread_member_list_update;
type_signal_removed_from_thread m_signal_removed_from_thread;
type_signal_added_to_thread m_signal_added_to_thread;
type_signal_message_sent m_signal_message_sent;
type_signal_message_send_fail m_signal_message_send_fail;
type_signal_disconnected m_signal_disconnected;

View File

@@ -1,36 +0,0 @@
#pragma once
enum class DiscordError {
GENERIC = 0,
INVALID_FORM_BODY = 50035,
RELATIONSHIP_INCOMING_DISABLED = 80000,
RELATIONSHIP_INCOMING_BLOCKED = 80001,
RELATIONSHIP_INVALID_USER_BOT = 80002, // this is misspelled in discord's source lul
RELATIONSHIP_INVALID_SELF = 80003,
RELATIONSHIP_INVALID_DISCORD_TAG = 80004,
RELATIONSHIP_ALREADY_FRIENDS = 80007,
NONE = -1,
};
constexpr const char *GetDiscordErrorDisplayString(DiscordError error) {
switch (error) {
case DiscordError::INVALID_FORM_BODY:
return "Something's wrong with your input";
case DiscordError::RELATIONSHIP_INCOMING_DISABLED:
return "This user isn't accepting friend requests";
case DiscordError::RELATIONSHIP_INCOMING_BLOCKED:
return "You are blocked by this user";
case DiscordError::RELATIONSHIP_INVALID_USER_BOT:
return "You can't send a request to a bot";
case DiscordError::RELATIONSHIP_INVALID_SELF:
return "You can't send a request to yourself";
case DiscordError::RELATIONSHIP_INVALID_DISCORD_TAG:
return "No users with that tag exist";
case DiscordError::RELATIONSHIP_ALREADY_FRIENDS:
return "You are already friends with that user";
case DiscordError::GENERIC:
default:
return "An error occurred";
}
}

View File

@@ -43,7 +43,6 @@ void from_json(const nlohmann::json &j, GuildData &m) {
// JS_O("voice_states", m.VoiceStates);
// JS_O("members", m.Members);
JS_O("channels", m.Channels);
JS_O("threads", m.Threads);
// JS_O("presences", m.Presences);
JS_ON("max_presences", m.MaxPresences);
JS_O("max_members", m.MaxMembers);

View File

@@ -80,7 +80,6 @@ struct GuildData {
std::optional<int> MaxVideoChannelUsers;
std::optional<int> ApproximateMemberCount;
std::optional<int> ApproximatePresenceCount;
std::optional<std::vector<ChannelData>> Threads; // only with permissions to view, id only
// undocumented
// std::map<std::string, Unknown> GuildHashes;

View File

@@ -1,14 +1,11 @@
#include "httpclient.hpp"
//#define USE_LOCAL_PROXY
HTTPClient::HTTPClient() {
HTTPClient::HTTPClient(std::string api_base)
: m_api_base(api_base) {
m_dispatcher.connect(sigc::mem_fun(*this, &HTTPClient::RunCallbacks));
}
void HTTPClient::SetBase(const std::string &url) {
m_api_base = url;
}
void HTTPClient::SetUserAgent(std::string agent) {
m_agent = agent;
}

View File

@@ -11,9 +11,7 @@
class HTTPClient {
public:
HTTPClient();
void SetBase(const std::string &url);
HTTPClient(std::string api_base);
void SetUserAgent(std::string agent);
void SetAuth(std::string auth);

View File

@@ -10,8 +10,6 @@ void from_json(const nlohmann::json &j, GuildMember &m) {
JS_D("deaf", m.IsDeafened);
JS_D("mute", m.IsMuted);
JS_O("user_id", m.UserID);
JS_ON("avatar", m.Avatar);
JS_O("pending", m.IsPending);
}
std::vector<RoleData> GuildMember::GetSortedRoles() const {
@@ -35,6 +33,4 @@ void GuildMember::update_from_json(const nlohmann::json &j) {
JS_RD("nick", Nickname);
JS_RD("joined_at", JoinedAt);
JS_RD("premium_since", PremiumSince);
JS_RD("avatar", Avatar);
JS_RD("pending", IsPending);
}

View File

@@ -8,17 +8,13 @@
struct GuildMember {
std::optional<UserData> User; // only reliable to access id. only opt in MESSAGE_*
std::string Nickname;
std::string Nickname; // null
std::vector<Snowflake> Roles;
std::string JoinedAt;
std::optional<std::string> PremiumSince; // null
bool IsDeafened;
bool IsMuted;
std::optional<Snowflake> UserID; // present in merged_members
std::optional<bool> IsPending; // this uses `pending` not `is_pending`
// undocuemtned moment !!!1
std::optional<std::string> Avatar;
std::vector<RoleData> GetSortedRoles() const;

View File

@@ -218,7 +218,6 @@ void from_json(const nlohmann::json &j, Message &m) {
m.ReferencedMessage = nullptr;
}
JS_O("interaction", m.Interaction);
JS_O("sticker_items", m.StickerItems);
}
void Message::from_json_edited(const nlohmann::json &j) {
@@ -245,7 +244,6 @@ void Message::from_json_edited(const nlohmann::json &j) {
JS_O("flags", Flags);
JS_O("stickers", Stickers);
JS_O("interaction", Interaction);
JS_O("sticker_items", StickerItems);
}
void Message::SetDeleted() {

View File

@@ -27,7 +27,7 @@ enum class MessageType {
GUILD_DISCOVERY_REQUALIFIED = 15, // yep
GUILD_DISCOVERY_GRACE_PERIOD_INITIAL_WARNING = 16, // yep
GUILD_DISCOVERY_GRACE_PERIOD_FINAL_WARNING = 17, // yep
THREAD_CREATED = 18, // yep
THREAD_CREATED = 18, // nope
INLINE_REPLY = 19, // yep
APPLICATION_COMMAND = 20, // yep
THREAD_STARTER_MESSAGE = 21, // nope
@@ -199,7 +199,6 @@ struct Message {
std::optional<std::vector<StickerData>> Stickers;
std::optional<std::shared_ptr<Message>> ReferencedMessage; // has_value && null means deleted
std::optional<MessageInteractionData> Interaction;
std::optional<std::vector<StickerItem>> StickerItems;
friend void from_json(const nlohmann::json &j, Message &m);
void from_json_edited(const nlohmann::json &j); // for MESSAGE_UPDATE

View File

@@ -85,16 +85,10 @@ void to_json(nlohmann::json &j, const LazyLoadRequestMessage &m) {
for (const auto &[key, chans] : *m.Channels)
j["d"]["channels"][std::to_string(key)] = chans;
}
if (m.ShouldGetTyping)
j["d"]["typing"] = *m.ShouldGetTyping;
if (m.ShouldGetActivities)
j["d"]["activities"] = *m.ShouldGetActivities;
if (m.ShouldGetThreads)
j["d"]["threads"] = *m.ShouldGetThreads;
j["d"]["typing"] = m.ShouldGetTyping;
j["d"]["activities"] = m.ShouldGetActivities;
if (m.Members.has_value())
j["d"]["members"] = *m.Members;
if (m.ThreadIDs.has_value())
j["d"]["thread_member_lists"] = *m.ThreadIDs;
}
void to_json(nlohmann::json &j, const UpdateStatusMessage &m) {
@@ -108,7 +102,7 @@ void to_json(nlohmann::json &j, const UpdateStatusMessage &m) {
j["d"]["status"] = "online";
break;
case PresenceStatus::Offline:
j["d"]["status"] = "invisible";
j["d"]["status"] = "offline";
break;
case PresenceStatus::Idle:
j["d"]["status"] = "idle";
@@ -458,77 +452,3 @@ void from_json(const nlohmann::json &j, RateLimitedResponse &m) {
JS_O("message", m.Message);
JS_D("retry_after", m.RetryAfter);
}
void from_json(const nlohmann::json &j, RelationshipRemoveData &m) {
JS_D("id", m.ID);
JS_D("type", m.Type);
}
void from_json(const nlohmann::json &j, RelationshipAddData &m) {
JS_D("id", m.ID);
JS_D("type", m.Type);
JS_D("user", m.User);
}
void to_json(nlohmann::json &j, const FriendRequestObject &m) {
j["username"] = m.Username;
j["discriminator"] = m.Discriminator;
}
void to_json(nlohmann::json &j, const PutRelationshipObject &m) {
JS_IF("type", m.Type);
}
void from_json(const nlohmann::json &j, ThreadCreateData &m) {
j.get_to(m.Channel);
}
void from_json(const nlohmann::json &j, ThreadDeleteData &m) {
JS_D("id", m.ID);
JS_D("guild_id", m.GuildID);
JS_D("parent_id", m.ParentID);
JS_D("type", m.Type);
}
void from_json(const nlohmann::json &j, ThreadListSyncData &m) {
JS_D("threads", m.Threads);
JS_D("guild_id", m.GuildID);
}
void from_json(const nlohmann::json &j, ThreadMembersUpdateData &m) {
JS_D("id", m.ID);
JS_D("guild_id", m.GuildID);
JS_D("member_count", m.MemberCount);
JS_O("added_members", m.AddedMembers);
JS_O("removed_member_ids", m.RemovedMemberIDs);
}
void from_json(const nlohmann::json &j, ArchivedThreadsResponseData &m) {
JS_D("threads", m.Threads);
JS_D("members", m.Members);
JS_D("has_more", m.HasMore);
}
void from_json(const nlohmann::json &j, ThreadMemberUpdateData &m) {
m.Member = j;
}
void from_json(const nlohmann::json &j, ThreadUpdateData &m) {
m.Thread = j;
}
void from_json(const nlohmann::json &j, ThreadMemberListUpdateData::UserEntry &m) {
JS_D("user_id", m.UserID);
JS_D("member", m.Member);
}
void from_json(const nlohmann::json &j, ThreadMemberListUpdateData &m) {
JS_D("thread_id", m.ThreadID);
JS_D("guild_id", m.GuildID);
JS_D("members", m.Members);
}
void to_json(nlohmann::json &j, const ModifyChannelObject &m) {
JS_IF("archived", m.Archived);
JS_IF("locked", m.Locked);
}

View File

@@ -19,7 +19,6 @@
#include "ban.hpp"
#include "auditlog.hpp"
#include "relationship.hpp"
#include "errors.hpp"
// most stuff below should just be objects that get processed and thrown away immediately
@@ -69,15 +68,6 @@ enum class GatewayEvent : int {
GUILD_JOIN_REQUEST_CREATE,
GUILD_JOIN_REQUEST_UPDATE,
GUILD_JOIN_REQUEST_DELETE,
RELATIONSHIP_REMOVE,
RELATIONSHIP_ADD,
THREAD_CREATE,
THREAD_UPDATE,
THREAD_DELETE,
THREAD_LIST_SYNC,
THREAD_MEMBER_UPDATE,
THREAD_MEMBERS_UPDATE,
THREAD_MEMBER_LIST_UPDATE,
};
enum class GatewayCloseCode : uint16_t {
@@ -205,12 +195,10 @@ struct GuildMemberListUpdateMessage {
struct LazyLoadRequestMessage {
Snowflake GuildID;
std::optional<bool> ShouldGetTyping;
std::optional<bool> ShouldGetActivities;
std::optional<bool> ShouldGetThreads;
std::optional<std::vector<std::string>> Members; // snowflake?
std::optional<std::map<Snowflake, std::vector<std::pair<int, int>>>> Channels; // channel ID -> range of sidebar
std::optional<std::vector<Snowflake>> ThreadIDs;
bool ShouldGetTyping = false;
bool ShouldGetActivities = false;
std::optional<std::vector<std::string>> Members; // snowflake?
std::optional<std::unordered_map<Snowflake, std::vector<std::pair<int, int>>>> Channels; // channel ID -> range of sidebar
friend void to_json(nlohmann::json &j, const LazyLoadRequestMessage &m);
};
@@ -638,110 +626,3 @@ struct RateLimitedResponse {
friend void from_json(const nlohmann::json &j, RateLimitedResponse &m);
};
struct RelationshipRemoveData {
Snowflake ID;
RelationshipType Type;
friend void from_json(const nlohmann::json &j, RelationshipRemoveData &m);
};
struct RelationshipAddData {
Snowflake ID;
// Nickname; same deal as the other comment somewhere else
RelationshipType Type;
UserData User;
// std::optional<bool> ShouldNotify; // i guess if the client should send a notification. not worth caring about
friend void from_json(const nlohmann::json &j, RelationshipAddData &m);
};
struct FriendRequestObject {
std::string Username;
int Discriminator;
friend void to_json(nlohmann::json &j, const FriendRequestObject &m);
};
struct PutRelationshipObject {
std::optional<RelationshipType> Type;
friend void to_json(nlohmann::json &j, const PutRelationshipObject &m);
};
struct ThreadCreateData {
ChannelData Channel;
friend void from_json(const nlohmann::json &j, ThreadCreateData &m);
};
struct ThreadDeleteData {
Snowflake ID;
Snowflake GuildID;
Snowflake ParentID;
ChannelType Type;
friend void from_json(const nlohmann::json &j, ThreadDeleteData &m);
};
// pretty different from docs
struct ThreadListSyncData {
std::vector<ChannelData> Threads;
Snowflake GuildID;
// std::optional<std::vector<???>> MostRecentMessages;
friend void from_json(const nlohmann::json &j, ThreadListSyncData &m);
};
struct ThreadMembersUpdateData {
Snowflake ID;
Snowflake GuildID;
int MemberCount;
std::optional<std::vector<ThreadMemberObject>> AddedMembers;
std::optional<std::vector<Snowflake>> RemovedMemberIDs;
friend void from_json(const nlohmann::json &j, ThreadMembersUpdateData &m);
};
struct ArchivedThreadsResponseData {
std::vector<ChannelData> Threads;
std::vector<ThreadMemberObject> Members;
bool HasMore;
friend void from_json(const nlohmann::json &j, ArchivedThreadsResponseData &m);
};
struct ThreadMemberUpdateData {
ThreadMemberObject Member;
friend void from_json(const nlohmann::json &j, ThreadMemberUpdateData &m);
};
struct ThreadUpdateData {
ChannelData Thread;
friend void from_json(const nlohmann::json &j, ThreadUpdateData &m);
};
struct ThreadMemberListUpdateData {
struct UserEntry {
Snowflake UserID;
// PresenceData Presence;
GuildMember Member;
friend void from_json(const nlohmann::json &j, UserEntry &m);
};
Snowflake ThreadID;
Snowflake GuildID;
std::vector<UserEntry> Members;
friend void from_json(const nlohmann::json &j, ThreadMemberListUpdateData &m);
};
struct ModifyChannelObject {
std::optional<bool> Archived;
std::optional<bool> Locked;
friend void to_json(nlohmann::json &j, const ModifyChannelObject &m);
};

View File

@@ -40,11 +40,10 @@ enum class Permission : uint64_t {
MANAGE_EMOJIS = (1ULL << 30), // Allows management and editing of emojis
USE_SLASH_COMMANDS = (1ULL << 31), // Allows members to use slash commands in text channels
REQUEST_TO_SPEAK = (1ULL << 32), // Allows for requesting to speak in stage channels
MANAGE_THREADS = (1ULL << 34), // Allows for deleting and archiving threads, and viewing all private threads
USE_PUBLIC_THREADS = (1ULL << 35), // Allows for creating and participating in threads
USE_PRIVATE_THREADS = (1ULL << 36), // Allows for creating and participating in private threads
USE_THREADS = (1ULL << 33), // Allows for creating and participating in threads
USE_PRIVATE_THREADS = (1ULL << 34), // Allows for creating and participating in private threads
ALL = 0x1FFFFFFFFFULL,
ALL = 0x7FFFFFFFFULL,
};
template<>
struct Bitwise<Permission> {
@@ -108,7 +107,7 @@ constexpr const char *GetPermissionString(Permission perm) {
case Permission::USE_EXTERNAL_EMOJIS:
return "Use External Emojis";
case Permission::VIEW_GUILD_INSIGHTS:
return "View Server Insights";
return "View Guild Insights";
case Permission::CONNECT:
return "Connect to Voice";
case Permission::SPEAK:
@@ -133,12 +132,6 @@ constexpr const char *GetPermissionString(Permission perm) {
return "Manage Emojis";
case Permission::USE_SLASH_COMMANDS:
return "Use Slash Commands";
case Permission::MANAGE_THREADS:
return "Manage Threads";
case Permission::USE_PUBLIC_THREADS:
return "Use Public Threads";
case Permission::USE_PRIVATE_THREADS:
return "Use Private Threads";
default:
return "Unknown Permission";
}
@@ -187,7 +180,7 @@ constexpr const char *GetPermissionDescription(Permission perm) {
case Permission::USE_EXTERNAL_EMOJIS:
return "Allows members to use emoji from other servers, if they're a Discord Nitro member";
case Permission::VIEW_GUILD_INSIGHTS:
return "Allows members to view Server Insights, which shows data on community growth, engagement, and more.";
return "";
case Permission::CONNECT:
return "Allows members to join voice channels and hear others.";
case Permission::SPEAK:
@@ -212,12 +205,6 @@ constexpr const char *GetPermissionDescription(Permission perm) {
return "Allows members to add or remove custom emojis in this server.";
case Permission::USE_SLASH_COMMANDS:
return "Allows members to use slash commands in text channels.";
case Permission::MANAGE_THREADS:
return "Allows members to rename, delete, archive/unarchive, and turn on slow mode for threads.";
case Permission::USE_PUBLIC_THREADS:
return "Allows members to talk in threads. The \"Send Messages\" permission must be enabled for members to start new threads; if it's disabled, they can only respond to existing threads.";
case Permission::USE_PRIVATE_THREADS:
return "Allows members to create and chat in private threads. The \"Send Messages\" permission must be enabled for members to start new private threads; if it's disabled, they can only respond to private threads they're added to.";
default:
return "";
}

View File

@@ -5,8 +5,6 @@
constexpr static uint64_t DiscordEpochSeconds = 1420070400;
const Snowflake Snowflake::Invalid = -1ULL;
Snowflake::Snowflake()
: m_num(Invalid) {}

View File

@@ -26,7 +26,7 @@ struct Snowflake {
return m_num;
}
const static Snowflake Invalid; // makes sense to me
const static uint64_t Invalid = -1ULL; // makes sense to me
const static uint64_t SecondsInterval = 4194304000ULL; // the "difference" between two snowflakes one second apart
friend void from_json(const nlohmann::json &j, Snowflake &s);

View File

@@ -30,23 +30,3 @@ std::string StickerData::GetURL() const {
return "https://media.discordapp.net/stickers/" + std::to_string(ID) + "/" + *AssetHash + ".json";
return "";
}
void to_json(nlohmann::json &j, const StickerItem &m) {
j["id"] = m.ID;
j["name"] = m.Name;
j["format_type"] = m.FormatType;
}
void from_json(const nlohmann::json &j, StickerItem &m) {
JS_D("id", m.ID);
JS_D("name", m.Name);
JS_D("format_type", m.FormatType);
}
std::string StickerItem::GetURL() const {
if (FormatType == StickerFormatType::PNG || FormatType == StickerFormatType::APNG)
return "https://media.discordapp.net/stickers/" + std::to_string(ID) + ".png?size=256";
else if (FormatType == StickerFormatType::LOTTIE)
return "https://media.discordapp.net/stickers/" + std::to_string(ID) + ".json";
return "";
}

View File

@@ -27,14 +27,3 @@ struct StickerData {
std::string GetURL() const;
};
struct StickerItem {
StickerFormatType FormatType;
Snowflake ID;
std::string Name;
friend void to_json(nlohmann::json &j, const StickerItem &m);
friend void from_json(const nlohmann::json &j, StickerItem &m);
std::string GetURL() const;
};

View File

@@ -105,16 +105,6 @@ void Store::SetChannel(Snowflake id, const ChannelData &chan) {
Bind(m_set_chan_stmt, 17, chan.ParentID);
Bind(m_set_chan_stmt, 18, chan.LastPinTimestamp);
if (chan.ThreadMetadata.has_value()) {
Bind(m_set_chan_stmt, 19, chan.ThreadMetadata->IsArchived);
Bind(m_set_chan_stmt, 20, chan.ThreadMetadata->AutoArchiveDuration);
Bind(m_set_chan_stmt, 21, chan.ThreadMetadata->ArchiveTimestamp);
} else {
Bind(m_set_chan_stmt, 19, nullptr);
Bind(m_set_chan_stmt, 20, nullptr);
Bind(m_set_chan_stmt, 21, nullptr);
}
if (!RunInsert(m_set_chan_stmt))
fprintf(stderr, "channel insert failed: %s\n", sqlite3_errstr(m_db_err));
@@ -204,12 +194,6 @@ void Store::SetGuild(Snowflake id, const GuildData &guild) {
Bind(m_set_guild_stmt, 37, guild.ApproximateMemberCount);
Bind(m_set_guild_stmt, 38, guild.ApproximatePresenceCount);
Bind(m_set_guild_stmt, 39, guild.IsLazy);
if (guild.Threads.has_value()) {
snowflakes.clear();
for (const auto &x : *guild.Threads) snowflakes.push_back(x.ID);
Bind(m_set_guild_stmt, 40, nlohmann::json(snowflakes).dump());
} else
Bind(m_set_guild_stmt, 40, "[]"s);
if (!RunInsert(m_set_guild_stmt))
fprintf(stderr, "guild insert failed: %s\n", sqlite3_errstr(m_db_err));
@@ -226,8 +210,6 @@ void Store::SetGuildMember(Snowflake guild_id, Snowflake user_id, const GuildMem
Bind(m_set_member_stmt, 6, data.PremiumSince);
Bind(m_set_member_stmt, 7, data.IsDeafened);
Bind(m_set_member_stmt, 8, data.IsMuted);
Bind(m_set_member_stmt, 9, data.Avatar);
Bind(m_set_member_stmt, 10, data.IsPending);
if (!RunInsert(m_set_member_stmt))
fprintf(stderr, "member insert failed: %s\n", sqlite3_errstr(m_db_err));
@@ -280,12 +262,6 @@ void Store::SetMessage(Snowflake id, const Message &message) {
Bind(m_set_msg_stmt, 23, message.IsPending);
Bind(m_set_msg_stmt, 24, message.Nonce); // sorry
if (message.StickerItems.has_value()) {
std::string tmp = nlohmann::json(*message.StickerItems).dump();
Bind(m_set_msg_stmt, 25, tmp);
} else
Bind(m_set_msg_stmt, 25, nullptr);
if (!RunInsert(m_set_msg_stmt))
fprintf(stderr, "message insert failed: %s\n", sqlite3_errstr(m_db_err));
@@ -338,86 +314,6 @@ void Store::SetUser(Snowflake id, const UserData &user) {
}
}
Message Store::GetMessageBound(sqlite3_stmt *stmt) const {
Message ret;
Get(stmt, 0, ret.ID);
Get(stmt, 1, ret.ChannelID);
Get(stmt, 2, ret.GuildID);
Get(stmt, 3, ret.Author.ID); // yike
Get(stmt, 4, ret.Content);
Get(stmt, 5, ret.Timestamp);
Get(stmt, 6, ret.EditedTimestamp);
Get(stmt, 7, ret.IsTTS);
Get(stmt, 8, ret.DoesMentionEveryone);
std::string tmps;
Get(stmt, 9, tmps);
nlohmann::json::parse(tmps).get_to(ret.Mentions);
Get(stmt, 10, tmps);
nlohmann::json::parse(tmps).get_to(ret.Attachments);
Get(stmt, 11, tmps);
nlohmann::json::parse(tmps).get_to(ret.Embeds);
Get(stmt, 12, ret.IsPinned);
Get(stmt, 13, ret.WebhookID);
uint64_t tmpi;
Get(stmt, 14, tmpi);
ret.Type = static_cast<MessageType>(tmpi);
Get(stmt, 15, tmps);
if (tmps != "")
ret.Application = nlohmann::json::parse(tmps).get<MessageApplicationData>();
Get(stmt, 16, tmps);
if (tmps != "")
ret.MessageReference = nlohmann::json::parse(tmps).get<MessageReferenceData>();
Get(stmt, 17, tmpi);
ret.Flags = static_cast<MessageFlags>(tmpi);
Get(stmt, 18, tmps);
if (tmps != "")
ret.Stickers = nlohmann::json::parse(tmps).get<std::vector<StickerData>>();
Get(stmt, 19, tmps);
if (tmps != "")
ret.Reactions = nlohmann::json::parse(tmps).get<std::vector<ReactionData>>();
bool tmpb = false;
Get(stmt, 20, tmpb);
if (tmpb) ret.SetDeleted();
Get(stmt, 21, tmpb);
if (tmpb) ret.SetEdited();
Get(stmt, 22, ret.IsPending);
Get(stmt, 23, ret.Nonce);
Get(stmt, 24, tmps);
if (tmps != "")
ret.StickerItems = nlohmann::json::parse(tmps).get<std::vector<StickerItem>>();
// interaction data from join
if (!IsNull(stmt, 25)) {
auto &interaction = ret.Interaction.emplace();
Get(stmt, 25, interaction.ID);
Get(stmt, 26, interaction.Name);
Get(stmt, 27, interaction.Type);
Get(stmt, 28, interaction.User.ID);
}
Reset(stmt);
if (ret.MessageReference.has_value() && ret.MessageReference->MessageID.has_value()) {
auto ref = GetMessage(*ret.MessageReference->MessageID);
if (ref.has_value())
ret.ReferencedMessage = std::make_unique<Message>(std::move(*ref));
else
ret.ReferencedMessage = nullptr;
}
return ret;
}
void Store::SetMessageInteractionPair(Snowflake message_id, const MessageInteractionData &interaction) {
Bind(m_set_msg_interaction_stmt, 1, message_id);
Bind(m_set_msg_interaction_stmt, 2, interaction.ID);
@@ -465,69 +361,6 @@ std::vector<BanData> Store::GetBans(Snowflake guild_id) const {
return ret;
}
std::vector<Message> Store::GetLastMessages(Snowflake id, size_t num) const {
auto ids = GetChannelMessageIDs(id);
std::vector<Message> ret;
for (auto it = ids.cend() - std::min(ids.size(), num); it != ids.cend(); it++)
ret.push_back(*GetMessage(*it));
return ret;
}
std::vector<Snowflake> Store::GetChannelMessageIDs(Snowflake id) const {
std::vector<Snowflake> ret;
Bind(m_get_msg_ids_stmt, 1, id);
while (FetchOne(m_get_msg_ids_stmt)) {
Snowflake x;
Get(m_get_msg_ids_stmt, 0, x);
ret.push_back(x);
}
Reset(m_get_msg_ids_stmt);
if (m_db_err != SQLITE_DONE)
fprintf(stderr, "error while fetching ids: %s\n", sqlite3_errstr(m_db_err));
return ret;
}
std::vector<Message> Store::GetPinnedMessages(Snowflake channel_id) const {
std::vector<Message> ret;
Bind(m_get_pins_stmt, 1, channel_id);
while (FetchOne(m_get_pins_stmt)) {
Snowflake x;
Get(m_get_pins_stmt, 0, x);
auto msg = GetMessage(x);
if (msg.has_value())
ret.push_back(*msg);
}
Reset(m_get_pins_stmt);
if (m_db_err != SQLITE_DONE)
fprintf(stderr, "error while fetching pins: %s\n", sqlite3_errstr(m_db_err));
return ret;
}
std::vector<ChannelData> Store::GetActiveThreads(Snowflake channel_id) const {
std::vector<ChannelData> ret;
Bind(m_get_threads_stmt, 1, channel_id);
while (FetchOne(m_get_threads_stmt)) {
Snowflake x;
Get(m_get_threads_stmt, 0, x);
auto chan = GetChannel(x);
if (chan.has_value())
ret.push_back(*chan);
}
Reset(m_get_threads_stmt);
if (m_db_err != SQLITE_DONE)
fprintf(stderr, "error while fetching threads: %s\n", sqlite3_errstr(m_db_err));
return ret;
}
std::optional<ChannelData> Store::GetChannel(Snowflake id) const {
Bind(m_get_chan_stmt, 1, id);
if (!FetchOne(m_get_chan_stmt)) {
@@ -562,12 +395,6 @@ std::optional<ChannelData> Store::GetChannel(Snowflake id) const {
Get(m_get_chan_stmt, 15, ret.ApplicationID);
Get(m_get_chan_stmt, 16, ret.ParentID);
Get(m_get_chan_stmt, 17, ret.LastPinTimestamp);
if (!IsNull(m_get_chan_stmt, 18)) {
ret.ThreadMetadata.emplace();
Get(m_get_chan_stmt, 18, ret.ThreadMetadata->IsArchived);
Get(m_get_chan_stmt, 19, ret.ThreadMetadata->AutoArchiveDuration);
Get(m_get_chan_stmt, 20, ret.ThreadMetadata->ArchiveTimestamp);
}
Reset(m_get_chan_stmt);
@@ -667,10 +494,6 @@ std::optional<GuildData> Store::GetGuild(Snowflake id) const {
Get(m_get_guild_stmt, 36, ret.ApproximateMemberCount);
Get(m_get_guild_stmt, 37, ret.ApproximatePresenceCount);
Get(m_get_guild_stmt, 38, ret.IsLazy);
Get(m_get_guild_stmt, 39, tmp);
ret.Threads.emplace();
for (const auto &id : nlohmann::json::parse(tmp).get<std::vector<Snowflake>>())
ret.Threads->emplace_back().ID = id;
Reset(m_get_guild_stmt);
@@ -697,8 +520,6 @@ std::optional<GuildMember> Store::GetGuildMember(Snowflake guild_id, Snowflake u
Get(m_get_member_stmt, 5, ret.PremiumSince);
Get(m_get_member_stmt, 6, ret.IsDeafened);
Get(m_get_member_stmt, 7, ret.IsMuted);
Get(m_get_member_stmt, 8, ret.Avatar);
Get(m_get_member_stmt, 9, ret.IsPending);
Reset(m_get_member_stmt);
@@ -714,7 +535,77 @@ std::optional<Message> Store::GetMessage(Snowflake id) const {
return std::nullopt;
}
auto ret = GetMessageBound(m_get_msg_stmt);
Message ret;
ret.ID = id;
Get(m_get_msg_stmt, 1, ret.ChannelID);
Get(m_get_msg_stmt, 2, ret.GuildID);
Get(m_get_msg_stmt, 3, ret.Author.ID); // yike
Get(m_get_msg_stmt, 4, ret.Content);
Get(m_get_msg_stmt, 5, ret.Timestamp);
Get(m_get_msg_stmt, 6, ret.EditedTimestamp);
Get(m_get_msg_stmt, 7, ret.IsTTS);
Get(m_get_msg_stmt, 8, ret.DoesMentionEveryone);
std::string tmps;
Get(m_get_msg_stmt, 9, tmps);
nlohmann::json::parse(tmps).get_to(ret.Mentions);
Get(m_get_msg_stmt, 10, tmps);
nlohmann::json::parse(tmps).get_to(ret.Attachments);
Get(m_get_msg_stmt, 11, tmps);
nlohmann::json::parse(tmps).get_to(ret.Embeds);
Get(m_get_msg_stmt, 12, ret.IsPinned);
Get(m_get_msg_stmt, 13, ret.WebhookID);
uint64_t tmpi;
Get(m_get_msg_stmt, 14, tmpi);
ret.Type = static_cast<MessageType>(tmpi);
Get(m_get_msg_stmt, 15, tmps);
if (tmps != "")
ret.Application = nlohmann::json::parse(tmps).get<MessageApplicationData>();
Get(m_get_msg_stmt, 16, tmps);
if (tmps != "")
ret.MessageReference = nlohmann::json::parse(tmps).get<MessageReferenceData>();
Get(m_get_msg_stmt, 17, tmpi);
ret.Flags = static_cast<MessageFlags>(tmpi);
Get(m_get_msg_stmt, 18, tmps);
if (tmps != "")
ret.Stickers = nlohmann::json::parse(tmps).get<std::vector<StickerData>>();
Get(m_get_msg_stmt, 19, tmps);
if (tmps != "")
ret.Reactions = nlohmann::json::parse(tmps).get<std::vector<ReactionData>>();
bool tmpb = false;
Get(m_get_msg_stmt, 20, tmpb);
if (tmpb) ret.SetDeleted();
Get(m_get_msg_stmt, 21, tmpb);
if (tmpb) ret.SetEdited();
Get(m_get_msg_stmt, 22, ret.IsPending);
Get(m_get_msg_stmt, 23, ret.Nonce);
// interaction data from join
if (!IsNull(m_get_msg_stmt, 24)) {
auto &interaction = ret.Interaction.emplace();
Get(m_get_msg_stmt, 24, interaction.ID);
Get(m_get_msg_stmt, 25, interaction.Name);
Get(m_get_msg_stmt, 26, interaction.Type);
Get(m_get_msg_stmt, 27, interaction.User.ID);
}
Reset(m_get_msg_stmt);
if (ret.MessageReference.has_value() && ret.MessageReference->MessageID.has_value()) {
auto ref = GetMessage(*ret.MessageReference->MessageID);
if (ref.has_value())
ret.ReferencedMessage = std::make_unique<Message>(std::move(*ref));
else
ret.ReferencedMessage = nullptr;
}
return std::optional<Message>(std::move(ret));
}
@@ -805,12 +696,6 @@ void Store::ClearGuild(Snowflake id) {
void Store::ClearChannel(Snowflake id) {
m_channels.erase(id);
Bind(m_clear_chan_stmt, 1, id);
if ((m_db_err = sqlite3_step(m_clear_chan_stmt)) != SQLITE_DONE)
printf("clearing channel failed: %s\n", sqlite3_errstr(m_db_err));
Reset(m_clear_chan_stmt);
}
void Store::ClearBan(Snowflake guild_id, Snowflake user_id) {
@@ -844,7 +729,7 @@ void Store::EndTransaction() {
}
bool Store::CreateTables() {
const char *create_users = R"(
constexpr const char *create_users = R"(
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY,
username TEXT NOT NULL,
@@ -862,7 +747,7 @@ bool Store::CreateTables() {
)
)";
const char *create_permissions = R"(
constexpr const char *create_permissions = R"(
CREATE TABLE IF NOT EXISTS permissions (
id INTEGER NOT NULL,
channel_id INTEGER NOT NULL,
@@ -873,7 +758,7 @@ bool Store::CreateTables() {
)
)";
const char *create_messages = R"(
constexpr const char *create_messages = R"(
CREATE TABLE IF NOT EXISTS messages (
id INTEGER PRIMARY KEY,
channel_id INTEGER NOT NULL,
@@ -898,12 +783,11 @@ bool Store::CreateTables() {
deleted BOOL, /* extra */
edited BOOL, /* extra */
pending BOOL, /* extra */
nonce TEXT,
sticker_items TEXT /* json */
nonce TEXT
)
)";
const char *create_roles = R"(
constexpr const char *create_roles = R"(
CREATE TABLE IF NOT EXISTS roles (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL,
@@ -916,7 +800,7 @@ bool Store::CreateTables() {
)
)";
const char *create_emojis = R"(
constexpr const char *create_emojis = R"(
CREATE TABLE IF NOT EXISTS emojis (
id INTEGER PRIMARY KEY, /*though nullable, only custom emojis (with non-null ids) are stored*/
name TEXT NOT NULL, /*same as id*/
@@ -929,7 +813,7 @@ bool Store::CreateTables() {
)
)";
const char *create_members = R"(
constexpr const char *create_members = R"(
CREATE TABLE IF NOT EXISTS members (
user_id INTEGER NOT NULL,
guild_id INTEGER NOT NULL,
@@ -939,13 +823,11 @@ bool Store::CreateTables() {
premium_since TEXT,
deaf BOOL NOT NULL,
mute BOOL NOT NULL,
avatar TEXT,
pending BOOL,
PRIMARY KEY(user_id, guild_id)
)
)";
const char *create_guilds = R"(
constexpr const char *create_guilds = R"(
CREATE TABLE IF NOT EXISTS guilds (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL,
@@ -985,12 +867,11 @@ bool Store::CreateTables() {
max_video_users INTEGER,
approx_members INTEGER,
approx_presences INTEGER,
lazy BOOL,
threads TEXT NOT NULL /* json */
lazy BOOL
)
)";
const char *create_channels = R"(
constexpr const char *create_channels = R"(
CREATE TABLE IF NOT EXISTS channels (
id INTEGER PRIMARY KEY,
type INTEGER NOT NULL,
@@ -1009,14 +890,11 @@ bool Store::CreateTables() {
owner_id INTEGER,
application_id INTEGER,
parent_id INTEGER,
last_pin_timestamp TEXT,
archived BOOL, /* threads */
auto_archive INTEGER, /* threads */
archived_ts TEXT /* threads */
last_pin_timestamp TEXT
)
)";
const char *create_bans = R"(
constexpr const char *create_bans = R"(
CREATE TABLE IF NOT EXISTS bans (
guild_id INTEGER NOT NULL,
user_id INTEGER NOT NULL,
@@ -1025,7 +903,7 @@ bool Store::CreateTables() {
)
)";
const char *create_interactions = R"(
constexpr const char *create_interactions = R"(
CREATE TABLE IF NOT EXISTS message_interactions (
message_id INTEGER NOT NULL,
interaction_id INTEGER NOT NULL,
@@ -1100,33 +978,33 @@ bool Store::CreateTables() {
}
bool Store::CreateStatements() {
const char *set_user = R"(
constexpr const char *set_user = R"(
REPLACE INTO users VALUES (
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
)
)";
const char *get_user = R"(
constexpr const char *get_user = R"(
SELECT * FROM users WHERE id = ?
)";
const char *set_perm = R"(
constexpr const char *set_perm = R"(
REPLACE INTO permissions VALUES (
?, ?, ?, ?, ?
)
)";
const char *get_perm = R"(
constexpr const char *get_perm = R"(
SELECT * FROM permissions WHERE id = ? AND channel_id = ?
)";
const char *set_msg = R"(
constexpr const char *set_msg = R"(
REPLACE INTO messages VALUES (
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
)
)";
const char *get_msg = R"(
constexpr const char *get_msg = R"(
SELECT messages.*,
message_interactions.interaction_id as interaction_id,
message_interactions.name as interaction_name,
@@ -1139,105 +1017,80 @@ bool Store::CreateStatements() {
WHERE id = ?
)";
const char *set_role = R"(
constexpr const char *set_role = R"(
REPLACE INTO roles VALUES (
?, ?, ?, ?, ?, ?, ?, ?
)
)";
const char *get_role = R"(
constexpr const char *get_role = R"(
SELECT * FROM roles WHERE id = ?
)";
const char *set_emoji = R"(
constexpr const char *set_emoji = R"(
REPLACE INTO emojis VALUES (
?, ?, ?, ?, ?, ?, ?, ?
)
)";
const char *get_emoji = R"(
constexpr const char *get_emoji = R"(
SELECT * FROM emojis WHERE id = ?
)";
const char *set_member = R"(
constexpr const char *set_member = R"(
REPLACE INTO members VALUES (
?, ?, ?, ?, ?, ?, ?, ?, ?, ?
?, ?, ?, ?, ?, ?, ?, ?
)
)";
const char *get_member = R"(
constexpr const char *get_member = R"(
SELECT * FROM members WHERE user_id = ? AND guild_id = ?
)";
const char *set_guild = R"(
constexpr const char *set_guild = R"(
REPLACE INTO guilds VALUES (
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
)
)";
const char *get_guild = R"(
constexpr const char *get_guild = R"(
SELECT * FROM guilds WHERE id = ?
)";
const char *set_chan = R"(
constexpr const char *set_chan = R"(
REPLACE INTO channels VALUES (
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
)
)";
const char *get_chan = R"(
constexpr const char *get_chan = R"(
SELECT * FROM channels WHERE id = ?
)";
const char *set_ban = R"(
constexpr const char *set_ban = R"(
REPLACE INTO bans VALUES (
?, ?, ?
)
)";
const char *get_ban = R"(
constexpr const char *get_ban = R"(
SELECT * FROM bans WHERE guild_id = ? AND user_id = ?
)";
const char *clear_ban = R"(
constexpr const char *clear_ban = R"(
DELETE FROM bans WHERE guild_id = ? AND user_id = ?
)";
const char *get_bans = R"(
constexpr const char *get_bans = R"(
SELECT * FROM bans WHERE guild_id = ?
)";
const char *set_interaction = R"(
constexpr const char *set_interaction = R"(
REPLACE INTO message_interactions VALUES (
?, ?, ?, ?, ?
)
)";
const char *get_last_msgs = R"(
SELECT * FROM (
SELECT * FROM messages
WHERE channel_id = ?
ORDER BY id DESC
LIMIT ?
) T1 ORDER BY id ASC
)";
const char *get_msg_ids = R"(
SELECT id FROM messages WHERE channel_id = ? AND pending = 0 ORDER BY id ASC
)";
const char *get_pins = R"(
SELECT id FROM messages WHERE channel_id = ? AND pinned = 1 ORDER BY id ASC
)";
const char *get_threads = R"(
SELECT id FROM channels WHERE parent_id = ? AND (type = 10 OR type = 11 OR type = 12) AND archived = FALSE
)";
const char *clear_chan = R"(
DELETE FROM channels WHERE id = ?
)";
m_db_err = sqlite3_prepare_v2(m_db, set_user, -1, &m_set_user_stmt, nullptr);
if (m_db_err != SQLITE_OK) {
fprintf(stderr, "failed to prepare set user statement: %s\n", sqlite3_errstr(m_db_err));
@@ -1364,36 +1217,6 @@ bool Store::CreateStatements() {
return false;
}
m_db_err = sqlite3_prepare_v2(m_db, get_last_msgs, -1, &m_get_last_msgs_stmt, nullptr);
if (m_db_err != SQLITE_OK) {
fprintf(stderr, "failed to prepare get last messages statement: %s\n", sqlite3_errstr(m_db_err));
return false;
}
m_db_err = sqlite3_prepare_v2(m_db, get_msg_ids, -1, &m_get_msg_ids_stmt, nullptr);
if (m_db_err != SQLITE_OK) {
fprintf(stderr, "failed to prepare get msg ids statement: %s\n", sqlite3_errstr(m_db_err));
return false;
}
m_db_err = sqlite3_prepare_v2(m_db, get_pins, -1, &m_get_pins_stmt, nullptr);
if (m_db_err != SQLITE_OK) {
fprintf(stderr, "failed to prepare get pins statement: %s\n", sqlite3_errstr(m_db_err));
return false;
}
m_db_err = sqlite3_prepare_v2(m_db, get_threads, -1, &m_get_threads_stmt, nullptr);
if (m_db_err != SQLITE_OK) {
fprintf(stderr, "failed to prepare get threads statement: %s\n", sqlite3_errstr(m_db_err));
return false;
}
m_db_err = sqlite3_prepare_v2(m_db, clear_chan, -1, &m_clear_chan_stmt, nullptr);
if (m_db_err != SQLITE_OK) {
fprintf(stderr, "failed to prepare clear channel statement: %s\n", sqlite3_errstr(m_db_err));
return false;
}
return true;
}
@@ -1419,11 +1242,6 @@ void Store::Cleanup() {
sqlite3_finalize(m_clear_ban_stmt);
sqlite3_finalize(m_get_bans_stmt);
sqlite3_finalize(m_set_msg_interaction_stmt);
sqlite3_finalize(m_get_last_msgs_stmt);
sqlite3_finalize(m_get_msg_ids_stmt);
sqlite3_finalize(m_get_pins_stmt);
sqlite3_finalize(m_get_threads_stmt);
sqlite3_finalize(m_clear_chan_stmt);
}
void Store::Bind(sqlite3_stmt *stmt, int index, int num) const {

View File

@@ -41,11 +41,6 @@ public:
std::optional<BanData> GetBan(Snowflake guild_id, Snowflake user_id) const;
std::vector<BanData> GetBans(Snowflake guild_id) const;
std::vector<Message> GetLastMessages(Snowflake id, size_t num) const;
std::vector<Snowflake> GetChannelMessageIDs(Snowflake id) const;
std::vector<Message> GetPinnedMessages(Snowflake channel_id) const;
std::vector<ChannelData> GetActiveThreads(Snowflake channel_id) const; // public
void ClearGuild(Snowflake id);
void ClearChannel(Snowflake id);
void ClearBan(Snowflake guild_id, Snowflake user_id);
@@ -68,8 +63,6 @@ public:
void EndTransaction();
private:
Message GetMessageBound(sqlite3_stmt *stmt) const;
void SetMessageInteractionPair(Snowflake message_id, const MessageInteractionData &interaction);
std::unordered_set<Snowflake> m_channels;
@@ -133,11 +126,6 @@ private:
mutable sqlite3_stmt *m_clear_ban_stmt;
mutable sqlite3_stmt *m_get_bans_stmt;
mutable sqlite3_stmt *m_set_msg_interaction_stmt;
mutable sqlite3_stmt *m_get_last_msgs_stmt;
mutable sqlite3_stmt *m_get_msg_ids_stmt;
mutable sqlite3_stmt *m_get_pins_stmt;
mutable sqlite3_stmt *m_get_threads_stmt;
mutable sqlite3_stmt *m_clear_chan_stmt;
};
template<typename T>

View File

@@ -13,33 +13,11 @@ bool UserData::HasAnimatedAvatar() const {
return Avatar.size() > 0 && Avatar[0] == 'a' && Avatar[1] == '_';
}
std::string UserData::GetAvatarURL(Snowflake guild_id, std::string ext, std::string size) const {
const auto member = Abaddon::Get().GetDiscordClient().GetMember(ID, guild_id);
if (member.has_value() && member->Avatar.has_value())
return "https://cdn.discordapp.com/guilds/" +
std::to_string(guild_id) + "/users/" + std::to_string(ID) +
"/avatars/" + *member->Avatar + "." +
ext + "?" + "size=" + size;
else
return GetAvatarURL(ext, size);
}
std::string UserData::GetAvatarURL(const std::optional<Snowflake> &guild_id, std::string ext, std::string size) const {
if (guild_id.has_value())
return GetAvatarURL(*guild_id, ext, size);
else
return GetAvatarURL(ext, size);
}
std::string UserData::GetAvatarURL(std::string ext, std::string size) const {
if (HasAvatar())
return "https://cdn.discordapp.com/avatars/" + std::to_string(ID) + "/" + Avatar + "." + ext + "?size=" + size;
else
return GetDefaultAvatarURL();
}
std::string UserData::GetDefaultAvatarURL() const {
return "https://cdn.discordapp.com/embed/avatars/" + std::to_string(std::stoul(Discriminator) % 5) + ".png"; // size isn't respected by the cdn
return "https://cdn.discordapp.com/embed/avatars/" + std::to_string(std::stoul(Discriminator) % 5) + ".png"; // size isn't respected by the cdn
}
Snowflake UserData::GetHoistedRole(Snowflake guild_id, bool with_color) const {
@@ -80,8 +58,6 @@ void from_json(const nlohmann::json &j, UserData &m) {
JS_O("mobile", m.IsMobile);
JS_ON("nsfw_allowed", m.IsNSFWAllowed);
JS_ON("phone", m.Phone);
JS_ON("bio", m.Bio);
JS_ON("banner", m.BannerHash);
}
void to_json(nlohmann::json &j, const UserData &m) {
@@ -154,8 +130,6 @@ const char *UserData::GetFlagName(uint64_t flag) {
return "verifiedbot";
case EarlyVerifiedBotDeveloper:
return "earlyverifiedbotdeveloper";
case CertifiedModerator:
return "certifiedmoderator";
default:
return "unknown";
}
@@ -189,8 +163,6 @@ const char *UserData::GetFlagReadableName(uint64_t flag) {
return "Verified Bot";
case EarlyVerifiedBotDeveloper:
return "Early Verified Bot Developer";
case CertifiedModerator:
return "Discord Certified Moderator";
default:
return "";
}

View File

@@ -24,10 +24,8 @@ struct UserData {
BugHunterLevel2 = 1 << 14,
VerifiedBot = 1 << 16,
EarlyVerifiedBotDeveloper = 1 << 17,
CertifiedModerator = 1 << 18,
MaxFlag_PlusOne,
MaxFlag = MaxFlag_PlusOne - 1,
MaxFlag = EarlyVerifiedBotDeveloper,
};
static const char *GetFlagName(uint64_t flag);
@@ -52,9 +50,6 @@ struct UserData {
std::optional<bool> IsMobile;
std::optional<bool> IsNSFWAllowed; // null
std::optional<std::string> Phone; // null?
// for now (unserialized)
std::optional<std::string> BannerHash; // null
std::optional<std::string> Bio; // null
friend void from_json(const nlohmann::json &j, UserData &m);
friend void to_json(nlohmann::json &j, const UserData &m);
@@ -63,10 +58,7 @@ struct UserData {
bool IsDeleted() const;
bool HasAvatar() const;
bool HasAnimatedAvatar() const;
std::string GetAvatarURL(Snowflake guild_id, std::string ext = "png", std::string size = "32") const;
std::string GetAvatarURL(const std::optional<Snowflake> &guild_id, std::string ext = "png", std::string size = "32") const;
std::string GetAvatarURL(std::string ext = "png", std::string size = "32") const;
std::string GetDefaultAvatarURL() const;
Snowflake GetHoistedRole(Snowflake guild_id, bool with_color = false) const;
std::string GetMention() const;
std::string GetEscapedName() const;

View File

@@ -125,10 +125,6 @@ void FileCacheWorkerThread::stop() {
}
void FileCacheWorkerThread::loop() {
timeval timeout;
timeout.tv_sec = 1;
timeout.tv_usec = 0;
while (!m_stop) {
if (m_handles.size() == 0) {
std::unique_lock<std::mutex> lock(m_queue_mutex);
@@ -137,8 +133,7 @@ void FileCacheWorkerThread::loop() {
m_cv.wait(lock);
}
static const auto concurrency = Abaddon::Get().GetSettings().GetCacheHTTPConcurrency();
if (m_handles.size() < concurrency) {
if (m_handles.size() < Abaddon::Get().GetSettings().GetCacheHTTPConcurrency()) {
std::optional<QueueEntry> entry;
m_queue_mutex.lock();
if (m_queue.size() > 0) {
@@ -176,20 +171,8 @@ void FileCacheWorkerThread::loop() {
}
}
fd_set fdread;
fd_set fdwrite;
fd_set fdexcep;
int maxfd = -1;
FD_ZERO(&fdread);
FD_ZERO(&fdwrite);
FD_ZERO(&fdexcep);
curl_multi_fdset(m_multi_handle, &fdread, &fdwrite, &fdexcep, &maxfd);
if (maxfd == -1) {
std::this_thread::sleep_for(std::chrono::milliseconds(100));
} else {
select(maxfd + 1, &fdread, &fdwrite, &fdexcep, &timeout);
}
//int fds;
//curl_multi_wait(m_multi_handle, nullptr, 0, 10, &fds);
curl_multi_perform(m_multi_handle, &m_running_handles);
int num_msgs;

Binary file not shown.

View File

@@ -1,14 +0,0 @@
<?xml version="1.0"?>
<!DOCTYPE fontconfig SYSTEM "fonts.dtd">
<fontconfig>
<!-- Use the Autohinter -->
<match target="pattern">
<!--
This configuration is available on the major desktop environments.
We shouldn't overwrite it with "assign" unconditionally.
Most clients may picks up the first value only. so using "append"
may simply works to avoid it.
-->
<edit name="autohint" mode="append"><bool>true</bool></edit>
</match>
</fontconfig>

View File

@@ -1,13 +0,0 @@
<?xml version="1.0"?>
<!DOCTYPE fontconfig SYSTEM "fonts.dtd">
<fontconfig>
<match target="pattern">
<!--
This configuration is available on the major desktop environments.
We shouldn't overwrite it with "assign" unconditionally.
Most clients may picks up the first value only. so using "append"
may simply works to avoid it.
-->
<edit name="hintstyle" mode="append"><const>hintfull</const></edit>
</match>
</fontconfig>

View File

@@ -1,13 +0,0 @@
<?xml version="1.0"?>
<!DOCTYPE fontconfig SYSTEM "fonts.dtd">
<fontconfig>
<match target="pattern">
<!--
This configuration is available on the major desktop environments.
We shouldn't overwrite it with "assign" unconditionally.
Most clients may picks up the first value only. so using "append"
may simply works to avoid it.
-->
<edit name="hintstyle" mode="append"><const>hintmedium</const></edit>
</match>
</fontconfig>

View File

@@ -1,13 +0,0 @@
<?xml version="1.0"?>
<!DOCTYPE fontconfig SYSTEM "fonts.dtd">
<fontconfig>
<match target="pattern">
<!--
This configuration is available on the major desktop environments.
We shouldn't overwrite it with "assign" unconditionally.
Most clients may picks up the first value only. so using "append"
may simply works to avoid it.
-->
<edit name="hintstyle" mode="append"><const>hintnone</const></edit>
</match>
</fontconfig>

View File

@@ -1,13 +0,0 @@
<?xml version="1.0"?>
<!DOCTYPE fontconfig SYSTEM "fonts.dtd">
<fontconfig>
<match target="pattern">
<!--
This configuration is available on the major desktop environments.
We shouldn't overwrite it with "assign" unconditionally.
Most clients may picks up the first value only. so using "append"
may simply works to avoid it.
-->
<edit name="hintstyle" mode="append"><const>hintslight</const></edit>
</match>
</fontconfig>

View File

@@ -1,14 +0,0 @@
<?xml version="1.0"?>
<!DOCTYPE fontconfig SYSTEM "fonts.dtd">
<fontconfig>
<!-- Disable sub-pixel rendering -->
<match target="pattern">
<!--
This configuration is available on the major desktop environments.
We shouldn't overwrite it with "assign" unconditionally.
Most clients may picks up the first value only. so using "append"
may simply works to avoid it.
-->
<edit name="rgba" mode="append"><const>none</const></edit>
</match>
</fontconfig>

View File

@@ -1,80 +0,0 @@
<?xml version="1.0"?>
<!DOCTYPE fontconfig SYSTEM "fonts.dtd">
<fontconfig>
<!--
If font is bitmap, calculate scale factor.
Note that color bitmap fonts have scalable=true, while
non-color ones have scalable=false. Both groups have outline=false.
-->
<match target="font">
<test name="outline" compare="eq">
<bool>false</bool>
</test>
<edit name="pixelsizefixupfactor" mode="assign">
<double>0.15</double>
</edit>
</match>
<!--
For non-scalable bitmap fonts (ie. non-color), skip
minor scaling if hinting is enabled.
-->
<match target="font">
<test name="outline" compare="eq">
<bool>false</bool>
</test>
<test name="scalable" compare="eq">
<bool>false</bool>
</test>
<test name="hinting" compare="eq">
<bool>true</bool>
</test>
<edit name="scalingnotneeded" mode="assign">
<and>
<less>
<name>pixelsizefixupfactor</name>
<double>1.2</double>
</less>
<more>
<name>pixelsizefixupfactor</name>
<double>0.8</double>
</more>
</and>
</edit>
</match>
<match target="font">
<test name="scalingnotneeded" compare="eq">
<bool>true</bool>
</test>
<edit name="pixelsizefixupfactor" mode="assign">
<double>1.0</double>
</edit>
</match>
<!--
If we *are* going to scale, go ahead and do it.
-->
<match target="font">
<test name="outline" compare="eq">
<bool>false</bool>
</test>
<test name="pixelsizefixupfactor" compare="not_eq">
<double>1.0</double>
</test>
<edit name="matrix" mode="assign">
<times>
<name>matrix</name>
<matrix>
<name>pixelsizefixupfactor</name> <double>0</double>
<double>0</double> <name>pixelsizefixupfactor</name>
</matrix>
</times>
</edit>
<edit name="size" mode="assign">
<divide>
<name>size</name>
<name>pixelsizefixupfactor</name>
</divide>
</edit>
</match>
</fontconfig>

View File

@@ -1,14 +0,0 @@
<?xml version="1.0"?>
<!DOCTYPE fontconfig SYSTEM "fonts.dtd">
<fontconfig>
<!-- Enable sub-pixel rendering -->
<match target="pattern">
<!--
This configuration is available on the major desktop environments.
We shouldn't overwrite it with "assign" unconditionally.
Most clients may picks up the first value only. so using "append"
may simply works to avoid it.
-->
<edit name="rgba" mode="append"><const>bgr</const></edit>
</match>
</fontconfig>

View File

@@ -1,14 +0,0 @@
<?xml version="1.0"?>
<!DOCTYPE fontconfig SYSTEM "fonts.dtd">
<fontconfig>
<!-- Enable sub-pixel rendering -->
<match target="pattern">
<!--
This configuration is available on the major desktop environments.
We shouldn't overwrite it with "assign" unconditionally.
Most clients may picks up the first value only. so using "append"
may simply works to avoid it.
-->
<edit name="rgba" mode="append"><const>rgb</const></edit>
</match>
</fontconfig>

View File

@@ -1,14 +0,0 @@
<?xml version="1.0"?>
<!DOCTYPE fontconfig SYSTEM "fonts.dtd">
<fontconfig>
<!-- Enable sub-pixel rendering -->
<match target="pattern">
<!--
This configuration is available on the major desktop environments.
We shouldn't overwrite it with "assign" unconditionally.
Most clients may picks up the first value only. so using "append"
may simply works to avoid it.
-->
<edit name="rgba" mode="append"><const>vbgr</const></edit>
</match>
</fontconfig>

View File

@@ -1,14 +0,0 @@
<?xml version="1.0"?>
<!DOCTYPE fontconfig SYSTEM "fonts.dtd">
<fontconfig>
<!-- Enable sub-pixel rendering -->
<match target="pattern">
<!--
This configuration is available on the major desktop environments.
We shouldn't overwrite it with "assign" unconditionally.
Most clients may picks up the first value only. so using "append"
may simply works to avoid it.
-->
<edit name="rgba" mode="append"><const>vrgb</const></edit>
</match>
</fontconfig>

View File

@@ -1,14 +0,0 @@
<?xml version="1.0"?>
<!DOCTYPE fontconfig SYSTEM "fonts.dtd">
<fontconfig>
<!-- Disable hinting -->
<match target="pattern">
<!--
This configuration is available on the major desktop environments.
We shouldn't overwrite it with "assign" unconditionally.
Most clients may picks up the first value only. so using "append"
may simply works to avoid it.
-->
<edit name="hinting" mode="append"><bool>false</bool></edit>
</match>
</fontconfig>

View File

@@ -1,16 +0,0 @@
<?xml version="1.0"?>
<!DOCTYPE fontconfig SYSTEM "fonts.dtd">
<fontconfig>
<!-- Use lcddefault as default for LCD filter -->
<match target="pattern">
<!--
This configuration is available on the major desktop environments.
We shouldn't overwrite it with "assign" unconditionally.
Most clients may picks up the first value only. so using "append"
may simply works to avoid it.
-->
<edit mode="append" name="lcdfilter">
<const>lcddefault</const>
</edit>
</match>
</fontconfig>

View File

@@ -1,16 +0,0 @@
<?xml version="1.0"?>
<!DOCTYPE fontconfig SYSTEM "fonts.dtd">
<fontconfig>
<!-- Use lcdlegacy as default for LCD filter -->
<match target="pattern">
<!--
This configuration is available on the major desktop environments.
We shouldn't overwrite it with "assign" unconditionally.
Most clients may picks up the first value only. so using "append"
may simply works to avoid it.
-->
<edit mode="append" name="lcdfilter">
<const>lcdlegacy</const>
</edit>
</match>
</fontconfig>

View File

@@ -1,16 +0,0 @@
<?xml version="1.0"?>
<!DOCTYPE fontconfig SYSTEM "fonts.dtd">
<fontconfig>
<!-- Use lcdlight as default for LCD filter -->
<match target="pattern">
<!--
This configuration is available on the major desktop environments.
We shouldn't overwrite it with "assign" unconditionally.
Most clients may picks up the first value only. so using "append"
may simply works to avoid it.
-->
<edit mode="append" name="lcdfilter">
<const>lcdlight</const>
</edit>
</match>
</fontconfig>

View File

@@ -1,48 +0,0 @@
<?xml version="1.0"?>
<!DOCTYPE fontconfig SYSTEM "fonts.dtd">
<fontconfig>
<!--
The Bitstream Vera fonts have GASP entries suggesting that hinting be
disabled below 8 ppem, but FreeType ignores those, preferring to use
the data found in the instructed hints. The initial Vera release
didn't include the right instructions in the 'prep' table. Fix this
by disabling hinting manually at smaller sizes (< 8ppem)
-->
<match target="font">
<test name="family" compare="eq" ignore-blanks="true">
<string>Bitstream Vera Sans</string>
</test>
<test name="pixelsize" compare="less">
<double>7.5</double>
</test>
<edit name="hinting">
<bool>false</bool>
</edit>
</match>
<match target="font">
<test name="family" compare="eq" ignore-blanks="true">
<string>Bitstream Vera Serif</string>
</test>
<test name="pixelsize" compare="less">
<double>7.5</double>
</test>
<edit name="hinting">
<bool>false</bool>
</edit>
</match>
<match target="font">
<test name="family" compare="eq" ignore-blanks="true">
<string>Bitstream Vera Sans Mono</string>
</test>
<test name="pixelsize" compare="less">
<double>7.5</double>
</test>
<edit name="hinting">
<bool>false</bool>
</edit>
</match>
</fontconfig>

View File

@@ -1,128 +0,0 @@
<?xml version="1.0"?>
<!DOCTYPE fontconfig SYSTEM "fonts.dtd">
<fontconfig>
<!-- We can't hint CJK fonts well, so turn off hinting for CJK fonts. -->
<match target="font">
<test name="family" compare="eq" ignore-blanks="true">
<string>Kochi Mincho</string>
</test>
<edit name="hinting" mode="assign">
<bool>false</bool>
</edit>
</match>
<match target="font">
<test name="family" compare="eq" ignore-blanks="true">
<string>Kochi Gothic</string>
</test>
<edit name="hinting" mode="assign">
<bool>false</bool>
</edit>
</match>
<match target="font">
<test name="family" compare="eq" ignore-blanks="true">
<string>Sazanami Mincho</string>
</test>
<edit name="hinting" mode="assign">
<bool>false</bool>
</edit>
</match>
<match target="font">
<test name="family" compare="eq" ignore-blanks="true">
<string>Sazanami Gothic</string>
</test>
<edit name="hinting" mode="assign">
<bool>false</bool>
</edit>
</match>
<match target="font">
<test name="family" compare="eq" ignore-blanks="true">
<string>Baekmuk Batang</string>
</test>
<edit name="hinting" mode="assign">
<bool>false</bool>
</edit>
</match>
<match target="font">
<test name="family" compare="eq" ignore-blanks="true">
<string>Baekmuk Dotum</string>
</test>
<edit name="hinting" mode="assign">
<bool>false</bool>
</edit>
</match>
<match target="font">
<test name="family" compare="eq" ignore-blanks="true">
<string>Baekmuk Gulim</string>
</test>
<edit name="hinting" mode="assign">
<bool>false</bool>
</edit>
</match>
<match target="font">
<test name="family" compare="eq" ignore-blanks="true">
<string>Baekmuk Headline</string>
</test>
<edit name="hinting" mode="assign">
<bool>false</bool>
</edit>
</match>
<match target="font">
<test name="family" compare="eq" ignore-blanks="true">
<string>AR PL Mingti2L Big5</string>
</test>
<edit name="hinting" mode="assign">
<bool>false</bool>
</edit>
</match>
<match target="font">
<test name="family" compare="eq" ignore-blanks="true">
<string>AR PL ShanHeiSun Uni</string>
</test>
<edit name="hinting" mode="assign">
<bool>false</bool>
</edit>
</match>
<match target="font">
<test name="family" compare="eq" ignore-blanks="true">
<string>AR PL KaitiM Big5</string>
</test>
<edit name="hinting" mode="assign">
<bool>false</bool>
</edit>
</match>
<match target="font">
<test name="family" compare="eq" ignore-blanks="true">
<string>AR PL ZenKai Uni</string>
</test>
<edit name="hinting" mode="assign">
<bool>false</bool>
</edit>
</match>
<match target="font">
<test name="family" compare="eq" ignore-blanks="true">
<string>AR PL SungtiL GB</string>
</test>
<edit name="hinting" mode="assign">
<bool>false</bool>
</edit>
</match>
<match target="font">
<test name="family" compare="eq" ignore-blanks="true">
<string>AR PL KaitiM GB</string>
</test>
<edit name="hinting" mode="assign">
<bool>false</bool>
</edit>
</match>
<match target="font">
<test name="family" compare="eq" ignore-blanks="true">
<string>ZYSong18030</string>
</test>
<edit name="hinting" mode="assign">
<bool>false</bool>
</edit>
</match>
</fontconfig>

View File

@@ -1,652 +0,0 @@
<?xml version="1.0"?>
<!DOCTYPE fontconfig SYSTEM "fonts.dtd">
<fontconfig>
<!--
Alias similar/metric-compatible families from various sources:
PostScript fonts: URW fonts: GUST fonts: Windows fonts:
====================== ================== ================= ==================
Helvetica Nimbus Sans TeX Gyre Heros
Helvetica Narrow Nimbus Sans Narrow TeX Gyre Heros Cn
Times Nimbus Roman TeX Gyre Termes
Courier Nimbus Mono PS TeX Gyre Cursor
ITC Avant Garde Gothic URW Gothic TeX Gyre Adventor
ITC Bookman URW Bookman TeX Gyre Bonum Bookman Old Style
ITC Zapf Chancery Z003 TeX Gyre Chorus
Palatino P052 TeX Gyre Pagella Palatino Linotype
New Century Schoolbook C059 TeX Gyre Schola Century Schoolbook
Microsoft fonts: Liberation fonts: Google CrOS core fonts: StarOffice fonts: AMT fonts:
================ ====================== ======================= ================= ==============
Arial Liberation Sans Arimo Albany Albany AMT
Arial Narrow Liberation Sans Narrow
Times New Roman Liberation Serif Tinos Thorndale Thorndale AMT
Courier New Liberation Mono Cousine Cumberland Cumberland AMT
Cambria Caladea
Calibri Carlito
Symbol SymbolNeu
Microsoft fonts: Other fonts:
================ ============
Georgia Gelasio
We want for each of them to fallback to any of these available,
but in an order preferring similar designs first. We do this in three steps:
1) Alias each specific to its generic family.
e.g. Liberation Sans to Arial
2) Weak alias each generic to the other generic of its family.
e.g. Arial to Helvetica
3) Alias each generic to its specifics.
e.g. Arial to Liberation Sans, Arimo, Albany, and Albany AMT
-->
<!-- Map specifics to generics -->
<!-- PostScript -->
<alias binding="same">
<family>Nimbus Sans L</family>
<default>
<family>Helvetica</family>
</default>
</alias>
<alias binding="same">
<family>Nimbus Sans</family>
<default>
<family>Helvetica</family>
</default>
</alias>
<alias binding="same">
<family>TeX Gyre Heros</family>
<default>
<family>Helvetica</family>
</default>
</alias>
<alias binding="same">
<family>Nimbus Sans Narrow</family>
<default>
<family>Helvetica Narrow</family>
</default>
</alias>
<alias binding="same">
<family>TeX Gyre Heros Cn</family>
<default>
<family>Helvetica Narrow</family>
</default>
</alias>
<alias binding="same">
<family>Nimbus Roman No9 L</family>
<default>
<family>Times</family>
</default>
</alias>
<alias binding="same">
<family>Nimbus Roman</family>
<default>
<family>Times</family>
</default>
</alias>
<alias binding="same">
<family>TeX Gyre Termes</family>
<default>
<family>Times</family>
</default>
</alias>
<alias binding="same">
<family>Nimbus Mono L</family>
<default>
<family>Courier</family>
</default>
</alias>
<alias binding="same">
<family>Nimbus Mono</family>
<default>
<family>Courier</family>
</default>
</alias>
<alias binding="same">
<family>Nimbus Mono PS</family>
<default>
<family>Courier</family>
</default>
</alias>
<alias binding="same">
<family>TeX Gyre Cursor</family>
<default>
<family>Courier</family>
</default>
</alias>
<alias binding="same">
<family>Avant Garde</family>
<default>
<family>ITC Avant Garde Gothic</family>
</default>
</alias>
<alias binding="same">
<family>URW Gothic L</family>
<default>
<family>ITC Avant Garde Gothic</family>
</default>
</alias>
<alias binding="same">
<family>URW Gothic</family>
<default>
<family>ITC Avant Garde Gothic</family>
</default>
</alias>
<alias binding="same">
<family>TeX Gyre Adventor</family>
<default>
<family>ITC Avant Garde Gothic</family>
</default>
</alias>
<alias binding="same">
<family>Bookman</family>
<default>
<family>ITC Bookman</family>
</default>
</alias>
<alias binding="same">
<family>URW Bookman L</family>
<default>
<family>ITC Bookman</family>
</default>
</alias>
<alias binding="same">
<family>Bookman URW</family>
<default>
<family>ITC Bookman</family>
</default>
</alias>
<alias binding="same">
<family>URW Bookman</family>
<default>
<family>ITC Bookman</family>
</default>
</alias>
<alias binding="same">
<family>TeX Gyre Bonum</family>
<default>
<family>ITC Bookman</family>
</default>
</alias>
<alias binding="same">
<family>Bookman Old Style</family>
<default>
<family>ITC Bookman</family>
</default>
</alias>
<alias binding="same">
<family>Zapf Chancery</family>
<default>
<family>ITC Zapf Chancery</family>
</default>
</alias>
<alias binding="same">
<family>URW Chancery L</family>
<default>
<family>ITC Zapf Chancery</family>
</default>
</alias>
<alias binding="same">
<family>Chancery URW</family>
<default>
<family>ITC Zapf Chancery</family>
</default>
</alias>
<alias binding="same">
<family>Z003</family>
<default>
<family>ITC Zapf Chancery</family>
</default>
</alias>
<alias binding="same">
<family>TeX Gyre Chorus</family>
<default>
<family>ITC Zapf Chancery</family>
</default>
</alias>
<alias binding="same">
<family>URW Palladio L</family>
<default>
<family>Palatino</family>
</default>
</alias>
<alias binding="same">
<family>Palladio URW</family>
<default>
<family>Palatino</family>
</default>
</alias>
<alias binding="same">
<family>P052</family>
<default>
<family>Palatino</family>
</default>
</alias>
<alias binding="same">
<family>TeX Gyre Pagella</family>
<default>
<family>Palatino</family>
</default>
</alias>
<alias binding="same">
<family>Palatino Linotype</family>
<default>
<family>Palatino</family>
</default>
</alias>
<alias binding="same">
<family>Century Schoolbook L</family>
<default>
<family>New Century Schoolbook</family>
</default>
</alias>
<alias binding="same">
<family>Century SchoolBook URW</family>
<default>
<family>New Century Schoolbook</family>
</default>
</alias>
<alias binding="same">
<family>C059</family>
<default>
<family>New Century Schoolbook</family>
</default>
</alias>
<alias binding="same">
<family>TeX Gyre Schola</family>
<default>
<family>New Century Schoolbook</family>
</default>
</alias>
<alias binding="same">
<family>Century Schoolbook</family>
<default>
<family>New Century Schoolbook</family>
</default>
</alias>
<!-- Microsoft -->
<alias binding="same">
<family>Arimo</family>
<default>
<family>Arial</family>
</default>
</alias>
<alias binding="same">
<family>Liberation Sans</family>
<default>
<family>Arial</family>
</default>
</alias>
<alias binding="same">
<family>Liberation Sans Narrow</family>
<default>
<family>Arial Narrow</family>
</default>
</alias>
<alias binding="same">
<family>Albany</family>
<default>
<family>Arial</family>
</default>
</alias>
<alias binding="same">
<family>Albany AMT</family>
<default>
<family>Arial</family>
</default>
</alias>
<alias binding="same">
<family>Tinos</family>
<default>
<family>Times New Roman</family>
</default>
</alias>
<alias binding="same">
<family>Liberation Serif</family>
<default>
<family>Times New Roman</family>
</default>
</alias>
<alias binding="same">
<family>Thorndale</family>
<default>
<family>Times New Roman</family>
</default>
</alias>
<alias binding="same">
<family>Thorndale AMT</family>
<default>
<family>Times New Roman</family>
</default>
</alias>
<alias binding="same">
<family>Cousine</family>
<default>
<family>Courier New</family>
</default>
</alias>
<alias binding="same">
<family>Liberation Mono</family>
<default>
<family>Courier New</family>
</default>
</alias>
<alias binding="same">
<family>Cumberland</family>
<default>
<family>Courier New</family>
</default>
</alias>
<alias binding="same">
<family>Cumberland AMT</family>
<default>
<family>Courier New</family>
</default>
</alias>
<alias binding="same">
<family>Gelasio</family>
<default>
<family>Georgia</family>
</default>
</alias>
<alias binding="same">
<family>Caladea</family>
<default>
<family>Cambria</family>
</default>
</alias>
<alias binding="same">
<family>Carlito</family>
<default>
<family>Calibri</family>
</default>
</alias>
<alias binding="same">
<family>SymbolNeu</family>
<default>
<family>Symbol</family>
</default>
</alias>
<!-- Accept the other group as fallback -->
<!-- PostScript -->
<alias>
<family>Helvetica</family>
<default>
<family>Arial</family>
</default>
</alias>
<alias>
<family>Helvetica Narrow</family>
<default>
<family>Arial Narrow</family>
</default>
</alias>
<alias>
<family>Times</family>
<default>
<family>Times New Roman</family>
</default>
</alias>
<alias>
<family>Courier</family>
<default>
<family>Courier New</family>
</default>
</alias>
<!-- Microsoft -->
<alias>
<family>Arial</family>
<default>
<family>Helvetica</family>
</default>
</alias>
<alias>
<family>Arial Narrow</family>
<default>
<family>Helvetica Narrow</family>
</default>
</alias>
<alias>
<family>Times New Roman</family>
<default>
<family>Times</family>
</default>
</alias>
<alias>
<family>Courier New</family>
<default>
<family>Courier</family>
</default>
</alias>
<!-- Map generics to specifics -->
<!-- PostScript -->
<alias binding="same">
<family>Helvetica</family>
<accept>
<family>TeX Gyre Heros</family>
<family>Nimbus Sans</family>
<family>Nimbus Sans L</family>
</accept>
</alias>
<alias binding="same">
<family>Helvetica Narrow</family>
<accept>
<family>TeX Gyre Heros Cn</family>
<family>Nimbus Sans Narrow</family>
</accept>
</alias>
<alias binding="same">
<family>Times</family>
<accept>
<family>TeX Gyre Termes</family>
<family>Nimbus Roman</family>
<family>Nimbus Roman No9 L</family>
</accept>
</alias>
<alias binding="same">
<family>Courier</family>
<accept>
<family>TeX Gyre Cursor</family>
<family>Nimbus Mono PS</family>
<family>Nimbus Mono</family>
<family>Nimbus Mono L</family>
</accept>
</alias>
<alias binding="same">
<family>ITC Avant Garde Gothic</family>
<accept>
<family>TeX Gyre Adventor</family>
<family>URW Gothic</family>
<family>URW Gothic L</family>
</accept>
</alias>
<alias binding="same">
<family>ITC Bookman</family>
<accept>
<family>Bookman Old Style</family>
<family>TeX Gyre Bonum</family>
<family>URW Bookman</family>
<family>Bookman URW</family>
<family>URW Bookman L</family>
</accept>
</alias>
<alias binding="same">
<family>ITC Zapf Chancery</family>
<accept>
<family>TeX Gyre Chorus</family>
<family>Z003</family>
<family>Chancery URW</family>
<family>URW Chancery L</family>
</accept>
</alias>
<alias binding="same">
<family>Palatino</family>
<accept>
<family>Palatino Linotype</family>
<family>TeX Gyre Pagella</family>
<family>P052</family>
<family>Palladio URW</family>
<family>URW Palladio L</family>
</accept>
</alias>
<alias binding="same">
<family>New Century Schoolbook</family>
<accept>
<family>Century Schoolbook</family>
<family>TeX Gyre Schola</family>
<family>C059</family>
<family>Century SchoolBook URW</family>
<family>Century Schoolbook L</family>
</accept>
</alias>
<!-- Microsoft -->
<alias binding="same">
<family>Arial</family>
<accept>
<family>Arimo</family>
<family>Liberation Sans</family>
<family>Albany</family>
<family>Albany AMT</family>
</accept>
</alias>
<alias binding="same">
<family>Arial Narrow</family>
<accept>
<family>Liberation Sans Narrow</family>
</accept>
</alias>
<alias binding="same">
<family>Times New Roman</family>
<accept>
<family>Tinos</family>
<family>Liberation Serif</family>
<family>Thorndale</family>
<family>Thorndale AMT</family>
</accept>
</alias>
<alias binding="same">
<family>Courier New</family>
<accept>
<family>Cousine</family>
<family>Liberation Mono</family>
<family>Cumberland</family>
<family>Cumberland AMT</family>
</accept>
</alias>
<alias binding="same">
<family>Georgia</family>
<accept>
<family>Gelasio</family>
</accept>
</alias>
<alias binding="same">
<family>Cambria</family>
<accept>
<family>Caladea</family>
</accept>
</alias>
<alias binding="same">
<family>Calibri</family>
<accept>
<family>Carlito</family>
</accept>
</alias>
<alias binding="same">
<family>Symbol</family>
<accept>
<family>SymbolNeu</family>
</accept>
</alias>
</fontconfig>

View File

@@ -1,33 +0,0 @@
<?xml version="1.0"?>
<!DOCTYPE fontconfig SYSTEM "fonts.dtd">
<fontconfig>
<!--
URW provides metric and shape compatible fonts for some Adobe families.
Most of these are handled in 30-metric-aliases.conf.
-->
<alias binding="same">
<family>Zapf Dingbats</family>
<accept>
<family>D050000L</family>
<family>Dingbats</family>
</accept>
</alias>
<alias binding="same">
<family>ITC Zapf Dingbats</family>
<accept>
<family>D050000L</family>
<family>Dingbats</family>
</accept>
</alias>
<match target="pattern">
<test name="family" compare="eq" ignore-blanks="true">
<string>Symbol</string>
</test>
<edit name="family" mode="append" binding="same">
<string>Standard Symbols PS</string>
</edit>
<edit name="family" mode="append" binding="same">
<string>Standard Symbols L</string>
</edit>
</match>
</fontconfig>

View File

@@ -1,231 +0,0 @@
<?xml version="1.0"?>
<!DOCTYPE fontconfig SYSTEM "fonts.dtd">
<fontconfig>
<!--
Mark common families with their generics so we'll get
something reasonable
-->
<!--
Serif faces
-->
<alias>
<family>Nazli</family>
<default><family>serif</family></default>
</alias>
<alias>
<family>Lotoos</family>
<default><family>serif</family></default>
</alias>
<alias>
<family>Mitra</family>
<default><family>serif</family></default>
</alias>
<alias>
<family>Ferdosi</family>
<default><family>serif</family></default>
</alias>
<alias>
<family>Badr</family>
<default><family>serif</family></default>
</alias>
<alias>
<family>Zar</family>
<default><family>serif</family></default>
</alias>
<alias>
<family>Titr</family>
<default><family>serif</family></default>
</alias>
<alias>
<family>Jadid</family>
<default><family>serif</family></default>
</alias>
<alias>
<family>Kochi Mincho</family>
<default><family>serif</family></default>
</alias>
<alias>
<family>AR PL SungtiL GB</family>
<default><family>serif</family></default>
</alias>
<alias>
<family>AR PL Mingti2L Big5</family>
<default><family>serif</family></default>
</alias>
<alias>
<family> 明朝</family>
<default><family>serif</family></default>
</alias>
<alias>
<family>NanumMyeongjo</family>
<default><family>serif</family></default>
</alias>
<alias>
<family>UnBatang</family>
<default><family>serif</family></default>
</alias>
<alias>
<family>Baekmuk Batang</family>
<default><family>serif</family></default>
</alias>
<alias>
<family>MgOpen Canonica</family>
<default><family>serif</family></default>
</alias>
<alias>
<family>Sazanami Mincho</family>
<default><family>serif</family></default>
</alias>
<alias>
<family>AR PL ZenKai Uni</family>
<default><family>serif</family></default>
</alias>
<alias>
<family>ZYSong18030</family>
<default><family>serif</family></default>
</alias>
<alias>
<family>FreeSerif</family>
<default><family>serif</family></default>
</alias>
<alias>
<family>SimSun</family>
<default><family>serif</family></default>
</alias>
<!--
Sans-serif faces
-->
<alias>
<family>Arshia</family>
<default><family>sans-serif</family></default>
</alias>
<alias>
<family>Elham</family>
<default><family>sans-serif</family></default>
</alias>
<alias>
<family>Farnaz</family>
<default><family>sans-serif</family></default>
</alias>
<alias>
<family>Nasim</family>
<default><family>sans-serif</family></default>
</alias>
<alias>
<family>Sina</family>
<default><family>sans-serif</family></default>
</alias>
<alias>
<family>Roya</family>
<default><family>sans-serif</family></default>
</alias>
<alias>
<family>Koodak</family>
<default><family>sans-serif</family></default>
</alias>
<alias>
<family>Terafik</family>
<default><family>sans-serif</family></default>
</alias>
<alias>
<family>Kochi Gothic</family>
<default><family>sans-serif</family></default>
</alias>
<alias>
<family>AR PL KaitiM GB</family>
<default><family>sans-serif</family></default>
</alias>
<alias>
<family>AR PL KaitiM Big5</family>
<default><family>sans-serif</family></default>
</alias>
<alias>
<family> ゴシック</family>
<default><family>sans-serif</family></default>
</alias>
<alias>
<family>NanumGothic</family>
<default><family>sans-serif</family></default>
</alias>
<alias>
<family>UnDotum</family>
<default><family>sans-serif</family></default>
</alias>
<alias>
<family>Baekmuk Dotum</family>
<default><family>sans-serif</family></default>
</alias>
<alias>
<family>MgOpen Modata</family>
<default><family>sans-serif</family></default>
</alias>
<alias>
<family>Sazanami Gothic</family>
<default><family>sans-serif</family></default>
</alias>
<alias>
<family>AR PL ShanHeiSun Uni</family>
<default><family>sans-serif</family></default>
</alias>
<alias>
<family>ZYSong18030</family>
<default><family>sans-serif</family></default>
</alias>
<alias>
<family>FreeSans</family>
<default><family>sans-serif</family></default>
</alias>
<!--
Monospace faces
-->
<alias>
<family>NSimSun</family>
<default><family>monospace</family></default>
</alias>
<alias>
<family>ZYSong18030</family>
<default><family>monospace</family></default>
</alias>
<alias>
<family>NanumGothicCoding</family>
<default><family>monospace</family></default>
</alias>
<alias>
<family>FreeMono</family>
<default><family>monospace</family></default>
</alias>
<!--
Fantasy faces
-->
<alias>
<family>Homa</family>
<default><family>fantasy</family></default>
</alias>
<alias>
<family>Kamran</family>
<default><family>fantasy</family></default>
</alias>
<alias>
<family>Fantezi</family>
<default><family>fantasy</family></default>
</alias>
<alias>
<family>Tabassom</family>
<default><family>fantasy</family></default>
</alias>
<!--
Cursive faces
-->
<alias>
<family>IranNastaliq</family>
<default><family>cursive</family></default>
</alias>
<alias>
<family>Nafees Nastaleeq</family>
<default><family>cursive</family></default>
</alias>
</fontconfig>

View File

@@ -1,273 +0,0 @@
<?xml version="1.0"?>
<!DOCTYPE fontconfig SYSTEM "fonts.dtd">
<fontconfig>
<!--
Mark common families with their generics so we'll get
something reasonable
-->
<!--
Serif faces
-->
<alias>
<family>Bitstream Vera Serif</family>
<default><family>serif</family></default>
</alias>
<alias>
<family>Cambria</family>
<default><family>serif</family></default>
</alias>
<alias>
<family>Constantia</family>
<default><family>serif</family></default>
</alias>
<alias>
<family>DejaVu Serif</family>
<default><family>serif</family></default>
</alias>
<alias>
<family>Elephant</family>
<default><family>serif</family></default>
</alias>
<alias>
<family>Garamond</family>
<default><family>serif</family></default>
</alias>
<alias>
<family>Georgia</family>
<default><family>serif</family></default>
</alias>
<alias>
<family>Liberation Serif</family>
<default><family>serif</family></default>
</alias>
<alias>
<family>Luxi Serif</family>
<default><family>serif</family></default>
</alias>
<alias>
<family>MS Serif</family>
<default><family>serif</family></default>
</alias>
<alias>
<family>Nimbus Roman No9 L</family>
<default><family>serif</family></default>
</alias>
<alias>
<family>Nimbus Roman</family>
<default><family>serif</family></default>
</alias>
<alias>
<family>Palatino Linotype</family>
<default><family>serif</family></default>
</alias>
<alias>
<family>Thorndale AMT</family>
<default><family>serif</family></default>
</alias>
<alias>
<family>Thorndale</family>
<default><family>serif</family></default>
</alias>
<alias>
<family>Times New Roman</family>
<default><family>serif</family></default>
</alias>
<alias>
<family>Times</family>
<default><family>serif</family></default>
</alias>
<!--
Sans-serif faces
-->
<alias>
<family>Albany AMT</family>
<default><family>sans-serif</family></default>
</alias>
<alias>
<family>Albany</family>
<default><family>sans-serif</family></default>
</alias>
<alias>
<family>Arial Unicode MS</family>
<default><family>sans-serif</family></default>
</alias>
<alias>
<family>Arial</family>
<default><family>sans-serif</family></default>
</alias>
<alias>
<family>Bitstream Vera Sans</family>
<default><family>sans-serif</family></default>
</alias>
<alias>
<family>Britannic</family>
<default><family>sans-serif</family></default>
</alias>
<alias>
<family>Calibri</family>
<default><family>sans-serif</family></default>
</alias>
<alias>
<family>Candara</family>
<default><family>sans-serif</family></default>
</alias>
<alias>
<family>Century Gothic</family>
<default><family>sans-serif</family></default>
</alias>
<alias>
<family>Corbel</family>
<default><family>sans-serif</family></default>
</alias>
<alias>
<family>DejaVu Sans</family>
<default><family>sans-serif</family></default>
</alias>
<alias>
<family>Helvetica</family>
<default><family>sans-serif</family></default>
</alias>
<alias>
<family>Haettenschweiler</family>
<default><family>sans-serif</family></default>
</alias>
<alias>
<family>Liberation Sans</family>
<default><family>sans-serif</family></default>
</alias>
<alias>
<family>MS Sans Serif</family>
<default><family>sans-serif</family></default>
</alias>
<alias>
<family>Nimbus Sans L</family>
<default><family>sans-serif</family></default>
</alias>
<alias>
<family>Nimbus Sans</family>
<default><family>sans-serif</family></default>
</alias>
<alias>
<family>Luxi Sans</family>
<default><family>sans-serif</family></default>
</alias>
<alias>
<family>Tahoma</family>
<default><family>sans-serif</family></default>
</alias>
<alias>
<family>Trebuchet MS</family>
<default><family>sans-serif</family></default>
</alias>
<alias>
<family>Twentieth Century</family>
<default><family>sans-serif</family></default>
</alias>
<alias>
<family>Verdana</family>
<default><family>sans-serif</family></default>
</alias>
<!--
Monospace faces
-->
<alias>
<family>Andale Mono</family>
<default><family>monospace</family></default>
</alias>
<alias>
<family>Bitstream Vera Sans Mono</family>
<default><family>monospace</family></default>
</alias>
<alias>
<family>Consolas</family>
<default><family>monospace</family></default>
</alias>
<alias>
<family>Courier New</family>
<default><family>monospace</family></default>
</alias>
<alias>
<family>Courier</family>
<default><family>monospace</family></default>
</alias>
<alias>
<family>Cumberland AMT</family>
<default><family>monospace</family></default>
</alias>
<alias>
<family>Cumberland</family>
<default><family>monospace</family></default>
</alias>
<alias>
<family>DejaVu Sans Mono</family>
<default><family>monospace</family></default>
</alias>
<alias>
<family>Fixedsys</family>
<default><family>monospace</family></default>
</alias>
<alias>
<family>Inconsolata</family>
<default><family>monospace</family></default>
</alias>
<alias>
<family>Liberation Mono</family>
<default><family>monospace</family></default>
</alias>
<alias>
<family>Luxi Mono</family>
<default><family>monospace</family></default>
</alias>
<alias>
<family>Nimbus Mono L</family>
<default><family>monospace</family></default>
</alias>
<alias>
<family>Nimbus Mono</family>
<default><family>monospace</family></default>
</alias>
<alias>
<family>Nimbus Mono PS</family>
<default><family>monospace</family></default>
</alias>
<alias>
<family>Terminal</family>
<default><family>monospace</family></default>
</alias>
<!--
Fantasy faces
-->
<alias>
<family>Bauhaus Std</family>
<default><family>fantasy</family></default>
</alias>
<alias>
<family>Cooper Std</family>
<default><family>fantasy</family></default>
</alias>
<alias>
<family>Copperplate Gothic Std</family>
<default><family>fantasy</family></default>
</alias>
<alias>
<family>Impact</family>
<default><family>fantasy</family></default>
</alias>
<!--
Cursive faces
-->
<alias>
<family>Comic Sans MS</family>
<default><family>cursive</family></default>
</alias>
<alias>
<family>ITC Zapf Chancery Std</family>
<default><family>cursive</family></default>
</alias>
<alias>
<family>Zapfino</family>
<default><family>cursive</family></default>
</alias>
</fontconfig>

View File

@@ -1,21 +0,0 @@
<?xml version="1.0"?>
<!DOCTYPE fontconfig SYSTEM "fonts.dtd">
<fontconfig>
<!--
If the font still has no generic name, add sans-serif
-->
<match target="pattern">
<test qual="all" name="family" compare="not_eq">
<string>sans-serif</string>
</test>
<test qual="all" name="family" compare="not_eq">
<string>serif</string>
</test>
<test qual="all" name="family" compare="not_eq">
<string>monospace</string>
</test>
<edit name="family" mode="append_last">
<string>sans-serif</string>
</edit>
</match>
</fontconfig>

View File

@@ -1,15 +0,0 @@
<?xml version="1.0"?>
<!DOCTYPE fontconfig SYSTEM "fonts.dtd">
<fontconfig>
<!--
Load per-user customization files where stored on XDG Base Directory
specification compliant places. it should be usually:
$HOME/.config/fontconfig/conf.d
$HOME/.config/fontconfig/fonts.conf
-->
<include ignore_missing="yes" prefix="xdg">fontconfig/conf.d</include>
<include ignore_missing="yes" prefix="xdg">fontconfig/fonts.conf</include>
<!-- the following elements will be removed in the future -->
<include ignore_missing="yes" deprecated="yes">~/.fonts.conf.d</include>
<include ignore_missing="yes" deprecated="yes">~/.fonts.conf</include>
</fontconfig>

View File

@@ -1,6 +0,0 @@
<?xml version="1.0"?>
<!DOCTYPE fontconfig SYSTEM "fonts.dtd">
<fontconfig>
<!-- Load local system customization file -->
<include ignore_missing="yes">local.conf</include>
</fontconfig>

View File

@@ -1,9 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE fontconfig SYSTEM "fonts.dtd">
<fontconfig>
<match>
<edit name="family" mode="prepend" binding="weak">
<string>Twitter Color Emoji</string>
</edit>
</match>
</fontconfig>

View File

@@ -1,74 +0,0 @@
<?xml version="1.0"?>
<!DOCTYPE fontconfig SYSTEM "fonts.dtd">
<fontconfig>
<alias>
<family>serif</family>
<prefer>
<family>Bitstream Vera Serif</family>
<family>DejaVu Serif</family>
<family>Times New Roman</family>
<family>Thorndale AMT</family>
<family>Luxi Serif</family>
<family>Nimbus Roman No9 L</family>
<family>Nimbus Roman</family>
<family>Times</family>
</prefer>
</alias>
<alias>
<family>sans-serif</family>
<prefer>
<family>Bitstream Vera Sans</family>
<family>DejaVu Sans</family>
<family>Verdana</family>
<family>Arial</family>
<family>Albany AMT</family>
<family>Luxi Sans</family>
<family>Nimbus Sans L</family>
<family>Nimbus Sans</family>
<family>Helvetica</family>
<family>Lucida Sans Unicode</family>
<family>BPG Glaho International</family> <!-- lat,cyr,arab,geor -->
<family>Tahoma</family> <!-- lat,cyr,greek,heb,arab,thai -->
</prefer>
</alias>
<alias>
<family>monospace</family>
<prefer>
<family>Bitstream Vera Sans Mono</family>
<family>DejaVu Sans Mono</family>
<family>Inconsolata</family>
<family>Andale Mono</family>
<family>Courier New</family>
<family>Cumberland AMT</family>
<family>Luxi Mono</family>
<family>Nimbus Mono L</family>
<family>Nimbus Mono</family>
<family>Nimbus Mono PS</family>
<family>Courier</family>
</prefer>
</alias>
<!--
Fantasy faces
-->
<alias>
<family>fantasy</family>
<prefer>
<family>Impact</family>
<family>Copperplate Gothic Std</family>
<family>Cooper Std</family>
<family>Bauhaus Std</family>
</prefer>
</alias>
<!--
Cursive faces
-->
<alias>
<family>cursive</family>
<prefer>
<family>ITC Zapf Chancery Std</family>
<family>Zapfino</family>
<family>Comic Sans MS</family>
</prefer>
</alias>
</fontconfig>

View File

@@ -1,419 +0,0 @@
<?xml version="1.0"?>
<!DOCTYPE fontconfig SYSTEM "fonts.dtd">
<!--
fonts-persian.conf
To configure Persian fonts from The FarsiWeb Project.
Copyright (C) 2005 Sharif FarsiWeb, Inc. <license@farsiweb.info>
Permission to use, copy, modify, distribute, and sell this software and its
documentation for any purpose is hereby granted without fee, provided that
the above copyright notice appear in all copies and that both that
copyright notice and this permission notice appear in supporting
documentation, and that the name of Sharif FarsiWeb, Inc. not be used in
advertising or publicity pertaining to distribution of the software without
specific, written prior permission. Sharif FarsiWeb, Inc. makes no
representations about the suitability of this software for any purpose. It
is provided "as is" without express or implied warranty.
SHARIF FARSIWEB, INC. DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY SPECIAL, INDIRECT OR
CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
ChangeLog:
2005-04-03 Behdad Esfahbod: Initial revision.
2005-10-09 Behdad Esfahbod: Turned off back-slant and Tahoma sections.
2005-11-30 Behdad Esfahbod: Set Titr susbtitution size to 24 points.
2008 Behdad Esfahbod: Cleanup. Add fantasy and cursive.
-->
<fontconfig>
<!-- Deprecated fonts are discouraged -->
<!-- Nesf[2] is officially deprecated and has problematic tables -->
<alias binding="same">
<family>Nesf</family>
<accept><family>Nesf2</family></accept>
</alias>
<alias binding="same">
<family>Nesf2</family>
<accept><family>Persian_sansserif_default</family></accept>
</alias>
<!-- Name changes and spelling variant aliases -->
<alias binding="same">
<family>Nazanin</family>
<accept><family>Nazli</family></accept>
</alias>
<alias binding="same">
<family>Lotus</family>
<accept><family>Lotoos</family></accept>
</alias>
<alias binding="same">
<family>Yaqut</family>
<accept><family>Yaghoot</family></accept>
</alias>
<alias binding="same">
<family>Yaghut</family>
<accept><family>Yaghoot</family></accept>
</alias>
<alias binding="same">
<family>Traffic</family>
<accept><family>Terafik</family></accept>
</alias>
<alias binding="same">
<family>Ferdowsi</family>
<accept><family>Ferdosi</family></accept>
</alias>
<alias binding="same">
<family>Fantezy</family>
<accept><family>Fantezi</family></accept>
</alias>
<!-- Classify fonts. -->
<!-- Persian_title class -->
<alias binding="same">
<family>Jadid</family>
<accept><family>Persian_title</family></accept>
</alias>
<alias binding="same">
<family>Titr</family>
<accept><family>Persian_title</family></accept>
</alias>
<!-- Persian_fantasy class -->
<alias binding="same">
<family>Kamran</family>
<accept>
<family>Persian_fantasy</family>
<family>Homa</family>
</accept>
</alias>
<alias binding="same">
<family>Homa</family>
<accept>
<family>Persian_fantasy</family>
<family>Kamran</family>
</accept>
</alias>
<alias binding="same">
<family>Fantezi</family>
<accept><family>Persian_fantasy</family></accept>
</alias>
<alias binding="same">
<family>Tabassom</family>
<accept><family>Persian_fantasy</family></accept>
</alias>
<!-- Persian_square class -->
<alias binding="same">
<family>Arshia</family>
<accept><family>Persian_square</family></accept>
</alias>
<alias binding="same">
<family>Nasim</family>
<accept><family>Persian_square</family></accept>
</alias>
<alias binding="same">
<family>Elham</family>
<accept>
<family>Persian_square</family>
<family>Farnaz</family>
</accept>
</alias>
<alias binding="same">
<family>Farnaz</family>
<accept>
<family>Persian_square</family>
<family>Elham</family>
</accept>
</alias>
<alias binding="same">
<family>Sina</family>
<accept><family>Persian_square</family></accept>
</alias>
<!-- Font ordering per class -->
<!-- Persian_title class -->
<alias binding="same">
<family>Persian_title</family>
<accept>
<family>Titr</family>
<family>Jadid</family>
<family>Persian_serif</family>
</accept>
</alias>
<!-- Persian_fantasy class -->
<alias binding="same">
<family>Persian_fantasy</family>
<accept>
<family>Homa</family>
<family>Kamran</family>
<family>Fantezi</family>
<family>Tabassom</family>
<family>Persian_square</family>
</accept>
</alias>
<!-- Persian_square class -->
<alias binding="same">
<family>Persian_square</family>
<accept>
<family>Arshia</family>
<family>Elham</family>
<family>Farnaz</family>
<family>Nasim</family>
<family>Sina</family>
<family>Persian_serif</family>
</accept>
</alias>
<!-- Register the fonts that we actually do have -->
<match target="scan">
<test name="family" compare="eq" ignore-blanks="true">
<string>Elham</string>
</test>
<edit name="foundry">
<string>farsiweb</string>
</edit>
</match>
<match target="scan">
<test name="family" compare="eq" ignore-blanks="true">
<string>Homa</string>
</test>
<edit name="foundry">
<string>farsiweb</string>
</edit>
</match>
<match target="scan">
<test name="family" compare="eq" ignore-blanks="true">
<string>Koodak</string>
</test>
<edit name="foundry">
<string>farsiweb</string>
</edit>
</match>
<match target="scan">
<test name="family" compare="eq" ignore-blanks="true">
<string>Nazli</string>
</test>
<edit name="foundry">
<string>farsiweb</string>
</edit>
</match>
<match target="scan">
<test name="family" compare="eq" ignore-blanks="true">
<string>Roya</string>
</test>
<edit name="foundry">
<string>farsiweb</string>
</edit>
</match>
<match target="scan">
<test name="family" compare="eq" ignore-blanks="true">
<string>Terafik</string>
</test>
<edit name="foundry">
<string>farsiweb</string>
</edit>
</match>
<match target="scan">
<test name="family" compare="eq" ignore-blanks="true">
<string>Titr</string>
</test>
<edit name="foundry">
<string>farsiweb</string>
</edit>
</match>
<!-- Our fonts should oblique to the other side (TURNED-OFF) -->
<match target="font">
<test name="foundry">
<!--string>farsiweb</string-->
<string>TURNED-OFF</string>
</test>
<test name="foundry">
<string>farsiweb</string>
</test>
<!-- check to see if the font is roman -->
<test name="slant">
<const>roman</const>
</test>
<!-- check to see if the pattern requested non-roman -->
<test target="pattern" name="slant" compare="not_eq">
<const>roman</const>
</test>
<!-- multiply the matrix to slant the font -->
<edit name="matrix" mode="assign">
<times>
<name>matrix</name>
<matrix><double>1</double><double>-0.2</double>
<double>0</double><double>1</double>
</matrix>
</times>
</edit>
<!-- pretend the font is oblique now -->
<edit name="slant" mode="assign">
<const>oblique</const>
</edit>
</match>
<!--
We can't hint our fonts well, so turn off hinting.
Moreover, the bitmaps we have designed (well, they
have designed), suck, so disable them too.
-->
<match target="font">
<test name="foundry">
<string>farsiweb</string>
</test>
<edit name="autohint">
<bool>false</bool>
</edit>
<edit name="hinting">
<bool>false</bool>
</edit>
<edit name="embeddedbitmap">
<bool>false</bool>
</edit>
</match>
<!-- Alias our fonts to common families -->
<!-- Persian serif fonts -->
<alias>
<family>serif</family>
<accept>
<family>Nazli</family>
<family>Lotoos</family>
<family>Mitra</family>
<family>Ferdosi</family>
<family>Badr</family>
<family>Zar</family>
</accept>
</alias>
<!-- Persian sans-serif fonts -->
<alias>
<family>sans-serif</family>
<accept>
<family>Roya</family>
<family>Koodak</family>
<family>Terafik</family>
</accept>
</alias>
<!-- Persian monospace fonts -->
<alias>
<family>monospace</family>
<accept>
<!-- Not really monospace -->
<family>Terafik</family>
</accept>
</alias>
<!-- Persian fantasy fonts -->
<alias>
<family>fantasy</family>
<accept>
<family>Homa</family>
<family>Kamran</family>
<family>Fantezi</family>
<family>Tabassom</family>
</accept>
</alias>
<!-- Persian (and Urdu) Nastaliq/cursive fonts -->
<alias>
<family>cursive</family>
<accept>
<family>IranNastaliq</family>
<family>Nafees Nastaleeq</family>
</accept>
</alias>
<!-- Use Titr in titles -->
<!-- Both serif... -->
<match>
<test name="family">
<string>serif</string>
</test>
<test name="weight" compare="more_eq">
<int>200</int>
</test>
<test name="size" compare="more_eq">
<double>24</double>
</test>
<edit name="family" mode="prepend">
<string>Titr</string>
</edit>
</match>
<!-- and sans-serif. -->
<match>
<test name="family">
<string>sans-serif</string>
</test>
<test name="weight" compare="more_eq">
<int>200</int>
</test>
<test name="size" compare="more_eq">
<double>24</double>
</test>
<edit name="family" mode="prepend">
<string>Titr</string>
</edit>
</match>
<!-- and more. -->
<match>
<test name="family">
<string>Persian_sansserif_default</string>
</test>
<test name="weight" compare="more_eq">
<int>200</int>
</test>
<test name="size" compare="more_eq">
<double>24</double>
</test>
<edit name="family" mode="prepend" binding="same">
<string>Titr</string>
</edit>
</match>
<!-- Default substituted for deprecated sans-serif fonts -->
<match>
<test name="family">
<string>Persian_sansserif_default</string>
</test>
<edit name="family" mode="assign" binding="same">
<string>Roya</string>
</edit>
</match>
</fontconfig>

View File

@@ -1,16 +0,0 @@
<?xml version="1.0"?>
<!DOCTYPE fontconfig SYSTEM "fonts.dtd">
<fontconfig>
<alias>
<family>serif</family>
<prefer>
<family>Khmer OS"</family>
</prefer>
</alias>
<alias>
<family>sans-serif</family>
<prefer>
<family>Khmer OS"</family>
</prefer>
</alias>
</fontconfig>

View File

@@ -1,196 +0,0 @@
<?xml version="1.0"?>
<!DOCTYPE fontconfig SYSTEM "fonts.dtd">
<fontconfig>
<alias>
<family>serif</family>
<prefer>
<family>Artsounk</family> <!-- armenian -->
<family>BPG UTF8 M</family> <!-- georgian -->
<family>Kinnari</family> <!-- thai -->
<family>Norasi</family> <!-- thai -->
<family>Frank Ruehl</family> <!-- hebrew -->
<family>Dror</family> <!-- hebrew -->
<family>JG LaoTimes</family> <!-- lao -->
<family>Saysettha Unicode</family> <!-- lao -->
<family>Pigiarniq</family> <!-- canadian syllabics -->
<family>B Davat</family> <!-- arabic (fa) -->
<family>B Compset</family> <!-- arabic (fa) -->
<family>Kacst-Qr</family> <!-- arabic (ar) -->
<family>Urdu Nastaliq Unicode</family> <!-- arabic (ur) -->
<family>Raghindi</family> <!-- devanagari -->
<family>Mukti Narrow</family> <!-- bengali -->
<family>malayalam</family> <!-- malayalam -->
<family>Sampige</family> <!-- kannada -->
<family>padmaa</family> <!-- gujarati -->
<family>Hapax Berbère</family> <!-- tifinagh -->
<family>MS Mincho</family> <!-- han (ja) -->
<family>SimSun</family> <!-- han (zh-cn,zh-tw) -->
<family>PMingLiu</family> <!-- han (zh-tw) -->
<family>WenQuanYi Zen Hei</family> <!-- han (zh-cn,zh-tw) -->
<family>WenQuanYi Bitmap Song</family> <!-- han (zh-cn,zh-tw) -->
<family>AR PL ShanHeiSun Uni</family> <!-- han (ja,zh-cn,zh-tw) -->
<family>AR PL New Sung</family> <!-- han (zh-cn,zh-tw) -->
<family>ZYSong18030</family> <!-- han (zh-cn,zh-tw) -->
<family>HanyiSong</family> <!-- han (zh-cn,zh-tw) -->
<family>MgOpen Canonica</family>
<family>Sazanami Mincho</family>
<family>IPAMonaMincho</family>
<family>IPAMincho</family>
<family>Kochi Mincho</family>
<family>AR PL SungtiL GB</family>
<family>AR PL Mingti2L Big5</family>
<family>AR PL Zenkai Uni</family>
<family> 明朝</family>
<family>ZYSong18030</family>
<family>NanumMyeongjo</family> <!-- hangul (ko) -->
<family>UnBatang</family> <!-- hangul (ko) -->
<family>Baekmuk Batang</family> <!-- hangul (ko) -->
<family>KacstQura</family>
<family>Frank Ruehl CLM</family>
<family>Lohit Bengali</family>
<family>Lohit Gujarati</family>
<family>Lohit Hindi</family>
<family>Lohit Marathi</family>
<family>Lohit Maithili</family>
<family>Lohit Kashmiri</family>
<family>Lohit Konkani</family>
<family>Lohit Nepali</family>
<family>Lohit Sindhi</family>
<family>Lohit Punjabi</family>
<family>Lohit Tamil</family>
<family>Meera</family>
<family>Lohit Malayalam</family>
<family>Lohit Kannada</family>
<family>Lohit Telugu</family>
<family>Lohit Oriya</family>
<family>LKLUG</family>
</prefer>
</alias>
<alias>
<family>sans-serif</family>
<prefer>
<family>Nachlieli</family> <!-- hebrew -->
<family>Lucida Sans Unicode</family>
<family>Yudit Unicode</family>
<family>Kerkis</family> <!-- greek -->
<family>ArmNet Helvetica</family> <!-- armenian -->
<family>Artsounk</family> <!-- armenian -->
<family>BPG UTF8 M</family> <!-- georgian -->
<family>Waree</family> <!-- thai -->
<family>Loma</family> <!-- thai -->
<family>Garuda</family> <!-- thai -->
<family>Umpush</family> <!-- thai -->
<family>Saysettha Unicode</family> <!-- lao? -->
<family>JG Lao Old Arial</family> <!-- lao -->
<family>GF Zemen Unicode</family> <!-- ethiopic -->
<family>Pigiarniq</family> <!-- canadian syllabics -->
<family>B Davat</family> <!-- arabic (fa) -->
<family>B Compset</family> <!-- arabic (fa) -->
<family>Kacst-Qr</family> <!-- arabic (ar) -->
<family>Urdu Nastaliq Unicode</family> <!-- arabic (ur) -->
<family>Raghindi</family> <!-- devanagari -->
<family>Mukti Narrow</family> <!-- bengali -->
<family>malayalam</family> <!-- malayalam -->
<family>Sampige</family> <!-- kannada -->
<family>padmaa</family> <!-- gujarati -->
<family>Hapax Berbère</family> <!-- tifinagh -->
<family>MS Gothic</family> <!-- han (ja) -->
<family>UmePlus P Gothic</family> <!-- han (ja) -->
<!-- chinese fonts are actually serifed -->
<family>SimSun</family> <!-- han (zh-cn,zh-tw) -->
<family>PMingLiu</family> <!-- han (zh-tw) -->
<family>WenQuanYi Zen Hei</family> <!-- han (zh-cn,zh-tw) -->
<family>WenQuanYi Bitmap Song</family> <!-- han (zh-cn,zh-tw) -->
<family>AR PL ShanHeiSun Uni</family> <!--han (ja,zh-cn,zh-tw) -->
<family>AR PL New Sung</family> <!-- han (zh-cn,zh-tw) -->
<family>MgOpen Modata</family>
<family>VL Gothic</family>
<family>IPAMonaGothic</family>
<family>IPAGothic</family>
<family>Sazanami Gothic</family>
<family>Kochi Gothic</family>
<family>AR PL KaitiM GB</family>
<family>AR PL KaitiM Big5</family>
<family>AR PL ShanHeiSun Uni</family>
<family>AR PL SungtiL GB</family>
<family>AR PL Mingti2L Big5</family>
<family> ゴシック</family>
<family>ZYSong18030</family> <!-- han (zh-cn,zh-tw) -->
<family>TSCu_Paranar</family> <!-- tamil -->
<family>NanumGothic</family> <!-- hangul (ko) -->
<family>UnDotum</family> <!-- hangul (ko) -->
<family>Baekmuk Dotum</family> <!-- hangul (ko) -->
<family>Baekmuk Gulim</family> <!-- hangul (ko) -->
<family>KacstQura</family>
<family>Lohit Bengali</family>
<family>Lohit Gujarati</family>
<family>Lohit Hindi</family>
<family>Lohit Marathi</family>
<family>Lohit Maithili</family>
<family>Lohit Kashmiri</family>
<family>Lohit Konkani</family>
<family>Lohit Nepali</family>
<family>Lohit Sindhi</family>
<family>Lohit Punjabi</family>
<family>Lohit Tamil</family>
<family>Meera</family>
<family>Lohit Malayalam</family>
<family>Lohit Kannada</family>
<family>Lohit Telugu</family>
<family>Lohit Oriya</family>
<family>LKLUG</family>
</prefer>
</alias>
<alias>
<family>monospace</family>
<prefer>
<family>Miriam Mono</family> <!-- hebrew -->
<family>VL Gothic</family>
<family>IPAMonaGothic</family>
<family>IPAGothic</family>
<family>Sazanami Gothic</family>
<family>Kochi Gothic</family>
<family>AR PL KaitiM GB</family>
<family>MS Gothic</family> <!-- han (ja) -->
<family>UmePlus Gothic</family> <!-- han (ja) -->
<family>NSimSun</family> <!-- han (zh-cn,zh-tw) -->
<family>MingLiu</family> <!-- han (zh-tw) -->
<family>AR PL ShanHeiSun Uni</family> <!-- han (ja,zh-cn,zh-tw) -->
<family>AR PL New Sung Mono</family> <!-- han (zh-cn,zh-tw) -->
<family>HanyiSong</family> <!-- han (zh-cn) -->
<family>AR PL SungtiL GB</family>
<family>AR PL Mingti2L Big5</family>
<family>ZYSong18030</family> <!-- han (zh-cn,zh-tw) -->
<family>NanumGothicCoding</family> <!-- hangul (ko) -->
<family>NanumGothic</family> <!-- hangul (ko) -->
<family>UnDotum</family> <!-- hangul (ko) -->
<family>Baekmuk Dotum</family> <!-- hangul (ko) -->
<family>Baekmuk Gulim</family> <!-- hangul (ko) -->
<family>TlwgTypo</family> <!-- thai -->
<family>TlwgTypist</family> <!-- thai -->
<family>TlwgTypewriter</family> <!-- thai -->
<family>TlwgMono</family> <!-- thai -->
<family>Hasida</family> <!-- hebrew -->
<family>Mitra Mono</family> <!-- bengali -->
<family>GF Zemen Unicode</family> <!-- ethiopic -->
<family>Hapax Berbère</family> <!-- tifinagh -->
<family>Lohit Bengali</family>
<family>Lohit Gujarati</family>
<family>Lohit Hindi</family>
<family>Lohit Marathi</family>
<family>Lohit Maithili</family>
<family>Lohit Kashmiri</family>
<family>Lohit Konkani</family>
<family>Lohit Nepali</family>
<family>Lohit Sindhi</family>
<family>Lohit Punjabi</family>
<family>Lohit Tamil</family>
<family>Meera</family>
<family>Lohit Malayalam</family>
<family>Lohit Kannada</family>
<family>Lohit Telugu</family>
<family>Lohit Oriya</family>
<family>LKLUG</family>
</prefer>
</alias>
</fontconfig>

View File

@@ -1,28 +0,0 @@
<?xml version="1.0"?>
<!DOCTYPE fontconfig SYSTEM "fonts.dtd">
<fontconfig>
<alias>
<family>serif</family>
<prefer>
<family>FreeSerif</family>
<family>Code2000</family>
<family>Code2001</family> <!-- plane1 and beyond -->
</prefer>
</alias>
<alias>
<family>sans-serif</family>
<prefer>
<family>FreeSans</family>
<family>Arial Unicode MS</family>
<family>Arial Unicode</family>
<family>Code2000</family> <!-- almost everything; serif actually -->
<family>Code2001</family> <!-- plane1 and beyond -->
</prefer>
</alias>
<alias>
<family>monospace</family>
<prefer>
<family>FreeMono</family>
</prefer>
</alias>
</fontconfig>

View File

@@ -1,12 +0,0 @@
<?xml version="1.0"?>
<!DOCTYPE fontconfig SYSTEM "fonts.dtd">
<fontconfig>
<!-- Accept bitmap fonts -->
<selectfont>
<acceptfont>
<pattern>
<patelt name="scalable"><bool>false</bool></patelt>
</pattern>
</acceptfont>
</selectfont>
</fontconfig>

View File

@@ -1,19 +0,0 @@
<?xml version="1.0"?>
<!DOCTYPE fontconfig SYSTEM "fonts.dtd">
<fontconfig>
<!-- Fix-ups for Delicious family -->
<!-- Delicious 'heavy' variant says its Medium weight -->
<match target="scan">
<test name="family" compare="eq" ignore-blanks="true">
<string>Delicious</string>
</test>
<test name="style">
<string>Heavy</string>
</test>
<edit name="weight">
<const>heavy</const>
</edit>
</match>
</fontconfig>

View File

@@ -1,64 +0,0 @@
<?xml version="1.0"?>
<!DOCTYPE fontconfig SYSTEM "fonts.dtd">
<fontconfig>
<!--
Artificial oblique for fonts without an italic or oblique version
-->
<match target="font">
<!-- check to see if the font is roman -->
<test name="slant">
<const>roman</const>
</test>
<!-- check to see if the pattern requested non-roman -->
<test target="pattern" name="slant" compare="not_eq">
<const>roman</const>
</test>
<!-- multiply the matrix to slant the font -->
<edit name="matrix" mode="assign">
<times>
<name>matrix</name>
<matrix><double>1</double><double>0.2</double>
<double>0</double><double>1</double>
</matrix>
</times>
</edit>
<!-- pretend the font is oblique now -->
<edit name="slant" mode="assign">
<const>oblique</const>
</edit>
<!-- and disable embedded bitmaps for artificial oblique -->
<edit name="embeddedbitmap" mode="assign">
<bool>false</bool>
</edit>
</match>
<!--
Synthetic emboldening for fonts that do not have bold face available
-->
<match target="font">
<!-- check to see if the font is just regular -->
<test name="weight" compare="less_eq">
<const>medium</const>
</test>
<!-- check to see if the pattern requests bold -->
<test target="pattern" name="weight" compare="more">
<const>medium</const>
</test>
<!--
set the embolden flag
needed for applications using cairo, e.g. gucharmap, gedit, ...
-->
<edit name="embolden" mode="assign">
<bool>true</bool>
</edit>
<!--
set weight to bold
needed for applications using Xft directly, e.g. Firefox, ...
-->
<edit name="weight" mode="assign">
<const>bold</const>
</edit>
</match>
</fontconfig>

View File

@@ -1,89 +0,0 @@
<?xml version="1.0"?>
<!DOCTYPE fontconfig SYSTEM "fonts.dtd">
<!-- /etc/fonts/fonts.conf file to configure system font access -->
<fontconfig>
<!--
DO NOT EDIT THIS FILE.
IT WILL BE REPLACED WHEN FONTCONFIG IS UPDATED.
LOCAL CHANGES BELONG IN 'local.conf'.
The intent of this standard configuration file is to be adequate for
most environments. If you have a reasonably normal environment and
have found problems with this configuration, they are probably
things that others will also want fixed. Please submit any
problems to the fontconfig bugzilla system located at fontconfig.org
Note that the normal 'make install' procedure for fontconfig is to
replace any existing fonts.conf file with the new version. Place
any local customizations in local.conf which this file references.
Keith Packard
-->
<!-- Font directory list -->
<dir>WINDOWSFONTDIR</dir>
<dir prefix="xdg">fonts</dir>
<!-- the following element will be removed in the future -->
<dir>~/.fonts</dir>
<!--
Accept deprecated 'mono' alias, replacing it with 'monospace'
-->
<match target="pattern">
<test qual="any" name="family">
<string>mono</string>
</test>
<edit name="family" mode="assign" binding="same">
<string>monospace</string>
</edit>
</match>
<!--
Accept alternate 'sans serif' spelling, replacing it with 'sans-serif'
-->
<match target="pattern">
<test qual="any" name="family">
<string>sans serif</string>
</test>
<edit name="family" mode="assign" binding="same">
<string>sans-serif</string>
</edit>
</match>
<!--
Accept deprecated 'sans' alias, replacing it with 'sans-serif'
-->
<match target="pattern">
<test qual="any" name="family">
<string>sans</string>
</test>
<edit name="family" mode="assign" binding="same">
<string>sans-serif</string>
</edit>
</match>
<!--
Load local system customization file
-->
<!--(CONFD)-->
<!-- Font cache directory list -->
<cachedir>LOCAL_APPDATA_FONTCONFIG_CACHE</cachedir>
<cachedir prefix="xdg">fontconfig</cachedir>
<!-- the following element will be removed in the future -->
<cachedir>~/.fontconfig</cachedir>
<config>
<!--
Rescan configuration every 30 seconds when FcFontSetList is called
-->
<rescan>
<int>30</int>
</rescan>
</config>
</fontconfig>

View File

@@ -64,14 +64,12 @@ response request::execute() {
detail::check_init();
std::string str;
curl_easy_setopt(m_curl, CURLOPT_NOSIGNAL, 1L);
curl_easy_setopt(m_curl, CURLOPT_CUSTOMREQUEST, m_method);
curl_easy_setopt(m_curl, CURLOPT_URL, m_url.c_str());
curl_easy_setopt(m_curl, CURLOPT_FOLLOWLOCATION, 1);
curl_easy_setopt(m_curl, CURLOPT_WRITEFUNCTION, detail::curl_write_data_callback);
curl_easy_setopt(m_curl, CURLOPT_WRITEDATA, &str);
curl_easy_setopt(m_curl, CURLOPT_ERRORBUFFER, m_error_buf);
m_error_buf[0] = '\0';
if (m_header_list != nullptr)
curl_easy_setopt(m_curl, CURLOPT_HTTPHEADER, m_header_list);
@@ -83,7 +81,7 @@ response request::execute() {
return response;
}
long response_code = 0;
int response_code = 0;
curl_easy_getinfo(m_curl, CURLINFO_RESPONSE_CODE, &response_code);
auto response = detail::make_response(m_url, response_code);
@@ -97,10 +95,10 @@ void request::prepare() {
}
namespace detail {
size_t curl_write_data_callback(void *ptr, size_t size, size_t nmemb, void *userdata) {
const size_t n = size * nmemb;
static_cast<std::string*>(userdata)->append(static_cast<char*>(ptr), n);
return n;
size_t curl_write_data_callback(void *ptr, size_t size, size_t nmemb, std::string *outstr) {
auto new_length = size * nmemb;
outstr->append(reinterpret_cast<char *>(ptr), new_length);
return new_length;
}
response make_response(const std::string &url, int code) {

View File

@@ -121,7 +121,7 @@ private:
using response_type = response;
namespace detail {
size_t curl_write_data_callback(void *ptr, size_t size, size_t nmemb, void *userdata);
size_t curl_write_data_callback(void *ptr, size_t size, size_t nmemb, std::string *outstr);
response make_response(const std::string &url, int code);
void check_init();

View File

@@ -1,6 +1,5 @@
#include "imgmanager.hpp"
#include "util.hpp"
#include "abaddon.hpp"
ImageManager::ImageManager() {
m_cb_dispatcher.connect(sigc::mem_fun(*this, &ImageManager::RunCallbacks));
@@ -113,7 +112,7 @@ Glib::RefPtr<Gdk::Pixbuf> ImageManager::GetPlaceholder(int size) {
return m_pixs.at(name);
try {
auto buf = Gdk::Pixbuf::create_from_file(Abaddon::Get().GetResPath() + "/decamarks.png", size, size);
auto buf = Gdk::Pixbuf::create_from_file("res/decamarks.png", size, size);
m_pixs[name] = buf;
return buf;
} catch (std::exception &e) {

View File

@@ -1,140 +0,0 @@
#include "platform.hpp"
#include <string>
#include <fstream>
#include <filesystem>
#include <config.h>
using namespace std::literals::string_literals;
bool IsFolder(std::string_view path) {
std::error_code ec;
const auto status = std::filesystem::status(path, ec);
if (ec) return false;
return status.type() == std::filesystem::file_type::directory;
}
bool IsFile(std::string_view path) {
std::error_code ec;
const auto status = std::filesystem::status(path, ec);
if (ec) return false;
return status.type() == std::filesystem::file_type::regular;
}
#if defined(_WIN32) && defined(_MSC_VER)
#include <Windows.h>
#include <Shlwapi.h>
#include <ShlObj_core.h>
#include <pango/pangocairo.h>
#include <pango/pangofc-fontmap.h>
#pragma comment(lib, "Shlwapi.lib")
bool Platform::SetupFonts() {
using namespace std::string_literals;
char buf[MAX_PATH] { 0 };
GetCurrentDirectoryA(MAX_PATH, buf);
{
// thanks @WorkingRobot for da help :^))
std::ifstream template_stream(buf + "\\fonts\\fonts.template.conf"s);
std::ofstream conf_stream(buf + "\\fonts\\fonts.conf"s);
if (!template_stream.good()) {
printf("can't open fonts/fonts.template.conf\n");
return false;
}
if (!conf_stream.good()) {
printf("can't open write to fonts.conf\n");
return false;
}
std::string line;
while (std::getline(template_stream, line)) {
if (line == "<!--(CONFD)-->")
conf_stream << "<include ignore_missing=\"no\">" << (buf + "\\fonts\\conf.d"s) << "</include>";
else
conf_stream << line;
conf_stream << '\n';
}
}
auto fc = FcConfigCreate();
FcConfigSetCurrent(fc);
FcConfigParseAndLoad(fc, const_cast<FcChar8 *>(reinterpret_cast<const FcChar8 *>((buf + "\\fonts\\fonts.conf"s).c_str())), true);
FcConfigAppFontAddDir(fc, const_cast<FcChar8 *>(reinterpret_cast<const FcChar8 *>((buf + "\\fonts"s).c_str())));
char fonts_path[MAX_PATH];
if (SHGetFolderPathA(NULL, CSIDL_FONTS, NULL, SHGFP_TYPE_CURRENT, fonts_path) == S_OK) {
FcConfigAppFontAddDir(fc, reinterpret_cast<FcChar8 *>(fonts_path));
}
auto map = pango_cairo_font_map_new_for_font_type(CAIRO_FONT_TYPE_FT);
pango_fc_font_map_set_config(reinterpret_cast<PangoFcFontMap *>(map), fc);
pango_cairo_font_map_set_default(reinterpret_cast<PangoCairoFontMap *>(map));
return true;
}
#else
bool Platform::SetupFonts() {
return true;
}
#endif
#if defined(_WIN32)
std::string Platform::FindResourceFolder() {
return ".";
}
std::string Platform::FindConfigFile() {
const auto x = std::getenv("ABADDON_CONFIG");
if (x != nullptr)
return x;
return "./abaddon.ini";
}
#elif defined(__linux__)
std::string Platform::FindResourceFolder() {
static std::string found_path;
static bool found = false;
if (found) return found_path;
const static std::string home_path = std::getenv("HOME") + "/.local/share/abaddon"s;
for (const auto &path : { "."s, home_path, std::string(ABADDON_DEFAULT_RESOURCE_DIR) }) {
if (IsFolder(path + "/res") && IsFolder(path + "/css")) {
found_path = path;
found = true;
return found_path;
}
}
puts("cant find a resources folder, will try to load from cwd");
found_path = ".";
found = true;
return found_path;
}
std::string Platform::FindConfigFile() {
const auto x = std::getenv("ABADDON_CONFIG");
if (x != nullptr)
return x;
const auto home_path = std::string(std::getenv("HOME")) + "/.config/abaddon/abaddon.ini";
for (const auto path : { "./abaddon.ini"s, home_path }) {
if (IsFile(path)) return path;
}
puts("can't find configuration file!");
return "./abaddon.ini";
}
#else
std::string Platform::FindResourceFolder() {
puts("unknown OS, trying to load resources from cwd");
return ".";
}
std::string Platform::FindConfigFile() {
const auto x = std::getenv("ABADDON_CONFIG");
if (x != nullptr)
return x;
puts("unknown OS, trying to load config from cwd");
return "./abaddon.ini";
}
#endif

View File

@@ -1,8 +0,0 @@
#pragma once
#include <string>
namespace Platform {
bool SetupFonts();
std::string FindResourceFolder();
std::string FindConfigFile();
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

View File

@@ -54,24 +54,12 @@ bool SettingsManager::GetShowMemberListDiscriminators() const {
return GetSettingBool("gui", "member_list_discriminator", true);
}
bool SettingsManager::GetShowStockEmojis() const {
return GetSettingBool("gui", "stock_emojis", true);
}
bool SettingsManager::GetShowCustomEmojis() const {
return GetSettingBool("gui", "custom_emojis", true);
bool SettingsManager::GetShowEmojis() const {
return GetSettingBool("gui", "emojis", true);
}
std::string SettingsManager::GetLinkColor() const {
return GetSettingString("style", "linkcolor", "rgba(40, 200, 180, 255)");
}
std::string SettingsManager::GetChannelsExpanderColor() const {
return GetSettingString("style", "expandercolor", "rgba(255, 83, 112, 255)");
}
std::string SettingsManager::GetNSFWChannelColor() const {
return GetSettingString("style", "nsfwchannelcolor", "#ed6666");
return GetSettingString("misc", "linkcolor", "rgba(40, 200, 180, 255)");
}
int SettingsManager::GetCacheHTTPConcurrency() const {
@@ -83,7 +71,7 @@ bool SettingsManager::GetPrefetch() const {
}
std::string SettingsManager::GetMainCSS() const {
return GetSettingString("gui", "css", "main.css");
return GetSettingString("gui", "css", "./css/main.css");
}
bool SettingsManager::GetShowAnimations() const {
@@ -94,14 +82,7 @@ bool SettingsManager::GetShowOwnerCrown() const {
return GetSettingBool("gui", "owner_crown", true);
}
std::string SettingsManager::GetGatewayURL() const {
return GetSettingString("discord", "gateway", "wss://gateway.discord.gg/?v=9&encoding=json&compress=zlib-stream");
}
std::string SettingsManager::GetAPIBaseURL() const {
return GetSettingString("discord", "api_base", "https://discord.com/api/v9");
}
bool SettingsManager::GetAnimatedGuildHoverOnly() const {
return GetSettingBool("gui", "animated_guild_hover_only", true);
bool SettingsManager::GetUseMobileLayout() const {
// todo: see if there's some sort of preprocessor directive to default this to true on mobile platforms
return GetSettingBool("gui", "mobile", false);
}

Some files were not shown because too many files have changed in this diff Show More