Merge remote-tracking branch 'origin/master' into feature/lua_scripting

This commit is contained in:
Mm2PL 2023-02-12 23:40:56 +01:00
commit 3ece8c8ed1
87 changed files with 1828 additions and 618 deletions

View file

@ -57,3 +57,6 @@ exec "$here/usr/bin/chatterino" "$@"' > appdir/AppRun
chmod a+x appdir/AppRun
./appimagetool-x86_64.AppImage appdir
# TODO: Create appimage in a unique directory instead maybe idk?
rm -rf appdir

View file

@ -1,6 +1,37 @@
#!/bin/sh
set -e
breakline() {
printf "================================================================================\n\n"
}
# Configured in the CI step
install_prefix="appdir/usr"
# The directory we finally pack into our .deb package
packaging_dir="package"
# Get the Ubuntu Release (e.g. 20.04 or 22.04)
ubuntu_release="$(lsb_release -rs)"
# Refactor opportunity:
case "$ubuntu_release" in
20.04)
dependencies="libc6, libstdc++6, libqt5core5a, libqt5concurrent5, libqt5dbus5, libqt5gui5, libqt5network5, libqt5svg5, libqt5widgets5, qt5-image-formats-plugins, libboost-filesystem1.71.0"
;;
22.04)
dependencies="libc6, libstdc++6, libqt5core5a, libqt5concurrent5, libqt5dbus5, libqt5gui5, libqt5network5, libqt5svg5, libqt5widgets5, qt5-image-formats-plugins, libboost-filesystem1.74.0"
;;
*)
echo "Unsupported Ubuntu release $ubuntu_release"
exit 1
;;
esac
echo "Building Ubuntu .deb file on '$ubuntu_release'"
echo "Dependencies: $dependencies"
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
@ -8,33 +39,53 @@ fi
chatterino_version=$(git describe 2>/dev/null | cut -c 2-) || true
if [ -z "$chatterino_version" ]; then
# Fall back to this in case the build happened outside of a git repo
chatterino_version="0.0.0-dev"
echo "Falling back to setting the version to '$chatterino_version'"
else
echo "Found Chatterino version $chatterino_version via git"
fi
rm -vrf "./package" || true # delete any old packaging dir
# Make sure no old remnants of a previous packaging remains
rm -vrf "$packaging_dir"
# create ./package/ from scratch
mkdir package/DEBIAN -p
packaging_dir="$(realpath ./package)"
mkdir -p "$packaging_dir/DEBIAN"
echo "Making control file"
cat >> "$packaging_dir/DEBIAN/control" << EOF
Package: chatterino
Section: net
Priority: optional
Version: $chatterino_version
Architecture: amd64
Maintainer: Mm2PL <mm2pl@kotmisia.pl>
Description: Testing out chatterino as a Ubuntu package
Depends: libc6, libqt5concurrent5, libqt5core5a, libqt5dbus5, libqt5gui5, libqt5multimedia5, libqt5network5, libqt5svg5, libqt5widgets5, libssl1.1, libstdc++6
Depends: $dependencies
Section: net
Priority: optional
Homepage: https://github.com/Chatterino/chatterino2
Description: Ubuntu package built for $ubuntu_release
EOF
echo "Version: $chatterino_version" >> "$packaging_dir/DEBIAN/control"
cat "$packaging_dir/DEBIAN/control"
breakline
echo "Running make install in package dir"
DESTDIR="$packaging_dir" make INSTALL_ROOT="$packaging_dir" -j"$(nproc)" install; find "$packaging_dir/"
echo ""
echo "Building package..."
echo "Running make install"
make install
find "$install_prefix"
breakline
echo "Merge install into packaging dir"
cp -rv "$install_prefix/" "$packaging_dir/"
find "$packaging_dir"
breakline
echo "Building package"
dpkg-deb --build "$packaging_dir" "Chatterino-x86_64.deb"
breakline
echo "Package info"
dpkg --info Chatterino-x86_64.deb
breakline
echo "Package contents"
dpkg --contents Chatterino-x86_64.deb # Shows folders and files inside .deb file
breakline

View file

@ -0,0 +1,54 @@
FROM ubuntu:20.04
ENV TZ=UTC
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
RUN apt-get update && apt-get -y install --no-install-recommends \
cmake \
virtualenv \
rapidjson-dev \
libfuse2 \
libssl-dev \
libboost-dev \
libxcb-randr0-dev \
libboost-system-dev \
libboost-filesystem-dev \
libpulse-dev \
libxkbcommon-x11-0 \
build-essential \
libgl1-mesa-dev \
libxcb-icccm4 \
libxcb-image0 \
libxcb-keysyms1 \
libxcb-render-util0 \
libxcb-xinerama0
RUN apt-get -y install \
git \
lsb-release \
python3-pip && \
apt-get clean all
# Install Qt as we do in CI
RUN pip3 install -U pip && \
pip3 install aqtinstall && \
aqt install-qt linux desktop 5.12.12 && \
mkdir -p /opt/qt512 && \
mv /5.12.12/gcc_64/* /opt/qt512
ADD . /src
RUN mkdir /src/build
# cmake
RUN cd /src/build && \
CXXFLAGS=-fno-sized-deallocation cmake \
-DCMAKE_INSTALL_PREFIX=appdir/usr/ \
-DCMAKE_PREFIX_PATH=/opt/qt512/lib/cmake \
-DBUILD_WITH_QTKEYCHAIN=OFF \
..
# build
RUN cd /src/build && \
make -j8

View file

@ -0,0 +1,13 @@
FROM chatterino-ubuntu-20.04-build
ADD .CI /src/.CI
WORKDIR /src/build
# RUN apt-get install -y wget
# create appimage
# RUN pwd && ./../.CI/CreateAppImage.sh
# package deb
RUN pwd && ./../.CI/CreateUbuntuDeb.sh

View file

@ -0,0 +1,57 @@
FROM ubuntu:22.04
ENV TZ=UTC
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
RUN apt-get update && apt-get -y install --no-install-recommends \
cmake \
virtualenv \
rapidjson-dev \
libfuse2 \
libssl-dev \
libboost-dev \
libxcb-randr0-dev \
libboost-system-dev \
libboost-filesystem-dev \
libpulse-dev \
libxkbcommon-x11-0 \
build-essential \
libgl1-mesa-dev \
libxcb-icccm4 \
libxcb-image0 \
libxcb-keysyms1 \
libxcb-render-util0 \
libxcb-xinerama0
RUN apt-get -y install \
git \
lsb-release \
python3-pip && \
apt-get clean all
# Install Qt as we do in CI
RUN pip3 install -U pip && \
pip3 install aqtinstall && \
aqt install-qt linux desktop 5.15.2 && \
mkdir -p /opt/qt515 && \
mv /5.15.2/gcc_64/* /opt/qt515
ADD . /src
RUN mkdir /src/build
# Apply Qt patches
RUN patch "/opt/qt515/include/QtConcurrent/qtconcurrentthreadengine.h" /src/.patches/qt5-on-newer-gcc.patch
# cmake
RUN cd /src/build && \
CXXFLAGS=-fno-sized-deallocation cmake \
-DCMAKE_INSTALL_PREFIX=appdir/usr/ \
-DCMAKE_PREFIX_PATH=/opt/qt515/lib/cmake \
-DBUILD_WITH_QTKEYCHAIN=OFF \
..
# build
RUN cd /src/build && \
make -j8

View file

@ -0,0 +1,8 @@
FROM chatterino-ubuntu-22.04-build
ADD .CI /src/.CI
WORKDIR /src/build
# package deb
RUN ./../.CI/CreateUbuntuDeb.sh

29
.docker/README.md Normal file
View file

@ -0,0 +1,29 @@
## Groups
### Ubuntu 20.04 package
`Dockerfile-ubuntu-20.04-package` relies on `Dockerfile-ubuntu-20.04-build`
To build, from the repo root
1. Build a docker image that contains all the build artifacts and source from building Chatterino on Ubuntu 20.04
`docker build -t chatterino-ubuntu-20.04-build -f .docker/Dockerfile-ubuntu-20.04-build .`
1. Build a docker image that uses the above-built image & packages it into a .deb file
`docker build -t chatterino-ubuntu-20.04-package -f .docker/Dockerfile-ubuntu-20.04-package .`
To extract the final package, you can run the following command:
`docker run -v $PWD:/opt/mount --rm -it chatterino-ubuntu-20.04-package bash -c "cp /src/build/Chatterino-x86_64.deb /opt/mount/"`
### Ubuntu 22.04 package
`Dockerfile-ubuntu-22.04-package` relies on `Dockerfile-ubuntu-22.04-build`
To build, from the repo root
1. Build a docker image that contains all the build artifacts and source from building Chatterino on Ubuntu 22.04
`docker build -t chatterino-ubuntu-22.04-build -f .docker/Dockerfile-ubuntu-22.04-build .`
1. Build a docker image that uses the above-built image & packages it into a .deb file
`docker build -t chatterino-ubuntu-22.04-package -f .docker/Dockerfile-ubuntu-22.04-package .`
To extract the final package, you can run the following command:
`docker run -v $PWD:/opt/mount --rm -it chatterino-ubuntu-22.04-package bash -c "cp /src/build/Chatterino-x86_64.deb /opt/mount/"`

4
.dockerignore Normal file
View file

@ -0,0 +1,4 @@
build*
.mypy_cache
.cache
.docker

View file

@ -17,19 +17,41 @@ env:
jobs:
build:
name: "Build ${{ matrix.os }}, Qt ${{ matrix.qt-version }} (PCH:${{ matrix.pch }}, LTO:${{ matrix.force-lto }})"
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [windows-latest, ubuntu-20.04, macos-latest]
os: [windows-latest, macos-latest]
qt-version: [5.15.2, 5.12.12]
pch: [true]
force-lto: [false]
plugins: [false]
skip_artifact: ["no"]
crashpad: [true]
include:
# Ubuntu 20.04, Qt 5.12
- os: ubuntu-20.04
qt-version: 5.12.12
pch: true
force-lto: false
# Ubuntu 22.04, Qt 5.15
- os: ubuntu-22.04
qt-version: 5.15.2
pch: true
force-lto: false
# Test for disabling Precompiled Headers & enabling link-time optimization
- os: ubuntu-22.04
qt-version: 5.15.2
pch: false
force-lto: true
skip_artifact: "yes"
# Test for disabling crashpad on Windows
- os: windows-latest
qt-version: 5.15.2
pch: false
force-lto: true
skip_artifact: "yes"
crashpad: false
- os: ubuntu-20.04
qt-version: 5.15.2
pch: true
@ -45,7 +67,6 @@ jobs:
pch: true
force-lto: false
plugins: true
fail-fast: false
steps:
@ -59,6 +80,18 @@ jobs:
if: matrix.plugins == true
run: |
echo "C2_PLUGINS=ON" >> "$GITHUB_ENV"
echo "artifact_descr=plugins" >> "$GITHUB_ENV"
shell: bash
- name: Disable plugin support
if: matrix.plugins == false
echo "artifact_descr=no-plugins" >> "$GITHUB_ENV"
shell: bash
- name: Set Crashpad
if: matrix.crashpad == true
run: |
echo "C2_ENABLE_CRASHPAD=ON" >> "$GITHUB_ENV"
shell: bash
- name: Set environment variables for windows-latest
@ -69,7 +102,7 @@ jobs:
- uses: actions/checkout@v3
with:
submodules: true
submodules: recursive
fetch-depth: 0 # allows for tags access
- name: Install Qt
@ -85,14 +118,14 @@ jobs:
if: startsWith(matrix.os, 'windows')
uses: actions/cache@v3
with:
key: ${{ runner.os }}-conan-user-${{ hashFiles('**/conanfile.txt') }}
key: ${{ runner.os }}-${{ matrix.crashpad }}-conan-user-${{ hashFiles('**/conanfile.txt') }}
path: ~/.conan/
- name: Cache conan packages part 2
if: startsWith(matrix.os, 'windows')
uses: actions/cache@v3
with:
key: ${{ runner.os }}-conan-root-${{ hashFiles('**/conanfile.txt') }}
key: ${{ runner.os }}-${{ matrix.crashpad }}-conan-root-${{ hashFiles('**/conanfile.txt') }}
path: C:/.conan/
- name: Add Conan to path
@ -106,7 +139,7 @@ jobs:
- name: Enable Developer Command Prompt
if: startsWith(matrix.os, 'windows')
uses: ilammy/msvc-dev-cmd@v1.12.0
uses: ilammy/msvc-dev-cmd@v1.12.1
- name: Setup Conan (Windows)
if: startsWith(matrix.os, 'windows')
@ -119,34 +152,50 @@ jobs:
run: |
mkdir build
cd build
conan install .. -s build_type=Release -b missing -pr:b=default
conan install .. -s build_type=RelWithDebInfo -b missing -pr:b=default
cmake `
-G"NMake Makefiles" `
-DCMAKE_BUILD_TYPE=Release `
-DCMAKE_BUILD_TYPE=RelWithDebInfo `
-DCMAKE_TOOLCHAIN_FILE="conan_toolchain.cmake" `
-DBUILD_WITH_CRASHPAD="$Env:C2_ENABLE_CRASHPAD" `
-DCHATTERINO_LTO="$Env:C2_ENABLE_LTO" `
-DCHATTERINO_PLUGINS="$Env:C2_PLUGINS" `
..
set cl=/MP
nmake /S /NOLOGO
- name: Build crashpad (Windows)
if: startsWith(matrix.os, 'windows') && matrix.crashpad
run: |
cd build
set cl=/MP
nmake /S /NOLOGO crashpad_handler
mkdir Chatterino2/crashpad
cp bin/crashpad/crashpad_handler.exe Chatterino2/crashpad/crashpad_handler.exe
7z a bin/chatterino.pdb.7z bin/chatterino.pdb
- name: Package (windows)
if: startsWith(matrix.os, 'windows')
run: |
cd build
windeployqt bin/chatterino.exe --release --no-compiler-runtime --no-translations --no-opengl-sw --dir Chatterino2/
cp bin/chatterino.exe Chatterino2/
echo nightly > Chatterino2/modes
7z a chatterino-windows-x86-64.zip Chatterino2/
- name: Upload artifact (Windows)
if: startsWith(matrix.os, 'windows') && matrix.plugins == false
- name: Upload artifact (Windows - binary)
if: startsWith(matrix.os, 'windows') && matrix.skip_artifact != 'yes'
uses: actions/upload-artifact@v3
with:
name: chatterino-windows-x86-64-${{ matrix.qt-version }}.zip
name: chatterino-windows-x86-64-${{ matrix.qt-version }}-${{ artifact_descr }}.zip
path: build/chatterino-windows-x86-64.zip
- name: Upload artifact (Windows, plugins)
if: startsWith(matrix.os, 'windows') && matrix.plugins == true
- name: Upload artifact (Windows - symbols)
if: startsWith(matrix.os, 'windows') && matrix.skip_artifact != 'yes'
uses: actions/upload-artifact@v3
with:
name: chatterino-windows-x86-64-${{ matrix.qt-version }}-plugins.zip
path: build/chatterino-windows-x86-64.zip
name: chatterino-windows-x86-64-${{ matrix.qt-version }}-symbols.pdb.7z
path: build/bin/chatterino.pdb.7z
- name: Clean Conan pkgs
if: startsWith(matrix.os, 'windows')
@ -170,7 +219,6 @@ jobs:
libboost-filesystem-dev \
libpulse-dev \
libxkbcommon-x11-0 \
libgstreamer-plugins-base1.0-0 \
build-essential \
libgl1-mesa-dev \
libxcb-icccm4 \
@ -179,12 +227,18 @@ jobs:
libxcb-render-util0 \
libxcb-xinerama0
- name: Apply Qt patches (Ubuntu)
if: startsWith(matrix.os, 'ubuntu') && startsWith(matrix.qt-version, '5.')
run: |
patch "$Qt5_DIR/include/QtConcurrent/qtconcurrentthreadengine.h" .patches/qt5-on-newer-gcc.patch
shell: bash
- name: Build (Ubuntu)
if: startsWith(matrix.os, 'ubuntu')
run: |
mkdir build
cd build
cmake \
CXXFLAGS=-fno-sized-deallocation cmake \
-DCMAKE_INSTALL_PREFIX=appdir/usr/ \
-DCMAKE_BUILD_TYPE=Release \
-DPAJLADA_SETTINGS_USE_BOOST_FILESYSTEM=On \
@ -215,24 +269,24 @@ jobs:
clang-tidy-review-metadata.json
- name: Package - AppImage (Ubuntu)
if: startsWith(matrix.os, 'ubuntu')
if: startsWith(matrix.os, 'ubuntu') && matrix.skip_artifact != 'yes'
run: |
cd build
sh ./../.CI/CreateAppImage.sh
shell: bash
- name: Package - .deb (Ubuntu)
if: startsWith(matrix.os, 'ubuntu')
if: startsWith(matrix.os, 'ubuntu') && matrix.skip_artifact != 'yes'
run: |
cd build
sh ./../.CI/CreateUbuntuDeb.sh
shell: bash
- name: Upload artifact - AppImage (Ubuntu)
if: startsWith(matrix.os, 'ubuntu') && matrix.plugins == false
if: startsWith(matrix.os, 'ubuntu') && matrix.skip_artifact != 'yes'
uses: actions/upload-artifact@v3
with:
name: Chatterino-x86_64-${{ matrix.qt-version }}.AppImage
name: Chatterino-x86_64-${{ matrix.qt-version }}-${{ artifact_descr }}.AppImage
path: build/Chatterino-x86_64.AppImage
- name: Upload artifact - AppImage (Ubuntu, plugins)
@ -243,17 +297,10 @@ jobs:
path: build/Chatterino-x86_64.AppImage
- name: Upload artifact - .deb (Ubuntu)
if: startsWith(matrix.os, 'ubuntu') && matrix.plugins == false
if: startsWith(matrix.os, 'ubuntu') && matrix.skip_artifact != 'yes'
uses: actions/upload-artifact@v3
with:
name: Chatterino-${{ matrix.qt-version }}.deb
path: build/Chatterino-x86_64.deb
- name: Upload artifact - .deb (Ubuntu, plugins)
if: startsWith(matrix.os, 'ubuntu') && matrix.plugins == true
uses: actions/upload-artifact@v3
with:
name: Chatterino-${{ matrix.qt-version }}-plugins.deb
name: Chatterino-${{ matrix.os }}-Qt-${{ matrix.qt-version }}-${{ artifact_descr }}.deb
path: build/Chatterino-x86_64.deb
# MACOS
@ -290,17 +337,10 @@ jobs:
shell: bash
- name: Upload artifact (MacOS)
if: startsWith(matrix.os, 'macos') && matrix.plugins == false
if: startsWith(matrix.os, 'macos')
uses: actions/upload-artifact@v3
with:
name: chatterino-osx-${{ matrix.qt-version }}.dmg
path: build/chatterino-osx.dmg
- name: Upload artifact (MacOS, plugins)
if: startsWith(matrix.os, 'macos') && matrix.plugins == true
uses: actions/upload-artifact@v3
with:
name: chatterino-osx-${{ matrix.qt-version }}.dmg
name: chatterino-osx-${{ matrix.qt-version }}-${{ artifact_descr }}.dmg
path: build/chatterino-osx.dmg
create-release:
needs: build
@ -320,7 +360,12 @@ jobs:
- uses: actions/download-artifact@v3
with:
name: Chatterino-5.15.2.deb
name: Chatterino-ubuntu-20.04-Qt-5.12.12.deb
path: release-artifacts/
- uses: actions/download-artifact@v3
with:
name: Chatterino-ubuntu-22.04-Qt-5.15.2.deb
path: release-artifacts/
- uses: actions/download-artifact@v3

View file

@ -6,7 +6,7 @@ on:
workflow_dispatch:
env:
TWITCH_PUBSUB_SERVER_IMAGE: ghcr.io/chatterino/twitch-pubsub-server-test:master
TWITCH_PUBSUB_SERVER_IMAGE: ghcr.io/chatterino/twitch-pubsub-server-test:v1.0.6
concurrency:
group: test-${{ github.ref }}

6
.gitmodules vendored
View file

@ -2,9 +2,6 @@
path = lib/libcommuni
url = https://github.com/Chatterino/libcommuni
branch = chatterino-cmake
[submodule "lib/qBreakpad"]
path = lib/qBreakpad
url = https://github.com/jiakuan/qBreakpad.git
[submodule "lib/WinToast"]
path = lib/WinToast
url = https://github.com/mohabouje/WinToast.git
@ -41,3 +38,6 @@
[submodule "lib/lua/src"]
path = lib/lua/src
url = https://github.com/lua/lua
[submodule "lib/crashpad"]
path = lib/crashpad
url = https://github.com/getsentry/crashpad

View file

@ -0,0 +1,20 @@
This patch ensures Qt 5.15 in particular can build with modern compilers
See https://bugreports.qt.io/browse/QTBUG-91909 and https://codereview.qt-project.org/c/qt/qtbase/+/339417
---
diff --git a/src/concurrent/qtconcurrentthreadengine.h b/src/concurrent/qtconcurrentthreadengine.h
index cbd8ad04..4cd5b85 100644
--- a/src/concurrent/qtconcurrentthreadengine.h
+++ b/src/concurrent/qtconcurrentthreadengine.h
@@ -256,8 +256,8 @@
class ThreadEngineStarter<void> : public ThreadEngineStarterBase<void>
{
public:
- ThreadEngineStarter<void>(ThreadEngine<void> *_threadEngine)
- :ThreadEngineStarterBase<void>(_threadEngine) {}
+ ThreadEngineStarter(ThreadEngine<void> *_threadEngine)
+ : ThreadEngineStarterBase<void>(_threadEngine) {}
void startBlocking()
{

View file

@ -3,6 +3,7 @@
## Unversioned
- Major: Added live emote updates for BTTV (#4147)
- Minor: Added option to highlight your own messages in Highlights page under Users tab. (#3833)
- Minor: Change the highlight order to prioritize Message highlights over User highlights. (#4303)
- Minor: Added ability to negate search options by prefixing it with an exclamation mark (e.g. `!badge:mod` to search for messages where the author does not have the moderator badge). (#4207)
- Minor: Search window input will automatically use currently selected text if present. (#4178)
@ -11,6 +12,9 @@
- Minor: Added link to streamlink docs for easier user setup. (#4217)
- Minor: Added setting to turn off rendering of reply context. (#4224)
- Minor: Added setting to select which channels to log. (#4302)
- Minor: Added support for HTTP and Socks5 proxies through environment variables. (#4321)
- Minor: Remove sending part of the multipart emoji workaround (#4361)
- Minor: Added crashpad to capture crashes on Windows locally. See PR for build/crash analysis instructions. (#4351)
- Bugfix: Fixed crash that would occur when performing certain actions after removing all tabs. (#4271)
- Bugfix: Fixed highlight sounds not reloading on change properly. (#4194)
- Bugfix: Fixed CTRL + C not working in reply thread popups. (#4209)
@ -27,6 +31,7 @@
- Bugfix: Fixed the split "Search" menu action not opening the correct search window. (#4305)
- Bugfix: Fixed an issue on Windows when opening links in incognito mode that contained forward slashes in hash (#4307)
- Bugfix: Fixed an issue where beta versions wouldn't update to stable versions correctly. (#4329)
- Bugfix: Avoided crash that could occur when receiving channel point reward information. (#4360)
- Dev: Changed sound backend from Qt to miniaudio. (#4334)
- Dev: Remove protocol from QApplication's Organization Domain (so changed from `https://www.chatterino.com` to `chatterino.com`). (#4256)
- Dev: Ignore `WM_SHOWWINDOW` hide events, causing fewer attempted rescales. (#4198)
@ -44,6 +49,10 @@
- Dev: Added CMake Install Support on Windows. (#4300)
- Dev: Changed conan generator to [`CMakeDeps`](https://docs.conan.io/en/latest/reference/conanfile/tools/cmake/cmakedeps.html) and [`CMakeToolchain`](https://docs.conan.io/en/latest/reference/conanfile/tools/cmake/cmaketoolchain.html). See PR for migration notes. (#4335)
- Dev: Add scripting capabilities with Lua (#4341)
- Dev: Refactored 7TV EventAPI implementation. (#4342)
- Dev: Disabled ImageExpirationPool in tests. (#4363)
- Dev: Don't rely on undocumented registry keys to find the default browser on Windows. (#4362)
- Dev: Use `QEnterEvent` for `QWidget::enterEvent` on Qt 6. (#4365)
## 2.4.0

View file

@ -17,6 +17,7 @@ option(USE_SYSTEM_PAJLADA_SETTINGS "Use system pajlada settings library" OFF)
option(USE_SYSTEM_LIBCOMMUNI "Use system communi library" OFF)
option(USE_SYSTEM_QTKEYCHAIN "Use system QtKeychain library" OFF)
option(BUILD_WITH_QTKEYCHAIN "Build Chatterino with support for your system key chain" ON)
option(BUILD_WITH_CRASHPAD "Build chatterino with crashpad" OFF)
option(USE_PRECOMPILED_HEADERS "Use precompiled headers" ON)
option(BUILD_WITH_QT6 "Use Qt6 instead of default Qt5" OFF)
option(CHATTERINO_GENERATE_COVERAGE "Generate coverage files" OFF)
@ -149,11 +150,16 @@ else()
add_subdirectory("${CMAKE_SOURCE_DIR}/lib/settings" EXCLUDE_FROM_ALL)
endif()
if (CHATTERINO_PLUGINS)
set(LUA_INCLUDE_DIRS "${CMAKE_SOURCE_DIR}/lib/lua/src")
add_subdirectory(lib/lua)
endif()
if (BUILD_WITH_CRASHPAD)
add_subdirectory("${CMAKE_SOURCE_DIR}/lib/crashpad" EXCLUDE_FROM_ALL)
endif()
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

@ -1 +1 @@
Subproject commit a6748f4f51273d86312e3d27ebe5277c9b1ff870
Subproject commit c3dc841af4dbf44669e65b82cb68a575864326bd

1
lib/crashpad Submodule

@ -0,0 +1 @@
Subproject commit 918fd319d679306c8c95ee92376c6fa6ef3407a0

@ -1 +0,0 @@
Subproject commit a4626c12e9ae6f02fc1ca7a4e399bd8307424103

View file

@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View file

@ -22,8 +22,8 @@
#include "providers/chatterino/ChatterinoBadges.hpp"
#include "providers/ffz/FfzBadges.hpp"
#include "providers/irc/Irc2.hpp"
#include "providers/seventv/eventapi/SeventvEventAPIDispatch.hpp"
#include "providers/seventv/eventapi/SeventvEventAPISubscription.hpp"
#include "providers/seventv/eventapi/Dispatch.hpp"
#include "providers/seventv/eventapi/Subscription.hpp"
#include "providers/seventv/SeventvBadges.hpp"
#include "providers/seventv/SeventvEventAPI.hpp"
#include "providers/twitch/ChannelPointReward.hpp"

View file

@ -200,10 +200,14 @@ set(SOURCE_FILES
messages/search/SubtierPredicate.cpp
messages/search/SubtierPredicate.hpp
providers/Crashpad.cpp
providers/Crashpad.hpp
providers/IvrApi.cpp
providers/IvrApi.hpp
providers/LinkResolver.cpp
providers/LinkResolver.hpp
providers/NetworkConfigurationProvider.cpp
providers/NetworkConfigurationProvider.hpp
providers/RecentMessagesApi.cpp
providers/RecentMessagesApi.hpp
@ -259,14 +263,14 @@ set(SOURCE_FILES
providers/seventv/SeventvEventAPI.cpp
providers/seventv/SeventvEventAPI.hpp
providers/seventv/eventapi/SeventvEventAPIClient.cpp
providers/seventv/eventapi/SeventvEventAPIClient.hpp
providers/seventv/eventapi/SeventvEventAPIDispatch.cpp
providers/seventv/eventapi/SeventvEventAPIDispatch.hpp
providers/seventv/eventapi/SeventvEventAPIMessage.cpp
providers/seventv/eventapi/SeventvEventAPIMessage.hpp
providers/seventv/eventapi/SeventvEventAPISubscription.cpp
providers/seventv/eventapi/SeventvEventAPISubscription.hpp
providers/seventv/eventapi/Client.cpp
providers/seventv/eventapi/Client.hpp
providers/seventv/eventapi/Dispatch.cpp
providers/seventv/eventapi/Dispatch.hpp
providers/seventv/eventapi/Message.cpp
providers/seventv/eventapi/Message.hpp
providers/seventv/eventapi/Subscription.cpp
providers/seventv/eventapi/Subscription.hpp
providers/twitch/ChannelPointReward.cpp
providers/twitch/ChannelPointReward.hpp
@ -651,6 +655,22 @@ else()
)
endif()
# Set the output of TARGET to be
# - CMAKE_BIN_DIR/lib for libraries
# - CMAKE_BIN_DIR/bin for BINARIES
# an additional argument specifies the subdirectory.
function(set_target_directory_hierarchy TARGET)
set_target_properties(${TARGET}
PROPERTIES
ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib/${ARGV1}"
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib/${ARGV1}"
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin/${ARGV1}"
RUNTIME_OUTPUT_DIRECTORY_RELEASE "${CMAKE_BINARY_DIR}/bin/${ARGV1}"
RUNTIME_OUTPUT_DIRECTORY_DEBUG "${CMAKE_BINARY_DIR}/bin/${ARGV1}"
RUNTIME_OUTPUT_DIRECTORY_RELWITHDEBINFO "${CMAKE_BINARY_DIR}/bin/${ARGV1}"
)
endfunction()
if (BUILD_APP)
if (APPLE)
add_executable(${EXECUTABLE_PROJECT} ${MACOS_BUNDLE_ICON_FILE} main.cpp)
@ -663,15 +683,7 @@ if (BUILD_APP)
target_link_libraries(${EXECUTABLE_PROJECT} PUBLIC ${LIBRARY_PROJECT})
set_target_properties(${EXECUTABLE_PROJECT}
PROPERTIES
ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib"
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib"
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin"
RUNTIME_OUTPUT_DIRECTORY_RELEASE "${CMAKE_BINARY_DIR}/bin"
RUNTIME_OUTPUT_DIRECTORY_DEBUG "${CMAKE_BINARY_DIR}/bin"
RUNTIME_OUTPUT_DIRECTORY_RELWITHDEBINFO "${CMAKE_BINARY_DIR}/bin"
)
set_target_directory_hierarchy(${EXECUTABLE_PROJECT})
if (WIN32)
if (NOT WINDEPLOYQT_PATH)
@ -851,8 +863,29 @@ if (LIBRT)
)
endif ()
if (BUILD_WITH_CRASHPAD)
target_compile_definitions(${LIBRARY_PROJECT} PUBLIC CHATTERINO_WITH_CRASHPAD)
target_link_libraries(${LIBRARY_PROJECT} PUBLIC crashpad::client)
set_target_directory_hierarchy(crashpad_handler crashpad)
endif()
# Configure compiler warnings
if (MSVC)
# Change flags for RelWithDebInfo
# Default: "/debug /INCREMENTAL"
# Changes:
# - Disable incremental linking to reduce padding
# - Enable all optimizations - by default when /DEBUG is specified,
# these optimizations will be disabled. We need /DEBUG to generate a PDB.
# See https://gitlab.kitware.com/cmake/cmake/-/issues/20812 for more details.
set(CMAKE_EXE_LINKER_FLAGS_RELWITHDEBINFO "/DEBUG /INCREMENTAL:NO /OPT:REF,ICF,LBR")
# Use the function inlining level from 'Release' mode (2).
string(REPLACE "/Ob1" "/Ob2" CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO}")
# Configure warnings
# Someone adds /W3 before we add /W4.
# This makes sure, only /W4 is specified.
string(REPLACE "/W3" "/W4" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}")
@ -881,6 +914,7 @@ if (MSVC)
/wd4100
/wd4267
)
# Disable min/max macros from Windows.h
target_compile_definitions(${LIBRARY_PROJECT} PUBLIC NOMINMAX)
else ()
target_compile_options(${LIBRARY_PROJECT} PUBLIC

View file

@ -162,7 +162,7 @@ namespace {
// true.
void initSignalHandler()
{
#ifdef NDEBUG
#if defined(NDEBUG) && !defined(CHATTERINO_WITH_CRASHPAD)
signalsInitTime = std::chrono::steady_clock::now();
signal(SIGSEGV, handleSignal);

View file

@ -44,6 +44,17 @@ namespace {
return defaultValue;
}
boost::optional<QString> readOptionalStringEnv(const char *envName)
{
auto envString = std::getenv(envName);
if (envString != nullptr)
{
return QString(envString);
}
return boost::none;
}
uint16_t readPortEnv(const char *envName, uint16_t defaultValue)
{
auto envString = std::getenv(envName);
@ -89,6 +100,7 @@ Env::Env()
readStringEnv("CHATTERINO2_TWITCH_SERVER_HOST", "irc.chat.twitch.tv"))
, twitchServerPort(readPortEnv("CHATTERINO2_TWITCH_SERVER_PORT", 443))
, twitchServerSecure(readBoolEnv("CHATTERINO2_TWITCH_SERVER_SECURE", true))
, proxyUrl(readOptionalStringEnv("CHATTERINO2_PROXY_URL"))
{
}

View file

@ -1,5 +1,6 @@
#pragma once
#include <boost/optional.hpp>
#include <QString>
namespace chatterino {
@ -16,6 +17,7 @@ public:
const QString twitchServerHost;
const uint16_t twitchServerPort;
const bool twitchServerSecure;
const boost::optional<QString> proxyUrl;
};
} // namespace chatterino

View file

@ -29,6 +29,7 @@ Q_LOGGING_CATEGORY(chatterinoMain, "chatterino.main", logThreshold);
Q_LOGGING_CATEGORY(chatterinoMessage, "chatterino.message", logThreshold);
Q_LOGGING_CATEGORY(chatterinoNativeMessage, "chatterino.nativemessage",
logThreshold);
Q_LOGGING_CATEGORY(chatterinoNetwork, "chatterino.network", logThreshold);
Q_LOGGING_CATEGORY(chatterinoNotification, "chatterino.notification",
logThreshold);
Q_LOGGING_CATEGORY(chatterinoNuulsuploader, "chatterino.nuulsuploader",

View file

@ -23,6 +23,7 @@ Q_DECLARE_LOGGING_CATEGORY(chatterinoLua);
Q_DECLARE_LOGGING_CATEGORY(chatterinoMain);
Q_DECLARE_LOGGING_CATEGORY(chatterinoMessage);
Q_DECLARE_LOGGING_CATEGORY(chatterinoNativeMessage);
Q_DECLARE_LOGGING_CATEGORY(chatterinoNetwork);
Q_DECLARE_LOGGING_CATEGORY(chatterinoNotification);
Q_DECLARE_LOGGING_CATEGORY(chatterinoNuulsuploader);
Q_DECLARE_LOGGING_CATEGORY(chatterinoPubSub);

View file

@ -100,6 +100,9 @@ QStringList Version::buildTags() const
#ifdef _MSC_FULL_VER
tags.append("MSVC " + QString::number(_MSC_FULL_VER, 10));
#endif
#ifdef CHATTERINO_WITH_CRASHPAD
tags.append("Crashpad");
#endif
return tags;
}

View file

@ -85,12 +85,6 @@ void sendWhisperMessage(const QString &text)
auto app = getApp();
QString toSend = text.simplified();
// This is to make sure that combined emoji go through properly, see
// https://github.com/Chatterino/chatterino2/issues/3384 and
// https://mm2pl.github.io/emoji_rfc.pdf for more details
// Constants used here are defined in TwitchChannel.hpp
toSend.replace(ZERO_WIDTH_JOINER, ESCAPE_TAG);
app->twitch->sendMessage("jtv", toSend);
}

View file

@ -210,6 +210,35 @@ void rebuildUserHighlights(Settings &settings,
{
auto userHighlights = settings.highlightedUsers.readOnly();
if (settings.enableSelfMessageHighlight)
{
bool showInMentions = settings.showSelfMessageHighlightInMentions;
checks.emplace_back(HighlightCheck{
[showInMentions](
const auto &args, const auto &badges, const auto &senderName,
const auto &originalMessage, const auto &flags,
const auto self) -> boost::optional<HighlightResult> {
(void)args; //unused
(void)badges; //unused
(void)senderName; //unused
(void)flags; //unused
(void)originalMessage; //unused
if (!self)
{
return boost::none;
}
// Highlight color is provided by the ColorProvider and will be updated accordingly
auto highlightColor = ColorProvider::instance().color(
ColorType::SelfMessageHighlight);
return HighlightResult{false, false, (QUrl) nullptr,
highlightColor, showInMentions};
}});
}
for (const auto &highlight : *userHighlights)
{
checks.emplace_back(HighlightCheck{
@ -391,6 +420,11 @@ void HighlightController::initialize(Settings &settings, Paths & /*paths*/)
this->rebuildListener_.addSetting(settings.enableSubHighlight);
this->rebuildListener_.addSetting(settings.enableSubHighlightSound);
this->rebuildListener_.addSetting(settings.enableSubHighlightTaskbar);
this->rebuildListener_.addSetting(settings.enableSelfMessageHighlight);
this->rebuildListener_.addSetting(
settings.showSelfMessageHighlightInMentions);
// We do not need to rebuild the listener for the selfMessagesHighlightColor
// The color is dynamically fetched any time the self message highlight is triggered
this->rebuildListener_.addSetting(settings.subHighlightSoundUrl);
this->rebuildListener_.addSetting(settings.enableThreadHighlight);

View file

@ -36,6 +36,10 @@ public:
ThreadMessageRow = 6,
};
enum UserHighlightRowIndexes {
SelfMessageRow = 0,
};
protected:
// turn a vector item into a model row
virtual HighlightPhrase getItemFromRow(

View file

@ -10,6 +10,8 @@ namespace {
} // namespace
QColor HighlightPhrase::FALLBACK_HIGHLIGHT_COLOR = QColor(127, 63, 73, 127);
QColor HighlightPhrase::FALLBACK_SELF_MESSAGE_HIGHLIGHT_COLOR =
QColor(0, 118, 221, 115);
QColor HighlightPhrase::FALLBACK_REDEEMED_HIGHLIGHT_COLOR =
QColor(28, 126, 141, 60);
QColor HighlightPhrase::FALLBACK_FIRST_MESSAGE_HIGHLIGHT_COLOR =

View file

@ -79,6 +79,8 @@ public:
* Qt>=5.13.
*/
static QColor FALLBACK_HIGHLIGHT_COLOR;
// Used for automatic self messages highlighing
static QColor FALLBACK_SELF_MESSAGE_HIGHLIGHT_COLOR;
static QColor FALLBACK_REDEEMED_HIGHLIGHT_COLOR;
static QColor FALLBACK_SUB_COLOR;
static QColor FALLBACK_FIRST_MESSAGE_HIGHLIGHT_COLOR;

View file

@ -3,7 +3,9 @@
#include "Application.hpp"
#include "controllers/highlights/HighlightModel.hpp"
#include "controllers/highlights/HighlightPhrase.hpp"
#include "providers/colors/ColorProvider.hpp"
#include "singletons/Settings.hpp"
#include "singletons/WindowManager.hpp"
#include "util/StandardItemHelper.hpp"
namespace chatterino {
@ -37,6 +39,86 @@ HighlightPhrase UserHighlightModel::getItemFromRow(
highlightColor};
}
void UserHighlightModel::afterInit()
{
// User highlight settings for your own messages
std::vector<QStandardItem *> messagesRow = this->createRow();
setBoolItem(messagesRow[Column::Pattern],
getSettings()->enableSelfMessageHighlight.getValue(), true,
false);
messagesRow[Column::Pattern]->setData("Your messages (automatic)",
Qt::DisplayRole);
setBoolItem(messagesRow[Column::ShowInMentions],
getSettings()->showSelfMessageHighlightInMentions.getValue(),
true, false);
messagesRow[Column::FlashTaskbar]->setFlags({});
messagesRow[Column::PlaySound]->setFlags({});
messagesRow[Column::UseRegex]->setFlags({});
messagesRow[Column::CaseSensitive]->setFlags({});
messagesRow[Column::SoundPath]->setFlags({});
auto selfColor =
ColorProvider::instance().color(ColorType::SelfMessageHighlight);
setColorItem(messagesRow[Column::Color], *selfColor, false);
this->insertCustomRow(
messagesRow, HighlightModel::UserHighlightRowIndexes::SelfMessageRow);
}
void UserHighlightModel::customRowSetData(
const std::vector<QStandardItem *> &row, int column, const QVariant &value,
int role, int rowIndex)
{
switch (column)
{
case Column::Pattern: {
if (role == Qt::CheckStateRole)
{
if (rowIndex ==
HighlightModel::UserHighlightRowIndexes::SelfMessageRow)
{
getSettings()->enableSelfMessageHighlight.setValue(
value.toBool());
}
}
}
break;
case Column::ShowInMentions: {
if (role == Qt::CheckStateRole)
{
if (rowIndex ==
HighlightModel::UserHighlightRowIndexes::SelfMessageRow)
{
getSettings()->showSelfMessageHighlightInMentions.setValue(
value.toBool());
}
}
}
break;
case Column::Color: {
// Custom color
if (role == Qt::DecorationRole)
{
auto colorName = value.value<QColor>().name(QColor::HexArgb);
if (rowIndex ==
HighlightModel::UserHighlightRowIndexes::SelfMessageRow)
{
// Update the setting with the new value
getSettings()->selfMessageHighlightColor.setValue(
colorName);
// Update the color provider with the new color to be used for future
const_cast<ColorProvider &>(ColorProvider::instance())
.updateColor(ColorType::SelfMessageHighlight,
QColor(colorName));
}
}
}
break;
}
getApp()->windows->forceLayoutChannelViews();
}
// row into vector item
void UserHighlightModel::getRowFromItem(const HighlightPhrase &item,
std::vector<QStandardItem *> &row)

View file

@ -22,6 +22,12 @@ protected:
virtual void getRowFromItem(const HighlightPhrase &item,
std::vector<QStandardItem *> &row) override;
virtual void afterInit() override;
virtual void customRowSetData(const std::vector<QStandardItem *> &row,
int column, const QVariant &value, int role,
int rowIndex) override;
};
} // namespace chatterino

View file

@ -1,15 +1,17 @@
#include "BrowserExtension.hpp"
#include "common/Args.hpp"
#include "common/Env.hpp"
#include "common/Modes.hpp"
#include "common/QLogging.hpp"
#include "common/Version.hpp"
#include "providers/Crashpad.hpp"
#include "providers/IvrApi.hpp"
#include "providers/NetworkConfigurationProvider.hpp"
#include "providers/twitch/api/Helix.hpp"
#include "RunGui.hpp"
#include "singletons/Paths.hpp"
#include "singletons/Settings.hpp"
#include "util/AttachToConsole.hpp"
#include "util/IncognitoBrowser.hpp"
#include <QApplication>
#include <QCommandLineParser>
@ -57,6 +59,10 @@ int main(int argc, char **argv)
initArgs(a);
#ifdef CHATTERINO_WITH_CRASHPAD
const auto crashpadHandler = installCrashHandler();
#endif
// run in gui mode or browser extension host mode
if (getArgs().shouldRunBrowserExtensionHost)
{
@ -81,6 +87,8 @@ int main(int argc, char **argv)
attachToConsole();
}
NetworkConfigurationProvider::applyFromEnv(Env::get());
IvrApi::initialize();
Helix::initialize();

View file

@ -272,7 +272,9 @@ namespace detail {
// IMAGE2
Image::~Image()
{
#ifndef DISABLE_IMAGE_EXPIRATION_POOL
ImageExpirationPool::instance().removeImagePtr(this);
#endif
if (this->empty_ && !this->frames_)
{
@ -425,7 +427,9 @@ void Image::load() const
Image *this2 = const_cast<Image *>(this);
this2->shouldLoad_ = false;
this2->actuallyLoad();
#ifndef DISABLE_IMAGE_EXPIRATION_POOL
ImageExpirationPool::instance().addImagePtr(this2->shared_from_this());
#endif
}
}
@ -551,6 +555,8 @@ void Image::expireFrames()
this->shouldLoad_ = true; // Mark as needing load again
}
#ifndef DISABLE_IMAGE_EXPIRATION_POOL
ImageExpirationPool::ImageExpirationPool()
{
QObject::connect(&this->freeTimer_, &QTimer::timeout, [this] {
@ -593,10 +599,10 @@ void ImageExpirationPool::freeOld()
{
std::lock_guard<std::mutex> lock(this->mutex_);
#ifndef NDEBUG
# ifndef NDEBUG
size_t numExpired = 0;
size_t eligible = 0;
#endif
# endif
auto now = std::chrono::steady_clock::now();
for (auto it = this->allImages_.begin(); it != this->allImages_.end();)
@ -617,17 +623,17 @@ void ImageExpirationPool::freeOld()
continue;
}
#ifndef NDEBUG
# ifndef NDEBUG
++eligible;
#endif
# endif
// Check if image has expired and, if so, expire its frame data
auto diff = now - img->lastUsed_;
if (diff > IMAGE_POOL_IMAGE_LIFETIME)
{
#ifndef NDEBUG
# ifndef NDEBUG
++numExpired;
#endif
# endif
img->expireFrames();
// erase without mutex locking issue
it = this->allImages_.erase(it);
@ -637,10 +643,12 @@ void ImageExpirationPool::freeOld()
++it;
}
#ifndef NDEBUG
# ifndef NDEBUG
qCDebug(chatterinoImage) << "freed frame data for" << numExpired << "/"
<< eligible << "eligible images";
#endif
# endif
}
#endif
} // namespace chatterino

View file

@ -19,6 +19,14 @@
#include <memory>
#include <mutex>
#ifdef CHATTERINO_TEST
// When running tests, the ImageExpirationPool destructor can be called before
// all images are deleted, leading to a use-after-free of its mutex. This
// happens despite the lifetime of the ImageExpirationPool being (apparently)
// static. Therefore, just disable it during testing.
# define DISABLE_IMAGE_EXPIRATION_POOL
#endif
namespace chatterino {
namespace detail {
template <typename Image>
@ -105,6 +113,8 @@ private:
// forward-declarable function that calls Image::getEmpty() under the hood.
ImagePtr getEmptyImagePtr();
#ifndef DISABLE_IMAGE_EXPIRATION_POOL
class ImageExpirationPool
{
private:
@ -131,4 +141,6 @@ private:
std::mutex mutex_;
};
#endif
} // namespace chatterino

View file

@ -9,6 +9,7 @@
#include <cinttypes>
#include <memory>
#include <unordered_map>
#include <vector>
namespace chatterino {

View file

@ -0,0 +1,95 @@
#ifdef CHATTERINO_WITH_CRASHPAD
# include "providers/Crashpad.hpp"
# include "common/QLogging.hpp"
# include "singletons/Paths.hpp"
# include <QApplication>
# include <QDir>
# include <QString>
# include <memory>
# include <string>
namespace {
/// The name of the crashpad handler executable.
/// This varies across platforms
# if defined(Q_OS_UNIX)
const QString CRASHPAD_EXECUTABLE_NAME = QStringLiteral("crashpad_handler");
# elif defined(Q_OS_WINDOWS)
const QString CRASHPAD_EXECUTABLE_NAME = QStringLiteral("crashpad_handler.exe");
# else
# error Unsupported platform
# endif
/// Converts a QString into the platform string representation.
# if defined(Q_OS_UNIX)
std::string nativeString(const QString &s)
{
return s.toStdString();
}
# elif defined(Q_OS_WINDOWS)
std::wstring nativeString(const QString &s)
{
return s.toStdWString();
}
# else
# error Unsupported platform
# endif
} // namespace
namespace chatterino {
std::unique_ptr<crashpad::CrashpadClient> installCrashHandler()
{
// Currently, the following directory layout is assumed:
// [applicationDirPath]
// │
// ├─chatterino
// │
// ╰─[crashpad]
// │
// ╰─crashpad_handler
// TODO: The location of the binary might vary across platforms
auto crashpadBinDir = QDir(QApplication::applicationDirPath());
if (!crashpadBinDir.cd("crashpad"))
{
qCDebug(chatterinoApp) << "Cannot find crashpad directory";
return nullptr;
}
if (!crashpadBinDir.exists(CRASHPAD_EXECUTABLE_NAME))
{
qCDebug(chatterinoApp) << "Cannot find crashpad handler executable";
return nullptr;
}
const auto handlerPath = base::FilePath(nativeString(
crashpadBinDir.absoluteFilePath(CRASHPAD_EXECUTABLE_NAME)));
// Argument passed in --database
// > Crash reports are written to this database, and if uploads are enabled,
// uploaded from this database to a crash report collection server.
const auto databaseDir =
base::FilePath(nativeString(getPaths()->crashdumpDirectory));
auto client = std::make_unique<crashpad::CrashpadClient>();
// See https://chromium.googlesource.com/crashpad/crashpad/+/HEAD/handler/crashpad_handler.md
// for documentation on available options.
if (!client->StartHandler(handlerPath, databaseDir, {}, {}, {}, {}, true,
false))
{
qCDebug(chatterinoApp) << "Failed to start crashpad handler";
return nullptr;
}
qCDebug(chatterinoApp) << "Started crashpad handler";
return client;
}
} // namespace chatterino
#endif

View file

@ -0,0 +1,14 @@
#pragma once
#ifdef CHATTERINO_WITH_CRASHPAD
# include <client/crashpad_client.h>
# include <memory>
namespace chatterino {
std::unique_ptr<crashpad::CrashpadClient> installCrashHandler();
} // namespace chatterino
#endif

View file

@ -0,0 +1,76 @@
#include "providers/NetworkConfigurationProvider.hpp"
#include "common/Env.hpp"
#include "common/QLogging.hpp"
#include <QNetworkProxy>
#include <QSslConfiguration>
#include <QUrl>
namespace {
/**
* Creates a QNetworkProxy from a given URL.
*
* Creates an HTTP proxy by default, a Socks5 will be created if the scheme is 'socks5'.
*/
QNetworkProxy createProxyFromUrl(const QUrl &url)
{
QNetworkProxy proxy;
proxy.setHostName(url.host(QUrl::FullyEncoded));
proxy.setUser(url.userName(QUrl::FullyEncoded));
proxy.setPassword(url.password(QUrl::FullyEncoded));
proxy.setPort(url.port(1080));
if (url.scheme().compare(QStringLiteral("socks5"), Qt::CaseInsensitive) ==
0)
{
proxy.setType(QNetworkProxy::Socks5Proxy);
}
else
{
proxy.setType(QNetworkProxy::HttpProxy);
if (!proxy.user().isEmpty() || !proxy.password().isEmpty())
{
// for some reason, Qt doesn't set the Proxy-Authorization header
const auto auth = proxy.user() + ":" + proxy.password();
const auto base64 = auth.toUtf8().toBase64();
proxy.setRawHeader("Proxy-Authorization",
QByteArray("Basic ").append(base64));
}
}
return proxy;
}
/**
* Attempts to apply the proxy specified by `url` as the application proxy.
*/
void applyProxy(const QString &url)
{
auto proxyUrl = QUrl(url);
if (!proxyUrl.isValid() || proxyUrl.isEmpty())
{
qCDebug(chatterinoNetwork)
<< "Invalid or empty proxy url: " << proxyUrl;
return;
}
const auto proxy = createProxyFromUrl(proxyUrl);
QNetworkProxy::setApplicationProxy(proxy);
qCDebug(chatterinoNetwork) << "Set application proxy to" << proxy;
}
} // namespace
namespace chatterino {
void NetworkConfigurationProvider::applyFromEnv(const Env &env)
{
if (env.proxyUrl)
{
applyProxy(env.proxyUrl.get());
}
}
} // namespace chatterino

View file

@ -0,0 +1,59 @@
#pragma once
#include "common/QLogging.hpp"
#include <QNetworkProxy>
#include <websocketpp/error.hpp>
#include <string>
namespace chatterino {
class Env;
/** This class manipulates the global network configuration (e.g. proxies). */
class NetworkConfigurationProvider
{
public:
/** This class should never be instantiated. */
NetworkConfigurationProvider() = delete;
/**
* Applies the configuration requested from the environment variables.
*
* Currently a proxy is applied if configured.
*/
static void applyFromEnv(const Env &env);
static void applyToWebSocket(const auto &connection)
{
const auto applicationProxy = QNetworkProxy::applicationProxy();
if (applicationProxy.type() != QNetworkProxy::HttpProxy)
{
return;
}
std::string url = "http://";
url += applicationProxy.hostName().toStdString();
url += ":";
url += std::to_string(applicationProxy.port());
websocketpp::lib::error_code ec;
connection->set_proxy(url, ec);
if (ec)
{
qCDebug(chatterinoNetwork)
<< "Couldn't set websocket proxy:" << ec.value();
return;
}
connection->set_proxy_basic_auth(
applicationProxy.user().toStdString(),
applicationProxy.password().toStdString(), ec);
if (ec)
{
qCDebug(chatterinoNetwork)
<< "Couldn't set websocket proxy auth:" << ec.value();
}
}
};
} // namespace chatterino

View file

@ -84,6 +84,9 @@ namespace {
for (const auto jsonMessage : jsonMessages)
{
auto content = jsonMessage.toString();
// For explanation of why this exists, see src/providers/twitch/TwitchChannel.hpp,
// where these constants are defined
content.replace(COMBINED_FIXER, ZERO_WIDTH_JOINER);
auto message =

View file

@ -81,6 +81,20 @@ void ColorProvider::initTypeColorMap()
HighlightPhrase::FALLBACK_HIGHLIGHT_COLOR)});
}
customColor = getSettings()->selfMessageHighlightColor;
if (QColor(customColor).isValid())
{
this->typeColorMap_.insert({ColorType::SelfMessageHighlight,
std::make_shared<QColor>(customColor)});
}
else
{
this->typeColorMap_.insert(
{ColorType::SelfMessageHighlight,
std::make_shared<QColor>(
HighlightPhrase::FALLBACK_SELF_MESSAGE_HIGHLIGHT_COLOR)});
}
customColor = getSettings()->subHighlightColor;
if (QColor(customColor).isValid())
{

View file

@ -16,6 +16,8 @@ enum class ColorType {
FirstMessageHighlight,
ElevatedMessageHighlight,
ThreadMessageHighlight,
// Used in automatic highlights of your own messages
SelfMessageHighlight,
};
class ColorProvider

View file

@ -4,6 +4,8 @@
#include <rapidjson/rapidjson.h>
#include <unordered_map>
class QAbstractTableModel;
namespace chatterino {

View file

@ -4,6 +4,7 @@
#include "common/Version.hpp"
#include "providers/liveupdates/BasicPubSubClient.hpp"
#include "providers/liveupdates/BasicPubSubWebsocket.hpp"
#include "providers/NetworkConfigurationProvider.hpp"
#include "providers/twitch/PubSubHelpers.hpp"
#include "util/DebugCount.hpp"
#include "util/ExponentialBackoff.hpp"
@ -336,6 +337,8 @@ private:
return;
}
NetworkConfigurationProvider::applyToWebSocket(con);
this->websocketClient_.connect(con);
}

View file

@ -7,7 +7,7 @@
#include "messages/Image.hpp"
#include "messages/ImageSet.hpp"
#include "messages/MessageBuilder.hpp"
#include "providers/seventv/eventapi/SeventvEventAPIDispatch.hpp"
#include "providers/seventv/eventapi/Dispatch.hpp"
#include "providers/twitch/TwitchChannel.hpp"
#include "singletons/Settings.hpp"
@ -30,6 +30,7 @@
namespace {
using namespace chatterino;
using namespace seventv::eventapi;
// These declarations won't throw an exception.
const QString CHANNEL_HAS_NO_EMOTES("This channel has no 7TV channel emotes.");
@ -224,7 +225,7 @@ EmoteMap parseEmotes(const QJsonArray &emoteSetEmotes, bool isGlobal)
}
EmotePtr createUpdatedEmote(const EmotePtr &oldEmote,
const SeventvEventAPIEmoteUpdateDispatch &dispatch)
const EmoteUpdateDispatch &dispatch)
{
bool toNonAliased = oldEmote->baseName.has_value() &&
dispatch.emoteName == oldEmote->baseName->string;
@ -245,6 +246,8 @@ EmotePtr createUpdatedEmote(const EmotePtr &oldEmote,
namespace chatterino {
using namespace seventv::eventapi;
SeventvEmotes::SeventvEmotes()
: global_(std::make_shared<EmoteMap>())
{
@ -401,7 +404,7 @@ void SeventvEmotes::loadChannelEmotes(
boost::optional<EmotePtr> SeventvEmotes::addEmote(
Atomic<std::shared_ptr<const EmoteMap>> &map,
const SeventvEventAPIEmoteAddDispatch &dispatch)
const EmoteAddDispatch &dispatch)
{
// Check for visibility first, so we don't copy the map.
auto emoteData = dispatch.emoteJson["data"].toObject();
@ -429,7 +432,7 @@ boost::optional<EmotePtr> SeventvEmotes::addEmote(
boost::optional<EmotePtr> SeventvEmotes::updateEmote(
Atomic<std::shared_ptr<const EmoteMap>> &map,
const SeventvEventAPIEmoteUpdateDispatch &dispatch)
const EmoteUpdateDispatch &dispatch)
{
auto oldMap = map.get();
auto oldEmote = oldMap->findEmote(dispatch.emoteName, dispatch.emoteID);
@ -451,7 +454,7 @@ boost::optional<EmotePtr> SeventvEmotes::updateEmote(
boost::optional<EmotePtr> SeventvEmotes::removeEmote(
Atomic<std::shared_ptr<const EmoteMap>> &map,
const SeventvEventAPIEmoteRemoveDispatch &dispatch)
const EmoteRemoveDispatch &dispatch)
{
// This copies the map.
EmoteMap updatedMap = *map.get();

View file

@ -10,9 +10,12 @@
namespace chatterino {
class Channel;
struct SeventvEventAPIEmoteAddDispatch;
struct SeventvEventAPIEmoteUpdateDispatch;
struct SeventvEventAPIEmoteRemoveDispatch;
namespace seventv::eventapi {
struct EmoteAddDispatch;
struct EmoteUpdateDispatch;
struct EmoteRemoveDispatch;
} // namespace seventv::eventapi
// https://github.com/SevenTV/API/blob/a84e884b5590dbb5d91a5c6b3548afabb228f385/data/model/emote-set.model.go#L29-L36
enum class SeventvActiveEmoteFlag : int64_t {
@ -86,7 +89,7 @@ public:
*/
static boost::optional<EmotePtr> addEmote(
Atomic<std::shared_ptr<const EmoteMap>> &map,
const SeventvEventAPIEmoteAddDispatch &dispatch);
const seventv::eventapi::EmoteAddDispatch &dispatch);
/**
* Updates an emote in this `map`.
@ -97,7 +100,7 @@ public:
*/
static boost::optional<EmotePtr> updateEmote(
Atomic<std::shared_ptr<const EmoteMap>> &map,
const SeventvEventAPIEmoteUpdateDispatch &dispatch);
const seventv::eventapi::EmoteUpdateDispatch &dispatch);
/**
* Removes an emote from this `map`.
@ -108,7 +111,7 @@ public:
*/
static boost::optional<EmotePtr> removeEmote(
Atomic<std::shared_ptr<const EmoteMap>> &map,
const SeventvEventAPIEmoteRemoveDispatch &dispatch);
const seventv::eventapi::EmoteRemoveDispatch &dispatch);
/** Fetches an emote-set by its id */
static void getEmoteSet(

View file

@ -1,8 +1,8 @@
#include "providers/seventv/SeventvEventAPI.hpp"
#include "providers/seventv/eventapi/SeventvEventAPIClient.hpp"
#include "providers/seventv/eventapi/SeventvEventAPIDispatch.hpp"
#include "providers/seventv/eventapi/SeventvEventAPIMessage.hpp"
#include "providers/seventv/eventapi/Client.hpp"
#include "providers/seventv/eventapi/Dispatch.hpp"
#include "providers/seventv/eventapi/Message.hpp"
#include <QJsonArray>
@ -10,6 +10,8 @@
namespace chatterino {
using namespace seventv::eventapi;
SeventvEventAPI::SeventvEventAPI(
QString host, std::chrono::milliseconds defaultHeartbeatInterval)
: BasicPubSubManager(std::move(host))
@ -22,13 +24,14 @@ void SeventvEventAPI::subscribeUser(const QString &userID,
{
if (!userID.isEmpty() && this->subscribedUsers_.insert(userID).second)
{
this->subscribe({userID, SeventvEventAPISubscriptionType::UpdateUser});
this->subscribe(
{ObjectIDCondition{userID}, SubscriptionType::UpdateUser});
}
if (!emoteSetID.isEmpty() &&
this->subscribedEmoteSets_.insert(emoteSetID).second)
{
this->subscribe(
{emoteSetID, SeventvEventAPISubscriptionType::UpdateEmoteSet});
{ObjectIDCondition{emoteSetID}, SubscriptionType::UpdateEmoteSet});
}
}
@ -37,7 +40,7 @@ void SeventvEventAPI::unsubscribeEmoteSet(const QString &id)
if (this->subscribedEmoteSets_.erase(id) > 0)
{
this->unsubscribe(
{id, SeventvEventAPISubscriptionType::UpdateEmoteSet});
{ObjectIDCondition{id}, SubscriptionType::UpdateEmoteSet});
}
}
@ -45,27 +48,27 @@ void SeventvEventAPI::unsubscribeUser(const QString &id)
{
if (this->subscribedUsers_.erase(id) > 0)
{
this->unsubscribe({id, SeventvEventAPISubscriptionType::UpdateUser});
this->unsubscribe(
{ObjectIDCondition{id}, SubscriptionType::UpdateUser});
}
}
std::shared_ptr<BasicPubSubClient<SeventvEventAPISubscription>>
SeventvEventAPI::createClient(liveupdates::WebsocketClient &client,
websocketpp::connection_hdl hdl)
std::shared_ptr<BasicPubSubClient<Subscription>> SeventvEventAPI::createClient(
liveupdates::WebsocketClient &client, websocketpp::connection_hdl hdl)
{
auto shared = std::make_shared<SeventvEventAPIClient>(
client, hdl, this->heartbeatInterval_);
return std::static_pointer_cast<
BasicPubSubClient<SeventvEventAPISubscription>>(std::move(shared));
auto shared =
std::make_shared<Client>(client, hdl, this->heartbeatInterval_);
return std::static_pointer_cast<BasicPubSubClient<Subscription>>(
std::move(shared));
}
void SeventvEventAPI::onMessage(
websocketpp::connection_hdl hdl,
BasicPubSubManager<SeventvEventAPISubscription>::WebsocketMessagePtr msg)
BasicPubSubManager<Subscription>::WebsocketMessagePtr msg)
{
const auto &payload = QString::fromStdString(msg->get_payload());
auto pMessage = parseSeventvEventAPIBaseMessage(payload);
auto pMessage = parseBaseMessage(payload);
if (!pMessage)
{
@ -76,11 +79,10 @@ void SeventvEventAPI::onMessage(
auto message = *pMessage;
switch (message.op)
{
case SeventvEventAPIOpcode::Hello: {
case Opcode::Hello: {
if (auto client = this->findClient(hdl))
{
if (auto *stvClient =
dynamic_cast<SeventvEventAPIClient *>(client.get()))
if (auto *stvClient = dynamic_cast<Client *>(client.get()))
{
stvClient->setHeartbeatInterval(
message.data["heartbeat_interval"].toInt());
@ -88,19 +90,18 @@ void SeventvEventAPI::onMessage(
}
}
break;
case SeventvEventAPIOpcode::Heartbeat: {
case Opcode::Heartbeat: {
if (auto client = this->findClient(hdl))
{
if (auto *stvClient =
dynamic_cast<SeventvEventAPIClient *>(client.get()))
if (auto *stvClient = dynamic_cast<Client *>(client.get()))
{
stvClient->handleHeartbeat();
}
}
}
break;
case SeventvEventAPIOpcode::Dispatch: {
auto dispatch = message.toInner<SeventvEventAPIDispatch>();
case Opcode::Dispatch: {
auto dispatch = message.toInner<Dispatch>();
if (!dispatch)
{
qCDebug(chatterinoSeventvEventAPI)
@ -110,133 +111,37 @@ void SeventvEventAPI::onMessage(
this->handleDispatch(*dispatch);
}
break;
case SeventvEventAPIOpcode::Reconnect: {
case Opcode::Reconnect: {
if (auto client = this->findClient(hdl))
{
if (auto *stvClient =
dynamic_cast<SeventvEventAPIClient *>(client.get()))
if (auto *stvClient = dynamic_cast<Client *>(client.get()))
{
stvClient->close("Reconnecting");
}
}
}
break;
case Opcode::Ack: {
// unhandled
}
break;
default: {
qCDebug(chatterinoSeventvEventAPI) << "Unhandled op: " << payload;
qCDebug(chatterinoSeventvEventAPI) << "Unhandled op:" << payload;
}
break;
}
}
void SeventvEventAPI::handleDispatch(const SeventvEventAPIDispatch &dispatch)
void SeventvEventAPI::handleDispatch(const Dispatch &dispatch)
{
switch (dispatch.type)
{
case SeventvEventAPISubscriptionType::UpdateEmoteSet: {
// dispatchBody: {
// pushed: Array<{ key, value }>,
// pulled: Array<{ key, old_value }>,
// updated: Array<{ key, value, old_value }>,
// }
for (const auto pushedRef : dispatch.body["pushed"].toArray())
{
auto pushed = pushedRef.toObject();
if (pushed["key"].toString() != "emotes")
{
continue;
}
SeventvEventAPIEmoteAddDispatch added(
dispatch, pushed["value"].toObject());
if (added.validate())
{
this->signals_.emoteAdded.invoke(added);
}
else
{
qCDebug(chatterinoSeventvEventAPI)
<< "Invalid dispatch" << dispatch.body;
}
}
for (const auto updatedRef : dispatch.body["updated"].toArray())
{
auto updated = updatedRef.toObject();
if (updated["key"].toString() != "emotes")
{
continue;
}
SeventvEventAPIEmoteUpdateDispatch update(
dispatch, updated["old_value"].toObject(),
updated["value"].toObject());
if (update.validate())
{
this->signals_.emoteUpdated.invoke(update);
}
else
{
qCDebug(chatterinoSeventvEventAPI)
<< "Invalid dispatch" << dispatch.body;
}
}
for (const auto pulledRef : dispatch.body["pulled"].toArray())
{
auto pulled = pulledRef.toObject();
if (pulled["key"].toString() != "emotes")
{
continue;
}
SeventvEventAPIEmoteRemoveDispatch removed(
dispatch, pulled["old_value"].toObject());
if (removed.validate())
{
this->signals_.emoteRemoved.invoke(removed);
}
else
{
qCDebug(chatterinoSeventvEventAPI)
<< "Invalid dispatch" << dispatch.body;
}
}
case SubscriptionType::UpdateEmoteSet: {
this->onEmoteSetUpdate(dispatch);
}
break;
case SeventvEventAPISubscriptionType::UpdateUser: {
// dispatchBody: {
// updated: Array<{ key, value: Array<{key, value}> }>
// }
for (const auto updatedRef : dispatch.body["updated"].toArray())
{
auto updated = updatedRef.toObject();
if (updated["key"].toString() != "connections")
{
continue;
}
for (const auto valueRef : updated["value"].toArray())
{
auto value = valueRef.toObject();
if (value["key"].toString() != "emote_set")
{
continue;
}
SeventvEventAPIUserConnectionUpdateDispatch update(
dispatch, value, (size_t)updated["index"].toInt());
if (update.validate())
{
this->signals_.userUpdated.invoke(update);
}
else
{
qCDebug(chatterinoSeventvEventAPI)
<< "Invalid dispatch" << dispatch.body;
}
}
}
case SubscriptionType::UpdateUser: {
this->onUserUpdate(dispatch);
}
break;
default: {
@ -248,4 +153,112 @@ void SeventvEventAPI::handleDispatch(const SeventvEventAPIDispatch &dispatch)
}
}
void SeventvEventAPI::onEmoteSetUpdate(const Dispatch &dispatch)
{
// dispatchBody: {
// pushed: Array<{ key, value }>,
// pulled: Array<{ key, old_value }>,
// updated: Array<{ key, value, old_value }>,
// }
for (const auto pushedRef : dispatch.body["pushed"].toArray())
{
auto pushed = pushedRef.toObject();
if (pushed["key"].toString() != "emotes")
{
continue;
}
const EmoteAddDispatch added(dispatch, pushed["value"].toObject());
if (added.validate())
{
this->signals_.emoteAdded.invoke(added);
}
else
{
qCDebug(chatterinoSeventvEventAPI)
<< "Invalid dispatch" << dispatch.body;
}
}
for (const auto updatedRef : dispatch.body["updated"].toArray())
{
auto updated = updatedRef.toObject();
if (updated["key"].toString() != "emotes")
{
continue;
}
const EmoteUpdateDispatch update(dispatch,
updated["old_value"].toObject(),
updated["value"].toObject());
if (update.validate())
{
this->signals_.emoteUpdated.invoke(update);
}
else
{
qCDebug(chatterinoSeventvEventAPI)
<< "Invalid dispatch" << dispatch.body;
}
}
for (const auto pulledRef : dispatch.body["pulled"].toArray())
{
auto pulled = pulledRef.toObject();
if (pulled["key"].toString() != "emotes")
{
continue;
}
const EmoteRemoveDispatch removed(dispatch,
pulled["old_value"].toObject());
if (removed.validate())
{
this->signals_.emoteRemoved.invoke(removed);
}
else
{
qCDebug(chatterinoSeventvEventAPI)
<< "Invalid dispatch" << dispatch.body;
}
}
}
void SeventvEventAPI::onUserUpdate(const Dispatch &dispatch)
{
// dispatchBody: {
// updated: Array<{ key, value: Array<{key, value}> }>
// }
for (const auto updatedRef : dispatch.body["updated"].toArray())
{
auto updated = updatedRef.toObject();
if (updated["key"].toString() != "connections")
{
continue;
}
for (const auto valueRef : updated["value"].toArray())
{
auto value = valueRef.toObject();
if (value["key"].toString() != "emote_set")
{
continue;
}
const UserConnectionUpdateDispatch update(
dispatch, value, (size_t)updated["index"].toInt());
if (update.validate())
{
this->signals_.userUpdated.invoke(update);
}
else
{
qCDebug(chatterinoSeventvEventAPI)
<< "Invalid dispatch" << dispatch.body;
}
}
}
}
} // namespace chatterino

View file

@ -8,14 +8,17 @@
namespace chatterino {
struct SeventvEventAPISubscription;
struct SeventvEventAPIDispatch;
struct SeventvEventAPIEmoteAddDispatch;
struct SeventvEventAPIEmoteUpdateDispatch;
struct SeventvEventAPIEmoteRemoveDispatch;
struct SeventvEventAPIUserConnectionUpdateDispatch;
namespace seventv::eventapi {
struct Subscription;
struct Dispatch;
struct EmoteAddDispatch;
struct EmoteUpdateDispatch;
struct EmoteRemoveDispatch;
struct UserConnectionUpdateDispatch;
} // namespace seventv::eventapi
class SeventvEventAPI : public BasicPubSubManager<SeventvEventAPISubscription>
class SeventvEventAPI
: public BasicPubSubManager<seventv::eventapi::Subscription>
{
template <typename T>
using Signal =
@ -27,10 +30,10 @@ public:
std::chrono::milliseconds(25000));
struct {
Signal<SeventvEventAPIEmoteAddDispatch> emoteAdded;
Signal<SeventvEventAPIEmoteUpdateDispatch> emoteUpdated;
Signal<SeventvEventAPIEmoteRemoveDispatch> emoteRemoved;
Signal<SeventvEventAPIUserConnectionUpdateDispatch> userUpdated;
Signal<seventv::eventapi::EmoteAddDispatch> emoteAdded;
Signal<seventv::eventapi::EmoteUpdateDispatch> emoteUpdated;
Signal<seventv::eventapi::EmoteRemoveDispatch> emoteRemoved;
Signal<seventv::eventapi::UserConnectionUpdateDispatch> userUpdated;
} signals_; // NOLINT(readability-identifier-naming)
/**
@ -48,18 +51,23 @@ public:
void unsubscribeEmoteSet(const QString &id);
protected:
std::shared_ptr<BasicPubSubClient<SeventvEventAPISubscription>>
std::shared_ptr<BasicPubSubClient<seventv::eventapi::Subscription>>
createClient(liveupdates::WebsocketClient &client,
websocketpp::connection_hdl hdl) override;
void onMessage(
websocketpp::connection_hdl hdl,
BasicPubSubManager<SeventvEventAPISubscription>::WebsocketMessagePtr
BasicPubSubManager<seventv::eventapi::Subscription>::WebsocketMessagePtr
msg) override;
private:
void handleDispatch(const SeventvEventAPIDispatch &dispatch);
void handleDispatch(const seventv::eventapi::Dispatch &dispatch);
void onEmoteSetUpdate(const seventv::eventapi::Dispatch &dispatch);
void onUserUpdate(const seventv::eventapi::Dispatch &dispatch);
/** emote-set ids */
std::unordered_set<QString> subscribedEmoteSets_;
/** user ids */
std::unordered_set<QString> subscribedUsers_;
std::chrono::milliseconds heartbeatInterval_;
};

View file

@ -1,44 +1,42 @@
#include "providers/seventv/eventapi/SeventvEventAPIClient.hpp"
#include "providers/seventv/eventapi/Client.hpp"
#include "providers/seventv/eventapi/SeventvEventAPISubscription.hpp"
#include "providers/seventv/eventapi/Subscription.hpp"
#include "providers/twitch/PubSubHelpers.hpp"
#include <utility>
namespace chatterino {
namespace chatterino::seventv::eventapi {
SeventvEventAPIClient::SeventvEventAPIClient(
liveupdates::WebsocketClient &websocketClient,
liveupdates::WebsocketHandle handle,
std::chrono::milliseconds heartbeatInterval)
: BasicPubSubClient<SeventvEventAPISubscription>(websocketClient,
std::move(handle))
Client::Client(liveupdates::WebsocketClient &websocketClient,
liveupdates::WebsocketHandle handle,
std::chrono::milliseconds heartbeatInterval)
: BasicPubSubClient<Subscription>(websocketClient, std::move(handle))
, lastHeartbeat_(std::chrono::steady_clock::now())
, heartbeatInterval_(heartbeatInterval)
{
}
void SeventvEventAPIClient::onConnectionEstablished()
void Client::onConnectionEstablished()
{
this->lastHeartbeat_.store(std::chrono::steady_clock::now(),
std::memory_order_release);
this->checkHeartbeat();
}
void SeventvEventAPIClient::setHeartbeatInterval(int intervalMs)
void Client::setHeartbeatInterval(int intervalMs)
{
qCDebug(chatterinoSeventvEventAPI)
<< "Setting expected heartbeat interval to" << intervalMs << "ms";
this->heartbeatInterval_ = std::chrono::milliseconds(intervalMs);
}
void SeventvEventAPIClient::handleHeartbeat()
void Client::handleHeartbeat()
{
this->lastHeartbeat_.store(std::chrono::steady_clock::now(),
std::memory_order_release);
}
void SeventvEventAPIClient::checkHeartbeat()
void Client::checkHeartbeat()
{
// Following the heartbeat docs, a connection is dead
// after three missed heartbeats.
@ -54,8 +52,7 @@ void SeventvEventAPIClient::checkHeartbeat()
return;
}
auto self = std::dynamic_pointer_cast<SeventvEventAPIClient>(
this->shared_from_this());
auto self = std::dynamic_pointer_cast<Client>(this->shared_from_this());
runAfter(this->websocketClient_.get_io_service(), this->heartbeatInterval_,
[self](auto) {
@ -67,4 +64,4 @@ void SeventvEventAPIClient::checkHeartbeat()
});
}
} // namespace chatterino
} // namespace chatterino::seventv::eventapi

View file

@ -2,18 +2,22 @@
#include "providers/liveupdates/BasicPubSubClient.hpp"
// this needs to be included for the specialization
// of std::hash for SeventvEventAPISubscription
#include "providers/seventv/eventapi/SeventvEventAPISubscription.hpp"
// of std::hash for Subscription
#include "providers/seventv/eventapi/Subscription.hpp"
namespace chatterino {
class SeventvEventAPI;
class SeventvEventAPIClient
: public BasicPubSubClient<SeventvEventAPISubscription>
} // namespace chatterino
namespace chatterino::seventv::eventapi {
class Client : public BasicPubSubClient<Subscription>
{
public:
SeventvEventAPIClient(liveupdates::WebsocketClient &websocketClient,
liveupdates::WebsocketHandle handle,
std::chrono::milliseconds heartbeatInterval);
Client(liveupdates::WebsocketClient &websocketClient,
liveupdates::WebsocketHandle handle,
std::chrono::milliseconds heartbeatInterval);
void setHeartbeatInterval(int intervalMs);
void handleHeartbeat();
@ -29,7 +33,7 @@ private:
// This will be set once on the welcome message.
std::chrono::milliseconds heartbeatInterval_;
friend class SeventvEventAPI;
friend SeventvEventAPI;
};
} // namespace chatterino
} // namespace chatterino::seventv::eventapi

View file

@ -1,21 +1,20 @@
#include "providers/seventv/eventapi/SeventvEventAPIDispatch.hpp"
#include "providers/seventv/eventapi/Dispatch.hpp"
#include <utility>
namespace chatterino {
namespace chatterino::seventv::eventapi {
SeventvEventAPIDispatch::SeventvEventAPIDispatch(QJsonObject obj)
: type(magic_enum::enum_cast<SeventvEventAPISubscriptionType>(
Dispatch::Dispatch(QJsonObject obj)
: type(magic_enum::enum_cast<SubscriptionType>(
obj["type"].toString().toStdString())
.value_or(SeventvEventAPISubscriptionType::INVALID))
.value_or(SubscriptionType::INVALID))
, body(obj["body"].toObject())
, id(this->body["id"].toString())
, actorName(this->body["actor"].toObject()["display_name"].toString())
{
}
SeventvEventAPIEmoteAddDispatch::SeventvEventAPIEmoteAddDispatch(
const SeventvEventAPIDispatch &dispatch, QJsonObject emote)
EmoteAddDispatch::EmoteAddDispatch(const Dispatch &dispatch, QJsonObject emote)
: emoteSetID(dispatch.id)
, actorName(dispatch.actorName)
, emoteJson(std::move(emote))
@ -23,7 +22,7 @@ SeventvEventAPIEmoteAddDispatch::SeventvEventAPIEmoteAddDispatch(
{
}
bool SeventvEventAPIEmoteAddDispatch::validate() const
bool EmoteAddDispatch::validate() const
{
bool validValues =
!this->emoteSetID.isEmpty() && !this->emoteJson.isEmpty();
@ -43,8 +42,8 @@ bool SeventvEventAPIEmoteAddDispatch::validate() const
emoteData.contains("owner");
}
SeventvEventAPIEmoteRemoveDispatch::SeventvEventAPIEmoteRemoveDispatch(
const SeventvEventAPIDispatch &dispatch, QJsonObject emote)
EmoteRemoveDispatch::EmoteRemoveDispatch(const Dispatch &dispatch,
QJsonObject emote)
: emoteSetID(dispatch.id)
, actorName(dispatch.actorName)
, emoteName(emote["name"].toString())
@ -52,15 +51,15 @@ SeventvEventAPIEmoteRemoveDispatch::SeventvEventAPIEmoteRemoveDispatch(
{
}
bool SeventvEventAPIEmoteRemoveDispatch::validate() const
bool EmoteRemoveDispatch::validate() const
{
return !this->emoteSetID.isEmpty() && !this->emoteName.isEmpty() &&
!this->emoteID.isEmpty();
}
SeventvEventAPIEmoteUpdateDispatch::SeventvEventAPIEmoteUpdateDispatch(
const SeventvEventAPIDispatch &dispatch, QJsonObject oldValue,
QJsonObject value)
EmoteUpdateDispatch::EmoteUpdateDispatch(const Dispatch &dispatch,
QJsonObject oldValue,
QJsonObject value)
: emoteSetID(dispatch.id)
, actorName(dispatch.actorName)
, emoteID(value["id"].toString())
@ -69,17 +68,15 @@ SeventvEventAPIEmoteUpdateDispatch::SeventvEventAPIEmoteUpdateDispatch(
{
}
bool SeventvEventAPIEmoteUpdateDispatch::validate() const
bool EmoteUpdateDispatch::validate() const
{
return !this->emoteSetID.isEmpty() && !this->emoteID.isEmpty() &&
!this->oldEmoteName.isEmpty() && !this->emoteName.isEmpty() &&
this->oldEmoteName != this->emoteName;
}
SeventvEventAPIUserConnectionUpdateDispatch::
SeventvEventAPIUserConnectionUpdateDispatch(
const SeventvEventAPIDispatch &dispatch, const QJsonObject &update,
size_t connectionIndex)
UserConnectionUpdateDispatch::UserConnectionUpdateDispatch(
const Dispatch &dispatch, const QJsonObject &update, size_t connectionIndex)
: userID(dispatch.id)
, actorName(dispatch.actorName)
, oldEmoteSetID(update["old_value"].toObject()["id"].toString())
@ -88,10 +85,10 @@ SeventvEventAPIUserConnectionUpdateDispatch::
{
}
bool SeventvEventAPIUserConnectionUpdateDispatch::validate() const
bool UserConnectionUpdateDispatch::validate() const
{
return !this->userID.isEmpty() && !this->oldEmoteSetID.isEmpty() &&
!this->emoteSetID.isEmpty();
}
} // namespace chatterino
} // namespace chatterino::seventv::eventapi

View file

@ -0,0 +1,70 @@
#pragma once
#include "providers/seventv/eventapi/Subscription.hpp"
#include <QJsonObject>
#include <QString>
namespace chatterino::seventv::eventapi {
// https://github.com/SevenTV/EventAPI/tree/ca4ff15cc42b89560fa661a76c5849047763d334#message-payload
struct Dispatch {
SubscriptionType type;
QJsonObject body;
QString id;
// it's okay for this to be empty
QString actorName;
Dispatch(QJsonObject obj);
};
struct EmoteAddDispatch {
QString emoteSetID;
QString actorName;
QJsonObject emoteJson;
QString emoteID;
EmoteAddDispatch(const Dispatch &dispatch, QJsonObject emote);
bool validate() const;
};
struct EmoteRemoveDispatch {
QString emoteSetID;
QString actorName;
QString emoteName;
QString emoteID;
EmoteRemoveDispatch(const Dispatch &dispatch, QJsonObject emote);
bool validate() const;
};
struct EmoteUpdateDispatch {
QString emoteSetID;
QString actorName;
QString emoteID;
QString oldEmoteName;
QString emoteName;
EmoteUpdateDispatch(const Dispatch &dispatch, QJsonObject oldValue,
QJsonObject value);
bool validate() const;
};
struct UserConnectionUpdateDispatch {
QString userID;
QString actorName;
QString oldEmoteSetID;
QString emoteSetID;
size_t connectionIndex;
UserConnectionUpdateDispatch(const Dispatch &dispatch,
const QJsonObject &update,
size_t connectionIndex);
bool validate() const;
};
} // namespace chatterino::seventv::eventapi

View file

@ -0,0 +1,11 @@
#include "providers/seventv/eventapi/Message.hpp"
namespace chatterino::seventv::eventapi {
Message::Message(QJsonObject _json)
: data(_json["d"].toObject())
, op(Opcode(_json["op"].toInt()))
{
}
} // namespace chatterino::seventv::eventapi

View file

@ -1,6 +1,6 @@
#pragma once
#include "providers/seventv/eventapi/SeventvEventAPISubscription.hpp"
#include "providers/seventv/eventapi/Subscription.hpp"
#include <boost/optional.hpp>
#include <magic_enum.hpp>
@ -8,27 +8,26 @@
#include <QJsonObject>
#include <QString>
namespace chatterino {
namespace chatterino::seventv::eventapi {
struct SeventvEventAPIMessage {
struct Message {
QJsonObject data;
SeventvEventAPIOpcode op;
Opcode op;
SeventvEventAPIMessage(QJsonObject _json);
Message(QJsonObject _json);
template <class InnerClass>
boost::optional<InnerClass> toInner();
};
template <class InnerClass>
boost::optional<InnerClass> SeventvEventAPIMessage::toInner()
boost::optional<InnerClass> Message::toInner()
{
return InnerClass{this->data};
}
static boost::optional<SeventvEventAPIMessage> parseSeventvEventAPIBaseMessage(
const QString &blob)
static boost::optional<Message> parseBaseMessage(const QString &blob)
{
QJsonDocument jsonDoc(QJsonDocument::fromJson(blob.toUtf8()));
@ -37,7 +36,7 @@ static boost::optional<SeventvEventAPIMessage> parseSeventvEventAPIBaseMessage(
return boost::none;
}
return SeventvEventAPIMessage(jsonDoc.object());
return Message(jsonDoc.object());
}
} // namespace chatterino
} // namespace chatterino::seventv::eventapi

View file

@ -1,72 +0,0 @@
#pragma once
#include "providers/seventv/eventapi/SeventvEventAPISubscription.hpp"
#include <QJsonObject>
#include <QString>
namespace chatterino {
// https://github.com/SevenTV/EventAPI/tree/ca4ff15cc42b89560fa661a76c5849047763d334#message-payload
struct SeventvEventAPIDispatch {
SeventvEventAPISubscriptionType type;
QJsonObject body;
QString id;
// it's okay for this to be empty
QString actorName;
SeventvEventAPIDispatch(QJsonObject obj);
};
struct SeventvEventAPIEmoteAddDispatch {
QString emoteSetID;
QString actorName;
QJsonObject emoteJson;
QString emoteID;
SeventvEventAPIEmoteAddDispatch(const SeventvEventAPIDispatch &dispatch,
QJsonObject emote);
bool validate() const;
};
struct SeventvEventAPIEmoteRemoveDispatch {
QString emoteSetID;
QString actorName;
QString emoteName;
QString emoteID;
SeventvEventAPIEmoteRemoveDispatch(const SeventvEventAPIDispatch &dispatch,
QJsonObject emote);
bool validate() const;
};
struct SeventvEventAPIEmoteUpdateDispatch {
QString emoteSetID;
QString actorName;
QString emoteID;
QString oldEmoteName;
QString emoteName;
SeventvEventAPIEmoteUpdateDispatch(const SeventvEventAPIDispatch &dispatch,
QJsonObject oldValue, QJsonObject value);
bool validate() const;
};
struct SeventvEventAPIUserConnectionUpdateDispatch {
QString userID;
QString actorName;
QString oldEmoteSetID;
QString emoteSetID;
size_t connectionIndex;
SeventvEventAPIUserConnectionUpdateDispatch(
const SeventvEventAPIDispatch &dispatch, const QJsonObject &update,
size_t connectionIndex);
bool validate() const;
};
} // namespace chatterino

View file

@ -1,11 +0,0 @@
#include "providers/seventv/eventapi/SeventvEventAPIMessage.hpp"
namespace chatterino {
SeventvEventAPIMessage::SeventvEventAPIMessage(QJsonObject _json)
: data(_json["d"].toObject())
, op(SeventvEventAPIOpcode(_json["op"].toInt()))
{
}
} // namespace chatterino

View file

@ -1,80 +0,0 @@
#include "providers/seventv/eventapi/SeventvEventAPISubscription.hpp"
#include <QDebug>
#include <QJsonDocument>
#include <QJsonObject>
#include <tuple>
namespace {
using namespace chatterino;
const char *typeToString(SeventvEventAPISubscriptionType type)
{
switch (type)
{
case SeventvEventAPISubscriptionType::UpdateEmoteSet:
return "emote_set.update";
case SeventvEventAPISubscriptionType::UpdateUser:
return "user.update";
default:
return "";
}
}
QJsonObject createDataJson(const char *typeName, const QString &condition)
{
QJsonObject data;
data["type"] = typeName;
{
QJsonObject conditionObj;
conditionObj["object_id"] = condition;
data["condition"] = conditionObj;
}
return data;
}
} // namespace
namespace chatterino {
bool SeventvEventAPISubscription::operator==(
const SeventvEventAPISubscription &rhs) const
{
return std::tie(this->condition, this->type) ==
std::tie(rhs.condition, rhs.type);
}
bool SeventvEventAPISubscription::operator!=(
const SeventvEventAPISubscription &rhs) const
{
return !(rhs == *this);
}
QByteArray SeventvEventAPISubscription::encodeSubscribe() const
{
const auto *typeName = typeToString(this->type);
QJsonObject root;
root["op"] = (int)SeventvEventAPIOpcode::Subscribe;
root["d"] = createDataJson(typeName, this->condition);
return QJsonDocument(root).toJson();
}
QByteArray SeventvEventAPISubscription::encodeUnsubscribe() const
{
const auto *typeName = typeToString(this->type);
QJsonObject root;
root["op"] = (int)SeventvEventAPIOpcode::Unsubscribe;
root["d"] = createDataJson(typeName, this->condition);
return QJsonDocument(root).toJson();
}
QDebug &operator<<(QDebug &dbg, const SeventvEventAPISubscription &subscription)
{
dbg << "SeventvEventAPISubscription{ condition:" << subscription.condition
<< "type:" << (int)subscription.type << '}';
return dbg;
}
} // namespace chatterino

View file

@ -1,76 +0,0 @@
#pragma once
#include <magic_enum.hpp>
#include <QByteArray>
#include <QHash>
#include <QString>
namespace chatterino {
// https://github.com/SevenTV/EventAPI/tree/ca4ff15cc42b89560fa661a76c5849047763d334#subscription-types
enum class SeventvEventAPISubscriptionType {
UpdateEmoteSet,
UpdateUser,
INVALID,
};
// https://github.com/SevenTV/EventAPI/tree/ca4ff15cc42b89560fa661a76c5849047763d334#opcodes
enum class SeventvEventAPIOpcode {
Dispatch = 0,
Hello = 1,
Heartbeat = 2,
Reconnect = 4,
Ack = 5,
Error = 6,
EndOfStream = 7,
Identify = 33,
Resume = 34,
Subscribe = 35,
Unsubscribe = 36,
Signal = 37,
};
struct SeventvEventAPISubscription {
bool operator==(const SeventvEventAPISubscription &rhs) const;
bool operator!=(const SeventvEventAPISubscription &rhs) const;
QString condition;
SeventvEventAPISubscriptionType type;
QByteArray encodeSubscribe() const;
QByteArray encodeUnsubscribe() const;
friend QDebug &operator<<(QDebug &dbg,
const SeventvEventAPISubscription &subscription);
};
} // namespace chatterino
template <>
constexpr magic_enum::customize::customize_t magic_enum::customize::enum_name<
chatterino::SeventvEventAPISubscriptionType>(
chatterino::SeventvEventAPISubscriptionType value) noexcept
{
switch (value)
{
case chatterino::SeventvEventAPISubscriptionType::UpdateEmoteSet:
return "emote_set.update";
case chatterino::SeventvEventAPISubscriptionType::UpdateUser:
return "user.update";
default:
return default_tag;
}
}
namespace std {
template <>
struct hash<chatterino::SeventvEventAPISubscription> {
size_t operator()(const chatterino::SeventvEventAPISubscription &sub) const
{
return (size_t)qHash(sub.condition, qHash((int)sub.type));
}
};
} // namespace std

View file

@ -0,0 +1,105 @@
#include "providers/seventv/eventapi/Subscription.hpp"
#include <QDebug>
#include <QJsonDocument>
#include <QJsonObject>
#include <tuple>
#include <utility>
namespace {
using namespace chatterino::seventv::eventapi;
const char *typeToString(SubscriptionType type)
{
return magic_enum::enum_name(type).data();
}
QJsonObject createDataJson(const char *typeName, const Condition &condition)
{
QJsonObject data;
data["type"] = typeName;
data["condition"] = std::visit(
[](const auto &c) {
return c.encode();
},
condition);
return data;
}
} // namespace
namespace chatterino::seventv::eventapi {
bool Subscription::operator==(const Subscription &rhs) const
{
return std::tie(this->condition, this->type) ==
std::tie(rhs.condition, rhs.type);
}
bool Subscription::operator!=(const Subscription &rhs) const
{
return !(rhs == *this);
}
QByteArray Subscription::encodeSubscribe() const
{
const auto *typeName = typeToString(this->type);
QJsonObject root;
root["op"] = (int)Opcode::Subscribe;
root["d"] = createDataJson(typeName, this->condition);
return QJsonDocument(root).toJson();
}
QByteArray Subscription::encodeUnsubscribe() const
{
const auto *typeName = typeToString(this->type);
QJsonObject root;
root["op"] = (int)Opcode::Unsubscribe;
root["d"] = createDataJson(typeName, this->condition);
return QJsonDocument(root).toJson();
}
QDebug &operator<<(QDebug &dbg, const Subscription &subscription)
{
std::visit(
[&](const auto &cond) {
dbg << "Subscription{ condition:" << cond
<< "type:" << magic_enum::enum_name(subscription.type).data()
<< '}';
},
subscription.condition);
return dbg;
}
ObjectIDCondition::ObjectIDCondition(QString objectID)
: objectID(std::move(objectID))
{
}
QJsonObject ObjectIDCondition::encode() const
{
QJsonObject obj;
obj["object_id"] = this->objectID;
return obj;
}
bool ObjectIDCondition::operator==(const ObjectIDCondition &rhs) const
{
return this->objectID == rhs.objectID;
}
bool ObjectIDCondition::operator!=(const ObjectIDCondition &rhs) const
{
return !(*this == rhs);
}
QDebug &operator<<(QDebug &dbg, const ObjectIDCondition &condition)
{
dbg << "{ objectID:" << condition.objectID << "}";
return dbg;
}
} // namespace chatterino::seventv::eventapi

View file

@ -0,0 +1,106 @@
#pragma once
#include <magic_enum.hpp>
#include <QByteArray>
#include <QHash>
#include <QJsonObject>
#include <QString>
#include <variant>
namespace chatterino::seventv::eventapi {
// https://github.com/SevenTV/EventAPI/tree/ca4ff15cc42b89560fa661a76c5849047763d334#subscription-types
enum class SubscriptionType {
UpdateEmoteSet,
UpdateUser,
INVALID,
};
// https://github.com/SevenTV/EventAPI/tree/ca4ff15cc42b89560fa661a76c5849047763d334#opcodes
enum class Opcode {
Dispatch = 0,
Hello = 1,
Heartbeat = 2,
Reconnect = 4,
Ack = 5,
Error = 6,
EndOfStream = 7,
Identify = 33,
Resume = 34,
Subscribe = 35,
Unsubscribe = 36,
Signal = 37,
};
struct ObjectIDCondition {
ObjectIDCondition(QString objectID);
QString objectID;
QJsonObject encode() const;
friend QDebug &operator<<(QDebug &dbg, const ObjectIDCondition &condition);
bool operator==(const ObjectIDCondition &rhs) const;
bool operator!=(const ObjectIDCondition &rhs) const;
};
using Condition = std::variant<ObjectIDCondition>;
struct Subscription {
bool operator==(const Subscription &rhs) const;
bool operator!=(const Subscription &rhs) const;
Condition condition;
SubscriptionType type;
QByteArray encodeSubscribe() const;
QByteArray encodeUnsubscribe() const;
friend QDebug &operator<<(QDebug &dbg, const Subscription &subscription);
};
} // namespace chatterino::seventv::eventapi
template <>
constexpr magic_enum::customize::customize_t magic_enum::customize::enum_name<
chatterino::seventv::eventapi::SubscriptionType>(
chatterino::seventv::eventapi::SubscriptionType value) noexcept
{
using chatterino::seventv::eventapi::SubscriptionType;
switch (value)
{
case SubscriptionType::UpdateEmoteSet:
return "emote_set.update";
case SubscriptionType::UpdateUser:
return "user.update";
default:
return default_tag;
}
}
namespace std {
template <>
struct hash<chatterino::seventv::eventapi::ObjectIDCondition> {
size_t operator()(
const chatterino::seventv::eventapi::ObjectIDCondition &c) const
{
return (size_t)qHash(c.objectID);
}
};
template <>
struct hash<chatterino::seventv::eventapi::Subscription> {
size_t operator()(
const chatterino::seventv::eventapi::Subscription &sub) const
{
const size_t conditionHash =
std::hash<chatterino::seventv::eventapi::Condition>{}(
sub.condition);
return (size_t)qHash(conditionHash, qHash((int)sub.type));
}
};
} // namespace std

View file

@ -311,10 +311,11 @@ std::vector<MessagePtr> IrcMessageHandler::parsePrivMessage(
void IrcMessageHandler::handlePrivMessage(Communi::IrcPrivateMessage *message,
TwitchIrcServer &server)
{
// This is to make sure that combined emoji go through properly, see
// https://github.com/Chatterino/chatterino2/issues/3384 and
// This is for compatibility with older Chatterino versions. Twitch didn't use
// to allow ZERO WIDTH JOINER unicode character, so Chatterino used ESCAPE_TAG
// instead.
// See https://github.com/Chatterino/chatterino2/issues/3384 and
// https://mm2pl.github.io/emoji_rfc.pdf for more details
// Constants used here are defined in TwitchChannel.hpp
this->addMessage(
message, message->target(),

View file

@ -1,6 +1,7 @@
#include "providers/twitch/PubSubManager.hpp"
#include "common/QLogging.hpp"
#include "providers/NetworkConfigurationProvider.hpp"
#include "providers/twitch/PubSubActions.hpp"
#include "providers/twitch/PubSubClient.hpp"
#include "providers/twitch/PubSubHelpers.hpp"
@ -514,6 +515,8 @@ void PubSub::addClient()
return;
}
NetworkConfigurationProvider::applyToWebSocket(con);
this->websocketClient.connect(con);
}

View file

@ -18,7 +18,7 @@
#include "providers/bttv/BttvLiveUpdates.hpp"
#include "providers/bttv/liveupdates/BttvLiveUpdateMessages.hpp"
#include "providers/RecentMessagesApi.hpp"
#include "providers/seventv/eventapi/SeventvEventAPIDispatch.hpp"
#include "providers/seventv/eventapi/Dispatch.hpp"
#include "providers/seventv/SeventvEmotes.hpp"
#include "providers/seventv/SeventvEventAPI.hpp"
#include "providers/twitch/api/Helix.hpp"
@ -121,16 +121,6 @@ TwitchChannel::TwitchChannel(const QString &name)
this->loadRecentMessagesReconnect();
});
this->destroyed.connect([this]() {
getApp()->twitch->dropSeventvChannel(this->seventvUserID_,
this->seventvEmoteSetID_);
if (getApp()->twitch->bttvLiveUpdates)
{
getApp()->twitch->bttvLiveUpdates->partChannel(this->roomId());
}
});
this->messageRemovedFromStart.connect([this](MessagePtr &msg) {
if (msg->replyThread)
{
@ -169,6 +159,17 @@ TwitchChannel::TwitchChannel(const QString &name)
#endif
}
TwitchChannel::~TwitchChannel()
{
getApp()->twitch->dropSeventvChannel(this->seventvUserID_,
this->seventvEmoteSetID_);
if (getApp()->twitch->bttvLiveUpdates)
{
getApp()->twitch->bttvLiveUpdates->partChannel(this->roomId());
}
}
void TwitchChannel::initialize()
{
this->fetchDisplayName();
@ -303,7 +304,21 @@ void TwitchChannel::addChannelPointReward(const ChannelPointReward &reward)
<< "[TwitchChannel" << this->getName()
<< "] Channel point reward added:" << reward.id << ","
<< reward.title << "," << reward.isUserInputRequired;
this->channelPointRewardAdded.invoke(reward);
// TODO: There's an underlying bug here. This bug should be fixed.
// This only attempts to prevent a crash when invoking the signal.
try
{
this->channelPointRewardAdded.invoke(reward);
}
catch (const std::bad_function_call &)
{
qCWarning(chatterinoTwitch).nospace()
<< "[TwitchChannel " << this->getName()
<< "] Caught std::bad_function_call when adding channel point "
"reward ChannelPointReward{ id: "
<< reward.id << ", title: " << reward.title << " }.";
}
}
}
@ -355,10 +370,6 @@ QString TwitchChannel::prepareMessage(const QString &message) const
auto app = getApp();
QString parsedMessage = app->emotes->emojis.replaceShortCodes(message);
// This is to make sure that combined emoji go through properly, see
// https://github.com/Chatterino/chatterino2/issues/3384 and
// https://mm2pl.github.io/emoji_rfc.pdf for more details
parsedMessage.replace(ZERO_WIDTH_JOINER, ESCAPE_TAG);
parsedMessage = parsedMessage.simplified();
if (parsedMessage.isEmpty())
@ -691,7 +702,7 @@ void TwitchChannel::removeBttvEmote(
}
void TwitchChannel::addSeventvEmote(
const SeventvEventAPIEmoteAddDispatch &dispatch)
const seventv::eventapi::EmoteAddDispatch &dispatch)
{
if (!SeventvEmotes::addEmote(this->seventvEmotes_, dispatch))
{
@ -703,7 +714,7 @@ void TwitchChannel::addSeventvEmote(
}
void TwitchChannel::updateSeventvEmote(
const SeventvEventAPIEmoteUpdateDispatch &dispatch)
const seventv::eventapi::EmoteUpdateDispatch &dispatch)
{
if (!SeventvEmotes::updateEmote(this->seventvEmotes_, dispatch))
{
@ -717,7 +728,7 @@ void TwitchChannel::updateSeventvEmote(
}
void TwitchChannel::removeSeventvEmote(
const SeventvEventAPIEmoteRemoveDispatch &dispatch)
const seventv::eventapi::EmoteRemoveDispatch &dispatch)
{
auto removed = SeventvEmotes::removeEmote(this->seventvEmotes_, dispatch);
if (!removed)
@ -730,7 +741,7 @@ void TwitchChannel::removeSeventvEmote(
}
void TwitchChannel::updateSeventvUser(
const SeventvEventAPIUserConnectionUpdateDispatch &dispatch)
const seventv::eventapi::UserConnectionUpdateDispatch &dispatch)
{
if (dispatch.connectionIndex != this->seventvUserTwitchConnectionIndex_)
{

View file

@ -22,8 +22,10 @@
namespace chatterino {
// This is to make sure that combined emoji go through properly, see
// https://github.com/Chatterino/chatterino2/issues/3384 and
// This is for compatibility with older Chatterino versions. Twitch didn't use
// to allow ZERO WIDTH JOINER unicode character, so Chatterino used ESCAPE_TAG
// instead.
// See https://github.com/Chatterino/chatterino2/issues/3384 and
// https://mm2pl.github.io/emoji_rfc.pdf for more details
const QString ZERO_WIDTH_JOINER = QString(QChar(0x200D));
@ -49,11 +51,15 @@ class FfzEmotes;
class BttvEmotes;
struct BttvLiveUpdateEmoteUpdateAddMessage;
struct BttvLiveUpdateEmoteRemoveMessage;
class SeventvEmotes;
struct SeventvEventAPIEmoteAddDispatch;
struct SeventvEventAPIEmoteUpdateDispatch;
struct SeventvEventAPIEmoteRemoveDispatch;
struct SeventvEventAPIUserConnectionUpdateDispatch;
namespace seventv::eventapi {
struct EmoteAddDispatch;
struct EmoteUpdateDispatch;
struct EmoteRemoveDispatch;
struct UserConnectionUpdateDispatch;
} // namespace seventv::eventapi
struct ChannelPointReward;
class MessageThread;
struct CheerEmoteSet;
@ -98,6 +104,7 @@ public:
};
explicit TwitchChannel(const QString &channelName);
~TwitchChannel() override;
void initialize();
@ -149,14 +156,16 @@ public:
void removeBttvEmote(const BttvLiveUpdateEmoteRemoveMessage &message);
/** Adds a 7TV channel emote to this channel. */
void addSeventvEmote(const SeventvEventAPIEmoteAddDispatch &dispatch);
void addSeventvEmote(const seventv::eventapi::EmoteAddDispatch &dispatch);
/** Updates a 7TV channel emote's name in this channel */
void updateSeventvEmote(const SeventvEventAPIEmoteUpdateDispatch &dispatch);
void updateSeventvEmote(
const seventv::eventapi::EmoteUpdateDispatch &dispatch);
/** Removes a 7TV channel emote from this channel */
void removeSeventvEmote(const SeventvEventAPIEmoteRemoveDispatch &dispatch);
void removeSeventvEmote(
const seventv::eventapi::EmoteRemoveDispatch &dispatch);
/** Updates the current 7TV user. Currently, only the emote-set is updated. */
void updateSeventvUser(
const SeventvEventAPIUserConnectionUpdateDispatch &dispatch);
const seventv::eventapi::UserConnectionUpdateDispatch &dispatch);
// Update the channel's 7TV information (the channel's 7TV user ID and emote set ID)
void updateSeventvData(const QString &newUserID,

View file

@ -7,7 +7,7 @@
#include "messages/Message.hpp"
#include "messages/MessageBuilder.hpp"
#include "providers/bttv/BttvLiveUpdates.hpp"
#include "providers/seventv/eventapi/SeventvEventAPISubscription.hpp"
#include "providers/seventv/eventapi/Subscription.hpp"
#include "providers/seventv/SeventvEventAPI.hpp"
#include "providers/twitch/api/Helix.hpp"
#include "providers/twitch/ChannelPointReward.hpp"

View file

@ -9,6 +9,8 @@
#include <QString>
#include <QVariant>
#include <unordered_map>
namespace chatterino {
struct Emote;

View file

@ -142,6 +142,7 @@ void Paths::initSubDirectories()
this->miscDirectory = makePath("Misc");
this->twitchProfileAvatars = makePath("ProfileAvatars");
this->pluginsDirectory = makePath("Plugins");
this->crashdumpDirectory = makePath("Crashes");
//QDir().mkdir(this->twitchProfileAvatars + "/twitch");
}

View file

@ -25,6 +25,9 @@ public:
// Directory for miscellaneous files. Same as <appDataDirectory>/Misc
QString miscDirectory;
// Directory for crashdumps. Same as <appDataDirectory>/Crashes
QString crashdumpDirectory;
// Hash of QCoreApplication::applicationFilePath()
QString applicationFilePathHash;

View file

@ -288,6 +288,13 @@ public:
QStringSetting selfHighlightColor = {"/highlighting/selfHighlightColor",
""};
BoolSetting enableSelfMessageHighlight = {
"/highlighting/selfMessageHighlight/enabled", false};
BoolSetting showSelfMessageHighlightInMentions = {
"/highlighting/selfMessageHighlight/showInMentions", false};
QStringSetting selfMessageHighlightColor = {
"/highlighting/selfMessageHighlight/color", ""};
BoolSetting enableWhisperHighlight = {
"/highlighting/whisperHighlight/whispersHighlighted", true};
BoolSetting enableWhisperHighlightSound = {

View file

@ -1,15 +1,17 @@
#include "util/IncognitoBrowser.hpp"
#ifdef USEWINSDK
# include "util/WindowsHelper.hpp"
#endif
#include <QProcess>
#include <QRegularExpression>
#include <QSettings>
#include <QVariant>
namespace {
using namespace chatterino;
#ifdef Q_OS_WIN
#ifdef USEWINSDK
QString injectPrivateSwitch(QString command)
{
// list of command line switches to turn on private browsing in browsers
@ -47,23 +49,27 @@ QString injectPrivateSwitch(QString command)
QString getCommand()
{
// get default browser prog id
auto browserId = QSettings("HKEY_CURRENT_"
"USER\\Software\\Microsoft\\Windows\\Shell\\"
"Associations\\UrlAssociatio"
"ns\\http\\UserChoice",
QSettings::NativeFormat)
.value("Progid")
.toString();
// get default browser start command, by protocol if possible, falling back to extension if not
QString command =
getAssociatedCommand(AssociationQueryType::Protocol, L"http");
// get default browser start command
auto command =
QSettings("HKEY_CLASSES_ROOT\\" + browserId + "\\shell\\open\\command",
QSettings::NativeFormat)
.value("Default")
.toString();
if (command.isNull())
{
// failed to fetch default browser by protocol, try by file extension instead
command =
getAssociatedCommand(AssociationQueryType::FileExtension, L".html");
}
if (command.isNull())
{
// also try the equivalent .htm extension
command =
getAssociatedCommand(AssociationQueryType::FileExtension, L".htm");
}
if (command.isNull())
{
// failed to find browser command
return QString();
}
@ -84,7 +90,7 @@ namespace chatterino {
bool supportsIncognitoLinks()
{
#ifdef Q_OS_WIN
#ifdef USEWINSDK
return !getCommand().isNull();
#else
return false;
@ -93,7 +99,7 @@ bool supportsIncognitoLinks()
bool openLinkIncognito(const QString &link)
{
#ifdef Q_OS_WIN
#ifdef USEWINSDK
auto command = getCommand();
// TODO: split command into program path and incognito argument

View file

@ -6,6 +6,9 @@
#ifdef USEWINSDK
# include <Shlwapi.h>
# include <VersionHelpers.h>
namespace chatterino {
typedef enum MONITOR_DPI_TYPE {
@ -17,6 +20,8 @@ typedef enum MONITOR_DPI_TYPE {
typedef HRESULT(CALLBACK *GetDpiForMonitor_)(HMONITOR, MONITOR_DPI_TYPE, UINT *,
UINT *);
typedef HRESULT(CALLBACK *AssocQueryString_)(ASSOCF, ASSOCSTR, LPCWSTR, LPCWSTR,
LPWSTR, DWORD *);
boost::optional<UINT> getWindowDpi(HWND hwnd)
{
@ -83,6 +88,67 @@ void setRegisteredForStartup(bool isRegistered)
}
}
QString getAssociatedCommand(AssociationQueryType queryType, LPCWSTR query)
{
static HINSTANCE shlwapi = LoadLibrary(L"shlwapi");
if (shlwapi == nullptr)
{
return QString();
}
static auto assocQueryString =
AssocQueryString_(GetProcAddress(shlwapi, "AssocQueryStringW"));
if (assocQueryString == nullptr)
{
return QString();
}
// always error out instead of returning a truncated string when the
// buffer is too small - avoids race condition when the user changes their
// default browser between calls to AssocQueryString
ASSOCF flags = ASSOCF_NOTRUNCATE;
if (queryType == AssociationQueryType::Protocol)
{
// ASSOCF_IS_PROTOCOL was introduced in Windows 8
if (IsWindows8OrGreater())
{
flags |= ASSOCF_IS_PROTOCOL;
}
else
{
return QString();
}
}
DWORD resultSize = 0;
if (FAILED(assocQueryString(flags, ASSOCSTR_COMMAND, query, nullptr,
nullptr, &resultSize)))
{
return QString();
}
if (resultSize <= 1)
{
// resultSize includes the null terminator. if resultSize is 1, the
// returned value would be the empty string.
return QString();
}
QString result;
auto buf = new wchar_t[resultSize];
if (SUCCEEDED(assocQueryString(flags, ASSOCSTR_COMMAND, query, nullptr, buf,
&resultSize)))
{
// QString::fromWCharArray expects the length in characters *not
// including* the null terminator, but AssocQueryStringW calculates
// length including the null terminator
result = QString::fromWCharArray(buf, resultSize - 1);
}
delete[] buf;
return result;
}
} // namespace chatterino
#endif

View file

@ -7,12 +7,16 @@
namespace chatterino {
enum class AssociationQueryType { Protocol, FileExtension };
boost::optional<UINT> getWindowDpi(HWND hwnd);
void flushClipboard();
bool isRegisteredForStartup();
void setRegisteredForStartup(bool isRegistered);
QString getAssociatedCommand(AssociationQueryType queryType, LPCWSTR query);
} // namespace chatterino
#endif

View file

@ -217,7 +217,11 @@ void Button::fancyPaint(QPainter &painter)
}
}
void Button::enterEvent(QEvent *)
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
void Button::enterEvent(QEnterEvent * /*event*/)
#else
void Button::enterEvent(QEvent * /*event*/)
#endif
{
this->mouseOver_ = true;
}

View file

@ -57,7 +57,11 @@ signals:
protected:
virtual void paintEvent(QPaintEvent *) override;
virtual void enterEvent(QEvent *) override;
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
void enterEvent(QEnterEvent * /*event*/) override;
#else
void enterEvent(QEvent * /*event*/) override;
#endif
virtual void leaveEvent(QEvent *) override;
virtual void mousePressEvent(QMouseEvent *event) override;
virtual void mouseReleaseEvent(QMouseEvent *event) override;

View file

@ -1440,7 +1440,11 @@ void ChannelView::wheelEvent(QWheelEvent *event)
}
}
void ChannelView::enterEvent(QEvent *)
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
void ChannelView::enterEvent(QEnterEvent * /*event*/)
#else
void ChannelView::enterEvent(QEvent * /*event*/)
#endif
{
}

View file

@ -167,7 +167,11 @@ protected:
void paintEvent(QPaintEvent *) override;
void wheelEvent(QWheelEvent *event) override;
void enterEvent(QEvent *) override;
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
void enterEvent(QEnterEvent * /*event*/) override;
#else
void enterEvent(QEvent * /*event*/) override;
#endif
void leaveEvent(QEvent *) override;
void mouseMoveEvent(QMouseEvent *event) override;

View file

@ -674,7 +674,11 @@ void NotebookTab::mouseDoubleClickEvent(QMouseEvent *event)
}
}
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
void NotebookTab::enterEvent(QEnterEvent *event)
#else
void NotebookTab::enterEvent(QEvent *event)
#endif
{
this->mouseOver_ = true;

View file

@ -61,7 +61,11 @@ protected:
virtual void mousePressEvent(QMouseEvent *event) override;
virtual void mouseReleaseEvent(QMouseEvent *event) override;
virtual void mouseDoubleClickEvent(QMouseEvent *event) override;
virtual void enterEvent(QEvent *) override;
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
void enterEvent(QEnterEvent *event) override;
#else
void enterEvent(QEvent *event) override;
#endif
virtual void leaveEvent(QEvent *) override;
virtual void dragEnterEvent(QDragEnterEvent *event) override;

View file

@ -119,6 +119,11 @@ AboutPage::AboutPage()
addLicense(form.getElement(), "Fluent icons",
"https://github.com/microsoft/fluentui-system-icons",
":/licenses/fluenticons.txt");
#endif
#ifdef CHATTERINO_WITH_CRASHPAD
addLicense(form.getElement(), "sentry-crashpad",
"https://github.com/getsentry/crashpad",
":/licenses/crashpad.txt");
#endif
}

View file

@ -822,7 +822,11 @@ void Split::resizeEvent(QResizeEvent *event)
this->overlay_->setGeometry(this->rect());
}
void Split::enterEvent(QEvent *event)
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
void Split::enterEvent(QEnterEvent * /*event*/)
#else
void Split::enterEvent(QEvent * /*event*/)
#endif
{
this->isMouseOver_ = true;

View file

@ -105,7 +105,11 @@ protected:
void keyPressEvent(QKeyEvent *event) override;
void keyReleaseEvent(QKeyEvent *event) override;
void resizeEvent(QResizeEvent *event) override;
void enterEvent(QEvent *event) override;
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
void enterEvent(QEnterEvent * /*event*/) override;
#else
void enterEvent(QEvent * /*event*/) override;
#endif
void leaveEvent(QEvent *event) override;
void dragEnterEvent(QDragEnterEvent *event) override;

View file

@ -945,7 +945,11 @@ void SplitHeader::mouseDoubleClickEvent(QMouseEvent *event)
this->doubleClicked_ = true;
}
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
void SplitHeader::enterEvent(QEnterEvent *event)
#else
void SplitHeader::enterEvent(QEvent *event)
#endif
{
if (!this->tooltipText_.isEmpty())
{

View file

@ -42,7 +42,11 @@ protected:
void mousePressEvent(QMouseEvent *event) override;
void mouseReleaseEvent(QMouseEvent *event) override;
void mouseMoveEvent(QMouseEvent *event) override;
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
void enterEvent(QEnterEvent *event) override;
#else
void enterEvent(QEvent *event) override;
#endif
void leaveEvent(QEvent *event) override;
void mouseDoubleClickEvent(QMouseEvent *event) override;

View file

@ -1,14 +1,15 @@
#include "providers/seventv/SeventvEventAPI.hpp"
#include "providers/seventv/eventapi/SeventvEventAPIClient.hpp"
#include "providers/seventv/eventapi/SeventvEventAPIDispatch.hpp"
#include "providers/seventv/eventapi/SeventvEventAPIMessage.hpp"
#include "providers/seventv/eventapi/Client.hpp"
#include "providers/seventv/eventapi/Dispatch.hpp"
#include "providers/seventv/eventapi/Message.hpp"
#include <boost/optional.hpp>
#include <gtest/gtest.h>
#include <QString>
using namespace chatterino;
using namespace chatterino::seventv::eventapi;
using namespace std::chrono_literals;
const QString EMOTE_SET_A = "60b39e943e203cc169dfc106";
@ -21,10 +22,10 @@ TEST(SeventvEventAPI, AllEvents)
auto *eventAPI = new SeventvEventAPI(host, std::chrono::milliseconds(1000));
eventAPI->start();
boost::optional<SeventvEventAPIEmoteAddDispatch> addDispatch;
boost::optional<SeventvEventAPIEmoteUpdateDispatch> updateDispatch;
boost::optional<SeventvEventAPIEmoteRemoveDispatch> removeDispatch;
boost::optional<SeventvEventAPIUserConnectionUpdateDispatch> userDispatch;
boost::optional<EmoteAddDispatch> addDispatch;
boost::optional<EmoteUpdateDispatch> updateDispatch;
boost::optional<EmoteRemoveDispatch> removeDispatch;
boost::optional<UserConnectionUpdateDispatch> userDispatch;
eventAPI->signals_.emoteAdded.connect([&](const auto &d) {
addDispatch = d;

View file

@ -1,8 +1,8 @@
{
"$schema": "https://raw.githubusercontent.com/microsoft/vcpkg/master/scripts/vcpkg.schema.json",
"$schema": "https://raw.githubusercontent.com/microsoft/vcpkg-tool/main/docs/vcpkg.schema.json",
"name": "chatterino",
"version": "2.0.0",
"builtin-baseline": "5ba2b95aea2a39aa89444949c7a047af38c401c1",
"builtin-baseline": "43f56137beabcd470ac2650cdf3954761f65b70e",
"dependencies": [
"benchmark",
"boost-asio",
@ -14,7 +14,6 @@
"boost-variant",
"gtest",
"openssl",
"qt5-multimedia",
"qt5-tools"
],
"overrides": [