feat: add command line argument to select/add tab with a channel (#5111)

This commit is contained in:
nerix 2024-01-20 13:20:40 +01:00 committed by GitHub
parent acee654bd2
commit 7951af6104
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 174 additions and 2 deletions

View file

@ -24,6 +24,7 @@
- Minor: Improved color selection and display. (#5057) - Minor: Improved color selection and display. (#5057)
- Minor: Improved Streamlink documentation in the settings dialog. (#5076) - Minor: Improved Streamlink documentation in the settings dialog. (#5076)
- Minor: Normalized the input padding between light & dark themes. (#5095) - Minor: Normalized the input padding between light & dark themes. (#5095)
- Minor: Add `--activate <channel>` (or `-a`) command line option to activate or add a Twitch channel. (#5111)
- Bugfix: Fixed an issue where certain emojis did not send to Twitch chat correctly. (#4840) - Bugfix: Fixed an issue where certain emojis did not send to Twitch chat correctly. (#4840)
- Bugfix: Fixed capitalized channel names in log inclusion list not being logged. (#4848) - Bugfix: Fixed capitalized channel names in log inclusion list not being logged. (#4848)
- Bugfix: Trimmed custom streamlink paths on all platforms making sure you don't accidentally add spaces at the beginning or end of its path. (#4834) - Bugfix: Trimmed custom streamlink paths on all platforms making sure you don't accidentally add spaces at the beginning or end of its path. (#4834)

View file

@ -17,6 +17,8 @@
namespace { namespace {
using namespace chatterino;
template <class... Args> template <class... Args>
QCommandLineOption hiddenOption(Args... args) QCommandLineOption hiddenOption(Args... args)
{ {
@ -62,6 +64,29 @@ QStringList extractCommandLine(
return args; return args;
} }
std::optional<Args::Channel> parseActivateOption(QString input)
{
auto colon = input.indexOf(u':');
if (colon >= 0)
{
auto ty = input.left(colon);
if (ty != u"t")
{
qCWarning(chatterinoApp).nospace()
<< "Failed to parse active channel (unknown type: " << ty
<< ")";
return std::nullopt;
}
input = input.mid(colon + 1);
}
return Args::Channel{
.provider = ProviderId::Twitch,
.name = input,
};
}
} // namespace } // namespace
namespace chatterino { namespace chatterino {
@ -100,6 +125,14 @@ Args::Args(const QApplication &app, const Paths &paths)
"If platform isn't specified, default is Twitch.", "If platform isn't specified, default is Twitch.",
"t:channel1;t:channel2;..."); "t:channel1;t:channel2;...");
QCommandLineOption activateOption(
{"a", "activate"},
"Activate the tab with this channel or add one in the main "
"window.\nOnly Twitch is "
"supported at the moment (prefix: 't:').\nIf the platform isn't "
"specified, Twitch is assumed.",
"t:channel");
parser.addOptions({ parser.addOptions({
{{"V", "version"}, "Displays version information."}, {{"V", "version"}, "Displays version information."},
crashRecoveryOption, crashRecoveryOption,
@ -110,6 +143,7 @@ Args::Args(const QApplication &app, const Paths &paths)
verboseOption, verboseOption,
safeModeOption, safeModeOption,
channelLayout, channelLayout,
activateOption,
}); });
if (!parser.parse(app.arguments())) if (!parser.parse(app.arguments()))
@ -163,10 +197,17 @@ Args::Args(const QApplication &app, const Paths &paths)
this->safeMode = true; this->safeMode = true;
} }
if (parser.isSet(activateOption))
{
this->activateChannel =
parseActivateOption(parser.value(activateOption));
}
this->currentArguments_ = extractCommandLine(parser, { this->currentArguments_ = extractCommandLine(parser, {
verboseOption, verboseOption,
safeModeOption, safeModeOption,
channelLayout, channelLayout,
activateOption,
}); });
} }

View file

@ -1,5 +1,6 @@
#pragma once #pragma once
#include "common/ProviderId.hpp"
#include "common/WindowDescriptors.hpp" #include "common/WindowDescriptors.hpp"
#include <QApplication> #include <QApplication>
@ -26,12 +27,18 @@ class Paths;
/// -v, --verbose /// -v, --verbose
/// -V, --version /// -V, --version
/// -c, --channels=t:channel1;t:channel2;... /// -c, --channels=t:channel1;t:channel2;...
/// -a, --activate=t:channel
/// --safe-mode /// --safe-mode
/// ///
/// See documentation on `QGuiApplication` for documentation on Qt arguments like -platform. /// See documentation on `QGuiApplication` for documentation on Qt arguments like -platform.
class Args class Args
{ {
public: public:
struct Channel {
ProviderId provider;
QString name;
};
Args() = default; Args() = default;
Args(const QApplication &app, const Paths &paths); Args(const QApplication &app, const Paths &paths);
@ -52,6 +59,7 @@ public:
bool dontSaveSettings{}; bool dontSaveSettings{};
bool dontLoadMainWindow{}; bool dontLoadMainWindow{};
std::optional<WindowLayout> customChannelLayout; std::optional<WindowLayout> customChannelLayout;
std::optional<Channel> activateChannel;
bool verbose{}; bool verbose{};
bool safeMode{}; bool safeMode{};

View file

@ -229,4 +229,108 @@ WindowLayout WindowLayout::loadFromFile(const QString &path)
return layout; return layout;
} }
void WindowLayout::activateOrAddChannel(ProviderId provider,
const QString &name)
{
if (provider != ProviderId::Twitch || name.startsWith(u'/') ||
name.startsWith(u'$'))
{
qCWarning(chatterinoWindowmanager)
<< "Only twitch channels can be set as active";
return;
}
auto mainWindow = std::find_if(this->windows_.begin(), this->windows_.end(),
[](const auto &win) {
return win.type_ == WindowType::Main;
});
if (mainWindow == this->windows_.end())
{
this->windows_.emplace_back(WindowDescriptor{
.type_ = WindowType::Main,
.geometry_ = {-1, -1, -1, -1},
.tabs_ =
{
TabDescriptor{
.selected_ = true,
.rootNode_ = SplitNodeDescriptor{{
.type_ = "twitch",
.channelName_ = name,
}},
},
},
});
return;
}
TabDescriptor *bestTab = nullptr;
// The tab score is calculated as follows:
// +2 for every split
// +1 if the desired split has filters
// Thus lower is better and having one split of a channel is preferred over multiple
size_t bestTabScore = std::numeric_limits<size_t>::max();
for (auto &tab : mainWindow->tabs_)
{
tab.selected_ = false;
if (!tab.rootNode_)
{
continue;
}
// recursive visitor
struct Visitor {
const QString &spec;
size_t score = 0;
bool hasChannel = false;
void operator()(const SplitNodeDescriptor &split)
{
this->score += 2;
if (split.channelName_ == this->spec)
{
hasChannel = true;
if (!split.filters_.empty())
{
this->score += 1;
}
}
}
void operator()(const ContainerNodeDescriptor &container)
{
for (const auto &item : container.items_)
{
std::visit(*this, item);
}
}
} visitor{name};
std::visit(visitor, *tab.rootNode_);
if (visitor.hasChannel && visitor.score < bestTabScore)
{
bestTab = &tab;
bestTabScore = visitor.score;
}
}
if (bestTab)
{
bestTab->selected_ = true;
return;
}
TabDescriptor tab{
.selected_ = true,
.rootNode_ = SplitNodeDescriptor{{
.type_ = "twitch",
.channelName_ = name,
}},
};
mainWindow->tabs_.emplace_back(tab);
}
} // namespace chatterino } // namespace chatterino

View file

@ -1,5 +1,7 @@
#pragma once #pragma once
#include "common/ProviderId.hpp"
#include <QJsonObject> #include <QJsonObject>
#include <QList> #include <QList>
#include <QRect> #include <QRect>
@ -95,12 +97,22 @@ struct WindowDescriptor {
class WindowLayout class WindowLayout
{ {
public: public:
static WindowLayout loadFromFile(const QString &path);
// A complete window layout has a single emote popup position that is shared among all windows // A complete window layout has a single emote popup position that is shared among all windows
QPoint emotePopupPos_; QPoint emotePopupPos_;
std::vector<WindowDescriptor> windows_; std::vector<WindowDescriptor> windows_;
/// Selects the split containing the channel specified by @a name for the specified
/// @a provider. Currently, only Twitch is supported as the provider
/// and special channels (such as /mentions) are ignored.
///
/// Tabs with fewer splits are preferred.
/// Channels without filters are preferred.
///
/// If no split with the channel exists, a new one is added.
/// If no window exists, a new one is added.
void activateOrAddChannel(ProviderId provider, const QString &name);
static WindowLayout loadFromFile(const QString &path);
}; };
} // namespace chatterino } // namespace chatterino

View file

@ -365,6 +365,12 @@ void WindowManager::initialize(Settings &settings, const Paths &paths)
windowLayout = this->loadWindowLayoutFromFile(); windowLayout = this->loadWindowLayoutFromFile();
} }
auto desired = getIApp()->getArgs().activateChannel;
if (desired)
{
windowLayout.activateOrAddChannel(desired->provider, desired->name);
}
this->emotePopupPos_ = windowLayout.emotePopupPos_; this->emotePopupPos_ = windowLayout.emotePopupPos_;
this->applyWindowLayout(windowLayout); this->applyWindowLayout(windowLayout);