Files
DankMaterialShell/quickshell/Common/SettingsData.qml
T
2026-09-28 14:26:42 -04:00

3568 lines
142 KiB
QML

pragma Singleton
pragma ComponentBehavior: Bound
import QtCore
import QtQuick
import "../DankCommon/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 "settings/SettingsSpec.js" as Spec
import "settings/SettingsStore.js" as Store
import "../DankCommon/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: 34
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 _hasUnsavedChanges: false
property bool _selfWrite: false
property var _loadedSettingsSnapshot: null
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 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 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 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 bool mangoTrackpadNaturalScrolling: Spec.SPEC.mangoTrackpadNaturalScrolling.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 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 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 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)
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()
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();
}
function _frameBarConfig() {
return barConfigs.find(bc => bc.enabled !== false && !isIslandBarConfig(bc));
}
readonly property real frameSurfaceOpacity: {
barConfigs;
return barTransparency(_frameBarConfig());
}
readonly property color frameSurfaceBase: {
barConfigs;
return 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 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 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": "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()
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 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 lockScreenShowSystemIcons: Spec.SPEC.lockScreenShowSystemIcons.def
property bool lockScreenShowTime: Spec.SPEC.lockScreenShowTime.def
property string lockScreenClockStyle: Spec.SPEC.lockScreenClockStyle.def
property bool lockScreenShowDate: Spec.SPEC.lockScreenShowDate.def
property bool lockScreenShowProfileImage: Spec.SPEC.lockScreenShowProfileImage.def
property bool lockScreenShowPasswordField: Spec.SPEC.lockScreenShowPasswordField.def
property bool lockScreenShowMediaPlayer: Spec.SPEC.lockScreenShowMediaPlayer.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
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
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 int lockScreenNotificationMode: Spec.SPEC.lockScreenNotificationMode.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))
// 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: ["media", "clock", "weather", "status", "volume", "brightness", "notifications"]
readonly property var _islandHomeLayoutDefault: [
{
"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
}
]
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 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 updaterIncludeFlatpak: Spec.SPEC.updaterIncludeFlatpak.def
property bool updaterAllowAUR: Spec.SPEC.updaterAllowAUR.def
property bool updaterReopenAfterUpgrade: Spec.SPEC.updaterReopenAfterUpgrade.def
property var updaterIgnoredPackages: Spec.SPEC.updaterIgnoredPackages.def
property string displayNameMode: Spec.SPEC.displayNameMode.def
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
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 createDesktopWidgetInstance(widgetType, name, config) {
const id = "dw_" + Date.now() + "_" + Math.random().toString(36).substr(2, 9);
const instance = {
id: id,
widgetType: widgetType,
name: name || widgetType,
enabled: true,
config: config || {}
};
const instances = JSON.parse(JSON.stringify(desktopWidgetInstances || []));
instances.push(instance);
desktopWidgetInstances = instances;
saveSettings();
return instance;
}
function updateDesktopWidgetInstance(instanceId, updates) {
const instances = JSON.parse(JSON.stringify(desktopWidgetInstances || []));
const idx = instances.findIndex(inst => inst.id === instanceId);
if (idx === -1)
return;
Object.assign(instances[idx], updates);
desktopWidgetInstances = instances;
saveSettings();
}
function updateDesktopWidgetInstanceConfig(instanceId, configUpdates) {
const instances = JSON.parse(JSON.stringify(desktopWidgetInstances || []));
const idx = instances.findIndex(inst => inst.id === instanceId);
if (idx === -1)
return;
instances[idx].config = Object.assign({}, instances[idx].config || {}, configUpdates);
desktopWidgetInstances = instances;
saveSettings();
}
function removeDesktopWidgetInstance(instanceId) {
const instances = (desktopWidgetInstances || []).filter(inst => inst.id !== instanceId);
desktopWidgetInstances = instances;
SessionData.removeDesktopWidgetInstancePositions(instanceId);
saveSettings();
}
function duplicateDesktopWidgetInstance(instanceId) {
const source = getDesktopWidgetInstance(instanceId);
if (!source)
return null;
const newId = "dw_" + Date.now() + "_" + Math.random().toString(36).substr(2, 9);
const instance = {
id: newId,
widgetType: source.widgetType,
name: source.name + " (Copy)",
enabled: source.enabled,
config: JSON.parse(JSON.stringify(source.config || {}))
};
const instances = JSON.parse(JSON.stringify(desktopWidgetInstances || []));
instances.push(instance);
desktopWidgetInstances = instances;
saveSettings();
return instance;
}
function getDesktopWidgetInstance(instanceId) {
return (desktopWidgetInstances || []).find(inst => inst.id === instanceId) || 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 compositorInputRefreshNeeded
signal compositorCursorRefreshNeeded
signal notificationPopupsInvalidated
function refreshAuthAvailability() {
if (isGreeterMode)
return;
Processes.detectAuthCapabilities();
}
Component.onCompleted: {
if (isGreeterMode)
return;
Processes.settingsRoot = root;
loadSettings();
initializeListModels();
refreshAuthAvailability();
Processes.checkPluginSettings();
}
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 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: 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 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();
}
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,
"applyStoredIconTheme": applyStoredIconTheme,
"updateBarConfigs": updateBarConfigs,
"updateCompositorCursor": updateCompositorCursor,
"scheduleAuthApply": scheduleAuthApply,
"scheduleGreeterAutoLoginSync": scheduleGreeterAutoLoginSync,
"markGreeterSyncPending": markGreeterSyncPending
})
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 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() {
_loading = true;
_parseError = false;
_hasUnsavedChanges = false;
_pendingMigration = null;
try {
const txt = settingsFile.text();
let obj = (txt && txt.trim()) ? JSON.parse(txt) : null;
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;
if (oldVersion < settingsConfigVersion) {
const migrated = Store.migrateToVersion(obj, settingsConfigVersion);
if (migrated) {
_pendingMigration = migrated;
obj = migrated;
}
}
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;
}
Store.parse(root, obj);
// set() enforces this pair, but a hand-edited settings.json bypasses 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();
}
_loadedSettingsSnapshot = JSON.stringify(Store.toJson(root));
_hasLoaded = true;
_mergeSessionState();
applyStoredTheme();
updateCompositorCursor();
Qt.callLater(checkIconThemeDrift);
_checkSettingsWritable();
} catch (e) {
_parseError = true;
const msg = e.message;
log.error("Failed to parse settings.json - file will not be overwritten. Error:", msg);
Qt.callLater(() => ToastService.showError(I18n.tr("Failed to parse %1", "error toast, %1 is a settings file name").arg("settings.json"), msg));
applyStoredTheme();
} finally {
_loading = false;
}
loadPluginSettings();
Qt.callLater(() => _reconcileConnectedFrameBarStyles());
}
property var _pendingMigration: null
function _mergeSessionState() {
if (!_hasLoaded || !SessionData._hasLoaded)
return;
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 _checkSettingsWritable() {
settingsWritableCheckProcess.running = true;
}
function _onWritableCheckComplete(writable) {
const wasReadOnly = _isReadOnly;
_isReadOnly = !writable;
if (_isReadOnly) {
_hasUnsavedChanges = _checkForUnsavedChanges();
if (!wasReadOnly)
log.info("settings.json is now read-only");
} else {
_loadedSettingsSnapshot = JSON.stringify(Store.toJson(root));
_hasUnsavedChanges = false;
if (wasReadOnly)
log.info("settings.json is now writable");
if (_pendingMigration) {
_selfWrite = true;
settingsFile.setText(JSON.stringify(_pendingMigration, null, 2));
}
}
_pendingMigration = null;
}
function _checkForUnsavedChanges() {
if (!_hasLoaded || !_loadedSettingsSnapshot)
return false;
const current = JSON.stringify(Store.toJson(root));
return current !== _loadedSettingsSnapshot;
}
function getCurrentSettingsJson() {
return JSON.stringify(Store.toJson(root), 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 saveSettings() {
if (isGreeterMode || _loading || _parseError || !_hasLoaded)
return;
const json = JSON.stringify(Store.toJson(root), null, 2);
if (json === settingsFile.text())
return;
_selfWrite = true;
settingsFile.setText(json);
if (_isReadOnly)
_checkSettingsWritable();
}
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 _hasConnectedFrameBarStyleBackups() {
return connectedFrameBarStyleBackups && Object.keys(connectedFrameBarStyleBackups).length > 0;
}
function _captureConnectedFrameBarStyleBackups(configs, overwriteExisting) {
if (!Array.isArray(configs))
return;
const nextBackups = JSON.parse(JSON.stringify(connectedFrameBarStyleBackups || {}));
const validIds = {};
let changed = false;
for (let i = 0; i < configs.length; i++) {
const config = configs[i];
if (!config?.id)
continue;
validIds[config.id] = true;
if (!overwriteExisting && nextBackups[config.id] !== undefined)
continue;
const snapshot = _connectedFrameBarStyleSnapshot(config);
if (JSON.stringify(nextBackups[config.id]) !== JSON.stringify(snapshot)) {
nextBackups[config.id] = snapshot;
changed = true;
}
}
if (overwriteExisting) {
for (const barId in nextBackups) {
if (validIds[barId])
continue;
delete nextBackups[barId];
changed = true;
}
}
if (changed)
connectedFrameBarStyleBackups = nextBackups;
}
function _restoreConnectedFrameBarStyleBackups() {
if (!_hasConnectedFrameBarStyleBackups())
return;
const backups = connectedFrameBarStyleBackups || {};
const configs = JSON.parse(JSON.stringify(barConfigs));
let changed = false;
for (let i = 0; i < configs.length; i++) {
const backup = backups[configs[i].id];
if (!backup)
continue;
for (const key in backup) {
if (configs[i][key] === backup[key])
continue;
configs[i][key] = backup[key];
changed = true;
}
}
if (changed)
barConfigs = configs;
connectedFrameBarStyleBackups = ({});
if (changed)
updateBarConfigs();
}
// Zeroes out connected-mode-hostile fields (shadow, square/goth corners, edge attach, border).
// Returns { configs, changed } — `configs` is the same ref when no change.
function _sanitizeBarConfigsForConnectedFrame(configs) {
if (!connectedFrameModeActive || !Array.isArray(configs))
return {
"configs": configs,
"changed": false
};
let anyChanged = false;
const out = configs.map(cfg => {
if (!cfg)
return cfg;
let dirty = false;
const s = Object.assign({}, cfg);
if ((s.shadowIntensity ?? 0) !== 0) {
s.shadowIntensity = 0;
dirty = true;
}
if (s.squareCorners ?? false) {
s.squareCorners = false;
dirty = true;
}
if (s.attachToScreenEdge ?? false) {
s.attachToScreenEdge = false;
dirty = true;
}
if (s.gothCornersEnabled ?? false) {
s.gothCornersEnabled = false;
dirty = true;
}
if (s.borderEnabled ?? false) {
s.borderEnabled = false;
dirty = true;
}
if (dirty)
anyChanged = true;
return dirty ? s : cfg;
});
return {
"configs": anyChanged ? out : configs,
"changed": anyChanged
};
}
function effectiveBarConfigForRender(config, usesFrameBarChrome) {
if (!config || !connectedFrameModeActive || usesFrameBarChrome)
return config;
const backup = connectedFrameBarStyleBackups[config.id];
if (!backup)
return config;
return Object.assign({}, config, backup);
}
// Single entry point for connected-mode settings state.
// !active → restore backups
function _reconcileConnectedFrameBarStyles() {
if (!connectedFrameModeActive) {
_restoreConnectedFrameBarStyleBackups();
return;
}
if (!_hasConnectedFrameBarStyleBackups())
_captureConnectedFrameBarStyleBackups(barConfigs, true);
const result = _sanitizeBarConfigsForConnectedFrame(barConfigs);
if (result.changed) {
barConfigs = result.configs;
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);
if (connectedFrameModeActive)
_captureConnectedFrameBarStyleBackups(configs, false);
barConfigs = _sanitizeBarConfigsForConnectedFrame(configs).configs;
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);
barConfigs = _sanitizeBarConfigsForConnectedFrame(configs).configs;
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";
}
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" ? "2025" : "2021";
if (matugenSpec === normalized)
return;
if (normalized === "2025" && matugenContrast < 0)
set("matugenContrast", 0);
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 addMediaExcludePlayer(identity) {
if (identity === undefined || identity === null)
return;
var normalizedIdentity = identity.toString().trim().toLowerCase();
if (!normalizedIdentity)
return;
var list = mediaExcludePlayers ? mediaExcludePlayers.slice() : [];
var normalizedList = list.map(function (id) {
return id ? id.toString().trim().toLowerCase() : "";
});
if (normalizedList.indexOf(normalizedIdentity) >= 0)
return;
list.push(normalizedIdentity);
mediaExcludePlayers = list;
saveSettings();
}
function removeMediaExcludePlayer(index) {
var list = mediaExcludePlayers ? mediaExcludePlayers.slice() : [];
if (index < 0 || index >= list.length)
return;
list.splice(index, 1);
mediaExcludePlayers = list;
saveSettings();
}
property bool _pendingExpandNotificationRules: false
property int _pendingNotificationRuleIndex: -1
function _newNotificationRule(overrides) {
return Object.assign({
enabled: true,
field: "appName",
pattern: "",
matchType: "contains",
action: "default",
urgency: "default",
bypassDnd: false
}, overrides || {});
}
function addNotificationRule() {
var rules = JSON.parse(JSON.stringify(notificationRules || []));
rules.push(_newNotificationRule());
notificationRules = rules;
saveSettings();
}
function addNotificationRuleForNotification(appName, desktopEntry) {
var rules = JSON.parse(JSON.stringify(notificationRules || []));
var pattern = desktopEntry || appName || "";
rules.push(_newNotificationRule(pattern ? {
field: desktopEntry ? "desktopEntry" : "appName",
pattern: pattern,
matchType: "exact"
} : {}));
notificationRules = rules;
saveSettings();
var index = rules.length - 1;
_pendingExpandNotificationRules = true;
_pendingNotificationRuleIndex = index;
return index;
}
function _isMuteRule(rule) {
return (rule.action || "").toString().toLowerCase() === "mute";
}
function _isDndBypassRule(rule) {
return rule.bypassDnd === true;
}
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))
return false;
const pattern = (rule.pattern || "").toString().toLowerCase();
return pattern !== "" && (pattern === app || pattern === desktop);
});
}
function _addAppRule(appName, desktopEntry, overrides) {
const pattern = desktopEntry || appName || "";
if (!pattern)
return;
var rules = JSON.parse(JSON.stringify(notificationRules || []));
rules.push(_newNotificationRule(Object.assign({
field: desktopEntry ? "desktopEntry" : "appName",
pattern: pattern,
matchType: "exact"
}, overrides)));
notificationRules = rules;
saveSettings();
}
function _removeAppRule(appName, desktopEntry, predicate) {
var rules = JSON.parse(JSON.stringify(notificationRules || []));
const index = _appRuleIndex(rules, appName, desktopEntry, predicate);
if (index === -1)
return;
rules.splice(index, 1);
notificationRules = rules;
saveSettings();
}
function addMuteRuleForApp(appName, desktopEntry) {
_addAppRule(appName, desktopEntry, {
action: "mute"
});
}
function isAppMuted(appName, desktopEntry) {
return _appRuleIndex(notificationRules || [], appName, desktopEntry, rule => rule.enabled !== false && _isMuteRule(rule)) !== -1;
}
function removeMuteRuleForApp(appName, desktopEntry) {
_removeAppRule(appName, desktopEntry, _isMuteRule);
}
function isAppDndBypassed(appName, desktopEntry) {
return _appRuleIndex(notificationRules || [], appName, desktopEntry, rule => rule.enabled !== false && _isDndBypassRule(rule)) !== -1;
}
function setAppDndBypass(appName, desktopEntry, enabled) {
if (!enabled) {
_removeAppRule(appName, desktopEntry, _isDndBypassRule);
return;
}
if (isAppDndBypassed(appName, desktopEntry))
return;
_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
}
property alias settingsFile: settingsFile
Timer {
id: settingsFileReloadDebounce
interval: 50
onTriggered: settingsFile.reload()
repeat: false
}
FileView {
id: settingsFile
path: isGreeterMode ? "" : StandardPaths.writableLocation(StandardPaths.ConfigLocation) + "/DankMaterialShell/settings.json"
blockLoading: true
blockWrites: true
atomicWrites: true
watchChanges: !isGreeterMode
onFileChanged: {
if (_selfWrite) {
_selfWrite = false;
return;
}
settingsFileReloadDebounce.restart();
}
onLoaded: {
if (isGreeterMode)
return;
const wasLoaded = _hasLoaded;
const prevFrameEnabled = frameEnabled;
const prevFrameMode = frameMode;
_loading = true;
_hasUnsavedChanges = false;
try {
const txt = settingsFile.text();
if (!txt || !txt.trim()) {
_parseError = true;
return;
}
const obj = JSON.parse(txt);
_parseError = false;
Store.parse(root, obj);
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();
}
_loadedSettingsSnapshot = JSON.stringify(Store.toJson(root));
_hasLoaded = true;
applyStoredTheme();
updateCompositorCursor();
} catch (e) {
_parseError = true;
const msg = e.message;
log.error("Failed to reload settings.json - file will not be overwritten. Error:", msg);
Qt.callLater(() => ToastService.showError(I18n.tr("Failed to parse %1").arg("settings.json"), msg));
} finally {
_loading = false;
}
// External edits reload under _loading, which skips the per-property transition triggers
if (wasLoaded && !_parseError && (frameEnabled !== prevFrameEnabled || (frameEnabled && frameMode !== prevFrameMode)))
updateFrameCompositorLayout();
}
onLoadFailed: error => {
if (isGreeterMode)
return;
applyStoredTheme();
}
onSaveFailed: error => {
root._isReadOnly = true;
root._hasUnsavedChanges = root._checkForUnsavedChanges();
}
}
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: 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;
parsePluginSettings(pluginSettingsFile.text());
}
onLoadFailed: error => {
if (isGreeterMode)
return;
const msg = String(error || "");
if (!_isMissingPluginSettingsError(error))
log.warn("Failed to load plugin_settings.json. Error:", msg);
_resetPluginSettings();
}
}
property bool pluginSettingsFileExists: false
Process {
id: settingsWritableCheckProcess
property string settingsPath: Paths.strip(settingsFile.path)
command: ["sh", "-c", "[ ! -f \"" + settingsPath + "\" ] || [ -w \"" + settingsPath + "\" ] && echo 'writable' || echo 'readonly'"]
running: false
stdout: StdioCollector {
onStreamFinished: {
const result = text.trim();
root._onWritableCheckComplete(result === "writable");
}
}
}
}