/* * SPDX-FileCopyrightText: 2022 Devin Lin * * SPDX-License-Identifier: GPL-2.0-or-later */ #include "mobileshellsettings.h" #include #include #include #include #include #include #include #include #include #include #include #include #include const QString CONFIG_FILE = QStringLiteral("plasmamobilerc"); const QString GENERAL_CONFIG_GROUP = QStringLiteral("General"); const QString KDE_GLOBALS_CONFIG_GROUP = QStringLiteral("General"); const QString LOCKSCREEN_CONFIG_GROUP = QStringLiteral("Lockscreen"); const QString QUICKSETTINGS_CONFIG_GROUP = QStringLiteral("QuickSettings"); const QString WALLPAPER_THEME_MANUAL_DARK_KEY = QStringLiteral("wallpaperThemeManualDarkTheme"); const QString SHIFT_DARK_COLOR_SCHEME = QStringLiteral("ShiftDark"); const QString SHIFT_LIGHT_COLOR_SCHEME = QStringLiteral("ShiftLight"); const QString SHIFT_WALLPAPER_DARK_COLOR_SCHEME = QStringLiteral("ShiftWallpaperDark"); const QString SHIFT_WALLPAPER_LIGHT_COLOR_SCHEME = QStringLiteral("ShiftWallpaperLight"); const QString SHIFT_DARK_PLASMA_THEME = QStringLiteral("shift-dark"); const QString SHIFT_LIGHT_PLASMA_THEME = QStringLiteral("shift-light"); constexpr qreal MIN_NORMAL_CONTRAST = 4.5; constexpr qreal MIN_INACTIVE_CONTRAST = 3.0; constexpr qreal MIN_LINK_CONTRAST = 3.5; namespace { QString baseColorSchemeName(bool dark) { return dark ? SHIFT_DARK_COLOR_SCHEME : SHIFT_LIGHT_COLOR_SCHEME; } QString wallpaperColorSchemeName(bool dark) { return dark ? SHIFT_WALLPAPER_DARK_COLOR_SCHEME : SHIFT_WALLPAPER_LIGHT_COLOR_SCHEME; } QString wallpaperColorSchemeDisplayName(bool dark) { return dark ? QStringLiteral("SHIFT Wallpaper Dark") : QStringLiteral("SHIFT Wallpaper Light"); } QString rgbString(const QColor &color) { return QStringLiteral("%1,%2,%3").arg(color.red()).arg(color.green()).arg(color.blue()); } qreal relativeLuminance(const QColor &color) { return 0.2126 * color.redF() + 0.7152 * color.greenF() + 0.0722 * color.blueF(); } qreal linearizedChannel(qreal channel) { return channel <= 0.04045 ? channel / 12.92 : qPow((channel + 0.055) / 1.055, 2.4); } qreal wcagLuminance(const QColor &color) { return 0.2126 * linearizedChannel(color.redF()) + 0.7152 * linearizedChannel(color.greenF()) + 0.0722 * linearizedChannel(color.blueF()); } qreal contrastRatio(const QColor &left, const QColor &right) { const qreal leftLum = wcagLuminance(left); const qreal rightLum = wcagLuminance(right); const qreal lighter = qMax(leftLum, rightLum); const qreal darker = qMin(leftLum, rightLum); return (lighter + 0.05) / (darker + 0.05); } QColor contrastingTextColor(const QColor &color); QColor blendColors(const QColor &base, const QColor &overlay, qreal overlayAmount); QColor ensureContrast(const QColor &foreground, const QColor &background, qreal minimumContrast) { if (contrastRatio(foreground, background) >= minimumContrast) { return foreground; } const QColor highContrastTarget = contrastingTextColor(background); for (int i = 1; i <= 12; ++i) { const qreal amount = i / 12.0; const QColor candidate = blendColors(foreground, highContrastTarget, amount); if (contrastRatio(candidate, background) >= minimumContrast) { return candidate; } } return highContrastTarget; } QColor contrastingTextColor(const QColor &color) { const QColor white{Qt::white}; const QColor black{Qt::black}; return contrastRatio(color, white) >= contrastRatio(color, black) ? white : black; } QColor blendColors(const QColor &base, const QColor &overlay, qreal overlayAmount) { const qreal clampedAmount = qBound(0.0, overlayAmount, 1.0); const qreal baseAmount = 1.0 - clampedAmount; return QColor::fromRgbF(base.redF() * baseAmount + overlay.redF() * clampedAmount, base.greenF() * baseAmount + overlay.greenF() * clampedAmount, base.blueF() * baseAmount + overlay.blueF() * clampedAmount, 1.0); } void writeAccentEntries(const KSharedConfig::Ptr &config, const QString &groupName, const QColor &accentColor) { auto group = KConfigGroup{config, groupName}; group.writeEntry("DecorationFocus", rgbString(accentColor)); group.writeEntry("DecorationHover", rgbString(accentColor)); group.writeEntry("ForegroundActive", rgbString(accentColor)); } void tintBackgroundEntries(const KSharedConfig::Ptr &config, const QString &groupName, const QColor &accentColor, bool dark) { auto group = KConfigGroup{config, groupName}; const QColor defaultBase = dark ? QColor(38, 41, 47) : QColor(244, 246, 251); const QColor baseNormal = group.readEntry("BackgroundNormal", defaultBase); const QColor baseAlternate = group.readEntry("BackgroundAlternate", baseNormal); const qreal normalBlend = dark ? 0.22 : 0.12; const qreal alternateBlend = dark ? 0.16 : 0.08; group.writeEntry("BackgroundNormal", rgbString(blendColors(baseNormal, accentColor, normalBlend))); group.writeEntry("BackgroundAlternate", rgbString(blendColors(baseAlternate, accentColor, alternateBlend))); } void tuneForegroundEntries(const KSharedConfig::Ptr &config, const QString &groupName, const QColor &accentColor, bool dark) { auto group = KConfigGroup{config, groupName}; const QColor bgNormal = group.readEntry("BackgroundNormal", dark ? QColor(34, 37, 50) : QColor(252, 252, 252)); const QColor baseNormal = contrastingTextColor(bgNormal); const QColor baseInactive = blendColors(baseNormal, bgNormal, dark ? 0.44 : 0.56); const QColor baseLink = dark ? blendColors(accentColor, QColor(Qt::white), 0.28) : blendColors(accentColor, QColor(Qt::black), 0.24); const QColor fgNormal = ensureContrast(baseNormal, bgNormal, MIN_NORMAL_CONTRAST); const QColor fgInactive = ensureContrast(baseInactive, bgNormal, MIN_INACTIVE_CONTRAST); const QColor fgLink = ensureContrast(baseLink, bgNormal, MIN_LINK_CONTRAST); const QColor fgActive = ensureContrast(accentColor, bgNormal, MIN_LINK_CONTRAST); group.writeEntry("ForegroundNormal", rgbString(fgNormal)); group.writeEntry("ForegroundInactive", rgbString(fgInactive)); group.writeEntry("ForegroundActive", rgbString(fgActive)); group.writeEntry("ForegroundLink", rgbString(fgLink)); } } MobileShellSettings::MobileShellSettings(QObject *parent) : QObject{parent} , m_config{KSharedConfig::openConfig(CONFIG_FILE)} , m_kdeGlobalsConfig{KSharedConfig::openConfig()} { m_wallpaperThemeTimer.setSingleShot(true); m_wallpaperThemeTimer.setInterval(450); connect(&m_wallpaperThemeTimer, &QTimer::timeout, this, [this]() -> void { setDarkThemeEnabled(m_pendingWallpaperThemeDark); }); m_configWatcher = KConfigWatcher::create(m_config); connect(m_configWatcher.data(), &KConfigWatcher::configChanged, this, [this](const KConfigGroup &group, const QByteArrayList &names) -> void { Q_UNUSED(names) if (group.name() == GENERAL_CONFIG_GROUP) { Q_EMIT vibrationsEnabledChanged(); Q_EMIT vibrationDurationChanged(); Q_EMIT animationsEnabledChanged(); Q_EMIT dateInStatusBarChanged(); Q_EMIT statusBarScaleFactorChanged(); Q_EMIT showBatteryPercentageChanged(); Q_EMIT navigationPanelEnabledChanged(); Q_EMIT gesturePanelEnabledChanged(); Q_EMIT alwaysShowKeyboardToggleOnNavigationPanelChanged(); Q_EMIT keyboardButtonEnabledChanged(); Q_EMIT taskSwitcherPreviewsEnabledChanged(); Q_EMIT actionDrawerTopLeftModeChanged(); Q_EMIT actionDrawerTopRightModeChanged(); Q_EMIT convergenceModeEnabledChanged(); Q_EMIT autoHidePanelsEnabledChanged(); Q_EMIT gamingModeEnabledChanged(); Q_EMIT gamingDismissHintEnabledChanged(); Q_EMIT dynamicTilingEnabledChanged(); Q_EMIT dynamicTilingWindowRequestChanged(); Q_EMIT dynamicTilingWindowStateChanged(); Q_EMIT dynamicTilingLayoutRequestChanged(); Q_EMIT dynamicTilingLayoutStateChanged(); Q_EMIT snapLayoutsEnabledChanged(); Q_EMIT allowLogoutChanged(); Q_EMIT wallpaperThemeEnabledChanged(); } if (group.name() == LOCKSCREEN_CONFIG_GROUP) { Q_EMIT lockscreenLeftButtonActionChanged(); Q_EMIT lockscreenRightButtonActionChanged(); } if (group.name() == QUICKSETTINGS_CONFIG_GROUP) { Q_EMIT quickSettingsColumnsChanged(); } }); m_kdeGlobalsConfigWatcher = KConfigWatcher::create(m_kdeGlobalsConfig); connect(m_kdeGlobalsConfigWatcher.data(), &KConfigWatcher::configChanged, this, [this](const KConfigGroup &group, const QByteArrayList &names) -> void { if (group.name() != KDE_GLOBALS_CONFIG_GROUP) { return; } if (names.contains(QByteArrayLiteral("ColorScheme"))) { Q_EMIT colorSchemeChanged(); Q_EMIT darkThemeEnabledChanged(); } if (names.contains(QByteArrayLiteral("AccentColor")) || names.contains(QByteArrayLiteral("LastUsedCustomAccentColor"))) { Q_EMIT accentColorChanged(); } if (names.contains(QByteArrayLiteral("accentColorFromWallpaper"))) { Q_EMIT wallpaperAccentEnabledChanged(); } }); } bool MobileShellSettings::vibrationsEnabled() const { auto group = KConfigGroup{m_config, GENERAL_CONFIG_GROUP}; return group.readEntry("vibrationsEnabled", true); } void MobileShellSettings::setVibrationsEnabled(bool vibrationsEnabled) { auto group = KConfigGroup{m_config, GENERAL_CONFIG_GROUP}; group.writeEntry("vibrationsEnabled", vibrationsEnabled, KConfigGroup::Notify); m_config->sync(); } int MobileShellSettings::vibrationDuration() const { auto group = KConfigGroup{m_config, GENERAL_CONFIG_GROUP}; return group.readEntry("vibrationDuration", 10); } void MobileShellSettings::setVibrationDuration(int vibrationDuration) { auto group = KConfigGroup{m_config, GENERAL_CONFIG_GROUP}; group.writeEntry("vibrationDuration", vibrationDuration, KConfigGroup::Notify); m_config->sync(); } bool MobileShellSettings::animationsEnabled() const { auto group = KConfigGroup{m_config, GENERAL_CONFIG_GROUP}; return group.readEntry("animationsEnabled", true); } void MobileShellSettings::setAnimationsEnabled(bool animationsEnabled) { auto group = KConfigGroup{m_config, GENERAL_CONFIG_GROUP}; group.writeEntry("animationsEnabled", animationsEnabled, KConfigGroup::Notify); m_config->sync(); } bool MobileShellSettings::dateInStatusBar() const { auto group = KConfigGroup{m_config, GENERAL_CONFIG_GROUP}; return group.readEntry("dateInStatusBar", false); } void MobileShellSettings::setDateInStatusBar(bool dateInStatusBar) { auto group = KConfigGroup{m_config, GENERAL_CONFIG_GROUP}; group.writeEntry("dateInStatusBar", dateInStatusBar, KConfigGroup::Notify); m_config->sync(); } float MobileShellSettings::statusBarScaleFactor() const { auto group = KConfigGroup{m_config, GENERAL_CONFIG_GROUP}; return group.readEntry("statusBarScaleFactor", 1.0); } void MobileShellSettings::setStatusBarScaleFactor(float statusBarScaleFactor) { auto group = KConfigGroup{m_config, GENERAL_CONFIG_GROUP}; group.writeEntry("statusBarScaleFactor", statusBarScaleFactor, KConfigGroup::Notify); m_config->sync(); } bool MobileShellSettings::showBatteryPercentage() const { auto group = KConfigGroup{m_config, GENERAL_CONFIG_GROUP}; return group.readEntry("showBatteryPercentage", true); } void MobileShellSettings::setShowBatteryPercentage(bool showBatteryPercentage) { auto group = KConfigGroup{m_config, GENERAL_CONFIG_GROUP}; group.writeEntry("showBatteryPercentage", showBatteryPercentage, KConfigGroup::Notify); m_config->sync(); } bool MobileShellSettings::navigationPanelEnabled() const { auto group = KConfigGroup{m_config, GENERAL_CONFIG_GROUP}; return group.readEntry("navigationPanelEnabled", true); } void MobileShellSettings::setNavigationPanelEnabled(bool navigationPanelEnabled) { auto group = KConfigGroup{m_config, GENERAL_CONFIG_GROUP}; group.writeEntry("navigationPanelEnabled", navigationPanelEnabled, KConfigGroup::Notify); m_config->sync(); updateNavigationBarsInPlasma(); } bool MobileShellSettings::gesturePanelEnabled() const { auto group = KConfigGroup{m_config, GENERAL_CONFIG_GROUP}; return group.readEntry("gesturePanelEnabled", true); } void MobileShellSettings::setGesturePanelEnabled(bool gesturePanelEnabled) { auto group = KConfigGroup{m_config, GENERAL_CONFIG_GROUP}; group.writeEntry("gesturePanelEnabled", gesturePanelEnabled, KConfigGroup::Notify); m_config->sync(); updateNavigationBarsInPlasma(); } bool MobileShellSettings::alwaysShowKeyboardToggleOnNavigationPanel() const { auto group = KConfigGroup{m_config, GENERAL_CONFIG_GROUP}; return group.readEntry("alwaysShowKeyboardToggleOnNavigationPanel", false); } void MobileShellSettings::setAlwaysShowKeyboardToggleOnNavigationPanel(bool alwaysShowKeyboardToggleOnNavigationPanel) { auto group = KConfigGroup{m_config, GENERAL_CONFIG_GROUP}; group.writeEntry("alwaysShowKeyboardToggleOnNavigationPanel", alwaysShowKeyboardToggleOnNavigationPanel, KConfigGroup::Notify); m_config->sync(); } MobileShellSettings::ActionDrawerMode MobileShellSettings::actionDrawerTopLeftMode() const { auto group = KConfigGroup{m_config, GENERAL_CONFIG_GROUP}; return (ActionDrawerMode)group.readEntry("actionDrawerTopLeftMode", (int)ActionDrawerMode::Pinned); } void MobileShellSettings::setActionDrawerTopLeftMode(ActionDrawerMode actionDrawerMode) { auto group = KConfigGroup{m_config, GENERAL_CONFIG_GROUP}; group.writeEntry("actionDrawerTopLeftMode", (int)actionDrawerMode, KConfigGroup::Notify); m_config->sync(); } int MobileShellSettings::quickSettingsColumns() const { auto group = KConfigGroup{m_config, QUICKSETTINGS_CONFIG_GROUP}; return group.readEntry("quickSettingsColumns", 3); } void MobileShellSettings::setQuickSettingsColumns(int columns) { auto group = KConfigGroup{m_config, QUICKSETTINGS_CONFIG_GROUP}; group.writeEntry("quickSettingsColumns", columns, KConfigGroup::Notify); m_config->sync(); } MobileShellSettings::ActionDrawerMode MobileShellSettings::actionDrawerTopRightMode() const { auto group = KConfigGroup{m_config, GENERAL_CONFIG_GROUP}; return (ActionDrawerMode)group.readEntry("actionDrawerTopRightMode", (int)ActionDrawerMode::Expanded); } void MobileShellSettings::setActionDrawerTopRightMode(ActionDrawerMode actionDrawerMode) { auto group = KConfigGroup{m_config, GENERAL_CONFIG_GROUP}; group.writeEntry("actionDrawerTopRightMode", (int)actionDrawerMode, KConfigGroup::Notify); m_config->sync(); } bool MobileShellSettings::convergenceModeEnabled() const { auto group = KConfigGroup{m_config, GENERAL_CONFIG_GROUP}; return group.readEntry("convergenceModeEnabled", false); } void MobileShellSettings::setConvergenceModeEnabled(bool enabled) { auto group = KConfigGroup{m_config, GENERAL_CONFIG_GROUP}; group.writeEntry("convergenceModeEnabled", enabled, KConfigGroup::Notify); m_config->sync(); // update environment settings auto *job = new KIO::CommandLauncherJob(QStringLiteral("plasma-mobile-envmanager --apply-settings"), {}); job->setUiDelegate(new KNotificationJobUiDelegate(KJobUiDelegate::AutoErrorHandlingEnabled)); job->setDesktopName(QStringLiteral("org.kde.plasma-mobile-envmanager")); job->start(); } QString MobileShellSettings::colorScheme() const { auto group = KConfigGroup{m_kdeGlobalsConfig, KDE_GLOBALS_CONFIG_GROUP}; return group.readEntry("ColorScheme", SHIFT_DARK_COLOR_SCHEME); } bool MobileShellSettings::darkThemeEnabled() const { return isDarkColorScheme(colorScheme()); } void MobileShellSettings::setDarkThemeEnabled(bool enabled) { const QString currentColorScheme = colorScheme(); const QString nextColorScheme = effectiveColorSchemeName(enabled); const QString plasmaTheme = enabled ? SHIFT_DARK_PLASMA_THEME : SHIFT_LIGHT_PLASMA_THEME; QColor accentOverride; if (wallpaperAccentEnabled() && m_lastWallpaperThemeColor.isValid() && m_lastWallpaperThemeColor.alpha() != 0) { accentOverride = m_lastWallpaperThemeColor; } else if (!wallpaperAccentEnabled()) { const QColor manualAccent = accentColor(); if (manualAccent.isValid() && manualAccent.alpha() != 0) { accentOverride = manualAccent; } } if (currentColorScheme == nextColorScheme && nextColorScheme == baseColorSchemeName(enabled)) { return; } if (accentOverride.isValid() && accentOverride.alpha() != 0) { applyColorScheme({QStringLiteral("--accent-color"), accentOverride.name(QColor::HexRgb), nextColorScheme}); } else { QProcess::execute(QStringLiteral("plasma-apply-colorscheme"), {nextColorScheme}); m_kdeGlobalsConfig->reparseConfiguration(); } QProcess::execute(QStringLiteral("plasma-apply-desktoptheme"), {plasmaTheme}); Q_EMIT colorSchemeChanged(); Q_EMIT darkThemeEnabledChanged(); } bool MobileShellSettings::wallpaperThemeEnabled() const { auto group = KConfigGroup{m_config, GENERAL_CONFIG_GROUP}; return group.readEntry("wallpaperThemeEnabled", false); } void MobileShellSettings::setWallpaperThemeEnabled(bool enabled) { if (wallpaperThemeEnabled() == enabled) { return; } auto group = KConfigGroup{m_config, GENERAL_CONFIG_GROUP}; if (enabled) { group.writeEntry(WALLPAPER_THEME_MANUAL_DARK_KEY, darkThemeEnabled(), KConfigGroup::Notify); } group.writeEntry("wallpaperThemeEnabled", enabled, KConfigGroup::Notify); m_config->sync(); if (!enabled) { m_wallpaperThemeTimer.stop(); setDarkThemeEnabled(group.readEntry(WALLPAPER_THEME_MANUAL_DARK_KEY, darkThemeEnabled())); } Q_EMIT wallpaperThemeEnabledChanged(); } void MobileShellSettings::applyWallpaperThemeColor(const QColor &color) { if (!wallpaperThemeEnabled() || !color.isValid() || color.alpha() == 0) { return; } QColor opaqueColor = color; opaqueColor.setAlpha(255); const bool colorChanged = m_lastWallpaperThemeColor != opaqueColor; m_lastWallpaperThemeColor = opaqueColor; if (colorChanged) { Q_EMIT wallpaperThemeColorChanged(); } const qreal luminance = relativeLuminance(opaqueColor); const bool shouldUseDarkTheme = luminance < 0.5; const bool sameThemeBucket = darkThemeEnabled() == shouldUseDarkTheme; if (wallpaperAccentEnabled() && colorChanged && sameThemeBucket) { applyColorScheme({QStringLiteral("--accent-color"), opaqueColor.name(QColor::HexRgb), colorScheme()}); } if (!m_wallpaperThemeTimer.isActive() && darkThemeEnabled() == shouldUseDarkTheme && !colorChanged) { return; } m_pendingWallpaperThemeDark = shouldUseDarkTheme; m_wallpaperThemeTimer.start(); } QColor MobileShellSettings::wallpaperThemeColor() const { return m_lastWallpaperThemeColor; } QColor MobileShellSettings::accentColor() const { auto group = KConfigGroup{m_kdeGlobalsConfig, KDE_GLOBALS_CONFIG_GROUP}; return group.readEntry("AccentColor", QColor(Qt::transparent)); } void MobileShellSettings::setAccentColor(const QColor &color) { if (!color.isValid() || color.alpha() == 0) { resetAccentColor(); return; } QColor opaqueColor = color; opaqueColor.setAlpha(255); auto group = KConfigGroup{m_kdeGlobalsConfig, KDE_GLOBALS_CONFIG_GROUP}; group.writeEntry("accentColorFromWallpaper", false, KConfigGroup::Notify); group.writeEntry("LastUsedCustomAccentColor", opaqueColor, KConfigGroup::Notify); m_kdeGlobalsConfig->sync(); applyColorScheme({QStringLiteral("--accent-color"), opaqueColor.name(QColor::HexRgb), colorScheme()}); Q_EMIT wallpaperAccentEnabledChanged(); Q_EMIT accentColorChanged(); } bool MobileShellSettings::wallpaperAccentEnabled() const { auto group = KConfigGroup{m_kdeGlobalsConfig, KDE_GLOBALS_CONFIG_GROUP}; return group.readEntry("accentColorFromWallpaper", false); } void MobileShellSettings::setWallpaperAccentEnabled(bool enabled) { if (wallpaperAccentEnabled() == enabled) { return; } auto group = KConfigGroup{m_kdeGlobalsConfig, KDE_GLOBALS_CONFIG_GROUP}; group.writeEntry("accentColorFromWallpaper", enabled, KConfigGroup::Notify); m_kdeGlobalsConfig->sync(); if (enabled && m_lastWallpaperThemeColor.isValid() && m_lastWallpaperThemeColor.alpha() != 0) { applyColorScheme({QStringLiteral("--accent-color"), m_lastWallpaperThemeColor.name(QColor::HexRgb), colorScheme()}); Q_EMIT accentColorChanged(); } if (!enabled) { const QColor lastCustomColor = lastUsedCustomAccentColor(); if (lastCustomColor.isValid() && lastCustomColor.alpha() != 0) { applyColorScheme({QStringLiteral("--accent-color"), lastCustomColor.name(QColor::HexRgb), colorScheme()}); } else { resetAccentColor(); return; } } Q_EMIT wallpaperAccentEnabledChanged(); } void MobileShellSettings::resetAccentColor() { auto group = KConfigGroup{m_kdeGlobalsConfig, KDE_GLOBALS_CONFIG_GROUP}; group.writeEntry("accentColorFromWallpaper", false, KConfigGroup::Notify); group.deleteEntry("AccentColor", KConfigGroup::Notify); m_kdeGlobalsConfig->sync(); applyColorScheme({colorScheme()}); Q_EMIT wallpaperAccentEnabledChanged(); Q_EMIT accentColorChanged(); } QColor MobileShellSettings::lastUsedCustomAccentColor() const { auto group = KConfigGroup{m_kdeGlobalsConfig, KDE_GLOBALS_CONFIG_GROUP}; return group.readEntry("LastUsedCustomAccentColor", QColor(Qt::transparent)); } void MobileShellSettings::applyColorScheme(const QStringList &arguments) { QProcess::execute(QStringLiteral("plasma-apply-colorscheme"), arguments); m_kdeGlobalsConfig->reparseConfiguration(); } QString MobileShellSettings::effectiveColorSchemeName(bool dark) const { if (!wallpaperThemeEnabled() || !m_lastWallpaperThemeColor.isValid() || m_lastWallpaperThemeColor.alpha() == 0) { return baseColorSchemeName(dark); } if (!ensureWallpaperColorScheme(m_lastWallpaperThemeColor, dark)) { return baseColorSchemeName(dark); } return wallpaperColorSchemeName(dark); } bool MobileShellSettings::isDarkColorScheme(const QString &schemeName) const { return schemeName == SHIFT_DARK_COLOR_SCHEME || schemeName == SHIFT_WALLPAPER_DARK_COLOR_SCHEME; } bool MobileShellSettings::ensureWallpaperColorScheme(const QColor &accentColor, bool dark) const { if (!accentColor.isValid() || accentColor.alpha() == 0) { return false; } const QString sourcePath = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("color-schemes/%1.colors").arg(baseColorSchemeName(dark))); if (sourcePath.isEmpty()) { qWarning() << "Unable to locate base Shift color scheme for wallpaper generation:" << baseColorSchemeName(dark); return false; } const QString outputDir = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QStringLiteral("/color-schemes"); if (!QDir().mkpath(outputDir)) { qWarning() << "Unable to create dynamic color-scheme directory:" << outputDir; return false; } const QString outputPath = outputDir + QStringLiteral("/%1.colors").arg(wallpaperColorSchemeName(dark)); QFile::remove(outputPath); if (!QFile::copy(sourcePath, outputPath)) { qWarning() << "Unable to create wallpaper-derived color scheme:" << outputPath; return false; } auto generatedConfig = KSharedConfig::openConfig(outputPath, KConfig::SimpleConfig); auto generalGroup = KConfigGroup{generatedConfig, QStringLiteral("General")}; generalGroup.writeEntry("ColorScheme", wallpaperColorSchemeName(dark)); generalGroup.writeEntry("Name", wallpaperColorSchemeDisplayName(dark)); writeAccentEntries(generatedConfig, QStringLiteral("Colors:Button"), accentColor); writeAccentEntries(generatedConfig, QStringLiteral("Colors:Complementary"), accentColor); writeAccentEntries(generatedConfig, QStringLiteral("Colors:Header"), accentColor); writeAccentEntries(generatedConfig, QStringLiteral("Colors:Header][Inactive"), accentColor); writeAccentEntries(generatedConfig, QStringLiteral("Colors:Tooltip"), accentColor); writeAccentEntries(generatedConfig, QStringLiteral("Colors:View"), accentColor); writeAccentEntries(generatedConfig, QStringLiteral("Colors:Window"), accentColor); tintBackgroundEntries(generatedConfig, QStringLiteral("Colors:Button"), accentColor, dark); tintBackgroundEntries(generatedConfig, QStringLiteral("Colors:View"), accentColor, dark); tintBackgroundEntries(generatedConfig, QStringLiteral("Colors:Window"), accentColor, dark); tintBackgroundEntries(generatedConfig, QStringLiteral("Colors:Header"), accentColor, dark); tintBackgroundEntries(generatedConfig, QStringLiteral("Colors:Header][Inactive"), accentColor, dark); tintBackgroundEntries(generatedConfig, QStringLiteral("Colors:Tooltip"), accentColor, dark); tuneForegroundEntries(generatedConfig, QStringLiteral("Colors:Button"), accentColor, dark); tuneForegroundEntries(generatedConfig, QStringLiteral("Colors:View"), accentColor, dark); tuneForegroundEntries(generatedConfig, QStringLiteral("Colors:Window"), accentColor, dark); tuneForegroundEntries(generatedConfig, QStringLiteral("Colors:Header"), accentColor, dark); tuneForegroundEntries(generatedConfig, QStringLiteral("Colors:Header][Inactive"), accentColor, dark); tuneForegroundEntries(generatedConfig, QStringLiteral("Colors:Tooltip"), accentColor, dark); auto complementaryGroup = KConfigGroup{generatedConfig, QStringLiteral("Colors:Complementary")}; const QColor complementaryBase = complementaryGroup.readEntry("BackgroundNormal", dark ? QColor(24, 27, 38) : QColor(42, 46, 50)); complementaryGroup.writeEntry("BackgroundNormal", rgbString(blendColors(complementaryBase, accentColor, dark ? 0.18 : 0.14))); complementaryGroup.writeEntry("BackgroundAlternate", rgbString(blendColors(complementaryBase, accentColor, dark ? 0.12 : 0.09))); tuneForegroundEntries(generatedConfig, QStringLiteral("Colors:Complementary"), accentColor, dark); auto selectionGroup = KConfigGroup{generatedConfig, QStringLiteral("Colors:Selection")}; const QColor selectionBackground = blendColors(accentColor, dark ? QColor(18, 20, 28) : QColor(255, 255, 255), dark ? 0.82 : 0.9); const QColor selectionAlternate = blendColors(selectionBackground, dark ? QColor(0, 0, 0) : QColor(255, 255, 255), dark ? 0.16 : 0.24); const QColor selectionForeground = ensureContrast(contrastingTextColor(selectionBackground), selectionBackground, MIN_NORMAL_CONTRAST); const QColor selectionInactive = ensureContrast(blendColors(selectionForeground, selectionBackground, dark ? 0.4 : 0.52), selectionBackground, MIN_INACTIVE_CONTRAST); const QColor selectionLink = ensureContrast(blendColors(accentColor, selectionForeground, dark ? 0.3 : 0.2), selectionBackground, MIN_LINK_CONTRAST); selectionGroup.writeEntry("BackgroundNormal", rgbString(selectionBackground)); selectionGroup.writeEntry("BackgroundAlternate", rgbString(selectionAlternate)); selectionGroup.writeEntry("DecorationFocus", rgbString(accentColor)); selectionGroup.writeEntry("DecorationHover", rgbString(accentColor)); selectionGroup.writeEntry("ForegroundActive", rgbString(selectionForeground)); selectionGroup.writeEntry("ForegroundNormal", rgbString(selectionForeground)); selectionGroup.writeEntry("ForegroundInactive", rgbString(selectionInactive)); selectionGroup.writeEntry("ForegroundLink", rgbString(selectionLink)); auto wmGroup = KConfigGroup{generatedConfig, QStringLiteral("WM")}; const QColor currentActiveBackground = wmGroup.readEntry("activeBackground", accentColor); const QColor tintedActiveBackground = blendColors(currentActiveBackground, accentColor, dark ? 0.42 : 0.26); const QColor currentInactiveBackground = wmGroup.readEntry("inactiveBackground", tintedActiveBackground); const QColor tintedInactiveBackground = blendColors(currentInactiveBackground, accentColor, dark ? 0.24 : 0.14); const QColor activeForeground = ensureContrast(contrastingTextColor(tintedActiveBackground), tintedActiveBackground, MIN_NORMAL_CONTRAST); const QColor inactiveForeground = ensureContrast(blendColors(contrastingTextColor(tintedInactiveBackground), tintedInactiveBackground, dark ? 0.36 : 0.52), tintedInactiveBackground, MIN_INACTIVE_CONTRAST); wmGroup.writeEntry("activeBackground", rgbString(tintedActiveBackground)); wmGroup.writeEntry("activeBlend", rgbString(activeForeground)); wmGroup.writeEntry("activeForeground", rgbString(activeForeground)); wmGroup.writeEntry("inactiveBackground", rgbString(tintedInactiveBackground)); wmGroup.writeEntry("inactiveBlend", rgbString(inactiveForeground)); wmGroup.writeEntry("inactiveForeground", rgbString(inactiveForeground)); generatedConfig->sync(); return true; } bool MobileShellSettings::autoHidePanelsEnabled() const { auto group = KConfigGroup{m_config, GENERAL_CONFIG_GROUP}; return group.readEntry("autoHidePanelsEnabled", false); } void MobileShellSettings::setAutoHidePanelsEnabled(bool enabled) { auto group = KConfigGroup{m_config, GENERAL_CONFIG_GROUP}; group.writeEntry("autoHidePanelsEnabled", enabled, KConfigGroup::Notify); m_config->sync(); } bool MobileShellSettings::gamingModeEnabled() const { auto group = KConfigGroup{m_config, GENERAL_CONFIG_GROUP}; return group.readEntry("gamingModeEnabled", false); } void MobileShellSettings::setGamingModeEnabled(bool enabled) { auto group = KConfigGroup{m_config, GENERAL_CONFIG_GROUP}; group.writeEntry("gamingModeEnabled", enabled, KConfigGroup::Notify); m_config->sync(); } bool MobileShellSettings::gamingDismissHintEnabled() const { auto group = KConfigGroup{m_config, GENERAL_CONFIG_GROUP}; return group.readEntry("gamingDismissHintEnabled", true); } void MobileShellSettings::setGamingDismissHintEnabled(bool enabled) { auto group = KConfigGroup{m_config, GENERAL_CONFIG_GROUP}; group.writeEntry("gamingDismissHintEnabled", enabled, KConfigGroup::Notify); m_config->sync(); } bool MobileShellSettings::dynamicTilingEnabled() const { auto group = KConfigGroup{m_config, GENERAL_CONFIG_GROUP}; return group.readEntry("dynamicTilingEnabled", true); } void MobileShellSettings::setDynamicTilingEnabled(bool enabled) { auto group = KConfigGroup{m_config, GENERAL_CONFIG_GROUP}; group.writeEntry("dynamicTilingEnabled", enabled, KConfigGroup::Notify); m_config->sync(); } QString MobileShellSettings::dynamicTilingWindowRequestAction() const { auto group = KConfigGroup{m_config, GENERAL_CONFIG_GROUP}; return group.readEntry("dynamicTilingWindowRequestAction", QString{}); } QString MobileShellSettings::dynamicTilingWindowRequestId() const { auto group = KConfigGroup{m_config, GENERAL_CONFIG_GROUP}; return group.readEntry("dynamicTilingWindowRequestId", QString{}); } int MobileShellSettings::dynamicTilingWindowRequestSerial() const { auto group = KConfigGroup{m_config, GENERAL_CONFIG_GROUP}; return group.readEntry("dynamicTilingWindowRequestSerial", 0); } void MobileShellSettings::requestDynamicTilingWindowAction(const QString &windowId, const QString &action) { if (windowId.isEmpty() || action.isEmpty()) { return; } auto group = KConfigGroup{m_config, GENERAL_CONFIG_GROUP}; const int serial = group.readEntry("dynamicTilingWindowRequestSerial", 0) + 1; group.writeEntry("dynamicTilingWindowRequestId", windowId, KConfigGroup::Notify); group.writeEntry("dynamicTilingWindowRequestAction", action, KConfigGroup::Notify); group.writeEntry("dynamicTilingWindowRequestSerial", serial, KConfigGroup::Notify); m_config->sync(); Q_EMIT dynamicTilingWindowRequestChanged(); } QStringList MobileShellSettings::dynamicTilingMaximizedWindowIds() const { auto group = KConfigGroup{m_config, GENERAL_CONFIG_GROUP}; return group.readEntry("dynamicTilingMaximizedWindowIds", QStringList{}); } int MobileShellSettings::dynamicTilingWindowStateSerial() const { auto group = KConfigGroup{m_config, GENERAL_CONFIG_GROUP}; return group.readEntry("dynamicTilingWindowStateSerial", 0); } bool MobileShellSettings::isDynamicTilingWindowMaximized(const QString &windowId) const { if (windowId.isEmpty()) { return false; } return dynamicTilingMaximizedWindowIds().contains(windowId); } void MobileShellSettings::reportDynamicTilingWindowState(const QStringList &maximizedWindowIds) { QStringList normalizedIds; for (const QString &windowId : maximizedWindowIds) { if (!windowId.isEmpty() && !normalizedIds.contains(windowId)) { normalizedIds.push_back(windowId); } } normalizedIds.sort(Qt::CaseSensitive); auto group = KConfigGroup{m_config, GENERAL_CONFIG_GROUP}; if (group.readEntry("dynamicTilingMaximizedWindowIds", QStringList{}) == normalizedIds) { return; } const int serial = group.readEntry("dynamicTilingWindowStateSerial", 0) + 1; group.writeEntry("dynamicTilingMaximizedWindowIds", normalizedIds, KConfigGroup::Notify); group.writeEntry("dynamicTilingWindowStateSerial", serial, KConfigGroup::Notify); m_config->sync(); Q_EMIT dynamicTilingWindowStateChanged(); } QString MobileShellSettings::dynamicTilingLayoutRequestMode() const { auto group = KConfigGroup{m_config, GENERAL_CONFIG_GROUP}; return group.readEntry("dynamicTilingLayoutRequestMode", QString{}); } int MobileShellSettings::dynamicTilingLayoutRequestSerial() const { auto group = KConfigGroup{m_config, GENERAL_CONFIG_GROUP}; return group.readEntry("dynamicTilingLayoutRequestSerial", 0); } void MobileShellSettings::requestDynamicTilingLayoutMode(const QString &mode) { if (mode.isEmpty()) { return; } auto group = KConfigGroup{m_config, GENERAL_CONFIG_GROUP}; const int serial = group.readEntry("dynamicTilingLayoutRequestSerial", 0) + 1; group.writeEntry("dynamicTilingLayoutRequestMode", mode, KConfigGroup::Notify); group.writeEntry("dynamicTilingLayoutRequestSerial", serial, KConfigGroup::Notify); m_config->sync(); Q_EMIT dynamicTilingLayoutRequestChanged(); } QString MobileShellSettings::dynamicTilingLayoutMode() const { auto group = KConfigGroup{m_config, GENERAL_CONFIG_GROUP}; return group.readEntry("dynamicTilingLayoutMode", QString{}); } int MobileShellSettings::dynamicTilingLayoutWindowCount() const { auto group = KConfigGroup{m_config, GENERAL_CONFIG_GROUP}; return group.readEntry("dynamicTilingLayoutWindowCount", 0); } int MobileShellSettings::dynamicTilingLayoutStateSerial() const { auto group = KConfigGroup{m_config, GENERAL_CONFIG_GROUP}; return group.readEntry("dynamicTilingLayoutStateSerial", 0); } void MobileShellSettings::reportDynamicTilingLayoutState(const QString &mode, int windowCount) { const int normalizedWindowCount = windowCount < 0 ? 0 : windowCount; auto group = KConfigGroup{m_config, GENERAL_CONFIG_GROUP}; if (group.readEntry("dynamicTilingLayoutMode", QString{}) == mode && group.readEntry("dynamicTilingLayoutWindowCount", 0) == normalizedWindowCount) { return; } const int serial = group.readEntry("dynamicTilingLayoutStateSerial", 0) + 1; group.writeEntry("dynamicTilingLayoutMode", mode, KConfigGroup::Notify); group.writeEntry("dynamicTilingLayoutWindowCount", normalizedWindowCount, KConfigGroup::Notify); group.writeEntry("dynamicTilingLayoutStateSerial", serial, KConfigGroup::Notify); m_config->sync(); Q_EMIT dynamicTilingLayoutStateChanged(); } bool MobileShellSettings::snapLayoutsEnabled() const { auto group = KConfigGroup{m_config, GENERAL_CONFIG_GROUP}; return group.readEntry("snapLayoutsEnabled", true); } void MobileShellSettings::setSnapLayoutsEnabled(bool enabled) { auto group = KConfigGroup{m_config, GENERAL_CONFIG_GROUP}; group.writeEntry("snapLayoutsEnabled", enabled, KConfigGroup::Notify); m_config->sync(); } void MobileShellSettings::updateNavigationBarsInPlasma() { // Do not update panels when not in Plasma Mobile bool isMobilePlatform = KRuntimePlatform::runtimePlatform().contains("phone"); if (!isMobilePlatform) { return; } auto message = QDBusMessage::createMethodCall(QLatin1String("org.kde.plasmashell"), QLatin1String("/PlasmaShell"), QLatin1String("org.kde.PlasmaShell"), QLatin1String("evaluateScript")); if (navigationPanelEnabled() || gesturePanelEnabled()) { QString createNavigationPanelScript = R"( let allPanels = panels(); let foundPanel = false; for (var i = 0; i < allPanels.length; i++) { if (allPanels[i].type === "org.kde.plasma.mobile.taskpanel") { foundPanel = true; } } if (!foundPanel) { loadTemplate("org.kde.plasma.mobile.defaultNavigationPanel"); } )"; message << createNavigationPanelScript; } else { QString deleteNavigationPanelScript = R"( let allPanels = panels(); for (var i = 0; i < allPanels.length; i++) { if (allPanels[i].type === "org.kde.plasma.mobile.taskpanel") { allPanels[i].remove(); } } )"; message << deleteNavigationPanelScript; } // TODO check for error response QDBusConnection::sessionBus().asyncCall(message); } bool MobileShellSettings::allowLogout() const { auto group = KConfigGroup{m_config, GENERAL_CONFIG_GROUP}; return group.readEntry("allowLogout", true); } MobileShellSettings::LockscreenButtonAction MobileShellSettings::lockscreenLeftButtonAction() const { auto group = KConfigGroup{m_config, LOCKSCREEN_CONFIG_GROUP}; return (LockscreenButtonAction)group.readEntry("lockscreenLeftButtonAction", (int)LockscreenButtonAction::None); } void MobileShellSettings::setLockscreenLeftButtonAction(const LockscreenButtonAction action) { auto group = KConfigGroup{m_config, LOCKSCREEN_CONFIG_GROUP}; group.writeEntry("lockscreenLeftButtonAction", (int)action, KConfigGroup::Notify); m_config->sync(); } MobileShellSettings::LockscreenButtonAction MobileShellSettings::lockscreenRightButtonAction() const { auto group = KConfigGroup{m_config, LOCKSCREEN_CONFIG_GROUP}; return (LockscreenButtonAction)group.readEntry("lockscreenRightButtonAction", (int)LockscreenButtonAction::None); } void MobileShellSettings::setLockscreenRightButtonAction(const LockscreenButtonAction action) { auto group = KConfigGroup{m_config, LOCKSCREEN_CONFIG_GROUP}; group.writeEntry("lockscreenRightButtonAction", (int)action, KConfigGroup::Notify); m_config->sync(); }