Compare commits
33 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8c72d4c18d | ||
|
|
0da913cd4a | ||
|
|
fb3d69c5e7 | ||
|
|
c40208ac66 | ||
|
|
069c22e9cd | ||
|
|
8f30bb33a3 | ||
|
|
4326c5e29b | ||
|
|
a51a54bc59 | ||
|
|
d88079000a | ||
|
|
574cbc35d8 | ||
|
|
fc76a15c46 | ||
|
|
9d21df8e1b | ||
|
|
1da2c57376 | ||
|
|
409af292af | ||
|
|
108002248c | ||
|
|
43795f4c87 | ||
|
|
2257ff9798 | ||
|
|
c40b8a4122 | ||
|
|
1f445742b4 | ||
|
|
d629846220 | ||
|
|
b26b2d4be0 | ||
|
|
b65c09411a | ||
|
|
a51b813f70 | ||
|
|
1078d94b73 | ||
|
|
12c105623c | ||
|
|
d950460e14 | ||
|
|
2c2686946d | ||
|
|
9175da2cf0 | ||
|
|
14d025a342 | ||
|
|
c98b62caca | ||
|
|
d425997c26 | ||
|
|
84b1b62e0e | ||
|
|
b248e0ae9a |
28
.github/workflows/ci.yml
vendored
@@ -15,17 +15,17 @@ jobs:
|
||||
submodules: true
|
||||
|
||||
- name: Fetch CMake
|
||||
uses: lukka/get-cmake@latest
|
||||
uses: lukka/get-cmake@v3.21.2
|
||||
|
||||
- name: Fetch dependencies
|
||||
uses: lukka/run-vcpkg@main
|
||||
uses: lukka/run-vcpkg@v7
|
||||
with:
|
||||
vcpkgArguments: gtkmm nlohmann-json zlib sqlite3 glibmm openssl ixwebsocket curl
|
||||
vcpkgDirectory: ${{ github.workspace }}/ci/vcpkg/
|
||||
vcpkgTriplet: x64-windows
|
||||
|
||||
- name: Build
|
||||
uses: lukka/run-cmake@main
|
||||
uses: lukka/run-cmake@v3
|
||||
with:
|
||||
useVcpkgToolchainFile: true
|
||||
vcpkgTriplet: x64-windows
|
||||
@@ -41,9 +41,9 @@ jobs:
|
||||
del /f /s /q "${{ runner.workspace }}\build\.ninja_log"
|
||||
del /f /s /q "${{ runner.workspace }}\build\abaddon.ilk"
|
||||
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"
|
||||
xcopy /E /I "${{ github.workspace }}\res\css" "${{ runner.workspace }}\build\css"
|
||||
xcopy /E /I "${{ github.workspace }}\res\res" "${{ runner.workspace }}\build\res"
|
||||
xcopy /E /I "${{ github.workspace }}\res\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"
|
||||
@@ -66,7 +66,7 @@ jobs:
|
||||
submodules: true
|
||||
|
||||
- name: Fetch CMake
|
||||
uses: lukka/get-cmake@latest
|
||||
uses: lukka/get-cmake@v3.21.2
|
||||
|
||||
- name: Fetch dependencies
|
||||
run: |
|
||||
@@ -74,7 +74,7 @@ jobs:
|
||||
brew install nlohmann-json
|
||||
|
||||
- name: Build
|
||||
uses: lukka/run-cmake@main
|
||||
uses: lukka/run-cmake@v3
|
||||
with:
|
||||
buildDirectory: ${{ runner.workspace }}/build
|
||||
cmakeBuildType: ${{ matrix.buildtype }}
|
||||
@@ -83,8 +83,8 @@ jobs:
|
||||
run: |
|
||||
mkdir "${{ runner.workspace }}/artifactdir"
|
||||
cp "${{runner.workspace}}/build/abaddon" "${{ runner.workspace }}/artifactdir/abaddon"
|
||||
cp -r "${{ github.workspace }}/css" "${{ runner.workspace }}/artifactdir/css"
|
||||
cp -r "${{ github.workspace }}/res" "${{ runner.workspace }}/artifactdir/res"
|
||||
cp -r "${{ github.workspace }}/res/css" "${{ runner.workspace }}/artifactdir/css"
|
||||
cp -r "${{ github.workspace }}/res/res" "${{ runner.workspace }}/artifactdir/res"
|
||||
|
||||
- name: Upload build
|
||||
uses: actions/upload-artifact@v2
|
||||
@@ -104,7 +104,7 @@ jobs:
|
||||
submodules: true
|
||||
|
||||
- name: Fetch CMake
|
||||
uses: lukka/get-cmake@latest
|
||||
uses: lukka/get-cmake@v3.21.2
|
||||
|
||||
- name: Fetch dependencies
|
||||
run: |
|
||||
@@ -123,7 +123,7 @@ jobs:
|
||||
sudo apt-get install libcurl4-gnutls-dev
|
||||
|
||||
- name: Build
|
||||
uses: lukka/run-cmake@main
|
||||
uses: lukka/run-cmake@v3
|
||||
env:
|
||||
CC: gcc-9
|
||||
CXX: g++-9
|
||||
@@ -136,8 +136,8 @@ jobs:
|
||||
run: |
|
||||
mkdir "${{ runner.workspace }}/artifactdir"
|
||||
cp "${{runner.workspace}}/build/abaddon" "${{ runner.workspace }}/artifactdir/abaddon"
|
||||
cp -r "${{ github.workspace }}/css" "${{ runner.workspace }}/artifactdir/css"
|
||||
cp -r "${{ github.workspace }}/res" "${{ runner.workspace }}/artifactdir/res"
|
||||
cp -r "${{ github.workspace }}/res/css" "${{ runner.workspace }}/artifactdir/css"
|
||||
cp -r "${{ github.workspace }}/res/res" "${{ runner.workspace }}/artifactdir/res"
|
||||
|
||||
- name: Upload build
|
||||
uses: actions/upload-artifact@v2
|
||||
|
||||
9
.gitmodules
vendored
@@ -1,15 +1,12 @@
|
||||
[submodule "vcpkg"]
|
||||
path = ci/vcpkg
|
||||
url = https://github.com/microsoft/vcpkg/
|
||||
[submodule "thirdparty/simpleini"]
|
||||
path = thirdparty/simpleini
|
||||
url = https://github.com/brofield/simpleini
|
||||
[submodule "thirdparty/IXWebSocket"]
|
||||
path = thirdparty/IXWebSocket
|
||||
url = https://github.com/machinezone/ixwebsocket
|
||||
[submodule "ci/vcpkg"]
|
||||
path = ci/vcpkg
|
||||
url = https://github.com/microsoft/vcpkg
|
||||
[submodule "ci/gtk-for-windows"]
|
||||
path = ci/gtk-for-windows
|
||||
url = https://github.com/tschoonj/GTK-for-Windows-Runtime-Environment-Installer
|
||||
[submodule "subprojects/ixwebsocket"]
|
||||
path = subprojects/ixwebsocket
|
||||
url = https://github.com/machinezone/ixwebsocket
|
||||
|
||||
@@ -18,17 +18,10 @@ set(USE_OPEN_SSL TRUE)
|
||||
find_package(IXWebSocket QUIET)
|
||||
if (NOT IXWebSocket_FOUND)
|
||||
message("ixwebsocket was not found and will be included as a submodule")
|
||||
add_subdirectory(thirdparty/IXWebSocket)
|
||||
add_subdirectory(subprojects/ixwebsocket)
|
||||
include_directories(IXWEBSOCKET_INCLUDE_DIRS)
|
||||
endif()
|
||||
|
||||
add_compile_definitions(SI_NO_CONVERSION) # only CSimpleIniA is used
|
||||
find_package(simpleini QUIET)
|
||||
if (NOT simpleini_FOUND)
|
||||
message("simpleini was not found and will be included as a submodule")
|
||||
include_directories(thirdparty/simpleini)
|
||||
endif()
|
||||
|
||||
if(MINGW OR WIN32)
|
||||
link_libraries(ws2_32)
|
||||
endif()
|
||||
@@ -41,28 +34,16 @@ if(WIN32)
|
||||
link_libraries(${Fontconfig_LIBRARIES})
|
||||
endif()
|
||||
|
||||
configure_file(${PROJECT_SOURCE_DIR}/config.h.in ${PROJECT_BINARY_DIR}/config.h)
|
||||
configure_file(${PROJECT_SOURCE_DIR}/src/config.h.in ${PROJECT_BINARY_DIR}/config.h)
|
||||
|
||||
file(GLOB ABADDON_SOURCES
|
||||
"*.h"
|
||||
"*.hpp"
|
||||
"*.cpp"
|
||||
"discord/*.hpp"
|
||||
"discord/*.cpp"
|
||||
"components/*.hpp"
|
||||
"components/*.cpp"
|
||||
"windows/*.hpp"
|
||||
"windows/*.cpp"
|
||||
"windows/guildsettings/*.hpp"
|
||||
"windows/guildsettings/*.cpp"
|
||||
"windows/profile/*.hpp"
|
||||
"windows/profile/*.cpp"
|
||||
"dialogs/*.hpp"
|
||||
"dialogs/*.cpp"
|
||||
file(GLOB_RECURSE ABADDON_SOURCES
|
||||
"src/*.h"
|
||||
"src/*.hpp"
|
||||
"src/*.cpp"
|
||||
)
|
||||
|
||||
add_executable(abaddon ${ABADDON_SOURCES})
|
||||
target_include_directories(abaddon PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
target_include_directories(abaddon PUBLIC ${PROJECT_SOURCE_DIR}/src)
|
||||
target_include_directories(abaddon PUBLIC ${PROJECT_BINARY_DIR})
|
||||
target_include_directories(abaddon PUBLIC ${GTKMM_INCLUDE_DIRS})
|
||||
target_include_directories(abaddon PUBLIC ${ZLIB_INCLUDE_DIRS})
|
||||
|
||||
23
README.md
@@ -4,7 +4,7 @@ Alternative Discord client made in C++ with GTK
|
||||
|
||||
<img src="/.readme/s3.png">
|
||||
|
||||
[😎Discord Server](https://discord.gg/wkCU3vuzG5)
|
||||
<a href="https://discord.gg/wkCU3vuzG5"><img src="https://discord.com/api/guilds/858156817711890443/widget.png?style=shield"></a>
|
||||
|
||||
Current features:
|
||||
* Not Electron
|
||||
@@ -25,14 +25,14 @@ 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.
|
||||
|
||||
### Building manually (recommended if not on Windows):
|
||||
#### Windows:
|
||||
1. `git clone https://github.com/uowuo/abaddon && cd abaddon`
|
||||
2. `vcpkg install gtkmm:x64-windows nlohmann-json:x64-windows ixwebsocket:x64-windows zlib:x64-windows simpleini:x64-windows sqlite3:x64-windows openssl:x64-windows curl:x64-windows`
|
||||
2. `vcpkg install gtkmm:x64-windows nlohmann-json:x64-windows ixwebsocket:x64-windows zlib:x64-windows sqlite3:x64-windows openssl:x64-windows curl:x64-windows`
|
||||
3. `mkdir build && cd build`
|
||||
4. `cmake -G"Visual Studio 16 2019" -A x64 -DCMAKE_TOOLCHAIN_FILE=c:\path\to\vcpkg\scripts\buildsystems\vcpkg.cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo -DVCPKG_TARGET_TRIPLET=x64-windows ..`
|
||||
5. Build with Visual Studio
|
||||
@@ -75,7 +75,6 @@ On Linux, `css` and `res` can also be loaded from `~/.local/share/abaddon` or `/
|
||||
* [IXWebSocket](https://github.com/machinezone/IXWebSocket)
|
||||
* [libcurl](https://curl.se/)
|
||||
* [zlib](https://zlib.net/)
|
||||
* [simpleini](https://github.com/brofield/simpleini)
|
||||
* [SQLite3](https://www.sqlite.org/index.html)
|
||||
|
||||
### TODO:
|
||||
@@ -93,12 +92,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
|
||||
@@ -184,18 +177,24 @@ Used in profile popup:
|
||||
|
||||
### Settings
|
||||
Settings are configured (for now) by editing abaddon.ini
|
||||
The format is similar to the standard Windows ini format **except**:
|
||||
* `#` is used to begin comments as opposed to `;`
|
||||
* Section and key names are case-sensitive
|
||||
|
||||
You should edit these while the client is closed even though there's an option to reload while running
|
||||
This listing is organized by section.
|
||||
For example, memory_db would be set by adding `memory_db = true` under the line `[discord]`
|
||||
|
||||
#### discord
|
||||
* 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
|
||||
* memory_db (true or false, default false) - if true, Discord data will be kept in memory as opposed to on disk
|
||||
* token (string) - Discord token used to login, this can be set from the menu
|
||||
* prefetch (true or false, default false) - if true, new messages will cause the avatar and image attachments to be automatically downloaded
|
||||
|
||||
#### http
|
||||
* user_agent (string) - sets the user-agent to use in HTTP requests to the Discord API (not including media/images)
|
||||
* concurrent (int, default 10) - how many images can be concurrently retrieved
|
||||
* concurrent (int, default 20) - how many images can be concurrently retrieved
|
||||
|
||||
#### gui
|
||||
* member_list_discriminator (true or false, default true) - show user discriminators in the member list
|
||||
@@ -205,8 +204,6 @@ For example, memory_db would be set by adding `memory_db = true` under the line
|
||||
* 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
|
||||
* linkcolor (string) - color to use for links in messages
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
set(simpleini_LIBRARY_NAME simpleini)
|
||||
|
||||
find_path(simpleini_INCLUDE_DIR
|
||||
NAMES SimpleIni.h
|
||||
HINTS /usr/include
|
||||
/usr/local/include
|
||||
/opt/local/include
|
||||
PATH_SUFFIXES ${simpleini_LIBRARY_NAME})
|
||||
|
||||
include(FindPackageHandleStandardArgs)
|
||||
find_package_handle_standard_args(simpleini
|
||||
REQUIRED_VARS
|
||||
simpleini_INCLUDE_DIR)
|
||||
|
||||
mark_as_advanced(simpleini_INCLUDE_DIR)
|
||||
1507
discord/store.cpp
@@ -1,172 +0,0 @@
|
||||
#pragma once
|
||||
#include "../util.hpp"
|
||||
#include "objects.hpp"
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <mutex>
|
||||
#include <filesystem>
|
||||
#include <sqlite3.h>
|
||||
|
||||
#ifdef GetMessage // fuck you windows.h
|
||||
#undef GetMessage
|
||||
#endif
|
||||
|
||||
class Store {
|
||||
public:
|
||||
Store(bool mem_store = false);
|
||||
~Store();
|
||||
|
||||
bool IsValid() const;
|
||||
|
||||
void SetUser(Snowflake id, const UserData &user);
|
||||
void SetChannel(Snowflake id, const ChannelData &chan);
|
||||
void SetGuild(Snowflake id, const GuildData &guild);
|
||||
void SetRole(Snowflake id, const RoleData &role);
|
||||
void SetMessage(Snowflake id, const Message &message);
|
||||
void SetGuildMember(Snowflake guild_id, Snowflake user_id, const GuildMember &data);
|
||||
void SetPermissionOverwrite(Snowflake channel_id, Snowflake id, const PermissionOverwrite &perm);
|
||||
void SetEmoji(Snowflake id, const EmojiData &emoji);
|
||||
void SetBan(Snowflake guild_id, Snowflake user_id, const BanData &ban);
|
||||
|
||||
// slap const on everything even tho its not *really* const
|
||||
|
||||
std::optional<ChannelData> GetChannel(Snowflake id) const;
|
||||
std::optional<EmojiData> GetEmoji(Snowflake id) const;
|
||||
std::optional<GuildData> GetGuild(Snowflake id) const;
|
||||
std::optional<GuildMember> GetGuildMember(Snowflake guild_id, Snowflake user_id) const;
|
||||
std::optional<Message> GetMessage(Snowflake id) const;
|
||||
std::optional<PermissionOverwrite> GetPermissionOverwrite(Snowflake channel_id, Snowflake id) const;
|
||||
std::optional<RoleData> GetRole(Snowflake id) const;
|
||||
std::optional<UserData> GetUser(Snowflake id) const;
|
||||
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);
|
||||
|
||||
using users_type = std::unordered_map<Snowflake, UserData>;
|
||||
using channels_type = std::unordered_map<Snowflake, ChannelData>;
|
||||
using guilds_type = std::unordered_map<Snowflake, GuildData>;
|
||||
using roles_type = std::unordered_map<Snowflake, RoleData>;
|
||||
using messages_type = std::unordered_map<Snowflake, Message>;
|
||||
using members_type = std::unordered_map<Snowflake, std::unordered_map<Snowflake, GuildMember>>; // [guild][user]
|
||||
using permission_overwrites_type = std::unordered_map<Snowflake, std::unordered_map<Snowflake, PermissionOverwrite>>; // [channel][user/role]
|
||||
using emojis_type = std::unordered_map<Snowflake, EmojiData>;
|
||||
|
||||
const std::unordered_set<Snowflake> &GetChannels() const;
|
||||
const std::unordered_set<Snowflake> &GetGuilds() const;
|
||||
|
||||
void ClearAll();
|
||||
|
||||
void BeginTransaction();
|
||||
void EndTransaction();
|
||||
|
||||
private:
|
||||
Message GetMessageBound(sqlite3_stmt *stmt) const;
|
||||
|
||||
void SetMessageInteractionPair(Snowflake message_id, const MessageInteractionData &interaction);
|
||||
|
||||
std::unordered_set<Snowflake> m_channels;
|
||||
std::unordered_set<Snowflake> m_guilds;
|
||||
|
||||
bool CreateTables();
|
||||
bool CreateStatements();
|
||||
void Cleanup();
|
||||
|
||||
template<typename T>
|
||||
void Bind(sqlite3_stmt *stmt, int index, const std::optional<T> &opt) const;
|
||||
|
||||
template<typename T>
|
||||
typename std::enable_if<std::is_enum<T>::value, void>::type
|
||||
Bind(sqlite3_stmt *stmt, int index, T val) const;
|
||||
|
||||
void Bind(sqlite3_stmt *stmt, int index, int num) const;
|
||||
void Bind(sqlite3_stmt *stmt, int index, uint64_t num) const;
|
||||
void Bind(sqlite3_stmt *stmt, int index, const std::string &str) const;
|
||||
void Bind(sqlite3_stmt *stmt, int index, bool val) const;
|
||||
void Bind(sqlite3_stmt *stmt, int index, std::nullptr_t) const;
|
||||
bool RunInsert(sqlite3_stmt *stmt);
|
||||
bool FetchOne(sqlite3_stmt *stmt) const;
|
||||
|
||||
template<typename T>
|
||||
void Get(sqlite3_stmt *stmt, int index, std::optional<T> &out) const;
|
||||
|
||||
template<typename T>
|
||||
typename std::enable_if<std::is_enum<T>::value, void>::type
|
||||
Get(sqlite3_stmt *stmt, int index, T &out) const;
|
||||
|
||||
void Get(sqlite3_stmt *stmt, int index, int &out) const;
|
||||
void Get(sqlite3_stmt *stmt, int index, uint64_t &out) const;
|
||||
void Get(sqlite3_stmt *stmt, int index, std::string &out) const;
|
||||
void Get(sqlite3_stmt *stmt, int index, bool &out) const;
|
||||
void Get(sqlite3_stmt *stmt, int index, Snowflake &out) const;
|
||||
bool IsNull(sqlite3_stmt *stmt, int index) const;
|
||||
void Reset(sqlite3_stmt *stmt) const;
|
||||
|
||||
std::filesystem::path m_db_path;
|
||||
mutable sqlite3 *m_db;
|
||||
mutable int m_db_err;
|
||||
mutable sqlite3_stmt *m_set_user_stmt;
|
||||
mutable sqlite3_stmt *m_get_user_stmt;
|
||||
mutable sqlite3_stmt *m_set_perm_stmt;
|
||||
mutable sqlite3_stmt *m_get_perm_stmt;
|
||||
mutable sqlite3_stmt *m_set_msg_stmt;
|
||||
mutable sqlite3_stmt *m_get_msg_stmt;
|
||||
mutable sqlite3_stmt *m_set_role_stmt;
|
||||
mutable sqlite3_stmt *m_get_role_stmt;
|
||||
mutable sqlite3_stmt *m_set_emote_stmt;
|
||||
mutable sqlite3_stmt *m_get_emote_stmt;
|
||||
mutable sqlite3_stmt *m_set_member_stmt;
|
||||
mutable sqlite3_stmt *m_get_member_stmt;
|
||||
mutable sqlite3_stmt *m_set_guild_stmt;
|
||||
mutable sqlite3_stmt *m_get_guild_stmt;
|
||||
mutable sqlite3_stmt *m_set_chan_stmt;
|
||||
mutable sqlite3_stmt *m_get_chan_stmt;
|
||||
mutable sqlite3_stmt *m_set_ban_stmt;
|
||||
mutable sqlite3_stmt *m_get_ban_stmt;
|
||||
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>
|
||||
inline void Store::Bind(sqlite3_stmt *stmt, int index, const std::optional<T> &opt) const {
|
||||
if (opt.has_value())
|
||||
Bind(stmt, index, *opt);
|
||||
else
|
||||
sqlite3_bind_null(stmt, index);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
inline typename std::enable_if<std::is_enum<T>::value, void>::type
|
||||
Store::Bind(sqlite3_stmt *stmt, int index, T val) const {
|
||||
Bind(stmt, index, static_cast<typename std::underlying_type<T>::type>(val));
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
inline void Store::Get(sqlite3_stmt *stmt, int index, std::optional<T> &out) const {
|
||||
if (sqlite3_column_type(stmt, index) == SQLITE_NULL)
|
||||
out = std::nullopt;
|
||||
else {
|
||||
T v;
|
||||
Get(stmt, index, v);
|
||||
out = std::optional<T>(v);
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
inline typename std::enable_if<std::is_enum<T>::value, void>::type
|
||||
Store::Get(sqlite3_stmt *stmt, int index, T &out) const {
|
||||
out = static_cast<T>(sqlite3_column_int(stmt, index));
|
||||
}
|
||||
|
Before Width: | Height: | Size: 7.9 KiB After Width: | Height: | Size: 7.9 KiB |
|
Before Width: | Height: | Size: 7.0 KiB After Width: | Height: | Size: 7.0 KiB |
|
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 48 KiB |
|
Before Width: | Height: | Size: 3.4 KiB After Width: | Height: | Size: 3.4 KiB |
|
Before Width: | Height: | Size: 6.5 KiB After Width: | Height: | Size: 6.5 KiB |
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 6.6 KiB After Width: | Height: | Size: 6.6 KiB |
|
Before Width: | Height: | Size: 6.9 KiB After Width: | Height: | Size: 6.9 KiB |
|
Before Width: | Height: | Size: 8.2 KiB After Width: | Height: | Size: 8.2 KiB |
|
Before Width: | Height: | Size: 8.8 KiB After Width: | Height: | Size: 8.8 KiB |
|
Before Width: | Height: | Size: 7.0 KiB After Width: | Height: | Size: 7.0 KiB |
|
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 2.3 KiB |
|
Before Width: | Height: | Size: 6.5 KiB After Width: | Height: | Size: 6.5 KiB |
|
Before Width: | Height: | Size: 4.3 KiB After Width: | Height: | Size: 4.3 KiB |
|
Before Width: | Height: | Size: 7.1 KiB After Width: | Height: | Size: 7.1 KiB |
|
Before Width: | Height: | Size: 7.3 KiB After Width: | Height: | Size: 7.3 KiB |
|
Before Width: | Height: | Size: 7.3 KiB After Width: | Height: | Size: 7.3 KiB |
|
Before Width: | Height: | Size: 6.6 KiB After Width: | Height: | Size: 6.6 KiB |
|
Before Width: | Height: | Size: 3.4 KiB After Width: | Height: | Size: 3.4 KiB |
|
Before Width: | Height: | Size: 5.2 KiB After Width: | Height: | Size: 5.2 KiB |
|
Before Width: | Height: | Size: 7.0 KiB After Width: | Height: | Size: 7.0 KiB |
|
Before Width: | Height: | Size: 6.3 KiB After Width: | Height: | Size: 6.3 KiB |
|
Before Width: | Height: | Size: 6.6 KiB After Width: | Height: | Size: 6.6 KiB |
|
Before Width: | Height: | Size: 4.8 KiB After Width: | Height: | Size: 4.8 KiB |
|
Before Width: | Height: | Size: 6.9 KiB After Width: | Height: | Size: 6.9 KiB |
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 3.6 KiB After Width: | Height: | Size: 3.6 KiB |
|
Before Width: | Height: | Size: 4.1 KiB After Width: | Height: | Size: 4.1 KiB |
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 3.3 KiB After Width: | Height: | Size: 3.3 KiB |
111
settings.cpp
@@ -1,111 +0,0 @@
|
||||
#include "settings.hpp"
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
|
||||
SettingsManager::SettingsManager(std::string filename)
|
||||
: m_filename(filename) {
|
||||
if (!std::filesystem::exists(filename)) {
|
||||
std::fstream fs;
|
||||
fs.open(filename, std::ios::out);
|
||||
fs.close();
|
||||
}
|
||||
|
||||
auto rc = m_ini.LoadFile(filename.c_str());
|
||||
m_ok = rc == SI_OK;
|
||||
}
|
||||
|
||||
void SettingsManager::Reload() {
|
||||
m_ok = m_ini.LoadFile(m_filename.c_str()) == SI_OK;
|
||||
}
|
||||
|
||||
std::string SettingsManager::GetSettingString(const std::string §ion, const std::string &key, std::string fallback) const {
|
||||
return m_ini.GetValue(section.c_str(), key.c_str(), fallback.c_str());
|
||||
}
|
||||
|
||||
int SettingsManager::GetSettingInt(const std::string §ion, const std::string &key, int fallback) const {
|
||||
return std::stoul(GetSettingString(section, key, std::to_string(fallback)));
|
||||
}
|
||||
|
||||
bool SettingsManager::GetSettingBool(const std::string §ion, const std::string &key, bool fallback) const {
|
||||
return GetSettingString(section, key, fallback ? "true" : "false") != "false";
|
||||
}
|
||||
|
||||
bool SettingsManager::IsValid() const {
|
||||
return m_ok;
|
||||
}
|
||||
|
||||
void SettingsManager::Close() {
|
||||
m_ini.SaveFile(m_filename.c_str());
|
||||
}
|
||||
|
||||
bool SettingsManager::GetUseMemoryDB() const {
|
||||
return GetSettingBool("discord", "memory_db", false);
|
||||
}
|
||||
|
||||
std::string SettingsManager::GetUserAgent() const {
|
||||
return GetSettingString("http", "user_agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.87 Safari/537.36");
|
||||
}
|
||||
|
||||
std::string SettingsManager::GetDiscordToken() const {
|
||||
return GetSettingString("discord", "token");
|
||||
}
|
||||
|
||||
bool SettingsManager::GetShowMemberListDiscriminators() const {
|
||||
return GetSettingBool("gui", "member_list_discriminator", true);
|
||||
}
|
||||
|
||||
bool SettingsManager::GetShowStockEmojis() const {
|
||||
#ifdef _WIN32
|
||||
return GetSettingBool("gui", "stock_emojis", false);
|
||||
#else
|
||||
return GetSettingBool("gui", "stock_emojis", true);
|
||||
#endif
|
||||
}
|
||||
|
||||
bool SettingsManager::GetShowCustomEmojis() const {
|
||||
return GetSettingBool("gui", "custom_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");
|
||||
}
|
||||
|
||||
int SettingsManager::GetCacheHTTPConcurrency() const {
|
||||
return GetSettingInt("http", "concurrent", 20);
|
||||
}
|
||||
|
||||
bool SettingsManager::GetPrefetch() const {
|
||||
return GetSettingBool("discord", "prefetch", false);
|
||||
}
|
||||
|
||||
std::string SettingsManager::GetMainCSS() const {
|
||||
return GetSettingString("gui", "css", "main.css");
|
||||
}
|
||||
|
||||
bool SettingsManager::GetShowAnimations() const {
|
||||
return GetSettingBool("gui", "animations", true);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
61
settings.hpp
@@ -1,61 +0,0 @@
|
||||
#pragma once
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
#include <SimpleIni.h>
|
||||
|
||||
class SettingsManager {
|
||||
public:
|
||||
SettingsManager(std::string filename);
|
||||
void Reload();
|
||||
|
||||
void Close();
|
||||
bool GetUseMemoryDB() const;
|
||||
std::string GetUserAgent() const;
|
||||
std::string GetDiscordToken() const;
|
||||
bool GetShowMemberListDiscriminators() const;
|
||||
bool GetShowStockEmojis() const;
|
||||
bool GetShowCustomEmojis() const;
|
||||
int GetCacheHTTPConcurrency() const;
|
||||
bool GetPrefetch() const;
|
||||
std::string GetMainCSS() const;
|
||||
bool GetShowAnimations() const;
|
||||
bool GetShowOwnerCrown() const;
|
||||
std::string GetGatewayURL() const;
|
||||
std::string GetAPIBaseURL() const;
|
||||
bool GetAnimatedGuildHoverOnly() 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
|
||||
// #2 it's still broken and doesn't function even when added to the solution
|
||||
// #3 it's a massive pain in the ass to try and bump the version to a functioning version
|
||||
// because they switch build systems to nmake/meson (took months to get merged in vcpkg)
|
||||
// #4 c++ build systems sucks
|
||||
// three options are: use gtk4 with updated vcpkg, try and port it myself, or use msys2 instead of vcpkg
|
||||
// im leaning towards msys
|
||||
std::string GetLinkColor() const;
|
||||
std::string GetChannelsExpanderColor() const;
|
||||
std::string GetNSFWChannelColor() const;
|
||||
|
||||
bool IsValid() const;
|
||||
|
||||
template<typename T>
|
||||
void SetSetting(std::string section, std::string key, T value) {
|
||||
m_ini.SetValue(section.c_str(), key.c_str(), std::to_string(value).c_str());
|
||||
m_ini.SaveFile(m_filename.c_str());
|
||||
}
|
||||
|
||||
void SetSetting(std::string section, std::string key, std::string value) {
|
||||
m_ini.SetValue(section.c_str(), key.c_str(), value.c_str());
|
||||
m_ini.SaveFile(m_filename.c_str());
|
||||
}
|
||||
|
||||
private:
|
||||
std::string GetSettingString(const std::string §ion, const std::string &key, std::string fallback = "") const;
|
||||
int GetSettingInt(const std::string §ion, const std::string &key, int fallback) const;
|
||||
bool GetSettingBool(const std::string §ion, const std::string &key, bool fallback) const;
|
||||
|
||||
private:
|
||||
bool m_ok;
|
||||
std::string m_filename;
|
||||
CSimpleIniA m_ini;
|
||||
};
|
||||
@@ -23,12 +23,12 @@
|
||||
|
||||
Abaddon::Abaddon()
|
||||
: m_settings(Platform::FindConfigFile())
|
||||
, m_emojis(GetResPath("/emojis.bin"))
|
||||
, m_discord(m_settings.GetUseMemoryDB()) { // stupid but easy
|
||||
, m_discord(GetSettings().UseMemoryDB) // stupid but easy
|
||||
, m_emojis(GetResPath("/emojis.bin")) {
|
||||
LoadFromSettings();
|
||||
|
||||
// todo: set user agent for non-client(?)
|
||||
std::string ua = m_settings.GetUserAgent();
|
||||
std::string ua = GetSettings().UserAgent;
|
||||
m_discord.SetUserAgent(ua);
|
||||
|
||||
m_discord.signal_gateway_ready().connect(sigc::mem_fun(*this, &Abaddon::DiscordOnReady));
|
||||
@@ -43,7 +43,7 @@ Abaddon::Abaddon()
|
||||
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())
|
||||
if (GetSettings().Prefetch)
|
||||
m_discord.signal_message_create().connect([this](const Message &message) {
|
||||
if (message.Author.HasAvatar())
|
||||
m_img_mgr.Prefetch(message.Author.GetAvatarURL());
|
||||
@@ -54,11 +54,6 @@ Abaddon::Abaddon()
|
||||
});
|
||||
}
|
||||
|
||||
Abaddon::~Abaddon() {
|
||||
m_settings.Close();
|
||||
m_discord.Stop();
|
||||
}
|
||||
|
||||
Abaddon &Abaddon::Get() {
|
||||
static Abaddon instance;
|
||||
return instance;
|
||||
@@ -83,9 +78,30 @@ int Abaddon::StartGTK() {
|
||||
|
||||
m_main_window = std::make_unique<MainWindow>();
|
||||
m_main_window->set_title(APP_TITLE);
|
||||
m_main_window->UpdateComponents();
|
||||
m_main_window->set_position(Gtk::WIN_POS_CENTER);
|
||||
|
||||
if (!m_settings.IsValid()) {
|
||||
Gtk::MessageDialog dlg(*m_main_window, "The settings file could not be opened!", false, Gtk::MESSAGE_ERROR, Gtk::BUTTONS_OK, true);
|
||||
dlg.set_position(Gtk::WIN_POS_CENTER);
|
||||
dlg.run();
|
||||
}
|
||||
|
||||
if (!m_emojis.Load()) {
|
||||
Gtk::MessageDialog dlg(*m_main_window, "The emoji file couldn't be loaded!", false, Gtk::MESSAGE_ERROR, Gtk::BUTTONS_OK, true);
|
||||
dlg.set_position(Gtk::WIN_POS_CENTER);
|
||||
dlg.run();
|
||||
}
|
||||
|
||||
if (!m_discord.IsStoreValid()) {
|
||||
Gtk::MessageDialog dlg(*m_main_window, "The Discord cache could not be created!", false, Gtk::MESSAGE_ERROR, Gtk::BUTTONS_OK, true);
|
||||
dlg.set_position(Gtk::WIN_POS_CENTER);
|
||||
dlg.run();
|
||||
return 1;
|
||||
}
|
||||
|
||||
// store must be checked before this can be called
|
||||
m_main_window->UpdateComponents();
|
||||
|
||||
// crashes for some stupid reason if i put it somewhere else
|
||||
SetupUserMenu();
|
||||
|
||||
@@ -113,35 +129,19 @@ int Abaddon::StartGTK() {
|
||||
|
||||
ActionReloadCSS();
|
||||
|
||||
m_gtk_app->signal_shutdown().connect([&]() {
|
||||
StopDiscord();
|
||||
});
|
||||
|
||||
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);
|
||||
dlg.set_position(Gtk::WIN_POS_CENTER);
|
||||
dlg.run();
|
||||
}
|
||||
|
||||
if (!m_emojis.Load()) {
|
||||
Gtk::MessageDialog dlg(*m_main_window, "The emoji file couldn't be loaded!", false, Gtk::MESSAGE_ERROR, Gtk::BUTTONS_OK, true);
|
||||
dlg.set_position(Gtk::WIN_POS_CENTER);
|
||||
dlg.run();
|
||||
}
|
||||
|
||||
if (!m_discord.IsStoreValid()) {
|
||||
Gtk::MessageDialog dlg(*m_main_window, "The Discord cache could not be created!", false, Gtk::MESSAGE_ERROR, Gtk::BUTTONS_OK, true);
|
||||
dlg.set_position(Gtk::WIN_POS_CENTER);
|
||||
dlg.run();
|
||||
return 1;
|
||||
}
|
||||
m_gtk_app->signal_shutdown().connect(sigc::mem_fun(*this, &Abaddon::OnShutdown), false);
|
||||
|
||||
m_main_window->show();
|
||||
return m_gtk_app->run(*m_main_window);
|
||||
}
|
||||
|
||||
void Abaddon::OnShutdown() {
|
||||
StopDiscord();
|
||||
m_settings.Close();
|
||||
}
|
||||
|
||||
void Abaddon::LoadFromSettings() {
|
||||
std::string token = m_settings.GetDiscordToken();
|
||||
std::string token = GetSettings().DiscordToken;
|
||||
if (token.size()) {
|
||||
m_discord_token = token;
|
||||
m_discord.UpdateToken(m_discord_token);
|
||||
@@ -154,6 +154,7 @@ void Abaddon::StartDiscord() {
|
||||
|
||||
void Abaddon::StopDiscord() {
|
||||
m_discord.Stop();
|
||||
SaveState();
|
||||
}
|
||||
|
||||
bool Abaddon::IsDiscordActive() const {
|
||||
@@ -176,6 +177,7 @@ const DiscordClient &Abaddon::GetDiscordClient() const {
|
||||
|
||||
void Abaddon::DiscordOnReady() {
|
||||
m_main_window->UpdateComponents();
|
||||
LoadState();
|
||||
}
|
||||
|
||||
void Abaddon::DiscordOnMessageCreate(const Message &message) {
|
||||
@@ -247,8 +249,8 @@ void Abaddon::DiscordOnThreadUpdate(const ThreadUpdateData &data) {
|
||||
}
|
||||
}
|
||||
|
||||
const SettingsManager &Abaddon::GetSettings() const {
|
||||
return m_settings;
|
||||
SettingsManager::Settings &Abaddon::GetSettings() {
|
||||
return m_settings.GetSettings();
|
||||
}
|
||||
|
||||
Glib::RefPtr<Gtk::CssProvider> Abaddon::GetStyleProvider() {
|
||||
@@ -268,7 +270,7 @@ void Abaddon::ShowUserMenu(const GdkEvent *event, Snowflake id, Snowflake guild_
|
||||
if (guild.has_value() && user.has_value()) {
|
||||
const auto roles = user->GetSortedRoles();
|
||||
m_user_menu_roles->set_visible(roles.size() > 0);
|
||||
for (const auto role : roles) {
|
||||
for (const auto &role : roles) {
|
||||
auto *item = Gtk::manage(new Gtk::MenuItem(role.Name));
|
||||
if (role.Color != 0) {
|
||||
Gdk::RGBA color;
|
||||
@@ -365,6 +367,40 @@ void Abaddon::SetupUserMenu() {
|
||||
m_user_menu->show_all();
|
||||
}
|
||||
|
||||
void Abaddon::SaveState() {
|
||||
if (!GetSettings().SaveState) 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 (!GetSettings().SaveState) 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 +458,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 +471,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();
|
||||
@@ -447,7 +492,7 @@ void Abaddon::ActionSetToken() {
|
||||
m_discord_token = dlg.GetToken();
|
||||
m_discord.UpdateToken(m_discord_token);
|
||||
m_main_window->UpdateComponents();
|
||||
m_settings.SetSetting("discord", "token", m_discord_token);
|
||||
GetSettings().DiscordToken = m_discord_token;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -461,7 +506,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("");
|
||||
|
||||
@@ -510,16 +555,9 @@ void Abaddon::ActionChatLoadHistory(Snowflake id) {
|
||||
return;
|
||||
|
||||
Snowflake before_id = m_main_window->GetChatOldestListedMessage();
|
||||
auto knownset = m_discord.GetMessageIDsForChannel(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);
|
||||
auto msgs = m_discord.GetMessagesBefore(id, before_id);
|
||||
|
||||
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));
|
||||
if (msgs.size() >= 50) {
|
||||
m_main_window->UpdateChatPrependHistory(msgs);
|
||||
return;
|
||||
}
|
||||
@@ -661,7 +699,7 @@ bool Abaddon::ShowConfirm(const Glib::ustring &prompt, Gtk::Window *window) {
|
||||
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(GetCSSPath("/" + GetSettings().MainCSS));
|
||||
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);
|
||||
@@ -14,7 +14,6 @@
|
||||
class Abaddon {
|
||||
private:
|
||||
Abaddon();
|
||||
~Abaddon();
|
||||
Abaddon(const Abaddon &) = delete;
|
||||
Abaddon &operator=(const Abaddon &) = delete;
|
||||
Abaddon(Abaddon &&) = delete;
|
||||
@@ -24,6 +23,8 @@ public:
|
||||
static Abaddon &Get();
|
||||
|
||||
int StartGTK();
|
||||
void OnShutdown();
|
||||
|
||||
void StartDiscord();
|
||||
void StopDiscord();
|
||||
|
||||
@@ -74,7 +75,7 @@ public:
|
||||
void DiscordOnDisconnect(bool is_reconnecting, GatewayCloseCode close_code);
|
||||
void DiscordOnThreadUpdate(const ThreadUpdateData &data);
|
||||
|
||||
const SettingsManager &GetSettings() const;
|
||||
SettingsManager::Settings &GetSettings();
|
||||
|
||||
Glib::RefPtr<Gtk::CssProvider> GetStyleProvider();
|
||||
|
||||
@@ -84,13 +85,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;
|
||||
@@ -61,7 +61,6 @@ void CellRendererPixbufAnimation::render_vfunc(const Cairo::RefPtr<Cairo::Contex
|
||||
Gtk::CellRendererState flags) {
|
||||
Gtk::Requisition minimum, natural;
|
||||
get_preferred_size(widget, minimum, natural);
|
||||
auto alloc = widget.get_allocation();
|
||||
int xpad, ypad;
|
||||
get_padding(xpad, ypad);
|
||||
int pix_x = cell_area.get_x() + xpad;
|
||||
@@ -2,9 +2,9 @@
|
||||
#include <algorithm>
|
||||
#include <map>
|
||||
#include <unordered_map>
|
||||
#include "../abaddon.hpp"
|
||||
#include "../imgmanager.hpp"
|
||||
#include "../util.hpp"
|
||||
#include "abaddon.hpp"
|
||||
#include "imgmanager.hpp"
|
||||
#include "util.hpp"
|
||||
#include "statusindicator.hpp"
|
||||
|
||||
ChannelList::ChannelList()
|
||||
@@ -16,8 +16,8 @@ ChannelList::ChannelList()
|
||||
, m_menu_guild_leave("_Leave", true)
|
||||
, m_menu_category_copy_id("_Copy ID", true)
|
||||
, m_menu_channel_copy_id("_Copy ID", true)
|
||||
, m_menu_dm_close("") // changes depending on if group or not
|
||||
, m_menu_dm_copy_id("_Copy ID", true)
|
||||
, m_menu_dm_close("") // changes depending on if group or not
|
||||
, m_menu_thread_copy_id("_Copy ID", true)
|
||||
, m_menu_thread_leave("_Leave", true)
|
||||
, m_menu_thread_archive("_Archive", true)
|
||||
@@ -167,7 +167,6 @@ void ChannelList::UpdateListing() {
|
||||
m_model->clear();
|
||||
|
||||
auto &discord = Abaddon::Get().GetDiscordClient();
|
||||
auto &img = Abaddon::Get().GetImageManager();
|
||||
|
||||
const auto guild_ids = discord.GetUserSortedGuilds();
|
||||
int sortnum = 0;
|
||||
@@ -185,10 +184,7 @@ void ChannelList::UpdateListing() {
|
||||
}
|
||||
|
||||
void ChannelList::UpdateNewGuild(const GuildData &guild) {
|
||||
auto &img = Abaddon::Get().GetImageManager();
|
||||
|
||||
auto iter = AddGuild(guild);
|
||||
|
||||
AddGuild(guild);
|
||||
// update sort order
|
||||
int sortnum = 0;
|
||||
for (const auto guild_id : Abaddon::Get().GetDiscordClient().GetUserSortedGuilds()) {
|
||||
@@ -267,11 +263,9 @@ void ChannelList::UpdateGuild(Snowflake id) {
|
||||
const auto guild = Abaddon::Get().GetDiscordClient().GetGuild(id);
|
||||
if (!iter || !guild.has_value()) return;
|
||||
|
||||
static const bool show_animations = Abaddon::Get().GetSettings().GetShowAnimations();
|
||||
|
||||
(*iter)[m_columns.m_name] = "<b>" + Glib::Markup::escape_text(guild->Name) + "</b>";
|
||||
(*iter)[m_columns.m_icon] = img.GetPlaceholder(GuildIconSize);
|
||||
if (show_animations && guild->HasAnimatedIcon()) {
|
||||
if (Abaddon::Get().GetSettings().ShowAnimations && guild->HasAnimatedIcon()) {
|
||||
const auto cb = [this, id](const Glib::RefPtr<Gdk::PixbufAnimation> &pb) {
|
||||
auto iter = GetIteratorForGuildFromID(id);
|
||||
if (iter) (*iter)[m_columns.m_icon_anim] = pb;
|
||||
@@ -380,6 +374,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<Snowflake>(child[m_columns.m_id])] = self(self, child);
|
||||
|
||||
return r;
|
||||
};
|
||||
|
||||
for (const auto &child : m_model->children()) {
|
||||
const auto id = static_cast<Snowflake>(child[m_columns.m_id]);
|
||||
if (static_cast<uint64_t>(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();
|
||||
@@ -390,9 +434,7 @@ Gtk::TreeModel::iterator ChannelList::AddGuild(const GuildData &guild) {
|
||||
guild_row[m_columns.m_name] = "<b>" + Glib::Markup::escape_text(guild.Name) + "</b>";
|
||||
guild_row[m_columns.m_icon] = img.GetPlaceholder(GuildIconSize);
|
||||
|
||||
static const bool show_animations = Abaddon::Get().GetSettings().GetShowAnimations();
|
||||
|
||||
if (show_animations && guild.HasAnimatedIcon()) {
|
||||
if (Abaddon::Get().GetSettings().ShowAnimations && guild.HasAnimatedIcon()) {
|
||||
const auto cb = [this, id = guild.ID](const Glib::RefPtr<Gdk::PixbufAnimation> &pb) {
|
||||
auto iter = GetIteratorForGuildFromID(id);
|
||||
if (iter) (*iter)[m_columns.m_icon_anim] = pb;
|
||||
@@ -511,7 +553,7 @@ void ChannelList::UpdateChannelCategory(const ChannelData &channel) {
|
||||
}
|
||||
|
||||
Gtk::TreeModel::iterator ChannelList::GetIteratorForGuildFromID(Snowflake id) {
|
||||
for (const auto child : m_model->children()) {
|
||||
for (const auto &child : m_model->children()) {
|
||||
if (child[m_columns.m_id] == id)
|
||||
return child;
|
||||
}
|
||||
@@ -520,14 +562,14 @@ Gtk::TreeModel::iterator ChannelList::GetIteratorForGuildFromID(Snowflake id) {
|
||||
|
||||
Gtk::TreeModel::iterator ChannelList::GetIteratorForChannelFromID(Snowflake id) {
|
||||
std::queue<Gtk::TreeModel::iterator> queue;
|
||||
for (const auto child : m_model->children())
|
||||
for (const auto child2 : child.children())
|
||||
for (const auto &child : m_model->children())
|
||||
for (const auto &child2 : child.children())
|
||||
queue.push(child2);
|
||||
|
||||
while (!queue.empty()) {
|
||||
auto item = queue.front();
|
||||
if ((*item)[m_columns.m_id] == id) return item;
|
||||
for (const auto child : item->children())
|
||||
for (const auto &child : item->children())
|
||||
queue.push(child);
|
||||
queue.pop();
|
||||
}
|
||||
@@ -952,7 +994,7 @@ void CellRendererChannels::render_vfunc_guild(const Cairo::RefPtr<Cairo::Context
|
||||
|
||||
m_renderer_text.render(cr, widget, background_area, text_cell_area, flags);
|
||||
|
||||
const static bool hover_only = Abaddon::Get().GetSettings().GetAnimatedGuildHoverOnly();
|
||||
const bool hover_only = Abaddon::Get().GetSettings().AnimatedGuildHoverOnly;
|
||||
const bool is_hovered = flags & Gtk::CELL_RENDERER_PRELIT;
|
||||
auto anim = m_property_pixbuf_animation.get_value();
|
||||
|
||||
@@ -1002,9 +1044,6 @@ void CellRendererChannels::get_preferred_height_for_width_vfunc_category(Gtk::Wi
|
||||
}
|
||||
|
||||
void CellRendererChannels::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) {
|
||||
int available_xpad = background_area.get_width();
|
||||
int available_ypad = background_area.get_height();
|
||||
|
||||
// todo: figure out how Gtk::Arrow is rendered because i like it better :^)
|
||||
constexpr static int len = 5;
|
||||
int x1, y1, x2, y2, x3, y3;
|
||||
@@ -1026,7 +1065,7 @@ void CellRendererChannels::render_vfunc_category(const Cairo::RefPtr<Cairo::Cont
|
||||
cr->move_to(x1, y1);
|
||||
cr->line_to(x2, y2);
|
||||
cr->line_to(x3, y3);
|
||||
static const auto expander_color = Gdk::RGBA(Abaddon::Get().GetSettings().GetChannelsExpanderColor());
|
||||
const auto expander_color = Gdk::RGBA(Abaddon::Get().GetSettings().ChannelsExpanderColor);
|
||||
cr->set_source_rgb(expander_color.get_red(), expander_color.get_green(), expander_color.get_blue());
|
||||
cr->stroke();
|
||||
|
||||
@@ -1072,7 +1111,7 @@ void CellRendererChannels::render_vfunc_channel(const Cairo::RefPtr<Cairo::Conte
|
||||
|
||||
Gdk::Rectangle text_cell_area(text_x, text_y, text_w, text_h);
|
||||
|
||||
const static auto nsfw_color = Gdk::RGBA(Abaddon::Get().GetSettings().GetNSFWChannelColor());
|
||||
const auto nsfw_color = Gdk::RGBA(Abaddon::Get().GetSettings().NSFWChannelColor);
|
||||
if (m_property_nsfw.get_value())
|
||||
m_renderer_text.property_foreground_rgba() = nsfw_color;
|
||||
m_renderer_text.render(cr, widget, background_area, text_cell_area, flags);
|
||||
@@ -6,7 +6,8 @@
|
||||
#include <unordered_set>
|
||||
#include <unordered_map>
|
||||
#include <sigc++/sigc++.h>
|
||||
#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);
|
||||
@@ -1,7 +1,7 @@
|
||||
#include <filesystem>
|
||||
#include "chatinputindicator.hpp"
|
||||
#include "../abaddon.hpp"
|
||||
#include "../util.hpp"
|
||||
#include "abaddon.hpp"
|
||||
#include "util.hpp"
|
||||
|
||||
constexpr static const int MaxUsersInIndicator = 4;
|
||||
|
||||
@@ -112,7 +112,7 @@ void ChatInputIndicator::ComputeTypingString() {
|
||||
SetTypingString(typers[0].Username + " and " + typers[1].Username + " are typing...");
|
||||
} else if (typers.size() > 2 && typers.size() <= MaxUsersInIndicator) {
|
||||
Glib::ustring str;
|
||||
for (int i = 0; i < typers.size() - 1; i++)
|
||||
for (size_t i = 0; i < typers.size() - 1; i++)
|
||||
str += typers[i].Username + ", ";
|
||||
SetTypingString(str + "and " + typers[typers.size() - 1].Username + " are typing...");
|
||||
} else { // size() > MaxUsersInIndicator
|
||||
@@ -1,8 +1,8 @@
|
||||
#pragma once
|
||||
#include <gtkmm.h>
|
||||
#include <unordered_map>
|
||||
#include "../discord/message.hpp"
|
||||
#include "../discord/user.hpp"
|
||||
#include "discord/message.hpp"
|
||||
#include "discord/user.hpp"
|
||||
|
||||
class ChatInputIndicator : public Gtk::Box {
|
||||
public:
|
||||
@@ -1,6 +1,6 @@
|
||||
#include "chatmessage.hpp"
|
||||
#include "../abaddon.hpp"
|
||||
#include "../util.hpp"
|
||||
#include "abaddon.hpp"
|
||||
#include "util.hpp"
|
||||
#include "lazyimage.hpp"
|
||||
#include <unordered_map>
|
||||
|
||||
@@ -116,8 +116,10 @@ void ChatMessageItemContainer::UpdateContent() {
|
||||
}
|
||||
|
||||
void ChatMessageItemContainer::UpdateReactions() {
|
||||
if (m_reactions_component != nullptr)
|
||||
if (m_reactions_component != nullptr) {
|
||||
delete m_reactions_component;
|
||||
m_reactions_component = nullptr;
|
||||
}
|
||||
|
||||
const auto data = Abaddon::Get().GetDiscordClient().GetMessage(ID);
|
||||
if (data->Reactions.has_value() && data->Reactions->size() > 0) {
|
||||
@@ -229,11 +231,13 @@ void ChatMessageItemContainer::UpdateTextComponent(Gtk::TextView *tv) {
|
||||
}
|
||||
} break;
|
||||
case MessageType::RECIPIENT_ADD: {
|
||||
if (data->Mentions.size() == 0) break;
|
||||
const auto &adder = Abaddon::Get().GetDiscordClient().GetUser(data->Author.ID);
|
||||
const auto &added = data->Mentions[0];
|
||||
b->insert_markup(s, "<i><span color='#999999'><span color='#eeeeee'>" + adder->Username + "</span> added <span color='#eeeeee'>" + added.Username + "</span></span></i>");
|
||||
} break;
|
||||
case MessageType::RECIPIENT_REMOVE: {
|
||||
if (data->Mentions.size() == 0) break;
|
||||
const auto &adder = Abaddon::Get().GetDiscordClient().GetUser(data->Author.ID);
|
||||
const auto &added = data->Mentions[0];
|
||||
if (adder->ID == added.ID)
|
||||
@@ -306,8 +310,6 @@ Gtk::Widget *ChatMessageItemContainer::CreateEmbedComponent(const EmbedData &emb
|
||||
|
||||
constexpr static int AuthorIconSize = 20;
|
||||
if (embed.Author->ProxyIconURL.has_value()) {
|
||||
auto &img = Abaddon::Get().GetImageManager();
|
||||
|
||||
auto *author_img = Gtk::manage(new LazyImage(*embed.Author->ProxyIconURL, AuthorIconSize, AuthorIconSize));
|
||||
author_img->set_halign(Gtk::ALIGN_START);
|
||||
author_img->set_valign(Gtk::ALIGN_START);
|
||||
@@ -355,7 +357,7 @@ Gtk::Widget *ChatMessageItemContainer::CreateEmbedComponent(const EmbedData &emb
|
||||
}
|
||||
return false;
|
||||
});
|
||||
static auto color = Abaddon::Get().GetSettings().GetLinkColor();
|
||||
static auto color = Abaddon::Get().GetSettings().LinkColor;
|
||||
title_label->override_color(Gdk::RGBA(color));
|
||||
title_label->set_markup("<b>" + Glib::Markup::escape_text(*embed.Title) + "</b>");
|
||||
}
|
||||
@@ -653,6 +655,14 @@ Gtk::Widget *ChatMessageItemContainer::CreateReplyComponent(const Message &data)
|
||||
return author->GetEscapedBoldString<false>();
|
||||
};
|
||||
|
||||
// if the message wasnt fetched from store it might have an un-fetched reference
|
||||
std::optional<std::shared_ptr<Message>> referenced_message = data.ReferencedMessage;
|
||||
if (data.MessageReference.has_value() && data.MessageReference->MessageID.has_value() && !referenced_message.has_value()) {
|
||||
auto refd = discord.GetMessage(*data.MessageReference->MessageID);
|
||||
if (refd.has_value())
|
||||
referenced_message = std::make_shared<Message>(std::move(*refd));
|
||||
}
|
||||
|
||||
if (data.Interaction.has_value()) {
|
||||
const auto user = *discord.GetUser(data.Interaction->User.ID);
|
||||
|
||||
@@ -664,16 +674,16 @@ Gtk::Widget *ChatMessageItemContainer::CreateReplyComponent(const Message &data)
|
||||
} else {
|
||||
lbl->set_markup(user.GetEscapedBoldString<false>());
|
||||
}
|
||||
} else if (data.ReferencedMessage.has_value()) {
|
||||
if (data.ReferencedMessage.value().get() == nullptr) {
|
||||
} else if (referenced_message.has_value()) {
|
||||
if (referenced_message.value() == nullptr) {
|
||||
lbl->set_markup("<i>deleted message</i>");
|
||||
} else {
|
||||
const auto &referenced = *data.ReferencedMessage.value().get();
|
||||
const auto &referenced = *referenced_message.value();
|
||||
Glib::ustring text;
|
||||
if (referenced.Content == "") {
|
||||
if (referenced.Attachments.size() > 0) {
|
||||
if (referenced.Content.empty()) {
|
||||
if (!referenced.Attachments.empty()) {
|
||||
text = "<i>attachment</i>";
|
||||
} else if (referenced.Embeds.size() > 0) {
|
||||
} else if (!referenced.Embeds.empty()) {
|
||||
text = "<i>embed</i>";
|
||||
}
|
||||
} else {
|
||||
@@ -788,7 +798,6 @@ void ChatMessageItemContainer::HandleCustomEmojis(Gtk::TextView &tv) {
|
||||
int mstart, mend;
|
||||
if (!match.fetch_pos(0, mstart, mend)) break;
|
||||
const bool is_animated = match.fetch(0)[1] == 'a';
|
||||
const bool show_animations = Abaddon::Get().GetSettings().GetShowAnimations();
|
||||
|
||||
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);
|
||||
@@ -796,7 +805,7 @@ void ChatMessageItemContainer::HandleCustomEmojis(Gtk::TextView &tv) {
|
||||
auto end_it = buf->get_iter_at_offset(chars_end);
|
||||
|
||||
startpos = mend;
|
||||
if (is_animated && show_animations) {
|
||||
if (is_animated && Abaddon::Get().GetSettings().ShowAnimations) {
|
||||
const auto mark_start = buf->create_mark(start_it, false);
|
||||
end_it.backward_char();
|
||||
const auto mark_end = buf->create_mark(end_it, false);
|
||||
@@ -835,11 +844,8 @@ 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);
|
||||
if (Abaddon::Get().GetSettings().ShowStockEmojis) HandleStockEmojis(tv);
|
||||
if (Abaddon::Get().GetSettings().ShowCustomEmojis) HandleCustomEmojis(tv);
|
||||
}
|
||||
|
||||
void ChatMessageItemContainer::CleanupEmojis(Glib::RefPtr<Gtk::TextBuffer> buf) {
|
||||
@@ -959,9 +965,6 @@ void ChatMessageItemContainer::HandleLinks(Gtk::TextView &tv) {
|
||||
auto buf = tv.get_buffer();
|
||||
Glib::ustring text = GetText(buf);
|
||||
|
||||
// i'd like to let this be done thru css like .message-link { color: #bitch; } but idk how
|
||||
static auto link_color = Abaddon::Get().GetSettings().GetLinkColor();
|
||||
|
||||
int startpos = 0;
|
||||
Glib::MatchInfo match;
|
||||
while (rgx->match(text, startpos, match)) {
|
||||
@@ -970,7 +973,7 @@ void ChatMessageItemContainer::HandleLinks(Gtk::TextView &tv) {
|
||||
std::string link = match.fetch(0);
|
||||
auto tag = buf->create_tag();
|
||||
m_link_tagmap[tag] = link;
|
||||
tag->property_foreground_rgba() = Gdk::RGBA(link_color);
|
||||
tag->property_foreground_rgba() = Gdk::RGBA(Abaddon::Get().GetSettings().LinkColor);
|
||||
tag->set_property("underline", 1); // stupid workaround for vcpkg bug (i think)
|
||||
|
||||
const auto chars_start = g_utf8_pointer_to_offset(text.c_str(), text.c_str() + mstart);
|
||||
@@ -1128,7 +1131,7 @@ ChatMessageHeader::ChatMessageHeader(const Message &data)
|
||||
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()) {
|
||||
if (Abaddon::Get().GetSettings().ShowAnimations) {
|
||||
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);
|
||||
@@ -1,6 +1,6 @@
|
||||
#pragma once
|
||||
#include <gtkmm.h>
|
||||
#include "../discord/discord.hpp"
|
||||
#include "discord/discord.hpp"
|
||||
|
||||
class ChatMessageItemContainer : public Gtk::Box {
|
||||
public:
|
||||
@@ -1,6 +1,6 @@
|
||||
#include "chatwindow.hpp"
|
||||
#include "chatmessage.hpp"
|
||||
#include "../abaddon.hpp"
|
||||
#include "abaddon.hpp"
|
||||
#include "chatinputindicator.hpp"
|
||||
#include "ratelimitindicator.hpp"
|
||||
#include "chatinput.hpp"
|
||||
@@ -2,7 +2,7 @@
|
||||
#include <gtkmm.h>
|
||||
#include <string>
|
||||
#include <set>
|
||||
#include "../discord/discord.hpp"
|
||||
#include "discord/discord.hpp"
|
||||
#include "completer.hpp"
|
||||
|
||||
class ChatMessageHeader;
|
||||
@@ -1,7 +1,7 @@
|
||||
#include <unordered_set>
|
||||
#include "completer.hpp"
|
||||
#include "../abaddon.hpp"
|
||||
#include "../util.hpp"
|
||||
#include "abaddon.hpp"
|
||||
#include "util.hpp"
|
||||
|
||||
constexpr const int CompleterHeight = 150;
|
||||
constexpr const int MaxCompleterEntries = 30;
|
||||
@@ -47,7 +47,7 @@ bool Completer::ProcessKeyPress(GdkEventKey *e) {
|
||||
switch (e->keyval) {
|
||||
case GDK_KEY_Down: {
|
||||
if (m_entries.size() == 0) return true;
|
||||
const int index = m_list.get_selected_row()->get_index();
|
||||
const auto index = static_cast<size_t>(m_list.get_selected_row()->get_index());
|
||||
if (index >= m_entries.size() - 1) return true;
|
||||
m_list.select_row(*m_entries[index + 1]);
|
||||
ScrollListBoxToSelected(m_list);
|
||||
@@ -55,7 +55,7 @@ bool Completer::ProcessKeyPress(GdkEventKey *e) {
|
||||
return true;
|
||||
case GDK_KEY_Up: {
|
||||
if (m_entries.size() == 0) return true;
|
||||
const int index = m_list.get_selected_row()->get_index();
|
||||
const auto index = static_cast<size_t>(m_list.get_selected_row()->get_index());
|
||||
if (index == 0) return true;
|
||||
m_list.select_row(*m_entries[index - 1]);
|
||||
ScrollListBoxToSelected(m_list);
|
||||
@@ -169,7 +169,7 @@ void Completer::CompleteEmojis(const Glib::ustring &term) {
|
||||
const auto guild = discord.GetGuild(*channel->GuildID);
|
||||
|
||||
if (guild.has_value() && guild->Emojis.has_value())
|
||||
for (const auto tmp : *guild->Emojis) {
|
||||
for (const auto &tmp : *guild->Emojis) {
|
||||
const auto emoji = *discord.GetEmoji(tmp.ID);
|
||||
if (emoji.IsAnimated.has_value() && *emoji.IsAnimated) continue;
|
||||
if (emoji.IsAvailable.has_value() && !*emoji.IsAvailable) continue;
|
||||
@@ -186,7 +186,7 @@ void Completer::CompleteEmojis(const Glib::ustring &term) {
|
||||
for (const auto guild_id : discord.GetGuilds()) {
|
||||
const auto guild = discord.GetGuild(guild_id);
|
||||
if (!guild.has_value()) continue;
|
||||
for (const auto tmp : *guild->Emojis) {
|
||||
for (const auto &tmp : *guild->Emojis) {
|
||||
const auto emoji = *discord.GetEmoji(tmp.ID);
|
||||
const bool is_animated = emoji.IsAnimated.has_value() && *emoji.IsAnimated;
|
||||
if (emoji.IsAvailable.has_value() && !*emoji.IsAvailable) continue;
|
||||
@@ -330,8 +330,8 @@ Glib::ustring Completer::GetTerm() {
|
||||
}
|
||||
|
||||
CompleterEntry::CompleterEntry(const Glib::ustring &completion, int index)
|
||||
: m_index(index)
|
||||
, m_completion(completion)
|
||||
: m_completion(completion)
|
||||
, m_index(index)
|
||||
, m_box(Gtk::ORIENTATION_HORIZONTAL) {
|
||||
set_halign(Gtk::ALIGN_START);
|
||||
get_style_context()->add_class("completer-entry");
|
||||
@@ -2,7 +2,7 @@
|
||||
#include <gtkmm.h>
|
||||
#include <functional>
|
||||
#include "lazyimage.hpp"
|
||||
#include "../discord/snowflake.hpp"
|
||||
#include "discord/snowflake.hpp"
|
||||
|
||||
constexpr static int CompleterImageSize = 24;
|
||||
|
||||