mirror of
https://github.com/uowuo/abaddon.git
synced 2024-09-20 06:51:51 +00:00
Merge branch 'master' into voice
This commit is contained in:
commit
c1303bd289
12
.github/workflows/ci.yml
vendored
12
.github/workflows/ci.yml
vendored
|
@ -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
3
.gitmodules
vendored
|
@ -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
|
||||
|
|
|
@ -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
249
README.md
|
@ -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 |
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
#pragma once
|
||||
#include <gtkmm.h>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
#pragma once
|
||||
#include <gtkmm.h>
|
||||
#include <unordered_map>
|
||||
|
||||
// handles both static and animated
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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>;
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
#include <filesystem>
|
||||
#include "chatinputindicator.hpp"
|
||||
#include "abaddon.hpp"
|
||||
#include "util.hpp"
|
||||
|
||||
constexpr static const int MaxUsersInIndicator = 4;
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
#pragma once
|
||||
#include <gtkmm.h>
|
||||
#include <unordered_map>
|
||||
#include "discord/message.hpp"
|
||||
#include "discord/user.hpp"
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
#pragma once
|
||||
#include <gtkmm.h>
|
||||
#include <map>
|
||||
#include <vector>
|
||||
#include "discord/message.hpp"
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
#pragma once
|
||||
#include <gtkmm.h>
|
||||
#include "discord/discord.hpp"
|
||||
|
||||
class ChatMessageItemContainer : public Gtk::EventBox {
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
#include "chatwindow.hpp"
|
||||
#include "abaddon.hpp"
|
||||
#include "chatinputindicator.hpp"
|
||||
#include "ratelimitindicator.hpp"
|
||||
#include "chatinput.hpp"
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
#pragma once
|
||||
#include <gtkmm.h>
|
||||
#include <string>
|
||||
#include <set>
|
||||
#include "discord/discord.hpp"
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
#pragma once
|
||||
#include <gtkmm.h>
|
||||
#include <functional>
|
||||
#include "lazyimage.hpp"
|
||||
#include "discord/snowflake.hpp"
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
#pragma once
|
||||
#include <gtkmm.h>
|
||||
|
||||
class DragListBox : public Gtk::ListBox {
|
||||
public:
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
#include "friendslist.hpp"
|
||||
#include "abaddon.hpp"
|
||||
#include "lazyimage.hpp"
|
||||
|
||||
using namespace std::string_literals;
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
#pragma once
|
||||
#include <gtkmm.h>
|
||||
#include "discord/objects.hpp"
|
||||
|
||||
class FriendsListAddComponent : public Gtk::Box {
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
#include "lazyimage.hpp"
|
||||
|
||||
#include <utility>
|
||||
#include "abaddon.hpp"
|
||||
|
||||
LazyImage::LazyImage(int w, int h, bool use_placeholder)
|
||||
: m_width(w)
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
#include "memberlist.hpp"
|
||||
#include "abaddon.hpp"
|
||||
#include "util.hpp"
|
||||
#include "lazyimage.hpp"
|
||||
#include "statusindicator.hpp"
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
#pragma once
|
||||
#include <gtkmm.h>
|
||||
#include <mutex>
|
||||
#include <unordered_map>
|
||||
#include <optional>
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
#include "progressbar.hpp"
|
||||
#include "abaddon.hpp"
|
||||
|
||||
MessageUploadProgressBar::MessageUploadProgressBar() {
|
||||
get_style_context()->add_class("message-progress");
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
#pragma once
|
||||
#include <gtkmm/progressbar.h>
|
||||
#include <string>
|
||||
|
||||
class MessageUploadProgressBar : public Gtk::ProgressBar {
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
#include "ratelimitindicator.hpp"
|
||||
#include "abaddon.hpp"
|
||||
#include <filesystem>
|
||||
|
||||
RateLimitIndicator::RateLimitIndicator()
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
#pragma once
|
||||
#include <gtkmm.h>
|
||||
#include <unordered_map>
|
||||
#include <chrono>
|
||||
#include "discord/message.hpp"
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
#include "statusindicator.hpp"
|
||||
#include "abaddon.hpp"
|
||||
|
||||
static const constexpr int Diameter = 8;
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
#pragma once
|
||||
#include <gtkmm.h>
|
||||
#include "discord/snowflake.hpp"
|
||||
#include "discord/activity.hpp"
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
#pragma once
|
||||
#include <gtkmm.h>
|
||||
|
||||
class ConfirmDialog : public Gtk::Dialog {
|
||||
public:
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
#pragma once
|
||||
#include <gtkmm.h>
|
||||
#include <string>
|
||||
|
||||
class EditMessageDialog : public Gtk::Dialog {
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
#include "friendpicker.hpp"
|
||||
#include "abaddon.hpp"
|
||||
|
||||
FriendPickerDialog::FriendPickerDialog(Gtk::Window &parent)
|
||||
: Gtk::Dialog("Pick a friend", parent, true)
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
#pragma once
|
||||
#include <gtkmm.h>
|
||||
#include "discord/snowflake.hpp"
|
||||
|
||||
class FriendPickerDialog : public Gtk::Dialog {
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
#pragma once
|
||||
#include <gtkmm.h>
|
||||
#include "discord/objects.hpp"
|
||||
|
||||
class SetStatusDialog : public Gtk::Dialog {
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
#pragma once
|
||||
#include <gtkmm/dialog.h>
|
||||
#include <gtkmm/entry.h>
|
||||
|
||||
class TextInputDialog : public Gtk::Dialog {
|
||||
public:
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
#pragma once
|
||||
#include <gtkmm.h>
|
||||
#include <string>
|
||||
|
||||
class TokenDialog : public Gtk::Dialog {
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
#include "verificationgate.hpp"
|
||||
#include "abaddon.hpp"
|
||||
|
||||
VerificationGateDialog::VerificationGateDialog(Gtk::Window &parent, Snowflake guild_id)
|
||||
: Gtk::Dialog("Verification Required", parent, true)
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
#pragma once
|
||||
#include <gtkmm.h>
|
||||
#include <optional>
|
||||
#include "discord/objects.hpp"
|
||||
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
#pragma once
|
||||
#include <string>
|
||||
#include <optional>
|
||||
#include "util.hpp"
|
||||
#include "json.hpp"
|
||||
#include "snowflake.hpp"
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -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())
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
#include "guild.hpp"
|
||||
#include "abaddon.hpp"
|
||||
|
||||
void from_json(const nlohmann::json &j, GuildData &m) {
|
||||
JS_D("id", m.ID);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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())
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
#include "member.hpp"
|
||||
#include "abaddon.hpp"
|
||||
|
||||
void from_json(const nlohmann::json &j, GuildMember &m) {
|
||||
JS_O("user", m.User);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
#include "snowflake.hpp"
|
||||
#include "util.hpp"
|
||||
#include <chrono>
|
||||
#include <ctime>
|
||||
#include <iomanip>
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
};
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
#include "user.hpp"
|
||||
#include "abaddon.hpp"
|
||||
|
||||
bool UserData::IsABot() const noexcept {
|
||||
return IsBot.has_value() && *IsBot;
|
||||
|
|
|
@ -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);
|
||||
};
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
#include <cstdio>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
#include <gtkmm.h>
|
||||
|
||||
// shoutout to gtk for only supporting .svg's sometimes
|
||||
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
#include "abaddon.hpp"
|
||||
#include "filecache.hpp"
|
||||
|
||||
#include <utility>
|
||||
|
|
|
@ -8,7 +8,6 @@
|
|||
#include <unordered_map>
|
||||
#include <future>
|
||||
#include <mutex>
|
||||
#include "util.hpp"
|
||||
#include "http.hpp"
|
||||
|
||||
class FileCacheWorkerThread {
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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
38
src/misc/bitwise.hpp
Normal 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
9
src/misc/is_optional.hpp
Normal 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 {};
|
|
@ -1,5 +1,4 @@
|
|||
#include "platform.hpp"
|
||||
#include "util.hpp"
|
||||
#include <config.h>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
#include "startup.hpp"
|
||||
#include "abaddon.hpp"
|
||||
#include <future>
|
||||
#include <memory>
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
#include "util.hpp"
|
||||
#include <array>
|
||||
#include <cstring>
|
||||
#include <filesystem>
|
||||
|
|
59
src/util.hpp
59
src/util.hpp
|
@ -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 {
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
#include "auditlogpane.hpp"
|
||||
#include "abaddon.hpp"
|
||||
|
||||
using namespace std::string_literals;
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
#pragma once
|
||||
#include <gtkmm.h>
|
||||
#include "discord/objects.hpp"
|
||||
|
||||
class GuildSettingsAuditLogPane : public Gtk::ScrolledWindow {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
#pragma once
|
||||
#include <gtkmm.h>
|
||||
#include "discord/snowflake.hpp"
|
||||
#include "discord/ban.hpp"
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
#include "emojispane.hpp"
|
||||
#include "abaddon.hpp"
|
||||
#include "components/cellrendererpixbufanimation.hpp"
|
||||
|
||||
GuildSettingsEmojisPane::GuildSettingsEmojisPane(Snowflake guild_id)
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
#pragma once
|
||||
#include <gtkmm.h>
|
||||
#include "discord/emoji.hpp"
|
||||
|
||||
class GuildSettingsEmojisPane : public Gtk::Box {
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
#include "infopane.hpp"
|
||||
#include "abaddon.hpp"
|
||||
#include <filesystem>
|
||||
|
||||
GuildSettingsInfoPane::GuildSettingsInfoPane(Snowflake id)
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
#pragma once
|
||||
#include <gtkmm.h>
|
||||
#include "discord/guild.hpp"
|
||||
|
||||
class GuildSettingsInfoPane : public Gtk::Grid {
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
#include "invitespane.hpp"
|
||||
#include "abaddon.hpp"
|
||||
|
||||
GuildSettingsInvitesPane::GuildSettingsInvitesPane(Snowflake id)
|
||||
: GuildID(id)
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
#pragma once
|
||||
#include <gtkmm.h>
|
||||
#include "discord/objects.hpp"
|
||||
|
||||
class GuildSettingsInvitesPane : public Gtk::ScrolledWindow {
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
#include "memberspane.hpp"
|
||||
#include "abaddon.hpp"
|
||||
|
||||
GuildSettingsMembersPane::GuildSettingsMembersPane(Snowflake id)
|
||||
: Gtk::Box(Gtk::ORIENTATION_VERTICAL)
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
#pragma once
|
||||
#include <unordered_set>
|
||||
#include <gtkmm.h>
|
||||
#include "discord/member.hpp"
|
||||
#include "discord/guild.hpp"
|
||||
#include "components/lazyimage.hpp"
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
#include "rolespane.hpp"
|
||||
#include "abaddon.hpp"
|
||||
|
||||
GuildSettingsRolesPane::GuildSettingsRolesPane(Snowflake id)
|
||||
: Gtk::Box(Gtk::ORIENTATION_HORIZONTAL)
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
#pragma once
|
||||
#include <gtkmm.h>
|
||||
#include <unordered_map>
|
||||
#include "discord/guild.hpp"
|
||||
#include "components/draglistbox.hpp"
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
#include "guildsettingswindow.hpp"
|
||||
#include "abaddon.hpp"
|
||||
|
||||
GuildSettingsWindow::GuildSettingsWindow(Snowflake id)
|
||||
: m_main(Gtk::ORIENTATION_VERTICAL)
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
#pragma once
|
||||
#include <gtkmm.h>
|
||||
#include "discord/snowflake.hpp"
|
||||
#include "guildsettings/infopane.hpp"
|
||||
#include "guildsettings/banspane.hpp"
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
#include "pinnedwindow.hpp"
|
||||
#include "abaddon.hpp"
|
||||
|
||||
PinnedWindow::PinnedWindow(const ChannelData &data)
|
||||
: ChannelID(data.ID) {
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
#pragma once
|
||||
#include <gtkmm.h>
|
||||
#include "discord/errors.hpp"
|
||||
#include "discord/channel.hpp"
|
||||
#include "discord/message.hpp"
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
#include "mutualfriendspane.hpp"
|
||||
#include "abaddon.hpp"
|
||||
|
||||
MutualFriendItem::MutualFriendItem(const UserData &user)
|
||||
: Gtk::Box(Gtk::ORIENTATION_HORIZONTAL) {
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
#pragma once
|
||||
#include <gtkmm.h>
|
||||
#include "discord/objects.hpp"
|
||||
|
||||
class MutualFriendItem : public Gtk::Box {
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
#include "mutualguildspane.hpp"
|
||||
#include "abaddon.hpp"
|
||||
|
||||
MutualGuildItem::MutualGuildItem(const MutualGuildData &guild)
|
||||
: Gtk::Box(Gtk::ORIENTATION_HORIZONTAL)
|
||||
|
|
|
@ -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
Loading…
Reference in New Issue
Block a user