.pragma library .import "./SettingsSpec.js" as SpecModule .import "../../DCommon/Common/settings/SpecUtil.js" as Util .import "../../DCommon/Common/Shape.js" as Shape .import "./BarWidgetDefaults.js" as WidgetDefaults .import "./DockConfig.js" as DockConfig .import "../GridLayout.js" as GridLayout var PIN_KEYS = ["brightnessDevicePins", "wifiNetworkPins", "bluetoothDevicePins", "audioInputDevicePins", "audioOutputDevicePins"]; var SESSION_MOVED_KEYS = ["niriOutputSettings", "hyprlandOutputSettings", "activeDisplayProfile", "activeDisplayProfileModes", "desktopWidgetGridSettings", "greeterSyncPending", "greeterSyncBaseline", "lastAppliedIconTheme"]; var CACHE_MOVED_KEYS = ["browserUsageHistory", "filePickerUsageHistory"]; var SESSION_BACKED_PLUGIN_IDS = ["dankNotepadModule"]; // Superseded by desktopWidgetInstances at v4; nothing has written them since var STALE_WIDGET_KEYS = ["desktopClockEnabled", "desktopClockStyle", "desktopClockTransparency", "desktopClockColorMode", "desktopClockCustomColor", "desktopClockShowDate", "desktopClockShowAnalogNumbers", "desktopClockShowAnalogSeconds", "desktopClockX", "desktopClockY", "desktopClockWidth", "desktopClockHeight", "desktopClockDisplayPreferences", "systemMonitorEnabled", "systemMonitorShowHeader", "systemMonitorTransparency", "systemMonitorColorMode", "systemMonitorCustomColor", "systemMonitorShowCpu", "systemMonitorShowCpuGraph", "systemMonitorShowCpuTemp", "systemMonitorShowGpuTemp", "systemMonitorGpuPciId", "systemMonitorShowMemory", "systemMonitorShowMemoryGraph", "systemMonitorShowNetwork", "systemMonitorShowNetworkGraph", "systemMonitorShowDisk", "systemMonitorShowTopProcesses", "systemMonitorTopProcessCount", "systemMonitorTopProcessSortBy", "systemMonitorGraphInterval", "systemMonitorLayoutMode", "systemMonitorX", "systemMonitorY", "systemMonitorWidth", "systemMonitorHeight", "systemMonitorDisplayPreferences", "systemMonitorVariants", "desktopWidgetPositions"]; var BAR_WIDGET_LIST_KEYS = ["leftWidgets", "centerWidgets", "rightWidgets"]; var CC_HEADER_IDS = ["userCard", "quickActions", "header"]; var CC_ACTION_TILE_IDS = ["settings", "lock", "power"]; var CC_USER_KEYS = ["w", "h", "col", "row", "hostname", "compositor", "uptime", "badge", "background"]; var CC_FULL_HEIGHT_IDS = ["user", "volumeSlider", "inputVolumeSlider", "brightnessSlider"]; // Grid rules as of v41, frozen for that migration. var CC_V41_GRID_STEP = 0.5; var CC_V41_DEFAULT_COLUMNS = 8; var CC_V41_MINIMUM_COLUMNS = 6; var CC_V41_SLIDER_IDS = ["volumeSlider", "inputVolumeSlider", "brightnessSlider"]; var CC_V41_FOOTER_CELLS = { "runningApps": 4, "user": 3, "volumeSlider": 4, "inputVolumeSlider": 4, "brightnessSlider": 4 }; var REMOVED_KEYS_V21 = ["showBattery", "showCapsLockIndicator", "showClipboard", "showClock", "showControlCenterButton", "showCpuUsage", "showFocusedWindow", "showLauncherButton", "showMemUsage", "showMusic", "showNotificationButton", "showPrivacyButton", "showSystemTray", "showWeather", "showWorkspaceSwitcher", "hideBrightnessSlider", "updaterHideWidget", "workspaceScrolling", "appLauncherViewMode", "spotlightModalViewMode", "audioDeviceScrollVolumeEnabled", "desktopClockX", "desktopClockY", "desktopClockWidth", "desktopClockHeight", "desktopClockDisplayPreferences", "systemMonitorX", "systemMonitorY", "systemMonitorWidth", "systemMonitorHeight", "systemMonitorDisplayPreferences", "systemMonitorVariants"]; // v18: the shell-wide island settings became per-bar-config island* keys var ISLAND_KEY_MOVES = { dankIslandFloating: "islandFloating", dankIslandUseOverlayLayer: "islandUseOverlayLayer", dankIslandReserveHeight: "islandReserveThickness", dankIslandCompactHeight: "islandCompactThickness", dankIslandOuterGap: "islandOuterGap", dankIslandHorizontalOffset: "islandAlongOffset", dankIslandInteractionMode: "islandInteractionMode", dankIslandHoverOpenDelay: "islandHoverOpenDelay", dankIslandHoverCloseDelay: "islandHoverCloseDelay", dankIslandPalette: "islandPalette", dankIslandTransparency: "islandTransparency", dankIslandCornerRadius: "islandCornerRadius", dankIslandHighContrast: "islandHighContrast", dankIslandMediaClockVisible: "islandMediaClockVisible", dankIslandNotificationBadgeClearOnOpen: "islandNotificationBadgeClearOnOpen", dankIslandNotificationExpand: "islandNotificationExpand", dankIslandHomeCompactTight: "islandHomeCompactTight", dankIslandHomeClockDisplay: "islandHomeClockDisplay", dankIslandHomeVolumeDisplay: "islandHomeVolumeDisplay", dankIslandHomeBrightnessDisplay: "islandHomeBrightnessDisplay", dankIslandHomeLayout: "islandHomeLayout", dankIslandBatteryStyle: "islandBatteryStyle", dankIslandSatellitesEnabled: "islandSatellitesEnabled", dankIslandSatellitePosition: "islandSatellitePosition", dankIslandSatelliteGap: "islandSatelliteGap", dankIslandSatelliteBackground: "islandSatelliteBackground", dankIslandSatelliteGothCorners: "islandSatelliteGothCorners", dankIslandSatelliteTransparency: "islandSatelliteTransparency", dankIslandReducedMotion: "islandReducedMotion", dankIslandSpringStiffness: "islandSpringStiffness", dankIslandSpringDamping: "islandSpringDamping", dankIslandSpringMass: "islandSpringMass" }; function migrateBatteryPillStyle(target) { if (!target || typeof target !== "object" || target.batteryPillStyle === undefined) return; var pill = target.batteryPillStyle === true; delete target.batteryPillStyle; if (target.batteryStyle !== undefined && target.batteryStyle !== "icon") return; if (pill) target.batteryStyle = "solid"; } function strengthFromWindowRadius(radius) { return Shape.strengthFromRadius((radius ?? Shape.corners.l) * Shape.corners.m / Shape.corners.l); } function withoutInstancePositions(instances) { if (!Array.isArray(instances)) return instances; return instances.map(function (inst) { if (!inst || !inst.positions) return inst; var copy = Object.assign({}, inst); delete copy.positions; return copy; }); } function withoutSessionBackedPluginState(pluginSettings) { if (!pluginSettings) return pluginSettings; var copy = Object.assign({}, pluginSettings); for (var i = 0; i < SESSION_BACKED_PLUGIN_IDS.length; i++) { delete copy[SESSION_BACKED_PLUGIN_IDS[i]]; } return copy; } function extractSessionPayload(obj) { if (!obj) return null; var payload = {}; for (var i = 0; i < SESSION_MOVED_KEYS.length; i++) { var key = SESSION_MOVED_KEYS[i]; if (key in obj) payload[key] = obj[key]; } var positions = {}; var instances = Array.isArray(obj.desktopWidgetInstances) ? obj.desktopWidgetInstances : []; for (var i = 0; i < instances.length; i++) { var inst = instances[i]; if (inst && inst.id && inst.positions && Object.keys(inst.positions).length > 0) { positions[inst.id] = inst.positions; } } if (Object.keys(positions).length > 0) payload.desktopWidgetInstancePositions = positions; var pluginState = {}; for (var i = 0; i < SESSION_BACKED_PLUGIN_IDS.length; i++) { var id = SESSION_BACKED_PLUGIN_IDS[i]; if (obj.builtInPluginSettings && obj.builtInPluginSettings[id]) { pluginState[id] = obj.builtInPluginSettings[id]; } } if (Object.keys(pluginState).length > 0) payload.builtInPluginState = pluginState; return Object.keys(payload).length > 0 ? payload : null; } function extractCachePayload(obj) { if (!obj) return null; var payload = {}; for (var i = 0; i < CACHE_MOVED_KEYS.length; i++) { var key = CACHE_MOVED_KEYS[i]; if (obj[key] && Object.keys(obj[key]).length > 0) payload[key] = obj[key]; } return Object.keys(payload).length > 0 ? payload : null; } function extractPins(obj) { if (!obj) return null; var pins = null; for (var i = 0; i < PIN_KEYS.length; i++) { var value = obj[PIN_KEYS[i]]; if (!value || Object.keys(value).length === 0) continue; if (!pins) pins = {}; pins[PIN_KEYS[i]] = value; } return pins; } function parse(root, jsonObj) { jsonObj = migrateToVersion(jsonObj || {}, root.settingsConfigVersion) || jsonObj || {}; jsonObj.dockConfigs = DockConfig.normalize(jsonObj.dockConfigs); // the dock pages configure whichever dock is selected, so an empty list leaves them blank if (!jsonObj.dockConfigs.length) delete jsonObj.dockConfigs; var SPEC = SpecModule.SPEC; if (!jsonObj) return; for (var k in SPEC) { if (k === "pluginSettings") continue; // Runtime-only keys are never in the JSON; resetting them here // would wipe values set by detection processes on every reload. if (SPEC[k].persist === false) continue; if (!(k in jsonObj)) { root[k] = Util.cloneDef(SPEC[k].def); } } for (var k in jsonObj) { if (!SPEC[k]) continue; if (k === "pluginSettings") continue; var raw = jsonObj[k]; var spec = SPEC[k]; var coerce = spec.coerce; root[k] = coerce ? (coerce(raw) !== undefined ? coerce(raw) : root[k]) : raw; } } function toJson(root, setKeys) { var SPEC = SpecModule.SPEC; var out = {}; for (var k in SPEC) { if (SPEC[k].persist === false) continue; if (k === "pluginSettings") continue; var value = root[k]; if (k === "desktopWidgetInstances") value = withoutInstancePositions(value); if (k === "builtInPluginSettings") value = withoutSessionBackedPluginState(value); if (!setKeys.has(k) && Util.isDefault(value, SPEC[k].def)) continue; out[k] = value; } out.configVersion = root.settingsConfigVersion; return out; } function migrateToVersion(obj, targetVersion) { if (!obj) return null; var settings = JSON.parse(JSON.stringify(obj)); var currentVersion = settings.configVersion || 0; if (currentVersion >= targetVersion) { return null; } if (currentVersion < 2 && targetVersion >= 2) { console.info("Migrating settings from version", currentVersion, "to version 2"); if (settings.barConfigs === undefined) { var position = 0; if (settings.dankBarAtBottom !== undefined || settings.topBarAtBottom !== undefined) { var atBottom = settings.dankBarAtBottom !== undefined ? settings.dankBarAtBottom : settings.topBarAtBottom; position = atBottom ? 1 : 0; } else if (settings.dankBarPosition !== undefined) { position = settings.dankBarPosition; } var defaultConfig = { id: "default", name: "Main Bar", enabled: true, position: position, screenPreferences: ["all"], showOnLastDisplay: true, leftWidgets: settings.dankBarLeftWidgets || ["launcherButton", "workspaceSwitcher", "focusedWindow"], centerWidgets: settings.dankBarCenterWidgets || ["music", "clock", "weather"], rightWidgets: settings.dankBarRightWidgets || ["systemTray", "clipboard", "cpuUsage", "memUsage", "notificationButton", "battery", "controlCenterButton"], spacing: settings.dankBarSpacing !== undefined ? settings.dankBarSpacing : 4, innerPadding: settings.dankBarInnerPadding !== undefined ? settings.dankBarInnerPadding : 4, bottomGap: settings.dankBarBottomGap !== undefined ? settings.dankBarBottomGap : 0, transparency: settings.dankBarTransparency !== undefined ? settings.dankBarTransparency : 1.0, widgetTransparency: settings.dankBarWidgetTransparency !== undefined ? settings.dankBarWidgetTransparency : 1.0, squareCorners: settings.dankBarSquareCorners !== undefined ? settings.dankBarSquareCorners : false, noBackground: settings.dankBarNoBackground !== undefined ? settings.dankBarNoBackground : false, gothCornersEnabled: settings.dankBarGothCornersEnabled !== undefined ? settings.dankBarGothCornersEnabled : false, gothCornerRadiusOverride: settings.dankBarGothCornerRadiusOverride !== undefined ? settings.dankBarGothCornerRadiusOverride : false, gothCornerRadiusValue: settings.dankBarGothCornerRadiusValue !== undefined ? settings.dankBarGothCornerRadiusValue : 12, borderEnabled: settings.dankBarBorderEnabled !== undefined ? settings.dankBarBorderEnabled : false, borderColor: settings.dankBarBorderColor || "surfaceText", borderOpacity: settings.dankBarBorderOpacity !== undefined ? settings.dankBarBorderOpacity : 1.0, borderThickness: settings.dankBarBorderThickness !== undefined ? settings.dankBarBorderThickness : 1, fontScale: settings.dankBarFontScale !== undefined ? settings.dankBarFontScale : 1.0, autoHide: settings.dankBarAutoHide !== undefined ? settings.dankBarAutoHide : false, autoHideDelay: settings.dankBarAutoHideDelay !== undefined ? settings.dankBarAutoHideDelay : 250, openOnOverview: settings.dankBarOpenOnOverview !== undefined ? settings.dankBarOpenOnOverview : false, visible: settings.dankBarVisible !== undefined ? settings.dankBarVisible : true, popupGapsAuto: settings.popupGapsAuto !== undefined ? settings.popupGapsAuto : true, popupGapsManual: settings.popupGapsManual !== undefined ? settings.popupGapsManual : 4 }; settings.barConfigs = [defaultConfig]; var legacyKeys = ["dankBarLeftWidgets", "dankBarCenterWidgets", "dankBarRightWidgets", "dankBarWidgetOrder", "dankBarAutoHide", "dankBarAutoHideDelay", "dankBarOpenOnOverview", "dankBarVisible", "dankBarSpacing", "dankBarBottomGap", "dankBarInnerPadding", "dankBarPosition", "dankBarSquareCorners", "dankBarNoBackground", "dankBarGothCornersEnabled", "dankBarGothCornerRadiusOverride", "dankBarGothCornerRadiusValue", "dankBarBorderEnabled", "dankBarBorderColor", "dankBarBorderOpacity", "dankBarBorderThickness", "popupGapsAuto", "popupGapsManual", "dankBarAtBottom", "topBarAtBottom", "dankBarTransparency", "dankBarWidgetTransparency"]; for (var i = 0; i < legacyKeys.length; i++) { delete settings[legacyKeys[i]]; } console.info("Migrated single bar settings to barConfigs"); } settings.configVersion = 2; } if (currentVersion < 3 && targetVersion >= 3) { console.info("Migrating settings from version", currentVersion, "to version 3"); console.info("Per-widget controlCenterButton config now supported via widgetData properties"); settings.configVersion = 3; } if (currentVersion < 4 && targetVersion >= 4) { console.info("Migrating settings from version", currentVersion, "to version 4"); console.info("Migrating desktop widgets to unified desktopWidgetInstances"); var instances = []; if (settings.desktopClockEnabled) { var clockPositions = {}; if (settings.desktopClockX !== undefined && settings.desktopClockX >= 0) { clockPositions["default"] = { x: settings.desktopClockX, y: settings.desktopClockY, width: settings.desktopClockWidth || 280, height: settings.desktopClockHeight || 180 }; } instances.push({ id: "dw_clock_primary", widgetType: "desktopClock", name: "Desktop Clock", enabled: true, config: { style: settings.desktopClockStyle || "analog", transparency: settings.desktopClockTransparency !== undefined ? settings.desktopClockTransparency : 0.8, colorMode: settings.desktopClockColorMode || "primary", customColor: settings.desktopClockCustomColor || "#ffffff", showDate: settings.desktopClockShowDate !== false, showAnalogNumbers: settings.desktopClockShowAnalogNumbers || false, showAnalogSeconds: settings.desktopClockShowAnalogSeconds !== false, displayPreferences: settings.desktopClockDisplayPreferences || ["all"] }, positions: clockPositions }); } if (settings.systemMonitorEnabled) { var sysmonPositions = {}; if (settings.systemMonitorX !== undefined && settings.systemMonitorX >= 0) { sysmonPositions["default"] = { x: settings.systemMonitorX, y: settings.systemMonitorY, width: settings.systemMonitorWidth || 320, height: settings.systemMonitorHeight || 480 }; } instances.push({ id: "dw_sysmon_primary", widgetType: "systemMonitor", name: "System Monitor", enabled: true, config: { showHeader: settings.systemMonitorShowHeader !== false, transparency: settings.systemMonitorTransparency !== undefined ? settings.systemMonitorTransparency : 0.8, colorMode: settings.systemMonitorColorMode || "primary", customColor: settings.systemMonitorCustomColor || "#ffffff", showCpu: settings.systemMonitorShowCpu !== false, showCpuGraph: settings.systemMonitorShowCpuGraph !== false, showCpuTemp: settings.systemMonitorShowCpuTemp !== false, showGpuTemp: settings.systemMonitorShowGpuTemp || false, gpuPciId: settings.systemMonitorGpuPciId || "", showMemory: settings.systemMonitorShowMemory !== false, showMemoryGraph: settings.systemMonitorShowMemoryGraph !== false, showNetwork: settings.systemMonitorShowNetwork !== false, showNetworkGraph: settings.systemMonitorShowNetworkGraph !== false, showDisk: settings.systemMonitorShowDisk !== false, showTopProcesses: settings.systemMonitorShowTopProcesses || false, topProcessCount: settings.systemMonitorTopProcessCount || 3, topProcessSortBy: settings.systemMonitorTopProcessSortBy || "cpu", layoutMode: settings.systemMonitorLayoutMode || "auto", graphInterval: settings.systemMonitorGraphInterval || 60, displayPreferences: settings.systemMonitorDisplayPreferences || ["all"] }, positions: sysmonPositions }); } var variants = settings.systemMonitorVariants || []; for (var i = 0; i < variants.length; i++) { var v = variants[i]; instances.push({ id: v.id, widgetType: "systemMonitor", name: v.name || ("System Monitor " + (i + 2)), enabled: true, config: v.config || {}, positions: v.positions || {} }); } settings.desktopWidgetInstances = instances; settings.configVersion = 4; } if (currentVersion < 5 && targetVersion >= 5) { console.info("Migrating settings from version", currentVersion, "to version 5"); console.info("Moving sensitive data (weather location, coordinates) to session.json"); delete settings.weatherLocation; delete settings.weatherCoordinates; settings.configVersion = 5; } if (currentVersion < 6 && targetVersion >= 6) { console.info("Migrating settings from version", currentVersion, "to version 6"); if (settings.barElevationEnabled === undefined) { var legacyBars = Array.isArray(settings.barConfigs) ? settings.barConfigs : []; var hadLegacyBarShadowEnabled = false; for (var j = 0; j < legacyBars.length; j++) { var legacyIntensity = Number(legacyBars[j] && legacyBars[j].shadowIntensity); if (!isNaN(legacyIntensity) && legacyIntensity > 0) { hadLegacyBarShadowEnabled = true; break; } } settings.barElevationEnabled = hadLegacyBarShadowEnabled; } settings.configVersion = 6; } if (currentVersion < 11 && targetVersion >= 11) { settings.configVersion = 11; } if (currentVersion < 12 && targetVersion >= 12) { console.info("Migrating settings from version", currentVersion, "to version 12"); if (settings.batteryNotificationType !== undefined) { settings.batteryChargeLimitNotificationType = settings.batteryNotificationType; settings.batteryLowNotificationType = settings.batteryNotificationType; settings.batteryCriticalNotificationType = settings.batteryNotificationType; delete settings.batteryNotificationType; } settings.configVersion = 12; } if (currentVersion < 13 && targetVersion >= 13) { console.info("Migrating settings from version", currentVersion, "to version 13"); console.info("Moving device and network pins to cache.json"); for (var p = 0; p < PIN_KEYS.length; p++) { delete settings[PIN_KEYS[p]]; } settings.configVersion = 13; } if (currentVersion < 14 && targetVersion >= 14) { console.info("Migrating settings from version", currentVersion, "to version 14"); console.info("Dropping keys that match defaults; settings.json now stores only changed values"); Util.stripDefaults(settings, SpecModule.SPEC); settings.configVersion = 14; } if (currentVersion < 15 && targetVersion >= 15) { console.info("Migrating settings from version", currentVersion, "to version 15"); console.info("Moving machine-specific state to session.json and usage histories to cache.json"); var movedKeys = SESSION_MOVED_KEYS.concat(CACHE_MOVED_KEYS, STALE_WIDGET_KEYS); for (var i = 0; i < movedKeys.length; i++) { delete settings[movedKeys[i]]; } if (Array.isArray(settings.desktopWidgetInstances)) { settings.desktopWidgetInstances = withoutInstancePositions(settings.desktopWidgetInstances); } if (settings.builtInPluginSettings) { settings.builtInPluginSettings = withoutSessionBackedPluginState(settings.builtInPluginSettings); } settings.configVersion = 15; } if (currentVersion < 16 && targetVersion >= 16) { console.info("Migrating settings from version", currentVersion, "to version 16"); console.info("Moving Niri overview close behavior to the window focus setting"); if (settings.closeNiriOverviewOnWindowFocus === undefined && settings.spotlightCloseNiriOverview !== undefined) { settings.closeNiriOverviewOnWindowFocus = settings.spotlightCloseNiriOverview; } delete settings.spotlightCloseNiriOverview; settings.configVersion = 16; } if (currentVersion < 17 && targetVersion >= 17) { console.info("Migrating settings from version", currentVersion, "to version 17"); console.info("Converting batteryPillStyle to batteryStyle"); migrateBatteryPillStyle(settings); var bars = Array.isArray(settings.barConfigs) ? settings.barConfigs : []; for (var b = 0; b < bars.length; b++) { for (var k = 0; k < BAR_WIDGET_LIST_KEYS.length; k++) { var widgets = bars[b] && bars[b][BAR_WIDGET_LIST_KEYS[k]]; if (!Array.isArray(widgets)) continue; for (var w = 0; w < widgets.length; w++) migrateBatteryPillStyle(widgets[w]); } } settings.configVersion = 17; } if (currentVersion < 18 && targetVersion >= 18) { console.info("Migrating settings from version", currentVersion, "to version 18"); console.info("Moving the shell-wide Dank Island onto its bar config as a per-instance mode"); var islandId = settings.dankIslandBarId; var islandBars = Array.isArray(settings.barConfigs) ? settings.barConfigs : []; for (var ib = 0; ib < islandBars.length; ib++) { if (!islandBars[ib] || islandBars[ib].id !== islandId) continue; islandBars[ib].island = true; for (var oldKey in ISLAND_KEY_MOVES) { if (!(oldKey in settings)) continue; islandBars[ib][ISLAND_KEY_MOVES[oldKey]] = settings[oldKey]; } } for (var dropKey in ISLAND_KEY_MOVES) delete settings[dropKey]; delete settings.dankIslandBarId; delete settings.dankIslandSatelliteSwoopRadius; settings.configVersion = 18; } if (currentVersion < 19 && targetVersion >= 19) { console.info("Migrating settings from version", currentVersion, "to version 19"); console.info("Moving global bar widget options onto each widget instance"); migrateBarWidgetGlobals(settings); settings.configVersion = 19; } if (currentVersion < 20 && targetVersion >= 20) { console.info("Migrating settings from version", currentVersion, "to version 20"); console.info("Marking bars and dock that already match the surface opacity as following the interface style"); var surfaceOpacity = settings.popupTransparency ?? 1.0; var bars19 = Array.isArray(settings.barConfigs) ? settings.barConfigs : []; for (var i19 = 0; i19 < bars19.length; i19++) { if (!bars19[i19] || bars19[i19].followInterfaceStyle !== undefined) continue; bars19[i19].followInterfaceStyle = (bars19[i19].transparency ?? 1.0) === surfaceOpacity; } if (settings.dockFollowInterfaceStyle === undefined) settings.dockFollowInterfaceStyle = (settings.dockTransparency ?? 1.0) === surfaceOpacity; settings.configVersion = 20; } if (currentVersion < 21 && targetVersion >= 21) { console.info("Migrating settings from version", currentVersion, "to version 21"); console.info("Dropping settings keys that no longer have a consumer"); for (var i21 = 0; i21 < REMOVED_KEYS_V21.length; i21++) delete settings[REMOVED_KEYS_V21[i21]]; settings.configVersion = 21; } if (currentVersion < 22 && targetVersion >= 22) { const moves = [["waveProgressEnabled", "waveProgress", true], ["mediaWallpaperEnabled", "albumArtBackdrop", true], ["mediaUseAlbumArtAccent", "albumArtAccent", true], ["appleMusicAnimatedArtEnabled", "animatedArt", false]]; const options = Object.assign({}, settings.dashOptions ?? {}); const media = Object.assign({}, options.media ?? {}); for (const [oldKey, key, def] of moves) { const value = settings[oldKey]; delete settings[oldKey]; if (media[key] !== undefined || typeof value !== "boolean" || value === def) continue; media[key] = value; } if (Object.keys(media).length > 0) options.media = media; if (Object.keys(options).length > 0) settings.dashOptions = options; settings.configVersion = 22; } if (currentVersion < 23 && targetVersion >= 23) { if (settings.radiusStrength === undefined) settings.radiusStrength = strengthFromWindowRadius(settings.cornerRadius); delete settings.cornerRadius; settings.configVersion = 23; } if (currentVersion < 24 && targetVersion >= 24) { const removed = ["greeterFontFamily", "greeterLockDateFormat", "greeterWallpaperFillMode", "greeterWallpaperPath", "greeterShowWeather"]; for (const key of removed) delete settings[key]; settings.dockConfigs = DockConfig.migrate(settings); for (const key of Object.keys(settings)) { if (key === "showDock" || (key !== "dockConfigs" && /^dock[A-Z]/.test(key))) delete settings[key]; } if (settings.screenPreferences) delete settings.screenPreferences.dock; if (settings.showOnLastDisplay) delete settings.showOnLastDisplay.dock; settings.configVersion = 24; } if (currentVersion < 25 && targetVersion >= 25) { const bars = Array.isArray(settings.barConfigs) ? settings.barConfigs : []; for (const bar of bars) { if (!bar) continue; if (bar.removeWidgetPadding === true) bar.widgetPadding = 0; delete bar.removeWidgetPadding; } settings.configVersion = 25; } if (currentVersion < 26 && targetVersion >= 26) { const customSpeed = 4; const speedMoves = [["animationSpeed", "customAnimationDuration", "animationDuration", [0, 250, 500, 750], 500], ["popoutAnimationSpeed", "popoutCustomAnimationDuration", "popoutAnimationDuration", [0, 150, 300, 500], 150], ["modalAnimationSpeed", "modalCustomAnimationDuration", "modalAnimationDuration", [0, 150, 300, 500], 150], ["notificationAnimationSpeed", "notificationCustomAnimationDuration", "notificationAnimationDuration", [0, 200, 400, 600], 400]]; for (const [speedKey, customKey, durationKey, presets, customDefault] of speedMoves) { const speed = settings[speedKey]; const custom = settings[customKey] ?? customDefault; delete settings[speedKey]; delete settings[customKey]; if (speed === undefined || settings[durationKey] !== undefined) continue; settings[durationKey] = speed === customSpeed ? custom : (presets[speed] ?? presets[1]); } settings.configVersion = 26; } if (currentVersion < 27 && targetVersion >= 27) { if (Array.isArray(settings.controlCenterWidgets)) { settings.controlCenterWidgets = settings.controlCenterWidgets.map(widget => { if (!widget || typeof widget !== "object") return widget; const next = Object.assign({}, widget); const width = Number(next.width); delete next.width; const cells = Number.isInteger(next.w) && next.w > 0 ? next.w : Number.isFinite(width) && width > 0 ? Math.max(1, Math.min(4, Math.round(width / 25))) : 2; next.w = cells * 2; if (next.h === undefined) next.h = 1; return next; }); } const sheetWidth = Number(settings.controlCenterWidth); delete settings.controlCenterWidth; const columns = Number(settings.controlCenterColumns); if (Number.isFinite(columns) && columns > 0) settings.controlCenterColumns = Math.max(3, Math.round(columns)) * 2; else if (Number.isFinite(sheetWidth) && sheetWidth > 0) settings.controlCenterColumns = Math.max(3, Math.min(6, Math.round(sheetWidth / (550 / 4)))) * 2; settings.configVersion = 27; } if (currentVersion < 28 && targetVersion >= 28) { const surfaceOpacity = Number.isFinite(Number(settings.popupTransparency)) ? Number(settings.popupTransparency) : 1.0; const bars = Array.isArray(settings.barConfigs) ? settings.barConfigs : []; const frameOpacity = Number(settings.frameOpacity); const frameBar = bars.find(bc => bc && bc.enabled !== false && bc.island !== true); if (frameBar && Number.isFinite(frameOpacity) && frameOpacity !== surfaceOpacity && frameBar.followInterfaceStyle !== false) { frameBar.followInterfaceStyle = false; frameBar.transparency = frameOpacity; } for (const bc of bars) { if (!bc || bc.island !== true) continue; const islandOpacity = Number(bc.islandTransparency); if (Number.isFinite(islandOpacity)) { bc.transparency = islandOpacity; bc.followInterfaceStyle = islandOpacity === surfaceOpacity; } delete bc.islandTransparency; delete bc.islandCornerRadius; } delete settings.frameColor; delete settings.frameOpacity; if (settings.blurBorderEnabled === undefined) settings.blurBorderEnabled = settings.blurEnabled === true; settings.blurBorderSeeded = settings.blurEnabled === true; settings.configVersion = 28; } if (currentVersion < 29 && targetVersion >= 29) { console.info("Migrating settings from version", currentVersion, "to version 29"); console.info("Moving launcher logo options onto each launcher button instance"); migrateBarWidgetGlobals(settings); delete settings.launcherLogoColorInvertOnMode; settings.configVersion = 29; } if (currentVersion < 30 && targetVersion >= 30) { if (settings.dmsWindowsFloating === false) settings.dmsWindowsFloatingSeeded = ["niri", "hyprland", "mango"]; delete settings.dmsWindowsFloating; settings.configVersion = 30; } if (currentVersion < 31 && targetVersion >= 31) { const glassLayers = settings.blurEnabled === true && settings.blurForegroundLayers === false; const foregroundOpacity = Util.percentToUnit(settings.foregroundLayerTransparency) ?? 1.0; const followedOpacity = glassLayers ? 0 : foregroundOpacity; const bars = Array.isArray(settings.barConfigs) ? settings.barConfigs : []; for (const bc of bars) { if (!bc || bc.widgetFollowInterfaceStyle !== undefined) continue; bc.widgetFollowInterfaceStyle = (bc.widgetTransparency ?? 1.0) === followedOpacity; } settings.configVersion = 31; } if (currentVersion < 35 && targetVersion >= 35) { if (Array.isArray(settings.controlCenterWidgets)) settings.controlCenterWidgets = migrateControlCenterHeader(settings.controlCenterWidgets, currentVersion < 33, settings.controlCenterColumns ?? 8); settings.configVersion = 35; } if (currentVersion < 37 && targetVersion >= 37) { if (Array.isArray(settings.controlCenterWidgets) && !settings.controlCenterWidgets.some(widget => widget?.id === "edit")) settings.controlCenterWidgets = settings.controlCenterWidgets.concat([Object.assign({}, SpecModule.SPEC.controlCenterWidgets.def.find(widget => widget.id === "edit"))]); settings.configVersion = 37; } if (currentVersion < 38 && targetVersion >= 38) { migrateLockScreenWidgets(settings); settings.configVersion = 38; } if (currentVersion < 39 && targetVersion >= 39) { migrateIslandReducedMotion(settings); settings.configVersion = 39; } if (currentVersion < 40 && targetVersion >= 40) { migrateScrollColumnBehavior(settings); settings.configVersion = 40; } if (currentVersion < 41 && targetVersion >= 41) { migrateControlCenterFooter(settings); settings.configVersion = 41; } if (currentVersion < 42 && targetVersion >= 42) { migrateDockAppsSizing(settings); settings.configVersion = 42; } return settings; } // v42: dock apps follow the dock's icon size and magnification instead of their own copies function migrateDockAppsSizing(settings) { if (!Array.isArray(settings.dockConfigs)) return; for (const dock of settings.dockConfigs) { for (const widget of Array.isArray(dock?.widgets) ? dock.widgets : []) { if (!widget || typeof widget !== "object") continue; if (widget.appsDockEnlargeOnHover === true && dock.magnification !== true) { dock.magnification = true; dock.magnificationScale = Math.max(105, Math.min(160, Number(widget.appsDockEnlargePercentage) || 125)); } const percentage = Number(widget.appsDockIconSizePercentage); if (percentage > 0 && percentage !== 100) dock.iconSize = Math.round((Number(dock.iconSize) || DockConfig.create("", "").iconSize) * percentage / 100); delete widget.appsDockEnlargeOnHover; delete widget.appsDockEnlargePercentage; delete widget.appsDockIconSizePercentage; } } } // v41: footer entries became a grid row under the rest, keeping their order and widths function migrateControlCenterFooter(settings) { delete settings.controlCenterFooterPosition; if (!Array.isArray(settings.controlCenterWidgets)) return; const kept = []; const starts = []; const ends = []; for (const widget of settings.controlCenterWidgets) { if (!widget || typeof widget !== "object") { kept.push(widget); continue; } const moved = Object.assign({}, widget); const inFooter = moved.footer === true; const atEnd = moved.footerEnd === true; const fills = moved.footerFill === true; const footerW = Number(moved.footerW); // Stale footer keys can sit on any entry. for (const key of ["footer", "footerEnd", "footerW", "footerFill"]) delete moved[key]; if (!inFooter) { kept.push(moved); continue; } const minW = CC_V41_SLIDER_IDS.includes(moved.id) ? 3 : 1; const cells = Number.isFinite(footerW) && footerW > 0 ? Math.round(footerW / CC_V41_GRID_STEP) * CC_V41_GRID_STEP : CC_V41_FOOTER_CELLS[moved.id] || 1; (atEnd ? ends : starts).push({ "widget": moved, "fill": fills, "w": Math.max(minW, fills ? minW : cells) }); } settings.controlCenterWidgets = kept; const footer = starts.concat(ends); if (footer.length === 0) return; const saved = Number(settings.controlCenterColumns); const columns = Number.isFinite(saved) && saved > 0 ? Math.max(CC_V41_MINIMUM_COLUMNS, saved) : CC_V41_DEFAULT_COLUMNS; for (const entry of footer) entry.w = Math.min(columns, entry.w); // Fill entries split the spare width as the strip did. const fillers = footer.filter(entry => entry.fill); const spare = Math.max(0, columns - footer.reduce((sum, entry) => sum + entry.w, 0)); if (fillers.length > 0 && spare > 0) { const share = Math.floor(spare / fillers.length / CC_V41_GRID_STEP) * CC_V41_GRID_STEP; fillers.forEach((entry, i) => entry.w += share + (i === 0 ? spare - share * fillers.length : 0)); } const cards = kept.filter(widget => widget && typeof widget === "object" && !widget.group); let row = GridLayout.packCells(cards, cards.map((card, i) => i), columns, null, CC_V41_GRID_STEP, true).rows; let col = 0; let rowHeight = 1; const endWidth = ends.reduce((sum, entry) => sum + entry.w, 0); for (const entry of footer) { const widget = entry.widget; if (!CC_FULL_HEIGHT_IDS.includes(widget.id)) { widget.h = 1; widget.small = true; } if (entry === ends[0]) col = Math.max(col, columns - endWidth); if (col + entry.w > columns) { col = 0; row += rowHeight; rowHeight = 1; } widget.w = entry.w; widget.col = col; widget.row = row; rowHeight = Math.max(rowHeight, Number.isFinite(widget.h) ? widget.h : 1); col += entry.w; kept.push(widget); } } // v40: the niri-only "column" scroll behavior became the compositor-neutral "focusWindow" function migrateScrollColumnBehavior(settings) { for (const bar of Array.isArray(settings.barConfigs) ? settings.barConfigs : []) { if (!bar || typeof bar !== "object") continue; for (const key of ["scrollXBehavior", "scrollYBehavior"]) { if (bar[key] === "column") bar[key] = "focusWindow"; } } } // v39: the per-island toggle folded into the global reduceMotion; any bar that had it on turns the global one on function migrateIslandReducedMotion(settings) { for (const bar of Array.isArray(settings.barConfigs) ? settings.barConfigs : []) { if (!bar || typeof bar !== "object") continue; const holders = [bar]; for (const sectionId of ["leftWidgets", "centerWidgets", "rightWidgets"]) holders.push(...(Array.isArray(bar[sectionId]) ? bar[sectionId] : []).filter(entry => entry && typeof entry === "object" && entry.id === "island")); for (const holder of holders) { if (holder.islandReducedMotion === true) settings.reduceMotion = true; delete holder.islandReducedMotion; } } } var LOCK_WIDGET_MOVED_KEYS = ["lockScreenShowSystemIcons", "lockScreenShowTime", "lockScreenClockStyle", "lockScreenShowDate", "lockScreenShowPasswordField", "lockScreenShowMediaPlayer", "lockScreenNotificationMode"]; var LOCK_WIDGET_GREETER_KEYS = ["lockScreenShowPowerActions", "lockScreenShowProfileImage", "lockScreenShowWeather"]; function migrateLockScreenWidgets(settings) { if (Array.isArray(settings.lockScreenWidgetInstances)) return; const present = key => key in settings; if (!LOCK_WIDGET_MOVED_KEYS.concat(LOCK_WIDGET_GREETER_KEYS).some(present)) return; const instances = SpecModule.lockWidgetDefaults(); const byId = id => instances.find(inst => inst.id === id); if (present("lockScreenShowTime")) byId("lock_clock").enabled = !!settings.lockScreenShowTime; if (present("lockScreenClockStyle")) byId("lock_clock").config.style = settings.lockScreenClockStyle === "vertical" ? "overlap" : "digital"; if (present("lockScreenShowDate")) byId("lock_date").enabled = !!settings.lockScreenShowDate; byId("lock_auth").config.profileVisibility = settings.lockScreenShowProfileImage === false ? "never" : "always"; byId("lock_auth").config.passwordVisibility = settings.lockScreenShowPasswordField === false ? "typing" : "always"; if (present("lockScreenNotificationMode")) { const mode = Number(settings.lockScreenNotificationMode) || 0; byId("lock_notifications").enabled = mode > 0; if (mode > 0) byId("lock_notifications").config.mode = mode; } if (present("lockScreenShowSystemIcons")) byId("lock_status").enabled = !!settings.lockScreenShowSystemIcons; if (present("lockScreenShowMediaPlayer")) byId("lock_status").config.showMediaPlayer = !!settings.lockScreenShowMediaPlayer; if (present("lockScreenShowWeather")) byId("lock_status").config.showWeather = !!settings.lockScreenShowWeather; if (present("lockScreenShowPowerActions")) byId("lock_power").enabled = !!settings.lockScreenShowPowerActions; settings.lockScreenWidgetInstances = instances; for (const key of LOCK_WIDGET_MOVED_KEYS) delete settings[key]; } function migrateControlCenterHeader(widgets, fixedHeader, columns) { const header = widgets.filter(widget => CC_HEADER_IDS.includes(widget?.id)); if (!fixedHeader && header.length === 0) return widgets; const rest = widgets.filter(widget => !CC_HEADER_IDS.includes(widget?.id)); const actions = header.find(widget => Array.isArray(widget?.actions))?.actions ?? []; const wanted = id => !rest.some(widget => widget?.id === id) && actions.find(action => action?.id === id)?.enabled !== false; const tiles = CC_ACTION_TILE_IDS.filter(wanted).map(id => ({ id: id, enabled: true, w: 1, h: 1, small: true })); const footer = rest.some(widget => widget?.id === "runningApps") ? [] : [ { id: "runningApps", enabled: true, w: 4, h: 1, footer: true } ]; const identity = header.length === 0 ? {} : header.find(widget => widget.id === "userCard" || (widget.id === "header" && widget.showUser !== false)); if (!identity || rest.some(widget => widget?.id === "user")) return tiles.concat(rest, footer); const user = { id: "user", enabled: true, w: Math.max(1, columns - tiles.length), h: 1 }; for (const key of CC_USER_KEYS) { if (key in identity) user[key] = identity[key]; } return [user].concat(tiles, rest, footer); } function migrateBarWidgetGlobals(settings) { var bars = Array.isArray(settings.barConfigs) ? settings.barConfigs : []; for (var b = 0; b < bars.length; b++) { for (var k = 0; k < BAR_WIDGET_LIST_KEYS.length; k++) { var widgets = bars[b] && bars[b][BAR_WIDGET_LIST_KEYS[k]]; if (!Array.isArray(widgets)) continue; for (var w = 0; w < widgets.length; w++) { var raw = widgets[w]; var id = typeof raw === "string" ? raw : (raw && raw.id); var map = WidgetDefaults.MIGRATED_GLOBALS[id]; if (!map) continue; var entry = typeof raw === "string" ? { id: raw, enabled: true } : raw; var copied = false; for (var entryKey in map) { var value = settings[map[entryKey]] ?? WidgetDefaults.LEGACY_GLOBAL_DEFAULTS[map[entryKey]]; if (entry[entryKey] !== undefined || value === undefined) continue; entry[entryKey] = value; copied = true; } if (copied) widgets[w] = entry; } } } var removed = WidgetDefaults.removedGlobals(); for (var i = 0; i < removed.length; i++) delete settings[removed[i]]; }