Compare commits

...

15 commits

Author SHA1 Message Date
pajlada
c257161112
Merge 56ba1ca2ee into 2a38e39e24 2024-11-21 17:16:31 +01:00
dependabot[bot]
2a38e39e24
chore(deps): bump codecov/codecov-action from 5.0.2 to 5.0.7 (#5722)
Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 5.0.2 to 5.0.7.
- [Release notes](https://github.com/codecov/codecov-action/releases)
- [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/codecov/codecov-action/compare/v5.0.2...v5.0.7)

---
updated-dependencies:
- dependency-name: codecov/codecov-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-21 11:08:33 +01:00
nerix
1c827f6288
chore: use condition variable to shutdown websocket pools (#5721) 2024-11-20 22:29:47 +01:00
dependabot[bot]
19f449866e
chore(deps): bump ZedThree/clang-tidy-review from 0.20.0 to 0.20.1 (#5719) 2024-11-19 15:12:25 +00:00
dependabot[bot]
0ed8311c1a
chore(deps): bump lib/settings from 4a0a1e5 to 46eb250 (#5718) 2024-11-19 04:22:29 +00:00
dependabot[bot]
ed6ddead3d
chore(deps): bump codecov/codecov-action from 5.0.0 to 5.0.2 (#5717) 2024-11-18 08:17:56 +01:00
dependabot[bot]
2215455ca0
chore(deps): bump jurplel/install-qt-action from 4.0.0 to 4.1.1 (#5709) 2024-11-17 12:05:55 +00:00
pajlada
9e73ba2919
dev: Default building with Qt6 on all platforms (#5716) 2024-11-17 12:39:22 +01:00
pajlada
50b51b2351
fix: codecov v5 (#5715) 2024-11-17 10:57:54 +00:00
dependabot[bot]
6b5cc5f8d9
chore(deps): bump codecov/codecov-action from 4.6.0 to 5.0.0 (#5713)
Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 4.6.0 to 5.0.0.
- [Release notes](https://github.com/codecov/codecov-action/releases)
- [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/codecov/codecov-action/compare/v4.6.0...v5.0.0)

---
updated-dependencies:
- dependency-name: codecov/codecov-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-17 11:18:05 +01:00
Rasmus Karlsson
56ba1ca2ee
oopsie 2 2024-11-06 18:08:07 +01:00
Rasmus Karlsson
8a69861ead
opsie 2024-11-06 17:36:38 +01:00
Rasmus Karlsson
40490a90fb
remove unused include 2024-11-06 17:16:12 +01:00
Rasmus Karlsson
1199f5e763
clean up reason generation 2024-11-06 17:14:32 +01:00
Rasmus Karlsson
12fa816564
improve automod blocked term messaging 2024-11-06 16:58:00 +01:00
29 changed files with 336 additions and 62 deletions

View file

@ -29,7 +29,6 @@ cmake \
-DCMAKE_EXPORT_COMPILE_COMMANDS=On \ -DCMAKE_EXPORT_COMPILE_COMMANDS=On \
-DCHATTERINO_PLUGINS="$C2_PLUGINS" \ -DCHATTERINO_PLUGINS="$C2_PLUGINS" \
-DCMAKE_PREFIX_PATH="$Qt6_DIR/lib/cmake" \ -DCMAKE_PREFIX_PATH="$Qt6_DIR/lib/cmake" \
-DBUILD_WITH_QT6="$C2_BUILD_WITH_QT6" \
-DCHATTERINO_STATIC_QT_BUILD=On \ -DCHATTERINO_STATIC_QT_BUILD=On \
-DCMAKE_CXX_FLAGS="-fno-sized-deallocation" \ -DCMAKE_CXX_FLAGS="-fno-sized-deallocation" \
. .

View file

@ -19,7 +19,6 @@ cmake -S. -Bbuild-clang-tidy \
-DCMAKE_EXPORT_COMPILE_COMMANDS=On \ -DCMAKE_EXPORT_COMPILE_COMMANDS=On \
-DCHATTERINO_LTO=Off \ -DCHATTERINO_LTO=Off \
-DCHATTERINO_PLUGINS=On \ -DCHATTERINO_PLUGINS=On \
-DBUILD_WITH_QT6=On \
-DBUILD_TESTS=On \ -DBUILD_TESTS=On \
-DBUILD_BENCHMARKS=On -DBUILD_BENCHMARKS=On

View file

@ -20,7 +20,6 @@ task:
-DUSE_SYSTEM_QTKEYCHAIN="ON" \ -DUSE_SYSTEM_QTKEYCHAIN="ON" \
-DCMAKE_BUILD_TYPE="release" \ -DCMAKE_BUILD_TYPE="release" \
-DCMAKE_EXPORT_COMPILE_COMMANDS="ON" \ -DCMAKE_EXPORT_COMPILE_COMMANDS="ON" \
-DBUILD_WITH_QT6="ON" \
.. ..
cat compile_commands.json cat compile_commands.json
make -j $(getconf _NPROCESSORS_ONLN) make -j $(getconf _NPROCESSORS_ONLN)

View file

@ -3,7 +3,6 @@ ARG UBUNTU_VERSION=22.04
FROM ubuntu:$UBUNTU_VERSION FROM ubuntu:$UBUNTU_VERSION
ARG QT_VERSION=6.2.4 ARG QT_VERSION=6.2.4
ARG BUILD_WITH_QT6=ON
ENV TZ=UTC ENV TZ=UTC
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
@ -48,7 +47,6 @@ RUN mkdir /src/build
# cmake # cmake
RUN cd /src/build && \ RUN cd /src/build && \
CXXFLAGS=-fno-sized-deallocation cmake \ CXXFLAGS=-fno-sized-deallocation cmake \
-DBUILD_WITH_QT6=$BUILD_WITH_QT6 \
-DCMAKE_INSTALL_PREFIX=appdir/usr/ \ -DCMAKE_INSTALL_PREFIX=appdir/usr/ \
-DCMAKE_PREFIX_PATH=/opt/qt/$QT_VERSION/gcc_64/lib/cmake \ -DCMAKE_PREFIX_PATH=/opt/qt/$QT_VERSION/gcc_64/lib/cmake \
-DBUILD_WITH_QTKEYCHAIN=OFF \ -DBUILD_WITH_QTKEYCHAIN=OFF \

View file

@ -62,7 +62,6 @@ jobs:
C2_ENABLE_LTO: ${{ matrix.force-lto }} C2_ENABLE_LTO: ${{ matrix.force-lto }}
C2_PLUGINS: ${{ matrix.plugins }} C2_PLUGINS: ${{ matrix.plugins }}
C2_ENABLE_CRASHPAD: ${{ matrix.skip-crashpad == false }} C2_ENABLE_CRASHPAD: ${{ matrix.skip-crashpad == false }}
C2_BUILD_WITH_QT6: ${{ startsWith(matrix.qt-version, '6.') }}
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
@ -87,7 +86,6 @@ jobs:
-DCHATTERINO_LTO="$C2_ENABLE_LTO" \ -DCHATTERINO_LTO="$C2_ENABLE_LTO" \
-DCHATTERINO_PLUGINS="$C2_PLUGINS" \ -DCHATTERINO_PLUGINS="$C2_PLUGINS" \
-DCMAKE_PREFIX_PATH="$Qt6_DIR/lib/cmake" \ -DCMAKE_PREFIX_PATH="$Qt6_DIR/lib/cmake" \
-DBUILD_WITH_QT6="$C2_BUILD_WITH_QT6" \
-DCHATTERINO_STATIC_QT_BUILD=On \ -DCHATTERINO_STATIC_QT_BUILD=On \
.. ..
make -j"$(nproc)" make -j"$(nproc)"
@ -163,7 +161,7 @@ jobs:
- name: Install Qt5 - name: Install Qt5
if: startsWith(matrix.qt-version, '5.') if: startsWith(matrix.qt-version, '5.')
uses: jurplel/install-qt-action@v4.0.0 uses: jurplel/install-qt-action@v4.1.1
with: with:
cache: true cache: true
cache-key-prefix: ${{ runner.os }}-QtCache-${{ matrix.qt-version }}-v2 cache-key-prefix: ${{ runner.os }}-QtCache-${{ matrix.qt-version }}-v2
@ -171,7 +169,7 @@ jobs:
- name: Install Qt6 - name: Install Qt6
if: startsWith(matrix.qt-version, '6.') if: startsWith(matrix.qt-version, '6.')
uses: jurplel/install-qt-action@v4.0.0 uses: jurplel/install-qt-action@v4.1.1
with: with:
cache: true cache: true
cache-key-prefix: ${{ runner.os }}-QtCache-${{ matrix.qt-version }}-v2 cache-key-prefix: ${{ runner.os }}-QtCache-${{ matrix.qt-version }}-v2

View file

@ -30,7 +30,7 @@ jobs:
- name: Install Qt6 - name: Install Qt6
if: startsWith(matrix.qt-version, '6.') if: startsWith(matrix.qt-version, '6.')
uses: jurplel/install-qt-action@v4.0.0 uses: jurplel/install-qt-action@v4.1.1
with: with:
cache: true cache: true
cache-key-prefix: ${{ runner.os }}-QtCache-${{ matrix.qt-version }}-v2 cache-key-prefix: ${{ runner.os }}-QtCache-${{ matrix.qt-version }}-v2
@ -41,7 +41,7 @@ jobs:
- name: clang-tidy review - name: clang-tidy review
timeout-minutes: 20 timeout-minutes: 20
uses: ZedThree/clang-tidy-review@v0.20.0 uses: ZedThree/clang-tidy-review@v0.20.1
with: with:
build_dir: build-clang-tidy build_dir: build-clang-tidy
config_file: ".clang-tidy" config_file: ".clang-tidy"
@ -63,4 +63,4 @@ jobs:
libxkbcommon-x11-0, libxcb-xkb-dev, libxcb-cursor0 libxkbcommon-x11-0, libxcb-xkb-dev, libxcb-cursor0
- name: clang-tidy-review upload - name: clang-tidy-review upload
uses: ZedThree/clang-tidy-review/upload@v0.20.0 uses: ZedThree/clang-tidy-review/upload@v0.20.1

View file

@ -14,7 +14,7 @@ jobs:
if: ${{ github.event.workflow_run.conclusion == 'success' }} if: ${{ github.event.workflow_run.conclusion == 'success' }}
steps: steps:
- uses: ZedThree/clang-tidy-review/post@v0.20.0 - uses: ZedThree/clang-tidy-review/post@v0.20.1
with: with:
lgtm_comment_body: "" lgtm_comment_body: ""
num_comments_as_exitcode: false num_comments_as_exitcode: false

View file

@ -48,7 +48,7 @@ jobs:
fetch-depth: 0 # allows for tags access fetch-depth: 0 # allows for tags access
- name: Install Qt - name: Install Qt
uses: jurplel/install-qt-action@v4.0.0 uses: jurplel/install-qt-action@v4.1.1
with: with:
cache: true cache: true
cache-key-prefix: ${{ runner.os }}-QtCache-${{ matrix.qt-version }}-v2 cache-key-prefix: ${{ runner.os }}-QtCache-${{ matrix.qt-version }}-v2

View file

@ -58,7 +58,7 @@ jobs:
fetch-depth: 0 # allows for tags access fetch-depth: 0 # allows for tags access
- name: Install Qt - name: Install Qt
uses: jurplel/install-qt-action@v4.0.0 uses: jurplel/install-qt-action@v4.1.1
with: with:
cache: true cache: true
cache-key-prefix: ${{ runner.os }}-QtCache-${{ matrix.qt-version }}-v2 cache-key-prefix: ${{ runner.os }}-QtCache-${{ matrix.qt-version }}-v2

View file

@ -33,7 +33,6 @@ jobs:
fail-fast: false fail-fast: false
env: env:
C2_PLUGINS: ${{ matrix.plugins }} C2_PLUGINS: ${{ matrix.plugins }}
C2_BUILD_WITH_QT6: ${{ startsWith(matrix.qt-version, '6.') }}
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
@ -57,7 +56,6 @@ jobs:
-DBUILD_APP=OFF \ -DBUILD_APP=OFF \
-DCHATTERINO_PLUGINS="$C2_PLUGINS" \ -DCHATTERINO_PLUGINS="$C2_PLUGINS" \
-DCMAKE_PREFIX_PATH="$Qt6_DIR/lib/cmake" \ -DCMAKE_PREFIX_PATH="$Qt6_DIR/lib/cmake" \
-DBUILD_WITH_QT6="$C2_BUILD_WITH_QT6" \
-DCHATTERINO_STATIC_QT_BUILD=On \ -DCHATTERINO_STATIC_QT_BUILD=On \
-DCHATTERINO_GENERATE_COVERAGE=On \ -DCHATTERINO_GENERATE_COVERAGE=On \
-DCMAKE_BUILD_TYPE=Debug \ -DCMAKE_BUILD_TYPE=Debug \
@ -98,9 +96,9 @@ jobs:
working-directory: build-test working-directory: build-test
- name: Upload coverage reports to Codecov - name: Upload coverage reports to Codecov
uses: codecov/codecov-action@v4.6.0 uses: codecov/codecov-action@v5.0.7
with: with:
token: ${{ secrets.CODECOV_TOKEN }} token: ${{ secrets.CODECOV_TOKEN }}
plugin: gcov plugins: gcov
fail_ci_if_error: true fail_ci_if_error: true
verbose: true verbose: true

View file

@ -17,7 +17,7 @@ FreeBSD 15.0-SNAP.
``` ```
1. Generate build files. To enable Lua plugins in your build add `-DCHATTERINO_PLUGINS=ON` to this command. 1. Generate build files. To enable Lua plugins in your build add `-DCHATTERINO_PLUGINS=ON` to this command.
```sh ```sh
cmake -DBUILD_WITH_QT6=ON .. cmake ..
``` ```
1. Build the project 1. Build the project
```sh ```sh

View file

@ -53,7 +53,7 @@ nix-shell -p openssl boost qt6.full pkg-config cmake
``` ```
1. Generate build files. To enable Lua plugins in your build add `-DCHATTERINO_PLUGINS=ON` to this command. 1. Generate build files. To enable Lua plugins in your build add `-DCHATTERINO_PLUGINS=ON` to this command.
```sh ```sh
cmake -DBUILD_WITH_QT6=ON -DBUILD_WITH_QTKEYCHAIN=OFF .. cmake -DBUILD_WITH_QTKEYCHAIN=OFF ..
``` ```
1. Build the project 1. Build the project
```sh ```sh

View file

@ -10,7 +10,7 @@ Local dev machines for testing are available on Apple Silicon on macOS 13.
1. Install [Homebrew](https://brew.sh/#install) 1. Install [Homebrew](https://brew.sh/#install)
We use this for dependency management on macOS We use this for dependency management on macOS
1. Install all dependencies: 1. Install all dependencies:
`brew install boost openssl@1.1 rapidjson cmake qt@5` `brew install boost openssl@3 rapidjson cmake qt@6`
## Building ## Building
@ -21,7 +21,7 @@ Local dev machines for testing are available on Apple Silicon on macOS 13.
1. Create a build directory and go into it: 1. Create a build directory and go into it:
`mkdir build && cd build` `mkdir build && cd build`
1. Run CMake. To enable Lua plugins in your build add `-DCHATTERINO_PLUGINS=ON` to this command. 1. Run CMake. To enable Lua plugins in your build add `-DCHATTERINO_PLUGINS=ON` to this command.
`cmake -DCMAKE_PREFIX_PATH=/opt/homebrew/opt/qt@5 -DOPENSSL_ROOT_DIR=/opt/homebrew/opt/openssl@1.1 ..` `cmake -DCMAKE_PREFIX_PATH=/opt/homebrew/opt/qt@6 ..`
1. Build: 1. Build:
`make` `make`

View file

@ -73,6 +73,7 @@
- Bugfix: Fixed incorrect messages getting replaced visually. (#5683) - Bugfix: Fixed incorrect messages getting replaced visually. (#5683)
- Bugfix: Fixed rendering of multi-line selection that starts at a trailing space. (#5691) - Bugfix: Fixed rendering of multi-line selection that starts at a trailing space. (#5691)
- Bugfix: Fixed pause indicator not appearing in certain cases. (#5707) - Bugfix: Fixed pause indicator not appearing in certain cases. (#5707)
- Dev: Default build with Qt6 on all platforms. (#5716)
- Dev: Update Windows build from Qt 6.5.0 to Qt 6.7.1. (#5420) - Dev: Update Windows build from Qt 6.5.0 to Qt 6.7.1. (#5420)
- Dev: Update vcpkg build Qt from 6.5.0 to 6.7.0, boost from 1.83.0 to 1.85.0, openssl from 3.1.3 to 3.3.0. (#5422) - Dev: Update vcpkg build Qt from 6.5.0 to 6.7.0, boost from 1.83.0 to 1.85.0, openssl from 3.1.3 to 3.3.0. (#5422)
- Dev: Unsingletonize `ISoundController`. (#5462) - Dev: Unsingletonize `ISoundController`. (#5462)
@ -130,6 +131,7 @@
- Dev: Unified parsing of historic and live IRC messages. (#5678) - Dev: Unified parsing of historic and live IRC messages. (#5678)
- Dev: 7TV's `entitlement.reset` is now explicitly ignored. (#5685) - Dev: 7TV's `entitlement.reset` is now explicitly ignored. (#5685)
- Dev: Qt 6.8 and later now default to the GDI fontengine. (#5710) - Dev: Qt 6.8 and later now default to the GDI fontengine. (#5710)
- Dev: Moved to condition variables when shutting down worker threads. (#5721)
## 2.5.1 ## 2.5.1

View file

@ -21,11 +21,7 @@ option(BUILD_WITH_QTKEYCHAIN "Build Chatterino with support for your system key
option(USE_SYSTEM_MINIAUDIO "Build Chatterino with your system miniaudio" OFF) option(USE_SYSTEM_MINIAUDIO "Build Chatterino with your system miniaudio" OFF)
option(BUILD_WITH_CRASHPAD "Build chatterino with crashpad" OFF) option(BUILD_WITH_CRASHPAD "Build chatterino with crashpad" OFF)
option(USE_PRECOMPILED_HEADERS "Use precompiled headers" ON) option(USE_PRECOMPILED_HEADERS "Use precompiled headers" ON)
if(WIN32) option(BUILD_WITH_QT6 "Build with Qt6" On)
option(BUILD_WITH_QT6 "Build with Qt6, default on for Windows" On)
else()
option(BUILD_WITH_QT6 "Use Qt6 instead of default Qt5" OFF)
endif()
option(CHATTERINO_GENERATE_COVERAGE "Generate coverage files" OFF) option(CHATTERINO_GENERATE_COVERAGE "Generate coverage files" OFF)
# We don't use translations, and we don't want qtkeychain to build translations # We don't use translations, and we don't want qtkeychain to build translations
option(BUILD_TRANSLATIONS "" OFF) option(BUILD_TRANSLATIONS "" OFF)

@ -1 +1 @@
Subproject commit 4a0a1e599377cdcdc91b0fbbefc312936b48730c Subproject commit 46eb2509eb37a1c8b593d37bc51359ec2f31a541

View file

@ -514,6 +514,8 @@ set(SOURCE_FILES
util/LayoutHelper.hpp util/LayoutHelper.hpp
util/LoadPixmap.cpp util/LoadPixmap.cpp
util/LoadPixmap.hpp util/LoadPixmap.hpp
util/OnceFlag.cpp
util/OnceFlag.hpp
util/RapidjsonHelpers.cpp util/RapidjsonHelpers.cpp
util/RapidjsonHelpers.hpp util/RapidjsonHelpers.hpp
util/RatelimitBucket.cpp util/RatelimitBucket.cpp

View file

@ -8,10 +8,12 @@
#include "providers/twitch/PubSubHelpers.hpp" #include "providers/twitch/PubSubHelpers.hpp"
#include "util/DebugCount.hpp" #include "util/DebugCount.hpp"
#include "util/ExponentialBackoff.hpp" #include "util/ExponentialBackoff.hpp"
#include "util/OnceFlag.hpp"
#include "util/RenameThread.hpp" #include "util/RenameThread.hpp"
#include <pajlada/signals/signal.hpp> #include <pajlada/signals/signal.hpp>
#include <QJsonObject> #include <QJsonObject>
#include <QScopeGuard>
#include <QString> #include <QString>
#include <QStringBuilder> #include <QStringBuilder>
#include <websocketpp/client.hpp> #include <websocketpp/client.hpp>
@ -120,6 +122,11 @@ public:
this->work_ = std::make_shared<boost::asio::io_service::work>( this->work_ = std::make_shared<boost::asio::io_service::work>(
this->websocketClient_.get_io_service()); this->websocketClient_.get_io_service());
this->mainThread_.reset(new std::thread([this] { this->mainThread_.reset(new std::thread([this] {
// make sure we set in any case, even exceptions
auto guard = qScopeGuard([&] {
this->stoppedFlag_.set();
});
runThread(); runThread();
})); }));
@ -142,22 +149,34 @@ public:
this->work_.reset(); this->work_.reset();
if (this->mainThread_->joinable()) if (!this->mainThread_->joinable())
{ {
// NOTE: We spawn a new thread to join the websocket thread. return;
}
// NOTE:
// There is a case where a new client was initiated but not added to the clients list. // There is a case where a new client was initiated but not added to the clients list.
// We just don't join the thread & let the operating system nuke the thread if joining fails // We just don't join the thread & let the operating system nuke the thread if joining fails
// within 1s. // within 1s.
auto joiner = std::async(std::launch::async, &std::thread::join, if (this->stoppedFlag_.waitFor(std::chrono::seconds{1}))
this->mainThread_.get());
if (joiner.wait_for(std::chrono::seconds(1)) ==
std::future_status::timeout)
{ {
this->mainThread_->join();
return;
}
qCWarning(chatterinoLiveupdates) qCWarning(chatterinoLiveupdates)
<< "Thread didn't join within 1 second, rip it out"; << "Thread didn't finish within 1 second, force-stop the client";
this->websocketClient_.stop(); this->websocketClient_.stop();
if (this->stoppedFlag_.waitFor(std::chrono::milliseconds{100}))
{
this->mainThread_->join();
return;
} }
}
qCWarning(chatterinoLiveupdates)
<< "Thread didn't finish after stopping, discard it";
// detach the thread so the destructor doesn't attempt any joining
this->mainThread_->detach();
} }
protected: protected:
@ -394,6 +413,7 @@ private:
liveupdates::WebsocketClient websocketClient_; liveupdates::WebsocketClient websocketClient_;
std::unique_ptr<std::thread> mainThread_; std::unique_ptr<std::thread> mainThread_;
OnceFlag stoppedFlag_;
const QString host_; const QString host_;

View file

@ -15,10 +15,10 @@
#include "util/RenameThread.hpp" #include "util/RenameThread.hpp"
#include <QJsonArray> #include <QJsonArray>
#include <QScopeGuard>
#include <algorithm> #include <algorithm>
#include <exception> #include <exception>
#include <future>
#include <iostream> #include <iostream>
#include <memory> #include <memory>
#include <thread> #include <thread>
@ -560,6 +560,11 @@ void PubSub::start()
this->work = std::make_shared<boost::asio::io_service::work>( this->work = std::make_shared<boost::asio::io_service::work>(
this->websocketClient.get_io_service()); this->websocketClient.get_io_service());
this->thread = std::make_unique<std::thread>([this] { this->thread = std::make_unique<std::thread>([this] {
// make sure we set in any case, even exceptions
auto guard = qScopeGuard([&] {
this->stoppedFlag_.set();
});
runThread(); runThread();
}); });
renameThread(*this->thread, "PubSub"); renameThread(*this->thread, "PubSub");
@ -578,23 +583,36 @@ void PubSub::stop()
this->work.reset(); this->work.reset();
if (this->thread->joinable()) if (!this->thread->joinable())
{ {
// NOTE: We spawn a new thread to join the websocket thread. return;
}
// NOTE:
// There is a case where a new client was initiated but not added to the clients list. // There is a case where a new client was initiated but not added to the clients list.
// We just don't join the thread & let the operating system nuke the thread if joining fails // We just don't join the thread & let the operating system nuke the thread if joining fails
// within 1s. // within 1s.
// We could fix the underlying bug, but this is easier & we realistically won't use this exact code // We could fix the underlying bug, but this is easier & we realistically won't use this exact code
// for super much longer. // for super much longer.
auto joiner = std::async(std::launch::async, &std::thread::join, if (this->stoppedFlag_.waitFor(std::chrono::seconds{1}))
this->thread.get());
if (joiner.wait_for(1s) == std::future_status::timeout)
{ {
qCWarning(chatterinoPubSub) this->thread->join();
<< "Thread didn't join within 1 second, rip it out"; return;
}
qCWarning(chatterinoLiveupdates)
<< "Thread didn't finish within 1 second, force-stop the client";
this->websocketClient.stop(); this->websocketClient.stop();
if (this->stoppedFlag_.waitFor(std::chrono::milliseconds{100}))
{
this->thread->join();
return;
} }
}
qCWarning(chatterinoLiveupdates)
<< "Thread didn't finish after stopping, discard it";
// detach the thread so the destructor doesn't attempt any joining
this->thread->detach();
} }
bool PubSub::listenToWhispers() bool PubSub::listenToWhispers()

View file

@ -3,6 +3,7 @@
#include "providers/twitch/PubSubClientOptions.hpp" #include "providers/twitch/PubSubClientOptions.hpp"
#include "providers/twitch/PubSubWebsocket.hpp" #include "providers/twitch/PubSubWebsocket.hpp"
#include "util/ExponentialBackoff.hpp" #include "util/ExponentialBackoff.hpp"
#include "util/OnceFlag.hpp"
#include <boost/asio/io_service.hpp> #include <boost/asio/io_service.hpp>
#include <boost/asio/ssl/context.hpp> #include <boost/asio/ssl/context.hpp>
@ -267,6 +268,8 @@ private:
const QString host_; const QString host_;
const PubSubClientOptions clientOptions_; const PubSubClientOptions clientOptions_;
OnceFlag stoppedFlag_;
bool stopping_{false}; bool stopping_{false};
#ifdef FRIEND_TEST #ifdef FRIEND_TEST

View file

@ -4,6 +4,7 @@
#include "common/Channel.hpp" #include "common/Channel.hpp"
#include "common/Common.hpp" #include "common/Common.hpp"
#include "common/Env.hpp" #include "common/Env.hpp"
#include "common/Literals.hpp"
#include "common/QLogging.hpp" #include "common/QLogging.hpp"
#include "controllers/accounts/AccountController.hpp" #include "controllers/accounts/AccountController.hpp"
#include "messages/LimitedQueueSnapshot.hpp" #include "messages/LimitedQueueSnapshot.hpp"
@ -147,6 +148,8 @@ bool shouldSendHelixChat()
namespace chatterino { namespace chatterino {
using namespace literals;
TwitchIrcServer::TwitchIrcServer() TwitchIrcServer::TwitchIrcServer()
: whispersChannel(new Channel("/whispers", Channel::Type::TwitchWhispers)) : whispersChannel(new Channel("/whispers", Channel::Type::TwitchWhispers))
, mentionsChannel(new Channel("/mentions", Channel::Type::TwitchMentions)) , mentionsChannel(new Channel("/mentions", Channel::Type::TwitchMentions))
@ -488,9 +491,49 @@ void TwitchIrcServer::initialize()
if (msg.status == "PENDING") if (msg.status == "PENDING")
{ {
AutomodAction action(msg.data, channelID); AutomodAction action(msg.data, channelID);
if (msg.reason ==
PubSubAutoModQueueMessage::Reason::BlockedTerm)
{
auto numBlockedTermsMatched =
msg.blockedTermsFound.count();
auto hideBlockedTerms =
getSettings()
->streamerModeHideBlockedTermText &&
getApp()->getStreamerMode()->isEnabled();
if (!msg.blockedTermsFound.isEmpty())
{
if (hideBlockedTerms)
{
action.reason =
u"matches %1 blocked term%2"_s
.arg(numBlockedTermsMatched)
.arg(numBlockedTermsMatched > 1
? u"s"
: u"");
}
else
{
action.reason =
u"matches %1 blocked term%2 \"%3\""_s
.arg(numBlockedTermsMatched)
.arg(numBlockedTermsMatched > 1
? u"s"
: u"")
.arg(msg.blockedTermsFound.join(
u"\", \""));
}
}
else
{
action.reason = "blocked term usage";
}
}
else
{
action.reason = QString("%1 level %2") action.reason = QString("%1 level %2")
.arg(msg.contentCategory) .arg(msg.contentCategory)
.arg(msg.contentLevel); .arg(msg.contentLevel);
}
action.msgID = msg.messageID; action.msgID = msg.messageID;
action.message = msg.messageText; action.message = msg.messageText;

View file

@ -2,6 +2,8 @@
#include "util/QMagicEnum.hpp" #include "util/QMagicEnum.hpp"
#include <QJsonArray>
namespace chatterino { namespace chatterino {
PubSubAutoModQueueMessage::PubSubAutoModQueueMessage(const QJsonObject &root) PubSubAutoModQueueMessage::PubSubAutoModQueueMessage(const QJsonObject &root)
@ -9,6 +11,7 @@ PubSubAutoModQueueMessage::PubSubAutoModQueueMessage(const QJsonObject &root)
, data(root.value("data").toObject()) , data(root.value("data").toObject())
, status(this->data.value("status").toString()) , status(this->data.value("status").toString())
{ {
qInfo() << "XXX: automod message over pubsub:" << root;
auto oType = qmagicenum::enumCast<Type>(this->typeString); auto oType = qmagicenum::enumCast<Type>(this->typeString);
if (oType.has_value()) if (oType.has_value())
{ {
@ -41,6 +44,27 @@ PubSubAutoModQueueMessage::PubSubAutoModQueueMessage(const QJsonObject &root)
messageSender.value("display_name").toString(); messageSender.value("display_name").toString();
this->senderUserChatColor = this->senderUserChatColor =
QColor(messageSender.value("chat_color").toString()); QColor(messageSender.value("chat_color").toString());
if (this->reason == Reason::BlockedTerm)
{
// Attempt to read the blocked term(s) that caused this message to be blocked
const auto caughtMessageReason =
data.value("caught_message_reason").toObject();
const auto blockedTermFailure =
caughtMessageReason.value("blocked_term_failure").toObject();
const auto termsFound =
blockedTermFailure.value("terms_found").toArray();
for (const auto &termValue : termsFound)
{
const auto term = termValue.toObject();
const auto termText = term.value("text").toString();
if (!termText.isEmpty())
{
this->blockedTermsFound.push_back(termText);
}
}
}
} }
} // namespace chatterino } // namespace chatterino

View file

@ -4,6 +4,7 @@
#include <QColor> #include <QColor>
#include <QJsonObject> #include <QJsonObject>
#include <QString> #include <QString>
#include <QStringList>
namespace chatterino { namespace chatterino {
@ -40,6 +41,8 @@ struct PubSubAutoModQueueMessage {
QString senderUserDisplayName; QString senderUserDisplayName;
QColor senderUserChatColor; QColor senderUserChatColor;
QStringList blockedTermsFound;
PubSubAutoModQueueMessage() = default; PubSubAutoModQueueMessage() = default;
explicit PubSubAutoModQueueMessage(const QJsonObject &root); explicit PubSubAutoModQueueMessage(const QJsonObject &root);
}; };

View file

@ -337,6 +337,10 @@ public:
"/streamerMode/supressLiveNotifications", false}; "/streamerMode/supressLiveNotifications", false};
BoolSetting streamerModeSuppressInlineWhispers = { BoolSetting streamerModeSuppressInlineWhispers = {
"/streamerMode/suppressInlineWhispers", true}; "/streamerMode/suppressInlineWhispers", true};
BoolSetting streamerModeHideBlockedTermText = {
"/streamerMode/hideBlockedTermText",
true,
};
/// Ignored Phrases /// Ignored Phrases
QStringSetting ignoredPhraseReplace = {"/ignore/ignoredPhraseReplace", QStringSetting ignoredPhraseReplace = {"/ignore/ignoredPhraseReplace",

33
src/util/OnceFlag.cpp Normal file
View file

@ -0,0 +1,33 @@
#include "util/OnceFlag.hpp"
namespace chatterino {
OnceFlag::OnceFlag() = default;
OnceFlag::~OnceFlag() = default;
void OnceFlag::set()
{
{
std::unique_lock guard(this->mutex);
this->flag = true;
}
this->condvar.notify_all();
}
bool OnceFlag::waitFor(std::chrono::milliseconds ms)
{
std::unique_lock lock(this->mutex);
return this->condvar.wait_for(lock, ms, [this] {
return this->flag;
});
}
void OnceFlag::wait()
{
std::unique_lock lock(this->mutex);
this->condvar.wait(lock, [this] {
return this->flag;
});
}
} // namespace chatterino

41
src/util/OnceFlag.hpp Normal file
View file

@ -0,0 +1,41 @@
#pragma once
#include <chrono>
#include <condition_variable>
#include <mutex>
namespace chatterino {
/// @brief A flag that can only be set once which notifies waiters.
///
/// This can be used to synchronize with other threads. Note that waiting
/// threads will be suspended.
class OnceFlag
{
public:
OnceFlag();
~OnceFlag();
/// Set this flag and notify waiters
void set();
/// @brief Wait for at most `ms` until this flag is set.
///
/// The calling thread will be suspended during the wait.
///
/// @param ms The maximum time to wait for this flag
/// @returns `true` if this flag was set during the wait or before
bool waitFor(std::chrono::milliseconds ms);
/// @brief Wait until this flag is set by another thread
///
/// The calling thread will be suspended during the wait.
void wait();
private:
std::mutex mutex;
std::condition_variable condvar;
bool flag = false;
};
} // namespace chatterino

View file

@ -667,6 +667,11 @@ void GeneralPage::initLayout(GeneralPageView &layout)
layout.addCheckbox( layout.addCheckbox(
"Hide moderation actions", s.streamerModeHideModActions, false, "Hide moderation actions", s.streamerModeHideModActions, false,
"Hide bans, timeouts, and automod messages from appearing in chat."); "Hide bans, timeouts, and automod messages from appearing in chat.");
layout.addCheckbox(
"Hide blocked terms", s.streamerModeHideBlockedTermText, false,
"Hide blocked terms from showing up in places like AutoMod messages. "
"This can be useful in case you have some blocked terms that you don't "
"want to show on stream.");
layout.addCheckbox("Mute mention sounds", s.streamerModeMuteMentions, false, layout.addCheckbox("Mute mention sounds", s.streamerModeMuteMentions, false,
"Mute your ping sound from playing."); "Mute your ping sound from playing.");
layout.addCheckbox( layout.addCheckbox(

View file

@ -51,6 +51,7 @@ set(test_SOURCES
${CMAKE_CURRENT_LIST_DIR}/src/Plugins.cpp ${CMAKE_CURRENT_LIST_DIR}/src/Plugins.cpp
${CMAKE_CURRENT_LIST_DIR}/src/TwitchIrc.cpp ${CMAKE_CURRENT_LIST_DIR}/src/TwitchIrc.cpp
${CMAKE_CURRENT_LIST_DIR}/src/IgnoreController.cpp ${CMAKE_CURRENT_LIST_DIR}/src/IgnoreController.cpp
${CMAKE_CURRENT_LIST_DIR}/src/OnceFlag.cpp
${CMAKE_CURRENT_LIST_DIR}/src/lib/Snapshot.cpp ${CMAKE_CURRENT_LIST_DIR}/src/lib/Snapshot.cpp
${CMAKE_CURRENT_LIST_DIR}/src/lib/Snapshot.hpp ${CMAKE_CURRENT_LIST_DIR}/src/lib/Snapshot.hpp
# Add your new file above this line! # Add your new file above this line!

88
tests/src/OnceFlag.cpp Normal file
View file

@ -0,0 +1,88 @@
#include "util/OnceFlag.hpp"
#include "Test.hpp"
#include <thread>
using namespace chatterino;
// this test shouldn't time out (no assert necessary)
TEST(OnceFlag, basic)
{
OnceFlag startedFlag;
OnceFlag startedAckFlag;
OnceFlag stoppedFlag;
std::thread t([&] {
startedFlag.set();
startedAckFlag.wait();
std::this_thread::sleep_for(std::chrono::milliseconds{50});
stoppedFlag.set();
});
startedFlag.wait();
startedAckFlag.set();
stoppedFlag.wait();
t.join();
}
TEST(OnceFlag, waitFor)
{
OnceFlag startedFlag;
OnceFlag startedAckFlag;
OnceFlag stoppedFlag;
std::thread t([&] {
startedFlag.set();
startedAckFlag.wait();
std::this_thread::sleep_for(std::chrono::milliseconds{100});
stoppedFlag.set();
});
startedFlag.wait();
startedAckFlag.set();
auto start = std::chrono::system_clock::now();
ASSERT_TRUE(stoppedFlag.waitFor(std::chrono::milliseconds{200}));
auto stop = std::chrono::system_clock::now();
ASSERT_LT(stop - start, std::chrono::milliseconds{150});
start = std::chrono::system_clock::now();
ASSERT_TRUE(stoppedFlag.waitFor(std::chrono::milliseconds{1000}));
stop = std::chrono::system_clock::now();
ASSERT_LT(stop - start, std::chrono::milliseconds{10});
start = std::chrono::system_clock::now();
stoppedFlag.wait();
stop = std::chrono::system_clock::now();
ASSERT_LT(stop - start, std::chrono::milliseconds{10});
t.join();
}
TEST(OnceFlag, waitForTimeout)
{
OnceFlag startedFlag;
OnceFlag startedAckFlag;
OnceFlag stoppedFlag;
std::thread t([&] {
startedFlag.set();
startedAckFlag.wait();
std::this_thread::sleep_for(std::chrono::milliseconds{100});
stoppedFlag.set();
});
startedFlag.wait();
startedAckFlag.set();
ASSERT_FALSE(stoppedFlag.waitFor(std::chrono::milliseconds{25}));
stoppedFlag.wait();
t.join();
}