// Copyright (C) Oleg Shparber, et al. // SPDX-License-Identifier: GPL-3.0-or-later #include "settings.h" #include "application.h" #include #include #include #include #include #include #include #include #include #include #include #if QT_VERSION >= QT_VERSION_CHECK(6, 5, 0) #include #else #include #endif namespace Zeal::Core { namespace { Q_LOGGING_CATEGORY(log, "zeal.core.settings") using Qt::Literals::StringLiterals::operator""_L1; // Configuration file groups constexpr auto GroupUI = "ui"_L1; constexpr auto GroupContent = "content"_L1; constexpr auto GroupDocsets = "docsets"_L1; constexpr auto GroupGlobalShortcuts = "global_shortcuts"_L1; constexpr auto GroupSearch = "search"_L1; constexpr auto GroupTabs = "tabs"_L1; constexpr auto GroupInternal = "internal"_L1; constexpr auto GroupProxy = "proxy"_L1; } // namespace // NOLINTNEXTLINE(cppcoreguidelines-pro-type-member-init,hicpp-member-init): initialized by load(). Settings::Settings(QObject *parent) : QObject(parent) { qRegisterMetaType("ContentAppearance"); qRegisterMetaType("ExternalLinkPolicy"); #if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0) // When the OS color scheme changes, reapply the color scheme. connect(qApp->styleHints(), &QStyleHints::colorSchemeChanged, this, [this]() { if (contentAppearance == ContentAppearance::Automatic) { applyColorScheme(); emit updated(); } }); #endif load(); } Settings::~Settings() { save(); } bool Settings::isDarkModeEnabled() const { if (contentAppearance == ContentAppearance::Dark) { return true; } if (contentAppearance == ContentAppearance::Automatic && colorScheme() == ColorScheme::Dark) { return true; } return false; } bool Settings::isTrayActive() const { return showSystrayIcon && QSystemTrayIcon::isSystemTrayAvailable(); } #if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0) void Settings::applyColorScheme() const { Qt::ColorScheme scheme = Qt::ColorScheme::Unknown; switch (contentAppearance) { case ContentAppearance::Light: scheme = Qt::ColorScheme::Light; break; case ContentAppearance::Dark: scheme = Qt::ColorScheme::Dark; break; default: break; } // Setting the scheme updates the platform palette. The full widget refresh // (re-deriving the palette and re-polishing existing widgets) is handled by // re-installing the application style on QStyleHints::colorSchemeChanged; see // app/main.cpp. Doing it there preserves the active QProxyStyle wrapper that // restyles the tab close button, which re-creating the style here would drop. qApp->styleHints()->setColorScheme(scheme); } #endif Settings::ColorScheme Settings::colorScheme() { #if QT_VERSION >= QT_VERSION_CHECK(6, 5, 0) return QApplication::styleHints()->colorScheme(); #else // Pre Qt 6.5 detection from https://www.qt.io/blog/dark-mode-on-windows-11-with-qt-6.5. const QPalette p; if (p.color(QPalette::WindowText).lightness() > p.color(QPalette::Window).lightness()) { return ColorScheme::Dark; } return ColorScheme::Light; #endif } void Settings::load() { auto settings = qsettings(); qCDebug(log, "Using settings file: %s", qPrintable(settings->fileName())); migrate(settings.get()); // TODO: Put everything in groups startMinimized = settings->value(QStringLiteral("start_minimized"), false).toBool(); #ifdef ZEAL_FEATURE_UPDATE_CHECK checkForUpdate = settings->value(QStringLiteral("check_for_update"), true).toBool(); #else checkForUpdate = settings->value(QStringLiteral("check_for_update"), false).toBool(); #endif showSystrayIcon = settings->value(QStringLiteral("show_systray_icon"), true).toBool(); minimizeToSystray = settings->value(QStringLiteral("minimize_to_systray"), false).toBool(); hideOnClose = settings->value(QStringLiteral("hide_on_close"), false).toBool(); settings->beginGroup(GroupUI); hideMenuBar = settings->value(QStringLiteral("hide_menu_bar"), false).toBool(); hideSidebar = settings->value(QStringLiteral("hide_sidebar"), false).toBool(); settings->endGroup(); settings->beginGroup(GroupGlobalShortcuts); showShortcut = settings->value(QStringLiteral("show")).value(); settings->endGroup(); settings->beginGroup(GroupTabs); openNewTabAfterActive = settings->value(QStringLiteral("open_new_tab_after_active"), false).toBool(); showTabCloseButton = settings->value(QStringLiteral("show_tab_close_button"), true).toBool(); settings->endGroup(); settings->beginGroup(GroupSearch); isFuzzySearchEnabled = settings->value(QStringLiteral("fuzzy_search_enabled"), true).toBool(); settings->endGroup(); settings->beginGroup(GroupContent); contentAppearance = settings->value(QStringLiteral("appearance"), QVariant::fromValue(ContentAppearance::Automatic)) .value(); #if QT_VERSION < QT_VERSION_CHECK(6, 7, 0) // Dark mode needs to be applied before Qt WebEngine is initialized. if (isDarkModeEnabled()) { QByteArray chromiumFlags = qgetenv("QTWEBENGINE_CHROMIUM_FLAGS"); if (!chromiumFlags.isEmpty()) { chromiumFlags += ' '; } chromiumFlags += "--blink-settings=forceDarkModeEnabled=true,darkModeInversionAlgorithm=4"; qputenv("QTWEBENGINE_CHROMIUM_FLAGS", chromiumFlags); } #endif #if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0) applyColorScheme(); #endif // Fonts QWebEngineSettings *webSettings = QWebEngineProfile::defaultProfile()->settings(); serifFontFamily = settings ->value(QStringLiteral("serif_font_family"), webSettings->fontFamily(QWebEngineSettings::SerifFont)) .toString(); sansSerifFontFamily = settings ->value(QStringLiteral("sans_serif_font_family"), webSettings->fontFamily(QWebEngineSettings::SansSerifFont)) .toString(); fixedFontFamily = settings ->value(QStringLiteral("fixed_font_family"), webSettings->fontFamily(QWebEngineSettings::FixedFont)) .toString(); static const QMap fontFamilies = {{QStringLiteral("sans-serif"), QWebEngineSettings::SansSerifFont}, {QStringLiteral("serif"), QWebEngineSettings::SerifFont}, {QStringLiteral("monospace"), QWebEngineSettings::FixedFont}}; defaultFontFamily = settings->value(QStringLiteral("default_font_family"), QStringLiteral("serif")).toString(); // Fallback to the serif font family. if (!fontFamilies.contains(defaultFontFamily)) { defaultFontFamily = QStringLiteral("serif"); } webSettings->setFontFamily(QWebEngineSettings::SansSerifFont, sansSerifFontFamily); webSettings->setFontFamily(QWebEngineSettings::SerifFont, serifFontFamily); webSettings->setFontFamily(QWebEngineSettings::FixedFont, fixedFontFamily); const QString defaultFontFamilyResolved = webSettings->fontFamily(fontFamilies.value(defaultFontFamily)); webSettings->setFontFamily(QWebEngineSettings::StandardFont, defaultFontFamilyResolved); defaultFontSize = settings ->value(QStringLiteral("default_font_size"), webSettings->fontSize(QWebEngineSettings::DefaultFontSize)) .toInt(); defaultFixedFontSize = settings ->value(QStringLiteral("default_fixed_font_size"), webSettings->fontSize(QWebEngineSettings::DefaultFixedFontSize)) .toInt(); minimumFontSize = settings ->value(QStringLiteral("minimum_font_size"), webSettings->fontSize(QWebEngineSettings::MinimumFontSize)) .toInt(); webSettings->setFontSize(QWebEngineSettings::DefaultFontSize, defaultFontSize); webSettings->setFontSize(QWebEngineSettings::DefaultFixedFontSize, defaultFixedFontSize); webSettings->setFontSize(QWebEngineSettings::MinimumFontSize, minimumFontSize); isHighlightOnNavigateEnabled = settings->value(QStringLiteral("highlight_on_navigate"), true).toBool(); customCssFile = settings->value(QStringLiteral("custom_css_file")).toString(); externalLinkPolicy = settings ->value(QStringLiteral("external_link_policy"), QVariant::fromValue(ExternalLinkPolicy::Ask)) .value(); isSmoothScrollingEnabled = settings->value(QStringLiteral("smooth_scrolling"), true).toBool(); settings->endGroup(); settings->beginGroup(GroupProxy); proxyType = settings->value(QStringLiteral("type"), QVariant::fromValue(ProxyType::System)).value(); proxyHost = settings->value(QStringLiteral("host")).toString(); proxyPort = static_cast(settings->value(QStringLiteral("port"), 0).toUInt()); proxyAuthenticate = settings->value(QStringLiteral("authenticate"), false).toBool(); proxyUserName = settings->value(QStringLiteral("username")).toString(); proxyPassword = settings->value(QStringLiteral("password")).toString(); isIgnoreSslErrorsEnabled = settings->value(QStringLiteral("ignore_ssl_errors"), false).toBool(); settings->endGroup(); settings->beginGroup(GroupDocsets); if (settings->contains(QStringLiteral("path"))) { docsetPath = settings->value(QStringLiteral("path")).toString(); } else { #ifndef PORTABLE_BUILD docsetPath = QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation) + QLatin1String("/docsets"); #else docsetPath = QStringLiteral("docsets"); #endif } settings->endGroup(); // Create the docset storage directory if it doesn't exist. const QFileInfo fi(docsetPath); if (!fi.exists()) { const QString path = fi.isRelative() ? QCoreApplication::applicationDirPath() + QLatin1String("/") + docsetPath : docsetPath; if (!QDir().mkpath(path)) { qCWarning(log, "Failed to create docset storage directory '%s'.", qPrintable(path)); } } settings->beginGroup(GroupInternal); installId = settings ->value(QStringLiteral("install_id"), // Avoid curly braces (QTBUG-885) QUuid::createUuid().toString().mid(1, 36)) .toString(); settings->endGroup(); } void Settings::save() { auto settings = qsettings(); // TODO: Put everything in groups settings->setValue(QStringLiteral("start_minimized"), startMinimized); settings->setValue(QStringLiteral("check_for_update"), checkForUpdate); settings->setValue(QStringLiteral("show_systray_icon"), showSystrayIcon); settings->setValue(QStringLiteral("minimize_to_systray"), minimizeToSystray); settings->setValue(QStringLiteral("hide_on_close"), hideOnClose); settings->beginGroup(GroupUI); settings->setValue(QStringLiteral("hide_menu_bar"), hideMenuBar); settings->setValue(QStringLiteral("hide_sidebar"), hideSidebar); settings->endGroup(); settings->beginGroup(GroupGlobalShortcuts); settings->setValue(QStringLiteral("show"), showShortcut); settings->endGroup(); settings->beginGroup(GroupTabs); settings->setValue(QStringLiteral("open_new_tab_after_active"), openNewTabAfterActive); settings->setValue(QStringLiteral("show_tab_close_button"), showTabCloseButton); settings->endGroup(); settings->beginGroup(GroupSearch); settings->setValue(QStringLiteral("fuzzy_search_enabled"), isFuzzySearchEnabled); settings->endGroup(); settings->beginGroup(GroupContent); settings->setValue(QStringLiteral("default_font_family"), defaultFontFamily); settings->setValue(QStringLiteral("serif_font_family"), serifFontFamily); settings->setValue(QStringLiteral("sans_serif_font_family"), sansSerifFontFamily); settings->setValue(QStringLiteral("fixed_font_family"), fixedFontFamily); settings->setValue(QStringLiteral("default_font_size"), defaultFontSize); settings->setValue(QStringLiteral("default_fixed_font_size"), defaultFixedFontSize); settings->setValue(QStringLiteral("minimum_font_size"), minimumFontSize); settings->setValue(QStringLiteral("appearance"), QVariant::fromValue(contentAppearance)); settings->setValue(QStringLiteral("highlight_on_navigate"), isHighlightOnNavigateEnabled); settings->setValue(QStringLiteral("custom_css_file"), customCssFile); settings->setValue(QStringLiteral("external_link_policy"), QVariant::fromValue(externalLinkPolicy)); settings->setValue(QStringLiteral("smooth_scrolling"), isSmoothScrollingEnabled); settings->endGroup(); settings->beginGroup(GroupProxy); settings->setValue(QStringLiteral("type"), QVariant::fromValue(proxyType)); settings->setValue(QStringLiteral("host"), proxyHost); settings->setValue(QStringLiteral("port"), proxyPort); settings->setValue(QStringLiteral("authenticate"), proxyAuthenticate); settings->setValue(QStringLiteral("username"), proxyUserName); settings->setValue(QStringLiteral("password"), proxyPassword); settings->setValue(QStringLiteral("ignore_ssl_errors"), isIgnoreSslErrorsEnabled); settings->endGroup(); settings->beginGroup(GroupDocsets); settings->setValue(QStringLiteral("path"), docsetPath); settings->endGroup(); settings->beginGroup(GroupInternal); settings->setValue(QStringLiteral("install_id"), installId); // Version of configuration file format, should match Zeal version. Used for migration rules. settings->setValue(QStringLiteral("version"), Application::version().toString()); settings->endGroup(); settings->sync(); emit updated(); } /*! * \internal * \brief Migrates settings from older application versions. * \param settings QSettings object to update. * * The settings migration process relies on 'internal/version' option, that was introduced in the * release 0.2.0, so a missing option indicates pre-0.2 release. */ void Settings::migrate(QSettings *settings) { settings->beginGroup(GroupInternal); const auto version = QVersionNumber::fromString(settings->value(QStringLiteral("version")).toString()); settings->endGroup(); // // 0.6.0 // // Unset content.default_fixed_font_size. // The causing bug was 0.6.1 (#903), but the incorrect setting still comes to haunt us (#1054). if (version == QVersionNumber(0, 6, 0)) { settings->beginGroup(GroupContent); settings->remove(QStringLiteral("default_fixed_font_size")); settings->endGroup(); } // // Pre 0.4 // // Rename 'browser' group into 'content'. if (version < QVersionNumber(0, 4, 0)) { settings->beginGroup(QStringLiteral("browser")); const QVariant tmpMinimumFontSize = settings->value(QStringLiteral("minimum_font_size")); settings->endGroup(); settings->remove(QStringLiteral("browser")); if (tmpMinimumFontSize.isValid()) { settings->beginGroup(GroupContent); settings->setValue(QStringLiteral("minimum_font_size"), tmpMinimumFontSize); settings->endGroup(); } } } /*! * \internal * \brief Returns an initialized QSettings object. * \param parent Optional parent object. * \return QSettings object. * * QSettings is initialized according to build options, e.g. standard vs portable. * Caller is responsible for deleting the returned object. */ std::unique_ptr Settings::qsettings() { #ifndef PORTABLE_BUILD return std::make_unique(); #else return std::make_unique(QCoreApplication::applicationDirPath() + QLatin1String("/zeal.ini"), QSettings::IniFormat); #endif } } // namespace Zeal::Core QDataStream &operator<<(QDataStream &out, Zeal::Core::Settings::ContentAppearance policy) { out << static_cast>(policy); return out; } QDataStream &operator>>(QDataStream &in, Zeal::Core::Settings::ContentAppearance &policy) { std::underlying_type_t value = 0; in >> value; policy = static_cast(value); return in; } QDataStream &operator<<(QDataStream &out, Zeal::Core::Settings::ExternalLinkPolicy policy) { out << static_cast>(policy); return out; } QDataStream &operator>>(QDataStream &in, Zeal::Core::Settings::ExternalLinkPolicy &policy) { std::underlying_type_t value = 0; in >> value; policy = static_cast(value); return in; }