mirror of
https://github.com/Chatterino/chatterino2.git
synced 2024-11-13 19:49:51 +01:00
Merge branch 'master' into git_is_pepega
This commit is contained in:
commit
20d8da8f2d
42
.CI/CreateAppImage.sh
Normal file → Executable file
42
.CI/CreateAppImage.sh
Normal file → Executable file
|
@ -1,21 +1,45 @@
|
||||||
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/opt/qt512/lib/
|
#!/bin/sh
|
||||||
ldd ./bin/chatterino
|
|
||||||
make INSTALL_ROOT=appdir -j$(nproc) install ; find appdir/
|
|
||||||
cp ./resources/icon.png ./appdir/chatterino.png
|
|
||||||
|
|
||||||
wget -nv "https://github.com/probonopd/linuxdeployqt/releases/download/continuous/linuxdeployqt-continuous-x86_64.AppImage"
|
set -e
|
||||||
chmod a+x linuxdeployqt-continuous-x86_64.AppImage
|
|
||||||
|
if [ ! -f ./bin/chatterino ] || [ ! -x ./bin/chatterino ]; then
|
||||||
|
echo "ERROR: No chatterino binary file found. This script must be run in the build folder, and chatterino must be built first."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
export LD_LIBRARY_PATH="$LD_LIBRARY_PATH:/opt/qt512/lib/"
|
||||||
|
export PATH="/opt/qt512/bin:$PATH"
|
||||||
|
|
||||||
|
script_path=$(readlink -f "$0")
|
||||||
|
script_dir=$(dirname "$script_path")
|
||||||
|
chatterino_dir=$(dirname "$script_dir")
|
||||||
|
|
||||||
|
qmake_path=$(command -v qmake)
|
||||||
|
|
||||||
|
ldd ./bin/chatterino
|
||||||
|
make INSTALL_ROOT=appdir -j"$(nproc)" install ; find appdir/
|
||||||
|
cp "$chatterino_dir"/resources/icon.png ./appdir/chatterino.png
|
||||||
|
|
||||||
|
linuxdeployqt_path="linuxdeployqt-6-x86_64.AppImage"
|
||||||
|
linuxdeployqt_url="https://github.com/probonopd/linuxdeployqt/releases/download/6/linuxdeployqt-6-x86_64.AppImage"
|
||||||
|
|
||||||
|
if [ ! -f "$linuxdeployqt_path" ]; then
|
||||||
|
wget -nv "$linuxdeployqt_url"
|
||||||
|
chmod a+x "$linuxdeployqt_path"
|
||||||
|
fi
|
||||||
|
if [ ! -f appimagetool-x86_64.AppImage ]; then
|
||||||
wget -nv "https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage"
|
wget -nv "https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage"
|
||||||
chmod a+x appimagetool-x86_64.AppImage
|
chmod a+x appimagetool-x86_64.AppImage
|
||||||
./linuxdeployqt-continuous-x86_64.AppImage \
|
fi
|
||||||
|
./"$linuxdeployqt_path" \
|
||||||
appdir/usr/share/applications/*.desktop \
|
appdir/usr/share/applications/*.desktop \
|
||||||
-no-translations \
|
-no-translations \
|
||||||
-bundle-non-qt-libs \
|
-bundle-non-qt-libs \
|
||||||
-unsupported-allow-new-glibc \
|
-unsupported-allow-new-glibc \
|
||||||
-qmake=/opt/qt512/bin/qmake
|
-qmake="$qmake_path"
|
||||||
|
|
||||||
rm -rf appdir/home
|
rm -rf appdir/home
|
||||||
rm appdir/AppRun
|
rm -f appdir/AppRun
|
||||||
|
|
||||||
# shellcheck disable=SC2016
|
# shellcheck disable=SC2016
|
||||||
echo '#!/bin/sh
|
echo '#!/bin/sh
|
||||||
|
|
13
.CI/CreateDMG.sh
Executable file
13
.CI/CreateDMG.sh
Executable file
|
@ -0,0 +1,13 @@
|
||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
echo "Running MACDEPLOYQT"
|
||||||
|
/usr/local/opt/qt/bin/macdeployqt chatterino.app -dmg
|
||||||
|
echo "Creating APP folder"
|
||||||
|
mkdir app
|
||||||
|
echo "Running hdiutil attach on the built DMG"
|
||||||
|
hdiutil attach chatterino.dmg
|
||||||
|
echo "Copying chatterino.app into the app folder"
|
||||||
|
cp -r /Volumes/chatterino/chatterino.app app/
|
||||||
|
echo "Creating DMG with create-dmg"
|
||||||
|
create-dmg --volname Chatterino2 --volicon ../resources/chatterino.icns --icon-size 50 --app-drop-link 0 0 --format UDBZ chatterino-osx.dmg app/
|
||||||
|
echo "DONE"
|
0
.CI/InstallQTStylePlugins.sh
Normal file → Executable file
0
.CI/InstallQTStylePlugins.sh
Normal file → Executable file
14
.github/ISSUE_TEMPLATE/bug_report.md
vendored
14
.github/ISSUE_TEMPLATE/bug_report.md
vendored
|
@ -10,17 +10,17 @@ assignees: ''
|
||||||
**Describe the bug**
|
**Describe the bug**
|
||||||
<!-- A clear and concise description of what the bug is. -->
|
<!-- A clear and concise description of what the bug is. -->
|
||||||
|
|
||||||
**To Reproduce**
|
**To reproduce**
|
||||||
<!-- Steps to reproduce the behavior -->
|
<!-- Steps to reproduce the behavior -->
|
||||||
|
|
||||||
**Expected behavior**
|
|
||||||
<!-- A clear and concise description of what you expected to happen. -->
|
|
||||||
|
|
||||||
**Screenshots**
|
**Screenshots**
|
||||||
<!-- If applicable, add screenshots to help explain your problem. Use the integrated uploader of the issue form to upload images, or copy an image to the clipboard and paste it in the input box -->
|
<!-- If applicable, add screenshots to help explain your problem. Use the integrated uploader of the issue form to upload images, or copy an image to the clipboard and paste it in the input box -->
|
||||||
|
|
||||||
**Chatterino version**
|
**Chatterino version**
|
||||||
<!-- Please copy the version information from the "About" page in the Settings, e.g. `Chatterino 2.1.4 (commit 35c7853c4, 16.09.2019)` -->
|
<!-- Copy the version information from the "About" page in the Settings, e.g. `Chatterino 2.1.4 (commit 35c7853c4, 16.09.2019)` -->
|
||||||
|
|
||||||
**Additional context**
|
**Operating system**
|
||||||
<!-- Add any other context about the problem here. -->
|
<!-- E.g. Windows 10 -->
|
||||||
|
|
||||||
|
**Additional information**
|
||||||
|
<!-- If applicable, add additional context. -->
|
||||||
|
|
207
.github/workflows/build.yml
vendored
Normal file
207
.github/workflows/build.yml
vendored
Normal file
|
@ -0,0 +1,207 @@
|
||||||
|
---
|
||||||
|
name: Build
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
paths-ignore:
|
||||||
|
- 'docs/**'
|
||||||
|
- '*.md'
|
||||||
|
pull_request:
|
||||||
|
paths-ignore:
|
||||||
|
- 'docs/**'
|
||||||
|
- '*.md'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ${{ matrix.os }}
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
os: [windows-latest, ubuntu-latest, macos-latest]
|
||||||
|
fail-fast: false
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v1
|
||||||
|
with:
|
||||||
|
submodules: true
|
||||||
|
|
||||||
|
- name: Install Qt
|
||||||
|
uses: jurplel/install-qt-action@v2
|
||||||
|
with:
|
||||||
|
modules: qtwebengine
|
||||||
|
|
||||||
|
# WINDOWS
|
||||||
|
- name: Install dependencies (Windows)
|
||||||
|
if: startsWith(matrix.os, 'windows')
|
||||||
|
run: |
|
||||||
|
REM We use this source (temporarily?) until choco has updated their version of conan
|
||||||
|
choco source add -n=AFG -s="https://api.bintray.com/nuget/anotherfoxguy/choco-pkg"
|
||||||
|
choco install conan -y
|
||||||
|
|
||||||
|
refreshenv
|
||||||
|
shell: cmd
|
||||||
|
|
||||||
|
- name: Build (Windows)
|
||||||
|
if: startsWith(matrix.os, 'windows')
|
||||||
|
run: |
|
||||||
|
call "%programfiles(x86)%\Microsoft Visual Studio\2019\Enterprise\VC\Auxiliary\Build\vcvars64.bat"
|
||||||
|
mkdir build
|
||||||
|
cd build
|
||||||
|
"C:\Program Files\Conan\conan\conan.exe" install ..
|
||||||
|
qmake ..
|
||||||
|
set cl=/MP
|
||||||
|
nmake /S /NOLOGO
|
||||||
|
windeployqt release/chatterino.exe --release --no-compiler-runtime --no-translations --no-opengl-sw --dir Chatterino2/
|
||||||
|
cp release/chatterino.exe Chatterino2/
|
||||||
|
echo nightly > Chatterino2/modes
|
||||||
|
7z a chatterino-windows-x86-64.zip Chatterino2/
|
||||||
|
shell: cmd
|
||||||
|
|
||||||
|
- name: Upload artifact (Windows)
|
||||||
|
if: startsWith(matrix.os, 'windows')
|
||||||
|
uses: actions/upload-artifact@v1
|
||||||
|
with:
|
||||||
|
name: chatterino-windows-x86-64.zip
|
||||||
|
path: build/chatterino-windows-x86-64.zip
|
||||||
|
|
||||||
|
# LINUX
|
||||||
|
- name: Install dependencies (Ubuntu)
|
||||||
|
if: startsWith(matrix.os, 'ubuntu')
|
||||||
|
run: sudo apt-get update && sudo apt-get -y install libssl-dev libboost-dev libboost-system-dev libboost-filesystem-dev libpulse-dev libxkbcommon-x11-0 libgstreamer-plugins-base1.0-0
|
||||||
|
|
||||||
|
- name: Build (Ubuntu)
|
||||||
|
if: startsWith(matrix.os, 'ubuntu')
|
||||||
|
run: |
|
||||||
|
mkdir build
|
||||||
|
cd build
|
||||||
|
qmake PREFIX=/usr ..
|
||||||
|
make -j8
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
- name: Package (Ubuntu)
|
||||||
|
if: startsWith(matrix.os, 'ubuntu')
|
||||||
|
run: |
|
||||||
|
cd build
|
||||||
|
sh ./../.CI/CreateAppImage.sh
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
- name: Upload artifact (Ubuntu)
|
||||||
|
if: startsWith(matrix.os, 'ubuntu')
|
||||||
|
uses: actions/upload-artifact@v1
|
||||||
|
with:
|
||||||
|
name: Chatterino-x86_64.AppImage
|
||||||
|
path: build/Chatterino-x86_64.AppImage
|
||||||
|
|
||||||
|
# MACOS
|
||||||
|
- name: Install dependencies (MacOS)
|
||||||
|
if: startsWith(matrix.os, 'macos')
|
||||||
|
run: |
|
||||||
|
brew install boost openssl rapidjson qt p7zip create-dmg
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
- name: Build (MacOS)
|
||||||
|
if: startsWith(matrix.os, 'macos')
|
||||||
|
run: |
|
||||||
|
mkdir build
|
||||||
|
cd build
|
||||||
|
/usr/local/opt/qt/bin/qmake .. DEFINES+=$dateOfBuild
|
||||||
|
sed -ie 's/-framework\\\ /-framework /g' Makefile
|
||||||
|
make -j8
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
- name: Package (MacOS)
|
||||||
|
if: startsWith(matrix.os, 'macos')
|
||||||
|
run: |
|
||||||
|
ls -la
|
||||||
|
pwd
|
||||||
|
ls -la build || true
|
||||||
|
cd build
|
||||||
|
sh ./../.CI/CreateDMG.sh
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
- name: Upload artifact (MacOS)
|
||||||
|
if: startsWith(matrix.os, 'macos')
|
||||||
|
uses: actions/upload-artifact@v1
|
||||||
|
with:
|
||||||
|
name: chatterino-osx.dmg
|
||||||
|
path: build/chatterino-osx.dmg
|
||||||
|
|
||||||
|
create-release:
|
||||||
|
needs: build
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
if: (github.event_name == 'push' && github.ref == 'refs/heads/master')
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Create release
|
||||||
|
id: create_release
|
||||||
|
uses: pajlada/create-release@v2
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
with:
|
||||||
|
tag_name: github-actions-nightly
|
||||||
|
backup_tag_name: backup-github-actions-nightly
|
||||||
|
release_name: GitHub Actions Nightly Test
|
||||||
|
body: |
|
||||||
|
1 ${{ github.eventName }}
|
||||||
|
2 ${{ github.sha }}
|
||||||
|
3 ${{ github.ref }}
|
||||||
|
4 ${{ github.workflow }}
|
||||||
|
5 ${{ github.action }}
|
||||||
|
6 ${{ github.actor }}
|
||||||
|
prerelease: true
|
||||||
|
|
||||||
|
- uses: actions/download-artifact@v1
|
||||||
|
with:
|
||||||
|
name: chatterino-windows-x86-64.zip
|
||||||
|
path: windows/
|
||||||
|
|
||||||
|
- uses: actions/download-artifact@v1
|
||||||
|
with:
|
||||||
|
name: Chatterino-x86_64.AppImage
|
||||||
|
path: linux/
|
||||||
|
|
||||||
|
- uses: actions/download-artifact@v1
|
||||||
|
with:
|
||||||
|
name: chatterino-osx.dmg
|
||||||
|
path: macos/
|
||||||
|
|
||||||
|
# TODO: Extract dmg and appimage
|
||||||
|
|
||||||
|
- name: TREE
|
||||||
|
run: |
|
||||||
|
sudo apt update && sudo apt install tree
|
||||||
|
tree .
|
||||||
|
|
||||||
|
# - name: Read upload URL into output
|
||||||
|
# id: upload_url
|
||||||
|
# run: |
|
||||||
|
# echo "::set-output name=upload_url::$(cat release-upload-url.txt/release-upload-url.txt)"
|
||||||
|
|
||||||
|
- name: Upload release asset (Windows)
|
||||||
|
uses: actions/upload-release-asset@v1.0.1
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
with:
|
||||||
|
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||||
|
asset_path: ./windows/chatterino-windows-x86-64.zip
|
||||||
|
asset_name: chatterino-windows-x86-64.zip
|
||||||
|
asset_content_type: application/zip
|
||||||
|
|
||||||
|
- name: Upload release asset (Ubuntu)
|
||||||
|
uses: actions/upload-release-asset@v1.0.1
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
with:
|
||||||
|
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||||
|
asset_path: ./linux/Chatterino-x86_64.AppImage
|
||||||
|
asset_name: Chatterino-x86_64.AppImage
|
||||||
|
asset_content_type: application/x-executable
|
||||||
|
|
||||||
|
- name: Upload release asset (MacOS)
|
||||||
|
uses: actions/upload-release-asset@v1.0.1
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
with:
|
||||||
|
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||||
|
asset_path: ./macos/chatterino-osx.dmg
|
||||||
|
asset_name: chatterino-osx.dmg
|
||||||
|
asset_content_type: application/x-bzip2
|
20
.github/workflows/check-formatting.yml
vendored
Normal file
20
.github/workflows/check-formatting.yml
vendored
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
name: Check formatting
|
||||||
|
|
||||||
|
on: [push, pull_request]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
container:
|
||||||
|
image: ubuntu:19.10
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v1
|
||||||
|
- name: apt-get update
|
||||||
|
run: apt-get update
|
||||||
|
|
||||||
|
- name: Install clang-format
|
||||||
|
run: apt-get -y install clang-format
|
||||||
|
|
||||||
|
- name: Check formatting
|
||||||
|
run: ./tools/check-format.sh
|
2
.gitmodules
vendored
2
.gitmodules
vendored
|
@ -1,6 +1,6 @@
|
||||||
[submodule "lib/libcommuni"]
|
[submodule "lib/libcommuni"]
|
||||||
path = lib/libcommuni
|
path = lib/libcommuni
|
||||||
url = https://github.com/hemirt/libcommuni
|
url = https://github.com/Chatterino/libcommuni
|
||||||
[submodule "lib/humanize"]
|
[submodule "lib/humanize"]
|
||||||
path = lib/humanize
|
path = lib/humanize
|
||||||
url = https://github.com/pajlada/humanize.git
|
url = https://github.com/pajlada/humanize.git
|
||||||
|
|
|
@ -64,7 +64,9 @@ matrix:
|
||||||
script:
|
script:
|
||||||
- mkdir build && cd build
|
- mkdir build && cd build
|
||||||
- dateOfBuild="CHATTERINO_NIGHTLY_VERSION_STRING=\"\\\"$(date +%d.%m.%Y)\\\"\""
|
- dateOfBuild="CHATTERINO_NIGHTLY_VERSION_STRING=\"\\\"$(date +%d.%m.%Y)\\\"\""
|
||||||
- /usr/local/opt/qt/bin/qmake .. DEFINES+=$dateOfBuild && make -j8
|
- /usr/local/opt/qt/bin/qmake .. DEFINES+=$dateOfBuild
|
||||||
|
- sed -ie 's/-framework\\\ /-framework /g' Makefile
|
||||||
|
- make -j8
|
||||||
- /usr/local/opt/qt/bin/macdeployqt chatterino.app -dmg
|
- /usr/local/opt/qt/bin/macdeployqt chatterino.app -dmg
|
||||||
- mkdir app
|
- mkdir app
|
||||||
- hdiutil attach chatterino.dmg
|
- hdiutil attach chatterino.dmg
|
||||||
|
|
|
@ -14,10 +14,16 @@ install [chatterino2-git](https://aur.archlinux.org/packages/chatterino2-git/) f
|
||||||
1. create build folder `mkdir build && cd build`
|
1. create build folder `mkdir build && cd build`
|
||||||
1. `qmake .. && make`
|
1. `qmake .. && make`
|
||||||
|
|
||||||
## Fedora 28
|
## Fedora 28 and above
|
||||||
*most likely works the same for other Red Hat-like distros*
|
*most likely works the same for other Red Hat-like distros. Substitue `dnf` with `yum`.*
|
||||||
1. `sudo yum install qt-creator qt5-qtmultimedia-devel qt5-qtsvg-devel openssl-devel gstreamer-plugins-ugly gstreamer-plugins-good boost-devel rapidjson-devel`
|
### Development dependencies
|
||||||
1. Open `chatterino.pro` with QT Creator and build
|
1. `sudo dnf install qt5-qtbase-devel qt5-qtmultimedia-devel qt5-qtsvg-devel libsecret-devel openssl-devel boost-devel`
|
||||||
|
1. go into project directory
|
||||||
|
1. create build folder `mkdir build && cd build`
|
||||||
|
1. `qmake-qt5 .. && make -j$(nproc)`
|
||||||
|
### Optional dependencies
|
||||||
|
*`gstreamer-plugins-good` package is retired in Fedora 31, see: [rhbz#1735324](https://bugzilla.redhat.com/show_bug.cgi?id=1735324)*
|
||||||
|
1. `sudo dnf install gstreamer-plugins-good` *(optional: for audio output)*
|
||||||
|
|
||||||
## NixOS 18.09+
|
## NixOS 18.09+
|
||||||
1. enter the development environment with all of the dependencies: `nix-shell -p openssl boost qt5.full`
|
1. enter the development environment with all of the dependencies: `nix-shell -p openssl boost qt5.full`
|
||||||
|
|
220
CONTRIBUTING.md
Normal file
220
CONTRIBUTING.md
Normal file
|
@ -0,0 +1,220 @@
|
||||||
|
# Chatterino code guidelines
|
||||||
|
|
||||||
|
This is a set of guidelines for contributing to Chatterino. The goal is to teach programmers without C++ background (java/python/etc.), people who haven't used Qt or otherwise have different experience the idioms of the codebase. Thus we will focus on those which are different from those other environments. There are extra guidelines available [here](https://hackmd.io/@fourtf/chatterino-pendantic-guidelines) but they are considered as extras and not as important.
|
||||||
|
|
||||||
|
# Tooling
|
||||||
|
|
||||||
|
Formatting
|
||||||
|
------
|
||||||
|
|
||||||
|
Code is automatically formatted using `clang-format`. It takes the burden off of the programmer and ensures that all contributors use the same style (even if mess something up accidentally). We recommend that you set up automatic formatting on file save in your editor.
|
||||||
|
|
||||||
|
# Comments
|
||||||
|
|
||||||
|
Comments should only be used to:
|
||||||
|
|
||||||
|
- Increase readability (e.g. grouping member variables).
|
||||||
|
- Containing information that can't be expressed in code.
|
||||||
|
|
||||||
|
Try to structure your code so that comments are not required.
|
||||||
|
|
||||||
|
#### Good example
|
||||||
|
|
||||||
|
``` cpp
|
||||||
|
/// Result is 0 if a == b, negative if a < b and positive if b > a.
|
||||||
|
/// ^^^ You can't know this from the function signature!
|
||||||
|
// Even better: Return a "strong ordering" type.
|
||||||
|
// (but we don't have such a type right now)
|
||||||
|
int compare(const QString &a, const QString &b);
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Bad example
|
||||||
|
|
||||||
|
``` cpp
|
||||||
|
/*
|
||||||
|
* Matches a link and returns boost::none if it failed and a
|
||||||
|
* QRegularExpressionMatch on success.
|
||||||
|
* ^^^ This comment just repeats the function signature!!!
|
||||||
|
*
|
||||||
|
* @param text The text that will be checked if it contains a
|
||||||
|
* link
|
||||||
|
* ^^^ No need to repeat the obvious.
|
||||||
|
*/
|
||||||
|
boost::optional<QRegularExpressionMatch> matchLink(const QString &text);
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
# Code
|
||||||
|
|
||||||
|
Arithmetic Types
|
||||||
|
-----
|
||||||
|
|
||||||
|
Arithmetic types (like char, short, int, long, float and double), bool, and pointers are NOT initialized by default in c++. They keep whatever value is already at their position in the memory. This makes debugging harder and is unpredictable, so we initialize them to zero by using `{}` after their name when declaring them.
|
||||||
|
|
||||||
|
``` cpp
|
||||||
|
class ArithmeticTypes
|
||||||
|
{
|
||||||
|
int thisIs0{};
|
||||||
|
QWidget *thisIsNull{};
|
||||||
|
bool thisIsFalse_{};
|
||||||
|
// int a; // <- Initialized to "random" value.
|
||||||
|
// QWidget *randomPtr.
|
||||||
|
|
||||||
|
std::vector<int> myVec; // <- other types call constructors instead, so no need for {}
|
||||||
|
// std::vector<int> myVec{}; <- pointless {}
|
||||||
|
|
||||||
|
int thisIs5 = 5; // <- Also fine, we initialize it with another value.
|
||||||
|
};
|
||||||
|
|
||||||
|
void myFunc() {
|
||||||
|
int a = 1 + 1; // <- here we initialize it immediately, so it's fine.
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Passing parameters
|
||||||
|
------
|
||||||
|
The way a parameter is passed signals how it is going to be used inside of the function. C++ doesn't have multiple return values so there is "out parameters" (reference to a variable that is going to be assigned inside of the function) to simulate multiple return values.
|
||||||
|
|
||||||
|
**Cheap to copy types** like int/enum/etc. can be passed in per value since copying them is fast.
|
||||||
|
``` cpp
|
||||||
|
void setValue(int value) {
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**References** mean that the variable doesn't need to be copied when it is passed to a function.
|
||||||
|
|
||||||
|
|type|meaning|
|
||||||
|
|-|-|
|
||||||
|
|`const Type& name`|*in* Parameter. It is NOT going to be modified and may be copied inside of the function.|
|
||||||
|
|`Type& name`|*out* or *in+out* Parmameter. It will be modified.|
|
||||||
|
|
||||||
|
**Pointers** signal that objects are managed manually. While the above are only guaranteed to live as long as the function call (= don't store and use later) these may have more complex lifetimes.
|
||||||
|
|
||||||
|
|type|meaning|
|
||||||
|
|-|-|
|
||||||
|
|`Type* name`|The lifetime of the parameter may exceed the length of the function call. It may use the `QObject` parent/children system.|
|
||||||
|
|
||||||
|
**R-value references** `&&` work similar to regular references but signal the parameter should be "consumed".
|
||||||
|
|
||||||
|
``` cpp
|
||||||
|
void storeLargeObject(LargeObject &&object) {
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
|
||||||
|
void storeObject(std::unique_ptr<Object> &&object) {
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
// initialize a large object (= will be expensive to copy)
|
||||||
|
LargeObject large = // ...
|
||||||
|
|
||||||
|
// Object accepts an r-value reference + we use std::move()
|
||||||
|
// => We move the object = no need to copy.
|
||||||
|
storeLargeObject(std::move(large));
|
||||||
|
|
||||||
|
// But even worse, you can't copy a unique_ptr so we need to move here!
|
||||||
|
std::unique_ptr<Object> unique = // ...
|
||||||
|
storeObject(std::move(unique));
|
||||||
|
|
||||||
|
// The pointer contained by unique has now been consumed by "storeObject"
|
||||||
|
// so it just holds a null pointer now.
|
||||||
|
assert(unique.get() == nullptr);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Generally the lowest level of requirement should be used e.g. passing `Channel&` instead of `std::shared_ptr<Channel>&` (aka `ChannelPtr`) if possible.
|
||||||
|
|
||||||
|
|
||||||
|
Members
|
||||||
|
-----
|
||||||
|
|
||||||
|
All functions names are in `camelCase`. *Private* member variables are in `camelCase_` (note the underscore at the end). We don't use the `get` prefix for getters. We mark functions as `const` [if applicable](https://stackoverflow.com/questions/751681/meaning-of-const-last-in-a-function-declaration-of-a-class).
|
||||||
|
``` cpp
|
||||||
|
class NamedObject
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
const QString &name() const; // <- no "get" prefix.
|
||||||
|
void setName(const QString &name);
|
||||||
|
bool hasLongName() const; // <- "has" or "is" prefix is okay
|
||||||
|
|
||||||
|
static void myStaticFunction(); // <- also lowercase
|
||||||
|
QString publicName;
|
||||||
|
|
||||||
|
private:
|
||||||
|
// Private variables have "_" suffix.
|
||||||
|
QString name_;
|
||||||
|
// QString name; <- collides with name() function
|
||||||
|
};
|
||||||
|
|
||||||
|
void myFreeStandingFunction(); // <- also lower case
|
||||||
|
```
|
||||||
|
|
||||||
|
Casts
|
||||||
|
------
|
||||||
|
|
||||||
|
- **Avoid** c-style casts: `(type)variable`.
|
||||||
|
- Instead use explicit type casts: `type(variable)`
|
||||||
|
- Or use one of [static_cast](https://en.cppreference.com/w/cpp/language/static_cast), [const_cast](https://en.cppreference.com/w/cpp/language/const_cast) and [dynamic_cast](https://en.cppreference.com/w/cpp/language/dynamic_cast)
|
||||||
|
- Try to avoid [reinterpret_cast](https://en.cppreference.com/w/cpp/language/reinterpret_cast) unless necessary.
|
||||||
|
|
||||||
|
``` cpp
|
||||||
|
void example() {
|
||||||
|
float f = 123.456;
|
||||||
|
int i = (int)f; // <- don't
|
||||||
|
int i = int(f); // <- do
|
||||||
|
|
||||||
|
Base* base = // ...
|
||||||
|
Derived* derived = (Derived*)base; // <- don't
|
||||||
|
Derived* derived = dynamic_cast<Derived*>(base); // <- do
|
||||||
|
|
||||||
|
// Only use "const_cast" solved if using proper const correctness doesn't work.
|
||||||
|
const int c = 123;
|
||||||
|
((int &)c) = 123; // <- don't
|
||||||
|
const_cast<int &>(c) = 123; // <- do (but only sometimes)
|
||||||
|
|
||||||
|
// "reinterpret_cast" is also only required in very rarely.
|
||||||
|
int p = 123;
|
||||||
|
float *pp = (float*)&p;
|
||||||
|
float *pp = reinterpret_cast<float*>(&p);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
This
|
||||||
|
------
|
||||||
|
Always use `this` to refer to instance members to make it clear where we use either locals or members.
|
||||||
|
|
||||||
|
``` cpp
|
||||||
|
class Test
|
||||||
|
{
|
||||||
|
void testFunc(int a);
|
||||||
|
int testInt_{};
|
||||||
|
}
|
||||||
|
|
||||||
|
Test::testFunc(int a)
|
||||||
|
{
|
||||||
|
// do
|
||||||
|
this->testInt_ += 2;
|
||||||
|
this->testFunc();
|
||||||
|
|
||||||
|
// don't
|
||||||
|
testInt_ -= 123;
|
||||||
|
testFunc(2);
|
||||||
|
this->testFunc(testInt_ + 1);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Managing resources
|
||||||
|
------
|
||||||
|
|
||||||
|
#### Regular classes
|
||||||
|
Keep the element on the stack if possible. If you need a pointer or have complex ownership you should use one of these classes:
|
||||||
|
- Use `std::unique_ptr` if the resource has a single owner.
|
||||||
|
- Use `std::shared_ptr` if the resource has multiple owners.
|
||||||
|
|
||||||
|
#### QObject classes
|
||||||
|
- Use the [object tree](https://doc.qt.io/qt-5/objecttrees.html#) to manage lifetime where possible. Objects are destroyed when their parent object is destroyed.
|
||||||
|
- If you have to explicitly delete an object use `variable->deleteLater()` instead of `delete variable`. This ensures that it will be deleted on the correct thread.
|
||||||
|
- If an object doesn't have a parent consider using `std::unique_ptr<Type, DeleteLater>` with `DeleteLater` from "src/common/Common.hpp". This will call `deleteLater()` on the pointer once it goes out of scope or the object is destroyed.
|
|
@ -33,7 +33,7 @@ git submodule update --init --recursive
|
||||||
[Building on Mac](../master/BUILDING_ON_MAC.md)
|
[Building on Mac](../master/BUILDING_ON_MAC.md)
|
||||||
|
|
||||||
## Code style
|
## Code style
|
||||||
The code is formatted using clang format in Qt Creator. [.clang-format](https://github.com/Chatterino/chatterino2/blob/master/.clang-format) contains the style file for clang format.
|
The code is formatted using clang format in Qt Creator. [.clang-format](src/.clang-format) contains the style file for clang format.
|
||||||
|
|
||||||
### Get it automated with QT Creator + Beautifier + Clang Format
|
### Get it automated with QT Creator + Beautifier + Clang Format
|
||||||
1. Download LLVM: http://releases.llvm.org/6.0.1/LLVM-6.0.1-win64.exe
|
1. Download LLVM: http://releases.llvm.org/6.0.1/LLVM-6.0.1-win64.exe
|
||||||
|
@ -46,3 +46,5 @@ The code is formatted using clang format in Qt Creator. [.clang-format](https://
|
||||||
|
|
||||||
Qt creator should now format the documents when saving it.
|
Qt creator should now format the documents when saving it.
|
||||||
|
|
||||||
|
## Doxygen
|
||||||
|
Doxygen is used to generate project information daily and is available [here](https://doxygen.chatterino.com).
|
||||||
|
|
|
@ -17,7 +17,7 @@ install:
|
||||||
|
|
||||||
git submodule update --init --recursive
|
git submodule update --init --recursive
|
||||||
|
|
||||||
set QTDIR=C:\Qt\5.11\msvc2017_64
|
set QTDIR=C:\Qt\5.13\msvc2017_64
|
||||||
|
|
||||||
set PATH=%PATH%;%QTDIR%\bin
|
set PATH=%PATH%;%QTDIR%\bin
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,16 @@
|
||||||
|
# Exposed build flags:
|
||||||
|
# from lib/fmt.pri
|
||||||
|
# - FMT_PREFIX ($$PWD by default)
|
||||||
|
# - FMT_SYSTEM (1 = true) (Linux only, uses pkg-config)
|
||||||
|
# from lib/websocketpp.pri
|
||||||
|
# - WEBSOCKETPP_PREFIX ($$PWD by default)
|
||||||
|
# - WEBSOCKETPP_SYSTEM (1 = true) (unix only)
|
||||||
|
# from lib/rapidjson.pri
|
||||||
|
# - RAPIDJSON_PREFIX ($$PWD by default)
|
||||||
|
# - RAPIDJSON_SYSTEM (1 = true) (Linux only, uses pkg-config)
|
||||||
|
# from lib/boost.pri
|
||||||
|
# - BOOST_DIRECTORY (C:\local\boost\ by default) (Windows only)
|
||||||
|
|
||||||
QT += widgets core gui network multimedia svg concurrent
|
QT += widgets core gui network multimedia svg concurrent
|
||||||
CONFIG += communi
|
CONFIG += communi
|
||||||
COMMUNI += core model util
|
COMMUNI += core model util
|
||||||
|
@ -8,7 +21,6 @@ TEMPLATE = app
|
||||||
PRECOMPILED_HEADER = src/PrecompiledHeader.hpp
|
PRECOMPILED_HEADER = src/PrecompiledHeader.hpp
|
||||||
CONFIG += precompile_header
|
CONFIG += precompile_header
|
||||||
DEFINES += CHATTERINO
|
DEFINES += CHATTERINO
|
||||||
DEFINES += "AB_NAMESPACE=chatterino"
|
|
||||||
DEFINES += AB_CUSTOM_THEME
|
DEFINES += AB_CUSTOM_THEME
|
||||||
DEFINES += AB_CUSTOM_SETTINGS
|
DEFINES += AB_CUSTOM_SETTINGS
|
||||||
CONFIG += AB_NOT_STANDALONE
|
CONFIG += AB_NOT_STANDALONE
|
||||||
|
@ -20,12 +32,29 @@ useBreakpad {
|
||||||
}
|
}
|
||||||
|
|
||||||
# use C++17
|
# use C++17
|
||||||
|
CONFIG += c++17
|
||||||
|
|
||||||
|
# C++17 backwards compatability
|
||||||
win32-msvc* {
|
win32-msvc* {
|
||||||
QMAKE_CXXFLAGS += /std:c++17
|
QMAKE_CXXFLAGS += /std:c++17
|
||||||
} else {
|
} else {
|
||||||
QMAKE_CXXFLAGS += -std=c++17
|
QMAKE_CXXFLAGS += -std=c++17
|
||||||
}
|
}
|
||||||
|
|
||||||
|
linux {
|
||||||
|
LIBS += -lrt
|
||||||
|
QMAKE_LFLAGS += -lrt
|
||||||
|
|
||||||
|
# Enable linking libraries using PKGCONFIG += libraryname
|
||||||
|
CONFIG += link_pkgconfig
|
||||||
|
}
|
||||||
|
|
||||||
|
macx {
|
||||||
|
INCLUDEPATH += /usr/local/include
|
||||||
|
INCLUDEPATH += /usr/local/opt/openssl/include
|
||||||
|
LIBS += -L/usr/local/opt/openssl/lib
|
||||||
|
}
|
||||||
|
|
||||||
# https://bugreports.qt.io/browse/QTBUG-27018
|
# https://bugreports.qt.io/browse/QTBUG-27018
|
||||||
equals(QMAKE_CXX, "clang++")|equals(QMAKE_CXX, "g++") {
|
equals(QMAKE_CXX, "clang++")|equals(QMAKE_CXX, "g++") {
|
||||||
TARGET = bin/chatterino
|
TARGET = bin/chatterino
|
||||||
|
@ -39,9 +68,14 @@ macx {
|
||||||
LIBS += -L/usr/local/lib
|
LIBS += -L/usr/local/lib
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Set C_DEBUG if it's a debug build
|
||||||
|
CONFIG(debug, debug|release) {
|
||||||
|
DEFINES += C_DEBUG
|
||||||
|
DEFINES += QT_DEBUG
|
||||||
|
}
|
||||||
|
|
||||||
# Submodules
|
# Submodules
|
||||||
include(lib/warnings.pri)
|
include(lib/warnings.pri)
|
||||||
include(lib/appbase.pri)
|
|
||||||
include(lib/fmt.pri)
|
include(lib/fmt.pri)
|
||||||
include(lib/humanize.pri)
|
include(lib/humanize.pri)
|
||||||
include(lib/libcommuni.pri)
|
include(lib/libcommuni.pri)
|
||||||
|
@ -75,9 +109,13 @@ else{
|
||||||
SOURCES += \
|
SOURCES += \
|
||||||
src/Application.cpp \
|
src/Application.cpp \
|
||||||
src/autogenerated/ResourcesAutogen.cpp \
|
src/autogenerated/ResourcesAutogen.cpp \
|
||||||
|
src/BaseSettings.cpp \
|
||||||
|
src/BaseTheme.cpp \
|
||||||
src/BrowserExtension.cpp \
|
src/BrowserExtension.cpp \
|
||||||
|
src/common/Args.cpp \
|
||||||
src/common/Channel.cpp \
|
src/common/Channel.cpp \
|
||||||
src/common/ChannelChatters.cpp \
|
src/common/ChannelChatters.cpp \
|
||||||
|
src/common/ChatterinoSetting.cpp \
|
||||||
src/common/CompletionModel.cpp \
|
src/common/CompletionModel.cpp \
|
||||||
src/common/Credentials.cpp \
|
src/common/Credentials.cpp \
|
||||||
src/common/DownloadManager.cpp \
|
src/common/DownloadManager.cpp \
|
||||||
|
@ -112,6 +150,7 @@ SOURCES += \
|
||||||
src/controllers/taggedusers/TaggedUser.cpp \
|
src/controllers/taggedusers/TaggedUser.cpp \
|
||||||
src/controllers/taggedusers/TaggedUsersController.cpp \
|
src/controllers/taggedusers/TaggedUsersController.cpp \
|
||||||
src/controllers/taggedusers/TaggedUsersModel.cpp \
|
src/controllers/taggedusers/TaggedUsersModel.cpp \
|
||||||
|
src/debug/Benchmark.cpp \
|
||||||
src/main.cpp \
|
src/main.cpp \
|
||||||
src/messages/Emote.cpp \
|
src/messages/Emote.cpp \
|
||||||
src/messages/Image.cpp \
|
src/messages/Image.cpp \
|
||||||
|
@ -150,6 +189,7 @@ SOURCES += \
|
||||||
src/providers/twitch/TwitchAccount.cpp \
|
src/providers/twitch/TwitchAccount.cpp \
|
||||||
src/providers/twitch/TwitchAccountManager.cpp \
|
src/providers/twitch/TwitchAccountManager.cpp \
|
||||||
src/providers/twitch/TwitchApi.cpp \
|
src/providers/twitch/TwitchApi.cpp \
|
||||||
|
src/providers/twitch/TwitchBadge.cpp \
|
||||||
src/providers/twitch/TwitchBadges.cpp \
|
src/providers/twitch/TwitchBadges.cpp \
|
||||||
src/providers/twitch/TwitchChannel.cpp \
|
src/providers/twitch/TwitchChannel.cpp \
|
||||||
src/providers/twitch/TwitchEmotes.cpp \
|
src/providers/twitch/TwitchEmotes.cpp \
|
||||||
|
@ -161,6 +201,7 @@ SOURCES += \
|
||||||
src/RunGui.cpp \
|
src/RunGui.cpp \
|
||||||
src/singletons/Badges.cpp \
|
src/singletons/Badges.cpp \
|
||||||
src/singletons/Emotes.cpp \
|
src/singletons/Emotes.cpp \
|
||||||
|
src/singletons/Fonts.cpp \
|
||||||
src/singletons/helper/GifTimer.cpp \
|
src/singletons/helper/GifTimer.cpp \
|
||||||
src/singletons/helper/LoggingChannel.cpp \
|
src/singletons/helper/LoggingChannel.cpp \
|
||||||
src/singletons/Logging.cpp \
|
src/singletons/Logging.cpp \
|
||||||
|
@ -175,15 +216,22 @@ SOURCES += \
|
||||||
src/singletons/WindowManager.cpp \
|
src/singletons/WindowManager.cpp \
|
||||||
src/util/DebugCount.cpp \
|
src/util/DebugCount.cpp \
|
||||||
src/util/FormatTime.cpp \
|
src/util/FormatTime.cpp \
|
||||||
|
src/util/FunctionEventFilter.cpp \
|
||||||
|
src/util/FuzzyConvert.cpp \
|
||||||
|
src/util/Helpers.cpp \
|
||||||
src/util/IncognitoBrowser.cpp \
|
src/util/IncognitoBrowser.cpp \
|
||||||
src/util/InitUpdateButton.cpp \
|
src/util/InitUpdateButton.cpp \
|
||||||
src/util/JsonQuery.cpp \
|
src/util/JsonQuery.cpp \
|
||||||
src/util/RapidjsonHelpers.cpp \
|
src/util/RapidjsonHelpers.cpp \
|
||||||
src/util/StreamLink.cpp \
|
src/util/StreamLink.cpp \
|
||||||
src/util/NuulsUploader.cpp \
|
src/util/NuulsUploader.cpp \
|
||||||
|
src/util/WindowsHelper.cpp \
|
||||||
src/widgets/AccountSwitchPopup.cpp \
|
src/widgets/AccountSwitchPopup.cpp \
|
||||||
src/widgets/AccountSwitchWidget.cpp \
|
src/widgets/AccountSwitchWidget.cpp \
|
||||||
src/widgets/AttachedWindow.cpp \
|
src/widgets/AttachedWindow.cpp \
|
||||||
|
src/widgets/BasePopup.cpp \
|
||||||
|
src/widgets/BaseWidget.cpp \
|
||||||
|
src/widgets/BaseWindow.cpp \
|
||||||
src/widgets/dialogs/EmotePopup.cpp \
|
src/widgets/dialogs/EmotePopup.cpp \
|
||||||
src/widgets/dialogs/IrcConnectionEditor.cpp \
|
src/widgets/dialogs/IrcConnectionEditor.cpp \
|
||||||
src/widgets/dialogs/LastRunCrashDialog.cpp \
|
src/widgets/dialogs/LastRunCrashDialog.cpp \
|
||||||
|
@ -197,16 +245,21 @@ SOURCES += \
|
||||||
src/widgets/dialogs/UpdateDialog.cpp \
|
src/widgets/dialogs/UpdateDialog.cpp \
|
||||||
src/widgets/dialogs/UserInfoPopup.cpp \
|
src/widgets/dialogs/UserInfoPopup.cpp \
|
||||||
src/widgets/dialogs/WelcomeDialog.cpp \
|
src/widgets/dialogs/WelcomeDialog.cpp \
|
||||||
|
src/widgets/helper/Button.cpp \
|
||||||
src/widgets/helper/ChannelView.cpp \
|
src/widgets/helper/ChannelView.cpp \
|
||||||
src/widgets/helper/ComboBoxItemDelegate.cpp \
|
src/widgets/helper/ComboBoxItemDelegate.cpp \
|
||||||
src/widgets/helper/DebugPopup.cpp \
|
src/widgets/helper/DebugPopup.cpp \
|
||||||
src/widgets/helper/EditableModelView.cpp \
|
src/widgets/helper/EditableModelView.cpp \
|
||||||
|
src/widgets/helper/EffectLabel.cpp \
|
||||||
src/widgets/helper/NotebookButton.cpp \
|
src/widgets/helper/NotebookButton.cpp \
|
||||||
src/widgets/helper/NotebookTab.cpp \
|
src/widgets/helper/NotebookTab.cpp \
|
||||||
src/widgets/helper/ResizingTextEdit.cpp \
|
src/widgets/helper/ResizingTextEdit.cpp \
|
||||||
src/widgets/helper/ScrollbarHighlight.cpp \
|
src/widgets/helper/ScrollbarHighlight.cpp \
|
||||||
src/widgets/helper/SearchPopup.cpp \
|
src/widgets/helper/SearchPopup.cpp \
|
||||||
src/widgets/helper/SettingsDialogTab.cpp \
|
src/widgets/helper/SettingsDialogTab.cpp \
|
||||||
|
src/widgets/helper/SignalLabel.cpp \
|
||||||
|
src/widgets/helper/TitlebarButton.cpp \
|
||||||
|
src/widgets/Label.cpp \
|
||||||
src/widgets/Notebook.cpp \
|
src/widgets/Notebook.cpp \
|
||||||
src/widgets/Scrollbar.cpp \
|
src/widgets/Scrollbar.cpp \
|
||||||
src/widgets/settingspages/AboutPage.cpp \
|
src/widgets/settingspages/AboutPage.cpp \
|
||||||
|
@ -227,22 +280,28 @@ SOURCES += \
|
||||||
src/widgets/splits/SplitInput.cpp \
|
src/widgets/splits/SplitInput.cpp \
|
||||||
src/widgets/splits/SplitOverlay.cpp \
|
src/widgets/splits/SplitOverlay.cpp \
|
||||||
src/widgets/StreamView.cpp \
|
src/widgets/StreamView.cpp \
|
||||||
|
src/widgets/TooltipWidget.cpp \
|
||||||
src/widgets/Window.cpp \
|
src/widgets/Window.cpp \
|
||||||
|
|
||||||
HEADERS += \
|
HEADERS += \
|
||||||
src/Application.hpp \
|
src/Application.hpp \
|
||||||
src/autogenerated/ResourcesAutogen.hpp \
|
src/autogenerated/ResourcesAutogen.hpp \
|
||||||
|
src/BaseSettings.hpp \
|
||||||
|
src/BaseTheme.hpp \
|
||||||
src/BrowserExtension.hpp \
|
src/BrowserExtension.hpp \
|
||||||
src/common/Aliases.hpp \
|
src/common/Aliases.hpp \
|
||||||
|
src/common/Args.hpp \
|
||||||
src/common/Atomic.hpp \
|
src/common/Atomic.hpp \
|
||||||
src/common/Channel.hpp \
|
src/common/Channel.hpp \
|
||||||
src/common/ChannelChatters.hpp \
|
src/common/ChannelChatters.hpp \
|
||||||
|
src/common/ChatterinoSetting.hpp \
|
||||||
src/common/Common.hpp \
|
src/common/Common.hpp \
|
||||||
src/common/CompletionModel.hpp \
|
src/common/CompletionModel.hpp \
|
||||||
src/common/ConcurrentMap.hpp \
|
src/common/ConcurrentMap.hpp \
|
||||||
src/common/Credentials.hpp \
|
src/common/Credentials.hpp \
|
||||||
src/common/DownloadManager.hpp \
|
src/common/DownloadManager.hpp \
|
||||||
src/common/Env.hpp \
|
src/common/Env.hpp \
|
||||||
|
src/common/FlagsEnum.hpp \
|
||||||
src/common/LinkParser.hpp \
|
src/common/LinkParser.hpp \
|
||||||
src/common/Modes.hpp \
|
src/common/Modes.hpp \
|
||||||
src/common/NetworkCommon.hpp \
|
src/common/NetworkCommon.hpp \
|
||||||
|
@ -251,9 +310,11 @@ HEADERS += \
|
||||||
src/common/NetworkRequest.hpp \
|
src/common/NetworkRequest.hpp \
|
||||||
src/common/NetworkResult.hpp \
|
src/common/NetworkResult.hpp \
|
||||||
src/common/NullablePtr.hpp \
|
src/common/NullablePtr.hpp \
|
||||||
|
src/common/Outcome.hpp \
|
||||||
src/common/ProviderId.hpp \
|
src/common/ProviderId.hpp \
|
||||||
src/common/SignalVector.hpp \
|
src/common/SignalVector.hpp \
|
||||||
src/common/SignalVectorModel.hpp \
|
src/common/SignalVectorModel.hpp \
|
||||||
|
src/common/Singleton.hpp \
|
||||||
src/common/UniqueAccess.hpp \
|
src/common/UniqueAccess.hpp \
|
||||||
src/common/UsernameSet.hpp \
|
src/common/UsernameSet.hpp \
|
||||||
src/common/Version.hpp \
|
src/common/Version.hpp \
|
||||||
|
@ -282,6 +343,9 @@ HEADERS += \
|
||||||
src/controllers/taggedusers/TaggedUser.hpp \
|
src/controllers/taggedusers/TaggedUser.hpp \
|
||||||
src/controllers/taggedusers/TaggedUsersController.hpp \
|
src/controllers/taggedusers/TaggedUsersController.hpp \
|
||||||
src/controllers/taggedusers/TaggedUsersModel.hpp \
|
src/controllers/taggedusers/TaggedUsersModel.hpp \
|
||||||
|
src/debug/AssertInGuiThread.hpp \
|
||||||
|
src/debug/Benchmark.hpp \
|
||||||
|
src/debug/Log.hpp \
|
||||||
src/ForwardDecl.hpp \
|
src/ForwardDecl.hpp \
|
||||||
src/messages/Emote.hpp \
|
src/messages/Emote.hpp \
|
||||||
src/messages/Image.hpp \
|
src/messages/Image.hpp \
|
||||||
|
@ -327,6 +391,7 @@ HEADERS += \
|
||||||
src/providers/twitch/TwitchAccount.hpp \
|
src/providers/twitch/TwitchAccount.hpp \
|
||||||
src/providers/twitch/TwitchAccountManager.hpp \
|
src/providers/twitch/TwitchAccountManager.hpp \
|
||||||
src/providers/twitch/TwitchApi.hpp \
|
src/providers/twitch/TwitchApi.hpp \
|
||||||
|
src/providers/twitch/TwitchBadge.hpp \
|
||||||
src/providers/twitch/TwitchBadges.hpp \
|
src/providers/twitch/TwitchBadges.hpp \
|
||||||
src/providers/twitch/TwitchChannel.hpp \
|
src/providers/twitch/TwitchChannel.hpp \
|
||||||
src/providers/twitch/TwitchCommon.hpp \
|
src/providers/twitch/TwitchCommon.hpp \
|
||||||
|
@ -339,6 +404,7 @@ HEADERS += \
|
||||||
src/RunGui.hpp \
|
src/RunGui.hpp \
|
||||||
src/singletons/Badges.hpp \
|
src/singletons/Badges.hpp \
|
||||||
src/singletons/Emotes.hpp \
|
src/singletons/Emotes.hpp \
|
||||||
|
src/singletons/Fonts.hpp \
|
||||||
src/singletons/helper/GifTimer.hpp \
|
src/singletons/helper/GifTimer.hpp \
|
||||||
src/singletons/helper/LoggingChannel.hpp \
|
src/singletons/helper/LoggingChannel.hpp \
|
||||||
src/singletons/Logging.hpp \
|
src/singletons/Logging.hpp \
|
||||||
|
@ -351,29 +417,44 @@ HEADERS += \
|
||||||
src/singletons/TooltipPreviewImage.hpp \
|
src/singletons/TooltipPreviewImage.hpp \
|
||||||
src/singletons/Updates.hpp \
|
src/singletons/Updates.hpp \
|
||||||
src/singletons/WindowManager.hpp \
|
src/singletons/WindowManager.hpp \
|
||||||
|
src/util/Clamp.hpp \
|
||||||
|
src/util/CombinePath.hpp \
|
||||||
src/util/ConcurrentMap.hpp \
|
src/util/ConcurrentMap.hpp \
|
||||||
src/util/DebugCount.hpp \
|
src/util/DebugCount.hpp \
|
||||||
|
src/util/DistanceBetweenPoints.hpp \
|
||||||
src/util/FormatTime.hpp \
|
src/util/FormatTime.hpp \
|
||||||
|
src/util/FunctionEventFilter.hpp \
|
||||||
|
src/util/FuzzyConvert.hpp \
|
||||||
|
src/util/Helpers.hpp \
|
||||||
src/util/IncognitoBrowser.hpp \
|
src/util/IncognitoBrowser.hpp \
|
||||||
src/util/InitUpdateButton.hpp \
|
src/util/InitUpdateButton.hpp \
|
||||||
src/util/IrcHelpers.hpp \
|
src/util/IrcHelpers.hpp \
|
||||||
src/util/IsBigEndian.hpp \
|
src/util/IsBigEndian.hpp \
|
||||||
src/util/JsonQuery.hpp \
|
src/util/JsonQuery.hpp \
|
||||||
src/util/LayoutCreator.hpp \
|
src/util/LayoutCreator.hpp \
|
||||||
|
src/util/LayoutHelper.hpp \
|
||||||
src/util/Overloaded.hpp \
|
src/util/Overloaded.hpp \
|
||||||
|
src/util/PostToThread.hpp \
|
||||||
src/util/QObjectRef.hpp \
|
src/util/QObjectRef.hpp \
|
||||||
src/util/QStringHash.hpp \
|
src/util/QStringHash.hpp \
|
||||||
src/util/rangealgorithm.hpp \
|
src/util/rangealgorithm.hpp \
|
||||||
src/util/RapidjsonHelpers.hpp \
|
src/util/RapidjsonHelpers.hpp \
|
||||||
|
src/util/RapidJsonSerializeQString.hpp \
|
||||||
src/util/RemoveScrollAreaBackground.hpp \
|
src/util/RemoveScrollAreaBackground.hpp \
|
||||||
src/util/SampleCheerMessages.hpp \
|
src/util/SampleCheerMessages.hpp \
|
||||||
|
src/util/SampleLinks.hpp \
|
||||||
src/util/SharedPtrElementLess.hpp \
|
src/util/SharedPtrElementLess.hpp \
|
||||||
|
src/util/Shortcut.hpp \
|
||||||
src/util/StandardItemHelper.hpp \
|
src/util/StandardItemHelper.hpp \
|
||||||
src/util/StreamLink.hpp \
|
src/util/StreamLink.hpp \
|
||||||
src/util/NuulsUploader.hpp \
|
src/util/NuulsUploader.hpp \
|
||||||
|
src/util/WindowsHelper.hpp \
|
||||||
src/widgets/AccountSwitchPopup.hpp \
|
src/widgets/AccountSwitchPopup.hpp \
|
||||||
src/widgets/AccountSwitchWidget.hpp \
|
src/widgets/AccountSwitchWidget.hpp \
|
||||||
src/widgets/AttachedWindow.hpp \
|
src/widgets/AttachedWindow.hpp \
|
||||||
|
src/widgets/BasePopup.hpp \
|
||||||
|
src/widgets/BaseWidget.hpp \
|
||||||
|
src/widgets/BaseWindow.hpp \
|
||||||
src/widgets/dialogs/EmotePopup.hpp \
|
src/widgets/dialogs/EmotePopup.hpp \
|
||||||
src/widgets/dialogs/IrcConnectionEditor.hpp \
|
src/widgets/dialogs/IrcConnectionEditor.hpp \
|
||||||
src/widgets/dialogs/LastRunCrashDialog.hpp \
|
src/widgets/dialogs/LastRunCrashDialog.hpp \
|
||||||
|
@ -387,11 +468,13 @@ HEADERS += \
|
||||||
src/widgets/dialogs/UpdateDialog.hpp \
|
src/widgets/dialogs/UpdateDialog.hpp \
|
||||||
src/widgets/dialogs/UserInfoPopup.hpp \
|
src/widgets/dialogs/UserInfoPopup.hpp \
|
||||||
src/widgets/dialogs/WelcomeDialog.hpp \
|
src/widgets/dialogs/WelcomeDialog.hpp \
|
||||||
|
src/widgets/helper/Button.hpp \
|
||||||
src/widgets/helper/ChannelView.hpp \
|
src/widgets/helper/ChannelView.hpp \
|
||||||
src/widgets/helper/ComboBoxItemDelegate.hpp \
|
src/widgets/helper/ComboBoxItemDelegate.hpp \
|
||||||
src/widgets/helper/CommonTexts.hpp \
|
src/widgets/helper/CommonTexts.hpp \
|
||||||
src/widgets/helper/DebugPopup.hpp \
|
src/widgets/helper/DebugPopup.hpp \
|
||||||
src/widgets/helper/EditableModelView.hpp \
|
src/widgets/helper/EditableModelView.hpp \
|
||||||
|
src/widgets/helper/EffectLabel.hpp \
|
||||||
src/widgets/helper/Line.hpp \
|
src/widgets/helper/Line.hpp \
|
||||||
src/widgets/helper/NotebookButton.hpp \
|
src/widgets/helper/NotebookButton.hpp \
|
||||||
src/widgets/helper/NotebookTab.hpp \
|
src/widgets/helper/NotebookTab.hpp \
|
||||||
|
@ -399,6 +482,9 @@ HEADERS += \
|
||||||
src/widgets/helper/ScrollbarHighlight.hpp \
|
src/widgets/helper/ScrollbarHighlight.hpp \
|
||||||
src/widgets/helper/SearchPopup.hpp \
|
src/widgets/helper/SearchPopup.hpp \
|
||||||
src/widgets/helper/SettingsDialogTab.hpp \
|
src/widgets/helper/SettingsDialogTab.hpp \
|
||||||
|
src/widgets/helper/SignalLabel.hpp \
|
||||||
|
src/widgets/helper/TitlebarButton.hpp \
|
||||||
|
src/widgets/Label.hpp \
|
||||||
src/widgets/Notebook.hpp \
|
src/widgets/Notebook.hpp \
|
||||||
src/widgets/Scrollbar.hpp \
|
src/widgets/Scrollbar.hpp \
|
||||||
src/widgets/settingspages/AboutPage.hpp \
|
src/widgets/settingspages/AboutPage.hpp \
|
||||||
|
@ -419,6 +505,7 @@ HEADERS += \
|
||||||
src/widgets/splits/SplitInput.hpp \
|
src/widgets/splits/SplitInput.hpp \
|
||||||
src/widgets/splits/SplitOverlay.hpp \
|
src/widgets/splits/SplitOverlay.hpp \
|
||||||
src/widgets/StreamView.hpp \
|
src/widgets/StreamView.hpp \
|
||||||
|
src/widgets/TooltipWidget.hpp \
|
||||||
src/widgets/Window.hpp \
|
src/widgets/Window.hpp \
|
||||||
|
|
||||||
RESOURCES += \
|
RESOURCES += \
|
||||||
|
@ -441,13 +528,13 @@ linux:isEmpty(PREFIX) {
|
||||||
}
|
}
|
||||||
|
|
||||||
linux {
|
linux {
|
||||||
desktop.files = resources/chatterino.desktop
|
desktop.files = resources/com.chatterino.chatterino.desktop
|
||||||
desktop.path = $$PREFIX/share/applications
|
desktop.path = $$PREFIX/share/applications
|
||||||
|
|
||||||
build_icons.path = .
|
build_icons.path = .
|
||||||
build_icons.commands = @echo $$PWD && mkdir -p $$PWD/resources/linuxinstall/icons/hicolor/256x256 && cp $$PWD/resources/icon.png $$PWD/resources/linuxinstall/icons/hicolor/256x256/chatterino.png
|
build_icons.commands = @echo $$PWD && mkdir -p $$PWD/resources/linuxinstall/icons/hicolor/256x256 && cp $$PWD/resources/icon.png $$PWD/resources/linuxinstall/icons/hicolor/256x256/com.chatterino.chatterino.png
|
||||||
|
|
||||||
icon.files = $$PWD/resources/linuxinstall/icons/hicolor/256x256/chatterino.png
|
icon.files = $$PWD/resources/linuxinstall/icons/hicolor/256x256/com.chatterino.chatterino.png
|
||||||
icon.path = $$PREFIX/share/icons/hicolor/256x256/apps
|
icon.path = $$PREFIX/share/icons/hicolor/256x256/apps
|
||||||
|
|
||||||
target.path = $$PREFIX/bin
|
target.path = $$PREFIX/bin
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
[requires]
|
[requires]
|
||||||
OpenSSL/1.0.2o@conan/stable
|
openssl/1.1.1d
|
||||||
boost/1.69.0@conan/stable
|
boost/1.71.0
|
||||||
|
|
||||||
[generators]
|
[generators]
|
||||||
qmake
|
qmake
|
||||||
|
|
||||||
[options]
|
[options]
|
||||||
OpenSSL:shared=True
|
openssl:shared=True
|
||||||
|
|
||||||
[imports]
|
[imports]
|
||||||
bin, *.dll -> ./Chatterino2 @ keep_path=False
|
bin, *.dll -> ./Chatterino2 @ keep_path=False
|
||||||
|
|
13
docs/ENV.md
13
docs/ENV.md
|
@ -30,3 +30,16 @@ Notes:
|
||||||
- If you want to host the images yourself. You need [Nuuls' filehost software](https://github.com/nuuls/fiehost)
|
- If you want to host the images yourself. You need [Nuuls' filehost software](https://github.com/nuuls/fiehost)
|
||||||
- Other image hosting software is currently not supported.
|
- Other image hosting software is currently not supported.
|
||||||
|
|
||||||
|
### CHATTERINO2_TWITCH_SERVER_HOST
|
||||||
|
String value used to change what Twitch chat server host to connect to.
|
||||||
|
Default value: `irc.chat.twitch.tv`
|
||||||
|
|
||||||
|
### CHATTERINO2_TWITCH_SERVER_PORT
|
||||||
|
Number value used to change what port to use when connecting to Twitch chat servers.
|
||||||
|
Currently known valid ports for secure usage: 6697, 443.
|
||||||
|
Currently known valid ports for non-secure usage (CHATTERINO2_TWITCH_SERVER_SECURE set to false): 6667, 80.
|
||||||
|
Default value: `443`
|
||||||
|
|
||||||
|
### CHATTERINO2_TWITCH_SERVER_SECURE
|
||||||
|
Bool value used to tell Chatterino whether to try to connect securely (secure irc) to the Twitch chat server.
|
||||||
|
Default value: `true`
|
|
@ -1,3 +0,0 @@
|
||||||
# include appbase
|
|
||||||
include($$PWD/appbase/main.pro)
|
|
||||||
INCLUDEPATH += $$PWD/appbase
|
|
|
@ -1,34 +0,0 @@
|
||||||
Language: Cpp
|
|
||||||
|
|
||||||
AccessModifierOffset: -1
|
|
||||||
AccessModifierOffset: -4
|
|
||||||
AlignEscapedNewlinesLeft: true
|
|
||||||
AllowShortFunctionsOnASingleLine: false
|
|
||||||
AllowShortIfStatementsOnASingleLine: false
|
|
||||||
AllowShortLoopsOnASingleLine: false
|
|
||||||
AlwaysBreakAfterDefinitionReturnType: false
|
|
||||||
AlwaysBreakBeforeMultilineStrings: false
|
|
||||||
BasedOnStyle: Google
|
|
||||||
BraceWrapping: {
|
|
||||||
AfterNamespace: 'false'
|
|
||||||
AfterClass: 'true'
|
|
||||||
BeforeElse: 'true'
|
|
||||||
AfterControlStatement: 'true'
|
|
||||||
AfterFunction: 'true'
|
|
||||||
BeforeCatch: 'true'
|
|
||||||
}
|
|
||||||
BreakBeforeBraces: Custom
|
|
||||||
BreakConstructorInitializersBeforeComma: true
|
|
||||||
ColumnLimit: 80
|
|
||||||
ConstructorInitializerAllOnOneLineOrOnePerLine: false
|
|
||||||
DerivePointerBinding: false
|
|
||||||
FixNamespaceComments: true
|
|
||||||
IndentCaseLabels: true
|
|
||||||
IndentWidth: 4
|
|
||||||
IndentWrappedFunctionNames: true
|
|
||||||
IndentPPDirectives: AfterHash
|
|
||||||
NamespaceIndentation: Inner
|
|
||||||
PointerBindsToType: false
|
|
||||||
SpacesBeforeTrailingComments: 2
|
|
||||||
Standard: Auto
|
|
||||||
ReflowComments: false
|
|
|
@ -1,36 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <QHash>
|
|
||||||
#include <QString>
|
|
||||||
#include <functional>
|
|
||||||
|
|
||||||
#define QStringAlias(name) \
|
|
||||||
namespace chatterino { \
|
|
||||||
struct name { \
|
|
||||||
QString string; \
|
|
||||||
bool operator==(const name &other) const \
|
|
||||||
{ \
|
|
||||||
return this->string == other.string; \
|
|
||||||
} \
|
|
||||||
bool operator!=(const name &other) const \
|
|
||||||
{ \
|
|
||||||
return this->string != other.string; \
|
|
||||||
} \
|
|
||||||
}; \
|
|
||||||
} /* namespace chatterino */ \
|
|
||||||
namespace std { \
|
|
||||||
template <> \
|
|
||||||
struct hash<chatterino::name> { \
|
|
||||||
size_t operator()(const chatterino::name &s) const \
|
|
||||||
{ \
|
|
||||||
return qHash(s.string); \
|
|
||||||
} \
|
|
||||||
}; \
|
|
||||||
} /* namespace std */
|
|
||||||
|
|
||||||
QStringAlias(UserName);
|
|
||||||
QStringAlias(UserId);
|
|
||||||
QStringAlias(Url);
|
|
||||||
QStringAlias(Tooltip);
|
|
||||||
QStringAlias(EmoteId);
|
|
||||||
QStringAlias(EmoteName);
|
|
|
@ -1,31 +0,0 @@
|
||||||
#include <QApplication>
|
|
||||||
#include <QDebug>
|
|
||||||
#include <QDir>
|
|
||||||
#include <QStandardPaths>
|
|
||||||
|
|
||||||
#include "ABSettings.hpp"
|
|
||||||
#include "ABTheme.hpp"
|
|
||||||
#include "singletons/Fonts.hpp"
|
|
||||||
#include "widgets/BaseWindow.hpp"
|
|
||||||
|
|
||||||
int main(int argc, char *argv[])
|
|
||||||
{
|
|
||||||
using namespace AB_NAMESPACE;
|
|
||||||
|
|
||||||
QApplication a(argc, argv);
|
|
||||||
|
|
||||||
auto path =
|
|
||||||
QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
|
|
||||||
qDebug() << path;
|
|
||||||
|
|
||||||
QDir(path).mkdir(".");
|
|
||||||
|
|
||||||
new Settings(path);
|
|
||||||
new Fonts();
|
|
||||||
|
|
||||||
BaseWindow widget(nullptr, BaseWindow::EnableCustomFrame);
|
|
||||||
widget.setWindowTitle("asdf");
|
|
||||||
widget.show();
|
|
||||||
|
|
||||||
return a.exec();
|
|
||||||
}
|
|
|
@ -1,100 +0,0 @@
|
||||||
#-------------------------------------------------
|
|
||||||
#
|
|
||||||
# Project created by QtCreator 2018-11-19T19:03:22
|
|
||||||
#
|
|
||||||
#-------------------------------------------------
|
|
||||||
|
|
||||||
!AB_NOT_STANDALONE {
|
|
||||||
message(appbase standalone)
|
|
||||||
QT += core gui widgets
|
|
||||||
TARGET = main
|
|
||||||
TEMPLATE = app
|
|
||||||
SOURCES += main.cpp
|
|
||||||
|
|
||||||
# https://bugreports.qt.io/browse/QTBUG-27018
|
|
||||||
equals(QMAKE_CXX, "clang++")|equals(QMAKE_CXX, "g++") {
|
|
||||||
TARGET = bin/appbase
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#DEFINES += QT_DEPRECATED_WARNINGS
|
|
||||||
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000
|
|
||||||
|
|
||||||
macx {
|
|
||||||
# osx (Tested on macOS Mojave and High Sierra)
|
|
||||||
CONFIG += c++17
|
|
||||||
} else {
|
|
||||||
CONFIG += c++17
|
|
||||||
win32-msvc* {
|
|
||||||
# win32 msvc
|
|
||||||
QMAKE_CXXFLAGS += /std:c++17
|
|
||||||
} else {
|
|
||||||
# clang/gcc on linux or win32
|
|
||||||
QMAKE_CXXFLAGS += -std=c++17
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
debug {
|
|
||||||
DEFINES += QT_DEBUG
|
|
||||||
}
|
|
||||||
|
|
||||||
linux {
|
|
||||||
LIBS += -lrt
|
|
||||||
QMAKE_LFLAGS += -lrt
|
|
||||||
}
|
|
||||||
|
|
||||||
macx {
|
|
||||||
INCLUDEPATH += /usr/local/include
|
|
||||||
INCLUDEPATH += /usr/local/opt/openssl/include
|
|
||||||
LIBS += -L/usr/local/opt/openssl/lib
|
|
||||||
}
|
|
||||||
|
|
||||||
SOURCES += \
|
|
||||||
$$PWD/BaseSettings.cpp \
|
|
||||||
$$PWD/BaseTheme.cpp \
|
|
||||||
$$PWD/common/ChatterinoSetting.cpp \
|
|
||||||
$$PWD/debug/Benchmark.cpp \
|
|
||||||
$$PWD/singletons/Fonts.cpp \
|
|
||||||
$$PWD/util/FunctionEventFilter.cpp \
|
|
||||||
$$PWD/util/FuzzyConvert.cpp \
|
|
||||||
$$PWD/util/Helpers.cpp \
|
|
||||||
$$PWD/util/WindowsHelper.cpp \
|
|
||||||
$$PWD/widgets/BaseWidget.cpp \
|
|
||||||
$$PWD/widgets/BaseWindow.cpp \
|
|
||||||
$$PWD/widgets/Label.cpp \
|
|
||||||
$$PWD/widgets/TooltipWidget.cpp \
|
|
||||||
$$PWD/widgets/helper/Button.cpp \
|
|
||||||
$$PWD/widgets/helper/EffectLabel.cpp \
|
|
||||||
$$PWD/widgets/helper/SignalLabel.cpp \
|
|
||||||
$$PWD/widgets/helper/TitlebarButton.cpp \
|
|
||||||
|
|
||||||
HEADERS += \
|
|
||||||
$$PWD/BaseSettings.hpp \
|
|
||||||
$$PWD/BaseTheme.hpp \
|
|
||||||
$$PWD/common/ChatterinoSetting.hpp \
|
|
||||||
$$PWD/common/FlagsEnum.hpp \
|
|
||||||
$$PWD/common/Outcome.hpp \
|
|
||||||
$$PWD/common/Singleton.hpp \
|
|
||||||
$$PWD/debug/AssertInGuiThread.hpp \
|
|
||||||
$$PWD/debug/Benchmark.hpp \
|
|
||||||
$$PWD/debug/Log.hpp \
|
|
||||||
$$PWD/singletons/Fonts.hpp \
|
|
||||||
$$PWD/util/Clamp.hpp \
|
|
||||||
$$PWD/util/CombinePath.hpp \
|
|
||||||
$$PWD/util/DistanceBetweenPoints.hpp \
|
|
||||||
$$PWD/util/FunctionEventFilter.hpp \
|
|
||||||
$$PWD/util/FuzzyConvert.hpp \
|
|
||||||
$$PWD/util/Helpers.hpp \
|
|
||||||
$$PWD/util/LayoutHelper.hpp \
|
|
||||||
$$PWD/util/PostToThread.hpp \
|
|
||||||
$$PWD/util/RapidJsonSerializeQString.hpp \
|
|
||||||
$$PWD/util/Shortcut.hpp \
|
|
||||||
$$PWD/util/WindowsHelper.hpp \
|
|
||||||
$$PWD/widgets/BaseWidget.hpp \
|
|
||||||
$$PWD/widgets/BaseWindow.hpp \
|
|
||||||
$$PWD/widgets/Label.hpp \
|
|
||||||
$$PWD/widgets/TooltipWidget.hpp \
|
|
||||||
$$PWD/widgets/helper/Button.hpp \
|
|
||||||
$$PWD/widgets/helper/EffectLabel.hpp \
|
|
||||||
$$PWD/widgets/helper/SignalLabel.hpp \
|
|
||||||
$$PWD/widgets/helper/TitlebarButton.hpp \
|
|
|
@ -1,3 +1,7 @@
|
||||||
|
# boost
|
||||||
|
# Exposed build flags:
|
||||||
|
# - BOOST_DIRECTORY (C:\local\boost\ by default) (Windows only)
|
||||||
|
|
||||||
pajlada {
|
pajlada {
|
||||||
BOOST_DIRECTORY = C:\dev\projects\boost_1_66_0\
|
BOOST_DIRECTORY = C:\dev\projects\boost_1_66_0\
|
||||||
}
|
}
|
||||||
|
|
16
lib/fmt.pri
16
lib/fmt.pri
|
@ -1,4 +1,18 @@
|
||||||
# fmt
|
# fmt
|
||||||
SOURCES += $$PWD/fmt/fmt/format.cpp
|
# Chatterino2 is tested with FMT 4.0
|
||||||
|
# Exposed build flags:
|
||||||
|
# - FMT_PREFIX ($$PWD by default)
|
||||||
|
# - FMT_SYSTEM (1 = true) (Linux only, uses pkg-config)
|
||||||
|
|
||||||
|
!defined(FMT_PREFIX) {
|
||||||
|
FMT_PREFIX = $$PWD
|
||||||
|
}
|
||||||
|
|
||||||
|
linux:equals(FMT_SYSTEM, "1") {
|
||||||
|
message("Building with system FMT")
|
||||||
|
PKGCONFIG += fmt
|
||||||
|
} else {
|
||||||
|
SOURCES += $$FMT_PREFIX/fmt/fmt/format.cpp
|
||||||
|
|
||||||
INCLUDEPATH += $$PWD/fmt/
|
INCLUDEPATH += $$PWD/fmt/
|
||||||
|
}
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
Subproject commit a31ffb037eadac65dba73ad2b2da6dafe31e3bf7
|
Subproject commit f3e7f97914d9bf1166d349a83d93a2b4f4743c39
|
|
@ -1,2 +1,15 @@
|
||||||
# rapidjson
|
# rapidjson
|
||||||
INCLUDEPATH += $$PWD/rapidjson/include/
|
# Chatterino2 is tested with RapidJSON v1.1.0
|
||||||
|
# - RAPIDJSON_PREFIX ($$PWD by default)
|
||||||
|
# - RAPIDJSON_SYSTEM (1 = true) (Linux only, uses pkg-config)
|
||||||
|
|
||||||
|
!defined(RAPIDJSON_PREFIX) {
|
||||||
|
RAPIDJSON_PREFIX = $$PWD
|
||||||
|
}
|
||||||
|
|
||||||
|
linux:equals(RAPIDJSON_SYSTEM, "1") {
|
||||||
|
message("Building with system RapidJSON")
|
||||||
|
PKGCONFIG += RapidJSON
|
||||||
|
} else {
|
||||||
|
INCLUDEPATH += $$RAPIDJSON_PREFIX/rapidjson/include/
|
||||||
|
}
|
||||||
|
|
|
@ -1 +1,20 @@
|
||||||
INCLUDEPATH += $$PWD/../lib/websocketpp
|
# websocketpp
|
||||||
|
# Chatterino2 is tested with websocketpp 0.8.1
|
||||||
|
# Exposed build flags:
|
||||||
|
# - WEBSOCKETPP_PREFIX ($$PWD by default)
|
||||||
|
# - WEBSOCKETPP_SYSTEM (1 = true) (unix only)
|
||||||
|
|
||||||
|
!defined(WEBSOCKETPP_PREFIX) {
|
||||||
|
WEBSOCKETPP_PREFIX = $$PWD
|
||||||
|
}
|
||||||
|
|
||||||
|
unix {
|
||||||
|
equals(WEBSOCKETPP_SYSTEM, "1") {
|
||||||
|
message("Building with system websocketpp")
|
||||||
|
} else {
|
||||||
|
message("Building with websocketpp submodule (Prefix: $$WEBSOCKETPP_PREFIX)")
|
||||||
|
INCLUDEPATH += $$WEBSOCKETPP_PREFIX/websocketpp/
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
INCLUDEPATH += $$WEBSOCKETPP_PREFIX/websocketpp
|
||||||
|
}
|
||||||
|
|
35
resources/com.chatterino.chatterino.appdata.xml
Normal file
35
resources/com.chatterino.chatterino.appdata.xml
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!-- 2019 Artem Polishchuk <ego.cordatus@gmail.com> -->
|
||||||
|
<component type="desktop-application">
|
||||||
|
<id>com.chatterino.chatterino.desktop</id>
|
||||||
|
<metadata_license>CC0-1.0</metadata_license>
|
||||||
|
<project_license>MIT</project_license>
|
||||||
|
<content_rating type="oars-1.0">
|
||||||
|
<content_attribute id="social-chat">intense</content_attribute>
|
||||||
|
</content_rating>
|
||||||
|
<name>Chatterino</name>
|
||||||
|
<summary>
|
||||||
|
Chat client for twitch.tv
|
||||||
|
</summary>
|
||||||
|
<description>
|
||||||
|
<p>
|
||||||
|
Chatterino 2 is the second installment of the Twitch chat client series
|
||||||
|
"Chatterino".
|
||||||
|
</p>
|
||||||
|
</description>
|
||||||
|
<screenshots>
|
||||||
|
<screenshot type="default">
|
||||||
|
<image>https://chatterino.com/img/screenshot-3.png</image>
|
||||||
|
</screenshot>
|
||||||
|
</screenshots>
|
||||||
|
<keywords>
|
||||||
|
<keyword>chat</keyword>
|
||||||
|
<keyword>twitch</keyword>
|
||||||
|
</keywords>
|
||||||
|
<url type="homepage">https://chatterino.com/</url>
|
||||||
|
<url type="bugtracker">https://github.com/Chatterino/chatterino2/issues</url>
|
||||||
|
<url type="donation">https://streamelements.com/fourtf/tip</url>
|
||||||
|
<provides>
|
||||||
|
<binary>chatterino</binary>
|
||||||
|
</provides>
|
||||||
|
</component>
|
|
@ -28,7 +28,7 @@
|
||||||
<file>buttons/unmod.png</file>
|
<file>buttons/unmod.png</file>
|
||||||
<file>buttons/update.png</file>
|
<file>buttons/update.png</file>
|
||||||
<file>buttons/updateError.png</file>
|
<file>buttons/updateError.png</file>
|
||||||
<file>chatterino.desktop</file>
|
<file>com.chatterino.chatterino.desktop</file>
|
||||||
<file>chatterino.icns</file>
|
<file>chatterino.icns</file>
|
||||||
<file>contributors.txt</file>
|
<file>contributors.txt</file>
|
||||||
<file>emoji.json</file>
|
<file>emoji.json</file>
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
Language: Cpp
|
Language: Cpp
|
||||||
|
|
||||||
AccessModifierOffset: -1
|
|
||||||
AccessModifierOffset: -4
|
AccessModifierOffset: -4
|
||||||
AlignEscapedNewlinesLeft: true
|
AlignEscapedNewlinesLeft: true
|
||||||
AllowShortFunctionsOnASingleLine: false
|
AllowShortFunctionsOnASingleLine: false
|
||||||
|
@ -10,12 +9,12 @@ AlwaysBreakAfterDefinitionReturnType: false
|
||||||
AlwaysBreakBeforeMultilineStrings: false
|
AlwaysBreakBeforeMultilineStrings: false
|
||||||
BasedOnStyle: Google
|
BasedOnStyle: Google
|
||||||
BraceWrapping: {
|
BraceWrapping: {
|
||||||
AfterNamespace: 'false'
|
|
||||||
AfterClass: 'true'
|
AfterClass: 'true'
|
||||||
BeforeElse: 'true'
|
|
||||||
AfterControlStatement: 'true'
|
AfterControlStatement: 'true'
|
||||||
AfterFunction: 'true'
|
AfterFunction: 'true'
|
||||||
|
AfterNamespace: 'false'
|
||||||
BeforeCatch: 'true'
|
BeforeCatch: 'true'
|
||||||
|
BeforeElse: 'true'
|
||||||
}
|
}
|
||||||
BreakBeforeBraces: Custom
|
BreakBeforeBraces: Custom
|
||||||
BreakConstructorInitializersBeforeComma: true
|
BreakConstructorInitializersBeforeComma: true
|
||||||
|
@ -27,6 +26,7 @@ IndentCaseLabels: true
|
||||||
IndentWidth: 4
|
IndentWidth: 4
|
||||||
IndentWrappedFunctionNames: true
|
IndentWrappedFunctionNames: true
|
||||||
IndentPPDirectives: AfterHash
|
IndentPPDirectives: AfterHash
|
||||||
|
IncludeBlocks: Preserve
|
||||||
NamespaceIndentation: Inner
|
NamespaceIndentation: Inner
|
||||||
PointerBindsToType: false
|
PointerBindsToType: false
|
||||||
SpacesBeforeTrailingComments: 2
|
SpacesBeforeTrailingComments: 2
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
#include "Application.hpp"
|
#include "Application.hpp"
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
|
|
||||||
|
#include "common/Args.hpp"
|
||||||
#include "controllers/accounts/AccountController.hpp"
|
#include "controllers/accounts/AccountController.hpp"
|
||||||
#include "controllers/commands/CommandController.hpp"
|
#include "controllers/commands/CommandController.hpp"
|
||||||
#include "controllers/highlights/HighlightController.hpp"
|
#include "controllers/highlights/HighlightController.hpp"
|
||||||
|
@ -29,9 +32,9 @@
|
||||||
#include "singletons/WindowManager.hpp"
|
#include "singletons/WindowManager.hpp"
|
||||||
#include "util/IsBigEndian.hpp"
|
#include "util/IsBigEndian.hpp"
|
||||||
#include "util/PostToThread.hpp"
|
#include "util/PostToThread.hpp"
|
||||||
|
#include "widgets/Notebook.hpp"
|
||||||
#include "widgets/Window.hpp"
|
#include "widgets/Window.hpp"
|
||||||
|
#include "widgets/splits/Split.hpp"
|
||||||
#include <atomic>
|
|
||||||
|
|
||||||
namespace chatterino {
|
namespace chatterino {
|
||||||
|
|
||||||
|
@ -44,9 +47,7 @@ Application *Application::instance = nullptr;
|
||||||
// to each other
|
// to each other
|
||||||
|
|
||||||
Application::Application(Settings &_settings, Paths &_paths)
|
Application::Application(Settings &_settings, Paths &_paths)
|
||||||
: resources(&this->emplace<Resources2>())
|
: themes(&this->emplace<Theme>())
|
||||||
|
|
||||||
, themes(&this->emplace<Theme>())
|
|
||||||
, fonts(&this->emplace<Fonts>())
|
, fonts(&this->emplace<Fonts>())
|
||||||
, emotes(&this->emplace<Emotes>())
|
, emotes(&this->emplace<Emotes>())
|
||||||
, windows(&this->emplace<WindowManager>())
|
, windows(&this->emplace<WindowManager>())
|
||||||
|
@ -79,13 +80,38 @@ void Application::initialize(Settings &settings, Paths &paths)
|
||||||
assert(isAppInitialized == false);
|
assert(isAppInitialized == false);
|
||||||
isAppInitialized = true;
|
isAppInitialized = true;
|
||||||
|
|
||||||
Irc::getInstance().load();
|
if (getSettings()->enableExperimentalIrc)
|
||||||
|
{
|
||||||
|
Irc::instance().load();
|
||||||
|
}
|
||||||
|
|
||||||
for (auto &singleton : this->singletons_)
|
for (auto &singleton : this->singletons_)
|
||||||
{
|
{
|
||||||
singleton->initialize(settings, paths);
|
singleton->initialize(settings, paths);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// add crash message
|
||||||
|
if (getArgs().crashRecovery)
|
||||||
|
{
|
||||||
|
if (auto selected =
|
||||||
|
this->windows->getMainWindow().getNotebook().getSelectedPage())
|
||||||
|
{
|
||||||
|
if (auto container = dynamic_cast<SplitContainer *>(selected))
|
||||||
|
{
|
||||||
|
for (auto &&split : container->getSplits())
|
||||||
|
{
|
||||||
|
if (auto channel = split->getChannel(); !channel->isEmpty())
|
||||||
|
{
|
||||||
|
channel->addMessage(makeSystemMessage(
|
||||||
|
"Chatterino unexpectedly crashed and restarted. "
|
||||||
|
"You can disable automatic restarts in the "
|
||||||
|
"settings."));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
this->windows->updateWordTypeMask();
|
this->windows->updateWordTypeMask();
|
||||||
|
|
||||||
this->initNm(paths);
|
this->initNm(paths);
|
||||||
|
@ -104,7 +130,7 @@ int Application::run(QApplication &qtApp)
|
||||||
this->windows->getMainWindow().show();
|
this->windows->getMainWindow().show();
|
||||||
|
|
||||||
getSettings()->betaUpdates.connect(
|
getSettings()->betaUpdates.connect(
|
||||||
[] { Updates::getInstance().checkForUpdates(); }, false);
|
[] { Updates::instance().checkForUpdates(); }, false);
|
||||||
|
|
||||||
return qtApp.exec();
|
return qtApp.exec();
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "common/Singleton.hpp"
|
|
||||||
#include "singletons/NativeMessaging.hpp"
|
|
||||||
|
|
||||||
#include <QApplication>
|
#include <QApplication>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
|
#include "common/Singleton.hpp"
|
||||||
|
#include "singletons/NativeMessaging.hpp"
|
||||||
|
|
||||||
namespace chatterino {
|
namespace chatterino {
|
||||||
|
|
||||||
class TwitchIrcServer;
|
class TwitchIrcServer;
|
||||||
|
@ -28,7 +28,6 @@ class AccountManager;
|
||||||
class Emotes;
|
class Emotes;
|
||||||
class Settings;
|
class Settings;
|
||||||
class Fonts;
|
class Fonts;
|
||||||
class Resources2;
|
|
||||||
class Toasts;
|
class Toasts;
|
||||||
class ChatterinoBadges;
|
class ChatterinoBadges;
|
||||||
|
|
||||||
|
@ -51,8 +50,6 @@ public:
|
||||||
|
|
||||||
friend void test();
|
friend void test();
|
||||||
|
|
||||||
Resources2 *const resources;
|
|
||||||
|
|
||||||
Theme *const themes{};
|
Theme *const themes{};
|
||||||
Fonts *const fonts{};
|
Fonts *const fonts{};
|
||||||
Emotes *const emotes{};
|
Emotes *const emotes{};
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
|
|
||||||
#include "util/Clamp.hpp"
|
#include "util/Clamp.hpp"
|
||||||
|
|
||||||
namespace AB_NAMESPACE {
|
namespace chatterino {
|
||||||
|
|
||||||
std::vector<std::weak_ptr<pajlada::Settings::SettingData>> _settings;
|
std::vector<std::weak_ptr<pajlada::Settings::SettingData>> _settings;
|
||||||
|
|
||||||
|
@ -125,4 +125,4 @@ AB_SETTINGS_CLASS *getABSettings()
|
||||||
return AB_SETTINGS_CLASS::instance;
|
return AB_SETTINGS_CLASS::instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace AB_NAMESPACE
|
} // namespace chatterino
|
|
@ -14,7 +14,7 @@
|
||||||
# define AB_SETTINGS_CLASS Settings
|
# define AB_SETTINGS_CLASS Settings
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
namespace AB_NAMESPACE {
|
namespace chatterino {
|
||||||
|
|
||||||
class Settings;
|
class Settings;
|
||||||
|
|
||||||
|
@ -44,7 +44,7 @@ private:
|
||||||
Settings *getSettings();
|
Settings *getSettings();
|
||||||
AB_SETTINGS_CLASS *getABSettings();
|
AB_SETTINGS_CLASS *getABSettings();
|
||||||
|
|
||||||
} // namespace AB_NAMESPACE
|
} // namespace chatterino
|
||||||
|
|
||||||
#ifdef CHATTERINO
|
#ifdef CHATTERINO
|
||||||
# include "singletons/Settings.hpp"
|
# include "singletons/Settings.hpp"
|
|
@ -1,6 +1,6 @@
|
||||||
#include "BaseTheme.hpp"
|
#include "BaseTheme.hpp"
|
||||||
|
|
||||||
namespace AB_NAMESPACE {
|
namespace chatterino {
|
||||||
namespace {
|
namespace {
|
||||||
double getMultiplierByTheme(const QString &themeName)
|
double getMultiplierByTheme(const QString &themeName)
|
||||||
{
|
{
|
||||||
|
@ -149,10 +149,7 @@ void AB_THEME_CLASS::actuallyUpdate(double hue, double multiplier)
|
||||||
// QColor("#777"), QColor("#666")}};
|
// QColor("#777"), QColor("#666")}};
|
||||||
|
|
||||||
this->tabs.bottomLine = this->tabs.selected.backgrounds.regular.color();
|
this->tabs.bottomLine = this->tabs.selected.backgrounds.regular.color();
|
||||||
} // namespace AB_NAMESPACE
|
}
|
||||||
|
|
||||||
// Split
|
|
||||||
bool flat = isLight_;
|
|
||||||
|
|
||||||
// Message
|
// Message
|
||||||
this->messages.textColors.link =
|
this->messages.textColors.link =
|
||||||
|
@ -222,4 +219,4 @@ Theme *getTheme()
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
} // namespace AB_NAMESPACE
|
} // namespace chatterino
|
|
@ -11,7 +11,7 @@
|
||||||
# define AB_THEME_CLASS Theme
|
# define AB_THEME_CLASS Theme
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
namespace AB_NAMESPACE {
|
namespace chatterino {
|
||||||
|
|
||||||
class Theme;
|
class Theme;
|
||||||
|
|
||||||
|
@ -109,7 +109,7 @@ private:
|
||||||
// Otherwise implemented in BaseThemecpp
|
// Otherwise implemented in BaseThemecpp
|
||||||
Theme *getTheme();
|
Theme *getTheme();
|
||||||
|
|
||||||
} // namespace AB_NAMESPACE
|
} // namespace chatterino
|
||||||
|
|
||||||
#ifdef CHATTERINO
|
#ifdef CHATTERINO
|
||||||
# include "singletons/Theme.hpp"
|
# include "singletons/Theme.hpp"
|
|
@ -4,10 +4,16 @@
|
||||||
#include <QFile>
|
#include <QFile>
|
||||||
#include <QPalette>
|
#include <QPalette>
|
||||||
#include <QStyleFactory>
|
#include <QStyleFactory>
|
||||||
|
#include <Qt>
|
||||||
|
#include <QtConcurrent>
|
||||||
|
#include <csignal>
|
||||||
|
|
||||||
#include "Application.hpp"
|
#include "Application.hpp"
|
||||||
|
#include "common/Modes.hpp"
|
||||||
#include "common/NetworkManager.hpp"
|
#include "common/NetworkManager.hpp"
|
||||||
#include "singletons/Paths.hpp"
|
#include "singletons/Paths.hpp"
|
||||||
|
#include "singletons/Resources.hpp"
|
||||||
|
#include "singletons/Settings.hpp"
|
||||||
#include "singletons/Updates.hpp"
|
#include "singletons/Updates.hpp"
|
||||||
#include "util/CombinePath.hpp"
|
#include "util/CombinePath.hpp"
|
||||||
#include "widgets/dialogs/LastRunCrashDialog.hpp"
|
#include "widgets/dialogs/LastRunCrashDialog.hpp"
|
||||||
|
@ -20,12 +26,6 @@
|
||||||
# include <QBreakpadHandler.h>
|
# include <QBreakpadHandler.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// void initQt();
|
|
||||||
// void installCustomPalette();
|
|
||||||
// void showLastCrashDialog();
|
|
||||||
// void createRunningFile(const QString &path);
|
|
||||||
// void removeRunningFile(const QString &path);
|
|
||||||
|
|
||||||
namespace chatterino {
|
namespace chatterino {
|
||||||
namespace {
|
namespace {
|
||||||
void installCustomPalette()
|
void installCustomPalette()
|
||||||
|
@ -108,11 +108,68 @@ namespace {
|
||||||
{
|
{
|
||||||
QFile::remove(path);
|
QFile::remove(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::chrono::steady_clock::time_point signalsInitTime;
|
||||||
|
bool restartOnSignal = false;
|
||||||
|
|
||||||
|
[[noreturn]] void handleSignal(int signum)
|
||||||
|
{
|
||||||
|
using namespace std::chrono_literals;
|
||||||
|
|
||||||
|
if (restartOnSignal &&
|
||||||
|
std::chrono::steady_clock::now() - signalsInitTime > 30s)
|
||||||
|
{
|
||||||
|
QProcess proc;
|
||||||
|
proc.setProgram(QApplication::applicationFilePath());
|
||||||
|
proc.setArguments({"--crash-recovery"});
|
||||||
|
proc.startDetached();
|
||||||
|
}
|
||||||
|
|
||||||
|
_exit(signum);
|
||||||
|
}
|
||||||
|
|
||||||
|
// We want to restart chatterino when it crashes and the setting is set to
|
||||||
|
// true.
|
||||||
|
void initSignalHandler()
|
||||||
|
{
|
||||||
|
#ifndef C_DEBUG
|
||||||
|
signalsInitTime = std::chrono::steady_clock::now();
|
||||||
|
|
||||||
|
signal(SIGSEGV, handleSignal);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
// We delete cache files that haven't been modified in 14 days. This strategy may be
|
||||||
|
// improved in the future.
|
||||||
|
void clearCache(const QDir &dir)
|
||||||
|
{
|
||||||
|
qDebug() << "[Cache] cleared cache";
|
||||||
|
|
||||||
|
QStringList toBeRemoved;
|
||||||
|
|
||||||
|
for (auto &&info : dir.entryInfoList(QDir::Files))
|
||||||
|
{
|
||||||
|
if (info.lastModified().addDays(14) < QDateTime::currentDateTime())
|
||||||
|
{
|
||||||
|
toBeRemoved << info.absoluteFilePath();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto &&path : toBeRemoved)
|
||||||
|
{
|
||||||
|
qDebug() << path << QFile(path).remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
void runGui(QApplication &a, Paths &paths, Settings &settings)
|
void runGui(QApplication &a, Paths &paths, Settings &settings)
|
||||||
{
|
{
|
||||||
initQt();
|
initQt();
|
||||||
|
initResources();
|
||||||
|
initSignalHandler();
|
||||||
|
|
||||||
|
settings.restartOnCrash.connect(
|
||||||
|
[](const bool &value) { restartOnSignal = value; });
|
||||||
|
|
||||||
auto thread = std::thread([dir = paths.miscDirectory] {
|
auto thread = std::thread([dir = paths.miscDirectory] {
|
||||||
{
|
{
|
||||||
|
@ -131,8 +188,13 @@ void runGui(QApplication &a, Paths &paths, Settings &settings)
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Clear the cache 1 minute after start.
|
||||||
|
QTimer::singleShot(60 * 1000, [cachePath = paths.cacheDirectory()] {
|
||||||
|
QtConcurrent::run([cachePath]() { clearCache(cachePath); });
|
||||||
|
});
|
||||||
|
|
||||||
chatterino::NetworkManager::init();
|
chatterino::NetworkManager::init();
|
||||||
chatterino::Updates::getInstance().checkForUpdates();
|
chatterino::Updates::instance().checkForUpdates();
|
||||||
|
|
||||||
#ifdef C_USE_BREAKPAD
|
#ifdef C_USE_BREAKPAD
|
||||||
QBreakpadInstance.setDumpPath(getPaths()->settingsFolderPath + "/Crashes");
|
QBreakpadInstance.setDumpPath(getPaths()->settingsFolderPath + "/Crashes");
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
#include <QPixmap>
|
#include <QPixmap>
|
||||||
|
|
||||||
#include "common/Singleton.hpp"
|
#include "common/Singleton.hpp"
|
||||||
|
|
||||||
namespace chatterino {
|
namespace chatterino {
|
||||||
|
|
34
src/common/Args.cpp
Normal file
34
src/common/Args.cpp
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
#include "Args.hpp"
|
||||||
|
|
||||||
|
namespace chatterino {
|
||||||
|
|
||||||
|
Args::Args(const QStringList &args)
|
||||||
|
{
|
||||||
|
for (auto &&arg : args)
|
||||||
|
{
|
||||||
|
if (arg == "--crash-recovery")
|
||||||
|
{
|
||||||
|
this->crashRecovery = true;
|
||||||
|
}
|
||||||
|
else if (arg == "--version")
|
||||||
|
{
|
||||||
|
this->printVersion = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static Args *instance = nullptr;
|
||||||
|
|
||||||
|
void initArgs(const QStringList &args)
|
||||||
|
{
|
||||||
|
instance = new Args(args);
|
||||||
|
}
|
||||||
|
|
||||||
|
const Args &getArgs()
|
||||||
|
{
|
||||||
|
assert(instance);
|
||||||
|
|
||||||
|
return *instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace chatterino
|
20
src/common/Args.hpp
Normal file
20
src/common/Args.hpp
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QStringList>
|
||||||
|
|
||||||
|
namespace chatterino {
|
||||||
|
|
||||||
|
/// Command line arguments passed to Chatterino.
|
||||||
|
class Args
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
Args(const QStringList &args);
|
||||||
|
|
||||||
|
bool printVersion{};
|
||||||
|
bool crashRecovery{};
|
||||||
|
};
|
||||||
|
|
||||||
|
void initArgs(const QStringList &args);
|
||||||
|
const Args &getArgs();
|
||||||
|
|
||||||
|
} // namespace chatterino
|
|
@ -178,7 +178,7 @@ void Channel::addOrReplaceTimeout(MessagePtr message)
|
||||||
}
|
}
|
||||||
|
|
||||||
// XXX: Might need the following line
|
// XXX: Might need the following line
|
||||||
// WindowManager::getInstance().repaintVisibleChatWidgets(this);
|
// WindowManager::instance().repaintVisibleChatWidgets(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Channel::disableAllMessages()
|
void Channel::disableAllMessages()
|
||||||
|
|
|
@ -2,11 +2,11 @@
|
||||||
|
|
||||||
#include "BaseSettings.hpp"
|
#include "BaseSettings.hpp"
|
||||||
|
|
||||||
namespace AB_NAMESPACE {
|
namespace chatterino {
|
||||||
|
|
||||||
void _registerSetting(std::weak_ptr<pajlada::Settings::SettingData> setting)
|
void _registerSetting(std::weak_ptr<pajlada::Settings::SettingData> setting)
|
||||||
{
|
{
|
||||||
_actuallyRegisterSetting(setting);
|
_actuallyRegisterSetting(setting);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace AB_NAMESPACE
|
} // namespace chatterino
|
|
@ -3,7 +3,7 @@
|
||||||
#include <QString>
|
#include <QString>
|
||||||
#include <pajlada/settings.hpp>
|
#include <pajlada/settings.hpp>
|
||||||
|
|
||||||
namespace AB_NAMESPACE {
|
namespace chatterino {
|
||||||
|
|
||||||
void _registerSetting(std::weak_ptr<pajlada::Settings::SettingData> setting);
|
void _registerSetting(std::weak_ptr<pajlada::Settings::SettingData> setting);
|
||||||
|
|
||||||
|
@ -85,4 +85,4 @@ public:
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace AB_NAMESPACE
|
} // namespace chatterino
|
|
@ -1,16 +1,15 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "common/Aliases.hpp"
|
|
||||||
#include "common/Outcome.hpp"
|
|
||||||
#include "common/ProviderId.hpp"
|
|
||||||
|
|
||||||
#include <QString>
|
#include <QString>
|
||||||
#include <QWidget>
|
#include <QWidget>
|
||||||
#include <boost/optional.hpp>
|
#include <boost/optional.hpp>
|
||||||
#include <boost/preprocessor.hpp>
|
#include <boost/preprocessor.hpp>
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
|
#include "common/Aliases.hpp"
|
||||||
|
#include "common/Outcome.hpp"
|
||||||
|
#include "common/ProviderId.hpp"
|
||||||
|
|
||||||
namespace chatterino {
|
namespace chatterino {
|
||||||
|
|
||||||
enum class HighlightState {
|
enum class HighlightState {
|
||||||
|
@ -46,4 +45,14 @@ enum class CopyMode {
|
||||||
OnlyTextAndEmotes,
|
OnlyTextAndEmotes,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct DeleteLater {
|
||||||
|
void operator()(QObject *obj)
|
||||||
|
{
|
||||||
|
obj->deleteLater();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
using QObjectPtr = std::unique_ptr<T, DeleteLater>;
|
||||||
|
|
||||||
} // namespace chatterino
|
} // namespace chatterino
|
||||||
|
|
|
@ -143,7 +143,7 @@ namespace {
|
||||||
}
|
}
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
Credentials &Credentials::getInstance()
|
Credentials &Credentials::instance()
|
||||||
{
|
{
|
||||||
static Credentials creds;
|
static Credentials creds;
|
||||||
return creds;
|
return creds;
|
||||||
|
@ -166,7 +166,8 @@ void Credentials::get(const QString &provider, const QString &name_,
|
||||||
auto job = new QKeychain::ReadPasswordJob("chatterino");
|
auto job = new QKeychain::ReadPasswordJob("chatterino");
|
||||||
job->setAutoDelete(true);
|
job->setAutoDelete(true);
|
||||||
job->setKey(name);
|
job->setKey(name);
|
||||||
QObject::connect(job, &QKeychain::Job::finished, receiver,
|
QObject::connect(
|
||||||
|
job, &QKeychain::Job::finished, receiver,
|
||||||
[job, onLoaded = std::move(onLoaded)](auto) mutable {
|
[job, onLoaded = std::move(onLoaded)](auto) mutable {
|
||||||
onLoaded(job->textData());
|
onLoaded(job->textData());
|
||||||
},
|
},
|
||||||
|
@ -199,7 +200,9 @@ void Credentials::set(const QString &provider, const QString &name_,
|
||||||
{
|
{
|
||||||
auto &instance = insecureInstance();
|
auto &instance = insecureInstance();
|
||||||
|
|
||||||
instance.object()[name] = credential;
|
auto obj = instance.object();
|
||||||
|
obj[name] = credential;
|
||||||
|
instance.setObject(obj);
|
||||||
|
|
||||||
queueInsecureSave();
|
queueInsecureSave();
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,7 @@ namespace chatterino {
|
||||||
class Credentials
|
class Credentials
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
static Credentials &getInstance();
|
static Credentials &instance();
|
||||||
|
|
||||||
void get(const QString &provider, const QString &name, QObject *receiver,
|
void get(const QString &provider, const QString &name, QObject *receiver,
|
||||||
std::function<void(const QString &)> &&onLoaded);
|
std::function<void(const QString &)> &&onLoaded);
|
||||||
|
|
|
@ -48,13 +48,11 @@ void DownloadManager::onFinished(QNetworkReply *reply)
|
||||||
{
|
{
|
||||||
switch (reply->error())
|
switch (reply->error())
|
||||||
{
|
{
|
||||||
case QNetworkReply::NoError:
|
case QNetworkReply::NoError: {
|
||||||
{
|
|
||||||
qDebug("file is downloaded successfully.");
|
qDebug("file is downloaded successfully.");
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default: {
|
||||||
{
|
|
||||||
qDebug() << reply->errorString().toLatin1();
|
qDebug() << reply->errorString().toLatin1();
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
#include "common/Env.hpp"
|
#include "common/Env.hpp"
|
||||||
|
|
||||||
|
#include <QVariant>
|
||||||
|
|
||||||
namespace chatterino {
|
namespace chatterino {
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
@ -15,6 +17,33 @@ namespace {
|
||||||
return defaultValue;
|
return defaultValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uint16_t readPortEnv(const char *envName, uint16_t defaultValue)
|
||||||
|
{
|
||||||
|
auto envString = std::getenv(envName);
|
||||||
|
if (envString != nullptr)
|
||||||
|
{
|
||||||
|
bool ok;
|
||||||
|
auto val = QString(envString).toUShort(&ok);
|
||||||
|
if (ok)
|
||||||
|
{
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t readBoolEnv(const char *envName, bool defaultValue)
|
||||||
|
{
|
||||||
|
auto envString = std::getenv(envName);
|
||||||
|
if (envString != nullptr)
|
||||||
|
{
|
||||||
|
return QVariant(QString(envString)).toBool();
|
||||||
|
}
|
||||||
|
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
Env::Env()
|
Env::Env()
|
||||||
|
@ -30,6 +59,10 @@ Env::Env()
|
||||||
"https://braize.pajlada.com/chatterino/twitchemotes/set/%1/"))
|
"https://braize.pajlada.com/chatterino/twitchemotes/set/%1/"))
|
||||||
, imageUploaderUrl(readStringEnv("CHATTERINO2_IMAGE_PASTE_SITE_URL",
|
, imageUploaderUrl(readStringEnv("CHATTERINO2_IMAGE_PASTE_SITE_URL",
|
||||||
"https://i.nuuls.com/upload"))
|
"https://i.nuuls.com/upload"))
|
||||||
|
, twitchServerHost(
|
||||||
|
readStringEnv("CHATTERINO2_TWITCH_SERVER_HOST", "irc.chat.twitch.tv"))
|
||||||
|
, twitchServerPort(readPortEnv("CHATTERINO2_TWITCH_SERVER_PORT", 6697))
|
||||||
|
, twitchServerSecure(readBoolEnv("CHATTERINO2_TWITCH_SERVER_SECURE", true))
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -15,6 +15,9 @@ public:
|
||||||
const QString linkResolverUrl;
|
const QString linkResolverUrl;
|
||||||
const QString twitchEmoteSetResolverUrl;
|
const QString twitchEmoteSetResolverUrl;
|
||||||
const QString imageUploaderUrl;
|
const QString imageUploaderUrl;
|
||||||
|
const QString twitchServerHost;
|
||||||
|
const uint16_t twitchServerPort;
|
||||||
|
const bool twitchServerSecure;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace chatterino
|
} // namespace chatterino
|
||||||
|
|
|
@ -1,67 +1,201 @@
|
||||||
#include "common/LinkParser.hpp"
|
#include "common/LinkParser.hpp"
|
||||||
|
|
||||||
#include <QFile>
|
#include <QFile>
|
||||||
|
#include <QMap>
|
||||||
#include <QRegularExpression>
|
#include <QRegularExpression>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
|
#include <QStringRef>
|
||||||
#include <QTextStream>
|
#include <QTextStream>
|
||||||
|
|
||||||
// ip 0.0.0.0 - 224.0.0.0
|
|
||||||
#define IP \
|
|
||||||
"(?:[1-9]\\d?|1\\d\\d|2[01]\\d|22[0-3])" \
|
|
||||||
"(?:\\.(?:1?\\d{1,2}|2[0-4]\\d|25[0-5])){2}" \
|
|
||||||
"(?:\\.(?:[1-9]\\d?|1\\d\\d|2[0-4]\\d|25[0-4]))"
|
|
||||||
#define PORT "(?::\\d{2,5})"
|
|
||||||
#define WEB_CHAR1 "[_a-z\\x{00a1}-\\x{ffff}0-9]"
|
|
||||||
#define WEB_CHAR2 "[a-z\\x{00a1}-\\x{ffff}0-9]"
|
|
||||||
|
|
||||||
#define SPOTIFY_1 "(?:artist|album|track|user:[^:]+:playlist):[a-zA-Z0-9]+"
|
|
||||||
#define SPOTIFY_2 "user:[^:]+"
|
|
||||||
#define SPOTIFY_3 "search:(?:[-\\w$\\.+!*'(),]+|%[a-fA-F0-9]{2})+"
|
|
||||||
#define SPOTIFY_PARAMS "(?:" SPOTIFY_1 "|" SPOTIFY_2 "|" SPOTIFY_3 ")"
|
|
||||||
#define SPOTIFY_LINK "(?x-mi:(spotify:" SPOTIFY_PARAMS "))"
|
|
||||||
|
|
||||||
#define WEB_PROTOCOL "(?:(?:https?|ftps?)://)?"
|
|
||||||
#define WEB_USER "(?:\\S+(?::\\S*)?@)?"
|
|
||||||
#define WEB_HOST "(?:(?:" WEB_CHAR1 "-*)*" WEB_CHAR2 "+)"
|
|
||||||
#define WEB_DOMAIN "(?:\\.(?:" WEB_CHAR2 "-*)*" WEB_CHAR2 "+)*"
|
|
||||||
#define WEB_TLD "(?:" + tldData + ")"
|
|
||||||
#define WEB_RESOURCE_PATH "(?:[/?#]\\S*)"
|
|
||||||
#define WEB_LINK \
|
|
||||||
WEB_PROTOCOL WEB_USER "(?:" IP "|" WEB_HOST WEB_DOMAIN "\\." WEB_TLD PORT \
|
|
||||||
"?" WEB_RESOURCE_PATH "?)"
|
|
||||||
|
|
||||||
#define LINK "^(?:" SPOTIFY_LINK "|" WEB_LINK ")$"
|
|
||||||
|
|
||||||
namespace chatterino {
|
namespace chatterino {
|
||||||
|
namespace {
|
||||||
|
QSet<QString> &tlds()
|
||||||
|
{
|
||||||
|
static QSet<QString> tlds = [] {
|
||||||
|
QFile file(":/tlds.txt");
|
||||||
|
file.open(QFile::ReadOnly);
|
||||||
|
QTextStream stream(&file);
|
||||||
|
stream.setCodec("UTF-8");
|
||||||
|
int safetyMax = 20000;
|
||||||
|
|
||||||
|
QSet<QString> set;
|
||||||
|
|
||||||
|
while (!stream.atEnd())
|
||||||
|
{
|
||||||
|
auto line = stream.readLine();
|
||||||
|
set.insert(line);
|
||||||
|
|
||||||
|
if (safetyMax-- == 0)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return set;
|
||||||
|
}();
|
||||||
|
return tlds;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isValidHostname(QStringRef &host)
|
||||||
|
{
|
||||||
|
int index = host.lastIndexOf('.');
|
||||||
|
|
||||||
|
return index != -1 &&
|
||||||
|
tlds().contains(host.mid(index + 1).toString().toLower());
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isValidIpv4(QStringRef &host)
|
||||||
|
{
|
||||||
|
static auto exp = QRegularExpression("^\\d{1,3}(?:\\.\\d{1,3}){3}$");
|
||||||
|
|
||||||
|
return exp.match(host).hasMatch();
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef C_MATCH_IPV6_LINK
|
||||||
|
bool isValidIpv6(QStringRef &host)
|
||||||
|
{
|
||||||
|
static auto exp = QRegularExpression("^\\[[a-fA-F0-9:%]+\\]$");
|
||||||
|
|
||||||
|
return exp.match(host).hasMatch();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
} // namespace
|
||||||
|
|
||||||
LinkParser::LinkParser(const QString &unparsedString)
|
LinkParser::LinkParser(const QString &unparsedString)
|
||||||
{
|
{
|
||||||
static QRegularExpression linkRegex = [] {
|
this->match_ = unparsedString;
|
||||||
static QRegularExpression newLineRegex("\r?\n");
|
|
||||||
QFile file(":/tlds.txt");
|
|
||||||
file.open(QFile::ReadOnly);
|
|
||||||
QTextStream tlds(&file);
|
|
||||||
tlds.setCodec("UTF-8");
|
|
||||||
|
|
||||||
// tldData gets injected into the LINK macro
|
// This is not implemented with a regex to increase performance.
|
||||||
auto tldData = tlds.readAll().replace(newLineRegex, "|");
|
// We keep removing parts of the url until there's either nothing left or we fail.
|
||||||
(void)tldData;
|
QStringRef l(&unparsedString);
|
||||||
|
|
||||||
return QRegularExpression(LINK,
|
bool hasHttp = false;
|
||||||
QRegularExpression::CaseInsensitiveOption);
|
|
||||||
}();
|
|
||||||
|
|
||||||
this->match_ = linkRegex.match(unparsedString);
|
// Protocol `https?://`
|
||||||
|
if (l.startsWith("https://", Qt::CaseInsensitive))
|
||||||
|
{
|
||||||
|
hasHttp = true;
|
||||||
|
l = l.mid(8);
|
||||||
|
}
|
||||||
|
else if (l.startsWith("http://", Qt::CaseInsensitive))
|
||||||
|
{
|
||||||
|
hasHttp = true;
|
||||||
|
l = l.mid(7);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Http basic auth `user:password`.
|
||||||
|
// Not supported for security reasons (misleading links)
|
||||||
|
|
||||||
|
// Host `a.b.c.com`
|
||||||
|
QStringRef host = l;
|
||||||
|
bool lastWasDot = true;
|
||||||
|
bool inIpv6 = false;
|
||||||
|
|
||||||
|
for (int i = 0; i < l.size(); i++)
|
||||||
|
{
|
||||||
|
if (l[i] == '.')
|
||||||
|
{
|
||||||
|
if (lastWasDot == true) // no double dots ..
|
||||||
|
goto error;
|
||||||
|
lastWasDot = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
lastWasDot = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (l[i] == ':' && !inIpv6)
|
||||||
|
{
|
||||||
|
host = l.mid(0, i);
|
||||||
|
l = l.mid(i + 1);
|
||||||
|
goto parsePort;
|
||||||
|
}
|
||||||
|
else if (l[i] == '/')
|
||||||
|
{
|
||||||
|
host = l.mid(0, i);
|
||||||
|
l = l.mid(i + 1);
|
||||||
|
goto parsePath;
|
||||||
|
}
|
||||||
|
else if (l[i] == '?')
|
||||||
|
{
|
||||||
|
host = l.mid(0, i);
|
||||||
|
l = l.mid(i + 1);
|
||||||
|
goto parseQuery;
|
||||||
|
}
|
||||||
|
else if (l[i] == '#')
|
||||||
|
{
|
||||||
|
host = l.mid(0, i);
|
||||||
|
l = l.mid(i + 1);
|
||||||
|
goto parseAnchor;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ipv6
|
||||||
|
if (l[i] == '[')
|
||||||
|
{
|
||||||
|
if (i == 0)
|
||||||
|
inIpv6 = true;
|
||||||
|
else
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
else if (l[i] == ']')
|
||||||
|
{
|
||||||
|
inIpv6 = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lastWasDot)
|
||||||
|
goto error;
|
||||||
|
else
|
||||||
|
goto done;
|
||||||
|
|
||||||
|
parsePort:
|
||||||
|
// Port `:12345`
|
||||||
|
for (int i = 0; i < std::min<int>(5, l.size()); i++)
|
||||||
|
{
|
||||||
|
if (l[i] == '/')
|
||||||
|
goto parsePath;
|
||||||
|
else if (l[i] == '?')
|
||||||
|
goto parseQuery;
|
||||||
|
else if (l[i] == '#')
|
||||||
|
goto parseAnchor;
|
||||||
|
|
||||||
|
if (!l[i].isDigit())
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
|
goto done;
|
||||||
|
|
||||||
|
parsePath:
|
||||||
|
parseQuery:
|
||||||
|
parseAnchor:
|
||||||
|
// we accept everything in the path/query/anchor
|
||||||
|
|
||||||
|
done:
|
||||||
|
// check host
|
||||||
|
this->hasMatch_ = isValidHostname(host) || isValidIpv4(host)
|
||||||
|
#ifdef C_MATCH_IPV6_LINK
|
||||||
|
|
||||||
|
|| (hasHttp && isValidIpv6(host))
|
||||||
|
#endif
|
||||||
|
;
|
||||||
|
|
||||||
|
if (this->hasMatch_)
|
||||||
|
{
|
||||||
|
this->match_ = unparsedString;
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
|
||||||
|
error:
|
||||||
|
hasMatch_ = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool LinkParser::hasMatch() const
|
bool LinkParser::hasMatch() const
|
||||||
{
|
{
|
||||||
return this->match_.hasMatch();
|
return this->hasMatch_;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString LinkParser::getCaptured() const
|
QString LinkParser::getCaptured() const
|
||||||
{
|
{
|
||||||
return this->match_.captured();
|
return this->match_;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace chatterino
|
} // namespace chatterino
|
||||||
|
|
|
@ -14,7 +14,8 @@ public:
|
||||||
QString getCaptured() const;
|
QString getCaptured() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QRegularExpressionMatch match_;
|
bool hasMatch_{false};
|
||||||
|
QString match_;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace chatterino
|
} // namespace chatterino
|
||||||
|
|
|
@ -27,7 +27,7 @@ Modes::Modes()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const Modes &Modes::getInstance()
|
const Modes &Modes::instance()
|
||||||
{
|
{
|
||||||
static Modes instance;
|
static Modes instance;
|
||||||
return instance;
|
return instance;
|
||||||
|
|
|
@ -7,7 +7,7 @@ class Modes
|
||||||
public:
|
public:
|
||||||
Modes();
|
Modes();
|
||||||
|
|
||||||
static const Modes &getInstance();
|
static const Modes &instance();
|
||||||
|
|
||||||
bool isNightly{};
|
bool isNightly{};
|
||||||
bool isPortable{};
|
bool isPortable{};
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
#include <boost/noncopyable.hpp>
|
#include <boost/noncopyable.hpp>
|
||||||
|
|
||||||
namespace AB_NAMESPACE {
|
namespace chatterino {
|
||||||
|
|
||||||
class Settings;
|
class Settings;
|
||||||
class Paths;
|
class Paths;
|
||||||
|
@ -23,4 +23,4 @@ public:
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace AB_NAMESPACE
|
} // namespace chatterino
|
|
@ -16,50 +16,46 @@ Version::Version()
|
||||||
this->commitHash_ =
|
this->commitHash_ =
|
||||||
QString(FROM_EXTERNAL_DEFINE(CHATTERINO_GIT_HASH)).remove('"');
|
QString(FROM_EXTERNAL_DEFINE(CHATTERINO_GIT_HASH)).remove('"');
|
||||||
|
|
||||||
// Date of build
|
// Date of build, this is depended on the format not changing
|
||||||
#ifdef CHATTERINO_NIGHTLY_VERSION_STRING
|
#ifdef CHATTERINO_NIGHTLY_VERSION_STRING
|
||||||
this->dateOfBuild_ =
|
this->dateOfBuild_ =
|
||||||
QString(FROM_EXTERNAL_DEFINE(CHATTERINO_NIGHTLY_VERSION_STRING))
|
QString(FROM_EXTERNAL_DEFINE(CHATTERINO_NIGHTLY_VERSION_STRING))
|
||||||
.remove('"');
|
.remove('"')
|
||||||
|
.split(' ')[0];
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// "Full" version string, as displayed in window title
|
// "Full" version string, as displayed in window title
|
||||||
this->fullVersion_ = "Chatterino ";
|
this->fullVersion_ = "Chatterino ";
|
||||||
if (Modes::getInstance().isNightly)
|
if (Modes::instance().isNightly)
|
||||||
{
|
{
|
||||||
this->fullVersion_ += "Nightly ";
|
this->fullVersion_ += "Nightly ";
|
||||||
}
|
}
|
||||||
|
|
||||||
this->fullVersion_ += this->version_;
|
this->fullVersion_ += this->version_;
|
||||||
|
|
||||||
if (Modes::getInstance().isNightly)
|
|
||||||
{
|
|
||||||
this->fullVersion_ += this->dateOfBuild_;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const Version &Version::getInstance()
|
const Version &Version::instance()
|
||||||
{
|
{
|
||||||
static Version instance;
|
static Version instance;
|
||||||
return instance;
|
return instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
const QString &Version::getVersion() const
|
const QString &Version::version() const
|
||||||
{
|
{
|
||||||
return this->version_;
|
return this->version_;
|
||||||
}
|
}
|
||||||
|
|
||||||
const QString &Version::getFullVersion() const
|
const QString &Version::fullVersion() const
|
||||||
{
|
{
|
||||||
return this->fullVersion_;
|
return this->fullVersion_;
|
||||||
}
|
}
|
||||||
|
|
||||||
const QString &Version::getCommitHash() const
|
const QString &Version::commitHash() const
|
||||||
{
|
{
|
||||||
return this->commitHash_;
|
return this->commitHash_;
|
||||||
}
|
}
|
||||||
|
|
||||||
const QString &Version::getDateOfBuild() const
|
const QString &Version::dateOfBuild() const
|
||||||
{
|
{
|
||||||
return this->dateOfBuild_;
|
return this->dateOfBuild_;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <QString>
|
||||||
#include <QtGlobal>
|
#include <QtGlobal>
|
||||||
|
|
||||||
#define CHATTERINO_VERSION "2.1.4"
|
#define CHATTERINO_VERSION "2.1.7"
|
||||||
|
|
||||||
#if defined(Q_OS_WIN)
|
#if defined(Q_OS_WIN)
|
||||||
# define CHATTERINO_OS "win"
|
# define CHATTERINO_OS "win"
|
||||||
|
@ -19,12 +20,12 @@ namespace chatterino {
|
||||||
class Version
|
class Version
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
static const Version &getInstance();
|
static const Version &instance();
|
||||||
|
|
||||||
const QString &getVersion() const;
|
const QString &version() const;
|
||||||
const QString &getCommitHash() const;
|
const QString &commitHash() const;
|
||||||
const QString &getDateOfBuild() const;
|
const QString &dateOfBuild() const;
|
||||||
const QString &getFullVersion() const;
|
const QString &fullVersion() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Version();
|
Version();
|
||||||
|
@ -35,4 +36,4 @@ private:
|
||||||
QString fullVersion_;
|
QString fullVersion_;
|
||||||
};
|
};
|
||||||
|
|
||||||
};
|
}; // namespace chatterino
|
||||||
|
|
|
@ -27,8 +27,7 @@ AccountController::AccountController()
|
||||||
this->accounts_.itemRemoved.connect([this](const auto &args) {
|
this->accounts_.itemRemoved.connect([this](const auto &args) {
|
||||||
switch (args.item->getProviderId())
|
switch (args.item->getProviderId())
|
||||||
{
|
{
|
||||||
case ProviderId::Twitch:
|
case ProviderId::Twitch: {
|
||||||
{
|
|
||||||
if (args.caller != this)
|
if (args.caller != this)
|
||||||
{
|
{
|
||||||
auto accs = this->twitch.accounts.cloneVector();
|
auto accs = this->twitch.accounts.cloneVector();
|
||||||
|
|
|
@ -70,8 +70,7 @@ void HighlightModel::customRowSetData(const std::vector<QStandardItem *> &row,
|
||||||
{
|
{
|
||||||
switch (column)
|
switch (column)
|
||||||
{
|
{
|
||||||
case 0:
|
case 0: {
|
||||||
{
|
|
||||||
if (role == Qt::CheckStateRole)
|
if (role == Qt::CheckStateRole)
|
||||||
{
|
{
|
||||||
if (rowIndex == 0)
|
if (rowIndex == 0)
|
||||||
|
@ -86,8 +85,7 @@ void HighlightModel::customRowSetData(const std::vector<QStandardItem *> &row,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 1:
|
case 1: {
|
||||||
{
|
|
||||||
if (role == Qt::CheckStateRole)
|
if (role == Qt::CheckStateRole)
|
||||||
{
|
{
|
||||||
if (rowIndex == 0)
|
if (rowIndex == 0)
|
||||||
|
@ -103,8 +101,7 @@ void HighlightModel::customRowSetData(const std::vector<QStandardItem *> &row,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 2:
|
case 2: {
|
||||||
{
|
|
||||||
if (role == Qt::CheckStateRole)
|
if (role == Qt::CheckStateRole)
|
||||||
{
|
{
|
||||||
if (rowIndex == 0)
|
if (rowIndex == 0)
|
||||||
|
@ -120,8 +117,7 @@ void HighlightModel::customRowSetData(const std::vector<QStandardItem *> &row,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 3:
|
case 3: {
|
||||||
{
|
|
||||||
// empty element
|
// empty element
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -119,8 +119,8 @@ struct Deserialize<chatterino::HighlightPhrase> {
|
||||||
chatterino::rj::getSafe(value, "regex", _isRegex);
|
chatterino::rj::getSafe(value, "regex", _isRegex);
|
||||||
chatterino::rj::getSafe(value, "case", _caseSensitive);
|
chatterino::rj::getSafe(value, "case", _caseSensitive);
|
||||||
|
|
||||||
return chatterino::HighlightPhrase(_pattern, _alert, _sound,
|
return chatterino::HighlightPhrase(_pattern, _alert, _sound, _isRegex,
|
||||||
_isRegex, _caseSensitive);
|
_caseSensitive);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -65,17 +65,17 @@ ModerationAction::ModerationAction(const QString &action)
|
||||||
// line1 = this->line1_;
|
// line1 = this->line1_;
|
||||||
// line2 = this->line2_;
|
// line2 = this->line2_;
|
||||||
// } else {
|
// } else {
|
||||||
// this->_moderationActions.emplace_back(app->resources->buttonTimeout,
|
// this->_moderationActions.emplace_back(getResources().buttonTimeout,
|
||||||
// str);
|
// str);
|
||||||
// }
|
// }
|
||||||
}
|
}
|
||||||
else if (action.startsWith("/ban "))
|
else if (action.startsWith("/ban "))
|
||||||
{
|
{
|
||||||
this->image_ = Image::fromPixmap(getApp()->resources->buttons.ban);
|
this->image_ = Image::fromPixmap(getResources().buttons.ban);
|
||||||
}
|
}
|
||||||
else if (action.startsWith("/delete "))
|
else if (action.startsWith("/delete "))
|
||||||
{
|
{
|
||||||
this->image_ = Image::fromPixmap(getApp()->resources->buttons.trashCan);
|
this->image_ = Image::fromPixmap(getResources().buttons.trashCan);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
#include <QThread>
|
#include <QThread>
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
|
|
||||||
namespace AB_NAMESPACE {
|
namespace chatterino {
|
||||||
|
|
||||||
static bool isGuiThread()
|
static bool isGuiThread()
|
||||||
{
|
{
|
||||||
|
@ -18,4 +18,4 @@ static void assertInGuiThread()
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace AB_NAMESPACE
|
} // namespace chatterino
|
|
@ -1,6 +1,6 @@
|
||||||
#include "Benchmark.hpp"
|
#include "Benchmark.hpp"
|
||||||
|
|
||||||
namespace AB_NAMESPACE {
|
namespace chatterino {
|
||||||
|
|
||||||
BenchmarkGuard::BenchmarkGuard(const QString &_name)
|
BenchmarkGuard::BenchmarkGuard(const QString &_name)
|
||||||
: name_(_name)
|
: name_(_name)
|
||||||
|
@ -18,4 +18,4 @@ qreal BenchmarkGuard::getElapsedMs()
|
||||||
return qreal(timer_.nsecsElapsed()) / 1000000.0;
|
return qreal(timer_.nsecsElapsed()) / 1000000.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace AB_NAMESPACE
|
} // namespace chatterino
|
|
@ -5,7 +5,7 @@
|
||||||
#include <QElapsedTimer>
|
#include <QElapsedTimer>
|
||||||
#include <boost/noncopyable.hpp>
|
#include <boost/noncopyable.hpp>
|
||||||
|
|
||||||
namespace AB_NAMESPACE {
|
namespace chatterino {
|
||||||
|
|
||||||
class BenchmarkGuard : boost::noncopyable
|
class BenchmarkGuard : boost::noncopyable
|
||||||
{
|
{
|
||||||
|
@ -19,4 +19,4 @@ private:
|
||||||
QString name_;
|
QString name_;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace AB_NAMESPACE
|
} // namespace chatterino
|
|
@ -5,7 +5,7 @@
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
#include <QTime>
|
#include <QTime>
|
||||||
|
|
||||||
namespace AB_NAMESPACE {
|
namespace chatterino {
|
||||||
|
|
||||||
template <typename... Args>
|
template <typename... Args>
|
||||||
inline void log(const std::string &formatString, Args &&... args)
|
inline void log(const std::string &formatString, Args &&... args)
|
||||||
|
@ -26,4 +26,4 @@ inline void log(const QString &formatString, Args &&... args)
|
||||||
log(formatString.toStdString(), std::forward<Args>(args)...);
|
log(formatString.toStdString(), std::forward<Args>(args)...);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace AB_NAMESPACE
|
} // namespace chatterino
|
51
src/main.cpp
51
src/main.cpp
|
@ -1,13 +1,18 @@
|
||||||
|
#include <QApplication>
|
||||||
|
#include <QDebug>
|
||||||
|
#include <QMessageBox>
|
||||||
|
#include <QStringList>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
#include "BrowserExtension.hpp"
|
#include "BrowserExtension.hpp"
|
||||||
#include "RunGui.hpp"
|
#include "RunGui.hpp"
|
||||||
|
#include "common/Args.hpp"
|
||||||
|
#include "common/Modes.hpp"
|
||||||
|
#include "common/Version.hpp"
|
||||||
#include "singletons/Paths.hpp"
|
#include "singletons/Paths.hpp"
|
||||||
#include "singletons/Settings.hpp"
|
#include "singletons/Settings.hpp"
|
||||||
#include "util/IncognitoBrowser.hpp"
|
#include "util/IncognitoBrowser.hpp"
|
||||||
|
|
||||||
#include <QApplication>
|
|
||||||
#include <QStringList>
|
|
||||||
#include <memory>
|
|
||||||
|
|
||||||
using namespace chatterino;
|
using namespace chatterino;
|
||||||
|
|
||||||
int main(int argc, char **argv)
|
int main(int argc, char **argv)
|
||||||
|
@ -18,17 +23,49 @@ int main(int argc, char **argv)
|
||||||
auto args = QStringList();
|
auto args = QStringList();
|
||||||
std::transform(argv + 1, argv + argc, std::back_inserter(args),
|
std::transform(argv + 1, argv + argc, std::back_inserter(args),
|
||||||
[&](auto s) { return s; });
|
[&](auto s) { return s; });
|
||||||
|
initArgs(args);
|
||||||
|
|
||||||
// run in gui mode or browser extension host mode
|
// run in gui mode or browser extension host mode
|
||||||
if (shouldRunBrowserExtensionHost(args))
|
if (shouldRunBrowserExtensionHost(args))
|
||||||
{
|
{
|
||||||
runBrowserExtensionHost();
|
runBrowserExtensionHost();
|
||||||
}
|
}
|
||||||
|
else if (getArgs().printVersion)
|
||||||
|
{
|
||||||
|
qInfo().noquote() << Version::instance().fullVersion();
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Paths paths;
|
Paths *paths{};
|
||||||
Settings settings(paths.settingsDirectory);
|
|
||||||
|
|
||||||
runGui(a, paths, settings);
|
try
|
||||||
|
{
|
||||||
|
paths = new Paths;
|
||||||
}
|
}
|
||||||
|
catch (std::runtime_error &error)
|
||||||
|
{
|
||||||
|
QMessageBox box;
|
||||||
|
if (Modes::instance().isPortable)
|
||||||
|
{
|
||||||
|
box.setText(
|
||||||
|
error.what() +
|
||||||
|
QStringLiteral(
|
||||||
|
"\n\nInfo: Portable mode requires the application to "
|
||||||
|
"be in a writeable location. If you don't want "
|
||||||
|
"portable mode reinstall the application. "
|
||||||
|
"https://chatterino.com."));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
box.setText(error.what());
|
||||||
|
}
|
||||||
|
box.exec();
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
Settings settings(paths->settingsDirectory);
|
||||||
|
|
||||||
|
runGui(a, *paths, settings);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,14 @@
|
||||||
#include "messages/Image.hpp"
|
#include "messages/Image.hpp"
|
||||||
|
|
||||||
|
#include <QBuffer>
|
||||||
|
#include <QImageReader>
|
||||||
|
#include <QNetworkAccessManager>
|
||||||
|
#include <QNetworkReply>
|
||||||
|
#include <QNetworkRequest>
|
||||||
|
#include <QTimer>
|
||||||
|
#include <functional>
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
#include "Application.hpp"
|
#include "Application.hpp"
|
||||||
#include "common/Common.hpp"
|
#include "common/Common.hpp"
|
||||||
#include "common/NetworkRequest.hpp"
|
#include "common/NetworkRequest.hpp"
|
||||||
|
@ -11,15 +20,6 @@
|
||||||
#include "util/DebugCount.hpp"
|
#include "util/DebugCount.hpp"
|
||||||
#include "util/PostToThread.hpp"
|
#include "util/PostToThread.hpp"
|
||||||
|
|
||||||
#include <QBuffer>
|
|
||||||
#include <QImageReader>
|
|
||||||
#include <QNetworkAccessManager>
|
|
||||||
#include <QNetworkReply>
|
|
||||||
#include <QNetworkRequest>
|
|
||||||
#include <QTimer>
|
|
||||||
#include <functional>
|
|
||||||
#include <thread>
|
|
||||||
|
|
||||||
namespace chatterino {
|
namespace chatterino {
|
||||||
namespace detail {
|
namespace detail {
|
||||||
// Frames
|
// Frames
|
||||||
|
@ -198,6 +198,15 @@ namespace detail {
|
||||||
} // namespace detail
|
} // namespace detail
|
||||||
|
|
||||||
// IMAGE2
|
// IMAGE2
|
||||||
|
Image::~Image()
|
||||||
|
{
|
||||||
|
// run destructor of Frames in gui thread
|
||||||
|
if (!isGuiThread())
|
||||||
|
{
|
||||||
|
postToThread([frames = this->frames_.release()]() { delete frames; });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ImagePtr Image::fromUrl(const Url &url, qreal scale)
|
ImagePtr Image::fromUrl(const Url &url, qreal scale)
|
||||||
{
|
{
|
||||||
static std::unordered_map<Url, std::weak_ptr<Image>> cache;
|
static std::unordered_map<Url, std::weak_ptr<Image>> cache;
|
||||||
|
@ -324,7 +333,7 @@ int Image::width() const
|
||||||
assertInGuiThread();
|
assertInGuiThread();
|
||||||
|
|
||||||
if (auto pixmap = this->frames_->first())
|
if (auto pixmap = this->frames_->first())
|
||||||
return pixmap->width() * this->scale_;
|
return int(pixmap->width() * this->scale_);
|
||||||
else
|
else
|
||||||
return 16;
|
return 16;
|
||||||
}
|
}
|
||||||
|
@ -369,6 +378,7 @@ void Image::actuallyLoad()
|
||||||
if (!shared)
|
if (!shared)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
// fourtf: is this the right thing to do?
|
||||||
shared->empty_ = true;
|
shared->empty_ = true;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
#include <pajlada/signals/signal.hpp>
|
#include <pajlada/signals/signal.hpp>
|
||||||
|
|
||||||
#include "common/Aliases.hpp"
|
#include "common/Aliases.hpp"
|
||||||
#include "common/NullablePtr.hpp"
|
#include "common/Common.hpp"
|
||||||
|
|
||||||
namespace chatterino {
|
namespace chatterino {
|
||||||
namespace detail {
|
namespace detail {
|
||||||
|
@ -45,9 +45,12 @@ namespace detail {
|
||||||
class Image;
|
class Image;
|
||||||
using ImagePtr = std::shared_ptr<Image>;
|
using ImagePtr = std::shared_ptr<Image>;
|
||||||
|
|
||||||
|
/// This class is thread safe.
|
||||||
class Image : public std::enable_shared_from_this<Image>, boost::noncopyable
|
class Image : public std::enable_shared_from_this<Image>, boost::noncopyable
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
~Image();
|
||||||
|
|
||||||
static ImagePtr fromUrl(const Url &url, qreal scale = 1);
|
static ImagePtr fromUrl(const Url &url, qreal scale = 1);
|
||||||
static ImagePtr fromPixmap(const QPixmap &pixmap, qreal scale = 1);
|
static ImagePtr fromPixmap(const QPixmap &pixmap, qreal scale = 1);
|
||||||
static ImagePtr getEmpty();
|
static ImagePtr getEmpty();
|
||||||
|
@ -72,14 +75,14 @@ private:
|
||||||
Image(qreal scale);
|
Image(qreal scale);
|
||||||
|
|
||||||
void setPixmap(const QPixmap &pixmap);
|
void setPixmap(const QPixmap &pixmap);
|
||||||
|
|
||||||
void actuallyLoad();
|
void actuallyLoad();
|
||||||
|
|
||||||
Url url_{};
|
const Url url_{};
|
||||||
qreal scale_{1};
|
const qreal scale_{1};
|
||||||
bool empty_{false};
|
std::atomic_bool empty_{false};
|
||||||
|
|
||||||
|
// gui thread only
|
||||||
bool shouldLoad_{false};
|
bool shouldLoad_{false};
|
||||||
std::unique_ptr<detail::Frames> frames_{};
|
std::unique_ptr<detail::Frames> frames_{};
|
||||||
QObject object_{};
|
|
||||||
};
|
};
|
||||||
} // namespace chatterino
|
} // namespace chatterino
|
||||||
|
|
|
@ -32,8 +32,7 @@ std::pair<MessagePtr, MessagePtr> makeAutomodMessage(
|
||||||
builder.message().flags.set(MessageFlag::PubSub);
|
builder.message().flags.set(MessageFlag::PubSub);
|
||||||
|
|
||||||
builder
|
builder
|
||||||
.emplace<ImageElement>(
|
.emplace<ImageElement>(Image::fromPixmap(getResources().twitch.automod),
|
||||||
Image::fromPixmap(getApp()->resources->twitch.automod),
|
|
||||||
MessageElementFlag::BadgeChannelAuthority)
|
MessageElementFlag::BadgeChannelAuthority)
|
||||||
->setTooltip("AutoMod");
|
->setTooltip("AutoMod");
|
||||||
builder.emplace<TextElement>("AutoMod:", MessageElementFlag::BoldUsername,
|
builder.emplace<TextElement>("AutoMod:", MessageElementFlag::BoldUsername,
|
||||||
|
@ -258,40 +257,35 @@ MessageBuilder::MessageBuilder(const AutomodUserAction &action)
|
||||||
QString text;
|
QString text;
|
||||||
switch (action.type)
|
switch (action.type)
|
||||||
{
|
{
|
||||||
case AutomodUserAction::AddPermitted:
|
case AutomodUserAction::AddPermitted: {
|
||||||
{
|
|
||||||
text = QString("%1 added %2 as a permitted term on AutoMod.")
|
text = QString("%1 added %2 as a permitted term on AutoMod.")
|
||||||
.arg(action.source.name)
|
.arg(action.source.name)
|
||||||
.arg(action.message);
|
.arg(action.message);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case AutomodUserAction::AddBlocked:
|
case AutomodUserAction::AddBlocked: {
|
||||||
{
|
|
||||||
text = QString("%1 added %2 as a blocked term on AutoMod.")
|
text = QString("%1 added %2 as a blocked term on AutoMod.")
|
||||||
.arg(action.source.name)
|
.arg(action.source.name)
|
||||||
.arg(action.message);
|
.arg(action.message);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case AutomodUserAction::RemovePermitted:
|
case AutomodUserAction::RemovePermitted: {
|
||||||
{
|
|
||||||
text = QString("%1 removed %2 as a permitted term term on AutoMod.")
|
text = QString("%1 removed %2 as a permitted term term on AutoMod.")
|
||||||
.arg(action.source.name)
|
.arg(action.source.name)
|
||||||
.arg(action.message);
|
.arg(action.message);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case AutomodUserAction::RemoveBlocked:
|
case AutomodUserAction::RemoveBlocked: {
|
||||||
{
|
|
||||||
text = QString("%1 removed %2 as a blocked term on AutoMod.")
|
text = QString("%1 removed %2 as a blocked term on AutoMod.")
|
||||||
.arg(action.source.name)
|
.arg(action.source.name)
|
||||||
.arg(action.message);
|
.arg(action.message);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case AutomodUserAction::Properties:
|
case AutomodUserAction::Properties: {
|
||||||
{
|
|
||||||
text = QString("%1 modified the AutoMod properties.")
|
text = QString("%1 modified the AutoMod properties.")
|
||||||
.arg(action.source.name);
|
.arg(action.source.name);
|
||||||
}
|
}
|
||||||
|
|
|
@ -103,6 +103,10 @@ enum class MessageElementFlag {
|
||||||
LowercaseLink = (1 << 29),
|
LowercaseLink = (1 << 29),
|
||||||
OriginalLink = (1 << 30),
|
OriginalLink = (1 << 30),
|
||||||
|
|
||||||
|
// ZeroWidthEmotes are emotes that are supposed to overlay over any pre-existing emotes
|
||||||
|
// e.g. BTTV's SoSnowy during christmas season
|
||||||
|
ZeroWidthEmote = (1 << 31),
|
||||||
|
|
||||||
Default = Timestamp | Badges | Username | BitsStatic | FfzEmoteImage |
|
Default = Timestamp | Badges | Username | BitsStatic | FfzEmoteImage |
|
||||||
BttvEmoteImage | TwitchEmoteImage | BitsAmount | Text |
|
BttvEmoteImage | TwitchEmoteImage | BitsAmount | Text |
|
||||||
AlwaysShow,
|
AlwaysShow,
|
||||||
|
|
|
@ -158,7 +158,7 @@ void MessageLayout::paint(QPainter &painter, int width, int y, int messageIndex,
|
||||||
// create new buffer if required
|
// create new buffer if required
|
||||||
if (!pixmap)
|
if (!pixmap)
|
||||||
{
|
{
|
||||||
#ifdef Q_OS_MACOS
|
#if defined(Q_OS_MACOS) || defined(Q_OS_LINUX)
|
||||||
pixmap = new QPixmap(int(width * painter.device()->devicePixelRatioF()),
|
pixmap = new QPixmap(int(width * painter.device()->devicePixelRatioF()),
|
||||||
int(container_->getHeight() *
|
int(container_->getHeight() *
|
||||||
painter.device()->devicePixelRatioF()));
|
painter.device()->devicePixelRatioF()));
|
||||||
|
|
|
@ -115,15 +115,27 @@ void MessageLayoutContainer::_addElement(MessageLayoutElement *element,
|
||||||
// update line height
|
// update line height
|
||||||
this->lineHeight_ = std::max(this->lineHeight_, newLineHeight);
|
this->lineHeight_ = std::max(this->lineHeight_, newLineHeight);
|
||||||
|
|
||||||
|
auto xOffset = 0;
|
||||||
|
|
||||||
|
if (element->getCreator().getFlags().has(
|
||||||
|
MessageElementFlag::ZeroWidthEmote))
|
||||||
|
{
|
||||||
|
xOffset -= element->getRect().width() + this->spaceWidth_;
|
||||||
|
}
|
||||||
|
|
||||||
// set move element
|
// set move element
|
||||||
element->setPosition(
|
element->setPosition(QPoint(this->currentX_ + xOffset,
|
||||||
QPoint(this->currentX_, this->currentY_ - element->getRect().height()));
|
this->currentY_ - element->getRect().height()));
|
||||||
|
|
||||||
// add element
|
// add element
|
||||||
this->elements_.push_back(std::unique_ptr<MessageLayoutElement>(element));
|
this->elements_.push_back(std::unique_ptr<MessageLayoutElement>(element));
|
||||||
|
|
||||||
// set current x
|
// set current x
|
||||||
|
if (!element->getCreator().getFlags().has(
|
||||||
|
MessageElementFlag::ZeroWidthEmote))
|
||||||
|
{
|
||||||
this->currentX_ += element->getRect().width();
|
this->currentX_ += element->getRect().width();
|
||||||
|
}
|
||||||
|
|
||||||
if (element->hasTrailingSpace())
|
if (element->hasTrailingSpace())
|
||||||
{
|
{
|
||||||
|
@ -137,7 +149,9 @@ void MessageLayoutContainer::breakLine()
|
||||||
|
|
||||||
if (this->flags_.has(MessageFlag::Centered) && this->elements_.size() > 0)
|
if (this->flags_.has(MessageFlag::Centered) && this->elements_.size() > 0)
|
||||||
{
|
{
|
||||||
xOffset = (width_ - this->elements_.at(0)->getRect().left() -
|
const int marginOffset = int(this->margin.left * this->scale_) +
|
||||||
|
int(this->margin.right * this->scale_);
|
||||||
|
xOffset = (width_ - marginOffset -
|
||||||
this->elements_.at(this->elements_.size() - 1)
|
this->elements_.at(this->elements_.size() - 1)
|
||||||
->getRect()
|
->getRect()
|
||||||
.right()) /
|
.right()) /
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
#include "providers/bttv/BttvEmotes.hpp"
|
#include "providers/bttv/BttvEmotes.hpp"
|
||||||
|
|
||||||
|
#include <QJsonArray>
|
||||||
|
#include <QThread>
|
||||||
|
|
||||||
#include "common/Common.hpp"
|
#include "common/Common.hpp"
|
||||||
#include "common/NetworkRequest.hpp"
|
#include "common/NetworkRequest.hpp"
|
||||||
#include "debug/Log.hpp"
|
#include "debug/Log.hpp"
|
||||||
|
@ -8,11 +11,11 @@
|
||||||
#include "messages/ImageSet.hpp"
|
#include "messages/ImageSet.hpp"
|
||||||
#include "providers/twitch/TwitchChannel.hpp"
|
#include "providers/twitch/TwitchChannel.hpp"
|
||||||
|
|
||||||
#include <QJsonArray>
|
|
||||||
#include <QThread>
|
|
||||||
|
|
||||||
namespace chatterino {
|
namespace chatterino {
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
|
QString emoteLinkFormat("https://betterttv.com/emotes/%1");
|
||||||
|
|
||||||
Url getEmoteLink(QString urlTemplate, const EmoteId &id,
|
Url getEmoteLink(QString urlTemplate, const EmoteId &id,
|
||||||
const QString &emoteScale)
|
const QString &emoteScale)
|
||||||
{
|
{
|
||||||
|
@ -47,13 +50,14 @@ namespace {
|
||||||
auto name =
|
auto name =
|
||||||
EmoteName{jsonEmote.toObject().value("code").toString()};
|
EmoteName{jsonEmote.toObject().value("code").toString()};
|
||||||
|
|
||||||
auto emote = Emote(
|
auto emote = Emote({
|
||||||
{name,
|
name,
|
||||||
ImageSet{Image::fromUrl(getEmoteLinkV3(id, "1x"), 1),
|
ImageSet{Image::fromUrl(getEmoteLinkV3(id, "1x"), 1),
|
||||||
Image::fromUrl(getEmoteLinkV3(id, "2x"), 0.5),
|
Image::fromUrl(getEmoteLinkV3(id, "2x"), 0.5),
|
||||||
Image::fromUrl(getEmoteLinkV3(id, "3x"), 0.25)},
|
Image::fromUrl(getEmoteLinkV3(id, "3x"), 0.25)},
|
||||||
Tooltip{name.string + "<br />Global BetterTTV Emote"},
|
Tooltip{name.string + "<br />Global BetterTTV Emote"},
|
||||||
Url{"https://manage.betterttv.net/emotes/" + id.string}});
|
Url{emoteLinkFormat.arg(id.string)},
|
||||||
|
});
|
||||||
|
|
||||||
emotes[name] =
|
emotes[name] =
|
||||||
cachedOrMakeEmotePtr(std::move(emote), currentEmotes);
|
cachedOrMakeEmotePtr(std::move(emote), currentEmotes);
|
||||||
|
@ -75,15 +79,16 @@ namespace {
|
||||||
auto name = EmoteName{jsonEmote.value("code").toString()};
|
auto name = EmoteName{jsonEmote.value("code").toString()};
|
||||||
// emoteObject.value("imageType").toString();
|
// emoteObject.value("imageType").toString();
|
||||||
|
|
||||||
auto emote = Emote(
|
auto emote = Emote({
|
||||||
{name,
|
name,
|
||||||
ImageSet{
|
ImageSet{
|
||||||
Image::fromUrl(getEmoteLinkV3(id, "1x"), 1),
|
Image::fromUrl(getEmoteLinkV3(id, "1x"), 1),
|
||||||
Image::fromUrl(getEmoteLinkV3(id, "2x"), 0.5),
|
Image::fromUrl(getEmoteLinkV3(id, "2x"), 0.5),
|
||||||
Image::fromUrl(getEmoteLinkV3(id, "3x"), 0.25),
|
Image::fromUrl(getEmoteLinkV3(id, "3x"), 0.25),
|
||||||
},
|
},
|
||||||
Tooltip{name.string + "<br />Channel BetterTTV Emote"},
|
Tooltip{name.string + "<br />Channel BetterTTV Emote"},
|
||||||
Url{"https://manage.betterttv.net/emotes/" + id.string}});
|
Url{emoteLinkFormat.arg(id.string)},
|
||||||
|
});
|
||||||
|
|
||||||
emotes[name] = cachedOrMake(std::move(emote), id);
|
emotes[name] = cachedOrMake(std::move(emote), id);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "providers/irc/IrcConnection2.hpp"
|
|
||||||
|
|
||||||
#include <IrcMessage>
|
#include <IrcMessage>
|
||||||
|
#include <functional>
|
||||||
|
#include <mutex>
|
||||||
#include <pajlada/signals/signal.hpp>
|
#include <pajlada/signals/signal.hpp>
|
||||||
#include <pajlada/signals/signalholder.hpp>
|
#include <pajlada/signals/signalholder.hpp>
|
||||||
|
|
||||||
#include <functional>
|
#include "common/Common.hpp"
|
||||||
#include <mutex>
|
#include "providers/irc/IrcConnection2.hpp"
|
||||||
|
|
||||||
namespace chatterino {
|
namespace chatterino {
|
||||||
|
|
||||||
|
@ -73,15 +73,8 @@ protected:
|
||||||
private:
|
private:
|
||||||
void initConnection();
|
void initConnection();
|
||||||
|
|
||||||
struct Deleter {
|
QObjectPtr<IrcConnection> writeConnection_ = nullptr;
|
||||||
void operator()(IrcConnection *conn)
|
QObjectPtr<IrcConnection> readConnection_ = nullptr;
|
||||||
{
|
|
||||||
conn->deleteLater();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
std::unique_ptr<IrcConnection, Deleter> writeConnection_ = nullptr;
|
|
||||||
std::unique_ptr<IrcConnection, Deleter> readConnection_ = nullptr;
|
|
||||||
|
|
||||||
QTimer reconnectTimer_;
|
QTimer reconnectTimer_;
|
||||||
int falloffCounter_ = 1;
|
int falloffCounter_ = 1;
|
||||||
|
|
|
@ -73,13 +73,13 @@ inline QString getCredentialName(const IrcServerData &data)
|
||||||
void IrcServerData::getPassword(
|
void IrcServerData::getPassword(
|
||||||
QObject *receiver, std::function<void(const QString &)> &&onLoaded) const
|
QObject *receiver, std::function<void(const QString &)> &&onLoaded) const
|
||||||
{
|
{
|
||||||
Credentials::getInstance().get("irc", getCredentialName(*this), receiver,
|
Credentials::instance().get("irc", getCredentialName(*this), receiver,
|
||||||
std::move(onLoaded));
|
std::move(onLoaded));
|
||||||
}
|
}
|
||||||
|
|
||||||
void IrcServerData::setPassword(const QString &password)
|
void IrcServerData::setPassword(const QString &password)
|
||||||
{
|
{
|
||||||
Credentials::getInstance().set("irc", getCredentialName(*this), password);
|
Credentials::instance().set("irc", getCredentialName(*this), password);
|
||||||
}
|
}
|
||||||
|
|
||||||
Irc::Irc()
|
Irc::Irc()
|
||||||
|
@ -133,8 +133,7 @@ Irc::Irc()
|
||||||
|
|
||||||
if (args.caller != Irc::noEraseCredentialCaller)
|
if (args.caller != Irc::noEraseCredentialCaller)
|
||||||
{
|
{
|
||||||
Credentials::getInstance().erase("irc",
|
Credentials::instance().erase("irc", getCredentialName(args.item));
|
||||||
getCredentialName(args.item));
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -164,7 +163,7 @@ ChannelPtr Irc::getOrAddChannel(int id, QString name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Irc &Irc::getInstance()
|
Irc &Irc::instance()
|
||||||
{
|
{
|
||||||
static Irc irc;
|
static Irc irc;
|
||||||
return irc;
|
return irc;
|
||||||
|
|
|
@ -36,7 +36,7 @@ class Irc
|
||||||
public:
|
public:
|
||||||
Irc();
|
Irc();
|
||||||
|
|
||||||
static Irc &getInstance();
|
static Irc &instance();
|
||||||
|
|
||||||
static inline void *const noEraseCredentialCaller =
|
static inline void *const noEraseCredentialCaller =
|
||||||
reinterpret_cast<void *>(1);
|
reinterpret_cast<void *>(1);
|
||||||
|
|
|
@ -66,6 +66,8 @@ void IrcServer::initializeConnection(IrcConnection *connection,
|
||||||
connection->setRealName(this->data_->real.isEmpty() ? this->data_->user
|
connection->setRealName(this->data_->real.isEmpty() ? this->data_->user
|
||||||
: this->data_->nick);
|
: this->data_->nick);
|
||||||
|
|
||||||
|
if (getSettings()->enableExperimentalIrc)
|
||||||
|
{
|
||||||
switch (this->data_->authType)
|
switch (this->data_->authType)
|
||||||
{
|
{
|
||||||
case IrcAuthType::Sasl:
|
case IrcAuthType::Sasl:
|
||||||
|
@ -87,6 +89,7 @@ void IrcServer::initializeConnection(IrcConnection *connection,
|
||||||
default:
|
default:
|
||||||
this->open(Both);
|
this->open(Both);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
QObject::connect(
|
QObject::connect(
|
||||||
connection, &Communi::IrcConnection::socketError, this,
|
connection, &Communi::IrcConnection::socketError, this,
|
||||||
|
@ -179,8 +182,7 @@ void IrcServer::readConnectionMessageReceived(Communi::IrcMessage *message)
|
||||||
|
|
||||||
switch (message->type())
|
switch (message->type())
|
||||||
{
|
{
|
||||||
case Communi::IrcMessage::Join:
|
case Communi::IrcMessage::Join: {
|
||||||
{
|
|
||||||
auto x = static_cast<Communi::IrcJoinMessage *>(message);
|
auto x = static_cast<Communi::IrcJoinMessage *>(message);
|
||||||
|
|
||||||
if (auto it =
|
if (auto it =
|
||||||
|
@ -205,8 +207,7 @@ void IrcServer::readConnectionMessageReceived(Communi::IrcMessage *message)
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
case Communi::IrcMessage::Part:
|
case Communi::IrcMessage::Part: {
|
||||||
{
|
|
||||||
auto x = static_cast<Communi::IrcPartMessage *>(message);
|
auto x = static_cast<Communi::IrcPartMessage *>(message);
|
||||||
|
|
||||||
if (auto it =
|
if (auto it =
|
||||||
|
|
|
@ -40,7 +40,7 @@ static QMap<QString, QString> parseBadges(QString badgesString)
|
||||||
return badges;
|
return badges;
|
||||||
}
|
}
|
||||||
|
|
||||||
IrcMessageHandler &IrcMessageHandler::getInstance()
|
IrcMessageHandler &IrcMessageHandler::instance()
|
||||||
{
|
{
|
||||||
static IrcMessageHandler instance;
|
static IrcMessageHandler instance;
|
||||||
return instance;
|
return instance;
|
||||||
|
@ -618,7 +618,7 @@ void IrcMessageHandler::handlePartMessage(Communi::IrcMessage *message)
|
||||||
{
|
{
|
||||||
if (message->nick() !=
|
if (message->nick() !=
|
||||||
getApp()->accounts->twitch.getCurrent()->getUserName() &&
|
getApp()->accounts->twitch.getCurrent()->getUserName() &&
|
||||||
getSettings()->showJoins.getValue())
|
getSettings()->showParts.getValue())
|
||||||
{
|
{
|
||||||
twitchChannel->addPartedUser(message->nick());
|
twitchChannel->addPartedUser(message->nick());
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,7 +13,7 @@ class IrcMessageHandler
|
||||||
IrcMessageHandler() = default;
|
IrcMessageHandler() = default;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
static IrcMessageHandler &getInstance();
|
static IrcMessageHandler &instance();
|
||||||
|
|
||||||
// parseMessage parses a single IRC message into 0+ Chatterino messages
|
// parseMessage parses a single IRC message into 0+ Chatterino messages
|
||||||
std::vector<MessagePtr> parseMessage(Channel *channel,
|
std::vector<MessagePtr> parseMessage(Channel *channel,
|
||||||
|
|
|
@ -156,7 +156,7 @@ namespace detail {
|
||||||
|
|
||||||
if (self->awaitingPong_)
|
if (self->awaitingPong_)
|
||||||
{
|
{
|
||||||
log("No pong respnose, disconnect!");
|
log("No pong response, disconnect!");
|
||||||
// TODO(pajlada): Label this connection as "disconnect
|
// TODO(pajlada): Label this connection as "disconnect
|
||||||
// me"
|
// me"
|
||||||
}
|
}
|
||||||
|
|
|
@ -102,14 +102,12 @@ void TwitchAccountManager::reloadUsers()
|
||||||
|
|
||||||
switch (this->addUser(userData))
|
switch (this->addUser(userData))
|
||||||
{
|
{
|
||||||
case AddUserResponse::UserAlreadyExists:
|
case AddUserResponse::UserAlreadyExists: {
|
||||||
{
|
|
||||||
log("User {} already exists", userData.username);
|
log("User {} already exists", userData.username);
|
||||||
// Do nothing
|
// Do nothing
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case AddUserResponse::UserValuesUpdated:
|
case AddUserResponse::UserValuesUpdated: {
|
||||||
{
|
|
||||||
log("User {} already exists, and values updated!",
|
log("User {} already exists, and values updated!",
|
||||||
userData.username);
|
userData.username);
|
||||||
if (userData.username == this->getCurrent()->getUserName())
|
if (userData.username == this->getCurrent()->getUserName())
|
||||||
|
@ -120,8 +118,7 @@ void TwitchAccountManager::reloadUsers()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case AddUserResponse::UserAdded:
|
case AddUserResponse::UserAdded: {
|
||||||
{
|
|
||||||
log("Added user {}", userData.username);
|
log("Added user {}", userData.username);
|
||||||
listUpdated = true;
|
listUpdated = true;
|
||||||
}
|
}
|
||||||
|
|
31
src/providers/twitch/TwitchBadge.cpp
Normal file
31
src/providers/twitch/TwitchBadge.cpp
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
#include "providers/twitch/TwitchBadge.hpp"
|
||||||
|
|
||||||
|
#include <QSet>
|
||||||
|
|
||||||
|
namespace chatterino {
|
||||||
|
|
||||||
|
// set of badge IDs that should be given specific flags.
|
||||||
|
// vanity flag is left out on purpose as it is our default flag
|
||||||
|
const QSet<QString> globalAuthority{"staff", "admin", "global_mod"};
|
||||||
|
const QSet<QString> channelAuthority{"moderator", "vip", "broadcaster"};
|
||||||
|
const QSet<QString> subBadges{"subscriber", "founder"};
|
||||||
|
|
||||||
|
Badge::Badge(QString key, QString value)
|
||||||
|
: key_(std::move(key))
|
||||||
|
, value_(std::move(value))
|
||||||
|
{
|
||||||
|
if (globalAuthority.contains(this->key_))
|
||||||
|
{
|
||||||
|
this->flag_ = MessageElementFlag::BadgeGlobalAuthority;
|
||||||
|
}
|
||||||
|
else if (channelAuthority.contains(this->key_))
|
||||||
|
{
|
||||||
|
this->flag_ = MessageElementFlag::BadgeChannelAuthority;
|
||||||
|
}
|
||||||
|
else if (subBadges.contains(this->key_))
|
||||||
|
{
|
||||||
|
this->flag_ = MessageElementFlag::BadgeSubscription;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace chatterino
|
21
src/providers/twitch/TwitchBadge.hpp
Normal file
21
src/providers/twitch/TwitchBadge.hpp
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "messages/MessageElement.hpp"
|
||||||
|
|
||||||
|
#include <QString>
|
||||||
|
|
||||||
|
namespace chatterino {
|
||||||
|
|
||||||
|
class Badge
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
Badge(QString key, QString value);
|
||||||
|
|
||||||
|
QString key_; // e.g. bits
|
||||||
|
QString value_; // e.g. 100
|
||||||
|
QString extraValue_{}; // e.g. 5 (the number of months subscribed)
|
||||||
|
MessageElementFlag flag_{
|
||||||
|
MessageElementFlag::BadgeVanity}; // badge slot it takes up
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace chatterino
|
|
@ -47,7 +47,7 @@ void TwitchBadges::loadTwitchBadges()
|
||||||
{versionObj.value("image_url_4x").toString()},
|
{versionObj.value("image_url_4x").toString()},
|
||||||
.25),
|
.25),
|
||||||
},
|
},
|
||||||
Tooltip{versionObj.value("description").toString()},
|
Tooltip{versionObj.value("title").toString()},
|
||||||
Url{versionObj.value("click_url").toString()}};
|
Url{versionObj.value("click_url").toString()}};
|
||||||
// "title"
|
// "title"
|
||||||
// "clickAction"
|
// "clickAction"
|
||||||
|
|
|
@ -31,6 +31,7 @@
|
||||||
|
|
||||||
namespace chatterino {
|
namespace chatterino {
|
||||||
namespace {
|
namespace {
|
||||||
|
constexpr int TITLE_REFRESH_PERIOD = 10;
|
||||||
constexpr char MAGIC_MESSAGE_SUFFIX[] = u8" \U000E0000";
|
constexpr char MAGIC_MESSAGE_SUFFIX[] = u8" \U000E0000";
|
||||||
|
|
||||||
// parseRecentMessages takes a json object and returns a vector of
|
// parseRecentMessages takes a json object and returns a vector of
|
||||||
|
@ -89,6 +90,7 @@ TwitchChannel::TwitchChannel(const QString &name,
|
||||||
, bttvEmotes_(std::make_shared<EmoteMap>())
|
, bttvEmotes_(std::make_shared<EmoteMap>())
|
||||||
, ffzEmotes_(std::make_shared<EmoteMap>())
|
, ffzEmotes_(std::make_shared<EmoteMap>())
|
||||||
, mod_(false)
|
, mod_(false)
|
||||||
|
, titleRefreshedTime_(QTime::currentTime().addSecs(-TITLE_REFRESH_PERIOD))
|
||||||
{
|
{
|
||||||
log("[TwitchChannel:{}] Opened", name);
|
log("[TwitchChannel:{}] Opened", name);
|
||||||
|
|
||||||
|
@ -110,6 +112,7 @@ TwitchChannel::TwitchChannel(const QString &name,
|
||||||
// room id loaded -> refresh live status
|
// room id loaded -> refresh live status
|
||||||
this->roomIdChanged.connect([this]() {
|
this->roomIdChanged.connect([this]() {
|
||||||
this->refreshPubsub();
|
this->refreshPubsub();
|
||||||
|
this->refreshTitle();
|
||||||
this->refreshLiveStatus();
|
this->refreshLiveStatus();
|
||||||
this->refreshBadges();
|
this->refreshBadges();
|
||||||
this->refreshCheerEmotes();
|
this->refreshCheerEmotes();
|
||||||
|
@ -229,7 +232,7 @@ bool TwitchChannel::isMod() const
|
||||||
return this->mod_;
|
return this->mod_;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool TwitchChannel::isVIP() const
|
bool TwitchChannel::isVip() const
|
||||||
{
|
{
|
||||||
return this->vip_;
|
return this->vip_;
|
||||||
}
|
}
|
||||||
|
@ -278,7 +281,7 @@ bool TwitchChannel::isBroadcaster() const
|
||||||
|
|
||||||
bool TwitchChannel::hasHighRateLimit() const
|
bool TwitchChannel::hasHighRateLimit() const
|
||||||
{
|
{
|
||||||
return this->isMod() || this->isBroadcaster() || this->isVIP();
|
return this->isMod() || this->isBroadcaster() || this->isVip();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool TwitchChannel::canReconnect() const
|
bool TwitchChannel::canReconnect() const
|
||||||
|
@ -437,6 +440,51 @@ void TwitchChannel::setLive(bool newLiveStatus)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void TwitchChannel::refreshTitle()
|
||||||
|
{
|
||||||
|
auto roomID = this->roomId();
|
||||||
|
if (roomID.isEmpty())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this->titleRefreshedTime_.elapsed() < TITLE_REFRESH_PERIOD * 1000)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this->titleRefreshedTime_ = QTime::currentTime();
|
||||||
|
|
||||||
|
QString url("https://api.twitch.tv/kraken/channels/" + roomID);
|
||||||
|
NetworkRequest::twitchRequest(url)
|
||||||
|
.onSuccess(
|
||||||
|
[this, weak = weakOf<Channel>(this)](auto result) -> Outcome {
|
||||||
|
ChannelPtr shared = weak.lock();
|
||||||
|
if (!shared)
|
||||||
|
return Failure;
|
||||||
|
|
||||||
|
const auto document = result.parseRapidJson();
|
||||||
|
|
||||||
|
auto statusIt = document.FindMember("status");
|
||||||
|
|
||||||
|
if (statusIt == document.MemberEnd())
|
||||||
|
{
|
||||||
|
return Failure;
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
auto status = this->streamStatus_.access();
|
||||||
|
if (!rj::getSafe(statusIt->value, status->title))
|
||||||
|
{
|
||||||
|
return Failure;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this->liveStatusChanged.invoke();
|
||||||
|
return Success;
|
||||||
|
})
|
||||||
|
.execute();
|
||||||
|
}
|
||||||
|
|
||||||
void TwitchChannel::refreshLiveStatus()
|
void TwitchChannel::refreshLiveStatus()
|
||||||
{
|
{
|
||||||
auto roomID = this->roomId();
|
auto roomID = this->roomId();
|
||||||
|
@ -566,7 +614,7 @@ void TwitchChannel::loadRecentMessages()
|
||||||
|
|
||||||
auto messages = parseRecentMessages(result.parseJson(), shared);
|
auto messages = parseRecentMessages(result.parseJson(), shared);
|
||||||
|
|
||||||
auto &handler = IrcMessageHandler::getInstance();
|
auto &handler = IrcMessageHandler::instance();
|
||||||
|
|
||||||
std::vector<MessagePtr> allBuiltMessages;
|
std::vector<MessagePtr> allBuiltMessages;
|
||||||
|
|
||||||
|
@ -711,6 +759,7 @@ void TwitchChannel::refreshCheerEmotes()
|
||||||
|
|
||||||
cheerEmote.color = QColor(tier.color);
|
cheerEmote.color = QColor(tier.color);
|
||||||
cheerEmote.minBits = tier.minBits;
|
cheerEmote.minBits = tier.minBits;
|
||||||
|
cheerEmote.regex = cheerEmoteSet.regex;
|
||||||
|
|
||||||
// TODO(pajlada): We currently hardcode dark here :|
|
// TODO(pajlada): We currently hardcode dark here :|
|
||||||
// We will continue to do so for now since we haven't had to
|
// We will continue to do so for now since we haven't had to
|
||||||
|
|
|
@ -63,12 +63,13 @@ public:
|
||||||
virtual bool canSendMessage() const override;
|
virtual bool canSendMessage() const override;
|
||||||
virtual void sendMessage(const QString &message) override;
|
virtual void sendMessage(const QString &message) override;
|
||||||
virtual bool isMod() const override;
|
virtual bool isMod() const override;
|
||||||
bool isVIP() const;
|
bool isVip() const;
|
||||||
bool isStaff() const;
|
bool isStaff() const;
|
||||||
virtual bool isBroadcaster() const override;
|
virtual bool isBroadcaster() const override;
|
||||||
virtual bool hasHighRateLimit() const override;
|
virtual bool hasHighRateLimit() const override;
|
||||||
virtual bool canReconnect() const override;
|
virtual bool canReconnect() const override;
|
||||||
virtual void reconnect() override;
|
virtual void reconnect() override;
|
||||||
|
void refreshTitle();
|
||||||
|
|
||||||
// Data
|
// Data
|
||||||
const QString &subscriptionUrl();
|
const QString &subscriptionUrl();
|
||||||
|
@ -166,6 +167,7 @@ private:
|
||||||
QObject lifetimeGuard_;
|
QObject lifetimeGuard_;
|
||||||
QTimer liveStatusTimer_;
|
QTimer liveStatusTimer_;
|
||||||
QTimer chattersListTimer_;
|
QTimer chattersListTimer_;
|
||||||
|
QTime titleRefreshedTime_;
|
||||||
|
|
||||||
friend class TwitchIrcServer;
|
friend class TwitchIrcServer;
|
||||||
friend class TwitchMessageBuilder;
|
friend class TwitchMessageBuilder;
|
||||||
|
|
|
@ -18,6 +18,7 @@ using EmotePtr = std::shared_ptr<const Emote>;
|
||||||
struct CheerEmote {
|
struct CheerEmote {
|
||||||
QColor color;
|
QColor color;
|
||||||
int minBits;
|
int minBits;
|
||||||
|
QRegularExpression regex;
|
||||||
|
|
||||||
EmotePtr animatedEmote;
|
EmotePtr animatedEmote;
|
||||||
EmotePtr staticEmote;
|
EmotePtr staticEmote;
|
||||||
|
|
|
@ -1,7 +1,11 @@
|
||||||
#include "TwitchIrcServer.hpp"
|
#include "TwitchIrcServer.hpp"
|
||||||
|
|
||||||
|
#include <IrcCommand>
|
||||||
|
#include <cassert>
|
||||||
|
|
||||||
#include "Application.hpp"
|
#include "Application.hpp"
|
||||||
#include "common/Common.hpp"
|
#include "common/Common.hpp"
|
||||||
|
#include "common/Env.hpp"
|
||||||
#include "controllers/accounts/AccountController.hpp"
|
#include "controllers/accounts/AccountController.hpp"
|
||||||
#include "controllers/highlights/HighlightController.hpp"
|
#include "controllers/highlights/HighlightController.hpp"
|
||||||
#include "messages/Message.hpp"
|
#include "messages/Message.hpp"
|
||||||
|
@ -15,9 +19,6 @@
|
||||||
#include "providers/twitch/TwitchMessageBuilder.hpp"
|
#include "providers/twitch/TwitchMessageBuilder.hpp"
|
||||||
#include "util/PostToThread.hpp"
|
#include "util/PostToThread.hpp"
|
||||||
|
|
||||||
#include <IrcCommand>
|
|
||||||
#include <cassert>
|
|
||||||
|
|
||||||
// using namespace Communi;
|
// using namespace Communi;
|
||||||
using namespace std::chrono_literals;
|
using namespace std::chrono_literals;
|
||||||
|
|
||||||
|
@ -86,13 +87,12 @@ void TwitchIrcServer::initializeConnection(IrcConnection *connection,
|
||||||
connection->setPassword(oauthToken);
|
connection->setPassword(oauthToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
connection->setSecure(true);
|
|
||||||
|
|
||||||
// https://dev.twitch.tv/docs/irc/guide/#connecting-to-twitch-irc
|
// https://dev.twitch.tv/docs/irc/guide/#connecting-to-twitch-irc
|
||||||
// SSL disabled: irc://irc.chat.twitch.tv:6667
|
// SSL disabled: irc://irc.chat.twitch.tv:6667 (or port 80)
|
||||||
// SSL enabled: irc://irc.chat.twitch.tv:6697
|
// SSL enabled: irc://irc.chat.twitch.tv:6697 (or port 443)
|
||||||
connection->setHost("irc.chat.twitch.tv");
|
connection->setHost(Env::get().twitchServerHost);
|
||||||
connection->setPort(6697);
|
connection->setPort(Env::get().twitchServerPort);
|
||||||
|
connection->setSecure(Env::get().twitchServerSecure);
|
||||||
|
|
||||||
this->open(type);
|
this->open(type);
|
||||||
}
|
}
|
||||||
|
@ -125,7 +125,7 @@ std::shared_ptr<Channel> TwitchIrcServer::createChannel(
|
||||||
void TwitchIrcServer::privateMessageReceived(
|
void TwitchIrcServer::privateMessageReceived(
|
||||||
Communi::IrcPrivateMessage *message)
|
Communi::IrcPrivateMessage *message)
|
||||||
{
|
{
|
||||||
IrcMessageHandler::getInstance().handlePrivMessage(message, *this);
|
IrcMessageHandler::instance().handlePrivMessage(message, *this);
|
||||||
}
|
}
|
||||||
|
|
||||||
void TwitchIrcServer::readConnectionMessageReceived(
|
void TwitchIrcServer::readConnectionMessageReceived(
|
||||||
|
@ -141,7 +141,7 @@ void TwitchIrcServer::readConnectionMessageReceived(
|
||||||
|
|
||||||
const QString &command = message->command();
|
const QString &command = message->command();
|
||||||
|
|
||||||
auto &handler = IrcMessageHandler::getInstance();
|
auto &handler = IrcMessageHandler::instance();
|
||||||
|
|
||||||
// Below commands enabled through the twitch.tv/membership CAP REQ
|
// Below commands enabled through the twitch.tv/membership CAP REQ
|
||||||
if (command == "MODE")
|
if (command == "MODE")
|
||||||
|
@ -194,14 +194,41 @@ void TwitchIrcServer::writeConnectionMessageReceived(
|
||||||
{
|
{
|
||||||
const QString &command = message->command();
|
const QString &command = message->command();
|
||||||
|
|
||||||
auto &handler = IrcMessageHandler::getInstance();
|
auto &handler = IrcMessageHandler::instance();
|
||||||
|
|
||||||
// Below commands enabled through the twitch.tv/commands CAP REQ
|
// Below commands enabled through the twitch.tv/commands CAP REQ
|
||||||
if (command == "USERSTATE")
|
if (command == "USERSTATE")
|
||||||
{
|
{
|
||||||
// Received USERSTATE upon PRIVMSGing
|
// Received USERSTATE upon PRIVMSGing
|
||||||
handler.handleUserStateMessage(message);
|
handler.handleUserStateMessage(message);
|
||||||
}
|
}
|
||||||
|
else if (command == "NOTICE")
|
||||||
|
{
|
||||||
|
static std::unordered_set<std::string> readConnectionOnlyIDs{
|
||||||
|
"host_on",
|
||||||
|
"host_off",
|
||||||
|
"host_target_went_offline",
|
||||||
|
"emote_only_on",
|
||||||
|
"emote_only_off",
|
||||||
|
"slow_on",
|
||||||
|
"slow_off",
|
||||||
|
"subs_on",
|
||||||
|
"subs_off",
|
||||||
|
"r9k_on",
|
||||||
|
"r9k_off",
|
||||||
|
|
||||||
|
// Display for user who times someone out. This implies you're a
|
||||||
|
// moderator, at which point you will be connected to PubSub and receive
|
||||||
|
// a better message from there.
|
||||||
|
"timeout_success",
|
||||||
|
"ban_success",
|
||||||
|
|
||||||
|
// Channel suspended notices
|
||||||
|
"msg_channel_suspended",
|
||||||
|
};
|
||||||
|
|
||||||
|
handler.handleNoticeMessage(
|
||||||
|
static_cast<Communi::IrcNoticeMessage *>(message));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void TwitchIrcServer::onReadConnected(IrcConnection *connection)
|
void TwitchIrcServer::onReadConnected(IrcConnection *connection)
|
||||||
|
|
|
@ -28,6 +28,10 @@
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
|
const QSet<QString> zeroWidthEmotes{
|
||||||
|
"SoSnowy", "IceCold", "SantaHat", "TopHat", "ReinDeer", "CandyCane",
|
||||||
|
};
|
||||||
|
|
||||||
QColor getRandomColor(const QVariant &userId)
|
QColor getRandomColor(const QVariant &userId)
|
||||||
{
|
{
|
||||||
static const std::vector<QColor> twitchUsernameColors = {
|
static const std::vector<QColor> twitchUsernameColors = {
|
||||||
|
@ -65,6 +69,59 @@ QColor getRandomColor(const QVariant &userId)
|
||||||
|
|
||||||
namespace chatterino {
|
namespace chatterino {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
QStringList parseTagList(const QVariantMap &tags, const QString &key)
|
||||||
|
{
|
||||||
|
auto iterator = tags.find(key);
|
||||||
|
if (iterator == tags.end())
|
||||||
|
return QStringList{};
|
||||||
|
|
||||||
|
return iterator.value().toString().split(
|
||||||
|
',', QString::SplitBehavior::SkipEmptyParts);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::map<QString, QString> parseBadgeInfos(const QVariantMap &tags)
|
||||||
|
{
|
||||||
|
std::map<QString, QString> badgeInfos;
|
||||||
|
|
||||||
|
for (QString badgeInfo : parseTagList(tags, "badge-info"))
|
||||||
|
{
|
||||||
|
QStringList parts = badgeInfo.split('/');
|
||||||
|
if (parts.size() != 2)
|
||||||
|
{
|
||||||
|
log("Skipping badge-info because it split weird: {}",
|
||||||
|
badgeInfo);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
badgeInfos.emplace(parts[0], parts[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return badgeInfos;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<Badge> parseBadges(const QVariantMap &tags)
|
||||||
|
{
|
||||||
|
std::vector<Badge> badges;
|
||||||
|
|
||||||
|
for (QString badge : parseTagList(tags, "badges"))
|
||||||
|
{
|
||||||
|
QStringList parts = badge.split('/');
|
||||||
|
if (parts.size() != 2)
|
||||||
|
{
|
||||||
|
log("Skipping badge because it split weird: {}", badge);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
badges.emplace_back(parts[0], parts[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return badges;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
TwitchMessageBuilder::TwitchMessageBuilder(
|
TwitchMessageBuilder::TwitchMessageBuilder(
|
||||||
Channel *_channel, const Communi::IrcPrivateMessage *_ircMessage,
|
Channel *_channel, const Communi::IrcPrivateMessage *_ircMessage,
|
||||||
const MessageParseArgs &_args)
|
const MessageParseArgs &_args)
|
||||||
|
@ -289,6 +346,7 @@ MessagePtr TwitchMessageBuilder::build()
|
||||||
if (iterator != this->tags.end())
|
if (iterator != this->tags.end())
|
||||||
{
|
{
|
||||||
this->hasBits_ = true;
|
this->hasBits_ = true;
|
||||||
|
this->bitsLeft = iterator.value().toInt();
|
||||||
this->bits = iterator.value().toString();
|
this->bits = iterator.value().toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -617,14 +675,12 @@ void TwitchMessageBuilder::appendUsername()
|
||||||
|
|
||||||
switch (usernameDisplayMode.getValue())
|
switch (usernameDisplayMode.getValue())
|
||||||
{
|
{
|
||||||
case UsernameDisplayMode::Username:
|
case UsernameDisplayMode::Username: {
|
||||||
{
|
|
||||||
usernameText = username;
|
usernameText = username;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case UsernameDisplayMode::LocalizedName:
|
case UsernameDisplayMode::LocalizedName: {
|
||||||
{
|
|
||||||
if (hasLocalizedName)
|
if (hasLocalizedName)
|
||||||
{
|
{
|
||||||
usernameText = localizedName;
|
usernameText = localizedName;
|
||||||
|
@ -637,8 +693,7 @@ void TwitchMessageBuilder::appendUsername()
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
case UsernameDisplayMode::UsernameAndLocalizedName:
|
case UsernameDisplayMode::UsernameAndLocalizedName: {
|
||||||
{
|
|
||||||
if (hasLocalizedName)
|
if (hasLocalizedName)
|
||||||
{
|
{
|
||||||
usernameText = username + "(" + localizedName + ")";
|
usernameText = username + "(" + localizedName + ")";
|
||||||
|
@ -655,7 +710,7 @@ void TwitchMessageBuilder::appendUsername()
|
||||||
{
|
{
|
||||||
// TODO(pajlada): Re-implement
|
// TODO(pajlada): Re-implement
|
||||||
// userDisplayString +=
|
// userDisplayString +=
|
||||||
// IrcManager::getInstance().getUser().getUserName();
|
// IrcManager::instance().getUser().getUserName();
|
||||||
}
|
}
|
||||||
else if (this->args.isReceivedWhisper)
|
else if (this->args.isReceivedWhisper)
|
||||||
{
|
{
|
||||||
|
@ -1112,6 +1167,11 @@ Outcome TwitchMessageBuilder::tryAppendEmote(const EmoteName &name)
|
||||||
else if ((emote = globalBttvEmotes.emote(name)))
|
else if ((emote = globalBttvEmotes.emote(name)))
|
||||||
{
|
{
|
||||||
flags = MessageElementFlag::BttvEmote;
|
flags = MessageElementFlag::BttvEmote;
|
||||||
|
|
||||||
|
if (zeroWidthEmotes.contains(name.string))
|
||||||
|
{
|
||||||
|
flags.set(MessageElementFlag::ZeroWidthEmote);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (emote)
|
if (emote)
|
||||||
|
@ -1123,7 +1183,24 @@ Outcome TwitchMessageBuilder::tryAppendEmote(const EmoteName &name)
|
||||||
return Failure;
|
return Failure;
|
||||||
}
|
}
|
||||||
|
|
||||||
// fourtf: this is ugly
|
boost::optional<EmotePtr> TwitchMessageBuilder::getTwitchBadge(
|
||||||
|
const Badge &badge)
|
||||||
|
{
|
||||||
|
if (auto channelBadge =
|
||||||
|
this->twitchChannel->twitchBadge(badge.key_, badge.value_))
|
||||||
|
{
|
||||||
|
return channelBadge;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (auto globalBadge = this->twitchChannel->globalTwitchBadges().badge(
|
||||||
|
badge.key_, badge.value_))
|
||||||
|
{
|
||||||
|
return globalBadge;
|
||||||
|
}
|
||||||
|
|
||||||
|
return boost::none;
|
||||||
|
}
|
||||||
|
|
||||||
void TwitchMessageBuilder::appendTwitchBadges()
|
void TwitchMessageBuilder::appendTwitchBadges()
|
||||||
{
|
{
|
||||||
if (this->twitchChannel == nullptr)
|
if (this->twitchChannel == nullptr)
|
||||||
|
@ -1131,68 +1208,25 @@ void TwitchMessageBuilder::appendTwitchBadges()
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto app = getApp();
|
auto badgeInfos = parseBadgeInfos(this->tags);
|
||||||
|
auto badges = parseBadges(this->tags);
|
||||||
|
|
||||||
auto iterator = this->tags.find("badges");
|
for (const auto &badge : badges)
|
||||||
if (iterator == this->tags.end())
|
|
||||||
return;
|
|
||||||
|
|
||||||
for (QString badge : iterator.value().toString().split(','))
|
|
||||||
{
|
{
|
||||||
if (badge.startsWith("bits/"))
|
auto badgeEmote = this->getTwitchBadge(badge);
|
||||||
|
if (!badgeEmote)
|
||||||
{
|
{
|
||||||
QString cheerAmount = badge.mid(5);
|
log("No channel/global variant found {}", badge.key_);
|
||||||
QString tooltip = QString("Twitch cheer ") + cheerAmount;
|
|
||||||
|
|
||||||
// Try to fetch channel-specific bit badge
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (twitchChannel)
|
|
||||||
if (const auto &_badge = this->twitchChannel->twitchBadge(
|
|
||||||
"bits", cheerAmount))
|
|
||||||
{
|
|
||||||
this->emplace<BadgeElement>(
|
|
||||||
_badge.get(), MessageElementFlag::BadgeVanity)
|
|
||||||
->setTooltip(tooltip);
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
auto tooltip = (*badgeEmote)->tooltip.string;
|
||||||
catch (const std::out_of_range &)
|
|
||||||
{
|
|
||||||
// Channel does not contain a special bit badge for this version
|
|
||||||
}
|
|
||||||
|
|
||||||
// Use default bit badge
|
if (badge.key_ == "bits")
|
||||||
if (auto _badge = this->twitchChannel->globalTwitchBadges().badge(
|
|
||||||
"bits", cheerAmount))
|
|
||||||
{
|
{
|
||||||
this->emplace<BadgeElement>(_badge.get(),
|
const auto &cheerAmount = badge.value_;
|
||||||
MessageElementFlag::BadgeVanity)
|
tooltip = QString("Twitch cheer %0").arg(cheerAmount);
|
||||||
->setTooltip(tooltip);
|
|
||||||
}
|
}
|
||||||
}
|
else if (badge.key_ == "moderator")
|
||||||
else if (badge == "staff/1")
|
|
||||||
{
|
|
||||||
this->emplace<ImageElement>(
|
|
||||||
Image::fromPixmap(app->resources->twitch.staff),
|
|
||||||
MessageElementFlag::BadgeGlobalAuthority)
|
|
||||||
->setTooltip("Twitch Staff");
|
|
||||||
}
|
|
||||||
else if (badge == "admin/1")
|
|
||||||
{
|
|
||||||
this->emplace<ImageElement>(
|
|
||||||
Image::fromPixmap(app->resources->twitch.admin),
|
|
||||||
MessageElementFlag::BadgeGlobalAuthority)
|
|
||||||
->setTooltip("Twitch Admin");
|
|
||||||
}
|
|
||||||
else if (badge == "global_mod/1")
|
|
||||||
{
|
|
||||||
this->emplace<ImageElement>(
|
|
||||||
Image::fromPixmap(app->resources->twitch.globalmod),
|
|
||||||
MessageElementFlag::BadgeGlobalAuthority)
|
|
||||||
->setTooltip("Twitch Global Moderator");
|
|
||||||
}
|
|
||||||
else if (badge == "moderator/1")
|
|
||||||
{
|
{
|
||||||
if (auto customModBadge = this->twitchChannel->ffzCustomModBadge())
|
if (auto customModBadge = this->twitchChannel->ffzCustomModBadge())
|
||||||
{
|
{
|
||||||
|
@ -1200,104 +1234,22 @@ void TwitchMessageBuilder::appendTwitchBadges()
|
||||||
customModBadge.get(),
|
customModBadge.get(),
|
||||||
MessageElementFlag::BadgeChannelAuthority)
|
MessageElementFlag::BadgeChannelAuthority)
|
||||||
->setTooltip((*customModBadge)->tooltip.string);
|
->setTooltip((*customModBadge)->tooltip.string);
|
||||||
|
// early out, since we have to add a custom badge element here
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
this->emplace<ImageElement>(
|
|
||||||
Image::fromPixmap(app->resources->twitch.moderator),
|
|
||||||
MessageElementFlag::BadgeChannelAuthority)
|
|
||||||
->setTooltip("Twitch Channel Moderator");
|
|
||||||
}
|
}
|
||||||
else if (badge == "vip/1")
|
else if (badge.flag_ == MessageElementFlag::BadgeSubscription)
|
||||||
{
|
{
|
||||||
this->emplace<ImageElement>(
|
auto badgeInfoIt = badgeInfos.find(badge.key_);
|
||||||
Image::fromPixmap(app->resources->twitch.vip),
|
if (badgeInfoIt != badgeInfos.end())
|
||||||
MessageElementFlag::BadgeChannelAuthority)
|
{
|
||||||
->setTooltip("VIP");
|
const auto &subMonths = badgeInfoIt->second;
|
||||||
|
tooltip += QString(" (%0 months)").arg(subMonths);
|
||||||
}
|
}
|
||||||
else if (badge == "broadcaster/1")
|
|
||||||
{
|
|
||||||
this->emplace<ImageElement>(
|
|
||||||
Image::fromPixmap(app->resources->twitch.broadcaster),
|
|
||||||
MessageElementFlag::BadgeChannelAuthority)
|
|
||||||
->setTooltip("Twitch Broadcaster");
|
|
||||||
}
|
|
||||||
else if (badge == "turbo/1")
|
|
||||||
{
|
|
||||||
this->emplace<ImageElement>(
|
|
||||||
Image::fromPixmap(app->resources->twitch.turbo),
|
|
||||||
MessageElementFlag::BadgeVanity)
|
|
||||||
->setTooltip("Twitch Turbo Subscriber");
|
|
||||||
}
|
|
||||||
else if (badge == "premium/1")
|
|
||||||
{
|
|
||||||
this->emplace<ImageElement>(
|
|
||||||
Image::fromPixmap(app->resources->twitch.prime),
|
|
||||||
MessageElementFlag::BadgeVanity)
|
|
||||||
->setTooltip("Twitch Prime Subscriber");
|
|
||||||
}
|
|
||||||
else if (badge.startsWith("partner/"))
|
|
||||||
{
|
|
||||||
int index = badge.midRef(8).toInt();
|
|
||||||
switch (index)
|
|
||||||
{
|
|
||||||
case 1:
|
|
||||||
{
|
|
||||||
this->emplace<ImageElement>(
|
|
||||||
Image::fromPixmap(app->resources->twitch.verified,
|
|
||||||
0.25),
|
|
||||||
MessageElementFlag::BadgeVanity)
|
|
||||||
->setTooltip("Twitch Verified");
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
{
|
|
||||||
printf("[TwitchMessageBuilder] Unhandled partner badge "
|
|
||||||
"index: %d\n",
|
|
||||||
index);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (badge.startsWith("subscriber/"))
|
|
||||||
{
|
|
||||||
if (auto badgeEmote = this->twitchChannel->twitchBadge(
|
|
||||||
"subscriber", badge.mid(11)))
|
|
||||||
{
|
|
||||||
this->emplace<BadgeElement>(
|
|
||||||
badgeEmote.get(), MessageElementFlag::BadgeSubscription)
|
|
||||||
->setTooltip((*badgeEmote)->tooltip.string);
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// use default subscriber badge if custom one not found
|
this->emplace<BadgeElement>(badgeEmote.get(), badge.flag_)
|
||||||
this->emplace<ImageElement>(
|
->setTooltip(tooltip);
|
||||||
Image::fromPixmap(app->resources->twitch.subscriber, 0.25),
|
|
||||||
MessageElementFlag::BadgeSubscription)
|
|
||||||
->setTooltip("Twitch Subscriber");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
auto splits = badge.split('/');
|
|
||||||
if (splits.size() != 2)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if (auto badgeEmote =
|
|
||||||
this->twitchChannel->twitchBadge(splits[0], splits[1]))
|
|
||||||
{
|
|
||||||
this->emplace<BadgeElement>(badgeEmote.get(),
|
|
||||||
MessageElementFlag::BadgeVanity)
|
|
||||||
->setTooltip((*badgeEmote)->tooltip.string);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (auto _badge = this->twitchChannel->globalTwitchBadges().badge(
|
|
||||||
splits[0], splits[1]))
|
|
||||||
{
|
|
||||||
this->emplace<BadgeElement>(_badge.get(),
|
|
||||||
MessageElementFlag::BadgeVanity)
|
|
||||||
->setTooltip((*_badge)->tooltip.string);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1312,12 +1264,34 @@ void TwitchMessageBuilder::appendChatterinoBadges()
|
||||||
|
|
||||||
Outcome TwitchMessageBuilder::tryParseCheermote(const QString &string)
|
Outcome TwitchMessageBuilder::tryParseCheermote(const QString &string)
|
||||||
{
|
{
|
||||||
|
if (this->bitsLeft == 0)
|
||||||
|
{
|
||||||
|
return Failure;
|
||||||
|
}
|
||||||
|
|
||||||
auto cheerOpt = this->twitchChannel->cheerEmote(string);
|
auto cheerOpt = this->twitchChannel->cheerEmote(string);
|
||||||
|
|
||||||
if (!cheerOpt)
|
if (!cheerOpt)
|
||||||
{
|
{
|
||||||
return Failure;
|
return Failure;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto &cheerEmote = *cheerOpt;
|
auto &cheerEmote = *cheerOpt;
|
||||||
|
auto match = cheerEmote.regex.match(string);
|
||||||
|
|
||||||
|
if (!match.hasMatch())
|
||||||
|
{
|
||||||
|
return Failure;
|
||||||
|
}
|
||||||
|
|
||||||
|
int cheerValue = match.captured(1).toInt();
|
||||||
|
|
||||||
|
if (getSettings()->stackBits)
|
||||||
|
{
|
||||||
|
if (this->bitsStacked)
|
||||||
|
{
|
||||||
|
return Success;
|
||||||
|
}
|
||||||
if (cheerEmote.staticEmote)
|
if (cheerEmote.staticEmote)
|
||||||
{
|
{
|
||||||
this->emplace<EmoteElement>(cheerEmote.staticEmote,
|
this->emplace<EmoteElement>(cheerEmote.staticEmote,
|
||||||
|
@ -1330,9 +1304,45 @@ Outcome TwitchMessageBuilder::tryParseCheermote(const QString &string)
|
||||||
}
|
}
|
||||||
if (cheerEmote.color != QColor())
|
if (cheerEmote.color != QColor())
|
||||||
{
|
{
|
||||||
this->emplace<TextElement>(this->bits, MessageElementFlag::BitsAmount,
|
this->emplace<TextElement>(QString::number(this->bitsLeft),
|
||||||
|
MessageElementFlag::BitsAmount,
|
||||||
cheerEmote.color);
|
cheerEmote.color);
|
||||||
}
|
}
|
||||||
|
this->bitsStacked = true;
|
||||||
return Success;
|
return Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this->bitsLeft >= cheerValue)
|
||||||
|
{
|
||||||
|
this->bitsLeft -= cheerValue;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
QString newString = string;
|
||||||
|
newString.chop(QString::number(cheerValue).length());
|
||||||
|
newString += QString::number(cheerValue - this->bitsLeft);
|
||||||
|
|
||||||
|
return tryParseCheermote(newString);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cheerEmote.staticEmote)
|
||||||
|
{
|
||||||
|
this->emplace<EmoteElement>(cheerEmote.staticEmote,
|
||||||
|
MessageElementFlag::BitsStatic);
|
||||||
|
}
|
||||||
|
if (cheerEmote.animatedEmote)
|
||||||
|
{
|
||||||
|
this->emplace<EmoteElement>(cheerEmote.animatedEmote,
|
||||||
|
MessageElementFlag::BitsAnimated);
|
||||||
|
}
|
||||||
|
if (cheerEmote.color != QColor())
|
||||||
|
{
|
||||||
|
this->emplace<TextElement>(match.captured(1),
|
||||||
|
MessageElementFlag::BitsAmount,
|
||||||
|
cheerEmote.color);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Success;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace chatterino
|
} // namespace chatterino
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
#include "common/Aliases.hpp"
|
#include "common/Aliases.hpp"
|
||||||
#include "common/Outcome.hpp"
|
#include "common/Outcome.hpp"
|
||||||
#include "messages/MessageBuilder.hpp"
|
#include "messages/MessageBuilder.hpp"
|
||||||
|
#include "providers/twitch/TwitchBadge.hpp"
|
||||||
|
|
||||||
#include <IrcMessage>
|
#include <IrcMessage>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
|
@ -60,6 +61,7 @@ private:
|
||||||
// parseHighlights only updates the visual state of the message, but leaves the playing of alerts and sounds to the triggerHighlights function
|
// parseHighlights only updates the visual state of the message, but leaves the playing of alerts and sounds to the triggerHighlights function
|
||||||
void parseHighlights();
|
void parseHighlights();
|
||||||
|
|
||||||
|
boost::optional<EmotePtr> getTwitchBadge(const Badge &badge);
|
||||||
void appendTwitchEmote(
|
void appendTwitchEmote(
|
||||||
const QString &emote,
|
const QString &emote,
|
||||||
std::vector<std::tuple<int, EmotePtr, EmoteName>> &vec,
|
std::vector<std::tuple<int, EmotePtr, EmoteName>> &vec,
|
||||||
|
@ -79,6 +81,8 @@ private:
|
||||||
QString roomID_;
|
QString roomID_;
|
||||||
bool hasBits_ = false;
|
bool hasBits_ = false;
|
||||||
QString bits;
|
QString bits;
|
||||||
|
int bitsLeft;
|
||||||
|
bool bitsStacked = false;
|
||||||
bool historicalMessage_ = false;
|
bool historicalMessage_ = false;
|
||||||
|
|
||||||
QString userId_;
|
QString userId_;
|
||||||
|
|
|
@ -24,7 +24,7 @@
|
||||||
# endif
|
# endif
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
namespace AB_NAMESPACE {
|
namespace chatterino {
|
||||||
namespace {
|
namespace {
|
||||||
int getBoldness()
|
int getBoldness()
|
||||||
{
|
{
|
||||||
|
@ -89,7 +89,7 @@ void Fonts::initialize(Settings &, Paths &)
|
||||||
},
|
},
|
||||||
false);
|
false);
|
||||||
#endif
|
#endif
|
||||||
} // namespace AB_NAMESPACE
|
}
|
||||||
|
|
||||||
QFont Fonts::getFont(FontStyle type, float scale)
|
QFont Fonts::getFont(FontStyle type, float scale)
|
||||||
{
|
{
|
||||||
|
@ -178,4 +178,4 @@ Fonts *getFonts()
|
||||||
return Fonts::instance;
|
return Fonts::instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace AB_NAMESPACE
|
} // namespace chatterino
|
|
@ -12,7 +12,7 @@
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
|
|
||||||
namespace AB_NAMESPACE {
|
namespace chatterino {
|
||||||
|
|
||||||
class Settings;
|
class Settings;
|
||||||
class Paths;
|
class Paths;
|
||||||
|
@ -90,4 +90,4 @@ private:
|
||||||
|
|
||||||
Fonts *getFonts();
|
Fonts *getFonts();
|
||||||
|
|
||||||
} // namespace AB_NAMESPACE
|
} // namespace chatterino
|
|
@ -11,6 +11,8 @@
|
||||||
#include "common/Modes.hpp"
|
#include "common/Modes.hpp"
|
||||||
#include "util/CombinePath.hpp"
|
#include "util/CombinePath.hpp"
|
||||||
|
|
||||||
|
using namespace std::literals;
|
||||||
|
|
||||||
namespace chatterino {
|
namespace chatterino {
|
||||||
|
|
||||||
Paths *Paths::instance = nullptr;
|
Paths *Paths::instance = nullptr;
|
||||||
|
@ -22,7 +24,7 @@ Paths::Paths()
|
||||||
this->initAppFilePathHash();
|
this->initAppFilePathHash();
|
||||||
|
|
||||||
this->initCheckPortable();
|
this->initCheckPortable();
|
||||||
this->initAppDataDirectory();
|
this->initRootDirectory();
|
||||||
this->initSubDirectories();
|
this->initSubDirectories();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -33,7 +35,7 @@ bool Paths::createFolder(const QString &folderPath)
|
||||||
|
|
||||||
bool Paths::isPortable()
|
bool Paths::isPortable()
|
||||||
{
|
{
|
||||||
return Modes::getInstance().isPortable;
|
return Modes::instance().isPortable;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString Paths::cacheDirectory()
|
QString Paths::cacheDirectory()
|
||||||
|
@ -76,7 +78,7 @@ void Paths::initCheckPortable()
|
||||||
combinePath(QCoreApplication::applicationDirPath(), "portable"));
|
combinePath(QCoreApplication::applicationDirPath(), "portable"));
|
||||||
}
|
}
|
||||||
|
|
||||||
void Paths::initAppDataDirectory()
|
void Paths::initRootDirectory()
|
||||||
{
|
{
|
||||||
assert(this->portable_.is_initialized());
|
assert(this->portable_.is_initialized());
|
||||||
|
|
||||||
|
@ -95,8 +97,8 @@ void Paths::initAppDataDirectory()
|
||||||
QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
|
QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
|
||||||
if (path.isEmpty())
|
if (path.isEmpty())
|
||||||
{
|
{
|
||||||
throw std::runtime_error(
|
throw std::runtime_error("Could not create directory \""s +
|
||||||
"Error finding writable location for settings");
|
path.toStdString() + "\"");
|
||||||
}
|
}
|
||||||
|
|
||||||
// create directory Chatterino2 instead of chatterino on windows because the
|
// create directory Chatterino2 instead of chatterino on windows because the
|
||||||
|
@ -123,8 +125,8 @@ void Paths::initSubDirectories()
|
||||||
|
|
||||||
if (!QDir().mkpath(path))
|
if (!QDir().mkpath(path))
|
||||||
{
|
{
|
||||||
throw std::runtime_error(
|
throw std::runtime_error("Could not create directory \""s +
|
||||||
"Error creating appdata path %appdata%/chatterino/" + name);
|
path.toStdString() + "\"");
|
||||||
}
|
}
|
||||||
|
|
||||||
return path;
|
return path;
|
||||||
|
|
|
@ -39,7 +39,7 @@ public:
|
||||||
private:
|
private:
|
||||||
void initAppFilePathHash();
|
void initAppFilePathHash();
|
||||||
void initCheckPortable();
|
void initCheckPortable();
|
||||||
void initAppDataDirectory();
|
void initRootDirectory();
|
||||||
void initSubDirectories();
|
void initSubDirectories();
|
||||||
|
|
||||||
boost::optional<bool> portable_;
|
boost::optional<bool> portable_;
|
||||||
|
|
|
@ -1 +1,24 @@
|
||||||
#include "singletons/Resources.hpp"
|
#include "singletons/Resources.hpp"
|
||||||
|
|
||||||
|
#include "debug/AssertInGuiThread.hpp"
|
||||||
|
|
||||||
|
namespace chatterino {
|
||||||
|
namespace {
|
||||||
|
static Resources2 *resources = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
Resources2 &getResources()
|
||||||
|
{
|
||||||
|
assert(resources);
|
||||||
|
|
||||||
|
return *resources;
|
||||||
|
}
|
||||||
|
|
||||||
|
void initResources()
|
||||||
|
{
|
||||||
|
assertInGuiThread();
|
||||||
|
|
||||||
|
resources = new Resources2;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace chatterino
|
||||||
|
|
|
@ -1,3 +1,12 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "autogenerated/ResourcesAutogen.hpp"
|
#include "autogenerated/ResourcesAutogen.hpp"
|
||||||
|
|
||||||
|
namespace chatterino {
|
||||||
|
|
||||||
|
/// This class in thread safe but needs to be initialized from the gui thread
|
||||||
|
/// first.
|
||||||
|
Resources2 &getResources();
|
||||||
|
void initResources();
|
||||||
|
|
||||||
|
} // namespace chatterino
|
||||||
|
|
|
@ -9,12 +9,12 @@
|
||||||
|
|
||||||
namespace chatterino {
|
namespace chatterino {
|
||||||
|
|
||||||
Settings *Settings::instance = nullptr;
|
Settings *Settings::instance_ = nullptr;
|
||||||
|
|
||||||
Settings::Settings(const QString &settingsDirectory)
|
Settings::Settings(const QString &settingsDirectory)
|
||||||
: ABSettings(settingsDirectory)
|
: ABSettings(settingsDirectory)
|
||||||
{
|
{
|
||||||
instance = this;
|
instance_ = this;
|
||||||
|
|
||||||
#ifdef USEWINSDK
|
#ifdef USEWINSDK
|
||||||
this->autorun = isRegisteredForStartup();
|
this->autorun = isRegisteredForStartup();
|
||||||
|
@ -23,14 +23,14 @@ Settings::Settings(const QString &settingsDirectory)
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
Settings &Settings::getInstance()
|
Settings &Settings::instance()
|
||||||
{
|
{
|
||||||
return *instance;
|
return *instance_;
|
||||||
}
|
}
|
||||||
|
|
||||||
Settings *getSettings()
|
Settings *getSettings()
|
||||||
{
|
{
|
||||||
return &Settings::getInstance();
|
return &Settings::instance();
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace chatterino
|
} // namespace chatterino
|
||||||
|
|
|
@ -1,25 +1,24 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "BaseSettings.hpp"
|
#include <pajlada/settings/setting.hpp>
|
||||||
|
#include <pajlada/settings/settinglistener.hpp>
|
||||||
|
|
||||||
|
#include "BaseSettings.hpp"
|
||||||
#include "common/Channel.hpp"
|
#include "common/Channel.hpp"
|
||||||
#include "controllers/highlights/HighlightPhrase.hpp"
|
#include "controllers/highlights/HighlightPhrase.hpp"
|
||||||
#include "controllers/moderationactions/ModerationAction.hpp"
|
#include "controllers/moderationactions/ModerationAction.hpp"
|
||||||
#include "singletons/Toasts.hpp"
|
#include "singletons/Toasts.hpp"
|
||||||
|
|
||||||
#include <pajlada/settings/setting.hpp>
|
|
||||||
#include <pajlada/settings/settinglistener.hpp>
|
|
||||||
|
|
||||||
namespace chatterino {
|
namespace chatterino {
|
||||||
|
|
||||||
class Settings : public ABSettings
|
class Settings : public ABSettings
|
||||||
{
|
{
|
||||||
static Settings *instance;
|
static Settings *instance_;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
Settings(const QString &settingsDirectory);
|
Settings(const QString &settingsDirectory);
|
||||||
|
|
||||||
static Settings &getInstance();
|
static Settings &instance();
|
||||||
|
|
||||||
/// Appearance
|
/// Appearance
|
||||||
BoolSetting showTimestamps = {"/appearance/messages/showTimestamps", true};
|
BoolSetting showTimestamps = {"/appearance/messages/showTimestamps", true};
|
||||||
|
@ -121,6 +120,8 @@ public:
|
||||||
|
|
||||||
QStringSetting emojiSet = {"/emotes/emojiSet", "EmojiOne 2"};
|
QStringSetting emojiSet = {"/emotes/emojiSet", "EmojiOne 2"};
|
||||||
|
|
||||||
|
BoolSetting stackBits = {"/emotes/stackBits", false};
|
||||||
|
|
||||||
/// Links
|
/// Links
|
||||||
BoolSetting linksDoubleClickOnly = {"/links/doubleClickToOpen", false};
|
BoolSetting linksDoubleClickOnly = {"/links/doubleClickToOpen", false};
|
||||||
BoolSetting linkInfoTooltip = {"/links/linkInfoTooltip", false};
|
BoolSetting linkInfoTooltip = {"/links/linkInfoTooltip", false};
|
||||||
|
@ -204,6 +205,7 @@ public:
|
||||||
#ifdef Q_OS_LINUX
|
#ifdef Q_OS_LINUX
|
||||||
BoolSetting useKeyring = {"/misc/useKeyring", true};
|
BoolSetting useKeyring = {"/misc/useKeyring", true};
|
||||||
#endif
|
#endif
|
||||||
|
BoolSetting enableExperimentalIrc = {"/misc/experimentalIrc", false};
|
||||||
|
|
||||||
IntSetting startUpNotification = {"/misc/startUpNotification", 0};
|
IntSetting startUpNotification = {"/misc/startUpNotification", 0};
|
||||||
QStringSetting currentVersion = {"/misc/currentVersion", ""};
|
QStringSetting currentVersion = {"/misc/currentVersion", ""};
|
||||||
|
@ -213,6 +215,9 @@ public:
|
||||||
BoolSetting openLinksIncognito = {"/misc/openLinksIncognito", 0};
|
BoolSetting openLinksIncognito = {"/misc/openLinksIncognito", 0};
|
||||||
|
|
||||||
QStringSetting cachePath = {"/cache/path", ""};
|
QStringSetting cachePath = {"/cache/path", ""};
|
||||||
|
BoolSetting restartOnCrash = {"/misc/restartOnCrash", false};
|
||||||
|
BoolSetting attachExtensionToAnyProcess = {
|
||||||
|
"/misc/attachExtensionToAnyProcess", false};
|
||||||
|
|
||||||
/// Debug
|
/// Debug
|
||||||
BoolSetting showUnhandledIrcMessages = {"/debug/showUnhandledIrcMessages",
|
BoolSetting showUnhandledIrcMessages = {"/debug/showUnhandledIrcMessages",
|
||||||
|
|
|
@ -85,7 +85,7 @@ void Theme::actuallyUpdate(double hue, double multiplier)
|
||||||
if (getSettings()->highlightColor != "")
|
if (getSettings()->highlightColor != "")
|
||||||
{
|
{
|
||||||
this->messages.backgrounds.highlighted =
|
this->messages.backgrounds.highlighted =
|
||||||
QColor(getSettings()->highlightColor);
|
QColor(getSettings()->highlightColor.getValue());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -134,8 +134,7 @@ public:
|
||||||
}
|
}
|
||||||
QDesktopServices::openUrl(QUrl(link));
|
QDesktopServices::openUrl(QUrl(link));
|
||||||
break;
|
break;
|
||||||
case ToastReaction::OpenInStreamlink:
|
case ToastReaction::OpenInStreamlink: {
|
||||||
{
|
|
||||||
openStreamlinkForChannel(channelName_);
|
openStreamlinkForChannel(channelName_);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue