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 defaultFontFamily: "Inter Variable" readonly property string defaultMonoFontFamily: "Fira Code" 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) Store.migrate(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" } }