From b65c09411a0e9e6240d3a7366328a7e63a91ac56 Mon Sep 17 00:00:00 2001 From: South <33138892+4hg@users.noreply.github.com> Date: Mon, 1 Nov 2021 18:56:47 -0500 Subject: fix typo (#41) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a79892e..16ce7b4 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ Current features: 1 - Abaddon tries its best to make Discord think it's a legitimate web client. Some of the things done to do this include: using a browser user agent, sending the same IDENTIFY message that the official web client does, using API v9 endpoints in all cases, and not using endpoints the web client does not normally use. There are still a few smaller inconsistencies, however. For example the web client sends lots of telemetry via the `/science` endpoint (uBlock origin stops this) as well as in the headers of all requests. **In any case,** you should use an official client for joining servers, sending new DMs, or managing your friends list if you are afraid of being caught in Discord's spam filters (unlikely). -2 - Unicode emojis are subtituted manually as opposed to rendered by GTK on non-Windows platforms. This can be changed with the `stock_emojis` setting as shown at the bottom of this README. A CBDT-based font using Twemoji is provided to allow GTK to render emojis natively on Windows. +2 - Unicode emojis are substituted manually as opposed to rendered by GTK on non-Windows platforms. This can be changed with the `stock_emojis` setting as shown at the bottom of this README. A CBDT-based font using Twemoji is provided to allow GTK to render emojis natively on Windows. 3 - There are some inconsistencies with thread state that might be encountered in some more uncommon cases, but they are the result of fundamental issues with Discord's thread implementation. -- cgit v1.2.3 From b26b2d4be0a8bdc64e3b1191f4ec7615cda27d07 Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Tue, 2 Nov 2021 21:01:54 -0400 Subject: remove channel row css classes from readme --- README.md | 6 ------ 1 file changed, 6 deletions(-) diff --git a/README.md b/README.md index a79892e..6d18d81 100644 --- a/README.md +++ b/README.md @@ -93,12 +93,6 @@ On Linux, `css` and `res` can also be loaded from `~/.local/share/abaddon` or `/ .app-popup - Additional class for `.app-window`s when the window is not the main window .channel-list - Container of the channel list -.channel-row - All rows within the channel container -.channel-row-channel - Only rows containing a channel -.channel-row-category - Only rows containing a category -.channel-row-guild - Only rows containing a guild -.channel-row-label - All labels within the channel container -.nsfw - Applied to channel row labels and their container for NSFW channels .messages - Container of user messages .message-container - The container which holds a user's messages -- cgit v1.2.3 From 1f445742b4fbc185fe0e24d9ed2478e4f7495f53 Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Thu, 4 Nov 2021 01:39:56 -0400 Subject: preserve channel list expansion and active channel (#36) also check getenv in platform --- abaddon.cpp | 53 +++++++++++++++++++++++++++++++++++++---- abaddon.hpp | 4 ++++ components/channels.cpp | 50 +++++++++++++++++++++++++++++++++++++++ components/channels.hpp | 7 +++++- platform.cpp | 63 +++++++++++++++++++++++++++++++------------------ platform.hpp | 1 + settings.cpp | 4 ++++ settings.hpp | 1 + util.cpp | 15 ++++++++++++ util.hpp | 4 ++++ 10 files changed, 173 insertions(+), 29 deletions(-) diff --git a/abaddon.cpp b/abaddon.cpp index 5bf6771..5373c6f 100644 --- a/abaddon.cpp +++ b/abaddon.cpp @@ -56,7 +56,7 @@ Abaddon::Abaddon() Abaddon::~Abaddon() { m_settings.Close(); - m_discord.Stop(); + StopDiscord(); } Abaddon &Abaddon::Get() { @@ -113,9 +113,7 @@ int Abaddon::StartGTK() { ActionReloadCSS(); - m_gtk_app->signal_shutdown().connect([&]() { - StopDiscord(); - }); + m_gtk_app->signal_shutdown().connect(sigc::mem_fun(*this, &Abaddon::StopDiscord), false); if (!m_settings.IsValid()) { Gtk::MessageDialog dlg(*m_main_window, "The settings file could not be created!", false, Gtk::MESSAGE_ERROR, Gtk::BUTTONS_OK, true); @@ -154,6 +152,7 @@ void Abaddon::StartDiscord() { void Abaddon::StopDiscord() { m_discord.Stop(); + SaveState(); } bool Abaddon::IsDiscordActive() const { @@ -176,6 +175,7 @@ const DiscordClient &Abaddon::GetDiscordClient() const { void Abaddon::DiscordOnReady() { m_main_window->UpdateComponents(); + LoadState(); } void Abaddon::DiscordOnMessageCreate(const Message &message) { @@ -365,6 +365,40 @@ void Abaddon::SetupUserMenu() { m_user_menu->show_all(); } +void Abaddon::SaveState() { + if (!m_settings.GetSaveState()) return; + + AbaddonApplicationState state; + state.ActiveChannel = m_main_window->GetChatActiveChannel(); + state.Expansion = m_main_window->GetChannelList()->GetExpansionState(); + + const auto path = GetStateCachePath(); + if (!util::IsFolder(path)) { + std::error_code ec; + std::filesystem::create_directories(path, ec); + } + + auto *fp = std::fopen(GetStateCachePath("/state.json").c_str(), "wb"); + if (fp == nullptr) return; + const auto s = nlohmann::json(state).dump(4); + std::fwrite(s.c_str(), 1, s.size(), fp); + std::fclose(fp); +} + +void Abaddon::LoadState() { + if (!m_settings.GetSaveState()) return; + + const auto data = ReadWholeFile(GetStateCachePath("/state.json")); + if (data.empty()) return; + try { + AbaddonApplicationState state = nlohmann::json::parse(data.begin(), data.end()); + m_main_window->GetChannelList()->UseExpansionState(state.Expansion); + ActionChannelOpened(state.ActiveChannel); + } catch (const std::exception &e) { + printf("failed to load application state: %s\n", e.what()); + } +} + void Abaddon::ManageHeapWindow(Gtk::Window *window) { window->signal_hide().connect([this, window]() { delete window; @@ -422,6 +456,11 @@ std::string Abaddon::GetResPath() { return path; } +std::string Abaddon::GetStateCachePath() { + const static auto path = Platform::FindStateCacheFolder() + "/state"; + return path; +} + std::string Abaddon::GetCSSPath(const std::string &path) { return GetCSSPath() + path; } @@ -430,6 +469,10 @@ std::string Abaddon::GetResPath(const std::string &path) { return GetResPath() + path; } +std::string Abaddon::GetStateCachePath(const std::string &path) { + return GetStateCachePath() + path; +} + void Abaddon::ActionConnect() { if (!m_discord.IsStarted()) StartDiscord(); @@ -461,7 +504,7 @@ void Abaddon::ActionJoinGuildDialog() { } void Abaddon::ActionChannelOpened(Snowflake id) { - if (id == m_main_window->GetChatActiveChannel()) return; + if (!id.IsValid() || id == m_main_window->GetChatActiveChannel()) return; m_main_window->GetChatWindow()->SetTopic(""); diff --git a/abaddon.hpp b/abaddon.hpp index 92485fb..0fb4f1f 100644 --- a/abaddon.hpp +++ b/abaddon.hpp @@ -84,13 +84,17 @@ public: static std::string GetCSSPath(); static std::string GetResPath(); + static std::string GetStateCachePath(); static std::string GetCSSPath(const std::string &path); static std::string GetResPath(const std::string &path); + static std::string GetStateCachePath(const std::string &path); protected: void ShowGuildVerificationGateDialog(Snowflake guild_id); void SetupUserMenu(); + void SaveState(); + void LoadState(); Snowflake m_shown_user_menu_id; Snowflake m_shown_user_menu_guild_id; diff --git a/components/channels.cpp b/components/channels.cpp index ac11a0c..621d7dc 100644 --- a/components/channels.cpp +++ b/components/channels.cpp @@ -380,6 +380,56 @@ void ChannelList::SetActiveChannel(Snowflake id) { } } +void ChannelList::UseExpansionState(const ExpansionStateRoot &root) { + auto recurse = [this](auto &self, const ExpansionStateRoot &root) -> void { + // and these are only channels + for (const auto &[id, state] : root.Children) { + if (const auto iter = GetIteratorForChannelFromID(id)) { + if (state.IsExpanded) + m_view.expand_row(m_model->get_path(iter), false); + else + m_view.collapse_row(m_model->get_path(iter)); + } + + self(self, state.Children); + } + }; + + // top level is guild + for (const auto &[id, state] : root.Children) { + if (const auto iter = GetIteratorForGuildFromID(id)) { + if (state.IsExpanded) + m_view.expand_row(m_model->get_path(iter), false); + else + m_view.collapse_row(m_model->get_path(iter)); + } + + recurse(recurse, state.Children); + } +} + +ExpansionStateRoot ChannelList::GetExpansionState() const { + ExpansionStateRoot r; + + auto recurse = [this](auto &self, const Gtk::TreeRow &row) -> ExpansionState { + ExpansionState r; + + r.IsExpanded = row[m_columns.m_expanded]; + for (const auto &child : row.children()) + r.Children.Children[static_cast(child[m_columns.m_id])] = self(self, child); + + return r; + }; + + for (const auto &child : m_model->children()) { + const auto id = static_cast(child[m_columns.m_id]); + if (static_cast(id) == 0ULL) continue; // dont save DM header + r.Children[id] = recurse(recurse, child); + } + + return r; +} + Gtk::TreeModel::iterator ChannelList::AddGuild(const GuildData &guild) { auto &discord = Abaddon::Get().GetDiscordClient(); auto &img = Abaddon::Get().GetImageManager(); diff --git a/components/channels.hpp b/components/channels.hpp index 5cfa9c3..1faf367 100644 --- a/components/channels.hpp +++ b/components/channels.hpp @@ -6,7 +6,8 @@ #include #include #include -#include "../discord/discord.hpp" +#include "discord/discord.hpp" +#include "state.hpp" constexpr static int GuildIconSize = 24; constexpr static int DMIconSize = 20; @@ -134,6 +135,10 @@ public: void UpdateListing(); void SetActiveChannel(Snowflake id); + // channel list should be populated when this is called + void UseExpansionState(const ExpansionStateRoot &state); + ExpansionStateRoot GetExpansionState() const; + protected: void UpdateNewGuild(const GuildData &guild); void UpdateRemoveGuild(Snowflake id); diff --git a/platform.cpp b/platform.cpp index df822cd..ce744d7 100644 --- a/platform.cpp +++ b/platform.cpp @@ -1,4 +1,5 @@ #include "platform.hpp" +#include "util.hpp" #include #include #include @@ -6,20 +7,6 @@ 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 #include @@ -90,19 +77,26 @@ std::string Platform::FindConfigFile() { return "./abaddon.ini"; } +std::string Platform::FindStateCacheFolder() { + return "."; +} + #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; + const auto home_env = std::getenv("HOME"); + if (home_env != nullptr) { + const static std::string home_path = home_env + "/.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; + for (const auto &path : { "."s, home_path, std::string(ABADDON_DEFAULT_RESOURCE_DIR) }) { + if (util::IsFolder(path + "/res") && util::IsFolder(path + "/css")) { + found_path = path; + found = true; + return found_path; + } } } @@ -117,13 +111,31 @@ std::string Platform::FindConfigFile() { 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; + const auto home_env = std::getenv("HOME"); + if (home_env != nullptr) { + const auto home_path = home_env + "/.config/abaddon/abaddon.ini"s; + for (auto path : { "./abaddon.ini"s, home_path }) { + if (util::IsFile(path)) return path; + } } puts("can't find configuration file!"); return "./abaddon.ini"; } + +std::string Platform::FindStateCacheFolder() { + const auto home_env = std::getenv("HOME"); + if (home_env != nullptr) { + auto home_path = home_env + "/.cache/abaddon"s; + std::error_code ec; + if (!util::IsFolder(home_path)) + std::filesystem::create_directories(home_path, ec); + if (util::IsFolder(home_path)) + return home_path; + } + puts("can't find cache folder!"); + return "."; +} + #else std::string Platform::FindResourceFolder() { puts("unknown OS, trying to load resources from cwd"); @@ -137,4 +149,9 @@ std::string Platform::FindConfigFile() { puts("unknown OS, trying to load config from cwd"); return "./abaddon.ini"; } + +std::string Platform::FindStateCacheFolder() { + puts("unknown OS, setting state cache folder to cwd"); + return "."; +} #endif diff --git a/platform.hpp b/platform.hpp index cde1cb4..e321d4e 100644 --- a/platform.hpp +++ b/platform.hpp @@ -5,4 +5,5 @@ namespace Platform { bool SetupFonts(); std::string FindResourceFolder(); std::string FindConfigFile(); +std::string FindStateCacheFolder(); } diff --git a/settings.cpp b/settings.cpp index f753cc1..0a7dbb7 100644 --- a/settings.cpp +++ b/settings.cpp @@ -109,3 +109,7 @@ std::string SettingsManager::GetAPIBaseURL() const { bool SettingsManager::GetAnimatedGuildHoverOnly() const { return GetSettingBool("gui", "animated_guild_hover_only", true); } + +bool SettingsManager::GetSaveState() const { + return GetSettingBool("gui", "save_state", true); +} diff --git a/settings.hpp b/settings.hpp index 2a9fb9c..3fff593 100644 --- a/settings.hpp +++ b/settings.hpp @@ -23,6 +23,7 @@ public: std::string GetGatewayURL() const; std::string GetAPIBaseURL() const; bool GetAnimatedGuildHoverOnly() const; + bool GetSaveState() const; // i would like to use Gtk::StyleProperty for this, but it will not work on windows // #1 it's missing from the project files for the version used by vcpkg diff --git a/util.cpp b/util.cpp index 7b686ca..34ca6d4 100644 --- a/util.cpp +++ b/util.cpp @@ -1,4 +1,5 @@ #include "util.hpp" +#include Semaphore::Semaphore(int count) : m_count(count) {} @@ -200,3 +201,17 @@ void AddPointerCursor(Gtk::Widget &widget) { window->set_cursor(cursor); }); } + +bool util::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 util::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; +} diff --git a/util.hpp b/util.hpp index 23b619b..feaf08d 100644 --- a/util.hpp +++ b/util.hpp @@ -21,6 +21,10 @@ struct is_optional : ::std::false_type {}; template struct is_optional<::std::optional> : ::std::true_type {}; + +bool IsFolder(std::string_view path); + +bool IsFile(std::string_view path); } // namespace util class Semaphore { -- cgit v1.2.3 From c40b8a412272a7a407764166917cf3c61e7dc47f Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Thu, 4 Nov 2021 01:48:13 -0400 Subject: add files that should have been pushed with last commit oops --- state.cpp | 37 +++++++++++++++++++++++++++++++++++++ state.hpp | 27 +++++++++++++++++++++++++++ 2 files changed, 64 insertions(+) create mode 100644 state.cpp create mode 100644 state.hpp diff --git a/state.cpp b/state.cpp new file mode 100644 index 0000000..043d181 --- /dev/null +++ b/state.cpp @@ -0,0 +1,37 @@ +#include "state.hpp" + +void to_json(nlohmann::json &j, const ExpansionStateRoot &m) { + if (m.Children.empty()) { + j = nlohmann::json::object(); + } else { + for (const auto &[id, state] : m.Children) + j[std::to_string(id)] = state; + } +} + +void from_json(const nlohmann::json &j, ExpansionStateRoot &m) { + for (const auto &[key, value] : j.items()) + m.Children[key] = value; +} + +void to_json(nlohmann::json &j, const ExpansionState &m) { + j["e"] = m.IsExpanded; + j["c"] = m.Children; +} + +void from_json(const nlohmann::json &j, ExpansionState &m) { + j.at("e").get_to(m.IsExpanded); + j.at("c").get_to(m.Children); +} + +void to_json(nlohmann::json &j, const AbaddonApplicationState &m) { + j["active_channel"] = m.ActiveChannel; + j["expansion"] = m.Expansion; +} + +void from_json(const nlohmann::json &j, AbaddonApplicationState &m) { + if (j.contains("active_channel")) + j.at("active_channel").get_to(m.ActiveChannel); + if (j.contains("expansion")) + j.at("expansion").get_to(m.Expansion); +} diff --git a/state.hpp b/state.hpp new file mode 100644 index 0000000..230808f --- /dev/null +++ b/state.hpp @@ -0,0 +1,27 @@ +#include +#include +#include "discord/snowflake.hpp" + +struct ExpansionState; +struct ExpansionStateRoot { + std::map Children; + + friend void to_json(nlohmann::json &j, const ExpansionStateRoot &m); + friend void from_json(const nlohmann::json &j, ExpansionStateRoot &m); +}; + +struct ExpansionState { + bool IsExpanded; + ExpansionStateRoot Children; + + friend void to_json(nlohmann::json &j, const ExpansionState &m); + friend void from_json(const nlohmann::json &j, ExpansionState &m); +}; + +struct AbaddonApplicationState { + Snowflake ActiveChannel; + ExpansionStateRoot Expansion; + + friend void to_json(nlohmann::json &j, const AbaddonApplicationState &m); + friend void from_json(const nlohmann::json &j, AbaddonApplicationState &m); +}; -- cgit v1.2.3 From 43795f4c875472d5d12276e32b0c9d1bd5788912 Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Sat, 13 Nov 2021 00:25:54 -0500 Subject: dont call StopDiscord in destructor since the destructor is run during static destruction it can cause issues it's redundant anyways as StopDiscord is slotted into Gtk::Application::signal_shutdown --- abaddon.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/abaddon.cpp b/abaddon.cpp index 5373c6f..a3f686f 100644 --- a/abaddon.cpp +++ b/abaddon.cpp @@ -56,7 +56,6 @@ Abaddon::Abaddon() Abaddon::~Abaddon() { m_settings.Close(); - StopDiscord(); } Abaddon &Abaddon::Get() { -- cgit v1.2.3 From 108002248c0739078302e00d5ca224a44b93ea56 Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Sat, 13 Nov 2021 03:09:17 -0500 Subject: presence of ThreadMember means user added to private thread --- discord/discord.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/discord/discord.cpp b/discord/discord.cpp index c494145..b4a5f92 100644 --- a/discord/discord.cpp +++ b/discord/discord.cpp @@ -1762,6 +1762,9 @@ void DiscordClient::HandleGatewayThreadCreate(const GatewayMessage &msg) { ThreadCreateData data = msg.Data; m_store.SetChannel(data.Channel.ID, data.Channel); m_signal_thread_create.emit(data.Channel); + if (data.Channel.ThreadMember.has_value()) { + m_signal_added_to_thread.emit(data.Channel.ID); + } } void DiscordClient::HandleGatewayThreadDelete(const GatewayMessage &msg) { -- cgit v1.2.3