From 70ca3fef77ee8bdc6e3ac28589a6fa08c024cc69 Mon Sep 17 00:00:00 2001 From: AlexanderCurl Date: Sat, 18 Apr 2026 17:46:06 +0100 Subject: Replaced file structures for themes in the correct format --- .../quickshell/Common/SettingsData.qml | 2840 ++++++++++++++++++++ 1 file changed, 2840 insertions(+) create mode 100644 raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Common/SettingsData.qml (limited to 'raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Common/SettingsData.qml') diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Common/SettingsData.qml b/raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Common/SettingsData.qml new file mode 100644 index 0000000..0790ea4 --- /dev/null +++ b/raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Common/SettingsData.qml @@ -0,0 +1,2840 @@ +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: 5 + + 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 _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: ({}) + + 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; + saveSettings(); + } + + property bool clipboardEnterToPaste: false + + property var launcherPluginVisibility: ({}) + + 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: [] + 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 alias dankBarLeftWidgetsModel: leftWidgetsModel + property alias dankBarCenterWidgetsModel: centerWidgetsModel + property alias dankBarRightWidgetsModel: rightWidgetsModel + + property string currentThemeName: "purple" + property string currentThemeCategory: "generic" + property string customThemeFile: "" + property var registryThemeVariants: ({}) + property string matugenScheme: "scheme-tonal-spot" + property real matugenContrast: 0 + 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 string controlCenterTileColorMode: "primary" + property string buttonColorMode: "primary" + property real cornerRadius: 12 + property int niriLayoutGapsOverride: -1 + property int niriLayoutRadiusOverride: -1 + property int niriLayoutBorderSize: -1 + property int hyprlandLayoutGapsOverride: -1 + property int hyprlandLayoutRadiusOverride: -1 + property int hyprlandLayoutBorderSize: -1 + property int mangoLayoutGapsOverride: -1 + property int mangoLayoutRadiusOverride: -1 + property int mangoLayoutBorderSize: -1 + + property int firstDayOfWeek: -1 + property bool showWeekNumber: false + property bool use24HourClock: true + property bool showSeconds: false + property bool padHours12Hour: false + property bool useFahrenheit: false + property string windSpeedUnit: "kmh" + property bool nightModeEnabled: false + property int animationSpeed: SettingsData.AnimationSpeed.Short + property int customAnimationDuration: 500 + property bool syncComponentAnimationSpeeds: true + onSyncComponentAnimationSpeedsChanged: saveSettings() + property int popoutAnimationSpeed: SettingsData.AnimationSpeed.Short + property int popoutCustomAnimationDuration: 150 + property int modalAnimationSpeed: SettingsData.AnimationSpeed.Short + property int modalCustomAnimationDuration: 150 + property bool enableRippleEffects: true + onEnableRippleEffectsChanged: saveSettings() + property bool m3ElevationEnabled: true + onM3ElevationEnabledChanged: saveSettings() + property int m3ElevationIntensity: 12 + onM3ElevationIntensityChanged: saveSettings() + property int m3ElevationOpacity: 30 + onM3ElevationOpacityChanged: saveSettings() + property string m3ElevationColorMode: "default" + onM3ElevationColorModeChanged: saveSettings() + property string m3ElevationLightDirection: "top" + onM3ElevationLightDirectionChanged: saveSettings() + property string m3ElevationCustomColor: "#000000" + onM3ElevationCustomColorChanged: saveSettings() + property bool modalElevationEnabled: true + onModalElevationEnabledChanged: saveSettings() + property bool popoutElevationEnabled: true + onPopoutElevationEnabledChanged: saveSettings() + property bool barElevationEnabled: true + onBarElevationEnabledChanged: saveSettings() + property bool blurEnabled: false + onBlurEnabledChanged: saveSettings() + property string blurBorderColor: "outline" + onBlurBorderColorChanged: saveSettings() + property string blurBorderCustomColor: "#ffffff" + onBlurBorderCustomColorChanged: saveSettings() + property real blurBorderOpacity: 1.0 + onBlurBorderOpacityChanged: saveSettings() + 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 controlCenterShowAudioPercent: false + property bool controlCenterShowVpnIcon: true + property bool controlCenterShowBrightnessIcon: false + property bool controlCenterShowBrightnessPercent: false + property bool controlCenterShowMicIcon: false + property bool controlCenterShowMicPercent: true + property bool controlCenterShowBatteryIcon: false + property bool controlCenterShowPrinterIcon: false + property bool controlCenterShowScreenSharingIcon: 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 showWorkspaceName: false + property bool showWorkspacePadding: false + property bool workspaceScrolling: false + property bool showWorkspaceApps: false + property bool workspaceDragReorder: true + property bool groupWorkspaceApps: true + property int maxWorkspaceIcons: 3 + property int workspaceAppIconSizeOffset: 0 + property bool workspaceFollowFocus: false + property bool showOccupiedWorkspacesOnly: false + property bool reverseScrolling: false + property bool dwlShowAllTags: false + property bool workspaceActiveAppHighlightEnabled: false + property string workspaceColorMode: "default" + property string workspaceOccupiedColorMode: "none" + property string workspaceUnfocusedColorMode: "default" + property string workspaceUrgentColorMode: "default" + property bool workspaceFocusedBorderEnabled: false + property string workspaceFocusedBorderColor: "primary" + property int workspaceFocusedBorderThickness: 2 + property var workspaceNameIcons: ({}) + property bool waveProgressEnabled: true + property bool scrollTitleEnabled: true + property bool mediaAdaptiveWidthEnabled: true + property bool audioVisualizerEnabled: true + property string audioScrollMode: "volume" + property int audioWheelScrollAmount: 5 + property bool clockCompactMode: false + property bool focusedWindowCompactMode: false + property bool runningAppsCompactMode: true + property int barMaxVisibleApps: 0 + property int barMaxVisibleRunningApps: 0 + property bool barShowOverflowBadge: true + property bool appsDockHideIndicators: false + property bool appsDockColorizeActive: false + property string appsDockActiveColorMode: "primary" + property bool appsDockEnlargeOnHover: false + property int appsDockEnlargePercentage: 125 + property int appsDockIconSizePercentage: 100 + property bool keyboardLayoutNameCompactMode: false + property bool runningAppsCurrentWorkspace: true + property bool runningAppsGroupByApp: false + property bool runningAppsCurrentMonitor: false + property var appIdSubstitutions: [] + property string centeringMode: "index" + property string clockDateFormat: "" + property string lockDateFormat: "" + property bool greeterRememberLastSession: true + property bool greeterRememberLastUser: true + property bool greeterEnableFprint: false + property bool greeterEnableU2f: false + property string greeterWallpaperPath: "" + property bool greeterUse24HourClock: true + property bool greeterShowSeconds: false + property bool greeterPadHours12Hour: false + property string greeterLockDateFormat: "" + property string greeterFontFamily: "" + property string greeterWallpaperFillMode: "" + property int mediaSize: 1 + + property string appLauncherViewMode: "list" + property string spotlightModalViewMode: "list" + property string browserPickerViewMode: "grid" + property var browserUsageHistory: ({}) + property string appPickerViewMode: "grid" + property var filePickerUsageHistory: ({}) + property bool sortAppsAlphabetically: false + property int appLauncherGridColumns: 4 + property bool spotlightCloseNiriOverview: true + property bool rememberLastQuery: false + property var spotlightSectionViewModes: ({}) + onSpotlightSectionViewModesChanged: saveSettings() + property var appDrawerSectionViewModes: ({}) + onAppDrawerSectionViewModesChanged: saveSettings() + property bool niriOverviewOverlayEnabled: true + property string dankLauncherV2Size: "compact" + property bool dankLauncherV2BorderEnabled: false + property int dankLauncherV2BorderThickness: 2 + property string dankLauncherV2BorderColor: "primary" + property bool dankLauncherV2ShowFooter: true + property bool dankLauncherV2UnloadOnClose: false + property bool dankLauncherV2IncludeFilesInAll: false + property bool dankLauncherV2IncludeFoldersInAll: false + + 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: false + property bool weatherEnabled: true + + property string networkPreference: "auto" + + 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 var cursorSettings: ({ + "theme": "System Default", + "size": 24, + "niri": { + "hideWhenTyping": false, + "hideAfterInactiveMs": 0 + }, + "hyprland": { + "hideOnKeyPress": false, + "hideOnTouch": false, + "inactiveTimeout": 0 + }, + "dwl": { + "cursorHideTimeout": 0 + } + }) + property var availableCursorThemes: ["System Default"] + property string systemDefaultCursorTheme: "" + + 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() + // onCenteringModeChanged: 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 bool soundLogin: false + + property int acMonitorTimeout: 0 + property int acLockTimeout: 0 + property int acSuspendTimeout: 0 + property int acSuspendBehavior: SettingsData.SuspendBehavior.Suspend + property string acProfileName: "" + property int batteryMonitorTimeout: 0 + property int batteryLockTimeout: 0 + property int batterySuspendTimeout: 0 + property int batterySuspendBehavior: SettingsData.SuspendBehavior.Suspend + property string batteryProfileName: "" + property int batteryChargeLimit: 100 + property bool lockBeforeSuspend: false + property bool loginctlLockIntegration: true + property bool fadeToLockEnabled: true + property int fadeToLockGracePeriod: 5 + property bool fadeToDpmsEnabled: true + property int fadeToDpmsGracePeriod: 5 + 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 string muxType: "tmux" + property bool muxUseCustomCommand: false + property string muxCustomCommand: "" + property string muxSessionFilter: "" + + property bool runDmsMatugenTemplates: true + property bool matugenTemplateGtk: true + property bool matugenTemplateNiri: true + property bool matugenTemplateHyprland: true + property bool matugenTemplateMangowc: true + property bool matugenTemplateQt5ct: true + property bool matugenTemplateQt6ct: true + property bool matugenTemplateFirefox: true + property bool matugenTemplatePywalfox: true + property bool matugenTemplateZenBrowser: true + property bool matugenTemplateVesktop: true + property bool matugenTemplateEquibop: true + property bool matugenTemplateGhostty: true + property bool matugenTemplateKitty: true + property bool matugenTemplateFoot: true + property bool matugenTemplateNeovim: false + property bool matugenTemplateAlacritty: true + property bool matugenTemplateWezterm: true + property bool matugenTemplateDgop: true + property bool matugenTemplateKcolorscheme: true + property bool matugenTemplateVscode: true + property bool matugenTemplateEmacs: true + property bool matugenTemplateZed: true + + property var matugenTemplateNeovimSettings: ({ + "dark": { + "baseTheme": "github_dark", + "harmony": 0.5 + }, + "light": { + "baseTheme": "github_light", + "harmony": 0.5 + } + }) + property bool matugenTemplateNeovimSetBackground: true + + property bool showDock: false + property bool dockAutoHide: false + property bool dockSmartAutoHide: false + property bool dockGroupByApp: false + property bool dockRestoreSpecialWorkspaceOnClick: 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 dockBorderEnabled: false + property string dockBorderColor: "surfaceText" + property real dockBorderOpacity: 1.0 + property int dockBorderThickness: 1 + property bool dockIsolateDisplays: false + property bool dockLauncherEnabled: false + property string dockLauncherLogoMode: "apps" + property string dockLauncherLogoCustomPath: "" + property string dockLauncherLogoColorOverride: "" + property int dockLauncherLogoSizeOffset: 0 + property real dockLauncherLogoBrightness: 0.5 + property real dockLauncherLogoContrast: 1 + property int dockMaxVisibleApps: 0 + property int dockMaxVisibleRunningApps: 0 + property bool dockShowOverflowBadge: true + + property bool notificationOverlayEnabled: false + property bool notificationPopupShadowEnabled: true + property bool notificationPopupPrivacyMode: false + property int overviewRows: 2 + property int overviewColumns: 5 + property real overviewScale: 0.16 + + property bool modalDarkenBackground: true + + property bool lockScreenShowPowerActions: true + property bool lockScreenShowSystemIcons: true + property bool lockScreenShowTime: true + property bool lockScreenShowDate: true + property bool lockScreenShowProfileImage: true + property bool lockScreenShowPasswordField: true + property bool lockScreenShowMediaPlayer: true + property bool lockScreenPowerOffMonitorsOnLock: false + property bool lockAtStartup: false + + property bool enableFprint: false + property int maxFprintTries: 15 + 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: false + property string u2fMode: "or" + 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 lockScreenActiveMonitor: "all" + property string lockScreenInactiveColor: "#000000" + property int lockScreenNotificationMode: 0 + property bool lockScreenVideoEnabled: false + property string lockScreenVideoPath: "" + property bool lockScreenVideoCycling: false + property bool hideBrightnessSlider: false + + property int notificationTimeoutLow: 5000 + property int notificationTimeoutNormal: 5000 + property int notificationTimeoutCritical: 0 + property bool notificationCompactMode: false + property int notificationPopupPosition: SettingsData.Position.Top + property int notificationAnimationSpeed: SettingsData.AnimationSpeed.Short + property int notificationCustomAnimationDuration: 400 + property bool notificationHistoryEnabled: true + property int notificationHistoryMaxCount: 50 + property int notificationHistoryMaxAgeDays: 7 + property bool notificationHistorySaveLow: true + property bool notificationHistorySaveNormal: true + property bool notificationHistorySaveCritical: true + property var notificationRules: [] + property bool notificationFocusedMonitor: false + + property bool osdAlwaysShowValue: false + property int osdPosition: SettingsData.Position.BottomCenter + property bool osdVolumeEnabled: true + property bool osdMediaVolumeEnabled: true + property bool osdMediaPlaybackEnabled: false + property bool osdBrightnessEnabled: true + property bool osdIdleInhibitorEnabled: true + property bool osdMicMuteEnabled: true + property bool osdCapsLockEnabled: true + property bool osdPowerProfileEnabled: true + property bool osdAudioOutputEnabled: true + + property bool powerActionConfirm: true + property real powerActionHoldDuration: 0.5 + 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 updaterHideWidget: false + property bool updaterUseCustomCommand: false + property string updaterCustomCommand: "" + property string updaterTerminalAdditionalParams: "" + + property string displayNameMode: "system" + property var screenPreferences: ({}) + property var showOnLastDisplay: ({}) + property var niriOutputSettings: ({}) + property var hyprlandOutputSettings: ({}) + property var displayProfiles: ({}) + property var activeDisplayProfile: ({}) + property bool displayProfileAutoSelect: false + property bool displayShowDisconnected: false + property bool displaySnapToEdge: true + + 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, + "maximizeWidgetIcons": false, + "maximizeWidgetText": false, + "removeWidgetPadding": false, + "widgetPadding": 8, + "gothCornersEnabled": false, + "gothCornerRadiusOverride": false, + "gothCornerRadiusValue": 12, + "borderEnabled": false, + "borderColor": "surfaceText", + "borderOpacity": 1.0, + "borderThickness": 1, + "widgetOutlineEnabled": false, + "widgetOutlineColor": "primary", + "widgetOutlineOpacity": 1.0, + "widgetOutlineThickness": 1, + "fontScale": 1.0, + "iconScale": 1.0, + "autoHide": false, + "autoHideDelay": 250, + "showOnWindowsOpen": false, + "openOnOverview": false, + "visible": true, + "popupGapsAuto": true, + "popupGapsManual": 4, + "maximizeDetection": true, + "scrollEnabled": true, + "scrollXBehavior": "column", + "scrollYBehavior": "workspace", + "shadowIntensity": 0, + "shadowOpacity": 60, + "shadowColorMode": "default", + "shadowCustomColor": "#000000", + "clickThrough": false + } + ] + + property bool desktopClockEnabled: false + property string desktopClockStyle: "analog" + property real desktopClockTransparency: 0.8 + property string desktopClockColorMode: "primary" + property color desktopClockCustomColor: "#ffffff" + property bool desktopClockShowDate: true + property bool desktopClockShowAnalogNumbers: false + property bool desktopClockShowAnalogSeconds: true + property real desktopClockX: -1 + property real desktopClockY: -1 + property real desktopClockWidth: 280 + property real desktopClockHeight: 180 + property var desktopClockDisplayPreferences: ["all"] + + property bool systemMonitorEnabled: false + property bool systemMonitorShowHeader: true + property real systemMonitorTransparency: 0.8 + property string systemMonitorColorMode: "primary" + property color systemMonitorCustomColor: "#ffffff" + property bool systemMonitorShowCpu: true + property bool systemMonitorShowCpuGraph: true + property bool systemMonitorShowCpuTemp: true + property bool systemMonitorShowGpuTemp: false + property string systemMonitorGpuPciId: "" + property bool systemMonitorShowMemory: true + property bool systemMonitorShowMemoryGraph: true + property bool systemMonitorShowNetwork: true + property bool systemMonitorShowNetworkGraph: true + property bool systemMonitorShowDisk: true + property bool systemMonitorShowTopProcesses: false + property int systemMonitorTopProcessCount: 3 + property string systemMonitorTopProcessSortBy: "cpu" + property string systemMonitorLayoutMode: "auto" + property int systemMonitorGraphInterval: 60 + property real systemMonitorX: -1 + property real systemMonitorY: -1 + property real systemMonitorWidth: 320 + property real systemMonitorHeight: 480 + property var systemMonitorDisplayPreferences: ["all"] + property var systemMonitorVariants: [] + property var desktopWidgetPositions: ({}) + property var desktopWidgetGridSettings: ({}) + property var desktopWidgetInstances: [] + property var desktopWidgetGroups: [] + + function getDesktopWidgetGridSetting(screenKey, property, defaultValue) { + const val = desktopWidgetGridSettings?.[screenKey]?.[property]; + return val !== undefined ? val : defaultValue; + } + + function setDesktopWidgetGridSetting(screenKey, property, value) { + const allSettings = JSON.parse(JSON.stringify(desktopWidgetGridSettings || {})); + if (!allSettings[screenKey]) + allSettings[screenKey] = {}; + allSettings[screenKey][property] = value; + desktopWidgetGridSettings = allSettings; + saveSettings(); + } + + function getDesktopWidgetPosition(pluginId, screenKey, property, defaultValue) { + const pos = desktopWidgetPositions?.[pluginId]?.[screenKey]?.[property]; + return pos !== undefined ? pos : defaultValue; + } + + function updateDesktopWidgetPosition(pluginId, screenKey, updates) { + const allPositions = JSON.parse(JSON.stringify(desktopWidgetPositions || {})); + if (!allPositions[pluginId]) + allPositions[pluginId] = {}; + allPositions[pluginId][screenKey] = Object.assign({}, allPositions[pluginId][screenKey] || {}, updates); + desktopWidgetPositions = allPositions; + saveSettings(); + } + + function getSystemMonitorVariants() { + return systemMonitorVariants || []; + } + + function createSystemMonitorVariant(name, config) { + const id = "sysmon_" + Date.now() + "_" + Math.random().toString(36).substr(2, 9); + const variant = { + id: id, + name: name, + config: config || getDefaultSystemMonitorConfig() + }; + const variants = JSON.parse(JSON.stringify(systemMonitorVariants || [])); + variants.push(variant); + systemMonitorVariants = variants; + saveSettings(); + return variant; + } + + function updateSystemMonitorVariant(variantId, updates) { + const variants = JSON.parse(JSON.stringify(systemMonitorVariants || [])); + const idx = variants.findIndex(v => v.id === variantId); + if (idx === -1) + return; + Object.assign(variants[idx], updates); + systemMonitorVariants = variants; + saveSettings(); + } + + function removeSystemMonitorVariant(variantId) { + const variants = (systemMonitorVariants || []).filter(v => v.id !== variantId); + systemMonitorVariants = variants; + saveSettings(); + } + + function getSystemMonitorVariant(variantId) { + return (systemMonitorVariants || []).find(v => v.id === variantId) || null; + } + + 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 || {}, + positions: {} + }; + 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 updateDesktopWidgetInstancePosition(instanceId, screenKey, positionUpdates) { + const instances = JSON.parse(JSON.stringify(desktopWidgetInstances || [])); + const idx = instances.findIndex(inst => inst.id === instanceId); + if (idx === -1) + return; + if (!instances[idx].positions) + instances[idx].positions = {}; + instances[idx].positions[screenKey] = Object.assign({}, instances[idx].positions[screenKey] || {}, positionUpdates); + desktopWidgetInstances = instances; + saveSettings(); + } + + function removeDesktopWidgetInstance(instanceId) { + const instances = (desktopWidgetInstances || []).filter(inst => inst.id !== instanceId); + desktopWidgetInstances = instances; + saveSettings(); + } + + function syncDesktopWidgetPositionToAllScreens(instanceId) { + const instances = JSON.parse(JSON.stringify(desktopWidgetInstances || [])); + const idx = instances.findIndex(inst => inst.id === instanceId); + if (idx === -1) + return; + const positions = instances[idx].positions || {}; + const screenKeys = Object.keys(positions).filter(k => k !== "_synced"); + if (screenKeys.length === 0) + return; + const sourceKey = screenKeys[0]; + const sourcePos = positions[sourceKey]; + if (!sourcePos) + return; + const screen = Array.from(Quickshell.screens.values()).find(s => getScreenDisplayName(s) === sourceKey); + if (!screen) + return; + const screenW = screen.width; + const screenH = screen.height; + const synced = {}; + if (sourcePos.x !== undefined) + synced.x = sourcePos.x / screenW; + if (sourcePos.y !== undefined) + synced.y = sourcePos.y / screenH; + if (sourcePos.width !== undefined) + synced.width = sourcePos.width; + if (sourcePos.height !== undefined) + synced.height = sourcePos.height; + instances[idx].positions["_synced"] = synced; + desktopWidgetInstances = instances; + 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 || {})), + positions: {} + }; + 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 getDesktopWidgetInstancesOfType(widgetType) { + return (desktopWidgetInstances || []).filter(inst => inst.widgetType === widgetType); + } + + function getEnabledDesktopWidgetInstances() { + return (desktopWidgetInstances || []).filter(inst => inst.enabled); + } + + function moveDesktopWidgetInstance(instanceId, direction) { + const instances = JSON.parse(JSON.stringify(desktopWidgetInstances || [])); + const idx = instances.findIndex(inst => inst.id === instanceId); + if (idx === -1) + return false; + const targetIdx = direction === "up" ? idx - 1 : idx + 1; + if (targetIdx < 0 || targetIdx >= instances.length) + return false; + const temp = instances[idx]; + instances[idx] = instances[targetIdx]; + instances[targetIdx] = temp; + desktopWidgetInstances = instances; + saveSettings(); + return true; + } + + function reorderDesktopWidgetInstance(instanceId, newIndex) { + const instances = JSON.parse(JSON.stringify(desktopWidgetInstances || [])); + const idx = instances.findIndex(inst => inst.id === instanceId); + if (idx === -1 || newIndex < 0 || newIndex >= instances.length) + return false; + const [item] = instances.splice(idx, 1); + instances.splice(newIndex, 0, item); + desktopWidgetInstances = instances; + saveSettings(); + return true; + } + + function reorderDesktopWidgetInstanceInGroup(instanceId, groupId, newIndexInGroup) { + const instances = JSON.parse(JSON.stringify(desktopWidgetInstances || [])); + const groups = desktopWidgetGroups || []; + const groupMatches = inst => { + if (groupId === null) + return !inst.group || !groups.some(g => g.id === inst.group); + return inst.group === groupId; + }; + const groupInstances = instances.filter(groupMatches); + const currentGroupIdx = groupInstances.findIndex(inst => inst.id === instanceId); + if (currentGroupIdx === -1 || currentGroupIdx === newIndexInGroup) + return false; + if (newIndexInGroup < 0 || newIndexInGroup >= groupInstances.length) + return false; + const globalIdx = instances.findIndex(inst => inst.id === instanceId); + if (globalIdx === -1) + return false; + const [item] = instances.splice(globalIdx, 1); + const targetInstance = groupInstances[newIndexInGroup]; + let targetGlobalIdx = instances.findIndex(inst => inst.id === targetInstance.id); + if (newIndexInGroup > currentGroupIdx) + targetGlobalIdx++; + 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(); + } + + function getDesktopWidgetGroup(groupId) { + return (desktopWidgetGroups || []).find(g => g.id === groupId) || null; + } + + function getDesktopWidgetInstancesByGroup(groupId) { + return (desktopWidgetInstances || []).filter(inst => inst.group === groupId); + } + + function getUngroupedDesktopWidgetInstances() { + return (desktopWidgetInstances || []).filter(inst => !inst.group); + } + + signal forceDankBarLayoutRefresh + signal forceDockLayoutRefresh + signal widgetDataChanged + signal workspaceIconsUpdated + + function refreshAuthAvailability() { + if (isGreeterMode) + return; + Processes.detectAuthCapabilities(); + } + + Component.onCompleted: { + if (!isGreeterMode) { + 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() { + if (typeof CompositorService === "undefined") + return; + if (CompositorService.isNiri && typeof NiriService !== "undefined") + NiriService.generateNiriLayoutConfig(); + if (CompositorService.isHyprland && typeof HyprlandService !== "undefined") + HyprlandService.generateLayoutConfig(); + if (CompositorService.isDwl && typeof DwlService !== "undefined") + DwlService.generateLayoutConfig(); + } + + function applyStoredIconTheme() { + updateGtkIconTheme(); + updateQtIconTheme(); + updateCosmicIconTheme(); + } + + function updateCosmicIconTheme() { + let cosmicThemeName = (iconTheme === "System Default") ? systemDefaultIconTheme : iconTheme; + if (!cosmicThemeName || cosmicThemeName === "System Default") { + const detectScript = `if command -v gsettings >/dev/null 2>&1; then + gsettings get org.gnome.desktop.interface icon-theme 2>/dev/null | sed "s/'//g" + elif command -v dconf >/dev/null 2>&1; then + dconf read /org/gnome/desktop/interface/icon-theme 2>/dev/null | sed "s/'//g" + fi`; + + 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) { + 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 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" + [ -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 + + 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}'`; + + Quickshell.execDetached(["sh", "-lc", script]); + } + + function scheduleAuthApply() { + if (isGreeterMode) + return; + Qt.callLater(() => { + Processes.settingsRoot = root; + Processes.scheduleAuthApply(); + }); + } + + readonly property var _hooks: ({ + "applyStoredTheme": applyStoredTheme, + "regenSystemThemes": regenSystemThemes, + "updateCompositorLayout": updateCompositorLayout, + "applyStoredIconTheme": applyStoredIconTheme, + "updateBarConfigs": updateBarConfigs, + "updateCompositorCursor": updateCompositorCursor, + "scheduleAuthApply": scheduleAuthApply + }) + + function set(key, value) { + Spec.set(root, key, value, saveSettings, _hooks); + } + + 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; + if (oldVersion < settingsConfigVersion) { + const migrated = Store.migrateToVersion(obj, settingsConfigVersion); + if (migrated) { + _pendingMigration = migrated; + obj = migrated; + } + } + + 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(); + Processes.detectQtTools(); + + _checkSettingsWritable(); + } catch (e) { + _parseError = true; + const msg = e.message; + console.error("SettingsData: Failed to parse settings.json - file will not be overwritten. Error:", msg); + Qt.callLater(() => ToastService.showError(I18n.tr("Failed to parse settings.json"), msg)); + applyStoredTheme(); + } finally { + _loading = false; + } + loadPluginSettings(); + } + + property var _pendingMigration: null + + function _checkSettingsWritable() { + settingsWritableCheckProcess.running = true; + } + + function _onWritableCheckComplete(writable) { + const wasReadOnly = _isReadOnly; + _isReadOnly = !writable; + if (_isReadOnly) { + _hasUnsavedChanges = _checkForUnsavedChanges(); + if (!wasReadOnly) + console.info("SettingsData: settings.json is now read-only"); + } else { + _loadedSettingsSnapshot = JSON.stringify(Store.toJson(root)); + _hasUnsavedChanges = false; + if (wasReadOnly) + console.info("SettingsData: settings.json is now writable"); + if (_pendingMigration) + 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)) + console.warn("SettingsData: 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; + console.error("SettingsData: Failed to parse plugin_settings.json - file will not be overwritten. Error:", msg); + Qt.callLater(() => ToastService.showError(I18n.tr("Failed to parse plugin_settings.json"), msg)); + pluginSettings = {}; + } finally { + _pluginSettingsLoading = false; + } + } + + function saveSettings() { + if (_loading || _parseError || !_hasLoaded) + return; + _selfWrite = true; + settingsFile.setText(JSON.stringify(Store.toJson(root), null, 2)); + if (_isReadOnly) + _checkSettingsWritable(); + } + + function savePluginSettings() { + if (_pluginSettingsLoading || _pluginParseError) + return; + pluginSettingsFile.setText(JSON.stringify(pluginSettings, null, 2)); + } + + 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 get org.gnome.desktop.interface icon-theme 2>/dev/null | sed "s/'//g" || echo '')" + 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 get org.gnome.desktop.interface cursor-theme 2>/dev/null | sed "s/'//g" || echo '')" + 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 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(pos, screen, barThickness, widgetWidth, barSpacing, barPosition, barConfig) { + const relativeX = pos.x; + const relativeY = pos.y; + 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 rawBottomGap = barConfig ? (barConfig.bottomGap !== undefined ? barConfig.bottomGap : (defaultBar?.bottomGap ?? 0)) : (defaultBar?.bottomGap ?? 0); + const bottomGap = Math.max(0, rawBottomGap); + + 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 (var 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 (var 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.dismissAllPopups(); + } + } + + function checkBarCollisions(barId) { + const bar = getBarConfig(barId); + if (!bar || !bar.enabled) + return []; + + const conflicts = []; + const enabledBars = getEnabledBarConfigs(); + + for (var 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 getScreensSortedByPosition() { + const screens = []; + for (var i = 0; i < Quickshell.screens.length; i++) { + screens.push(Quickshell.screens[i]); + } + screens.sort((a, b) => { + if (a.x !== b.x) + return a.x - b.x; + return a.y - b.y; + }); + return screens; + } + + function getScreenModelIndex(screen) { + if (!screen || !screen.model) + return -1; + const sorted = getScreensSortedByPosition(); + let modelCount = 0; + let screenIndex = -1; + for (var i = 0; i < sorted.length; i++) { + if (sorted[i].model === screen.model) { + if (sorted[i].name === screen.name) { + screenIndex = modelCount; + } + modelCount++; + } + } + if (modelCount <= 1) + return -1; + return screenIndex; + } + + 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) { + if (!screen) + return false; + + const screenDisplayName = getScreenDisplayName(screen); + + return prefs.some(pref => { + if (typeof pref === "string") { + if (pref === "all" || pref === screen.name) + return true; + if (displayNameMode === "model") { + return pref === screenDisplayName; + } + return pref === screen.model; + } + + if (displayNameMode === "model") { + if (pref.model && screen.model) { + if (pref.modelIndex !== undefined) { + const screenModelIndex = getScreenModelIndex(screen); + return pref.model === screen.model && pref.modelIndex === screenModelIndex; + } + return pref.model === screen.model; + } + return false; + } + 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.dismissAllPopups(); + 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 setMatugenContrast(value) { + if (matugenContrast === value) + return; + set("matugenContrast", value); + } + + 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); + updateCompositorLayout(); + } + + function setWeatherLocation(displayName, coordinates) { + SessionData.setWeatherLocation(displayName, coordinates); + } + + function setIconTheme(themeName) { + iconTheme = themeName; + updateGtkIconTheme(); + updateQtIconTheme(); + updateCosmicIconTheme(); + 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() { + if (typeof CompositorService === "undefined") + return; + if (CompositorService.isNiri && typeof NiriService !== "undefined") { + NiriService.generateNiriCursorConfig(); + return; + } + if (CompositorService.isHyprland && typeof HyprlandService !== "undefined") { + HyprlandService.generateCursorConfig(); + return; + } + if (CompositorService.isDwl && typeof DwlService !== "undefined") { + DwlService.generateCursorConfig(); + return; + } + } + + 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; + if (isSystemDefault && isDefaultSize) + return {}; + + const themeName = isSystemDefault ? "" : cursorSettings.theme; + const size = String(cursorSettings.size || 24); + const env = {}; + + if (!isDefaultSize) { + env["XCURSOR_SIZE"] = size; + env["HYPRCURSOR_SIZE"] = size; + } + if (themeName) { + env["XCURSOR_THEME"] = themeName; + env["HYPRCURSOR_THEME"] = themeName; + } + return env; + } + + 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 + }); + } + updateCompositorLayout(); + } + + 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 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(); + } + + property bool _pendingExpandNotificationRules: false + property int _pendingNotificationRuleIndex: -1 + + function addNotificationRule() { + var rules = JSON.parse(JSON.stringify(notificationRules || [])); + rules.push({ + enabled: true, + field: "appName", + pattern: "", + matchType: "contains", + action: "default", + urgency: "default" + }); + notificationRules = rules; + saveSettings(); + } + + function addNotificationRuleForNotification(appName, desktopEntry) { + var rules = JSON.parse(JSON.stringify(notificationRules || [])); + var pattern = (desktopEntry && desktopEntry !== "") ? desktopEntry : (appName || ""); + var field = (desktopEntry && desktopEntry !== "") ? "desktopEntry" : "appName"; + var rule = { + enabled: true, + field: pattern ? field : "appName", + pattern: pattern || "", + matchType: pattern ? "exact" : "contains", + action: "default", + urgency: "default" + }; + rules.push(rule); + notificationRules = rules; + saveSettings(); + var index = rules.length - 1; + _pendingExpandNotificationRules = true; + _pendingNotificationRuleIndex = index; + return index; + } + + function addMuteRuleForApp(appName, desktopEntry) { + var rules = JSON.parse(JSON.stringify(notificationRules || [])); + var pattern = (desktopEntry && desktopEntry !== "") ? desktopEntry : (appName || ""); + var field = (desktopEntry && desktopEntry !== "") ? "desktopEntry" : "appName"; + if (pattern === "") + return; + rules.push({ + enabled: true, + field: field, + pattern: pattern, + matchType: "exact", + action: "mute", + urgency: "default" + }); + notificationRules = rules; + saveSettings(); + } + + function isAppMuted(appName, desktopEntry) { + const rules = notificationRules || []; + const pat = (desktopEntry && desktopEntry !== "" ? desktopEntry : appName || "").toString().toLowerCase(); + if (!pat) + return false; + for (let i = 0; i < rules.length; i++) { + const r = rules[i]; + if ((r.action || "").toString().toLowerCase() !== "mute" || r.enabled === false) + continue; + const field = (r.field || "appName").toString().toLowerCase(); + const rulePat = (r.pattern || "").toString().toLowerCase(); + if (!rulePat) + continue; + const useDesktop = field === "desktopentry"; + const matches = (useDesktop && desktopEntry) ? (desktopEntry.toString().toLowerCase() === rulePat) : (appName && appName.toString().toLowerCase() === rulePat); + if (matches) + return true; + if (rulePat === pat) + return true; + } + return false; + } + + function removeMuteRuleForApp(appName, desktopEntry) { + var rules = JSON.parse(JSON.stringify(notificationRules || [])); + const app = (appName || "").toString().toLowerCase(); + const desktop = (desktopEntry || "").toString().toLowerCase(); + if (!app && !desktop) + return; + for (let i = rules.length - 1; i >= 0; i--) { + const r = rules[i]; + if ((r.action || "").toString().toLowerCase() !== "mute") + continue; + const rulePat = (r.pattern || "").toString().toLowerCase(); + if (!rulePat) + continue; + if (rulePat === app || rulePat === desktop) { + rules.splice(i, 1); + notificationRules = rules; + saveSettings(); + return; + } + } + } + + 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 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)) : {}; + } + + function getNiriOutputSetting(outputId, key, defaultValue) { + if (!niriOutputSettings[outputId]) + return defaultValue; + return niriOutputSettings[outputId][key] !== undefined ? niriOutputSettings[outputId][key] : defaultValue; + } + + function setNiriOutputSetting(outputId, key, value) { + const updated = JSON.parse(JSON.stringify(niriOutputSettings)); + if (!updated[outputId]) + updated[outputId] = {}; + updated[outputId][key] = value; + niriOutputSettings = updated; + saveSettings(); + } + + function getNiriOutputSettings(outputId) { + const settings = niriOutputSettings[outputId]; + return settings ? JSON.parse(JSON.stringify(settings)) : {}; + } + + function setNiriOutputSettings(outputId, settings) { + const updated = JSON.parse(JSON.stringify(niriOutputSettings)); + updated[outputId] = settings; + niriOutputSettings = updated; + saveSettings(); + } + + function removeNiriOutputSettings(outputId) { + if (!niriOutputSettings[outputId]) + return; + const updated = JSON.parse(JSON.stringify(niriOutputSettings)); + delete updated[outputId]; + niriOutputSettings = updated; + saveSettings(); + } + + function getHyprlandOutputSetting(outputId, key, defaultValue) { + if (!hyprlandOutputSettings[outputId]) + return defaultValue; + return hyprlandOutputSettings[outputId][key] !== undefined ? hyprlandOutputSettings[outputId][key] : defaultValue; + } + + function setHyprlandOutputSetting(outputId, key, value) { + const updated = JSON.parse(JSON.stringify(hyprlandOutputSettings)); + if (!updated[outputId]) + updated[outputId] = {}; + updated[outputId][key] = value; + hyprlandOutputSettings = updated; + saveSettings(); + } + + function removeHyprlandOutputSetting(outputId, key) { + if (!hyprlandOutputSettings[outputId] || !(key in hyprlandOutputSettings[outputId])) + return; + const updated = JSON.parse(JSON.stringify(hyprlandOutputSettings)); + delete updated[outputId][key]; + hyprlandOutputSettings = updated; + saveSettings(); + } + + function getHyprlandOutputSettings(outputId) { + const settings = hyprlandOutputSettings[outputId]; + return settings ? JSON.parse(JSON.stringify(settings)) : {}; + } + + function setHyprlandOutputSettings(outputId, settings) { + const updated = JSON.parse(JSON.stringify(hyprlandOutputSettings)); + updated[outputId] = settings; + hyprlandOutputSettings = updated; + saveSettings(); + } + + function removeHyprlandOutputSettings(outputId) { + if (!hyprlandOutputSettings[outputId]) + return; + const updated = JSON.parse(JSON.stringify(hyprlandOutputSettings)); + delete updated[outputId]; + hyprlandOutputSettings = updated; + saveSettings(); + } + + function getDisplayProfiles(compositor) { + return displayProfiles[compositor] || {}; + } + + function setDisplayProfile(compositor, profileId, data) { + const updated = JSON.parse(JSON.stringify(displayProfiles)); + if (!updated[compositor]) + updated[compositor] = {}; + updated[compositor][profileId] = data; + displayProfiles = updated; + saveSettings(); + } + + 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 getActiveDisplayProfile(compositor) { + return activeDisplayProfile[compositor] || ""; + } + + function setActiveDisplayProfile(compositor, profileId) { + const updated = JSON.parse(JSON.stringify(activeDisplayProfile)); + updated[compositor] = profileId; + activeDisplayProfile = updated; + saveSettings(); + } + + 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 + + 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: true + onFileChanged: { + if (_selfWrite) { + _selfWrite = false; + return; + } + settingsFileReloadDebounce.restart(); + } + onLoaded: { + if (isGreeterMode) + return; + _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; + console.error("SettingsData: Failed to reload settings.json - file will not be overwritten. Error:", msg); + Qt.callLater(() => ToastService.showError(I18n.tr("Failed to parse settings.json"), msg)); + } finally { + _loading = false; + } + } + onLoadFailed: error => { + if (!isGreeterMode) { + applyStoredTheme(); + } + } + onSaveFailed: error => { + root._isReadOnly = true; + root._hasUnsavedChanges = root._checkForUnsavedChanges(); + } + } + + FileView { + id: pluginSettingsFile + + path: isGreeterMode ? "" : pluginSettingsPath + blockLoading: true + blockWrites: true + atomicWrites: true + printErrors: false + watchChanges: !isGreeterMode + onLoaded: { + if (!isGreeterMode) { + parsePluginSettings(pluginSettingsFile.text()); + } + } + onLoadFailed: error => { + if (!isGreeterMode) { + const msg = String(error || ""); + if (!_isMissingPluginSettingsError(error)) + console.warn("SettingsData: 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"); + } + } + } +} -- cgit v1.3