/* * This file is part of smolbote. It's copyrighted by the contributors recorded * in the version control history of the file, available from its original * location: https://neueland.iserlohn-fortress.net/gitea/aqua/smolbote * * SPDX-License-Identifier: GPL-3.0 */ #include "browser.h" #include "aboutdialog.h" #include "aboutplugin.h" #include "addressbar.h" #include "bookmarkswidget.h" #include "config.h" #include "configuration.h" #include "downloadswidget.h" #include "mainwindow/mainwindow.h" #include "webprofilemanager.h" #include "subwindow/subwindow.h" #include "util.h" #include "webengine/urlinterceptor.h" #include "webprofile.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "mainwindow/menubar.h" #include "webengine/webview.h" #include "urlfilter.h" #include "adblock/adblocklist.h" #include "hostlist/hostlist.h" #include #include Browser::Browser(int &argc, char *argv[], bool allowSecondary) : SingleApplication(argc, argv, allowSecondary, SingleApplication::User | SingleApplication::SecondaryNotification | SingleApplication::ExcludeAppVersion) { setApplicationName(CONFIG_POI_NAME); setWindowIcon(QIcon(CONFIG_POI_ICON)); setApplicationVersion(QVersionNumber::fromString(QLatin1String(poi_Version)).toString()); } Browser::~Browser() { if(m_bookmarks) m_bookmarks->save(); for(auto *info : m_plugins) delete info; qDeleteAll(m_windows); m_windows.clear(); } void Browser::about() { auto *dlg = new AboutDialog; dlg->exec(); } const QStringList Browser::configurationOptions() const { QStringList options; for(const auto &option : m_config->description().options()) { options.append(QString::fromStdString(option->long_name())); } return options; } const QString Browser::configuration(const QString &key) const { return m_config->value(qUtf8Printable(key)).value_or(QString()); } void Browser::setConfiguration(const QString &key, const QString &value) { m_config->setValue(qUtf8Printable(key), value); } const QList> Browser::profileList() const { QList> profiles; for(const QString &id : m_profileManager->idList()) { profiles.append(qMakePair(id, m_profileManager->profile(id))); } return profiles; } QPair Browser::loadProfile(const QString &id, bool isOffTheRecord) { const QString _id = [id](){ // if id contains a separator, it should be a path if(id.contains(QDir::separator())) { return QFileInfo(id).baseName(); } else { return id; } }(); auto *profile = m_profileManager->profile(/* id */ _id, /* path */ (_id == id) ? QString() : id, isOffTheRecord); connect(profile, &WebProfile::downloadRequested, m_downloads.get(), &DownloadsWidget::addDownload); auto *interceptor = new UrlRequestInterceptor(profile, profile); for(UrlFilter *filter : m_filters) { interceptor->addFilter(filter); } const auto headers = m_config->value("filter.header").value_or(QStringList()); for(const QString &header : headers) { const auto h = header.split(QLatin1Literal(":")); if(h.length() == 2) interceptor->addHttpHeader(h.at(0).toLatin1(), h.at(1).toLatin1()); } profile->setUrlRequestInterceptor(interceptor); spdlog::info("Added profile: {}{}", qUtf8Printable(_id), profile->isOffTheRecord() ? " (otr)" : ""); return QPair(_id, profile); } void Browser::removeProfile(const QString &id) { m_profileManager->deleteProfile(id); } void Browser::setConfiguration(std::unique_ptr &config) { Q_ASSERT(config); m_config = std::move(config); } Configuration *Browser::getConfiguration() const { Q_ASSERT(m_config); return m_config.get(); } WebProfileManager *Browser::getProfileManager() { return m_profileManager; } QPluginLoader *Browser::addPlugin(const QString &path) { if(path.isEmpty()) return nullptr; auto *loader = new PluginLoader(path, PluginLoader::SignatureMatchIfAvailable, this); const bool loaded = loader->load(); spdlog::info("Loading plugin [{}]: {}", qUtf8Printable(path), loaded ? "passed" : "failed"); if(!loaded) { delete loader; return nullptr; } auto *info = new PluginInfo(loader); m_plugins.append(info); for(MainWindow *window : m_windows) { addPluginTo(info, window); } return loader; } void Browser::setup(QVector plugins) { Q_ASSERT(m_config); for(QPluginLoader *loader : plugins) { m_plugins.append(new PluginInfo(loader)); } auto stylesheet = m_config->value("browser.stylesheet"); if(stylesheet) { QFile f(stylesheet.value()); if(f.open(QIODevice::ReadOnly)) { setStyleSheet(f.readAll()); f.close(); } } // downloads m_downloads = std::make_unique(m_config->value("downloads.path").value()); // url request filter for(const QString &hostlist : Util::files(m_config->value("filter.hosts").value_or(QString()))) { QFile f(hostlist); if(f.open(QIODevice::ReadOnly | QIODevice::Text)) { m_filters.append(new HostList(&f)); f.close(); } } for(const QString &adblock : Util::files(m_config->value("filter.adblock").value_or(QString()))) { QFile f(adblock); if(f.open(QIODevice::ReadOnly | QIODevice::Text)) { m_filters.append(new AdBlockList(&f)); f.close(); } } // cookie request filter // load profiles m_profileManager = new WebProfileManager(m_config->section("profile"), this); for(const QString &profilePath : Util::files(m_config->value("profile.path").value(), { "*.profile" })) { this->loadProfile(profilePath); } // set default profile { const QString id = m_config->value("profile.default").value(); auto *profile = m_profileManager->profile(id); if(profile == nullptr) { profile = qobject_cast(loadProfile(id).second); } WebProfile::setDefaultProfile(profile); } // bookmarks m_bookmarks = std::make_shared(QString::fromStdString(m_config->value("bookmarks.path").value())); connect(m_bookmarks.get(), &BookmarksWidget::showContextMenu, this, [this](const QUrl &url, const QPoint &pos) { auto *subwindow = m_windows.last()->currentSubWindow(); if(subwindow == nullptr) return; auto *menu = new QMenu(m_bookmarks.get()); menu->addAction(tr("Open link in current tab"), subwindow, [url, subwindow]() { subwindow->currentView()->load(url); }); auto *openInCurrentTabWithProfile = menu->addMenu(tr("Open link in current tab with profile")); m_profileManager->profileMenu(openInCurrentTabWithProfile, [url, subwindow](WebProfile *profile) { subwindow->currentView()->setProfile(profile); subwindow->currentView()->load(url); }); menu->addAction(tr("Open link in new tab"), subwindow, [url, subwindow]() { subwindow->addTab(url); }); auto *openInNewTabWithProfile = menu->addMenu(tr("Open link in new tab with profile")); m_profileManager->profileMenu(openInNewTabWithProfile, [url, subwindow](WebProfile *profile) { subwindow->addTab(url, profile); }); menu->exec(pos); }); connect(m_bookmarks.get(), &BookmarksWidget::openUrl, this, [this](const QUrl &url) { m_windows.last()->createTab(url); }); auto *timer = new QTimer(this); connect(timer, &QTimer::timeout, m_bookmarks.get(), &BookmarksWidget::save); // 5min * 60sec * 1000ms timer->start(5 * 60 * 1000); } void Browser::showWidget(QWidget *widget, MainWindow *where) const { bool wasVisible = widget->isVisible(); for(MainWindow *w : qAsConst(m_windows)) w->removeDockWidget(widget); if(!wasVisible) where->addDockWidget(Qt::RightDockWidgetArea, widget); } MainWindow *Browser::createWindow() { // the window will delete itself when it closes, so we don't need to delete it auto *window = new MainWindow(m_config); connect(window->addressBar, &AddressBar::complete, m_bookmarks.get(), &BookmarksWidget::search); for(auto *info : m_plugins) { addPluginTo(info, window); } m_windows.append(window); connect(window, &MainWindow::destroyed, this, [this, window]() { m_windows.removeOne(window); }); return window; } void Browser::addPluginTo(PluginInfo *info, MainWindow *window) { QPluginLoader *loader = info->loader; auto *pluginMenu = new QMenu(loader->metaData().value("MetaData").toObject().value("name").toString()); window->m_menuBar->insertPlugin(pluginMenu); info->menus.append(pluginMenu); auto *aboutAction = pluginMenu->addAction(tr("About")); connect(aboutAction, &QAction::triggered, this, [loader, window]() { auto *dlg = new AboutPluginDialog(loader, window); dlg->exec(); }); auto *runAction = pluginMenu->addAction(tr("Run")); runAction->setShortcut(QKeySequence::fromString(loader->metaData().value("MetaData").toObject().value("shortcut").toString())); connect(runAction, &QAction::triggered, window, [loader, window]() { if(loader->isLoaded()) { const auto *interface = qobject_cast(loader->instance()); Q_CHECK_PTR(interface); interface->createWidget(window)->exec(); } }); auto *removeAction = pluginMenu->addAction(tr("Remove")); connect(removeAction, &QAction::triggered, this, [this, info]() {; m_plugins.removeOne(info); delete info; }); }