1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2026-01-24 13:32:50 -05:00
Files
DankMaterialShell/quickshell/Common/SettingsData.qml
Jon Rogers f8c5f07e9f Fix: Add view mode persistence for xdg-open picker modals (#1465)
* fix: Add browserPickerViewMode persistence to settings spec

The BrowserPickerModal (used by xdg-open feature) was not persisting
view mode selection between sessions. While the modal had code to save
the view mode preference, the browserPickerViewMode property was not
registered in SettingsSpec.js, preventing it from being saved to disk.

Added browserPickerViewMode and browserUsageHistory to SettingsSpec.js
to ensure user's view preference (list/grid) is properly persisted.

Fixes view mode reverting to grid after restarting DMS/QuickShell.

* fix: Add view mode persistence for both browser and file pickers

Extended the fix to include both picker modals used by xdg-open:

BrowserPickerModal (URLs):
- Added browserPickerViewMode and browserUsageHistory to SettingsSpec.js
- Already had save logic in BrowserPickerModal.qml

AppPickerModal/filePickerModal (files):
- Added appPickerViewMode and filePickerUsageHistory to SettingsSpec.js
- Added appPickerViewMode and filePickerUsageHistory properties to SettingsData.qml
- Added viewMode binding and onViewModeChanged handler to filePickerModal

Both modals now properly persist user's view preference (list/grid) and
usage history between sessions.

Fixes view mode reverting to default grid after restarting DMS/QuickShell
for both 'dms open https://...' and 'dms open file.pdf' workflows.
2026-01-23 19:39:13 -05:00

2378 lines
87 KiB
QML

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 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 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 bool runUserMatugenTemplates: true
property string matugenTargetMonitor: ""
property real popupTransparency: 1.0
property real dockTransparency: 1
property string widgetBackgroundColor: "sch"
property string widgetColorMode: "default"
property real cornerRadius: 12
property 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 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 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 groupWorkspaceApps: true
property int maxWorkspaceIcons: 3
property bool workspaceFollowFocus: false
property bool showOccupiedWorkspacesOnly: false
property bool reverseScrolling: false
property bool dwlShowAllTags: 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 audioVisualizerEnabled: true
property string audioScrollMode: "volume"
property bool clockCompactMode: false
property bool focusedWindowCompactMode: false
property bool runningAppsCompactMode: true
property bool keyboardLayoutNameCompactMode: false
property bool runningAppsCurrentWorkspace: false
property bool runningAppsGroupByApp: false
property var appIdSubstitutions: []
property string centeringMode: "index"
property string clockDateFormat: ""
property string lockDateFormat: ""
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 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 string _legacyWeatherLocation: "New York, NY"
property string _legacyWeatherCoordinates: "40.7128,-74.0060"
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 vpnLastConnected: ""
property string iconTheme: "System Default"
property var availableIconThemes: ["System Default"]
property string systemDefaultIconTheme: ""
property bool qt5ctAvailable: false
property bool qt6ctAvailable: false
property bool gtkAvailable: false
property 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 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 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: true
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 showDock: false
property bool dockAutoHide: false
property bool dockSmartAutoHide: false
property bool dockGroupByApp: false
property bool dockOpenOnOverview: false
property int dockPosition: SettingsData.Position.Bottom
property real dockSpacing: 4
property real dockBottomGap: 0
property real dockMargin: 0
property real dockIconSize: 40
property string dockIndicatorStyle: "circle"
property bool 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 bool notificationOverlayEnabled: false
property int overviewRows: 2
property int overviewColumns: 5
property real overviewScale: 0.16
property bool modalDarkenBackground: true
property bool lockScreenShowPowerActions: true
property bool 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 enableFprint: false
property int maxFprintTries: 15
property bool fprintdAvailable: false
property string lockScreenActiveMonitor: "all"
property string lockScreenInactiveColor: "#000000"
property int lockScreenNotificationMode: 0
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 bool notificationHistoryEnabled: true
property int notificationHistoryMaxCount: 50
property int notificationHistoryMaxAgeDays: 7
property bool notificationHistorySaveLow: true
property bool notificationHistorySaveNormal: true
property bool notificationHistorySaveCritical: true
property bool osdAlwaysShowValue: false
property int osdPosition: SettingsData.Position.BottomCenter
property bool osdVolumeEnabled: true
property bool osdMediaVolumeEnabled: true
property bool osdBrightnessEnabled: true
property bool osdIdleInhibitorEnabled: true
property bool osdMicMuteEnabled: true
property bool osdCapsLockEnabled: true
property bool osdPowerProfileEnabled: true
property bool 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 barConfigs: [
{
"id": "default",
"name": "Main Bar",
"enabled": true,
"position": 0,
"screenPreferences": ["all"],
"showOnLastDisplay": true,
"leftWidgets": ["launcherButton", "workspaceSwitcher", "focusedWindow"],
"centerWidgets": ["music", "clock", "weather"],
"rightWidgets": ["systemTray", "clipboard", "cpuUsage", "memUsage", "notificationButton", "battery", "controlCenterButton"],
"spacing": 4,
"innerPadding": 4,
"bottomGap": 0,
"transparency": 1.0,
"widgetTransparency": 1.0,
"squareCorners": false,
"noBackground": false,
"gothCornersEnabled": false,
"gothCornerRadiusOverride": false,
"gothCornerRadiusValue": 12,
"borderEnabled": false,
"borderColor": "surfaceText",
"borderOpacity": 1.0,
"borderThickness": 1,
"widgetOutlineEnabled": false,
"widgetOutlineColor": "primary",
"widgetOutlineOpacity": 1.0,
"widgetOutlineThickness": 1,
"fontScale": 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": "text",
"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
Component.onCompleted: {
if (!isGreeterMode) {
Processes.settingsRoot = root;
loadSettings();
initializeListModels();
Processes.detectFprintd();
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();
}
function updateGtkIconTheme() {
const gtkThemeName = (iconTheme === "System Default") ? systemDefaultIconTheme : iconTheme;
if (gtkThemeName === "System Default" || gtkThemeName === "")
return;
if (typeof DMSService !== "undefined" && DMSService.apiVersion >= 3 && typeof PortalService !== "undefined") {
PortalService.setSystemIconTheme(gtkThemeName);
}
const configScript = `mkdir -p ${_configDir}/gtk-3.0 ${_configDir}/gtk-4.0
for config_dir in ${_configDir}/gtk-3.0 ${_configDir}/gtk-4.0; do
settings_file="$config_dir/settings.ini"
if [ -f "$settings_file" ]; then
if grep -q "^gtk-icon-theme-name=" "$settings_file"; then
sed -i 's/^gtk-icon-theme-name=.*/gtk-icon-theme-name=${gtkThemeName}/' "$settings_file"
else
if grep -q "\\[Settings\\]" "$settings_file"; then
sed -i '/\\[Settings\\]/a gtk-icon-theme-name=${gtkThemeName}' "$settings_file"
else
echo -e '\\n[Settings]\\ngtk-icon-theme-name=${gtkThemeName}' >> "$settings_file"
fi
fi
else
echo -e '[Settings]\\ngtk-icon-theme-name=${gtkThemeName}' > "$settings_file"
fi
done
rm -rf ~/.cache/icon-cache ~/.cache/thumbnails 2>/dev/null || true
pkill -HUP -f 'gtk' 2>/dev/null || true`;
Quickshell.execDetached(["sh", "-lc", configScript]);
}
function updateQtIconTheme() {
const qtThemeName = (iconTheme === "System Default") ? "" : iconTheme;
if (!qtThemeName)
return;
const home = _homeUrl.replace("file://", "").replace(/'/g, "'\\''");
const qtThemeNameEscaped = qtThemeName.replace(/'/g, "'\\''");
const script = `mkdir -p ${_configDir}/qt5ct ${_configDir}/qt6ct ${_configDir}/environment.d 2>/dev/null || true
update_qt_icon_theme() {
local config_file="$1"
local theme_name="$2"
if [ -f "$config_file" ]; then
if grep -q "^\\[Appearance\\]" "$config_file"; then
if grep -q "^icon_theme=" "$config_file"; then
sed -i "s/^icon_theme=.*/icon_theme=$theme_name/" "$config_file"
else
sed -i "/^\\[Appearance\\]/a icon_theme=$theme_name" "$config_file"
fi
else
printf "\\n[Appearance]\\nicon_theme=%s\\n" "$theme_name" >> "$config_file"
fi
else
printf "[Appearance]\\nicon_theme=%s\\n" "$theme_name" > "$config_file"
fi
}
update_qt_icon_theme ${_configDir}/qt5ct/qt5ct.conf '${qtThemeNameEscaped}'
update_qt_icon_theme ${_configDir}/qt6ct/qt6ct.conf '${qtThemeNameEscaped}'
rm -rf '${home}'/.cache/icon-cache '${home}'/.cache/thumbnails 2>/dev/null || true`;
Quickshell.execDetached(["sh", "-lc", script]);
}
readonly property var _hooks: ({
"applyStoredTheme": applyStoredTheme,
"regenSystemThemes": regenSystemThemes,
"updateCompositorLayout": updateCompositorLayout,
"applyStoredIconTheme": applyStoredIconTheme,
"updateBarConfigs": updateBarConfigs,
"updateCompositorCursor": updateCompositorCursor
})
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;
_loadedSettingsSnapshot = JSON.stringify(Store.toJson(root));
_hasLoaded = true;
applyStoredTheme();
applyStoredIconTheme();
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();
applyStoredIconTheme();
} 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 loadPluginSettings() {
_pluginSettingsLoading = true;
parsePluginSettings(pluginSettingsFile.text());
_pluginSettingsLoading = false;
}
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;
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 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();
saveSettings();
if (typeof Theme !== "undefined" && Theme.currentTheme === Theme.dynamic)
Theme.generateSystemThemesFromCurrentTheme();
}
function setCursorTheme(themeName) {
const updated = JSON.parse(JSON.stringify(cursorSettings));
updated.theme = themeName;
cursorSettings = updated;
saveSettings();
updateCompositorCursor();
}
function setCursorSize(size) {
const updated = JSON.parse(JSON.stringify(cursorSettings));
updated.size = size;
cursorSettings = updated;
saveSettings();
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() {
updateXResources();
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}"
temp_file="\${xresources_file}.tmp.$$"
theme_name="${themeName}"
cursor_size="${size}"
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();
}
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) {
var stored = registryThemeVariants[themeId];
if (stored && typeof stored === "object")
return stored;
return defaults || {};
}
function setRegistryThemeMultiVariant(themeId, flavor, accent) {
var variants = JSON.parse(JSON.stringify(registryThemeVariants));
variants[themeId] = {
flavor: flavor,
accent: accent
};
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();
}
ListModel {
id: leftWidgetsModel
}
ListModel {
id: centerWidgetsModel
}
ListModel {
id: rightWidgetsModel
}
property Process testNotificationProcess
testNotificationProcess: Process {
command: []
running: false
}
property Timer testNotifTimer1
testNotifTimer1: Timer {
interval: 400
repeat: false
onTriggered: sendTestNotification(1)
}
property Timer testNotifTimer2
testNotifTimer2: Timer {
interval: 800
repeat: false
onTriggered: sendTestNotification(2)
}
property alias settingsFile: settingsFile
FileView {
id: settingsFile
path: isGreeterMode ? "" : StandardPaths.writableLocation(StandardPaths.ConfigLocation) + "/DankMaterialShell/settings.json"
blockLoading: true
blockWrites: true
atomicWrites: true
watchChanges: !isGreeterMode
onLoaded: {
if (isGreeterMode)
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;
_loadedSettingsSnapshot = JSON.stringify(Store.toJson(root));
_hasLoaded = true;
applyStoredTheme();
applyStoredIconTheme();
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
watchChanges: !isGreeterMode
onLoaded: {
if (!isGreeterMode) {
parsePluginSettings(pluginSettingsFile.text());
}
}
onLoadFailed: error => {
if (!isGreeterMode) {
pluginSettings = {};
}
}
}
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");
}
}
}
}