Compare commits
275 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5c148d19dd | ||
|
|
acb03642c2 | ||
|
|
ba2aea86f9 | ||
|
|
c704703b14 | ||
|
|
33a329f16a | ||
|
|
f0df06a795 | ||
|
|
9ae41b7335 | ||
|
|
b9fee0f6c9 | ||
|
|
92273829bb | ||
|
|
86f6f81d9b | ||
|
|
573a619191 | ||
|
|
c5807a3463 | ||
|
|
2a9f49a148 | ||
|
|
64245bf745 | ||
|
|
772598996c | ||
|
|
ccb82c1676 | ||
|
|
cd900cdfee | ||
|
|
1767575728 | ||
|
|
0a34c04b44 | ||
|
|
7e85168576 | ||
|
|
3027e00905 | ||
|
|
2ecbacc924 | ||
|
|
84eb56d6b1 | ||
|
|
f3e5dcbe65 | ||
|
|
a78fdd386f | ||
|
|
348c1cb965 | ||
|
|
32fc7def7c | ||
|
|
14602a7384 | ||
|
|
c683ef9ad9 | ||
|
|
039cc7458d | ||
|
|
d99f16f82d | ||
|
|
243e48e609 | ||
|
|
fac9f1ba58 | ||
|
|
6a5ecb4d95 | ||
|
|
dc28eae95a | ||
|
|
baf96da80c | ||
|
|
31ca6d9fd2 | ||
|
|
04befeb180 | ||
|
|
a4c8a2290d | ||
|
|
96ec5bb665 | ||
|
|
f60cea2216 | ||
|
|
77dd9fabfa | ||
|
|
ee67037a3f | ||
|
|
b46cf53be5 | ||
|
|
02741f2c1b | ||
|
|
955b9239b9 | ||
|
|
53ac853367 | ||
|
|
1c38671356 | ||
|
|
91527fbd0d | ||
|
|
537d4163c2 | ||
|
|
c0e4a3a988 | ||
|
|
860049fad5 | ||
|
|
344f269414 | ||
|
|
3487353fc7 | ||
|
|
86fc8f4186 | ||
|
|
acb80da387 | ||
|
|
d99d8443ee | ||
|
|
319f9c392c | ||
|
|
a61a630ee6 | ||
|
|
e8260c164f | ||
|
|
4e4986f670 | ||
|
|
3610a2508b | ||
|
|
8396d07d9e | ||
|
|
59acd0f82f | ||
|
|
544ae6f915 | ||
|
|
111399cf4a | ||
|
|
fba5cee519 | ||
|
|
f95d79129e | ||
|
|
02ce353c6d | ||
|
|
241d9a2140 | ||
|
|
849ebf17f1 | ||
|
|
41776fbd02 | ||
|
|
5c7631e713 | ||
|
|
41e2478a6f | ||
|
|
a9d35dcccd | ||
|
|
e87766f106 | ||
|
|
a038f47a25 | ||
|
|
d841a2c862 | ||
|
|
4ee7025ab0 | ||
|
|
d0fa308f6e | ||
|
|
4456c8771d | ||
|
|
caa551a469 | ||
|
|
ccf5afbba9 | ||
|
|
2474ffc2ba | ||
|
|
5cf4d8e160 | ||
|
|
49ff9a249e | ||
|
|
abc448eca0 | ||
|
|
d7177cac97 | ||
|
|
c6182e8923 | ||
|
|
270730d9b3 | ||
|
|
4ec5c1dfcc | ||
|
|
da27c67e6b | ||
|
|
de3b53c676 | ||
|
|
3ac993bae4 | ||
|
|
756de57919 | ||
|
|
d9cf989813 | ||
|
|
ffc69576f2 | ||
|
|
2bed7f161b | ||
|
|
607607ef0a | ||
|
|
b2ba7709df | ||
|
|
8b488a5ca9 | ||
|
|
1d8ef79da6 | ||
|
|
bbf32730cd | ||
|
|
f58ca39e8c | ||
|
|
3b5f4ded31 | ||
|
|
f6fdfeb95f | ||
|
|
2c25319fb8 | ||
|
|
7daa0a250c | ||
|
|
121c2585c8 | ||
|
|
b18b94818a | ||
|
|
63db16a711 | ||
|
|
c30ab91738 | ||
|
|
e8f16292d1 | ||
|
|
db28abaa44 | ||
|
|
bfb2490938 | ||
|
|
b4ab88f708 | ||
|
|
2dab595476 | ||
|
|
a98967fccc | ||
|
|
32e4540464 | ||
|
|
02dc28e89c | ||
|
|
47545d9d32 | ||
|
|
5670dfc1d5 | ||
|
|
34f8af599d | ||
|
|
d36fe4d0e9 | ||
|
|
44317e2d34 | ||
|
|
5b806a2589 | ||
|
|
5a13c7fef7 | ||
|
|
c22a49f64e | ||
|
|
436024b4a0 | ||
|
|
61cde0f7e1 | ||
|
|
a9399873fd | ||
|
|
c2be1d3668 | ||
|
|
1d981d2c5a | ||
|
|
c2b7ca780e | ||
|
|
57e95c8969 | ||
|
|
56a74fb5dd | ||
|
|
49685c3989 | ||
|
|
9767e1e7fd | ||
|
|
a83e9c01a6 | ||
|
|
7b1ceeedf4 | ||
|
|
a0b3c9f8a4 | ||
|
|
a2a45757e9 | ||
|
|
271d21c7bd | ||
|
|
46b88566f1 | ||
|
|
af60bceada | ||
|
|
3583a5d251 | ||
|
|
4503aeabc4 | ||
|
|
7f1d3df4a5 | ||
|
|
17f1289c84 | ||
|
|
fc3d0fddd2 | ||
|
|
4bd5c89266 | ||
|
|
a0599ab812 | ||
|
|
6c54296ba3 | ||
|
|
7ed415040a | ||
|
|
011cb159cf | ||
|
|
25fd2c3840 | ||
|
|
7e3976785f | ||
|
|
75213fcede | ||
|
|
179ff980e9 | ||
|
|
f784550964 | ||
|
|
ce238d08e9 | ||
|
|
f9864a24ed | ||
|
|
738d50dd43 | ||
|
|
7d49f934bc | ||
|
|
fbb5522861 | ||
|
|
0ce509f80e | ||
|
|
b6b215ee6f | ||
|
|
d7f3ee9f98 | ||
|
|
2328c8bafe | ||
|
|
dfd642bb82 | ||
|
|
6c9bf4ff81 | ||
|
|
481685b3bb | ||
|
|
f31d431517 | ||
|
|
604f2ffe3d | ||
|
|
4e0b22375f | ||
|
|
9d0c7691d8 | ||
|
|
cef28e94ea | ||
|
|
40106ddeb1 | ||
|
|
8695562cb4 | ||
|
|
5338eab3a5 | ||
|
|
d7bb6049e1 | ||
|
|
ea7464722b | ||
|
|
d6da646d87 | ||
|
|
17c1f913df | ||
|
|
6c94e75513 | ||
|
|
801894abc6 | ||
|
|
207c004228 | ||
|
|
36f73a6106 | ||
|
|
41d80af128 | ||
|
|
145504bdd6 | ||
|
|
9fd0d404a1 | ||
|
|
b75599e55d | ||
|
|
67062d6ed8 | ||
|
|
c43d49ed54 | ||
|
|
e9867173c9 | ||
|
|
f580535d35 | ||
|
|
1d7529e609 | ||
|
|
1fb7ca0007 | ||
|
|
b576bd0fcc | ||
|
|
f19dcc0114 | ||
|
|
38a49d172c | ||
|
|
72935b0558 | ||
|
|
a5332efcfb | ||
|
|
15954830e2 | ||
|
|
46ab760a56 | ||
|
|
0b0135268e | ||
|
|
511fb445d1 | ||
|
|
bcfb2146cd | ||
|
|
a1b662a325 | ||
|
|
14b5bf7d0d | ||
|
|
d288989386 | ||
|
|
d63941797f | ||
|
|
1ea2811713 | ||
|
|
af56784797 | ||
|
|
2461406887 | ||
|
|
8e11dd97e9 | ||
|
|
2690febf20 | ||
|
|
af3d278825 | ||
|
|
c9647f9b33 | ||
|
|
e1703aea3f | ||
|
|
e02107feea | ||
|
|
192b043e7a | ||
|
|
8c72d4c18d | ||
|
|
0da913cd4a | ||
|
|
fb3d69c5e7 | ||
|
|
c40208ac66 | ||
|
|
069c22e9cd | ||
|
|
8f30bb33a3 | ||
|
|
4326c5e29b | ||
|
|
a51a54bc59 | ||
|
|
d88079000a | ||
|
|
574cbc35d8 | ||
|
|
fc76a15c46 | ||
|
|
fd53a76bf6 | ||
|
|
b5c1394662 | ||
|
|
66d7cb581c | ||
|
|
8f823420b6 | ||
|
|
36d5e011e8 | ||
|
|
5a4bcbf377 | ||
|
|
0fe007569e | ||
|
|
c49e454ec0 | ||
|
|
8afc8c62ef | ||
|
|
4644adff94 | ||
|
|
5e08083b5a | ||
|
|
95a8da803a | ||
|
|
43a41b34bc | ||
|
|
9c285a09e5 | ||
|
|
9d21df8e1b | ||
|
|
1da2c57376 | ||
|
|
409af292af | ||
|
|
108002248c | ||
|
|
43795f4c87 | ||
|
|
1f68da6b77 | ||
|
|
98e0e84d57 | ||
|
|
6ddba4363a | ||
|
|
b5b5c40ecd | ||
|
|
d84fe2b800 | ||
|
|
da561ba4d5 | ||
|
|
2257ff9798 | ||
|
|
c40b8a4122 | ||
|
|
1f445742b4 | ||
|
|
d629846220 | ||
|
|
b26b2d4be0 | ||
|
|
b65c09411a | ||
|
|
a51b813f70 | ||
|
|
1078d94b73 | ||
|
|
12c105623c | ||
|
|
d950460e14 | ||
|
|
2c2686946d | ||
|
|
9175da2cf0 | ||
|
|
14d025a342 | ||
|
|
c98b62caca | ||
|
|
d425997c26 | ||
|
|
84b1b62e0e | ||
|
|
b248e0ae9a |
135
.github/workflows/ci.yml
vendored
@@ -3,57 +3,103 @@ name: Abaddon CI
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
windows:
|
||||
name: windows-${{ matrix.buildtype }}
|
||||
msys2:
|
||||
name: msys2-mingw64
|
||||
runs-on: windows-latest
|
||||
strategy:
|
||||
matrix:
|
||||
buildtype: [Debug, RelWithDebInfo, MinSizeRel]
|
||||
mindeps: [false]
|
||||
include:
|
||||
- buildtype: RelWithDebInfo
|
||||
mindeps: true
|
||||
defaults:
|
||||
run:
|
||||
shell: msys2 {0}
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
with:
|
||||
submodules: true
|
||||
|
||||
- name: Fetch CMake
|
||||
uses: lukka/get-cmake@latest
|
||||
|
||||
- name: Fetch dependencies
|
||||
uses: lukka/run-vcpkg@main
|
||||
- name: Setup MSYS2 (1)
|
||||
uses: haya14busa/action-cond@v1
|
||||
id: setupmsys
|
||||
with:
|
||||
vcpkgArguments: gtkmm nlohmann-json zlib sqlite3 glibmm openssl ixwebsocket curl
|
||||
vcpkgDirectory: ${{ github.workspace }}/ci/vcpkg/
|
||||
vcpkgTriplet: x64-windows
|
||||
cond: ${{ matrix.mindeps == true }}
|
||||
if_true: >-
|
||||
git
|
||||
make
|
||||
mingw-w64-x86_64-toolchain
|
||||
mingw-w64-x86_64-cmake
|
||||
mingw-w64-x86_64-ninja
|
||||
mingw-w64-x86_64-sqlite3
|
||||
mingw-w64-x86_64-nlohmann-json
|
||||
mingw-w64-x86_64-curl
|
||||
mingw-w64-x86_64-zlib
|
||||
mingw-w64-x86_64-gtkmm3
|
||||
if_false: >-
|
||||
git
|
||||
make
|
||||
mingw-w64-x86_64-toolchain
|
||||
mingw-w64-x86_64-cmake
|
||||
mingw-w64-x86_64-ninja
|
||||
mingw-w64-x86_64-sqlite3
|
||||
mingw-w64-x86_64-nlohmann-json
|
||||
mingw-w64-x86_64-curl
|
||||
mingw-w64-x86_64-zlib
|
||||
mingw-w64-x86_64-gtkmm3
|
||||
mingw-w64-x86_64-libhandy
|
||||
|
||||
- name: Setup MSYS2 (2)
|
||||
uses: msys2/setup-msys2@v2
|
||||
with:
|
||||
msystem: mingw64
|
||||
update: true
|
||||
install: ${{ steps.setupmsys.outputs.value }}
|
||||
|
||||
- name: Build
|
||||
uses: lukka/run-cmake@main
|
||||
with:
|
||||
useVcpkgToolchainFile: true
|
||||
vcpkgTriplet: x64-windows
|
||||
buildDirectory: ${{ runner.workspace }}/build
|
||||
cmakeBuildType: ${{ matrix.buildtype }}
|
||||
|
||||
- name: Setup artifact files
|
||||
shell: cmd
|
||||
run: |
|
||||
del /f /s /q "${{ runner.workspace }}\build\CMakeFiles"
|
||||
rmdir /s /q "${{ runner.workspace }}\build\CMakeFiles"
|
||||
del /f /s /q "${{ runner.workspace }}\build\.ninja_deps"
|
||||
del /f /s /q "${{ runner.workspace }}\build\.ninja_log"
|
||||
del /f /s /q "${{ runner.workspace }}\build\abaddon.ilk"
|
||||
del /f /s /q "${{ runner.workspace }}\build\CMakeCache.txt"
|
||||
xcopy /E /I "${{ github.workspace }}\css" "${{ runner.workspace }}\build\css"
|
||||
xcopy /E /I "${{ github.workspace }}\res" "${{ runner.workspace }}\build\res"
|
||||
xcopy /E /I "${{ github.workspace }}\fonts" "${{ runner.workspace }}\build\fonts"
|
||||
mkdir "${{ runner.workspace }}\build\share"
|
||||
xcopy /E /I "${{ github.workspace }}\ci\gtk-for-windows\gtk-nsis-pack\share\glib-2.0" "${{ runner.workspace }}\build\share\glib-2.0"
|
||||
copy "${{ github.workspace }}\ci\vcpkg\installed\x64-windows\tools\glib\gspawn-win64-helper.exe" "${{ runner.workspace }}\build\gspawn-win64-helper.exe"
|
||||
copy "${{ github.workspace }}\ci\vcpkg\installed\x64-windows\tools\glib\gspawn-win64-helper-console.exe" "${{ runner.workspace }}\build\gspawn-win64-helper-console.exe"
|
||||
cmake -GNinja -Bbuild -DCMAKE_BUILD_TYPE=${{ matrix.buildtype }}
|
||||
cmake --build build
|
||||
|
||||
- name: Upload build
|
||||
- name: Setup Artifact
|
||||
run: |
|
||||
mkdir -p build/artifactdir/bin build/artifactdir/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 -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
|
||||
cat "ci/msys-deps.txt" | sed 's/\r$//' | xargs -I % cp /mingw64% build/artifactdir/bin || :
|
||||
cp /usr/bin/msys-ffi-8.dll build/artifactdir/bin/libffi-8.dll
|
||||
mkdir -p build/artifactdir/share/icons/Adwaita
|
||||
cd build/artifactdir/share/icons/Adwaita
|
||||
mkdir -p 16x16/actions 24x24/actions 32x32/actions 48x48/actions 64x64/actions 96x96/actions scalable/actions
|
||||
cd ../../../../../
|
||||
cat "ci/used-icons.txt" | sed 's/\r$//' | xargs -I % cp ci/gtk-for-windows/gtk-nsis-pack/share/icons/Adwaita/16x16/actions/%.symbolic.png build/artifactdir/share/icons/Adwaita/16x16/actions || :
|
||||
cat "ci/used-icons.txt" | sed 's/\r$//' | xargs -I % cp ci/gtk-for-windows/gtk-nsis-pack/share/icons/Adwaita/24x24/actions/%.symbolic.png build/artifactdir/share/icons/Adwaita/24x24/actions || :
|
||||
cat "ci/used-icons.txt" | sed 's/\r$//' | xargs -I % cp ci/gtk-for-windows/gtk-nsis-pack/share/icons/Adwaita/32x32/actions/%.symbolic.png build/artifactdir/share/icons/Adwaita/32x32/actions || :
|
||||
cat "ci/used-icons.txt" | sed 's/\r$//' | xargs -I % cp ci/gtk-for-windows/gtk-nsis-pack/share/icons/Adwaita/48x48/actions/%.symbolic.png build/artifactdir/share/icons/Adwaita/48x48/actions || :
|
||||
cat "ci/used-icons.txt" | sed 's/\r$//' | xargs -I % cp ci/gtk-for-windows/gtk-nsis-pack/share/icons/Adwaita/64x64/actions/%.symbolic.png build/artifactdir/share/icons/Adwaita/64x64/actions || :
|
||||
cat "ci/used-icons.txt" | sed 's/\r$//' | xargs -I % cp ci/gtk-for-windows/gtk-nsis-pack/share/icons/Adwaita/96x96/actions/%.symbolic.png build/artifactdir/share/icons/Adwaita/96x96/actions || :
|
||||
cat "ci/used-icons.txt" | sed 's/\r$//' | xargs -I % cp ci/gtk-for-windows/gtk-nsis-pack/share/icons/Adwaita/scalable/actions/%.svg build/artifactdir/share/icons/Adwaita/scalable/actions || :
|
||||
|
||||
- name: Upload build (1)
|
||||
uses: haya14busa/action-cond@v1
|
||||
id: buildname
|
||||
with:
|
||||
cond: ${{ matrix.mindeps == true }}
|
||||
if_true: "${{ matrix.buildtype }}-mindeps"
|
||||
if_false: "${{ matrix.buildtype }}"
|
||||
|
||||
- name: Upload build (2)
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: build-windows-${{ matrix.buildtype }}
|
||||
path: ${{ runner.workspace }}/build
|
||||
name: build-windows-msys2-${{ steps.buildname.outputs.value }}
|
||||
path: build/artifactdir
|
||||
|
||||
mac:
|
||||
name: macos-${{ matrix.buildtype }}
|
||||
runs-on: macos-latest
|
||||
@@ -66,15 +112,16 @@ jobs:
|
||||
submodules: true
|
||||
|
||||
- name: Fetch CMake
|
||||
uses: lukka/get-cmake@latest
|
||||
uses: lukka/get-cmake@v3.21.2
|
||||
|
||||
- name: Fetch dependencies
|
||||
run: |
|
||||
brew install gtkmm3
|
||||
brew install nlohmann-json
|
||||
brew install jpeg
|
||||
|
||||
- name: Build
|
||||
uses: lukka/run-cmake@main
|
||||
uses: lukka/run-cmake@v3
|
||||
with:
|
||||
buildDirectory: ${{ runner.workspace }}/build
|
||||
cmakeBuildType: ${{ matrix.buildtype }}
|
||||
@@ -83,8 +130,8 @@ jobs:
|
||||
run: |
|
||||
mkdir "${{ runner.workspace }}/artifactdir"
|
||||
cp "${{runner.workspace}}/build/abaddon" "${{ runner.workspace }}/artifactdir/abaddon"
|
||||
cp -r "${{ github.workspace }}/css" "${{ runner.workspace }}/artifactdir/css"
|
||||
cp -r "${{ github.workspace }}/res" "${{ runner.workspace }}/artifactdir/res"
|
||||
cp -r "${{ github.workspace }}/res/css" "${{ runner.workspace }}/artifactdir/css"
|
||||
cp -r "${{ github.workspace }}/res/res" "${{ runner.workspace }}/artifactdir/res"
|
||||
|
||||
- name: Upload build
|
||||
uses: actions/upload-artifact@v2
|
||||
@@ -104,7 +151,7 @@ jobs:
|
||||
submodules: true
|
||||
|
||||
- name: Fetch CMake
|
||||
uses: lukka/get-cmake@latest
|
||||
uses: lukka/get-cmake@v3.21.2
|
||||
|
||||
- name: Fetch dependencies
|
||||
run: |
|
||||
@@ -113,7 +160,7 @@ jobs:
|
||||
cd deps
|
||||
git clone https://github.com/nlohmann/json
|
||||
cd json
|
||||
git checkout db78ac1d7716f56fc9f1b030b715f872f93964e4
|
||||
git checkout bc889afb4c5bf1c0d8ee29ef35eaaf4c8bef8a5d
|
||||
mkdir build
|
||||
cd build
|
||||
cmake ..
|
||||
@@ -123,7 +170,7 @@ jobs:
|
||||
sudo apt-get install libcurl4-gnutls-dev
|
||||
|
||||
- name: Build
|
||||
uses: lukka/run-cmake@main
|
||||
uses: lukka/run-cmake@v3
|
||||
env:
|
||||
CC: gcc-9
|
||||
CXX: g++-9
|
||||
@@ -136,8 +183,8 @@ jobs:
|
||||
run: |
|
||||
mkdir "${{ runner.workspace }}/artifactdir"
|
||||
cp "${{runner.workspace}}/build/abaddon" "${{ runner.workspace }}/artifactdir/abaddon"
|
||||
cp -r "${{ github.workspace }}/css" "${{ runner.workspace }}/artifactdir/css"
|
||||
cp -r "${{ github.workspace }}/res" "${{ runner.workspace }}/artifactdir/res"
|
||||
cp -r "${{ github.workspace }}/res/css" "${{ runner.workspace }}/artifactdir/css"
|
||||
cp -r "${{ github.workspace }}/res/res" "${{ runner.workspace }}/artifactdir/res"
|
||||
|
||||
- name: Upload build
|
||||
uses: actions/upload-artifact@v2
|
||||
|
||||
6
.gitignore
vendored
@@ -356,3 +356,9 @@ build/
|
||||
out/
|
||||
|
||||
fonts/fonts.conf
|
||||
|
||||
# To make sure no zipped resources are added to the repo
|
||||
*.7z
|
||||
*.zip
|
||||
*.tar.*
|
||||
*.rar
|
||||
|
||||
18
.gitmodules
vendored
@@ -1,15 +1,9 @@
|
||||
[submodule "vcpkg"]
|
||||
path = ci/vcpkg
|
||||
url = https://github.com/microsoft/vcpkg/
|
||||
[submodule "thirdparty/simpleini"]
|
||||
path = thirdparty/simpleini
|
||||
url = https://github.com/brofield/simpleini
|
||||
[submodule "thirdparty/IXWebSocket"]
|
||||
path = thirdparty/IXWebSocket
|
||||
url = https://github.com/machinezone/ixwebsocket
|
||||
[submodule "ci/vcpkg"]
|
||||
path = ci/vcpkg
|
||||
url = https://github.com/microsoft/vcpkg
|
||||
[submodule "ci/gtk-for-windows"]
|
||||
path = ci/gtk-for-windows
|
||||
url = https://github.com/tschoonj/GTK-for-Windows-Runtime-Environment-Installer
|
||||
[submodule "subprojects/ixwebsocket"]
|
||||
path = subprojects/ixwebsocket
|
||||
url = https://github.com/machinezone/ixwebsocket
|
||||
[submodule "subprojects/keychain"]
|
||||
path = subprojects/keychain
|
||||
url = https://github.com/hrantzsch/keychain
|
||||
|
||||
136
CMakeLists.txt
@@ -7,6 +7,9 @@ set(ABADDON_RESOURCE_DIR "/usr/share/abaddon" CACHE PATH "Fallback directory for
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake/")
|
||||
|
||||
option(USE_LIBHANDY "Enable features that require libhandy (default)" ON)
|
||||
option(USE_KEYCHAIN "Store the token in the keychain (default)" ON)
|
||||
|
||||
find_package(nlohmann_json REQUIRED)
|
||||
find_package(CURL)
|
||||
find_package(ZLIB REQUIRED)
|
||||
@@ -17,52 +20,36 @@ set(USE_TLS TRUE)
|
||||
set(USE_OPEN_SSL TRUE)
|
||||
find_package(IXWebSocket QUIET)
|
||||
if (NOT IXWebSocket_FOUND)
|
||||
message("ixwebsocket was not found and will be included as a submodule")
|
||||
add_subdirectory(thirdparty/IXWebSocket)
|
||||
include_directories(IXWEBSOCKET_INCLUDE_DIRS)
|
||||
endif()
|
||||
message("ixwebsocket was not found and will be included as a submodule")
|
||||
add_subdirectory(subprojects/ixwebsocket)
|
||||
include_directories(IXWEBSOCKET_INCLUDE_DIRS)
|
||||
endif ()
|
||||
|
||||
add_compile_definitions(SI_NO_CONVERSION) # only CSimpleIniA is used
|
||||
find_package(simpleini QUIET)
|
||||
if (NOT simpleini_FOUND)
|
||||
message("simpleini was not found and will be included as a submodule")
|
||||
include_directories(thirdparty/simpleini)
|
||||
endif()
|
||||
if (MINGW OR WIN32)
|
||||
link_libraries(ws2_32)
|
||||
endif ()
|
||||
|
||||
if(MINGW OR WIN32)
|
||||
link_libraries(ws2_32)
|
||||
endif()
|
||||
if (WIN32)
|
||||
add_compile_definitions(_CRT_SECURE_NO_WARNINGS)
|
||||
add_compile_definitions(NOMINMAX)
|
||||
endif ()
|
||||
|
||||
if(WIN32)
|
||||
add_compile_definitions(_CRT_SECURE_NO_WARNINGS)
|
||||
add_compile_definitions(NOMINMAX)
|
||||
include(TestBigEndian)
|
||||
test_big_endian(IS_BIG_ENDIAN)
|
||||
if (IS_BIG_ENDIAN)
|
||||
add_compile_definitions(ABADDON_IS_BIG_ENDIAN)
|
||||
endif ()
|
||||
|
||||
find_package(Fontconfig REQUIRED)
|
||||
link_libraries(${Fontconfig_LIBRARIES})
|
||||
endif()
|
||||
configure_file(${PROJECT_SOURCE_DIR}/src/config.h.in ${PROJECT_BINARY_DIR}/config.h)
|
||||
|
||||
configure_file(${PROJECT_SOURCE_DIR}/config.h.in ${PROJECT_BINARY_DIR}/config.h)
|
||||
|
||||
file(GLOB ABADDON_SOURCES
|
||||
"*.h"
|
||||
"*.hpp"
|
||||
"*.cpp"
|
||||
"discord/*.hpp"
|
||||
"discord/*.cpp"
|
||||
"components/*.hpp"
|
||||
"components/*.cpp"
|
||||
"windows/*.hpp"
|
||||
"windows/*.cpp"
|
||||
"windows/guildsettings/*.hpp"
|
||||
"windows/guildsettings/*.cpp"
|
||||
"windows/profile/*.hpp"
|
||||
"windows/profile/*.cpp"
|
||||
"dialogs/*.hpp"
|
||||
"dialogs/*.cpp"
|
||||
)
|
||||
file(GLOB_RECURSE ABADDON_SOURCES
|
||||
"src/*.h"
|
||||
"src/*.hpp"
|
||||
"src/*.cpp"
|
||||
)
|
||||
|
||||
add_executable(abaddon ${ABADDON_SOURCES})
|
||||
target_include_directories(abaddon PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
target_include_directories(abaddon PUBLIC ${PROJECT_SOURCE_DIR}/src)
|
||||
target_include_directories(abaddon PUBLIC ${PROJECT_BINARY_DIR})
|
||||
target_include_directories(abaddon PUBLIC ${GTKMM_INCLUDE_DIRS})
|
||||
target_include_directories(abaddon PUBLIC ${ZLIB_INCLUDE_DIRS})
|
||||
@@ -70,36 +57,63 @@ target_include_directories(abaddon PUBLIC ${SQLite3_INCLUDE_DIRS})
|
||||
target_include_directories(abaddon PUBLIC ${NLOHMANN_JSON_INCLUDE_DIRS})
|
||||
|
||||
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))))
|
||||
target_link_libraries(abaddon stdc++fs)
|
||||
endif()
|
||||
(CMAKE_CXX_COMPILER_ID STREQUAL "Clang" AND
|
||||
((CMAKE_SYSTEM_NAME STREQUAL "Linux") OR (CMAKE_CXX_COMPILER_VERSION LESS 9))))
|
||||
target_link_libraries(abaddon stdc++fs)
|
||||
endif ()
|
||||
|
||||
if (IXWebSocket_LIBRARIES)
|
||||
target_link_libraries(abaddon ${IXWebSocket_LIBRARIES})
|
||||
find_library(MBEDTLS_X509_LIBRARY mbedx509)
|
||||
find_library(MBEDTLS_TLS_LIBRARY mbedtls)
|
||||
find_library(MBEDTLS_CRYPTO_LIBRARY mbedcrypto)
|
||||
if (MBEDTLS_TLS_LIBRARY)
|
||||
target_link_libraries(abaddon ${MBEDTLS_TLS_LIBRARY})
|
||||
endif()
|
||||
if (MBEDTLS_X509_LIBRARY)
|
||||
target_link_libraries(abaddon ${MBEDTLS_X509_LIBRARY})
|
||||
endif()
|
||||
if (MBEDTLS_CRYPTO_LIBRARY)
|
||||
target_link_libraries(abaddon ${MBEDTLS_CRYPTO_LIBRARY})
|
||||
endif()
|
||||
else()
|
||||
target_link_libraries(abaddon $<BUILD_INTERFACE:ixwebsocket>)
|
||||
endif()
|
||||
target_link_libraries(abaddon ${IXWebSocket_LIBRARIES})
|
||||
find_library(MBEDTLS_X509_LIBRARY mbedx509)
|
||||
find_library(MBEDTLS_TLS_LIBRARY mbedtls)
|
||||
find_library(MBEDTLS_CRYPTO_LIBRARY mbedcrypto)
|
||||
if (MBEDTLS_TLS_LIBRARY)
|
||||
target_link_libraries(abaddon ${MBEDTLS_TLS_LIBRARY})
|
||||
endif ()
|
||||
if (MBEDTLS_X509_LIBRARY)
|
||||
target_link_libraries(abaddon ${MBEDTLS_X509_LIBRARY})
|
||||
endif ()
|
||||
if (MBEDTLS_CRYPTO_LIBRARY)
|
||||
target_link_libraries(abaddon ${MBEDTLS_CRYPTO_LIBRARY})
|
||||
endif ()
|
||||
else ()
|
||||
target_link_libraries(abaddon $<BUILD_INTERFACE:ixwebsocket>)
|
||||
endif ()
|
||||
|
||||
find_package(Threads)
|
||||
if (Threads_FOUND)
|
||||
target_link_libraries(abaddon Threads::Threads)
|
||||
endif()
|
||||
target_link_libraries(abaddon Threads::Threads)
|
||||
endif ()
|
||||
|
||||
find_package(Fontconfig QUIET)
|
||||
if (Fontconfig_FOUND)
|
||||
target_link_libraries(abaddon Fontconfig::Fontconfig)
|
||||
endif ()
|
||||
|
||||
target_link_libraries(abaddon ${SQLite3_LIBRARIES})
|
||||
target_link_libraries(abaddon ${GTKMM_LIBRARIES})
|
||||
target_link_libraries(abaddon ${CURL_LIBRARIES})
|
||||
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)
|
||||
endif ()
|
||||
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 ()
|
||||
|
||||
365
README.md
@@ -4,14 +4,16 @@ Alternative Discord client made in C++ with GTK
|
||||
|
||||
<img src="/.readme/s3.png">
|
||||
|
||||
[😎Discord Server](https://discord.gg/wkCU3vuzG5)
|
||||
<a href="https://discord.gg/wkCU3vuzG5"><img src="https://discord.com/api/guilds/858156817711890443/widget.png?style=shield"></a>
|
||||
|
||||
Current features:
|
||||
|
||||
* Not Electron
|
||||
* Handles most types of chat messages including embeds, images, and replies
|
||||
* Completely styleable/customizable with CSS (if you have a system GTK theme it won't really use it though)
|
||||
* Identifies to Discord as the web client unlike other clients so less likely to be falsely flagged as spam<sup>1</sup>
|
||||
* Set status
|
||||
* Unread and mention indicators
|
||||
* Start new DMs and group DMs
|
||||
* View user profiles (notes, mutual servers, mutual friends)
|
||||
* Kick, ban, and unban members
|
||||
@@ -22,33 +24,66 @@ Current features:
|
||||
* Emojis<sup>2</sup>
|
||||
* Thread support<sup>3</sup>
|
||||
* Animated avatars, server icons, emojis (can be turned off)
|
||||
|
||||
1 - Abaddon tries its best to make Discord think it's a legitimate web client. Some of the things done to do this include: using a browser user agent, sending the same IDENTIFY message that the official web client does, using API v9 endpoints in all cases, and not using endpoints the web client does not normally use. There are still a few smaller inconsistencies, however. For example the web client sends lots of telemetry via the `/science` endpoint (uBlock origin stops this) as well as in the headers of all requests. **In any case,** you should use an official client for joining servers, sending new DMs, or managing your friends list if you are afraid of being caught in Discord's spam filters (unlikely).
|
||||
|
||||
2 - Unicode emojis are subtituted manually as opposed to rendered by GTK on non-Windows platforms. This can be changed with the `stock_emojis` setting as shown at the bottom of this README. A CBDT-based font using Twemoji is provided to allow GTK to render emojis natively on Windows.
|
||||
1 - Abaddon tries its best (though is not perfect) to make Discord think it's a legitimate web client. Some of the
|
||||
things done to do this
|
||||
include: using a browser user agent, sending the same IDENTIFY message that the official web client does, using API v9
|
||||
endpoints in all cases, and not using endpoints the web client does not normally use. There are still a few smaller
|
||||
inconsistencies, however. For example the web client sends lots of telemetry via the `/science` endpoint (uBlock origin
|
||||
stops this) as well as in the headers of all requests.<br>
|
||||
|
||||
**See [here](#the-spam-filter)** for things you might want to avoid if you are worried about being caught in the spam
|
||||
filter.
|
||||
|
||||
2 - Unicode emojis are substituted manually as opposed to rendered by GTK on non-Windows platforms. This can be changed
|
||||
with the `stock_emojis` setting as shown at the bottom of this README. A CBDT-based font using Twemoji is provided to
|
||||
allow GTK to render emojis natively on Windows.
|
||||
|
||||
3 - There are some inconsistencies with thread state that might be encountered in some more uncommon cases, but they are
|
||||
the result of fundamental issues with Discord's thread implementation.
|
||||
|
||||
3 - There are some inconsistencies with thread state that might be encountered in some more uncommon cases, but they are the result of fundamental issues with Discord's thread implementation.
|
||||
|
||||
### Building manually (recommended if not on Windows):
|
||||
#### Windows:
|
||||
1. `git clone https://github.com/uowuo/abaddon && cd abaddon`
|
||||
2. `vcpkg install gtkmm:x64-windows nlohmann-json:x64-windows ixwebsocket:x64-windows zlib:x64-windows simpleini:x64-windows sqlite3:x64-windows openssl:x64-windows curl:x64-windows`
|
||||
|
||||
#### Windows (with MSYS2):
|
||||
|
||||
1. Install following packages:
|
||||
* mingw-w64-x86_64-cmake
|
||||
* mingw-w64-x86_64-ninja
|
||||
* mingw-w64-x86_64-sqlite3
|
||||
* mingw-w64-x86_64-nlohmann-json
|
||||
* mingw-w64-x86_64-curl
|
||||
* mingw-w64-x86_64-zlib
|
||||
* mingw-w64-x86_64-gtkmm3
|
||||
* mingw-w64-x86_64-libhandy
|
||||
2. `git clone --recurse-submodules="subprojects" https://github.com/uowuo/abaddon && cd abaddon`
|
||||
3. `mkdir build && cd build`
|
||||
4. `cmake -G"Visual Studio 16 2019" -A x64 -DCMAKE_TOOLCHAIN_FILE=c:\path\to\vcpkg\scripts\buildsystems\vcpkg.cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo -DVCPKG_TARGET_TRIPLET=x64-windows ..`
|
||||
5. Build with Visual Studio
|
||||
|
||||
Or, do steps 1 and 2, and open CMakeLists.txt in Visual Studio if `vcpkg integrate install` was run
|
||||
4. `cmake -GNinja -DCMAKE_BUILD_TYPE=RelWithDebInfo ..`
|
||||
5. `ninja`
|
||||
|
||||
#### Mac:
|
||||
1. `git clone https://github.com/uowuo/abaddon && cd abaddon`
|
||||
2. `brew install gtkmm3 nlohmann-json`
|
||||
|
||||
1. `git clone https://github.com/uowuo/abaddon --recurse-submodules="subprojects" && cd abaddon`
|
||||
2. `brew install gtkmm3 nlohmann-json libhandy`
|
||||
3. `mkdir build && cd build`
|
||||
4. `cmake ..`
|
||||
5. `make`
|
||||
|
||||
#### Linux:
|
||||
1. Install dependencies: `libgtkmm-3.0-dev`, `libcurl4-gnutls-dev`, and [nlohmann-json](https://github.com/nlohmann/json)
|
||||
2. `git clone https://github.com/uowuo/abaddon && cd abaddon`
|
||||
|
||||
1. Install dependencies
|
||||
* On Ubuntu 20.04 (Focal) and newer:
|
||||
```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 ..`
|
||||
5. `make`
|
||||
@@ -59,161 +94,207 @@ Latest release version: https://github.com/uowuo/abaddon/releases/latest
|
||||
|
||||
**CI:**
|
||||
|
||||
- Windows: [here](https://nightly.link/uowuo/abaddon/workflows/ci/master/build-windows-RelWithDebInfo.zip)
|
||||
- MacOS: [here](https://nightly.link/uowuo/abaddon/workflows/ci/master/build-macos-RelWithDebInfo.zip) unsigned, unpackaged, requires gtkmm3 (e.g. from homebrew)
|
||||
- Linux: [here](https://nightly.link/uowuo/abaddon/workflows/ci/master/build-linux-MinSizeRel.zip) unpackaged (for now), requires gtkmm3. built on Ubuntu 18.04 + gcc9
|
||||
- Windows: [here](https://nightly.link/uowuo/abaddon/workflows/ci/master/build-windows-msys2-MinSizeRel.zip)
|
||||
- MacOS: [here](https://nightly.link/uowuo/abaddon/workflows/ci/master/build-macos-RelWithDebInfo.zip) unsigned,
|
||||
unpackaged, requires gtkmm3 (e.g. from homebrew)
|
||||
- Linux: [here](https://nightly.link/uowuo/abaddon/workflows/ci/master/build-linux-MinSizeRel.zip) unpackaged (for now),
|
||||
requires gtkmm3. built on Ubuntu 18.04 + gcc9
|
||||
|
||||
⚠️ If you use Windows, make sure to start from the directory containing `css` and `res`
|
||||
> **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`
|
||||
|
||||
`abaddon.ini` will also be automatically used if located at `~/.config/abaddon/abaddon.ini` and there is no `abaddon.ini` in the working directory
|
||||
`abaddon.ini` will also be automatically used if located at `~/.config/abaddon/abaddon.ini` and there is
|
||||
no `abaddon.ini` in the working directory
|
||||
|
||||
#### The Spam Filter
|
||||
|
||||
Discord likes disabling accounts/forcing them to reset their passwords if they think the user is a spam bot or
|
||||
potentially had their account compromised. While the official client still often gets users caught in the spam filter,
|
||||
third party clients tend to upset the spam filter more often. If you get caught by it, you can
|
||||
usually [appeal](https://support.discord.com/hc/en-us/requests/new?ticket_form_id=360000029731) it and get it restored.
|
||||
Here are some things you might want to do with the official client instead if you are particularly afraid of evoking the
|
||||
spam filter's wrath:
|
||||
|
||||
* Joining or leaving servers (usually main cause of getting caught)
|
||||
* Frequently disconnecting and reconnecting
|
||||
* Starting new DMs with people
|
||||
* Managing your friends list
|
||||
* Managing your user profile while connected to a third party client
|
||||
|
||||
#### Dependencies:
|
||||
|
||||
#### Dependencies:
|
||||
* [gtkmm](https://www.gtkmm.org/en/)
|
||||
* [JSON for Modern C++](https://github.com/nlohmann/json)
|
||||
* [IXWebSocket](https://github.com/machinezone/IXWebSocket)
|
||||
* [libcurl](https://curl.se/)
|
||||
* [zlib](https://zlib.net/)
|
||||
* [simpleini](https://github.com/brofield/simpleini)
|
||||
* [SQLite3](https://www.sqlite.org/index.html)
|
||||
* [libhandy](https://gnome.pages.gitlab.gnome.org/libhandy/) (optional)
|
||||
|
||||
### TODO:
|
||||
|
||||
* Voice support
|
||||
* Unread indicators
|
||||
* User activities
|
||||
* Nicknames
|
||||
* More server management stuff
|
||||
* Manage friends
|
||||
* A bunch of other stuff
|
||||
|
||||
### Styling
|
||||
#### 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
|
||||
|
||||
.channel-list - Container of the channel list
|
||||
.channel-row - All rows within the channel container
|
||||
.channel-row-channel - Only rows containing a channel
|
||||
.channel-row-category - Only rows containing a category
|
||||
.channel-row-guild - Only rows containing a guild
|
||||
.channel-row-label - All labels within the channel container
|
||||
.nsfw - Applied to channel row labels and their container for NSFW channels
|
||||
|
||||
.messages - Container of user messages
|
||||
.message-container - The container which holds a user's messages
|
||||
.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)
|
||||
|
||||
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
|
||||
|
||||
#### CSS selectors
|
||||
|
||||
| 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) |
|
||||
|
||||
Used in reorderable list implementation:
|
||||
| Selector |
|
||||
|----------------------|
|
||||
| `.drag-icon` |
|
||||
| `.drag-hover-top` |
|
||||
| `.drag-hover-bottom` |
|
||||
|
||||
Used in guild settings popup:
|
||||
|
||||
| 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 |
|
||||
|
||||
Used in profile popup:
|
||||
|
||||
| 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
|
||||
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.
|
||||
|
||||
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
|
||||
|
||||
> **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
|
||||
* 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 10) - 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
|
||||
* 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
|
||||
|
||||
| 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
|
||||
|
||||
| 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 |
|
||||
|
||||
59
ci/msys-deps.txt
Normal file
@@ -0,0 +1,59 @@
|
||||
/bin/gdbus.exe
|
||||
/bin/gspawn-win64-helper-console.exe
|
||||
/bin/libatk-1.0-0.dll
|
||||
/bin/libatkmm-1.6-1.dll
|
||||
/bin/libbrotlicommon.dll
|
||||
/bin/libbrotlidec.dll
|
||||
/bin/libbz2-1.dll
|
||||
/bin/libcairo-2.dll
|
||||
/bin/libcairo-gobject-2.dll
|
||||
/bin/libcairomm-1.0-1.dll
|
||||
/bin/libcrypto-1_1-x64.dll
|
||||
/bin/libcurl-4.dll
|
||||
/bin/libdatrie-1.dll
|
||||
/bin/libdeflate.dll
|
||||
/bin/libepoxy-0.dll
|
||||
/bin/libexpat-1.dll
|
||||
/bin/libffi-8.dll
|
||||
/bin/libfontconfig-1.dll
|
||||
/bin/libfreetype-6.dll
|
||||
/bin/libfribidi-0.dll
|
||||
/bin/libgcc_s_seh-1.dll
|
||||
/bin/libgdk-3-0.dll
|
||||
/bin/libgdk_pixbuf-2.0-0.dll
|
||||
/bin/libgdkmm-3.0-1.dll
|
||||
/bin/libgio-2.0-0.dll
|
||||
/bin/libgiomm-2.4-1.dll
|
||||
/bin/libglib-2.0-0.dll
|
||||
/bin/libglibmm-2.4-1.dll
|
||||
/bin/libgmodule-2.0-0.dll
|
||||
/bin/libgobject-2.0-0.dll
|
||||
/bin/libgraphite2.dll
|
||||
/bin/libgtk-3-0.dll
|
||||
/bin/libgtkmm-3.0-1.dll
|
||||
/bin/libhandy-1-0.dll
|
||||
/bin/libharfbuzz-0.dll
|
||||
/bin/libiconv-2.dll
|
||||
/bin/libidn2-0.dll
|
||||
/bin/libintl-8.dll
|
||||
/bin/libnghttp2-14.dll
|
||||
/bin/libpango-1.0-0.dll
|
||||
/bin/libpangocairo-1.0-0.dll
|
||||
/bin/libpangoft2-1.0-0.dll
|
||||
/bin/libpangomm-1.4-1.dll
|
||||
/bin/libpangowin32-1.0-0.dll
|
||||
/bin/libpcre2-8-0.dll
|
||||
/bin/libpixman-1-0.dll
|
||||
/bin/libpng16-16.dll
|
||||
/bin/libpsl-5.dll
|
||||
/bin/libsigc-2.0-0.dll
|
||||
/bin/libsqlite3-0.dll
|
||||
/bin/libssh2-1.dll
|
||||
/bin/libssl-1_1-x64.dll
|
||||
/bin/libstdc++-6.dll
|
||||
/bin/libthai-0.dll
|
||||
/bin/libunistring-2.dll
|
||||
/bin/libwinpthread-1.dll
|
||||
/bin/libzstd.dll
|
||||
/bin/zlib1.dll
|
||||
/../usr/bin/msys-2.0.dll
|
||||
1
ci/used-icons.txt
Normal file
@@ -0,0 +1 @@
|
||||
document-send-symbolic
|
||||
1
ci/vcpkg
37
cmake/Findgdk.cmake
Normal file
@@ -0,0 +1,37 @@
|
||||
find_package(PkgConfig)
|
||||
if (PKG_CONFIG_FOUND)
|
||||
pkg_check_modules(PC_gdk QUIET gdk-3.0)
|
||||
set(gdk_DEFINITIONS ${PC_gdk_CFLAGS_OTHER})
|
||||
endif ()
|
||||
|
||||
set(gdk_INCLUDE_HINTS ${PC_gdk_INCLUDEDIR} ${PC_gdk_INCLUDE_DIRS})
|
||||
set(gdk_LIBRARY_HINTS ${PC_gdk_LIBDIR} ${PC_gdk_LIBRARY_DIRS})
|
||||
|
||||
find_path(gdk_INCLUDE_DIR
|
||||
NAMES gdk/gdk.h
|
||||
HINTS ${gdk_INCLUDE_HINTS}
|
||||
/usr/include
|
||||
/usr/local/include
|
||||
/opt/local/include
|
||||
PATH_SUFFIXES gdk-3.0)
|
||||
|
||||
find_library(gdk_LIBRARY
|
||||
NAMES gdk-3.0
|
||||
gdk-3
|
||||
gdk
|
||||
HINTS ${gdk_LIBRARY_HINTS}
|
||||
/usr/lib
|
||||
/usr/local/lib
|
||||
/opt/local/lib)
|
||||
|
||||
set(gdk_LIBRARIES ${gdk_LIBRARY})
|
||||
set(gdk_INCLUDE_DIRS ${gdk_INCLUDE_DIR})
|
||||
|
||||
include(FindPackageHandleStandardArgs)
|
||||
find_package_handle_standard_args(gdk
|
||||
REQUIRED_VARS
|
||||
gdk_LIBRARY
|
||||
gdk_INCLUDE_DIR
|
||||
VERSION_VAR gdk_VERSION)
|
||||
|
||||
mark_as_advanced(gdk_INCLUDE_DIR gdk_LIBRARY)
|
||||
@@ -1,48 +1,50 @@
|
||||
set(GDKMM_LIBRARY_NAME gdkmm-3.0)
|
||||
set(gdkmm_LIBRARY_NAME gdkmm-3.0)
|
||||
|
||||
find_package(PkgConfig)
|
||||
if (PKG_CONFIG_FOUND)
|
||||
pkg_check_modules(PKGCONFIG_GDKMM QUIET ${GDKMM_LIBRARY_NAME})
|
||||
set(GDKMM_DEFINITIONS ${PKGCONFIG_GDKMM_CFLAGS_OTHER})
|
||||
pkg_check_modules(PKGCONFIG_gdkmm QUIET ${gdkmm_LIBRARY_NAME})
|
||||
set(gdkmm_DEFINITIONS ${PKGCONFIG_gdkmm_CFLAGS_OTHER})
|
||||
endif (PKG_CONFIG_FOUND)
|
||||
|
||||
set(GDKMM_INCLUDE_HINTS ${PKGCONFIG_GDKMM_INCLUDEDIR} ${PKGCONFIG_GDKMM_INCLUDE_DIRS})
|
||||
set(GDKMM_LIBRARY_HINTS ${PKGCONFIG_GDKMM_LIBDIR} ${PKGCONFIG_GDKMM_LIBRARY_DIRS})
|
||||
set(gdkmm_INCLUDE_HINTS ${PKGCONFIG_gdkmm_INCLUDEDIR} ${PKGCONFIG_gdkmm_INCLUDE_DIRS})
|
||||
set(gdkmm_LIBRARY_HINTS ${PKGCONFIG_gdkmm_LIBDIR} ${PKGCONFIG_gdkmm_LIBRARY_DIRS})
|
||||
|
||||
find_path(GDKMM_INCLUDE_DIR
|
||||
find_path(gdkmm_INCLUDE_DIR
|
||||
NAMES gdkmm.h
|
||||
HINTS ${GDKMM_INCLUDE_HINTS}
|
||||
HINTS ${gdkmm_INCLUDE_HINTS}
|
||||
/usr/include
|
||||
/usr/local/include
|
||||
/opt/local/include
|
||||
PATH_SUFFIXES ${GDKMM_LIBRARY_NAME})
|
||||
PATH_SUFFIXES ${gdkmm_LIBRARY_NAME})
|
||||
|
||||
find_path(GDKMM_CONFIG_INCLUDE_DIR
|
||||
find_path(gdkmm_CONFIG_INCLUDE_DIR
|
||||
NAMES gdkmmconfig.h
|
||||
HINTS ${GDKMM_LIBRARY_HINTS}
|
||||
HINTS ${gdkmm_LIBRARY_HINTS}
|
||||
/usr/lib
|
||||
/usr/local/lib
|
||||
/opt/local/lib
|
||||
PATH_SUFFIXES ${GDKMM_LIBRARY_NAME}/include)
|
||||
PATH_SUFFIXES ${gdkmm_LIBRARY_NAME}/include)
|
||||
|
||||
find_library(GDKMM_LIBRARY
|
||||
NAMES ${GDKMM_LIBRARY_NAME}
|
||||
gdkmm
|
||||
HINTS ${GDKMM_LIBRARY_HINTS}
|
||||
find_library(gdkmm_LIBRARY
|
||||
NAMES ${gdkmm_LIBRARY_NAME}
|
||||
gdkmm
|
||||
HINTS ${gdkmm_LIBRARY_HINTS}
|
||||
/usr/lib
|
||||
/usr/local/lib
|
||||
/opt/local/lib
|
||||
PATH_SUFFIXES ${GDKMM_LIBRARY_NAME}
|
||||
${GDKMM_LIBRARY_NAME}/include)
|
||||
PATH_SUFFIXES ${gdkmm_LIBRARY_NAME}
|
||||
${gdkmm_LIBRARY_NAME}/include)
|
||||
|
||||
set(GDKMM_LIBRARIES ${GDKMM_LIBRARY})
|
||||
set(GDKMM_INCLUDE_DIRS ${GDKMM_INCLUDE_DIR};${GDKMM_CONFIG_INCLUDE_DIRS};${GDKMM_CONFIG_INCLUDE_DIR})
|
||||
find_package(gdk)
|
||||
|
||||
set(gdkmm_LIBRARIES ${gdkmm_LIBRARY};${gdk_LIBRARIES})
|
||||
set(gdkmm_INCLUDE_DIRS ${gdkmm_INCLUDE_DIR};${gdkmm_CONFIG_INCLUDE_DIRS};${gdkmm_CONFIG_INCLUDE_DIR};${gdk_INCLUDE_DIRS})
|
||||
|
||||
include(FindPackageHandleStandardArgs)
|
||||
find_package_handle_standard_args(gdkmm
|
||||
REQUIRED_VARS
|
||||
GDKMM_LIBRARY
|
||||
GDKMM_INCLUDE_DIRS
|
||||
VERSION_VAR GDKMM_VERSION)
|
||||
gdkmm_LIBRARY
|
||||
gdkmm_INCLUDE_DIRS
|
||||
VERSION_VAR gdkmm_VERSION)
|
||||
|
||||
mark_as_advanced(GDKMM_INCLUDE_DIR GDKMM_LIBRARY)
|
||||
mark_as_advanced(gdkmm_INCLUDE_DIR gdkmm_LIBRARY)
|
||||
|
||||
@@ -2,56 +2,70 @@ find_package(PkgConfig)
|
||||
pkg_check_modules(PC_GLIB2 QUIET glib-2.0)
|
||||
|
||||
find_path(GLIB_INCLUDE_DIR
|
||||
NAMES glib.h
|
||||
HINTS ${PC_GLIB2_INCLUDEDIR}
|
||||
${PC_GLIB2_INCLUDE_DIRS}
|
||||
$ENV{GLIB2_HOME}/include
|
||||
$ENV{GLIB2_ROOT}/include
|
||||
/usr/local/include
|
||||
/usr/include
|
||||
/glib2/include
|
||||
/glib-2.0/include
|
||||
PATH_SUFFIXES glib2 glib-2.0 glib-2.0/include
|
||||
)
|
||||
NAMES glib.h
|
||||
HINTS ${PC_GLIB2_INCLUDEDIR}
|
||||
${PC_GLIB2_INCLUDE_DIRS}
|
||||
$ENV{GLIB2_HOME}/include
|
||||
$ENV{GLIB2_ROOT}/include
|
||||
/usr/local/include
|
||||
/usr/include
|
||||
/glib2/include
|
||||
/glib-2.0/include
|
||||
PATH_SUFFIXES glib2 glib-2.0 glib-2.0/include
|
||||
)
|
||||
set(GLIB_INCLUDE_DIRS ${GLIB_INCLUDE_DIR})
|
||||
|
||||
find_library(GLIB_LIBRARIES
|
||||
NAMES glib2
|
||||
glib-2.0
|
||||
HINTS ${PC_GLIB2_LIBDIR}
|
||||
${PC_GLIB2_LIBRARY_DIRS}
|
||||
$ENV{GLIB2_HOME}/lib
|
||||
$ENV{GLIB2_ROOT}/lib
|
||||
/usr/local/lib
|
||||
/usr/lib
|
||||
/lib
|
||||
/glib-2.0/lib
|
||||
PATH_SUFFIXES glib2 glib-2.0
|
||||
)
|
||||
NAMES glib2
|
||||
glib-2.0
|
||||
HINTS ${PC_GLIB2_LIBDIR}
|
||||
${PC_GLIB2_LIBRARY_DIRS}
|
||||
$ENV{GLIB2_HOME}/lib
|
||||
$ENV{GLIB2_ROOT}/lib
|
||||
/usr/local/lib
|
||||
/usr/lib
|
||||
/lib
|
||||
/glib-2.0/lib
|
||||
PATH_SUFFIXES glib2 glib-2.0
|
||||
)
|
||||
|
||||
find_library(glib_GOBJECT_LIBRARIES
|
||||
NAMES gobject-2.0
|
||||
HINTS ${PC_GLIB2_LIBDIR}
|
||||
${PC_GLIB2_LIBRARY_DIRS}
|
||||
)
|
||||
|
||||
find_library(glib_GIO_LIBRARIES
|
||||
NAMES gio-2.0
|
||||
HINTS ${PC_GLIB2_LIBDIR}
|
||||
${PC_GLIB2_LIBRARY_DIRS}
|
||||
)
|
||||
|
||||
get_filename_component(_GLIB2_LIB_DIR "${GLIB_LIBRARIES}" PATH)
|
||||
find_path(GLIB_CONFIG_INCLUDE_DIR
|
||||
NAMES glibconfig.h
|
||||
HINTS ${PC_GLIB2_INCLUDEDIR}
|
||||
${PC_GLIB2_INCLUDE_DIRS}
|
||||
$ENV{GLIB2_HOME}/include
|
||||
$ENV{GLIB2_ROOT}/include
|
||||
/usr/local/include
|
||||
/usr/include
|
||||
/glib2/include
|
||||
/glib-2.0/include
|
||||
${_GLIB2_LIB_DIR}
|
||||
${CMAKE_SYSTEM_LIBRARY_PATH}
|
||||
PATH_SUFFIXES glib2 glib-2.0 glib-2.0/include
|
||||
)
|
||||
NAMES glibconfig.h
|
||||
HINTS ${PC_GLIB2_INCLUDEDIR}
|
||||
${PC_GLIB2_INCLUDE_DIRS}
|
||||
$ENV{GLIB2_HOME}/include
|
||||
$ENV{GLIB2_ROOT}/include
|
||||
/usr/local/include
|
||||
/usr/include
|
||||
/glib2/include
|
||||
/glib-2.0/include
|
||||
${_GLIB2_LIB_DIR}
|
||||
${CMAKE_SYSTEM_LIBRARY_PATH}
|
||||
PATH_SUFFIXES glib2 glib-2.0 glib-2.0/include
|
||||
)
|
||||
if (GLIB_CONFIG_INCLUDE_DIR)
|
||||
set(GLIB_INCLUDE_DIRS ${GLIB_INCLUDE_DIRS} ${GLIB_CONFIG_INCLUDE_DIR})
|
||||
endif()
|
||||
endif ()
|
||||
|
||||
set(GLIB_LIBRARIES ${GLIB_LIBRARIES} ${glib_GOBJECT_LIBRARIES} ${glib_GIO_LIBRARIES})
|
||||
|
||||
include(FindPackageHandleStandardArgs)
|
||||
find_package_handle_standard_args(glib
|
||||
REQUIRED_VARS
|
||||
GLIB_LIBRARIES
|
||||
GLIB_INCLUDE_DIRS
|
||||
VERSION_VAR GLIB_VERSION)
|
||||
mark_as_advanced(GLIB_INCLUDE_DIR GLIB_CONFIG_INCLUDE_DIR)
|
||||
REQUIRED_VARS
|
||||
GLIB_LIBRARIES
|
||||
GLIB_INCLUDE_DIRS
|
||||
VERSION_VAR GLIB_VERSION)
|
||||
mark_as_advanced(GLIB_INCLUDE_DIR GLIB_CONFIG_INCLUDE_DIR glib_GOBJECT_LIBRARIES)
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
set(GTKMM_LIBRARY_NAME gtkmm-3.0)
|
||||
set(GDKMM_LIBRARY_NAME gdkmm-3.0)
|
||||
set(GTKMM_LIBRARY_NAME gtkmm-3.0)
|
||||
set(GDKMM_LIBRARY_NAME gdkmm-3.0)
|
||||
|
||||
find_package(PkgConfig)
|
||||
if(PKG_CONFIG_FOUND)
|
||||
if (PKG_CONFIG_FOUND)
|
||||
pkg_check_modules(PC_GTKMM QUIET ${GTKMM_LIBRARY_NAME})
|
||||
pkg_check_modules(PC_GDKMM QUIET ${GDKMM_LIBRARY_NAME})
|
||||
pkg_check_modules(PC_PANGOMM QUIET ${PANGOMM_LIBRARY_NAME})
|
||||
set(GTKMM_DEFINITIONS ${PC_GTKMM_CFLAGS_OTHER})
|
||||
endif()
|
||||
set(GTKMM_DEFINITIONS ${PC_GTKMM_CFLAGS_OTHER})
|
||||
endif ()
|
||||
|
||||
find_package(gtk)
|
||||
find_package(glibmm)
|
||||
@@ -46,14 +46,14 @@ find_path(GDKMM_CONFIG_INCLUDE_DIR
|
||||
HINTS ${GDKMM_INCLUDE_HINTS}
|
||||
PATH_SUFFIXES ${GDKMM_LIBRARY_NAME}/include)
|
||||
|
||||
set(GTKMM_LIBRARIES ${GTKMM_LIB};${GDKMM_LIBRARY};${GTK_LIBRARIES};${GLIBMM_LIBRARIES};${PANGOMM_LIBRARIES};${CAIROMM_LIBRARIES};${ATKMM_LIBRARIES};${SIGC++_LIBRARIES})
|
||||
set(GTKMM_INCLUDE_DIRS ${GTKMM_INCLUDE_DIR};${GTKMM_CONFIG_INCLUDE_DIR};${GDKMM_INCLUDE_DIR};${GDKMM_CONFIG_INCLUDE_DIR};${GTK_INCLUDE_DIRS};${GLIBMM_INCLUDE_DIRS};${PANGOMM_INCLUDE_DIRS};${CAIROMM_INCLUDE_DIRS};${ATKMM_INCLUDE_DIRS};${SIGC++_INCLUDE_DIRS})
|
||||
set(GTKMM_LIBRARIES ${GTKMM_LIB};${gdkmm_LIBRARIES};${GTK_LIBRARIES};${GLIBMM_LIBRARIES};${PANGOMM_LIBRARIES};${CAIROMM_LIBRARIES};${ATKMM_LIBRARIES};${SIGC++_LIBRARIES})
|
||||
set(GTKMM_INCLUDE_DIRS ${GTKMM_INCLUDE_DIR};${GTKMM_CONFIG_INCLUDE_DIR};${gdkmm_INCLUDE_DIRS};${gdkmm_CONFIG_INCLUDE_DIR};${GTK_INCLUDE_DIRS};${GLIBMM_INCLUDE_DIRS};${PANGOMM_INCLUDE_DIRS};${CAIROMM_INCLUDE_DIRS};${ATKMM_INCLUDE_DIRS};${SIGC++_INCLUDE_DIRS})
|
||||
|
||||
include(FindPackageHandleStandardArgs)
|
||||
find_package_handle_standard_args(gtkmm
|
||||
REQUIRED_VARS
|
||||
GTKMM_LIB
|
||||
GTKMM_INCLUDE_DIRS
|
||||
GTKMM_LIB
|
||||
GTKMM_INCLUDE_DIRS
|
||||
VERSION_VAR GTKMM_VERSION)
|
||||
|
||||
mark_as_advanced(GTKMM_INCLUDE_DIR GTKMM_LIBRARY)
|
||||
|
||||
39
cmake/Findlibhandy.cmake
Normal file
@@ -0,0 +1,39 @@
|
||||
set(libhandy_LIBRARY_NAME libhandy-1)
|
||||
|
||||
find_package(PkgConfig)
|
||||
if (PKG_CONFIG_FOUND)
|
||||
pkg_check_modules(PC_libhandy QUIET ${libhandy_LIBRARY_NAME})
|
||||
set(libhandy_DEfINITIONS ${PC_libhandy_CFLAGS_OTHER})
|
||||
endif (PKG_CONFIG_FOUND)
|
||||
|
||||
set(libhandy_INCLUDE_HINTS ${PC_libhandy_INCLUDEDIR} ${PC_libhandy_INCLUDE_DIRS})
|
||||
set(libhandy_LIBRARY_HINTS ${PC_libhandy_LIBDIR} ${PC_libhandy_LIBRARY_DIRS})
|
||||
|
||||
find_path(libhandy_INCLUDE_DIR
|
||||
NAMES handy.h
|
||||
HINTS ${libhandy_INCLUDE_HINTS}
|
||||
/usr/include
|
||||
/usr/local/include
|
||||
/opt/local/include
|
||||
PATH_SUFFIXES ${libhandy_LIBRARY_NAME})
|
||||
|
||||
find_library(libhandy_LIBRARY
|
||||
NAMES ${libhandy_LIBRARY_NAME} handy-1
|
||||
HINTS ${libhandy_LIBRARY_HINTS}
|
||||
/usr/lib
|
||||
/usr/local/lib
|
||||
/opt/local/lib
|
||||
PATH_SUFFIXES ${libhandy_LIBRARY_NAME}
|
||||
${libhandy_LIBRARY_NAME}/include)
|
||||
|
||||
set(libhandy_LIBRARIES ${libhandy_LIBRARY})
|
||||
set(libhandy_INCLUDE_DIRS ${libhandy_INCLUDE_DIR};${libhandy_CONFIG_INCLUDE_DIRS})
|
||||
|
||||
include(FindPackageHandleStandardArgs)
|
||||
find_package_handle_standard_args(libhandy
|
||||
REQUIRED_VARS
|
||||
libhandy_LIBRARY
|
||||
libhandy_INCLUDE_DIR
|
||||
VERSION_VAR libhandy_VERSION)
|
||||
|
||||
mark_as_advanced(libhandy_INCLUDE_DIR libhandy_LIBRARY)
|
||||
@@ -1,15 +0,0 @@
|
||||
set(simpleini_LIBRARY_NAME simpleini)
|
||||
|
||||
find_path(simpleini_INCLUDE_DIR
|
||||
NAMES SimpleIni.h
|
||||
HINTS /usr/include
|
||||
/usr/local/include
|
||||
/opt/local/include
|
||||
PATH_SUFFIXES ${simpleini_LIBRARY_NAME})
|
||||
|
||||
include(FindPackageHandleStandardArgs)
|
||||
find_package_handle_standard_args(simpleini
|
||||
REQUIRED_VARS
|
||||
simpleini_INCLUDE_DIR)
|
||||
|
||||
mark_as_advanced(simpleini_INCLUDE_DIR)
|
||||
@@ -1,66 +0,0 @@
|
||||
#include "chatinput.hpp"
|
||||
|
||||
ChatInput::ChatInput() {
|
||||
get_style_context()->add_class("message-input");
|
||||
set_propagate_natural_height(true);
|
||||
set_min_content_height(20);
|
||||
set_max_content_height(250);
|
||||
set_policy(Gtk::POLICY_NEVER, Gtk::POLICY_AUTOMATIC);
|
||||
|
||||
// hack
|
||||
auto cb = [this](GdkEventKey *e) -> bool {
|
||||
return event(reinterpret_cast<GdkEvent *>(e));
|
||||
};
|
||||
m_textview.signal_key_press_event().connect(cb, false);
|
||||
m_textview.set_hexpand(false);
|
||||
m_textview.set_halign(Gtk::ALIGN_FILL);
|
||||
m_textview.set_valign(Gtk::ALIGN_CENTER);
|
||||
m_textview.set_wrap_mode(Gtk::WRAP_WORD_CHAR);
|
||||
m_textview.show();
|
||||
add(m_textview);
|
||||
}
|
||||
|
||||
void ChatInput::InsertText(const Glib::ustring &text) {
|
||||
GetBuffer()->insert_at_cursor(text);
|
||||
m_textview.grab_focus();
|
||||
}
|
||||
|
||||
Glib::RefPtr<Gtk::TextBuffer> ChatInput::GetBuffer() {
|
||||
return m_textview.get_buffer();
|
||||
}
|
||||
|
||||
// this isnt connected directly so that the chat window can handle stuff like the completer first
|
||||
bool ChatInput::ProcessKeyPress(GdkEventKey *event) {
|
||||
if (event->keyval == GDK_KEY_Escape) {
|
||||
m_signal_escape.emit();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (event->keyval == GDK_KEY_Return) {
|
||||
if (event->state & GDK_SHIFT_MASK)
|
||||
return false;
|
||||
|
||||
auto buf = GetBuffer();
|
||||
auto text = buf->get_text();
|
||||
|
||||
const bool accepted = m_signal_submit.emit(text);
|
||||
if (accepted)
|
||||
buf->set_text("");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void ChatInput::on_grab_focus() {
|
||||
m_textview.grab_focus();
|
||||
}
|
||||
|
||||
ChatInput::type_signal_submit ChatInput::signal_submit() {
|
||||
return m_signal_submit;
|
||||
}
|
||||
|
||||
ChatInput::type_signal_escape ChatInput::signal_escape() {
|
||||
return m_signal_escape;
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
#pragma once
|
||||
#include <gtkmm.h>
|
||||
|
||||
class ChatInput : public Gtk::ScrolledWindow {
|
||||
public:
|
||||
ChatInput();
|
||||
|
||||
void InsertText(const Glib::ustring &text);
|
||||
Glib::RefPtr<Gtk::TextBuffer> GetBuffer();
|
||||
bool ProcessKeyPress(GdkEventKey *event);
|
||||
|
||||
protected:
|
||||
void on_grab_focus() override;
|
||||
|
||||
private:
|
||||
Gtk::TextView m_textview;
|
||||
|
||||
public:
|
||||
typedef sigc::signal<bool, Glib::ustring> type_signal_submit;
|
||||
typedef sigc::signal<void> type_signal_escape;
|
||||
|
||||
type_signal_submit signal_submit();
|
||||
type_signal_escape signal_escape();
|
||||
|
||||
private:
|
||||
type_signal_submit m_signal_submit;
|
||||
type_signal_escape m_signal_escape;
|
||||
};
|
||||
@@ -1,4 +0,0 @@
|
||||
#include <cstdint>
|
||||
|
||||
constexpr static uint64_t SnowflakeSplitDifference = 600;
|
||||
constexpr static int MaxMessagesForChatCull = 50; // this has to be 50 (for now) cuz that magic number is used in a couple other places and i dont feel like replacing them
|
||||
@@ -1,97 +0,0 @@
|
||||
#include "joinguild.hpp"
|
||||
#include "../abaddon.hpp"
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <regex>
|
||||
|
||||
JoinGuildDialog::JoinGuildDialog(Gtk::Window &parent)
|
||||
: Gtk::Dialog("Join Server", parent, true)
|
||||
, m_layout(Gtk::ORIENTATION_VERTICAL)
|
||||
, m_ok("OK")
|
||||
, m_cancel("Cancel")
|
||||
, m_info("Enter code") {
|
||||
set_default_size(300, 50);
|
||||
get_style_context()->add_class("app-window");
|
||||
get_style_context()->add_class("app-popup");
|
||||
|
||||
Glib::signal_idle().connect(sigc::mem_fun(*this, &JoinGuildDialog::on_idle_slot));
|
||||
|
||||
m_entry.signal_changed().connect(sigc::mem_fun(*this, &JoinGuildDialog::on_entry_changed));
|
||||
|
||||
m_ok.set_sensitive(false);
|
||||
|
||||
m_ok.signal_clicked().connect([&]() {
|
||||
response(Gtk::RESPONSE_OK);
|
||||
});
|
||||
|
||||
m_cancel.signal_clicked().connect([&]() {
|
||||
response(Gtk::RESPONSE_CANCEL);
|
||||
});
|
||||
|
||||
m_entry.set_hexpand(true);
|
||||
m_layout.add(m_entry);
|
||||
m_lower.set_hexpand(true);
|
||||
m_lower.pack_start(m_info);
|
||||
m_info.set_halign(Gtk::ALIGN_START);
|
||||
m_lower.pack_start(m_ok, Gtk::PACK_SHRINK);
|
||||
m_lower.pack_start(m_cancel, Gtk::PACK_SHRINK);
|
||||
m_ok.set_halign(Gtk::ALIGN_END);
|
||||
m_cancel.set_halign(Gtk::ALIGN_END);
|
||||
m_layout.add(m_lower);
|
||||
get_content_area()->add(m_layout);
|
||||
|
||||
show_all_children();
|
||||
}
|
||||
|
||||
void JoinGuildDialog::on_entry_changed() {
|
||||
std::string s = m_entry.get_text();
|
||||
std::regex invite_regex(R"((https?:\/\/)?discord\.(gg(\/invite)?\/|com\/invite\/)([A-Za-z0-9\-]+))", std::regex_constants::ECMAScript);
|
||||
std::smatch match;
|
||||
bool full_url = std::regex_search(s, match, invite_regex);
|
||||
if (full_url || IsCode(s)) {
|
||||
m_code = full_url ? match[4].str() : s;
|
||||
m_needs_request = true;
|
||||
m_ok.set_sensitive(false);
|
||||
} else {
|
||||
m_ok.set_sensitive(false);
|
||||
}
|
||||
}
|
||||
|
||||
void JoinGuildDialog::CheckCode() {
|
||||
auto cb = [this](const std::optional<InviteData> &invite) {
|
||||
if (invite.has_value()) {
|
||||
m_ok.set_sensitive(true);
|
||||
if (invite->Guild.has_value()) {
|
||||
if (invite->MemberCount.has_value())
|
||||
m_info.set_text(invite->Guild->Name + " (" + std::to_string(*invite->MemberCount) + " members)");
|
||||
else
|
||||
m_info.set_text(invite->Guild->Name);
|
||||
} else {
|
||||
m_info.set_text("Group DM (" + std::to_string(*invite->MemberCount) + " members)");
|
||||
}
|
||||
} else {
|
||||
m_ok.set_sensitive(false);
|
||||
m_info.set_text("Invalid invite");
|
||||
}
|
||||
};
|
||||
Abaddon::Get().GetDiscordClient().FetchInvite(m_code, sigc::track_obj(cb, *this));
|
||||
}
|
||||
|
||||
bool JoinGuildDialog::IsCode(std::string str) {
|
||||
return str.length() >= 2 && std::all_of(str.begin(), str.end(), [](char c) -> bool { return std::isalnum(c) || c == '-'; });
|
||||
}
|
||||
|
||||
std::string JoinGuildDialog::GetCode() {
|
||||
return m_code;
|
||||
}
|
||||
|
||||
static const constexpr int RateLimitMS = 1500;
|
||||
bool JoinGuildDialog::on_idle_slot() {
|
||||
const auto now = std::chrono::steady_clock::now();
|
||||
if (m_needs_request && ((now - m_last_req_time) > std::chrono::milliseconds(RateLimitMS))) {
|
||||
m_needs_request = false;
|
||||
m_last_req_time = now;
|
||||
CheckCode();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
#pragma once
|
||||
#include <gtkmm.h>
|
||||
#include <string>
|
||||
#include <chrono>
|
||||
|
||||
class JoinGuildDialog : public Gtk::Dialog {
|
||||
public:
|
||||
JoinGuildDialog(Gtk::Window &parent);
|
||||
std::string GetCode();
|
||||
|
||||
protected:
|
||||
void on_entry_changed();
|
||||
bool IsCode(std::string str);
|
||||
|
||||
Gtk::Box m_layout;
|
||||
Gtk::Button m_ok;
|
||||
Gtk::Button m_cancel;
|
||||
Gtk::Box m_lower;
|
||||
Gtk::Label m_info;
|
||||
Gtk::Entry m_entry;
|
||||
|
||||
void CheckCode();
|
||||
|
||||
// needs a rate limit cuz if u hit it u get ip banned from /invites for a long time :(
|
||||
bool m_needs_request = false;
|
||||
std::chrono::time_point<std::chrono::steady_clock> m_last_req_time;
|
||||
bool on_idle_slot();
|
||||
|
||||
private:
|
||||
std::string m_code;
|
||||
};
|
||||
@@ -1,39 +0,0 @@
|
||||
#pragma once
|
||||
#include <functional>
|
||||
#include <future>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <queue>
|
||||
#include <glibmm.h>
|
||||
#include "../http.hpp"
|
||||
|
||||
class HTTPClient {
|
||||
public:
|
||||
HTTPClient();
|
||||
|
||||
void SetBase(const std::string &url);
|
||||
|
||||
void SetUserAgent(std::string agent);
|
||||
void SetAuth(std::string auth);
|
||||
void MakeDELETE(const std::string &path, std::function<void(http::response_type r)> cb);
|
||||
void MakeGET(const std::string &path, std::function<void(http::response_type r)> cb);
|
||||
void MakePATCH(const std::string &path, const std::string &payload, std::function<void(http::response_type r)> cb);
|
||||
void MakePOST(const std::string &path, const std::string &payload, std::function<void(http::response_type r)> cb);
|
||||
void MakePUT(const std::string &path, const std::string &payload, std::function<void(http::response_type r)> cb);
|
||||
|
||||
private:
|
||||
void OnResponse(const http::response_type &r, std::function<void(http::response_type r)> cb);
|
||||
void CleanupFutures();
|
||||
|
||||
mutable std::mutex m_mutex;
|
||||
Glib::Dispatcher m_dispatcher;
|
||||
std::queue<std::function<void()>> m_queue;
|
||||
void RunCallbacks();
|
||||
|
||||
std::vector<std::future<void>> m_futures;
|
||||
std::string m_api_base;
|
||||
std::string m_authorization;
|
||||
std::string m_agent;
|
||||
};
|
||||
1507
discord/store.cpp
@@ -1,172 +0,0 @@
|
||||
#pragma once
|
||||
#include "../util.hpp"
|
||||
#include "objects.hpp"
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <mutex>
|
||||
#include <filesystem>
|
||||
#include <sqlite3.h>
|
||||
|
||||
#ifdef GetMessage // fuck you windows.h
|
||||
#undef GetMessage
|
||||
#endif
|
||||
|
||||
class Store {
|
||||
public:
|
||||
Store(bool mem_store = false);
|
||||
~Store();
|
||||
|
||||
bool IsValid() const;
|
||||
|
||||
void SetUser(Snowflake id, const UserData &user);
|
||||
void SetChannel(Snowflake id, const ChannelData &chan);
|
||||
void SetGuild(Snowflake id, const GuildData &guild);
|
||||
void SetRole(Snowflake id, const RoleData &role);
|
||||
void SetMessage(Snowflake id, const Message &message);
|
||||
void SetGuildMember(Snowflake guild_id, Snowflake user_id, const GuildMember &data);
|
||||
void SetPermissionOverwrite(Snowflake channel_id, Snowflake id, const PermissionOverwrite &perm);
|
||||
void SetEmoji(Snowflake id, const EmojiData &emoji);
|
||||
void SetBan(Snowflake guild_id, Snowflake user_id, const BanData &ban);
|
||||
|
||||
// slap const on everything even tho its not *really* const
|
||||
|
||||
std::optional<ChannelData> GetChannel(Snowflake id) const;
|
||||
std::optional<EmojiData> GetEmoji(Snowflake id) const;
|
||||
std::optional<GuildData> GetGuild(Snowflake id) const;
|
||||
std::optional<GuildMember> GetGuildMember(Snowflake guild_id, Snowflake user_id) const;
|
||||
std::optional<Message> GetMessage(Snowflake id) const;
|
||||
std::optional<PermissionOverwrite> GetPermissionOverwrite(Snowflake channel_id, Snowflake id) const;
|
||||
std::optional<RoleData> GetRole(Snowflake id) const;
|
||||
std::optional<UserData> GetUser(Snowflake id) const;
|
||||
std::optional<BanData> GetBan(Snowflake guild_id, Snowflake user_id) const;
|
||||
std::vector<BanData> GetBans(Snowflake guild_id) const;
|
||||
|
||||
std::vector<Message> GetLastMessages(Snowflake id, size_t num) const;
|
||||
std::vector<Snowflake> GetChannelMessageIDs(Snowflake id) const;
|
||||
std::vector<Message> GetPinnedMessages(Snowflake channel_id) const;
|
||||
std::vector<ChannelData> GetActiveThreads(Snowflake channel_id) const; // public
|
||||
|
||||
void ClearGuild(Snowflake id);
|
||||
void ClearChannel(Snowflake id);
|
||||
void ClearBan(Snowflake guild_id, Snowflake user_id);
|
||||
|
||||
using users_type = std::unordered_map<Snowflake, UserData>;
|
||||
using channels_type = std::unordered_map<Snowflake, ChannelData>;
|
||||
using guilds_type = std::unordered_map<Snowflake, GuildData>;
|
||||
using roles_type = std::unordered_map<Snowflake, RoleData>;
|
||||
using messages_type = std::unordered_map<Snowflake, Message>;
|
||||
using members_type = std::unordered_map<Snowflake, std::unordered_map<Snowflake, GuildMember>>; // [guild][user]
|
||||
using permission_overwrites_type = std::unordered_map<Snowflake, std::unordered_map<Snowflake, PermissionOverwrite>>; // [channel][user/role]
|
||||
using emojis_type = std::unordered_map<Snowflake, EmojiData>;
|
||||
|
||||
const std::unordered_set<Snowflake> &GetChannels() const;
|
||||
const std::unordered_set<Snowflake> &GetGuilds() const;
|
||||
|
||||
void ClearAll();
|
||||
|
||||
void BeginTransaction();
|
||||
void EndTransaction();
|
||||
|
||||
private:
|
||||
Message GetMessageBound(sqlite3_stmt *stmt) const;
|
||||
|
||||
void SetMessageInteractionPair(Snowflake message_id, const MessageInteractionData &interaction);
|
||||
|
||||
std::unordered_set<Snowflake> m_channels;
|
||||
std::unordered_set<Snowflake> m_guilds;
|
||||
|
||||
bool CreateTables();
|
||||
bool CreateStatements();
|
||||
void Cleanup();
|
||||
|
||||
template<typename T>
|
||||
void Bind(sqlite3_stmt *stmt, int index, const std::optional<T> &opt) const;
|
||||
|
||||
template<typename T>
|
||||
typename std::enable_if<std::is_enum<T>::value, void>::type
|
||||
Bind(sqlite3_stmt *stmt, int index, T val) const;
|
||||
|
||||
void Bind(sqlite3_stmt *stmt, int index, int num) const;
|
||||
void Bind(sqlite3_stmt *stmt, int index, uint64_t num) const;
|
||||
void Bind(sqlite3_stmt *stmt, int index, const std::string &str) const;
|
||||
void Bind(sqlite3_stmt *stmt, int index, bool val) const;
|
||||
void Bind(sqlite3_stmt *stmt, int index, std::nullptr_t) const;
|
||||
bool RunInsert(sqlite3_stmt *stmt);
|
||||
bool FetchOne(sqlite3_stmt *stmt) const;
|
||||
|
||||
template<typename T>
|
||||
void Get(sqlite3_stmt *stmt, int index, std::optional<T> &out) const;
|
||||
|
||||
template<typename T>
|
||||
typename std::enable_if<std::is_enum<T>::value, void>::type
|
||||
Get(sqlite3_stmt *stmt, int index, T &out) const;
|
||||
|
||||
void Get(sqlite3_stmt *stmt, int index, int &out) const;
|
||||
void Get(sqlite3_stmt *stmt, int index, uint64_t &out) const;
|
||||
void Get(sqlite3_stmt *stmt, int index, std::string &out) const;
|
||||
void Get(sqlite3_stmt *stmt, int index, bool &out) const;
|
||||
void Get(sqlite3_stmt *stmt, int index, Snowflake &out) const;
|
||||
bool IsNull(sqlite3_stmt *stmt, int index) const;
|
||||
void Reset(sqlite3_stmt *stmt) const;
|
||||
|
||||
std::filesystem::path m_db_path;
|
||||
mutable sqlite3 *m_db;
|
||||
mutable int m_db_err;
|
||||
mutable sqlite3_stmt *m_set_user_stmt;
|
||||
mutable sqlite3_stmt *m_get_user_stmt;
|
||||
mutable sqlite3_stmt *m_set_perm_stmt;
|
||||
mutable sqlite3_stmt *m_get_perm_stmt;
|
||||
mutable sqlite3_stmt *m_set_msg_stmt;
|
||||
mutable sqlite3_stmt *m_get_msg_stmt;
|
||||
mutable sqlite3_stmt *m_set_role_stmt;
|
||||
mutable sqlite3_stmt *m_get_role_stmt;
|
||||
mutable sqlite3_stmt *m_set_emote_stmt;
|
||||
mutable sqlite3_stmt *m_get_emote_stmt;
|
||||
mutable sqlite3_stmt *m_set_member_stmt;
|
||||
mutable sqlite3_stmt *m_get_member_stmt;
|
||||
mutable sqlite3_stmt *m_set_guild_stmt;
|
||||
mutable sqlite3_stmt *m_get_guild_stmt;
|
||||
mutable sqlite3_stmt *m_set_chan_stmt;
|
||||
mutable sqlite3_stmt *m_get_chan_stmt;
|
||||
mutable sqlite3_stmt *m_set_ban_stmt;
|
||||
mutable sqlite3_stmt *m_get_ban_stmt;
|
||||
mutable sqlite3_stmt *m_clear_ban_stmt;
|
||||
mutable sqlite3_stmt *m_get_bans_stmt;
|
||||
mutable sqlite3_stmt *m_set_msg_interaction_stmt;
|
||||
mutable sqlite3_stmt *m_get_last_msgs_stmt;
|
||||
mutable sqlite3_stmt *m_get_msg_ids_stmt;
|
||||
mutable sqlite3_stmt *m_get_pins_stmt;
|
||||
mutable sqlite3_stmt *m_get_threads_stmt;
|
||||
mutable sqlite3_stmt *m_clear_chan_stmt;
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
inline void Store::Bind(sqlite3_stmt *stmt, int index, const std::optional<T> &opt) const {
|
||||
if (opt.has_value())
|
||||
Bind(stmt, index, *opt);
|
||||
else
|
||||
sqlite3_bind_null(stmt, index);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
inline typename std::enable_if<std::is_enum<T>::value, void>::type
|
||||
Store::Bind(sqlite3_stmt *stmt, int index, T val) const {
|
||||
Bind(stmt, index, static_cast<typename std::underlying_type<T>::type>(val));
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
inline void Store::Get(sqlite3_stmt *stmt, int index, std::optional<T> &out) const {
|
||||
if (sqlite3_column_type(stmt, index) == SQLITE_NULL)
|
||||
out = std::nullopt;
|
||||
else {
|
||||
T v;
|
||||
Get(stmt, index, v);
|
||||
out = std::optional<T>(v);
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
inline typename std::enable_if<std::is_enum<T>::value, void>::type
|
||||
Store::Get(sqlite3_stmt *stmt, int index, T &out) const {
|
||||
out = static_cast<T>(sqlite3_column_int(stmt, index));
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
#include "usersettings.hpp"
|
||||
|
||||
void from_json(const nlohmann::json &j, UserSettingsGuildFoldersEntry &m) {
|
||||
JS_N("color", m.Color);
|
||||
JS_D("guild_ids", m.GuildIDs);
|
||||
JS_N("id", m.ID);
|
||||
JS_N("name", m.Name);
|
||||
}
|
||||
|
||||
void from_json(const nlohmann::json &j, UserSettings &m) {
|
||||
JS_D("timezone_offset", m.TimezoneOffset);
|
||||
JS_D("theme", m.Theme);
|
||||
JS_D("stream_notifications_enabled", m.AreStreamNotificationsEnabled);
|
||||
JS_D("status", m.Status);
|
||||
JS_D("show_current_game", m.ShouldShowCurrentGame);
|
||||
// JS_D("restricted_guilds", m.RestrictedGuilds);
|
||||
JS_D("render_reactions", m.ShouldRenderReactions);
|
||||
JS_D("render_embeds", m.ShouldRenderEmbeds);
|
||||
JS_D("native_phone_integration_enabled", m.IsNativePhoneIntegrationEnabled);
|
||||
JS_D("message_display_compact", m.ShouldMessageDisplayCompact);
|
||||
JS_D("locale", m.Locale);
|
||||
JS_D("inline_embed_media", m.ShouldInlineEmbedMedia);
|
||||
JS_D("inline_attachment_media", m.ShouldInlineAttachmentMedia);
|
||||
JS_D("guild_positions", m.GuildPositions);
|
||||
JS_D("guild_folders", m.GuildFolders);
|
||||
JS_D("gif_auto_play", m.ShouldGIFAutoplay);
|
||||
// JS_D("friend_source_flags", m.FriendSourceFlags);
|
||||
JS_D("explicit_content_filter", m.ExplicitContentFilter);
|
||||
JS_D("enable_tts_command", m.IsTTSCommandEnabled);
|
||||
JS_D("disable_games_tab", m.ShouldDisableGamesTab);
|
||||
JS_D("developer_mode", m.DeveloperMode);
|
||||
JS_D("detect_platform_accounts", m.ShouldDetectPlatformAccounts);
|
||||
JS_D("default_guilds_restricted", m.AreDefaultGuildsRestricted);
|
||||
// JS_N("custom_status", m.CustomStatus);
|
||||
JS_D("convert_emoticons", m.ShouldConvertEmoticons);
|
||||
JS_D("contact_sync_enabled", m.IsContactSyncEnabled);
|
||||
JS_D("animate_emoji", m.ShouldAnimateEmojis);
|
||||
JS_D("allow_accessibility_detection", m.IsAccessibilityDetectionAllowed);
|
||||
JS_D("afk_timeout", m.AFKTimeout);
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
#pragma once
|
||||
#include "json.hpp"
|
||||
#include "snowflake.hpp"
|
||||
#include <string>
|
||||
|
||||
struct UserSettingsGuildFoldersEntry {
|
||||
int Color = -1; // null
|
||||
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
|
||||
|
||||
friend void from_json(const nlohmann::json &j, UserSettingsGuildFoldersEntry &m);
|
||||
};
|
||||
|
||||
struct UserSettings {
|
||||
int TimezoneOffset; //
|
||||
std::string Theme; //
|
||||
bool AreStreamNotificationsEnabled; //
|
||||
std::string Status; //
|
||||
bool ShouldShowCurrentGame; //
|
||||
// std::vector<Unknown> RestrictedGuilds; //
|
||||
bool ShouldRenderReactions; //
|
||||
bool ShouldRenderEmbeds; //
|
||||
bool IsNativePhoneIntegrationEnabled; //
|
||||
bool ShouldMessageDisplayCompact; //
|
||||
std::string Locale; //
|
||||
bool ShouldInlineEmbedMedia; //
|
||||
bool ShouldInlineAttachmentMedia; //
|
||||
std::vector<Snowflake> GuildPositions; // deprecated?
|
||||
std::vector<UserSettingsGuildFoldersEntry> GuildFolders; //
|
||||
bool ShouldGIFAutoplay; //
|
||||
// Unknown FriendSourceFlags; //
|
||||
int ExplicitContentFilter; //
|
||||
bool IsTTSCommandEnabled; //
|
||||
bool ShouldDisableGamesTab; //
|
||||
bool DeveloperMode; //
|
||||
bool ShouldDetectPlatformAccounts; //
|
||||
bool AreDefaultGuildsRestricted; //
|
||||
// Unknown CustomStatus; // null
|
||||
bool ShouldConvertEmoticons; //
|
||||
bool IsContactSyncEnabled; //
|
||||
bool ShouldAnimateEmojis; //
|
||||
bool IsAccessibilityDetectionAllowed; //
|
||||
int AFKTimeout;
|
||||
|
||||
friend void from_json(const nlohmann::json &j, UserSettings &m);
|
||||
};
|
||||
@@ -101,17 +101,38 @@
|
||||
.message-input, .message-input textview, .message-input textview text {
|
||||
background-color: #242424;
|
||||
color: #adadad;
|
||||
border-radius: 15px;
|
||||
border-radius: 3px;
|
||||
border: 1px solid transparent;
|
||||
}
|
||||
|
||||
.message-input {
|
||||
border: 1px solid #444444;
|
||||
margin-right: 15px;
|
||||
}
|
||||
|
||||
.message-input.replying {
|
||||
border: 1px solid #026FB9;
|
||||
}
|
||||
|
||||
.message-input {
|
||||
.message-input.bad-input {
|
||||
border: 1px solid #dd3300;
|
||||
}
|
||||
|
||||
.message-input-browse-icon {
|
||||
color: #b9bbbe;
|
||||
margin-left: 5px;
|
||||
margin-top: 11px;
|
||||
}
|
||||
|
||||
/* i dont think theres a way to circumvent having to do this to adjust around the browse icon */
|
||||
.message-input:not(.with-browser-icon) {
|
||||
padding: 0px 0px 0px 5px;
|
||||
}
|
||||
|
||||
.message-input.with-browse-icon {
|
||||
padding: 0px 0px 0px 30px;
|
||||
}
|
||||
|
||||
.members {
|
||||
background-color: @background_color;
|
||||
}
|
||||
@@ -282,3 +303,60 @@
|
||||
.friends-list-row-bot {
|
||||
color: #ff0000;
|
||||
}
|
||||
|
||||
.channel-tab-switcher .box {
|
||||
margin: -7px -1px -7px -1px;
|
||||
background: #2a2a2a;
|
||||
border: 1px solid black;
|
||||
}
|
||||
|
||||
.channel-tab-switcher tab:hover {
|
||||
box-shadow: inset 0 -6px #17633e;
|
||||
}
|
||||
|
||||
.channel-tab-switcher tab:checked {
|
||||
box-shadow: inset 0 -6px #2feb90;
|
||||
}
|
||||
|
||||
.channel-tab-switcher tab {
|
||||
background: #1A1A1A;
|
||||
border: 1px solid #808080;
|
||||
min-height: 35px;
|
||||
}
|
||||
|
||||
.channel-tab-switcher tab.needs-attention:not(:checked) {
|
||||
font-weight: bold;
|
||||
animation: 150ms ease-in;
|
||||
/* background-image: radial-gradient(ellipse at bottom, #FF5370, #1A1A1A 30%); */
|
||||
box-shadow: inset 0 -6px red;
|
||||
}
|
||||
|
||||
.channel-tab-switcher tab > button {
|
||||
border: none;
|
||||
padding: 0;
|
||||
margin: 5px;
|
||||
min-width: 16px;
|
||||
min-height: 16px;
|
||||
color: #FF5370;
|
||||
background-color: rgba(0.21, 0.21, 0.21, 0.5);
|
||||
}
|
||||
|
||||
.channel-tab-switcher tab > button:hover {
|
||||
background-color: alpha(#ff0000, 0.5);
|
||||
}
|
||||
|
||||
.message-progress {
|
||||
border: none;
|
||||
margin-bottom: -8px;
|
||||
}
|
||||
|
||||
.message-progress trough {
|
||||
border: none;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.message-progress progress {
|
||||
border: none;
|
||||
background-color: #dd3300;
|
||||
margin-left: 1px;
|
||||
}
|
||||
|
Before Width: | Height: | Size: 7.9 KiB After Width: | Height: | Size: 7.9 KiB |
|
Before Width: | Height: | Size: 7.0 KiB After Width: | Height: | Size: 7.0 KiB |
|
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 48 KiB |
|
Before Width: | Height: | Size: 3.4 KiB After Width: | Height: | Size: 3.4 KiB |
|
Before Width: | Height: | Size: 6.5 KiB After Width: | Height: | Size: 6.5 KiB |
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 6.6 KiB After Width: | Height: | Size: 6.6 KiB |
|
Before Width: | Height: | Size: 6.9 KiB After Width: | Height: | Size: 6.9 KiB |
|
Before Width: | Height: | Size: 8.2 KiB After Width: | Height: | Size: 8.2 KiB |
|
Before Width: | Height: | Size: 8.8 KiB After Width: | Height: | Size: 8.8 KiB |
|
Before Width: | Height: | Size: 7.0 KiB After Width: | Height: | Size: 7.0 KiB |
|
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 2.3 KiB |
|
Before Width: | Height: | Size: 6.5 KiB After Width: | Height: | Size: 6.5 KiB |
|
Before Width: | Height: | Size: 4.3 KiB After Width: | Height: | Size: 4.3 KiB |
|
Before Width: | Height: | Size: 7.1 KiB After Width: | Height: | Size: 7.1 KiB |
|
Before Width: | Height: | Size: 7.3 KiB After Width: | Height: | Size: 7.3 KiB |
|
Before Width: | Height: | Size: 7.3 KiB After Width: | Height: | Size: 7.3 KiB |
|
Before Width: | Height: | Size: 6.6 KiB After Width: | Height: | Size: 6.6 KiB |
|
Before Width: | Height: | Size: 3.4 KiB After Width: | Height: | Size: 3.4 KiB |
|
Before Width: | Height: | Size: 5.2 KiB After Width: | Height: | Size: 5.2 KiB |
|
Before Width: | Height: | Size: 7.0 KiB After Width: | Height: | Size: 7.0 KiB |
|
Before Width: | Height: | Size: 6.3 KiB After Width: | Height: | Size: 6.3 KiB |
|
Before Width: | Height: | Size: 6.6 KiB After Width: | Height: | Size: 6.6 KiB |
|
Before Width: | Height: | Size: 4.8 KiB After Width: | Height: | Size: 4.8 KiB |
|
Before Width: | Height: | Size: 6.9 KiB After Width: | Height: | Size: 6.9 KiB |
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 3.6 KiB After Width: | Height: | Size: 3.6 KiB |
|
Before Width: | Height: | Size: 4.1 KiB After Width: | Height: | Size: 4.1 KiB |
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 3.3 KiB After Width: | Height: | Size: 3.3 KiB |
111
settings.cpp
@@ -1,111 +0,0 @@
|
||||
#include "settings.hpp"
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
|
||||
SettingsManager::SettingsManager(std::string filename)
|
||||
: m_filename(filename) {
|
||||
if (!std::filesystem::exists(filename)) {
|
||||
std::fstream fs;
|
||||
fs.open(filename, std::ios::out);
|
||||
fs.close();
|
||||
}
|
||||
|
||||
auto rc = m_ini.LoadFile(filename.c_str());
|
||||
m_ok = rc == SI_OK;
|
||||
}
|
||||
|
||||
void SettingsManager::Reload() {
|
||||
m_ok = m_ini.LoadFile(m_filename.c_str()) == SI_OK;
|
||||
}
|
||||
|
||||
std::string SettingsManager::GetSettingString(const std::string §ion, const std::string &key, std::string fallback) const {
|
||||
return m_ini.GetValue(section.c_str(), key.c_str(), fallback.c_str());
|
||||
}
|
||||
|
||||
int SettingsManager::GetSettingInt(const std::string §ion, const std::string &key, int fallback) const {
|
||||
return std::stoul(GetSettingString(section, key, std::to_string(fallback)));
|
||||
}
|
||||
|
||||
bool SettingsManager::GetSettingBool(const std::string §ion, const std::string &key, bool fallback) const {
|
||||
return GetSettingString(section, key, fallback ? "true" : "false") != "false";
|
||||
}
|
||||
|
||||
bool SettingsManager::IsValid() const {
|
||||
return m_ok;
|
||||
}
|
||||
|
||||
void SettingsManager::Close() {
|
||||
m_ini.SaveFile(m_filename.c_str());
|
||||
}
|
||||
|
||||
bool SettingsManager::GetUseMemoryDB() const {
|
||||
return GetSettingBool("discord", "memory_db", false);
|
||||
}
|
||||
|
||||
std::string SettingsManager::GetUserAgent() const {
|
||||
return GetSettingString("http", "user_agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.87 Safari/537.36");
|
||||
}
|
||||
|
||||
std::string SettingsManager::GetDiscordToken() const {
|
||||
return GetSettingString("discord", "token");
|
||||
}
|
||||
|
||||
bool SettingsManager::GetShowMemberListDiscriminators() const {
|
||||
return GetSettingBool("gui", "member_list_discriminator", true);
|
||||
}
|
||||
|
||||
bool SettingsManager::GetShowStockEmojis() const {
|
||||
#ifdef _WIN32
|
||||
return GetSettingBool("gui", "stock_emojis", false);
|
||||
#else
|
||||
return GetSettingBool("gui", "stock_emojis", true);
|
||||
#endif
|
||||
}
|
||||
|
||||
bool SettingsManager::GetShowCustomEmojis() const {
|
||||
return GetSettingBool("gui", "custom_emojis", true);
|
||||
}
|
||||
|
||||
std::string SettingsManager::GetLinkColor() const {
|
||||
return GetSettingString("style", "linkcolor", "rgba(40, 200, 180, 255)");
|
||||
}
|
||||
|
||||
std::string SettingsManager::GetChannelsExpanderColor() const {
|
||||
return GetSettingString("style", "expandercolor", "rgba(255, 83, 112, 255)");
|
||||
}
|
||||
|
||||
std::string SettingsManager::GetNSFWChannelColor() const {
|
||||
return GetSettingString("style", "nsfwchannelcolor", "#ed6666");
|
||||
}
|
||||
|
||||
int SettingsManager::GetCacheHTTPConcurrency() const {
|
||||
return GetSettingInt("http", "concurrent", 20);
|
||||
}
|
||||
|
||||
bool SettingsManager::GetPrefetch() const {
|
||||
return GetSettingBool("discord", "prefetch", false);
|
||||
}
|
||||
|
||||
std::string SettingsManager::GetMainCSS() const {
|
||||
return GetSettingString("gui", "css", "main.css");
|
||||
}
|
||||
|
||||
bool SettingsManager::GetShowAnimations() const {
|
||||
return GetSettingBool("gui", "animations", true);
|
||||
}
|
||||
|
||||
bool SettingsManager::GetShowOwnerCrown() const {
|
||||
return GetSettingBool("gui", "owner_crown", true);
|
||||
}
|
||||
|
||||
std::string SettingsManager::GetGatewayURL() const {
|
||||
return GetSettingString("discord", "gateway", "wss://gateway.discord.gg/?v=9&encoding=json&compress=zlib-stream");
|
||||
}
|
||||
|
||||
std::string SettingsManager::GetAPIBaseURL() const {
|
||||
return GetSettingString("discord", "api_base", "https://discord.com/api/v9");
|
||||
}
|
||||
|
||||
bool SettingsManager::GetAnimatedGuildHoverOnly() const {
|
||||
return GetSettingBool("gui", "animated_guild_hover_only", true);
|
||||
}
|
||||
61
settings.hpp
@@ -1,61 +0,0 @@
|
||||
#pragma once
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
#include <SimpleIni.h>
|
||||
|
||||
class SettingsManager {
|
||||
public:
|
||||
SettingsManager(std::string filename);
|
||||
void Reload();
|
||||
|
||||
void Close();
|
||||
bool GetUseMemoryDB() const;
|
||||
std::string GetUserAgent() const;
|
||||
std::string GetDiscordToken() const;
|
||||
bool GetShowMemberListDiscriminators() const;
|
||||
bool GetShowStockEmojis() const;
|
||||
bool GetShowCustomEmojis() const;
|
||||
int GetCacheHTTPConcurrency() const;
|
||||
bool GetPrefetch() const;
|
||||
std::string GetMainCSS() const;
|
||||
bool GetShowAnimations() const;
|
||||
bool GetShowOwnerCrown() const;
|
||||
std::string GetGatewayURL() const;
|
||||
std::string GetAPIBaseURL() const;
|
||||
bool GetAnimatedGuildHoverOnly() const;
|
||||
|
||||
// i would like to use Gtk::StyleProperty for this, but it will not work on windows
|
||||
// #1 it's missing from the project files for the version used by vcpkg
|
||||
// #2 it's still broken and doesn't function even when added to the solution
|
||||
// #3 it's a massive pain in the ass to try and bump the version to a functioning version
|
||||
// because they switch build systems to nmake/meson (took months to get merged in vcpkg)
|
||||
// #4 c++ build systems sucks
|
||||
// three options are: use gtk4 with updated vcpkg, try and port it myself, or use msys2 instead of vcpkg
|
||||
// im leaning towards msys
|
||||
std::string GetLinkColor() const;
|
||||
std::string GetChannelsExpanderColor() const;
|
||||
std::string GetNSFWChannelColor() const;
|
||||
|
||||
bool IsValid() const;
|
||||
|
||||
template<typename T>
|
||||
void SetSetting(std::string section, std::string key, T value) {
|
||||
m_ini.SetValue(section.c_str(), key.c_str(), std::to_string(value).c_str());
|
||||
m_ini.SaveFile(m_filename.c_str());
|
||||
}
|
||||
|
||||
void SetSetting(std::string section, std::string key, std::string value) {
|
||||
m_ini.SetValue(section.c_str(), key.c_str(), value.c_str());
|
||||
m_ini.SaveFile(m_filename.c_str());
|
||||
}
|
||||
|
||||
private:
|
||||
std::string GetSettingString(const std::string §ion, const std::string &key, std::string fallback = "") const;
|
||||
int GetSettingInt(const std::string §ion, const std::string &key, int fallback) const;
|
||||
bool GetSettingBool(const std::string §ion, const std::string &key, bool fallback) const;
|
||||
|
||||
private:
|
||||
bool m_ok;
|
||||
std::string m_filename;
|
||||
CSimpleIniA m_ini;
|
||||
};
|
||||
@@ -6,16 +6,21 @@
|
||||
#include "discord/discord.hpp"
|
||||
#include "dialogs/token.hpp"
|
||||
#include "dialogs/editmessage.hpp"
|
||||
#include "dialogs/joinguild.hpp"
|
||||
#include "dialogs/confirm.hpp"
|
||||
#include "dialogs/setstatus.hpp"
|
||||
#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"
|
||||
#include "windows/threadswindow.hpp"
|
||||
#include "startup.hpp"
|
||||
|
||||
#ifdef WITH_LIBHANDY
|
||||
#include <handy.h>
|
||||
#endif
|
||||
|
||||
#ifdef _WIN32
|
||||
#pragma comment(lib, "crypt32.lib")
|
||||
@@ -23,12 +28,12 @@
|
||||
|
||||
Abaddon::Abaddon()
|
||||
: m_settings(Platform::FindConfigFile())
|
||||
, m_emojis(GetResPath("/emojis.bin"))
|
||||
, m_discord(m_settings.GetUseMemoryDB()) { // stupid but easy
|
||||
, m_discord(GetSettings().UseMemoryDB) // stupid but easy
|
||||
, m_emojis(GetResPath("/emojis.bin")) {
|
||||
LoadFromSettings();
|
||||
|
||||
// todo: set user agent for non-client(?)
|
||||
std::string ua = m_settings.GetUserAgent();
|
||||
std::string ua = GetSettings().UserAgent;
|
||||
m_discord.SetUserAgent(ua);
|
||||
|
||||
m_discord.signal_gateway_ready().connect(sigc::mem_fun(*this, &Abaddon::DiscordOnReady));
|
||||
@@ -43,7 +48,11 @@ Abaddon::Abaddon()
|
||||
m_discord.signal_thread_update().connect(sigc::mem_fun(*this, &Abaddon::DiscordOnThreadUpdate));
|
||||
m_discord.signal_message_sent().connect(sigc::mem_fun(*this, &Abaddon::DiscordOnMessageSent));
|
||||
m_discord.signal_disconnected().connect(sigc::mem_fun(*this, &Abaddon::DiscordOnDisconnect));
|
||||
if (m_settings.GetPrefetch())
|
||||
m_discord.signal_channel_accessibility_changed().connect([this](Snowflake id, bool accessible) {
|
||||
if (!accessible)
|
||||
m_channels_requested.erase(id);
|
||||
});
|
||||
if (GetSettings().Prefetch)
|
||||
m_discord.signal_message_create().connect([this](const Message &message) {
|
||||
if (message.Author.HasAvatar())
|
||||
m_img_mgr.Prefetch(message.Author.GetAvatarURL());
|
||||
@@ -54,71 +63,154 @@ Abaddon::Abaddon()
|
||||
});
|
||||
}
|
||||
|
||||
Abaddon::~Abaddon() {
|
||||
m_settings.Close();
|
||||
m_discord.Stop();
|
||||
}
|
||||
|
||||
Abaddon &Abaddon::Get() {
|
||||
static Abaddon instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
constexpr static guint BUTTON_BACK = 4;
|
||||
constexpr static guint BUTTON_FORWARD = 5;
|
||||
#else
|
||||
constexpr static guint BUTTON_BACK = 8;
|
||||
constexpr static guint BUTTON_FORWARD = 9;
|
||||
#endif
|
||||
|
||||
static bool HandleButtonEvents(GdkEvent *event, MainWindow *main_window) {
|
||||
if (event->type != GDK_BUTTON_PRESS) return false;
|
||||
|
||||
auto *widget = gtk_get_event_widget(event);
|
||||
if (widget == nullptr) return false;
|
||||
auto *window = gtk_widget_get_toplevel(widget);
|
||||
if (static_cast<void *>(window) != static_cast<void *>(main_window->gobj())) return false; // is this the right way???
|
||||
|
||||
#ifdef WITH_LIBHANDY
|
||||
switch (event->button.button) {
|
||||
case BUTTON_BACK:
|
||||
main_window->GoBack();
|
||||
break;
|
||||
case BUTTON_FORWARD:
|
||||
main_window->GoForward();
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool HandleKeyEvents(GdkEvent *event, MainWindow *main_window) {
|
||||
if (event->type != GDK_KEY_PRESS) return false;
|
||||
|
||||
auto *widget = gtk_get_event_widget(event);
|
||||
if (widget == nullptr) return false;
|
||||
auto *window = gtk_widget_get_toplevel(widget);
|
||||
if (static_cast<void *>(window) != static_cast<void *>(main_window->gobj())) return false;
|
||||
|
||||
const bool ctrl = (event->key.state & GDK_CONTROL_MASK) == GDK_CONTROL_MASK;
|
||||
const bool shft = (event->key.state & GDK_SHIFT_MASK) == GDK_SHIFT_MASK;
|
||||
|
||||
constexpr static guint EXCLUDE_STATES = GDK_CONTROL_MASK | GDK_SHIFT_MASK;
|
||||
|
||||
if (!(event->key.state & EXCLUDE_STATES) && event->key.keyval == GDK_KEY_Alt_L) {
|
||||
if (Abaddon::Get().GetSettings().AltMenu) {
|
||||
main_window->ToggleMenuVisibility();
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef WITH_LIBHANDY
|
||||
if (ctrl) {
|
||||
switch (event->key.keyval) {
|
||||
case GDK_KEY_Tab:
|
||||
case GDK_KEY_KP_Tab:
|
||||
case GDK_KEY_ISO_Left_Tab:
|
||||
if (shft)
|
||||
main_window->GoToPreviousTab();
|
||||
else
|
||||
main_window->GoToNextTab();
|
||||
return true;
|
||||
case GDK_KEY_1:
|
||||
case GDK_KEY_2:
|
||||
case GDK_KEY_3:
|
||||
case GDK_KEY_4:
|
||||
case GDK_KEY_5:
|
||||
case GDK_KEY_6:
|
||||
case GDK_KEY_7:
|
||||
case GDK_KEY_8:
|
||||
case GDK_KEY_9:
|
||||
main_window->GoToTab(event->key.keyval - GDK_KEY_1);
|
||||
return true;
|
||||
case GDK_KEY_0:
|
||||
main_window->GoToTab(9);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static void MainEventHandler(GdkEvent *event, void *main_window) {
|
||||
if (HandleButtonEvents(event, static_cast<MainWindow *>(main_window))) return;
|
||||
if (HandleKeyEvents(event, static_cast<MainWindow *>(main_window))) return;
|
||||
gtk_main_do_event(event);
|
||||
}
|
||||
|
||||
int Abaddon::StartGTK() {
|
||||
m_gtk_app = Gtk::Application::create("com.github.uowuo.abaddon");
|
||||
|
||||
#ifdef WITH_LIBHANDY
|
||||
m_gtk_app->signal_activate().connect([] {
|
||||
hdy_init();
|
||||
});
|
||||
#endif
|
||||
|
||||
m_css_provider = Gtk::CssProvider::create();
|
||||
m_css_provider->signal_parsing_error().connect([this](const Glib::RefPtr<const Gtk::CssSection> §ion, const Glib::Error &error) {
|
||||
Gtk::MessageDialog dlg(*m_main_window, "css failed parsing (" + error.what() + ")", false, Gtk::MESSAGE_ERROR, Gtk::BUTTONS_OK, true);
|
||||
m_css_provider->signal_parsing_error().connect([](const Glib::RefPtr<const Gtk::CssSection> §ion, const Glib::Error &error) {
|
||||
Gtk::MessageDialog dlg("css failed parsing (" + error.what() + ")", false, Gtk::MESSAGE_ERROR, Gtk::BUTTONS_OK, true);
|
||||
dlg.set_position(Gtk::WIN_POS_CENTER);
|
||||
dlg.run();
|
||||
});
|
||||
|
||||
m_css_low_provider = Gtk::CssProvider::create();
|
||||
m_css_low_provider->signal_parsing_error().connect([this](const Glib::RefPtr<const Gtk::CssSection> §ion, const Glib::Error &error) {
|
||||
Gtk::MessageDialog dlg(*m_main_window, "low-priority css failed parsing (" + error.what() + ")", false, Gtk::MESSAGE_ERROR, Gtk::BUTTONS_OK, true);
|
||||
m_css_low_provider->signal_parsing_error().connect([](const Glib::RefPtr<const Gtk::CssSection> §ion, const Glib::Error &error) {
|
||||
Gtk::MessageDialog dlg("low-priority css failed parsing (" + error.what() + ")", false, Gtk::MESSAGE_ERROR, Gtk::BUTTONS_OK, true);
|
||||
dlg.set_position(Gtk::WIN_POS_CENTER);
|
||||
dlg.run();
|
||||
});
|
||||
|
||||
#ifdef _WIN32
|
||||
bool png_found = false;
|
||||
bool gif_found = false;
|
||||
for (const auto &fmt : Gdk::Pixbuf::get_formats()) {
|
||||
if (fmt.get_name() == "png")
|
||||
png_found = true;
|
||||
else if (fmt.get_name() == "gif")
|
||||
gif_found = true;
|
||||
}
|
||||
|
||||
if (!png_found) {
|
||||
Gtk::MessageDialog dlg("The PNG pixbufloader wasn't detected. Abaddon may not work as a result.", false, Gtk::MESSAGE_ERROR, Gtk::BUTTONS_OK, true);
|
||||
dlg.set_position(Gtk::WIN_POS_CENTER);
|
||||
dlg.run();
|
||||
}
|
||||
|
||||
if (!gif_found) {
|
||||
Gtk::MessageDialog dlg("The GIF pixbufloader wasn't detected. Animations may not display as a result.", false, Gtk::MESSAGE_ERROR, Gtk::BUTTONS_OK, true);
|
||||
dlg.set_position(Gtk::WIN_POS_CENTER);
|
||||
dlg.run();
|
||||
}
|
||||
#endif
|
||||
|
||||
m_main_window = std::make_unique<MainWindow>();
|
||||
m_main_window->set_title(APP_TITLE);
|
||||
m_main_window->UpdateComponents();
|
||||
m_main_window->set_position(Gtk::WIN_POS_CENTER);
|
||||
|
||||
// crashes for some stupid reason if i put it somewhere else
|
||||
SetupUserMenu();
|
||||
|
||||
m_main_window->signal_action_connect().connect(sigc::mem_fun(*this, &Abaddon::ActionConnect));
|
||||
m_main_window->signal_action_disconnect().connect(sigc::mem_fun(*this, &Abaddon::ActionDisconnect));
|
||||
m_main_window->signal_action_set_token().connect(sigc::mem_fun(*this, &Abaddon::ActionSetToken));
|
||||
m_main_window->signal_action_reload_css().connect(sigc::mem_fun(*this, &Abaddon::ActionReloadCSS));
|
||||
m_main_window->signal_action_join_guild().connect(sigc::mem_fun(*this, &Abaddon::ActionJoinGuildDialog));
|
||||
m_main_window->signal_action_set_status().connect(sigc::mem_fun(*this, &Abaddon::ActionSetStatus));
|
||||
m_main_window->signal_action_add_recipient().connect(sigc::mem_fun(*this, &Abaddon::ActionAddRecipient));
|
||||
m_main_window->signal_action_view_pins().connect(sigc::mem_fun(*this, &Abaddon::ActionViewPins));
|
||||
m_main_window->signal_action_view_threads().connect(sigc::mem_fun(*this, &Abaddon::ActionViewThreads));
|
||||
|
||||
m_main_window->GetChannelList()->signal_action_channel_item_select().connect(sigc::mem_fun(*this, &Abaddon::ActionChannelOpened));
|
||||
m_main_window->GetChannelList()->signal_action_guild_leave().connect(sigc::mem_fun(*this, &Abaddon::ActionLeaveGuild));
|
||||
m_main_window->GetChannelList()->signal_action_guild_settings().connect(sigc::mem_fun(*this, &Abaddon::ActionGuildSettings));
|
||||
|
||||
m_main_window->GetChatWindow()->signal_action_message_edit().connect(sigc::mem_fun(*this, &Abaddon::ActionChatEditMessage));
|
||||
m_main_window->GetChatWindow()->signal_action_chat_submit().connect(sigc::mem_fun(*this, &Abaddon::ActionChatInputSubmit));
|
||||
m_main_window->GetChatWindow()->signal_action_chat_load_history().connect(sigc::mem_fun(*this, &Abaddon::ActionChatLoadHistory));
|
||||
m_main_window->GetChatWindow()->signal_action_channel_click().connect(sigc::mem_fun(*this, &Abaddon::ActionChannelOpened));
|
||||
m_main_window->GetChatWindow()->signal_action_insert_mention().connect(sigc::mem_fun(*this, &Abaddon::ActionInsertMention));
|
||||
m_main_window->GetChatWindow()->signal_action_reaction_add().connect(sigc::mem_fun(*this, &Abaddon::ActionReactionAdd));
|
||||
m_main_window->GetChatWindow()->signal_action_reaction_remove().connect(sigc::mem_fun(*this, &Abaddon::ActionReactionRemove));
|
||||
|
||||
ActionReloadCSS();
|
||||
|
||||
m_gtk_app->signal_shutdown().connect([&]() {
|
||||
StopDiscord();
|
||||
});
|
||||
#ifdef WITH_LIBHANDY
|
||||
gdk_event_handler_set(&MainEventHandler, m_main_window.get(), nullptr);
|
||||
#endif
|
||||
|
||||
if (!m_settings.IsValid()) {
|
||||
Gtk::MessageDialog dlg(*m_main_window, "The settings file could not be created!", false, Gtk::MESSAGE_ERROR, Gtk::BUTTONS_OK, true);
|
||||
Gtk::MessageDialog dlg(*m_main_window, "The settings file could not be opened!", false, Gtk::MESSAGE_ERROR, Gtk::BUTTONS_OK, true);
|
||||
dlg.set_position(Gtk::WIN_POS_CENTER);
|
||||
dlg.run();
|
||||
}
|
||||
@@ -136,13 +228,68 @@ int Abaddon::StartGTK() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// store must be checked before this can be called
|
||||
m_main_window->UpdateComponents();
|
||||
|
||||
// crashes for some stupid reason if i put it somewhere else
|
||||
SetupUserMenu();
|
||||
|
||||
m_main_window->signal_action_connect().connect(sigc::mem_fun(*this, &Abaddon::ActionConnect));
|
||||
m_main_window->signal_action_disconnect().connect(sigc::mem_fun(*this, &Abaddon::ActionDisconnect));
|
||||
m_main_window->signal_action_set_token().connect(sigc::mem_fun(*this, &Abaddon::ActionSetToken));
|
||||
m_main_window->signal_action_reload_css().connect(sigc::mem_fun(*this, &Abaddon::ActionReloadCSS));
|
||||
m_main_window->signal_action_set_status().connect(sigc::mem_fun(*this, &Abaddon::ActionSetStatus));
|
||||
m_main_window->signal_action_add_recipient().connect(sigc::mem_fun(*this, &Abaddon::ActionAddRecipient));
|
||||
m_main_window->signal_action_view_pins().connect(sigc::mem_fun(*this, &Abaddon::ActionViewPins));
|
||||
m_main_window->signal_action_view_threads().connect(sigc::mem_fun(*this, &Abaddon::ActionViewThreads));
|
||||
|
||||
m_main_window->GetChannelList()->signal_action_channel_item_select().connect(sigc::bind(sigc::mem_fun(*this, &Abaddon::ActionChannelOpened), true));
|
||||
m_main_window->GetChannelList()->signal_action_guild_leave().connect(sigc::mem_fun(*this, &Abaddon::ActionLeaveGuild));
|
||||
m_main_window->GetChannelList()->signal_action_guild_settings().connect(sigc::mem_fun(*this, &Abaddon::ActionGuildSettings));
|
||||
|
||||
m_main_window->GetChatWindow()->signal_action_message_edit().connect(sigc::mem_fun(*this, &Abaddon::ActionChatEditMessage));
|
||||
m_main_window->GetChatWindow()->signal_action_chat_submit().connect(sigc::mem_fun(*this, &Abaddon::ActionChatInputSubmit));
|
||||
m_main_window->GetChatWindow()->signal_action_chat_load_history().connect(sigc::mem_fun(*this, &Abaddon::ActionChatLoadHistory));
|
||||
m_main_window->GetChatWindow()->signal_action_channel_click().connect(sigc::mem_fun(*this, &Abaddon::ActionChannelOpened));
|
||||
m_main_window->GetChatWindow()->signal_action_insert_mention().connect(sigc::mem_fun(*this, &Abaddon::ActionInsertMention));
|
||||
m_main_window->GetChatWindow()->signal_action_reaction_add().connect(sigc::mem_fun(*this, &Abaddon::ActionReactionAdd));
|
||||
m_main_window->GetChatWindow()->signal_action_reaction_remove().connect(sigc::mem_fun(*this, &Abaddon::ActionReactionRemove));
|
||||
|
||||
ActionReloadCSS();
|
||||
if (m_settings.GetSettings().HideToTray) {
|
||||
m_tray = Gtk::StatusIcon::create("discord");
|
||||
m_tray->signal_activate().connect(sigc::mem_fun(*this, &Abaddon::on_tray_click));
|
||||
m_tray->signal_popup_menu().connect(sigc::mem_fun(*this, &Abaddon::on_tray_popup_menu));
|
||||
}
|
||||
m_tray_menu = Gtk::make_managed<Gtk::Menu>();
|
||||
m_tray_exit = Gtk::make_managed<Gtk::MenuItem>("Quit", false);
|
||||
|
||||
m_tray_exit->signal_activate().connect(sigc::mem_fun(*this, &Abaddon::on_tray_menu_click));
|
||||
|
||||
m_tray_menu->append(*m_tray_exit);
|
||||
m_tray_menu->show_all();
|
||||
|
||||
m_main_window->signal_hide().connect(sigc::mem_fun(*this, &Abaddon::on_window_hide));
|
||||
m_gtk_app->signal_shutdown().connect(sigc::mem_fun(*this, &Abaddon::OnShutdown), false);
|
||||
|
||||
m_main_window->UpdateMenus();
|
||||
|
||||
m_gtk_app->hold();
|
||||
m_main_window->show();
|
||||
|
||||
RunFirstTimeDiscordStartup();
|
||||
|
||||
return m_gtk_app->run(*m_main_window);
|
||||
}
|
||||
|
||||
void Abaddon::OnShutdown() {
|
||||
StopDiscord();
|
||||
m_settings.Close();
|
||||
}
|
||||
|
||||
void Abaddon::LoadFromSettings() {
|
||||
std::string token = m_settings.GetDiscordToken();
|
||||
if (token.size()) {
|
||||
std::string token = GetSettings().DiscordToken;
|
||||
if (!token.empty()) {
|
||||
m_discord_token = token;
|
||||
m_discord.UpdateToken(m_discord_token);
|
||||
}
|
||||
@@ -150,10 +297,13 @@ void Abaddon::LoadFromSettings() {
|
||||
|
||||
void Abaddon::StartDiscord() {
|
||||
m_discord.Start();
|
||||
m_main_window->UpdateMenus();
|
||||
}
|
||||
|
||||
void Abaddon::StopDiscord() {
|
||||
m_discord.Stop();
|
||||
if (m_discord.Stop())
|
||||
SaveState();
|
||||
m_main_window->UpdateMenus();
|
||||
}
|
||||
|
||||
bool Abaddon::IsDiscordActive() const {
|
||||
@@ -176,6 +326,7 @@ const DiscordClient &Abaddon::GetDiscordClient() const {
|
||||
|
||||
void Abaddon::DiscordOnReady() {
|
||||
m_main_window->UpdateComponents();
|
||||
LoadState();
|
||||
}
|
||||
|
||||
void Abaddon::DiscordOnMessageCreate(const Message &message) {
|
||||
@@ -247,8 +398,8 @@ void Abaddon::DiscordOnThreadUpdate(const ThreadUpdateData &data) {
|
||||
}
|
||||
}
|
||||
|
||||
const SettingsManager &Abaddon::GetSettings() const {
|
||||
return m_settings;
|
||||
SettingsManager::Settings &Abaddon::GetSettings() {
|
||||
return m_settings.GetSettings();
|
||||
}
|
||||
|
||||
Glib::RefPtr<Gtk::CssProvider> Abaddon::GetStyleProvider() {
|
||||
@@ -265,10 +416,11 @@ void Abaddon::ShowUserMenu(const GdkEvent *event, Snowflake id, Snowflake guild_
|
||||
|
||||
for (const auto child : m_user_menu_roles_submenu->get_children())
|
||||
delete child;
|
||||
|
||||
if (guild.has_value() && user.has_value()) {
|
||||
const auto roles = user->GetSortedRoles();
|
||||
m_user_menu_roles->set_visible(roles.size() > 0);
|
||||
for (const auto role : roles) {
|
||||
m_user_menu_roles->set_visible(!roles.empty());
|
||||
for (const auto &role : roles) {
|
||||
auto *item = Gtk::manage(new Gtk::MenuItem(role.Name));
|
||||
if (role.Color != 0) {
|
||||
Gdk::RGBA color;
|
||||
@@ -287,7 +439,7 @@ void Abaddon::ShowUserMenu(const GdkEvent *event, Snowflake id, Snowflake guild_
|
||||
if (me == id) {
|
||||
m_user_menu_ban->set_visible(false);
|
||||
m_user_menu_kick->set_visible(false);
|
||||
m_user_menu_open_dm->set_visible(false);
|
||||
m_user_menu_open_dm->set_sensitive(false);
|
||||
} else {
|
||||
const bool has_kick = m_discord.HasGuildPermission(me, guild_id, Permission::KICK_MEMBERS);
|
||||
const bool has_ban = m_discord.HasGuildPermission(me, guild_id, Permission::BAN_MEMBERS);
|
||||
@@ -295,7 +447,7 @@ void Abaddon::ShowUserMenu(const GdkEvent *event, Snowflake id, Snowflake guild_
|
||||
|
||||
m_user_menu_kick->set_visible(has_kick && can_manage);
|
||||
m_user_menu_ban->set_visible(has_ban && can_manage);
|
||||
m_user_menu_open_dm->set_visible(true);
|
||||
m_user_menu_open_dm->set_sensitive(m_discord.FindDM(id).has_value());
|
||||
}
|
||||
|
||||
m_user_menu_remove_recipient->hide();
|
||||
@@ -309,6 +461,48 @@ void Abaddon::ShowUserMenu(const GdkEvent *event, Snowflake id, Snowflake guild_
|
||||
m_user_menu->popup_at_pointer(event);
|
||||
}
|
||||
|
||||
void Abaddon::RunFirstTimeDiscordStartup() {
|
||||
DiscordStartupDialog dlg(*m_main_window);
|
||||
dlg.set_position(Gtk::WIN_POS_CENTER);
|
||||
|
||||
std::optional<std::string> cookie;
|
||||
std::optional<uint32_t> build_number;
|
||||
|
||||
dlg.signal_response().connect([&](int response) {
|
||||
if (response == Gtk::RESPONSE_OK) {
|
||||
cookie = dlg.GetCookie();
|
||||
build_number = dlg.GetBuildNumber();
|
||||
}
|
||||
});
|
||||
|
||||
dlg.run();
|
||||
|
||||
Glib::signal_idle().connect_once([this, cookie, build_number]() {
|
||||
if (cookie.has_value()) {
|
||||
m_discord.SetCookie(*cookie);
|
||||
} else {
|
||||
ConfirmDialog confirm(*m_main_window);
|
||||
confirm.SetConfirmText("Cookies could not be fetched. This may increase your chances of being flagged by Discord's anti-spam");
|
||||
confirm.SetAcceptOnly(true);
|
||||
confirm.run();
|
||||
}
|
||||
|
||||
if (build_number.has_value()) {
|
||||
m_discord.SetBuildNumber(*build_number);
|
||||
} else {
|
||||
ConfirmDialog confirm(*m_main_window);
|
||||
confirm.SetConfirmText("Build number could not be fetched. This may increase your chances of being flagged by Discord's anti-spam");
|
||||
confirm.SetAcceptOnly(true);
|
||||
confirm.run();
|
||||
}
|
||||
|
||||
// autoconnect
|
||||
if (cookie.has_value() && build_number.has_value() && GetSettings().Autoconnect && !GetDiscordToken().empty()) {
|
||||
ActionConnect();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void Abaddon::ShowGuildVerificationGateDialog(Snowflake guild_id) {
|
||||
VerificationGateDialog dlg(*m_main_window, guild_id);
|
||||
if (dlg.run() == Gtk::RESPONSE_OK) {
|
||||
@@ -323,13 +517,27 @@ void Abaddon::ShowGuildVerificationGateDialog(Snowflake guild_id) {
|
||||
}
|
||||
}
|
||||
|
||||
void Abaddon::CheckMessagesForMembers(const ChannelData &chan, const std::vector<Message> &msgs) {
|
||||
if (!chan.GuildID.has_value()) return;
|
||||
|
||||
std::vector<Snowflake> unknown;
|
||||
std::transform(msgs.begin(), msgs.end(),
|
||||
std::back_inserter(unknown),
|
||||
[](const Message &msg) -> Snowflake {
|
||||
return msg.Author.ID;
|
||||
});
|
||||
|
||||
const auto fetch = m_discord.FilterUnknownMembersFrom(*chan.GuildID, unknown.begin(), unknown.end());
|
||||
m_discord.RequestMembers(*chan.GuildID, fetch.begin(), fetch.end());
|
||||
}
|
||||
|
||||
void Abaddon::SetupUserMenu() {
|
||||
m_user_menu = Gtk::manage(new Gtk::Menu);
|
||||
m_user_menu_insert_mention = Gtk::manage(new Gtk::MenuItem("Insert Mention"));
|
||||
m_user_menu_ban = Gtk::manage(new Gtk::MenuItem("Ban"));
|
||||
m_user_menu_kick = Gtk::manage(new Gtk::MenuItem("Kick"));
|
||||
m_user_menu_copy_id = Gtk::manage(new Gtk::MenuItem("Copy ID"));
|
||||
m_user_menu_open_dm = Gtk::manage(new Gtk::MenuItem("Open DM"));
|
||||
m_user_menu_open_dm = Gtk::manage(new Gtk::MenuItem("Go to DM"));
|
||||
m_user_menu_roles = Gtk::manage(new Gtk::MenuItem("Roles"));
|
||||
m_user_menu_info = Gtk::manage(new Gtk::MenuItem("View Profile"));
|
||||
m_user_menu_remove_recipient = Gtk::manage(new Gtk::MenuItem("Remove From Group"));
|
||||
@@ -365,6 +573,52 @@ void Abaddon::SetupUserMenu() {
|
||||
m_user_menu->show_all();
|
||||
}
|
||||
|
||||
void Abaddon::SaveState() {
|
||||
if (!GetSettings().SaveState) return;
|
||||
|
||||
AbaddonApplicationState state;
|
||||
state.ActiveChannel = m_main_window->GetChatActiveChannel();
|
||||
state.Expansion = m_main_window->GetChannelList()->GetExpansionState();
|
||||
#ifdef WITH_LIBHANDY
|
||||
state.Tabs = m_main_window->GetChatWindow()->GetTabsState();
|
||||
#endif
|
||||
|
||||
const auto path = GetStateCachePath();
|
||||
if (!util::IsFolder(path)) {
|
||||
std::error_code ec;
|
||||
std::filesystem::create_directories(path, ec);
|
||||
}
|
||||
|
||||
auto file_name = "/" + std::to_string(m_discord.GetUserData().ID) + ".json";
|
||||
auto *fp = std::fopen(GetStateCachePath(file_name).c_str(), "wb");
|
||||
if (fp == nullptr) return;
|
||||
const auto s = nlohmann::json(state).dump(4);
|
||||
std::fwrite(s.c_str(), 1, s.size(), fp);
|
||||
std::fclose(fp);
|
||||
}
|
||||
|
||||
void Abaddon::LoadState() {
|
||||
if (!GetSettings().SaveState) {
|
||||
// call with empty data to purge the temporary table
|
||||
m_main_window->GetChannelList()->UseExpansionState({});
|
||||
return;
|
||||
}
|
||||
|
||||
auto file_name = "/" + std::to_string(m_discord.GetUserData().ID) + ".json";
|
||||
const auto data = ReadWholeFile(GetStateCachePath(file_name));
|
||||
if (data.empty()) return;
|
||||
try {
|
||||
AbaddonApplicationState state = nlohmann::json::parse(data.begin(), data.end());
|
||||
m_main_window->GetChannelList()->UseExpansionState(state.Expansion);
|
||||
#ifdef WITH_LIBHANDY
|
||||
m_main_window->GetChatWindow()->UseTabsState(state.Tabs);
|
||||
#endif
|
||||
ActionChannelOpened(state.ActiveChannel, false);
|
||||
} catch (const std::exception &e) {
|
||||
printf("failed to load application state: %s\n", e.what());
|
||||
}
|
||||
}
|
||||
|
||||
void Abaddon::ManageHeapWindow(Gtk::Window *window) {
|
||||
window->signal_hide().connect([this, window]() {
|
||||
delete window;
|
||||
@@ -394,18 +648,9 @@ void Abaddon::on_user_menu_copy_id() {
|
||||
|
||||
void Abaddon::on_user_menu_open_dm() {
|
||||
const auto existing = m_discord.FindDM(m_shown_user_menu_id);
|
||||
if (existing.has_value())
|
||||
if (existing.has_value()) {
|
||||
ActionChannelOpened(*existing);
|
||||
else
|
||||
m_discord.CreateDM(m_shown_user_menu_id, [this](DiscordError code, Snowflake channel_id) {
|
||||
if (code == DiscordError::NONE) {
|
||||
// give the gateway a little window to send CHANNEL_CREATE
|
||||
auto cb = [this, channel_id] {
|
||||
ActionChannelOpened(channel_id);
|
||||
};
|
||||
Glib::signal_timeout().connect_once(sigc::track_obj(cb, *this), 200);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void Abaddon::on_user_menu_remove_recipient() {
|
||||
@@ -422,6 +667,11 @@ std::string Abaddon::GetResPath() {
|
||||
return path;
|
||||
}
|
||||
|
||||
std::string Abaddon::GetStateCachePath() {
|
||||
const static auto path = Platform::FindStateCacheFolder() + "/state";
|
||||
return path;
|
||||
}
|
||||
|
||||
std::string Abaddon::GetCSSPath(const std::string &path) {
|
||||
return GetCSSPath() + path;
|
||||
}
|
||||
@@ -430,6 +680,10 @@ std::string Abaddon::GetResPath(const std::string &path) {
|
||||
return GetResPath() + path;
|
||||
}
|
||||
|
||||
std::string Abaddon::GetStateCachePath(const std::string &path) {
|
||||
return GetStateCachePath() + path;
|
||||
}
|
||||
|
||||
void Abaddon::ActionConnect() {
|
||||
if (!m_discord.IsStarted())
|
||||
StartDiscord();
|
||||
@@ -447,26 +701,30 @@ void Abaddon::ActionSetToken() {
|
||||
m_discord_token = dlg.GetToken();
|
||||
m_discord.UpdateToken(m_discord_token);
|
||||
m_main_window->UpdateComponents();
|
||||
m_settings.SetSetting("discord", "token", m_discord_token);
|
||||
GetSettings().DiscordToken = m_discord_token;
|
||||
}
|
||||
m_main_window->UpdateMenus();
|
||||
}
|
||||
|
||||
void Abaddon::ActionJoinGuildDialog() {
|
||||
JoinGuildDialog dlg(*m_main_window);
|
||||
auto response = dlg.run();
|
||||
if (response == Gtk::RESPONSE_OK) {
|
||||
auto code = dlg.GetCode();
|
||||
m_discord.JoinGuild(code);
|
||||
void Abaddon::ActionChannelOpened(Snowflake id, bool expand_to) {
|
||||
if (!id.IsValid()) {
|
||||
m_discord.SetReferringChannel(Snowflake::Invalid);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void Abaddon::ActionChannelOpened(Snowflake id) {
|
||||
if (id == m_main_window->GetChatActiveChannel()) return;
|
||||
|
||||
m_main_window->GetChatWindow()->SetTopic("");
|
||||
|
||||
const auto channel = m_discord.GetChannel(id);
|
||||
if (!channel.has_value()) return;
|
||||
if (!channel.has_value()) {
|
||||
m_discord.SetReferringChannel(Snowflake::Invalid);
|
||||
m_main_window->UpdateChatActiveChannel(Snowflake::Invalid, false);
|
||||
m_main_window->UpdateChatWindowContents();
|
||||
return;
|
||||
}
|
||||
|
||||
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 {
|
||||
@@ -480,26 +738,35 @@ void Abaddon::ActionChannelOpened(Snowflake id) {
|
||||
display = "Empty group";
|
||||
m_main_window->set_title(std::string(APP_TITLE) + " - " + display);
|
||||
}
|
||||
m_main_window->UpdateChatActiveChannel(id);
|
||||
m_main_window->UpdateChatActiveChannel(id, expand_to);
|
||||
if (m_channels_requested.find(id) == m_channels_requested.end()) {
|
||||
m_discord.FetchMessagesInChannel(id, [this, id](const std::vector<Message> &msgs) {
|
||||
m_main_window->UpdateChatWindowContents();
|
||||
m_channels_requested.insert(id);
|
||||
});
|
||||
// dont fire requests we know will fail
|
||||
if (can_access) {
|
||||
m_discord.FetchMessagesInChannel(id, [channel, this, id](const std::vector<Message> &msgs) {
|
||||
CheckMessagesForMembers(*channel, msgs);
|
||||
m_main_window->UpdateChatWindowContents();
|
||||
m_channels_requested.insert(id);
|
||||
});
|
||||
}
|
||||
} else {
|
||||
m_main_window->UpdateChatWindowContents();
|
||||
}
|
||||
|
||||
if (channel->IsThread()) {
|
||||
m_discord.SendThreadLazyLoad(id);
|
||||
if (channel->ThreadMetadata->IsArchived)
|
||||
m_main_window->GetChatWindow()->SetTopic("This thread is archived. Sending a message will unarchive it");
|
||||
} else if (channel->Type != ChannelType::DM && channel->Type != ChannelType::GROUP_DM && channel->GuildID.has_value()) {
|
||||
m_discord.SendLazyLoad(id);
|
||||
if (can_access) {
|
||||
if (channel->IsThread()) {
|
||||
m_discord.SendThreadLazyLoad(id);
|
||||
if (channel->ThreadMetadata->IsArchived)
|
||||
m_main_window->GetChatWindow()->SetTopic("This thread is archived. Sending a message will unarchive it");
|
||||
} else if (channel->Type != ChannelType::DM && channel->Type != ChannelType::GROUP_DM && channel->GuildID.has_value()) {
|
||||
m_discord.SendLazyLoad(id);
|
||||
|
||||
if (m_discord.IsVerificationRequired(*channel->GuildID))
|
||||
ShowGuildVerificationGateDialog(*channel->GuildID);
|
||||
if (m_discord.IsVerificationRequired(*channel->GuildID))
|
||||
ShowGuildVerificationGateDialog(*channel->GuildID);
|
||||
}
|
||||
}
|
||||
|
||||
m_main_window->UpdateMenus();
|
||||
m_discord.SetReferringChannel(id);
|
||||
}
|
||||
|
||||
void Abaddon::ActionChatLoadHistory(Snowflake id) {
|
||||
@@ -510,16 +777,9 @@ void Abaddon::ActionChatLoadHistory(Snowflake id) {
|
||||
return;
|
||||
|
||||
Snowflake before_id = m_main_window->GetChatOldestListedMessage();
|
||||
auto knownset = m_discord.GetMessageIDsForChannel(id);
|
||||
std::vector<Snowflake> knownvec(knownset.begin(), knownset.end());
|
||||
std::sort(knownvec.begin(), knownvec.end());
|
||||
auto latest = std::find_if(knownvec.begin(), knownvec.end(), [&before_id](Snowflake x) -> bool { return x == before_id; });
|
||||
int distance = std::distance(knownvec.begin(), latest);
|
||||
auto msgs = m_discord.GetMessagesBefore(id, before_id);
|
||||
|
||||
if (distance >= 50) {
|
||||
std::vector<Message> msgs;
|
||||
for (auto it = knownvec.begin() + distance - 50; it != knownvec.begin() + distance; it++)
|
||||
msgs.push_back(*m_discord.GetMessage(*it));
|
||||
if (msgs.size() >= 50) {
|
||||
m_main_window->UpdateChatPrependHistory(msgs);
|
||||
return;
|
||||
}
|
||||
@@ -529,7 +789,11 @@ void Abaddon::ActionChatLoadHistory(Snowflake id) {
|
||||
m_discord.FetchMessagesInChannelBefore(id, before_id, [this, id](const std::vector<Message> &msgs) {
|
||||
m_channels_history_loading.erase(id);
|
||||
|
||||
if (msgs.size() == 0) {
|
||||
const auto channel = m_discord.GetChannel(id);
|
||||
if (channel.has_value())
|
||||
CheckMessagesForMembers(*channel, msgs);
|
||||
|
||||
if (msgs.empty()) {
|
||||
m_channels_history_loaded.insert(id);
|
||||
} else {
|
||||
m_main_window->UpdateChatPrependHistory(msgs);
|
||||
@@ -537,13 +801,13 @@ void Abaddon::ActionChatLoadHistory(Snowflake id) {
|
||||
});
|
||||
}
|
||||
|
||||
void Abaddon::ActionChatInputSubmit(std::string msg, Snowflake channel, Snowflake referenced_message) {
|
||||
if (msg.substr(0, 7) == "/shrug " || msg == "/shrug")
|
||||
msg = msg.substr(6) + "\xC2\xAF\x5C\x5F\x28\xE3\x83\x84\x29\x5F\x2F\xC2\xAF"; // this is important
|
||||
if (referenced_message.IsValid())
|
||||
m_discord.SendChatMessage(msg, channel, referenced_message);
|
||||
else
|
||||
m_discord.SendChatMessage(msg, channel);
|
||||
void Abaddon::ActionChatInputSubmit(ChatSubmitParams data) {
|
||||
if (data.Message.substr(0, 7) == "/shrug " || data.Message == "/shrug")
|
||||
data.Message = data.Message.substr(6) + "\xC2\xAF\x5C\x5F\x28\xE3\x83\x84\x29\x5F\x2F\xC2\xAF"; // this is important
|
||||
|
||||
if (!m_discord.HasChannelPermission(m_discord.GetUserData().ID, data.ChannelID, Permission::VIEW_CHANNEL)) return;
|
||||
|
||||
m_discord.SendChatMessage(data, NOOP_CALLBACK);
|
||||
}
|
||||
|
||||
void Abaddon::ActionChatEditMessage(Snowflake channel_id, Snowflake id) {
|
||||
@@ -599,7 +863,7 @@ void Abaddon::ActionSetStatus() {
|
||||
const auto status = dlg.GetStatusType();
|
||||
const auto activity_type = dlg.GetActivityType();
|
||||
const auto activity_name = dlg.GetActivityName();
|
||||
if (activity_name == "") {
|
||||
if (activity_name.empty()) {
|
||||
m_discord.UpdateStatus(status, false);
|
||||
} else {
|
||||
ActivityData activity;
|
||||
@@ -652,6 +916,15 @@ void Abaddon::ActionViewThreads(Snowflake channel_id) {
|
||||
window->show();
|
||||
}
|
||||
|
||||
std::optional<Glib::ustring> Abaddon::ShowTextPrompt(const Glib::ustring &prompt, const Glib::ustring &title, const Glib::ustring &placeholder, Gtk::Window *window) {
|
||||
TextInputDialog dlg(prompt, title, placeholder, window != nullptr ? *window : *m_main_window);
|
||||
const auto code = dlg.run();
|
||||
if (code == Gtk::RESPONSE_OK)
|
||||
return dlg.GetInput();
|
||||
else
|
||||
return {};
|
||||
}
|
||||
|
||||
bool Abaddon::ShowConfirm(const Glib::ustring &prompt, Gtk::Window *window) {
|
||||
ConfirmDialog dlg(window != nullptr ? *window : *m_main_window);
|
||||
dlg.SetConfirmText(prompt);
|
||||
@@ -661,7 +934,7 @@ bool Abaddon::ShowConfirm(const Glib::ustring &prompt, Gtk::Window *window) {
|
||||
void Abaddon::ActionReloadCSS() {
|
||||
try {
|
||||
Gtk::StyleContext::remove_provider_for_screen(Gdk::Screen::get_default(), m_css_provider);
|
||||
m_css_provider->load_from_path(GetCSSPath("/" + m_settings.GetMainCSS()));
|
||||
m_css_provider->load_from_path(GetCSSPath("/" + GetSettings().MainCSS));
|
||||
Gtk::StyleContext::add_provider_for_screen(Gdk::Screen::get_default(), m_css_provider, GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
|
||||
|
||||
Gtk::StyleContext::remove_provider_for_screen(Gdk::Screen::get_default(), m_css_low_provider);
|
||||
@@ -682,9 +955,39 @@ EmojiResource &Abaddon::GetEmojis() {
|
||||
return m_emojis;
|
||||
}
|
||||
|
||||
void Abaddon::on_tray_click() {
|
||||
m_main_window->set_visible(!m_main_window->is_visible());
|
||||
}
|
||||
void Abaddon::on_tray_menu_click() {
|
||||
m_gtk_app->quit();
|
||||
}
|
||||
void Abaddon::on_tray_popup_menu(int button, int activate_time) {
|
||||
m_tray->popup_menu_at_position(*m_tray_menu, button, activate_time);
|
||||
}
|
||||
void Abaddon::on_window_hide() {
|
||||
if (!m_settings.GetSettings().HideToTray) {
|
||||
m_gtk_app->quit();
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
if (std::getenv("ABADDON_NO_FC") == nullptr)
|
||||
Platform::SetupFonts();
|
||||
|
||||
char *systemLocale = std::setlocale(LC_ALL, "");
|
||||
try {
|
||||
if (systemLocale != nullptr) {
|
||||
std::locale::global(std::locale(systemLocale));
|
||||
}
|
||||
} catch (...) {
|
||||
try {
|
||||
std::locale::global(std::locale::classic());
|
||||
if (systemLocale != nullptr) {
|
||||
std::setlocale(LC_ALL, systemLocale);
|
||||
}
|
||||
} catch (...) {}
|
||||
}
|
||||
|
||||
#if defined(_WIN32) && defined(_MSC_VER)
|
||||
TCHAR buf[2] { 0 };
|
||||
GetEnvironmentVariableA("GTK_CSD", buf, sizeof(buf));
|
||||
@@ -1,3 +1,4 @@
|
||||
#pragma once
|
||||
#include <gtkmm.h>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
@@ -14,16 +15,18 @@
|
||||
class Abaddon {
|
||||
private:
|
||||
Abaddon();
|
||||
~Abaddon();
|
||||
|
||||
public:
|
||||
static Abaddon &Get();
|
||||
|
||||
Abaddon(const Abaddon &) = delete;
|
||||
Abaddon &operator=(const Abaddon &) = delete;
|
||||
Abaddon(Abaddon &&) = delete;
|
||||
Abaddon &operator=(Abaddon &&) = delete;
|
||||
|
||||
public:
|
||||
static Abaddon &Get();
|
||||
|
||||
int StartGTK();
|
||||
void OnShutdown();
|
||||
|
||||
void StartDiscord();
|
||||
void StopDiscord();
|
||||
|
||||
@@ -33,8 +36,8 @@ public:
|
||||
void ActionDisconnect();
|
||||
void ActionSetToken();
|
||||
void ActionJoinGuildDialog();
|
||||
void ActionChannelOpened(Snowflake id);
|
||||
void ActionChatInputSubmit(std::string msg, Snowflake channel, Snowflake referenced_message);
|
||||
void ActionChannelOpened(Snowflake id, bool expand_to = true);
|
||||
void ActionChatInputSubmit(ChatSubmitParams data);
|
||||
void ActionChatLoadHistory(Snowflake id);
|
||||
void ActionChatEditMessage(Snowflake channel_id, Snowflake id);
|
||||
void ActionInsertMention(Snowflake id);
|
||||
@@ -49,6 +52,7 @@ public:
|
||||
void ActionViewPins(Snowflake channel_id);
|
||||
void ActionViewThreads(Snowflake channel_id);
|
||||
|
||||
std::optional<Glib::ustring> ShowTextPrompt(const Glib::ustring &prompt, const Glib::ustring &title, const Glib::ustring &placeholder = "", Gtk::Window *window = nullptr);
|
||||
bool ShowConfirm(const Glib::ustring &prompt, Gtk::Window *window = nullptr);
|
||||
|
||||
void ActionReloadCSS();
|
||||
@@ -74,7 +78,7 @@ public:
|
||||
void DiscordOnDisconnect(bool is_reconnecting, GatewayCloseCode close_code);
|
||||
void DiscordOnThreadUpdate(const ThreadUpdateData &data);
|
||||
|
||||
const SettingsManager &GetSettings() const;
|
||||
SettingsManager::Settings &GetSettings();
|
||||
|
||||
Glib::RefPtr<Gtk::CssProvider> GetStyleProvider();
|
||||
|
||||
@@ -84,13 +88,21 @@ public:
|
||||
|
||||
static std::string GetCSSPath();
|
||||
static std::string GetResPath();
|
||||
static std::string GetStateCachePath();
|
||||
static std::string GetCSSPath(const std::string &path);
|
||||
static std::string GetResPath(const std::string &path);
|
||||
static std::string GetStateCachePath(const std::string &path);
|
||||
|
||||
protected:
|
||||
void RunFirstTimeDiscordStartup();
|
||||
|
||||
void ShowGuildVerificationGateDialog(Snowflake guild_id);
|
||||
|
||||
void CheckMessagesForMembers(const ChannelData &chan, const std::vector<Message> &msgs);
|
||||
|
||||
void SetupUserMenu();
|
||||
void SaveState();
|
||||
void LoadState();
|
||||
|
||||
Snowflake m_shown_user_menu_id;
|
||||
Snowflake m_shown_user_menu_guild_id;
|
||||
@@ -105,6 +117,8 @@ protected:
|
||||
Gtk::MenuItem *m_user_menu_roles;
|
||||
Gtk::MenuItem *m_user_menu_remove_recipient;
|
||||
Gtk::Menu *m_user_menu_roles_submenu;
|
||||
Gtk::Menu *m_tray_menu;
|
||||
Gtk::MenuItem *m_tray_exit;
|
||||
|
||||
void on_user_menu_insert_mention();
|
||||
void on_user_menu_ban();
|
||||
@@ -112,6 +126,10 @@ protected:
|
||||
void on_user_menu_copy_id();
|
||||
void on_user_menu_open_dm();
|
||||
void on_user_menu_remove_recipient();
|
||||
void on_tray_click();
|
||||
void on_tray_popup_menu(int button, int activate_time);
|
||||
void on_tray_menu_click();
|
||||
void on_window_hide();
|
||||
|
||||
private:
|
||||
SettingsManager m_settings;
|
||||
@@ -130,5 +148,6 @@ private:
|
||||
Glib::RefPtr<Gtk::Application> m_gtk_app;
|
||||
Glib::RefPtr<Gtk::CssProvider> m_css_provider;
|
||||
Glib::RefPtr<Gtk::CssProvider> m_css_low_provider; // registered with a lower priority to allow better customization
|
||||
std::unique_ptr<MainWindow> m_main_window; // wah wah cant create a gtkstylecontext fuck you
|
||||
Glib::RefPtr<Gtk::StatusIcon> m_tray;
|
||||
std::unique_ptr<MainWindow> m_main_window; // wah wah cant create a gtkstylecontext fuck you
|
||||
};
|
||||