Merge branch 'master' into voice

This commit is contained in:
ouwou 2023-02-07 15:03:03 -05:00
commit c1303bd289
107 changed files with 695 additions and 373 deletions

View File

@ -63,26 +63,26 @@ jobs:
- name: Build (1)
uses: haya14busa/action-cond@v1
id: buildcmd
id: build
with:
cond: ${{ matrix.mindeps == true }}
if_true: |
cmake -GNinja -Bbuild -DUSE_LIBHANDY=OFF -DENABLE_VOICE=OFF -DCMAKE_BUILD_TYPE=${{ matrix.buildtype }}
cmake -GNinja -Bbuild -DCMAKE_BUILD_TYPE=${{ matrix.buildtype }} -DUSE_LIBHANDY=OFF -DENABLE_VOICE=OFF
cmake --build build
if_false: |
cmake -GNinja -Bbuild -DCMAKE_BUILD_TYPE=${{ matrix.buildtype }}
cmake --build build
- name: Build (2)
run: ${{ steps.buildcmd.outputs.value }}
- name: Build (2) }}
run: ${{ steps.build.outputs.value }}
- name: Setup Artifact
run: |
mkdir -p build/artifactdir/bin build/artifactdir/ssl/certs build/artifactdir/lib build/artifactdir/share/glib-2.0/schemas
mkdir -p build/artifactdir/bin build/artifactdir/etc/ssl/certs build/artifactdir/lib build/artifactdir/share/glib-2.0/schemas
cd build
cp *.exe artifactdir/bin
cd ..
cp /mingw64/ssl/certs/ca-bundle.crt build/artifactdir/ssl/certs
cp /mingw64/etc/ssl/certs/ca-bundle.crt build/artifactdir/etc/ssl/certs
cp -r /mingw64/lib/gdk-pixbuf-2.0 build/artifactdir/lib
cp -r res/css res/res res/fonts build/artifactdir/bin
cp /mingw64/share/glib-2.0/schemas/gschemas.compiled build/artifactdir/share/glib-2.0/schemas

3
.gitmodules vendored
View File

@ -7,3 +7,6 @@
[submodule "subprojects/miniaudio"]
path = subprojects/miniaudio
url = https://github.com/mackron/miniaudio
[submodule "subprojects/keychain"]
path = subprojects/keychain
url = https://github.com/hrantzsch/keychain

View File

@ -9,6 +9,7 @@ set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake/")
option(USE_LIBHANDY "Enable features that require libhandy (default)" ON)
option(ENABLE_VOICE "Enable voice suppport" ON)
option(USE_KEYCHAIN "Store the token in the keychain (default)" ON)
find_package(nlohmann_json REQUIRED)
find_package(CURL)
@ -56,6 +57,8 @@ target_include_directories(abaddon PUBLIC ${ZLIB_INCLUDE_DIRS})
target_include_directories(abaddon PUBLIC ${SQLite3_INCLUDE_DIRS})
target_include_directories(abaddon PUBLIC ${NLOHMANN_JSON_INCLUDE_DIRS})
target_precompile_headers(abaddon PRIVATE <gtkmm.h> src/abaddon.hpp src/util.hpp)
if ((CMAKE_CXX_COMPILER_ID STREQUAL "GNU") OR
(CMAKE_CXX_COMPILER_ID STREQUAL "Clang" AND
((CMAKE_SYSTEM_NAME STREQUAL "Linux") OR (CMAKE_CXX_COMPILER_VERSION LESS 9))))
@ -100,14 +103,19 @@ target_link_libraries(abaddon ${ZLIB_LIBRARY})
target_link_libraries(abaddon ${NLOHMANN_JSON_LIBRARIES})
if (USE_LIBHANDY)
find_package(libhandy)
if (NOT libhandy_FOUND)
message("libhandy could not be found. features requiring it have been disabled")
set(USE_LIBHANDY OFF)
else ()
target_include_directories(abaddon PUBLIC ${libhandy_INCLUDE_DIRS})
target_link_libraries(abaddon ${libhandy_LIBRARIES})
target_compile_definitions(abaddon PRIVATE WITH_LIBHANDY)
find_package(libhandy REQUIRED)
target_include_directories(abaddon PUBLIC ${libhandy_INCLUDE_DIRS})
target_link_libraries(abaddon ${libhandy_LIBRARIES})
target_compile_definitions(abaddon PRIVATE WITH_LIBHANDY)
endif ()
if (USE_KEYCHAIN)
find_package(keychain QUIET)
if (NOT keychain_FOUND)
message("keychain was not found and will be included as a submodule")
add_subdirectory(subprojects/keychain)
target_link_libraries(abaddon keychain)
target_compile_definitions(abaddon PRIVATE WITH_KEYCHAIN)
endif ()
endif ()

249
README.md
View File

@ -75,6 +75,14 @@ the result of fundamental issues with Discord's thread implementation.
```Shell
$ sudo apt install g++ cmake libgtkmm-3.0-dev libcurl4-gnutls-dev libsqlite3-dev libssl-dev nlohmann-json3-dev
```
* On Arch Linux
```Shell
$ sudo pacman -S gcc cmake gtkmm3 libcurl-gnutls lib32-sqlite lib32-openssl nlohmann-json libhandy
```
* On Fedora Linux:
```Shell
$ sudo dnf install g++ cmake gtkmm3.0-devel libcurl-devel sqlite-devel openssl-devel json-devel libsecret-devel libhandy-devel
```
2. `git clone https://github.com/uowuo/abaddon --recurse-submodules="subprojects" && cd abaddon`
3. `mkdir build && cd build`
4. `cmake ..`
@ -92,7 +100,7 @@ Latest release version: https://github.com/uowuo/abaddon/releases/latest
- 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 `bin` directory
> **Warning**: If you use Windows, make sure to start from the `bin` directory
On Linux, `css` and `res` can also be loaded from `~/.local/share/abaddon` or `/usr/share/abaddon`
@ -135,145 +143,158 @@ spam filter's wrath:
#### CSS selectors
.app-window - Applied to all windows. This means the main window and all popups
.app-popup - Additional class for `.app-window`s when the window is not the main window
| Selector | Description |
|--------------------------------|---------------------------------------------------------------------------------------------------|
| `.app-window` | Applied to all windows. This means the main window and all popups |
| `.app-popup` | Additional class for `.app-window`s when the window is not the main window |
| `.channel-list` | Container of the channel list |
| `.messages` | Container of user messages |
| `.message-container` | The container which holds a user's messages |
| `.message-container-author` | The author label for a message container |
| `.message-container-timestamp` | The timestamp label for a message container |
| `.message-container-avatar` | Avatar for a user in a message |
| `.message-container-extra` | Label containing BOT/Webhook |
| `.message-text` | The text of a user message |
| `.pending` | Extra class of .message-text for messages pending to be sent |
| `.failed` | Extra class of .message-text for messages that failed to be sent |
| `.message-attachment-box` | Contains attachment info |
| `.message-reply` | Container for the replied-to message in a reply (these elements will also have .message-text set) |
| `.message-input` | Applied to the chat input container |
| `.replying` | Extra class for chat input container when a reply is currently being created |
| `.reaction-box` | Contains a reaction image and the count |
| `.reacted` | Additional class for reaction-box when the user has reacted with a particular reaction |
| `.reaction-count` | Contains the count for reaction |
| `.completer` | Container for the message completer |
| `.completer-entry` | Container for a single entry in the completer |
| `.completer-entry-label` | Contains the label for an entry in the completer |
| `.completer-entry-image` | Contains the image for an entry in the completer |
| `.embed` | Container for a message embed |
| `.embed-author` | The author of an embed |
| `.embed-title` | The title of an embed |
| `.embed-description` | The description of an embed |
| `.embed-field-title` | The title of an embed field |
| `.embed-field-value` | The value of an embed field |
| `.embed-footer` | The footer of an embed |
| `.members` | Container of the member list |
| `.members-row` | All rows within the members container |
| `.members-row-label` | All labels in the members container |
| `.members-row-member` | Rows containing a member |
| `.members-row-role` | Rows containing a role |
| `.members-row-avatar` | Contains the avatar for a row in the member list |
| `.status-indicator` | The status indicator |
| `.online` | Applied to status indicators when the associated user is online |
| `.idle` | Applied to status indicators when the associated user is away |
| `.dnd` | Applied to status indicators when the associated user is on do not disturb |
| `.offline` | Applied to status indicators when the associated user is offline |
| `.typing-indicator` | The typing indicator (also used for replies) |
.channel-list - Container of the channel list
Used in reorderable list implementation:
| Selector |
|----------------------|
| `.drag-icon` |
| `.drag-hover-top` |
| `.drag-hover-bottom` |
.messages - Container of user messages
.message-container - The container which holds a user's messages
.message-container-author - The author label for a message container
.message-container-timestamp - The timestamp label for a message container
.message-container-avatar - Avatar for a user in a message
.message-container-extra - Label containing BOT/Webhook
.message-text - The text of a user message
.pending - Extra class of .message-text for messages pending to be sent
.failed - Extra class of .message-text for messages that failed to be sent
.message-attachment-box - Contains attachment info
.message-reply - Container for the replied-to message in a reply (these elements will also have .message-text set)
.message-input - Applied to the chat input container
.replying - Extra class for chat input container when a reply is currently being created
.reaction-box - Contains a reaction image and the count
.reacted - Additional class for reaction-box when the user has reacted with a particular reaction
.reaction-count - Contains the count for reaction
Used in guild settings popup:
.completer - Container for the message completer
.completer-entry - Container for a single entry in the completer
.completer-entry-label - Contains the label for an entry in the completer
.completer-entry-image - Contains the image for an entry in the completer
| Selector | Description |
|----------------------------|---------------------------------------------------|
| `.guild-settings-window` | Container for list of members in the members pane |
| `.guild-members-pane-list` | |
| `.guild-members-pane-info` | Container for member info |
| `.guild-roles-pane-list` | Container for list of roles in the roles pane |
.embed - Container for a message embed
.embed-author - The author of an embed
.embed-title - The title of an embed
.embed-description - The description of an embed
.embed-field-title - The title of an embed field
.embed-field-value - The value of an embed field
.embed-footer - The footer of an embed
Used in profile popup:
.members - Container of the member list
.members-row - All rows within the members container
.members-row-label - All labels in the members container
.members-row-member - Rows containing a member
.members-row-role - Rows containing a role
.members-row-avatar - Contains the avatar for a row in the member list
.status-indicator - The status indicator
.online - Applied to status indicators when the associated user is online
.idle - Applied to status indicators when the associated user is away
.dnd - Applied to status indicators when the associated user is on do not disturb
.offline - Applied to status indicators when the associated user is offline
.typing-indicator - The typing indicator (also used for replies)
Used in reorderable list implementation:
.drag-icon .drag-hover-top .drag-hover-bottom
Used in guild settings popup:
.guild-settings-window
.guild-members-pane-list - Container for list of members in the members pane
.guild-members-pane-info - Container for member info
.guild-roles-pane-list - Container for list of roles in the roles pane
Used in profile popup:
.mutual-friend-item - Applied to every item in the mutual friends list
.mutual-friend-item-name - Name in mutual friend item
.mutual-friend-item-avatar - Avatar in mutual friend item
.mutual-guild-item - Applied to every item in the mutual guilds list
.mutual-guild-item-name - Name in mutual guild item
.mutual-guild-item-icon - Icon in mutual guild item
.mutual-guild-item-nick - User nickname in mutual guild item
.profile-connection - Applied to every item in the user connections list
.profile-connection-label - Label in profile connection item
.profile-connection-check - Checkmark in verified profile connection items
.profile-connections - Container for profile connections
.profile-notes - Container for notes in profile window
.profile-notes-label - Label that says "NOTE"
.profile-notes-text - Actual note text
.profile-info-pane - Applied to container for info section of profile popup
.profile-info-created - Label for creation date of profile
.user-profile-window
.profile-main-container - Inner container for profile
.profile-avatar
.profile-username
.profile-switcher - Buttons used to switch viewed section of profile
.profile-stack - Container for profile info that can be switched between
.profile-badges - Container for badges
.profile-badge
| Selector | Description |
|------------------------------|---------------------------------------------------------|
| `.mutual-friend-item` | Applied to every item in the mutual friends list |
| `.mutual-friend-item-name` | Name in mutual friend item |
| `.mutual-friend-item-avatar` | Avatar in mutual friend item |
| `.mutual-guild-item` | Applied to every item in the mutual guilds list |
| `.mutual-guild-item-name` | Name in mutual guild item |
| `.mutual-guild-item-icon` | Icon in mutual guild item |
| `.mutual-guild-item-nick` | User nickname in mutual guild item |
| `.profile-connection` | Applied to every item in the user connections list |
| `.profile-connection-label` | Label in profile connection item |
| `.profile-connection-check` | Checkmark in verified profile connection items |
| `.profile-connections` | Container for profile connections |
| `.profile-notes` | Container for notes in profile window |
| `.profile-notes-label` | Label that says "NOTE" |
| `.profile-notes-text` | Actual note text |
| `.profile-info-pane` | Applied to container for info section of profile popup |
| `.profile-info-created` | Label for creation date of profile |
| `.user-profile-window` | |
| `.profile-main-container` | Inner container for profile |
| `.profile-avatar` | |
| `.profile-username` | |
| `.profile-switcher` | Buttons used to switch viewed section of profile |
| `.profile-stack` | Container for profile info that can be switched between |
| `.profile-badges` | Container for badges |
| `.profile-badge` | |
### Settings
Settings are configured (for now) by editing abaddon.ini
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.
> **Warning**: 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
| Setting | Type | Default | Description |
|---------------|---------|---------|--------------------------------------------------------------------------------------------------|
| `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` | boolean | 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` | boolean | false | if true, new messages will cause the avatar and image attachments to be automatically downloaded |
| `autoconnect` | boolean | false | autoconnect to discord |
#### http
* user_agent (string) - sets the user-agent to use in HTTP requests to the Discord API (not including media/images)
* concurrent (int, default 20) - how many images can be concurrently retrieved
| Setting | Type | Default | Description |
|--------------|--------|---------|---------------------------------------------------------------------------------------------|
| `user_agent` | string | | sets the user-agent to use in HTTP requests to the Discord API (not including media/images) |
| `concurrent` | int | 20 | how many images can be concurrently retrieved |
#### 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
* 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
* unreads (true or false, default true) - show unread indicators and mention badges
* save_state (true or false, default true) - save the state of the gui (active channels, tabs, expanded channels)
* alt_menu (true or false, default false) - keep the menu hidden unless revealed with alt key
* hide_to_tray (true or false, default false) - hide abaddon to the system tray on window close
| Setting | Type | Default | Description |
|-----------------------------|---------|---------|----------------------------------------------------------------------------------------------------------------------------|
| `member_list_discriminator` | boolean | true | show user discriminators in the member list |
| `stock_emojis` | boolean | true | allow abaddon to substitute unicode emojis with images from emojis.bin, must be false to allow GTK to render emojis itself |
| `custom_emojis` | boolean | true | download and use custom Discord emojis |
| `css` | string | | path to the main CSS file |
| `animations` | boolean | true | use animated images where available (e.g. server icons, emojis, avatars). false means static images will be used |
| `animated_guild_hover_only` | boolean | true | only animate guild icons when the guild is being hovered over |
| `owner_crown` | boolean | true | show a crown next to the owner |
| `unreads` | boolean | true | show unread indicators and mention badges |
| `save_state` | boolean | true | save the state of the gui (active channels, tabs, expanded channels) |
| `alt_menu` | boolean | false | keep the menu hidden unless revealed with alt key |
| `hide_to_tray` | boolean | false | hide abaddon to the system tray on window close |
#### style
* 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
* channelcolor (string) - color to use for SFW channels in the channel list
* mentionbadgecolor (string) - background color for mention badges
* mentionbadgetextcolor (string) - color to use for number displayed on mention badges
* unreadcolor (string) - color to use for the unread indicator
| Setting | Type | Description |
|-------------------------|--------|-----------------------------------------------------|
| `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 |
| `channelcolor` | string | color to use for SFW channels in the channel list |
| `mentionbadgecolor` | string | background color for mention badges |
| `mentionbadgetextcolor` | string | color to use for number displayed on mention badges |
| `unreadcolor` | string | color to use for the unread indicator |
### 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
| variable | Description |
|------------------|------------------------------------------------------------------------------|
| `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

@ -8,7 +8,7 @@
/bin/libcairo-2.dll
/bin/libcairo-gobject-2.dll
/bin/libcairomm-1.0-1.dll
/bin/libcrypto-1_1-x64.dll
/bin/libcrypto-3-x64.dll
/bin/libcurl-4.dll
/bin/libdatrie-1.dll
/bin/libdeflate.dll
@ -50,7 +50,7 @@
/bin/libspdlog.dll
/bin/libsqlite3-0.dll
/bin/libssh2-1.dll
/bin/libssl-1_1-x64.dll
/bin/libssl-3-x64.dll
/bin/libstdc++-6.dll
/bin/libthai-0.dll
/bin/libunistring-2.dll

View File

@ -1,4 +1,3 @@
#include <gtkmm.h>
#include <memory>
#include <spdlog/spdlog.h>
#include <spdlog/cfg/env.h>
@ -15,7 +14,6 @@
#include "dialogs/friendpicker.hpp"
#include "dialogs/verificationgate.hpp"
#include "dialogs/textinput.hpp"
#include "abaddon.hpp"
#include "windows/guildsettingswindow.hpp"
#include "windows/profilewindow.hpp"
#include "windows/pinnedwindow.hpp"
@ -817,19 +815,7 @@ void Abaddon::ActionChannelOpened(Snowflake id, bool expand_to) {
const bool can_access = channel->IsDM() || m_discord.HasChannelPermission(m_discord.GetUserData().ID, id, Permission::VIEW_CHANNEL);
if (channel->Type == ChannelType::GUILD_TEXT || channel->Type == ChannelType::GUILD_NEWS)
m_main_window->set_title(std::string(APP_TITLE) + " - #" + *channel->Name);
else {
std::string display;
const auto recipients = channel->GetDMRecipients();
if (recipients.size() > 1)
display = std::to_string(recipients.size()) + " users";
else if (recipients.size() == 1)
display = recipients[0].Username;
else
display = "Empty group";
m_main_window->set_title(std::string(APP_TITLE) + " - " + display);
}
m_main_window->set_title(std::string(APP_TITLE) + " - " + channel->GetDisplayName());
m_main_window->UpdateChatActiveChannel(id, expand_to);
if (m_channels_requested.find(id) == m_channels_requested.end()) {
// dont fire requests we know will fail

View File

@ -1,5 +1,4 @@
#pragma once
#include <gtkmm.h>
#include <memory>
#include <mutex>
#include <string>

View File

@ -1,5 +1,4 @@
#pragma once
#include <gtkmm.h>
#include <unordered_map>
// handles both static and animated

View File

@ -1,8 +1,6 @@
#include "abaddon.hpp"
#include "channels.hpp"
#include "imgmanager.hpp"
#include "statusindicator.hpp"
#include "util.hpp"
#include <algorithm>
#include <map>
#include <unordered_map>
@ -105,6 +103,7 @@ ChannelList::ChannelList()
column->add_attribute(renderer->property_id(), m_columns.m_id);
column->add_attribute(renderer->property_expanded(), m_columns.m_expanded);
column->add_attribute(renderer->property_nsfw(), m_columns.m_nsfw);
column->add_attribute(renderer->property_color(), m_columns.m_color);
m_view.append_column(*column);
m_menu_guild_copy_id.signal_activate().connect([this] {
@ -309,14 +308,51 @@ void ChannelList::UpdateListing() {
auto &discord = Abaddon::Get().GetDiscordClient();
const auto guild_ids = discord.GetUserSortedGuilds();
int sortnum = 0;
for (const auto &guild_id : guild_ids) {
const auto guild = discord.GetGuild(guild_id);
if (!guild.has_value()) continue;
/*
guild_folders looks something like this
"guild_folders": [
{
"color": null,
"guild_ids": [
"8009060___________"
],
"id": null,
"name": null
},
{
"color": null,
"guild_ids": [
"99615594__________",
"86132141__________",
"35450138__________",
"83714048__________"
],
"id": 2853066769,
"name": null
}
]
auto iter = AddGuild(*guild);
(*iter)[m_columns.m_sort] = sortnum++;
so if id != null then its a folder (they can have single entries)
*/
int sort_value = 0;
const auto folders = discord.GetUserSettings().GuildFolders;
if (folders.empty()) {
// fallback if no organization has occurred (guild_folders will be empty)
const auto guild_ids = discord.GetUserSortedGuilds();
for (const auto &guild_id : guild_ids) {
const auto guild = discord.GetGuild(guild_id);
if (!guild.has_value()) continue;
auto iter = AddGuild(*guild, m_model->children());
if (iter) (*iter)[m_columns.m_sort] = sort_value++;
}
} else {
for (const auto &group : folders) {
auto iter = AddFolder(group);
if (iter) (*iter)[m_columns.m_sort] = sort_value++;
}
}
m_updating_listing = false;
@ -324,8 +360,9 @@ void ChannelList::UpdateListing() {
AddPrivateChannels();
}
// TODO update for folders
void ChannelList::UpdateNewGuild(const GuildData &guild) {
AddGuild(guild);
AddGuild(guild, m_model->children());
// update sort order
int sortnum = 0;
for (const auto guild_id : Abaddon::Get().GetDiscordClient().GetUserSortedGuilds()) {
@ -452,6 +489,8 @@ void ChannelList::OnThreadListSync(const ThreadListSyncData &data) {
// get the threads in the guild
std::vector<Snowflake> threads;
auto guild_iter = GetIteratorForGuildFromID(data.GuildID);
if (!guild_iter) return;
std::queue<Gtk::TreeModel::iterator> queue;
queue.push(guild_iter);
@ -567,22 +606,27 @@ void ChannelList::SetActiveChannel(Snowflake id, bool expand_to) {
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 = m_tmp_channel_map.find(id); iter != m_tmp_channel_map.end()) {
Gtk::TreeModel::iterator row_iter;
if (const auto map_iter = m_tmp_row_map.find(id); map_iter != m_tmp_row_map.end()) {
row_iter = map_iter->second;
} else if (const auto map_iter = m_tmp_guild_row_map.find(id); map_iter != m_tmp_guild_row_map.end()) {
row_iter = map_iter->second;
}
if (row_iter) {
if (state.IsExpanded)
m_view.expand_row(m_model->get_path(iter->second), false);
m_view.expand_row(m_model->get_path(row_iter), false);
else
m_view.collapse_row(m_model->get_path(iter->second));
m_view.collapse_row(m_model->get_path(row_iter));
}
self(self, state.Children);
}
};
// top level is guild
for (const auto &[id, state] : root.Children) {
if (const auto iter = GetIteratorForGuildFromID(id)) {
if (const auto iter = GetIteratorForTopLevelFromID(id)) {
if (state.IsExpanded)
m_view.expand_row(m_model->get_path(iter), false);
else
@ -592,7 +636,7 @@ void ChannelList::UseExpansionState(const ExpansionStateRoot &root) {
recurse(recurse, state.Children);
}
m_tmp_channel_map.clear();
m_tmp_row_map.clear();
}
ExpansionStateRoot ChannelList::GetExpansionState() const {
@ -617,15 +661,54 @@ ExpansionStateRoot ChannelList::GetExpansionState() const {
return r;
}
Gtk::TreeModel::iterator ChannelList::AddGuild(const GuildData &guild) {
Gtk::TreeModel::iterator ChannelList::AddFolder(const UserSettingsGuildFoldersEntry &folder) {
if (!folder.ID.has_value()) {
// just a guild
if (!folder.GuildIDs.empty()) {
const auto guild = Abaddon::Get().GetDiscordClient().GetGuild(folder.GuildIDs[0]);
if (guild.has_value()) {
return AddGuild(*guild, m_model->children());
}
}
} else {
auto folder_row = *m_model->append();
folder_row[m_columns.m_type] = RenderType::Folder;
folder_row[m_columns.m_id] = *folder.ID;
m_tmp_row_map[*folder.ID] = folder_row;
if (folder.Name.has_value()) {
folder_row[m_columns.m_name] = Glib::Markup::escape_text(*folder.Name);
} else {
folder_row[m_columns.m_name] = "Folder";
}
if (folder.Color.has_value()) {
folder_row[m_columns.m_color] = IntToRGBA(*folder.Color);
}
int sort_value = 0;
for (const auto &guild_id : folder.GuildIDs) {
const auto guild = Abaddon::Get().GetDiscordClient().GetGuild(guild_id);
if (guild.has_value()) {
auto guild_row = AddGuild(*guild, folder_row->children());
(*guild_row)[m_columns.m_sort] = sort_value++;
}
}
return folder_row;
}
return {};
}
Gtk::TreeModel::iterator ChannelList::AddGuild(const GuildData &guild, const Gtk::TreeNodeChildren &root) {
auto &discord = Abaddon::Get().GetDiscordClient();
auto &img = Abaddon::Get().GetImageManager();
auto guild_row = *m_model->append();
auto guild_row = *m_model->append(root);
guild_row[m_columns.m_type] = RenderType::Guild;
guild_row[m_columns.m_id] = guild.ID;
guild_row[m_columns.m_name] = "<b>" + Glib::Markup::escape_text(guild.Name) + "</b>";
guild_row[m_columns.m_icon] = img.GetPlaceholder(GuildIconSize);
m_tmp_guild_row_map[guild.ID] = guild_row;
if (Abaddon::Get().GetSettings().ShowAnimations && guild.HasAnimatedIcon()) {
const auto cb = [this, id = guild.ID](const Glib::RefPtr<Gdk::PixbufAnimation> &pb) {
@ -677,7 +760,7 @@ Gtk::TreeModel::iterator ChannelList::AddGuild(const GuildData &guild) {
if (it == threads.end()) return;
for (const auto &thread : it->second)
m_tmp_channel_map[thread.ID] = CreateThreadRow(row.children(), thread);
m_tmp_row_map[thread.ID] = CreateThreadRow(row.children(), thread);
};
auto add_voice_participants = [this, &discord](const ChannelData &channel, const Gtk::TreeNodeChildren &root) {
@ -710,7 +793,7 @@ Gtk::TreeModel::iterator ChannelList::AddGuild(const GuildData &guild) {
channel_row[m_columns.m_sort] = *channel.Position + OrphanChannelSortOffset;
channel_row[m_columns.m_nsfw] = channel.NSFW();
add_threads(channel, channel_row);
m_tmp_channel_map[channel.ID] = channel_row;
m_tmp_row_map[channel.ID] = channel_row;
}
for (const auto &[category_id, channels] : categories) {
@ -722,7 +805,7 @@ Gtk::TreeModel::iterator ChannelList::AddGuild(const GuildData &guild) {
cat_row[m_columns.m_name] = Glib::Markup::escape_text(*category->Name);
cat_row[m_columns.m_sort] = *category->Position;
cat_row[m_columns.m_expanded] = true;
m_tmp_channel_map[category_id] = cat_row;
m_tmp_row_map[category_id] = cat_row;
// m_view.expand_row wont work because it might not have channels
for (const auto &channel : channels) {
@ -740,7 +823,7 @@ Gtk::TreeModel::iterator ChannelList::AddGuild(const GuildData &guild) {
channel_row[m_columns.m_sort] = *channel.Position;
channel_row[m_columns.m_nsfw] = channel.NSFW();
add_threads(channel, channel_row);
m_tmp_channel_map[channel.ID] = channel_row;
m_tmp_row_map[channel.ID] = channel_row;
}
}
@ -781,10 +864,33 @@ void ChannelList::UpdateChannelCategory(const ChannelData &channel) {
(*iter)[m_columns.m_name] = Glib::Markup::escape_text(*channel.Name);
}
// todo this all needs refactoring for shooore
Gtk::TreeModel::iterator ChannelList::GetIteratorForTopLevelFromID(Snowflake id) {
for (const auto &child : m_model->children()) {
if ((child[m_columns.m_type] == RenderType::Guild || child[m_columns.m_type] == RenderType::Folder) && child[m_columns.m_id] == id) {
return child;
} else if (child[m_columns.m_type] == RenderType::Folder) {
for (const auto &folder_child : child->children()) {
if (folder_child[m_columns.m_id] == id) {
return folder_child;
}
}
}
}
return {};
}
Gtk::TreeModel::iterator ChannelList::GetIteratorForGuildFromID(Snowflake id) {
for (const auto &child : m_model->children()) {
if (child[m_columns.m_id] == id)
if (child[m_columns.m_type] == RenderType::Guild && child[m_columns.m_id] == id) {
return child;
} else if (child[m_columns.m_type] == RenderType::Folder) {
for (const auto &folder_child : child->children()) {
if (folder_child[m_columns.m_id] == id) {
return folder_child;
}
}
}
}
return {};
}
@ -797,7 +903,7 @@ Gtk::TreeModel::iterator ChannelList::GetIteratorForRowFromID(Snowflake id) {
while (!queue.empty()) {
auto item = queue.front();
if ((*item)[m_columns.m_id] == id) return item;
if ((*item)[m_columns.m_id] == id && (*item)[m_columns.m_type] != RenderType::Guild) return item;
for (const auto &child : item->children())
queue.push(child);
queue.pop();
@ -884,14 +990,10 @@ void ChannelList::AddPrivateChannels() {
auto row = *iter;
row[m_columns.m_type] = RenderType::DM;
row[m_columns.m_id] = dm_id;
row[m_columns.m_name] = Glib::Markup::escape_text(dm->GetDisplayName());
row[m_columns.m_sort] = static_cast<int64_t>(-(dm->LastMessageID.has_value() ? *dm->LastMessageID : dm_id));
row[m_columns.m_icon] = img.GetPlaceholder(DMIconSize);
if (dm->Type == ChannelType::DM && top_recipient.has_value())
row[m_columns.m_name] = Glib::Markup::escape_text(top_recipient->Username);
else if (dm->Type == ChannelType::GROUP_DM)
row[m_columns.m_name] = std::to_string(recipients.size()) + " members";
if (dm->HasIcon()) {
const auto cb = [this, iter](const Glib::RefPtr<Gdk::Pixbuf> &pb) {
if (iter)
@ -921,14 +1023,10 @@ void ChannelList::UpdateCreateDMChannel(const ChannelData &dm) {
auto row = *iter;
row[m_columns.m_type] = RenderType::DM;
row[m_columns.m_id] = dm.ID;
row[m_columns.m_name] = Glib::Markup::escape_text(dm.GetDisplayName());
row[m_columns.m_sort] = static_cast<int64_t>(-(dm.LastMessageID.has_value() ? *dm.LastMessageID : dm.ID));
row[m_columns.m_icon] = img.GetPlaceholder(DMIconSize);
if (dm.Type == ChannelType::DM && top_recipient.has_value())
row[m_columns.m_name] = Glib::Markup::escape_text(top_recipient->Username);
else if (dm.Type == ChannelType::GROUP_DM)
row[m_columns.m_name] = std::to_string(recipients.size()) + " members";
if (top_recipient.has_value()) {
const auto cb = [this, iter](const Glib::RefPtr<Gdk::Pixbuf> &pb) {
if (iter)
@ -1025,6 +1123,7 @@ void ChannelList::MoveRow(const Gtk::TreeModel::iterator &iter, const Gtk::TreeM
M(m_sort);
M(m_nsfw);
M(m_expanded);
M(m_color);
#undef M
// recursively move children
@ -1174,4 +1273,5 @@ ChannelList::ModelColumns::ModelColumns() {
add(m_sort);
add(m_nsfw);
add(m_expanded);
add(m_color);
}

View File

@ -1,10 +1,14 @@
#pragma once
#include <gtkmm.h>
#include <string>
#include <queue>
#include <mutex>
#include <unordered_set>
#include <unordered_map>
#include <gtkmm/paned.h>
#include <gtkmm/scrolledwindow.h>
#include <gtkmm/treemodel.h>
#include <gtkmm/treestore.h>
#include <gtkmm/treeview.h>
#include <sigc++/sigc++.h>
#include "discord/discord.hpp"
#include "state.hpp"
@ -64,6 +68,7 @@ protected:
Gtk::TreeModelColumn<Glib::RefPtr<Gdk::PixbufAnimation>> m_icon_anim;
Gtk::TreeModelColumn<int64_t> m_sort;
Gtk::TreeModelColumn<bool> m_nsfw;
Gtk::TreeModelColumn<std::optional<Gdk::RGBA>> m_color; // for folders right now
// 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
@ -75,13 +80,15 @@ protected:
ModelColumns m_columns;
Glib::RefPtr<Gtk::TreeStore> m_model;
Gtk::TreeModel::iterator AddGuild(const GuildData &guild);
Gtk::TreeModel::iterator AddFolder(const UserSettingsGuildFoldersEntry &folder);
Gtk::TreeModel::iterator AddGuild(const GuildData &guild, const Gtk::TreeNodeChildren &root);
Gtk::TreeModel::iterator UpdateCreateChannelCategory(const ChannelData &channel);
Gtk::TreeModel::iterator CreateThreadRow(const Gtk::TreeNodeChildren &children, const ChannelData &channel);
void UpdateChannelCategory(const ChannelData &channel);
// separation necessary because a channel and guild can share the same id
Gtk::TreeModel::iterator GetIteratorForTopLevelFromID(Snowflake id);
Gtk::TreeModel::iterator GetIteratorForGuildFromID(Snowflake id);
Gtk::TreeModel::iterator GetIteratorForRowFromID(Snowflake id);
Gtk::TreeModel::iterator GetIteratorForRowFromIDOfType(Snowflake id, RenderType type);
@ -172,7 +179,8 @@ protected:
// (GetIteratorForChannelFromID is rather slow)
// only temporary since i dont want to worry about maintaining this map
std::unordered_map<Snowflake, Gtk::TreeModel::iterator> m_tmp_channel_map;
std::unordered_map<Snowflake, Gtk::TreeModel::iterator> m_tmp_row_map;
std::unordered_map<Snowflake, Gtk::TreeModel::iterator> m_tmp_guild_row_map;
public:
using type_signal_action_channel_item_select = sigc::signal<void, Snowflake>;

View File

@ -1,6 +1,4 @@
#include "abaddon.hpp"
#include "channelscellrenderer.hpp"
#include <gtkmm.h>
constexpr static int MentionsRightPad = 7;
#ifndef M_PI
@ -18,7 +16,8 @@ CellRendererChannels::CellRendererChannels()
, m_property_pixbuf(*this, "pixbuf")
, m_property_pixbuf_animation(*this, "pixbuf-animation")
, m_property_expanded(*this, "expanded")
, m_property_nsfw(*this, "nsfw") {
, m_property_nsfw(*this, "nsfw")
, m_property_color(*this, "color") {
property_mode() = Gtk::CELL_RENDERER_MODE_ACTIVATABLE;
property_xpad() = 2;
property_ypad() = 2;
@ -55,8 +54,14 @@ Glib::PropertyProxy<bool> CellRendererChannels::property_nsfw() {
return m_property_nsfw.get_proxy();
}
Glib::PropertyProxy<std::optional<Gdk::RGBA>> CellRendererChannels::property_color() {
return m_property_color.get_proxy();
}
void CellRendererChannels::get_preferred_width_vfunc(Gtk::Widget &widget, int &minimum_width, int &natural_width) const {
switch (m_property_type.get_value()) {
case RenderType::Folder:
return get_preferred_width_vfunc_folder(widget, minimum_width, natural_width);
case RenderType::Guild:
return get_preferred_width_vfunc_guild(widget, minimum_width, natural_width);
case RenderType::Category:
@ -80,6 +85,8 @@ void CellRendererChannels::get_preferred_width_vfunc(Gtk::Widget &widget, int &m
void CellRendererChannels::get_preferred_width_for_height_vfunc(Gtk::Widget &widget, int height, int &minimum_width, int &natural_width) const {
switch (m_property_type.get_value()) {
case RenderType::Folder:
return get_preferred_width_for_height_vfunc_folder(widget, height, minimum_width, natural_width);
case RenderType::Guild:
return get_preferred_width_for_height_vfunc_guild(widget, height, minimum_width, natural_width);
case RenderType::Category:
@ -103,6 +110,8 @@ void CellRendererChannels::get_preferred_width_for_height_vfunc(Gtk::Widget &wid
void CellRendererChannels::get_preferred_height_vfunc(Gtk::Widget &widget, int &minimum_height, int &natural_height) const {
switch (m_property_type.get_value()) {
case RenderType::Folder:
return get_preferred_height_vfunc_folder(widget, minimum_height, natural_height);
case RenderType::Guild:
return get_preferred_height_vfunc_guild(widget, minimum_height, natural_height);
case RenderType::Category:
@ -126,6 +135,8 @@ void CellRendererChannels::get_preferred_height_vfunc(Gtk::Widget &widget, int &
void CellRendererChannels::get_preferred_height_for_width_vfunc(Gtk::Widget &widget, int width, int &minimum_height, int &natural_height) const {
switch (m_property_type.get_value()) {
case RenderType::Folder:
return get_preferred_height_for_width_vfunc_folder(widget, width, minimum_height, natural_height);
case RenderType::Guild:
return get_preferred_height_for_width_vfunc_guild(widget, width, minimum_height, natural_height);
case RenderType::Category:
@ -149,6 +160,8 @@ void CellRendererChannels::get_preferred_height_for_width_vfunc(Gtk::Widget &wid
void CellRendererChannels::render_vfunc(const Cairo::RefPtr<Cairo::Context> &cr, Gtk::Widget &widget, const Gdk::Rectangle &background_area, const Gdk::Rectangle &cell_area, Gtk::CellRendererState flags) {
switch (m_property_type.get_value()) {
case RenderType::Folder:
return render_vfunc_folder(cr, widget, background_area, cell_area, flags);
case RenderType::Guild:
return render_vfunc_guild(cr, widget, background_area, cell_area, flags);
case RenderType::Category:
@ -170,6 +183,69 @@ void CellRendererChannels::render_vfunc(const Cairo::RefPtr<Cairo::Context> &cr,
}
}
// folder functions
void CellRendererChannels::get_preferred_width_vfunc_folder(Gtk::Widget &widget, int &minimum_width, int &natural_width) const {
m_renderer_text.get_preferred_width(widget, minimum_width, natural_width);
}
void CellRendererChannels::get_preferred_width_for_height_vfunc_folder(Gtk::Widget &widget, int height, int &minimum_width, int &natural_width) const {
m_renderer_text.get_preferred_width_for_height(widget, height, minimum_width, natural_width);
}
void CellRendererChannels::get_preferred_height_vfunc_folder(Gtk::Widget &widget, int &minimum_height, int &natural_height) const {
m_renderer_text.get_preferred_height(widget, minimum_height, natural_height);
}
void CellRendererChannels::get_preferred_height_for_width_vfunc_folder(Gtk::Widget &widget, int width, int &minimum_height, int &natural_height) const {
m_renderer_text.get_preferred_height_for_width(widget, width, minimum_height, natural_height);
}
void CellRendererChannels::render_vfunc_folder(const Cairo::RefPtr<Cairo::Context> &cr, Gtk::Widget &widget, const Gdk::Rectangle &background_area, const Gdk::Rectangle &cell_area, Gtk::CellRendererState flags) {
constexpr static int len = 5;
int x1, y1, x2, y2, x3, y3;
if (property_expanded()) {
x1 = background_area.get_x() + 7;
y1 = background_area.get_y() + background_area.get_height() / 2 - len;
x2 = background_area.get_x() + 7 + len;
y2 = background_area.get_y() + background_area.get_height() / 2 + len;
x3 = background_area.get_x() + 7 + len * 2;
y3 = background_area.get_y() + background_area.get_height() / 2 - len;
} else {
x1 = background_area.get_x() + 7;
y1 = background_area.get_y() + background_area.get_height() / 2 - len;
x2 = background_area.get_x() + 7 + len * 2;
y2 = background_area.get_y() + background_area.get_height() / 2;
x3 = background_area.get_x() + 7;
y3 = background_area.get_y() + background_area.get_height() / 2 + len;
}
cr->move_to(x1, y1);
cr->line_to(x2, y2);
cr->line_to(x3, y3);
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();
Gtk::Requisition text_minimum, text_natural;
m_renderer_text.get_preferred_size(widget, text_minimum, text_natural);
const int text_x = background_area.get_x() + 22;
const int text_y = background_area.get_y() + background_area.get_height() / 2 - text_natural.height / 2;
const int text_w = text_natural.width;
const int text_h = text_natural.height;
Gdk::Rectangle text_cell_area(text_x, text_y, text_w, text_h);
static const auto color = Gdk::RGBA(Abaddon::Get().GetSettings().ChannelColor);
if (m_property_color.get_value().has_value()) {
m_renderer_text.property_foreground_rgba() = *m_property_color.get_value();
} else {
m_renderer_text.property_foreground_rgba() = color;
}
m_renderer_text.render(cr, widget, background_area, text_cell_area, flags);
m_renderer_text.property_foreground_set() = false;
}
// guild functions
void CellRendererChannels::get_preferred_width_vfunc_guild(Gtk::Widget &widget, int &minimum_width, int &natural_width) const {

View File

@ -1,11 +1,11 @@
#pragma once
#include <gtkmm/cellrenderertext.h>
#include <gdkmm/pixbufanimation.h>
#include <glibmm/property.h>
#include <map>
#include "discord/snowflake.hpp"
enum class RenderType : uint8_t {
Folder,
Guild,
Category,
TextChannel,
@ -33,6 +33,7 @@ public:
Glib::PropertyProxy<Glib::RefPtr<Gdk::PixbufAnimation>> property_icon_animation();
Glib::PropertyProxy<bool> property_expanded();
Glib::PropertyProxy<bool> property_nsfw();
Glib::PropertyProxy<std::optional<Gdk::RGBA>> property_color();
protected:
void get_preferred_width_vfunc(Gtk::Widget &widget, int &minimum_width, int &natural_width) const override;
@ -45,6 +46,17 @@ protected:
const Gdk::Rectangle &cell_area,
Gtk::CellRendererState flags) override;
// guild functions
void get_preferred_width_vfunc_folder(Gtk::Widget &widget, int &minimum_width, int &natural_width) const;
void get_preferred_width_for_height_vfunc_folder(Gtk::Widget &widget, int height, int &minimum_width, int &natural_width) const;
void get_preferred_height_vfunc_folder(Gtk::Widget &widget, int &minimum_height, int &natural_height) const;
void get_preferred_height_for_width_vfunc_folder(Gtk::Widget &widget, int width, int &minimum_height, int &natural_height) const;
void render_vfunc_folder(const Cairo::RefPtr<Cairo::Context> &cr,
Gtk::Widget &widget,
const Gdk::Rectangle &background_area,
const Gdk::Rectangle &cell_area,
Gtk::CellRendererState flags);
// 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;
@ -148,6 +160,7 @@ private:
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
Glib::Property<std::optional<Gdk::RGBA>> m_property_color; // folder
// 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

View File

@ -1,7 +1,6 @@
#ifdef WITH_LIBHANDY
#include "channeltabswitcherhandy.hpp"
#include "abaddon.hpp"
void selected_page_notify_cb(HdyTabView *view, GParamSpec *pspec, ChannelTabSwitcherHandy *switcher) {
auto *page = hdy_tab_view_get_selected_page(view);

View File

@ -1,8 +1,7 @@
#pragma once
// perhaps this should be conditionally included within cmakelists?
#ifdef WITH_LIBHANDY
#include <gtkmm/box.h>
#include <unordered_map>
#include <unordered_map>
#include <handy.h>
#include "discord/snowflake.hpp"
#include "state.hpp"

View File

@ -1,5 +1,4 @@
#include "chatinput.hpp"
#include "abaddon.hpp"
#include "constants.hpp"
#include <filesystem>
@ -12,7 +11,10 @@ ChatInputText::ChatInputText() {
// hack
auto cb = [this](GdkEventKey *e) -> bool {
return event(reinterpret_cast<GdkEvent *>(e));
// we cant use Widget::event here specifically or else for some reason
// it prevents the default binding set from activating sometimes
// specifically event() will return true (why) preventing default handler from running
return m_signal_key_press_proxy.emit(e);
};
m_textview.signal_key_press_event().connect(cb, false);
m_textview.set_hexpand(false);
@ -91,12 +93,16 @@ ChatInputText::type_signal_image_paste ChatInputText::signal_image_paste() {
return m_signal_image_paste;
}
ChatInputText::type_signal_key_press_proxy ChatInputText::signal_key_press_proxy() {
return m_signal_key_press_proxy;
}
ChatInputTextContainer::ChatInputTextContainer() {
// triple hack !!!
auto cb = [this](GdkEventKey *e) -> bool {
return event(reinterpret_cast<GdkEvent *>(e));
};
m_input.signal_key_press_event().connect(cb, false);
m_input.signal_key_press_proxy().connect(cb);
m_upload_img.property_icon_name() = "document-send-symbolic";
m_upload_img.property_icon_size() = Gtk::ICON_SIZE_LARGE_TOOLBAR;

View File

@ -1,5 +1,4 @@
#pragma once
#include <gtkmm.h>
#include "discord/chatsubmitparams.hpp"
#include "discord/permissions.hpp"
@ -84,15 +83,18 @@ public:
using type_signal_submit = sigc::signal<bool, Glib::ustring>;
using type_signal_escape = sigc::signal<void>;
using type_signal_image_paste = sigc::signal<void, Glib::RefPtr<Gdk::Pixbuf>>;
using type_signal_key_press_proxy = sigc::signal<bool, GdkEventKey *>;
type_signal_submit signal_submit();
type_signal_escape signal_escape();
type_signal_image_paste signal_image_paste();
type_signal_key_press_proxy signal_key_press_proxy();
private:
type_signal_submit m_signal_submit;
type_signal_escape m_signal_escape;
type_signal_image_paste m_signal_image_paste;
type_signal_key_press_proxy m_signal_key_press_proxy;
};
// file upload, text

View File

@ -1,7 +1,5 @@
#include <filesystem>
#include "chatinputindicator.hpp"
#include "abaddon.hpp"
#include "util.hpp"
constexpr static const int MaxUsersInIndicator = 4;

View File

@ -1,5 +1,4 @@
#pragma once
#include <gtkmm.h>
#include <unordered_map>
#include "discord/message.hpp"
#include "discord/user.hpp"

View File

@ -1,12 +1,10 @@
#include "chatlist.hpp"
#include "abaddon.hpp"
#include "chatmessage.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);
get_vadjustment()->signal_value_changed().connect(sigc::mem_fun(*this, &ChatList::OnVAdjustmentValueChanged));

View File

@ -1,5 +1,4 @@
#pragma once
#include <gtkmm.h>
#include <map>
#include <vector>
#include "discord/message.hpp"

View File

@ -1,7 +1,5 @@
#include "abaddon.hpp"
#include "chatmessage.hpp"
#include "lazyimage.hpp"
#include "util.hpp"
#include <unordered_map>
constexpr static int EmojiSize = 24; // settings eventually
@ -166,7 +164,8 @@ Gtk::TextView *ChatMessageItemContainer::CreateTextComponent(const Message &data
if (data.IsPending)
tv->get_style_context()->add_class("pending");
tv->get_style_context()->add_class("message-text");
tv->set_can_focus(false);
tv->set_can_focus(true);
tv->set_cursor_visible(false);
tv->set_editable(false);
tv->set_wrap_mode(Gtk::WRAP_WORD_CHAR);
tv->set_halign(Gtk::ALIGN_FILL);
@ -335,6 +334,7 @@ Gtk::Widget *ChatMessageItemContainer::CreateEmbedComponent(const EmbedData &emb
author_lbl->set_hexpand(false);
author_lbl->set_text(*embed.Author->Name);
author_lbl->get_style_context()->add_class("embed-author");
author_lbl->set_selectable(true);
author_box->add(*author_lbl);
}
}
@ -351,6 +351,7 @@ Gtk::Widget *ChatMessageItemContainer::CreateEmbedComponent(const EmbedData &emb
title_label->set_line_wrap(true);
title_label->set_line_wrap_mode(Pango::WRAP_WORD_CHAR);
title_label->set_max_width_chars(50);
title_label->set_selectable(true);
title_ev->add(*title_label);
content->pack_start(*title_ev);
@ -380,6 +381,7 @@ Gtk::Widget *ChatMessageItemContainer::CreateEmbedComponent(const EmbedData &emb
desc_label->set_halign(Gtk::ALIGN_START);
desc_label->set_hexpand(false);
desc_label->get_style_context()->add_class("embed-description");
desc_label->set_selectable(true);
content->pack_start(*desc_label);
}
}
@ -422,6 +424,8 @@ Gtk::Widget *ChatMessageItemContainer::CreateEmbedComponent(const EmbedData &emb
field_box->pack_start(*field_val);
field_lbl->get_style_context()->add_class("embed-field-title");
field_val->get_style_context()->add_class("embed-field-value");
field_lbl->set_selectable(true);
field_val->set_selectable(true);
flow->insert(*field_box, -1);
}
}
@ -445,6 +449,7 @@ Gtk::Widget *ChatMessageItemContainer::CreateEmbedComponent(const EmbedData &emb
footer_lbl->set_hexpand(false);
footer_lbl->set_text(embed.Footer->Text);
footer_lbl->get_style_context()->add_class("embed-footer");
footer_lbl->set_selectable(true);
content->pack_start(*footer_lbl);
}
@ -1170,8 +1175,6 @@ ChatMessageHeader::ChatMessageHeader(const Message &data)
m_meta_box.set_hexpand(true);
m_meta_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;

View File

@ -1,5 +1,4 @@
#pragma once
#include <gtkmm.h>
#include "discord/discord.hpp"
class ChatMessageItemContainer : public Gtk::EventBox {

View File

@ -1,5 +1,4 @@
#include "chatwindow.hpp"
#include "abaddon.hpp"
#include "chatinputindicator.hpp"
#include "ratelimitindicator.hpp"
#include "chatinput.hpp"

View File

@ -1,5 +1,4 @@
#pragma once
#include <gtkmm.h>
#include <string>
#include <set>
#include "discord/discord.hpp"

View File

@ -1,8 +1,6 @@
#include <unordered_set>
#include <utility>
#include "completer.hpp"
#include "abaddon.hpp"
#include "util.hpp"
constexpr const int CompleterHeight = 150;
constexpr const int MaxCompleterEntries = 30;

View File

@ -1,5 +1,4 @@
#pragma once
#include <gtkmm.h>
#include <functional>
#include "lazyimage.hpp"
#include "discord/snowflake.hpp"

View File

@ -1,5 +1,4 @@
#pragma once
#include <gtkmm.h>
class DragListBox : public Gtk::ListBox {
public:

View File

@ -1,5 +1,4 @@
#include "friendslist.hpp"
#include "abaddon.hpp"
#include "lazyimage.hpp"
using namespace std::string_literals;

View File

@ -1,5 +1,4 @@
#pragma once
#include <gtkmm.h>
#include "discord/objects.hpp"
class FriendsListAddComponent : public Gtk::Box {

View File

@ -1,7 +1,6 @@
#include "lazyimage.hpp"
#include <utility>
#include "abaddon.hpp"
LazyImage::LazyImage(int w, int h, bool use_placeholder)
: m_width(w)

View File

@ -1,5 +1,4 @@
#pragma once
#include <gtkmm.h>
// loads an image only when the widget is drawn for the first time
class LazyImage : public Gtk::Image {

View File

@ -1,6 +1,4 @@
#include "memberlist.hpp"
#include "abaddon.hpp"
#include "util.hpp"
#include "lazyimage.hpp"
#include "statusindicator.hpp"

View File

@ -1,5 +1,4 @@
#pragma once
#include <gtkmm.h>
#include <mutex>
#include <unordered_map>
#include <optional>

View File

@ -1,5 +1,4 @@
#include "progressbar.hpp"
#include "abaddon.hpp"
MessageUploadProgressBar::MessageUploadProgressBar() {
get_style_context()->add_class("message-progress");

View File

@ -1,5 +1,4 @@
#pragma once
#include <gtkmm/progressbar.h>
#include <string>
class MessageUploadProgressBar : public Gtk::ProgressBar {

View File

@ -1,5 +1,4 @@
#include "ratelimitindicator.hpp"
#include "abaddon.hpp"
#include <filesystem>
RateLimitIndicator::RateLimitIndicator()

View File

@ -1,5 +1,4 @@
#pragma once
#include <gtkmm.h>
#include <unordered_map>
#include <chrono>
#include "discord/message.hpp"

View File

@ -1,5 +1,4 @@
#include "statusindicator.hpp"
#include "abaddon.hpp"
static const constexpr int Diameter = 8;

View File

@ -1,5 +1,4 @@
#pragma once
#include <gtkmm.h>
#include "discord/snowflake.hpp"
#include "discord/activity.hpp"

View File

@ -1,5 +1,4 @@
#pragma once
#include <gtkmm.h>
class ConfirmDialog : public Gtk::Dialog {
public:

View File

@ -1,5 +1,4 @@
#pragma once
#include <gtkmm.h>
#include <string>
class EditMessageDialog : public Gtk::Dialog {

View File

@ -1,5 +1,4 @@
#include "friendpicker.hpp"
#include "abaddon.hpp"
FriendPickerDialog::FriendPickerDialog(Gtk::Window &parent)
: Gtk::Dialog("Pick a friend", parent, true)

View File

@ -1,5 +1,4 @@
#pragma once
#include <gtkmm.h>
#include "discord/snowflake.hpp"
class FriendPickerDialog : public Gtk::Dialog {

View File

@ -1,5 +1,4 @@
#pragma once
#include <gtkmm.h>
#include "discord/objects.hpp"
class SetStatusDialog : public Gtk::Dialog {

View File

@ -1,6 +1,4 @@
#pragma once
#include <gtkmm/dialog.h>
#include <gtkmm/entry.h>
class TextInputDialog : public Gtk::Dialog {
public:

View File

@ -1,5 +1,4 @@
#pragma once
#include <gtkmm.h>
#include <string>
class TokenDialog : public Gtk::Dialog {

View File

@ -1,5 +1,4 @@
#include "verificationgate.hpp"
#include "abaddon.hpp"
VerificationGateDialog::VerificationGateDialog(Gtk::Window &parent, Snowflake guild_id)
: Gtk::Dialog("Verification Required", parent, true)

View File

@ -1,5 +1,4 @@
#pragma once
#include <gtkmm.h>
#include <optional>
#include "discord/objects.hpp"

View File

@ -1,7 +1,6 @@
#pragma once
#include <string>
#include <optional>
#include "util.hpp"
#include "json.hpp"
#include "snowflake.hpp"

View File

@ -1,4 +1,3 @@
#include "abaddon.hpp"
#include "channel.hpp"
void from_json(const nlohmann::json &j, ThreadMetadataData &m) {
@ -84,6 +83,10 @@ bool ChannelData::IsCategory() const noexcept {
return Type == ChannelType::GUILD_CATEGORY;
}
bool ChannelData::IsText() const noexcept {
return Type == ChannelType::GUILD_TEXT || Type == ChannelType::GUILD_NEWS;
}
bool ChannelData::HasIcon() const noexcept {
return Icon.has_value();
}
@ -102,15 +105,14 @@ std::string ChannelData::GetIconURL() const {
std::string ChannelData::GetDisplayName() const {
if (Name.has_value()) {
return "#" + *Name;
if (IsDM()) {
return *Name;
} else {
return "#" + *Name;
}
} else {
const auto recipients = GetDMRecipients();
if (Type == ChannelType::DM && !recipients.empty())
return recipients[0].Username;
else if (Type == ChannelType::GROUP_DM)
return std::to_string(recipients.size()) + " members";
return GetRecipientsDisplay();
}
return "Unknown";
}
std::vector<Snowflake> ChannelData::GetChildIDs() const {
@ -139,6 +141,25 @@ std::vector<UserData> ChannelData::GetDMRecipients() const {
return {};
}
bool ChannelData::IsText() const noexcept {
return Type == ChannelType::GUILD_TEXT || Type == ChannelType::GUILD_NEWS;
std::string ChannelData::GetRecipientsDisplay() const {
const auto self_id = Abaddon::Get().GetDiscordClient().GetUserData().ID;
const auto recipients = GetDMRecipients();
if (Type == ChannelType::DM && !recipients.empty()) {
return recipients[0].Username;
}
Glib::ustring r;
for (size_t i = 0; i < recipients.size(); i++) {
const auto &recipient = recipients[i];
r += recipient.Username;
if (i < recipients.size() - 1) {
r += ", ";
}
}
if (r.empty()) r = "Unnamed";
return r;
}

View File

@ -106,4 +106,5 @@ struct ChannelData {
[[nodiscard]] std::vector<Snowflake> GetChildIDs() const;
[[nodiscard]] std::optional<PermissionOverwrite> GetOverwrite(Snowflake id) const;
[[nodiscard]] std::vector<UserData> GetDMRecipients() const;
[[nodiscard]] std::string GetRecipientsDisplay() const;
};

View File

@ -1,6 +1,4 @@
#include "abaddon.hpp"
#include "discord.hpp"
#include "util.hpp"
#include <spdlog/spdlog.h>
#include <cinttypes>
#include <utility>
@ -354,12 +352,10 @@ bool DiscordClient::HasChannelPermission(Snowflake user_id, Snowflake channel_id
}
Permission DiscordClient::ComputePermissions(Snowflake member_id, Snowflake guild_id) const {
const auto member = GetMember(member_id, guild_id);
const auto guild = GetGuild(guild_id);
if (!member.has_value() || !guild.has_value())
return Permission::NONE;
const auto member_roles = m_store.GetMemberRoles(guild_id, member_id);
const auto guild_owner = m_store.GetGuildOwner(guild_id);
if (guild->OwnerID == member_id)
if (guild_owner == member_id)
return Permission::ALL;
const auto everyone = GetRole(guild_id);
@ -367,7 +363,7 @@ Permission DiscordClient::ComputePermissions(Snowflake member_id, Snowflake guil
return Permission::NONE;
Permission perms = everyone->Permissions;
for (const auto role_id : member->Roles) {
for (const auto role_id : member_roles) {
const auto role = GetRole(role_id);
if (role.has_value())
perms |= role->Permissions;
@ -384,8 +380,8 @@ Permission DiscordClient::ComputeOverwrites(Permission base, Snowflake member_id
return Permission::ALL;
const auto channel = GetChannel(channel_id);
const auto member = GetMember(member_id, *channel->GuildID);
if (!member.has_value() || !channel.has_value())
const auto member_roles = m_store.GetMemberRoles(*channel->GuildID, member_id);
if (!channel.has_value())
return Permission::NONE;
Permission perms = base;
@ -397,7 +393,7 @@ Permission DiscordClient::ComputeOverwrites(Permission base, Snowflake member_id
Permission allow = Permission::NONE;
Permission deny = Permission::NONE;
for (const auto role_id : member->Roles) {
for (const auto role_id : member_roles) {
const auto overwrite = GetPermissionOverwrite(channel_id, role_id);
if (overwrite.has_value()) {
allow |= overwrite->Allow;
@ -1280,6 +1276,10 @@ void DiscordClient::SetUserAgent(const std::string &agent) {
m_websocket.SetUserAgent(agent);
}
void DiscordClient::SetDumpReady(bool dump) {
m_dump_ready = dump;
}
bool DiscordClient::IsChannelMuted(Snowflake id) const noexcept {
return m_muted_channels.find(id) != m_muted_channels.end();
}
@ -1650,6 +1650,17 @@ void DiscordClient::ProcessNewGuild(GuildData &guild) {
void DiscordClient::HandleGatewayReady(const GatewayMessage &msg) {
m_ready_received = true;
if (m_dump_ready) {
const auto name = "./payload_ready-" + Glib::DateTime::create_now_utc().format("%Y-%m-%d_%H-%M-%S") + ".json";
auto *fp = std::fopen(name.c_str(), "wb");
if (fp != nullptr) {
const auto contents = msg.Data.dump(4);
std::fwrite(contents.data(), contents.size(), 1, fp);
std::fclose(fp);
}
}
ReadyEventData data = msg.Data;
for (auto &g : data.Guilds)
ProcessNewGuild(g);
@ -2428,6 +2439,10 @@ std::set<Snowflake> DiscordClient::GetPrivateChannels() const {
return {};
}
const UserSettings &DiscordClient::GetUserSettings() const {
return m_user_settings;
}
EPremiumType DiscordClient::GetSelfPremiumType() const {
const auto &data = GetUserData();
if (data.PremiumType.has_value())

View File

@ -37,6 +37,7 @@ public:
std::vector<Message> GetMessagesForChannel(Snowflake id, size_t limit = 50) const;
std::vector<Message> GetMessagesBefore(Snowflake channel_id, Snowflake message_id, size_t limit = 50) const;
std::set<Snowflake> GetPrivateChannels() const;
const UserSettings &GetUserSettings() const;
EPremiumType GetSelfPremiumType() const;
@ -205,6 +206,8 @@ public:
void UpdateToken(const std::string &token);
void SetUserAgent(const std::string &agent);
void SetDumpReady(bool dump);
bool IsChannelMuted(Snowflake id) const noexcept;
bool IsGuildMuted(Snowflake id) const noexcept;
int GetUnreadStateForChannel(Snowflake id) const noexcept;
@ -223,6 +226,8 @@ private:
std::vector<uint8_t> m_decompress_buf;
z_stream m_zstream;
bool m_dump_ready = false;
static std::string GetAPIURL();
static std::string GetGatewayURL();

View File

@ -1,5 +1,4 @@
#include "guild.hpp"
#include "abaddon.hpp"
void from_json(const nlohmann::json &j, GuildData &m) {
JS_D("id", m.ID);

View File

@ -1,6 +1,5 @@
#include "interactions.hpp"
#include "json.hpp"
#include "abaddon.hpp"
void from_json(const nlohmann::json &j, MessageInteractionData &m) {
JS_D("id", m.ID);

View File

@ -1,24 +1,24 @@
#pragma once
#include <nlohmann/json.hpp>
#include <optional>
#include "util.hpp"
#include "misc/is_optional.hpp"
namespace detail { // more or less because idk what to name this stuff
template<typename T>
inline void json_direct(const ::nlohmann::json &j, const char *key, T &val) {
if constexpr (::util::is_optional<T>::value)
inline void json_direct(const nlohmann::json &j, const char *key, T &val) {
if constexpr (is_optional<T>::value)
val = j.at(key).get<typename T::value_type>();
else
j.at(key).get_to(val);
}
template<typename T>
inline void json_optional(const ::nlohmann::json &j, const char *key, T &val) {
if constexpr (::util::is_optional<T>::value) {
inline void json_optional(const nlohmann::json &j, const char *key, T &val) {
if constexpr (is_optional<T>::value) {
if (j.contains(key))
val = j.at(key).get<typename T::value_type>();
else
val = ::std::nullopt;
val = std::nullopt;
} else {
if (j.contains(key))
j.at(key).get_to(val);
@ -26,13 +26,13 @@ inline void json_optional(const ::nlohmann::json &j, const char *key, T &val) {
}
template<typename T>
inline void json_nullable(const ::nlohmann::json &j, const char *key, T &val) {
if constexpr (::util::is_optional<T>::value) {
inline void json_nullable(const nlohmann::json &j, const char *key, T &val) {
if constexpr (is_optional<T>::value) {
const auto &at = j.at(key);
if (!at.is_null())
val = at.get<typename T::value_type>();
else
val = ::std::nullopt;
val = std::nullopt;
} else {
const auto &at = j.at(key);
if (!at.is_null())
@ -41,16 +41,16 @@ inline void json_nullable(const ::nlohmann::json &j, const char *key, T &val) {
}
template<typename T>
inline void json_optional_nullable(const ::nlohmann::json &j, const char *key, T &val) {
if constexpr (::util::is_optional<T>::value) {
inline void json_optional_nullable(const nlohmann::json &j, const char *key, T &val) {
if constexpr (is_optional<T>::value) {
if (j.contains(key)) {
const auto &at = j.at(key);
if (!at.is_null())
val = at.get<typename T::value_type>();
else
val = ::std::nullopt;
val = std::nullopt;
} else {
val = ::std::nullopt;
val = std::nullopt;
}
} else {
if (j.contains(key)) {
@ -62,14 +62,14 @@ inline void json_optional_nullable(const ::nlohmann::json &j, const char *key, T
}
template<typename T>
inline void json_update_optional_nullable(const ::nlohmann::json &j, const char *key, T &val) {
if constexpr (::util::is_optional<T>::value) {
inline void json_update_optional_nullable(const nlohmann::json &j, const char *key, T &val) {
if constexpr (is_optional<T>::value) {
if (j.contains(key)) {
const auto &at = j.at(key);
if (!at.is_null())
val = at.get<typename T::value_type>();
else
val = ::std::nullopt;
val = std::nullopt;
}
} else {
if (j.contains(key)) {
@ -83,8 +83,8 @@ inline void json_update_optional_nullable(const ::nlohmann::json &j, const char
}
template<typename T, typename U>
inline void json_update_optional_nullable_default(const ::nlohmann::json &j, const char *key, T &val, const U &fallback) {
if constexpr (::util::is_optional<T>::value) {
inline void json_update_optional_nullable_default(const nlohmann::json &j, const char *key, T &val, const U &fallback) {
if constexpr (is_optional<T>::value) {
if (j.contains(key)) {
const auto &at = j.at(key);
if (at.is_null())

View File

@ -1,5 +1,4 @@
#include "member.hpp"
#include "abaddon.hpp"
void from_json(const nlohmann::json &j, GuildMember &m) {
JS_O("user", m.User);

View File

@ -1,8 +1,8 @@
#pragma once
#include <cstdint>
#include "snowflake.hpp"
#include "json.hpp"
#include "util.hpp"
#include "misc/bitwise.hpp"
#include "snowflake.hpp"
constexpr static uint64_t PERMISSION_MAX_BIT = 36;
enum class Permission : uint64_t {
@ -46,6 +46,7 @@ enum class Permission : uint64_t {
ALL = 0x1FFFFFFFFFULL,
};
template<>
struct Bitwise<Permission> {
static const bool enable = true;

View File

@ -1,5 +1,4 @@
#include "snowflake.hpp"
#include "util.hpp"
#include <chrono>
#include <ctime>
#include <iomanip>

View File

@ -473,6 +473,40 @@ std::vector<BanData> Store::GetBans(Snowflake guild_id) const {
return ret;
}
Snowflake Store::GetGuildOwner(Snowflake guild_id) const {
auto &s = m_stmt_get_guild_owner;
s->Bind(1, guild_id);
if (s->FetchOne()) {
Snowflake ret;
s->Get(0, ret);
s->Reset();
return ret;
}
s->Reset();
return Snowflake::Invalid;
}
std::vector<Snowflake> Store::GetMemberRoles(Snowflake guild_id, Snowflake user_id) const {
std::vector<Snowflake> ret;
auto &s = m_stmt_get_member_roles;
s->Bind(1, user_id);
s->Bind(2, guild_id);
while (s->FetchOne()) {
auto &f = ret.emplace_back();
s->Get(0, f);
}
s->Reset();
return ret;
}
std::vector<Message> Store::GetLastMessages(Snowflake id, size_t num) const {
auto &s = m_stmt_get_last_msgs;
std::vector<Message> msgs;
@ -2198,6 +2232,14 @@ bool Store::CreateStatements() {
return false;
}
m_stmt_get_guild_owner = std::make_unique<Statement>(m_db, R"(
SELECT owner_id FROM guilds WHERE id = ?
)");
if (!m_stmt_get_guild_owner->OK()) {
fprintf(stderr, "failed to prepare get guild owner statement: %s\n", m_db.ErrStr());
return false;
}
return true;
}

View File

@ -1,5 +1,4 @@
#pragma once
#include "util.hpp"
#include "objects.hpp"
#include <unordered_map>
#include <unordered_set>
@ -39,6 +38,9 @@ public:
std::optional<BanData> GetBan(Snowflake guild_id, Snowflake user_id) const;
std::vector<BanData> GetBans(Snowflake guild_id) const;
Snowflake GetGuildOwner(Snowflake guild_id) const;
std::vector<Snowflake> GetMemberRoles(Snowflake guild_id, Snowflake user_id) const;
std::vector<Message> GetLastMessages(Snowflake id, size_t num) const;
std::vector<Message> GetMessagesBefore(Snowflake channel_id, Snowflake message_id, size_t limit) const;
std::vector<Message> GetPinnedMessages(Snowflake channel_id) const;
@ -308,5 +310,6 @@ private:
STMT(get_chan_ids_parent);
STMT(get_guild_member_ids);
STMT(clr_role);
STMT(get_guild_owner);
#undef STMT
};

View File

@ -1,5 +1,4 @@
#include "user.hpp"
#include "abaddon.hpp"
bool UserData::IsABot() const noexcept {
return IsBot.has_value() && *IsBot;

View File

@ -1,13 +1,14 @@
#pragma once
#include "json.hpp"
#include "snowflake.hpp"
#include <optional>
#include <string>
struct UserSettingsGuildFoldersEntry {
int Color = -1; // null
std::optional<int> Color;
std::vector<Snowflake> GuildIDs;
Snowflake ID; // null (this can be a snowflake as a string or an int that isnt a snowflake lol)
std::string Name; // null
std::optional<Snowflake> ID; // (this can be a snowflake as a string or an int that isnt a snowflake lol)
std::optional<std::string> Name;
friend void from_json(const nlohmann::json &j, UserSettingsGuildFoldersEntry &m);
};

View File

@ -3,7 +3,6 @@
#include <cstdio>
#include <unordered_map>
#include <vector>
#include <gtkmm.h>
// shoutout to gtk for only supporting .svg's sometimes

View File

@ -1,4 +1,3 @@
#include "abaddon.hpp"
#include "filecache.hpp"
#include <utility>

View File

@ -8,7 +8,6 @@
#include <unordered_map>
#include <future>
#include <mutex>
#include "util.hpp"
#include "http.hpp"
class FileCacheWorkerThread {

View File

@ -1,8 +1,6 @@
#include "imgmanager.hpp"
#include <utility>
#include "util.hpp"
#include "abaddon.hpp"
ImageManager::ImageManager() {
m_cb_dispatcher.connect(sigc::mem_fun(*this, &ImageManager::RunCallbacks));

View File

@ -3,7 +3,6 @@
#include <unordered_map>
#include <functional>
#include <queue>
#include <gtkmm.h>
#include "filecache.hpp"
class ImageManager {

38
src/misc/bitwise.hpp Normal file
View File

@ -0,0 +1,38 @@
#pragma once
#include <type_traits>
template<typename T>
struct Bitwise {
static const bool enable = false;
};
template<typename T>
typename std::enable_if<Bitwise<T>::enable, T>::type operator|(T a, T b) {
using x = typename std::underlying_type<T>::type;
return static_cast<T>(static_cast<x>(a) | static_cast<x>(b));
}
template<typename T>
typename std::enable_if<Bitwise<T>::enable, T>::type operator|=(T &a, T b) {
using x = typename std::underlying_type<T>::type;
a = static_cast<T>(static_cast<x>(a) | static_cast<x>(b));
return a;
}
template<typename T>
typename std::enable_if<Bitwise<T>::enable, T>::type operator&(T a, T b) {
using x = typename std::underlying_type<T>::type;
return static_cast<T>(static_cast<x>(a) & static_cast<x>(b));
}
template<typename T>
typename std::enable_if<Bitwise<T>::enable, T>::type operator&=(T &a, T b) {
using x = typename std::underlying_type<T>::type;
a = static_cast<T>(static_cast<x>(a) & static_cast<x>(b));
return a;
}
template<typename T>
typename std::enable_if<Bitwise<T>::enable, T>::type operator~(T a) {
return static_cast<T>(~static_cast<typename std::underlying_type<T>::type>(a));
}

9
src/misc/is_optional.hpp Normal file
View File

@ -0,0 +1,9 @@
#pragma once
#include <optional>
#include <type_traits>
template<typename T>
struct is_optional : std::false_type {};
template<typename T>
struct is_optional<std::optional<T>> : std::true_type {};

View File

@ -1,5 +1,4 @@
#include "platform.hpp"
#include "util.hpp"
#include <config.h>
#include <filesystem>
#include <fstream>

View File

@ -1,6 +1,15 @@
#include "settings.hpp"
#include <filesystem>
#include <fstream>
#include <glibmm/miscutils.h>
#ifdef WITH_KEYCHAIN
#include <keychain/keychain.h>
#endif
const std::string KeychainPackage = "com.github.uowuo.abaddon";
const std::string KeychainService = "abaddon-client-token";
const std::string KeychainUser = "";
SettingsManager::SettingsManager(const std::string &filename)
: m_filename(filename) {
@ -36,7 +45,6 @@ void SettingsManager::ReadSettings() {
SMSTR("discord", "api_base", APIBaseURL);
SMSTR("discord", "gateway", GatewayURL);
SMSTR("discord", "token", DiscordToken);
SMBOOL("discord", "memory_db", UseMemoryDB);
SMBOOL("discord", "prefetch", Prefetch);
SMBOOL("discord", "autoconnect", Autoconnect);
@ -61,6 +69,32 @@ void SettingsManager::ReadSettings() {
SMSTR("style", "mentionbadgetextcolor", MentionBadgeTextColor);
SMSTR("style", "unreadcolor", UnreadIndicatorColor);
#ifdef WITH_KEYCHAIN
keychain::Error error {};
// convert to keychain if present in normal settings
SMSTR("discord", "token", DiscordToken);
if (!m_settings.DiscordToken.empty()) {
keychain::Error set_error {};
keychain::setPassword(KeychainPackage, KeychainService, KeychainUser, m_settings.DiscordToken, set_error);
if (set_error) {
printf("keychain error setting token: %s\n", set_error.message.c_str());
} else {
m_file.remove_key("discord", "token");
}
}
m_settings.DiscordToken = keychain::getPassword(KeychainPackage, KeychainService, KeychainUser, error);
if (error && error.type != keychain::ErrorType::NotFound) {
printf("keychain error reading token: %s (%d)\n", error.message.c_str(), error.code);
}
#else
SMSTR("discord", "token", DiscordToken);
#endif
#undef SMBOOL
#undef SMSTR
#undef SMINT
@ -92,7 +126,6 @@ void SettingsManager::Close() {
SMSTR("discord", "api_base", APIBaseURL);
SMSTR("discord", "gateway", GatewayURL);
SMSTR("discord", "token", DiscordToken);
SMBOOL("discord", "memory_db", UseMemoryDB);
SMBOOL("discord", "prefetch", Prefetch);
SMBOOL("discord", "autoconnect", Autoconnect);
@ -117,6 +150,17 @@ void SettingsManager::Close() {
SMSTR("style", "mentionbadgetextcolor", MentionBadgeTextColor);
SMSTR("style", "unreadcolor", UnreadIndicatorColor);
#ifdef WITH_KEYCHAIN
keychain::Error error {};
keychain::setPassword(KeychainPackage, KeychainService, KeychainUser, m_settings.DiscordToken, error);
if (error) {
printf("keychain error setting token: %s\n", error.message.c_str());
}
#else
SMSTR("discord", "token", DiscordToken);
#endif
#undef SMSTR
#undef SMBOOL
#undef SMINT

View File

@ -1,5 +1,4 @@
#include "startup.hpp"
#include "abaddon.hpp"
#include <future>
#include <memory>

View File

@ -1,7 +1,5 @@
#pragma once
#include <glibmm/dispatcher.h>
#include <gtkmm/messagedialog.h>
#include <gtkmm/window.h>
#include <optional>
// fetch cookies, build number async

View File

@ -1,4 +1,3 @@
#include "util.hpp"
#include <array>
#include <cstring>
#include <filesystem>

View File

@ -13,17 +13,26 @@
#include <condition_variable>
#include <optional>
#include <type_traits>
#include <gtkmm.h>
#include <sigc++/slot.h>
namespace Glib {
class ustring;
}
namespace Gdk {
class RGBA;
}
namespace Gtk {
class Widget;
class Menu;
class ListBox;
} // namespace Gtk
#define NOOP_CALLBACK [](...) {}
namespace util {
template<typename T>
struct is_optional : ::std::false_type {};
template<typename T>
struct is_optional<::std::optional<T>> : ::std::true_type {};
bool IsFolder(std::string_view path);
bool IsFile(std::string_view path);
@ -44,42 +53,6 @@ std::string HumanReadableBytes(uint64_t bytes);
std::string FormatISO8601(const std::string &in, int extra_offset = 0, const std::string &fmt = "%x %X");
void AddPointerCursor(Gtk::Widget &widget);
template<typename T>
struct Bitwise {
static const bool enable = false;
};
template<typename T>
typename std::enable_if<Bitwise<T>::enable, T>::type operator|(T a, T b) {
using x = typename std::underlying_type<T>::type;
return static_cast<T>(static_cast<x>(a) | static_cast<x>(b));
}
template<typename T>
typename std::enable_if<Bitwise<T>::enable, T>::type operator|=(T &a, T b) {
using x = typename std::underlying_type<T>::type;
a = static_cast<T>(static_cast<x>(a) | static_cast<x>(b));
return a;
}
template<typename T>
typename std::enable_if<Bitwise<T>::enable, T>::type operator&(T a, T b) {
using x = typename std::underlying_type<T>::type;
return static_cast<T>(static_cast<x>(a) & static_cast<x>(b));
}
template<typename T>
typename std::enable_if<Bitwise<T>::enable, T>::type operator&=(T &a, T b) {
using x = typename std::underlying_type<T>::type;
a = static_cast<T>(static_cast<x>(a) & static_cast<x>(b));
return a;
}
template<typename T>
typename std::enable_if<Bitwise<T>::enable, T>::type operator~(T a) {
return static_cast<T>(~static_cast<typename std::underlying_type<T>::type>(a));
}
template<typename T>
inline void AlphabeticalSort(T start, T end, std::function<std::string(const typename std::iterator_traits<T>::value_type &)> get_string) {
std::sort(start, end, [&](const auto &a, const auto &b) -> bool {

View File

@ -1,5 +1,4 @@
#include "auditlogpane.hpp"
#include "abaddon.hpp"
using namespace std::string_literals;

View File

@ -1,5 +1,4 @@
#pragma once
#include <gtkmm.h>
#include "discord/objects.hpp"
class GuildSettingsAuditLogPane : public Gtk::ScrolledWindow {

View File

@ -1,5 +1,4 @@
#include "banspane.hpp"
#include "abaddon.hpp"
// gtk_list_store_set_value: assertion 'column >= 0 && column < priv->n_columns' failed
// dont care to figure out why this happens cuz it doesnt seem to break anything

View File

@ -1,5 +1,4 @@
#pragma once
#include <gtkmm.h>
#include "discord/snowflake.hpp"
#include "discord/ban.hpp"

View File

@ -1,5 +1,4 @@
#include "emojispane.hpp"
#include "abaddon.hpp"
#include "components/cellrendererpixbufanimation.hpp"
GuildSettingsEmojisPane::GuildSettingsEmojisPane(Snowflake guild_id)

View File

@ -1,5 +1,4 @@
#pragma once
#include <gtkmm.h>
#include "discord/emoji.hpp"
class GuildSettingsEmojisPane : public Gtk::Box {

View File

@ -1,5 +1,4 @@
#include "infopane.hpp"
#include "abaddon.hpp"
#include <filesystem>
GuildSettingsInfoPane::GuildSettingsInfoPane(Snowflake id)

View File

@ -1,5 +1,4 @@
#pragma once
#include <gtkmm.h>
#include "discord/guild.hpp"
class GuildSettingsInfoPane : public Gtk::Grid {

View File

@ -1,5 +1,4 @@
#include "invitespane.hpp"
#include "abaddon.hpp"
GuildSettingsInvitesPane::GuildSettingsInvitesPane(Snowflake id)
: GuildID(id)

View File

@ -1,5 +1,4 @@
#pragma once
#include <gtkmm.h>
#include "discord/objects.hpp"
class GuildSettingsInvitesPane : public Gtk::ScrolledWindow {

View File

@ -1,5 +1,4 @@
#include "memberspane.hpp"
#include "abaddon.hpp"
GuildSettingsMembersPane::GuildSettingsMembersPane(Snowflake id)
: Gtk::Box(Gtk::ORIENTATION_VERTICAL)

View File

@ -1,6 +1,5 @@
#pragma once
#include <unordered_set>
#include <gtkmm.h>
#include "discord/member.hpp"
#include "discord/guild.hpp"
#include "components/lazyimage.hpp"

View File

@ -1,5 +1,4 @@
#include "rolespane.hpp"
#include "abaddon.hpp"
GuildSettingsRolesPane::GuildSettingsRolesPane(Snowflake id)
: Gtk::Box(Gtk::ORIENTATION_HORIZONTAL)

View File

@ -1,5 +1,4 @@
#pragma once
#include <gtkmm.h>
#include <unordered_map>
#include "discord/guild.hpp"
#include "components/draglistbox.hpp"

View File

@ -1,5 +1,4 @@
#include "guildsettingswindow.hpp"
#include "abaddon.hpp"
GuildSettingsWindow::GuildSettingsWindow(Snowflake id)
: m_main(Gtk::ORIENTATION_VERTICAL)

View File

@ -1,5 +1,4 @@
#pragma once
#include <gtkmm.h>
#include "discord/snowflake.hpp"
#include "guildsettings/infopane.hpp"
#include "guildsettings/banspane.hpp"

View File

@ -1,5 +1,4 @@
#include "mainwindow.hpp"
#include "abaddon.hpp"
MainWindow::MainWindow()
: m_main_box(Gtk::ORIENTATION_VERTICAL)
@ -262,8 +261,10 @@ void MainWindow::SetupMenu() {
m_menu_file.set_submenu(m_menu_file_sub);
m_menu_file_reload_css.set_label("Reload CSS");
m_menu_file_clear_cache.set_label("Clear file cache");
m_menu_file_dump_ready.set_label("Dump ready message");
m_menu_file_sub.append(m_menu_file_reload_css);
m_menu_file_sub.append(m_menu_file_clear_cache);
m_menu_file_sub.append(m_menu_file_dump_ready);
m_menu_view.set_label("View");
m_menu_view.set_submenu(m_menu_view_sub);
@ -272,6 +273,12 @@ void MainWindow::SetupMenu() {
m_menu_view_threads.set_label("Threads");
m_menu_view_mark_guild_as_read.set_label("Mark Server as Read");
m_menu_view_mark_guild_as_read.add_accelerator("activate", m_accels, GDK_KEY_Escape, Gdk::SHIFT_MASK, Gtk::ACCEL_VISIBLE);
m_menu_view_channels.set_label("Channels");
m_menu_view_channels.add_accelerator("activate", m_accels, GDK_KEY_L, Gdk::CONTROL_MASK, Gtk::ACCEL_VISIBLE);
m_menu_view_channels.set_active(true);
m_menu_view_members.set_label("Members");
m_menu_view_members.add_accelerator("activate", m_accels, GDK_KEY_M, Gdk::CONTROL_MASK, Gtk::ACCEL_VISIBLE);
m_menu_view_members.set_active(true);
#ifdef WITH_LIBHANDY
m_menu_view_go_back.set_label("Go Back");
m_menu_view_go_forward.set_label("Go Forward");
@ -282,6 +289,8 @@ void MainWindow::SetupMenu() {
m_menu_view_sub.append(m_menu_view_pins);
m_menu_view_sub.append(m_menu_view_threads);
m_menu_view_sub.append(m_menu_view_mark_guild_as_read);
m_menu_view_sub.append(m_menu_view_channels);
m_menu_view_sub.append(m_menu_view_members);
#ifdef WITH_LIBHANDY
m_menu_view_sub.append(m_menu_view_go_back);
m_menu_view_sub.append(m_menu_view_go_forward);
@ -334,6 +343,10 @@ void MainWindow::SetupMenu() {
Abaddon::Get().GetImageManager().ClearCache();
});
m_menu_file_dump_ready.signal_toggled().connect([this]() {
Abaddon::Get().GetDiscordClient().SetDumpReady(m_menu_file_dump_ready.get_active());
});
m_menu_discord_add_recipient.signal_activate().connect([this] {
m_signal_action_add_recipient.emit(GetChatActiveChannel());
});
@ -361,6 +374,14 @@ void MainWindow::SetupMenu() {
}
});
m_menu_view_channels.signal_activate().connect([this]() {
m_channel_list.set_visible(m_menu_view_channels.get_active());
});
m_menu_view_members.signal_activate().connect([this]() {
m_members.GetRoot()->set_visible(m_menu_view_members.get_active());
});
#ifdef WITH_LIBHANDY
m_menu_view_go_back.signal_activate().connect([this] {
GoBack();

View File

@ -3,8 +3,11 @@
#include "components/chatwindow.hpp"
#include "components/memberlist.hpp"
#include "components/friendslist.hpp"
<<<<<<< HEAD
#include "components/voiceinfobox.hpp"
#include <gtkmm.h>
=======
>>>>>>> master
class MainWindow : public Gtk::Window {
public:
@ -76,6 +79,7 @@ private:
Gtk::Menu m_menu_file_sub;
Gtk::MenuItem m_menu_file_reload_css;
Gtk::MenuItem m_menu_file_clear_cache;
Gtk::CheckMenuItem m_menu_file_dump_ready;
Gtk::MenuItem m_menu_view;
Gtk::Menu m_menu_view_sub;
@ -83,6 +87,8 @@ private:
Gtk::MenuItem m_menu_view_pins;
Gtk::MenuItem m_menu_view_threads;
Gtk::MenuItem m_menu_view_mark_guild_as_read;
Gtk::CheckMenuItem m_menu_view_channels;
Gtk::CheckMenuItem m_menu_view_members;
#ifdef WITH_LIBHANDY
Gtk::MenuItem m_menu_view_go_back;
Gtk::MenuItem m_menu_view_go_forward;

View File

@ -1,5 +1,4 @@
#include "pinnedwindow.hpp"
#include "abaddon.hpp"
PinnedWindow::PinnedWindow(const ChannelData &data)
: ChannelID(data.ID) {

View File

@ -1,5 +1,4 @@
#pragma once
#include <gtkmm.h>
#include "discord/errors.hpp"
#include "discord/channel.hpp"
#include "discord/message.hpp"

View File

@ -1,5 +1,4 @@
#include "mutualfriendspane.hpp"
#include "abaddon.hpp"
MutualFriendItem::MutualFriendItem(const UserData &user)
: Gtk::Box(Gtk::ORIENTATION_HORIZONTAL) {

View File

@ -1,5 +1,4 @@
#pragma once
#include <gtkmm.h>
#include "discord/objects.hpp"
class MutualFriendItem : public Gtk::Box {

View File

@ -1,5 +1,4 @@
#include "mutualguildspane.hpp"
#include "abaddon.hpp"
MutualGuildItem::MutualGuildItem(const MutualGuildData &guild)
: Gtk::Box(Gtk::ORIENTATION_HORIZONTAL)

View File

@ -1,5 +1,4 @@
#pragma once
#include <gtkmm.h>
#include "discord/objects.hpp"
class MutualGuildItem : public Gtk::Box {

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