File indexing completed on 2024-04-21 05:02:10

0001 // SPDX-FileCopyrightText: 2021 Carl Schwan <carl@carlschwan.eu>
0002 // SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0003 
0004 #include <QQuickStyle>
0005 #include <QtWebView>
0006 
0007 #ifdef Q_OS_ANDROID
0008 #include "utils/androidutils.h"
0009 #include <QGuiApplication>
0010 #else
0011 #include <QApplication>
0012 #endif
0013 
0014 #ifdef HAVE_KDBUSADDONS
0015 #include <KDBusService>
0016 #include <KWindowSystem>
0017 #endif
0018 #include <KLocalizedString>
0019 
0020 #ifdef HAVE_KUNIFIEDPUSH
0021 #include <kunifiedpush/connector.h>
0022 #endif
0023 
0024 #include "tokodon-version.h"
0025 
0026 #include "account/accountmanager.h"
0027 #include "accountconfig.h"
0028 #include "admin/emailinfo.h"
0029 #include "admin/ipinfo.h"
0030 #include "config.h"
0031 #include "network/networkaccessmanagerfactory.h"
0032 #include "network/networkcontroller.h"
0033 #include "tokodon_debug.h"
0034 #include "utils/blurhashimageprovider.h"
0035 #include "utils/colorschemer.h"
0036 #include "utils/navigation.h"
0037 #include "utils/windowcontroller.h"
0038 
0039 #ifdef Q_OS_WINDOWS
0040 #include <Windows.h>
0041 #endif
0042 
0043 #ifdef TEST_MODE
0044 #include "autotests/helperreply.h"
0045 #include "autotests/mockaccount.h"
0046 #endif
0047 
0048 using namespace Qt::Literals::StringLiterals;
0049 
0050 void setupUnifiedPush()
0051 {
0052 #ifdef HAVE_KUNIFIEDPUSH
0053     auto connector = new KUnifiedPush::Connector(QStringLiteral("org.kde.tokodon"));
0054     QObject::connect(connector, &KUnifiedPush::Connector::endpointChanged, [=](const auto &endpoint) {
0055         NetworkController::instance().endpoint = endpoint;
0056     });
0057 
0058     NetworkController::instance().endpoint = connector->endpoint();
0059 
0060     connector->registerClient(i18n("Receiving push notifications"));
0061 #endif
0062 }
0063 
0064 #ifdef Q_OS_ANDROID
0065 Q_DECL_EXPORT
0066 #endif
0067 int main(int argc, char *argv[])
0068 {
0069     QNetworkProxyFactory::setUseSystemConfiguration(true);
0070     QtWebView::initialize();
0071 
0072 #ifdef Q_OS_ANDROID
0073     QGuiApplication app(argc, argv);
0074     app.connect(&app, &QGuiApplication::applicationStateChanged, [](Qt::ApplicationState state) {
0075         if (state == Qt::ApplicationActive) {
0076             AndroidUtils::instance().checkPendingIntents();
0077         }
0078     });
0079     QQuickStyle::setStyle(QStringLiteral("org.kde.breeze"));
0080 #else
0081     QApplication app(argc, argv);
0082     // Default to org.kde.desktop style unless the user forces another style
0083     if (qEnvironmentVariableIsEmpty("QT_QUICK_CONTROLS_STYLE")) {
0084         QQuickStyle::setStyle(QStringLiteral("org.kde.desktop"));
0085     }
0086 #endif
0087 
0088 #ifdef Q_OS_WINDOWS
0089     if (AttachConsole(ATTACH_PARENT_PROCESS)) {
0090         freopen("CONOUT$", "w", stdout);
0091         freopen("CONOUT$", "w", stderr);
0092     }
0093 
0094     QApplication::setStyle(QStringLiteral("breeze"));
0095     auto font = app.font();
0096     font.setPointSize(10);
0097     app.setFont(font);
0098 #endif
0099     KLocalizedString::setApplicationDomain(QByteArrayLiteral("tokodon"));
0100 
0101     QCoreApplication::setOrganizationName(QStringLiteral("KDE"));
0102 
0103     KAboutData about(QStringLiteral("tokodon"),
0104                      i18n("Tokodon"),
0105                      QStringLiteral(TOKODON_VERSION_STRING),
0106                      i18n("Mastodon client"),
0107                      KAboutLicense::GPL_V3,
0108                      i18n("© 2021-2023 Carl Schwan, 2021-2023 KDE Community"));
0109     about.addAuthor(i18n("Carl Schwan"),
0110                     i18n("Maintainer"),
0111                     QStringLiteral("carl@carlschwan.eu"),
0112                     QStringLiteral("https://carlschwan.eu"),
0113                     QUrl(QStringLiteral("https://carlschwan.eu/avatar.png")));
0114     about.addAuthor(i18n("Joshua Goins"),
0115                     i18n("Maintainer"),
0116                     QStringLiteral("josh@redstrate.com"),
0117                     QStringLiteral("https://redstrate.com/"),
0118                     QUrl(QStringLiteral("https://redstrate.com/rss-image.png")));
0119     about.setTranslator(i18nc("NAME OF TRANSLATORS", "Your names"), i18nc("EMAIL OF TRANSLATORS", "Your emails"));
0120     about.setOrganizationDomain("kde.org");
0121     about.setBugAddress("https://bugs.kde.org/describecomponents.cgi?product=tokodon");
0122 
0123     KAboutData::setApplicationData(about);
0124     QGuiApplication::setWindowIcon(QIcon::fromTheme(QStringLiteral("org.kde.tokodon")));
0125 
0126     QCommandLineParser parser;
0127     parser.setApplicationDescription(i18n("Client for decentralized social networks like Mastodon"));
0128     parser.addPositionalArgument(QStringLiteral("urls"), i18n("Supports https and web+ap url scheme"));
0129 
0130     QCommandLineOption shareOption(QStringLiteral("share"), i18n("Share a line of text in the standalone composer."), i18n("The text to share."));
0131     shareOption.setFlags(QCommandLineOption::Flag::HiddenFromHelp);
0132     parser.addOption(shareOption);
0133 
0134     QCommandLineOption notifyOption(QStringLiteral("dbus-activated"), i18n("Internal usage only."));
0135     notifyOption.setFlags(QCommandLineOption::Flag::HiddenFromHelp);
0136     parser.addOption(notifyOption);
0137 
0138     about.setupCommandLine(&parser);
0139     parser.process(app);
0140     about.processCommandLine(&parser);
0141 
0142     auto &colorSchemer = ColorSchemer::instance();
0143     auto config = Config::self();
0144     if (!config->colorScheme().isEmpty()) {
0145         colorSchemer.apply(config->colorScheme());
0146     }
0147 
0148     qmlRegisterSingletonInstance("org.kde.tokodon.private", 1, 0, "Config", config);
0149     qmlRegisterUncreatableType<AccountConfig>("org.kde.tokodon.private", 1, 0, "AccountConfig", QStringLiteral("Use via Account.config"));
0150     qmlRegisterUncreatableMetaObject(Notification::staticMetaObject, "org.kde.tokodon", 1, 0, "Notification", QStringLiteral("Only for enums"));
0151     qmlRegisterUncreatableMetaObject(IpInfo::staticMetaObject, "org.kde.tokodon", 1, 0, "IpInfo", QStringLiteral("Only for enums"));
0152     qmlRegisterUncreatableMetaObject(EmailInfo::staticMetaObject, "org.kde.tokodon", 1, 0, "EmailInfo", QStringLiteral("Only for enums"));
0153 
0154     QQmlApplicationEngine engine;
0155 
0156 #ifdef HAVE_KUNIFIEDPUSH
0157     if (parser.isSet(notifyOption)) {
0158         qInfo(TOKODON_LOG) << "Beginning to check for notifications...";
0159 
0160 #ifdef HAVE_KDBUSADDONS
0161         // We want to be replaceable by the main client
0162         KDBusService service(KDBusService::Replace);
0163 #endif
0164 
0165         setupUnifiedPush();
0166 
0167         // create the lazy instance
0168         AccountManager::instance().loadFromSettings();
0169 
0170         QObject::connect(&AccountManager::instance(), &AccountManager::accountsReady, [] {
0171             qInfo(TOKODON_LOG) << "Accounts have finished loading. Checking notification queue...";
0172             // queue notification
0173             AccountManager::instance().queueNotifications();
0174         });
0175 
0176         QObject::connect(&AccountManager::instance(), &AccountManager::finishedNotificationQueue, [] {
0177             // Sleep for a bit so the notifications stay open
0178             // TODO: make this depend on all of the knotifications closing
0179             QTimer *timer = new QTimer();
0180             timer->setInterval(1000);
0181             timer->connect(timer, &QTimer::timeout, qApp, &QCoreApplication::quit);
0182         });
0183 
0184         return QCoreApplication::exec();
0185     }
0186 #endif
0187 
0188 #ifdef HAVE_KDBUSADDONS
0189     KDBusService service(KDBusService::Unique);
0190 #endif
0191 
0192     setupUnifiedPush();
0193 
0194 #ifdef HAVE_KDBUSADDONS
0195     QObject::connect(&service, &KDBusService::activateRequested, &engine, [&engine](const QStringList &arguments, const QString & /*workingDirectory*/) {
0196         const auto rootObjects = engine.rootObjects();
0197         for (auto obj : rootObjects) {
0198             if (auto view = qobject_cast<QQuickWindow *>(obj)) {
0199                 KWindowSystem::updateStartupId(view);
0200                 KWindowSystem::activateWindow(view);
0201 
0202                 if (arguments.isEmpty()) {
0203                     return;
0204                 }
0205 
0206                 auto args = arguments;
0207                 args.removeFirst();
0208 
0209                 if (arguments.length() >= 1) {
0210                     if (args[0].startsWith("web+ap"_L1)) {
0211                         NetworkController::instance().openWebApLink(args[0]);
0212                     } else if (args[0].startsWith("tokodon"_L1)) {
0213                         NetworkController::instance().setAuthCode(QUrl(args[0]));
0214                     } else if (args[0] == "--share"_L1) {
0215                         NetworkController::instance().startComposing(args[1]);
0216                     } else {
0217                         NetworkController::instance().openWebApLink(args[0]);
0218                     }
0219                 }
0220 
0221                 return;
0222             }
0223         }
0224     });
0225 #endif
0226     engine.rootContext()->setContextObject(new KLocalizedContext(&engine));
0227     QObject::connect(&engine, &QQmlApplicationEngine::quit, &app, &QCoreApplication::quit);
0228 
0229     NetworkAccessManagerFactory namFactory;
0230     engine.setNetworkAccessManagerFactory(&namFactory);
0231 
0232     engine.addImageProvider(QLatin1String("blurhash"), new BlurhashImageProvider);
0233 
0234 #ifdef TEST_MODE
0235     AccountManager::instance().setTestMode(true);
0236 
0237     auto account = new MockAccount();
0238     AccountManager::instance().addAccount(account, true);
0239     AccountManager::instance().selectAccount(account);
0240 
0241     QUrl url = account->apiUrl(QStringLiteral("/api/v2/search"));
0242     url.setQuery(QUrlQuery{{QStringLiteral("q"), QStringLiteral("myquery")}});
0243     account->registerGet(url, new TestReply(QStringLiteral("search-result.json"), account));
0244 
0245     account->registerGet(account->apiUrl(QStringLiteral("/api/v1/timelines/home")), new TestReply(QStringLiteral("statuses.json"), account));
0246 #else
0247     AccountManager::instance().migrateSettings();
0248     AccountManager::instance().loadFromSettings();
0249 #endif
0250 
0251     if (parser.isSet(shareOption)) {
0252         engine.loadFromModule("org.kde.tokodon", "StandaloneComposer");
0253 
0254         NetworkController::instance().startComposing(parser.value(shareOption));
0255     } else {
0256         engine.loadFromModule("org.kde.tokodon", "Main");
0257 
0258         if (parser.positionalArguments().length() > 0) {
0259             NetworkController::instance().openWebApLink(parser.positionalArguments()[0]);
0260         }
0261     }
0262 
0263     if (engine.rootObjects().isEmpty()) {
0264         return -1;
0265     }
0266 #ifdef HAVE_KDBUSADDONS
0267     QQuickWindow *window = nullptr;
0268 
0269     const auto rootObjects = engine.rootObjects();
0270     for (auto obj : rootObjects) {
0271         auto view = qobject_cast<QQuickWindow *>(obj);
0272         if (view) {
0273             window = view;
0274             break;
0275         }
0276     }
0277 
0278     if (window != nullptr) {
0279         auto controller = engine.singletonInstance<WindowController *>(QStringLiteral("org.kde.tokodon"), QStringLiteral("WindowController"));
0280         controller->setWindow(window);
0281         controller->restoreGeometry();
0282     }
0283 #endif
0284     return QCoreApplication::exec();
0285 }