pragma Singleton pragma ComponentBehavior: Bound import QtCore import QtQuick import Quickshell import Quickshell.Io import qs.Common import qs.Common.settings import qs.Services import "settings/SettingsSpec.js" as Spec import "settings/SettingsStore.js" as Store Singleton { id: root readonly property int settingsConfigVersion: 2 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 SuspendBehavior { Suspend, Hibernate, SuspendThenHibernate } enum WidgetColorMode { Default, Colorful } 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" property bool _loading: false property bool _pluginSettingsLoading: false property bool hasTriedDefaultSettings: false property var pluginSettings: ({}) property alias dankBarLeftWidgetsModel: leftWidgetsModel property alias dankBarCenterWidgetsModel: centerWidgetsModel property alias dankBarRightWidgetsModel: rightWidgetsModel property string currentThemeName: "blue" property string customThemeFile: "" property string matugenScheme: "scheme-tonal-spot" property bool runUserMatugenTemplates: true property string matugenTargetMonitor: "" property real popupTransparency: 1.0 property real dockTransparency: 1 property string widgetBackgroundColor: "sch" property string widgetColorMode: "default" property real cornerRadius: 12 property bool use24HourClock: true property bool showSeconds: false property bool useFahrenheit: false property bool nightModeEnabled: false property int animationSpeed: SettingsData.AnimationSpeed.Short property int customAnimationDuration: 500 property string wallpaperFillMode: "Fill" property bool blurredWallpaperLayer: false property bool blurWallpaperOnOverview: false property bool showLauncherButton: true property bool showWorkspaceSwitcher: true property bool showFocusedWindow: true property bool showWeather: true property bool showMusic: true property bool showClipboard: true property bool showCpuUsage: true property bool showMemUsage: true property bool showCpuTemp: true property bool showGpuTemp: true property int selectedGpuIndex: 0 property var enabledGpuPciIds: [] property bool showSystemTray: true property bool showClock: true property bool showNotificationButton: true property bool showBattery: true property bool showControlCenterButton: true property bool showCapsLockIndicator: true property bool controlCenterShowNetworkIcon: true property bool controlCenterShowBluetoothIcon: true property bool controlCenterShowAudioIcon: true property bool showPrivacyButton: true property bool privacyShowMicIcon: false property bool privacyShowCameraIcon: false property bool privacyShowScreenShareIcon: false property var controlCenterWidgets: [ { "id": "volumeSlider", "enabled": true, "width": 50 }, { "id": "brightnessSlider", "enabled": true, "width": 50 }, { "id": "wifi", "enabled": true, "width": 50 }, { "id": "bluetooth", "enabled": true, "width": 50 }, { "id": "audioOutput", "enabled": true, "width": 50 }, { "id": "audioInput", "enabled": true, "width": 50 }, { "id": "nightMode", "enabled": true, "width": 50 }, { "id": "darkMode", "enabled": true, "width": 50 } ] property bool showWorkspaceIndex: false property bool showWorkspacePadding: false property bool workspaceScrolling: false property bool showWorkspaceApps: false property int maxWorkspaceIcons: 3 property bool workspacesPerMonitor: true property bool dwlShowAllTags: false property var workspaceNameIcons: ({}) property bool waveProgressEnabled: true property bool clockCompactMode: false property bool focusedWindowCompactMode: false property bool runningAppsCompactMode: true property bool keyboardLayoutNameCompactMode: false property bool runningAppsCurrentWorkspace: false property bool runningAppsGroupByApp: false property string clockDateFormat: "" property string lockDateFormat: "" property int mediaSize: 1 property string appLauncherViewMode: "list" property string spotlightModalViewMode: "list" property bool sortAppsAlphabetically: false property int appLauncherGridColumns: 4 property bool spotlightCloseNiriOverview: true property string weatherLocation: "New York, NY" property string weatherCoordinates: "40.7128,-74.0060" property bool useAutoLocation: false property bool weatherEnabled: true property string networkPreference: "auto" property string vpnLastConnected: "" property string iconTheme: "System Default" property var availableIconThemes: ["System Default"] property string systemDefaultIconTheme: "" property bool qt5ctAvailable: false property bool qt6ctAvailable: false property bool gtkAvailable: false property string launcherLogoMode: "apps" property string launcherLogoCustomPath: "" property string launcherLogoColorOverride: "" property bool launcherLogoColorInvertOnMode: false property real launcherLogoBrightness: 0.5 property real launcherLogoContrast: 1 property int launcherLogoSizeOffset: 0 property string fontFamily: "Inter Variable" property string monoFontFamily: "Fira Code" property int fontWeight: Font.Normal property real fontScale: 1.0 property real dankBarFontScale: 1.0 property bool notepadUseMonospace: true property string notepadFontFamily: "" property real notepadFontSize: 14 property bool notepadShowLineNumbers: false property real notepadTransparencyOverride: -1 property real notepadLastCustomTransparency: 0.7 onNotepadUseMonospaceChanged: saveSettings() onNotepadFontFamilyChanged: saveSettings() onNotepadFontSizeChanged: saveSettings() onNotepadShowLineNumbersChanged: saveSettings() onNotepadTransparencyOverrideChanged: { if (notepadTransparencyOverride > 0) { notepadLastCustomTransparency = notepadTransparencyOverride; } saveSettings(); } onNotepadLastCustomTransparencyChanged: saveSettings() property bool soundsEnabled: true property bool useSystemSoundTheme: false property bool soundNewNotification: true property bool soundVolumeChanged: true property bool soundPluggedIn: true property int acMonitorTimeout: 0 property int acLockTimeout: 0 property int acSuspendTimeout: 0 property int acSuspendBehavior: SettingsData.SuspendBehavior.Suspend property int batteryMonitorTimeout: 0 property int batteryLockTimeout: 0 property int batterySuspendTimeout: 0 property int batterySuspendBehavior: SettingsData.SuspendBehavior.Suspend property bool lockBeforeSuspend: false property bool preventIdleForMedia: false property bool loginctlLockIntegration: true property string launchPrefix: "" property var brightnessDevicePins: ({}) property var wifiNetworkPins: ({}) property var bluetoothDevicePins: ({}) property var audioInputDevicePins: ({}) property var audioOutputDevicePins: ({}) property bool gtkThemingEnabled: false property bool qtThemingEnabled: false property bool syncModeWithPortal: true property bool terminalsAlwaysDark: false property bool showDock: false property bool dockAutoHide: false property bool dockGroupByApp: false property bool dockOpenOnOverview: false property int dockPosition: SettingsData.Position.Bottom property real dockSpacing: 4 property real dockBottomGap: 0 property real dockMargin: 0 property real dockIconSize: 40 property string dockIndicatorStyle: "circle" property bool notificationOverlayEnabled: false property int overviewRows: 2 property int overviewColumns: 5 property real overviewScale: 0.16 property bool modalDarkenBackground: true property bool lockScreenShowPowerActions: true property bool enableFprint: false property int maxFprintTries: 3 property bool fprintdAvailable: false property bool hideBrightnessSlider: false property int notificationTimeoutLow: 5000 property int notificationTimeoutNormal: 5000 property int notificationTimeoutCritical: 0 property int notificationPopupPosition: SettingsData.Position.Top property bool osdAlwaysShowValue: false property int osdPosition: SettingsData.Position.BottomCenter property bool osdVolumeEnabled: true property bool osdMediaVolumeEnabled: true property bool osdBrightnessEnabled: true property bool osdIdleInhibitorEnabled: true property bool osdMicMuteEnabled: true property bool osdCapsLockEnabled: true property bool osdPowerProfileEnabled: true property bool powerActionConfirm: true property var powerMenuActions: ["reboot", "logout", "poweroff", "lock", "suspend", "restart"] property string powerMenuDefaultAction: "logout" property bool powerMenuGridLayout: false property string customPowerActionLock: "" property string customPowerActionLogout: "" property string customPowerActionSuspend: "" property string customPowerActionHibernate: "" property string customPowerActionReboot: "" property string customPowerActionPowerOff: "" property bool updaterUseCustomCommand: false property string updaterCustomCommand: "" property string updaterTerminalAdditionalParams: "" property string displayNameMode: "system" property var screenPreferences: ({}) property var showOnLastDisplay: ({}) property var barConfigs: [ { id: "default", name: "Main Bar", enabled: true, position: 0, screenPreferences: ["all"], showOnLastDisplay: true, leftWidgets: ["launcherButton", "workspaceSwitcher", "focusedWindow"], centerWidgets: ["music", "clock", "weather"], rightWidgets: ["systemTray", "clipboard", "cpuUsage", "memUsage", "notificationButton", "battery", "controlCenterButton"], spacing: 4, innerPadding: 4, bottomGap: 0, transparency: 1.0, widgetTransparency: 1.0, squareCorners: false, noBackground: false, gothCornersEnabled: false, gothCornerRadiusOverride: false, gothCornerRadiusValue: 12, borderEnabled: false, borderColor: "surfaceText", borderOpacity: 1.0, borderThickness: 1, fontScale: 1.0, autoHide: false, autoHideDelay: 250, openOnOverview: false, visible: true, popupGapsAuto: true, popupGapsManual: 4 } ] signal forceDankBarLayoutRefresh signal forceDockLayoutRefresh signal widgetDataChanged signal workspaceIconsUpdated Component.onCompleted: { if (!isGreeterMode) { Processes.settingsRoot = root; loadSettings(); initializeListModels(); Processes.detectFprintd(); Processes.checkPluginSettings(); } } function applyStoredTheme() { if (typeof Theme !== "undefined") { Theme.switchTheme(currentThemeName, false, false); } else { Qt.callLater(function () { if (typeof Theme !== "undefined") { Theme.switchTheme(currentThemeName, false, false); } }); } } function regenSystemThemes() { if (typeof Theme !== "undefined") { Theme.generateSystemThemesFromCurrentTheme(); } } function updateNiriLayout() { if (typeof NiriService !== "undefined" && typeof CompositorService !== "undefined" && CompositorService.isNiri) { NiriService.generateNiriLayoutConfig(); } } function applyStoredIconTheme() { updateGtkIconTheme(); updateQtIconTheme(); } function updateGtkIconTheme() { const gtkThemeName = (iconTheme === "System Default") ? systemDefaultIconTheme : iconTheme; if (gtkThemeName === "System Default" || gtkThemeName === "") return; 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" 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 rm -rf ~/.cache/icon-cache ~/.cache/thumbnails 2>/dev/null || true pkill -HUP -f 'gtk' 2>/dev/null || true`; Quickshell.execDetached(["sh", "-lc", configScript]); } function updateQtIconTheme() { const qtThemeName = (iconTheme === "System Default") ? "" : iconTheme; 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}' rm -rf '${home}'/.cache/icon-cache '${home}'/.cache/thumbnails 2>/dev/null || true`; Quickshell.execDetached(["sh", "-lc", script]); } readonly property var _hooks: ({ applyStoredTheme: applyStoredTheme, regenSystemThemes: regenSystemThemes, updateNiriLayout: updateNiriLayout, applyStoredIconTheme: applyStoredIconTheme, updateBarConfigs: updateBarConfigs }) function set(key, value) { Spec.set(root, key, value, saveSettings, _hooks); } function loadSettings() { _loading = true; try { const txt = settingsFile.text(); let obj = (txt && txt.trim()) ? JSON.parse(txt) : null; const oldVersion = obj?.configVersion ?? 0; if (oldVersion < settingsConfigVersion) { const migrated = Store.migrateToVersion(obj, settingsConfigVersion); if (migrated) { settingsFile.setText(JSON.stringify(migrated, null, 2)); obj = migrated; } } Store.parse(root, obj); applyStoredTheme(); applyStoredIconTheme(); Processes.detectIcons(); Processes.detectQtTools(); } catch (e) { console.warn("SettingsData: Failed to load settings:", e.message); applyStoredTheme(); applyStoredIconTheme(); } finally { _loading = false; } loadPluginSettings(); } function loadPluginSettings() { _pluginSettingsLoading = true; parsePluginSettings(pluginSettingsFile.text()); _pluginSettingsLoading = false; } function parsePluginSettings(content) { _pluginSettingsLoading = true; try { if (content && content.trim()) { pluginSettings = JSON.parse(content); } else { pluginSettings = {}; } } catch (e) { console.warn("SettingsData: Failed to parse plugin settings:", e.message); pluginSettings = {}; } finally { _pluginSettingsLoading = false; } } function saveSettings() { if (_loading) return; settingsFile.setText(JSON.stringify(Store.toJson(root), null, 2)); } function savePluginSettings() { if (_pluginSettingsLoading) return; pluginSettingsFile.setText(JSON.stringify(pluginSettings, null, 2)); } function detectAvailableIconThemes() { Processes.detectIcons(); } function getEffectiveTimeFormat() { if (use24HourClock) { return showSeconds ? "hh:mm:ss" : "hh:mm"; } else { return showSeconds ? "h:mm:ss AP" : "h:mm AP"; } } function getEffectiveClockDateFormat() { return clockDateFormat && clockDateFormat.length > 0 ? clockDateFormat : "ddd d"; } function getEffectiveLockDateFormat() { return lockDateFormat && lockDateFormat.length > 0 ? lockDateFormat : Locale.LongFormat; } function initializeListModels() { const defaultBar = barConfigs[0] || getBarConfig("default"); if (defaultBar) { Lists.init(leftWidgetsModel, centerWidgetsModel, rightWidgetsModel, defaultBar.leftWidgets, defaultBar.centerWidgets, defaultBar.rightWidgets); } } function updateListModel(listModel, order) { Lists.update(listModel, order); widgetDataChanged(); } function hasNamedWorkspaces() { if (typeof NiriService === "undefined" || !CompositorService.isNiri) return false; for (var i = 0; i < NiriService.allWorkspaces.length; i++) { var ws = NiriService.allWorkspaces[i]; if (ws.name && ws.name.trim() !== "") return true; } return false; } function getNamedWorkspaces() { var namedWorkspaces = []; if (typeof NiriService === "undefined" || !CompositorService.isNiri) return namedWorkspaces; for (const ws of NiriService.allWorkspaces) { if (ws.name && ws.name.trim() !== "") { namedWorkspaces.push(ws.name); } } return namedWorkspaces; } function getPopupYPosition(barHeight) { const defaultBar = barConfigs[0] || getBarConfig("default"); const gothOffset = defaultBar?.gothCornersEnabled ? Theme.cornerRadius : 0; const spacing = defaultBar?.spacing ?? 4; const bottomGap = defaultBar?.bottomGap ?? 0; return barHeight + spacing + bottomGap - gothOffset + Theme.popupDistance; } function getPopupTriggerPosition(globalPos, screen, barThickness, widgetWidth, barSpacing, barPosition, barConfig) { const screenX = screen ? screen.x : 0; const screenY = screen ? screen.y : 0; const relativeX = globalPos.x - screenX; const relativeY = globalPos.y - screenY; const defaultBar = barConfigs[0] || getBarConfig("default"); const spacing = barSpacing !== undefined ? barSpacing : (defaultBar?.spacing ?? 4); const position = barPosition !== undefined ? barPosition : (defaultBar?.position ?? SettingsData.Position.Top); const bottomGap = barConfig ? (barConfig.bottomGap !== undefined ? barConfig.bottomGap : (defaultBar?.bottomGap ?? 0)) : (defaultBar?.bottomGap ?? 0); const useAutoGaps = (barConfig && barConfig.popupGapsAuto !== undefined) ? barConfig.popupGapsAuto : (defaultBar?.popupGapsAuto ?? true); const manualGapValue = (barConfig && barConfig.popupGapsManual !== undefined) ? barConfig.popupGapsManual : (defaultBar?.popupGapsManual ?? 4); const popupGap = useAutoGaps ? Math.max(4, spacing) : manualGapValue; switch (position) { case SettingsData.Position.Left: return { "x": barThickness + spacing + popupGap, "y": relativeY, "width": widgetWidth }; case SettingsData.Position.Right: return { "x": (screen?.width || 0) - (barThickness + spacing + popupGap), "y": relativeY, "width": widgetWidth }; case SettingsData.Position.Bottom: return { "x": relativeX, "y": (screen?.height || 0) - (barThickness + spacing + bottomGap + popupGap), "width": widgetWidth }; default: return { "x": relativeX, "y": barThickness + spacing + bottomGap + popupGap, "width": widgetWidth }; } } function getAdjacentBarInfo(screen, barPosition, barConfig) { if (!screen || !barConfig) { return { "topBar": 0, "bottomBar": 0, "leftBar": 0, "rightBar": 0 }; } if (barConfig.autoHide) { return { "topBar": 0, "bottomBar": 0, "leftBar": 0, "rightBar": 0 }; } const enabledBars = getEnabledBarConfigs(); const defaultBar = barConfigs[0] || getBarConfig("default"); const position = barPosition !== undefined ? barPosition : (defaultBar?.position ?? SettingsData.Position.Top); let topBar = 0; let bottomBar = 0; let leftBar = 0; let rightBar = 0; for (let i = 0; i < enabledBars.length; i++) { const other = enabledBars[i]; if (other.id === barConfig.id) continue; if (other.autoHide) continue; const otherScreens = other.screenPreferences || ["all"]; const barScreens = barConfig.screenPreferences || ["all"]; const onSameScreen = otherScreens.includes("all") || barScreens.includes("all") || otherScreens.some(s => isScreenInPreferences(screen, [s])); if (!onSameScreen) continue; const otherSpacing = other.spacing !== undefined ? other.spacing : (defaultBar?.spacing ?? 4); const otherPadding = other.innerPadding !== undefined ? other.innerPadding : (defaultBar?.innerPadding ?? 4); const otherThickness = Math.max(26 + otherPadding * 0.6, Theme.barHeight - 4 - (8 - otherPadding)) + otherSpacing; const useAutoGaps = other.popupGapsAuto !== undefined ? other.popupGapsAuto : (defaultBar?.popupGapsAuto ?? true); const manualGap = other.popupGapsManual !== undefined ? other.popupGapsManual : (defaultBar?.popupGapsManual ?? 4); const popupGap = useAutoGaps ? Math.max(4, otherSpacing) : manualGap; switch (other.position) { case SettingsData.Position.Top: topBar = Math.max(topBar, otherThickness + popupGap); break; case SettingsData.Position.Bottom: bottomBar = Math.max(bottomBar, otherThickness + popupGap); break; case SettingsData.Position.Left: leftBar = Math.max(leftBar, otherThickness + popupGap); break; case SettingsData.Position.Right: rightBar = Math.max(rightBar, otherThickness + popupGap); break; } } return { "topBar": topBar, "bottomBar": bottomBar, "leftBar": leftBar, "rightBar": rightBar }; } function getBarBounds(screen, barThickness, barPosition, barConfig) { if (!screen) { return { "x": 0, "y": 0, "width": 0, "height": 0, "wingSize": 0 }; } const defaultBar = barConfigs[0] || getBarConfig("default"); const wingRadius = (defaultBar?.gothCornerRadiusOverride ?? false) ? (defaultBar?.gothCornerRadiusValue ?? 12) : Theme.cornerRadius; const wingSize = (defaultBar?.gothCornersEnabled ?? false) ? Math.max(0, wingRadius) : 0; const screenWidth = screen.width; const screenHeight = screen.height; const position = barPosition !== undefined ? barPosition : (defaultBar?.position ?? SettingsData.Position.Top); const bottomGap = barConfig ? (barConfig.bottomGap !== undefined ? barConfig.bottomGap : (defaultBar?.bottomGap ?? 0)) : (defaultBar?.bottomGap ?? 0); let topOffset = 0; let bottomOffset = 0; let leftOffset = 0; let rightOffset = 0; if (barConfig) { const enabledBars = getEnabledBarConfigs(); for (let i = 0; i < enabledBars.length; i++) { const other = enabledBars[i]; if (other.id === barConfig.id) continue; const otherScreens = other.screenPreferences || ["all"]; const barScreens = barConfig.screenPreferences || ["all"]; const onSameScreen = otherScreens.includes("all") || barScreens.includes("all") || otherScreens.some(s => isScreenInPreferences(screen, [s])); if (!onSameScreen) continue; const otherSpacing = other.spacing !== undefined ? other.spacing : (defaultBar?.spacing ?? 4); const otherPadding = other.innerPadding !== undefined ? other.innerPadding : (defaultBar?.innerPadding ?? 4); const otherThickness = Math.max(26 + otherPadding * 0.6, Theme.barHeight - 4 - (8 - otherPadding)) + otherSpacing + wingSize; const otherBottomGap = other.bottomGap !== undefined ? other.bottomGap : (defaultBar?.bottomGap ?? 0); switch (other.position) { case SettingsData.Position.Top: if (position === SettingsData.Position.Top && other.id < barConfig.id) { topOffset += otherThickness; // Simple stacking for same pos } else if (position === SettingsData.Position.Left || position === SettingsData.Position.Right) { topOffset = Math.max(topOffset, otherThickness); } break; case SettingsData.Position.Bottom: if (position === SettingsData.Position.Bottom && other.id < barConfig.id) { bottomOffset += (otherThickness + otherBottomGap); } else if (position === SettingsData.Position.Left || position === SettingsData.Position.Right) { bottomOffset = Math.max(bottomOffset, otherThickness + otherBottomGap); } break; case SettingsData.Position.Left: if (position === SettingsData.Position.Top || position === SettingsData.Position.Bottom) { leftOffset = Math.max(leftOffset, otherThickness); } else if (position === SettingsData.Position.Left && other.id < barConfig.id) { leftOffset += otherThickness; } break; case SettingsData.Position.Right: if (position === SettingsData.Position.Top || position === SettingsData.Position.Bottom) { rightOffset = Math.max(rightOffset, otherThickness); } else if (position === SettingsData.Position.Right && other.id < barConfig.id) { rightOffset += otherThickness; } break; } } } switch (position) { case SettingsData.Position.Top: return { "x": leftOffset, "y": topOffset + bottomGap, "width": screenWidth - leftOffset - rightOffset, "height": barThickness + wingSize, "wingSize": wingSize }; case SettingsData.Position.Bottom: return { "x": leftOffset, "y": screenHeight - barThickness - wingSize - bottomGap - bottomOffset, "width": screenWidth - leftOffset - rightOffset, "height": barThickness + wingSize, "wingSize": wingSize }; case SettingsData.Position.Left: return { "x": 0, "y": topOffset, "width": barThickness + wingSize, "height": screenHeight - topOffset - bottomOffset, "wingSize": wingSize }; case SettingsData.Position.Right: return { "x": screenWidth - barThickness - wingSize, "y": topOffset, "width": barThickness + wingSize, "height": screenHeight - topOffset - bottomOffset, "wingSize": wingSize }; } return { "x": 0, "y": 0, "width": 0, "height": 0, "wingSize": 0 }; } function updateBarConfigs() { barConfigsChanged(); saveSettings(); } function getBarConfig(barId) { return barConfigs.find(cfg => cfg.id === barId) || null; } function addBarConfig(config) { const configs = JSON.parse(JSON.stringify(barConfigs)); configs.push(config); barConfigs = 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; Object.assign(configs[index], updates); barConfigs = configs; updateBarConfigs(); if (positionChanged) { NotificationService.clearAllPopups(); } } function checkBarCollisions(barId) { const bar = getBarConfig(barId); if (!bar || !bar.enabled) return []; const conflicts = []; const enabledBars = getEnabledBarConfigs(); for (let i = 0; i < enabledBars.length; i++) { const other = enabledBars[i]; if (other.id === barId) continue; const samePosition = bar.position === other.position; if (!samePosition) continue; const barScreens = bar.screenPreferences || ["all"]; const otherScreens = other.screenPreferences || ["all"]; const hasAll = barScreens.includes("all") || otherScreens.includes("all"); if (hasAll) { conflicts.push({ barId: other.id, barName: other.name, reason: "Same position on all screens" }); continue; } const overlapping = barScreens.some(screen => otherScreens.includes(screen)); if (overlapping) { conflicts.push({ barId: other.id, barName: other.name, reason: "Same position on overlapping screens" }); } } return conflicts; } function deleteBarConfig(barId) { if (barId === "default") return; const configs = barConfigs.filter(cfg => cfg.id !== barId); barConfigs = configs; updateBarConfigs(); } function getEnabledBarConfigs() { return barConfigs.filter(cfg => cfg.enabled); } function getScreenDisplayName(screen) { if (!screen) return ""; if (displayNameMode === "model" && screen.model) { return screen.model; } return screen.name; } function isScreenInPreferences(screen, prefs) { if (!screen) return false; return prefs.some(pref => { if (typeof pref === "string") { return pref === "all" || pref === screen.name || pref === screen.model; } if (displayNameMode === "model") { return pref.model && screen.model && pref.model === screen.model; } return pref.name === screen.name; }); } function getFilteredScreens(componentId) { var prefs = screenPreferences && screenPreferences[componentId] || ["all"]; if (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 sendTestNotifications() { NotificationService.clearAllPopups(); sendTestNotification(0); testNotifTimer1.start(); testNotifTimer2.start(); } function sendTestNotification(index) { const notifications = [["Notification Position Test", "DMS test notification 1 of 3 ~ Hi there!", "preferences-system"], ["Second Test", "DMS Notification 2 of 3 ~ Check it out!", "applications-graphics"], ["Third Test", "DMS notification 3 of 3 ~ Enjoy!", "face-smile"]]; if (index < 0 || index >= notifications.length) { return; } const notif = notifications[index]; testNotificationProcess.command = ["notify-send", "-h", "int:transient:1", "-a", "DMS", "-i", notif[2], notif[0], notif[1]]; testNotificationProcess.running = true; } function setMatugenScheme(scheme) { var normalized = scheme || "scheme-tonal-spot"; if (matugenScheme === normalized) return; set("matugenScheme", normalized); if (typeof Theme !== "undefined") { Theme.generateSystemThemesFromCurrentTheme(); } } function setRunUserMatugenTemplates(enabled) { if (runUserMatugenTemplates === enabled) return; set("runUserMatugenTemplates", enabled); if (typeof Theme !== "undefined") { Theme.generateSystemThemesFromCurrentTheme(); } } function setMatugenTargetMonitor(monitorName) { if (matugenTargetMonitor === monitorName) return; set("matugenTargetMonitor", monitorName); if (typeof Theme !== "undefined") { Theme.generateSystemThemesFromCurrentTheme(); } } function setCornerRadius(radius) { set("cornerRadius", radius); NiriService.generateNiriLayoutConfig(); } function setWeatherLocation(displayName, coordinates) { weatherLocation = displayName; weatherCoordinates = coordinates; saveSettings(); } function setIconTheme(themeName) { iconTheme = themeName; updateGtkIconTheme(); updateQtIconTheme(); saveSettings(); if (typeof Theme !== "undefined" && Theme.currentTheme === Theme.dynamic) Theme.generateSystemThemesFromCurrentTheme(); } function setGtkThemingEnabled(enabled) { set("gtkThemingEnabled", enabled); if (enabled && typeof Theme !== "undefined") { Theme.generateSystemThemesFromCurrentTheme(); } } function setQtThemingEnabled(enabled) { set("qtThemingEnabled", enabled); if (enabled && typeof Theme !== "undefined") { Theme.generateSystemThemesFromCurrentTheme(); } } function setShowDock(enabled) { showDock = enabled; const defaultBar = barConfigs[0] || getBarConfig("default"); const barPos = defaultBar?.position ?? SettingsData.Position.Top; if (enabled && dockPosition === barPos) { if (barPos === SettingsData.Position.Top) { setDockPosition(SettingsData.Position.Bottom); return; } if (barPos === SettingsData.Position.Bottom) { setDockPosition(SettingsData.Position.Top); return; } if (barPos === SettingsData.Position.Left) { setDockPosition(SettingsData.Position.Right); return; } if (barPos === SettingsData.Position.Right) { setDockPosition(SettingsData.Position.Left); return; } } saveSettings(); } function setDockPosition(position) { dockPosition = position; const defaultBar = barConfigs[0] || getBarConfig("default"); const barPos = defaultBar?.position ?? SettingsData.Position.Top; if (position === SettingsData.Position.Bottom && barPos === SettingsData.Position.Bottom && showDock) { setDankBarPosition(SettingsData.Position.Top); } if (position === SettingsData.Position.Top && barPos === SettingsData.Position.Top && showDock) { setDankBarPosition(SettingsData.Position.Bottom); } if (position === SettingsData.Position.Left && barPos === SettingsData.Position.Left && showDock) { setDankBarPosition(SettingsData.Position.Right); } if (position === SettingsData.Position.Right && barPos === SettingsData.Position.Right && showDock) { setDankBarPosition(SettingsData.Position.Left); } saveSettings(); Qt.callLater(() => forceDockLayoutRefresh()); } function setDankBarSpacing(spacing) { const defaultBar = barConfigs[0] || getBarConfig("default"); if (defaultBar) { updateBarConfig(defaultBar.id, { spacing: spacing }); } if (typeof NiriService !== "undefined" && CompositorService.isNiri) { NiriService.generateNiriLayoutConfig(); } } function setDankBarPosition(position) { const defaultBar = barConfigs[0] || getBarConfig("default"); if (!defaultBar) return; if (position === SettingsData.Position.Bottom && dockPosition === SettingsData.Position.Bottom && showDock) { setDockPosition(SettingsData.Position.Top); return; } if (position === SettingsData.Position.Top && dockPosition === SettingsData.Position.Top && showDock) { setDockPosition(SettingsData.Position.Bottom); return; } if (position === SettingsData.Position.Left && dockPosition === SettingsData.Position.Left && showDock) { setDockPosition(SettingsData.Position.Right); return; } if (position === SettingsData.Position.Right && dockPosition === SettingsData.Position.Right && showDock) { setDockPosition(SettingsData.Position.Left); return; } updateBarConfig(defaultBar.id, { position: position }); } function setDankBarLeftWidgets(order) { const defaultBar = barConfigs[0] || getBarConfig("default"); if (defaultBar) { updateBarConfig(defaultBar.id, { leftWidgets: order }); updateListModel(leftWidgetsModel, order); } } function setDankBarCenterWidgets(order) { const defaultBar = barConfigs[0] || getBarConfig("default"); if (defaultBar) { updateBarConfig(defaultBar.id, { centerWidgets: order }); updateListModel(centerWidgetsModel, order); } } function setDankBarRightWidgets(order) { const defaultBar = barConfigs[0] || getBarConfig("default"); if (defaultBar) { updateBarConfig(defaultBar.id, { rightWidgets: order }); updateListModel(rightWidgetsModel, order); } } function resetDankBarWidgetsToDefault() { var defaultLeft = ["launcherButton", "workspaceSwitcher", "focusedWindow"]; var defaultCenter = ["music", "clock", "weather"]; var defaultRight = ["systemTray", "clipboard", "notificationButton", "battery", "controlCenterButton"]; const defaultBar = barConfigs[0] || getBarConfig("default"); if (defaultBar) { updateBarConfig(defaultBar.id, { leftWidgets: defaultLeft, centerWidgets: defaultCenter, rightWidgets: defaultRight }); } updateListModel(leftWidgetsModel, defaultLeft); updateListModel(centerWidgetsModel, defaultCenter); updateListModel(rightWidgetsModel, defaultRight); showLauncherButton = true; showWorkspaceSwitcher = true; showFocusedWindow = true; showWeather = true; showMusic = true; showClipboard = true; showCpuUsage = true; showMemUsage = true; showCpuTemp = true; showGpuTemp = true; showSystemTray = true; showClock = true; showNotificationButton = true; showBattery = true; showControlCenterButton = true; showCapsLockIndicator = true; } 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 toggleDankBarVisible() { const defaultBar = barConfigs[0] || getBarConfig("default"); if (defaultBar) { updateBarConfig(defaultBar.id, { visible: !defaultBar.visible }); } } function toggleShowDock() { setShowDock(!showDock); } 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 removePluginSettings(pluginId) { if (pluginSettings[pluginId]) { delete pluginSettings[pluginId]; savePluginSettings(); } } function getPluginSettingsForPlugin(pluginId) { const settings = pluginSettings[pluginId]; return settings ? JSON.parse(JSON.stringify(settings)) : {}; } ListModel { id: leftWidgetsModel } ListModel { id: centerWidgetsModel } ListModel { id: rightWidgetsModel } property Process testNotificationProcess testNotificationProcess: Process { command: [] running: false } property Timer testNotifTimer1 testNotifTimer1: Timer { interval: 400 repeat: false onTriggered: sendTestNotification(1) } property Timer testNotifTimer2 testNotifTimer2: Timer { interval: 800 repeat: false onTriggered: sendTestNotification(2) } property alias settingsFile: settingsFile FileView { id: settingsFile path: isGreeterMode ? "" : StandardPaths.writableLocation(StandardPaths.ConfigLocation) + "/DankMaterialShell/settings.json" blockLoading: true blockWrites: true atomicWrites: true watchChanges: !isGreeterMode onLoaded: { if (!isGreeterMode) { try { const txt = settingsFile.text(); const obj = (txt && txt.trim()) ? JSON.parse(txt) : null; Store.parse(root, obj); } catch (e) { console.warn("SettingsData: Failed to reload settings:", e.message); } hasTriedDefaultSettings = false; } } onLoadFailed: error => { if (!isGreeterMode && !hasTriedDefaultSettings) { hasTriedDefaultSettings = true; Processes.checkDefaultSettings(); } else if (!isGreeterMode) { applyStoredTheme(); } } } FileView { id: pluginSettingsFile path: isGreeterMode ? "" : pluginSettingsPath blockLoading: true blockWrites: true atomicWrites: true watchChanges: !isGreeterMode onLoaded: { if (!isGreeterMode) { parsePluginSettings(pluginSettingsFile.text()); } } onLoadFailed: error => { if (!isGreeterMode) { pluginSettings = {}; } } } property bool pluginSettingsFileExists: false IpcHandler { function reveal(): string { root.setShowDock(true); return "DOCK_SHOW_SUCCESS"; } function hide(): string { root.setShowDock(false); return "DOCK_HIDE_SUCCESS"; } function toggle(): string { root.toggleShowDock(); return root.showDock ? "DOCK_SHOW_SUCCESS" : "DOCK_HIDE_SUCCESS"; } function status(): string { return root.showDock ? "visible" : "hidden"; } target: "dock" } }