Merge branch '4tf'

This commit is contained in:
fourtf 2018-08-06 18:41:30 +02:00
commit c71795da17
197 changed files with 3807 additions and 2801 deletions

View file

@ -17,6 +17,10 @@ DEFINES += QT_DEPRECATED_WARNINGS
PRECOMPILED_HEADER = src/PrecompiledHeader.hpp PRECOMPILED_HEADER = src/PrecompiledHeader.hpp
CONFIG += precompile_header CONFIG += precompile_header
debug {
DEFINES += QT_DEBUG
}
useBreakpad { useBreakpad {
LIBS += -L$$PWD/lib/qBreakpad/handler/build LIBS += -L$$PWD/lib/qBreakpad/handler/build
include(lib/qBreakpad/qBreakpad.pri) include(lib/qBreakpad/qBreakpad.pri)
@ -132,9 +136,7 @@ SOURCES += \
src/messages/MessageBuilder.cpp \ src/messages/MessageBuilder.cpp \
src/messages/MessageColor.cpp \ src/messages/MessageColor.cpp \
src/messages/MessageElement.cpp \ src/messages/MessageElement.cpp \
src/providers/bttv/BttvEmotes.cpp \
src/providers/emoji/Emojis.cpp \ src/providers/emoji/Emojis.cpp \
src/providers/ffz/FfzEmotes.cpp \
src/providers/irc/AbstractIrcServer.cpp \ src/providers/irc/AbstractIrcServer.cpp \
src/providers/irc/IrcAccount.cpp \ src/providers/irc/IrcAccount.cpp \
src/providers/irc/IrcChannel2.cpp \ src/providers/irc/IrcChannel2.cpp \
@ -232,7 +234,21 @@ SOURCES += \
src/widgets/dialogs/UpdateDialog.cpp \ src/widgets/dialogs/UpdateDialog.cpp \
src/widgets/settingspages/IgnoresPage.cpp \ src/widgets/settingspages/IgnoresPage.cpp \
src/providers/twitch/PubsubClient.cpp \ src/providers/twitch/PubsubClient.cpp \
src/providers/twitch/TwitchApi.cpp src/providers/twitch/TwitchApi.cpp \
src/messages/Emote.cpp \
src/messages/EmoteMap.cpp \
src/messages/ImageSet.cpp \
src/providers/bttv/BttvEmotes.cpp \
src/providers/ffz/FfzEmotes.cpp \
src/autogenerated/ResourcesAutogen.cpp \
src/singletons/Badges.cpp \
src/providers/twitch/TwitchBadges.cpp \
src/providers/chatterino/ChatterinoBadges.cpp \
src/providers/twitch/TwitchParseCheerEmotes.cpp \
src/providers/bttv/LoadBttvChannelEmote.cpp \
src/util/JsonQuery.cpp \
src/RunGui.cpp \
src/BrowserExtension.cpp
HEADERS += \ HEADERS += \
src/Application.hpp \ src/Application.hpp \
@ -292,9 +308,7 @@ HEADERS += \
src/messages/MessageParseArgs.hpp \ src/messages/MessageParseArgs.hpp \
src/messages/Selection.hpp \ src/messages/Selection.hpp \
src/PrecompiledHeader.hpp \ src/PrecompiledHeader.hpp \
src/providers/bttv/BttvEmotes.hpp \
src/providers/emoji/Emojis.hpp \ src/providers/emoji/Emojis.hpp \
src/providers/ffz/FfzEmotes.hpp \
src/providers/irc/AbstractIrcServer.hpp \ src/providers/irc/AbstractIrcServer.hpp \
src/providers/irc/IrcAccount.hpp \ src/providers/irc/IrcAccount.hpp \
src/providers/irc/IrcChannel2.hpp \ src/providers/irc/IrcChannel2.hpp \
@ -413,10 +427,28 @@ HEADERS += \
src/widgets/dialogs/UpdateDialog.hpp \ src/widgets/dialogs/UpdateDialog.hpp \
src/widgets/settingspages/IgnoresPage.hpp \ src/widgets/settingspages/IgnoresPage.hpp \
src/providers/twitch/PubsubClient.hpp \ src/providers/twitch/PubsubClient.hpp \
src/providers/twitch/TwitchApi.hpp src/providers/twitch/TwitchApi.hpp \
src/messages/Emote.hpp \
src/messages/EmoteMap.hpp \
src/messages/EmoteCache.hpp \
src/messages/ImageSet.hpp \
src/common/Outcome.hpp \
src/providers/bttv/BttvEmotes.hpp \
src/providers/ffz/FfzEmotes.hpp \
src/autogenerated/ResourcesAutogen.hpp \
src/singletons/Badges.hpp \
src/providers/twitch/TwitchBadges.hpp \
src/providers/chatterino/ChatterinoBadges.hpp \
src/common/Aliases.hpp \
src/providers/twitch/TwitchParseCheerEmotes.hpp \
src/providers/bttv/LoadBttvChannelEmote.hpp \
src/util/JsonQuery.hpp \
src/RunGui.hpp \
src/BrowserExtension.hpp
RESOURCES += \ RESOURCES += \
resources/resources.qrc \ resources/resources.qrc \
resources/resources_autogenerated.qrc
DISTFILES += DISTFILES +=

View file

@ -0,0 +1,38 @@
resources_header = \
'''<RCC>
<qresource prefix="/">'''
resources_footer = \
''' </qresource>
</RCC>'''
header_header = \
'''#include <QPixmap>
#include "common/Singleton.hpp"
namespace chatterino {
class Resources2 : public Singleton {
public:
Resources2();
'''
header_footer = \
'''};
} // namespace chatterino'''
source_header = \
'''#include "ResourcesAutogen.hpp"
namespace chatterino {
Resources2::Resources2()
{
'''
source_footer = \
'''}
} // namespace chatterino'''

View file

Before

Width:  |  Height:  |  Size: 8.1 KiB

After

Width:  |  Height:  |  Size: 8.1 KiB

View file

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

View file

Before

Width:  |  Height:  |  Size: 847 B

After

Width:  |  Height:  |  Size: 847 B

View file

Before

Width:  |  Height:  |  Size: 847 B

After

Width:  |  Height:  |  Size: 847 B

View file

Before

Width:  |  Height:  |  Size: 307 B

After

Width:  |  Height:  |  Size: 307 B

View file

Before

Width:  |  Height:  |  Size: 328 B

After

Width:  |  Height:  |  Size: 328 B

View file

Before

Width:  |  Height:  |  Size: 376 B

After

Width:  |  Height:  |  Size: 376 B

View file

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

View file

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 17 KiB

View file

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

View file

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

View file

Before

Width:  |  Height:  |  Size: 8.2 KiB

After

Width:  |  Height:  |  Size: 8.2 KiB

View file

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

View file

Before

Width:  |  Height:  |  Size: 349 B

After

Width:  |  Height:  |  Size: 349 B

View file

Before

Width:  |  Height:  |  Size: 344 B

After

Width:  |  Height:  |  Size: 344 B

View file

Before

Width:  |  Height:  |  Size: 332 B

After

Width:  |  Height:  |  Size: 332 B

BIN
resources/error.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 685 B

61
resources/generate_resources.py Executable file
View file

@ -0,0 +1,61 @@
#!/usr/bin/env python3
from pathlib import Path
from _generate_resources import *
ignored_files = ['qt.conf', 'resources.qrc', 'resources_autogenerated.qrc', 'windows.rc',
'generate_resources.py', '_generate_resources.py']
ignored_directories = ['__pycache__']
def isNotIgnored(file):
return str(file) not in ignored_files
all_files = list(filter(isNotIgnored, \
filter(Path.is_file, Path('.').glob('**/*'))))
image_files = list(filter(isNotIgnored, \
filter(Path.is_file, Path('.').glob('**/*.png'))))
with open('./resources_autogenerated.qrc', 'w') as out:
out.write(resources_header)
for file in all_files:
out.write(f" <file>{str(file)}</file>\n")
out.write(resources_footer)
with open('../src/autogenerated/ResourcesAutogen.cpp', 'w') as out:
out.write(source_header)
for file in sorted(image_files):
var_name = str(file.with_suffix("")).replace("/",".")
out.write(f' this->{var_name}')
out.write(f' = QPixmap(":/{file}");\n')
out.write(source_footer)
def writeHeader(out, name, element, indent):
if isinstance(element, dict):
if name != "":
out.write(f"{indent}struct {{\n")
for (key, value) in element.items():
writeHeader(out, key, value, indent + ' ')
if name != "":
out.write(f"{indent}}} {name};\n");
else:
out.write(f"{indent}QPixmap {element};\n")
with open('../src/autogenerated/ResourcesAutogen.hpp', 'w') as out:
out.write(header_header)
elements = {}
for file in sorted(image_files):
elements_ref = elements
directories = str(file).split('/')[:-1]
filename = file.stem
for directory in directories:
if directory not in elements_ref:
if directory not in ignored_directories:
elements_ref[directory] = {}
elements_ref = elements_ref[directory]
elements_ref[filename] = filename
writeHeader(out, "", elements, '')
out.write(header_footer)

View file

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 347 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 680 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 403 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 368 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 660 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 280 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 179 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 286 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 480 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 360 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 351 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 330 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 285 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 362 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 236 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 354 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 274 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 279 B

View file

Before

Width:  |  Height:  |  Size: 3.8 KiB

After

Width:  |  Height:  |  Size: 3.8 KiB

View file

@ -1,81 +1,4 @@
<RCC> <RCC>
<qresource prefix="/">
<file>images/AppearanceEditorPart_16x.png</file>
<file>images/BrowserLink_16x.png</file>
<file>images/cheer1.png</file>
<file>images/cheer100.png</file>
<file>images/cheer1000.png</file>
<file>images/cheer10000.png</file>
<file>images/cheer100000.png</file>
<file>images/cheer5000.png</file>
<file>images/verified.png</file>
<file>images/CopyLongTextToClipboard_16x.png</file>
<file>images/CustomActionEditor_16x.png</file>
<file>images/Emoji_Color_1F60A_19.png</file>
<file>images/Filter_16x.png</file>
<file>images/format_Bold_16xLG.png</file>
<file>images/Message_16xLG.png</file>
<file>images/settings.png</file>
<file>images/tool_moreCollapser_off16.png</file>
<file>images/twitchprime_bg.png</file>
<file>qss/settings.qss</file>
<file>images/admin_bg.png</file>
<file>images/broadcaster_bg.png</file>
<file>images/globalmod_bg.png</file>
<file>images/moderator_bg.png</file>
<file>images/staff_bg.png</file>
<file>images/turbo_bg.png</file>
<file>emojidata.txt</file>
<file>images/button_ban.png</file>
<file>images/button_timeout.png</file>
<file>images/StatusAnnotations_Blocked_16xLG_color.png</file>
<file>images/UserProfile_22x.png</file>
<file>images/VSO_Link_blue_16x.png</file>
<file>sounds/ping2.wav</file>
<file>images/subscriber.png</file>
<file>images/collapse.png</file>
<file>images/emote.svg</file>
<file>images/notifications.svg</file>
<file>images/behave.svg</file>
<file>images/theme.svg</file>
<file>images/accounts.svg</file>
<file>images/chatterino2.icns</file>
<file>images/icon.png</file>
<file>images/commands.svg</file>
<file>images/aboutlogo.png</file>
<file>images/about.svg</file>
<file>images/moderatormode_disabled.png</file>
<file>images/moderatormode_enabled.png</file>
<file>images/split/splitdown.png</file>
<file>images/split/splitleft.png</file>
<file>images/split/splitright.png</file>
<file>images/split/splitup.png</file>
<file>images/split/splitmove.png</file>
<file>licenses/boost_boost.txt</file>
<file>licenses/fmt_bsd2.txt</file>
<file>licenses/libcommuni_BSD3.txt</file>
<file>licenses/openssl.txt</file>
<file>licenses/pajlada_settings.txt</file>
<file>licenses/pajlada_signals.txt</file>
<file>licenses/qt_lgpl-3.0.txt</file>
<file>licenses/rapidjson.txt</file>
<file>licenses/websocketpp.txt</file>
<file>emoji.json</file>
<file>images/buttons/ban.png</file>
<file>images/buttons/mod.png</file>
<file>images/buttons/unban.png</file>
<file>images/buttons/unmod.png</file>
<file>images/emote_dark.svg</file>
<file>tlds.txt</file>
<file>images/menu_black.png</file>
<file>images/menu_white.png</file>
<file>contributors.txt</file>
<file>avatars/fourtf.png</file>
<file>avatars/pajlada.png</file>
<file>images/download_update.png</file>
<file>images/download_update_error.png</file>
<file>images/pajaDank.png</file>
</qresource>
<qresource prefix="/qt/etc"> <qresource prefix="/qt/etc">
<file>qt.conf</file> <file>qt.conf</file>
</qresource> </qresource>

View file

@ -0,0 +1,64 @@
<RCC>
<qresource prefix="/"> <file>pajaDank.png</file>
<file>icon.png</file>
<file>emojidata.txt</file>
<file>contributors.txt</file>
<file>error.png</file>
<file>emoji.json</file>
<file>icon.ico</file>
<file>tlds.txt</file>
<file>chatterino2.icns</file>
<file>qss/settings.qss</file>
<file>__pycache__/_generate_resources.cpython-36.pyc</file>
<file>licenses/fmt_bsd2.txt</file>
<file>licenses/openssl.txt</file>
<file>licenses/pajlada_settings.txt</file>
<file>licenses/qt_lgpl-3.0.txt</file>
<file>licenses/pajlada_signals.txt</file>
<file>licenses/rapidjson.txt</file>
<file>licenses/websocketpp.txt</file>
<file>licenses/boost_boost.txt</file>
<file>licenses/libcommuni_BSD3.txt</file>
<file>settings/aboutlogo.png</file>
<file>settings/behave.svg</file>
<file>settings/accounts.svg</file>
<file>settings/about.svg</file>
<file>settings/notifications.svg</file>
<file>settings/commands.svg</file>
<file>settings/theme.svg</file>
<file>split/up.png</file>
<file>split/left.png</file>
<file>split/move.png</file>
<file>split/right.png</file>
<file>split/down.png</file>
<file>buttons/unban.png</file>
<file>buttons/menuDark.png</file>
<file>buttons/mod.png</file>
<file>buttons/emote.svg</file>
<file>buttons/modModeEnabled2.png</file>
<file>buttons/ban.png</file>
<file>buttons/unmod.png</file>
<file>buttons/emoteDark.svg</file>
<file>buttons/updateError.png</file>
<file>buttons/modModeDisabled.png</file>
<file>buttons/modModeDisabled2.png</file>
<file>buttons/modModeEnabled.png</file>
<file>buttons/menuLight.png</file>
<file>buttons/update.png</file>
<file>buttons/timeout.png</file>
<file>buttons/banRed.png</file>
<file>sounds/ping2.wav</file>
<file>twitch/prime.png</file>
<file>twitch/verified.png</file>
<file>twitch/admin.png</file>
<file>twitch/subscriber.png</file>
<file>twitch/turbo.png</file>
<file>twitch/moderator.png</file>
<file>twitch/globalmod.png</file>
<file>twitch/cheer1.png</file>
<file>twitch/broadcaster.png</file>
<file>twitch/staff.png</file>
<file>avatars/fourtf.png</file>
<file>avatars/pajlada.png</file>
</qresource>
</RCC>

View file

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

View file

Before

Width:  |  Height:  |  Size: 51 KiB

After

Width:  |  Height:  |  Size: 51 KiB

View file

Before

Width:  |  Height:  |  Size: 805 B

After

Width:  |  Height:  |  Size: 805 B

View file

Before

Width:  |  Height:  |  Size: 820 B

After

Width:  |  Height:  |  Size: 820 B

View file

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

View file

Before

Width:  |  Height:  |  Size: 955 B

After

Width:  |  Height:  |  Size: 955 B

View file

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

View file

Before

Width:  |  Height:  |  Size: 361 B

After

Width:  |  Height:  |  Size: 361 B

View file

Before

Width:  |  Height:  |  Size: 437 B

After

Width:  |  Height:  |  Size: 437 B

View file

Before

Width:  |  Height:  |  Size: 772 B

After

Width:  |  Height:  |  Size: 772 B

View file

Before

Width:  |  Height:  |  Size: 494 B

After

Width:  |  Height:  |  Size: 494 B

View file

Before

Width:  |  Height:  |  Size: 361 B

After

Width:  |  Height:  |  Size: 361 B

View file

Before

Width:  |  Height:  |  Size: 439 B

After

Width:  |  Height:  |  Size: 439 B

View file

Before

Width:  |  Height:  |  Size: 191 B

After

Width:  |  Height:  |  Size: 191 B

View file

Before

Width:  |  Height:  |  Size: 255 B

After

Width:  |  Height:  |  Size: 255 B

View file

Before

Width:  |  Height:  |  Size: 397 B

After

Width:  |  Height:  |  Size: 397 B

View file

Before

Width:  |  Height:  |  Size: 376 B

After

Width:  |  Height:  |  Size: 376 B

View file

Before

Width:  |  Height:  |  Size: 116 B

After

Width:  |  Height:  |  Size: 116 B

View file

Before

Width:  |  Height:  |  Size: 269 B

After

Width:  |  Height:  |  Size: 269 B

View file

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

View file

Before

Width:  |  Height:  |  Size: 257 B

After

Width:  |  Height:  |  Size: 257 B

View file

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

View file

@ -6,9 +6,10 @@
#include "controllers/ignores/IgnoreController.hpp" #include "controllers/ignores/IgnoreController.hpp"
#include "controllers/moderationactions/ModerationActions.hpp" #include "controllers/moderationactions/ModerationActions.hpp"
#include "controllers/taggedusers/TaggedUsersController.hpp" #include "controllers/taggedusers/TaggedUsersController.hpp"
#include "providers/bttv/BttvEmotes.hpp"
#include "providers/ffz/FfzEmotes.hpp"
#include "providers/twitch/PubsubClient.hpp" #include "providers/twitch/PubsubClient.hpp"
#include "providers/twitch/TwitchServer.hpp" #include "providers/twitch/TwitchServer.hpp"
#include "singletons/Emotes.hpp"
#include "singletons/Fonts.hpp" #include "singletons/Fonts.hpp"
#include "singletons/Logging.hpp" #include "singletons/Logging.hpp"
#include "singletons/NativeMessaging.hpp" #include "singletons/NativeMessaging.hpp"
@ -24,69 +25,75 @@
namespace chatterino { namespace chatterino {
static std::atomic<bool> isAppConstructed{false};
static std::atomic<bool> isAppInitialized{false}; static std::atomic<bool> isAppInitialized{false};
static Application *staticApp = nullptr; Application *Application::instance = nullptr;
// this class is responsible for handling the workflow of Chatterino // this class is responsible for handling the workflow of Chatterino
// It will create the instances of the major classes, and connect their signals to each other // It will create the instances of the major classes, and connect their signals to each other
Application::Application(int _argc, char **_argv) Application::Application(Settings &_settings, Paths &_paths)
: argc_(_argc) : settings(&_settings)
, argv_(_argv) , paths(&_paths)
, resources(&this->emplace<Resources2>())
, themes(&this->emplace<Theme>())
, fonts(&this->emplace<Fonts>())
, emotes(&this->emplace<Emotes>())
, windows(&this->emplace<WindowManager>())
, accounts(&this->emplace<AccountController>())
, commands(&this->emplace<CommandController>())
, highlights(&this->emplace<HighlightController>())
, ignores(&this->emplace<IgnoreController>())
, taggedUsers(&this->emplace<TaggedUsersController>())
, moderationActions(&this->emplace<ModerationActions>())
, twitch2(&this->emplace<TwitchServer>())
, logging(&this->emplace<Logging>())
{ {
getSettings()->initialize(); this->instance = this;
getSettings()->load();
}
void Application::construct() this->fonts->fontChanged.connect([this]() { this->windows->layoutChannelViews(); });
{
assert(isAppConstructed == false);
isAppConstructed = true;
// 1. Instantiate all classes
this->settings = getSettings();
this->paths = getPaths();
this->addSingleton(this->themes = new Theme);
this->addSingleton(this->windows = new WindowManager);
this->addSingleton(this->logging = new Logging);
this->addSingleton(this->commands = new CommandController);
this->addSingleton(this->highlights = new HighlightController);
this->addSingleton(this->ignores = new IgnoreController);
this->addSingleton(this->taggedUsers = new TaggedUsersController);
this->addSingleton(this->accounts = new AccountController);
this->addSingleton(this->emotes = new Emotes);
this->addSingleton(this->fonts = new Fonts);
this->addSingleton(this->resources = new Resources);
this->addSingleton(this->moderationActions = new ModerationActions);
this->addSingleton(this->twitch2 = new TwitchServer);
this->twitch.server = this->twitch2; this->twitch.server = this->twitch2;
this->twitch.pubsub = this->twitch2->pubsub; this->twitch.pubsub = this->twitch2->pubsub;
} }
void Application::instantiate(int argc, char **argv) void Application::initialize(Settings &settings, Paths &paths)
{
assert(staticApp == nullptr);
staticApp = new Application(argc, argv);
}
void Application::initialize()
{ {
assert(isAppInitialized == false); assert(isAppInitialized == false);
isAppInitialized = true; isAppInitialized = true;
// 2. Initialize/load classes for (auto &singleton : this->singletons_) {
for (Singleton *singleton : this->singletons_) { singleton->initialize(settings, paths);
singleton->initialize(*this);
} }
// XXX
this->windows->updateWordTypeMask(); this->windows->updateWordTypeMask();
this->initNm();
this->initPubsub();
}
int Application::run(QApplication &qtApp)
{
assert(isAppInitialized);
this->twitch.server->connect();
this->windows->getMainWindow().show();
return qtApp.exec();
}
void Application::save()
{
for (auto &singleton : this->singletons_) {
singleton->save();
}
}
void Application::initNm()
{
#ifdef Q_OS_WIN #ifdef Q_OS_WIN
#ifdef QT_DEBUG #ifdef QT_DEBUG
#ifdef C_DEBUG_NM #ifdef C_DEBUG_NM
@ -98,7 +105,10 @@ void Application::initialize()
this->nativeMessaging->openGuiMessageQueue(); this->nativeMessaging->openGuiMessageQueue();
#endif #endif
#endif #endif
}
void Application::initPubsub()
{
this->twitch.pubsub->signals_.whisper.sent.connect([](const auto &msg) { this->twitch.pubsub->signals_.whisper.sent.connect([](const auto &msg) {
Log("WHISPER SENT LOL"); // Log("WHISPER SENT LOL"); //
}); });
@ -197,39 +207,11 @@ void Application::initialize()
RequestModerationActions(); RequestModerationActions();
} }
int Application::run(QApplication &qtApp)
{
// Start connecting to the IRC Servers (Twitch only for now)
this->twitch.server->connect();
// Show main window
this->windows->getMainWindow().show();
return qtApp.exec();
}
void Application::save()
{
for (Singleton *singleton : this->singletons_) {
singleton->save();
}
}
void Application::addSingleton(Singleton *singleton)
{
this->singletons_.push_back(singleton);
}
Application *getApp() Application *getApp()
{ {
assert(staticApp != nullptr); assert(Application::instance != nullptr);
return staticApp; return Application::instance;
}
bool appInitialized()
{
return isAppInitialized;
} }
} // namespace chatterino } // namespace chatterino

View file

@ -1,13 +1,13 @@
#pragma once #pragma once
#include "common/Singleton.hpp"
#include "singletons/Resources.hpp" #include "singletons/Resources.hpp"
#include <QApplication> #include <QApplication>
#include <memory>
namespace chatterino { namespace chatterino {
class Singleton;
class TwitchServer; class TwitchServer;
class PubSub; class PubSub;
@ -24,45 +24,47 @@ class Logging;
class Paths; class Paths;
class AccountManager; class AccountManager;
class Emotes; class Emotes;
class NativeMessaging;
class Settings; class Settings;
class Fonts; class Fonts;
class Resources; class Resources;
class Application class Application
{ {
Application(int _argc, char **_argv); std::vector<std::unique_ptr<Singleton>> singletons_;
int argc_;
char **argv_;
public: public:
static void instantiate(int argc_, char **argv_); static Application *instance;
~Application() = delete; Application(Settings &settings, Paths &paths);
void construct(); void initialize(Settings &settings, Paths &paths);
void initialize();
void load(); void load();
void save();
int run(QApplication &qtApp); int run(QApplication &qtApp);
friend void test(); friend void test();
[[deprecated("use getSettings() instead")]] Settings *settings = nullptr; Settings *const settings = nullptr;
[[deprecated("use getPaths() instead")]] Paths *paths = nullptr; Paths *const paths = nullptr;
Resources2 *const resources;
Theme *themes = nullptr; Theme *const themes = nullptr;
WindowManager *windows = nullptr; Fonts *const fonts = nullptr;
Logging *logging = nullptr; Emotes *const emotes = nullptr;
CommandController *commands = nullptr; WindowManager *const windows = nullptr;
HighlightController *highlights = nullptr;
IgnoreController *ignores = nullptr; AccountController *const accounts = nullptr;
TaggedUsersController *taggedUsers = nullptr; CommandController *const commands = nullptr;
AccountController *accounts = nullptr; HighlightController *const highlights = nullptr;
Emotes *emotes = nullptr; IgnoreController *const ignores = nullptr;
NativeMessaging *nativeMessaging = nullptr; TaggedUsersController *const taggedUsers = nullptr;
Fonts *fonts = nullptr; ModerationActions *const moderationActions = nullptr;
Resources *resources = nullptr; TwitchServer *const twitch2 = nullptr;
ModerationActions *moderationActions = nullptr;
TwitchServer *twitch2 = nullptr; [[deprecated]] Logging *const logging = nullptr;
/// Provider-specific /// Provider-specific
struct { struct {
@ -70,22 +72,20 @@ public:
[[deprecated("use twitch2->pubsub instead")]] PubSub *pubsub = nullptr; [[deprecated("use twitch2->pubsub instead")]] PubSub *pubsub = nullptr;
} twitch; } twitch;
void save();
// Special application mode that only initializes the native messaging host
static void runNativeMessagingHost();
private: private:
void addSingleton(Singleton *singleton); void addSingleton(Singleton *singleton);
void initPubsub();
void initNm();
int argc_; template <typename T, typename = std::enable_if_t<std::is_base_of<Singleton, T>::value>>
char **argv_; T &emplace()
{
std::vector<Singleton *> singletons_; auto t = new T;
this->singletons_.push_back(std::unique_ptr<T>(t));
return *t;
}
}; };
Application *getApp(); Application *getApp();
bool appInitialized();
} // namespace chatterino } // namespace chatterino

88
src/BrowserExtension.cpp Normal file
View file

@ -0,0 +1,88 @@
#include "BrowserExtension.hpp"
#include "singletons/NativeMessaging.hpp"
#include <QStringList>
#include <QTimer>
#include <fstream>
#include <iostream>
#include <memory>
#ifdef Q_OS_WIN
#include <fcntl.h>
#include <io.h>
#include <stdio.h>
#endif
namespace chatterino {
namespace {
void initFileMode()
{
#ifdef Q_OS_WIN
_setmode(_fileno(stdin), _O_BINARY);
_setmode(_fileno(stdout), _O_BINARY);
#endif
}
void runLoop(NativeMessagingClient &client)
{
while (true) {
char size_c[4];
std::cin.read(size_c, 4);
if (std::cin.eof()) {
break;
}
uint32_t size = *reinterpret_cast<uint32_t *>(size_c);
#if 0
bool bigEndian = isBigEndian();
// To avoid breaking strict-aliasing rules and potentially inducing undefined behaviour, the following code can be run instead
uint32_t size = 0;
if (bigEndian) {
size = size_c[3] | static_cast<uint32_t>(size_c[2]) << 8 |
static_cast<uint32_t>(size_c[1]) << 16 | static_cast<uint32_t>(size_c[0]) << 24;
} else {
size = size_c[0] | static_cast<uint32_t>(size_c[1]) << 8 |
static_cast<uint32_t>(size_c[2]) << 16 | static_cast<uint32_t>(size_c[3]) << 24;
}
#endif
std::unique_ptr<char[]> b(new char[size + 1]);
std::cin.read(b.get(), size);
*(b.get() + size) = '\0';
client.sendMessage(QByteArray::fromRawData(b.get(), static_cast<int32_t>(size)));
}
}
} // namespace
bool shouldRunBrowserExtensionHost(const QStringList &args)
{
return args.size() > 0 &&
(args[0].startsWith("chrome-extension://") || args[0].endsWith(".json"));
}
void runBrowserExtensionHost()
{
initFileMode();
std::atomic<bool> ping(false);
QTimer timer;
QObject::connect(&timer, &QTimer::timeout, [&ping] {
if (!ping.exchange(false)) {
_Exit(0);
}
});
timer.setInterval(11000);
timer.start();
NativeMessagingClient client;
runLoop(client);
}
} // namespace chatterino

10
src/BrowserExtension.hpp Normal file
View file

@ -0,0 +1,10 @@
#pragma once
class QStringList;
namespace chatterino {
bool shouldRunBrowserExtensionHost(const QStringList &args);
void runBrowserExtensionHost();
} // namespace chatterino

130
src/RunGui.cpp Normal file
View file

@ -0,0 +1,130 @@
#include "RunGui.hpp"
#include <QApplication>
#include <QFile>
#include <QPalette>
#include <QStyleFactory>
#include "Application.hpp"
#include "common/NetworkManager.hpp"
#include "singletons/Paths.hpp"
#include "singletons/Updates.hpp"
#include "widgets/dialogs/LastRunCrashDialog.hpp"
#ifdef C_USE_BREAKPAD
#include <QBreakpadHandler.h>
#endif
// void initQt();
// void installCustomPalette();
// void showLastCrashDialog();
// void createRunningFile(const QString &path);
// void removeRunningFile(const QString &path);
namespace chatterino {
namespace {
void installCustomPalette()
{
// borrowed from
// https://stackoverflow.com/questions/15035767/is-the-qt-5-dark-fusion-theme-available-for-windows
QPalette darkPalette = qApp->palette();
darkPalette.setColor(QPalette::Window, QColor(22, 22, 22));
darkPalette.setColor(QPalette::WindowText, Qt::white);
darkPalette.setColor(QPalette::Text, Qt::white);
darkPalette.setColor(QPalette::Disabled, QPalette::WindowText, QColor(127, 127, 127));
darkPalette.setColor(QPalette::Base, QColor("#333"));
darkPalette.setColor(QPalette::AlternateBase, QColor("#444"));
darkPalette.setColor(QPalette::ToolTipBase, Qt::white);
darkPalette.setColor(QPalette::ToolTipText, Qt::white);
darkPalette.setColor(QPalette::Disabled, QPalette::Text, QColor(127, 127, 127));
darkPalette.setColor(QPalette::Dark, QColor(35, 35, 35));
darkPalette.setColor(QPalette::Shadow, QColor(20, 20, 20));
darkPalette.setColor(QPalette::Button, QColor(70, 70, 70));
darkPalette.setColor(QPalette::ButtonText, Qt::white);
darkPalette.setColor(QPalette::Disabled, QPalette::ButtonText, QColor(127, 127, 127));
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::Disabled, QPalette::Highlight, QColor(80, 80, 80));
darkPalette.setColor(QPalette::HighlightedText, Qt::white);
darkPalette.setColor(QPalette::Disabled, QPalette::HighlightedText, QColor(127, 127, 127));
qApp->setPalette(darkPalette);
}
void initQt()
{
// set up the QApplication flags
QApplication::setAttribute(Qt::AA_Use96Dpi, true);
#ifdef Q_OS_WIN32
QApplication::setAttribute(Qt::AA_DisableHighDpiScaling, true);
#endif
QApplication::setStyle(QStyleFactory::create("Fusion"));
installCustomPalette();
}
void showLastCrashDialog()
{
#ifndef C_DISABLE_CRASH_DIALOG
LastRunCrashDialog dialog;
switch (dialog.exec()) {
case QDialog::Accepted: {
}; break;
default: {
_exit(0);
}
}
#endif
}
void createRunningFile(const QString &path)
{
QFile runningFile(path);
runningFile.open(QIODevice::WriteOnly | QIODevice::Truncate);
runningFile.flush();
runningFile.close();
}
void removeRunningFile(const QString &path)
{
QFile::remove(path);
}
} // namespace
void runGui(QApplication &a, Paths &paths, Settings &settings)
{
chatterino::NetworkManager::init();
chatterino::Updates::getInstance().checkForUpdates();
#ifdef C_USE_BREAKPAD
QBreakpadInstance.setDumpPath(app->paths->settingsFolderPath + "/Crashes");
#endif
// Running file
auto runningPath = paths.miscDirectory + "/running_" + paths.applicationFilePathHash;
if (QFile::exists(runningPath)) {
showLastCrashDialog();
} else {
createRunningFile(runningPath);
}
Application app(settings, paths);
app.initialize(settings, paths);
app.run(a);
app.save();
removeRunningFile(runningPath);
pajlada::Settings::SettingManager::gSave();
chatterino::NetworkManager::deinit();
_exit(0);
}
} // namespace chatterino

10
src/RunGui.hpp Normal file
View file

@ -0,0 +1,10 @@
#pragma once
class QApplication;
namespace chatterino {
class Paths;
class Settings;
void runGui(QApplication &a, Paths &paths, Settings &settings);
} // namespace chatterino

View file

@ -0,0 +1,44 @@
#include "ResourcesAutogen.hpp"
namespace chatterino {
Resources2::Resources2()
{
this->avatars.fourtf = QPixmap(":/avatars/fourtf.png");
this->avatars.pajlada = QPixmap(":/avatars/pajlada.png");
this->buttons.ban = QPixmap(":/buttons/ban.png");
this->buttons.banRed = QPixmap(":/buttons/banRed.png");
this->buttons.menuDark = QPixmap(":/buttons/menuDark.png");
this->buttons.menuLight = QPixmap(":/buttons/menuLight.png");
this->buttons.mod = QPixmap(":/buttons/mod.png");
this->buttons.modModeDisabled = QPixmap(":/buttons/modModeDisabled.png");
this->buttons.modModeDisabled2 = QPixmap(":/buttons/modModeDisabled2.png");
this->buttons.modModeEnabled = QPixmap(":/buttons/modModeEnabled.png");
this->buttons.modModeEnabled2 = QPixmap(":/buttons/modModeEnabled2.png");
this->buttons.timeout = QPixmap(":/buttons/timeout.png");
this->buttons.unban = QPixmap(":/buttons/unban.png");
this->buttons.unmod = QPixmap(":/buttons/unmod.png");
this->buttons.update = QPixmap(":/buttons/update.png");
this->buttons.updateError = QPixmap(":/buttons/updateError.png");
this->error = QPixmap(":/error.png");
this->icon = QPixmap(":/icon.png");
this->pajaDank = QPixmap(":/pajaDank.png");
this->settings.aboutlogo = QPixmap(":/settings/aboutlogo.png");
this->split.down = QPixmap(":/split/down.png");
this->split.left = QPixmap(":/split/left.png");
this->split.move = QPixmap(":/split/move.png");
this->split.right = QPixmap(":/split/right.png");
this->split.up = QPixmap(":/split/up.png");
this->twitch.admin = QPixmap(":/twitch/admin.png");
this->twitch.broadcaster = QPixmap(":/twitch/broadcaster.png");
this->twitch.cheer1 = QPixmap(":/twitch/cheer1.png");
this->twitch.globalmod = QPixmap(":/twitch/globalmod.png");
this->twitch.moderator = QPixmap(":/twitch/moderator.png");
this->twitch.prime = QPixmap(":/twitch/prime.png");
this->twitch.staff = QPixmap(":/twitch/staff.png");
this->twitch.subscriber = QPixmap(":/twitch/subscriber.png");
this->twitch.turbo = QPixmap(":/twitch/turbo.png");
this->twitch.verified = QPixmap(":/twitch/verified.png");
}
} // namespace chatterino

View file

@ -0,0 +1,57 @@
#include <QPixmap>
#include "common/Singleton.hpp"
namespace chatterino {
class Resources2 : public Singleton {
public:
Resources2();
struct {
QPixmap fourtf;
QPixmap pajlada;
} avatars;
struct {
QPixmap ban;
QPixmap banRed;
QPixmap menuDark;
QPixmap menuLight;
QPixmap mod;
QPixmap modModeDisabled;
QPixmap modModeDisabled2;
QPixmap modModeEnabled;
QPixmap modModeEnabled2;
QPixmap timeout;
QPixmap unban;
QPixmap unmod;
QPixmap update;
QPixmap updateError;
} buttons;
QPixmap error;
QPixmap icon;
QPixmap pajaDank;
struct {
QPixmap aboutlogo;
} settings;
struct {
QPixmap down;
QPixmap left;
QPixmap move;
QPixmap right;
QPixmap up;
} split;
struct {
QPixmap admin;
QPixmap broadcaster;
QPixmap cheer1;
QPixmap globalmod;
QPixmap moderator;
QPixmap prime;
QPixmap staff;
QPixmap subscriber;
QPixmap turbo;
QPixmap verified;
} twitch;
};
} // namespace chatterino

34
src/common/Aliases.hpp Normal file
View file

@ -0,0 +1,34 @@
#pragma once
#include <QHash>
#include <QString>
#include <functional>
#define QStringAlias(name) \
namespace chatterino { \
struct name { \
QString string; \
bool operator==(const name &other) const \
{ \
return this->string == other.string; \
} \
bool operator!=(const name &other) const \
{ \
return this->string != other.string; \
} \
}; \
} /* namespace chatterino */ \
namespace std { \
template <> \
struct hash<chatterino::name> { \
size_t operator()(const chatterino::name &s) const \
{ \
return qHash(s.string); \
} \
}; \
} /* namespace std */
QStringAlias(UserName);
QStringAlias(UserId);
QStringAlias(Url);
QStringAlias(Tooltip);

View file

@ -17,9 +17,9 @@
namespace chatterino { namespace chatterino {
Channel::Channel(const QString &_name, Type type) Channel::Channel(const QString &name, Type type)
: name(_name) : completionModel(name)
, completionModel(this->name) , name_(name)
, type_(type) , type_(type)
{ {
QObject::connect(&this->clearCompletionModelTimer_, &QTimer::timeout, [this]() { QObject::connect(&this->clearCompletionModelTimer_, &QTimer::timeout, [this]() {
@ -38,6 +38,11 @@ Channel::Type Channel::getType() const
return this->type_; return this->type_;
} }
const QString &Channel::getName() const
{
return this->name_;
}
bool Channel::isTwitchChannel() const bool Channel::isTwitchChannel() const
{ {
return this->type_ >= Type::Twitch && this->type_ < Type::TwitchEnd; return this->type_ >= Type::Twitch && this->type_ < Type::TwitchEnd;
@ -45,7 +50,7 @@ bool Channel::isTwitchChannel() const
bool Channel::isEmpty() const bool Channel::isEmpty() const
{ {
return this->name.isEmpty(); return this->name_.isEmpty();
} }
LimitedQueueSnapshot<MessagePtr> Channel::getMessageSnapshot() LimitedQueueSnapshot<MessagePtr> Channel::getMessageSnapshot()
@ -66,7 +71,7 @@ void Channel::addMessage(MessagePtr message)
// FOURTF: change this when adding more providers // FOURTF: change this when adding more providers
if (this->isTwitchChannel()) { if (this->isTwitchChannel()) {
app->logging->addMessage(this->name, message); app->logging->addMessage(this->name_, message);
} }
if (this->messages_.pushBack(message, deleted)) { if (this->messages_.pushBack(message, deleted)) {

View file

@ -41,6 +41,7 @@ public:
pajlada::Signals::NoArgSignal destroyed; pajlada::Signals::NoArgSignal destroyed;
Type getType() const; Type getType() const;
const QString &getName() const;
bool isTwitchChannel() const; bool isTwitchChannel() const;
virtual bool isEmpty() const; virtual bool isEmpty() const;
LimitedQueueSnapshot<MessagePtr> getMessageSnapshot(); LimitedQueueSnapshot<MessagePtr> getMessageSnapshot();
@ -52,7 +53,6 @@ public:
void replaceMessage(MessagePtr message, MessagePtr replacement); void replaceMessage(MessagePtr message, MessagePtr replacement);
virtual void addRecentChatter(const std::shared_ptr<Message> &message); virtual void addRecentChatter(const std::shared_ptr<Message> &message);
QString name;
QStringList modList; QStringList modList;
virtual bool canSendMessage() const; virtual bool canSendMessage() const;
@ -72,6 +72,7 @@ protected:
virtual void onConnected(); virtual void onConnected();
private: private:
const QString name_;
LimitedQueue<MessagePtr> messages_; LimitedQueue<MessagePtr> messages_;
Type type_; Type type_;
QTimer clearCompletionModelTimer_; QTimer clearCompletionModelTimer_;

View file

@ -1,9 +1,13 @@
#pragma once #pragma once
#include "common/Aliases.hpp"
#include "common/Outcome.hpp"
#include "common/ProviderId.hpp"
#include "debug/Log.hpp" #include "debug/Log.hpp"
#include <QString> #include <QString>
#include <QWidget> #include <QWidget>
#include <boost/optional.hpp>
#include <boost/preprocessor.hpp> #include <boost/preprocessor.hpp>
#include <string> #include <string>
@ -27,14 +31,10 @@ const Qt::KeyboardModifiers showResizeHandlesModifiers = Qt::ControlModifier;
static const char *ANONYMOUS_USERNAME_LABEL ATTR_UNUSED = " - anonymous - "; static const char *ANONYMOUS_USERNAME_LABEL ATTR_UNUSED = " - anonymous - ";
#define return_if(condition) \ template <typename T>
if ((condition)) { \ std::weak_ptr<T> weakOf(T *element)
return; \ {
} return element->shared_from_this();
}
#define return_unless(condition) \
if (!(condition)) { \
return; \
}
} // namespace chatterino } // namespace chatterino

View file

@ -2,8 +2,10 @@
#include "Application.hpp" #include "Application.hpp"
#include "common/Common.hpp" #include "common/Common.hpp"
#include "controllers/accounts/AccountController.hpp"
#include "controllers/commands/CommandController.hpp" #include "controllers/commands/CommandController.hpp"
#include "debug/Log.hpp" #include "debug/Log.hpp"
#include "providers/twitch/TwitchServer.hpp"
#include "singletons/Emotes.hpp" #include "singletons/Emotes.hpp"
#include <QtAlgorithms> #include <QtAlgorithms>
@ -107,41 +109,45 @@ void CompletionModel::refresh()
auto app = getApp(); auto app = getApp();
// User-specific: Twitch Emotes // User-specific: Twitch Emotes
// TODO: Fix this so it properly updates with the proper api. oauth token needs proper scope if (auto account = app->accounts->twitch.getCurrent()) {
for (const auto &m : app->emotes->twitch.emotes) { for (const auto &emote : account->accessEmotes()->allEmoteNames) {
for (const auto &emoteName : m.second.emoteCodes) {
// XXX: No way to discern between a twitch global emote and sub emote right now // XXX: No way to discern between a twitch global emote and sub emote right now
this->addString(emoteName, TaggedString::Type::TwitchGlobalEmote); this->addString(emote.string, TaggedString::Type::TwitchGlobalEmote);
} }
} }
// Global: BTTV Global Emotes // // Global: BTTV Global Emotes
std::vector<QString> &bttvGlobalEmoteCodes = app->emotes->bttv.globalEmoteCodes; // std::vector<QString> &bttvGlobalEmoteCodes = app->emotes->bttv.globalEmoteNames_;
for (const auto &m : bttvGlobalEmoteCodes) { // for (const auto &m : bttvGlobalEmoteCodes) {
this->addString(m, TaggedString::Type::BTTVGlobalEmote); // this->addString(m, TaggedString::Type::BTTVGlobalEmote);
} // }
// Global: FFZ Global Emotes // // Global: FFZ Global Emotes
std::vector<QString> &ffzGlobalEmoteCodes = app->emotes->ffz.globalEmoteCodes; // std::vector<QString> &ffzGlobalEmoteCodes = app->emotes->ffz.globalEmoteCodes;
for (const auto &m : ffzGlobalEmoteCodes) { // for (const auto &m : ffzGlobalEmoteCodes) {
this->addString(m, TaggedString::Type::FFZGlobalEmote); // this->addString(m, TaggedString::Type::FFZGlobalEmote);
} // }
// Channel-specific: BTTV Channel Emotes // Channel emotes
std::vector<QString> &bttvChannelEmoteCodes = if (auto channel = dynamic_cast<TwitchChannel *>(
app->emotes->bttv.channelEmoteCodes[this->channelName_]; getApp()->twitch2->getChannelOrEmptyByID(this->channelName_).get())) {
for (const auto &m : bttvChannelEmoteCodes) { auto bttv = channel->accessBttvEmotes();
this->addString(m, TaggedString::Type::BTTVChannelEmote); // auto it = bttv->begin();
} // for (const auto &emote : *bttv) {
// }
// std::vector<QString> &bttvChannelEmoteCodes =
// app->emotes->bttv.channelEmoteName_[this->channelName_];
// for (const auto &m : bttvChannelEmoteCodes) {
// this->addString(m, TaggedString::Type::BTTVChannelEmote);
// }
// Channel-specific: FFZ Channel Emotes // Channel-specific: FFZ Channel Emotes
std::vector<QString> &ffzChannelEmoteCodes = for (const auto &emote : *channel->accessFfzEmotes()) {
app->emotes->ffz.channelEmoteCodes[this->channelName_]; this->addString(emote.second->name.string, TaggedString::Type::FFZChannelEmote);
for (const auto &m : ffzChannelEmoteCodes) { }
this->addString(m, TaggedString::Type::FFZChannelEmote);
} }
// Global: Emojis // Emojis
const auto &emojiShortCodes = app->emotes->emojis.shortCodes; const auto &emojiShortCodes = app->emotes->emojis.shortCodes;
for (const auto &m : emojiShortCodes) { for (const auto &m : emojiShortCodes) {
this->addString(":" + m + ":", TaggedString::Type::Emoji); this->addString(":" + m + ":", TaggedString::Type::Emoji);

View file

@ -10,6 +10,8 @@
namespace chatterino { namespace chatterino {
class TwitchChannel;
class CompletionModel : public QAbstractListModel class CompletionModel : public QAbstractListModel
{ {
struct TaggedString { struct TaggedString {

View file

@ -5,42 +5,42 @@
namespace chatterino { namespace chatterino {
EmoteData::EmoteData(Image *image) // EmoteData::EmoteData(Image *image)
: image1x(image) // : image1x(image)
{ //{
} //}
// Emotes must have a 1x image to be valid //// Emotes must have a 1x image to be valid
bool EmoteData::isValid() const // bool EmoteData::isValid() const
{ //{
return this->image1x != nullptr; // return this->image1x != nullptr;
} //}
Image *EmoteData::getImage(float scale) const // Image *EmoteData::getImage(float scale) const
{ //{
int quality = getApp()->settings->preferredEmoteQuality; // int quality = getApp()->settings->preferredEmoteQuality;
if (quality == 0) { // if (quality == 0) {
scale *= getApp()->settings->emoteScale.getValue(); // scale *= getApp()->settings->emoteScale.getValue();
quality = [&] { // quality = [&] {
if (scale <= 1) // if (scale <= 1)
return 1; // return 1;
if (scale <= 2) // if (scale <= 2)
return 2; // return 2;
return 3; // return 3;
}(); // }();
} // }
Image *_image; // Image *_image;
if (quality == 3 && this->image3x != nullptr) { // if (quality == 3 && this->image3x != nullptr) {
_image = this->image3x; // _image = this->image3x;
} else if (quality >= 2 && this->image2x != nullptr) { // } else if (quality >= 2 && this->image2x != nullptr) {
_image = this->image2x; // _image = this->image2x;
} else { // } else {
_image = this->image1x; // _image = this->image1x;
} // }
return _image; // return _image;
} //}
} // namespace chatterino } // namespace chatterino

View file

@ -5,23 +5,23 @@
namespace chatterino { namespace chatterino {
struct EmoteData { // struct EmoteData {
EmoteData() = default; // EmoteData() = default;
EmoteData(Image *image); // EmoteData(Image *image);
// Emotes must have a 1x image to be valid // // Emotes must have a 1x image to be valid
bool isValid() const; // bool isValid() const;
Image *getImage(float scale) const; // Image *getImage(float scale) const;
// Link to the emote page i.e. https://www.frankerfacez.com/emoticon/144722-pajaCringe // // Link to the emote page i.e. https://www.frankerfacez.com/emoticon/144722-pajaCringe
QString pageLink; // QString pageLink;
Image *image1x = nullptr; // Image *image1x = nullptr;
Image *image2x = nullptr; // Image *image2x = nullptr;
Image *image3x = nullptr; // Image *image3x = nullptr;
}; //};
using EmoteMap = ConcurrentMap<QString, EmoteData>; // using EmoteMap = ConcurrentMap<QString, EmoteData>;
} // namespace chatterino } // namespace chatterino

View file

@ -2,13 +2,15 @@
#include <functional> #include <functional>
#include "Common.hpp"
class QNetworkReply; class QNetworkReply;
namespace chatterino { namespace chatterino {
class NetworkResult; class NetworkResult;
using NetworkSuccessCallback = std::function<bool(NetworkResult)>; using NetworkSuccessCallback = std::function<Outcome(NetworkResult)>;
using NetworkErrorCallback = std::function<bool(int)>; using NetworkErrorCallback = std::function<bool(int)>;
using NetworkReplyCreatedCallback = std::function<void(QNetworkReply *)>; using NetworkReplyCreatedCallback = std::function<void(QNetworkReply *)>;

View file

@ -2,12 +2,23 @@
#include "Application.hpp" #include "Application.hpp"
#include "singletons/Paths.hpp" #include "singletons/Paths.hpp"
#include "util/DebugCount.hpp"
#include <QCryptographicHash> #include <QCryptographicHash>
#include <QFile> #include <QFile>
namespace chatterino { namespace chatterino {
NetworkData::NetworkData()
{
DebugCount::increase("NetworkData");
}
NetworkData::~NetworkData()
{
DebugCount::decrease("NetworkData");
}
QString NetworkData::getHash() QString NetworkData::getHash()
{ {
if (this->hash_.isEmpty()) { if (this->hash_.isEmpty()) {

View file

@ -13,6 +13,9 @@ namespace chatterino {
class NetworkResult; class NetworkResult;
struct NetworkData { struct NetworkData {
NetworkData();
~NetworkData();
QNetworkRequest request_; QNetworkRequest request_;
const QObject *caller_ = nullptr; const QObject *caller_ = nullptr;
bool useQuickLoadCache_{}; bool useQuickLoadCache_{};

View file

@ -129,7 +129,7 @@ void NetworkRequest::execute()
} }
} }
bool NetworkRequest::tryLoadCachedFile() Outcome NetworkRequest::tryLoadCachedFile()
{ {
auto app = getApp(); auto app = getApp();
@ -137,24 +137,24 @@ bool NetworkRequest::tryLoadCachedFile()
if (!cachedFile.exists()) { if (!cachedFile.exists()) {
// File didn't exist // File didn't exist
return false; return Failure;
} }
if (!cachedFile.open(QIODevice::ReadOnly)) { if (!cachedFile.open(QIODevice::ReadOnly)) {
// File could not be opened // File could not be opened
return false; return Failure;
} }
QByteArray bytes = cachedFile.readAll(); QByteArray bytes = cachedFile.readAll();
NetworkResult result(bytes); NetworkResult result(bytes);
bool success = this->data->onSuccess_(result); auto outcome = this->data->onSuccess_(result);
cachedFile.close(); cachedFile.close();
// XXX: If success is false, we should invalidate the cache file somehow/somewhere // XXX: If success is false, we should invalidate the cache file somehow/somewhere
return success; return outcome;
} }
void NetworkRequest::doRequest() void NetworkRequest::doRequest()
@ -167,20 +167,21 @@ void NetworkRequest::doRequest()
this->timer->start(); this->timer->start();
auto onUrlRequested = [data = this->data, timer = this->timer, worker]() mutable { auto onUrlRequested = [data = this->data, timer = this->timer, worker]() mutable {
QNetworkReply *reply = nullptr; auto reply = [&]() -> QNetworkReply * {
switch (data->requestType_) { switch (data->requestType_) {
case NetworkRequestType::Get: { case NetworkRequestType::Get:
reply = NetworkManager::NaM.get(data->request_); return NetworkManager::NaM.get(data->request_);
} break;
case NetworkRequestType::Put: { case NetworkRequestType::Put:
reply = NetworkManager::NaM.put(data->request_, data->payload_); return NetworkManager::NaM.put(data->request_, data->payload_);
} break;
case NetworkRequestType::Delete: { case NetworkRequestType::Delete:
reply = NetworkManager::NaM.deleteResource(data->request_); return NetworkManager::NaM.deleteResource(data->request_);
} break;
default:
return nullptr;
} }
}();
if (reply == nullptr) { if (reply == nullptr) {
Log("Unhandled request type"); Log("Unhandled request type");
@ -201,8 +202,6 @@ void NetworkRequest::doRequest()
data->onReplyCreated_(reply); data->onReplyCreated_(reply);
} }
bool directAction = (data->caller_ == nullptr);
auto handleReply = [data, timer, reply]() mutable { auto handleReply = [data, timer, reply]() mutable {
// TODO(pajlada): A reply was received, kill the timeout timer // TODO(pajlada): A reply was received, kill the timeout timer
if (reply->error() != QNetworkReply::NetworkError::NoError) { if (reply->error() != QNetworkReply::NetworkError::NoError) {
@ -222,8 +221,7 @@ void NetworkRequest::doRequest()
}; };
if (data->caller_ != nullptr) { if (data->caller_ != nullptr) {
QObject::connect(worker, &NetworkWorker::doneUrl, data->caller_, QObject::connect(worker, &NetworkWorker::doneUrl, data->caller_, handleReply);
std::move(handleReply));
QObject::connect(reply, &QNetworkReply::finished, worker, [worker]() mutable { QObject::connect(reply, &QNetworkReply::finished, worker, [worker]() mutable {
emit worker->doneUrl(); emit worker->doneUrl();
@ -231,7 +229,7 @@ void NetworkRequest::doRequest()
}); });
} else { } else {
QObject::connect(reply, &QNetworkReply::finished, worker, QObject::connect(reply, &QNetworkReply::finished, worker,
[handleReply = std::move(handleReply), worker]() mutable { [handleReply, worker]() mutable {
handleReply(); handleReply();
delete worker; delete worker;

View file

@ -33,7 +33,7 @@ public:
explicit NetworkRequest(const std::string &url, explicit NetworkRequest(const std::string &url,
NetworkRequestType requestType = NetworkRequestType::Get); NetworkRequestType requestType = NetworkRequestType::Get);
NetworkRequest(QUrl url, NetworkRequestType requestType = NetworkRequestType::Get); explicit NetworkRequest(QUrl url, NetworkRequestType requestType = NetworkRequestType::Get);
~NetworkRequest(); ~NetworkRequest();
@ -58,7 +58,7 @@ private:
// Returns true if the file was successfully loaded from cache // Returns true if the file was successfully loaded from cache
// Returns false if the cache file either didn't exist, or it contained "invalid" data // Returns false if the cache file either didn't exist, or it contained "invalid" data
// "invalid" is specified by the onSuccess callback // "invalid" is specified by the onSuccess callback
bool tryLoadCachedFile(); Outcome tryLoadCachedFile();
void doRequest(); void doRequest();

View file

@ -38,7 +38,7 @@ rapidjson::Document NetworkResult::parseRapidJson() const
return ret; return ret;
} }
QByteArray NetworkResult::getData() const const QByteArray &NetworkResult::getData() const
{ {
return this->data_; return this->data_;
} }

View file

@ -7,14 +7,15 @@ namespace chatterino {
class NetworkResult class NetworkResult
{ {
QByteArray data_;
public: public:
NetworkResult(const QByteArray &data); NetworkResult(const QByteArray &data);
QJsonObject parseJson() const; QJsonObject parseJson() const;
rapidjson::Document parseRapidJson() const; rapidjson::Document parseRapidJson() const;
QByteArray getData() const; const QByteArray &getData() const;
private:
QByteArray data_;
}; };
} // namespace chatterino } // namespace chatterino

View file

@ -1,5 +1,7 @@
#pragma once #pragma once
#include <type_traits>
namespace chatterino { namespace chatterino {
template <typename T> template <typename T>
@ -23,7 +25,7 @@ public:
return element_; return element_;
} }
T &operator*() const typename std::add_lvalue_reference<T>::type operator*() const
{ {
assert(this->hasElement()); assert(this->hasElement());
@ -52,6 +54,17 @@ public:
return this->hasElement(); return this->hasElement();
} }
bool operator!() const
{
return !this->hasElement();
}
template <typename X = T, typename = std::enable_if_t<!std::is_const<X>::value>>
operator NullablePtr<const T>() const
{
return NullablePtr<const T>(this->element_);
}
private: private:
T *element_; T *element_;
}; };

51
src/common/Outcome.hpp Normal file
View file

@ -0,0 +1,51 @@
#pragma once
namespace chatterino {
struct SuccessTag {
};
struct FailureTag {
};
const SuccessTag Success{};
const FailureTag Failure{};
class Outcome
{
public:
Outcome(SuccessTag)
: success_(true)
{
}
Outcome(FailureTag)
: success_(false)
{
}
explicit operator bool() const
{
return this->success_;
}
bool operator!() const
{
return !this->success_;
}
bool operator==(const Outcome &other) const
{
return this->success_ == other.success_;
}
bool operator!=(const Outcome &other) const
{
return !this->operator==(other);
}
private:
bool success_;
};
} // namespace chatterino

View file

@ -4,14 +4,18 @@
namespace chatterino { namespace chatterino {
class Application; class Settings;
class Paths;
class Singleton : boost::noncopyable class Singleton : boost::noncopyable
{ {
public: public:
virtual void initialize(Application &app) virtual ~Singleton() = default;
virtual void initialize(Settings &settings, Paths &paths)
{ {
(void)(app); (void)(settings);
(void)(paths);
} }
virtual void save() virtual void save()

View file

@ -10,52 +10,52 @@ class AccessGuard
{ {
public: public:
AccessGuard(T &element, std::mutex &mutex) AccessGuard(T &element, std::mutex &mutex)
: element_(element) : element_(&element)
, mutex_(mutex) , mutex_(&mutex)
{ {
this->mutex_.lock(); this->mutex_->lock();
}
AccessGuard(AccessGuard<T> &&other)
: element_(other.element_)
, mutex_(other.mutex_)
{
other.isValid_ = false;
}
AccessGuard<T> &operator=(AccessGuard<T> &&other)
{
other.isValid_ = false;
this->element_ = other.element_;
this->mutex_ = other.element_;
} }
~AccessGuard() ~AccessGuard()
{ {
this->mutex_.unlock(); if (this->isValid_) this->mutex_->unlock();
} }
const T *operator->() const T *operator->() const
{
return &this->element_;
}
T *operator->()
{
return &this->element_;
}
const T &operator*() const
{ {
return this->element_; return this->element_;
} }
T &operator*() T &operator*() const
{ {
return this->element_; return *this->element_;
}
T clone() const
{
return T(this->element_);
} }
private: private:
T &element_; T *element_;
std::mutex &mutex_; std::mutex *mutex_;
bool isValid_ = true;
}; };
template <typename T> template <typename T>
class UniqueAccess class UniqueAccess
{ {
public: public:
template <typename X = decltype(T())> // template <typename X = decltype(T())>
UniqueAccess() UniqueAccess()
: element_(T()) : element_(T())
{ {
@ -83,14 +83,15 @@ public:
return *this; return *this;
} }
AccessGuard<T> access() AccessGuard<T> access() const
{ {
return AccessGuard<T>(this->element_, this->mutex_); return AccessGuard<T>(this->element_, this->mutex_);
} }
const AccessGuard<T> access() const template <typename X = T, typename = std::enable_if_t<!std::is_const_v<X>>>
AccessGuard<const X> accessConst() const
{ {
return AccessGuard<T>(this->element_, this->mutex_); return AccessGuard<const T>(this->element_, this->mutex_);
} }
private: private:

View file

@ -33,7 +33,7 @@ AccountController::AccountController()
}); });
} }
void AccountController::initialize(Application &app) void AccountController::initialize(Settings &settings, Paths &paths)
{ {
this->twitch.load(); this->twitch.load();
} }

View file

@ -11,16 +11,19 @@
namespace chatterino { namespace chatterino {
class Settings;
class Paths;
class AccountModel; class AccountModel;
class AccountController : public Singleton class AccountController final : public Singleton
{ {
public: public:
AccountController(); AccountController();
AccountModel *createModel(QObject *parent); AccountModel *createModel(QObject *parent);
virtual void initialize(Application &app) override; virtual void initialize(Settings &settings, Paths &paths) override;
TwitchAccountManager twitch; TwitchAccountManager twitch;

View file

@ -11,6 +11,7 @@
#include "providers/twitch/TwitchServer.hpp" #include "providers/twitch/TwitchServer.hpp"
#include "singletons/Paths.hpp" #include "singletons/Paths.hpp"
#include "singletons/Settings.hpp" #include "singletons/Settings.hpp"
#include "util/CombinePath.hpp"
#include "widgets/dialogs/LogsPopup.hpp" #include "widgets/dialogs/LogsPopup.hpp"
#include <QApplication> #include <QApplication>
@ -44,15 +45,14 @@ CommandController::CommandController()
this->items.itemRemoved.connect(addFirstMatchToMap); this->items.itemRemoved.connect(addFirstMatchToMap);
} }
void CommandController::initialize(Application &app) void CommandController::initialize(Settings &, Paths &paths)
{ {
this->load(); this->load(paths);
} }
void CommandController::load() void CommandController::load(Paths &paths)
{ {
auto app = getApp(); this->filePath_ = combinePath(paths.settingsDirectory, "commands.txt");
this->filePath_ = app->paths->settingsDirectory + "/commands.txt";
QFile textFile(this->filePath_); QFile textFile(this->filePath_);
if (!textFile.open(QIODevice::ReadOnly)) { if (!textFile.open(QIODevice::ReadOnly)) {
@ -140,7 +140,7 @@ QString CommandController::execCommand(const QString &text, ChannelPtr channel,
app->twitch.server->whispersChannel->addMessage(b.getMessage()); app->twitch.server->whispersChannel->addMessage(b.getMessage());
app->twitch.server->getWriteConnection()->sendRaw("PRIVMSG #jtv :" + text + "\r\n"); app->twitch.server->sendMessage("jtv", text);
if (getSettings()->inlineWhispers) { if (getSettings()->inlineWhispers) {
app->twitch.server->forEachChannel( app->twitch.server->forEachChannel(

View file

@ -10,11 +10,14 @@
#include "controllers/commands/Command.hpp" #include "controllers/commands/Command.hpp"
namespace chatterino { namespace chatterino {
class Settings;
class Paths;
class Channel; class Channel;
class CommandModel; class CommandModel;
class CommandController : public Singleton class CommandController final : public Singleton
{ {
public: public:
CommandController(); CommandController();
@ -22,7 +25,7 @@ public:
QString execCommand(const QString &text, std::shared_ptr<Channel> channel, bool dryRun); QString execCommand(const QString &text, std::shared_ptr<Channel> channel, bool dryRun);
QStringList getDefaultTwitchCommandList(); QStringList getDefaultTwitchCommandList();
virtual void initialize(Application &app) override; virtual void initialize(Settings &settings, Paths &paths) override;
virtual void save() override; virtual void save() override;
CommandModel *createModel(QObject *parent); CommandModel *createModel(QObject *parent);
@ -30,7 +33,7 @@ public:
UnsortedSignalVector<Command> items; UnsortedSignalVector<Command> items;
private: private:
void load(); void load(Paths &paths);
QMap<QString, Command> commandsMap_; QMap<QString, Command> commandsMap_;

View file

@ -12,7 +12,7 @@ HighlightController::HighlightController()
{ {
} }
void HighlightController::initialize(Application &app) void HighlightController::initialize(Settings &settings, Paths &paths)
{ {
assert(!this->initialized_); assert(!this->initialized_);
this->initialized_ = true; this->initialized_ = true;

Some files were not shown because too many files have changed in this diff Show more