Files
DankMaterialShell/quickshell/Common/SettingsData.qml
T
Marlon Kranzandpurian23 6d331b8fde fix(settings): show what frame mode controls (#3654)
* fix(settings): show what frame mode controls

With frame mode on, many settings did nothing or were hidden while
still applying, and nothing said so. The frame takes its color and
opacity from the first non-island bar, but that bar's Surface card is
hidden while the frame styles it, so neither had a visible control.

Add Background and Override/Opacity rows to the Appearance Frame card,
bound to the same frame bar the renderer reads. Settings the frame
provides in connected mode get a "Managed by Frame in Connected Mode"
note (Interface style border, corner style, background blur and dock
opacity, shadow targets, launcher border); settings with no effect get
"Disabled by Frame Mode" (maximize detection, bar shadow, the bar's
overlay layer in separate mode). Bar size, the bar's overlay layer in
connected mode, dock Over fullscreen and the controls of overlay-layer
docks and launchers are shown again, since they still apply.
Notification Over fullscreen shows as forced on when the frame covers
every display, and launcher rows the Spotlight style ignores are
disabled.

Also relabel the frame Width row as Thickness, raise the inset padding
maximum to its automatic range, and drop a FrameWindow handler for the
removed frameOpacity signal. No new strings.

* settings: show what frame mode controls, fix review findings

---------

Co-authored-by: purian23 <[email protected]>
2026-10-10 00:37:54 -04:00

4097 lines
164 KiB
QML

pragma Singleton
pragma ComponentBehavior: Bound
import QtCore
import Qt.labs.folderlistmodel
import QtQuick
import "../DCommon/Common/Shape.js" as Shape
import Quickshell
import Quickshell.Io
import qs.Common
import qs.Common.settings
import qs.Services
import "GSettings.js" as GSettings
import "LayoutResolver.js" as LayoutResolver
import "NotificationRuleExpiry.js" as RuleExpiry
import "settings/SettingsSpec.js" as Spec
import "settings/SettingsStore.js" as Store
import "../DCommon/Common/settings/SpecUtil.js" as SpecUtil
import "settings/BarWidgetDefaults.js" as WidgetDefaults
import "settings/DockConfig.js" as DockConfig
Singleton {
id: root
readonly property var log: Log.scoped("SettingsData")
readonly property int settingsConfigVersion: 40
readonly property bool isGreeterMode: Quickshell.env("DMS_RUN_GREETER") === "1" || Quickshell.env("DMS_RUN_GREETER") === "true"
enum Position {
Top,
Bottom,
Left,
Right,
TopCenter,
BottomCenter,
LeftCenter,
RightCenter
}
enum AnimationSpeed {
None,
Short,
Medium,
Long,
Custom
}
enum AnimationEffect {
Standard, // 0 — M3: scale-in, rises from below
Directional, // 1 — pure large slide, no scale
Depth, // 2 — medium slide with deep depth scale pop
Fluid
}
enum SuspendBehavior {
Suspend,
Hibernate,
SuspendThenHibernate
}
enum WidgetColorMode {
Default,
Colorful
}
enum TextRenderType {
Qt,
Native,
Curve
}
enum TextRenderQuality {
Default,
Low,
Normal,
High,
VeryHigh
}
readonly property string _homeUrl: StandardPaths.writableLocation(StandardPaths.HomeLocation)
readonly property string _configUrl: StandardPaths.writableLocation(StandardPaths.ConfigLocation)
readonly property string _configDir: Paths.strip(_configUrl)
readonly property string pluginSettingsPath: _configDir + "/DankMaterialShell/plugin_settings.json"
readonly property bool qtengineActive: Quickshell.env("QT_QPA_PLATFORMTHEME") === "qtengine" || Quickshell.env("QT_QPA_PLATFORMTHEME_QT6") === "qtengine"
property bool _loading: false
property bool _pluginSettingsLoading: false
property bool _parseError: false
property bool _pluginParseError: false
property bool _hasLoaded: false
property bool isReadOnly: false
property bool unsavedUserChanges: false
property var pluginSettings: ({})
property var builtInPluginSettings: Spec.SPEC.builtInPluginSettings.def
function getBuiltInPluginSetting(pluginId, key, defaultValue) {
if (!builtInPluginSettings[pluginId])
return defaultValue;
return builtInPluginSettings[pluginId][key] !== undefined ? builtInPluginSettings[pluginId][key] : defaultValue;
}
function setBuiltInPluginSetting(pluginId, key, value) {
const updated = JSON.parse(JSON.stringify(builtInPluginSettings));
if (!updated[pluginId])
updated[pluginId] = {};
updated[pluginId][key] = value;
builtInPluginSettings = updated;
if (Store.SESSION_BACKED_PLUGIN_IDS.includes(pluginId)) {
SessionData.setBuiltInPluginState(pluginId, updated[pluginId]);
return;
}
saveSettings();
}
property bool clipboardClickToPaste: Spec.SPEC.clipboardClickToPaste.def
property bool clipboardEnterToPaste: Spec.SPEC.clipboardEnterToPaste.def
property bool clipboardRememberTypeFilter: Spec.SPEC.clipboardRememberTypeFilter.def
property bool clipboardUseOverlayLayer: Spec.SPEC.clipboardUseOverlayLayer.def
property string clipboardSize: Spec.SPEC.clipboardSize.def
property string clipboardTypeFilter: Spec.SPEC.clipboardTypeFilter.def
property var clipboardVisibleEntryActions: Spec.SPEC.clipboardVisibleEntryActions.def
property var launcherPluginVisibility: Spec.SPEC.launcherPluginVisibility.def
function getPluginAllowWithoutTrigger(pluginId) {
if (!launcherPluginVisibility[pluginId])
return true;
return launcherPluginVisibility[pluginId].allowWithoutTrigger !== false;
}
function setPluginAllowWithoutTrigger(pluginId, allow) {
const updated = JSON.parse(JSON.stringify(launcherPluginVisibility));
if (!updated[pluginId])
updated[pluginId] = {};
updated[pluginId].allowWithoutTrigger = allow;
launcherPluginVisibility = updated;
saveSettings();
}
property var launcherPluginOrder: Spec.SPEC.launcherPluginOrder.def
onLauncherPluginOrderChanged: saveSettings()
function setLauncherPluginOrder(order) {
launcherPluginOrder = order;
}
function getOrderedLauncherPlugins(allPlugins) {
if (!launcherPluginOrder || launcherPluginOrder.length === 0)
return allPlugins;
const orderMap = {};
for (let i = 0; i < launcherPluginOrder.length; i++)
orderMap[launcherPluginOrder[i]] = i;
return allPlugins.slice().sort((a, b) => {
const aOrder = orderMap[a.id] ?? 9999;
const bOrder = orderMap[b.id] ?? 9999;
if (aOrder !== bOrder)
return aOrder - bOrder;
return a.name.localeCompare(b.name);
});
}
property string currentThemeName: Spec.SPEC.currentThemeName.def
property string currentThemeCategory: Spec.SPEC.currentThemeCategory.def
property string customThemeFile: Spec.SPEC.customThemeFile.def
property var registryThemeVariants: Spec.SPEC.registryThemeVariants.def
property bool generateThemeAtStartup: Spec.SPEC.generateThemeAtStartup.def
property string matugenScheme: Spec.SPEC.matugenScheme.def
property bool matugenSmartMode: Spec.SPEC.matugenSmartMode.def
property string matugenSourceMode: Spec.SPEC.matugenSourceMode.def
property real matugenContrast: Spec.SPEC.matugenContrast.def
property string matugenSeedColor: Spec.SPEC.matugenSeedColor.def
property string matugenSpec: Spec.SPEC.matugenSpec.def
property bool runUserMatugenTemplates: Spec.SPEC.runUserMatugenTemplates.def
property string matugenTargetMonitor: Spec.SPEC.matugenTargetMonitor.def
property real popupTransparency: Spec.SPEC.popupTransparency.def
property bool floatingWindowSyncGlobal: Spec.SPEC.floatingWindowSyncGlobal.def
property bool floatingWindowTitleBars: Spec.SPEC.floatingWindowTitleBars.def
property real floatingWindowTransparency: Spec.SPEC.floatingWindowTransparency.def
property bool floatingWindowForegroundLayers: Spec.SPEC.floatingWindowForegroundLayers.def
property real floatingWindowForegroundTransparency: Spec.SPEC.floatingWindowForegroundTransparency.def
property var dmsWindowsFloatingSeeded: Spec.SPEC.dmsWindowsFloatingSeeded.def
property string hostSurfaceColor: Spec.SPEC.hostSurfaceColor.def
property string hostSurfaceCustomColor: Spec.SPEC.hostSurfaceCustomColor.def
property string cardSurfaceColor: Spec.SPEC.cardSurfaceColor.def
property string cardSurfaceCustomColor: Spec.SPEC.cardSurfaceCustomColor.def
property string chipSurfaceColor: Spec.SPEC.chipSurfaceColor.def
property string chipSurfaceCustomColor: Spec.SPEC.chipSurfaceCustomColor.def
property string chipSurfaceNestedColor: Spec.SPEC.chipSurfaceNestedColor.def
property string chipSurfaceNestedCustomColor: Spec.SPEC.chipSurfaceNestedCustomColor.def
property string widgetBackgroundColor: Spec.SPEC.widgetBackgroundColor.def
property string widgetBackgroundCustomColor: Spec.SPEC.widgetBackgroundCustomColor.def
property real widgetBackgroundCustomStrength: Spec.SPEC.widgetBackgroundCustomStrength.def
property string widgetColorMode: Spec.SPEC.widgetColorMode.def
property string controlCenterTileColorMode: Spec.SPEC.controlCenterTileColorMode.def
property string buttonColorMode: Spec.SPEC.buttonColorMode.def
property int containerSaturation: Spec.SPEC.containerSaturation.def
property int radiusStrength: Spec.SPEC.radiusStrength.def
property string radiusMode: Spec.SPEC.radiusMode.def
property int fixedRadius: Spec.SPEC.fixedRadius.def
readonly property real cornerRadius: Shape.radius("m", Shape.scaleForStrength(radiusStrength), radiusMode === "fixed" ? fixedRadius : -1)
property int niriLayoutGapsOverride: Spec.SPEC.niriLayoutGapsOverride.def
property int niriLayoutRadiusOverride: Spec.SPEC.niriLayoutRadiusOverride.def
property int niriLayoutBorderSize: Spec.SPEC.niriLayoutBorderSize.def
property int hyprlandLayoutGapsOverride: Spec.SPEC.hyprlandLayoutGapsOverride.def
property int hyprlandLayoutGapsOutOverride: Spec.SPEC.hyprlandLayoutGapsOutOverride.def
property int hyprlandLayoutRadiusOverride: Spec.SPEC.hyprlandLayoutRadiusOverride.def
property int hyprlandLayoutBorderSize: Spec.SPEC.hyprlandLayoutBorderSize.def
property bool hyprlandResizeOnBorder: Spec.SPEC.hyprlandResizeOnBorder.def
property int hyprlandWindowOpacity: Spec.SPEC.hyprlandWindowOpacity.def
property int hyprlandBorderGrabArea: Spec.SPEC.hyprlandBorderGrabArea.def
property string hyprlandBlurVariant: Spec.SPEC.hyprlandBlurVariant.def
property int hyprlandBlurAcrylicClarity: Spec.SPEC.hyprlandBlurAcrylicClarity.def
property int hyprlandBlurAuroraIntensity: Spec.SPEC.hyprlandBlurAuroraIntensity.def
property int hyprlandBlurAuroraSpeed: Spec.SPEC.hyprlandBlurAuroraSpeed.def
property int hyprlandBlurHazeIntensity: Spec.SPEC.hyprlandBlurHazeIntensity.def
property int hyprlandBlurHazeIridescence: Spec.SPEC.hyprlandBlurHazeIridescence.def
property int hyprlandBlurRippleStrength: Spec.SPEC.hyprlandBlurRippleStrength.def
property int hyprlandBlurWaterStrength: Spec.SPEC.hyprlandBlurWaterStrength.def
property bool hyprlandGlowEnabled: Spec.SPEC.hyprlandGlowEnabled.def
property int hyprlandGlowRange: Spec.SPEC.hyprlandGlowRange.def
property int hyprlandGlowRenderPower: Spec.SPEC.hyprlandGlowRenderPower.def
property bool hyprlandWobbleEnabled: Spec.SPEC.hyprlandWobbleEnabled.def
property int hyprlandWobbleIntensity: Spec.SPEC.hyprlandWobbleIntensity.def
property int hyprlandWobbleStiffness: Spec.SPEC.hyprlandWobbleStiffness.def
property bool hyprlandMotionBlurEnabled: Spec.SPEC.hyprlandMotionBlurEnabled.def
property int hyprlandMotionBlurSamples: Spec.SPEC.hyprlandMotionBlurSamples.def
property bool hyprlandGroupbarBlur: Spec.SPEC.hyprlandGroupbarBlur.def
property int hyprlandGroupbarTextPadding: Spec.SPEC.hyprlandGroupbarTextPadding.def
property bool hyprlandGroupbarMiddleClickClose: Spec.SPEC.hyprlandGroupbarMiddleClickClose.def
property bool hyprlandGroupbarDisableWhenOnly: Spec.SPEC.hyprlandGroupbarDisableWhenOnly.def
property string hyprlandTilingLayout: Spec.SPEC.hyprlandTilingLayout.def
property bool hyprlandDwindlePreserveSplit: Spec.SPEC.hyprlandDwindlePreserveSplit.def
property bool hyprlandDwindleSmartSplit: Spec.SPEC.hyprlandDwindleSmartSplit.def
property int hyprlandDwindleForceSplit: Spec.SPEC.hyprlandDwindleForceSplit.def
property string hyprlandMasterOrientation: Spec.SPEC.hyprlandMasterOrientation.def
property string hyprlandMasterNewStatus: Spec.SPEC.hyprlandMasterNewStatus.def
property bool hyprlandMasterNewOnTop: Spec.SPEC.hyprlandMasterNewOnTop.def
property int hyprlandMasterSize: Spec.SPEC.hyprlandMasterSize.def
property string hyprlandScrollingDirection: Spec.SPEC.hyprlandScrollingDirection.def
property int hyprlandScrollingColumnWidth: Spec.SPEC.hyprlandScrollingColumnWidth.def
property bool hyprlandScrollingFullscreenOneColumn: Spec.SPEC.hyprlandScrollingFullscreenOneColumn.def
property bool hyprlandScrollingFollowFocus: Spec.SPEC.hyprlandScrollingFollowFocus.def
property int mangoLayoutGapsOverride: Spec.SPEC.mangoLayoutGapsOverride.def
property int mangoLayoutGapsOutOverride: Spec.SPEC.mangoLayoutGapsOutOverride.def
property int mangoLayoutRadiusOverride: Spec.SPEC.mangoLayoutRadiusOverride.def
property int mangoLayoutBorderSize: Spec.SPEC.mangoLayoutBorderSize.def
property string mouseAccelProfile: Spec.SPEC.mouseAccelProfile.def
property real mouseAccelSpeed: Spec.SPEC.mouseAccelSpeed.def
property bool mouseLeftHanded: Spec.SPEC.mouseLeftHanded.def
property bool mouseMiddleEmulation: Spec.SPEC.mouseMiddleEmulation.def
property bool mouseNaturalScroll: Spec.SPEC.mouseNaturalScroll.def
property real mouseScrollFactor: Spec.SPEC.mouseScrollFactor.def
property string mouseScrollMethod: Spec.SPEC.mouseScrollMethod.def
property string touchpadAccelProfile: Spec.SPEC.touchpadAccelProfile.def
property real touchpadAccelSpeed: Spec.SPEC.touchpadAccelSpeed.def
property string touchpadClickMethod: Spec.SPEC.touchpadClickMethod.def
property bool touchpadDisableOnExternalMouse: Spec.SPEC.touchpadDisableOnExternalMouse.def
property bool touchpadDisableWhileTyping: Spec.SPEC.touchpadDisableWhileTyping.def
property bool touchpadDragLock: Spec.SPEC.touchpadDragLock.def
property bool touchpadMiddleEmulation: Spec.SPEC.touchpadMiddleEmulation.def
property bool touchpadNaturalScroll: Spec.SPEC.touchpadNaturalScroll.def
property real touchpadScrollFactor: Spec.SPEC.touchpadScrollFactor.def
property string touchpadScrollMethod: Spec.SPEC.touchpadScrollMethod.def
property bool touchpadTapAndDrag: Spec.SPEC.touchpadTapAndDrag.def
property bool touchpadTapToClick: Spec.SPEC.touchpadTapToClick.def
property string keyboardLayouts: Spec.SPEC.keyboardLayouts.def
property string keyboardVariants: Spec.SPEC.keyboardVariants.def
property string keyboardModel: Spec.SPEC.keyboardModel.def
property string keyboardOptions: Spec.SPEC.keyboardOptions.def
property string keyboardKeymapFile: Spec.SPEC.keyboardKeymapFile.def
property string keyboardTrackLayout: Spec.SPEC.keyboardTrackLayout.def
property int keyboardRepeatDelay: Spec.SPEC.keyboardRepeatDelay.def
property int keyboardRepeatRate: Spec.SPEC.keyboardRepeatRate.def
property bool keyboardNumlock: Spec.SPEC.keyboardNumlock.def
property int firstDayOfWeek: Spec.SPEC.firstDayOfWeek.def
property bool showWeekNumber: Spec.SPEC.showWeekNumber.def
property string calendarBackend: Spec.SPEC.calendarBackend.def
property string defaultTaskCalendarId: Spec.SPEC.defaultTaskCalendarId.def
property bool audioShowStreamDevices: Spec.SPEC.audioShowStreamDevices.def
property bool audioMono: Spec.SPEC.audioMono.def
property string clockFormat: Spec.SPEC.clockFormat.def
readonly property bool localeUses24Hour: {
const fmt = Qt.locale().timeFormat(Locale.ShortFormat).replace(/'[^']*'/g, "");
return !/[aA]/.test(fmt);
}
readonly property bool use24HourClock: clockFormat === "24h" ? true : (clockFormat === "12h" ? false : localeUses24Hour)
property bool showSeconds: Spec.SPEC.showSeconds.def
property bool padHours12Hour: Spec.SPEC.padHours12Hour.def
property bool useFahrenheit: Spec.SPEC.useFahrenheit.def
property string windSpeedUnit: Spec.SPEC.windSpeedUnit.def
property int animationDuration: Spec.SPEC.animationDuration.def
property bool syncComponentAnimationSpeeds: Spec.SPEC.syncComponentAnimationSpeeds.def
onSyncComponentAnimationSpeedsChanged: saveSettings()
property int popoutAnimationDuration: Spec.SPEC.popoutAnimationDuration.def
property int modalAnimationDuration: Spec.SPEC.modalAnimationDuration.def
property bool reduceMotion: Spec.SPEC.reduceMotion.def
onReduceMotionChanged: saveSettings()
property int springBounce: Spec.SPEC.springBounce.def
onSpringBounceChanged: saveSettings()
property bool enableRippleEffects: Spec.SPEC.enableRippleEffects.def
onEnableRippleEffectsChanged: saveSettings()
property bool scrollbarsEnabled: Spec.SPEC.scrollbarsEnabled.def
onScrollbarsEnabledChanged: saveSettings()
property int motionEffect: SettingsData.AnimationEffect.Standard
onMotionEffectChanged: saveSettings()
property bool m3ElevationEnabled: Spec.SPEC.m3ElevationEnabled.def
onM3ElevationEnabledChanged: saveSettings()
property int m3ElevationIntensity: Spec.SPEC.m3ElevationIntensity.def
onM3ElevationIntensityChanged: saveSettings()
property int m3ElevationOpacity: Spec.SPEC.m3ElevationOpacity.def
onM3ElevationOpacityChanged: saveSettings()
property string m3ElevationColorMode: Spec.SPEC.m3ElevationColorMode.def
onM3ElevationColorModeChanged: saveSettings()
property string m3ElevationLightDirection: Spec.SPEC.m3ElevationLightDirection.def
onM3ElevationLightDirectionChanged: saveSettings()
property string m3ElevationCustomColor: Spec.SPEC.m3ElevationCustomColor.def
onM3ElevationCustomColorChanged: saveSettings()
property bool modalElevationEnabled: Spec.SPEC.modalElevationEnabled.def
onModalElevationEnabledChanged: saveSettings()
property bool popoutElevationEnabled: Spec.SPEC.popoutElevationEnabled.def
onPopoutElevationEnabledChanged: saveSettings()
property bool barElevationEnabled: Spec.SPEC.barElevationEnabled.def
onBarElevationEnabledChanged: saveSettings()
property bool blurEnabled: Spec.SPEC.blurEnabled.def
onBlurEnabledChanged: saveSettings()
property int blurStrength: Spec.SPEC.blurStrength.def
property bool blurBorderSeeded: Spec.SPEC.blurBorderSeeded.def
onBlurBorderSeededChanged: saveSettings()
property bool blurForegroundLayers: Spec.SPEC.blurForegroundLayers.def
onBlurForegroundLayersChanged: saveSettings()
property real foregroundLayerTransparency: Spec.SPEC.foregroundLayerTransparency.def
property real blurLayerOutlineOpacity: Spec.SPEC.blurLayerOutlineOpacity.def
onBlurLayerOutlineOpacityChanged: saveSettings()
property bool blurBorderEnabled: Spec.SPEC.blurBorderEnabled.def
onBlurBorderEnabledChanged: saveSettings()
property string blurBorderColor: Spec.SPEC.blurBorderColor.def
onBlurBorderColorChanged: saveSettings()
property string blurBorderCustomColor: Spec.SPEC.blurBorderCustomColor.def
onBlurBorderCustomColorChanged: saveSettings()
property real blurBorderOpacity: Spec.SPEC.blurBorderOpacity.def
onBlurBorderOpacityChanged: saveSettings()
property bool focusRingEnabled: Spec.SPEC.focusRingEnabled.def
property real focusRingWidth: Spec.SPEC.focusRingWidth.def
property string focusRingColor: Spec.SPEC.focusRingColor.def
property string wallpaperFillMode: Spec.SPEC.wallpaperFillMode.def
property bool blurredWallpaperLayer: Spec.SPEC.blurredWallpaperLayer.def
property bool blurWallpaperOnOverview: Spec.SPEC.blurWallpaperOnOverview.def
property string wallpaperBackgroundColorMode: Spec.SPEC.wallpaperBackgroundColorMode.def
property string wallpaperBackgroundCustomColor: Spec.SPEC.wallpaperBackgroundCustomColor.def
readonly property color effectiveWallpaperBackgroundColor: wallpaperBackgroundColorFor(wallpaperBackgroundColorMode)
property bool nightModeExcludeFullscreen: Spec.SPEC.nightModeExcludeFullscreen.def
property var nightModeExcludedApps: Spec.SPEC.nightModeExcludedApps.def
function wallpaperBackgroundColorFor(mode) {
switch (mode) {
case "white":
return "#ffffff";
case "primary":
return Theme.primary;
case "surface":
return Theme.surfaceContainer;
case "custom":
return wallpaperBackgroundCustomColor;
default:
return "#000000";
}
}
property bool frameEnabled: Spec.SPEC.frameEnabled.def
onFrameEnabledChanged: {
saveSettings();
if (!_loading)
updateFrameCompositorLayout();
}
property real frameThickness: Spec.SPEC.frameThickness.def
onFrameThicknessChanged: saveSettings()
property int barInsetPaddingShared: Spec.SPEC.barInsetPaddingShared.def
onBarInsetPaddingSharedChanged: saveSettings()
property bool barInsetPaddingSyncAll: Spec.SPEC.barInsetPaddingSyncAll.def
onBarInsetPaddingSyncAllChanged: saveSettings()
property int frameBarInsetPadding: Spec.SPEC.frameBarInsetPadding.def
onFrameBarInsetPaddingChanged: saveSettings()
property real frameRounding: Spec.SPEC.frameRounding.def
onFrameRoundingChanged: saveSettings()
property var frameScreenPreferences: Spec.SPEC.frameScreenPreferences.def
onFrameScreenPreferencesChanged: {
saveSettings();
if (!_loading)
Qt.callLater(_reconcileConnectedFrameBarStyles);
}
property real frameBarSize: Spec.SPEC.frameBarSize.def
onFrameBarSizeChanged: saveSettings()
property bool frameShowOnOverview: Spec.SPEC.frameShowOnOverview.def
onFrameShowOnOverviewChanged: saveSettings()
property bool frameBlurEnabled: Spec.SPEC.frameBlurEnabled.def
onFrameBlurEnabledChanged: saveSettings()
property bool frameCloseGaps: Spec.SPEC.frameCloseGaps.def
onFrameCloseGapsChanged: saveSettings()
property string frameLauncherEmergeSide: Spec.SPEC.frameLauncherEmergeSide.def
onFrameLauncherEmergeSideChanged: saveSettings()
property bool frameLauncherArcExtender: Spec.SPEC.frameLauncherArcExtender.def
onFrameLauncherArcExtenderChanged: saveSettings()
property bool frameLauncherEdgeHover: Spec.SPEC.frameLauncherEdgeHover.def
onFrameLauncherEdgeHoverChanged: saveSettings()
readonly property string frameModalEmergeSide: frameLauncherEmergeSide === "top" ? "bottom" : "top"
property string frameMode: Spec.SPEC.frameMode.def
onFrameModeChanged: {
saveSettings();
if (!_loading && frameEnabled)
updateFrameCompositorLayout();
}
property var connectedFrameBarStyleBackups: Spec.SPEC.connectedFrameBarStyleBackups.def
onConnectedFrameBarStyleBackupsChanged: saveSettings()
readonly property bool connectedFrameModeActive: frameEnabled && frameMode === "connected"
onConnectedFrameModeActiveChanged: {
if (_loading)
return;
_reconcileConnectedFrameBarStyles();
}
readonly property var frameBarConfig: barConfigs.find(bc => bc.enabled !== false && !isIslandBarConfig(bc)) ?? null
readonly property real frameSurfaceOpacity: barTransparency(frameBarConfig)
readonly property color frameSurfaceBase: barSurfaceColor(frameBarConfig)
property string systemTrayIconTintMode: Spec.SPEC.systemTrayIconTintMode.def
property int systemTrayIconTintSaturation: Spec.SPEC.systemTrayIconTintSaturation.def
property int systemTrayIconTintStrength: Spec.SPEC.systemTrayIconTintStrength.def
property int controlCenterColumns: Spec.SPEC.controlCenterColumns.def
property real controlCenterIconScale: Spec.SPEC.controlCenterIconScale.def
property string controlCenterFooterPosition: Spec.SPEC.controlCenterFooterPosition.def
property var controlCenterWidgets: Spec.SPEC.controlCenterWidgets.def
property var workspaceNameIcons: Spec.SPEC.workspaceNameIcons.def
property bool scrollTitleEnabled: Spec.SPEC.scrollTitleEnabled.def
property bool audioVisualizerEnabled: Spec.SPEC.audioVisualizerEnabled.def
property int audioWheelScrollAmount: Spec.SPEC.audioWheelScrollAmount.def
property bool bluetoothMprisEnabled: Spec.SPEC.bluetoothMprisEnabled.def
property var mediaExcludePlayers: Spec.SPEC.mediaExcludePlayers.def
property var mediaLyricsProviders: Spec.SPEC.mediaLyricsProviders.def
property var appIdSubstitutions: Spec.SPEC.appIdSubstitutions.def
property string centeringMode: Spec.SPEC.centeringMode.def
property string clockDateFormat: Spec.SPEC.clockDateFormat.def
property string lockDateFormat: Spec.SPEC.lockDateFormat.def
property bool greeterRememberLastSession: Spec.SPEC.greeterRememberLastSession.def
property bool greeterRememberLastUser: Spec.SPEC.greeterRememberLastUser.def
property bool greeterAutoLogin: Spec.SPEC.greeterAutoLogin.def
property bool greeterEnableFprint: Spec.SPEC.greeterEnableFprint.def
property bool greeterEnableU2f: Spec.SPEC.greeterEnableU2f.def
property string browserPickerViewMode: Spec.SPEC.browserPickerViewMode.def
property string appPickerViewMode: Spec.SPEC.appPickerViewMode.def
property bool sortAppsAlphabetically: Spec.SPEC.sortAppsAlphabetically.def
property int appLauncherGridColumns: Spec.SPEC.appLauncherGridColumns.def
property bool closeNiriOverviewOnWindowFocus: Spec.SPEC.closeNiriOverviewOnWindowFocus.def
property bool rememberLastQuery: Spec.SPEC.rememberLastQuery.def
property bool launcherHistoryEnabled: Spec.SPEC.launcherHistoryEnabled.def
property bool rememberLastMode: Spec.SPEC.rememberLastMode.def
property var spotlightSectionViewModes: Spec.SPEC.spotlightSectionViewModes.def
onSpotlightSectionViewModesChanged: saveSettings()
property var appDrawerSectionViewModes: Spec.SPEC.appDrawerSectionViewModes.def
onAppDrawerSectionViewModesChanged: saveSettings()
property bool niriOverviewOverlayEnabled: Spec.SPEC.niriOverviewOverlayEnabled.def
property string niriOverviewLauncherStyle: Spec.SPEC.niriOverviewLauncherStyle.def
property string dankLauncherV2Size: Spec.SPEC.dankLauncherV2Size.def
property bool dankLauncherV2ShowSourceBadges: Spec.SPEC.dankLauncherV2ShowSourceBadges.def
property bool dankLauncherV2BorderEnabled: Spec.SPEC.dankLauncherV2BorderEnabled.def
property int dankLauncherV2BorderThickness: Spec.SPEC.dankLauncherV2BorderThickness.def
property string dankLauncherV2BorderColor: Spec.SPEC.dankLauncherV2BorderColor.def
property bool dankLauncherV2ShowFooter: Spec.SPEC.dankLauncherV2ShowFooter.def
property bool dankLauncherV2UnloadOnClose: Spec.SPEC.dankLauncherV2UnloadOnClose.def
property bool dankLauncherV2IncludeFilesInAll: Spec.SPEC.dankLauncherV2IncludeFilesInAll.def
property bool dankLauncherV2IncludeFoldersInAll: Spec.SPEC.dankLauncherV2IncludeFoldersInAll.def
property bool launcherUseOverlayLayer: Spec.SPEC.launcherUseOverlayLayer.def
property string launcherStyle: Spec.SPEC.launcherStyle.def
property string avatarRing: Spec.SPEC.avatarRing.def
property bool spotlightBarShowModeChips: Spec.SPEC.spotlightBarShowModeChips.def
property bool keybindsFloatingWindow: Spec.SPEC.keybindsFloatingWindow.def
onKeybindsFloatingWindowChanged: saveSettings()
property string _legacyWeatherLocation: "New York, NY"
property string _legacyWeatherCoordinates: "40.7128,-74.0060"
property string _legacyVpnLastConnected: ""
readonly property string weatherLocation: SessionData.weatherLocation
readonly property string weatherCoordinates: SessionData.weatherCoordinates
property bool useAutoLocation: Spec.SPEC.useAutoLocation.def
property bool weatherEnabled: Spec.SPEC.weatherEnabled.def
readonly property var _dashTabsDefault: [
{
"id": "overview",
"enabled": true
},
{
"id": "media",
"enabled": true
},
{
"id": "wallpaper",
"enabled": true
},
{
"id": "weather",
"enabled": true
},
{
"id": "wellbeing",
"enabled": true
},
{
"id": "notifications",
"enabled": false
}
]
property string dashTabPosition: Spec.SPEC.dashTabPosition.def
property bool dashTabsEvenlySpaced: Spec.SPEC.dashTabsEvenlySpaced.def
property var dashTabs: Spec.SPEC.dashTabs.def
onDashTabsChanged: saveSettings()
readonly property var _dashCardsDefault: [
{
"id": "clock",
"w": 2,
"h": 1
},
{
"id": "weather",
"w": 1,
"h": 1
},
{
"id": "notifications",
"w": 3,
"h": 5
},
{
"id": "calendar",
"w": 3,
"h": 3
},
{
"id": "media",
"w": 3,
"h": 1
}
]
property var dashCards: Spec.SPEC.dashCards.def
onDashCardsChanged: saveSettings()
property var dashOptions: Spec.SPEC.dashOptions.def
onDashOptionsChanged: saveSettings()
property bool wellbeingEnabled: Spec.SPEC.wellbeingEnabled.def
property int wellbeingDailyLimit: Spec.SPEC.wellbeingDailyLimit.def
property var wellbeingAppLimits: Spec.SPEC.wellbeingAppLimits.def
function getDashTabs() {
const stored = Array.isArray(dashTabs) ? dashTabs : [];
const result = [];
const seen = {};
for (var i = 0; i < stored.length; i++) {
const id = stored[i] && stored[i].id;
if (typeof id !== "string" || id === "" || seen[id])
continue;
seen[id] = true;
result.push({
"id": id,
"enabled": stored[i].enabled !== false
});
}
for (var j = 0; j < _dashTabsDefault.length; j++) {
if (seen[_dashTabsDefault[j].id])
continue;
result.push({
"id": _dashTabsDefault[j].id,
"enabled": _dashTabsDefault[j].enabled
});
}
return result;
}
function setDashTabOrder(ids) {
const current = getDashTabs();
const ordered = [];
for (var i = 0; i < ids.length; i++) {
const existing = current.find(t => t.id === ids[i]);
if (existing)
ordered.push(existing);
}
for (var j = 0; j < current.length; j++) {
if (ids.indexOf(current[j].id) < 0)
ordered.push(current[j]);
}
dashTabs = ordered;
}
function setDashTabEnabled(id, on) {
const current = getDashTabs();
if (!current.some(t => t.id === id))
current.push({
"id": id,
"enabled": true
});
dashTabs = current.map(t => t.id === id ? {
"id": t.id,
"enabled": on
} : t);
}
function resetDashTabs() {
dashTabs = _dashTabsDefault.map(t => ({
"id": t.id,
"enabled": t.enabled
}));
}
function resetDashCards() {
dashCards = _dashCardsDefault.map(c => ({
"id": c.id,
"w": c.w,
"h": c.h
}));
const options = Object.assign({}, dashOptions);
delete options.overview;
dashOptions = options;
}
property string networkPreference: Spec.SPEC.networkPreference.def
property string iconThemeDark: Spec.SPEC.iconThemeDark.def
property string iconThemeLight: Spec.SPEC.iconThemeLight.def
property bool iconThemePerMode: Spec.SPEC.iconThemePerMode.def
readonly property string iconTheme: resolveIconTheme()
property var availableIconThemes: Spec.SPEC.availableIconThemes.def
property string systemDefaultIconTheme: Spec.SPEC.systemDefaultIconTheme.def
property var cursorSettings: Spec.SPEC.cursorSettings.def
property var availableCursorThemes: Spec.SPEC.availableCursorThemes.def
property string systemDefaultCursorTheme: Spec.SPEC.systemDefaultCursorTheme.def
property string fontFamily: Spec.SPEC.fontFamily.def
property string monoFontFamily: Spec.SPEC.monoFontFamily.def
property string displayFontFamily: Spec.SPEC.displayFontFamily.def
property int fontWeight: Font.Normal
property real fontScale: Spec.SPEC.fontScale.def
property int textRenderType: SettingsData.TextRenderType.Qt
property int textRenderQuality: SettingsData.TextRenderQuality.Default
property bool notepadUseMonospace: Spec.SPEC.notepadUseMonospace.def
property string notepadFontFamily: Spec.SPEC.notepadFontFamily.def
property real notepadFontSize: Spec.SPEC.notepadFontSize.def
property real notificationSummaryFontSize: Spec.SPEC.notificationSummaryFontSize.def
property real notificationBodyFontSize: Spec.SPEC.notificationBodyFontSize.def
property bool notepadShowLineNumbers: Spec.SPEC.notepadShowLineNumbers.def
property bool notepadAutoSave: Spec.SPEC.notepadAutoSave.def
property string notepadSlideoutSide: Spec.SPEC.notepadSlideoutSide.def
property string notepadDefaultMode: Spec.SPEC.notepadDefaultMode.def
property real notepadTransparencyOverride: Spec.SPEC.notepadTransparencyOverride.def
property real notepadLastCustomTransparency: Spec.SPEC.notepadLastCustomTransparency.def
property bool notepadUseCompositorGap: Spec.SPEC.notepadUseCompositorGap.def
property int notepadEdgeGap: Spec.SPEC.notepadEdgeGap.def
property string activeCompositor: ""
// Compositor layout gap when enabled and available, else the manual value.
readonly property int notepadEffectiveEdgeGap: {
if (notepadUseCompositorGap) {
var g = -1;
switch (activeCompositor) {
case "niri":
g = niriLayoutGapsOverride;
break;
case "hyprland":
g = hyprlandLayoutGapsOverride;
break;
case "mango":
g = mangoLayoutGapsOverride;
break;
}
if (g >= 0)
return g;
}
return Math.max(0, notepadEdgeGap);
}
onNotepadUseMonospaceChanged: saveSettings()
onNotepadFontFamilyChanged: saveSettings()
onNotepadFontSizeChanged: saveSettings()
onNotepadShowLineNumbersChanged: saveSettings()
onNotepadAutoSaveChanged: saveSettings()
onNotepadSlideoutSideChanged: saveSettings()
onNotepadDefaultModeChanged: saveSettings()
onNotepadUseCompositorGapChanged: saveSettings()
onNotepadEdgeGapChanged: saveSettings()
// onCenteringModeChanged: saveSettings()
onNotepadTransparencyOverrideChanged: {
if (notepadTransparencyOverride > 0) {
notepadLastCustomTransparency = notepadTransparencyOverride;
}
saveSettings();
}
onNotepadLastCustomTransparencyChanged: saveSettings()
property bool soundsEnabled: Spec.SPEC.soundsEnabled.def
property bool useSystemSoundTheme: Spec.SPEC.useSystemSoundTheme.def
property bool soundNewNotification: Spec.SPEC.soundNewNotification.def
property bool soundVolumeChanged: Spec.SPEC.soundVolumeChanged.def
property bool soundPluggedIn: Spec.SPEC.soundPluggedIn.def
property bool soundLogin: Spec.SPEC.soundLogin.def
property bool muteSoundsWhenMediaPlaying: Spec.SPEC.muteSoundsWhenMediaPlaying.def
property int acMonitorTimeout: Spec.SPEC.acMonitorTimeout.def
property int acLockTimeout: Spec.SPEC.acLockTimeout.def
property int acSuspendTimeout: Spec.SPEC.acSuspendTimeout.def
property int acSuspendBehavior: SettingsData.SuspendBehavior.Suspend
property string acProfileName: Spec.SPEC.acProfileName.def
property int acPostLockMonitorTimeout: Spec.SPEC.acPostLockMonitorTimeout.def
property int batteryMonitorTimeout: Spec.SPEC.batteryMonitorTimeout.def
property int batteryLockTimeout: Spec.SPEC.batteryLockTimeout.def
property int batterySuspendTimeout: Spec.SPEC.batterySuspendTimeout.def
property int batterySuspendBehavior: SettingsData.SuspendBehavior.Suspend
property string batteryProfileName: Spec.SPEC.batteryProfileName.def
property int batteryPostLockMonitorTimeout: Spec.SPEC.batteryPostLockMonitorTimeout.def
property int batteryChargeLimit: Spec.SPEC.batteryChargeLimit.def
property bool batteryNotifyChargeLimit: Spec.SPEC.batteryNotifyChargeLimit.def
property int batteryCriticalThreshold: Spec.SPEC.batteryCriticalThreshold.def
property bool batteryCriticalAnimation: Spec.SPEC.batteryCriticalAnimation.def
property bool batteryNotifyCritical: Spec.SPEC.batteryNotifyCritical.def
property int batteryLowThreshold: Spec.SPEC.batteryLowThreshold.def
property bool batteryNotifyLow: Spec.SPEC.batteryNotifyLow.def
property int batteryChargeLimitNotificationType: Spec.SPEC.batteryChargeLimitNotificationType.def
property int batteryLowNotificationType: Spec.SPEC.batteryLowNotificationType.def
property int batteryCriticalNotificationType: Spec.SPEC.batteryCriticalNotificationType.def
property bool batteryAutoPowerSaver: Spec.SPEC.batteryAutoPowerSaver.def
property bool lowerDisplayRefreshRateOnBattery: Spec.SPEC.lowerDisplayRefreshRateOnBattery.def
property bool lockBeforeSuspend: Spec.SPEC.lockBeforeSuspend.def
property bool loginctlLockIntegration: Spec.SPEC.loginctlLockIntegration.def
property bool fadeToLockEnabled: Spec.SPEC.fadeToLockEnabled.def
property int fadeToLockGracePeriod: Spec.SPEC.fadeToLockGracePeriod.def
property bool fadeToDpmsEnabled: Spec.SPEC.fadeToDpmsEnabled.def
property int fadeToDpmsGracePeriod: Spec.SPEC.fadeToDpmsGracePeriod.def
property string launchPrefix: Spec.SPEC.launchPrefix.def
property bool syncModeWithPortal: Spec.SPEC.syncModeWithPortal.def
property bool terminalsAlwaysDark: Spec.SPEC.terminalsAlwaysDark.def
property string muxType: Spec.SPEC.muxType.def
property bool muxUseCustomCommand: Spec.SPEC.muxUseCustomCommand.def
property string muxCustomCommand: Spec.SPEC.muxCustomCommand.def
property string muxSessionFilter: Spec.SPEC.muxSessionFilter.def
property bool runDmsMatugenTemplates: Spec.SPEC.runDmsMatugenTemplates.def
property bool matugenTemplateGtk: Spec.SPEC.matugenTemplateGtk.def
property bool matugenTemplateNiri: Spec.SPEC.matugenTemplateNiri.def
property bool matugenTemplateHyprland: Spec.SPEC.matugenTemplateHyprland.def
property bool matugenTemplateMangowc: Spec.SPEC.matugenTemplateMangowc.def
property bool matugenTemplateQt5ct: Spec.SPEC.matugenTemplateQt5ct.def
property bool matugenTemplateQt6ct: Spec.SPEC.matugenTemplateQt6ct.def
property bool matugenTemplateFcitx5: Spec.SPEC.matugenTemplateFcitx5.def
property bool matugenTemplateQtengine: Spec.SPEC.matugenTemplateQtengine.def
property bool matugenTemplateFirefox: Spec.SPEC.matugenTemplateFirefox.def
property bool matugenTemplatePywalfox: Spec.SPEC.matugenTemplatePywalfox.def
property bool matugenTemplateZenBrowser: Spec.SPEC.matugenTemplateZenBrowser.def
property bool matugenTemplateVesktop: Spec.SPEC.matugenTemplateVesktop.def
property bool matugenTemplateVencord: Spec.SPEC.matugenTemplateVencord.def
property bool matugenTemplateEquibop: Spec.SPEC.matugenTemplateEquibop.def
property bool matugenTemplateGhostty: Spec.SPEC.matugenTemplateGhostty.def
property bool matugenTemplateKitty: Spec.SPEC.matugenTemplateKitty.def
property bool matugenTemplateFoot: Spec.SPEC.matugenTemplateFoot.def
property bool matugenTemplateNeovim: Spec.SPEC.matugenTemplateNeovim.def
property bool matugenTemplateAlacritty: Spec.SPEC.matugenTemplateAlacritty.def
property bool matugenTemplateWezterm: Spec.SPEC.matugenTemplateWezterm.def
property bool matugenTemplateDgop: Spec.SPEC.matugenTemplateDgop.def
property bool matugenTemplateKcolorscheme: Spec.SPEC.matugenTemplateKcolorscheme.def
property bool matugenTemplateVscode: Spec.SPEC.matugenTemplateVscode.def
property bool matugenTemplateEmacs: Spec.SPEC.matugenTemplateEmacs.def
property bool matugenTemplateZed: Spec.SPEC.matugenTemplateZed.def
property var matugenTemplateNeovimSettings: Spec.SPEC.matugenTemplateNeovimSettings.def
property bool matugenTemplateNeovimSetBackground: Spec.SPEC.matugenTemplateNeovimSetBackground.def
property bool notificationOverlayEnabled: Spec.SPEC.notificationOverlayEnabled.def
property bool notificationPopupShadowEnabled: Spec.SPEC.notificationPopupShadowEnabled.def
property bool notificationPopupPrivacyMode: Spec.SPEC.notificationPopupPrivacyMode.def
property bool notificationPopupBodyInvokesAction: Spec.SPEC.notificationPopupBodyInvokesAction.def
property bool notificationForegroundLayers: Spec.SPEC.notificationForegroundLayers.def
property int overviewRows: Spec.SPEC.overviewRows.def
property int overviewColumns: Spec.SPEC.overviewColumns.def
property real overviewScale: Spec.SPEC.overviewScale.def
property bool modalDarkenBackground: Spec.SPEC.modalDarkenBackground.def
property bool lockScreenShowPowerActions: Spec.SPEC.lockScreenShowPowerActions.def
property bool lockScreenShowProfileImage: Spec.SPEC.lockScreenShowProfileImage.def
property bool lockScreenShowWeather: Spec.SPEC.lockScreenShowWeather.def
property bool lockScreenPowerOffMonitorsOnLock: Spec.SPEC.lockScreenPowerOffMonitorsOnLock.def
property bool lockAtStartup: Spec.SPEC.lockAtStartup.def
property bool enableFprint: Spec.SPEC.enableFprint.def
onEnableFprintChanged: {
if (enableFprint)
refreshAuthAvailability();
}
property int maxFprintTries: Spec.SPEC.maxFprintTries.def
readonly property bool fprintdAvailable: Processes.fprintdAvailable
readonly property bool lockFingerprintCanEnable: Processes.lockFingerprintCanEnable
readonly property bool lockFingerprintReady: Processes.lockFingerprintReady
readonly property string lockFingerprintReason: Processes.lockFingerprintReason
readonly property bool greeterFingerprintCanEnable: Processes.greeterFingerprintCanEnable
readonly property bool greeterFingerprintReady: Processes.greeterFingerprintReady
readonly property string greeterFingerprintReason: Processes.greeterFingerprintReason
readonly property string greeterFingerprintSource: Processes.greeterFingerprintSource
property bool enableU2f: Spec.SPEC.enableU2f.def
onEnableU2fChanged: {
if (enableU2f)
refreshAuthAvailability();
}
property string u2fMode: Spec.SPEC.u2fMode.def
readonly property bool u2fAvailable: Processes.u2fAvailable
readonly property bool lockU2fCanEnable: Processes.lockU2fCanEnable
readonly property bool lockU2fReady: Processes.lockU2fReady
readonly property string lockU2fReason: Processes.lockU2fReason
readonly property bool greeterU2fCanEnable: Processes.greeterU2fCanEnable
readonly property bool greeterU2fReady: Processes.greeterU2fReady
readonly property string greeterU2fReason: Processes.greeterU2fReason
readonly property string greeterU2fSource: Processes.greeterU2fSource
property string lockPamPath: Spec.SPEC.lockPamPath.def
property bool lockPamInlineFprint: Spec.SPEC.lockPamInlineFprint.def
property bool lockPamInlineU2f: Spec.SPEC.lockPamInlineU2f.def
property bool lockPamExternallyManaged: Spec.SPEC.lockPamExternallyManaged.def
property string lockU2fPamPath: Spec.SPEC.lockU2fPamPath.def
property string lockScreenSecurityKeyShortcut: Spec.SPEC.lockScreenSecurityKeyShortcut.def
property bool lockScreenSecurityKeyShortcutEnabled: Spec.SPEC.lockScreenSecurityKeyShortcutEnabled.def
property bool greeterPamExternallyManaged: Spec.SPEC.greeterPamExternallyManaged.def
property string lockScreenInactiveColor: Spec.SPEC.lockScreenInactiveColor.def
property bool lockScreenVideoEnabled: Spec.SPEC.lockScreenVideoEnabled.def
property string lockScreenVideoPath: Spec.SPEC.lockScreenVideoPath.def
property bool lockScreenVideoCycling: Spec.SPEC.lockScreenVideoCycling.def
property string lockScreenWallpaperPath: Spec.SPEC.lockScreenWallpaperPath.def
property string lockScreenWallpaperFillMode: Spec.SPEC.lockScreenWallpaperFillMode.def
property string lockScreenFontFamily: Spec.SPEC.lockScreenFontFamily.def
property int notificationTimeoutLow: Spec.SPEC.notificationTimeoutLow.def
property int notificationTimeoutNormal: Spec.SPEC.notificationTimeoutNormal.def
property int notificationTimeoutCritical: Spec.SPEC.notificationTimeoutCritical.def
property bool notificationIgnoreAppTimeout: Spec.SPEC.notificationIgnoreAppTimeout.def
property bool notificationCompactMode: Spec.SPEC.notificationCompactMode.def
property bool notificationShowTimeoutBar: Spec.SPEC.notificationShowTimeoutBar.def
property bool notificationDedupeEnabled: Spec.SPEC.notificationDedupeEnabled.def
property int notificationPopupPosition: SettingsData.Position.Top
property int notificationAnimationDuration: Spec.SPEC.notificationAnimationDuration.def
property bool notificationHistoryEnabled: Spec.SPEC.notificationHistoryEnabled.def
property int notificationHistoryMaxCount: Spec.SPEC.notificationHistoryMaxCount.def
property int notificationHistoryMaxAgeDays: Spec.SPEC.notificationHistoryMaxAgeDays.def
property bool notificationHistorySaveLow: Spec.SPEC.notificationHistorySaveLow.def
property bool notificationHistorySaveNormal: Spec.SPEC.notificationHistorySaveNormal.def
property bool notificationHistorySaveCritical: Spec.SPEC.notificationHistorySaveCritical.def
property var notificationRules: Spec.SPEC.notificationRules.def
property bool notificationDndAllowCritical: Spec.SPEC.notificationDndAllowCritical.def
property bool notificationDndWhileScreenSharing: Spec.SPEC.notificationDndWhileScreenSharing.def
property bool notificationFocusedMonitor: Spec.SPEC.notificationFocusedMonitor.def
readonly property var islandBarConfigs: {
barConfigs;
return (barConfigs || []).filter(cfg => isIslandBarConfig(cfg));
}
readonly property bool dankIslandEnabled: (barConfigs || []).some(cfg => (cfg.enabled ?? false) && hostsIsland(cfg))
readonly property var enabledIslandBarConfigs: (barConfigs || []).filter(cfg => cfg?.island === true && (cfg.enabled ?? false))
// Session-only: which bar, island or dot last-used shared shortcuts follow on each screen.
property var lastUsedBarByScreen: ({})
// One slot per edge; a dot floats, so it never takes one.
readonly property int edgeBarConfigCount: (barConfigs || []).filter(cfg => cfg && !isDotBarConfig(cfg)).length
readonly property var dotBarConfig: (barConfigs || []).find(cfg => isDotBarConfig(cfg)) ?? null
readonly property var islandDefaults: WidgetDefaults.ISLAND_DEFAULTS
readonly property var islandWidgetDefaults: WidgetDefaults.DEFAULTS.island
function islandSettings(bc) {
if (!bc)
return {};
return isIslandBarConfig(bc) ? bc : (islandWidgetEntry(bc) ?? {});
}
function islandDefaultsFor(bc) {
return isIslandBarConfig(bc) ? islandDefaults : islandWidgetDefaults;
}
function _isIslandEntry(entry) {
return (typeof entry === "string" ? entry : entry?.id) === "island";
}
// A widget toggled off in the list is still placed, but it owns no screen.
function _islandWidgetLocation(cfg, enabledOnly) {
for (const sectionId of ["left", "center", "right"]) {
const list = cfg?.[sectionId + "Widgets"] ?? [];
const index = list.findIndex(entry => _isIslandEntry(entry));
if (index < 0)
continue;
if (enabledOnly && list[index]?.enabled === false)
return null;
return {
sectionId,
index
};
}
return null;
}
function islandWidgetSection(cfg) {
return _islandWidgetLocation(cfg, true)?.sectionId ?? "";
}
// Mutates cfg in place, so callers pass a clone; a bare string entry is upgraded to carry settings.
function _writableIslandSettings(cfg) {
if (isIslandBarConfig(cfg))
return cfg;
const location = _islandWidgetLocation(cfg);
if (!location)
return {};
const list = cfg[location.sectionId + "Widgets"];
if (typeof list[location.index] === "string")
list[location.index] = {
"id": "island",
"enabled": true
};
return list[location.index];
}
function setIslandSettings(barId, patch) {
const config = getBarConfig(barId);
if (!config)
return;
if ("islandRouteDash" in patch) {
patch = Object.assign({}, patch);
const settings = islandSettings(config);
for (const activity of islandDashActivities) {
const key = islandRouteKey(activity);
if (patch[key] === undefined && (settings[key] === undefined || settings[key] === null))
patch[key] = islandSetting(config, key);
}
}
if (isIslandBarConfig(config)) {
updateBarConfig(barId, patch);
return;
}
const location = _islandWidgetLocation(config);
if (!location)
return;
updateBarWidget(barId, location.sectionId, location.index, patch);
}
function islandSetting(bc, key) {
const value = islandSettings(bc)[key];
if (value !== undefined && value !== null)
return value;
switch (key) {
case "islandRouteMedia":
case "islandRouteWeather":
case "islandRouteWallpaper":
return islandSetting(bc, "islandRouteDash");
}
return islandDefaultsFor(bc)[key];
}
function islandSatelliteTransparency(bc) {
if (islandSetting(bc, "islandSatelliteFollowInterfaceStyle"))
return barTransparency(bc);
return islandSetting(bc, "islandSatelliteTransparency");
}
function islandLevelDisplay(bc, key) {
const value = islandSetting(bc, key);
return value === "icon" || value === "percentage" || value === "both" ? value : "both";
}
function islandClockDisplay(bc) {
const value = islandSetting(bc, "islandHomeClockDisplay");
return value === "time" || value === "date" || value === "both" ? value : "both";
}
function islandHomeStatusContent(bc) {
const value = islandSetting(bc, "islandHomeStatusContent");
return value === "battery" || value === "connectivity" ? value : "battery";
}
function islandEdge(bc) {
return positionToSide(bc?.position ?? SettingsData.Position.Top) || "top";
}
function islandVertical(bc) {
const edge = islandEdge(bc);
return edge === "left" || edge === "right";
}
function islandStripThickness(bc) {
return LayoutResolver.islandThickness(islandSettings(bc), islandDefaultsFor(bc));
}
readonly property var _islandHomeGroupIds: ["workspaces", "media", "clock", "weather", "status", "volume", "brightness", "notifications", "privacy"]
readonly property var _islandHomeLayoutDefault: [
{
"id": "workspaces",
"enabled": false
},
{
"id": "media",
"enabled": true
},
{
"id": "clock",
"enabled": true
},
{
"id": "weather",
"enabled": false
},
{
"id": "status",
"enabled": false
},
{
"id": "volume",
"enabled": false
},
{
"id": "brightness",
"enabled": false
},
{
"id": "notifications",
"enabled": true
},
{
"id": "privacy",
"enabled": true
}
]
function getIslandHomeLayout(bc) {
const layout = islandSettings(bc).islandHomeLayout;
const stored = Array.isArray(layout) ? layout : [];
const result = [];
const seen = {};
for (const entry of stored) {
const id = entry && entry.id;
if (_islandHomeGroupIds.indexOf(id) < 0 || seen[id])
continue;
seen[id] = true;
result.push({
"id": id,
"enabled": id === "clock" || entry.enabled !== false
});
}
for (const fallback of _islandHomeLayoutDefault) {
if (!seen[fallback.id])
result.push({
"id": fallback.id,
"enabled": fallback.enabled
});
}
return result;
}
function islandHomeGroupEnabled(bc, id) {
const entry = getIslandHomeLayout(bc).find(g => g.id === id);
return entry ? entry.enabled : false;
}
function setIslandHomeLayoutOrder(barId, ids) {
const current = getIslandHomeLayout(getBarConfig(barId));
const ordered = ids.map(id => current.find(g => g.id === id)).filter(g => g);
for (const entry of current) {
if (ids.indexOf(entry.id) < 0)
ordered.push(entry);
}
setIslandSettings(barId, {
islandHomeLayout: ordered
});
}
function setIslandHomeGroupEnabled(barId, id, on) {
if (id === "clock")
return;
setIslandSettings(barId, {
islandHomeLayout: getIslandHomeLayout(getBarConfig(barId)).map(g => g.id === id ? {
"id": g.id,
"enabled": on
} : g)
});
}
property bool osdAlwaysShowValue: Spec.SPEC.osdAlwaysShowValue.def
property int osdPosition: SettingsData.Position.BottomCenter
property var osdPositionOverrides: Spec.SPEC.osdPositionOverrides.def
property bool osdVolumeEnabled: Spec.SPEC.osdVolumeEnabled.def
property bool osdMediaVolumeEnabled: Spec.SPEC.osdMediaVolumeEnabled.def
property bool osdMediaPlaybackEnabled: Spec.SPEC.osdMediaPlaybackEnabled.def
property bool osdBrightnessEnabled: Spec.SPEC.osdBrightnessEnabled.def
property bool osdIdleInhibitorEnabled: Spec.SPEC.osdIdleInhibitorEnabled.def
property bool osdMicMuteEnabled: Spec.SPEC.osdMicMuteEnabled.def
property bool osdMicVolumeEnabled: Spec.SPEC.osdMicVolumeEnabled.def
property bool osdCapsLockEnabled: Spec.SPEC.osdCapsLockEnabled.def
property bool osdPowerProfileEnabled: Spec.SPEC.osdPowerProfileEnabled.def
property bool osdAudioOutputEnabled: Spec.SPEC.osdAudioOutputEnabled.def
property bool osdWorkspaceEnabled: Spec.SPEC.osdWorkspaceEnabled.def
property bool powerActionConfirm: Spec.SPEC.powerActionConfirm.def
property real powerActionHoldDuration: Spec.SPEC.powerActionHoldDuration.def
property var powerMenuActions: Spec.SPEC.powerMenuActions.def
property string powerMenuDefaultAction: Spec.SPEC.powerMenuDefaultAction.def
property bool powerMenuGridLayout: Spec.SPEC.powerMenuGridLayout.def
property string customPowerActionLock: Spec.SPEC.customPowerActionLock.def
property string customPowerActionLogout: Spec.SPEC.customPowerActionLogout.def
property string customPowerActionSuspend: Spec.SPEC.customPowerActionSuspend.def
property string customPowerActionHibernate: Spec.SPEC.customPowerActionHibernate.def
property string customPowerActionReboot: Spec.SPEC.customPowerActionReboot.def
property string customPowerActionPowerOff: Spec.SPEC.customPowerActionPowerOff.def
property var customPowerButtons: Spec.SPEC.customPowerButtons.def
property var powerMenuBootEntries: Spec.SPEC.powerMenuBootEntries.def
property bool updaterCheckOnStart: Spec.SPEC.updaterCheckOnStart.def
property bool updaterUseCustomCommand: Spec.SPEC.updaterUseCustomCommand.def
property string updaterCustomCommand: Spec.SPEC.updaterCustomCommand.def
property string updaterTerminalAdditionalParams: Spec.SPEC.updaterTerminalAdditionalParams.def
property int updaterIntervalSeconds: Spec.SPEC.updaterIntervalSeconds.def
property bool updaterNotify: Spec.SPEC.updaterNotify.def
property bool updaterPauseOnBattery: Spec.SPEC.updaterPauseOnBattery.def
property int updaterNotifyMinSeconds: Spec.SPEC.updaterNotifyMinSeconds.def
property bool updaterIncludeFlatpak: Spec.SPEC.updaterIncludeFlatpak.def
property bool updaterAllowAUR: Spec.SPEC.updaterAllowAUR.def
property bool updaterReopenAfterUpgrade: Spec.SPEC.updaterReopenAfterUpgrade.def
property bool updaterUpgradeInWindow: Spec.SPEC.updaterUpgradeInWindow.def
property var updaterIgnoredPackages: Spec.SPEC.updaterIgnoredPackages.def
property string displayNameMode: Spec.SPEC.displayNameMode.def
onDisplayNameModeChanged: {
if (!_loading)
_reconcileConnectedFrameBarStyles();
}
property var screenPreferences: Spec.SPEC.screenPreferences.def
property var showOnLastDisplay: Spec.SPEC.showOnLastDisplay.def
property var displayProfiles: Spec.SPEC.displayProfiles.def
property var displayPreviousRefreshModes: Spec.SPEC.displayPreviousRefreshModes.def
property bool displayProfileAutoSelect: Spec.SPEC.displayProfileAutoSelect.def
property bool displayShowDisconnected: Spec.SPEC.displayShowDisconnected.def
property bool displaySnapToEdge: Spec.SPEC.displaySnapToEdge.def
property var barIpcRevealStates: ({})
property var dockConfigs: Spec.SPEC.dockConfigs.def
property var barConfigs: Spec.SPEC.barConfigs.def
property var desktopWidgetInstances: Spec.SPEC.desktopWidgetInstances.def
property var desktopWidgetGroups: Spec.SPEC.desktopWidgetGroups.def
property string desktopContextMenu: Spec.SPEC.desktopContextMenu.def
property var lockScreenWidgetInstances: Spec.SPEC.lockScreenWidgetInstances.def
property var greeterWidgetInstances: Spec.SPEC.greeterWidgetInstances.def
property bool greeterFollowLockScreen: Spec.SPEC.greeterFollowLockScreen.def
readonly property var widgetInstanceListKeys: ["desktopWidgetInstances", "lockScreenWidgetInstances", "greeterWidgetInstances"]
// Released greeters still read these three shared keys, so they follow the lock widgets.
onLockScreenWidgetInstancesChanged: {
const status = widgetInstanceOfType("lockScreenWidgetInstances", "lockStatus");
const auth = widgetInstanceOfType("lockScreenWidgetInstances", "lockAuth");
const power = widgetInstanceOfType("lockScreenWidgetInstances", "lockPower");
const mirror = (key, value) => {
if (root[key] !== value)
set(key, value);
};
mirror("lockScreenShowWeather", !!status && status.enabled !== false && (status.config?.showWeather ?? true));
mirror("lockScreenShowProfileImage", !!auth && (auth.config?.showProfileImage ?? true));
mirror("lockScreenShowPowerActions", !!power && power.enabled !== false);
syncGreeterWidgets();
}
function widgetInstanceOfType(listKey, widgetType) {
return (root[listKey] || []).find(inst => inst.widgetType === widgetType) ?? null;
}
function lockWidgetInstance(widgetType) {
return widgetInstanceOfType("lockScreenWidgetInstances", widgetType);
}
function syncGreeterWidgets() {
if (!greeterFollowLockScreen)
return;
const next = Spec.greeterWidgetsFromLock(lockScreenWidgetInstances, greeterWidgetInstances);
if (JSON.stringify(next) === JSON.stringify(greeterWidgetInstances))
return;
set("greeterWidgetInstances", next);
}
function setGreeterFollowLockScreen(follow) {
if (follow === greeterFollowLockScreen)
return;
if (follow) {
for (const inst of greeterWidgetInstances || []) {
if (inst.id.startsWith("gw_"))
SessionData.removeDesktopWidgetInstancePositions(inst.id);
}
set("greeterFollowLockScreen", true);
syncGreeterWidgets();
return;
}
const detached = (greeterWidgetInstances || []).map(inst => {
if (inst.widgetType === "greeterSession")
return inst;
const copy = JSON.parse(JSON.stringify(inst));
copy.id = "gw_" + inst.id;
SessionData.copyDesktopWidgetInstancePositions(inst.id, copy.id);
if (inst.widgetType !== "desktopClock" || inst.config?.autoPosition === false)
return copy;
copy.config.autoPosition = false;
SessionData.pinPublishedLockPosition(inst.id, copy.id, inst.config?.syncPositionAcrossScreens ?? false);
return copy;
});
set("greeterWidgetInstances", detached);
set("greeterFollowLockScreen", false);
}
function resetLockScreenWidgets() {
for (const inst of lockScreenWidgetInstances || [])
SessionData.removeDesktopWidgetInstancePositions(inst.id);
for (const inst of Spec.SPEC.lockScreenWidgetInstances.def)
SessionData.removeDesktopWidgetInstancePositions(inst.id);
resetToDefault(["lockScreenWidgetInstances"]);
}
// Following means the lock layout is the greeter layout, so that is what resets.
function resetGreeterWidgets() {
for (const inst of greeterWidgetInstances || []) {
if (!greeterFollowLockScreen || inst.widgetType === "greeterSession")
SessionData.removeDesktopWidgetInstancePositions(inst.id);
}
if (greeterFollowLockScreen) {
resetLockScreenWidgets();
set("greeterWidgetInstances", Spec.greeterWidgetsFromLock(lockScreenWidgetInstances, []));
return;
}
set("greeterWidgetInstances", Spec.greeterWidgetDefaults().map(inst => inst.widgetType === "greeterSession" ? inst : Object.assign(inst, {
id: "gw_" + inst.id
})));
}
function getDefaultSystemMonitorConfig() {
return {
showHeader: true,
transparency: 0.8,
colorMode: "primary",
customColor: "#ffffff",
showCpu: true,
showCpuGraph: true,
showCpuTemp: true,
showGpuTemp: false,
gpuPciId: "",
showMemory: true,
showMemoryGraph: true,
showNetwork: true,
showNetworkGraph: true,
showDisk: true,
showTopProcesses: false,
topProcessCount: 3,
topProcessSortBy: "cpu",
layoutMode: "auto",
graphInterval: 60,
x: -1,
y: -1,
width: 320,
height: 480,
displayPreferences: ["all"]
};
}
function widgetInstanceListKey(instanceId) {
return widgetInstanceListKeys.find(key => (root[key] || []).some(inst => inst.id === instanceId)) ?? "desktopWidgetInstances";
}
readonly property var widgetInstanceIdPrefixes: ({
desktopWidgetInstances: "dw_",
lockScreenWidgetInstances: "lw_",
greeterWidgetInstances: "gw_"
})
function createDesktopWidgetInstance(widgetType, name, config, listKey = "desktopWidgetInstances") {
const lockScreen = listKey !== "desktopWidgetInstances";
const instance = {
id: widgetInstanceIdPrefixes[listKey] + Date.now() + "_" + Math.random().toString(36).substr(2, 9),
widgetType: widgetType,
name: name || widgetType,
enabled: true,
config: Object.assign(lockScreen ? {
syncPositionAcrossScreens: true
} : {}, config || {})
};
const instances = JSON.parse(JSON.stringify(root[listKey] || []));
instances.push(instance);
root[listKey] = instances;
saveSettings();
return instance;
}
function updateDesktopWidgetInstance(instanceId, updates) {
const listKey = widgetInstanceListKey(instanceId);
const instances = JSON.parse(JSON.stringify(root[listKey] || []));
const idx = instances.findIndex(inst => inst.id === instanceId);
if (idx === -1)
return;
Object.assign(instances[idx], updates);
root[listKey] = instances;
saveSettings();
}
function updateDesktopWidgetInstanceConfig(instanceId, configUpdates) {
const listKey = widgetInstanceListKey(instanceId);
const instances = JSON.parse(JSON.stringify(root[listKey] || []));
const idx = instances.findIndex(inst => inst.id === instanceId);
if (idx === -1)
return;
instances[idx].config = Object.assign({}, instances[idx].config || {}, configUpdates);
root[listKey] = instances;
saveSettings();
}
function removeDesktopWidgetInstance(instanceId) {
const listKey = widgetInstanceListKey(instanceId);
root[listKey] = (root[listKey] || []).filter(inst => inst.id !== instanceId);
SessionData.removeDesktopWidgetInstancePositions(instanceId);
saveSettings();
}
function duplicateDesktopWidgetInstance(instanceId) {
const source = getDesktopWidgetInstance(instanceId);
if (!source)
return null;
const instance = createDesktopWidgetInstance(source.widgetType, source.name + " (Copy)", JSON.parse(JSON.stringify(source.config || {})), widgetInstanceListKey(instanceId));
if (!source.enabled)
updateDesktopWidgetInstance(instance.id, {
enabled: false
});
return instance;
}
function getDesktopWidgetInstance(instanceId) {
for (const key of widgetInstanceListKeys) {
const found = (root[key] || []).find(inst => inst.id === instanceId);
if (found)
return found;
}
return null;
}
function moveDesktopWidgetInstanceToGroup(instanceId, groupId, newIndexInGroup) {
const instances = JSON.parse(JSON.stringify(desktopWidgetInstances || []));
const groups = desktopWidgetGroups || [];
const idx = instances.findIndex(inst => inst.id === instanceId);
if (idx === -1)
return false;
const [item] = instances.splice(idx, 1);
item.group = groupId || null;
const groupMatches = inst => {
if (!groupId)
return !inst.group || !groups.some(g => g.id === inst.group);
return inst.group === groupId;
};
const groupInstances = instances.filter(groupMatches);
const clamped = Math.max(0, Math.min(newIndexInGroup, groupInstances.length));
let targetGlobalIdx;
if (clamped >= groupInstances.length) {
const last = groupInstances[groupInstances.length - 1];
targetGlobalIdx = last ? instances.findIndex(inst => inst.id === last.id) + 1 : instances.length;
} else {
const targetInstance = groupInstances[clamped];
targetGlobalIdx = instances.findIndex(inst => inst.id === targetInstance.id);
}
instances.splice(targetGlobalIdx, 0, item);
desktopWidgetInstances = instances;
saveSettings();
return true;
}
function createDesktopWidgetGroup(name) {
const id = "dwg_" + Date.now() + "_" + Math.random().toString(36).substr(2, 9);
const group = {
id: id,
name: name,
collapsed: false
};
const groups = JSON.parse(JSON.stringify(desktopWidgetGroups || []));
groups.push(group);
desktopWidgetGroups = groups;
saveSettings();
return group;
}
function updateDesktopWidgetGroup(groupId, updates) {
const groups = JSON.parse(JSON.stringify(desktopWidgetGroups || []));
const idx = groups.findIndex(g => g.id === groupId);
if (idx === -1)
return;
Object.assign(groups[idx], updates);
desktopWidgetGroups = groups;
saveSettings();
}
function removeDesktopWidgetGroup(groupId) {
const instances = JSON.parse(JSON.stringify(desktopWidgetInstances || []));
for (let i = 0; i < instances.length; i++) {
if (instances[i].group === groupId)
instances[i].group = null;
}
desktopWidgetInstances = instances;
const groups = (desktopWidgetGroups || []).filter(g => g.id !== groupId);
desktopWidgetGroups = groups;
saveSettings();
}
signal forceDankBarLayoutRefresh
signal forceDockLayoutRefresh
signal widgetDataChanged
signal workspaceIconsUpdated
signal compositorLayoutRefreshNeeded(bool frame)
signal blurStrengthRefreshNeeded
signal compositorInputRefreshNeeded
signal compositorCursorRefreshNeeded
signal notificationPopupsInvalidated
function refreshAuthAvailability() {
if (isGreeterMode)
return;
Processes.detectAuthCapabilities();
}
function _runStartSequence() {
Processes.settingsRoot = root;
const unsaved = _loadSettings();
initializeListModels();
return unsaved;
}
function applyStoredTheme() {
if (typeof Theme !== "undefined") {
Theme.currentThemeCategory = currentThemeCategory;
Theme.switchTheme(currentThemeName, false, false);
} else {
Qt.callLater(function () {
if (typeof Theme !== "undefined") {
Theme.currentThemeCategory = currentThemeCategory;
Theme.switchTheme(currentThemeName, false, false);
}
});
}
}
function regenSystemThemes() {
if (typeof Theme !== "undefined") {
Theme.generateSystemThemesFromCurrentTheme();
}
}
function updateCompositorLayout() {
compositorLayoutRefreshNeeded(false);
}
function updateCompositorInput() {
compositorInputRefreshNeeded();
}
function updateBlurStrength() {
blurStrengthRefreshNeeded();
}
function updateFrameCompositorLayout() {
compositorLayoutRefreshNeeded(true);
FrameTransitionState.begin();
}
function resolveIconTheme() {
if (iconThemePerMode && typeof SessionData !== "undefined" && SessionData.isLightMode)
return iconThemeLight;
return iconThemeDark;
}
function applyStoredIconTheme() {
updateGtkIconTheme();
updateQtIconTheme();
updateCosmicIconTheme();
}
function setIconThemeUnmanaged() {
iconThemePerMode = false;
iconThemeDark = "System Default";
iconThemeLight = "System Default";
SessionData.lastAppliedIconTheme = "";
SessionData.saveSettings();
saveSettings();
}
function checkIconThemeDrift() {
if (isGreeterMode)
return;
if (resolveIconTheme() === "System Default")
return;
if (!SessionData.lastAppliedIconTheme)
return;
const script = GSettings.getCmd("org.gnome.desktop.interface", "icon-theme");
Proc.runCommand("iconThemeDriftCheck", ["sh", "-c", script], (output, exitCode) => {
const platform = (output || "").trim();
if (!platform)
return;
if (platform === SessionData.lastAppliedIconTheme || platform === root.iconThemeDark || platform === root.iconThemeLight)
return;
root.setIconThemeUnmanaged();
ToastService.showWarning(I18n.tr("Icon theme changed outside DMS; switched to System Default", "shown when an external tool overrides the icon theme DMS applied"));
});
}
Connections {
target: Quickshell
function onScreensChanged() {
if (!root._loading)
Qt.callLater(root._reconcileConnectedFrameBarStyles);
}
}
Connections {
target: typeof SessionData !== "undefined" ? SessionData : null
function onIsLightModeChanged() {
if (!SessionData.isSwitchingMode)
return;
if (!root.iconThemePerMode)
return;
if (root.iconThemeLight === root.iconThemeDark)
return;
root.applyStoredIconTheme();
root.saveSettings();
}
}
function cosmicIntegrationAvailable() {
const desktop = (Quickshell.env("XDG_CURRENT_DESKTOP") || "").toUpperCase();
return desktop.includes("COSMIC");
}
function updateCosmicIconTheme() {
if (!cosmicIntegrationAvailable())
return;
const resolved = resolveIconTheme();
let cosmicThemeName = (resolved === "System Default") ? systemDefaultIconTheme : resolved;
if (!cosmicThemeName || cosmicThemeName === "System Default") {
const detectScript = GSettings.getCmd("org.gnome.desktop.interface", "icon-theme");
Proc.runCommand("detectCosmicIconTheme", ["sh", "-c", detectScript], (output, exitCode) => {
if (exitCode !== 0)
return;
const detected = (output || "").trim();
if (!detected || detected === "System Default")
return;
const detectedEscaped = detected.replace(/'/g, "'\\''");
const writeScript = `mkdir -p ${_configDir}/cosmic/com.system76.CosmicTk/v1
printf '"%s"\\n' '${detectedEscaped}' > ${_configDir}/cosmic/com.system76.CosmicTk/v1/icon_theme 2>/dev/null || true`;
Quickshell.execDetached(["sh", "-lc", writeScript]);
});
return;
}
const cosmicThemeNameEscaped = cosmicThemeName.replace(/'/g, "'\\''");
const script = `mkdir -p ${_configDir}/cosmic/com.system76.CosmicTk/v1
printf '"%s"\\n' '${cosmicThemeNameEscaped}' > ${_configDir}/cosmic/com.system76.CosmicTk/v1/icon_theme 2>/dev/null || true`;
Quickshell.execDetached(["sh", "-lc", script]);
}
function updateCosmicThemeMode(isLightMode) {
if (!cosmicIntegrationAvailable())
return;
const isDark = isLightMode ? "false" : "true";
const script = `mkdir -p ${_configDir}/cosmic/com.system76.CosmicTheme.Mode/v1
printf '%s\\n' ${isDark} > ${_configDir}/cosmic/com.system76.CosmicTheme.Mode/v1/is_dark 2>/dev/null || true`;
Quickshell.execDetached(["sh", "-lc", script]);
}
function updateGtkIconTheme() {
const resolved = resolveIconTheme();
const gtkThemeName = (resolved === "System Default") ? systemDefaultIconTheme : resolved;
if (gtkThemeName === "System Default" || gtkThemeName === "")
return;
SessionData.lastAppliedIconTheme = gtkThemeName;
SessionData.saveSettings();
if (typeof DMSService !== "undefined" && DMSService.apiVersion >= 3 && typeof PortalService !== "undefined") {
PortalService.setSystemIconTheme(gtkThemeName);
}
const configScript = `mkdir -p ${_configDir}/gtk-3.0 ${_configDir}/gtk-4.0
for config_dir in ${_configDir}/gtk-3.0 ${_configDir}/gtk-4.0; do
settings_file="$config_dir/settings.ini"
[ -f "$settings_file" ] && [ ! -w "$settings_file" ] && continue
if [ -f "$settings_file" ]; then
if grep -q "^gtk-icon-theme-name=" "$settings_file"; then
sed -i 's/^gtk-icon-theme-name=.*/gtk-icon-theme-name=${gtkThemeName}/' "$settings_file"
else
if grep -q "\\[Settings\\]" "$settings_file"; then
sed -i '/\\[Settings\\]/a gtk-icon-theme-name=${gtkThemeName}' "$settings_file"
else
echo -e '\\n[Settings]\\ngtk-icon-theme-name=${gtkThemeName}' >> "$settings_file"
fi
fi
else
echo -e '[Settings]\\ngtk-icon-theme-name=${gtkThemeName}' > "$settings_file"
fi
done
${GSettings.setCmd("org.gnome.desktop.interface", "icon-theme", gtkThemeName)} || true
pkill -HUP -f 'gtk' 2>/dev/null || true`;
Quickshell.execDetached(["sh", "-lc", configScript]);
}
function updateQtIconTheme() {
const resolved = resolveIconTheme();
const qtThemeName = (resolved === "System Default") ? "" : resolved;
if (!qtThemeName)
return;
const home = _homeUrl.replace("file://", "").replace(/'/g, "'\\''");
const qtThemeNameEscaped = qtThemeName.replace(/'/g, "'\\''");
const script = `mkdir -p ${_configDir}/qt5ct ${_configDir}/qt6ct ${_configDir}/environment.d 2>/dev/null || true
update_qt_icon_theme() {
local config_file="$1"
local theme_name="$2"
if [ -f "$config_file" ]; then
if grep -q "^\\[Appearance\\]" "$config_file"; then
if grep -q "^icon_theme=" "$config_file"; then
sed -i "s/^icon_theme=.*/icon_theme=$theme_name/" "$config_file"
else
sed -i "/^\\[Appearance\\]/a icon_theme=$theme_name" "$config_file"
fi
else
printf "\\n[Appearance]\\nicon_theme=%s\\n" "$theme_name" >> "$config_file"
fi
else
printf "[Appearance]\\nicon_theme=%s\\n" "$theme_name" > "$config_file"
fi
}
update_qt_icon_theme ${_configDir}/qt5ct/qt5ct.conf '${qtThemeNameEscaped}'
update_qt_icon_theme ${_configDir}/qt6ct/qt6ct.conf '${qtThemeNameEscaped}'`;
Quickshell.execDetached(["sh", "-lc", script]);
if (!qtengineActive || !runDmsMatugenTemplates || !matugenTemplateQtengine)
return;
Proc.runCommand("updateQtengineIconTheme", [Proc.dmsBin, "matugen", "qtengine", "--icon-theme", qtThemeName], () => {});
}
function scheduleAuthApply() {
if (isGreeterMode)
return;
Qt.callLater(() => {
Processes.settingsRoot = root;
Processes.scheduleAuthApply();
});
}
function scheduleGreeterAutoLoginSync() {
if (isGreeterMode)
return;
Qt.callLater(() => {
Processes.settingsRoot = root;
Processes.scheduleGreeterAutoLoginSync();
});
}
function syncLauncherHistory(who, key) {
if (who[key])
return;
AppUsageHistoryData.clear();
SessionData.clearLauncherHistory();
}
function markGreeterSyncPending(who, key, oldValue) {
if (isGreeterMode)
return;
if (!(key in SessionData.greeterSyncBaseline)) {
var baseline = Object.assign({}, SessionData.greeterSyncBaseline);
baseline[key] = oldValue;
SessionData.greeterSyncBaseline = baseline;
}
SessionData.greeterSyncPending = true;
SessionData.saveSettings();
}
// Older builds flagged keys the linked slot now serves live; drop them so Apply does not nag forever.
function pruneGreeterSyncPending() {
const baseline = SessionData.greeterSyncBaseline || {};
const keys = Object.keys(baseline);
const live = keys.filter(key => Spec.SPEC[key]?.onChange === "markGreeterSyncPending");
if (live.length === keys.length)
return;
const pruned = {};
for (const key of live)
pruned[key] = baseline[key];
SessionData.greeterSyncBaseline = pruned;
if (live.length === 0)
SessionData.greeterSyncPending = false;
SessionData.saveSettings();
}
function clearGreeterSyncPending() {
SessionData.greeterSyncBaseline = {};
SessionData.greeterSyncPending = false;
SessionData.saveSettings();
}
function revertGreeterSyncPending() {
for (var key in SessionData.greeterSyncBaseline) {
if (!(key in Spec.SPEC))
continue;
root[key] = SessionData.greeterSyncBaseline[key];
}
SessionData.greeterSyncBaseline = {};
SessionData.greeterSyncPending = false;
SessionData.saveSettings();
saveSettings();
}
readonly property var _hooks: ({
"applyStoredTheme": applyStoredTheme,
"regenSystemThemes": regenSystemThemes,
"updateCompositorLayout": updateCompositorLayout,
"updateCompositorInput": updateCompositorInput,
"updateBlurStrength": updateBlurStrength,
"applyStoredIconTheme": applyStoredIconTheme,
"updateBarConfigs": updateBarConfigs,
"updateCompositorCursor": updateCompositorCursor,
"scheduleAuthApply": scheduleAuthApply,
"scheduleGreeterAutoLoginSync": scheduleGreeterAutoLoginSync,
"markGreeterSyncPending": markGreeterSyncPending,
"syncLauncherHistory": syncLauncherHistory
})
function set(key, value) {
if (key === "cornerRadius") {
setCornerRadius(value);
return;
}
if (key === "radiusStrength")
value = Shape.normalizeStrength(value);
Spec.set(root, key, value, saveSettings, _hooks);
if (key === "frameEnabled" && value)
clearIslandBars();
}
function hasSetting(key) {
return key in Spec.SPEC;
}
function specDefault(key) {
return SpecUtil.cloneDef(Spec.SPEC[key]?.def);
}
function isDefault(keys) {
return keys.every(key => !(key in Spec.SPEC) || SpecUtil.isDefault(root[key], Spec.SPEC[key].def));
}
function resetToDefault(keys) {
for (const key of keys) {
if (key in Spec.SPEC)
Spec.set(root, key, null, saveSettings, _hooks);
}
}
function osdPositionFor(kind) {
const override = osdPositionOverrides?.[kind];
return typeof override === "number" ? override : osdPosition;
}
function hasOsdPositionOverride(kind) {
return typeof osdPositionOverrides?.[kind] === "number";
}
function setOsdPosition(kind, position) {
if (!kind) {
set("osdPosition", position);
return;
}
if (position === osdPosition) {
resetOsdPosition(kind);
return;
}
set("osdPositionOverrides", Object.assign({}, osdPositionOverrides, {
[kind]: position
}));
}
function resetOsdPosition(kind) {
if (!kind) {
resetToDefault(["osdPosition"]);
return;
}
if (!hasOsdPositionOverride(kind))
return;
const next = Object.assign({}, osdPositionOverrides);
delete next[kind];
set("osdPositionOverrides", next);
}
function barConfigDefault(field) {
return Spec.SPEC.barConfigs.def[0][field];
}
function dockConfigDefaults() {
return DockConfig.create("", "");
}
// Frame mode hosts bars inside the frame surface, which has nowhere to put an edge island; a dot floats over it.
function clearIslandBars() {
if (!islandBarConfigs.some(cfg => !isDotBarConfig(cfg)))
return;
const configs = JSON.parse(JSON.stringify(barConfigs));
for (const cfg of configs)
delete cfg.island;
_commitBarConfigs(configs);
}
function _loadSettings() {
const isInitial = !_hasLoaded || _settingsStage === SettingsData.Stage.Partial;
let unsavedChanges;
let obj = _getSettingsObjectFromFiles();
let loadedSettings = JSON.stringify(obj);
const oldVersion = obj?.configVersion ?? 0;
const legacyPins = oldVersion < 13 ? Store.extractPins(obj) : null;
const sessionPayload = oldVersion < 15 ? Store.extractSessionPayload(obj) : null;
const cachePayload = oldVersion < 15 ? Store.extractCachePayload(obj) : null;
let migrationRan = false;
if (oldVersion < settingsConfigVersion) {
const migrated = Store.migrateToVersion(obj, settingsConfigVersion);
if (migrated) {
obj = migrated;
migrationRan = true;
}
}
if (legacyPins) {
Qt.callLater(() => CacheData.migratePins(legacyPins));
}
if (cachePayload) {
Qt.callLater(() => CacheData.migrateUsageHistories(cachePayload));
}
if (sessionPayload) {
Qt.callLater(() => {
SessionData.importFromSettings(sessionPayload);
_mergeSessionState();
});
}
if (obj?.lockScreenActiveMonitor !== undefined) {
var oldVal = obj.lockScreenActiveMonitor;
if (oldVal && oldVal !== "all") {
if (!obj.screenPreferences) {
obj.screenPreferences = {};
}
if (obj.screenPreferences.lockScreen === undefined) {
obj.screenPreferences.lockScreen = [oldVal];
}
}
delete obj.lockScreenActiveMonitor;
}
if (obj?.use24HourClock !== undefined && obj?.clockFormat === undefined) {
obj.clockFormat = obj.use24HourClock ? "24h" : "12h";
delete obj.use24HourClock;
}
const prevFrameEnabled = frameEnabled;
const prevFrameMode = frameMode;
unsavedChanges = loadedSettings !== JSON.stringify(obj);
Store.parse(root, obj);
// set() enforces this pair, but a hand-edited settings bypass set() entirely.
if (frameEnabled)
clearIslandBars();
if (obj?.directionalAnimationMode === 3 && frameMode !== "connected")
frameMode = "connected";
if (obj?.iconTheme !== undefined && obj?.iconThemeDark === undefined)
iconThemeDark = obj.iconTheme;
if (obj?.weatherLocation !== undefined)
_legacyWeatherLocation = obj.weatherLocation;
if (obj?.weatherCoordinates !== undefined)
_legacyWeatherCoordinates = obj.weatherCoordinates;
if (obj?.vpnLastConnected !== undefined && obj.vpnLastConnected !== "") {
_legacyVpnLastConnected = obj.vpnLastConnected;
SessionData.vpnLastConnected = _legacyVpnLastConnected;
SessionData.saveSettings();
}
_hasLoaded = true;
if (!_parseError) {
_settingsStage = SettingsData.Stage.Ready;
}
if (isInitial) {
_mergeSessionState();
Qt.callLater(checkIconThemeDrift);
}
applyStoredTheme();
updateCompositorCursor();
if (!isInitial) {
// External edits reload under _loading, which skips the per-property transition triggers
const frameChanged = (frameEnabled !== prevFrameEnabled || (frameEnabled && frameMode !== prevFrameMode));
if (!_parseError && frameChanged) {
updateFrameCompositorLayout();
}
}
if (isInitial && _settingsStage === SettingsData.Stage.Ready) {
loadPluginSettings();
Qt.callLater(() => _reconcileConnectedFrameBarStyles());
}
return unsavedChanges;
}
function _mergeSessionState() {
if (!_hasLoaded || !SessionData._hasLoaded)
return;
pruneGreeterSyncPending();
const pluginState = SessionData.builtInPluginState || {};
if (Object.keys(pluginState).length > 0) {
const updated = JSON.parse(JSON.stringify(builtInPluginSettings));
for (const id in pluginState) {
updated[id] = pluginState[id];
}
builtInPluginSettings = updated;
}
}
Connections {
target: SessionData
function onLoaded() {
root._mergeSessionState();
}
}
function _getCurrentSettings() {
const setKeys = new Set();
for (const path of _settingsFilesPaths) {
const file = _settingsFiles.get(path);
for (const key in file.settings) {
setKeys.add(key);
}
}
return Store.toJson(root, setKeys);
}
function getCurrentSettingsJson() {
return JSON.stringify(_getCurrentSettings(), null, 2);
}
function _resetPluginSettings() {
_pluginParseError = false;
pluginSettings = {};
}
function _pluginSettingsErrorCode(error) {
if (typeof error === "number")
return error;
if (error && typeof error === "object") {
if (typeof error.code === "number")
return error.code;
if (typeof error.errno === "number")
return error.errno;
}
const msg = String(error || "").trim();
if (/^\d+$/.test(msg))
return Number(msg);
return -1;
}
function _isMissingPluginSettingsError(error) {
if (_pluginSettingsErrorCode(error) === 2)
return true;
const msg = String(error || "").toLowerCase();
return msg.indexOf("file does not exist") !== -1 || msg.indexOf("no such file") !== -1 || msg.indexOf("enoent") !== -1;
}
function loadPluginSettings() {
try {
parsePluginSettings(pluginSettingsFile.text());
} catch (e) {
const msg = e.message || String(e);
if (!_isMissingPluginSettingsError(e))
log.warn("Failed to load plugin_settings.json. Error:", msg);
_resetPluginSettings();
}
}
function parsePluginSettings(content) {
_pluginSettingsLoading = true;
_pluginParseError = false;
try {
if (content && content.trim()) {
pluginSettings = JSON.parse(content);
} else {
pluginSettings = {};
}
} catch (e) {
_pluginParseError = true;
const msg = e.message;
log.error("Failed to parse plugin_settings.json - file will not be overwritten. Error:", msg);
Qt.callLater(() => ToastService.showError(I18n.tr("Failed to parse %1").arg("plugin_settings.json"), msg));
pluginSettings = {};
} finally {
_pluginSettingsLoading = false;
}
}
function _splitSettingsByFile(settings) {
const savedSettings = new Set();
const splitSettings = {};
for (let i = _settingsFilesPaths.length - 1; i >= 0; i--) {
const path = _settingsFilesPaths[i];
const file = _settingsFiles.get(path);
const fileSettings = file.getSettings();
for (const setting in fileSettings) {
if (!(setting in settings)) {
delete fileSettings[setting];
continue;
}
if (savedSettings.has(setting)) {
continue;
}
fileSettings[setting] = settings[setting];
savedSettings.add(setting);
}
splitSettings[path] = fileSettings;
}
for (const setting in settings) {
if (!savedSettings.has(setting)) {
splitSettings[defaultSettingsFile.filePath][setting] = settings[setting];
}
}
return splitSettings;
}
function saveSettings() {
if (isGreeterMode || _loading || !_hasLoaded)
return;
unsavedUserChanges = true;
settingsSaveDebounce.restart();
}
function _saveSettings(userChanges) {
let reason = null;
if (isGreeterMode) {
reason = "running in greeter mode.";
} else if (_loading) {
reason = "some files are being loaded";
} else if (_parseError) {
reason = "failed to parse settings.";
}
if (reason !== null) {
log.warn("Refusing to save settings, recent changes may be lost: " + reason);
return;
}
const settings = _getCurrentSettings();
const splitSettings = _splitSettingsByFile(settings);
for (const path in splitSettings) {
const fileSettings = splitSettings[path];
const file = _settingsFiles.get(path);
file.setSettings(fileSettings, userChanges);
}
unsavedUserChanges = _anySettingsFile(file => file.fileUnsavedUserChanges);
}
function savePluginSettings() {
if (isGreeterMode || _pluginSettingsLoading || _pluginParseError)
return;
pluginSettingsFile.setText(JSON.stringify(pluginSettings, null, 2));
}
function _connectedFrameBarStyleSnapshot(config) {
return {
"shadowIntensity": config?.shadowIntensity ?? 0,
"squareCorners": config?.squareCorners ?? false,
"attachToScreenEdge": config?.attachToScreenEdge ?? false,
"gothCornersEnabled": config?.gothCornersEnabled ?? false,
"borderEnabled": config?.borderEnabled ?? false
};
}
function barUsesConnectedFrameStyle(config) {
if (!config || !connectedFrameModeActive)
return false;
return Quickshell.screens.some(screen => barConfigCoversScreen(config, screen) && isScreenInPreferences(screen, frameScreenPreferences));
}
function _applyConnectedFrameBarStyles(configs) {
if (!Array.isArray(configs))
return {
"configs": configs,
"changed": false,
"backups": null
};
const backups = connectedFrameBarStyleBackups || {};
const nextBackups = {};
let backupsChanged = false;
let anyChanged = false;
const zeroed = _connectedFrameBarStyleSnapshot(null);
const out = configs.map(cfg => {
if (!cfg?.id)
return cfg;
const covered = barUsesConnectedFrameStyle(cfg);
if (covered) {
nextBackups[cfg.id] = backups[cfg.id] ?? _connectedFrameBarStyleSnapshot(cfg);
backupsChanged = backupsChanged || backups[cfg.id] === undefined;
}
const target = covered ? zeroed : backups[cfg.id];
if (!target)
return cfg;
const current = _connectedFrameBarStyleSnapshot(cfg);
if (Object.keys(target).every(key => current[key] === target[key]))
return cfg;
anyChanged = true;
return Object.assign({}, cfg, target);
});
const backupsDirty = backupsChanged || Object.keys(nextBackups).length !== Object.keys(backups).length;
return {
"configs": anyChanged ? out : configs,
"changed": anyChanged,
"backups": backupsDirty ? nextBackups : null
};
}
function effectiveBarConfigForRender(config, usesFrameBarChrome) {
if (!config || !connectedFrameModeActive || usesFrameBarChrome)
return config;
const backup = connectedFrameBarStyleBackups[config.id];
if (!backup)
return config;
return Object.assign({}, config, backup);
}
function _reconcileConnectedFrameBarStyles() {
if (Quickshell.screens.length === 0)
return;
const result = _applyConnectedFrameBarStyles(barConfigs);
if (result.changed)
barConfigs = result.configs;
if (result.backups)
connectedFrameBarStyleBackups = result.backups;
if (result.changed)
updateBarConfigs();
}
function detectAvailableIconThemes() {
const xdgDataDirs = Quickshell.env("XDG_DATA_DIRS") || "";
const localData = Paths.strip(StandardPaths.writableLocation(StandardPaths.GenericDataLocation));
const homeDir = Paths.strip(StandardPaths.writableLocation(StandardPaths.HomeLocation));
const dataDirs = xdgDataDirs.trim() !== "" ? xdgDataDirs.split(":").concat([localData]) : ["/usr/share", "/usr/local/share", localData];
const iconPaths = dataDirs.map(d => d + "/icons").concat([homeDir + "/.icons"]);
const pathsArg = iconPaths.join(" ");
const script = `
echo "SYSDEFAULT:$(${GSettings.getCmd("org.gnome.desktop.interface", "icon-theme")})"
for dir in ${pathsArg}; do
[ -d "$dir" ] || continue
for theme in "$dir"/*/; do
[ -d "$theme" ] || continue
basename "$theme"
done
done | grep -v '^icons$' | grep -v '^default$' | grep -v '^hicolor$' | grep -v '^locolor$' | sort -u
`;
Proc.runCommand("detectIconThemes", ["sh", "-c", script], (output, exitCode) => {
const themes = ["System Default"];
if (output && output.trim()) {
const lines = output.trim().split('\n');
for (let i = 0; i < lines.length; i++) {
const line = lines[i].trim();
if (line.startsWith("SYSDEFAULT:")) {
systemDefaultIconTheme = line.substring(11).trim();
continue;
}
if (line)
themes.push(line);
}
}
availableIconThemes = themes;
});
}
function detectAvailableCursorThemes() {
const xdgDataDirs = Quickshell.env("XDG_DATA_DIRS") || "";
const localData = Paths.strip(StandardPaths.writableLocation(StandardPaths.GenericDataLocation));
const homeDir = Paths.strip(StandardPaths.writableLocation(StandardPaths.HomeLocation));
const dataDirs = xdgDataDirs.trim() !== "" ? xdgDataDirs.split(":").concat([localData]) : ["/usr/share", "/usr/local/share", localData];
const cursorPaths = dataDirs.map(d => d + "/icons").concat([homeDir + "/.icons", homeDir + "/.local/share/icons"]);
const pathsArg = cursorPaths.join(" ");
const script = `
echo "SYSDEFAULT:$(${GSettings.getCmd("org.gnome.desktop.interface", "cursor-theme")})"
for dir in ${pathsArg}; do
[ -d "$dir" ] || continue
for theme in "$dir"/*/; do
[ -d "$theme" ] || continue
[ -d "$theme/cursors" ] || continue
basename "$theme"
done
done | grep -v '^icons$' | grep -v '^default$' | sort -u
`;
Proc.runCommand("detectCursorThemes", ["sh", "-c", script], (output, exitCode) => {
const themes = ["System Default"];
if (output && output.trim()) {
const lines = output.trim().split('\n');
for (let i = 0; i < lines.length; i++) {
const line = lines[i].trim();
if (line.startsWith("SYSDEFAULT:")) {
systemDefaultCursorTheme = line.substring(11).trim();
continue;
}
if (line)
themes.push(line);
}
}
availableCursorThemes = themes;
});
}
function getEffectiveTimeFormat() {
if (use24HourClock)
return showSeconds ? "hh:mm:ss" : "hh:mm";
if (padHours12Hour)
return showSeconds ? "hh:mm:ss AP" : "hh:mm AP";
return showSeconds ? "h:mm:ss AP" : "h:mm AP";
}
function getEffectiveDateFormat(fallback) {
if (clockDateFormat && clockDateFormat.length > 0)
return clockDateFormat;
return fallback || "ddd d";
}
function initializeListModels() {
const defaultBar = getPrimaryBarConfig();
if (defaultBar) {
Lists.init(leftWidgetsModel, centerWidgetsModel, rightWidgetsModel, defaultBar.leftWidgets, defaultBar.centerWidgets, defaultBar.rightWidgets);
}
}
function updateListModel(listModel, order) {
Lists.update(listModel, order);
widgetDataChanged();
}
function getPopupTriggerPosition(pos, screen, barThickness, widgetWidth, barSpacing, barPosition, barConfig) {
return ShellLayout.popupTrigger(pos, screen, barThickness, widgetWidth, barSpacing, barPosition, barConfig);
}
function getAdjacentBarInfo(screen, barPosition, barConfig) {
return ShellLayout.adjacentInfo(screen, barConfig);
}
function getBarBounds(screen, barThickness, barPosition, barConfig) {
return ShellLayout.barBounds(screen, barThickness, barPosition, barConfig);
}
function updateBarConfigs() {
barConfigsChanged();
saveSettings();
}
// Each screen edge can hold one dock, so resolution and reservation are always edge-scoped.
function dockConfigsForScreen(screen) {
if (typeof screen === "string")
screen = Quickshell.screens.find(s => s.name === screen);
if (!screen)
return [];
return DockConfig.resolveAll(dockConfigs, screen, Quickshell.screens, (config, target) => isScreenInPreferences(target, config.screenPreferences)).filter(config => !_barClaimsEdge(screen, config.position));
}
function dockConfigForScreenEdge(screen, side) {
return dockConfigsForScreen(screen).find(config => DockConfig.EDGES[config.position] === side) ?? null;
}
function dockConfigForScreen(screen) {
return dockConfigsForScreen(screen)[0] ?? null;
}
function _barClaimsEdge(screen, position) {
return barConfigs.some(bar => bar.enabled && (bar.visible ?? true) && bar.position === position && barConfigCoversScreen(bar, screen));
}
function taskbarInsetForEdge(screen, side) {
const config = dockConfigForScreenEdge(screen, side);
if (!config?.enabled || config.mode !== "taskbar")
return 0;
const frameInset = !CompositorService.frameWindowVisibleForScreen(screen) ? 0 : !config.useOverlayLayer && CompositorService.usesConnectedFrameChromeForScreen(screen) ? frameEdgeReservation(screen, side) : frameThickness;
return DockConfig.effectiveThickness(config) + frameInset;
}
function dockReservationForEdge(screen, side) {
const config = dockConfigForScreenEdge(screen, side);
if (!config?.enabled || config.autoHide || config.smartAutoHide)
return 0;
return Math.max(0, DockConfig.effectiveThickness(config) + (config.mode === "taskbar" || (!config.useOverlayLayer && CompositorService.usesConnectedFrameChromeForScreen(screen)) ? 0 : config.margin + config.bottomGap));
}
function _screensCoveredBy(preferences) {
return Quickshell.screens.filter(screen => preferences.includes("all") || isScreenInPreferences(screen, preferences));
}
// A dock only clashes with something sharing both a screen and an edge; other edges stay free.
function dockAssignmentConflict(id, preferences, position) {
const edge = position ?? getDockConfig(id)?.position ?? SettingsData.Position.Bottom;
const covered = _screensCoveredBy(preferences);
for (const other of dockConfigs) {
if (other.id === id || other.position !== edge || !other.enabled)
continue;
if (covered.some(screen => isScreenInPreferences(screen, other.screenPreferences) || other.screenPreferences.includes("all")))
return other.name;
}
for (const bar of barConfigs) {
if (!bar.enabled || !(bar.visible ?? true) || bar.position !== edge)
continue;
if (covered.some(screen => barConfigCoversScreen(bar, screen)))
return bar.name || bar.id;
}
return "";
}
// Edges this configuration could still be moved to, with its current edge always offered.
function dockAvailableEdges(id, preferences) {
const current = getDockConfig(id)?.position;
return [SettingsData.Position.Top, SettingsData.Position.Bottom, SettingsData.Position.Left, SettingsData.Position.Right].filter(edge => edge === current || !dockAssignmentConflict(id, preferences, edge));
}
function dockConfigForAction(screen, selector) {
if (selector)
return getDockConfig(selector) || dockConfigs.find(config => config.name === selector) || null;
if (typeof screen === "string")
screen = Quickshell.screens.find(item => item.name === screen);
const resolved = dockConfigForScreen(screen);
if (resolved)
return resolved;
return dockConfigs.find(config => !config.screenPreferences.includes("all") && screen && isScreenInPreferences(screen, config.screenPreferences)) || dockConfigs.find(config => config.screenPreferences.includes("all")) || dockConfigs[0] || null;
}
function getDockConfig(id) {
return dockConfigs.find(config => config.id === id) ?? null;
}
function updateDockConfig(id, updates) {
const current = getDockConfig(id);
if (!current)
return;
const next = DockConfig.normalize([Object.assign({}, current, updates, {
id
})])[0];
if (!next)
return;
dockConfigs = dockConfigs.map(config => config.id === id ? next : config);
saveSettings();
}
// First edge in `order` that no enabled bar or dock already holds on the covered displays.
function firstFreeEdge(id, preferences, order) {
return order.find(edge => !dockAssignmentConflict(id, preferences, edge)) ?? -1;
}
function firstFreeDockEdge(id, preferences) {
return firstFreeEdge(id, preferences, [SettingsData.Position.Bottom, SettingsData.Position.Left, SettingsData.Position.Right, SettingsData.Position.Top]);
}
// A new dock shows up right away on every display when an edge is free for it.
function createDockConfig() {
const id = "dock_" + Date.now();
let name = I18n.tr("Dock");
let number = 2;
while (dockConfigs.some(config => config.name === name))
name = I18n.tr("Dock") + " " + number++;
const config = DockConfig.create(id, name);
const edge = firstFreeDockEdge(id, config.screenPreferences);
config.enabled = edge >= 0;
config.position = Math.max(SettingsData.Position.Top, edge);
dockConfigs = dockConfigs.concat([config]);
saveSettings();
return id;
}
function removeDockConfig(id) {
dockConfigs = dockConfigs.filter(config => config.id !== id);
SessionData.removeDockPins(id);
saveSettings();
}
function updateDockWidget(id, instanceId, updates) {
const config = getDockConfig(id);
if (!config)
return;
updateDockConfig(id, {
widgets: config.widgets.map(item => item.id === instanceId ? Object.assign({}, item, updates) : item)
});
}
// Pinning an app to a dock whose Apps row was removed would otherwise pin into nothing.
function ensureDockApps(id) {
const config = getDockConfig(id);
if (!config)
return;
const apps = config.widgets.find(item => item.widgetId === "appsDock");
if (apps && apps.enabled !== false)
return;
if (apps) {
updateDockWidget(id, apps.id, {
enabled: true
});
return;
}
updateDockConfig(id, {
widgets: config.widgets.concat([
{
id: id + "_apps",
widgetId: "appsDock",
enabled: true
}
])
});
}
function getBarConfig(barId) {
return barConfigs.find(cfg => cfg.id === barId) || null;
}
function barTransparency(config) {
if (config?.followInterfaceStyle !== false)
return popupTransparency;
return config?.transparency ?? 1.0;
}
function barWidgetTransparency(config) {
if (config?.widgetFollowInterfaceStyle !== false)
return Theme.foregroundAlpha;
return config?.widgetTransparency ?? 1.0;
}
function barSurfaceColor(config) {
return Theme.surfaceRoleColor(config?.surfaceColor, config?.surfaceCustomColor, Theme.hostSurface);
}
function widgetOption(widgetType, data, key) {
return WidgetDefaults.option(widgetType, data, key);
}
function widgetDefaults(widgetType) {
return WidgetDefaults.DEFAULTS[widgetType] ?? {};
}
function barWidgetEntry(barConfig, widgetType) {
if (!barConfig)
return null;
for (const listKey of ["leftWidgets", "centerWidgets", "rightWidgets"]) {
for (const entry of barConfig[listKey] ?? []) {
const id = typeof entry === "string" ? entry : (entry.id ?? entry.widgetId);
if (id !== widgetType)
continue;
return typeof entry === "string" ? {
"id": entry
} : entry;
}
}
return null;
}
function addBarWidget(barId, sectionId, widgetId) {
const config = getBarConfig(barId);
if (!config)
return -1;
const entry = {
"id": widgetId,
"enabled": true
};
switch (widgetId) {
case "spacer":
entry.size = 20;
break;
case "gpuTemp":
entry.selectedGpuIndex = 0;
entry.pciId = "";
break;
case "diskUsage":
entry.mountPath = "/";
break;
}
const listKey = sectionId + "Widgets";
const list = (config[listKey] ?? []).slice();
if (widgetId === "island") {
if (isIslandBarConfig(config) || islandWidgetBlocked(config) || _islandWidgetLocation(config))
return -1;
const configs = JSON.parse(JSON.stringify(barConfigs));
const target = configs.find(cfg => cfg.id === barId);
list.push(entry);
target[listKey] = list;
_commitIslandOwner(configs, target);
return list.length - 1;
}
list.push(entry);
const patch = {};
patch[listKey] = list;
updateBarConfig(barId, patch);
return list.length - 1;
}
function locateBarWidget(widgetId, preferredBarId) {
const ordered = barConfigs.slice().sort((a, b) => (b.id === preferredBarId) - (a.id === preferredBarId));
for (const config of ordered) {
for (const sectionId of ["left", "center", "right"]) {
const list = config[sectionId + "Widgets"] ?? [];
for (let i = 0; i < list.length; i++) {
const id = typeof list[i] === "string" ? list[i] : list[i].id;
if (id === widgetId)
return {
"barId": config.id,
"section": sectionId,
"index": i
};
}
}
}
return null;
}
function updateBarWidget(barId, sectionId, index, updates) {
const config = getBarConfig(barId);
const listKey = sectionId + "Widgets";
const list = (config?.[listKey] ?? []).slice();
if (index < 0 || index >= list.length)
return;
const entry = typeof list[index] === "string" ? {
"id": list[index],
"enabled": true
} : Object.assign({}, list[index]);
Object.assign(entry, updates);
list[index] = entry;
const patch = {};
patch[listKey] = list;
updateBarConfig(barId, patch);
}
function setBarIsland(barId, on, dot) {
const config = getBarConfig(barId);
const wantIsland = on === true && dot !== true;
const wantDot = on === true && dot === true;
if (!config || ((config.island === true) === wantIsland && (config.dot === true) === wantDot))
return;
const updates = {
island: wantIsland,
dot: wantDot
};
const configs = JSON.parse(JSON.stringify(barConfigs));
const target = Object.assign(configs.find(cfg => cfg.id === barId), updates);
if (on !== true) {
_commitBarConfigs(configs);
return;
}
if (!config.enabled)
target.enabled = true;
// A hidden island would draw but leave routing, with no Visibility card and no `bar reveal` to bring it back.
if (config.visible === false)
target.visible = true;
if ((config.screenPreferences ?? []).length === 0)
target.screenPreferences = ["all"];
_stashIslandWidget(target);
if (wantIsland)
_evictIslandWidgets(configs, target);
_commitBarConfigs(configs);
}
function isBarIpcRevealed(barId) {
if (!barId)
return false;
return !!barIpcRevealStates[barId];
}
function setBarIpcReveal(barId, revealed) {
if (!barId || isIslandBarConfig(getBarConfig(barId)))
return;
const nextRevealed = !!revealed;
if (!!barIpcRevealStates[barId] === nextRevealed)
return;
const states = Object.assign({}, barIpcRevealStates);
if (nextRevealed) {
states[barId] = true;
} else {
delete states[barId];
}
barIpcRevealStates = states;
}
function toggleBarIpcReveal(barId) {
const revealed = !isBarIpcRevealed(barId);
setBarIpcReveal(barId, revealed);
return revealed;
}
function addBarConfig(config) {
const configs = JSON.parse(JSON.stringify(barConfigs));
configs.push(config);
const result = _applyConnectedFrameBarStyles(configs);
barConfigs = result.configs;
if (result.backups)
connectedFrameBarStyleBackups = result.backups;
updateBarConfigs();
}
function updateBarConfig(barId, updates) {
const configs = JSON.parse(JSON.stringify(barConfigs));
const index = configs.findIndex(cfg => cfg.id === barId);
if (index === -1)
return;
const positionChanged = updates.position !== undefined && configs[index].position !== updates.position;
if (updates.autoHide === false || updates.visible === false)
setBarIpcReveal(barId, false);
Object.assign(configs[index], updates);
_commitIslandOwner(configs, configs[index]);
if (positionChanged) {
notificationPopupsInvalidated();
}
}
function deleteBarConfig(barId) {
if (barId === "default")
return;
const configs = barConfigs.filter(cfg => cfg.id !== barId);
barConfigs = configs;
if (connectedFrameBarStyleBackups?.[barId] !== undefined) {
const nextBackups = JSON.parse(JSON.stringify(connectedFrameBarStyleBackups || {}));
delete nextBackups[barId];
connectedFrameBarStyleBackups = nextBackups;
}
setBarIpcReveal(barId, false);
updateBarConfigs();
}
function getBarKindConfigs() {
return barConfigs.filter(cfg => !isIslandBarConfig(cfg));
}
// Style and popup fallbacks never treat the island as "the bar".
function getPrimaryBarConfig() {
const bars = getBarKindConfigs();
if (bars.length > 0)
return bars[0];
const fallback = getBarConfig("default");
return isIslandBarConfig(fallback) ? null : fallback;
}
function _sideToPosition(side) {
return LayoutResolver.edges.indexOf(side);
}
function positionToSide(pos) {
return LayoutResolver.edgeName(pos);
}
function barOccupiesSide(screen, side) {
const position = _sideToPosition(side);
return position >= 0 && (ShellLayout.forScreen(screen)?.bars.some(input => input.config.position === position) ?? false);
}
// Check if a dock occupies the specified screen edge.
function dockOccupiesSide(screen, side) {
const config = dockConfigForScreenEdge(screen, side);
return !!config?.enabled;
}
function getScreenModelIndex(screen) {
return LayoutResolver.screenModelIndex(screen, Quickshell.screens);
}
function getScreenDisplayName(screen) {
if (!screen)
return "";
if (displayNameMode === "model" && screen.model) {
const modelIndex = getScreenModelIndex(screen);
if (modelIndex >= 0) {
return screen.model + "-" + modelIndex;
}
return screen.model;
}
return screen.name;
}
function isScreenInPreferences(screen, prefs) {
return LayoutResolver.screenMatches(screen, prefs, Quickshell.screens, displayNameMode);
}
function getFilteredScreens(componentId) {
var prefs = screenPreferences && screenPreferences[componentId] || ["all"];
if (componentId === "wallpaper" && Array.isArray(prefs) && prefs.length === 0) {
return [];
}
if (!prefs || prefs.length === 0 || prefs.includes("all") || (typeof prefs[0] === "string" && prefs[0] === "all")) {
return Quickshell.screens;
}
var filtered = Quickshell.screens.filter(screen => isScreenInPreferences(screen, prefs));
if (filtered.length === 0 && showOnLastDisplay && showOnLastDisplay[componentId] && Quickshell.screens.length === 1) {
return Quickshell.screens;
}
return filtered;
}
function barConfigCoversScreen(bc, screen) {
return ShellLayout.coversScreen(bc, screen);
}
function isIslandBarConfig(bc) {
return !!bc && (bc.island === true || bc.dot === true);
}
function islandWidgetEntry(bc) {
const location = isIslandBarConfig(bc) ? null : _islandWidgetLocation(bc);
if (!location)
return null;
const entry = bc[location.sectionId + "Widgets"][location.index];
return typeof entry === "string" ? {
"id": entry
} : entry;
}
function hostsIsland(bc) {
return isIslandBarConfig(bc) || !!_islandWidgetLocation(bc, true);
}
function _sharesScreen(a, b) {
return Quickshell.screens.some(screen => barConfigCoversScreen(a, screen) && barConfigCoversScreen(b, screen));
}
function islandWidgetBlocked(bc) {
if (!bc)
return true;
return (barConfigs || []).some(cfg => cfg.id !== bc.id && cfg.island === true && cfg.enabled !== false && _sharesScreen(cfg, bc));
}
// Clones and dots drop the widget for good; an island-layout switch stashes it first.
function stripIslandWidget(cfg) {
delete cfg.islandWidgetStash;
_stripIslandEntries(cfg);
}
function _stripIslandEntries(cfg) {
for (const sectionId of ["left", "center", "right"]) {
const key = sectionId + "Widgets";
if (Array.isArray(cfg[key]))
cfg[key] = cfg[key].filter(entry => !_isIslandEntry(entry));
}
}
// An island-layout bar cannot host the widget, but switching back should not cost the user its placement and settings.
function _stashIslandWidget(cfg) {
const location = _islandWidgetLocation(cfg);
if (location)
cfg.islandWidgetStash = Object.assign({
"entry": cfg[location.sectionId + "Widgets"][location.index]
}, location);
_stripIslandEntries(cfg);
}
// A restore never evicts: while another island holds the screen the stash waits for a later commit to free it.
function _restoreIslandWidget(cfg, configs) {
const stash = cfg.islandWidgetStash;
if (!stash?.entry) {
delete cfg.islandWidgetStash;
return;
}
if (isIslandBarConfig(cfg) || _islandWidgetLocation(cfg) || _islandScreenTaken(configs, cfg))
return;
delete cfg.islandWidgetStash;
const key = stash.sectionId + "Widgets";
const list = Array.isArray(cfg[key]) ? cfg[key].slice() : [];
list.splice(Math.min(stash.index, list.length), 0, stash.entry);
cfg[key] = list;
}
function _islandScreenTaken(configs, bc) {
return configs.some(cfg => cfg.id !== bc.id && cfg.enabled !== false && (cfg.island === true || _islandWidgetLocation(cfg, true)) && _sharesScreen(cfg, bc));
}
function _evictIslandWidgets(configs, owner) {
for (const cfg of configs) {
if (cfg.id !== owner.id && cfg.enabled !== false && _islandWidgetLocation(cfg, true) && _sharesScreen(cfg, owner))
_stashIslandWidget(cfg);
}
}
// Island-relevant writes must go through here, or a freed screen never gets its stashed widget back.
function _commitBarConfigs(configs) {
for (const cfg of configs)
_restoreIslandWidget(cfg, configs);
const result = _applyConnectedFrameBarStyles(configs);
barConfigs = result.configs;
if (result.backups)
connectedFrameBarStyleBackups = result.backups;
updateBarConfigs();
}
// The bar just written wins its screens: enabling a bar or its island entry evicts the previous host.
function _commitIslandOwner(configs, cfg) {
if (cfg.enabled !== false && !isIslandBarConfig(cfg) && _islandWidgetLocation(cfg, true))
_evictIslandWidgets(configs, cfg);
_commitBarConfigs(configs);
}
function isDotBarConfig(bc) {
return !!bc && bc.dot === true;
}
// The dot is a companion, not a layout: the first enable clones the base bar so it inherits its look.
function setDotEnabled(enabled, baseId) {
const existing = dotBarConfig;
if (existing) {
if ((existing.enabled ?? false) !== enabled)
updateBarConfig(existing.id, {
enabled
});
return;
}
if (!enabled)
return;
const base = getBarConfig(baseId);
const source = base && !isDotBarConfig(base) ? base : getBarConfig("default");
if (!source)
return;
const config = Object.assign(JSON.parse(JSON.stringify(source)), {
id: "dot" + Date.now(),
name: I18n.tr("Dot", "bar layout: free-floating dot that opens island activities"),
enabled: true,
island: false,
dot: true,
screenPreferences: ["all"],
showOnLastDisplay: true,
followInterfaceStyle: false,
transparency: barTransparency(source)
});
// An inherited "always here" would make the base island and the dot fight by config order.
delete config.islandSharedRouting;
stripIslandWidget(config);
addBarConfig(config);
}
function islandFreePlacement(bc) {
return isDotBarConfig(bc) || (isIslandBarConfig(bc) && islandSetting(bc, "islandFloating") && islandSetting(bc, "islandPlacement") === "free");
}
// A hosted island shares its config id with the bar, so last-used cannot tell them apart and would degenerate into always.
function islandSharedRoutingMode(bc) {
const mode = islandSetting(bc, "islandSharedRouting");
if (mode === "always")
return mode;
return mode === "last-used" && isIslandBarConfig(bc) ? mode : "normal";
}
// Dashboard activities share an IPC close, but choose their destinations independently.
readonly property var islandDashActivities: ["home", "media", "weather", "wallpaper"]
function islandRouteKey(activity) {
switch (activity) {
case "controlcenter":
return "islandRouteControlCenter";
case "notificationcenter":
return "islandRouteNotificationCenter";
case "launcher":
return "islandRouteLauncher";
case "home":
return "islandRouteDash";
case "media":
return "islandRouteMedia";
case "weather":
return "islandRouteWeather";
case "wallpaper":
return "islandRouteWallpaper";
case "clipboard":
return "islandRouteClipboard";
}
return "";
}
function islandActivityRoutingMode(bc, activity) {
const key = islandRouteKey(activity);
const override = key ? islandSetting(bc, key) : "follow";
if (override === "island")
return "always";
if (override === "bar")
return "never";
return islandSharedRoutingMode(bc);
}
function sharedShortcutsFollowLastUsed(screen) {
return activeIslandConfigsForScreen(screen).some(cfg => islandSharedRoutingMode(cfg) === "last-used");
}
function sharedShortcutsOverridden(screen, activity) {
return activeIslandConfigsForScreen(screen).some(cfg => {
const mode = islandActivityRoutingMode(cfg, activity);
return mode === "always" || mode === "last-used";
});
}
function recordBarInteraction(screen, barId) {
const name = screen?.name;
if (!name || !barId || lastUsedBarByScreen[name] === barId || !sharedShortcutsFollowLastUsed(screen))
return;
lastUsedBarByScreen = Object.assign({}, lastUsedBarByScreen, {
[name]: barId
});
}
// A satellite carrying the activity's own widget is a standard destination on this screen, same as a bar.
function islandSatelliteHosts(cfg, activity) {
const widgetId = activity === "controlcenter" ? "controlCenterButton" : activity === "notificationcenter" ? "notificationButton" : "";
if (!widgetId || !isIslandBarConfig(cfg) || isDotBarConfig(cfg) || !islandSetting(cfg, "islandSatellitesEnabled"))
return false;
return ["leftWidgets", "centerWidgets", "rightWidgets"].some(key => (cfg[key] ?? []).some(entry => (typeof entry === "string" ? entry : entry?.id) === widgetId && (typeof entry === "string" || entry.enabled !== false)));
}
function sharedTriggerIslandConfig(screen, activity) {
const active = activeIslandConfigsForScreen(screen);
const configs = active.filter(cfg => islandActivityRoutingMode(cfg, activity) !== "never");
const key = islandRouteKey(activity);
const pinned = key ? configs.find(cfg => {
// Saved choices outrank defaults, including legacy dashboard-family pins.
const settings = islandSettings(cfg);
const override = settings[key] ?? (islandDashActivities.indexOf(activity) >= 0 ? settings.islandRouteDash : undefined);
return override === "island";
}) : null;
if (pinned)
return pinned;
if (configs.some(cfg => islandActivityRoutingMode(cfg, activity) === "last-used")) {
const lastId = lastUsedBarByScreen[screen?.name];
const lastIsland = configs.find(cfg => cfg.id === lastId);
if (lastIsland)
return lastIsland;
} else {
const fixed = configs.find(cfg => islandActivityRoutingMode(cfg, activity) === "always");
if (fixed)
return fixed;
}
if (getActiveBarEdgesForScreen(screen).length > 0 || active.some(cfg => islandSatelliteHosts(cfg, activity)))
return null;
return configs.find(cfg => !isDotBarConfig(cfg)) ?? configs[0] ?? null;
}
function islandLauncherHostConfig(screen) {
const config = sharedTriggerIslandConfig(screen, "launcher");
if (config || launcherStyle !== "island")
return config;
const configs = activeIslandConfigsForScreen(screen).filter(cfg => islandActivityRoutingMode(cfg, "launcher") !== "never");
return configs.find(cfg => !isDotBarConfig(cfg)) ?? configs[0] ?? null;
}
// "Always here" is exclusive per screen, or config order would silently pick the winner.
function setIslandSharedRouting(barId, mode) {
const configs = JSON.parse(JSON.stringify(barConfigs));
const target = configs.find(cfg => cfg.id === barId);
if (!target || !(isIslandBarConfig(target) || _islandWidgetLocation(target)))
return;
_writableIslandSettings(target).islandSharedRouting = mode;
if (mode === "always") {
for (const cfg of configs) {
if (cfg.id !== barId && hostsIsland(cfg) && islandSharedRoutingMode(cfg) === "always" && _sharesScreen(cfg, target))
delete _writableIslandSettings(cfg).islandSharedRouting;
}
}
_commitBarConfigs(configs);
}
// A hidden bar takes its island with it, so it must not keep swallowing popups and OSDs.
function activeIslandConfigsForScreen(screen) {
return ShellLayout.islandConfigs(screen).filter(cfg => cfg.visible !== false);
}
function islandConfigForEdge(screen, edge) {
return ShellLayout.edge(screen, edge)?.island ?? null;
}
function dankIslandHandlesNotifications(screen) {
return activeIslandConfigsForScreen(screen).some(cfg => !islandSetting(cfg, "islandNotificationPopups"));
}
function dankIslandHandlesSystemOsd(screen) {
return activeIslandConfigsForScreen(screen).some(cfg => islandSetting(cfg, "islandSystemOsd"));
}
function dankIslandOwnsEdge(screen, edge) {
return islandConfigForEdge(screen, edge) !== null;
}
function dankIslandEdgeOffset(screen, edge) {
return ShellLayout.edge(screen, edge)?.islandThickness ?? 0;
}
function getActiveBarEdgesForScreen(screen) {
return ShellLayout.barEdges(screen, false);
}
readonly property real frameBarContentGap: frameBarInsetPadding < 0 ? frameThickness : frameBarInsetPadding
readonly property real frameBarContentGapExtra: Math.max(0, frameBarContentGap - frameThickness)
function frameEdgeReservation(screen, edge) {
return ShellLayout.frameReservation(screen, edge);
}
function frameEdgeInsetForSide(screen, side) {
if (!frameEnabled)
return 0;
return frameEdgeReservation(screen, side);
}
function setMatugenScheme(scheme) {
var normalized = scheme || "scheme-tonal-spot";
if (matugenScheme === normalized)
return;
set("matugenScheme", normalized);
if (typeof Theme !== "undefined") {
Theme.generateSystemThemesFromCurrentTheme();
}
}
function setMatugenSmartMode(enabled) {
if (matugenSmartMode === enabled)
return;
set("matugenSmartMode", enabled);
}
function setMatugenSourceMode(mode) {
var normalized = mode || "dominant";
if (matugenSourceMode === normalized)
return;
// Regeneration comes from the regenSystemThemes onChange hook in
// SettingsSpec.js, which set() dispatches. matugenScheme above also
// calls Theme.generateSystemThemesFromCurrentTheme() directly, which is
// redundant with its own hook.
set("matugenSourceMode", normalized);
}
function setMatugenContrast(value) {
if (matugenContrast === value)
return;
set("matugenContrast", value);
}
function setMatugenSeedColor(hex) {
var normalized = /^#[0-9a-f]{6}$/i.test(hex || "") ? hex.toLowerCase() : "";
if (matugenSeedColor === normalized)
return;
set("matugenSeedColor", normalized);
}
function setMatugenSpec(spec) {
var normalized = spec === "2025" || spec === "dms" ? spec : "2021";
if (matugenSpec === normalized)
return;
if (normalized !== "2021") {
if (matugenContrast < 0)
set("matugenContrast", 0);
if (typeof Theme !== "undefined" && !Theme.getMatugenScheme(matugenScheme).spec2025)
set("matugenScheme", "scheme-tonal-spot");
}
set("matugenSpec", normalized);
}
function setMatugenTargetMonitor(monitorName) {
if (matugenTargetMonitor === monitorName)
return;
set("matugenTargetMonitor", monitorName);
if (typeof Theme !== "undefined") {
Theme.generateSystemThemesFromCurrentTheme();
}
}
function setCornerRadius(radius) {
set("radiusStrength", Shape.strengthFromRadius(radius));
}
function setWeatherLocation(displayName, coordinates) {
SessionData.setWeatherLocation(displayName, coordinates);
}
function setIconThemeForMode(themeName, light) {
if (light)
iconThemeLight = themeName;
else
iconThemeDark = themeName;
applyStoredIconTheme();
saveSettings();
if (typeof Theme !== "undefined" && Theme.currentTheme === Theme.dynamic)
Theme.generateSystemThemesFromCurrentTheme();
}
function setIconThemePerMode(enabled) {
iconThemePerMode = enabled;
applyStoredIconTheme();
saveSettings();
if (typeof Theme !== "undefined" && Theme.currentTheme === Theme.dynamic)
Theme.generateSystemThemesFromCurrentTheme();
}
function setCursorTheme(themeName) {
const updated = JSON.parse(JSON.stringify(cursorSettings));
if (updated.theme === themeName)
return;
updated.theme = themeName;
cursorSettings = updated;
saveSettings();
updateXResources();
updateCompositorCursor();
}
function setCursorSize(size) {
const updated = JSON.parse(JSON.stringify(cursorSettings));
if (updated.size === size)
return;
updated.size = size;
cursorSettings = updated;
saveSettings();
updateXResources();
updateCompositorCursor();
}
// This solution for xwayland cursor themes is from the xwls discussion:
// https://github.com/Supreeeme/xwayland-satellite/issues/104
// no idea if this matters on other compositors but we also set XCURSOR stuff in the launcher
function updateCompositorCursor() {
compositorCursorRefreshNeeded();
}
function updateXResources() {
const homeDir = Paths.strip(StandardPaths.writableLocation(StandardPaths.HomeLocation));
const xresourcesPath = homeDir + "/.Xresources";
const themeName = cursorSettings.theme === "System Default" ? systemDefaultCursorTheme : cursorSettings.theme;
const size = cursorSettings.size || 24;
if (!themeName)
return;
const script = `
xresources_file="${xresourcesPath}"
[ -f "$xresources_file" ] && [ ! -w "$xresources_file" ] && exit 0
theme_name="${themeName}"
cursor_size="${size}"
current_theme=""
current_size=""
if [ -f "$xresources_file" ]; then
current_theme=$(grep -E '^[[:space:]]*Xcursor\\.theme:' "$xresources_file" 2>/dev/null | sed 's/.*:[[:space:]]*//' | head -1)
current_size=$(grep -E '^[[:space:]]*Xcursor\\.size:' "$xresources_file" 2>/dev/null | sed 's/.*:[[:space:]]*//' | head -1)
fi
[ "$current_theme" = "$theme_name" ] && [ "$current_size" = "$cursor_size" ] && exit 0
if [ -f "$xresources_file" ]; then
cp "$xresources_file" "\${xresources_file}.backup$(date +%s)"
fi
temp_file="\${xresources_file}.tmp.$$"
if [ -f "$xresources_file" ]; then
grep -v '^[[:space:]]*Xcursor\\.theme:' "$xresources_file" | grep -v '^[[:space:]]*Xcursor\\.size:' > "$temp_file" 2>/dev/null || true
else
touch "$temp_file"
fi
echo "Xcursor.theme: $theme_name" >> "$temp_file"
echo "Xcursor.size: $cursor_size" >> "$temp_file"
mv "$temp_file" "$xresources_file"
xrdb -merge "$xresources_file" 2>/dev/null || true
`;
Quickshell.execDetached(["sh", "-c", script]);
}
function getCursorEnvironment() {
const isSystemDefault = cursorSettings.theme === "System Default";
const isDefaultSize = !cursorSettings.size || cursorSettings.size === 24;
const themeName = isSystemDefault ? "" : cursorSettings.theme;
const size = String(cursorSettings.size || 24);
const env = {};
// Started from systemd the shell inherits no XCURSOR_SIZE from the compositor
// XWayland children would size their cursor from the display instead
if (!isDefaultSize || !Quickshell.env("XCURSOR_SIZE")) {
env["XCURSOR_SIZE"] = size;
env["HYPRCURSOR_SIZE"] = size;
}
if (themeName) {
env["XCURSOR_THEME"] = themeName;
env["HYPRCURSOR_THEME"] = themeName;
}
return env;
}
function setDankBarLeftWidgets(order) {
const defaultBar = getPrimaryBarConfig();
if (defaultBar) {
updateBarConfig(defaultBar.id, {
"leftWidgets": order
});
updateListModel(leftWidgetsModel, order);
}
}
function setDankBarCenterWidgets(order) {
const defaultBar = getPrimaryBarConfig();
if (defaultBar) {
updateBarConfig(defaultBar.id, {
"centerWidgets": order
});
updateListModel(centerWidgetsModel, order);
}
}
function setDankBarRightWidgets(order) {
const defaultBar = getPrimaryBarConfig();
if (defaultBar) {
updateBarConfig(defaultBar.id, {
"rightWidgets": order
});
updateListModel(rightWidgetsModel, order);
}
}
function setWorkspaceNameIcon(workspaceName, iconData) {
var iconMap = JSON.parse(JSON.stringify(workspaceNameIcons));
iconMap[workspaceName] = iconData;
workspaceNameIcons = iconMap;
saveSettings();
workspaceIconsUpdated();
}
function removeWorkspaceNameIcon(workspaceName) {
var iconMap = JSON.parse(JSON.stringify(workspaceNameIcons));
delete iconMap[workspaceName];
workspaceNameIcons = iconMap;
saveSettings();
workspaceIconsUpdated();
}
function getWorkspaceNameIcon(workspaceName) {
return workspaceNameIcons[workspaceName] || null;
}
function addAppIdSubstitution(pattern, replacement, type) {
var subs = JSON.parse(JSON.stringify(appIdSubstitutions));
subs.push({
pattern: pattern,
replacement: replacement,
type: type
});
appIdSubstitutions = subs;
saveSettings();
}
function updateAppIdSubstitution(index, pattern, replacement, type) {
var subs = JSON.parse(JSON.stringify(appIdSubstitutions));
if (index < 0 || index >= subs.length)
return;
subs[index] = {
pattern: pattern,
replacement: replacement,
type: type
};
appIdSubstitutions = subs;
saveSettings();
}
function removeAppIdSubstitution(index) {
var subs = JSON.parse(JSON.stringify(appIdSubstitutions));
if (index < 0 || index >= subs.length)
return;
subs.splice(index, 1);
appIdSubstitutions = subs;
saveSettings();
}
function addAppIdToList(identity: string, appList: list<string>): list<string> {
identity = identity ?? "";
appList = appList ?? [];
if (!identity)
return appList;
var normalizedIdentity = Paths.normalizeAppId(identity);
if (!normalizedIdentity)
return appList;
var cleanList = appList.map(id => id ? Paths.normalizeAppId(id) : "").filter(id => id !== "");
if (cleanList.includes(normalizedIdentity))
return cleanList;
cleanList.push(normalizedIdentity);
return cleanList;
}
function removeAppIdFromList(index: int, appList: list<string>): list<string> {
var moddedList = appList ? appList.slice() : [];
if (index < 0 || index >= moddedList.length)
return moddedList;
moddedList.splice(index, 1);
return moddedList;
}
function addNightModeExcludedApp(identity: string) {
var newList = addAppIdToList(identity, nightModeExcludedApps);
nightModeExcludedApps = newList;
saveSettings();
}
function removeNightModeExcludedApp(index: int) {
var newList = removeAppIdFromList(index, nightModeExcludedApps);
nightModeExcludedApps = newList;
saveSettings();
}
function addMediaExcludePlayer(identity) {
var newList = addAppIdToList(identity, mediaExcludePlayers);
mediaExcludePlayers = newList;
saveSettings();
}
function removeMediaExcludePlayer(index) {
var newList = removeAppIdFromList(index, mediaExcludePlayers);
mediaExcludePlayers = newList;
saveSettings();
}
property var pendingNotificationRule: null
function _newNotificationRule(overrides) {
return Object.assign({
enabled: true,
field: "appName",
pattern: "",
matchType: "contains",
action: "default",
urgency: "default",
bypassDnd: false
}, overrides || {});
}
function addNotificationRule(ruleData) {
var rules = JSON.parse(JSON.stringify(notificationRules || []));
rules.push(_newNotificationRule(ruleData));
notificationRules = rules;
saveSettings();
}
function requestNotificationRuleForNotification(appName, desktopEntry) {
const pattern = desktopEntry || appName || "";
pendingNotificationRule = _newNotificationRule(pattern ? {
field: desktopEntry ? "desktopEntry" : "appName",
pattern: pattern,
matchType: "exact"
} : {});
}
function _isMuteRule(rule) {
return (rule.action || "").toString().toLowerCase() === "mute";
}
function _isDndBypassRule(rule) {
return rule.bypassDnd === true;
}
// Timed mute rules carry an expiresAt timestamp (ms since epoch); 0 or
// absent means the mute never expires. Only the mute lapses: a DND bypass
// or urgency on the same rule stays in force, and
// pruneExpiredNotificationRules() clears the lapsed mute from the
// persisted rule. nowMs is optional (defaults to the current time);
// reactive callers pass NotificationService.notificationRuleNowMs so
// their bindings refresh as rules expire instead of freezing on a stale
// Date.now().
function isNotificationRuleExpired(rule, nowMs) {
return RuleExpiry.isRuleExpired(rule, nowMs);
}
// True while any rule carries an expiry timestamp. Gates the expiry
// clock and sweeper in NotificationService so they only run when a
// timed rule can actually expire.
readonly property bool hasTimedNotificationRules: RuleExpiry.hasTimedRule(notificationRules)
function _appRuleIndex(rules, appName, desktopEntry, predicate) {
const app = (appName || "").toString().toLowerCase();
const desktop = (desktopEntry || "").toString().toLowerCase();
if (!app && !desktop)
return -1;
return rules.findIndex(rule => {
if (!predicate(rule) || (rule.matchType || "contains").toString().toLowerCase() !== "exact")
return false;
const pattern = (rule.pattern || "").toString().toLowerCase();
const value = !rule.field || rule.field === "appName" ? app : rule.field === "desktopEntry" ? desktop : "";
return pattern !== "" && pattern === value;
});
}
function _addAppRule(appName, desktopEntry, overrides) {
const pattern = desktopEntry || appName || "";
if (!pattern)
return;
var rules = JSON.parse(JSON.stringify(notificationRules || []));
const rule = _newNotificationRule(Object.assign({
field: desktopEntry ? "desktopEntry" : "appName",
pattern: pattern,
matchType: "exact"
}, overrides));
if (!rule.expiresAt)
delete rule.expiresAt;
rules.push(rule);
notificationRules = rules;
saveSettings();
}
function _hasNoAction(rule) {
return (rule.action || "default").toString().toLowerCase() === "default";
}
function _isNoopRule(rule) {
return _hasNoAction(rule) && (rule.urgency || "default").toString().toLowerCase() === "default" && !_isDndBypassRule(rule);
}
// Edits the first enabled matching rule and drops it once it no longer does anything.
function _updateAppRule(appName, desktopEntry, predicate, changes) {
var rules = JSON.parse(JSON.stringify(notificationRules || []));
const index = _appRuleIndex(rules, appName, desktopEntry, rule => rule.enabled !== false && predicate(rule));
if (index === -1)
return false;
const rule = Object.assign(rules[index], changes);
if (!rule.expiresAt)
delete rule.expiresAt;
if (_isNoopRule(rule))
rules.splice(index, 1);
notificationRules = rules;
saveSettings();
return true;
}
function addMuteRuleForApp(appName, desktopEntry, expiresAt) {
// Re-muting edits the app's existing no-action or mute rule (including an
// expired one not yet swept) so duplicates never accumulate.
const changes = {
action: "mute",
expiresAt: expiresAt || 0
};
if (!_updateAppRule(appName, desktopEntry, rule => _hasNoAction(rule) || _isMuteRule(rule), changes))
_addAppRule(appName, desktopEntry, changes);
}
function isAppMuted(appName, desktopEntry, nowMs) {
return _appRuleIndex(notificationRules || [], appName, desktopEntry, rule => rule.enabled !== false && _isMuteRule(rule) && !isNotificationRuleExpired(rule, nowMs)) !== -1;
}
// ExpiresAt of the active mute rule for an app (ms since epoch),
// or 0 when the app is not muted or the mute is permanent.
function muteExpiresAt(appName, desktopEntry, nowMs) {
const rules = notificationRules || [];
const index = _appRuleIndex(rules, appName, desktopEntry, rule => rule.enabled !== false && _isMuteRule(rule) && !isNotificationRuleExpired(rule, nowMs));
return index === -1 ? 0 : (rules[index].expiresAt || 0);
}
// Clears lapsed timed mutes from the persisted list so the settings UI
// stays truthful. Only the mute goes; the rule is dropped once nothing
// else is left on it. The idle sweep must stay cheap, so nothing is
// copied unless a rule actually expired.
function pruneExpiredNotificationRules() {
const rules = notificationRules || [];
if (!rules.some(rule => isNotificationRuleExpired(rule)))
return;
const kept = [];
for (const source of rules) {
if (!isNotificationRuleExpired(source)) {
kept.push(source);
continue;
}
const rule = Object.assign({}, source, {
action: "default"
});
delete rule.expiresAt;
if (!_isNoopRule(rule))
kept.push(rule);
}
notificationRules = kept;
saveSettings();
}
function removeMuteRuleForApp(appName, desktopEntry) {
_updateAppRule(appName, desktopEntry, _isMuteRule, {
action: "default",
expiresAt: 0
});
}
function isAppDndBypassed(appName, desktopEntry) {
return _appRuleIndex(notificationRules || [], appName, desktopEntry, rule => rule.enabled !== false && _isDndBypassRule(rule)) !== -1;
}
function setAppDndBypass(appName, desktopEntry, enabled) {
if (!enabled) {
_updateAppRule(appName, desktopEntry, _isDndBypassRule, {
bypassDnd: false
});
return;
}
if (isAppDndBypassed(appName, desktopEntry))
return;
if (!_updateAppRule(appName, desktopEntry, () => true, {
bypassDnd: true
}))
_addAppRule(appName, desktopEntry, {
bypassDnd: true
});
}
function updateNotificationRule(index, ruleData) {
var rules = JSON.parse(JSON.stringify(notificationRules || []));
if (index < 0 || index >= rules.length)
return;
var existing = rules[index] || {};
rules[index] = Object.assign({}, existing, ruleData || {});
notificationRules = rules;
saveSettings();
}
function updateNotificationRuleField(index, key, value) {
if (key === undefined || key === null || key === "")
return;
var patch = {};
patch[key] = value;
updateNotificationRule(index, patch);
}
function removeNotificationRule(index) {
var rules = JSON.parse(JSON.stringify(notificationRules || []));
if (index < 0 || index >= rules.length)
return;
rules.splice(index, 1);
notificationRules = rules;
saveSettings();
}
function getDefaultNotificationRules() {
return Spec.SPEC.notificationRules.def;
}
function resetNotificationRules() {
notificationRules = JSON.parse(JSON.stringify(Spec.SPEC.notificationRules.def));
saveSettings();
}
function getDefaultAppIdSubstitutions() {
return Spec.SPEC.appIdSubstitutions.def;
}
function resetAppIdSubstitutions() {
appIdSubstitutions = JSON.parse(JSON.stringify(Spec.SPEC.appIdSubstitutions.def));
saveSettings();
}
function getRegistryThemeVariant(themeId, defaultVariant) {
var stored = registryThemeVariants[themeId];
if (typeof stored === "string")
return stored || defaultVariant || "";
return defaultVariant || "";
}
function setRegistryThemeVariant(themeId, variantId) {
var variants = JSON.parse(JSON.stringify(registryThemeVariants));
variants[themeId] = variantId;
registryThemeVariants = variants;
saveSettings();
if (typeof Theme !== "undefined")
Theme.reloadCustomThemeVariant();
}
function getRegistryThemeMultiVariant(themeId, defaults, mode) {
var stored = registryThemeVariants[themeId];
if (!stored || typeof stored !== "object")
return defaults || {};
if ((stored.dark && typeof stored.dark === "object") || (stored.light && typeof stored.light === "object")) {
if (!mode)
return stored.dark || stored.light || defaults || {};
var modeData = stored[mode];
if (modeData && typeof modeData === "object")
return modeData;
return defaults || {};
}
return stored;
}
function setRegistryThemeMultiVariant(themeId, flavor, accent, mode) {
var variants = JSON.parse(JSON.stringify(registryThemeVariants));
var existing = variants[themeId];
var perMode = {};
if (existing && typeof existing === "object") {
if ((existing.dark && typeof existing.dark === "object") || (existing.light && typeof existing.light === "object")) {
perMode = existing;
} else if (typeof existing.flavor === "string") {
perMode.dark = {
flavor: existing.flavor,
accent: existing.accent || ""
};
}
}
perMode[mode || "dark"] = {
flavor: flavor,
accent: accent
};
variants[themeId] = perMode;
registryThemeVariants = variants;
saveSettings();
if (typeof Theme !== "undefined")
Theme.reloadCustomThemeVariant();
}
function getPluginSetting(pluginId, key, defaultValue) {
if (!pluginSettings[pluginId]) {
return defaultValue;
}
return pluginSettings[pluginId][key] !== undefined ? pluginSettings[pluginId][key] : defaultValue;
}
function setPluginSetting(pluginId, key, value) {
const updated = JSON.parse(JSON.stringify(pluginSettings));
if (!updated[pluginId]) {
updated[pluginId] = {};
}
updated[pluginId][key] = value;
pluginSettings = updated;
savePluginSettings();
}
function getPluginSettingsForPlugin(pluginId) {
const settings = pluginSettings[pluginId];
return settings ? JSON.parse(JSON.stringify(settings)) : {};
}
function removeDisplayProfile(compositor, profileId) {
if (!displayProfiles[compositor] || !displayProfiles[compositor][profileId])
return;
const updated = JSON.parse(JSON.stringify(displayProfiles));
delete updated[compositor][profileId];
displayProfiles = updated;
saveSettings();
}
function setDisplayPreviousRefreshModes(compositor, modes) {
if (JSON.stringify(displayPreviousRefreshModes[compositor] || {}) === JSON.stringify(modes || {}))
return;
const updated = JSON.parse(JSON.stringify(displayPreviousRefreshModes));
if (Object.keys(modes || {}).length > 0)
updated[compositor] = modes;
else
delete updated[compositor];
displayPreviousRefreshModes = updated;
saveSettings();
}
ListModel {
id: leftWidgetsModel
}
ListModel {
id: centerWidgetsModel
}
ListModel {
id: rightWidgetsModel
}
component SettingsFile: QtObject {
id: settingsFile
required property string filePath
property var settings: ({})
property bool isLoading: false
property bool hasLoaded: false
property bool hasParseFailed: false
property bool fileUnsavedUserChanges: false
property bool isFileReadOnly: false
property bool selfWrite: false
function setSettings(newSettings, userChanges) {
const newSettingsJson = JSON.stringify(newSettings, null, 2);
if (JSON.stringify(settings, null, 2) === newSettingsJson) {
return;
}
settings = newSettings;
fileUnsavedUserChanges = fileUnsavedUserChanges || userChanges;
selfWrite = true;
settingsFileView.setText(newSettingsJson);
}
function getSettings() {
return Object.assign({}, settings);
}
property Timer timer: Timer {
id: settingsFileReloadDebounce
interval: 50
onTriggered: settingsFileView.reload()
repeat: false
}
property FileView fileView: FileView {
id: settingsFileView
path: isGreeterMode ? "" : filePath
blockLoading: false
blockWrites: true
atomicWrites: true
watchChanges: !isGreeterMode
onFileChanged: {
if (selfWrite) {
selfWrite = false;
} else {
isLoading = true;
_loading = true;
settingsFileReloadDebounce.restart();
}
}
onLoaded: {
if (isGreeterMode) {
return;
}
isLoading = true;
_loading = true;
const hadParseFailed = hasParseFailed;
hasParseFailed = false;
fileUnsavedUserChanges = false;
if (unsavedUserChanges) {
unsavedUserChanges = _anySettingsFile(file => file.fileUnsavedUserChanges);
}
const fileName = filePath?.split("/").pop();
try {
let txt = settingsFileView.text();
if (!txt || !txt.trim()) {
txt = "{}";
}
settings = JSON.parse(txt);
hasLoaded = true;
} catch (error) {
hasParseFailed = true;
_parseError = true;
const msg = error.message;
log.error(`Failed to reload ${fileName} - file will not be overwritten. Error:`, msg);
Qt.callLater(() => ToastService.showError(I18n.tr("Failed to parse %1").arg(fileName), msg));
} finally {
isLoading = false;
if (hadParseFailed && !hasParseFailed) {
_parseError = _anySettingsFile(file => file.hasParseFailed);
}
_tryCompleteLoading();
_loadSettingsOrStartIfReady();
}
}
onLoadFailed: error => {
if (isGreeterMode) {
return;
}
isLoading = false;
_loading = _anySettingsFile(file => file.isLoading);
if (error === FileViewError.FileNotFound) {
// Fake that the file has been loaded so that it gets created after a save.
hasLoaded = true;
}
applyStoredTheme();
_tryCompleteLoading();
_loadSettingsOrStartIfReady();
}
onSaved: {
isFileReadOnly = false;
isReadOnly = isReadOnly ? _anySettingsFile(file => file.isFileReadOnly) : false;
fileUnsavedUserChanges = false;
if (unsavedUserChanges) {
unsavedUserChanges = _anySettingsFile(file => file.fileUnsavedUserChanges);
}
}
onSaveFailed: error => {
selfWrite = false;
if (error === FileViewError.PermissionDenied) {
isFileReadOnly = true;
isReadOnly = true;
}
if (fileUnsavedUserChanges) {
const fileName = filePath?.split("/").pop() || "unknown";
log.warn(`Failed to save ${fileName}`);
}
}
}
}
enum Stage {
Discovering = 0,
Loading = 1,
Partial = 2,
Ready = 3
}
property int _settingsStage: SettingsData.Stage.Discovering
property var _settingsFiles: new Map()
property var _settingsFilesPaths: ([])
function _registerSettingsFile(file) {
const filePath = file.filePath;
if (_settingsFiles.has(filePath)) {
return;
}
let index;
if (file === defaultSettingsFile) {
index = 0;
} else {
index = _settingsFilesPaths.findIndex((path, i) => {
return path > filePath && i > 0;
});
}
_settingsFiles.set(filePath, file);
if (index >= 0) {
_settingsFilesPaths.splice(index, 0, filePath);
} else {
_settingsFilesPaths.push(filePath);
}
_tryCompleteDiscovery();
}
function _unregisterSettingsFile(file) {
_settingsFiles.delete(file.filePath);
_settingsFilesPaths = _settingsFilesPaths.filter(path => path != file.filePath);
}
function _tryCompleteDiscovery() {
if (_settingsStage !== SettingsData.Stage.Discovering || !_settingsFolderChecked) {
return;
}
let expectedCount = 1;
if (_settingsFolderExists) {
expectedCount += settingsFolderLoader.item.count;
}
if (_settingsFilesPaths.length == expectedCount) {
_settingsStage = SettingsData.Stage.Loading;
_tryCompleteLoading();
_loadSettingsOrStartIfReady();
}
}
function _tryCompleteLoading() {
if (_settingsStage !== SettingsData.Stage.Loading) {
return;
}
if (_everySettingsFile(file => file.hasLoaded || file.hasParseFailed)) {
_settingsStage = _parseError ? SettingsData.Stage.Partial : SettingsData.Stage.Ready;
}
}
function _loadSettingsOrStartIfReady() {
if (_settingsStage < SettingsData.Stage.Partial || _anySettingsFile(file => file.isLoading)) {
return;
}
let unsaved;
_loading = true;
if (!_hasLoaded || _settingsStage === SettingsData.Stage.Partial) {
unsaved = _runStartSequence();
} else {
unsaved = _loadSettings();
}
_loading = false;
if (unsaved) {
_saveSettings(false);
}
}
function _getSettingsObjectFromFiles() {
const settingsObject = {};
for (const path of _settingsFilesPaths) {
const settingsFile = _settingsFiles.get(path);
Object.assign(settingsObject, settingsFile.getSettings());
}
return settingsObject;
}
function _anySettingsFile(predicate) {
for (const file of _settingsFiles.values()) {
if (predicate(file)) {
return true;
}
}
return false;
}
function _everySettingsFile(predicate) {
for (const file of _settingsFiles.values()) {
if (!predicate(file)) {
return false;
}
}
return true;
}
SettingsFile {
id: defaultSettingsFile
filePath: StandardPaths.writableLocation(StandardPaths.ConfigLocation) + "/DankMaterialShell/settings.json"
Component.onCompleted: {
_registerSettingsFile(defaultSettingsFile);
}
}
property bool _settingsFolderExists: false
property bool _settingsFolderChecked: isGreeterMode
function _syncSettingsFilesModels() {
if (!_settingsFolderChecked) {
settingsFilesModelSyncDebounce.restart();
return;
}
if (!_settingsFolderExists) {
return;
}
const listModel = settingsFilesListModel;
const folderModel = settingsFolderLoader.item;
const folderPathsSet = new Set();
for (let i = 0; i < folderModel.count; i++) {
const filePath = folderModel.get(i, "filePath");
folderPathsSet.add(filePath);
if (!_settingsFiles.has(filePath)) {
listModel.append({
filePath
});
}
}
for (let i = listModel.count - 1; i >= 0; i--) {
const filePath = listModel.get(i).filePath;
if (!folderPathsSet.has(filePath)) {
listModel.remove(i);
}
}
}
Timer {
id: settingsFilesModelSyncDebounce
interval: 50
repeat: false
running: false
onTriggered: _syncSettingsFilesModels()
}
ListModel {
id: settingsFilesListModel
}
Loader {
id: settingsFolderLoader
active: !isGreeterMode
function unloadModel() {
active = false;
}
sourceComponent: FolderListModel {
id: settingsFolderModel
property url dir: StandardPaths.writableLocation(StandardPaths.ConfigLocation) + "/DankMaterialShell/config.d"
folder: dir
showDirs: false
nameFilters: ["*.json"]
onStatusChanged: {
// Folder gets reset to the CWD if the directory doesn't exist.
_settingsFolderExists = folder == dir;
if (status !== FolderListModel.Ready) {
return;
}
_settingsFolderChecked = true;
if (!_settingsFolderExists) {
Qt.callLater(() => settingsFolderLoader.unloadModel());
}
if (_hasLoaded) {
settingsFilesModelSyncDebounce.restart();
} else {
_syncSettingsFilesModels();
}
_tryCompleteDiscovery();
}
}
}
Instantiator {
id: settingsLoader
model: settingsFilesListModel
onObjectAdded: (_, file) => {
_registerSettingsFile(file);
}
onObjectRemoved: (_, file) => {
_unregisterSettingsFile(file);
_loadSettingsOrStartIfReady();
}
delegate: SettingsFile {
id: settingsFile
}
}
readonly property string _greeterCacheDir: Quickshell.env("DMS_GREET_CFG_DIR") || "/var/cache/dms-greeter"
property string greeterSettingsBaseDir: root._greeterCacheDir
function setGreeterSettingsBaseDir(dir) {
const next = dir || root._greeterCacheDir;
if (greeterSettingsBaseDir === next)
return;
greeterSettingsBaseDir = next;
if (isGreeterMode)
greeterSettingsFile.reload();
}
function resetGreeterSettingsBaseDir() {
setGreeterSettingsBaseDir(root._greeterCacheDir);
}
function loadGreeterSettings(txt) {
_loading = true;
_hasLoaded = false;
try {
Store.parse(root, (txt && txt.trim()) ? JSON.parse(txt) : {});
_parseError = false;
} catch (e) {
_parseError = true;
log.error("Failed to parse greeter settings.json:", e.message);
Store.parse(root, {});
} finally {
_loading = false;
}
_hasLoaded = true;
applyStoredTheme();
}
FileView {
id: greeterSettingsFile
path: isGreeterMode && root.greeterSettingsBaseDir ? (root.greeterSettingsBaseDir + "/settings.json") : ""
preload: isGreeterMode
blockLoading: false
blockWrites: true
watchChanges: false
printErrors: false
onLoaded: {
if (isGreeterMode)
loadGreeterSettings(greeterSettingsFile.text());
}
onLoadFailed: {
if (isGreeterMode)
loadGreeterSettings("");
}
}
FileView {
id: pluginSettingsFile
path: isGreeterMode ? "" : pluginSettingsPath
blockLoading: true
blockWrites: true
atomicWrites: true
printErrors: false
watchChanges: !isGreeterMode
onLoaded: {
if (isGreeterMode)
return;
pluginSettingsFileExists = true;
parsePluginSettings(pluginSettingsFile.text());
}
onLoadFailed: error => {
if (isGreeterMode)
return;
const msg = String(error || "");
const missing = _isMissingPluginSettingsError(error);
pluginSettingsFileExists = !missing;
if (!missing)
log.warn("Failed to load plugin_settings.json. Error:", msg);
_resetPluginSettings();
}
}
Timer {
id: settingsSaveDebounce
interval: 50
repeat: false
running: false
onTriggered: _saveSettings(true)
}
property bool pluginSettingsFileExists: false
}