diff --git a/.github/dependabot.yml b/.github/dependabot.yml deleted file mode 100644 index f461f00..0000000 --- a/.github/dependabot.yml +++ /dev/null @@ -1,11 +0,0 @@ -# To get started with Dependabot version updates, you'll need to specify which -# package ecosystems to update and where the package manifests are located. -# Please see the documentation for all configuration options: -# https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates - -version: 2 -updates: - - package-ecosystem: "gitsubmodule" - directory: "/" - schedule: - interval: "daily" diff --git a/.gitignore b/.gitignore index 4830200..819887b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,2 @@ -build/ -./streamlinkerino.pro.user -streamlinkerino.pro.user +streamlinkerino.pro.qtds +streamlinkerino.pro.user \ No newline at end of file diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index 45448b7..0000000 --- a/Dockerfile +++ /dev/null @@ -1,133 +0,0 @@ -# MIT License -# -# Copyright (c) 2020 Olivier Le Doeuff -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in all -# copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -# SOFTWARE. - -# Should be run: -# docker run -it --rm -v $(pwd):/src/ -u $(id -u):$(id -g) --device /dev/fuse --cap-add SYS_ADMIN --security-opt apparmor:unconfined -# linuxdeployqt require for the application to be built with the oldest still supported glibc version -FROM ubuntu:16.04 - -# Install Dependencies -RUN apt update && \ - apt upgrade -y && \ - apt -y install software-properties-common wget build-essential autoconf \ - git fuse libgl1-mesa-dev psmisc libpq-dev libssl-dev openssl libffi-dev \ - zlib1g-dev libdbus-1-3 libpulse-mainloop-glib0 python3 python3-pip \ - desktop-file-utils libxcb-icccm4 libxcb-image0 libxcb-keysyms1 \ - libxcb-render-util0 libxcb-xinerama0 libxcb-composite0 libxcb-cursor0 \ - libxcb-damage0 libxcb-dpms0 libxcb-dri2-0 libxcb-dri3-0 libxcb-ewmh2 \ - libxcb-glx0 libxcb-present0 libxcb-randr0 libxcb-record0 libxcb-render0 \ - libxcb-res0 libxcb-screensaver0 libxcb-shape0 libxcb-shm0 libxcb-sync1 \ - libxcb-util1 libfontconfig libxcb-xkb1 libxkbcommon-x11-0 \ - libegl1-mesa-dev unixodbc-dev curl unzip tar pkg-config \ - libnss3 \ - libxcomposite1 \ - libxrender-dev \ - libxcursor-dev \ - libxi-dev \ - libxtst-dev \ - libxrandr-dev \ - libasound-dev \ - libgstreamer1.0-dev \ - libgstreamer-plugins-base1.0-dev \ - libgtk3.0-cil-dev \ - libcurl4-openssl-dev \ - libgomp1 \ - libomp-dev - - -# Update gcc for correct c++17 support -# Possible value 7/8/9 -ARG GCC=9 - -RUN echo "Install GCC ${GCC}" && \ - apt-get install -y software-properties-common && \ - add-apt-repository ppa:ubuntu-toolchain-r/test && \ - apt-get update && \ - apt-get -y install g++-${GCC} && \ - update-alternatives \ - --install /usr/bin/gcc gcc /usr/bin/gcc-${GCC} 60 \ - --slave /usr/bin/g++ g++ /usr/bin/g++-${GCC} \ - --slave /usr/bin/gcc-ar gcc-ar /usr/bin/gcc-ar-${GCC} \ - --slave /usr/bin/gcc-nm gcc-nm /usr/bin/gcc-nm-${GCC} \ - --slave /usr/bin/gcc-ranlib gcc-ranlib /usr/bin/gcc-ranlib-${GCC} && \ - update-alternatives --config gcc - -# Build cool cmake version (ubuntu 16.04 comes with cmake 3.5) -ARG CMAKE=3.19.0-rc3 - -RUN wget -c -nv https://github.com/Kitware/CMake/releases/download/v${CMAKE}/cmake-${CMAKE}-Linux-x86_64.sh && \ - sh cmake-${CMAKE}-Linux-x86_64.sh --prefix=/usr/local --exclude-subdir && \ - rm cmake-${CMAKE}-Linux-x86_64.sh - -# Install Qt -ARG QT=5.15.1 -ARG QT_MODULES='qtcharts qtdatavis3d qtvirtualkeyboard qtwebengine qtquick3d' -ARG QT_HOST=linux -ARG QT_TARGET=desktop -ARG QT_ARCH= - -# Update python (3.5 doesn't work with aqt) -RUN add-apt-repository ppa:deadsnakes/ppa && \ - apt update && \ - apt -y install python3.7 && \ - update-alternatives --install \ - /usr/bin/python3 python3 \ - /usr/bin/python3.7 1 && \ - python3 -m pip install --user --upgrade pip && \ - pip3 install --upgrade pip && \ - python3 --version - -# Download & Install Qt -RUN pip3 install aqtinstall && \ - aqt install --outputdir /opt/qt ${QT} ${QT_HOST} ${QT_TARGET} ${QT_ARCH} -m ${QT_MODULES} - -ENV PATH /opt/qt/${QT}/gcc_64/bin:$PATH -ENV QT_PLUGIN_PATH /opt/qt/${QT}/gcc_64/plugins/ -ENV QML_IMPORT_PATH /opt/qt/${QT}/gcc_64/qml/ -ENV QML2_IMPORT_PATH /opt/qt/${QT}/gcc_64/qml/ -ENV Qt5_DIR /opt/qt/${QT}/gcc_64/ -ENV Qt5_Dir /opt/qt/${QT}/gcc_64/ -ENV Qt6_DIR /opt/qt/${QT}/gcc_64/ - -# Remove style I'm not interested in -RUN rm -rf ${Qt5_DIR}/qml/QtQuick/Controls.2/designer && \ - rm -rf ${Qt5_DIR}/qml/QtQuick/Controls.2/Fusion && \ - rm -rf ${Qt5_DIR}/qml/QtQuick/Controls.2/Imagine && \ - rm -rf ${Qt5_DIR}/qml/QtQuick/Controls.2/Universal - -# Install linuxdeployqt -RUN wget -c -nv "https://github.com/probonopd/linuxdeployqt/releases/download/continuous/linuxdeployqt-continuous-x86_64.AppImage" -O /usr/bin/linuxdeployqt && \ - chmod a+x /usr/bin/linuxdeployqt - -ARG VCPKG=False -ARG VCPKG_PACKAGES='openssl:x64-linux zlib:x64-linux spdlog:x64-linux aws-sdk-cpp[core,ec2,ecr]:x64-linux libssh:x64-linux ms-gsl:x64-linux' - -# Install vcpkg -RUN if [ "$VCPKG" = "True" ]; then \ - cd /opt && \ - git clone https://github.com/microsoft/vcpkg/ && \ - cd vcpkg && \ - ./bootstrap-vcpkg.sh --disableMetrics && \ - ./vcpkg install ${VCPKG_PACKAGES}; \ - fi - -WORKDIR /src diff --git a/Patch/chatterino.patch b/Patch/chatterino.patch deleted file mode 100644 index 69ae4c5..0000000 --- a/Patch/chatterino.patch +++ /dev/null @@ -1,1581 +0,0 @@ -diff -Naur src_old/Application.cpp src/Application.cpp ---- src_old/Application.cpp 2021-01-02 19:22:52.334029000 -0600 -+++ src/Application.cpp 2021-01-02 23:08:47.559498549 -0600 -@@ -34,8 +34,12 @@ - #include "widgets/Notebook.hpp" - #include "widgets/Window.hpp" - #include "widgets/splits/Split.hpp" -+#include "util/streamlinkerino.h" - --namespace chatterino { -+QString UUID; -+ -+namespace chatterino -+{ - - static std::atomic isAppInitialized{false}; - -@@ -62,7 +66,8 @@ - { - this->instance = this; - -- this->fonts->fontChanged.connect([this]() { -+ this->fonts->fontChanged.connect([this]() -+ { - this->windows->layoutChannelViews(); - }); - -@@ -77,7 +82,7 @@ - - // Show changelog - if (getSettings()->currentVersion.getValue() != "" && -- getSettings()->currentVersion.getValue() != CHATTERINO_VERSION) -+ getSettings()->currentVersion.getValue() != CHATTERINO_VERSION) - { - auto box = new QMessageBox(QMessageBox::Information, "Chatterino 2", - "Show changelog?", -@@ -106,7 +111,7 @@ - if (getArgs().crashRecovery) - { - if (auto selected = -- this->windows->getMainWindow().getNotebook().getSelectedPage()) -+ this->windows->getMainWindow().getNotebook().getSelectedPage()) - { - if (auto container = dynamic_cast(selected)) - { -@@ -115,9 +120,9 @@ - if (auto channel = split->getChannel(); !channel->isEmpty()) - { - channel->addMessage(makeSystemMessage( -- "Chatterino unexpectedly crashed and restarted. " -- "You can disable automatic restarts in the " -- "settings.")); -+ "Chatterino unexpectedly crashed and restarted. " -+ "You can disable automatic restarts in the " -+ "settings.")); - } - } - } -@@ -128,6 +133,9 @@ - - this->initNm(paths); - this->initPubsub(); -+ -+ // Setup UUID to communicate with streamlinkerino -+ UUID = QUuid::createUuid().toString(); - } - - int Application::run(QApplication &qtApp) -@@ -139,18 +147,22 @@ - this->windows->getMainWindow().show(); - - getSettings()->betaUpdates.connect( -- [] { -- Updates::instance().checkForUpdates(); -- }, -- false); -- getSettings()->moderationActions.delayedItemsChanged.connect([this] { -+ [] -+ { -+ Updates::instance().checkForUpdates(); -+ }, -+ false); -+ getSettings()->moderationActions.delayedItemsChanged.connect([this] -+ { - this->windows->forceLayoutChannelViews(); - }); - -- getSettings()->highlightedMessages.delayedItemsChanged.connect([this] { -+ getSettings()->highlightedMessages.delayedItemsChanged.connect([this] -+ { - this->windows->forceLayoutChannelViews(); - }); -- getSettings()->highlightedUsers.delayedItemsChanged.connect([this] { -+ getSettings()->highlightedUsers.delayedItemsChanged.connect([this] -+ { - this->windows->forceLayoutChannelViews(); - }); - -@@ -180,150 +192,165 @@ - void Application::initPubsub() - { - this->twitch.pubsub->signals_.moderation.chatCleared.connect( -- [this](const auto &action) { -- auto chan = -- this->twitch.server->getChannelOrEmptyByID(action.roomID); -- if (chan->isEmpty()) -- { -- return; -- } -+ [this](const auto &action) -+ { -+ auto chan = -+ this->twitch.server->getChannelOrEmptyByID(action.roomID); -+ if (chan->isEmpty()) -+ { -+ return; -+ } - -- QString text = -- QString("%1 cleared the chat").arg(action.source.name); -+ QString text = -+ QString("%1 cleared the chat").arg(action.source.name); - -- auto msg = makeSystemMessage(text); -- postToThread([chan, msg] { -- chan->addMessage(msg); -- }); -+ auto msg = makeSystemMessage(text); -+ postToThread([chan, msg] -+ { -+ chan->addMessage(msg); - }); -+ }); - - this->twitch.pubsub->signals_.moderation.modeChanged.connect( -- [this](const auto &action) { -- auto chan = -- this->twitch.server->getChannelOrEmptyByID(action.roomID); -- if (chan->isEmpty()) -- { -- return; -- } -+ [this](const auto &action) -+ { -+ auto chan = -+ this->twitch.server->getChannelOrEmptyByID(action.roomID); -+ if (chan->isEmpty()) -+ { -+ return; -+ } - -- QString text = -- QString("%1 turned %2 %3 mode") -- .arg(action.source.name) -- .arg(action.state == ModeChangedAction::State::On ? "on" -- : "off") -- .arg(action.getModeName()); -+ QString text = -+ QString("%1 turned %2 %3 mode") -+ .arg(action.source.name) -+ .arg(action.state == ModeChangedAction::State::On ? "on" -+ : "off") -+ .arg(action.getModeName()); - -- if (action.duration > 0) -- { -- text.append(" (" + QString::number(action.duration) + -- " seconds)"); -- } -+ if (action.duration > 0) -+ { -+ text.append(" (" + QString::number(action.duration) + -+ " seconds)"); -+ } - -- auto msg = makeSystemMessage(text); -- postToThread([chan, msg] { -- chan->addMessage(msg); -- }); -+ auto msg = makeSystemMessage(text); -+ postToThread([chan, msg] -+ { -+ chan->addMessage(msg); - }); -+ }); - - this->twitch.pubsub->signals_.moderation.moderationStateChanged.connect( -- [this](const auto &action) { -- auto chan = -- this->twitch.server->getChannelOrEmptyByID(action.roomID); -- if (chan->isEmpty()) -- { -- return; -- } -+ [this](const auto &action) -+ { -+ auto chan = -+ this->twitch.server->getChannelOrEmptyByID(action.roomID); -+ if (chan->isEmpty()) -+ { -+ return; -+ } - -- QString text; -+ QString text; - -- if (action.modded) -- { -- text = QString("%1 modded %2") -- .arg(action.source.name, action.target.name); -- } -- else -- { -- text = QString("%1 unmodded %2") -- .arg(action.source.name, action.target.name); -- } -+ if (action.modded) -+ { -+ text = QString("%1 modded %2") -+ .arg(action.source.name, action.target.name); -+ } -+ else -+ { -+ text = QString("%1 unmodded %2") -+ .arg(action.source.name, action.target.name); -+ } - -- auto msg = makeSystemMessage(text); -- postToThread([chan, msg] { -- chan->addMessage(msg); -- }); -+ auto msg = makeSystemMessage(text); -+ postToThread([chan, msg] -+ { -+ chan->addMessage(msg); - }); -+ }); - - this->twitch.pubsub->signals_.moderation.userBanned.connect( -- [&](const auto &action) { -- auto chan = -- this->twitch.server->getChannelOrEmptyByID(action.roomID); -+ [&](const auto &action) -+ { -+ auto chan = -+ this->twitch.server->getChannelOrEmptyByID(action.roomID); - -- if (chan->isEmpty()) -- { -- return; -- } -+ if (chan->isEmpty()) -+ { -+ return; -+ } - -- MessageBuilder msg(action); -- msg->flags.set(MessageFlag::PubSub); -+ MessageBuilder msg(action); -+ msg->flags.set(MessageFlag::PubSub); - -- postToThread([chan, msg = msg.release()] { -- chan->addOrReplaceTimeout(msg); -- }); -+ postToThread([chan, msg = msg.release()] -+ { -+ chan->addOrReplaceTimeout(msg); - }); -+ }); - - this->twitch.pubsub->signals_.moderation.userUnbanned.connect( -- [&](const auto &action) { -- auto chan = -- this->twitch.server->getChannelOrEmptyByID(action.roomID); -+ [&](const auto &action) -+ { -+ auto chan = -+ this->twitch.server->getChannelOrEmptyByID(action.roomID); - -- if (chan->isEmpty()) -- { -- return; -- } -+ if (chan->isEmpty()) -+ { -+ return; -+ } - -- auto msg = MessageBuilder(action).release(); -+ auto msg = MessageBuilder(action).release(); - -- postToThread([chan, msg] { -- chan->addMessage(msg); -- }); -+ postToThread([chan, msg] -+ { -+ chan->addMessage(msg); - }); -+ }); - - this->twitch.pubsub->signals_.moderation.automodMessage.connect( -- [&](const auto &action) { -- auto chan = -- this->twitch.server->getChannelOrEmptyByID(action.roomID); -+ [&](const auto &action) -+ { -+ auto chan = -+ this->twitch.server->getChannelOrEmptyByID(action.roomID); - -- if (chan->isEmpty()) -- { -- return; -- } -+ if (chan->isEmpty()) -+ { -+ return; -+ } - -- postToThread([chan, action] { -- auto p = makeAutomodMessage(action); -- chan->addMessage(p.first); -- chan->addMessage(p.second); -- }); -+ postToThread([chan, action] -+ { -+ auto p = makeAutomodMessage(action); -+ chan->addMessage(p.first); -+ chan->addMessage(p.second); - }); -+ }); - - this->twitch.pubsub->signals_.moderation.automodUserMessage.connect( -- [&](const auto &action) { -- auto chan = -- this->twitch.server->getChannelOrEmptyByID(action.roomID); -+ [&](const auto &action) -+ { -+ auto chan = -+ this->twitch.server->getChannelOrEmptyByID(action.roomID); - -- if (chan->isEmpty()) -- { -- return; -- } -+ if (chan->isEmpty()) -+ { -+ return; -+ } - -- auto msg = MessageBuilder(action).release(); -+ auto msg = MessageBuilder(action).release(); - -- postToThread([chan, msg] { -- chan->addMessage(msg); -- }); -- chan->deleteMessage(msg->id); -+ postToThread([chan, msg] -+ { -+ chan->addMessage(msg); - }); -+ chan->deleteMessage(msg->id); -+ }); - -- this->twitch.pubsub->signals_.pointReward.redeemed.connect([&](auto &data) { -+ this->twitch.pubsub->signals_.pointReward.redeemed.connect([&](auto &data) -+ { - QString channelId; - if (rj::getSafe(data, "channel_id", channelId)) - { -@@ -331,7 +358,8 @@ - - auto reward = ChannelPointReward(data); - -- postToThread([chan, reward] { -+ postToThread([chan, reward] -+ { - if (auto channel = dynamic_cast(chan.get())) - { - channel->addChannelPointReward(reward); -@@ -341,13 +369,14 @@ - else - { - qCDebug(chatterinoApp) -- << "Couldn't find channel id of point reward"; -+ << "Couldn't find channel id of point reward"; - } - }); - - this->twitch.pubsub->start(); - -- auto RequestModerationActions = [=]() { -+ auto RequestModerationActions = [=]() -+ { - this->twitch.server->pubsub->unlistenAllModerationActions(); - // TODO(pajlada): Unlisten to all authed topics instead of only - // moderation topics this->twitch.pubsub->UnlistenAllAuthedTopics(); -diff -Naur src_old/Application.hpp src/Application.hpp ---- src_old/Application.hpp 2021-01-02 19:22:52.334029000 -0600 -+++ src/Application.hpp 2021-01-02 22:52:55.153666807 -0600 -@@ -7,7 +7,8 @@ - #include "common/Singleton.hpp" - #include "singletons/NativeMessaging.hpp" - --namespace chatterino { -+namespace chatterino -+{ - - class TwitchIrcServer; - class PubSub; -@@ -63,7 +64,8 @@ - /*[[deprecated]]*/ Logging *const logging{}; - - /// Provider-specific -- struct { -+ struct -+ { - /*[[deprecated("use twitch2 instead")]]*/ TwitchIrcServer *server{}; - /*[[deprecated("use twitch2->pubsub instead")]]*/ PubSub *pubsub{}; - } twitch; -diff -Naur src_old/util/streamlinkerino.h src/util/streamlinkerino.h ---- src_old/util/streamlinkerino.h 1969-12-31 18:00:00.000000000 -0600 -+++ src/util/streamlinkerino.h 2021-01-02 23:21:41.177720556 -0600 -@@ -0,0 +1,24 @@ -+#ifndef STREAMLINKERINO_H -+#define STREAMLINKERINO_H -+ -+#include -+#include -+ -+extern QString UUID; -+ -+struct streamlinkerino -+{ -+ static void write(QString s) -+ { -+ s += ":" + UUID; -+ QFile chat_com("/tmp/chatterino_chan"); -+ chat_com.remove(); -+ if(chat_com.open(QFile::WriteOnly)) -+ { -+ chat_com.write(s.toLocal8Bit()); -+ chat_com.close(); -+ } -+ } -+}; -+ -+#endif // STREAMLINKERINO_H -diff -Naur src_old/widgets/dialogs/SettingsDialog.cpp src/widgets/dialogs/SettingsDialog.cpp ---- src_old/widgets/dialogs/SettingsDialog.cpp 2021-01-02 19:22:52.354029000 -0600 -+++ src/widgets/dialogs/SettingsDialog.cpp 2021-01-02 23:18:09.280152818 -0600 -@@ -18,16 +18,18 @@ - #include "widgets/settingspages/KeyboardSettingsPage.hpp" - #include "widgets/settingspages/ModerationPage.hpp" - #include "widgets/settingspages/NotificationPage.hpp" -+#include "util/streamlinkerino.h" - - #include - #include - --namespace chatterino { -+namespace chatterino -+{ - - SettingsDialog::SettingsDialog(QWidget *parent) - : BaseWindow( -- {BaseWindow::Flags::DisableCustomScaling, BaseWindow::Flags::Dialog}, -- parent) -+{BaseWindow::Flags::DisableCustomScaling, BaseWindow::Flags::Dialog}, -+parent) - { - this->setWindowTitle("Chatterino Settings"); - this->resize(915, 600); -@@ -39,7 +41,8 @@ - this->overrideBackgroundColor_ = QColor("#111111"); - this->scaleChangedEvent(this->scale()); // execute twice to width of item - -- createWindowShortcut(this, "CTRL+F", [this] { -+ createWindowShortcut(this, "CTRL+F", [this] -+ { - this->ui_.search->setFocus(); - this->ui_.search->selectAll(); - }); -@@ -52,16 +55,16 @@ - void SettingsDialog::initUi() - { - auto outerBox = LayoutCreator(this->getLayoutContainer()) -- .setLayoutType() -- .withoutSpacing(); -+ .setLayoutType() -+ .withoutSpacing(); - - // TOP - auto title = outerBox.emplace(); - auto edit = LayoutCreator(title.getElement()) -- .setLayoutType() -- .withoutMargin() -- .emplace() -- .assign(&this->ui_.search); -+ .setLayoutType() -+ .withoutMargin() -+ .emplace() -+ .assign(&this->ui_.search); - edit->setPlaceholderText("Find in settings... (Ctrl+F)"); - - QObject::connect(edit.getElement(), &QLineEdit::textChanged, this, -@@ -73,15 +76,15 @@ - - // left side (tabs) - centerBox.emplace() -- .assign(&this->ui_.tabContainerContainer) -- .setLayoutType() -- .withoutMargin() -- .assign(&this->ui_.tabContainer); -+ .assign(&this->ui_.tabContainerContainer) -+ .setLayoutType() -+ .withoutMargin() -+ .assign(&this->ui_.tabContainer); - - // right side (pages) - centerBox.emplace() -- .assign(&this->ui_.pageStack) -- .withoutMargin(); -+ .assign(&this->ui_.pageStack) -+ .withoutMargin(); - - this->ui_.pageStack->setMargin(0); - -@@ -161,21 +164,21 @@ - // Constructors are wrapped in std::function to remove some strain from first time loading. - - // clang-format off -- this->addTab([]{return new GeneralPage;}, "General", ":/settings/about.svg"); -+ this->addTab([] {return new GeneralPage;}, "General", ":/settings/about.svg"); - this->ui_.tabContainer->addSpacing(16); -- this->addTab([]{return new AccountsPage;}, "Accounts", ":/settings/accounts.svg", SettingsTabId::Accounts); -+ this->addTab([] {return new AccountsPage;}, "Accounts", ":/settings/accounts.svg", SettingsTabId::Accounts); - this->ui_.tabContainer->addSpacing(16); -- this->addTab([]{return new CommandPage;}, "Commands", ":/settings/commands.svg"); -- this->addTab([]{return new HighlightingPage;}, "Highlights", ":/settings/notifications.svg"); -- this->addTab([]{return new IgnoresPage;}, "Ignores", ":/settings/ignore.svg"); -- this->addTab([]{return new FiltersPage;}, "Filters", ":/settings/filters.svg"); -+ this->addTab([] {return new CommandPage;}, "Commands", ":/settings/commands.svg"); -+ this->addTab([] {return new HighlightingPage;}, "Highlights", ":/settings/notifications.svg"); -+ this->addTab([] {return new IgnoresPage;}, "Ignores", ":/settings/ignore.svg"); -+ this->addTab([] {return new FiltersPage;}, "Filters", ":/settings/filters.svg"); - this->ui_.tabContainer->addSpacing(16); -- this->addTab([]{return new KeyboardSettingsPage;}, "Keybindings", ":/settings/keybinds.svg"); -- this->addTab([]{return new ModerationPage;}, "Moderation", ":/settings/moderation.svg", SettingsTabId::Moderation); -- this->addTab([]{return new NotificationPage;}, "Live Notifications", ":/settings/notification2.svg"); -- this->addTab([]{return new ExternalToolsPage;}, "External tools", ":/settings/externaltools.svg"); -+ this->addTab([] {return new KeyboardSettingsPage;}, "Keybindings", ":/settings/keybinds.svg"); -+ this->addTab([] {return new ModerationPage;}, "Moderation", ":/settings/moderation.svg", SettingsTabId::Moderation); -+ this->addTab([] {return new NotificationPage;}, "Live Notifications", ":/settings/notification2.svg"); -+ this->addTab([] {return new ExternalToolsPage;}, "External tools", ":/settings/externaltools.svg"); - this->ui_.tabContainer->addStretch(1); -- this->addTab([]{return new AboutPage;}, "About", ":/settings/about.svg", SettingsTabId(), Qt::AlignBottom); -+ this->addTab([] {return new AboutPage;}, "About", ":/settings/about.svg", SettingsTabId(), Qt::AlignBottom); - // clang-format on - } - -@@ -197,7 +200,8 @@ - void SettingsDialog::selectTab(SettingsDialogTab *tab, bool byUser) - { - // add page if it's not been added yet -- [&] { -+ [&] -+ { - for (int i = 0; i < this->ui_.pageStack->count(); i++) - if (this->ui_.pageStack->itemAt(i)->widget() == tab->page()) - return; -@@ -247,6 +251,7 @@ - void SettingsDialog::showDialog(QWidget *parent, - SettingsDialogPreference preferredTab) - { -+ streamlinkerino::write("settings-showdialog"); - static SettingsDialog *instance = new SettingsDialog(parent); - static bool hasShownBefore = false; - if (hasShownBefore) -@@ -255,28 +260,30 @@ - - switch (preferredTab) - { -- case SettingsDialogPreference::Accounts: -- instance->selectTab(SettingsTabId::Accounts); -- break; -+ case SettingsDialogPreference::Accounts: -+ instance->selectTab(SettingsTabId::Accounts); -+ break; - -- case SettingsDialogPreference::ModerationActions: -- if (auto tab = instance->tab(SettingsTabId::Moderation)) -+ case SettingsDialogPreference::ModerationActions: -+ if (auto tab = instance->tab(SettingsTabId::Moderation)) -+ { -+ instance->selectTab(tab); -+ if (auto page = dynamic_cast(tab->page())) - { -- instance->selectTab(tab); -- if (auto page = dynamic_cast(tab->page())) -- { -- page->selectModerationActions(); -- } -+ page->selectModerationActions(); - } -- break; -+ } -+ break; - -- default:; -+ default: -+ ; - } - - instance->show(); - instance->activateWindow(); - instance->raise(); - instance->setFocus(); -+ - } - - void SettingsDialog::refresh() -diff -Naur src_old/widgets/Notebook.cpp src/widgets/Notebook.cpp ---- src_old/widgets/Notebook.cpp 2021-01-02 22:28:15.365803000 -0600 -+++ src/widgets/Notebook.cpp 2021-01-02 22:39:42.799712909 -0600 -@@ -25,7 +25,8 @@ - #include - #include - --namespace chatterino { -+namespace chatterino -+{ - - Notebook::Notebook(QWidget *parent) - : BaseWidget(parent) -@@ -155,7 +156,7 @@ - else - { - qCDebug(chatterinoWidget) -- << "Notebook: selected child of page doesn't exist anymore"; -+ << "Notebook: selected child of page doesn't exist anymore"; - } - } - } -@@ -182,17 +183,19 @@ - bool Notebook::containsPage(QWidget *page) - { - return std::any_of(this->items_.begin(), this->items_.end(), -- [page](const auto &item) { -- return item.page == page; -- }); -+ [page](const auto &item) -+ { -+ return item.page == page; -+ }); - } - - Notebook::Item &Notebook::findItem(QWidget *page) - { - auto it = std::find_if(this->items_.begin(), this->items_.end(), -- [page](const auto &item) { -- return page == item.page; -- }); -+ [page](const auto &item) -+ { -+ return page == item.page; -+ }); - assert(it != this->items_.end()); - return *it; - } -@@ -200,14 +203,15 @@ - bool Notebook::containsChild(const QObject *obj, const QObject *child) - { - return std::any_of(obj->children().begin(), obj->children().end(), -- [child](const QObject *o) { -- if (o == child) -- { -- return true; -- } -+ [child](const QObject *o) -+ { -+ if (o == child) -+ { -+ return true; -+ } - -- return containsChild(o, child); -- }); -+ return containsChild(o, child); -+ }); - } - - void Notebook::selectIndex(int index) -@@ -493,11 +497,11 @@ - for (int i = colStart; i < colEnd; i++) - { - largestWidth = std::max( -- this->items_.at(i).tab->normalTabWidth(), largestWidth); -+ this->items_.at(i).tab->normalTabWidth(), largestWidth); - } - - if (col == columnCount - 1 && this->showAddButton_ && -- largestWidth == 0) -+ largestWidth == 0) - { - largestWidth = this->addButton_->width(); - } -@@ -620,8 +624,10 @@ - SplitNotebook::SplitNotebook(Window *parent) - : Notebook(parent) - { -- this->connect(this->getAddButton(), &NotebookButton::leftClicked, [this]() { -- QTimer::singleShot(80, this, [this] { -+ this->connect(this->getAddButton(), &NotebookButton::leftClicked, [this]() -+ { -+ QTimer::singleShot(80, this, [this] -+ { - this->addPage(true); - }); - }); -@@ -652,14 +658,16 @@ - settingsBtn->setVisible(!getSettings()->hidePreferencesButton.getValue()); - - getSettings()->hidePreferencesButton.connect( -- [settingsBtn](bool hide, auto) { -- settingsBtn->setVisible(!hide); -- }, -- this->connections_); -+ [settingsBtn](bool hide, auto) -+ { -+ settingsBtn->setVisible(!hide); -+ }, -+ this->connections_); - - settingsBtn->setIcon(NotebookButton::Settings); - -- QObject::connect(settingsBtn, &NotebookButton::leftClicked, [this] { -+ QObject::connect(settingsBtn, &NotebookButton::leftClicked, [this] -+ { - getApp()->windows->showSettingsDialog(this); - }); - -@@ -667,13 +675,15 @@ - auto userBtn = this->addCustomButton(); - userBtn->setVisible(!getSettings()->hideUserButton.getValue()); - getSettings()->hideUserButton.connect( -- [userBtn](bool hide, auto) { -- userBtn->setVisible(!hide); -- }, -- this->connections_); -+ [userBtn](bool hide, auto) -+ { -+ userBtn->setVisible(!hide); -+ }, -+ this->connections_); - - userBtn->setIcon(NotebookButton::User); -- QObject::connect(userBtn, &NotebookButton::leftClicked, [this, userBtn] { -+ QObject::connect(userBtn, &NotebookButton::leftClicked, [this, userBtn] -+ { - getApp()->windows->showAccountSelectPopup( - this->mapToGlobal(userBtn->rect().bottomRight())); - }); -@@ -699,7 +709,7 @@ - auto *selectedPage = this->getSelectedPage(); - - return selectedPage != nullptr ? (SplitContainer *)selectedPage -- : this->addPage(); -+ : this->addPage(); - } - - void SplitNotebook::select(QWidget *page) -diff -Naur src_old/widgets/Notebook.hpp src/widgets/Notebook.hpp ---- src_old/widgets/Notebook.hpp 2021-01-02 22:28:15.365803000 -0600 -+++ src/widgets/Notebook.hpp 2021-01-02 22:39:12.396052080 -0600 -@@ -8,7 +8,8 @@ - #include - #include - --namespace chatterino { -+namespace chatterino -+{ - - class Window; - class UpdateDialog; -@@ -65,7 +66,8 @@ - NotebookButton *addCustomButton(); - - private: -- struct Item { -+ struct Item -+ { - NotebookTab *tab{}; - QWidget *page{}; - QWidget *selectedWidget{}; -diff -Naur src_old/widgets/splits/SplitContainer.cpp src/widgets/splits/SplitContainer.cpp ---- src_old/widgets/splits/SplitContainer.cpp 2021-01-02 22:28:15.366803000 -0600 -+++ src/widgets/splits/SplitContainer.cpp 2021-01-02 23:21:22.541848698 -0600 -@@ -12,6 +12,7 @@ - #include "widgets/helper/ChannelView.hpp" - #include "widgets/helper/NotebookTab.hpp" - #include "widgets/splits/Split.hpp" -+#include "util/streamlinkerino.h" - - #include - #include -@@ -26,7 +27,8 @@ - #include - #include - --namespace chatterino { -+namespace chatterino -+{ - - bool SplitContainer::isDraggingSplit = false; - Split *SplitContainer::draggingSplit = nullptr; -@@ -39,7 +41,8 @@ - { - this->refreshTabTitle(); - -- this->managedConnect(Split::modifierStatusChanged, [this](auto modifiers) { -+ this->managedConnect(Split::modifierStatusChanged, [this](auto modifiers) -+ { - this->layout(); - - if (modifiers == showResizeHandlesModifiers) -@@ -71,7 +74,8 @@ - this->setCursor(Qt::PointingHandCursor); - this->setAcceptDrops(true); - -- this->managedConnect(this->overlay_.dragEnded, [this]() { -+ this->managedConnect(this->overlay_.dragEnded, [this]() -+ { - this->isDragging_ = false; - this->layout(); - }); -@@ -80,6 +84,7 @@ - - this->setMouseTracking(true); - this->setAcceptDrops(true); -+ - } - - NotebookTab *SplitContainer::getTab() const -@@ -121,7 +126,8 @@ - - if (openChannelNameDialog) - { -- split->showChangeChannelPopup("Open channel name", true, [=](bool ok) { -+ split->showChangeChannelPopup("Open channel name", true, [=](bool ok) -+ { - if (!ok) - { - this->deleteSplit(split); -@@ -212,18 +218,21 @@ - this->refreshTab(); - - this->managedConnect(split->getChannelView().tabHighlightRequested, -- [this](HighlightState state) { -- if (this->tab_ != nullptr) -- { -- this->tab_->setHighlightState(state); -- } -- }); -+ [this](HighlightState state) -+ { -+ if (this->tab_ != nullptr) -+ { -+ this->tab_->setHighlightState(state); -+ } -+ }); - -- this->managedConnect(split->getChannelView().liveStatusChanged, [this]() { -+ this->managedConnect(split->getChannelView().liveStatusChanged, [this]() -+ { - this->refreshTabLiveStatus(); - }); - -- this->managedConnect(split->focused, [this, split] { -+ this->managedConnect(split->focused, [this, split] -+ { - this->setSelected(split); - }); - -@@ -232,9 +241,10 @@ - - void SplitContainer::setSelected(Split *split) - { -+ streamlinkerino::write(split->getChannel()->getName()); - // safety - if (std::find(this->splits_.begin(), this->splits_.end(), split) == -- this->splits_.end()) -+ this->splits_.end()) - { - return; - } -@@ -321,9 +331,10 @@ - auto &siblings = node->parent_->children_; - - auto it = std::find_if(siblings.begin(), siblings.end(), -- [node](const auto &other) { -- return other.get() == node; -- }); -+ [node](const auto &other) -+ { -+ return other.get() == node; -+ }); - assert(it != siblings.end()); - - if (direction == Direction::Left || direction == Direction::Above) -@@ -362,32 +373,36 @@ - { - switch (node->type_) - { -- case Node::_Split: { -- node->split_->giveFocus(Qt::OtherFocusReason); -- } -- break; -+ case Node::_Split: -+ { -+ node->split_->giveFocus(Qt::OtherFocusReason); -+ } -+ break; - -- case Node::HorizontalContainer: -- case Node::VerticalContainer: { -- auto &children = node->children_; -+ case Node::HorizontalContainer: -+ case Node::VerticalContainer: -+ { -+ auto &children = node->children_; - -- auto it = std::find_if( -- children.begin(), children.end(), [node](const auto &other) { -- return node->preferedFocusTarget_ == other.get(); -- }); -+ auto it = std::find_if( -+ children.begin(), children.end(), [node](const auto &other) -+ { -+ return node->preferedFocusTarget_ == other.get(); -+ }); - -- if (it != children.end()) -- { -- this->focusSplitRecursive(it->get()); -- } -- else -- { -- this->focusSplitRecursive(node->children_.front().get()); -- } -+ if (it != children.end()) -+ { -+ this->focusSplitRecursive(it->get()); - } -- break; -+ else -+ { -+ this->focusSplitRecursive(node->children_.front().get()); -+ } -+ } -+ break; - -- default:; -+ default: -+ ; - } - } - -@@ -395,17 +410,18 @@ - { - switch (node.getType()) - { -- case Node::_Split: -- return node.getSplit(); -- case Node::VerticalContainer: -- if (!node.getChildren().empty()) -- return getTopRightSplit(*node.getChildren().front()); -- break; -- case Node::HorizontalContainer: -- if (!node.getChildren().empty()) -- return getTopRightSplit(*node.getChildren().back()); -- break; -- default:; -+ case Node::_Split: -+ return node.getSplit(); -+ case Node::VerticalContainer: -+ if (!node.getChildren().empty()) -+ return getTopRightSplit(*node.getChildren().front()); -+ break; -+ case Node::HorizontalContainer: -+ if (!node.getChildren().empty()) -+ return getTopRightSplit(*node.getChildren().back()); -+ break; -+ default: -+ ; - } - return nullptr; - } -@@ -527,9 +543,10 @@ - { - auto it = - std::find_if(this->dropRects_.begin(), this->dropRects_.end(), -- [event](DropRect &rect) { -- return rect.rect.contains(event->pos()); -- }); -+ [event](DropRect &rect) -+ { -+ return rect.rect.contains(event->pos()); -+ }); - if (it != this->dropRects_.end()) - { - this->insertSplit(new Split(this), it->position); -@@ -624,8 +641,8 @@ - - QBrush accentColor = - (QApplication::activeWindow() == this->window() -- ? this->theme->tabs.selected.backgrounds.regular -- : this->theme->tabs.selected.backgrounds.unfocused); -+ ? this->theme->tabs.selected.backgrounds.regular -+ : this->theme->tabs.selected.backgrounds.unfocused); - - painter.fillRect(0, 0, width(), 1, accentColor); - } -@@ -861,7 +878,7 @@ - } - - const std::vector> -- &SplitContainer::Node::getChildren() -+ &SplitContainer::Node::getChildren() - { - return this->children_; - } -@@ -888,9 +905,10 @@ - } - - return std::any_of(this->children_.begin(), this->children_.end(), -- [_node](std::unique_ptr &n) { -- return n->isOrContainsNode(_node); -- }); -+ [_node](std::unique_ptr &n) -+ { -+ return n->isOrContainsNode(_node); -+ }); - } - - SplitContainer::Node *SplitContainer::Node::findNodeContainingSplit( -@@ -914,28 +932,32 @@ - } - - void SplitContainer::Node::insertSplitRelative(Split *_split, -- Direction _direction) -+ Direction _direction) - { - if (this->parent_ == nullptr) - { - switch (this->type_) - { -- case Node::EmptyRoot: { -- this->setSplit(_split); -- } -- break; -- case Node::_Split: { -- this->nestSplitIntoCollection(_split, _direction); -- } -- break; -- case Node::HorizontalContainer: { -- this->nestSplitIntoCollection(_split, _direction); -- } -- break; -- case Node::VerticalContainer: { -- this->nestSplitIntoCollection(_split, _direction); -- } -- break; -+ case Node::EmptyRoot: -+ { -+ this->setSplit(_split); -+ } -+ break; -+ case Node::_Split: -+ { -+ this->nestSplitIntoCollection(_split, _direction); -+ } -+ break; -+ case Node::HorizontalContainer: -+ { -+ this->nestSplitIntoCollection(_split, _direction); -+ } -+ break; -+ case Node::VerticalContainer: -+ { -+ this->nestSplitIntoCollection(_split, _direction); -+ } -+ break; - } - return; - } -@@ -953,7 +975,7 @@ - } - - void SplitContainer::Node::nestSplitIntoCollection(Split *_split, -- Direction _direction) -+ Direction _direction) - { - if (toContainerType(_direction) == this->type_) - { -@@ -997,9 +1019,10 @@ - } - - auto it = -- std::find_if(siblings.begin(), siblings.end(), [this](auto &node) { -- return this == node.get(); -- }); -+ std::find_if(siblings.begin(), siblings.end(), [this](auto &node) -+ { -+ return this == node.get(); -+ }); - - assert(it != siblings.end()); - if (_direction == Direction::Right || _direction == Direction::Below) -@@ -1040,9 +1063,10 @@ - auto &siblings = this->parent_->children_; - - auto it = -- std::find_if(begin(siblings), end(siblings), [this](auto &node) { -- return this == node.get(); -- }); -+ std::find_if(begin(siblings), end(siblings), [this](auto &node) -+ { -+ return this == node.get(); -+ }); - assert(it != siblings.end()); - - Position position; -@@ -1053,7 +1077,7 @@ - if (this->parent_->type_ == Type::VerticalContainer) - { - position.direction_ = siblings.begin() == it ? Direction::Above -- : Direction::Below; -+ : Direction::Below; - } - else - { -@@ -1067,7 +1091,7 @@ - _parent->type_ = sibling->type_; - _parent->split_ = sibling->split_; - std::vector> nodes = -- std::move(sibling->children_); -+ std::move(sibling->children_); - for (auto &node : nodes) - { - node->parent_ = _parent; -@@ -1080,8 +1104,8 @@ - { - position.direction_ = - this->parent_->type_ == Type::VerticalContainer -- ? Direction::Below -- : Direction::Right; -+ ? Direction::Below -+ : Direction::Right; - siblings.erase(it); - position.relativeNode_ = siblings.back().get(); - } -@@ -1090,8 +1114,8 @@ - position.relativeNode_ = (it + 1)->get(); - position.direction_ = - this->parent_->type_ == Type::VerticalContainer -- ? Direction::Above -- : Direction::Left; -+ ? Direction::Above -+ : Direction::Left; - siblings.erase(it); - } - } -@@ -1114,9 +1138,10 @@ - { - return std::accumulate(this->children_.begin(), this->children_.end(), - qreal(0), -- [=](qreal val, std::unique_ptr &node) { -- return val + node->getFlex(isVertical); -- }); -+ [=](qreal val, std::unique_ptr &node) -+ { -+ return val + node->getFlex(isVertical); -+ }); - } - - void SplitContainer::Node::layout(bool addSpacing, float _scale, -@@ -1133,156 +1158,159 @@ - - switch (this->type_) - { -- case Node::_Split: { -- QRect rect = this->geometry_.toRect(); -- this->split_->setGeometry( -- rect.marginsRemoved(QMargins(1, 1, 0, 0))); -- } -- break; -- case Node::VerticalContainer: -- case Node::HorizontalContainer: { -- bool isVertical = this->type_ == Node::VerticalContainer; -+ case Node::_Split: -+ { -+ QRect rect = this->geometry_.toRect(); -+ this->split_->setGeometry( -+ rect.marginsRemoved(QMargins(1, 1, 0, 0))); -+ } -+ break; -+ case Node::VerticalContainer: -+ case Node::HorizontalContainer: -+ { -+ bool isVertical = this->type_ == Node::VerticalContainer; -+ -+ // vars -+ qreal minSize = qreal(48 * _scale); -+ -+ qreal totalFlex = std::max( -+ 0.0001, this->getChildrensTotalFlex(isVertical)); -+ qreal totalSize = std::accumulate( -+ this->children_.begin(), this->children_.end(), qreal(0), -+ [=](int val, std::unique_ptr &node) -+ { -+ return val + std::max( -+ this->getSize(isVertical) / -+ std::max(0.0001, totalFlex) * -+ node->getFlex(isVertical), -+ minSize); -+ }); - -- // vars -- qreal minSize = qreal(48 * _scale); -+ totalSize = std::max(0.0001, totalSize); - -- qreal totalFlex = std::max( -- 0.0001, this->getChildrensTotalFlex(isVertical)); -- qreal totalSize = std::accumulate( -- this->children_.begin(), this->children_.end(), qreal(0), -- [=](int val, std::unique_ptr &node) { -- return val + std::max( -- this->getSize(isVertical) / -- std::max(0.0001, totalFlex) * -- node->getFlex(isVertical), -- minSize); -- }); -- -- totalSize = std::max(0.0001, totalSize); -- -- qreal sizeMultiplier = this->getSize(isVertical) / totalSize; -- QRectF childRect = this->geometry_; -+ qreal sizeMultiplier = this->getSize(isVertical) / totalSize; -+ QRectF childRect = this->geometry_; - -- // add spacing if reqested -- if (addSpacing) -- { -- qreal offset = std::min(this->getSize(!isVertical) * 0.1, -- qreal(_scale * 24)); -+ // add spacing if reqested -+ if (addSpacing) -+ { -+ qreal offset = std::min(this->getSize(!isVertical) * 0.1, -+ qreal(_scale * 24)); -+ -+ // droprect left / above -+ dropRects.emplace_back( -+ QRectF(this->geometry_.left(), this->geometry_.top(), -+ isVertical ? offset : this->geometry_.width(), -+ isVertical ? this->geometry_.height() : offset) -+ .toRect(), -+ Position(this, -+ isVertical ? Direction::Left : Direction::Above)); - -- // droprect left / above -+ // droprect right / below -+ if (isVertical) -+ { - dropRects.emplace_back( -- QRectF(this->geometry_.left(), this->geometry_.top(), -- isVertical ? offset : this->geometry_.width(), -- isVertical ? this->geometry_.height() : offset) -- .toRect(), -- Position(this, -- isVertical ? Direction::Left : Direction::Above)); -- -- // droprect right / below -- if (isVertical) -- { -- dropRects.emplace_back( -- QRectF(this->geometry_.right() - offset, -- this->geometry_.top(), offset, -- this->geometry_.height()) -- .toRect(), -- Position(this, Direction::Right)); -- } -- else -- { -- dropRects.emplace_back( -- QRectF(this->geometry_.left(), -- this->geometry_.bottom() - offset, -- this->geometry_.width(), offset) -- .toRect(), -- Position(this, Direction::Below)); -- } -+ QRectF(this->geometry_.right() - offset, -+ this->geometry_.top(), offset, -+ this->geometry_.height()) -+ .toRect(), -+ Position(this, Direction::Right)); -+ } -+ else -+ { -+ dropRects.emplace_back( -+ QRectF(this->geometry_.left(), -+ this->geometry_.bottom() - offset, -+ this->geometry_.width(), offset) -+ .toRect(), -+ Position(this, Direction::Below)); -+ } - -- // shrink childRect -- if (isVertical) -- { -- childRect.setLeft(childRect.left() + offset); -- childRect.setRight(childRect.right() - offset); -- } -- else -- { -- childRect.setTop(childRect.top() + offset); -- childRect.setBottom(childRect.bottom() - offset); -- } -+ // shrink childRect -+ if (isVertical) -+ { -+ childRect.setLeft(childRect.left() + offset); -+ childRect.setRight(childRect.right() - offset); -+ } -+ else -+ { -+ childRect.setTop(childRect.top() + offset); -+ childRect.setBottom(childRect.bottom() - offset); - } -+ } - -- // iterate children -- auto pos = int(isVertical ? childRect.top() : childRect.left()); -- for (std::unique_ptr &child : this->children_) -- { -- // set rect -- QRect rect = childRect.toRect(); -- if (isVertical) -- { -- rect.setTop(pos); -- rect.setHeight( -- std::max(this->geometry_.height() / totalFlex * -- child->flexV_, -- minSize) * -- sizeMultiplier); -- } -- else -- { -- rect.setLeft(pos); -- rect.setWidth(std::max(this->geometry_.width() / -- totalFlex * child->flexH_, -- minSize) * -- sizeMultiplier); -- } -+ // iterate children -+ auto pos = int(isVertical ? childRect.top() : childRect.left()); -+ for (std::unique_ptr &child : this->children_) -+ { -+ // set rect -+ QRect rect = childRect.toRect(); -+ if (isVertical) -+ { -+ rect.setTop(pos); -+ rect.setHeight( -+ std::max(this->geometry_.height() / totalFlex * -+ child->flexV_, -+ minSize) * -+ sizeMultiplier); -+ } -+ else -+ { -+ rect.setLeft(pos); -+ rect.setWidth(std::max(this->geometry_.width() / -+ totalFlex * child->flexH_, -+ minSize) * -+ sizeMultiplier); -+ } - -- if (child == this->children_.back()) -- { -- rect.setRight(childRect.right() - 1); -- rect.setBottom(childRect.bottom() - 1); -- } -+ if (child == this->children_.back()) -+ { -+ rect.setRight(childRect.right() - 1); -+ rect.setBottom(childRect.bottom() - 1); -+ } - -- child->geometry_ = rect; -- child->layout(addSpacing, _scale, dropRects, resizeRects); -+ child->geometry_ = rect; -+ child->layout(addSpacing, _scale, dropRects, resizeRects); - -- pos += child->getSize(isVertical); -+ pos += child->getSize(isVertical); - -- // add resize rect -- if (child != this->children_.front()) -- { -- QRectF r = isVertical ? QRectF(this->geometry_.left(), -- child->geometry_.top() - 4, -- this->geometry_.width(), 8) -- : QRectF(child->geometry_.left() - 4, -- this->geometry_.top(), 8, -- this->geometry_.height()); -- resizeRects.push_back( -- ResizeRect(r.toRect(), child.get(), isVertical)); -- } -+ // add resize rect -+ if (child != this->children_.front()) -+ { -+ QRectF r = isVertical ? QRectF(this->geometry_.left(), -+ child->geometry_.top() - 4, -+ this->geometry_.width(), 8) -+ : QRectF(child->geometry_.left() - 4, -+ this->geometry_.top(), 8, -+ this->geometry_.height()); -+ resizeRects.push_back( -+ ResizeRect(r.toRect(), child.get(), isVertical)); -+ } - -- // normalize flex -- if (isVertical) -- { -- child->flexV_ = -- child->flexV_ / totalFlex * this->children_.size(); -- child->flexH_ = 1; -- } -- else -- { -- child->flexH_ = -- child->flexH_ / totalFlex * this->children_.size(); -- child->flexV_ = 1; -- } -+ // normalize flex -+ if (isVertical) -+ { -+ child->flexV_ = -+ child->flexV_ / totalFlex * this->children_.size(); -+ child->flexH_ = 1; -+ } -+ else -+ { -+ child->flexH_ = -+ child->flexH_ / totalFlex * this->children_.size(); -+ child->flexV_ = 1; - } - } -- break; -+ } -+ break; - } - } - - SplitContainer::Node::Type SplitContainer::Node::toContainerType(Direction _dir) - { - return _dir == Direction::Left || _dir == Direction::Right -- ? Type::HorizontalContainer -- : Type::VerticalContainer; -+ ? Type::HorizontalContainer -+ : Type::VerticalContainer; - } - - // -@@ -1441,9 +1469,10 @@ - - auto &siblings = node->parent_->getChildren(); - auto it = std::find_if(siblings.begin(), siblings.end(), -- [this](const std::unique_ptr &n) { -- return n.get() == this->node; -- }); -+ [this](const std::unique_ptr &n) -+ { -+ return n.get() == this->node; -+ }); - - assert(it != siblings.end()); - Node *before = siblings[it - siblings.begin() - 1].get(); -@@ -1451,16 +1480,16 @@ - QPoint topLeft = - this->parent->mapToGlobal(before->geometry_.topLeft().toPoint()); - QPoint bottomRight = this->parent->mapToGlobal( -- this->node->geometry_.bottomRight().toPoint()); -+ this->node->geometry_.bottomRight().toPoint()); - - int globalX = topLeft.x() > event->globalX() -- ? topLeft.x() -- : (bottomRight.x() < event->globalX() ? bottomRight.x() -- : event->globalX()); -+ ? topLeft.x() -+ : (bottomRight.x() < event->globalX() ? bottomRight.x() -+ : event->globalX()); - int globalY = topLeft.y() > event->globalY() -- ? topLeft.y() -- : (bottomRight.y() < event->globalY() ? bottomRight.y() -- : event->globalY()); -+ ? topLeft.y() -+ : (bottomRight.y() < event->globalY() ? bottomRight.y() -+ : event->globalY()); - - QPoint mousePoint(globalX, globalY); - -diff -Naur src_old/widgets/splits/SplitContainer.hpp src/widgets/splits/SplitContainer.hpp ---- src_old/widgets/splits/SplitContainer.hpp 2021-01-02 22:28:15.366803000 -0600 -+++ src/widgets/splits/SplitContainer.hpp 2021-01-02 22:28:49.993239456 -0600 -@@ -15,10 +15,13 @@ - #include - #include - #include -+#include -+#include - - class QJsonObject; - --namespace chatterino { -+namespace chatterino -+{ - - class Split; - class NotebookTab; -@@ -39,7 +42,8 @@ - // fourtf: !!! preserve the order of left, up, right and down - enum Direction { Left, Above, Right, Below }; - -- struct Position final { -+ struct Position final -+ { - private: - Position() = default; - Position(Node *relativeNode, Direction direcion) -@@ -56,7 +60,8 @@ - }; - - private: -- struct DropRect final { -+ struct DropRect final -+ { - QRect rect; - Position position; - -@@ -67,7 +72,8 @@ - } - }; - -- struct ResizeRect final { -+ struct ResizeRect final -+ { - QRect rect; - Node *node; - bool vertical; -@@ -81,7 +87,8 @@ - }; - - public: -- struct Node final { -+ struct Node final -+ { - enum Type { EmptyRoot, _Split, VerticalContainer, HorizontalContainer }; - - Type getType(); -@@ -230,7 +237,8 @@ - void refreshTabTitle(); - void refreshTabLiveStatus(); - -- struct DropRegion { -+ struct DropRegion -+ { - QRect rect; - std::pair position; - diff --git a/README.md b/README.md index fa468de..ae0c6b5 100644 --- a/README.md +++ b/README.md @@ -1,107 +1,52 @@ -## Official Chatterino Implementation +## Streamlinkerino for Windows -Integration of this application into a first-class chatterino feature is currently in process. Pull request can be [viewed here](https://github.com/Chatterino/chatterino2/pull/2854) +This project is an unofficial window porting of Streamlinkerino. For more information, take a look at [Official Streamlinkerino README page](https://github.com/JohnCiubuc/StreamLinkerino#official-chatterino-implementation). -## Testing Branch +## Experimental Features -**Testing branch does not require chatterino to be patched**, but it does have a considerable delay (5-10s) on switching streams due to chatterino json writing frequency. +Windows port of Streamlinkerino provides experimental features. As porting isn't completely done, some features from original project may be possible and some maybe not. -## Pre-Release Windows Port +### Known Issues and bugs -[dewcked](https://github.com/dewcked) is working on a windows port version of this project. Repository can be viewed here: [https://github.com/dewcked/StreamLinkerino/releases](https://github.com/dewcked/StreamLinkerino/releases) +1. Streamlinkerino implementation use `--wid` argument to execute streamlink with mpv player, but in Windows original features of mpv player is not possible. +2. Focusing problem occurs between embeded chatterino window and main window. So if shortcuts doesn't work, just click title bar or screen (not Chatterino area) once. Also, **double-click Chatterino channel to load stream** function makes main window lose focus. +3. Currently, `load stream` function needs specific logic that prompts and close a window briefly. +4. Don't ever try to touch minimize button in embedded Chatterino. If you did so, only way to recover Chatterino is to restart or click `load stream` from context menu. +5. Settings and screen messages doesn't work properly. Gonna fix this soon.. -## streamlinkerino -Ever wanted to use StreamLink + MPV + Chatterino all in one application? Look no further, as this project has your solution! +### Features -streamlinkerino embeds MPV (using [StreamLink](https://streamlink.github.io/index.html) as its base) and [Chatterino](https://chatterino.com/) into one application. If you patch Chatterino [(Git Link)](https://github.com/Chatterino/chatterino2) with the supplied patch file, the MPV stream will auto-update if you switch channels in Chatterino! +- press `f` or `double-click screen` to enable/disable fullscreen mode +- press `w` to enable/disable frameless window mode +- press `esc` to disable fullscreen +- `double-click Chatterino channel` to load stream +- right-click screen to open context menu + - You can open `Settings`, `Load Stream` or `Close` application -## Why does this application exist +### Usabilities -This project was created for the following reasons: -1. Avoiding usage of the twitch web application - * It is unfortunately laggy and resource heavy - * Embedded stream ads can not be avoided - * Twitch Player is feature poor (compared to other players) - * Several bug with streams not loading when using third party extensions like FFZ/BTTV -2. Avoiding Electron / JS applications - * Alternatives such as [StreamLink Twitch GUI](https://streamlink.github.io/streamlink-twitch-gui/) do exist, but you still have to deal with the resource heaviness of electron-based apps and the website itself -3. Performance - * The base app and chatterino is C++/Qt. The Player is MPV and StreamLink is used for an ad-free stream experience. - * The result is a light weight, high performance twitch client +- Windows dark theme supported. To turn on this, press `Window logo` button and search setting `dark mode`. This is supported only for upper than specific version of Windows 10 +- Auto screen resize + - Responsive for both resizing Chatterino and main window +- Fit screen edge and adjusted screen position to center +- Draggable screen (Like default window drag behavior) - -## Features +## Building -1. High performance, low memory, and responsive +[My gist](https://gist.github.com/dewcked) shows how to Install Qt framework with Aqtinstall in Windows. Also, for code formatting. I recommend you to use Qt Creator for editting source codes. -2. No mid-roll ads (Thanks streamlink!) +To compile your project, just clck build button at bottom-left edge. You can find build directory in Project tab (in left area). -3. MPV player features: +To deploy this to normal users, you need to use `windeployqt.exe` in Qt directory. In my case, Qt directory was `C:\Qt\6.3.0\msvc2019_64\bin`. - * Pause - * Rewind - * Fast forward - * And more! +move executable file (generated from build) into empty path and run command like below: -4. Streamlink and Chatterino Integration +``` +C:\Qt\6.3.0\msvc2019_64\bin\windeployqt.exe +``` -5. Seamless stream switching (Chatterino patch required) +then everything is done automatically. Just ZIP it and deploy. -## Building - -Building streamlinkerino - -Contributed by [dewcked](https://github.com/dewcked) from [Release #22](https://github.com/JohnCiubuc/StreamLinkerino/issues/22): - - -1. Install docker and git -2. `git clone https://github.com/JohnCiubuc/streamlinkerino.git --recurse-submodules` -3. `git submodule update --recursive --remote` -4. `cd streamlinkerino` -5. Pull docker image from [Here](https://github.com/OlivierLDff/QtLinuxCMakeDocker). - * Linux) `docker run -it --rm -v $(pwd):/src/ --device /dev/fuse --cap-add SYS_ADMIN --security-opt apparmor:unconfined reivilo1234/qt-linux-cmake:qt5.15.1 bash` - * Windows) `docker run -it --rm -v %CD%:/src/ --device /dev/fuse --cap-add SYS_ADMIN --security-opt apparmor:unconfined reivilo1234/qt-linux-cmake:qt5.15.1 bash` -6. `mkdir build && cd build` -7. `cmake ../src && make` - - -## Patching Chatterino - -1. Copy `chatterino.patch` from the streamlinkerino Patch directory, into chatterino's submodule project directory (same location as `chatterino.pro`) -2. `patch -p0 < chatterino.patch` -3. Create build folder `mkdir build && cd build` -4. Go into `build` directory -5. `qmake .. && make` - - -## TODO: - -~~1. Detect if streamlink is installed, and if not, prompt user~~ - -~~2. Detect if chatterino is installed, and if not, prompt user~~ - -~~3. On switching streams, setup two mpv clients -- one playing current stream and the other loading new stream. When new stream finishes loading, swap to the new client~~ - -4. Currently can only change stream based on chatterino. Add option to change stream ignoring chatterino (or if chatterino isn't patched) - -5. Resize chatterino window - -~~6. Create a settings dialog to specify streamlink/chatterino location and settings~~ - -~~7. Link the settings button in chatterino (if patched) with the settings dialogue in streamlinkerino~~ - -8. Auto apply patch and build chatterino together with streamlinkerino via CMakeLists - - - ## Screenshots -![ss1](https://github.com/JohnCiubuc/JohnCiubucGifs/raw/main/screenshots/streamlinkerino1.png) - -(Older Version) -

- -

-(Current Version) -

- -

+ +Not yet :) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt deleted file mode 100644 index f159466..0000000 --- a/src/CMakeLists.txt +++ /dev/null @@ -1,38 +0,0 @@ -project(streamlinkerino) - -set(CMAKE_CXX_STANDARD 11) -cmake_minimum_required(VERSION 3.18) - -# Find includes in corresponding build directories -set(CMAKE_INCLUDE_CURRENT_DIR ON) -# Instruct CMake to run moc automatically when needed. -set(CMAKE_AUTOMOC ON) -# Instruct CMake to run uic automatically when needed. -set(CMAKE_AUTOUIC ON) - - - -find_package(Qt5Core REQUIRED) -find_package(Qt5Widgets REQUIRED) -find_package(X11 REQUIRED) - -set(SOURCES - main.cpp - MainWindow.cpp - WindowsMatchingPID.cpp - Submodules.cpp -) -set(HEADERS - MainWindow.h - WindowsMatchingPID.h - Submodules.h -) -set(UI -MainWindow.ui -SubmodulesDialog.ui) - - - - -add_executable(streamlinkerino ${SOURCES} ${HEADERS} ${UI}) -target_link_libraries(streamlinkerino Qt5::Widgets X11) diff --git a/src/ChatterinoMonitor.cpp b/src/ChatterinoMonitor.cpp deleted file mode 100644 index 447972b..0000000 --- a/src/ChatterinoMonitor.cpp +++ /dev/null @@ -1,7 +0,0 @@ -#include "ChatterinoMonitor.h" - -ChatterinoMonitor::ChatterinoMonitor(QObject *parent) - : QObject{parent} -{ - -} diff --git a/src/ChatterinoMonitor.h b/src/ChatterinoMonitor.h deleted file mode 100644 index f52c6d5..0000000 --- a/src/ChatterinoMonitor.h +++ /dev/null @@ -1,18 +0,0 @@ -#ifndef CHATTERINOMONITOR_H -#define CHATTERINOMONITOR_H - -#include -#include -#include - -class ChatterinoMonitor : public QObject -{ - Q_OBJECT -public: - explicit ChatterinoMonitor(QObject *parent = nullptr); - -signals: - -}; - -#endif // CHATTERINOMONITOR_H diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp index 8b3646e..85c7de2 100644 --- a/src/MainWindow.cpp +++ b/src/MainWindow.cpp @@ -1,299 +1,516 @@ -#include "MainWindow.h" -#include "ui_MainWindow.h" +#include "mainwindow.h" +#include "ui_mainwindow.h" - -MainWindow::MainWindow(QWidget *parent) - : QMainWindow(parent) - , ui(new Ui::MainWindow) +// Constructor +MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow) { - ui->setupUi(this); + // Setup UI + ui->setupUi(this); + MainWindow::setupUI(); - ui->menubar->hide(); - ui->statusbar->hide(); - ui->textEdit_SwitchAlert->hide(); + // Setup Modules + _Submodules = new Submodules::SubmodulesDialog; + connect(_Submodules, &Submodules::SubmodulesDialog::settingFinished, this, &MainWindow::initialize); + connect(_Submodules, &Submodules::SubmodulesDialog::refreshStream, this, &MainWindow::refreshStream); + + // Setup right-click context menu + MainWindow::setupCustomContextMenu(); - // Run external tool checker - _Submodules = new Submodules::SubmodulesDialog; - connect(_Submodules, &Submodules::SubmodulesDialog::submodulesFinished, this, &MainWindow::initialize); - connect(_Submodules, &Submodules::SubmodulesDialog::refreshStream, this, &MainWindow::refreshStream); + // Init submodules + _Submodules->initialize(); - _Submodules->initialize(); + // Setup Event handler (needed for MainWindow::eventFilter) + qApp->installEventFilter(this); } -// Kill StreamLink and Chatterino on exit +// Deconstructor MainWindow::~MainWindow() { - _pChatterinoProcess->terminate(); - _pStreamlinkProcess.at(0)->terminate(); - _pStreamlinkProcess.at(1)->terminate(); - delete ui; + delete ui; } -// Run until Chatterino is launched -// Get winID from Chatterino Process -// Embed Chatterino into App -// Monitor /tmp for chat changes (if patched) -void MainWindow::setupChatterinoEmbed() +void MainWindow::setupUI() { - _chatterinoUUID.clear(); - QTimer * findChatterino = new QTimer; - connect(findChatterino, &QTimer::timeout, this, [=]() - { - Window result = _WMP.getWID(_pChatterinoProcess->processId()); - if(result != 0) - { - QWindow *window = QWindow::fromWinId(result); - _chatContainer = createWindowContainer(window); - _chatContainer->setParent(ui->widget); - _chatContainer->setSizePolicy(QSizePolicy::Policy::Expanding,QSizePolicy::Policy::Expanding); - _chatContainer->show(); - - resizeEmbeds(); - - _bChatterinoEmbedded = true; - _tChatChannelMonitor->start(500); - findChatterino->deleteLater(); - } - }); - findChatterino->start(10); + // TODO: Setup dark title bar + if (WinDark::isDarkTheme()) + WinDark::setDark_Titlebar(reinterpret_cast(winId())); + + // Hide menubar & statusbar + this->setWindowTitle(QString("Streamlinkerino")); + ui->menubar->hide(); + ui->statusbar->hide(); + + // Load screen setting from registry. + QSettings settings = QSettings(QSettings::NativeFormat, QSettings::UserScope, "streamlinkerino", "streamlinkerino"); // Registry setting + settings.beginGroup("screen"); + QSizeF screenSize = QGuiApplication::primaryScreen()->virtualSize(); + quint32 screenPos_x = settings.value("screenpos_x", 0).toUInt(); + quint32 screenPos_y = settings.value("screenpos_y", 0).toUInt(); + quint32 screenSize_x = settings.value("screensize_x", 0).toUInt(); + quint32 screenSize_y = settings.value("screensize_y", 0).toUInt(); + settings.endGroup(); + if (screenPos_x > 0 && screenPos_y > 0) + this->move(screenPos_x, screenPos_y); + if (screenSize_x >= 500 && screenSize_x <= screenSize.width() && screenSize_y <= screenSize.height()) + this->resize(screenSize_x, screenSize_y); + else + { + this->resize(screenSize.width() * 0.7, screenSize.height() * 0.7); + } } -// Requires patch to chatterino to monitor channel chages -// Would use QFileSystemWatcher but it is bugged for linux -void MainWindow::chatterinoMonitor() +// Setup custom context menu +void MainWindow::setupCustomContextMenu() { - QFile channel("/tmp/chatterino_chan"); - if(channel.open(QFile::ReadOnly)) - { - // sChans - // @Arg1: New Channel - // @Arg2: Unique UUID for each program (if running multiple) - QList sChans = channel.readAll().split(':'); - - // Invalid communication - if(sChans.size() < 2) - { - db "Invalid Coms - " << sChans; - channel.remove(); - return; - } - - // Initiate and save Chatterino UUID unique to this client - if(_chatterinoUUID.isEmpty()) - _chatterinoUUID = sChans[1]; - - channel.close(); - // Communication is to this instant of streamlinkerino - if(sChans[1] == _chatterinoUUID) - { - // Show Settings Dialog button clicked in chatterino - // Open settings here too - if(sChans[0] == "settings-showdialog") - { - _Submodules->showDialog(); -// QTimer::singleShot(250, _Submodules, &Submodules::SubmodulesDialog::showDialog); - } - // Otherwise a channel change was requested - else if (sChans[0] != _cChatChannel) - { - _cChatChannel = sChans[0]; + qDebug() << "MainWindow::setupCustomContextMenu()"; + // Generate Menu + _contextMenuActions << new QAction("Settings", this) << new QAction("Load Stream", this) << new QAction("Quit", this) << new QAction("Experimental", this); + connect(_contextMenuActions[0], SIGNAL(triggered()), _Submodules, SLOT(showDialog())); + connect(_contextMenuActions[1], SIGNAL(triggered()), this, SLOT(loadStream())); + connect(_contextMenuActions[2], SIGNAL(triggered()), this, SLOT(close())); + connect(_contextMenuActions[3], SIGNAL(triggered()), this, SLOT(experimental())); + _contextMenu = new QMenu(); + _contextMenu->addActions(_contextMenuActions); +} - ui->textEdit_SwitchAlert->setHtml(generateStatusHTML()); - ui->textEdit_SwitchAlert->show(); - resizeEmbeds(); +void MainWindow::quitChatterino() +{ + // Setting part (need to revert) + qDebug2() << "MainWindow::quitChatterino()"; + _lChatterinoLock = true; + _wChatterinoWindow->setParent(nullptr); + _pChatterinoProcess->terminate(); +} - if(_bStreamlinkAllowSwitching) - { - _bStreamLinkProcessSelector = !_bStreamLinkProcessSelector; - _bStreamlinkAllowSwitching = !_bStreamlinkAllowSwitching; - } -// _mpvContainer->setParent(NULL); -// ui->statusbar->show(); - _pStreamlinkProcess.at(_bStreamLinkProcessSelector)->terminate(); - -// Wait for current streamlink to die before restarting - QTimer * restart = new QTimer(this); - connect(restart, &QTimer::timeout, this, [=]() - { - if(_pStreamlinkProcess.at(_bStreamLinkProcessSelector)->state() == 0) - { - _pStreamlinkProcess.at(_bStreamLinkProcessSelector)->setArguments(_Submodules->getStreamLinkArguments(_cChatChannel, _mpvContainerWID)); - _pStreamlinkProcess.at(_bStreamLinkProcessSelector)->start(); +void MainWindow::closeEvent(QCloseEvent *event) +{ + MainWindow::quitChatterino(); + _pStreamlinkProcess.at(0)->kill(); // terminate doesn't work properly on Windows + _pStreamlinkProcess.at(1)->kill(); // terminate doesn't work properly on Windows + QTimer *exit = new QTimer(); + connect(exit, &QTimer::timeout, this, + [=]() + { + if (_pChatterinoProcess->state() == QProcess::NotRunning && _pStreamlinkProcess.at(0)->state() == QProcess::NotRunning && _pStreamlinkProcess.at(0)->state() == QProcess::NotRunning) + event->accept(); + }); + exit->start(10); +} - restart->deleteLater(); - } - }); - restart->start(10); - } - } - // Delete file to avoid duplicated commands - // Also, ironically, allows duplicated commands to come though - // I.e. if settings in chatterino is clicked twice - channel.remove(); +// Show context menu +void MainWindow::showContextMenu(const QPoint &pos) // this is a slot +{ + qDebugSlot() << "MainWindow::showContextMenu(const QPoint &pos)"; + // Temporary fix for bug: Context menu popups twice for first attempt + static int cnt = 0; + if (cnt++ == 1) + return; + else + { + _bIsMenuOff = false; + _contextMenu->exec(pos); + _bIsMenuOff = true; + } +} + +// Handle every input events +// Toggle Frameless Window & Fullscreen +// TODO: When toggling FramelessWindowHint, Focus is not working properly - https://doc.qt.io/archives/qt-4.8/qwidget.html#clearFocus +bool MainWindow::eventFilter(QObject *obj, QEvent *event) +{ + + if (event->type() == QEvent::KeyPress && obj->isWindowType()) + { + QKeyEvent *keyEvent = static_cast(event); + switch (keyEvent->key()) + { + case Qt::Key_W: + if (this->windowFlags().testFlag(Qt::FramelessWindowHint)) + this->setWindowFlag(Qt::FramelessWindowHint, false); + else + this->setWindowFlag(Qt::FramelessWindowHint, true); + show(); + break; + case Qt::Key_F: + if (this->windowState() == Qt::WindowFullScreen) + this->setWindowState(Qt::WindowNoState); + else + this->setWindowState(Qt::WindowFullScreen); + break; + case Qt::Key_Escape: + if (this->windowState() == Qt::WindowFullScreen) + this->setWindowState(Qt::WindowNoState); + break; + // case Qt::Key_Plus: + // controlVolume(_mpvContainer->winId(), true, 10); + // break; + } + // qDebug() << "key " << keyEvent->key() << "from" << obj; + } + else if (event->type() == QEvent::MouseButtonPress && obj->isWindowType() && _bIsMenuOff && this->isActiveWindow()) + { + QMouseEvent *mouseEvent = static_cast(event); + if (mouseEvent->button() == Qt::LeftButton) + { + qDebug() << "Pressed!"; + // _dragMainWindowPos = this->pos(); + // _dragMousePos = mouseEvent->globalPosition().toPoint(); + this->windowHandle()->startSystemMove(); } + // qDebug() << this->x() + this->width() - 100 << this->y() + 50 << mouseEvent->globalPosition().toPoint(); + } + else if (event->type() == QEvent::MouseButtonRelease && obj->isWindowType()) + { + QMouseEvent *mouseEvent = static_cast(event); + qDebug() << "Released!" << mouseEvent->button(); + qDebug() << event << obj; + if (mouseEvent->button() == Qt::LeftButton) + { + qDebug() << mapToGlobal(ui->widget->pos()) << mouseEvent->globalPosition().toPoint(); + QPoint calc = mapToGlobal(ui->widget->pos()) - mouseEvent->globalPosition().toPoint(); + if (calc.x() < 0 && calc.y() < 0) + { + quitChatterino(); + loadStream(); + } + } + else if (mouseEvent->button() == Qt::RightButton) + { + showContextMenu(mouseEvent->globalPosition().toPoint()); + } + } + else if (event->type() == QEvent::MouseButtonDblClick) + { + if (this->windowState() == Qt::WindowFullScreen) + this->setWindowState(Qt::WindowNoState); + else + this->setWindowState(Qt::WindowFullScreen); + } + return QObject::eventFilter(obj, event); } +// void MainWindow::controlVolume(WId handle, bool isPositive, int quantity) +//{ +// _WMP.ControlVolume(HWND(handle), isPositive, quantity); +// } + +// Init, run and embed Chatterino process & window +void MainWindow::setupChatterino() +{ + qDebugSlot() << "MainWindow::setupChatterino()"; + + if (_lChatterinoLock) + return; + _lChatterinoLock = true; + // If Chatterino isn't running, run + if (_pChatterinoProcess->state() == QProcess::NotRunning) + { + QTimer *findChatterino = new QTimer; + _pChatterinoProcess->start(); + _pChatterinoProcess->waitForStarted(); + connect(findChatterino, &QTimer::timeout, this, + [=]() + { + WId wid = WId(_WMP.getWID(_pChatterinoProcess->processId())); + if (wid != 0) + { + // Make QTimer monitoring for chat size changes + connect(monitorChatterinoWidth, &QTimer::timeout, this, + [=]() + { + int tmpChatterinoWidth = _wChatterinoWindow->width(); + if (_chatterinoWidth != tmpChatterinoWidth && _lChatterinoLock != true) + { + _chatterinoWidth = tmpChatterinoWidth; + ui->widget->setMinimumWidth(_chatterinoWidth); + MainWindow::resizeEmbeds(); + } + }); + connect(monitorChatterinoHeight, &QTimer::timeout, this, + [=]() + { + int tmpChatterinoHeight = _wChatterinoWindow->height(); + if (_chatterinoHeight != tmpChatterinoHeight && _lChatterinoLock != true) + MainWindow::resizeEmbeds(); + }); + // Setting part + _wChatterinoWindow = QWindow::fromWinId(wid); + findChatterino->deleteLater(); + _chatContainer = createWindowContainer(_wChatterinoWindow); + + _chatContainer->setParent(ui->widget); // TODO: Is this needed? possible duplicate of line 98 `_pChatterinoProcess = new QProcess(ui->widget);` + _chatContainer->setSizePolicy(QSizePolicy::Policy::Expanding, QSizePolicy::Policy::Expanding); + _chatContainer->show(); + // connect(window, &QWindow::widthChanged, this, &MainWindow::resizeEmbeds); + _bChatterinoEmbedded = true; + _lChatterinoLock = false; + monitorChatterinoWidth->start(50); + monitorChatterinoHeight->start(50); + resizeEmbeds(); + } + }); + findChatterino->start(10); + } +} + +// Init, run and change Streamlink process // Read streamlink output // show on QPlainTextEdit debug // Update Statusbar // Load MPV when ready -void MainWindow::readStreamLink() +void MainWindow::setupStreamlink() { - QString s = _pStreamlinkProcess.at(_bStreamLinkProcessSelector)->readAll(); - ui->plainTextEdit->setPlainText(ui->plainTextEdit->toPlainText().append(s).append("\n")); - ui->statusbar->showMessage(s); - if(s.contains("pre-roll ads")) + qDebugSlot() << "MainWindow::setupStreamlink()"; + + if (_bDebug) + { + ui->plainTextEdit->show(); + } + QString s = _pStreamlinkProcess.at(_bStreamlinkProcessSelector)->readAll(); + ui->plainTextEdit->setPlainText(ui->plainTextEdit->toPlainText().append(s).append("\n")); + // ui->statusbar->showMessage(s); + if (s.contains("pre-roll ads")) + { + ui->textEdit_SwitchAlert->setHtml(generateStatusHTML(true)); + resizeEmbeds(); + } + if (s.contains("player: mpv")) + { + _mpvContainer->setParent(ui->widget_2); + _mpvContainer->show(); + ui->textEdit_SwitchAlert->hide(); + ui->plainTextEdit->hide(); + _bStreamlinkAllowSwitching = true; + + // Turnoff other streamlink if it's running + // half second delay for better transition + if (_pStreamlinkProcess.at(!_bStreamlinkProcessSelector)->state() != QProcess::NotRunning) { - ui->textEdit_SwitchAlert->setHtml(generateStatusHTML(true)); - resizeEmbeds(); + QTimer::singleShot(600, this, [=]() + { _pStreamlinkProcess.at(!_bStreamlinkProcessSelector)->kill(); }); } - if(s.contains("player: mpv")) - { - _mpvContainer->setParent(ui->widget_2); - _mpvContainer->show(); - ui->statusbar->hide(); - ui->textEdit_SwitchAlert->hide(); - _bStreamlinkAllowSwitching = true; - - // Turnoff other streamlink if it's running - // half second delay for better transition - if(_pStreamlinkProcess.at(!_bStreamLinkProcessSelector)->state() != QProcess::NotRunning) - { - QTimer::singleShot(600, this, [=]() - { - _pStreamlinkProcess.at(!_bStreamLinkProcessSelector)->terminate(); - }) ; - } + resizeEmbeds(); + } +} + +// Init MVP container +void MainWindow::setupMVP() +{ + qDebugSlot() << "MainWindow::setupMVP()"; + // Setup mpv container and wid //changed some ****************** + QWindow *mpv_window = new QWindow; + _mpvContainerWID = mpv_window->winId(); + _mpvContainer = createWindowContainer(mpv_window); + _mpvContainer->setSizePolicy(QSizePolicy::Policy::Expanding, QSizePolicy::Policy::Expanding); + _mpvContainer->hide(); +} - resizeEmbeds(); +// Load current channel from json file +QString MainWindow::loadCurrentChannel() +{ + qDebug2() << "MainWindow::loadCurrentChannel()"; + // load json + QString val; + QFile file(QDir::homePath() + "/Appdata/Roaming/Chatterino2/Settings/window-layout.json"); + if (file.open(QIODevice::ReadOnly | QIODevice::Text)) + { + val = file.readAll(); + file.close(); + QJsonDocument jdoc = QJsonDocument::fromJson(val.toUtf8()); + QJsonArray channels = jdoc.object() + .value(QString("windows")) + .toArray() + .at(0) + .toObject() + .value(QString("tabs")) + .toArray(); + foreach (QJsonValue channel, channels) + { + QString channel_name; + QJsonObject channel_info = channel["splits2"]["data"].toObject(); + channel_name = channel_info.value(QString("name")).toString(); + // qDebug_() << channel_info.value(QString("type")).toString() << channel_name << channel["selected"]; + + // If it is twitch channel + if (channel_info.value(QString("type")).toString() == QString("twitch") && channel["selected"].toBool() == true) + { + qDebug_() << channel_name; + return channel_name; + } } + } + return ""; } +// Initialize Chatterino, Streamlink, MVP +// TODO: Implement case for rapid setting changes void MainWindow::initialize() { - // If we are re-initializing - if(_bChatterinoEmbedded) + qDebugSlot() << "MainWindow::initialize()"; + // If we are re-initializing + if (_Submodules->getChanges() > 0) + { + qDebug_() << "Reinitialize"; + if (_Submodules->getChanges() & Submodules::ChangeFlags::Chatterino) { - // Tools changed, re-initialize only what's needed - if(_Submodules->getChanges() > 0) - { - if(_Submodules->getChanges() & Submodules::ChangeFlags::Chatterino) - { - _pChatterinoProcess->terminate(); - QTimer * restart = new QTimer(this); - connect(restart, &QTimer::timeout, this, [=]() + if (_pChatterinoProcess->state() == QProcess::NotRunning) + setupChatterino(); + else + { + _lChatterinoLock = true; + _bChatterinoEmbedded = false; + quitChatterino(); + QTimer *restart = new QTimer(this); + connect(restart, &QTimer::timeout, this, + [=]() { - if(_pChatterinoProcess->state() == 0) - { - restart->deleteLater(); - _pChatterinoProcess->setProgram(_Submodules->chatterinoPath()); - _pChatterinoProcess->start(); - setupChatterinoEmbed(); - } + if (_pChatterinoProcess->state() == QProcess::NotRunning) + { + restart->deleteLater(); + _pChatterinoProcess->setProgram(_Submodules->chatterinoPath()); + _lChatterinoLock = false; + setupChatterino(); + } }); - restart->start(10); - } - if(_Submodules->getChanges() & Submodules::ChangeFlags::StreamLink) - { - if(_bStreamlinkAllowSwitching) - { - _bStreamLinkProcessSelector = !_bStreamLinkProcessSelector; - _bStreamlinkAllowSwitching = !_bStreamlinkAllowSwitching; - } - _pStreamlinkProcess.at(_bStreamLinkProcessSelector)->setArguments(_Submodules->getStreamLinkArguments(_cChatChannel, _mpvContainerWID)); - _pStreamlinkProcess.at(_bStreamLinkProcessSelector)->start(); -// _pStreamlinkProcess->terminate(); -// _mpvContainer->setParent(NULL); -// ui->statusbar->show(); -// _mpvContainer->setParent(NULL); -// ui->statusbar->show(); -// _pStreamlinkProcess->terminate(); -// // Wait for current streamlink to die before restarting -// QTimer * restart = new QTimer(this); -// connect(restart, &QTimer::timeout, this, [=]() -// { -// if(_pStreamlinkProcess->state() == 0) -// { - -// _pStreamlinkProcess->setProgram(_Submodules->streamlinkPath()); -// _pStreamlinkProcess->setArguments(_Submodules->getStreamLinkArguments(_cChatChannel, _mpvContainerWID)); -// _pStreamlinkProcess->start(); -// restart->deleteLater(); -// } -// }); -// restart->start(10); - } - } - return; + restart->start(10); + } } - // Timer to check for chat channel switches - _tChatChannelMonitor = new QTimer; - connect(_tChatChannelMonitor, &QTimer::timeout, this, &MainWindow::chatterinoMonitor); - - // Setup StreamLink and Chatterino Processes + if (_Submodules->getChanges() & Submodules::ChangeFlags::StreamLink) + { // Only change program setting and don't touch current running stream + // To reload streamlink, click `load Stream` in context Menu + _pStreamlinkProcess[0]->setProgram(_Submodules->streamlinkPath()); + _pStreamlinkProcess[1]->setProgram(_Submodules->streamlinkPath()); + } + } + else + { + qDebug_() << "Initialize"; + // Init MVP + setupMVP(); + + // Init Chatterino _pChatterinoProcess = new QProcess(ui->widget); _pChatterinoProcess->setProgram(_Submodules->chatterinoPath()); - _pChatterinoProcess->start(); - - for(int i = 0; i < 2; ++i) - { - _pStreamlinkProcess << new QProcess(ui->widget_2); - _pStreamlinkProcess.last()->setProgram(_Submodules->streamlinkPath()); - connect(_pStreamlinkProcess.last(), &QProcess::readyRead, this, &MainWindow::readStreamLink); + setupChatterino(); + + // Init Streamlink + _pStreamlinkProcess << new QProcess(ui->widget_2) << new QProcess(ui->widget_2); + _pStreamlinkProcess[0]->setProgram(_Submodules->streamlinkPath()); + _pStreamlinkProcess[1]->setProgram(_Submodules->streamlinkPath()); + connect(_pStreamlinkProcess[0], &QProcess::readyRead, this, &MainWindow::setupStreamlink); + connect(_pStreamlinkProcess[1], &QProcess::readyRead, this, &MainWindow::setupStreamlink); + + QString channel = MainWindow::loadCurrentChannel(); + if (channel != "" && channel != _cChatChannel) + { // TODO: Check if works correctly + _cChatChannel = channel; + _pStreamlinkProcess.at(_bStreamlinkProcessSelector)->setArguments(_Submodules->getStreamLinkArguments(_cChatChannel, _mpvContainerWID)); + _pStreamlinkProcess[_bStreamlinkProcessSelector]->start(); } + // QTimer *initChatterinoFocus = new QTimer; + // QTimer *initChatterinoFocus2 = new QTimer; + // connect(initChatterinoFocus, &QTimer::timeout, this, + // [=]() + // { + // if (_pChatterinoProcess->state() == QProcess::Running) + // { + // initChatterinoFocus2->start(10000); + // initChatterinoFocus->deleteLater(); + // } + // }); + // connect(initChatterinoFocus2, &QTimer::timeout, this, + // [=]() + // { + // MainWindow::initChatterinoFocus(); + // initChatterinoFocus2->deleteLater(); + // }); + // initChatterinoFocus->start(10); + } +} - // Setup mpv container and wid - QWindow * mpv_window = new QWindow; - _mpvContainerWID = mpv_window->winId(); - _mpvContainer = createWindowContainer(mpv_window); - _mpvContainer->setSizePolicy(QSizePolicy::Policy::Expanding,QSizePolicy::Policy::Expanding); - _mpvContainer->hide(); - - // Settings - connect(ui->actionSettings, &QAction::triggered, _Submodules, &Submodules::SubmodulesDialog::showDialog); +// NEED FIX +void MainWindow::loadStream() +{ + MainWindow::quitChatterino(); + // Load channel if new channel exists + QTimer *loadStream = new QTimer(this); + connect(loadStream, &QTimer::timeout, this, + [=]() + { + qDebug() << _pChatterinoProcess->state(); + if (_pChatterinoProcess->state() == QProcess::NotRunning) + { + QString channel = loadCurrentChannel(); + loadStream->deleteLater(); + _lChatterinoLock = false; + setupChatterino(); + if (channel == "" || _cChatChannel == channel) + return; + else + { + _cChatChannel = channel; + _bStreamlinkProcessSelector = !_bStreamlinkProcessSelector; + _pStreamlinkProcess.at(_bStreamlinkProcessSelector)->setArguments(_Submodules->getStreamLinkArguments(_cChatChannel, _mpvContainerWID)); + _pStreamlinkProcess[_bStreamlinkProcessSelector]->start(); + } + } + }); + loadStream->start(10); +} - // Run Chatterino and Embed it - setupChatterinoEmbed(); +void MainWindow::experimental() +{ + return; } void MainWindow::refreshStream() { - if(_bStreamlinkAllowSwitching) - { - _bStreamLinkProcessSelector = !_bStreamLinkProcessSelector; - _bStreamlinkAllowSwitching = !_bStreamlinkAllowSwitching; - } - auto tChannel = _cChatChannel; - _cChatChannel = ""; - _pStreamlinkProcess.at(_bStreamLinkProcessSelector)->setArguments(_Submodules->getStreamLinkArguments(tChannel, _mpvContainerWID)); - _pStreamlinkProcess.at(_bStreamLinkProcessSelector)->start(); + qDebugSlot() << "MainWindow::refreshStream()"; + if (_bStreamlinkAllowSwitching) + { + _bStreamlinkProcessSelector = !_bStreamlinkProcessSelector; + _bStreamlinkAllowSwitching = !_bStreamlinkAllowSwitching; + } + auto tChannel = _cChatChannel; + _cChatChannel = ""; + _pStreamlinkProcess.at(_bStreamlinkProcessSelector)->setArguments(_Submodules->getStreamLinkArguments(tChannel, _mpvContainerWID)); + _pStreamlinkProcess.at(_bStreamlinkProcessSelector)->start(); } void MainWindow::resizeEvent(QResizeEvent *event) { - if(_bChatterinoEmbedded) - resizeEmbeds(); - event->accept(); + // qDebugSlot() << "MainWindow::resizeEvent(QResizeEvent *event)"; + if (_bChatterinoEmbedded == true && _lChatterinoLock == false) + resizeEmbeds(); + event->accept(); } void MainWindow::resizeEmbeds() { - // Delay needed for X - QTimer::singleShot(10, this, [=]() - { - _chatContainer->setGeometry(0,0,ui->widget->geometry().width(), ui->widget->geometry().height()); - _mpvContainer->setGeometry(0,0,ui->widget_2->geometry().width(), ui->widget_2->geometry().height()); - }); + // qDebug2() << "MainWindow::resizeEmbeds()"; + QTimer::singleShot(10, this, + [=]() + { + _chatContainer->setGeometry(0, 1, ui->widget->geometry().width(), ui->widget->geometry().height() - 1); + _mpvContainer->setGeometry(9, 32, ui->widget_2->geometry().width() - 18, ui->widget_2->geometry().height() - 20); + }); + // Load screen setting from registry. + QSettings settings = QSettings(QSettings::NativeFormat, QSettings::UserScope, "streamlinkerino", "streamlinkerino"); // Registry setting + settings.beginGroup("screen"); + settings.setValue("screenpos_x", this->pos().x()); + settings.setValue("screenpos_y", this->pos().y()); + settings.setValue("screensize_x", this->width()); + settings.setValue("screensize_y", this->height()); + settings.endGroup(); } QString MainWindow::generateStatusHTML(bool bPrerollAds) { - - QString out = "

Switching to Channel: "; - out += _cChatChannel; - out += ". "; - if(bPrerollAds) - out += "Waiting for pre-roll ads to finish!

"; - return out; + qDebug2() << "MainWindow::generateStatusHTML(bool bPrerollAds)"; + QString out = "

Switching to Channel: "; + out += _cChatChannel; + out += ". "; + if (bPrerollAds) + out += "Waiting for pre-roll ads to finish!

"; + return out; } - diff --git a/src/MainWindow.h b/src/MainWindow.h index 19632b6..fc258e9 100644 --- a/src/MainWindow.h +++ b/src/MainWindow.h @@ -1,65 +1,102 @@ #ifndef MAINWINDOW_H #define MAINWINDOW_H -// Qt Compatibility - -#include - - +#include #include -#include -#include +#include +#include +#include -#include -#include +#include "debugging.h" +#include "submodules.h" +#include "windark.h" +#include "windows_interface.h" #include #include -#include "WindowsMatchingPID.h" -#include "Submodules.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +// test +#include +#include +#include QT_BEGIN_NAMESPACE namespace Ui { -class MainWindow; + class MainWindow; } QT_END_NAMESPACE class MainWindow : public QMainWindow { - Q_OBJECT + Q_OBJECT public: - MainWindow(QWidget *parent = nullptr); - ~MainWindow(); - + MainWindow(QWidget *parent = nullptr); + ~MainWindow(); private slots: - void setupChatterinoEmbed(); - void chatterinoMonitor(); - void readStreamLink(); - void initialize(); - void refreshStream(); + void setupUI(); + void setupCustomContextMenu(); + // Context menu + void showContextMenu(const QPoint &pos); + // Main logic + void setupChatterino(); + void setupStreamlink(); + void setupMVP(); + QString loadCurrentChannel(); + void initialize(); + void refreshStream(); + void loadStream(); + void experimental(); + // static void controlVolume(WId handle, bool isPositive, int quantity); protected: - void resizeEvent(QResizeEvent * event); + void resizeEvent(QResizeEvent *event) override; + bool eventFilter(QObject *obj, QEvent *event); + void closeEvent(QCloseEvent *event); private: - void resizeEmbeds(); - QString generateStatusHTML(bool bPrerollAds = false); + Ui::MainWindow *ui; + // Main logic + void resizeEmbeds(); + QString generateStatusHTML(bool bPrerollAds = false); + void quitChatterino(); + void quitChatterino_(); + void initChatterinoFocus(); + void simulateMouse(unsigned int pos, INPUT *inputs); - Ui::MainWindow *ui; - WindowsMatchingPid _WMP; - Submodules::SubmodulesDialog * _Submodules; - QWidget * _chatContainer; - QWidget * _mpvContainer; - bool _bChatterinoEmbedded = false; - QList _pStreamlinkProcess; - QProcess * _pChatterinoProcess; - QTimer * _tChatChannelMonitor; - QByteArray _cChatChannel; - QByteArray _chatterinoUUID; - unsigned long _mpvContainerWID; - bool _bStreamLinkProcessSelector = true; - bool _bStreamlinkAllowSwitching = false; + WindowsInterface _WMP; + Submodules::SubmodulesDialog *_Submodules; + QWidget *_chatContainer; + QWidget *_mpvContainer; + QMenu *_contextMenu; + QList _contextMenuActions; + QList _pStreamlinkProcess; + QProcess *_pChatterinoProcess; + QWindow *_wChatterinoWindow; + QTimer *monitorChatterinoWidth = new QTimer; + QTimer *monitorChatterinoHeight = new QTimer; + int _chatterinoWidth; + int _chatterinoHeight; + bool _lChatterinoLock = false; + bool _bChatterinoEmbedded = false; + QString _cChatChannel; + unsigned long _mpvContainerWID; + bool _bStreamlinkProcessSelector = false; + bool _bStreamlinkAllowSwitching = false; + bool _bDebug = true; + bool _bLeftMouseButtonPressed = false; + QPoint _dragMousePos; + QPoint _dragMainWindowPos; + bool _bIsMenuOff = true; }; #endif // MAINWINDOW_H diff --git a/src/Submodules.cpp b/src/Submodules.cpp index 386e26a..56add57 100644 --- a/src/Submodules.cpp +++ b/src/Submodules.cpp @@ -1,261 +1,275 @@ -#include "Submodules.h" -#include "ui_SubmodulesDialog.h" +#include "submodules.h" +#include "ui_submodulesDialog.h" using namespace Submodules; -SubmodulesDialog::SubmodulesDialog(QWidget *parent) : - QDialog(parent), - ui(new Ui::SubmodulesDialog) +SubmodulesDialog::SubmodulesDialog(QWidget *parent) + : QDialog(parent), ui(new Ui::SubmodulesDialog), isInit(true) { - ui->setupUi(this); - this->hide(); - _SL = new Streamlink; - - hideAlerts(); - - setupConnections(); + ui->setupUi(this); + this->setWindowTitle(QString("Settings")); + this->hide(); + hideAlerts(); + setupConnections(); } -SubmodulesDialog::~SubmodulesDialog() -{ - delete ui; -} +SubmodulesDialog::~SubmodulesDialog() { delete ui; } void SubmodulesDialog::showMessageBox() { - QMessageBox alert; - QString text = "Warning! The following external tools have not been found:\n\n"; - text += _streamlinkPath.isEmpty() ? "streamlink" : "chatterino"; - text += "\n\nPlease select the folder where the missing tools can be found."; - alert.setText(text); - alert.exec(); - - showDialog(); + QMessageBox alert; + QString text = "Warning! The following external tools have not been found:\n\n"; + if (_streamlinkPath.isEmpty()) + text += "[streamlink]"; + if (_chatterinoPath.isEmpty()) + text += "[chatterino]"; + text += "\n\nPlease select the folder where the missing tools can be found."; + alert.setText(text); + alert.exec(); + + showDialog(); } -QString SubmodulesDialog::streamlinkPath() -{ - return _streamlinkPath; -} +Streamlink::Streamlink(QString quality) : _quality(quality){}; -QString SubmodulesDialog::chatterinoPath() -{ - return _chatterinoPath; -} +QString SubmodulesDialog::streamlinkPath() { return _streamlinkPath; } -QString SubmodulesDialog::streamlinkArguments() -{ - return _streamlinkArgs; -} +QString SubmodulesDialog::chatterinoPath() { return _chatterinoPath; } -QString SubmodulesDialog::streamlinkQuality() -{ - return ui->comboBox_StreamlinkQuality->currentText(); -} +QString SubmodulesDialog::streamlinkArguments() { return _streamlinkArgs; } QStringList SubmodulesDialog::getStreamLinkArguments(QString channel, unsigned long mpvContainer) { - return _SL->getArgs(channel, mpvContainer); -} - -void SubmodulesDialog::saveSettings() -{ - QSettings settings(QSettings::NativeFormat, QSettings::UserScope,"streamlinkerino", "streamlinkerino"); - settings.beginGroup("preferences"); - settings.setValue("streamlink", _streamlinkPath); - settings.setValue("chatterino", _chatterinoPath); - settings.setValue("streamlink_args", _streamlinkArgs); - settings.setValue("streamlink_quality", ui->comboBox_StreamlinkQuality->currentIndex()); - settings.endGroup(); - - _SL->setQuality(ui->comboBox_StreamlinkQuality->currentIndex()); -} - -void SubmodulesDialog::closeEvent(QCloseEvent *e) -{ - hideAlerts(); - emit submodulesFinished(); - e->accept(); + return _SL->getArgs(channel, mpvContainer); } void SubmodulesDialog::loadSettings() { - bool bMissing = false; - QStringList paths = QStringList() << "/usr/bin/" << "/bin/" << "/usr/local/bin/" << "~/.local/bin/"; - QSettings settings(QSettings::NativeFormat, QSettings::UserScope,"streamlinkerino", "streamlinkerino"); - settings.beginGroup("preferences"); - - ui->comboBox_StreamlinkQuality->setCurrentIndex(settings.value("streamlink_quality").toInt()); - _streamlinkPath = settings.value("streamlink", "").toString(); - _streamlinkArgs = settings.value("streamlink_args").toString(); - _chatterinoPath = settings.value("chatterino", "").toString(); - - if(_streamlinkPath.isEmpty()) - { - bMissing=true; - foreach(QString path, paths) + qDebug2() << "SubmodulesDialog::loadSettings()"; + // drive, path, executable location + QFileInfoList drives = QDir::drives(); + QStringList paths = QStringList() << "Program Files (x86)/" + << "Program Files/"; + QFileInfo executable; + + // Load registries + settings.beginGroup("preferences"); + ui->comboBox_StreamlinkQuality->setCurrentIndex(settings.value("streamlink_quality").toInt()); + _streamlinkPath = settings.value("streamlink", "").toString(); + _streamlinkArgs = settings.value("streamlink_args", "").toString(); + _chatterinoPath = settings.value("chatterino", "").toString(); + _streamlinkQuality = settings.value("streamlink_quality", "best").toString(); + qDebug2_() << "_streamlinkPath: " << _streamlinkPath; + qDebug2_() << "_streamlinkArgs: " << _streamlinkArgs; + qDebug2_() << "chatterinoPath: " << _chatterinoPath; + qDebug2_() << "streamlinkQuality: " << _streamlinkQuality; + + // Set default quality + if (StreamQuality.find(_streamlinkQuality) == StreamQuality.end()) + _streamlinkQuality = "Best"; + settings.setValue("streamlink_quality", _streamlinkQuality); + + // Check if executables that predefined in registries exist. If not defined or exist, search executables' path + executable = QFileInfo(_streamlinkPath); + if (!executable.exists() || !executable.isFile()) + { + _streamlinkPath = ""; + foreach (QFileInfo drive, drives) + foreach (QString path, paths) + { + executable = QFileInfo(drive.path() + path + "streamlink/bin/streamlink.exe"); + if (executable.exists() && executable.isFile()) { - QFileInfo check_file(path + "streamlink"); - if (check_file.exists() && check_file.isFile()) - { - _streamlinkPath = path + "streamlink"; - break; - } + _streamlinkPath = drive.path() + path + "streamlink/bin/streamlink.exe"; + settings.setValue("streamlink", _streamlinkPath); + break; } - } - if(_chatterinoPath.isEmpty()) - { - bMissing=true; - foreach(QString path, paths) + } + } + executable = QFileInfo(_chatterinoPath); + if (!executable.exists() || !executable.isFile()) + { + _chatterinoPath = ""; + foreach (QFileInfo drive, drives) + foreach (QString path, paths) + { + executable = QFileInfo(drive.path() + path + "Chatterino/chatterino.exe"); + if (executable.exists() && executable.isFile()) { - QFileInfo check_file(path + "chatterino"); - if (check_file.exists() && check_file.isFile()) - { - _streamlinkPath = path + "chatterino"; - break; - } + _chatterinoPath = drive.path() + path + "Chatterino/chatterino.exe"; + settings.setValue("chatterino", _chatterinoPath); + break; } - } - if(bMissing) - { - settings.setValue("streamlink", _streamlinkPath); - settings.setValue("chatterino", _chatterinoPath); - } - settings.endGroup(); - - _SL->setQuality(ui->comboBox_StreamlinkQuality->currentIndex()); - if (_streamlinkPath.isEmpty() || _chatterinoPath.isEmpty()) - showMessageBox(); - else - emit submodulesFinished(); + } + } + settings.endGroup(); + + qDebug2_() << "(loaded)_streamlinkPath: " << _streamlinkPath; + qDebug2_() << "(loaded)_streamlinkArgs: " << _streamlinkArgs; + qDebug2_() << "(loaded)chatterinoPath: " << _chatterinoPath; + qDebug2_() << "(loaded)streamlinkQuality: " << _streamlinkQuality; + if (_streamlinkPath.isEmpty() || _chatterinoPath.isEmpty()) + showMessageBox(); } void SubmodulesDialog::showDialog() { - ui->lineEdit_chatterinoPath->setText(_chatterinoPath); - ui->lineEdit_streamLinkPath->setText(_streamlinkPath); - ui->lineEdit_streamLinkOptions->setText(_streamlinkArgs); - _changes = 0; - this->show(); + ui->lineEdit_chatterinoPath->setText(_chatterinoPath); + ui->lineEdit_streamLinkPath->setText(_streamlinkPath); + ui->lineEdit_streamLinkOptions->setText(_streamlinkArgs); + ui->comboBox_StreamlinkQuality->setCurrentIndex(StreamQuality[_streamlinkQuality]); + _changes = 0; + if (WinDark::isDarkTheme()) + WinDark::setDark_Titlebar(reinterpret_cast(winId())); + this->show(); } void SubmodulesDialog::initialize() { - loadSettings(); + loadSettings(); + _SL = new Streamlink(_streamlinkQuality); + emit SubmodulesDialog::settingFinished(); } +/* TODO - Make setting possible while streaming */ void SubmodulesDialog::setupConnections() { - connect(ui->lineEdit_chatterinoPath, &QLineEdit::editingFinished, this, [=]() - { - QString loc = ui->lineEdit_chatterinoPath->text().simplified(); - QStringList paths =QStringList() << loc - << loc + "/chatterino"; - foreach(QString path, paths) - { - if(!path.contains("chatterino")) continue; - QFileInfo check_file(path); - if (check_file.exists() && check_file.isFile()) + qDebug() << "SubmodulesDialog::setupConnections()"; + connect(ui->lineEdit_chatterinoPath, &QLineEdit::editingFinished, this, + [=]() + { + QString path = ui->lineEdit_chatterinoPath->text().simplified(); + QString chatterino = "chatterino.exe"; + qDebug() << "(input)path: " << path; + + if (!(path.length() > chatterino.length() && chatterino == path[path.length() - chatterino.length()])) { - ui->label_chatterinoAlert->setText("Chatterino Found"); - ui->label_chatterinoAlert->setStyleSheet(_styleGreen); - ui->label_chatterinoAlert->show(); - _chatterinoPath = path; - // If there's a difference - if(!_chatterinoPath.isEmpty() && _chatterinoPath != loc) - { - if(!(_changes & ChangeFlags::Chatterino)) - _changes ^= ChangeFlags::Chatterino; - } - saveSettings(); - return; + if (path[path.length() - 1] != '/') + path += '/'; + path += chatterino; } - } -// db_f(not found); - ui->label_chatterinoAlert->setText("Chatterino Not Found"); - ui->label_chatterinoAlert->setStyleSheet(_styleRed); - ui->label_chatterinoAlert->show(); - }); - connect(ui->lineEdit_streamLinkPath, &QLineEdit::editingFinished, this, [=]() - { - QString loc = ui->lineEdit_streamLinkPath->text().simplified(); - QStringList paths =QStringList() << loc - << loc + "/streamlink"; - foreach(QString path, paths) - { - if(!path.contains("streamlink")) continue; - QFileInfo check_file(path); - if (check_file.exists() && check_file.isFile()) + QFileInfo executable(path); + if (executable.exists() && executable.isFile()) { - ui->label_streamlinkAlert->setText("Streamlink Found"); - ui->label_streamlinkAlert->setStyleSheet(_styleGreen); - ui->label_streamlinkAlert->show(); - _streamlinkPath = path; - // If there's no difference - if(!_streamlinkPath.isEmpty() && _streamlinkPath != loc) - { - if(!(_changes & ChangeFlags::StreamLink)) - _changes ^= ChangeFlags::StreamLink; - } - saveSettings(); - return; + qDebug() << "Chatterino Found: " << path; + ui->label_chatterinoAlert->setText("Chatterino Found"); + ui->label_chatterinoAlert->setStyleSheet(_styleGreen); + ui->label_chatterinoAlert->show(); + + // If there's a difference + if (_chatterinoPath != path) + _changes |= ChangeFlags::Chatterino; + _chatterinoPath = path; + return; } - } - - ui->label_streamlinkAlert->setText("Streamlink Not Found"); - ui->label_streamlinkAlert->setStyleSheet(_styleRed); - ui->label_streamlinkAlert->show(); - }); + // db_f(not found); + ui->label_chatterinoAlert->setText("Chatterino Not Found"); + ui->label_chatterinoAlert->setStyleSheet(_styleRed); + ui->label_chatterinoAlert->show(); + }); + connect(ui->lineEdit_streamLinkPath, &QLineEdit::editingFinished, this, + [=]() + { + QString path = ui->lineEdit_streamLinkPath->text().simplified(); + QString streamlink = "streamlink.exe"; + qDebug() << "(input)path: " << path; + + if (!(path.length() > streamlink.length() && streamlink == path[path.length() - streamlink.length()])) + { + if (path[path.length() - 1] != '/') + path += '/'; + path += streamlink; + } - connect(ui->lineEdit_streamLinkOptions, &QLineEdit::editingFinished, this, &SubmodulesDialog::saveSettings); - // Man, this connect is ugly af - connect(ui->comboBox_StreamlinkQuality, static_cast(&QComboBox::currentIndexChanged), this, [=]() - { - // Queue up streamlink reset - if(!(_changes & ChangeFlags::StreamLink)) - _changes ^= ChangeFlags::StreamLink; + QFileInfo executable(path); + if (executable.exists() && executable.isFile()) + { + qDebug() << "Streamlink Found: " << path; + ui->label_streamlinkAlert->setText("Streamlink Found"); + ui->label_streamlinkAlert->setStyleSheet(_styleGreen); + ui->label_streamlinkAlert->show(); + + // If there's a difference + if (_streamlinkPath != path) + _changes |= ChangeFlags::StreamLink; + _streamlinkPath = path; + return; + } - saveSettings(); - }); - connect(ui->pushButton, &QPushButton::clicked, this, &SubmodulesDialog::close); - connect(ui->pushButton_2, &QPushButton::clicked, this, &SubmodulesDialog::close); - connect(ui->pushButton_3, &QPushButton::clicked, this, &SubmodulesDialog::refreshStream); + // db_f(not found); + ui->label_streamlinkAlert->setText("Streamlink Not Found"); + ui->label_streamlinkAlert->setStyleSheet(_styleRed); + ui->label_streamlinkAlert->show(); + }); + // Man, this connect is ugly af + connect(ui->comboBox_StreamlinkQuality, + static_cast( + &QComboBox::currentIndexChanged), + this, + [=]() + { + // Queue up streamlink reset + _changes |= ChangeFlags::StreamLink; + + // Save default stream quality + _streamlinkQuality = ui->comboBox_StreamlinkQuality->currentText(); + }); + // If click 'OK' button + connect(ui->pushButton, &QPushButton::clicked, this, + [=]() + { + if (ui->label_chatterinoAlert->text() == "Chatterino Found" && ui->label_streamlinkAlert->text() == "Streamlink Found" && StreamQuality.find(ui->comboBox_StreamlinkQuality->currentText()) != StreamQuality.end()) + { // TODO: check if valid streamlink_args + settings.beginGroup("preferences"); + settings.setValue("chatterino", _chatterinoPath); + settings.setValue("streamlink", _streamlinkPath); + settings.setValue("streamlink_args", _streamlinkArgs); + settings.setValue("streamlink_quality", _streamlinkQuality); + settings.endGroup(); + this->close(); // Quit dialog + } + }); + // If click 'Cancel' button + connect(ui->pushButton_2, &QPushButton::clicked, this, + [=]() + { + if (isInit) // If current process is init process + this->close(); // Quit dialog + else + QApplication::quit(); // Terminate program + }); + // If click 'Refresh current stream' button + connect(ui->pushButton_3, &QPushButton::clicked, this, &SubmodulesDialog::refreshStream); } void SubmodulesDialog::hideAlerts() { - ui->label_chatterinoAlert->hide(); - ui->label_streamlinkAlert->hide(); + ui->label_chatterinoAlert->hide(); + ui->label_streamlinkAlert->hide(); } -int Submodules::SubmodulesDialog::getChanges() -{ - return _changes; -} +int Submodules::SubmodulesDialog::getChanges() { return _changes; } //? -QStringList Streamlink::getArgs(QString channel, unsigned long mpvContainer) +QStringList Streamlink::getArgs(QString channel, unsigned long mpvContainer) // { - QStringList args; - args << "--twitch-low-latency"; - args << "--twitch-disable-ads"; - args<< "--player"; - args << "mpv --wid="+QString::number(mpvContainer); - args << "https://www.twitch.tv/"+channel; - args << _quality; - return args; + QStringList args; + args << "--twitch-low-latency"; + args << "--twitch-disable-ads"; + args << "--player"; + args << "mpv --wid=" + QString::number(mpvContainer); + args << "https://www.twitch.tv/" + channel; + args << _quality; + return args; } -void Streamlink::setQuality(int qual) +void Streamlink::setQuality(QString quality) { - if(qual >8) - { - db "setQuality OOR"; - return; - } - const char * qualityList[] = {"best", "1080p60", "720p60", "720p", "480p", "360p", "160p", "worst", "audio_only"}; - - _quality = QString(qualityList[qual]); + if (StreamQuality.find(quality) == StreamQuality.end()) + { + db "setQuality OOR"; + return; + } + _quality = quality; } - diff --git a/src/Submodules.h b/src/Submodules.h index 09ba5e1..b83429c 100644 --- a/src/Submodules.h +++ b/src/Submodules.h @@ -1,50 +1,53 @@ #ifndef SUBMODULESDIALOG_H #define SUBMODULESDIALOG_H - -#include +#include "debugging.h" +#include "windark.h" +#include #include +#include +#include +#include #include #include -#include -#include +//#include #define db qDebug() << this << - +// need to fix this +const QMap StreamQuality = {{"Best", 0}, {"1080p60", 1}, {"720p60", 2}, {"720p", 3}, {"480p", 4}, {"360p", 5}, {"160p", 6}, {"Worst", 7}, {"Audio only", 8}}; namespace Ui { -class SubmodulesDialog; + class SubmodulesDialog; } namespace Submodules { -enum ChangeFlags -{ + enum ChangeFlags + { StreamLink = 0x01, Chatterino = 0x02 -}; - -class Streamlink -{ -public: - Streamlink() - { + }; - } + class Streamlink + { + public: + Streamlink(QString quality); ~Streamlink(); QStringList getArgs(QString channel, unsigned long mpvContainer); - void setQuality(int qual); -private: + void setQuality(QString quality); + + private: QString _quality; -}; + }; -class SubmodulesDialog : public QDialog -{ + class SubmodulesDialog : public QDialog + { Q_OBJECT -signals: - void submodulesFinished(); + signals: + void settingFinished(); void refreshStream(); -public: + + public: explicit SubmodulesDialog(QWidget *parent = nullptr); ~SubmodulesDialog(); @@ -57,31 +60,30 @@ class SubmodulesDialog : public QDialog QStringList getStreamLinkArguments(QString channel, unsigned long mpvContainer); - int getChanges();; -public slots: + int getChanges(); + ; + public slots: void showDialog(); void initialize(); -private slots: - void saveSettings(); -protected: - virtual void closeEvent(QCloseEvent *e) override; -private: + private: void loadSettings(); void setupConnections(); void hideAlerts(); - Ui::SubmodulesDialog *ui; - Streamlink * _SL; + Streamlink *_SL; QString _streamlinkPath; QString _streamlinkArgs; QString _chatterinoPath; + QString _streamlinkQuality; // default quality QString _styleGreen = "color: rgb(44, 145, 7);"; QString _styleRed = "color: rgb(156, 12, 21);"; + QSettings settings = QSettings(QSettings::NativeFormat, QSettings::UserScope, "streamlinkerino", "streamlinkerino"); // Registry setting + bool isInit; int _changes = 0; -}; -} + }; +} // namespace Submodules #endif // SUBMODULESDIALOG_H diff --git a/src/WindowsMatchingPID.cpp b/src/WindowsMatchingPID.cpp deleted file mode 100644 index 10b9aac..0000000 --- a/src/WindowsMatchingPID.cpp +++ /dev/null @@ -1,59 +0,0 @@ -#include "WindowsMatchingPID.h" -// Attempt to identify a window by name or attribute. -// by Adam Pierce -// modified by John Ciubuc - -WindowsMatchingPid::WindowsMatchingPid() -{ - _display = XOpenDisplay(0); - // Get the PID property atom. - _atomPID = XInternAtom(_display, "_NET_WM_PID", True); - if(_atomPID == None) - { - qWarning("No Such Atom"); - return; - } -} - -Window WindowsMatchingPid::getWID(unsigned long pid) -{ - _pid = pid; - _result = 0; - search(XDefaultRootWindow(_display)); - return _result; -} - -void WindowsMatchingPid::search(Window w) -{ - // Get the PID for the current Window. - Atom type; - int format; - unsigned long nItems; - unsigned long bytesAfter; - unsigned char *propPID = 0; - if(Success == XGetWindowProperty(_display, w, _atomPID, 0, 1, False, XA_CARDINAL, - &type, &format, &nItems, &bytesAfter, &propPID)) - { - if(propPID != 0) - { - // If the PID matches, add this window to the result and end. - if(_pid == *((unsigned long *)propPID)) - { - _result = w; - XFree(propPID); - return; - } - } - } - - // Recurse into child windows. - Window wRoot; - Window wParent; - Window *wChild; - unsigned nChildren; - if(0 != XQueryTree(_display, w, &wRoot, &wParent, &wChild, &nChildren)) - { - for(unsigned i = 0; i < nChildren; i++) - search(wChild[i]); - } -} diff --git a/src/WindowsMatchingPID.h b/src/WindowsMatchingPID.h deleted file mode 100644 index d251ef6..0000000 --- a/src/WindowsMatchingPID.h +++ /dev/null @@ -1,31 +0,0 @@ -#ifndef WINDOWSMATCHINGPID_H -#define WINDOWSMATCHINGPID_H - -// Explictly including QActionGroup fixes compile bug on linux -#include -#include - -#include -#include - -// Qt Compatibility -#undef Bool -#ifdef FrameFeature -#undef FrameFeature -#endif - -class WindowsMatchingPid -{ -public: - WindowsMatchingPid(); - Window getWID(unsigned long pid); - -private: - void search(Window w); - - unsigned long _pid; - Atom _atomPID; - Display *_display; - Window _result; -}; -#endif // WINDOWSMATCHINGPID_H diff --git a/src/debugging.h b/src/debugging.h new file mode 100644 index 0000000..9c3dd11 --- /dev/null +++ b/src/debugging.h @@ -0,0 +1,11 @@ +#ifndef DEBUGGING_H +#define DEBUGGING_H + +#define qDebug2() qDebug() << " └" +#define qDebug3() qDebug() << " └" +#define qDebug_() qDebug() << " " +#define qDebug2_() qDebug() << " " +#define qDebug3_() qDebug() << " " +#define qDebugSlot() qDebug() << "[Slot]" + +#endif // DEBUGGING_H diff --git a/src/main.cpp b/src/main.cpp index 0a0916f..52bb385 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,11 +1,14 @@ -#include "MainWindow.h" - +#include "mainwindow.h" #include int main(int argc, char *argv[]) { - QApplication a(argc, argv); - MainWindow w; - w.show(); - return a.exec(); + QApplication a(argc, argv); + + if (WinDark::isDarkTheme()) + WinDark::setDark_qApp(); + + MainWindow w; + w.show(); + return a.exec(); } diff --git a/src/windark.cpp b/src/windark.cpp new file mode 100644 index 0000000..237431e --- /dev/null +++ b/src/windark.cpp @@ -0,0 +1,98 @@ +/** + * + * Respect Windows Dark/Light themes for QT windows + * Naveen K + * + * Based on https://github.com/statiolake/neovim-qt/commit/da8eaba7f0e38b6b51f3bacd02a8cc2d1f7a34d8 + * Warning: this is not a proper solution, and Windows api can change !! + * Stylesheet based on https://forum.qt.io/post/523388 + * + **/ + +#include "windark.h" +#include +#include +#include +#include + +namespace WinDark +{ + + /** + * @brief isDarkTheme + * @return bool + */ + bool isDarkTheme() + { +#ifdef Q_OS_WIN + QSettings settings("HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize", + QSettings::NativeFormat); + if (settings.value("AppsUseLightTheme") == 0) + { + return true; + } +#endif + return false; + } + + /** + * @brief setDark_qApp + * @details use this after creating QApplication + * if(winDark::isDarkTheme()) winDark::setDark_qApp(); + * + */ + void setDark_qApp() + { +#ifdef Q_OS_WIN + qApp->setStyle(QStyleFactory::create("Fusion")); + QPalette darkPalette; + QColor darkColor = QColor(25, 25, 25); + QColor disabledColor = QColor(127, 127, 127); + darkPalette.setColor(QPalette::Window, darkColor); + darkPalette.setColor(QPalette::WindowText, Qt::white); + darkPalette.setColor(QPalette::Base, QColor(18, 18, 18)); + darkPalette.setColor(QPalette::AlternateBase, darkColor); + darkPalette.setColor(QPalette::ToolTipBase, Qt::white); + darkPalette.setColor(QPalette::ToolTipText, Qt::white); + darkPalette.setColor(QPalette::Text, Qt::white); + darkPalette.setColor(QPalette::Disabled, QPalette::Text, disabledColor); + darkPalette.setColor(QPalette::Button, darkColor); + darkPalette.setColor(QPalette::ButtonText, Qt::white); + darkPalette.setColor(QPalette::Disabled, QPalette::ButtonText, disabledColor); + darkPalette.setColor(QPalette::BrightText, Qt::red); + darkPalette.setColor(QPalette::Link, QColor(42, 130, 218)); + darkPalette.setColor(QPalette::Highlight, QColor(42, 130, 218)); + darkPalette.setColor(QPalette::HighlightedText, Qt::black); + darkPalette.setColor(QPalette::Disabled, QPalette::HighlightedText, disabledColor); + qApp->setPalette(darkPalette); + qApp->setStyleSheet("QToolTip { color: #ffffff; background-color: #2a82da; border: 1px solid white; }"); +#endif + } + + /** + * @brief setDark_Titlebar + * @param hwnd + * @details Use for each window like + * if(winDark::isDarkTheme()) winDark::setDark_Titlebar(reinterpret_cast(winId())); + */ + void setDark_Titlebar(HWND hwnd) + { +#ifdef Q_OS_WIN + HMODULE hUxtheme = LoadLibraryExW(L"uxtheme.dll", NULL, LOAD_LIBRARY_SEARCH_SYSTEM32); + HMODULE hUser32 = GetModuleHandleW(L"user32.dll"); + fnAllowDarkModeForWindow AllowDarkModeForWindow = reinterpret_cast(GetProcAddress(hUxtheme, MAKEINTRESOURCEA(133))); + fnSetPreferredAppMode SetPreferredAppMode = reinterpret_cast(GetProcAddress(hUxtheme, MAKEINTRESOURCEA(135))); + fnSetWindowCompositionAttribute SetWindowCompositionAttribute = reinterpret_cast(GetProcAddress(hUser32, "SetWindowCompositionAttribute")); + + SetPreferredAppMode(AllowDark); + BOOL dark = TRUE; + AllowDarkModeForWindow(hwnd, dark); + WINDOWCOMPOSITIONATTRIBDATA data = { + WCA_USEDARKMODECOLORS, + &dark, + sizeof(dark)}; + SetWindowCompositionAttribute(hwnd, &data); +#endif + } + +} // namespace WinDark diff --git a/src/windark.h b/src/windark.h new file mode 100644 index 0000000..e58964b --- /dev/null +++ b/src/windark.h @@ -0,0 +1,90 @@ +/** + * + * Respect Windows Dark/Light themes for QT windows + * Naveen K + * + * Based on https://github.com/statiolake/neovim-qt/commit/da8eaba7f0e38b6b51f3bacd02a8cc2d1f7a34d8 + * Warning: this is not a proper solution, and Windows api can change !! + * Stylesheet based on https://forum.qt.io/post/523388 + * + **/ +#ifndef WINDARK_H +#define WINDARK_H + +#include +#include +#include +#include +#include + +#ifdef Q_OS_WIN +#include +#include +#pragma comment(lib, "Dwmapi.lib") +#endif + +namespace WinDark +{ + + void setDark_qApp(); + void setDark_Titlebar(HWND hwnd); + bool isDarkTheme(); + +#ifdef Q_OS_WIN + enum PreferredAppMode + { + Default, + AllowDark, + ForceDark, + ForceLight, + Max + }; + + enum WINDOWCOMPOSITIONATTRIB + { + WCA_UNDEFINED = 0, + WCA_NCRENDERING_ENABLED = 1, + WCA_NCRENDERING_POLICY = 2, + WCA_TRANSITIONS_FORCEDISABLED = 3, + WCA_ALLOW_NCPAINT = 4, + WCA_CAPTION_BUTTON_BOUNDS = 5, + WCA_NONCLIENT_RTL_LAYOUT = 6, + WCA_FORCE_ICONIC_REPRESENTATION = 7, + WCA_EXTENDED_FRAME_BOUNDS = 8, + WCA_HAS_ICONIC_BITMAP = 9, + WCA_THEME_ATTRIBUTES = 10, + WCA_NCRENDERING_EXILED = 11, + WCA_NCADORNMENTINFO = 12, + WCA_EXCLUDED_FROM_LIVEPREVIEW = 13, + WCA_VIDEO_OVERLAY_ACTIVE = 14, + WCA_FORCE_ACTIVEWINDOW_APPEARANCE = 15, + WCA_DISALLOW_PEEK = 16, + WCA_CLOAK = 17, + WCA_CLOAKED = 18, + WCA_ACCENT_POLICY = 19, + WCA_FREEZE_REPRESENTATION = 20, + WCA_EVER_UNCLOAKED = 21, + WCA_VISUAL_OWNER = 22, + WCA_HOLOGRAPHIC = 23, + WCA_EXCLUDED_FROM_DDA = 24, + WCA_PASSIVEUPDATEMODE = 25, + WCA_USEDARKMODECOLORS = 26, + WCA_LAST = 27 + }; + + struct WINDOWCOMPOSITIONATTRIBDATA + { + WINDOWCOMPOSITIONATTRIB Attrib; + PVOID pvData; + SIZE_T cbData; + }; + + using fnAllowDarkModeForWindow = BOOL(WINAPI *)(HWND hWnd, BOOL allow); + using fnSetPreferredAppMode = PreferredAppMode(WINAPI *)(PreferredAppMode appMode); + using fnSetWindowCompositionAttribute = BOOL(WINAPI *)(HWND hwnd, WINDOWCOMPOSITIONATTRIBDATA *); + +#endif + +} // namespace WinDark + +#endif // WINDARK_H diff --git a/src/windows_interface.cpp b/src/windows_interface.cpp new file mode 100644 index 0000000..bd86ad3 --- /dev/null +++ b/src/windows_interface.cpp @@ -0,0 +1,32 @@ +#include "windows_interface.h" + +/* Code of 'getWID', 'enum_windows_callback', 'is_main_window' from + * https://stackoverflow.com/questions/1888863/how-to-get-main-window-handle-from-process-id + * + * Return window ID of given process ID + */ + +HWND WindowsInterface::getWID(PROPID pid) +{ + handle_data data; + data.process_id = pid; + data.window_handle = 0; + EnumWindows(WindowsInterface::enum_windows_callback, (LPARAM)&data); + return data.window_handle; +} + +BOOL CALLBACK WindowsInterface::enum_windows_callback(HWND handle, LPARAM lParam) +{ + handle_data &data = *(handle_data *)lParam; + PROPID process_id = 0; + GetWindowThreadProcessId(handle, &process_id); + if (data.process_id != process_id || !is_main_window(handle)) + return TRUE; + data.window_handle = handle; + return FALSE; +} + +BOOL WindowsInterface::is_main_window(HWND handle) +{ + return GetWindow(handle, GW_OWNER) == (HWND)0 && IsWindowVisible(handle); +} diff --git a/src/windows_interface.h b/src/windows_interface.h new file mode 100644 index 0000000..81dd430 --- /dev/null +++ b/src/windows_interface.h @@ -0,0 +1,22 @@ +#ifndef WINDOWS_INTERFACE_H +#define WINDOWS_INTERFACE_H + +#include + +class WindowsInterface +{ + +public: + HWND getWID(PROPID pid); + +private: + struct handle_data + { + PROPID process_id; + HWND window_handle; + }; + static BOOL CALLBACK enum_windows_callback(HWND handle, LPARAM lParam); + static BOOL is_main_window(HWND handle); +}; + +#endif // WINDOWS_INTERFACE_H diff --git a/streamlinkerino.pro b/streamlinkerino.pro index 09e53b1..aec1825 100644 --- a/streamlinkerino.pro +++ b/streamlinkerino.pro @@ -3,31 +3,23 @@ QT += core gui greaterThan(QT_MAJOR_VERSION, 4): QT += widgets CONFIG += c++11 -# You can make your code fail to compile if it uses deprecated APIs. -# In order to do so, uncomment the following line. -#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 -INCLUDEPATH += src/ SOURCES += \ - src/ChatterinoMonitor.cpp \ src/main.cpp \ - src/MainWindow.cpp \ - src/Submodules.cpp \ - src/WindowsMatchingPID.cpp + src/mainwindow.cpp \ + src/submodules.cpp \ + src/windark.cpp \ + src/windows_interface.cpp HEADERS += \ - src/ChatterinoMonitor.h \ - src/MainWindow.h \ - src/Submodules.h \ - src/WindowsMatchingPID.h + src/debugging.h \ + src/mainwindow.h \ + src/submodules.h \ + src/windark.h \ + src/windows_interface.h FORMS += \ - src/Forms/MainWindow.ui \ - src/Forms/SubmodulesDialog.ui + src/Forms/mainwindow.ui \ + src/Forms/submodulesDialog.ui -# Default rules for deployment. -qnx: target.path = /tmp/$${TARGET}/bin -else: unix:!android: target.path = /opt/$${TARGET}/bin -!isEmpty(target.path): INSTALLS += target - -LIBS += -lX11 +LIBS += -luser32 diff --git a/streamlinkerino.pro.user b/streamlinkerino.pro.user index 0f6a756..6af1652 100644 --- a/streamlinkerino.pro.user +++ b/streamlinkerino.pro.user @@ -1,10 +1,10 @@ - + EnvironmentId - {a23f6e12-a438-4505-ac05-fd9a0294c4df} + {8e216bbf-37f8-4e6b-b033-f9e84ef5debb} ProjectExplorer.Project.ActiveTarget @@ -70,19 +70,24 @@ 0 true - + + -fno-delayed-template-parsing + true - Builtin.Questionable + Builtin.BuildSystem true true Builtin.DefaultTidyAndClazy - 2 + 4 true + + true + @@ -91,13 +96,53 @@ Desktop Desktop Desktop - {a501c5af-71b1-4b4b-96b5-fa69dc603337} + {2ab4bf8b-ca68-46fa-833e-263f2ba77bce} 0 0 0 - /home/inathero/Gits/streamlinkerino/build - /home/inathero/Gits/streamlinkerino/build + 0 + C:\Users\USER-\Qt\build-streamlinkerino-Desktop-Debug + C:/Users/USER-/Qt/build-streamlinkerino-Desktop-Debug + + + true + QtProjectManager.QMakeBuildStep + false + + + + true + Qt4ProjectManager.MakeStep + + 2 + Build + Build + ProjectExplorer.BuildSteps.Build + + + + true + Qt4ProjectManager.MakeStep + clean + + 1 + Clean + Clean + ProjectExplorer.BuildSteps.Clean + + 2 + false + + false + + Debug + Qt4ProjectManager.Qt4BuildConfiguration + 2 + + + C:\Users\USER-\Qt\build-streamlinkerino-Desktop-Release + C:/Users/USER-/Qt/build-streamlinkerino-Desktop-Release true @@ -134,9 +179,50 @@ Qt4ProjectManager.Qt4BuildConfiguration 0 0 - 0 - 1 + + 0 + C:\Users\USER-\Qt\build-streamlinkerino-Desktop-Profile + C:/Users/USER-/Qt/build-streamlinkerino-Desktop-Profile + + + true + QtProjectManager.QMakeBuildStep + false + + + + true + Qt4ProjectManager.MakeStep + + 2 + Build + Build + ProjectExplorer.BuildSteps.Build + + + + true + Qt4ProjectManager.MakeStep + clean + + 1 + Clean + Clean + ProjectExplorer.BuildSteps.Clean + + 2 + false + + false + + Profile + Qt4ProjectManager.Qt4BuildConfiguration + 0 + 0 + 0 + + 3 0 @@ -151,26 +237,21 @@ 1 - dwarf - - cpu-cycles - - -F true true true 2 - Qt4ProjectManager.Qt4RunConfiguration:/home/inathero/Gits/streamlinkerino/streamlinkerino.pro - /home/inathero/Gits/streamlinkerino/streamlinkerino.pro + streamlinkerino2 + Qt4ProjectManager.Qt4RunConfiguration:C:/Users/USER-/Qt/Streamlinkerino/streamlinkerino.pro + C:/Users/USER-/Qt/Streamlinkerino/streamlinkerino.pro false true true false true - true - /home/inathero/Gits/streamlinkerino/build + C:/Users/USER-/Qt/build-streamlinkerino-Desktop-Debug 1