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
46
.CI/CreateAppImage.sh
Normal file → Executable file
46
.CI/CreateAppImage.sh
Normal file → Executable file
|
@ -1,21 +1,45 @@
|
|||
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/opt/qt512/lib/
|
||||
ldd ./bin/chatterino
|
||||
make INSTALL_ROOT=appdir -j$(nproc) install ; find appdir/
|
||||
cp ./resources/icon.png ./appdir/chatterino.png
|
||||
#!/bin/sh
|
||||
|
||||
wget -nv "https://github.com/probonopd/linuxdeployqt/releases/download/continuous/linuxdeployqt-continuous-x86_64.AppImage"
|
||||
chmod a+x linuxdeployqt-continuous-x86_64.AppImage
|
||||
wget -nv "https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage"
|
||||
chmod a+x appimagetool-x86_64.AppImage
|
||||
./linuxdeployqt-continuous-x86_64.AppImage \
|
||||
set -e
|
||||
|
||||
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"
|
||||
chmod a+x appimagetool-x86_64.AppImage
|
||||
fi
|
||||
./"$linuxdeployqt_path" \
|
||||
appdir/usr/share/applications/*.desktop \
|
||||
-no-translations \
|
||||
-bundle-non-qt-libs \
|
||||
-unsupported-allow-new-glibc \
|
||||
-qmake=/opt/qt512/bin/qmake
|
||||
-qmake="$qmake_path"
|
||||
|
||||
rm -rf appdir/home
|
||||
rm appdir/AppRun
|
||||
rm -f appdir/AppRun
|
||||
|
||||
# shellcheck disable=SC2016
|
||||
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**
|
||||
<!-- A clear and concise description of what the bug is. -->
|
||||
|
||||
**To Reproduce**
|
||||
**To reproduce**
|
||||
<!-- Steps to reproduce the behavior -->
|
||||
|
||||
**Expected behavior**
|
||||
<!-- A clear and concise description of what you expected to happen. -->
|
||||
|
||||
**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 -->
|
||||
|
||||
**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**
|
||||
<!-- Add any other context about the problem here. -->
|
||||
**Operating system**
|
||||
<!-- 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"]
|
||||
path = lib/libcommuni
|
||||
url = https://github.com/hemirt/libcommuni
|
||||
url = https://github.com/Chatterino/libcommuni
|
||||
[submodule "lib/humanize"]
|
||||
path = lib/humanize
|
||||
url = https://github.com/pajlada/humanize.git
|
||||
|
|
|
@ -64,7 +64,9 @@ matrix:
|
|||
script:
|
||||
- mkdir build && cd build
|
||||
- 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
|
||||
- mkdir app
|
||||
- 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. `qmake .. && make`
|
||||
|
||||
## Fedora 28
|
||||
*most likely works the same for other Red Hat-like distros*
|
||||
1. `sudo yum install qt-creator qt5-qtmultimedia-devel qt5-qtsvg-devel openssl-devel gstreamer-plugins-ugly gstreamer-plugins-good boost-devel rapidjson-devel`
|
||||
1. Open `chatterino.pro` with QT Creator and build
|
||||
## Fedora 28 and above
|
||||
*most likely works the same for other Red Hat-like distros. Substitue `dnf` with `yum`.*
|
||||
### Development dependencies
|
||||
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+
|
||||
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)
|
||||
|
||||
## 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
|
||||
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.
|
||||
|
||||
## 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
|
||||
|
||||
set QTDIR=C:\Qt\5.11\msvc2017_64
|
||||
set QTDIR=C:\Qt\5.13\msvc2017_64
|
||||
|
||||
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
|
||||
CONFIG += communi
|
||||
COMMUNI += core model util
|
||||
|
@ -8,7 +21,6 @@ TEMPLATE = app
|
|||
PRECOMPILED_HEADER = src/PrecompiledHeader.hpp
|
||||
CONFIG += precompile_header
|
||||
DEFINES += CHATTERINO
|
||||
DEFINES += "AB_NAMESPACE=chatterino"
|
||||
DEFINES += AB_CUSTOM_THEME
|
||||
DEFINES += AB_CUSTOM_SETTINGS
|
||||
CONFIG += AB_NOT_STANDALONE
|
||||
|
@ -20,12 +32,29 @@ useBreakpad {
|
|||
}
|
||||
|
||||
# use C++17
|
||||
CONFIG += c++17
|
||||
|
||||
# C++17 backwards compatability
|
||||
win32-msvc* {
|
||||
QMAKE_CXXFLAGS += /std:c++17
|
||||
} else {
|
||||
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
|
||||
equals(QMAKE_CXX, "clang++")|equals(QMAKE_CXX, "g++") {
|
||||
TARGET = bin/chatterino
|
||||
|
@ -39,9 +68,14 @@ macx {
|
|||
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
|
||||
include(lib/warnings.pri)
|
||||
include(lib/appbase.pri)
|
||||
include(lib/fmt.pri)
|
||||
include(lib/humanize.pri)
|
||||
include(lib/libcommuni.pri)
|
||||
|
@ -75,9 +109,13 @@ else{
|
|||
SOURCES += \
|
||||
src/Application.cpp \
|
||||
src/autogenerated/ResourcesAutogen.cpp \
|
||||
src/BaseSettings.cpp \
|
||||
src/BaseTheme.cpp \
|
||||
src/BrowserExtension.cpp \
|
||||
src/common/Args.cpp \
|
||||
src/common/Channel.cpp \
|
||||
src/common/ChannelChatters.cpp \
|
||||
src/common/ChatterinoSetting.cpp \
|
||||
src/common/CompletionModel.cpp \
|
||||
src/common/Credentials.cpp \
|
||||
src/common/DownloadManager.cpp \
|
||||
|
@ -112,6 +150,7 @@ SOURCES += \
|
|||
src/controllers/taggedusers/TaggedUser.cpp \
|
||||
src/controllers/taggedusers/TaggedUsersController.cpp \
|
||||
src/controllers/taggedusers/TaggedUsersModel.cpp \
|
||||
src/debug/Benchmark.cpp \
|
||||
src/main.cpp \
|
||||
src/messages/Emote.cpp \
|
||||
src/messages/Image.cpp \
|
||||
|
@ -150,6 +189,7 @@ SOURCES += \
|
|||
src/providers/twitch/TwitchAccount.cpp \
|
||||
src/providers/twitch/TwitchAccountManager.cpp \
|
||||
src/providers/twitch/TwitchApi.cpp \
|
||||
src/providers/twitch/TwitchBadge.cpp \
|
||||
src/providers/twitch/TwitchBadges.cpp \
|
||||
src/providers/twitch/TwitchChannel.cpp \
|
||||
src/providers/twitch/TwitchEmotes.cpp \
|
||||
|
@ -161,6 +201,7 @@ SOURCES += \
|
|||
src/RunGui.cpp \
|
||||
src/singletons/Badges.cpp \
|
||||
src/singletons/Emotes.cpp \
|
||||
src/singletons/Fonts.cpp \
|
||||
src/singletons/helper/GifTimer.cpp \
|
||||
src/singletons/helper/LoggingChannel.cpp \
|
||||
src/singletons/Logging.cpp \
|
||||
|
@ -175,15 +216,22 @@ SOURCES += \
|
|||
src/singletons/WindowManager.cpp \
|
||||
src/util/DebugCount.cpp \
|
||||
src/util/FormatTime.cpp \
|
||||
src/util/FunctionEventFilter.cpp \
|
||||
src/util/FuzzyConvert.cpp \
|
||||
src/util/Helpers.cpp \
|
||||
src/util/IncognitoBrowser.cpp \
|
||||
src/util/InitUpdateButton.cpp \
|
||||
src/util/JsonQuery.cpp \
|
||||
src/util/RapidjsonHelpers.cpp \
|
||||
src/util/StreamLink.cpp \
|
||||
src/util/NuulsUploader.cpp \
|
||||
src/util/WindowsHelper.cpp \
|
||||
src/widgets/AccountSwitchPopup.cpp \
|
||||
src/widgets/AccountSwitchWidget.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/IrcConnectionEditor.cpp \
|
||||
src/widgets/dialogs/LastRunCrashDialog.cpp \
|
||||
|
@ -197,16 +245,21 @@ SOURCES += \
|
|||
src/widgets/dialogs/UpdateDialog.cpp \
|
||||
src/widgets/dialogs/UserInfoPopup.cpp \
|
||||
src/widgets/dialogs/WelcomeDialog.cpp \
|
||||
src/widgets/helper/Button.cpp \
|
||||
src/widgets/helper/ChannelView.cpp \
|
||||
src/widgets/helper/ComboBoxItemDelegate.cpp \
|
||||
src/widgets/helper/DebugPopup.cpp \
|
||||
src/widgets/helper/EditableModelView.cpp \
|
||||
src/widgets/helper/EffectLabel.cpp \
|
||||
src/widgets/helper/NotebookButton.cpp \
|
||||
src/widgets/helper/NotebookTab.cpp \
|
||||
src/widgets/helper/ResizingTextEdit.cpp \
|
||||
src/widgets/helper/ScrollbarHighlight.cpp \
|
||||
src/widgets/helper/SearchPopup.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/Scrollbar.cpp \
|
||||
src/widgets/settingspages/AboutPage.cpp \
|
||||
|
@ -227,22 +280,28 @@ SOURCES += \
|
|||
src/widgets/splits/SplitInput.cpp \
|
||||
src/widgets/splits/SplitOverlay.cpp \
|
||||
src/widgets/StreamView.cpp \
|
||||
src/widgets/TooltipWidget.cpp \
|
||||
src/widgets/Window.cpp \
|
||||
|
||||
HEADERS += \
|
||||
src/Application.hpp \
|
||||
src/autogenerated/ResourcesAutogen.hpp \
|
||||
src/BaseSettings.hpp \
|
||||
src/BaseTheme.hpp \
|
||||
src/BrowserExtension.hpp \
|
||||
src/common/Aliases.hpp \
|
||||
src/common/Args.hpp \
|
||||
src/common/Atomic.hpp \
|
||||
src/common/Channel.hpp \
|
||||
src/common/ChannelChatters.hpp \
|
||||
src/common/ChatterinoSetting.hpp \
|
||||
src/common/Common.hpp \
|
||||
src/common/CompletionModel.hpp \
|
||||
src/common/ConcurrentMap.hpp \
|
||||
src/common/Credentials.hpp \
|
||||
src/common/DownloadManager.hpp \
|
||||
src/common/Env.hpp \
|
||||
src/common/FlagsEnum.hpp \
|
||||
src/common/LinkParser.hpp \
|
||||
src/common/Modes.hpp \
|
||||
src/common/NetworkCommon.hpp \
|
||||
|
@ -251,9 +310,11 @@ HEADERS += \
|
|||
src/common/NetworkRequest.hpp \
|
||||
src/common/NetworkResult.hpp \
|
||||
src/common/NullablePtr.hpp \
|
||||
src/common/Outcome.hpp \
|
||||
src/common/ProviderId.hpp \
|
||||
src/common/SignalVector.hpp \
|
||||
src/common/SignalVectorModel.hpp \
|
||||
src/common/Singleton.hpp \
|
||||
src/common/UniqueAccess.hpp \
|
||||
src/common/UsernameSet.hpp \
|
||||
src/common/Version.hpp \
|
||||
|
@ -282,6 +343,9 @@ HEADERS += \
|
|||
src/controllers/taggedusers/TaggedUser.hpp \
|
||||
src/controllers/taggedusers/TaggedUsersController.hpp \
|
||||
src/controllers/taggedusers/TaggedUsersModel.hpp \
|
||||
src/debug/AssertInGuiThread.hpp \
|
||||
src/debug/Benchmark.hpp \
|
||||
src/debug/Log.hpp \
|
||||
src/ForwardDecl.hpp \
|
||||
src/messages/Emote.hpp \
|
||||
src/messages/Image.hpp \
|
||||
|
@ -327,6 +391,7 @@ HEADERS += \
|
|||
src/providers/twitch/TwitchAccount.hpp \
|
||||
src/providers/twitch/TwitchAccountManager.hpp \
|
||||
src/providers/twitch/TwitchApi.hpp \
|
||||
src/providers/twitch/TwitchBadge.hpp \
|
||||
src/providers/twitch/TwitchBadges.hpp \
|
||||
src/providers/twitch/TwitchChannel.hpp \
|
||||
src/providers/twitch/TwitchCommon.hpp \
|
||||
|
@ -339,6 +404,7 @@ HEADERS += \
|
|||
src/RunGui.hpp \
|
||||
src/singletons/Badges.hpp \
|
||||
src/singletons/Emotes.hpp \
|
||||
src/singletons/Fonts.hpp \
|
||||
src/singletons/helper/GifTimer.hpp \
|
||||
src/singletons/helper/LoggingChannel.hpp \
|
||||
src/singletons/Logging.hpp \
|
||||
|
@ -351,29 +417,44 @@ HEADERS += \
|
|||
src/singletons/TooltipPreviewImage.hpp \
|
||||
src/singletons/Updates.hpp \
|
||||
src/singletons/WindowManager.hpp \
|
||||
src/util/Clamp.hpp \
|
||||
src/util/CombinePath.hpp \
|
||||
src/util/ConcurrentMap.hpp \
|
||||
src/util/DebugCount.hpp \
|
||||
src/util/DistanceBetweenPoints.hpp \
|
||||
src/util/FormatTime.hpp \
|
||||
src/util/FunctionEventFilter.hpp \
|
||||
src/util/FuzzyConvert.hpp \
|
||||
src/util/Helpers.hpp \
|
||||
src/util/IncognitoBrowser.hpp \
|
||||
src/util/InitUpdateButton.hpp \
|
||||
src/util/IrcHelpers.hpp \
|
||||
src/util/IsBigEndian.hpp \
|
||||
src/util/JsonQuery.hpp \
|
||||
src/util/LayoutCreator.hpp \
|
||||
src/util/LayoutHelper.hpp \
|
||||
src/util/Overloaded.hpp \
|
||||
src/util/PostToThread.hpp \
|
||||
src/util/QObjectRef.hpp \
|
||||
src/util/QStringHash.hpp \
|
||||
src/util/rangealgorithm.hpp \
|
||||
src/util/RapidjsonHelpers.hpp \
|
||||
src/util/RapidJsonSerializeQString.hpp \
|
||||
src/util/RemoveScrollAreaBackground.hpp \
|
||||
src/util/SampleCheerMessages.hpp \
|
||||
src/util/SampleLinks.hpp \
|
||||
src/util/SharedPtrElementLess.hpp \
|
||||
src/util/Shortcut.hpp \
|
||||
src/util/StandardItemHelper.hpp \
|
||||
src/util/StreamLink.hpp \
|
||||
src/util/NuulsUploader.hpp \
|
||||
src/util/WindowsHelper.hpp \
|
||||
src/widgets/AccountSwitchPopup.hpp \
|
||||
src/widgets/AccountSwitchWidget.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/IrcConnectionEditor.hpp \
|
||||
src/widgets/dialogs/LastRunCrashDialog.hpp \
|
||||
|
@ -387,11 +468,13 @@ HEADERS += \
|
|||
src/widgets/dialogs/UpdateDialog.hpp \
|
||||
src/widgets/dialogs/UserInfoPopup.hpp \
|
||||
src/widgets/dialogs/WelcomeDialog.hpp \
|
||||
src/widgets/helper/Button.hpp \
|
||||
src/widgets/helper/ChannelView.hpp \
|
||||
src/widgets/helper/ComboBoxItemDelegate.hpp \
|
||||
src/widgets/helper/CommonTexts.hpp \
|
||||
src/widgets/helper/DebugPopup.hpp \
|
||||
src/widgets/helper/EditableModelView.hpp \
|
||||
src/widgets/helper/EffectLabel.hpp \
|
||||
src/widgets/helper/Line.hpp \
|
||||
src/widgets/helper/NotebookButton.hpp \
|
||||
src/widgets/helper/NotebookTab.hpp \
|
||||
|
@ -399,6 +482,9 @@ HEADERS += \
|
|||
src/widgets/helper/ScrollbarHighlight.hpp \
|
||||
src/widgets/helper/SearchPopup.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/Scrollbar.hpp \
|
||||
src/widgets/settingspages/AboutPage.hpp \
|
||||
|
@ -419,6 +505,7 @@ HEADERS += \
|
|||
src/widgets/splits/SplitInput.hpp \
|
||||
src/widgets/splits/SplitOverlay.hpp \
|
||||
src/widgets/StreamView.hpp \
|
||||
src/widgets/TooltipWidget.hpp \
|
||||
src/widgets/Window.hpp \
|
||||
|
||||
RESOURCES += \
|
||||
|
@ -441,13 +528,13 @@ linux:isEmpty(PREFIX) {
|
|||
}
|
||||
|
||||
linux {
|
||||
desktop.files = resources/chatterino.desktop
|
||||
desktop.files = resources/com.chatterino.chatterino.desktop
|
||||
desktop.path = $$PREFIX/share/applications
|
||||
|
||||
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
|
||||
|
||||
target.path = $$PREFIX/bin
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
[requires]
|
||||
OpenSSL/1.0.2o@conan/stable
|
||||
boost/1.69.0@conan/stable
|
||||
openssl/1.1.1d
|
||||
boost/1.71.0
|
||||
|
||||
[generators]
|
||||
qmake
|
||||
|
||||
[options]
|
||||
OpenSSL:shared=True
|
||||
openssl:shared=True
|
||||
|
||||
[imports]
|
||||
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)
|
||||
- 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 {
|
||||
BOOST_DIRECTORY = C:\dev\projects\boost_1_66_0\
|
||||
}
|
||||
|
|
18
lib/fmt.pri
18
lib/fmt.pri
|
@ -1,4 +1,18 @@
|
|||
# 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)
|
||||
|
||||
INCLUDEPATH += $$PWD/fmt/
|
||||
!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/
|
||||
}
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit a31ffb037eadac65dba73ad2b2da6dafe31e3bf7
|
||||
Subproject commit f3e7f97914d9bf1166d349a83d93a2b4f4743c39
|
|
@ -1,2 +1,15 @@
|
|||
# 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/update.png</file>
|
||||
<file>buttons/updateError.png</file>
|
||||
<file>chatterino.desktop</file>
|
||||
<file>com.chatterino.chatterino.desktop</file>
|
||||
<file>chatterino.icns</file>
|
||||
<file>contributors.txt</file>
|
||||
<file>emoji.json</file>
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
Language: Cpp
|
||||
|
||||
AccessModifierOffset: -1
|
||||
AccessModifierOffset: -4
|
||||
AlignEscapedNewlinesLeft: true
|
||||
AllowShortFunctionsOnASingleLine: false
|
||||
|
@ -10,12 +9,12 @@ AlwaysBreakAfterDefinitionReturnType: false
|
|||
AlwaysBreakBeforeMultilineStrings: false
|
||||
BasedOnStyle: Google
|
||||
BraceWrapping: {
|
||||
AfterNamespace: 'false'
|
||||
AfterClass: 'true'
|
||||
BeforeElse: 'true'
|
||||
AfterControlStatement: 'true'
|
||||
AfterFunction: 'true'
|
||||
AfterNamespace: 'false'
|
||||
BeforeCatch: 'true'
|
||||
BeforeElse: 'true'
|
||||
}
|
||||
BreakBeforeBraces: Custom
|
||||
BreakConstructorInitializersBeforeComma: true
|
||||
|
@ -27,6 +26,7 @@ IndentCaseLabels: true
|
|||
IndentWidth: 4
|
||||
IndentWrappedFunctionNames: true
|
||||
IndentPPDirectives: AfterHash
|
||||
IncludeBlocks: Preserve
|
||||
NamespaceIndentation: Inner
|
||||
PointerBindsToType: false
|
||||
SpacesBeforeTrailingComments: 2
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
#include "Application.hpp"
|
||||
|
||||
#include <atomic>
|
||||
|
||||
#include "common/Args.hpp"
|
||||
#include "controllers/accounts/AccountController.hpp"
|
||||
#include "controllers/commands/CommandController.hpp"
|
||||
#include "controllers/highlights/HighlightController.hpp"
|
||||
|
@ -29,9 +32,9 @@
|
|||
#include "singletons/WindowManager.hpp"
|
||||
#include "util/IsBigEndian.hpp"
|
||||
#include "util/PostToThread.hpp"
|
||||
#include "widgets/Notebook.hpp"
|
||||
#include "widgets/Window.hpp"
|
||||
|
||||
#include <atomic>
|
||||
#include "widgets/splits/Split.hpp"
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
|
@ -44,9 +47,7 @@ Application *Application::instance = nullptr;
|
|||
// to each other
|
||||
|
||||
Application::Application(Settings &_settings, Paths &_paths)
|
||||
: resources(&this->emplace<Resources2>())
|
||||
|
||||
, themes(&this->emplace<Theme>())
|
||||
: themes(&this->emplace<Theme>())
|
||||
, fonts(&this->emplace<Fonts>())
|
||||
, emotes(&this->emplace<Emotes>())
|
||||
, windows(&this->emplace<WindowManager>())
|
||||
|
@ -79,13 +80,38 @@ void Application::initialize(Settings &settings, Paths &paths)
|
|||
assert(isAppInitialized == false);
|
||||
isAppInitialized = true;
|
||||
|
||||
Irc::getInstance().load();
|
||||
if (getSettings()->enableExperimentalIrc)
|
||||
{
|
||||
Irc::instance().load();
|
||||
}
|
||||
|
||||
for (auto &singleton : this->singletons_)
|
||||
{
|
||||
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->initNm(paths);
|
||||
|
@ -104,7 +130,7 @@ int Application::run(QApplication &qtApp)
|
|||
this->windows->getMainWindow().show();
|
||||
|
||||
getSettings()->betaUpdates.connect(
|
||||
[] { Updates::getInstance().checkForUpdates(); }, false);
|
||||
[] { Updates::instance().checkForUpdates(); }, false);
|
||||
|
||||
return qtApp.exec();
|
||||
}
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
#pragma once
|
||||
|
||||
#include "common/Singleton.hpp"
|
||||
#include "singletons/NativeMessaging.hpp"
|
||||
|
||||
#include <QApplication>
|
||||
#include <memory>
|
||||
|
||||
#include "common/Singleton.hpp"
|
||||
#include "singletons/NativeMessaging.hpp"
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
class TwitchIrcServer;
|
||||
|
@ -28,7 +28,6 @@ class AccountManager;
|
|||
class Emotes;
|
||||
class Settings;
|
||||
class Fonts;
|
||||
class Resources2;
|
||||
class Toasts;
|
||||
class ChatterinoBadges;
|
||||
|
||||
|
@ -51,8 +50,6 @@ public:
|
|||
|
||||
friend void test();
|
||||
|
||||
Resources2 *const resources;
|
||||
|
||||
Theme *const themes{};
|
||||
Fonts *const fonts{};
|
||||
Emotes *const emotes{};
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
#include "util/Clamp.hpp"
|
||||
|
||||
namespace AB_NAMESPACE {
|
||||
namespace chatterino {
|
||||
|
||||
std::vector<std::weak_ptr<pajlada::Settings::SettingData>> _settings;
|
||||
|
||||
|
@ -125,4 +125,4 @@ AB_SETTINGS_CLASS *getABSettings()
|
|||
return AB_SETTINGS_CLASS::instance;
|
||||
}
|
||||
|
||||
} // namespace AB_NAMESPACE
|
||||
} // namespace chatterino
|
|
@ -14,7 +14,7 @@
|
|||
# define AB_SETTINGS_CLASS Settings
|
||||
#endif
|
||||
|
||||
namespace AB_NAMESPACE {
|
||||
namespace chatterino {
|
||||
|
||||
class Settings;
|
||||
|
||||
|
@ -44,7 +44,7 @@ private:
|
|||
Settings *getSettings();
|
||||
AB_SETTINGS_CLASS *getABSettings();
|
||||
|
||||
} // namespace AB_NAMESPACE
|
||||
} // namespace chatterino
|
||||
|
||||
#ifdef CHATTERINO
|
||||
# include "singletons/Settings.hpp"
|
|
@ -1,6 +1,6 @@
|
|||
#include "BaseTheme.hpp"
|
||||
|
||||
namespace AB_NAMESPACE {
|
||||
namespace chatterino {
|
||||
namespace {
|
||||
double getMultiplierByTheme(const QString &themeName)
|
||||
{
|
||||
|
@ -63,9 +63,9 @@ void AB_THEME_CLASS::actuallyUpdate(double hue, double multiplier)
|
|||
/// WINDOW
|
||||
{
|
||||
#ifdef Q_OS_LINUX
|
||||
this->window.background = lightWin ? "#fff" : QColor(61, 60, 56);
|
||||
this->window.background = lightWin ? "#fff" : QColor(61, 60, 56);
|
||||
#else
|
||||
this->window.background = lightWin ? "#fff" : "#111";
|
||||
this->window.background = lightWin ? "#fff" : "#111";
|
||||
#endif
|
||||
|
||||
QColor fg = this->window.text = lightWin ? "#000" : "#eee";
|
||||
|
@ -149,10 +149,7 @@ void AB_THEME_CLASS::actuallyUpdate(double hue, double multiplier)
|
|||
// QColor("#777"), QColor("#666")}};
|
||||
|
||||
this->tabs.bottomLine = this->tabs.selected.backgrounds.regular.color();
|
||||
} // namespace AB_NAMESPACE
|
||||
|
||||
// Split
|
||||
bool flat = isLight_;
|
||||
}
|
||||
|
||||
// Message
|
||||
this->messages.textColors.link =
|
||||
|
@ -222,4 +219,4 @@ Theme *getTheme()
|
|||
}
|
||||
#endif
|
||||
|
||||
} // namespace AB_NAMESPACE
|
||||
} // namespace chatterino
|
|
@ -11,7 +11,7 @@
|
|||
# define AB_THEME_CLASS Theme
|
||||
#endif
|
||||
|
||||
namespace AB_NAMESPACE {
|
||||
namespace chatterino {
|
||||
|
||||
class Theme;
|
||||
|
||||
|
@ -109,7 +109,7 @@ private:
|
|||
// Otherwise implemented in BaseThemecpp
|
||||
Theme *getTheme();
|
||||
|
||||
} // namespace AB_NAMESPACE
|
||||
} // namespace chatterino
|
||||
|
||||
#ifdef CHATTERINO
|
||||
# include "singletons/Theme.hpp"
|
|
@ -4,10 +4,16 @@
|
|||
#include <QFile>
|
||||
#include <QPalette>
|
||||
#include <QStyleFactory>
|
||||
#include <Qt>
|
||||
#include <QtConcurrent>
|
||||
#include <csignal>
|
||||
|
||||
#include "Application.hpp"
|
||||
#include "common/Modes.hpp"
|
||||
#include "common/NetworkManager.hpp"
|
||||
#include "singletons/Paths.hpp"
|
||||
#include "singletons/Resources.hpp"
|
||||
#include "singletons/Settings.hpp"
|
||||
#include "singletons/Updates.hpp"
|
||||
#include "util/CombinePath.hpp"
|
||||
#include "widgets/dialogs/LastRunCrashDialog.hpp"
|
||||
|
@ -20,12 +26,6 @@
|
|||
# include <QBreakpadHandler.h>
|
||||
#endif
|
||||
|
||||
// void initQt();
|
||||
// void installCustomPalette();
|
||||
// void showLastCrashDialog();
|
||||
// void createRunningFile(const QString &path);
|
||||
// void removeRunningFile(const QString &path);
|
||||
|
||||
namespace chatterino {
|
||||
namespace {
|
||||
void installCustomPalette()
|
||||
|
@ -108,11 +108,68 @@ namespace {
|
|||
{
|
||||
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
|
||||
|
||||
void runGui(QApplication &a, Paths &paths, Settings &settings)
|
||||
{
|
||||
initQt();
|
||||
initResources();
|
||||
initSignalHandler();
|
||||
|
||||
settings.restartOnCrash.connect(
|
||||
[](const bool &value) { restartOnSignal = value; });
|
||||
|
||||
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::Updates::getInstance().checkForUpdates();
|
||||
chatterino::Updates::instance().checkForUpdates();
|
||||
|
||||
#ifdef C_USE_BREAKPAD
|
||||
QBreakpadInstance.setDumpPath(getPaths()->settingsFolderPath + "/Crashes");
|
||||
|
|
|
@ -50,4 +50,4 @@ Resources2::Resources2()
|
|||
this->twitch.vip = QPixmap(":/twitch/vip.png");
|
||||
}
|
||||
|
||||
} // namespace chatterino
|
||||
} // namespace chatterino
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
#include <QPixmap>
|
||||
|
||||
#include "common/Singleton.hpp"
|
||||
|
||||
namespace chatterino {
|
||||
|
@ -64,4 +65,4 @@ public:
|
|||
} twitch;
|
||||
};
|
||||
|
||||
} // 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
|
||||
// WindowManager::getInstance().repaintVisibleChatWidgets(this);
|
||||
// WindowManager::instance().repaintVisibleChatWidgets(this);
|
||||
}
|
||||
|
||||
void Channel::disableAllMessages()
|
||||
|
|
|
@ -2,11 +2,11 @@
|
|||
|
||||
#include "BaseSettings.hpp"
|
||||
|
||||
namespace AB_NAMESPACE {
|
||||
namespace chatterino {
|
||||
|
||||
void _registerSetting(std::weak_ptr<pajlada::Settings::SettingData> setting)
|
||||
{
|
||||
_actuallyRegisterSetting(setting);
|
||||
}
|
||||
|
||||
} // namespace AB_NAMESPACE
|
||||
} // namespace chatterino
|
|
@ -3,7 +3,7 @@
|
|||
#include <QString>
|
||||
#include <pajlada/settings.hpp>
|
||||
|
||||
namespace AB_NAMESPACE {
|
||||
namespace chatterino {
|
||||
|
||||
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
|
||||
|
||||
#include "common/Aliases.hpp"
|
||||
#include "common/Outcome.hpp"
|
||||
#include "common/ProviderId.hpp"
|
||||
|
||||
#include <QString>
|
||||
#include <QWidget>
|
||||
#include <boost/optional.hpp>
|
||||
#include <boost/preprocessor.hpp>
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "common/Aliases.hpp"
|
||||
#include "common/Outcome.hpp"
|
||||
#include "common/ProviderId.hpp"
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
enum class HighlightState {
|
||||
|
@ -46,4 +45,14 @@ enum class CopyMode {
|
|||
OnlyTextAndEmotes,
|
||||
};
|
||||
|
||||
struct DeleteLater {
|
||||
void operator()(QObject *obj)
|
||||
{
|
||||
obj->deleteLater();
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
using QObjectPtr = std::unique_ptr<T, DeleteLater>;
|
||||
|
||||
} // namespace chatterino
|
||||
|
|
|
@ -143,7 +143,7 @@ namespace {
|
|||
}
|
||||
} // namespace
|
||||
|
||||
Credentials &Credentials::getInstance()
|
||||
Credentials &Credentials::instance()
|
||||
{
|
||||
static Credentials creds;
|
||||
return creds;
|
||||
|
@ -166,11 +166,12 @@ void Credentials::get(const QString &provider, const QString &name_,
|
|||
auto job = new QKeychain::ReadPasswordJob("chatterino");
|
||||
job->setAutoDelete(true);
|
||||
job->setKey(name);
|
||||
QObject::connect(job, &QKeychain::Job::finished, receiver,
|
||||
[job, onLoaded = std::move(onLoaded)](auto) mutable {
|
||||
onLoaded(job->textData());
|
||||
},
|
||||
Qt::DirectConnection);
|
||||
QObject::connect(
|
||||
job, &QKeychain::Job::finished, receiver,
|
||||
[job, onLoaded = std::move(onLoaded)](auto) mutable {
|
||||
onLoaded(job->textData());
|
||||
},
|
||||
Qt::DirectConnection);
|
||||
job->start();
|
||||
}
|
||||
else
|
||||
|
@ -199,7 +200,9 @@ void Credentials::set(const QString &provider, const QString &name_,
|
|||
{
|
||||
auto &instance = insecureInstance();
|
||||
|
||||
instance.object()[name] = credential;
|
||||
auto obj = instance.object();
|
||||
obj[name] = credential;
|
||||
instance.setObject(obj);
|
||||
|
||||
queueInsecureSave();
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@ namespace chatterino {
|
|||
class Credentials
|
||||
{
|
||||
public:
|
||||
static Credentials &getInstance();
|
||||
static Credentials &instance();
|
||||
|
||||
void get(const QString &provider, const QString &name, QObject *receiver,
|
||||
std::function<void(const QString &)> &&onLoaded);
|
||||
|
|
|
@ -48,13 +48,11 @@ void DownloadManager::onFinished(QNetworkReply *reply)
|
|||
{
|
||||
switch (reply->error())
|
||||
{
|
||||
case QNetworkReply::NoError:
|
||||
{
|
||||
case QNetworkReply::NoError: {
|
||||
qDebug("file is downloaded successfully.");
|
||||
}
|
||||
break;
|
||||
default:
|
||||
{
|
||||
default: {
|
||||
qDebug() << reply->errorString().toLatin1();
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
#include "common/Env.hpp"
|
||||
|
||||
#include <QVariant>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
namespace {
|
||||
|
@ -15,6 +17,33 @@ namespace {
|
|||
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
|
||||
|
||||
Env::Env()
|
||||
|
@ -30,6 +59,10 @@ Env::Env()
|
|||
"https://braize.pajlada.com/chatterino/twitchemotes/set/%1/"))
|
||||
, imageUploaderUrl(readStringEnv("CHATTERINO2_IMAGE_PASTE_SITE_URL",
|
||||
"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 twitchEmoteSetResolverUrl;
|
||||
const QString imageUploaderUrl;
|
||||
const QString twitchServerHost;
|
||||
const uint16_t twitchServerPort;
|
||||
const bool twitchServerSecure;
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
||||
|
|
|
@ -1,67 +1,201 @@
|
|||
#include "common/LinkParser.hpp"
|
||||
|
||||
#include <QFile>
|
||||
#include <QMap>
|
||||
#include <QRegularExpression>
|
||||
#include <QString>
|
||||
#include <QStringRef>
|
||||
#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 {
|
||||
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)
|
||||
{
|
||||
static QRegularExpression linkRegex = [] {
|
||||
static QRegularExpression newLineRegex("\r?\n");
|
||||
QFile file(":/tlds.txt");
|
||||
file.open(QFile::ReadOnly);
|
||||
QTextStream tlds(&file);
|
||||
tlds.setCodec("UTF-8");
|
||||
this->match_ = unparsedString;
|
||||
|
||||
// tldData gets injected into the LINK macro
|
||||
auto tldData = tlds.readAll().replace(newLineRegex, "|");
|
||||
(void)tldData;
|
||||
// This is not implemented with a regex to increase performance.
|
||||
// We keep removing parts of the url until there's either nothing left or we fail.
|
||||
QStringRef l(&unparsedString);
|
||||
|
||||
return QRegularExpression(LINK,
|
||||
QRegularExpression::CaseInsensitiveOption);
|
||||
}();
|
||||
bool hasHttp = false;
|
||||
|
||||
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
|
||||
{
|
||||
return this->match_.hasMatch();
|
||||
return this->hasMatch_;
|
||||
}
|
||||
|
||||
QString LinkParser::getCaptured() const
|
||||
{
|
||||
return this->match_.captured();
|
||||
return this->match_;
|
||||
}
|
||||
|
||||
} // namespace chatterino
|
||||
|
|
|
@ -14,7 +14,8 @@ public:
|
|||
QString getCaptured() const;
|
||||
|
||||
private:
|
||||
QRegularExpressionMatch match_;
|
||||
bool hasMatch_{false};
|
||||
QString match_;
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
||||
|
|
|
@ -27,7 +27,7 @@ Modes::Modes()
|
|||
}
|
||||
}
|
||||
|
||||
const Modes &Modes::getInstance()
|
||||
const Modes &Modes::instance()
|
||||
{
|
||||
static Modes instance;
|
||||
return instance;
|
||||
|
|
|
@ -7,7 +7,7 @@ class Modes
|
|||
public:
|
||||
Modes();
|
||||
|
||||
static const Modes &getInstance();
|
||||
static const Modes &instance();
|
||||
|
||||
bool isNightly{};
|
||||
bool isPortable{};
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
#include <boost/noncopyable.hpp>
|
||||
|
||||
namespace AB_NAMESPACE {
|
||||
namespace chatterino {
|
||||
|
||||
class Settings;
|
||||
class Paths;
|
||||
|
@ -23,4 +23,4 @@ public:
|
|||
}
|
||||
};
|
||||
|
||||
} // namespace AB_NAMESPACE
|
||||
} // namespace chatterino
|
|
@ -16,50 +16,46 @@ Version::Version()
|
|||
this->commitHash_ =
|
||||
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
|
||||
this->dateOfBuild_ =
|
||||
QString(FROM_EXTERNAL_DEFINE(CHATTERINO_NIGHTLY_VERSION_STRING))
|
||||
.remove('"');
|
||||
.remove('"')
|
||||
.split(' ')[0];
|
||||
#endif
|
||||
|
||||
// "Full" version string, as displayed in window title
|
||||
this->fullVersion_ = "Chatterino ";
|
||||
if (Modes::getInstance().isNightly)
|
||||
if (Modes::instance().isNightly)
|
||||
{
|
||||
this->fullVersion_ += "Nightly ";
|
||||
}
|
||||
|
||||
this->fullVersion_ += this->version_;
|
||||
|
||||
if (Modes::getInstance().isNightly)
|
||||
{
|
||||
this->fullVersion_ += this->dateOfBuild_;
|
||||
}
|
||||
}
|
||||
|
||||
const Version &Version::getInstance()
|
||||
const Version &Version::instance()
|
||||
{
|
||||
static Version instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
const QString &Version::getVersion() const
|
||||
const QString &Version::version() const
|
||||
{
|
||||
return this->version_;
|
||||
}
|
||||
|
||||
const QString &Version::getFullVersion() const
|
||||
const QString &Version::fullVersion() const
|
||||
{
|
||||
return this->fullVersion_;
|
||||
}
|
||||
|
||||
const QString &Version::getCommitHash() const
|
||||
const QString &Version::commitHash() const
|
||||
{
|
||||
return this->commitHash_;
|
||||
}
|
||||
|
||||
const QString &Version::getDateOfBuild() const
|
||||
const QString &Version::dateOfBuild() const
|
||||
{
|
||||
return this->dateOfBuild_;
|
||||
}
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
#pragma once
|
||||
|
||||
#include <QString>
|
||||
#include <QtGlobal>
|
||||
|
||||
#define CHATTERINO_VERSION "2.1.4"
|
||||
#define CHATTERINO_VERSION "2.1.7"
|
||||
|
||||
#if defined(Q_OS_WIN)
|
||||
# define CHATTERINO_OS "win"
|
||||
|
@ -19,12 +20,12 @@ namespace chatterino {
|
|||
class Version
|
||||
{
|
||||
public:
|
||||
static const Version &getInstance();
|
||||
static const Version &instance();
|
||||
|
||||
const QString &getVersion() const;
|
||||
const QString &getCommitHash() const;
|
||||
const QString &getDateOfBuild() const;
|
||||
const QString &getFullVersion() const;
|
||||
const QString &version() const;
|
||||
const QString &commitHash() const;
|
||||
const QString &dateOfBuild() const;
|
||||
const QString &fullVersion() const;
|
||||
|
||||
private:
|
||||
Version();
|
||||
|
@ -35,4 +36,4 @@ private:
|
|||
QString fullVersion_;
|
||||
};
|
||||
|
||||
};
|
||||
}; // namespace chatterino
|
||||
|
|
|
@ -27,8 +27,7 @@ AccountController::AccountController()
|
|||
this->accounts_.itemRemoved.connect([this](const auto &args) {
|
||||
switch (args.item->getProviderId())
|
||||
{
|
||||
case ProviderId::Twitch:
|
||||
{
|
||||
case ProviderId::Twitch: {
|
||||
if (args.caller != this)
|
||||
{
|
||||
auto accs = this->twitch.accounts.cloneVector();
|
||||
|
|
|
@ -70,8 +70,7 @@ void HighlightModel::customRowSetData(const std::vector<QStandardItem *> &row,
|
|||
{
|
||||
switch (column)
|
||||
{
|
||||
case 0:
|
||||
{
|
||||
case 0: {
|
||||
if (role == Qt::CheckStateRole)
|
||||
{
|
||||
if (rowIndex == 0)
|
||||
|
@ -86,8 +85,7 @@ void HighlightModel::customRowSetData(const std::vector<QStandardItem *> &row,
|
|||
}
|
||||
}
|
||||
break;
|
||||
case 1:
|
||||
{
|
||||
case 1: {
|
||||
if (role == Qt::CheckStateRole)
|
||||
{
|
||||
if (rowIndex == 0)
|
||||
|
@ -103,8 +101,7 @@ void HighlightModel::customRowSetData(const std::vector<QStandardItem *> &row,
|
|||
}
|
||||
}
|
||||
break;
|
||||
case 2:
|
||||
{
|
||||
case 2: {
|
||||
if (role == Qt::CheckStateRole)
|
||||
{
|
||||
if (rowIndex == 0)
|
||||
|
@ -120,8 +117,7 @@ void HighlightModel::customRowSetData(const std::vector<QStandardItem *> &row,
|
|||
}
|
||||
}
|
||||
break;
|
||||
case 3:
|
||||
{
|
||||
case 3: {
|
||||
// empty element
|
||||
}
|
||||
break;
|
||||
|
|
|
@ -119,8 +119,8 @@ struct Deserialize<chatterino::HighlightPhrase> {
|
|||
chatterino::rj::getSafe(value, "regex", _isRegex);
|
||||
chatterino::rj::getSafe(value, "case", _caseSensitive);
|
||||
|
||||
return chatterino::HighlightPhrase(_pattern, _alert, _sound,
|
||||
_isRegex, _caseSensitive);
|
||||
return chatterino::HighlightPhrase(_pattern, _alert, _sound, _isRegex,
|
||||
_caseSensitive);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -65,17 +65,17 @@ ModerationAction::ModerationAction(const QString &action)
|
|||
// line1 = this->line1_;
|
||||
// line2 = this->line2_;
|
||||
// } else {
|
||||
// this->_moderationActions.emplace_back(app->resources->buttonTimeout,
|
||||
// this->_moderationActions.emplace_back(getResources().buttonTimeout,
|
||||
// str);
|
||||
// }
|
||||
}
|
||||
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 "))
|
||||
{
|
||||
this->image_ = Image::fromPixmap(getApp()->resources->buttons.trashCan);
|
||||
this->image_ = Image::fromPixmap(getResources().buttons.trashCan);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
#include <QThread>
|
||||
#include <cassert>
|
||||
|
||||
namespace AB_NAMESPACE {
|
||||
namespace chatterino {
|
||||
|
||||
static bool isGuiThread()
|
||||
{
|
||||
|
@ -18,4 +18,4 @@ static void assertInGuiThread()
|
|||
#endif
|
||||
}
|
||||
|
||||
} // namespace AB_NAMESPACE
|
||||
} // namespace chatterino
|
|
@ -1,6 +1,6 @@
|
|||
#include "Benchmark.hpp"
|
||||
|
||||
namespace AB_NAMESPACE {
|
||||
namespace chatterino {
|
||||
|
||||
BenchmarkGuard::BenchmarkGuard(const QString &_name)
|
||||
: name_(_name)
|
||||
|
@ -18,4 +18,4 @@ qreal BenchmarkGuard::getElapsedMs()
|
|||
return qreal(timer_.nsecsElapsed()) / 1000000.0;
|
||||
}
|
||||
|
||||
} // namespace AB_NAMESPACE
|
||||
} // namespace chatterino
|
|
@ -5,7 +5,7 @@
|
|||
#include <QElapsedTimer>
|
||||
#include <boost/noncopyable.hpp>
|
||||
|
||||
namespace AB_NAMESPACE {
|
||||
namespace chatterino {
|
||||
|
||||
class BenchmarkGuard : boost::noncopyable
|
||||
{
|
||||
|
@ -19,4 +19,4 @@ private:
|
|||
QString name_;
|
||||
};
|
||||
|
||||
} // namespace AB_NAMESPACE
|
||||
} // namespace chatterino
|
|
@ -5,7 +5,7 @@
|
|||
#include <QDebug>
|
||||
#include <QTime>
|
||||
|
||||
namespace AB_NAMESPACE {
|
||||
namespace chatterino {
|
||||
|
||||
template <typename... 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)...);
|
||||
}
|
||||
|
||||
} // 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 "RunGui.hpp"
|
||||
#include "common/Args.hpp"
|
||||
#include "common/Modes.hpp"
|
||||
#include "common/Version.hpp"
|
||||
#include "singletons/Paths.hpp"
|
||||
#include "singletons/Settings.hpp"
|
||||
#include "util/IncognitoBrowser.hpp"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QStringList>
|
||||
#include <memory>
|
||||
|
||||
using namespace chatterino;
|
||||
|
||||
int main(int argc, char **argv)
|
||||
|
@ -18,17 +23,49 @@ int main(int argc, char **argv)
|
|||
auto args = QStringList();
|
||||
std::transform(argv + 1, argv + argc, std::back_inserter(args),
|
||||
[&](auto s) { return s; });
|
||||
initArgs(args);
|
||||
|
||||
// run in gui mode or browser extension host mode
|
||||
if (shouldRunBrowserExtensionHost(args))
|
||||
{
|
||||
runBrowserExtensionHost();
|
||||
}
|
||||
else if (getArgs().printVersion)
|
||||
{
|
||||
qInfo().noquote() << Version::instance().fullVersion();
|
||||
}
|
||||
else
|
||||
{
|
||||
Paths paths;
|
||||
Settings settings(paths.settingsDirectory);
|
||||
Paths *paths{};
|
||||
|
||||
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 <QBuffer>
|
||||
#include <QImageReader>
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QNetworkReply>
|
||||
#include <QNetworkRequest>
|
||||
#include <QTimer>
|
||||
#include <functional>
|
||||
#include <thread>
|
||||
|
||||
#include "Application.hpp"
|
||||
#include "common/Common.hpp"
|
||||
#include "common/NetworkRequest.hpp"
|
||||
|
@ -11,15 +20,6 @@
|
|||
#include "util/DebugCount.hpp"
|
||||
#include "util/PostToThread.hpp"
|
||||
|
||||
#include <QBuffer>
|
||||
#include <QImageReader>
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QNetworkReply>
|
||||
#include <QNetworkRequest>
|
||||
#include <QTimer>
|
||||
#include <functional>
|
||||
#include <thread>
|
||||
|
||||
namespace chatterino {
|
||||
namespace detail {
|
||||
// Frames
|
||||
|
@ -198,6 +198,15 @@ namespace detail {
|
|||
} // namespace detail
|
||||
|
||||
// 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)
|
||||
{
|
||||
static std::unordered_map<Url, std::weak_ptr<Image>> cache;
|
||||
|
@ -324,7 +333,7 @@ int Image::width() const
|
|||
assertInGuiThread();
|
||||
|
||||
if (auto pixmap = this->frames_->first())
|
||||
return pixmap->width() * this->scale_;
|
||||
return int(pixmap->width() * this->scale_);
|
||||
else
|
||||
return 16;
|
||||
}
|
||||
|
@ -369,6 +378,7 @@ void Image::actuallyLoad()
|
|||
if (!shared)
|
||||
return false;
|
||||
|
||||
// fourtf: is this the right thing to do?
|
||||
shared->empty_ = true;
|
||||
|
||||
return true;
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
#include <pajlada/signals/signal.hpp>
|
||||
|
||||
#include "common/Aliases.hpp"
|
||||
#include "common/NullablePtr.hpp"
|
||||
#include "common/Common.hpp"
|
||||
|
||||
namespace chatterino {
|
||||
namespace detail {
|
||||
|
@ -45,9 +45,12 @@ namespace detail {
|
|||
class Image;
|
||||
using ImagePtr = std::shared_ptr<Image>;
|
||||
|
||||
/// This class is thread safe.
|
||||
class Image : public std::enable_shared_from_this<Image>, boost::noncopyable
|
||||
{
|
||||
public:
|
||||
~Image();
|
||||
|
||||
static ImagePtr fromUrl(const Url &url, qreal scale = 1);
|
||||
static ImagePtr fromPixmap(const QPixmap &pixmap, qreal scale = 1);
|
||||
static ImagePtr getEmpty();
|
||||
|
@ -72,14 +75,14 @@ private:
|
|||
Image(qreal scale);
|
||||
|
||||
void setPixmap(const QPixmap &pixmap);
|
||||
|
||||
void actuallyLoad();
|
||||
|
||||
Url url_{};
|
||||
qreal scale_{1};
|
||||
bool empty_{false};
|
||||
const Url url_{};
|
||||
const qreal scale_{1};
|
||||
std::atomic_bool empty_{false};
|
||||
|
||||
// gui thread only
|
||||
bool shouldLoad_{false};
|
||||
std::unique_ptr<detail::Frames> frames_{};
|
||||
QObject object_{};
|
||||
};
|
||||
} // namespace chatterino
|
||||
|
|
|
@ -32,9 +32,8 @@ std::pair<MessagePtr, MessagePtr> makeAutomodMessage(
|
|||
builder.message().flags.set(MessageFlag::PubSub);
|
||||
|
||||
builder
|
||||
.emplace<ImageElement>(
|
||||
Image::fromPixmap(getApp()->resources->twitch.automod),
|
||||
MessageElementFlag::BadgeChannelAuthority)
|
||||
.emplace<ImageElement>(Image::fromPixmap(getResources().twitch.automod),
|
||||
MessageElementFlag::BadgeChannelAuthority)
|
||||
->setTooltip("AutoMod");
|
||||
builder.emplace<TextElement>("AutoMod:", MessageElementFlag::BoldUsername,
|
||||
MessageColor(QColor("blue")),
|
||||
|
@ -258,40 +257,35 @@ MessageBuilder::MessageBuilder(const AutomodUserAction &action)
|
|||
QString text;
|
||||
switch (action.type)
|
||||
{
|
||||
case AutomodUserAction::AddPermitted:
|
||||
{
|
||||
case AutomodUserAction::AddPermitted: {
|
||||
text = QString("%1 added %2 as a permitted term on AutoMod.")
|
||||
.arg(action.source.name)
|
||||
.arg(action.message);
|
||||
}
|
||||
break;
|
||||
|
||||
case AutomodUserAction::AddBlocked:
|
||||
{
|
||||
case AutomodUserAction::AddBlocked: {
|
||||
text = QString("%1 added %2 as a blocked term on AutoMod.")
|
||||
.arg(action.source.name)
|
||||
.arg(action.message);
|
||||
}
|
||||
break;
|
||||
|
||||
case AutomodUserAction::RemovePermitted:
|
||||
{
|
||||
case AutomodUserAction::RemovePermitted: {
|
||||
text = QString("%1 removed %2 as a permitted term term on AutoMod.")
|
||||
.arg(action.source.name)
|
||||
.arg(action.message);
|
||||
}
|
||||
break;
|
||||
|
||||
case AutomodUserAction::RemoveBlocked:
|
||||
{
|
||||
case AutomodUserAction::RemoveBlocked: {
|
||||
text = QString("%1 removed %2 as a blocked term on AutoMod.")
|
||||
.arg(action.source.name)
|
||||
.arg(action.message);
|
||||
}
|
||||
break;
|
||||
|
||||
case AutomodUserAction::Properties:
|
||||
{
|
||||
case AutomodUserAction::Properties: {
|
||||
text = QString("%1 modified the AutoMod properties.")
|
||||
.arg(action.source.name);
|
||||
}
|
||||
|
|
|
@ -103,6 +103,10 @@ enum class MessageElementFlag {
|
|||
LowercaseLink = (1 << 29),
|
||||
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 |
|
||||
BttvEmoteImage | TwitchEmoteImage | BitsAmount | Text |
|
||||
AlwaysShow,
|
||||
|
|
|
@ -158,7 +158,7 @@ void MessageLayout::paint(QPainter &painter, int width, int y, int messageIndex,
|
|||
// create new buffer if required
|
||||
if (!pixmap)
|
||||
{
|
||||
#ifdef Q_OS_MACOS
|
||||
#if defined(Q_OS_MACOS) || defined(Q_OS_LINUX)
|
||||
pixmap = new QPixmap(int(width * painter.device()->devicePixelRatioF()),
|
||||
int(container_->getHeight() *
|
||||
painter.device()->devicePixelRatioF()));
|
||||
|
|
|
@ -115,15 +115,27 @@ void MessageLayoutContainer::_addElement(MessageLayoutElement *element,
|
|||
// update line height
|
||||
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
|
||||
element->setPosition(
|
||||
QPoint(this->currentX_, this->currentY_ - element->getRect().height()));
|
||||
element->setPosition(QPoint(this->currentX_ + xOffset,
|
||||
this->currentY_ - element->getRect().height()));
|
||||
|
||||
// add element
|
||||
this->elements_.push_back(std::unique_ptr<MessageLayoutElement>(element));
|
||||
|
||||
// set current x
|
||||
this->currentX_ += element->getRect().width();
|
||||
if (!element->getCreator().getFlags().has(
|
||||
MessageElementFlag::ZeroWidthEmote))
|
||||
{
|
||||
this->currentX_ += element->getRect().width();
|
||||
}
|
||||
|
||||
if (element->hasTrailingSpace())
|
||||
{
|
||||
|
@ -137,7 +149,9 @@ void MessageLayoutContainer::breakLine()
|
|||
|
||||
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)
|
||||
->getRect()
|
||||
.right()) /
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
#include "providers/bttv/BttvEmotes.hpp"
|
||||
|
||||
#include <QJsonArray>
|
||||
#include <QThread>
|
||||
|
||||
#include "common/Common.hpp"
|
||||
#include "common/NetworkRequest.hpp"
|
||||
#include "debug/Log.hpp"
|
||||
|
@ -8,11 +11,11 @@
|
|||
#include "messages/ImageSet.hpp"
|
||||
#include "providers/twitch/TwitchChannel.hpp"
|
||||
|
||||
#include <QJsonArray>
|
||||
#include <QThread>
|
||||
|
||||
namespace chatterino {
|
||||
namespace {
|
||||
|
||||
QString emoteLinkFormat("https://betterttv.com/emotes/%1");
|
||||
|
||||
Url getEmoteLink(QString urlTemplate, const EmoteId &id,
|
||||
const QString &emoteScale)
|
||||
{
|
||||
|
@ -47,13 +50,14 @@ namespace {
|
|||
auto name =
|
||||
EmoteName{jsonEmote.toObject().value("code").toString()};
|
||||
|
||||
auto emote = Emote(
|
||||
{name,
|
||||
ImageSet{Image::fromUrl(getEmoteLinkV3(id, "1x"), 1),
|
||||
Image::fromUrl(getEmoteLinkV3(id, "2x"), 0.5),
|
||||
Image::fromUrl(getEmoteLinkV3(id, "3x"), 0.25)},
|
||||
Tooltip{name.string + "<br />Global BetterTTV Emote"},
|
||||
Url{"https://manage.betterttv.net/emotes/" + id.string}});
|
||||
auto emote = Emote({
|
||||
name,
|
||||
ImageSet{Image::fromUrl(getEmoteLinkV3(id, "1x"), 1),
|
||||
Image::fromUrl(getEmoteLinkV3(id, "2x"), 0.5),
|
||||
Image::fromUrl(getEmoteLinkV3(id, "3x"), 0.25)},
|
||||
Tooltip{name.string + "<br />Global BetterTTV Emote"},
|
||||
Url{emoteLinkFormat.arg(id.string)},
|
||||
});
|
||||
|
||||
emotes[name] =
|
||||
cachedOrMakeEmotePtr(std::move(emote), currentEmotes);
|
||||
|
@ -75,15 +79,16 @@ namespace {
|
|||
auto name = EmoteName{jsonEmote.value("code").toString()};
|
||||
// emoteObject.value("imageType").toString();
|
||||
|
||||
auto emote = Emote(
|
||||
{name,
|
||||
ImageSet{
|
||||
Image::fromUrl(getEmoteLinkV3(id, "1x"), 1),
|
||||
Image::fromUrl(getEmoteLinkV3(id, "2x"), 0.5),
|
||||
Image::fromUrl(getEmoteLinkV3(id, "3x"), 0.25),
|
||||
},
|
||||
Tooltip{name.string + "<br />Channel BetterTTV Emote"},
|
||||
Url{"https://manage.betterttv.net/emotes/" + id.string}});
|
||||
auto emote = Emote({
|
||||
name,
|
||||
ImageSet{
|
||||
Image::fromUrl(getEmoteLinkV3(id, "1x"), 1),
|
||||
Image::fromUrl(getEmoteLinkV3(id, "2x"), 0.5),
|
||||
Image::fromUrl(getEmoteLinkV3(id, "3x"), 0.25),
|
||||
},
|
||||
Tooltip{name.string + "<br />Channel BetterTTV Emote"},
|
||||
Url{emoteLinkFormat.arg(id.string)},
|
||||
});
|
||||
|
||||
emotes[name] = cachedOrMake(std::move(emote), id);
|
||||
}
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
#pragma once
|
||||
|
||||
#include "providers/irc/IrcConnection2.hpp"
|
||||
|
||||
#include <IrcMessage>
|
||||
#include <functional>
|
||||
#include <mutex>
|
||||
#include <pajlada/signals/signal.hpp>
|
||||
#include <pajlada/signals/signalholder.hpp>
|
||||
|
||||
#include <functional>
|
||||
#include <mutex>
|
||||
#include "common/Common.hpp"
|
||||
#include "providers/irc/IrcConnection2.hpp"
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
|
@ -73,15 +73,8 @@ protected:
|
|||
private:
|
||||
void initConnection();
|
||||
|
||||
struct Deleter {
|
||||
void operator()(IrcConnection *conn)
|
||||
{
|
||||
conn->deleteLater();
|
||||
}
|
||||
};
|
||||
|
||||
std::unique_ptr<IrcConnection, Deleter> writeConnection_ = nullptr;
|
||||
std::unique_ptr<IrcConnection, Deleter> readConnection_ = nullptr;
|
||||
QObjectPtr<IrcConnection> writeConnection_ = nullptr;
|
||||
QObjectPtr<IrcConnection> readConnection_ = nullptr;
|
||||
|
||||
QTimer reconnectTimer_;
|
||||
int falloffCounter_ = 1;
|
||||
|
|
|
@ -73,13 +73,13 @@ inline QString getCredentialName(const IrcServerData &data)
|
|||
void IrcServerData::getPassword(
|
||||
QObject *receiver, std::function<void(const QString &)> &&onLoaded) const
|
||||
{
|
||||
Credentials::getInstance().get("irc", getCredentialName(*this), receiver,
|
||||
std::move(onLoaded));
|
||||
Credentials::instance().get("irc", getCredentialName(*this), receiver,
|
||||
std::move(onLoaded));
|
||||
}
|
||||
|
||||
void IrcServerData::setPassword(const QString &password)
|
||||
{
|
||||
Credentials::getInstance().set("irc", getCredentialName(*this), password);
|
||||
Credentials::instance().set("irc", getCredentialName(*this), password);
|
||||
}
|
||||
|
||||
Irc::Irc()
|
||||
|
@ -133,8 +133,7 @@ Irc::Irc()
|
|||
|
||||
if (args.caller != Irc::noEraseCredentialCaller)
|
||||
{
|
||||
Credentials::getInstance().erase("irc",
|
||||
getCredentialName(args.item));
|
||||
Credentials::instance().erase("irc", getCredentialName(args.item));
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -164,7 +163,7 @@ ChannelPtr Irc::getOrAddChannel(int id, QString name)
|
|||
}
|
||||
}
|
||||
|
||||
Irc &Irc::getInstance()
|
||||
Irc &Irc::instance()
|
||||
{
|
||||
static Irc irc;
|
||||
return irc;
|
||||
|
|
|
@ -36,7 +36,7 @@ class Irc
|
|||
public:
|
||||
Irc();
|
||||
|
||||
static Irc &getInstance();
|
||||
static Irc &instance();
|
||||
|
||||
static inline void *const noEraseCredentialCaller =
|
||||
reinterpret_cast<void *>(1);
|
||||
|
|
|
@ -66,26 +66,29 @@ void IrcServer::initializeConnection(IrcConnection *connection,
|
|||
connection->setRealName(this->data_->real.isEmpty() ? this->data_->user
|
||||
: this->data_->nick);
|
||||
|
||||
switch (this->data_->authType)
|
||||
if (getSettings()->enableExperimentalIrc)
|
||||
{
|
||||
case IrcAuthType::Sasl:
|
||||
connection->setSaslMechanism("PLAIN");
|
||||
[[fallthrough]];
|
||||
case IrcAuthType::Pass:
|
||||
this->data_->getPassword(
|
||||
this, [conn = new QObjectRef(connection) /* can't copy */,
|
||||
this](const QString &password) mutable {
|
||||
if (*conn)
|
||||
{
|
||||
(*conn)->setPassword(password);
|
||||
this->open(Both);
|
||||
}
|
||||
switch (this->data_->authType)
|
||||
{
|
||||
case IrcAuthType::Sasl:
|
||||
connection->setSaslMechanism("PLAIN");
|
||||
[[fallthrough]];
|
||||
case IrcAuthType::Pass:
|
||||
this->data_->getPassword(
|
||||
this, [conn = new QObjectRef(connection) /* can't copy */,
|
||||
this](const QString &password) mutable {
|
||||
if (*conn)
|
||||
{
|
||||
(*conn)->setPassword(password);
|
||||
this->open(Both);
|
||||
}
|
||||
|
||||
delete conn;
|
||||
});
|
||||
break;
|
||||
default:
|
||||
this->open(Both);
|
||||
delete conn;
|
||||
});
|
||||
break;
|
||||
default:
|
||||
this->open(Both);
|
||||
}
|
||||
}
|
||||
|
||||
QObject::connect(
|
||||
|
@ -179,8 +182,7 @@ void IrcServer::readConnectionMessageReceived(Communi::IrcMessage *message)
|
|||
|
||||
switch (message->type())
|
||||
{
|
||||
case Communi::IrcMessage::Join:
|
||||
{
|
||||
case Communi::IrcMessage::Join: {
|
||||
auto x = static_cast<Communi::IrcJoinMessage *>(message);
|
||||
|
||||
if (auto it =
|
||||
|
@ -205,8 +207,7 @@ void IrcServer::readConnectionMessageReceived(Communi::IrcMessage *message)
|
|||
return;
|
||||
}
|
||||
|
||||
case Communi::IrcMessage::Part:
|
||||
{
|
||||
case Communi::IrcMessage::Part: {
|
||||
auto x = static_cast<Communi::IrcPartMessage *>(message);
|
||||
|
||||
if (auto it =
|
||||
|
|
|
@ -40,7 +40,7 @@ static QMap<QString, QString> parseBadges(QString badgesString)
|
|||
return badges;
|
||||
}
|
||||
|
||||
IrcMessageHandler &IrcMessageHandler::getInstance()
|
||||
IrcMessageHandler &IrcMessageHandler::instance()
|
||||
{
|
||||
static IrcMessageHandler instance;
|
||||
return instance;
|
||||
|
@ -618,7 +618,7 @@ void IrcMessageHandler::handlePartMessage(Communi::IrcMessage *message)
|
|||
{
|
||||
if (message->nick() !=
|
||||
getApp()->accounts->twitch.getCurrent()->getUserName() &&
|
||||
getSettings()->showJoins.getValue())
|
||||
getSettings()->showParts.getValue())
|
||||
{
|
||||
twitchChannel->addPartedUser(message->nick());
|
||||
}
|
||||
|
|
|
@ -13,7 +13,7 @@ class IrcMessageHandler
|
|||
IrcMessageHandler() = default;
|
||||
|
||||
public:
|
||||
static IrcMessageHandler &getInstance();
|
||||
static IrcMessageHandler &instance();
|
||||
|
||||
// parseMessage parses a single IRC message into 0+ Chatterino messages
|
||||
std::vector<MessagePtr> parseMessage(Channel *channel,
|
||||
|
|
|
@ -156,7 +156,7 @@ namespace detail {
|
|||
|
||||
if (self->awaitingPong_)
|
||||
{
|
||||
log("No pong respnose, disconnect!");
|
||||
log("No pong response, disconnect!");
|
||||
// TODO(pajlada): Label this connection as "disconnect
|
||||
// me"
|
||||
}
|
||||
|
|
|
@ -102,14 +102,12 @@ void TwitchAccountManager::reloadUsers()
|
|||
|
||||
switch (this->addUser(userData))
|
||||
{
|
||||
case AddUserResponse::UserAlreadyExists:
|
||||
{
|
||||
case AddUserResponse::UserAlreadyExists: {
|
||||
log("User {} already exists", userData.username);
|
||||
// Do nothing
|
||||
}
|
||||
break;
|
||||
case AddUserResponse::UserValuesUpdated:
|
||||
{
|
||||
case AddUserResponse::UserValuesUpdated: {
|
||||
log("User {} already exists, and values updated!",
|
||||
userData.username);
|
||||
if (userData.username == this->getCurrent()->getUserName())
|
||||
|
@ -120,8 +118,7 @@ void TwitchAccountManager::reloadUsers()
|
|||
}
|
||||
}
|
||||
break;
|
||||
case AddUserResponse::UserAdded:
|
||||
{
|
||||
case AddUserResponse::UserAdded: {
|
||||
log("Added user {}", userData.username);
|
||||
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()},
|
||||
.25),
|
||||
},
|
||||
Tooltip{versionObj.value("description").toString()},
|
||||
Tooltip{versionObj.value("title").toString()},
|
||||
Url{versionObj.value("click_url").toString()}};
|
||||
// "title"
|
||||
// "clickAction"
|
||||
|
|
|
@ -31,6 +31,7 @@
|
|||
|
||||
namespace chatterino {
|
||||
namespace {
|
||||
constexpr int TITLE_REFRESH_PERIOD = 10;
|
||||
constexpr char MAGIC_MESSAGE_SUFFIX[] = u8" \U000E0000";
|
||||
|
||||
// parseRecentMessages takes a json object and returns a vector of
|
||||
|
@ -89,6 +90,7 @@ TwitchChannel::TwitchChannel(const QString &name,
|
|||
, bttvEmotes_(std::make_shared<EmoteMap>())
|
||||
, ffzEmotes_(std::make_shared<EmoteMap>())
|
||||
, mod_(false)
|
||||
, titleRefreshedTime_(QTime::currentTime().addSecs(-TITLE_REFRESH_PERIOD))
|
||||
{
|
||||
log("[TwitchChannel:{}] Opened", name);
|
||||
|
||||
|
@ -110,6 +112,7 @@ TwitchChannel::TwitchChannel(const QString &name,
|
|||
// room id loaded -> refresh live status
|
||||
this->roomIdChanged.connect([this]() {
|
||||
this->refreshPubsub();
|
||||
this->refreshTitle();
|
||||
this->refreshLiveStatus();
|
||||
this->refreshBadges();
|
||||
this->refreshCheerEmotes();
|
||||
|
@ -229,7 +232,7 @@ bool TwitchChannel::isMod() const
|
|||
return this->mod_;
|
||||
}
|
||||
|
||||
bool TwitchChannel::isVIP() const
|
||||
bool TwitchChannel::isVip() const
|
||||
{
|
||||
return this->vip_;
|
||||
}
|
||||
|
@ -278,7 +281,7 @@ bool TwitchChannel::isBroadcaster() const
|
|||
|
||||
bool TwitchChannel::hasHighRateLimit() const
|
||||
{
|
||||
return this->isMod() || this->isBroadcaster() || this->isVIP();
|
||||
return this->isMod() || this->isBroadcaster() || this->isVip();
|
||||
}
|
||||
|
||||
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()
|
||||
{
|
||||
auto roomID = this->roomId();
|
||||
|
@ -566,7 +614,7 @@ void TwitchChannel::loadRecentMessages()
|
|||
|
||||
auto messages = parseRecentMessages(result.parseJson(), shared);
|
||||
|
||||
auto &handler = IrcMessageHandler::getInstance();
|
||||
auto &handler = IrcMessageHandler::instance();
|
||||
|
||||
std::vector<MessagePtr> allBuiltMessages;
|
||||
|
||||
|
@ -711,6 +759,7 @@ void TwitchChannel::refreshCheerEmotes()
|
|||
|
||||
cheerEmote.color = QColor(tier.color);
|
||||
cheerEmote.minBits = tier.minBits;
|
||||
cheerEmote.regex = cheerEmoteSet.regex;
|
||||
|
||||
// TODO(pajlada): We currently hardcode dark here :|
|
||||
// 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 void sendMessage(const QString &message) override;
|
||||
virtual bool isMod() const override;
|
||||
bool isVIP() const;
|
||||
bool isVip() const;
|
||||
bool isStaff() const;
|
||||
virtual bool isBroadcaster() const override;
|
||||
virtual bool hasHighRateLimit() const override;
|
||||
virtual bool canReconnect() const override;
|
||||
virtual void reconnect() override;
|
||||
void refreshTitle();
|
||||
|
||||
// Data
|
||||
const QString &subscriptionUrl();
|
||||
|
@ -166,6 +167,7 @@ private:
|
|||
QObject lifetimeGuard_;
|
||||
QTimer liveStatusTimer_;
|
||||
QTimer chattersListTimer_;
|
||||
QTime titleRefreshedTime_;
|
||||
|
||||
friend class TwitchIrcServer;
|
||||
friend class TwitchMessageBuilder;
|
||||
|
|
|
@ -18,6 +18,7 @@ using EmotePtr = std::shared_ptr<const Emote>;
|
|||
struct CheerEmote {
|
||||
QColor color;
|
||||
int minBits;
|
||||
QRegularExpression regex;
|
||||
|
||||
EmotePtr animatedEmote;
|
||||
EmotePtr staticEmote;
|
||||
|
|
|
@ -1,7 +1,11 @@
|
|||
#include "TwitchIrcServer.hpp"
|
||||
|
||||
#include <IrcCommand>
|
||||
#include <cassert>
|
||||
|
||||
#include "Application.hpp"
|
||||
#include "common/Common.hpp"
|
||||
#include "common/Env.hpp"
|
||||
#include "controllers/accounts/AccountController.hpp"
|
||||
#include "controllers/highlights/HighlightController.hpp"
|
||||
#include "messages/Message.hpp"
|
||||
|
@ -15,9 +19,6 @@
|
|||
#include "providers/twitch/TwitchMessageBuilder.hpp"
|
||||
#include "util/PostToThread.hpp"
|
||||
|
||||
#include <IrcCommand>
|
||||
#include <cassert>
|
||||
|
||||
// using namespace Communi;
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
|
@ -86,13 +87,12 @@ void TwitchIrcServer::initializeConnection(IrcConnection *connection,
|
|||
connection->setPassword(oauthToken);
|
||||
}
|
||||
|
||||
connection->setSecure(true);
|
||||
|
||||
// https://dev.twitch.tv/docs/irc/guide/#connecting-to-twitch-irc
|
||||
// SSL disabled: irc://irc.chat.twitch.tv:6667
|
||||
// SSL enabled: irc://irc.chat.twitch.tv:6697
|
||||
connection->setHost("irc.chat.twitch.tv");
|
||||
connection->setPort(6697);
|
||||
// SSL disabled: irc://irc.chat.twitch.tv:6667 (or port 80)
|
||||
// SSL enabled: irc://irc.chat.twitch.tv:6697 (or port 443)
|
||||
connection->setHost(Env::get().twitchServerHost);
|
||||
connection->setPort(Env::get().twitchServerPort);
|
||||
connection->setSecure(Env::get().twitchServerSecure);
|
||||
|
||||
this->open(type);
|
||||
}
|
||||
|
@ -125,7 +125,7 @@ std::shared_ptr<Channel> TwitchIrcServer::createChannel(
|
|||
void TwitchIrcServer::privateMessageReceived(
|
||||
Communi::IrcPrivateMessage *message)
|
||||
{
|
||||
IrcMessageHandler::getInstance().handlePrivMessage(message, *this);
|
||||
IrcMessageHandler::instance().handlePrivMessage(message, *this);
|
||||
}
|
||||
|
||||
void TwitchIrcServer::readConnectionMessageReceived(
|
||||
|
@ -141,7 +141,7 @@ void TwitchIrcServer::readConnectionMessageReceived(
|
|||
|
||||
const QString &command = message->command();
|
||||
|
||||
auto &handler = IrcMessageHandler::getInstance();
|
||||
auto &handler = IrcMessageHandler::instance();
|
||||
|
||||
// Below commands enabled through the twitch.tv/membership CAP REQ
|
||||
if (command == "MODE")
|
||||
|
@ -194,14 +194,41 @@ void TwitchIrcServer::writeConnectionMessageReceived(
|
|||
{
|
||||
const QString &command = message->command();
|
||||
|
||||
auto &handler = IrcMessageHandler::getInstance();
|
||||
|
||||
auto &handler = IrcMessageHandler::instance();
|
||||
// Below commands enabled through the twitch.tv/commands CAP REQ
|
||||
if (command == "USERSTATE")
|
||||
{
|
||||
// Received USERSTATE upon PRIVMSGing
|
||||
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)
|
||||
|
|
|
@ -28,6 +28,10 @@
|
|||
|
||||
namespace {
|
||||
|
||||
const QSet<QString> zeroWidthEmotes{
|
||||
"SoSnowy", "IceCold", "SantaHat", "TopHat", "ReinDeer", "CandyCane",
|
||||
};
|
||||
|
||||
QColor getRandomColor(const QVariant &userId)
|
||||
{
|
||||
static const std::vector<QColor> twitchUsernameColors = {
|
||||
|
@ -65,6 +69,59 @@ QColor getRandomColor(const QVariant &userId)
|
|||
|
||||
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(
|
||||
Channel *_channel, const Communi::IrcPrivateMessage *_ircMessage,
|
||||
const MessageParseArgs &_args)
|
||||
|
@ -289,6 +346,7 @@ MessagePtr TwitchMessageBuilder::build()
|
|||
if (iterator != this->tags.end())
|
||||
{
|
||||
this->hasBits_ = true;
|
||||
this->bitsLeft = iterator.value().toInt();
|
||||
this->bits = iterator.value().toString();
|
||||
}
|
||||
|
||||
|
@ -617,14 +675,12 @@ void TwitchMessageBuilder::appendUsername()
|
|||
|
||||
switch (usernameDisplayMode.getValue())
|
||||
{
|
||||
case UsernameDisplayMode::Username:
|
||||
{
|
||||
case UsernameDisplayMode::Username: {
|
||||
usernameText = username;
|
||||
}
|
||||
break;
|
||||
|
||||
case UsernameDisplayMode::LocalizedName:
|
||||
{
|
||||
case UsernameDisplayMode::LocalizedName: {
|
||||
if (hasLocalizedName)
|
||||
{
|
||||
usernameText = localizedName;
|
||||
|
@ -637,8 +693,7 @@ void TwitchMessageBuilder::appendUsername()
|
|||
break;
|
||||
|
||||
default:
|
||||
case UsernameDisplayMode::UsernameAndLocalizedName:
|
||||
{
|
||||
case UsernameDisplayMode::UsernameAndLocalizedName: {
|
||||
if (hasLocalizedName)
|
||||
{
|
||||
usernameText = username + "(" + localizedName + ")";
|
||||
|
@ -655,7 +710,7 @@ void TwitchMessageBuilder::appendUsername()
|
|||
{
|
||||
// TODO(pajlada): Re-implement
|
||||
// userDisplayString +=
|
||||
// IrcManager::getInstance().getUser().getUserName();
|
||||
// IrcManager::instance().getUser().getUserName();
|
||||
}
|
||||
else if (this->args.isReceivedWhisper)
|
||||
{
|
||||
|
@ -1112,6 +1167,11 @@ Outcome TwitchMessageBuilder::tryAppendEmote(const EmoteName &name)
|
|||
else if ((emote = globalBttvEmotes.emote(name)))
|
||||
{
|
||||
flags = MessageElementFlag::BttvEmote;
|
||||
|
||||
if (zeroWidthEmotes.contains(name.string))
|
||||
{
|
||||
flags.set(MessageElementFlag::ZeroWidthEmote);
|
||||
}
|
||||
}
|
||||
|
||||
if (emote)
|
||||
|
@ -1123,7 +1183,24 @@ Outcome TwitchMessageBuilder::tryAppendEmote(const EmoteName &name)
|
|||
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()
|
||||
{
|
||||
if (this->twitchChannel == nullptr)
|
||||
|
@ -1131,68 +1208,25 @@ void TwitchMessageBuilder::appendTwitchBadges()
|
|||
return;
|
||||
}
|
||||
|
||||
auto app = getApp();
|
||||
auto badgeInfos = parseBadgeInfos(this->tags);
|
||||
auto badges = parseBadges(this->tags);
|
||||
|
||||
auto iterator = this->tags.find("badges");
|
||||
if (iterator == this->tags.end())
|
||||
return;
|
||||
|
||||
for (QString badge : iterator.value().toString().split(','))
|
||||
for (const auto &badge : badges)
|
||||
{
|
||||
if (badge.startsWith("bits/"))
|
||||
auto badgeEmote = this->getTwitchBadge(badge);
|
||||
if (!badgeEmote)
|
||||
{
|
||||
QString cheerAmount = badge.mid(5);
|
||||
QString tooltip = QString("Twitch cheer ") + cheerAmount;
|
||||
log("No channel/global variant found {}", badge.key_);
|
||||
continue;
|
||||
}
|
||||
auto tooltip = (*badgeEmote)->tooltip.string;
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
catch (const std::out_of_range &)
|
||||
{
|
||||
// Channel does not contain a special bit badge for this version
|
||||
}
|
||||
|
||||
// Use default bit badge
|
||||
if (auto _badge = this->twitchChannel->globalTwitchBadges().badge(
|
||||
"bits", cheerAmount))
|
||||
{
|
||||
this->emplace<BadgeElement>(_badge.get(),
|
||||
MessageElementFlag::BadgeVanity)
|
||||
->setTooltip(tooltip);
|
||||
}
|
||||
}
|
||||
else if (badge == "staff/1")
|
||||
if (badge.key_ == "bits")
|
||||
{
|
||||
this->emplace<ImageElement>(
|
||||
Image::fromPixmap(app->resources->twitch.staff),
|
||||
MessageElementFlag::BadgeGlobalAuthority)
|
||||
->setTooltip("Twitch Staff");
|
||||
const auto &cheerAmount = badge.value_;
|
||||
tooltip = QString("Twitch cheer %0").arg(cheerAmount);
|
||||
}
|
||||
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")
|
||||
else if (badge.key_ == "moderator")
|
||||
{
|
||||
if (auto customModBadge = this->twitchChannel->ffzCustomModBadge())
|
||||
{
|
||||
|
@ -1200,104 +1234,22 @@ void TwitchMessageBuilder::appendTwitchBadges()
|
|||
customModBadge.get(),
|
||||
MessageElementFlag::BadgeChannelAuthority)
|
||||
->setTooltip((*customModBadge)->tooltip.string);
|
||||
// early out, since we have to add a custom badge element here
|
||||
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>(
|
||||
Image::fromPixmap(app->resources->twitch.vip),
|
||||
MessageElementFlag::BadgeChannelAuthority)
|
||||
->setTooltip("VIP");
|
||||
}
|
||||
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)
|
||||
auto badgeInfoIt = badgeInfos.find(badge.key_);
|
||||
if (badgeInfoIt != badgeInfos.end())
|
||||
{
|
||||
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;
|
||||
const auto &subMonths = badgeInfoIt->second;
|
||||
tooltip += QString(" (%0 months)").arg(subMonths);
|
||||
}
|
||||
}
|
||||
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<ImageElement>(
|
||||
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;
|
||||
}
|
||||
}
|
||||
this->emplace<BadgeElement>(badgeEmote.get(), badge.flag_)
|
||||
->setTooltip(tooltip);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1312,12 +1264,67 @@ void TwitchMessageBuilder::appendChatterinoBadges()
|
|||
|
||||
Outcome TwitchMessageBuilder::tryParseCheermote(const QString &string)
|
||||
{
|
||||
if (this->bitsLeft == 0)
|
||||
{
|
||||
return Failure;
|
||||
}
|
||||
|
||||
auto cheerOpt = this->twitchChannel->cheerEmote(string);
|
||||
|
||||
if (!cheerOpt)
|
||||
{
|
||||
return Failure;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
this->emplace<EmoteElement>(cheerEmote.staticEmote,
|
||||
MessageElementFlag::BitsStatic);
|
||||
}
|
||||
if (cheerEmote.animatedEmote)
|
||||
{
|
||||
this->emplace<EmoteElement>(cheerEmote.animatedEmote,
|
||||
MessageElementFlag::BitsAnimated);
|
||||
}
|
||||
if (cheerEmote.color != QColor())
|
||||
{
|
||||
this->emplace<TextElement>(QString::number(this->bitsLeft),
|
||||
MessageElementFlag::BitsAmount,
|
||||
cheerEmote.color);
|
||||
}
|
||||
this->bitsStacked = true;
|
||||
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,
|
||||
|
@ -1330,9 +1337,12 @@ Outcome TwitchMessageBuilder::tryParseCheermote(const QString &string)
|
|||
}
|
||||
if (cheerEmote.color != QColor())
|
||||
{
|
||||
this->emplace<TextElement>(this->bits, MessageElementFlag::BitsAmount,
|
||||
this->emplace<TextElement>(match.captured(1),
|
||||
MessageElementFlag::BitsAmount,
|
||||
cheerEmote.color);
|
||||
}
|
||||
|
||||
return Success;
|
||||
}
|
||||
|
||||
} // namespace chatterino
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
#include "common/Aliases.hpp"
|
||||
#include "common/Outcome.hpp"
|
||||
#include "messages/MessageBuilder.hpp"
|
||||
#include "providers/twitch/TwitchBadge.hpp"
|
||||
|
||||
#include <IrcMessage>
|
||||
#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
|
||||
void parseHighlights();
|
||||
|
||||
boost::optional<EmotePtr> getTwitchBadge(const Badge &badge);
|
||||
void appendTwitchEmote(
|
||||
const QString &emote,
|
||||
std::vector<std::tuple<int, EmotePtr, EmoteName>> &vec,
|
||||
|
@ -79,6 +81,8 @@ private:
|
|||
QString roomID_;
|
||||
bool hasBits_ = false;
|
||||
QString bits;
|
||||
int bitsLeft;
|
||||
bool bitsStacked = false;
|
||||
bool historicalMessage_ = false;
|
||||
|
||||
QString userId_;
|
||||
|
|
|
@ -24,7 +24,7 @@
|
|||
# endif
|
||||
#endif
|
||||
|
||||
namespace AB_NAMESPACE {
|
||||
namespace chatterino {
|
||||
namespace {
|
||||
int getBoldness()
|
||||
{
|
||||
|
@ -89,7 +89,7 @@ void Fonts::initialize(Settings &, Paths &)
|
|||
},
|
||||
false);
|
||||
#endif
|
||||
} // namespace AB_NAMESPACE
|
||||
}
|
||||
|
||||
QFont Fonts::getFont(FontStyle type, float scale)
|
||||
{
|
||||
|
@ -178,4 +178,4 @@ Fonts *getFonts()
|
|||
return Fonts::instance;
|
||||
}
|
||||
|
||||
} // namespace AB_NAMESPACE
|
||||
} // namespace chatterino
|
|
@ -12,7 +12,7 @@
|
|||
#include <array>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace AB_NAMESPACE {
|
||||
namespace chatterino {
|
||||
|
||||
class Settings;
|
||||
class Paths;
|
||||
|
@ -90,4 +90,4 @@ private:
|
|||
|
||||
Fonts *getFonts();
|
||||
|
||||
} // namespace AB_NAMESPACE
|
||||
} // namespace chatterino
|
|
@ -11,6 +11,8 @@
|
|||
#include "common/Modes.hpp"
|
||||
#include "util/CombinePath.hpp"
|
||||
|
||||
using namespace std::literals;
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
Paths *Paths::instance = nullptr;
|
||||
|
@ -22,7 +24,7 @@ Paths::Paths()
|
|||
this->initAppFilePathHash();
|
||||
|
||||
this->initCheckPortable();
|
||||
this->initAppDataDirectory();
|
||||
this->initRootDirectory();
|
||||
this->initSubDirectories();
|
||||
}
|
||||
|
||||
|
@ -33,7 +35,7 @@ bool Paths::createFolder(const QString &folderPath)
|
|||
|
||||
bool Paths::isPortable()
|
||||
{
|
||||
return Modes::getInstance().isPortable;
|
||||
return Modes::instance().isPortable;
|
||||
}
|
||||
|
||||
QString Paths::cacheDirectory()
|
||||
|
@ -76,7 +78,7 @@ void Paths::initCheckPortable()
|
|||
combinePath(QCoreApplication::applicationDirPath(), "portable"));
|
||||
}
|
||||
|
||||
void Paths::initAppDataDirectory()
|
||||
void Paths::initRootDirectory()
|
||||
{
|
||||
assert(this->portable_.is_initialized());
|
||||
|
||||
|
@ -95,8 +97,8 @@ void Paths::initAppDataDirectory()
|
|||
QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
|
||||
if (path.isEmpty())
|
||||
{
|
||||
throw std::runtime_error(
|
||||
"Error finding writable location for settings");
|
||||
throw std::runtime_error("Could not create directory \""s +
|
||||
path.toStdString() + "\"");
|
||||
}
|
||||
|
||||
// create directory Chatterino2 instead of chatterino on windows because the
|
||||
|
@ -123,8 +125,8 @@ void Paths::initSubDirectories()
|
|||
|
||||
if (!QDir().mkpath(path))
|
||||
{
|
||||
throw std::runtime_error(
|
||||
"Error creating appdata path %appdata%/chatterino/" + name);
|
||||
throw std::runtime_error("Could not create directory \""s +
|
||||
path.toStdString() + "\"");
|
||||
}
|
||||
|
||||
return path;
|
||||
|
|
|
@ -39,7 +39,7 @@ public:
|
|||
private:
|
||||
void initAppFilePathHash();
|
||||
void initCheckPortable();
|
||||
void initAppDataDirectory();
|
||||
void initRootDirectory();
|
||||
void initSubDirectories();
|
||||
|
||||
boost::optional<bool> portable_;
|
||||
|
|
|
@ -1 +1,24 @@
|
|||
#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
|
||||
|
||||
#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 {
|
||||
|
||||
Settings *Settings::instance = nullptr;
|
||||
Settings *Settings::instance_ = nullptr;
|
||||
|
||||
Settings::Settings(const QString &settingsDirectory)
|
||||
: ABSettings(settingsDirectory)
|
||||
{
|
||||
instance = this;
|
||||
instance_ = this;
|
||||
|
||||
#ifdef USEWINSDK
|
||||
this->autorun = isRegisteredForStartup();
|
||||
|
@ -23,14 +23,14 @@ Settings::Settings(const QString &settingsDirectory)
|
|||
#endif
|
||||
}
|
||||
|
||||
Settings &Settings::getInstance()
|
||||
Settings &Settings::instance()
|
||||
{
|
||||
return *instance;
|
||||
return *instance_;
|
||||
}
|
||||
|
||||
Settings *getSettings()
|
||||
{
|
||||
return &Settings::getInstance();
|
||||
return &Settings::instance();
|
||||
}
|
||||
|
||||
} // namespace chatterino
|
||||
|
|
|
@ -1,25 +1,24 @@
|
|||
#pragma once
|
||||
|
||||
#include "BaseSettings.hpp"
|
||||
#include <pajlada/settings/setting.hpp>
|
||||
#include <pajlada/settings/settinglistener.hpp>
|
||||
|
||||
#include "BaseSettings.hpp"
|
||||
#include "common/Channel.hpp"
|
||||
#include "controllers/highlights/HighlightPhrase.hpp"
|
||||
#include "controllers/moderationactions/ModerationAction.hpp"
|
||||
#include "singletons/Toasts.hpp"
|
||||
|
||||
#include <pajlada/settings/setting.hpp>
|
||||
#include <pajlada/settings/settinglistener.hpp>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
class Settings : public ABSettings
|
||||
{
|
||||
static Settings *instance;
|
||||
static Settings *instance_;
|
||||
|
||||
public:
|
||||
Settings(const QString &settingsDirectory);
|
||||
|
||||
static Settings &getInstance();
|
||||
static Settings &instance();
|
||||
|
||||
/// Appearance
|
||||
BoolSetting showTimestamps = {"/appearance/messages/showTimestamps", true};
|
||||
|
@ -121,6 +120,8 @@ public:
|
|||
|
||||
QStringSetting emojiSet = {"/emotes/emojiSet", "EmojiOne 2"};
|
||||
|
||||
BoolSetting stackBits = {"/emotes/stackBits", false};
|
||||
|
||||
/// Links
|
||||
BoolSetting linksDoubleClickOnly = {"/links/doubleClickToOpen", false};
|
||||
BoolSetting linkInfoTooltip = {"/links/linkInfoTooltip", false};
|
||||
|
@ -204,6 +205,7 @@ public:
|
|||
#ifdef Q_OS_LINUX
|
||||
BoolSetting useKeyring = {"/misc/useKeyring", true};
|
||||
#endif
|
||||
BoolSetting enableExperimentalIrc = {"/misc/experimentalIrc", false};
|
||||
|
||||
IntSetting startUpNotification = {"/misc/startUpNotification", 0};
|
||||
QStringSetting currentVersion = {"/misc/currentVersion", ""};
|
||||
|
@ -213,6 +215,9 @@ public:
|
|||
BoolSetting openLinksIncognito = {"/misc/openLinksIncognito", 0};
|
||||
|
||||
QStringSetting cachePath = {"/cache/path", ""};
|
||||
BoolSetting restartOnCrash = {"/misc/restartOnCrash", false};
|
||||
BoolSetting attachExtensionToAnyProcess = {
|
||||
"/misc/attachExtensionToAnyProcess", false};
|
||||
|
||||
/// Debug
|
||||
BoolSetting showUnhandledIrcMessages = {"/debug/showUnhandledIrcMessages",
|
||||
|
|
|
@ -85,7 +85,7 @@ void Theme::actuallyUpdate(double hue, double multiplier)
|
|||
if (getSettings()->highlightColor != "")
|
||||
{
|
||||
this->messages.backgrounds.highlighted =
|
||||
QColor(getSettings()->highlightColor);
|
||||
QColor(getSettings()->highlightColor.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue