diff --git a/dank-qml-common b/dank-qml-common index a172b3984..7cc4564e5 160000 --- a/dank-qml-common +++ b/dank-qml-common @@ -1 +1 @@ -Subproject commit a172b39841d8f42bac46ac133b76cade1fae91b4 +Subproject commit 7cc4564e5903a2955fe7da76969f20252cacf9bf diff --git a/flake.lock b/flake.lock index 1d6fbc7d9..d83e5eb67 100644 --- a/flake.lock +++ b/flake.lock @@ -3,11 +3,11 @@ "dank-qml-common": { "flake": false, "locked": { - "lastModified": 1784935106, - "narHash": "sha256-aVgOBRynme6XNKYCZlf/oCjPDZFwddyUQPRRkEupC3c=", + "lastModified": 1785121997, + "narHash": "sha256-/MslqFCpjxws8DZqipEKCCTBa0m/SCV3+v1NRT6EuvQ=", "owner": "AvengeMedia", "repo": "dank-qml-common", - "rev": "a172b39841d8f42bac46ac133b76cade1fae91b4", + "rev": "7cc4564e5903a2955fe7da76969f20252cacf9bf", "type": "github" }, "original": { diff --git a/quickshell/Common/CacheUtils.qml b/quickshell/Common/CacheUtils.qml deleted file mode 100644 index 85d4f2b4e..000000000 --- a/quickshell/Common/CacheUtils.qml +++ /dev/null @@ -1,29 +0,0 @@ -pragma Singleton -pragma ComponentBehavior: Bound -import QtQuick -import Quickshell -import qs.Common - -Singleton { - id: root - - function clearImageCache() { - Quickshell.execDetached(["rm", "-rf", Paths.stringify(Paths.imagecache)]); - Paths.mkdir(Paths.imagecache); - } - - function clearOldCache(ageInMinutes) { - Quickshell.execDetached(["find", Paths.stringify(Paths.imagecache), "-name", "*.png", "-mmin", `+${ageInMinutes}`, "-delete"]); - } - - function clearCacheForSize(size) { - Quickshell.execDetached(["find", Paths.stringify(Paths.imagecache), "-name", `*@${size}x${size}.png`, "-delete"]); - } - - function getCacheSize(callback) { - Proc.runCommand("cache_size", ["du", "-sm", Paths.stringify(Paths.imagecache)], function (output, exitCode) { - const sizeMB = parseInt(output.split("\t")[0]) || 0; - callback(sizeMB); - }); - } -} diff --git a/quickshell/Common/ConnectedSurfaceDescriptor.js b/quickshell/Common/ConnectedSurfaceDescriptor.js index 203e1eb06..6b4d7173c 100644 --- a/quickshell/Common/ConnectedSurfaceDescriptor.js +++ b/quickshell/Common/ConnectedSurfaceDescriptor.js @@ -114,26 +114,6 @@ function withRevision(descriptor, revision) { return next; } -function withAnimationOffset(descriptor, x, y) { - var next = normalize(descriptor); - next.animationOffset = { - "x": x === undefined ? next.animationOffset.x : _number(x, next.animationOffset.x), - "y": y === undefined ? next.animationOffset.y : _number(y, next.animationOffset.y) - }; - return next; -} - -function withBodyRect(descriptor, x, y, width, height) { - var next = normalize(descriptor); - next.bodyRect = { - "x": x === undefined ? next.bodyRect.x : _number(x, next.bodyRect.x), - "y": y === undefined ? next.bodyRect.y : _number(y, next.bodyRect.y), - "width": width === undefined ? next.bodyRect.width : Math.max(0, _number(width, next.bodyRect.width)), - "height": height === undefined ? next.bodyRect.height : Math.max(0, _number(height, next.bodyRect.height)) - }; - return next; -} - function same(a, b, threshold) { if (!a || !b) return false; diff --git a/quickshell/Common/ConnectedSurfaceGeometry.js b/quickshell/Common/ConnectedSurfaceGeometry.js index fcdf30f08..bbda786eb 100644 --- a/quickshell/Common/ConnectedSurfaceGeometry.js +++ b/quickshell/Common/ConnectedSurfaceGeometry.js @@ -162,21 +162,6 @@ function fillBounds(rect, side, seamOverlap, dpr) { }; } -function clipEnvelope(rect, side, radii, seamOverlap, dpr) { - var fill = fillBounds(rect, side, seamOverlap, dpr); - var chrome = chromeBounds(fill, side, radii.start, radii.end, radii.farExtent, dpr); - return { - "x": chrome.x, - "y": chrome.y, - "width": chrome.width, - "height": chrome.height, - "bodyX": snap(fill.x - chrome.x, dpr), - "bodyY": snap(fill.y - chrome.y, dpr), - "bodyWidth": fill.width, - "bodyHeight": fill.height - }; -} - function blurRegions(descriptor, rect, radii, dpr) { var side = descriptor.barSide; var regions = [bodyRect(rect, dpr)]; @@ -219,14 +204,3 @@ function unionBounds(rects, padding, dpr) { "height": Math.max(0, snap(maxY - minY + pad * 2, dpr)) }; } - -function shadowSourceBounds(descriptor, rect, radii, padding, dpr) { - return unionBounds(blurRegions(descriptor, rect, radii, dpr), padding, dpr); -} - -function stableEqual(a, b, dpr) { - if (!a || !b) - return false; - var threshold = 0.5 / (dpr || 1); - return Math.abs(a.x - b.x) < threshold && Math.abs(a.y - b.y) < threshold && Math.abs(a.width - b.width) < threshold && Math.abs(a.height - b.height) < threshold; -} diff --git a/quickshell/Common/ConnectorGeometry.js b/quickshell/Common/ConnectorGeometry.js index c24bd7dba..52c74ff76 100644 --- a/quickshell/Common/ConnectorGeometry.js +++ b/quickshell/Common/ConnectorGeometry.js @@ -44,16 +44,6 @@ function connectorX(barSide, baseX, bodyWidth, placement, spacing, radius) { return barSide === "left" ? s : s - w; } -function connectorY(barSide, baseY, bodyHeight, placement, spacing, radius) { - var s = seamY(barSide, baseY, bodyHeight, placement); - var h = connectorHeight(barSide, spacing, radius); - if (barSide === "top") - return s; - if (barSide === "bottom") - return s - h; - return placement === "left" ? s - h : s; -} - // Which corner of the connector's bounding rect hosts the concave arc that // carves into the body. Used for arc-sweep orientation. function arcCorner(barSide, placement) { diff --git a/quickshell/Common/Format.js b/quickshell/Common/Format.js new file mode 100644 index 000000000..edde790a6 --- /dev/null +++ b/quickshell/Common/Format.js @@ -0,0 +1,72 @@ +.pragma library + +function formatRate(bytesPerSec, gbDecimals) { + if (bytesPerSec < 1024) + return bytesPerSec.toFixed(0) + " B/s"; + if (bytesPerSec < 1024 * 1024) + return (bytesPerSec / 1024).toFixed(1) + " KB/s"; + if (bytesPerSec < 1024 * 1024 * 1024) + return (bytesPerSec / (1024 * 1024)).toFixed(1) + " MB/s"; + return (bytesPerSec / (1024 * 1024 * 1024)).toFixed(gbDecimals ?? 2) + " GB/s"; +} + +function formatBytes(bytes) { + if (bytes < 1024) + return bytes.toFixed(0) + "B"; + if (bytes < 1024 * 1024) + return (bytes / 1024).toFixed(0) + "K"; + if (bytes < 1024 * 1024 * 1024) + return (bytes / (1024 * 1024)).toFixed(1) + "M"; + return (bytes / (1024 * 1024 * 1024)).toFixed(1) + "G"; +} + +function formatIsoTime(isoString) { + if (!isoString) + return ""; + try { + const date = new Date(isoString); + if (isNaN(date.getTime())) + return ""; + return date.toLocaleTimeString(Qt.locale(), "HH:mm"); + } catch (e) { + return ""; + } +} + +function formatRemaining(ms, zeroText, minText, hText, hmText) { + if (ms <= 0) + return zeroText; + const totalMinutes = Math.ceil(ms / 60000); + if (totalMinutes < 60) + return minText.arg(totalMinutes); + const hours = Math.floor(totalMinutes / 60); + const mins = totalMinutes - hours * 60; + if (mins === 0) + return hText.arg(hours); + return hmText.arg(hours).arg(mins); +} + +function pad2(n) { + return n < 10 ? "0" + n : "" + n; +} + +function formatUntil(ts, use24h) { + if (!ts) + return ""; + const d = new Date(ts); + const hours = d.getHours(); + const minutes = d.getMinutes(); + if (use24h) + return pad2(hours) + ":" + pad2(minutes); + const suffix = hours >= 12 ? "PM" : "AM"; + const h12 = ((hours + 11) % 12) + 1; + return h12 + ":" + pad2(minutes) + " " + suffix; +} + +function addToHistory(arr, val, max) { + const newArr = arr.slice(); + newArr.push(val); + if (newArr.length > max) + newArr.shift(); + return newArr; +} diff --git a/quickshell/Common/KeybindActions.js b/quickshell/Common/KeybindActions.js index 0f9136114..857c1b783 100644 --- a/quickshell/Common/KeybindActions.js +++ b/quickshell/Common/KeybindActions.js @@ -1370,10 +1370,3 @@ function buildDmsAction(baseKey, args) { return parts.join(" "); } - -function getScreenshotOptions() { - return [ - { id: "write-to-disk", label: "Save to disk", type: "bool" }, - { id: "show-pointer", label: "Show pointer", type: "bool" } - ]; -} diff --git a/quickshell/Common/Paths.qml b/quickshell/Common/Paths.qml index f0856b19b..13da34db3 100644 --- a/quickshell/Common/Paths.qml +++ b/quickshell/Common/Paths.qml @@ -22,6 +22,10 @@ Singleton { readonly property url imagecache: `${cache}/imagecache` + property var iconResolver: null + property var desktopIconResolver: null + property var trashHandler: null + Component.onCompleted: mkdir(imagecache) function stringify(path: url): string { @@ -88,7 +92,7 @@ Singleton { function themedIconPath(name: string): string { if (!name) return ""; - const themed = (typeof IconThemeService !== "undefined") ? IconThemeService.resolve(name) : ""; + const themed = iconResolver ? iconResolver(name) : ""; if (themed) return themed; return Quickshell.iconPath(name, true); @@ -105,11 +109,15 @@ Singleton { return moddedId; return themedIconPath(moddedId); } - return themedIconPath(iconName) || DesktopService.resolveIconPath(iconName); + if (!desktopIconResolver) + return themedIconPath(iconName); + return themedIconPath(iconName) || desktopIconResolver(iconName); } function trashPath(path: string, callback): void { - TrashService.trashPath(path, callback); + if (!trashHandler) + return; + trashHandler(path, callback); } function copyPathToClipboard(path: string): void { @@ -125,7 +133,7 @@ Singleton { return toFileUrl(expandTilde(target)); if (target.startsWith("file://")) return target; - const themed = (typeof IconThemeService !== "undefined") ? IconThemeService.resolve(target) : ""; + const themed = iconResolver ? iconResolver(target) : ""; if (themed) return themed; return "image://icon/" + target; @@ -148,7 +156,9 @@ Singleton { if (icon && icon !== "") return icon; - return DesktopService.resolveIconPath(appId); + if (!desktopIconResolver) + return ""; + return desktopIconResolver(appId); } function getAppName(appId: string, desktopEntry: var): string { diff --git a/quickshell/Common/QmlUtils.js b/quickshell/Common/QmlUtils.js new file mode 100644 index 000000000..87acd0aa3 --- /dev/null +++ b/quickshell/Common/QmlUtils.js @@ -0,0 +1,27 @@ +.pragma library + +function findParentFlickable(item) { + while (item) { + if (item.hasOwnProperty("contentY") && item.hasOwnProperty("contentItem")) + return item; + item = item.parent; + } + return null; +} + +function findSettings(item) { + while (item) { + if (item.saveValue !== undefined && item.loadValue !== undefined) + return item; + item = item.parent; + } + return null; +} + +function normalizePinList(value) { + if (Array.isArray(value)) + return value.filter(v => v); + if (typeof value === "string" && value.length > 0) + return [value]; + return []; +} diff --git a/quickshell/Common/SessionData.qml b/quickshell/Common/SessionData.qml index 66305fde3..5c5a4b22c 100644 --- a/quickshell/Common/SessionData.qml +++ b/quickshell/Common/SessionData.qml @@ -16,6 +16,10 @@ Singleton { readonly property int sessionConfigVersion: 3 + signal loaded + signal brightnessDisplayHintChanged(string deviceName) + signal loadErrorOccurred(string file, string message) + property bool _parseError: false property bool _hasLoaded: false property bool _isReadOnly: false @@ -276,15 +280,14 @@ Singleton { if (typeof Theme !== "undefined") Theme.generateSystemThemesFromCurrentTheme(); - if (typeof WallpaperCyclingService !== "undefined") - WallpaperCyclingService.updateCyclingState(); + loaded(); _checkSessionWritable(); } catch (e) { _parseError = true; const msg = e.message; log.error("Failed to parse session.json - file will not be overwritten."); - Qt.callLater(() => ToastService.showError(I18n.tr("Failed to parse %1").arg("session.json"), msg)); + Qt.callLater(() => loadErrorOccurred("session.json", msg)); } } @@ -358,13 +361,12 @@ Singleton { if (typeof Theme !== "undefined") Theme.generateSystemThemesFromCurrentTheme(); - if (typeof WallpaperCyclingService !== "undefined") - WallpaperCyclingService.updateCyclingState(); + loaded(); } catch (e) { _parseError = true; const msg = e.message; log.error("Failed to parse session.json - file will not be overwritten."); - Qt.callLater(() => ToastService.showError(I18n.tr("Failed to parse %1").arg("session.json"), msg)); + Qt.callLater(() => loadErrorOccurred("session.json", msg)); } } @@ -465,11 +467,6 @@ Singleton { saveSettings(); } - function setWallpaperPath(path) { - wallpaperPath = path; - saveSettings(); - } - function setWallpaper(imagePath) { wallpaperPath = imagePath; if (perModeWallpaper) { @@ -848,11 +845,6 @@ Singleton { saveSettings(); } - function setNightModeLocationProvider(provider) { - nightModeLocationProvider = provider; - saveSettings(); - } - function setThemeModeAutoEnabled(enabled) { themeModeAutoEnabled = enabled; saveSettings(); @@ -941,10 +933,6 @@ Singleton { setBarPinnedApps(currentPinned); } - function isBarPinnedApp(appId) { - return appId && barPinnedApps.indexOf(appId) !== -1; - } - function hideTrayId(trayId) { if (!trayId) return; @@ -1007,11 +995,6 @@ Singleton { saveSettings(); } - function setLaunchPrefix(prefix) { - launchPrefix = prefix; - saveSettings(); - } - function setLastBrightnessDevice(device) { lastBrightnessDevice = device; saveSettings(); @@ -1026,10 +1009,7 @@ Singleton { } brightnessExponentialDevices = newSettings; saveSettings(); - - if (typeof DisplayService !== "undefined") { - DisplayService.updateDeviceBrightnessDisplay(deviceName); - } + brightnessDisplayHintChanged(deviceName); } function getBrightnessExponential(deviceName) { @@ -1043,10 +1023,6 @@ Singleton { saveSettings(); } - function getBrightnessUserSetValue(deviceName) { - return brightnessUserSetValues[deviceName]; - } - function clearBrightnessUserSetValue(deviceName) { var newValues = Object.assign({}, brightnessUserSetValues); delete newValues[deviceName]; @@ -1070,21 +1046,6 @@ Singleton { return value !== undefined ? value : 1.2; } - function setSelectedGpuIndex(index) { - selectedGpuIndex = index; - saveSettings(); - } - - function setNvidiaGpuTempEnabled(enabled) { - nvidiaGpuTempEnabled = enabled; - saveSettings(); - } - - function setNonNvidiaGpuTempEnabled(enabled) { - nonNvidiaGpuTempEnabled = enabled; - saveSettings(); - } - function setEnabledGpuPciIds(pciIds) { enabledGpuPciIds = pciIds; saveSettings(); @@ -1200,15 +1161,6 @@ Singleton { return deviceMaxVolumes[nodeName] ?? 100; } - function removeDeviceMaxVolume(nodeName) { - if (!nodeName) - return; - const updated = Object.assign({}, deviceMaxVolumes); - delete updated[nodeName]; - deviceMaxVolumes = updated; - saveSettings(); - } - function updateLocale() { if (!locale) { I18n._pickTranslation(); @@ -1272,12 +1224,6 @@ Singleton { saveSettings(); } - function clearLauncherHistory() { - launcherLastQuery = ""; - launcherSearchHistory = []; - saveSettings(); - } - function setAppDrawerLastMode(mode) { appDrawerLastMode = mode; saveSettings(); @@ -1417,141 +1363,4 @@ Singleton { } } } - - IpcHandler { - target: "wallpaper" - - function get(): string { - if (root.perMonitorWallpaper) { - return "ERROR: Per-monitor mode enabled. Use getFor(screenName) instead."; - } - return root.wallpaperPath || ""; - } - - function set(path: string): string { - if (root.perMonitorWallpaper) { - return "ERROR: Per-monitor mode enabled. Use setFor(screenName, path) instead."; - } - - if (!path) { - return "ERROR: No path provided"; - } - - var absolutePath = path.startsWith("/") ? path : StandardPaths.writableLocation(StandardPaths.HomeLocation) + "/" + path; - - try { - root.setWallpaper(absolutePath); - return "SUCCESS: Wallpaper set to " + absolutePath; - } catch (e) { - return "ERROR: Failed to set wallpaper: " + e.toString(); - } - } - - function clear(): string { - root.setWallpaper(""); - root.setPerMonitorWallpaper(false); - root.monitorWallpapers = {}; - root.saveSettings(); - return "SUCCESS: All wallpapers cleared"; - } - - function next(): string { - if (root.perMonitorWallpaper) { - return "ERROR: Per-monitor mode enabled. Use nextFor(screenName) instead."; - } - - if (!root.wallpaperPath) { - return "ERROR: No wallpaper set"; - } - - try { - WallpaperCyclingService.cycleNextManually(); - return "SUCCESS: Cycling to next wallpaper"; - } catch (e) { - return "ERROR: Failed to cycle wallpaper: " + e.toString(); - } - } - - function prev(): string { - if (root.perMonitorWallpaper) { - return "ERROR: Per-monitor mode enabled. Use prevFor(screenName) instead."; - } - - if (!root.wallpaperPath) { - return "ERROR: No wallpaper set"; - } - - try { - WallpaperCyclingService.cyclePrevManually(); - return "SUCCESS: Cycling to previous wallpaper"; - } catch (e) { - return "ERROR: Failed to cycle wallpaper: " + e.toString(); - } - } - - function getFor(screenName: string): string { - if (!screenName) { - return "ERROR: No screen name provided"; - } - return root.getMonitorWallpaper(screenName) || ""; - } - - function setFor(screenName: string, path: string): string { - if (!screenName) { - return "ERROR: No screen name provided"; - } - - if (!path) { - return "ERROR: No path provided"; - } - - var absolutePath = path.startsWith("/") ? path : StandardPaths.writableLocation(StandardPaths.HomeLocation) + "/" + path; - - try { - if (!root.perMonitorWallpaper) { - root.setPerMonitorWallpaper(true); - } - root.setMonitorWallpaper(screenName, absolutePath); - return "SUCCESS: Wallpaper set for " + screenName + " to " + absolutePath; - } catch (e) { - return "ERROR: Failed to set wallpaper for " + screenName + ": " + e.toString(); - } - } - - function nextFor(screenName: string): string { - if (!screenName) { - return "ERROR: No screen name provided"; - } - - var currentWallpaper = root.getMonitorWallpaper(screenName); - if (!currentWallpaper) { - return "ERROR: No wallpaper set for " + screenName; - } - - try { - WallpaperCyclingService.cycleNextForMonitor(screenName); - return "SUCCESS: Cycling to next wallpaper for " + screenName; - } catch (e) { - return "ERROR: Failed to cycle wallpaper for " + screenName + ": " + e.toString(); - } - } - - function prevFor(screenName: string): string { - if (!screenName) { - return "ERROR: No screen name provided"; - } - - var currentWallpaper = root.getMonitorWallpaper(screenName); - if (!currentWallpaper) { - return "ERROR: No wallpaper set for " + screenName; - } - - try { - WallpaperCyclingService.cyclePrevForMonitor(screenName); - return "SUCCESS: Cycling to previous wallpaper for " + screenName; - } catch (e) { - return "ERROR: Failed to cycle wallpaper for " + screenName + ": " + e.toString(); - } - } - } } diff --git a/quickshell/Common/SettingsData.qml b/quickshell/Common/SettingsData.qml index e8b69a0a6..c56183e28 100644 --- a/quickshell/Common/SettingsData.qml +++ b/quickshell/Common/SettingsData.qml @@ -712,16 +712,23 @@ Singleton { property bool notepadUseCompositorGap: false property int notepadEdgeGap: 0 + property string activeCompositor: "" + // Compositor layout gap when enabled and available, else the manual value. readonly property int notepadEffectiveEdgeGap: { if (notepadUseCompositorGap) { var g = -1; - if (CompositorService.isNiri) + switch (activeCompositor) { + case "niri": g = niriLayoutGapsOverride; - else if (CompositorService.isHyprland) + break; + case "hyprland": g = hyprlandLayoutGapsOverride; - else if (CompositorService.isMango) + break; + case "mango": g = mangoLayoutGapsOverride; + break; + } if (g >= 0) return g; } @@ -1126,44 +1133,6 @@ Singleton { 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, @@ -1304,70 +1273,6 @@ Singleton { 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 moveDesktopWidgetInstanceToGroup(instanceId, groupId, newIndexInGroup) { const instances = JSON.parse(JSON.stringify(desktopWidgetInstances || [])); const groups = desktopWidgetGroups || []; @@ -1433,22 +1338,14 @@ Singleton { 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 + signal compositorLayoutRefreshNeeded(bool frame) + signal compositorInputRefreshNeeded + signal compositorCursorRefreshNeeded + signal notificationPopupsInvalidated function refreshAuthAvailability() { Processes.detectAuthCapabilities(); @@ -1483,31 +1380,15 @@ Singleton { } 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.isMango && typeof MangoService !== "undefined") - MangoService.generateLayoutConfig(); + compositorLayoutRefreshNeeded(false); } function updateCompositorInput() { - if (typeof CompositorService === "undefined") - return; - if (CompositorService.isNiri && typeof NiriService !== "undefined") - NiriService.generateNiriInputConfig(); + compositorInputRefreshNeeded(); } function updateFrameCompositorLayout() { - // Generate before begin() so compositor readiness is already pending at transitionRequested - if (typeof CompositorService !== "undefined") { - if (CompositorService.isNiri && typeof NiriService !== "undefined") - NiriService.generateNiriLayoutConfig(true); - if (CompositorService.isHyprland && typeof HyprlandService !== "undefined") - HyprlandService.generateLayoutConfig(true); - } + compositorLayoutRefreshNeeded(true); FrameTransitionState.begin(); } @@ -2160,14 +2041,6 @@ Singleton { 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) { @@ -2180,39 +2053,6 @@ Singleton { 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; @@ -2506,51 +2346,10 @@ Singleton { updateBarConfigs(); if (positionChanged) { - NotificationService.dismissAllPopups(); + notificationPopupsInvalidated(); } } - 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; @@ -2699,38 +2498,6 @@ Singleton { return filtered; } - function getFrameFilteredScreens() { - var prefs = frameScreenPreferences || ["all"]; - if (!prefs || prefs.length === 0 || prefs.includes("all")) { - return Quickshell.screens; - } - return Quickshell.screens.filter(screen => isScreenInPreferences(screen, prefs)); - } - - function getActiveBarEdgeForScreen(screen) { - if (!screen) - return ""; - for (var i = 0; i < barConfigs.length; i++) { - var bc = barConfigs[i]; - if (!bc.enabled) - continue; - var prefs = bc.screenPreferences || ["all"]; - if (!prefs.includes("all") && !isScreenInPreferences(screen, prefs)) - continue; - switch (bc.position ?? 0) { - case SettingsData.Position.Top: - return "top"; - case SettingsData.Position.Bottom: - return "bottom"; - case SettingsData.Position.Left: - return "left"; - case SettingsData.Position.Right: - return "right"; - } - } - return ""; - } - function getActiveBarEdgesForScreen(screen) { if (!screen) return []; @@ -2796,46 +2563,6 @@ Singleton { return edges.includes(side) ? frameBarSize : frameThickness; } - function getActiveBarThicknessForScreen(screen) { - if (frameEnabled) - return frameBarSize; - if (!screen) - return frameThickness; - for (var i = 0; i < barConfigs.length; i++) { - var bc = barConfigs[i]; - if (!bc.enabled) - continue; - var prefs = bc.screenPreferences || ["all"]; - if (!prefs.includes("all") && !isScreenInPreferences(screen, prefs)) - continue; - const innerPadding = bc.innerPadding ?? 4; - const barT = Math.max(26 + innerPadding * 0.6, Theme.barHeight - 4 - (8 - innerPadding)); - const spacing = bc.spacing ?? 4; - const bottomGap = bc.bottomGap ?? 0; - return barT + spacing + bottomGap; - } - return frameThickness; - } - - 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) @@ -2852,15 +2579,6 @@ Singleton { set("matugenContrast", value); } - function setRunUserMatugenTemplates(enabled) { - if (runUserMatugenTemplates === enabled) - return; - set("runUserMatugenTemplates", enabled); - if (typeof Theme !== "undefined") { - Theme.generateSystemThemesFromCurrentTheme(); - } - } - function setMatugenTargetMonitor(monitorName) { if (matugenTargetMonitor === monitorName) return; @@ -2879,11 +2597,6 @@ Singleton { SessionData.setWeatherLocation(displayName, coordinates); } - function setIconTheme(themeName) { - const light = iconThemePerMode && typeof SessionData !== "undefined" && SessionData.isLightMode; - setIconThemeForMode(themeName, light); - } - function setIconThemeForMode(themeName, light) { if (light) iconThemeLight = themeName; @@ -2929,20 +2642,7 @@ Singleton { // https://github.com/Supreeeme/xwayland-satellite/issues/104 // no idea if this matters on other compositors but we also set XCURSOR stuff in the launcher function updateCompositorCursor() { - if (typeof CompositorService === "undefined") - return; - if (CompositorService.isNiri && typeof NiriService !== "undefined") { - NiriService.generateNiriCursorConfig(); - return; - } - if (CompositorService.isHyprland && typeof HyprlandService !== "undefined") { - HyprlandService.generateCursorConfig(); - return; - } - if (CompositorService.isMango && typeof MangoService !== "undefined") { - MangoService.generateCursorConfig(); - return; - } + compositorCursorRefreshNeeded(); } function updateXResources() { @@ -3010,20 +2710,6 @@ Singleton { 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"); @@ -3069,16 +2755,6 @@ Singleton { 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) @@ -3134,39 +2810,6 @@ Singleton { } } - 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; @@ -3448,15 +3091,6 @@ Singleton { Theme.reloadCustomThemeVariant(); } - function toggleDankBarVisible() { - const defaultBar = barConfigs[0] || getBarConfig("default"); - if (defaultBar) { - updateBarConfig(defaultBar.id, { - "visible": !defaultBar.visible - }); - } - } - function toggleShowDock() { setShowDock(!showDock); } @@ -3478,13 +3112,6 @@ Singleton { 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)) : {}; @@ -3510,22 +3137,6 @@ Singleton { 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; @@ -3550,40 +3161,6 @@ Singleton { saveSettings(); } - function getHyprlandOutputSettings(outputId) { - const settings = hyprlandOutputSettings[outputId]; - return settings ? JSON.parse(JSON.stringify(settings)) : {}; - } - - function setHyprlandOutputSettings(outputId, settings) { - const updated = JSON.parse(JSON.stringify(hyprlandOutputSettings)); - updated[outputId] = settings; - hyprlandOutputSettings = updated; - saveSettings(); - } - - function removeHyprlandOutputSettings(outputId) { - if (!hyprlandOutputSettings[outputId]) - return; - const updated = JSON.parse(JSON.stringify(hyprlandOutputSettings)); - delete updated[outputId]; - hyprlandOutputSettings = updated; - saveSettings(); - } - - function getDisplayProfiles(compositor) { - return displayProfiles[compositor] || {}; - } - - function setDisplayProfile(compositor, profileId, data) { - const updated = JSON.parse(JSON.stringify(displayProfiles)); - if (!updated[compositor]) - updated[compositor] = {}; - updated[compositor][profileId] = data; - displayProfiles = updated; - saveSettings(); - } - function removeDisplayProfile(compositor, profileId) { if (!displayProfiles[compositor] || !displayProfiles[compositor][profileId]) return; @@ -3637,29 +3214,6 @@ Singleton { id: rightWidgetsModel } - property Process testNotificationProcess - - testNotificationProcess: Process { - command: [] - running: false - } - - property Timer testNotifTimer1 - - testNotifTimer1: Timer { - interval: 400 - repeat: false - onTriggered: sendTestNotification(1) - } - - property Timer testNotifTimer2 - - testNotifTimer2: Timer { - interval: 800 - repeat: false - onTriggered: sendTestNotification(2) - } - property alias settingsFile: settingsFile Timer { diff --git a/quickshell/Common/StockThemes.js b/quickshell/Common/StockThemes.js index 0a21afa57..9e87a8c57 100644 --- a/quickshell/Common/StockThemes.js +++ b/quickshell/Common/StockThemes.js @@ -416,24 +416,6 @@ const StockThemes = { }, }; -const ThemeCategories = { - GENERIC: { - name: "Generic", - variants: [ - "blue", - "purple", - "green", - "orange", - "red", - "cyan", - "pink", - "amber", - "coral", - "monochrome", - ], - }, -}; - const ThemeNames = { BLUE: "blue", PURPLE: "purple", @@ -448,10 +430,6 @@ const ThemeNames = { DYNAMIC: "dynamic", }; -function isStockTheme(themeName) { - return Object.keys(StockThemes.DARK).includes(themeName); -} - function getAvailableThemes(isLight = false) { return isLight ? StockThemes.LIGHT : StockThemes.DARK; } @@ -464,7 +442,3 @@ function getThemeByName(themeName, isLight = false) { function getAllThemeNames() { return Object.keys(StockThemes.DARK); } - -function getThemeCategories() { - return ThemeCategories; -} diff --git a/quickshell/Common/Theme.qml b/quickshell/Common/Theme.qml index 8516cf460..ec86726fa 100644 --- a/quickshell/Common/Theme.qml +++ b/quickshell/Common/Theme.qml @@ -107,8 +107,11 @@ Singleton { property int _colorsRetryCount: 0 property double _lastGenerateMs: 0 - property bool themeModeAutomationActive: false - property bool dmsServiceWasDisconnected: true + property bool blurLayersActive: false + property bool matugenToastSuppressed: false + + signal screenTransitionNeeded + signal themeGenerationStarting readonly property var dank16: { const raw = matugenColors?.dank16; @@ -196,235 +199,6 @@ Singleton { const currentIsLight = (typeof SessionData !== "undefined") ? SessionData.isLightMode : false; SettingsData.updateCosmicThemeMode(currentIsLight); } - - if (typeof SessionData !== "undefined" && SessionData.themeModeAutoEnabled) { - startThemeModeAutomation(); - } - } - - Connections { - target: SessionData - enabled: typeof SessionData !== "undefined" - - function onThemeModeAutoEnabledChanged() { - if (SessionData.themeModeAutoEnabled) { - root.startThemeModeAutomation(); - } else { - root.stopThemeModeAutomation(); - } - } - - function onThemeModeAutoModeChanged() { - if (root.themeModeAutomationActive) { - root.evaluateThemeMode(); - root.syncTimeThemeSchedule(); - root.syncLocationThemeSchedule(); - } - } - - function onThemeModeStartHourChanged() { - if (root.themeModeAutomationActive && !SessionData.themeModeShareGammaSettings) { - root.evaluateThemeMode(); - root.syncTimeThemeSchedule(); - } - } - - function onThemeModeStartMinuteChanged() { - if (root.themeModeAutomationActive && !SessionData.themeModeShareGammaSettings) { - root.evaluateThemeMode(); - root.syncTimeThemeSchedule(); - } - } - - function onThemeModeEndHourChanged() { - if (root.themeModeAutomationActive && !SessionData.themeModeShareGammaSettings) { - root.evaluateThemeMode(); - root.syncTimeThemeSchedule(); - } - } - - function onThemeModeEndMinuteChanged() { - if (root.themeModeAutomationActive && !SessionData.themeModeShareGammaSettings) { - root.evaluateThemeMode(); - root.syncTimeThemeSchedule(); - } - } - - function onThemeModeShareGammaSettingsChanged() { - if (root.themeModeAutomationActive) { - root.evaluateThemeMode(); - root.syncTimeThemeSchedule(); - root.syncLocationThemeSchedule(); - } - } - - function onNightModeStartHourChanged() { - if (root.themeModeAutomationActive && SessionData.themeModeShareGammaSettings) { - root.evaluateThemeMode(); - root.syncTimeThemeSchedule(); - } - } - - function onNightModeStartMinuteChanged() { - if (root.themeModeAutomationActive && SessionData.themeModeShareGammaSettings) { - root.evaluateThemeMode(); - root.syncTimeThemeSchedule(); - } - } - - function onNightModeEndHourChanged() { - if (root.themeModeAutomationActive && SessionData.themeModeShareGammaSettings) { - root.evaluateThemeMode(); - root.syncTimeThemeSchedule(); - } - } - - function onNightModeEndMinuteChanged() { - if (root.themeModeAutomationActive && SessionData.themeModeShareGammaSettings) { - root.evaluateThemeMode(); - root.syncTimeThemeSchedule(); - } - } - - function onLatitudeChanged() { - if (root.themeModeAutomationActive && SessionData.themeModeAutoMode === "location") { - if (!SessionData.nightModeUseIPLocation && SessionData.latitude !== 0.0 && SessionData.longitude !== 0.0 && typeof DMSService !== "undefined") { - DMSService.sendRequest("wayland.gamma.setLocation", { - "latitude": SessionData.latitude, - "longitude": SessionData.longitude - }); - } - root.evaluateThemeMode(); - root.syncLocationThemeSchedule(); - } - } - - function onLongitudeChanged() { - if (root.themeModeAutomationActive && SessionData.themeModeAutoMode === "location") { - if (!SessionData.nightModeUseIPLocation && SessionData.latitude !== 0.0 && SessionData.longitude !== 0.0 && typeof DMSService !== "undefined") { - DMSService.sendRequest("wayland.gamma.setLocation", { - "latitude": SessionData.latitude, - "longitude": SessionData.longitude - }); - } - root.evaluateThemeMode(); - root.syncLocationThemeSchedule(); - } - } - - function onNightModeUseIPLocationChanged() { - if (root.themeModeAutomationActive && SessionData.themeModeAutoMode === "location") { - if (typeof DMSService !== "undefined") { - DMSService.sendRequest("wayland.gamma.setUseIPLocation", { - "use": SessionData.nightModeUseIPLocation - }, response => { - if (!response.error && !SessionData.nightModeUseIPLocation && SessionData.latitude !== 0.0 && SessionData.longitude !== 0.0) { - DMSService.sendRequest("wayland.gamma.setLocation", { - "latitude": SessionData.latitude, - "longitude": SessionData.longitude - }); - } - }); - } - root.evaluateThemeMode(); - root.syncLocationThemeSchedule(); - } - } - } - - // React to gamma backend's isDay state changes for location-based mode - Connections { - target: DisplayService - enabled: typeof DisplayService !== "undefined" && typeof SessionData !== "undefined" && SessionData.themeModeAutoEnabled && SessionData.themeModeAutoMode === "location" && !themeAutoBackendAvailable() - - function onGammaIsDayChanged() { - if (root.isLightMode !== DisplayService.gammaIsDay) { - root.setLightMode(DisplayService.gammaIsDay, true, true); - } - } - } - - Connections { - target: DMSService - - function onThemeAutoStateUpdate(data) { - if (!SessionData.themeModeAutoEnabled) { - return; - } - applyThemeAutoState(data); - } - - function onConnectionStateChanged() { - if (DMSService.isConnected && SessionData.themeModeAutoMode === "time") { - root.syncTimeThemeSchedule(); - } - - if (DMSService.isConnected && SessionData.themeModeAutoMode === "location") { - root.syncLocationThemeSchedule(); - } - - if (themeAutoBackendAvailable() && SessionData.themeModeAutoEnabled) { - DMSService.sendRequest("theme.auto.getState", null, response => { - if (response && response.result) { - applyThemeAutoState(response.result); - } - }); - } - - if (!SessionData.themeModeAutoEnabled) { - return; - } - - if (DMSService.isConnected && SessionData.themeModeAutoMode === "location") { - if (SessionData.nightModeUseIPLocation) { - DMSService.sendRequest("wayland.gamma.setUseIPLocation", { - "use": true - }, response => { - if (!response.error) { - log.info("Theme automation: IP location enabled after connection"); - } - }); - } else if (SessionData.latitude !== 0.0 && SessionData.longitude !== 0.0) { - DMSService.sendRequest("wayland.gamma.setUseIPLocation", { - "use": false - }, response => { - if (!response.error) { - DMSService.sendRequest("wayland.gamma.setLocation", { - "latitude": SessionData.latitude, - "longitude": SessionData.longitude - }, locationResponse => { - if (locationResponse?.error) { - log.warn("Theme automation: Failed to set location", locationResponse.error); - } - }); - } - }); - } else { - log.warn("Theme automation: No location configured"); - } - } - } - } - - Connections { - target: SessionService - enabled: SessionData.themeModeAutoEnabled - - function onSessionUnlocked() { - root.triggerThemeAutomationRefresh(); - } - - function onSessionResumed() { - root.triggerThemeAutomationRefresh(); - } - } - - function triggerThemeAutomationRefresh() { - if (!themeAutoBackendAvailable()) { - root.evaluateThemeMode(); - return; - } - DMSService.sendRequest("theme.auto.trigger", {}); } function getMatugenColor(path, fallback) { @@ -571,8 +345,8 @@ Singleton { property color surfaceVariantAlpha: withAlpha(surfaceVariant, 0.2) readonly property bool foregroundLayers: typeof SettingsData === "undefined" || (SettingsData.blurForegroundLayers ?? true) - readonly property bool blurForegroundLayers: BlurService.enabled && foregroundLayers - readonly property bool transparentBlurLayers: BlurService.enabled && !foregroundLayers + readonly property bool blurForegroundLayers: blurLayersActive && foregroundLayers + readonly property bool transparentBlurLayers: blurLayersActive && !foregroundLayers readonly property bool notificationForegroundLayers: typeof SettingsData === "undefined" || (SettingsData.notificationForegroundLayers ?? true) readonly property color readableSurface: withAlpha(surfaceContainer, popupTransparency) readonly property color readableSurfaceHigh: withAlpha(surfaceContainerHigh, popupTransparency) @@ -658,7 +432,7 @@ Singleton { } } - readonly property color ccTileInactiveBg: transparentBlurLayers ? withAlpha(surfaceContainerHigh, 0.16) : (foregroundLayers ? withAlpha(surfaceContainerHigh, BlurService.enabled ? Math.min(popupTransparency, 0.24) : popupTransparency) : withAlpha(surfaceContainer, 0)) + readonly property color ccTileInactiveBg: transparentBlurLayers ? withAlpha(surfaceContainerHigh, 0.16) : (foregroundLayers ? withAlpha(surfaceContainerHigh, blurLayersActive ? Math.min(popupTransparency, 0.24) : popupTransparency) : withAlpha(surfaceContainer, 0)) readonly property color ccPillInactiveBg: transparentBlurLayers ? withAlpha(surfaceContainerHigh, 0.08) : nestedSurface readonly property color ccPillInactiveHoverBg: transparentBlurLayers ? withAlpha(primary, 0.10) : primaryPressed readonly property color ccSliderTrackColor: transparentBlurLayers ? surfaceText : surfaceContainerHigh @@ -978,22 +752,6 @@ Singleton { }; } - function elevationTintOpacity(level) { - if (!level) - return 0; - if (level === elevationLevel1) - return 0.05; - if (level === elevationLevel2) - return 0.08; - if (level === elevationLevel3) - return 0.11; - if (level === elevationLevel4) - return 0.12; - if (level === elevationLevel5) - return 0.14; - return 0.08; - } - readonly property var animationDurations: [ { "shorter": 0, @@ -1261,9 +1019,7 @@ Singleton { } function screenTransition() { - if (CompositorService.isNiri) { - NiriService.doScreenTransition(); - } + screenTransitionNeeded(); } function switchTheme(themeName, savePrefs = true, enableTransition = true) { @@ -1315,7 +1071,6 @@ Singleton { SessionData.setLightMode(light); } - PortalService.setLightMode(light); if (typeof SettingsData !== "undefined") { SettingsData.updateCosmicThemeMode(light); } @@ -1326,22 +1081,6 @@ Singleton { setLightMode(!isLightMode, savePrefs, true); } - function forceGenerateSystemThemes() { - if (!matugenAvailable) { - return; - } - generateSystemThemesFromCurrentTheme(); - } - - function getAvailableThemes() { - return StockThemes.getAllThemeNames(); - } - - function getThemeDisplayName(themeName) { - const themeData = StockThemes.getThemeByName(themeName, isLightMode); - return themeData.name; - } - function getThemeColors(themeName) { if (themeName === "custom" && customThemeData) { return customThemeData; @@ -1457,10 +1196,6 @@ Singleton { readonly property var _availableThemeNames: StockThemes.getAllThemeNames() property string currentThemeName: currentTheme - function panelBackground() { - return withAlpha(surfaceContainer, panelTransparency); - } - property real notepadTransparency: SettingsData.notepadTransparencyOverride >= 0 ? SettingsData.notepadTransparencyOverride : popupTransparency property bool widgetBackgroundHasAlpha: { @@ -1537,10 +1272,6 @@ Singleton { } } - function isColorDark(c) { - return (0.299 * c.r + 0.587 * c.g + 0.114 * c.b) < 0.5; - } - function barIconSize(barThickness, offset, maximizeIcon, iconScale) { const defaultOffset = offset !== undefined ? offset : -6; const size = (maximizeIcon ?? false) ? iconSizeLarge : iconSize; @@ -1623,19 +1354,6 @@ Singleton { } } - function getPowerProfileDescription(profile) { - switch (profile) { - case 0: - return I18n.tr("Extend battery life", "power profile description"); - case 1: - return I18n.tr("Balance power and performance", "power profile description"); - case 2: - return I18n.tr("Prioritize performance", "power profile description"); - default: - return I18n.tr("Custom power profile", "power profile description"); - } - } - function onLightModeChanged() { if (currentTheme === "custom" && customThemeFileView.path) { customThemeFileView.reload(); @@ -1663,9 +1381,7 @@ Singleton { log.info("Setting desired theme -", kind, "mode:", isLight ? "light" : "dark", stockColors ? "(stock colors)" : "(dynamic)"); - if (typeof NiriService !== "undefined" && CompositorService.isNiri) { - NiriService.suppressNextToast(); - } + themeGenerationStarting(); const desired = { "kind": kind, @@ -1777,8 +1493,7 @@ Singleton { if (currentTheme === dynamic) { if (!rawWallpaperPath) { - log.warn("Auto theme has no wallpaper - skipping matugen, syncing portal mode only"); - PortalService.setLightMode(isLight); + log.warn("Auto theme has no wallpaper - skipping matugen"); return; } const selectedMatugenType = (typeof SettingsData !== "undefined" && SettingsData.matugenScheme) ? SettingsData.matugenScheme : "scheme-tonal-spot"; @@ -1978,7 +1693,7 @@ Singleton { const isLight = (typeof SessionData !== "undefined" && SessionData.isLightMode) ? "true" : "false"; Proc.runCommand("gtkApplier", ["bash", shellDir + "/scripts/gtk.sh", configDir, "apply", isLight, shellDir], (output, exitCode) => { if (exitCode === 0) { - if (typeof ToastService !== "undefined" && typeof NiriService !== "undefined" && !NiriService.matugenSuppression) { + if (typeof ToastService !== "undefined" && !root.matugenToastSuppressed) { ToastService.showInfo(I18n.tr("GTK colors applied successfully")); } } else { @@ -2016,18 +1731,17 @@ Singleton { return Qt.rgba(c.r, c.g, c.b, a); } - function popupLayerColor(baseColor) { - if (isConnectedEffect) - return connectedSurfaceColor; - return withAlpha(baseColor, popupTransparency); - } - function blendAlpha(c, a) { if (!c || c.r === undefined) return Qt.rgba(0, 0, 0, 0); return Qt.rgba(c.r, c.g, c.b, c.a * a); } + function hoverTint(base) { + const factor = 1.2; + return isLightMode ? Qt.darker(base, factor) : Qt.lighter(base, factor); + } + function blend(c1, c2, r) { return Qt.rgba(c1.r * (1 - r) + c2.r * r, c1.g * (1 - r) + c2.g * r, c1.b * (1 - r) + c2.b * r, c1.a * (1 - r) + c2.a * r); } @@ -2344,303 +2058,4 @@ Singleton { root.switchTheme(defaultTheme, true, false); } } - - // Theme mode automation functions - function themeAutoBackendAvailable() { - return typeof DMSService !== "undefined" && DMSService.isConnected && Array.isArray(DMSService.capabilities) && DMSService.capabilities.includes("theme.auto"); - } - - function applyThemeAutoState(state) { - if (!state) { - return; - } - if (state.config && state.config.mode && state.config.mode !== SessionData.themeModeAutoMode) { - return; - } - if (typeof SessionData !== "undefined" && state.nextTransition !== undefined) { - SessionData.themeModeNextTransition = state.nextTransition || ""; - } - if (state.isLight !== undefined && root.isLightMode !== state.isLight) { - root.setLightMode(state.isLight, true, true); - } - } - - function syncTimeThemeSchedule() { - if (typeof SessionData === "undefined" || typeof DMSService === "undefined") { - return; - } - - if (!DMSService.isConnected) { - return; - } - - const timeModeActive = SessionData.themeModeAutoEnabled && SessionData.themeModeAutoMode === "time"; - - if (!timeModeActive) { - return; - } - - DMSService.sendRequest("theme.auto.setMode", { - "mode": "time" - }); - - const shareSettings = SessionData.themeModeShareGammaSettings; - const startHour = shareSettings ? SessionData.nightModeStartHour : SessionData.themeModeStartHour; - const startMinute = shareSettings ? SessionData.nightModeStartMinute : SessionData.themeModeStartMinute; - const endHour = shareSettings ? SessionData.nightModeEndHour : SessionData.themeModeEndHour; - const endMinute = shareSettings ? SessionData.nightModeEndMinute : SessionData.themeModeEndMinute; - - DMSService.sendRequest("theme.auto.setSchedule", { - "startHour": startHour, - "startMinute": startMinute, - "endHour": endHour, - "endMinute": endMinute - }, response => { - if (response && response.error) { - log.error("Theme automation: Failed to sync time schedule:", response.error); - } - }); - - DMSService.sendRequest("theme.auto.setEnabled", { - "enabled": true - }); - DMSService.sendRequest("theme.auto.trigger", {}); - } - - function syncLocationThemeSchedule() { - if (typeof SessionData === "undefined" || typeof DMSService === "undefined") { - return; - } - - if (!DMSService.isConnected) { - return; - } - - const locationModeActive = SessionData.themeModeAutoEnabled && SessionData.themeModeAutoMode === "location"; - - if (!locationModeActive) { - return; - } - - DMSService.sendRequest("theme.auto.setMode", { - "mode": "location" - }); - - if (SessionData.nightModeUseIPLocation) { - DMSService.sendRequest("theme.auto.setUseIPLocation", { - "use": true - }); - } else { - DMSService.sendRequest("theme.auto.setUseIPLocation", { - "use": false - }); - if (SessionData.latitude !== 0.0 && SessionData.longitude !== 0.0) { - DMSService.sendRequest("theme.auto.setLocation", { - "latitude": SessionData.latitude, - "longitude": SessionData.longitude - }); - } - } - - DMSService.sendRequest("theme.auto.setEnabled", { - "enabled": true - }); - DMSService.sendRequest("theme.auto.trigger", {}); - } - - function evaluateThemeMode() { - if (typeof SessionData === "undefined" || !SessionData.themeModeAutoEnabled) { - return; - } - - if (themeAutoBackendAvailable()) { - DMSService.sendRequest("theme.auto.getState", null, response => { - if (response && response.result) { - applyThemeAutoState(response.result); - } - }); - return; - } - - const mode = SessionData.themeModeAutoMode; - - if (mode === "location") { - evaluateLocationBasedThemeMode(); - } else { - evaluateTimeBasedThemeMode(); - } - } - - function evaluateLocationBasedThemeMode() { - if (typeof DisplayService !== "undefined") { - const shouldBeLight = DisplayService.gammaIsDay; - if (root.isLightMode !== shouldBeLight) { - root.setLightMode(shouldBeLight, true, true); - } - return; - } - - if (!SessionData.nightModeUseIPLocation && SessionData.latitude !== 0.0 && SessionData.longitude !== 0.0) { - const shouldBeLight = calculateIsDaytime(SessionData.latitude, SessionData.longitude); - if (root.isLightMode !== shouldBeLight) { - root.setLightMode(shouldBeLight, true, true); - } - return; - } - - if (root.themeModeAutomationActive) { - if (SessionData.nightModeUseIPLocation) { - log.warn("Theme automation: Waiting for IP location from backend"); - } else { - log.warn("Theme automation: Location mode requires coordinates"); - } - } - } - - function evaluateTimeBasedThemeMode() { - const shareSettings = SessionData.themeModeShareGammaSettings; - - const startHour = shareSettings ? SessionData.nightModeStartHour : SessionData.themeModeStartHour; - const startMinute = shareSettings ? SessionData.nightModeStartMinute : SessionData.themeModeStartMinute; - const endHour = shareSettings ? SessionData.nightModeEndHour : SessionData.themeModeEndHour; - const endMinute = shareSettings ? SessionData.nightModeEndMinute : SessionData.themeModeEndMinute; - - const now = new Date(); - const currentMinutes = now.getHours() * 60 + now.getMinutes(); - const startMinutes = startHour * 60 + startMinute; - const endMinutes = endHour * 60 + endMinute; - - let shouldBeLight; - if (startMinutes < endMinutes) { - shouldBeLight = currentMinutes < startMinutes || currentMinutes >= endMinutes; - } else { - shouldBeLight = currentMinutes >= endMinutes && currentMinutes < startMinutes; - } - - if (root.isLightMode !== shouldBeLight) { - root.setLightMode(shouldBeLight, true, true); - } - } - - function calculateIsDaytime(lat, lng) { - const now = new Date(); - const start = new Date(now.getFullYear(), 0, 0); - const diff = now - start; - const dayOfYear = Math.floor(diff / 86400000); - const latRad = lat * Math.PI / 180; - - const declination = 23.45 * Math.sin((360 / 365) * (dayOfYear - 81) * Math.PI / 180); - const declinationRad = declination * Math.PI / 180; - - const cosHourAngle = -Math.tan(latRad) * Math.tan(declinationRad); - - if (cosHourAngle > 1) { - return false; // Polar night - } - if (cosHourAngle < -1) { - return true; // Midnight sun - } - - const hourAngle = Math.acos(cosHourAngle); - const hourAngleDeg = hourAngle * 180 / Math.PI; - - const sunriseHour = 12 - hourAngleDeg / 15; - const sunsetHour = 12 + hourAngleDeg / 15; - - const timeZoneOffset = now.getTimezoneOffset() / 60; - const localSunrise = sunriseHour - lng / 15 - timeZoneOffset; - const localSunset = sunsetHour - lng / 15 - timeZoneOffset; - - const currentHour = now.getHours() + now.getMinutes() / 60; - - const normalizeSunrise = ((localSunrise % 24) + 24) % 24; - const normalizeSunset = ((localSunset % 24) + 24) % 24; - - return currentHour >= normalizeSunrise && currentHour < normalizeSunset; - } - - // Helper function to send location to backend - function sendLocationToBackend() { - if (typeof SessionData === "undefined" || typeof DMSService === "undefined") { - return false; - } - - if (!DMSService.isConnected) { - return false; - } - - if (SessionData.nightModeUseIPLocation) { - DMSService.sendRequest("wayland.gamma.setUseIPLocation", { - "use": true - }, response => { - if (response?.error) { - log.warn("Theme automation: Failed to enable IP location", response.error); - } - }); - return true; - } else if (SessionData.latitude !== 0.0 && SessionData.longitude !== 0.0) { - DMSService.sendRequest("wayland.gamma.setUseIPLocation", { - "use": false - }, response => { - if (!response.error) { - DMSService.sendRequest("wayland.gamma.setLocation", { - "latitude": SessionData.latitude, - "longitude": SessionData.longitude - }, locResp => { - if (locResp?.error) { - log.warn("Theme automation: Failed to set location", locResp.error); - } - }); - } - }); - return true; - } - return false; - } - - Timer { - id: locationRetryTimer - interval: 1000 - repeat: true - running: false - property int retryCount: 0 - - onTriggered: { - if (root.sendLocationToBackend()) { - stop(); - retryCount = 0; - root.evaluateThemeMode(); - } else { - retryCount++; - if (retryCount >= 10) { - stop(); - retryCount = 0; - } - } - } - } - - function startThemeModeAutomation() { - root.themeModeAutomationActive = true; - - root.syncTimeThemeSchedule(); - root.syncLocationThemeSchedule(); - - const sent = root.sendLocationToBackend(); - - if (!sent && typeof SessionData !== "undefined" && SessionData.themeModeAutoMode === "location") { - locationRetryTimer.start(); - } else { - root.evaluateThemeMode(); - } - } - - function stopThemeModeAutomation() { - root.themeModeAutomationActive = false; - if (typeof DMSService !== "undefined" && DMSService.isConnected) { - DMSService.sendRequest("theme.auto.setEnabled", { - "enabled": false - }); - } - } } diff --git a/quickshell/Common/markdown2html.js b/quickshell/Common/markdown2html.js index 04a9b52e2..787820f2e 100644 --- a/quickshell/Common/markdown2html.js +++ b/quickshell/Common/markdown2html.js @@ -4,17 +4,13 @@ function markdownToHtml(text) { if (!text) return ""; - // Store code blocks and inline code to protect them from further processing const codeBlocks = []; const inlineCode = []; let blockIndex = 0; let inlineIndex = 0; - // First, extract and replace code blocks with placeholders let html = text.replace(/```([\s\S]*?)```/g, (match, code) => { - // Trim leading and trailing blank lines only const trimmedCode = code.replace(/^\n+|\n+$/g, ''); - // Escape HTML entities in code const escapedCode = trimmedCode.replace(/&/g, '&') .replace(//g, '>'); @@ -22,9 +18,7 @@ function markdownToHtml(text) { return `\x00CODEBLOCK${blockIndex++}\x00`; }); - // Extract and replace inline code html = html.replace(/`([^`]+)`/g, (match, code) => { - // Escape HTML entities in code const escapedCode = code.replace(/&/g, '&') .replace(//g, '>'); @@ -40,12 +34,10 @@ function markdownToHtml(text) { return prefix + `\x00URL${urlIndex++}\x00`; }); - // Escape HTML entities (but not in code blocks or URLs) html = html.replace(/&/g, '&') .replace(//g, '>'); - // Headers html = html.replace(/^### (.*?)$/gm, '
');
html = html.replace(/\n/g, '
');
- // Wrap in paragraph tags if not already wrapped
if (!html.startsWith('<')) {
html = '
' + html + '
'; } - // Clean up the final HTML - // Remove/g, '');
html = html.replace(/
\s*\s*<\/p>/g, ''); html = html.replace(/
\s*
\s*<\/p>/g, '');
- // Remove excessive line breaks
- html = html.replace(/(
){3,}/g, '
'); // Max 2 consecutive line breaks
- html = html.replace(/(<\/p>)\s*(
)/g, '$1$2'); // Remove whitespace between paragraphs
+ html = html.replace(/(
){3,}/g, '
');
+ html = html.replace(/(<\/p>)\s*(
)/g, '$1$2');
- // Remove leading/trailing whitespace
html = html.trim();
return html;
diff --git a/quickshell/Common/settings/Processes.qml b/quickshell/Common/settings/Processes.qml
index 4af5a8f68..992d23001 100644
--- a/quickshell/Common/settings/Processes.qml
+++ b/quickshell/Common/settings/Processes.qml
@@ -5,11 +5,13 @@ import QtQuick
import Quickshell
import Quickshell.Io
import qs.Common
-import qs.Services
Singleton {
id: root
+ signal toastRequested(int severity, string title, string body, string command, string category)
+ signal toastCategoryDismissed(string category)
+
property var settingsRoot: null
onSettingsRootChanged: {
@@ -317,7 +319,7 @@ Singleton {
function launchAuthApplyTerminalFallback(fromPrecheck, details) {
authApplyTerminalFallbackFromPrecheck = fromPrecheck;
if (details && details !== "")
- ToastService.showInfo(I18n.tr("Authentication changes need sudo. Opening terminal so you can use password or fingerprint."), details, "", "auth-sync");
+ toastRequested(0, I18n.tr("Authentication changes need sudo. Opening terminal so you can use password or fingerprint."), details, "", "auth-sync");
authApplyTerminalFallbackStderr = "";
authApplyTerminalFallbackProcess.running = true;
}
@@ -364,21 +366,21 @@ Singleton {
}
function deferGreeterAutoLoginSyncToPill(details) {
- ToastService.dismissCategory("greeter-autologin-sync");
+ toastCategoryDismissed("greeter-autologin-sync");
if (settingsRoot)
settingsRoot.set("greeterSyncPending", true);
- ToastService.showWarning(I18n.tr("Auto-login change needs a sync"), I18n.tr("Administrator access is required. Use the Sync button in Settings → Greeter to apply.") + (details ? "\n\n" + details : ""), "dms-greeter sync --autologin", "greeter-autologin-sync");
+ toastRequested(1, I18n.tr("Auto-login change needs a sync"), I18n.tr("Administrator access is required. Use the Sync button in Settings → Greeter to apply.") + (details ? "\n\n" + details : ""), "dms-greeter sync --autologin", "greeter-autologin-sync");
finishGreeterAutoLoginSync();
}
function greeterAutoLoginSyncSuccessToast(details) {
const enabling = settingsRoot && settingsRoot.greeterAutoLogin;
// Clear the sticky in-progress toast, then confirm with an auto-dismissing toast.
- ToastService.dismissCategory("greeter-autologin-sync");
+ toastCategoryDismissed("greeter-autologin-sync");
if (enabling) {
- ToastService.showWarning(I18n.tr("Auto-login enabled"), I18n.tr("You'll skip the greeter password after the next reboot. The lock screen and signing out still require your password.") + (details ? "\n\n" + details : ""));
+ toastRequested(1, I18n.tr("Auto-login enabled"), I18n.tr("You'll skip the greeter password after the next reboot. The lock screen and signing out still require your password.") + (details ? "\n\n" + details : ""), "", "");
} else {
- ToastService.showInfo(I18n.tr("Auto-login disabled"), I18n.tr("You'll enter your password at the greeter after the next reboot.") + (details ? "\n\n" + details : ""));
+ toastRequested(0, I18n.tr("Auto-login disabled"), I18n.tr("You'll enter your password at the greeter after the next reboot.") + (details ? "\n\n" + details : ""), "", "");
}
}
@@ -575,7 +577,7 @@ Singleton {
onExited: exitCode => {
const enabling = root.settingsRoot && root.settingsRoot.greeterAutoLogin;
if (exitCode === 0) {
- ToastService.showWarning(enabling ? I18n.tr("Applying auto-login on startup...") : I18n.tr("Disabling auto-login on startup..."), "", "dms-greeter sync --autologin", "greeter-autologin-sync");
+ root.toastRequested(1, enabling ? I18n.tr("Applying auto-login on startup...") : I18n.tr("Disabling auto-login on startup..."), "", "dms-greeter sync --autologin", "greeter-autologin-sync");
root.greeterAutoLoginSyncProcess.running = true;
return;
}
@@ -604,7 +606,7 @@ Singleton {
let details = out;
if (err !== "")
details = details !== "" ? details + "\n\nstderr:\n" + err : "stderr:\n" + err;
- ToastService.showInfo(I18n.tr("Authentication changes applied"), details, "", "auth-sync");
+ root.toastRequested(0, I18n.tr("Authentication changes applied"), details, "", "auth-sync");
root.detectAuthCapabilities();
root.finishAuthApply();
return;
@@ -615,7 +617,7 @@ Singleton {
details = out;
if (err !== "")
details = details !== "" ? details + "\n\nstderr:\n" + err : "stderr:\n" + err;
- ToastService.showWarning(I18n.tr("Background authentication sync failed. Trying terminal mode."), details, "", "auth-sync");
+ root.toastRequested(1, I18n.tr("Background authentication sync failed. Trying terminal mode."), details, "", "auth-sync");
root.launchAuthApplyTerminalFallback(false, "");
}
}
@@ -631,7 +633,7 @@ Singleton {
onExited: exitCode => {
const err = (root.authApplySudoProbeStderr || "").trim();
if (exitCode === 0) {
- ToastService.showInfo(I18n.tr("Applying authentication changes..."), "", "", "auth-sync");
+ root.toastRequested(0, I18n.tr("Applying authentication changes..."), "", "", "auth-sync");
root.authApplyProcess.running = true;
return;
}
@@ -651,10 +653,10 @@ Singleton {
onExited: exitCode => {
if (exitCode === 0) {
const message = root.authApplyTerminalFallbackFromPrecheck ? I18n.tr("Terminal opened. Complete authentication there; it will close automatically when done.") : I18n.tr("Terminal fallback opened. Complete authentication there; it will close automatically when done.");
- ToastService.showInfo(message, "", "", "auth-sync");
+ root.toastRequested(0, message, "", "", "auth-sync");
} else {
let details = (root.authApplyTerminalFallbackStderr || "").trim();
- ToastService.showError(I18n.tr("Terminal fallback failed. Install a supported terminal emulator or run 'dms auth sync' manually.") + " (exit " + exitCode + ")", details, "", "auth-sync");
+ root.toastRequested(2, I18n.tr("Terminal fallback failed. Install a supported terminal emulator or run 'dms auth sync' manually.") + " (exit " + exitCode + ")", details, "", "auth-sync");
}
root.finishAuthApply();
}
diff --git a/quickshell/Common/settings/SessionStore.js b/quickshell/Common/settings/SessionStore.js
index c1e21906e..7fcdb65fe 100644
--- a/quickshell/Common/settings/SessionStore.js
+++ b/quickshell/Common/settings/SessionStore.js
@@ -75,26 +75,3 @@ function migrateToVersion(obj, targetVersion, settingsData) {
return session;
}
-
-function cleanup(fileText) {
- var getValidKeys = SpecModule.getValidKeys;
- if (!fileText || !fileText.trim()) return null;
-
- try {
- var session = JSON.parse(fileText);
- var validKeys = getValidKeys();
- var needsSave = false;
-
- for (var key in session) {
- if (validKeys.indexOf(key) < 0) {
- delete session[key];
- needsSave = true;
- }
- }
-
- return needsSave ? JSON.stringify(session, null, 2) : null;
- } catch (e) {
- console.warn("SessionData: Failed to cleanup unused keys:", e.message);
- return null;
- }
-}
diff --git a/quickshell/Common/settings/SettingsStore.js b/quickshell/Common/settings/SettingsStore.js
index 1a4720831..e10ad9527 100644
--- a/quickshell/Common/settings/SettingsStore.js
+++ b/quickshell/Common/settings/SettingsStore.js
@@ -291,26 +291,3 @@ function migrateToVersion(obj, targetVersion) {
return settings;
}
-
-function cleanup(fileText) {
- var getValidKeys = SpecModule.getValidKeys;
- if (!fileText || !fileText.trim()) return;
-
- try {
- var settings = JSON.parse(fileText);
- var validKeys = getValidKeys();
- var needsSave = false;
-
- for (var key in settings) {
- if (validKeys.indexOf(key) < 0) {
- delete settings[key];
- needsSave = true;
- }
- }
-
- return needsSave ? JSON.stringify(settings, null, 2) : null;
- } catch (e) {
- console.warn("SettingsData: Failed to cleanup unused keys:", e.message);
- return null;
- }
-}
diff --git a/quickshell/Common/suncalc.js b/quickshell/Common/suncalc.js
index db55b9a34..a3dda81a4 100644
--- a/quickshell/Common/suncalc.js
+++ b/quickshell/Common/suncalc.js
@@ -122,11 +122,6 @@ const times = [
// adds a custom time to the times config
-function addTime(angle, riseName, setName) {
- times.push([angle, riseName, setName]);
-};
-
-
// calculations for sun times
const J0 = 0.0009;
@@ -246,63 +241,3 @@ function getMoonIllumination(date) {
angle
};
};
-
-
-function hoursLater(date, h) {
- return new Date(date.valueOf() + h * dayMs / 24);
-}
-
-// calculations for moon rise/set times are based on http://www.stargazing.net/kepler/moonrise.html article
-
-function getMoonTimes(date, lat, lng, inUTC) {
- const t = new Date(date);
- if (inUTC) t.setUTCHours(0, 0, 0, 0);
- else t.setHours(0, 0, 0, 0);
-
- const hc = 0.133 * rad;
- let h0 = getMoonPosition(t, lat, lng).altitude - hc,
- rise, set, ye;
-
- // go in 2-hour chunks, each time seeing if a 3-point quadratic curve crosses zero (which means rise or set)
- for (let i = 1; i <= 24; i += 2) {
- const h1 = getMoonPosition(hoursLater(t, i), lat, lng).altitude - hc;
- const h2 = getMoonPosition(hoursLater(t, i + 1), lat, lng).altitude - hc;
- const a = (h0 + h2) / 2 - h1;
- const b = (h2 - h0) / 2;
- const xe = -b / (2 * a);
- const d = b * b - 4 * a * h1;
- let roots = 0, x1 = 0, x2 = 0;
- ye = (a * xe + b) * xe + h1;
-
- if (d >= 0) {
- const dx = Math.sqrt(d) / (Math.abs(a) * 2);
- x1 = xe - dx;
- x2 = xe + dx;
- if (Math.abs(x1) <= 1) roots++;
- if (Math.abs(x2) <= 1) roots++;
- if (x1 < -1) x1 = x2;
- }
-
- if (roots === 1) {
- if (h0 < 0) rise = i + x1;
- else set = i + x1;
-
- } else if (roots === 2) {
- rise = i + (ye < 0 ? x2 : x1);
- set = i + (ye < 0 ? x1 : x2);
- }
-
- if (rise && set) break;
-
- h0 = h2;
- }
-
- const result = {};
-
- if (rise) result.rise = hoursLater(t, rise);
- if (set) result.set = hoursLater(t, set);
-
- if (!rise && !set) result[ye > 0 ? 'alwaysUp' : 'alwaysDown'] = true;
-
- return result;
-};
diff --git a/quickshell/DMSShell.qml b/quickshell/DMSShell.qml
index 136c93404..da78a151a 100644
--- a/quickshell/DMSShell.qml
+++ b/quickshell/DMSShell.qml
@@ -233,6 +233,11 @@ Item {
PolkitService.polkitAvailable;
DisplayConfigState.hasOutputBackend;
PortalService.systemColorScheme;
+ IconThemeService.revision;
+ DesktopService.isSystemd;
+ TrashService.count;
+ WallpaperCyclingService.cyclingActive;
+ ThemeAutoService.active;
}
Loader {
diff --git a/quickshell/Modals/Common/DankModalConnected.qml b/quickshell/Modals/Common/DankModalConnected.qml
index 238002b6a..ea49ebd6a 100644
--- a/quickshell/Modals/Common/DankModalConnected.qml
+++ b/quickshell/Modals/Common/DankModalConnected.qml
@@ -314,26 +314,20 @@ Item {
readonly property real alignedWidth: Theme.px(modalWidth, dpr)
readonly property real alignedHeight: Theme.px(modalHeight, dpr)
- function _frameEdgeInset(side) {
- if (!effectiveScreen)
- return 0;
- return SettingsData.frameEdgeInsetForSide(effectiveScreen, side);
- }
-
readonly property real _connectedAlignedX: {
switch (resolvedConnectedBarSide) {
case "top":
case "bottom":
{
- const insetL = _frameEdgeInset("left");
- const insetR = _frameEdgeInset("right");
+ const insetL = SettingsData.frameEdgeInsetForSide(effectiveScreen, "left");
+ const insetR = SettingsData.frameEdgeInsetForSide(effectiveScreen, "right");
const usable = Math.max(0, screenWidth - insetL - insetR);
return insetL + Math.max(0, (usable - alignedWidth) / 2);
}
case "left":
- return _frameEdgeInset("left");
+ return SettingsData.frameEdgeInsetForSide(effectiveScreen, "left");
case "right":
- return screenWidth - alignedWidth - _frameEdgeInset("right");
+ return screenWidth - alignedWidth - SettingsData.frameEdgeInsetForSide(effectiveScreen, "right");
}
return 0;
}
@@ -341,14 +335,14 @@ Item {
readonly property real _connectedAlignedY: {
switch (resolvedConnectedBarSide) {
case "top":
- return _frameEdgeInset("top");
+ return SettingsData.frameEdgeInsetForSide(effectiveScreen, "top");
case "bottom":
- return screenHeight - alignedHeight - _frameEdgeInset("bottom");
+ return screenHeight - alignedHeight - SettingsData.frameEdgeInsetForSide(effectiveScreen, "bottom");
case "left":
case "right":
{
- const insetT = _frameEdgeInset("top");
- const insetB = _frameEdgeInset("bottom");
+ const insetT = SettingsData.frameEdgeInsetForSide(effectiveScreen, "top");
+ const insetB = SettingsData.frameEdgeInsetForSide(effectiveScreen, "bottom");
const usable = Math.max(0, screenHeight - insetT - insetB);
return insetT + Math.max(0, (usable - alignedHeight) / 2);
}
diff --git a/quickshell/Modals/DankLauncherV2/DankLauncherV2ModalConnected.qml b/quickshell/Modals/DankLauncherV2/DankLauncherV2ModalConnected.qml
index c06fcb768..89da4b2e0 100644
--- a/quickshell/Modals/DankLauncherV2/DankLauncherV2ModalConnected.qml
+++ b/quickshell/Modals/DankLauncherV2/DankLauncherV2ModalConnected.qml
@@ -105,12 +105,6 @@ Item {
}
readonly property bool _dockBlocksEmergence: frameOwnsConnectedChrome && _dockOccupiesSide(resolvedConnectedBarSide)
- function _frameEdgeInset(side) {
- if (!effectiveScreen)
- return 0;
- return SettingsData.frameEdgeInsetForSide(effectiveScreen, side);
- }
-
readonly property var _connectedModalPos: {
const fallback = {
"x": (screenWidth - modalWidth) / 2,
@@ -120,10 +114,10 @@ Item {
case "top":
case "bottom":
{
- const insetL = _frameEdgeInset("left");
- const insetR = _frameEdgeInset("right");
- const insetT = _frameEdgeInset("top");
- const insetB = _frameEdgeInset("bottom");
+ const insetL = SettingsData.frameEdgeInsetForSide(effectiveScreen, "left");
+ const insetR = SettingsData.frameEdgeInsetForSide(effectiveScreen, "right");
+ const insetT = SettingsData.frameEdgeInsetForSide(effectiveScreen, "top");
+ const insetB = SettingsData.frameEdgeInsetForSide(effectiveScreen, "bottom");
const usable = Math.max(0, screenWidth - insetL - insetR);
const usableH = Math.max(0, screenHeight - insetT - insetB);
return {
@@ -134,11 +128,11 @@ Item {
case "left":
case "right":
{
- const insetT = _frameEdgeInset("top");
- const insetB = _frameEdgeInset("bottom");
+ const insetT = SettingsData.frameEdgeInsetForSide(effectiveScreen, "top");
+ const insetB = SettingsData.frameEdgeInsetForSide(effectiveScreen, "bottom");
const usable = Math.max(0, screenHeight - insetT - insetB);
return {
- "x": resolvedConnectedBarSide === "left" ? _frameEdgeInset("left") : screenWidth - modalWidth - _frameEdgeInset("right"),
+ "x": resolvedConnectedBarSide === "left" ? SettingsData.frameEdgeInsetForSide(effectiveScreen, "left") : screenWidth - modalWidth - SettingsData.frameEdgeInsetForSide(effectiveScreen, "right"),
"y": insetT + Math.max(0, (usable - modalHeight) / 2)
};
}
@@ -188,16 +182,16 @@ Item {
readonly property real _connectedChromeY: {
if (!launcherArcExtenderActive)
return alignedY;
- return resolvedConnectedBarSide === "top" ? Theme.snap(_frameEdgeInset("top"), dpr) : alignedY;
+ return resolvedConnectedBarSide === "top" ? Theme.snap(SettingsData.frameEdgeInsetForSide(effectiveScreen, "top"), dpr) : alignedY;
}
readonly property real _connectedChromeWidth: alignedWidth
readonly property real _connectedChromeHeight: {
if (!launcherArcExtenderActive)
return alignedHeight;
if (resolvedConnectedBarSide === "top")
- return Theme.snap(Math.max(alignedHeight, alignedY + alignedHeight - _frameEdgeInset("top")), dpr);
+ return Theme.snap(Math.max(alignedHeight, alignedY + alignedHeight - SettingsData.frameEdgeInsetForSide(effectiveScreen, "top")), dpr);
if (resolvedConnectedBarSide === "bottom")
- return Theme.snap(Math.max(alignedHeight, screenHeight - _frameEdgeInset("bottom") - alignedY), dpr);
+ return Theme.snap(Math.max(alignedHeight, screenHeight - SettingsData.frameEdgeInsetForSide(effectiveScreen, "bottom") - alignedY), dpr);
return alignedHeight;
}
readonly property real contentSurfaceHeight: launcherArcExtenderActive ? _connectedChromeHeight : alignedHeight
diff --git a/quickshell/Modals/DankLauncherV2/Scorer.js b/quickshell/Modals/DankLauncherV2/Scorer.js
index 4d3a6707f..de28288ee 100644
--- a/quickshell/Modals/DankLauncherV2/Scorer.js
+++ b/quickshell/Modals/DankLauncherV2/Scorer.js
@@ -89,15 +89,6 @@ function fuzzyScore(text, query) {
return bestScore
}
-function getTimeBucketWeight(daysSinceUsed) {
- for (var i = 0; i < TimeBuckets.length; i++) {
- if (daysSinceUsed <= TimeBuckets[i].maxDays) {
- return TimeBuckets[i].weight
- }
- }
- return 10
-}
-
function calculateTextScore(name, query) {
if (name === query) return Weights.exactMatch
if (name.startsWith(query)) return Weights.prefixMatch
diff --git a/quickshell/Modals/PowerProfileModal.qml b/quickshell/Modals/PowerProfileModal.qml
index cdb9e862f..8815c7761 100644
--- a/quickshell/Modals/PowerProfileModal.qml
+++ b/quickshell/Modals/PowerProfileModal.qml
@@ -18,6 +18,19 @@ DankModal {
open();
}
+ function getPowerProfileDescription(profile) {
+ switch (profile) {
+ case 0:
+ return I18n.tr("Extend battery life", "power profile description");
+ case 1:
+ return I18n.tr("Balance power and performance", "power profile description");
+ case 2:
+ return I18n.tr("Prioritize performance", "power profile description");
+ default:
+ return I18n.tr("Custom power profile", "power profile description");
+ }
+ }
+
function hideDialog() {
close();
}
@@ -244,7 +257,7 @@ DankModal {
// Selected power profile description
StyledText {
- text: (root.selectedIndex >= 0 && root.selectedIndex < root.profileModel.length) ? Theme.getPowerProfileDescription(root.profileModel[root.selectedIndex]) : ""
+ text: (root.selectedIndex >= 0 && root.selectedIndex < root.profileModel.length) ? root.getPowerProfileDescription(root.profileModel[root.selectedIndex]) : ""
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceTextMedium
horizontalAlignment: Text.AlignHCenter
diff --git a/quickshell/Modals/ProcessListModal.qml b/quickshell/Modals/ProcessListModal.qml
index 59d76752a..08f75f677 100644
--- a/quickshell/Modals/ProcessListModal.qml
+++ b/quickshell/Modals/ProcessListModal.qml
@@ -6,6 +6,7 @@ import qs.Common
import qs.Modules.ProcessList
import qs.Services
import qs.Widgets
+import "../Common/Format.js" as Format
FloatingWindow {
id: processListModal
@@ -67,16 +68,6 @@ FloatingWindow {
show();
}
- function formatBytes(bytes) {
- if (bytes < 1024)
- return bytes.toFixed(0) + " B/s";
- if (bytes < 1024 * 1024)
- return (bytes / 1024).toFixed(1) + " KB/s";
- if (bytes < 1024 * 1024 * 1024)
- return (bytes / (1024 * 1024)).toFixed(1) + " MB/s";
- return (bytes / (1024 * 1024 * 1024)).toFixed(2) + " GB/s";
- }
-
function nextTab() {
currentTab = (currentTab + 1) % 4;
}
@@ -541,7 +532,7 @@ FloatingWindow {
}
StyledText {
- text: "↓" + formatBytes(DgopService.networkRxRate) + " ↑" + formatBytes(DgopService.networkTxRate)
+ text: "↓" + Format.formatRate(DgopService.networkRxRate) + " ↑" + Format.formatRate(DgopService.networkTxRate)
font.pixelSize: Theme.fontSizeSmall
font.family: SettingsData.monoFontFamily
color: Theme.surfaceText
@@ -559,7 +550,7 @@ FloatingWindow {
}
StyledText {
- text: "↓" + formatBytes(DgopService.diskReadRate) + " ↑" + formatBytes(DgopService.diskWriteRate)
+ text: "↓" + Format.formatRate(DgopService.diskReadRate) + " ↑" + Format.formatRate(DgopService.diskWriteRate)
font.pixelSize: Theme.fontSizeSmall
font.family: SettingsData.monoFontFamily
color: Theme.surfaceText
diff --git a/quickshell/Modules/BlurredWallpaperLive.qml b/quickshell/Modules/BlurredWallpaperLive.qml
index 16753b1ac..592a720a4 100644
--- a/quickshell/Modules/BlurredWallpaperLive.qml
+++ b/quickshell/Modules/BlurredWallpaperLive.qml
@@ -25,29 +25,6 @@ Item {
property bool effectActive: false
property bool useNextForEffect: false
- function getFillMode(modeName) {
- switch (modeName) {
- case "Stretch":
- return Image.Stretch;
- case "Fit":
- case "PreserveAspectFit":
- return Image.PreserveAspectFit;
- case "Fill":
- case "PreserveAspectCrop":
- return Image.PreserveAspectCrop;
- case "Tile":
- return Image.Tile;
- case "TileVertically":
- return Image.TileVertically;
- case "TileHorizontally":
- return Image.TileHorizontally;
- case "Pad":
- return Image.Pad;
- default:
- return Image.PreserveAspectCrop;
- }
- }
-
Component.onCompleted: {
if (initialSource && initialSource !== wallpaperSource && !(CompositorService.isNiri && SessionData.isSwitchingMode)) {
currentWallpaper.source = initialSource;
@@ -144,7 +121,7 @@ Item {
smooth: true
cache: true
sourceSize: root.blurTextureSize
- fillMode: root.getFillMode(SessionData.getMonitorWallpaperFillMode(root.screenName))
+ fillMode: Theme.getFillMode(SessionData.getMonitorWallpaperFillMode(root.screenName))
onStatusChanged: {
if (status === Image.Error) {
@@ -166,7 +143,7 @@ Item {
smooth: true
cache: true
sourceSize: root.blurTextureSize
- fillMode: root.getFillMode(SessionData.getMonitorWallpaperFillMode(root.screenName))
+ fillMode: Theme.getFillMode(SessionData.getMonitorWallpaperFillMode(root.screenName))
onStatusChanged: {
if (status === Image.Error) {
diff --git a/quickshell/Modules/BuiltinDesktopPlugins/SystemMonitorWidget.qml b/quickshell/Modules/BuiltinDesktopPlugins/SystemMonitorWidget.qml
index 8402842a9..b482e93a7 100644
--- a/quickshell/Modules/BuiltinDesktopPlugins/SystemMonitorWidget.qml
+++ b/quickshell/Modules/BuiltinDesktopPlugins/SystemMonitorWidget.qml
@@ -4,6 +4,7 @@ import Quickshell
import qs.Common
import qs.Services
import qs.Widgets
+import "../../Common/Format.js" as Format
Item {
id: root
@@ -188,16 +189,6 @@ Item {
return DgopService.availableGpus.find(g => g.pciId === selectedGpuPciId);
}
- function formatBytes(bytes) {
- if (bytes < 1024)
- return bytes.toFixed(0) + "B";
- if (bytes < 1024 * 1024)
- return (bytes / 1024).toFixed(0) + "K";
- if (bytes < 1024 * 1024 * 1024)
- return (bytes / (1024 * 1024)).toFixed(1) + "M";
- return (bytes / (1024 * 1024 * 1024)).toFixed(1) + "G";
- }
-
function formatMemKB(kb) {
if (kb < 1024)
return kb.toFixed(0) + "K";
@@ -206,26 +197,18 @@ Item {
return (kb / (1024 * 1024)).toFixed(1) + "G";
}
- function addToHistory(arr, val) {
- var newArr = arr.slice();
- newArr.push(val);
- if (newArr.length > historySize)
- newArr.shift();
- return newArr;
- }
-
function sampleData() {
if (showCpuGraph)
- cpuHistory = addToHistory(cpuHistory, DgopService.cpuUsage);
+ cpuHistory = Format.addToHistory(cpuHistory, DgopService.cpuUsage, historySize);
if (showMemoryGraph)
- memHistory = addToHistory(memHistory, DgopService.memoryUsage);
+ memHistory = Format.addToHistory(memHistory, DgopService.memoryUsage, historySize);
if (showNetworkGraph) {
- netRxHistory = addToHistory(netRxHistory, DgopService.networkRxRate);
- netTxHistory = addToHistory(netTxHistory, DgopService.networkTxRate);
+ netRxHistory = Format.addToHistory(netRxHistory, DgopService.networkRxRate, historySize);
+ netTxHistory = Format.addToHistory(netTxHistory, DgopService.networkTxRate, historySize);
}
if (showDisk) {
- diskReadHistory = addToHistory(diskReadHistory, DgopService.diskReadRate);
- diskWriteHistory = addToHistory(diskWriteHistory, DgopService.diskWriteRate);
+ diskReadHistory = Format.addToHistory(diskReadHistory, DgopService.diskReadRate, historySize);
+ diskWriteHistory = Format.addToHistory(diskWriteHistory, DgopService.diskWriteRate, historySize);
}
}
@@ -519,7 +502,7 @@ Item {
color: root.accentColor
}
StyledText {
- text: root.formatBytes(DgopService.networkRxRate) + "/s"
+ text: Format.formatBytes(DgopService.networkRxRate) + "/s"
isMonospace: true
font.pixelSize: Theme.fontSizeMedium
color: root.textColor
@@ -533,7 +516,7 @@ Item {
color: root.dimColor
}
StyledText {
- text: root.formatBytes(DgopService.networkTxRate) + "/s"
+ text: Format.formatBytes(DgopService.networkTxRate) + "/s"
isMonospace: true
font.pixelSize: Theme.fontSizeMedium
color: root.textColor
@@ -552,7 +535,7 @@ Item {
color: root.accentColor
}
StyledText {
- text: root.formatBytes(DgopService.diskReadRate) + "/s"
+ text: Format.formatBytes(DgopService.diskReadRate) + "/s"
isMonospace: true
font.pixelSize: Theme.fontSizeMedium
color: root.textColor
@@ -566,7 +549,7 @@ Item {
color: root.dimColor
}
StyledText {
- text: root.formatBytes(DgopService.diskWriteRate) + "/s"
+ text: Format.formatBytes(DgopService.diskWriteRate) + "/s"
isMonospace: true
font.pixelSize: Theme.fontSizeMedium
color: root.textColor
diff --git a/quickshell/Modules/ControlCenter/BuiltinPlugins/VpnWidget.qml b/quickshell/Modules/ControlCenter/BuiltinPlugins/VpnWidget.qml
index 978da4747..75b69d867 100644
--- a/quickshell/Modules/ControlCenter/BuiltinPlugins/VpnWidget.qml
+++ b/quickshell/Modules/ControlCenter/BuiltinPlugins/VpnWidget.qml
@@ -1,7 +1,7 @@
import QtQuick
import qs.Common
import qs.Services
-import qs.Widgets
+import qs.Modules.ControlCenter.Details
import qs.Modules.Plugins
PluginComponent {
diff --git a/quickshell/Modules/ControlCenter/Components/ActionTile.qml b/quickshell/Modules/ControlCenter/Components/ActionTile.qml
deleted file mode 100644
index 2f8bc47a2..000000000
--- a/quickshell/Modules/ControlCenter/Components/ActionTile.qml
+++ /dev/null
@@ -1,131 +0,0 @@
-import QtQuick
-import qs.Common
-import qs.Widgets
-
-Rectangle {
- id: root
-
- LayoutMirroring.enabled: I18n.isRtl
- LayoutMirroring.childrenInherit: true
-
- property string iconName: ""
- property string text: ""
- property string secondaryText: ""
- property bool isActive: false
- property int widgetIndex: 0
- property var widgetData: null
- property bool editMode: false
-
- signal clicked
-
- width: parent ? parent.width : 200
- height: 60
- radius: {
- if (Theme.cornerRadius === 0)
- return 0;
- return isActive ? Theme.cornerRadius : Theme.cornerRadius + 4;
- }
-
- readonly property color _tileBgActive: Theme.ccTileActiveBg
- readonly property color _tileBgInactive: Theme.ccPillInactiveBg
- readonly property color _tileRingActive: Theme.ccTileRing
-
- color: isActive ? _tileBgActive : _tileBgInactive
- border.color: isActive ? _tileRingActive : Theme.outlineMedium
- border.width: isActive ? 1 : Theme.layerOutlineWidth
- opacity: enabled ? 1.0 : 0.6
-
- function hoverTint(base) {
- const factor = 1.2;
- return Theme.isLightMode ? Qt.darker(base, factor) : Qt.lighter(base, factor);
- }
-
- Rectangle {
- anchors.fill: parent
- radius: Theme.cornerRadius
- color: mouseArea.containsMouse ? hoverTint(root.color) : Theme.withAlpha(hoverTint(root.color), 0)
- opacity: mouseArea.containsMouse ? 0.08 : 0.0
-
- Behavior on opacity {
- NumberAnimation {
- duration: Theme.shortDuration
- }
- }
- }
-
- Row {
- anchors.fill: parent
- anchors.leftMargin: Theme.spacingL + 2
- anchors.rightMargin: Theme.spacingM
- spacing: Theme.spacingM
-
- DankIcon {
- name: root.iconName
- size: Theme.iconSize
- color: isActive ? Theme.ccTileActiveText : Theme.ccTileInactiveIcon
- anchors.verticalCenter: parent.verticalCenter
- }
-
- Item {
- width: parent.width - Theme.iconSize - parent.spacing
- height: parent.height
-
- Column {
- anchors.left: parent.left
- anchors.right: parent.right
- anchors.verticalCenter: parent.verticalCenter
- spacing: Theme.spacingXXS
-
- Typography {
- width: parent.width
- text: root.text
- style: Typography.Style.Body
- color: isActive ? Theme.ccTileActiveText : Theme.surfaceText
- elide: Text.ElideRight
- wrapMode: Text.NoWrap
- horizontalAlignment: Text.AlignLeft
- }
-
- Typography {
- width: parent.width
- text: root.secondaryText
- style: Typography.Style.Caption
- color: isActive ? Theme.ccTileActiveText : Theme.surfaceVariantText
- visible: text.length > 0
- elide: Text.ElideRight
- wrapMode: Text.NoWrap
- horizontalAlignment: Text.AlignLeft
- }
- }
- }
- }
-
- DankRipple {
- id: ripple
- cornerRadius: root.radius
- }
-
- MouseArea {
- id: mouseArea
- anchors.fill: parent
- hoverEnabled: true
- cursorShape: Qt.PointingHandCursor
- enabled: root.enabled
- onPressed: mouse => ripple.trigger(mouse.x, mouse.y)
- onClicked: root.clicked()
- }
-
- Behavior on color {
- ColorAnimation {
- duration: Theme.shortDuration
- easing.type: Theme.standardEasing
- }
- }
-
- Behavior on radius {
- NumberAnimation {
- duration: Theme.shortDuration
- easing.type: Theme.standardEasing
- }
- }
-}
diff --git a/quickshell/Modules/ControlCenter/Components/DragDropDetailHost.qml b/quickshell/Modules/ControlCenter/Components/DragDropDetailHost.qml
deleted file mode 100644
index 4a7ff19f0..000000000
--- a/quickshell/Modules/ControlCenter/Components/DragDropDetailHost.qml
+++ /dev/null
@@ -1,91 +0,0 @@
-import QtQuick
-import qs.Common
-import qs.Modules.ControlCenter.Details
-
-Item {
- id: root
-
- property string expandedSection: ""
- property var expandedWidgetData: null
-
- height: active ? 250 : 0
- visible: active
-
- readonly property bool active: expandedSection !== ""
-
- Loader {
- anchors.fill: parent
- anchors.topMargin: Theme.spacingS
- sourceComponent: {
- if (!root.active)
- return null;
-
- if (expandedSection.startsWith("diskUsage_")) {
- return diskUsageDetailComponent;
- }
-
- switch (expandedSection) {
- case "wifi":
- return networkDetailComponent;
- case "bluetooth":
- return bluetoothDetailComponent;
- case "audioOutput":
- return audioOutputDetailComponent;
- case "audioInput":
- return audioInputDetailComponent;
- case "battery":
- return batteryDetailComponent;
- default:
- return null;
- }
- }
- }
-
- Component {
- id: networkDetailComponent
- NetworkDetail {}
- }
-
- Component {
- id: bluetoothDetailComponent
- BluetoothDetail {}
- }
-
- Component {
- id: audioOutputDetailComponent
- AudioOutputDetail {}
- }
-
- Component {
- id: audioInputDetailComponent
- AudioInputDetail {}
- }
-
- Component {
- id: batteryDetailComponent
- BatteryDetail {}
- }
-
- Component {
- id: diskUsageDetailComponent
- DiskUsageDetail {
- currentMountPath: root.expandedWidgetData?.mountPath || "/"
- instanceId: root.expandedWidgetData?.instanceId || ""
-
- onMountPathChanged: newMountPath => {
- if (root.expandedWidgetData && root.expandedWidgetData.id === "diskUsage") {
- const widgets = SettingsData.controlCenterWidgets || [];
- const newWidgets = widgets.map(w => {
- if (w.id === "diskUsage" && w.instanceId === root.expandedWidgetData.instanceId) {
- const updatedWidget = Object.assign({}, w);
- updatedWidget.mountPath = newMountPath;
- return updatedWidget;
- }
- return w;
- });
- SettingsData.set("controlCenterWidgets", newWidgets);
- }
- }
- }
- }
-}
diff --git a/quickshell/Modules/ControlCenter/Components/PowerButton.qml b/quickshell/Modules/ControlCenter/Components/PowerButton.qml
deleted file mode 100644
index e8c474906..000000000
--- a/quickshell/Modules/ControlCenter/Components/PowerButton.qml
+++ /dev/null
@@ -1,55 +0,0 @@
-import QtQuick
-import qs.Common
-import qs.Widgets
-
-Rectangle {
- id: root
-
- LayoutMirroring.enabled: I18n.isRtl
- LayoutMirroring.childrenInherit: true
-
- property string iconName: ""
- property string text: ""
-
- signal pressed
-
- height: 34
- radius: Theme.cornerRadius
- color: mouseArea.containsMouse ? Theme.primaryHover : Theme.withAlpha(Theme.surfaceVariant, 0.5)
-
- Row {
- anchors.centerIn: parent
- spacing: Theme.spacingXS
-
- DankIcon {
- name: root.iconName
- size: Theme.fontSizeSmall
- color: mouseArea.containsMouse ? Theme.primary : Theme.surfaceText
- anchors.verticalCenter: parent.verticalCenter
- }
-
- Typography {
- text: root.text
- style: Typography.Style.Button
- color: mouseArea.containsMouse ? Theme.primary : Theme.surfaceText
- anchors.verticalCenter: parent.verticalCenter
- }
- }
-
- DankRipple {
- id: ripple
- cornerRadius: root.radius
- }
-
- MouseArea {
- id: mouseArea
-
- anchors.fill: parent
- hoverEnabled: true
- cursorShape: Qt.PointingHandCursor
- onPressed: mouse => {
- ripple.trigger(mouse.x, mouse.y);
- root.pressed();
- }
- }
-}
diff --git a/quickshell/Modules/ControlCenter/Details/AudioInputDetail.qml b/quickshell/Modules/ControlCenter/Details/AudioInputDetail.qml
index 120774525..bc3a8abaa 100644
--- a/quickshell/Modules/ControlCenter/Details/AudioInputDetail.qml
+++ b/quickshell/Modules/ControlCenter/Details/AudioInputDetail.qml
@@ -4,6 +4,7 @@ import Quickshell.Services.Pipewire
import qs.Common
import qs.Services
import qs.Widgets
+import "../../../Common/QmlUtils.js" as QmlUtils
Rectangle {
id: root
@@ -150,17 +151,9 @@ Rectangle {
property int maxPinnedInputs: 3
- function normalizePinList(value) {
- if (Array.isArray(value))
- return value.filter(v => v);
- if (typeof value === "string" && value.length > 0)
- return [value];
- return [];
- }
-
function getPinnedInputs() {
const pins = CacheData.audioInputDevicePins || {};
- return normalizePinList(pins["preferredInput"]);
+ return QmlUtils.normalizePinList(pins["preferredInput"]);
}
Column {
@@ -316,7 +309,7 @@ Rectangle {
onPressed: mouse => pinRipple.trigger(mouse.x, mouse.y)
onClicked: {
const pins = JSON.parse(JSON.stringify(CacheData.audioInputDevicePins || {}));
- let pinnedList = audioContent.normalizePinList(pins["preferredInput"]);
+ let pinnedList = QmlUtils.normalizePinList(pins["preferredInput"]);
const pinIndex = pinnedList.indexOf(modelData.name);
if (pinIndex !== -1) {
diff --git a/quickshell/Modules/ControlCenter/Details/AudioOutputDetail.qml b/quickshell/Modules/ControlCenter/Details/AudioOutputDetail.qml
index 2bbec13c4..974377838 100644
--- a/quickshell/Modules/ControlCenter/Details/AudioOutputDetail.qml
+++ b/quickshell/Modules/ControlCenter/Details/AudioOutputDetail.qml
@@ -4,6 +4,7 @@ import Quickshell.Services.Pipewire
import qs.Common
import qs.Services
import qs.Widgets
+import "../../../Common/QmlUtils.js" as QmlUtils
Rectangle {
id: root
@@ -160,17 +161,9 @@ Rectangle {
property int maxPinnedOutputs: 3
- function normalizePinList(value) {
- if (Array.isArray(value))
- return value.filter(v => v);
- if (typeof value === "string" && value.length > 0)
- return [value];
- return [];
- }
-
function getPinnedOutputs() {
const pins = CacheData.audioOutputDevicePins || {};
- return normalizePinList(pins["preferredOutput"]);
+ return QmlUtils.normalizePinList(pins["preferredOutput"]);
}
Column {
@@ -325,7 +318,7 @@ Rectangle {
onPressed: mouse => pinRipple.trigger(mouse.x, mouse.y)
onClicked: {
const pins = JSON.parse(JSON.stringify(CacheData.audioOutputDevicePins || {}));
- let pinnedList = audioContent.normalizePinList(pins["preferredOutput"]);
+ let pinnedList = QmlUtils.normalizePinList(pins["preferredOutput"]);
const pinIndex = pinnedList.indexOf(modelData.name);
if (pinIndex !== -1) {
diff --git a/quickshell/Modules/ControlCenter/Details/BluetoothDetail.qml b/quickshell/Modules/ControlCenter/Details/BluetoothDetail.qml
index ba277eddc..b44d3edf8 100644
--- a/quickshell/Modules/ControlCenter/Details/BluetoothDetail.qml
+++ b/quickshell/Modules/ControlCenter/Details/BluetoothDetail.qml
@@ -5,6 +5,7 @@ import Quickshell.Bluetooth
import qs.Common
import qs.Services
import qs.Widgets
+import "../../../Common/QmlUtils.js" as QmlUtils
Rectangle {
id: root
@@ -95,17 +96,9 @@ Rectangle {
BluetoothService.updateDeviceCodec(deviceAddress, codecName);
}
- function normalizePinList(value) {
- if (Array.isArray(value))
- return value.filter(v => v);
- if (typeof value === "string" && value.length > 0)
- return [value];
- return [];
- }
-
function getPinnedDevices() {
const pins = CacheData.bluetoothDevicePins || {};
- return normalizePinList(pins["preferredDevice"]);
+ return QmlUtils.normalizePinList(pins["preferredDevice"]);
}
Row {
@@ -396,7 +389,7 @@ Rectangle {
cursorShape: Qt.PointingHandCursor
onClicked: {
const pins = JSON.parse(JSON.stringify(CacheData.bluetoothDevicePins || {}));
- let pinnedList = root.normalizePinList(pins["preferredDevice"]);
+ let pinnedList = QmlUtils.normalizePinList(pins["preferredDevice"]);
const pinIndex = pinnedList.indexOf(pairedDelegate.modelData.address);
if (pinIndex !== -1) {
diff --git a/quickshell/Modules/ControlCenter/Details/DoNotDisturbDetail.qml b/quickshell/Modules/ControlCenter/Details/DoNotDisturbDetail.qml
index 7613f7eaf..dd5c10b12 100644
--- a/quickshell/Modules/ControlCenter/Details/DoNotDisturbDetail.qml
+++ b/quickshell/Modules/ControlCenter/Details/DoNotDisturbDetail.qml
@@ -1,6 +1,7 @@
import QtQuick
import qs.Common
import qs.Widgets
+import "../../../Common/Format.js" as Format
Rectangle {
id: root
@@ -23,33 +24,8 @@ Rectangle {
onTriggered: root.nowMs = Date.now()
}
- function _pad2(n) {
- return n < 10 ? "0" + n : "" + n;
- }
-
- function formatUntil(ts) {
- if (!ts)
- return "";
- const d = new Date(ts);
- const use24h = (typeof SettingsData !== "undefined") ? SettingsData.use24HourClock : true;
- if (use24h)
- return _pad2(d.getHours()) + ":" + _pad2(d.getMinutes());
- const suffix = d.getHours() >= 12 ? "PM" : "AM";
- const h12 = ((d.getHours() + 11) % 12) + 1;
- return h12 + ":" + _pad2(d.getMinutes()) + " " + suffix;
- }
-
function formatRemaining(ms) {
- if (ms <= 0)
- return "";
- const totalMinutes = Math.ceil(ms / 60000);
- if (totalMinutes < 60)
- return I18n.tr("%1 min left").arg(totalMinutes);
- const hours = Math.floor(totalMinutes / 60);
- const mins = totalMinutes - hours * 60;
- if (mins === 0)
- return I18n.tr("%1 h left").arg(hours);
- return I18n.tr("%1 h %2 m left").arg(hours).arg(mins);
+ return Format.formatRemaining(ms, "", I18n.tr("%1 min left"), I18n.tr("%1 h left"), I18n.tr("%1 h %2 m left"));
}
function minutesUntilTomorrowMorning() {
@@ -125,7 +101,7 @@ Rectangle {
if (SessionData.doNotDisturbUntil <= 0)
return I18n.tr("On indefinitely");
const remaining = Math.max(0, SessionData.doNotDisturbUntil - root.nowMs);
- return root.formatRemaining(remaining) + " · " + I18n.tr("until %1").arg(root.formatUntil(SessionData.doNotDisturbUntil));
+ return root.formatRemaining(remaining) + " · " + I18n.tr("until %1").arg(Format.formatUntil(SessionData.doNotDisturbUntil, SettingsData.use24HourClock));
}
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
diff --git a/quickshell/Modules/ControlCenter/Details/NetworkDetail.qml b/quickshell/Modules/ControlCenter/Details/NetworkDetail.qml
index 79abb4853..684534157 100644
--- a/quickshell/Modules/ControlCenter/Details/NetworkDetail.qml
+++ b/quickshell/Modules/ControlCenter/Details/NetworkDetail.qml
@@ -7,6 +7,7 @@ import qs.Services
import qs.Widgets
import qs.Modals
import qs.Modals.Common
+import "../../../Common/QmlUtils.js" as QmlUtils
Rectangle {
id: root
@@ -66,17 +67,9 @@ Rectangle {
PopoutService.openSettingsWithTab("network_wifi");
}
- function normalizePinList(value) {
- if (Array.isArray(value))
- return value.filter(v => v);
- if (typeof value === "string" && value.length > 0)
- return [value];
- return [];
- }
-
function getPinnedNetworks() {
const pins = CacheData.wifiNetworkPins || {};
- return normalizePinList(pins["preferredWifi"]);
+ return QmlUtils.normalizePinList(pins["preferredWifi"]);
}
property int currentPreferenceIndex: {
@@ -908,7 +901,7 @@ Rectangle {
onPressed: mouse => pinRipple.trigger(mouse.x, mouse.y)
onClicked: {
const pins = JSON.parse(JSON.stringify(CacheData.wifiNetworkPins || {}));
- let pinnedList = root.normalizePinList(pins["preferredWifi"]);
+ let pinnedList = QmlUtils.normalizePinList(pins["preferredWifi"]);
const pinIndex = pinnedList.indexOf(modelData.ssid);
if (pinIndex !== -1) {
diff --git a/quickshell/Widgets/VpnDetailContent.qml b/quickshell/Modules/ControlCenter/Details/VpnDetailContent.qml
similarity index 100%
rename from quickshell/Widgets/VpnDetailContent.qml
rename to quickshell/Modules/ControlCenter/Details/VpnDetailContent.qml
diff --git a/quickshell/Widgets/VpnProfileDelegate.qml b/quickshell/Modules/ControlCenter/Details/VpnProfileDelegate.qml
similarity index 100%
rename from quickshell/Widgets/VpnProfileDelegate.qml
rename to quickshell/Modules/ControlCenter/Details/VpnProfileDelegate.qml
diff --git a/quickshell/Modules/ControlCenter/Widgets/CompactSlider.qml b/quickshell/Modules/ControlCenter/Widgets/CompactSlider.qml
deleted file mode 100644
index 405e5392e..000000000
--- a/quickshell/Modules/ControlCenter/Widgets/CompactSlider.qml
+++ /dev/null
@@ -1,72 +0,0 @@
-import QtQuick
-import qs.Common
-import qs.Widgets
-
-Rectangle {
- id: root
-
- LayoutMirroring.enabled: I18n.isRtl
- LayoutMirroring.childrenInherit: true
-
- property string iconName: ""
- property color iconColor: Theme.surfaceText
- property string labelText: ""
- property real value: 0.0
- property real maximumValue: 1.0
- property real minimumValue: 0.0
-
- signal sliderValueChanged(real value)
-
- width: parent ? parent.width : 200
- height: 60
- radius: Theme.cornerRadius
- color: Theme.nestedSurface
- border.color: Theme.outlineMedium
- border.width: Theme.layerOutlineWidth
- opacity: enabled ? 1.0 : 0.6
-
- Row {
- anchors.left: parent.left
- anchors.verticalCenter: parent.verticalCenter
- anchors.leftMargin: Theme.spacingM
- anchors.right: sliderContainer.left
- anchors.rightMargin: Theme.spacingS
- spacing: Theme.spacingS
-
- DankIcon {
- name: root.iconName
- size: Theme.iconSize
- color: root.iconColor
- anchors.verticalCenter: parent.verticalCenter
- }
-
- StyledText {
- text: root.labelText
- font.pixelSize: Theme.fontSizeMedium
- color: Theme.surfaceText
- font.weight: Font.Medium
- anchors.verticalCenter: parent.verticalCenter
- }
- }
-
- Rectangle {
- id: sliderContainer
- anchors.right: parent.right
- anchors.verticalCenter: parent.verticalCenter
- anchors.rightMargin: Theme.spacingM
- width: 120
- height: parent.height - Theme.spacingS * 2
-
- DankSlider {
- anchors.centerIn: parent
- width: parent.width
- enabled: root.enabled
- minimum: Math.round(root.minimumValue * 100)
- maximum: Math.round(root.maximumValue * 100)
- value: Math.round(root.value * 100)
- trackColor: Theme.ccSliderTrackColor
- trackOpacity: Theme.ccSliderTrackOpacity
- onSliderValueChanged: root.sliderValueChanged(newValue / 100.0)
- }
- }
-}
diff --git a/quickshell/Modules/ControlCenter/Widgets/CompoundPill.qml b/quickshell/Modules/ControlCenter/Widgets/CompoundPill.qml
index 50f910b8e..e09c4472e 100644
--- a/quickshell/Modules/ControlCenter/Widgets/CompoundPill.qml
+++ b/quickshell/Modules/ControlCenter/Widgets/CompoundPill.qml
@@ -25,11 +25,6 @@ Rectangle {
height: 60
radius: Theme.cornerRadius
- function hoverTint(base) {
- const factor = 1.2;
- return Theme.isLightMode ? Qt.darker(base, factor) : Qt.lighter(base, factor);
- }
-
readonly property color _containerBg: Theme.ccPillInactiveBg
color: {
@@ -61,7 +56,7 @@ Rectangle {
radius: root.radius
z: 0
visible: false
- color: hoverTint(_containerBg)
+ color: Theme.hoverTint(_containerBg)
opacity: 0.08
antialiasing: true
Behavior on opacity {
@@ -98,7 +93,7 @@ Rectangle {
Rectangle {
anchors.fill: parent
radius: _tileRadius
- color: hoverTint(iconTile.color)
+ color: Theme.hoverTint(iconTile.color)
opacity: tileMouse.pressed ? 0.3 : (tileMouse.containsMouse ? 0.2 : 0.0)
visible: opacity > 0
antialiasing: true
diff --git a/quickshell/Modules/ControlCenter/Widgets/DetailView.qml b/quickshell/Modules/ControlCenter/Widgets/DetailView.qml
deleted file mode 100644
index 2d946f395..000000000
--- a/quickshell/Modules/ControlCenter/Widgets/DetailView.qml
+++ /dev/null
@@ -1,23 +0,0 @@
-import QtQuick
-
-Rectangle {
- id: root
-
- property string title: ""
- property Component content: null
- property bool isVisible: true
- property int contentHeight: 300
-
- width: parent ? parent.width : 400
- implicitHeight: isVisible ? contentHeight : 0
- height: implicitHeight
- color: "transparent"
- clip: true
-
- Loader {
- id: contentLoader
- anchors.fill: parent
- sourceComponent: root.content
- asynchronous: true
- }
-}
diff --git a/quickshell/Modules/ControlCenter/Widgets/SmallBatteryButton.qml b/quickshell/Modules/ControlCenter/Widgets/SmallBatteryButton.qml
index 0eb730f8c..0fc6e6ad3 100644
--- a/quickshell/Modules/ControlCenter/Widgets/SmallBatteryButton.qml
+++ b/quickshell/Modules/ControlCenter/Widgets/SmallBatteryButton.qml
@@ -22,11 +22,6 @@ Rectangle {
return isActive ? Theme.cornerRadius : Theme.cornerRadius + 4;
}
- function hoverTint(base) {
- const factor = 1.2;
- return Theme.isLightMode ? Qt.darker(base, factor) : Qt.lighter(base, factor);
- }
-
readonly property color _tileBgActive: Theme.ccTileActiveBg
readonly property color _tileBgInactive: Theme.ccPillInactiveBg
readonly property color _tileRingActive: Theme.ccTileRing
@@ -47,7 +42,7 @@ Rectangle {
Rectangle {
anchors.fill: parent
radius: parent.radius
- color: hoverTint(root.color)
+ color: Theme.hoverTint(root.color)
opacity: mouseArea.pressed ? 0.3 : (mouseArea.containsMouse ? 0.2 : 0.0)
visible: opacity > 0
antialiasing: true
diff --git a/quickshell/Modules/ControlCenter/Widgets/SmallColorPickerButton.qml b/quickshell/Modules/ControlCenter/Widgets/SmallColorPickerButton.qml
index d2d761df6..b1cd7c199 100644
--- a/quickshell/Modules/ControlCenter/Widgets/SmallColorPickerButton.qml
+++ b/quickshell/Modules/ControlCenter/Widgets/SmallColorPickerButton.qml
@@ -16,11 +16,6 @@ Rectangle {
height: 48
radius: Theme.cornerRadius === 0 ? 0 : Theme.cornerRadius
- function hoverTint(base) {
- const factor = 1.2;
- return Theme.isLightMode ? Qt.darker(base, factor) : Qt.lighter(base, factor);
- }
-
color: Theme.primary
border.color: Theme.ccTileRing
border.width: 1
@@ -29,7 +24,7 @@ Rectangle {
Rectangle {
anchors.fill: parent
radius: parent.radius
- color: hoverTint(root.color)
+ color: Theme.hoverTint(root.color)
opacity: mouseArea.pressed ? 0.3 : (mouseArea.containsMouse ? 0.2 : 0.0)
visible: opacity > 0
antialiasing: true
diff --git a/quickshell/Modules/ControlCenter/Widgets/SmallCompoundButton.qml b/quickshell/Modules/ControlCenter/Widgets/SmallCompoundButton.qml
index bb9d2417c..8d7a22aa9 100644
--- a/quickshell/Modules/ControlCenter/Widgets/SmallCompoundButton.qml
+++ b/quickshell/Modules/ControlCenter/Widgets/SmallCompoundButton.qml
@@ -25,11 +25,6 @@ Rectangle {
return isActive ? Theme.cornerRadius : Theme.cornerRadius + 4;
}
- function hoverTint(base) {
- const factor = 1.2;
- return Theme.isLightMode ? Qt.darker(base, factor) : Qt.lighter(base, factor);
- }
-
readonly property color _tileBgActive: Theme.ccTileActiveBg
readonly property color _tileBgInactive: Theme.ccPillInactiveBg
readonly property color _tileRingActive: Theme.ccTileRing
@@ -50,7 +45,7 @@ Rectangle {
Rectangle {
anchors.fill: parent
radius: parent.radius
- color: hoverTint(root.color)
+ color: Theme.hoverTint(root.color)
opacity: mouseArea.pressed ? 0.3 : (mouseArea.containsMouse ? 0.2 : 0.0)
visible: opacity > 0
antialiasing: true
diff --git a/quickshell/Modules/ControlCenter/Widgets/SmallDiskUsageButton.qml b/quickshell/Modules/ControlCenter/Widgets/SmallDiskUsageButton.qml
index 32db097a4..564eab5cf 100644
--- a/quickshell/Modules/ControlCenter/Widgets/SmallDiskUsageButton.qml
+++ b/quickshell/Modules/ControlCenter/Widgets/SmallDiskUsageButton.qml
@@ -34,11 +34,6 @@ Rectangle {
height: 48
radius: Theme.cornerRadius + 4
- function hoverTint(base) {
- const factor = 1.2;
- return Theme.isLightMode ? Qt.darker(base, factor) : Qt.lighter(base, factor);
- }
-
readonly property color _tileBg: Theme.ccPillInactiveBg
color: mouseArea.containsMouse ? Theme.ccPillInactiveHoverBg : _tileBg
@@ -50,7 +45,7 @@ Rectangle {
Rectangle {
anchors.fill: parent
radius: parent.radius
- color: hoverTint(root.color)
+ color: Theme.hoverTint(root.color)
opacity: mouseArea.pressed ? 0.3 : (mouseArea.containsMouse ? 0.2 : 0.0)
visible: opacity > 0
antialiasing: true
diff --git a/quickshell/Modules/ControlCenter/Widgets/SmallToggleButton.qml b/quickshell/Modules/ControlCenter/Widgets/SmallToggleButton.qml
index 7ea5a3bb9..d3c156249 100644
--- a/quickshell/Modules/ControlCenter/Widgets/SmallToggleButton.qml
+++ b/quickshell/Modules/ControlCenter/Widgets/SmallToggleButton.qml
@@ -20,11 +20,6 @@ Rectangle {
return isActive ? Theme.cornerRadius : Theme.cornerRadius + 4;
}
- function hoverTint(base) {
- const factor = 1.2;
- return Theme.isLightMode ? Qt.darker(base, factor) : Qt.lighter(base, factor);
- }
-
readonly property color _tileBgActive: Theme.ccTileActiveBg
readonly property color _tileBgInactive: Theme.ccPillInactiveBg
readonly property color _tileRingActive: Theme.ccTileRing
@@ -45,7 +40,7 @@ Rectangle {
Rectangle {
anchors.fill: parent
radius: parent.radius
- color: hoverTint(root.color)
+ color: Theme.hoverTint(root.color)
opacity: mouseArea.pressed ? 0.3 : (mouseArea.containsMouse ? 0.2 : 0.0)
visible: opacity > 0
antialiasing: true
diff --git a/quickshell/Modules/ControlCenter/Widgets/ToggleButton.qml b/quickshell/Modules/ControlCenter/Widgets/ToggleButton.qml
index 6042b0cb2..11cfb3791 100644
--- a/quickshell/Modules/ControlCenter/Widgets/ToggleButton.qml
+++ b/quickshell/Modules/ControlCenter/Widgets/ToggleButton.qml
@@ -39,17 +39,12 @@ Rectangle {
border.width: isActive ? 1 : Theme.layerOutlineWidth
opacity: enabled ? 1.0 : 0.6
- function hoverTint(base) {
- const factor = 1.2;
- return Theme.isLightMode ? Qt.darker(base, factor) : Qt.lighter(base, factor);
- }
-
readonly property color _containerBg: Theme.ccPillInactiveBg
Rectangle {
anchors.fill: parent
radius: Theme.cornerRadius
- color: mouseArea.containsMouse ? hoverTint(_containerBg) : Theme.withAlpha(_containerBg, 0)
+ color: mouseArea.containsMouse ? Theme.hoverTint(_containerBg) : Theme.withAlpha(_containerBg, 0)
opacity: mouseArea.containsMouse ? 0.08 : 0.0
Behavior on opacity {
diff --git a/quickshell/Modules/DankBar/DankBarContent.qml b/quickshell/Modules/DankBar/DankBarContent.qml
index c96423590..7eab90e20 100644
--- a/quickshell/Modules/DankBar/DankBarContent.qml
+++ b/quickshell/Modules/DankBar/DankBarContent.qml
@@ -274,22 +274,6 @@ Item {
return ws.num !== -1 ? ws.num : ws.name;
}
- function escapeSwayWorkspaceName(name) {
- return String(name ?? "").replace(/\\/g, "\\\\").replace(/"/g, "\\\"");
- }
-
- function dispatchSwayWorkspace(ws) {
- if (!ws)
- return;
- try {
- if (ws.num !== undefined && ws.num !== -1) {
- I3.dispatch(`workspace number ${ws.num}`);
- } else if (ws.name) {
- I3.dispatch(`workspace "${escapeSwayWorkspaceName(ws.name)}"`);
- }
- } catch (_) {}
- }
-
function switchWorkspace(direction) {
const realWorkspaces = getRealWorkspaces();
if (realWorkspaces.length < 2) {
@@ -334,7 +318,7 @@ Item {
const nextIndex = direction > 0 ? Math.min(validIndex + 1, realWorkspaces.length - 1) : Math.max(validIndex - 1, 0);
if (nextIndex !== validIndex) {
- dispatchSwayWorkspace(realWorkspaces[nextIndex]);
+ CompositorService.dispatchSwayWorkspace(realWorkspaces[nextIndex]);
}
}
}
diff --git a/quickshell/Modules/DankBar/Popouts/VpnPopout.qml b/quickshell/Modules/DankBar/Popouts/VpnPopout.qml
index 064f5782f..1d363fd2f 100644
--- a/quickshell/Modules/DankBar/Popouts/VpnPopout.qml
+++ b/quickshell/Modules/DankBar/Popouts/VpnPopout.qml
@@ -3,6 +3,7 @@ import QtQuick.Layouts
import qs.Common
import qs.Services
import qs.Widgets
+import qs.Modules.ControlCenter.Details
DankPopout {
id: root
diff --git a/quickshell/Modules/DankBar/Widgets/NetworkMonitor.qml b/quickshell/Modules/DankBar/Widgets/NetworkMonitor.qml
index 69ab31f46..5d60fc6f1 100644
--- a/quickshell/Modules/DankBar/Widgets/NetworkMonitor.qml
+++ b/quickshell/Modules/DankBar/Widgets/NetworkMonitor.qml
@@ -3,22 +3,11 @@ import qs.Common
import qs.Modules.Plugins
import qs.Services
import qs.Widgets
+import "../../../Common/Format.js" as Format
BasePill {
id: root
- function formatNetworkSpeed(bytesPerSec) {
- if (bytesPerSec < 1024) {
- return bytesPerSec.toFixed(0) + " B/s";
- } else if (bytesPerSec < 1024 * 1024) {
- return (bytesPerSec / 1024).toFixed(1) + " KB/s";
- } else if (bytesPerSec < 1024 * 1024 * 1024) {
- return (bytesPerSec / (1024 * 1024)).toFixed(1) + " MB/s";
- } else {
- return (bytesPerSec / (1024 * 1024 * 1024)).toFixed(1) + " GB/s";
- }
- }
-
Component.onCompleted: {
DgopService.addRef(["network"]);
}
@@ -97,7 +86,7 @@ BasePill {
}
StyledText {
- text: DgopService.networkRxRate > 0 ? root.formatNetworkSpeed(DgopService.networkRxRate) : "0 B/s"
+ text: DgopService.networkRxRate > 0 ? Format.formatRate(DgopService.networkRxRate, 1) : "0 B/s"
font.pixelSize: Theme.barTextSize(root.barThickness, root.barConfig?.fontScale, root.barConfig?.maximizeWidgetText)
color: Theme.widgetTextColor
anchors.verticalCenter: parent.verticalCenter
@@ -126,7 +115,7 @@ BasePill {
}
StyledText {
- text: DgopService.networkTxRate > 0 ? root.formatNetworkSpeed(DgopService.networkTxRate) : "0 B/s"
+ text: DgopService.networkTxRate > 0 ? Format.formatRate(DgopService.networkTxRate, 1) : "0 B/s"
font.pixelSize: Theme.barTextSize(root.barThickness, root.barConfig?.fontScale, root.barConfig?.maximizeWidgetText)
color: Theme.widgetTextColor
anchors.verticalCenter: parent.verticalCenter
diff --git a/quickshell/Modules/DankBar/Widgets/WorkspaceSwitcher.qml b/quickshell/Modules/DankBar/Widgets/WorkspaceSwitcher.qml
index e94a4c49a..79533929f 100644
--- a/quickshell/Modules/DankBar/Widgets/WorkspaceSwitcher.qml
+++ b/quickshell/Modules/DankBar/Widgets/WorkspaceSwitcher.qml
@@ -228,22 +228,6 @@ Item {
return ws.num !== -1 ? ws.num : ws.name;
}
- function escapeSwayWorkspaceName(name) {
- return String(name ?? "").replace(/\\/g, "\\\\").replace(/"/g, "\\\"");
- }
-
- function dispatchSwayWorkspace(ws) {
- if (!ws)
- return;
- try {
- if (ws.num !== undefined && ws.num !== -1) {
- I3.dispatch(`workspace number ${ws.num}`);
- } else if (ws.name) {
- I3.dispatch(`workspace "${escapeSwayWorkspaceName(ws.name)}"`);
- }
- } catch (_) {}
- }
-
function getSwayActiveWorkspace() {
if (!root.screenName || SettingsData.workspaceFollowFocus) {
const focusedWs = I3.workspaces?.values?.find(ws => ws.focused === true);
@@ -711,7 +695,7 @@ Item {
case "sway":
case "scroll":
case "miracle":
- dispatchSwayWorkspace(data);
+ CompositorService.dispatchSwayWorkspace(data);
break;
}
}
@@ -818,7 +802,7 @@ Item {
return;
}
- dispatchSwayWorkspace(realWorkspaces[nextIndex]);
+ CompositorService.dispatchSwayWorkspace(realWorkspaces[nextIndex]);
}
}
@@ -1486,7 +1470,7 @@ Item {
} else if (root.isMango && modelData?.tag !== undefined) {
MangoService.switchToTag(root.screenName, modelData.tag);
} else if ((CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle) && modelData?.num !== undefined) {
- root.dispatchSwayWorkspace(modelData);
+ CompositorService.dispatchSwayWorkspace(modelData);
}
} else if (mouse.button === Qt.RightButton) {
if (CompositorService.isNiri) {
diff --git a/quickshell/Modules/DankDash/MediaDropdownOverlay.qml b/quickshell/Modules/DankDash/MediaDropdownOverlay.qml
index 0916d4e7d..34ae3ae62 100644
--- a/quickshell/Modules/DankDash/MediaDropdownOverlay.qml
+++ b/quickshell/Modules/DankDash/MediaDropdownOverlay.qml
@@ -415,11 +415,24 @@ Item {
}
}
onWheel: wheelEvent => {
- if (SettingsData.audioDeviceScrollVolumeEnabled && wheelEvent.x >= deviceMouseArea.width / 2) {
- AudioService.handleNodeVolumeWheel(modelData, wheelEvent);
- } else {
+ if (!SettingsData.audioDeviceScrollVolumeEnabled || wheelEvent.x < deviceMouseArea.width / 2) {
wheelEvent.accepted = false;
+ return;
}
+ if (!modelData?.audio)
+ return;
+ SessionData.suppressOSDTemporarily();
+ const delta = wheelEvent.angleDelta.y;
+ if (delta === 0)
+ return;
+ const current = Math.round(modelData.audio.volume * 100);
+ const maxVol = AudioService.getMaxVolumePercent(modelData);
+ const newVolume = delta > 0 ? Math.min(maxVol, current + AudioService.wheelVolumeStep) : Math.max(0, current - AudioService.wheelVolumeStep);
+ modelData.audio.muted = false;
+ modelData.audio.volume = newVolume / 100;
+ if (modelData === AudioService.sink)
+ AudioService.playVolumeChangeSoundIfEnabled();
+ wheelEvent.accepted = true;
}
onClicked: mouse => {
if (mouse.button === Qt.RightButton) {
diff --git a/quickshell/Modules/DankDash/MediaPlayerTab.qml b/quickshell/Modules/DankDash/MediaPlayerTab.qml
index 167e6a210..61411403f 100644
--- a/quickshell/Modules/DankDash/MediaPlayerTab.qml
+++ b/quickshell/Modules/DankDash/MediaPlayerTab.qml
@@ -451,6 +451,9 @@ Item {
height: width
anchors.centerIn: parent
activePlayer: root.activePlayer
+ artUrl: TrackArtService.resolvedArtUrl
+ accentColor: MediaAccentService.accent
+ cavaService: CavaService
}
}
@@ -522,6 +525,10 @@ Item {
height: 20
anchors.horizontalCenter: parent.horizontalCenter
activePlayer: root.activePlayer
+ stableLength: MprisController.activePlayerStableLength
+ accentColor: MediaAccentService.accent
+ accentTrackColor: MediaAccentService.accentTrack
+ accentSubtleColor: MediaAccentService.accentSubtle
isSeeking: root.isSeeking
onIsSeekingChanged: root.isSeeking = isSeeking
}
diff --git a/quickshell/Modules/DankDash/Overview/MediaOverviewCard.qml b/quickshell/Modules/DankDash/Overview/MediaOverviewCard.qml
index 2d48e2fe7..9b68a7119 100644
--- a/quickshell/Modules/DankDash/Overview/MediaOverviewCard.qml
+++ b/quickshell/Modules/DankDash/Overview/MediaOverviewCard.qml
@@ -77,6 +77,9 @@ Card {
height: 80
anchors.centerIn: parent
activePlayer: root.activePlayer
+ artUrl: TrackArtService.resolvedArtUrl
+ accentColor: MediaAccentService.accent
+ cavaService: CavaService
albumSize: 76
animationScale: 1.05
}
@@ -114,6 +117,10 @@ Card {
height: 20
x: -2
activePlayer: root.activePlayer
+ stableLength: MprisController.activePlayerStableLength
+ accentColor: MediaAccentService.accent
+ accentTrackColor: MediaAccentService.accentTrack
+ accentSubtleColor: MediaAccentService.accentSubtle
isSeeking: root.isSeeking
onIsSeekingChanged: root.isSeeking = isSeeking
}
diff --git a/quickshell/Modules/Dock/DockBody.qml b/quickshell/Modules/Dock/DockBody.qml
index c509b5f55..ab8803d90 100644
--- a/quickshell/Modules/Dock/DockBody.qml
+++ b/quickshell/Modules/Dock/DockBody.qml
@@ -114,10 +114,6 @@ Item {
readonly property real positionSpacing: barSpacing + effectiveDockBottomGap + effectiveDockMargin
readonly property real joinedEdgeMargin: dockGeometry.joinedEdgeMargin
readonly property real _dpr: (dock.screen && dock.screen.devicePixelRatio) ? dock.screen.devicePixelRatio : 1
- function px(v) {
- return Math.round(v * _dpr) / _dpr;
- }
-
DockGeometry {
id: dockGeometry
@@ -397,8 +393,8 @@ Item {
property real animationHeadroom: Math.ceil(SettingsData.dockIconSize * 0.35)
- readonly property real surfaceImplicitWidth: isVertical ? (px(dockGeometry.surfaceThickness + SettingsData.dockIconSize * 0.3) + animationHeadroom) : 0
- readonly property real surfaceImplicitHeight: !isVertical ? (px(dockGeometry.surfaceThickness + SettingsData.dockIconSize * 0.3) + animationHeadroom) : 0
+ readonly property real surfaceImplicitWidth: isVertical ? (Theme.px(dockGeometry.surfaceThickness + SettingsData.dockIconSize * 0.3, _dpr) + animationHeadroom) : 0
+ readonly property real surfaceImplicitHeight: !isVertical ? (Theme.px(dockGeometry.surfaceThickness + SettingsData.dockIconSize * 0.3, _dpr) + animationHeadroom) : 0
readonly property real blurX: dockBackground.x + dockContainer.x + dockMouseArea.x + dockCore.x + dockSlide.x
readonly property real blurY: dockBackground.y + dockContainer.y + dockMouseArea.y + dockCore.y + dockSlide.y
@@ -585,11 +581,11 @@ Item {
// Keep the taller hit area regardless of the reveal state to prevent shrinking loop
return Math.min(Math.max(dockBackground.height + 64, 200), maxDockHeight);
}
- return dock.reveal ? px(dockGeometry.motionThickness) : 1;
+ return dock.reveal ? Theme.px(dockGeometry.motionThickness, _dpr) : 1;
}
width: {
if (dock.isVertical) {
- return dock.reveal ? px(dockGeometry.motionThickness) : 1;
+ return dock.reveal ? Theme.px(dockGeometry.motionThickness, _dpr) : 1;
}
// Keep the wider hit area regardless of the reveal state to prevent shrinking loop
return Math.min(dockBackground.width + 8 + dock.borderThickness, maxDockWidth);
diff --git a/quickshell/Modules/Dock/DockGeometry.qml b/quickshell/Modules/Dock/DockGeometry.qml
index 611739b7f..b4a8c9342 100644
--- a/quickshell/Modules/Dock/DockGeometry.qml
+++ b/quickshell/Modules/Dock/DockGeometry.qml
@@ -19,10 +19,6 @@ QtObject {
property real barSpacing: 0
property real dpr: 1
- function px(value) {
- return Math.round(value * dpr) / dpr;
- }
-
readonly property bool frameExclusionActive: CompositorService.frameWindowVisibleForScreen(screen)
readonly property bool usesConnectedFrameChrome: CompositorService.usesConnectedFrameChromeForScreen(screen)
readonly property bool connectedBarActiveOnEdge: usesConnectedFrameChrome && !!screen && SettingsData.getActiveBarEdgesForScreen(screen).includes(edge)
@@ -56,6 +52,6 @@ QtObject {
// Frame/bar edge exclusions already reserve the edge itself, so the dock
// reservation covers only the dock body and user offset beyond that edge.
- readonly property real reserveZone: px(bodyThickness + reserveOffset + effectiveMargin)
+ readonly property real reserveZone: Theme.px(bodyThickness + reserveOffset + effectiveMargin, dpr)
readonly property bool shouldReserveSpace: dockVisible && !autoHide && barSpacing <= 0
}
diff --git a/quickshell/Modules/Notifications/Center/DndDurationMenu.qml b/quickshell/Modules/Notifications/Center/DndDurationMenu.qml
index df8478de2..23d852348 100644
--- a/quickshell/Modules/Notifications/Center/DndDurationMenu.qml
+++ b/quickshell/Modules/Notifications/Center/DndDurationMenu.qml
@@ -2,6 +2,7 @@ import QtQuick
import qs.Common
import qs.Services
import qs.Widgets
+import "../../../Common/Format.js" as Format
Rectangle {
id: root
@@ -22,36 +23,8 @@ Rectangle {
onTriggered: root.nowMs = Date.now()
}
- function _pad2(n) {
- return n < 10 ? "0" + n : "" + n;
- }
-
function formatRemaining(ms) {
- if (ms <= 0)
- return I18n.tr("Off");
- const totalMinutes = Math.ceil(ms / 60000);
- if (totalMinutes < 60)
- return I18n.tr("%1 min left").arg(totalMinutes);
- const hours = Math.floor(totalMinutes / 60);
- const mins = totalMinutes - hours * 60;
- if (mins === 0)
- return I18n.tr("%1 h left").arg(hours);
- return I18n.tr("%1 h %2 m left").arg(hours).arg(mins);
- }
-
- function formatUntilTimestamp(ts) {
- if (!ts)
- return "";
- const d = new Date(ts);
- const hours = d.getHours();
- const minutes = d.getMinutes();
- const use24h = (typeof SettingsData !== "undefined") ? SettingsData.use24HourClock : true;
- if (use24h) {
- return _pad2(hours) + ":" + _pad2(minutes);
- }
- const suffix = hours >= 12 ? "PM" : "AM";
- const h12 = ((hours + 11) % 12) + 1;
- return h12 + ":" + _pad2(minutes) + " " + suffix;
+ return Format.formatRemaining(ms, I18n.tr("Off"), I18n.tr("%1 min left"), I18n.tr("%1 h left"), I18n.tr("%1 h %2 m left"));
}
function minutesUntilTomorrowMorning() {
@@ -149,7 +122,7 @@ Rectangle {
visible: root.currentlyActive
text: {
if (SessionData.doNotDisturbUntil > 0) {
- return root.formatRemaining(root.currentRemainingMs) + " · " + I18n.tr("until %1").arg(root.formatUntilTimestamp(SessionData.doNotDisturbUntil));
+ return root.formatRemaining(root.currentRemainingMs) + " · " + I18n.tr("until %1").arg(Format.formatUntil(SessionData.doNotDisturbUntil, SettingsData.use24HourClock));
}
return I18n.tr("On indefinitely");
}
diff --git a/quickshell/Modules/Notifications/Center/HistoryNotificationCard.qml b/quickshell/Modules/Notifications/Center/HistoryNotificationCard.qml
index 7e51e945a..29ac502c7 100644
--- a/quickshell/Modules/Notifications/Center/HistoryNotificationCard.qml
+++ b/quickshell/Modules/Notifications/Center/HistoryNotificationCard.qml
@@ -27,6 +27,36 @@ Rectangle {
readonly property real collapsedContentHeight: iconSize + cardPadding
readonly property real baseCardHeight: cardPadding * 2 + collapsedContentHeight
+ function formatHistoryTime(timestamp) {
+ NotificationService.timeUpdateTick;
+ NotificationService.clockFormatChanged;
+ const now = new Date();
+ const date = new Date(timestamp);
+ const diff = now.getTime() - timestamp;
+ const minutes = Math.floor(diff / 60000);
+ const hours = Math.floor(minutes / 60);
+ if (hours < 1) {
+ if (minutes < 1)
+ return I18n.tr("now");
+ return I18n.tr("%1m ago").arg(minutes);
+ }
+ const nowDate = new Date(now.getFullYear(), now.getMonth(), now.getDate());
+ const itemDate = new Date(date.getFullYear(), date.getMonth(), date.getDate());
+ const daysDiff = Math.floor((nowDate - itemDate) / (1000 * 60 * 60 * 24));
+ const timeStr = SettingsData.use24HourClock ? date.toLocaleTimeString(Qt.locale(), "HH:mm") : date.toLocaleTimeString(Qt.locale(), "h:mm AP");
+ if (daysDiff === 0)
+ return timeStr;
+ try {
+ const localeName = (typeof I18n !== "undefined" && I18n.locale) ? I18n.locale().name : "en-US";
+ const weekday = date.toLocaleDateString(localeName, {
+ weekday: "long"
+ });
+ return weekday + ", " + timeStr;
+ } catch (e) {
+ return timeStr;
+ }
+ }
+
width: parent ? parent.width : 400
height: baseCardHeight + contentItem.extraHeight
radius: Theme.cornerRadius
@@ -219,7 +249,7 @@ Rectangle {
}
StyledText {
id: historyTimeText
- text: NotificationService.formatHistoryTime(historyItem.timestamp)
+ text: root.formatHistoryTime(historyItem.timestamp)
color: Theme.surfaceTextMedium
font.pixelSize: Theme.fontSizeSmall
font.weight: Font.Normal
diff --git a/quickshell/Modules/Plugins/ColorSetting.qml b/quickshell/Modules/Plugins/ColorSetting.qml
index 5ef31905f..57780541e 100644
--- a/quickshell/Modules/Plugins/ColorSetting.qml
+++ b/quickshell/Modules/Plugins/ColorSetting.qml
@@ -2,6 +2,7 @@ import QtQuick
import qs.Common
import qs.Services
import qs.Widgets
+import "../../Common/QmlUtils.js" as QmlUtils
Column {
id: root
@@ -18,7 +19,7 @@ Column {
property bool isInitialized: false
function loadValue() {
- const settings = findSettings();
+ const settings = QmlUtils.findSettings(root.parent);
if (settings && settings.pluginService) {
const loadedValue = settings.loadValue(settingKey, defaultValue);
value = loadedValue;
@@ -33,23 +34,12 @@ Column {
onValueChanged: {
if (!isInitialized)
return;
- const settings = findSettings();
+ const settings = QmlUtils.findSettings(root.parent);
if (settings) {
settings.saveValue(settingKey, value);
}
}
- function findSettings() {
- let item = parent;
- while (item) {
- if (item.saveValue !== undefined && item.loadValue !== undefined) {
- return item;
- }
- item = item.parent;
- }
- return null;
- }
-
StyledText {
text: root.label
font.pixelSize: Theme.fontSizeMedium
diff --git a/quickshell/Modules/Plugins/ListSetting.qml b/quickshell/Modules/Plugins/ListSetting.qml
index c90a9f18f..e8213750e 100644
--- a/quickshell/Modules/Plugins/ListSetting.qml
+++ b/quickshell/Modules/Plugins/ListSetting.qml
@@ -1,6 +1,7 @@
import QtQuick
import qs.Common
import qs.Widgets
+import "../../Common/QmlUtils.js" as QmlUtils
Column {
id: root
@@ -16,30 +17,19 @@ Column {
spacing: Theme.spacingM
Component.onCompleted: {
- const settings = findSettings();
+ const settings = QmlUtils.findSettings(root.parent);
if (settings) {
items = settings.loadValue(settingKey, defaultValue);
}
}
onItemsChanged: {
- const settings = findSettings();
+ const settings = QmlUtils.findSettings(root.parent);
if (settings) {
settings.saveValue(settingKey, items);
}
}
- function findSettings() {
- let item = parent;
- while (item) {
- if (item.saveValue !== undefined && item.loadValue !== undefined) {
- return item;
- }
- item = item.parent;
- }
- return null;
- }
-
function addItem(item) {
items = items.concat([item]);
}
diff --git a/quickshell/Modules/Plugins/ListSettingWithInput.qml b/quickshell/Modules/Plugins/ListSettingWithInput.qml
index 941c6995f..ebe808991 100644
--- a/quickshell/Modules/Plugins/ListSettingWithInput.qml
+++ b/quickshell/Modules/Plugins/ListSettingWithInput.qml
@@ -1,6 +1,7 @@
import QtQuick
import qs.Common
import qs.Widgets
+import "../../Common/QmlUtils.js" as QmlUtils
Column {
id: root
@@ -22,7 +23,7 @@ Column {
}
function loadValue() {
- const settings = findSettings();
+ const settings = QmlUtils.findSettings(root.parent);
if (settings) {
isLoading = true;
items = settings.loadValue(settingKey, defaultValue);
@@ -34,23 +35,12 @@ Column {
if (isLoading) {
return;
}
- const settings = findSettings();
+ const settings = QmlUtils.findSettings(root.parent);
if (settings) {
settings.saveValue(settingKey, items);
}
}
- function findSettings() {
- let item = parent;
- while (item) {
- if (item.saveValue !== undefined && item.loadValue !== undefined) {
- return item;
- }
- item = item.parent;
- }
- return null;
- }
-
function addItem(item) {
items = items.concat([item]);
}
diff --git a/quickshell/Modules/Plugins/PluginControlCenterWrapper.qml b/quickshell/Modules/Plugins/PluginControlCenterWrapper.qml
deleted file mode 100644
index b5ccd4fb9..000000000
--- a/quickshell/Modules/Plugins/PluginControlCenterWrapper.qml
+++ /dev/null
@@ -1,43 +0,0 @@
-import QtQuick
-import qs.Common
-import qs.Services
-import qs.Widgets
-
-Item {
- id: root
-
- property string pluginId: ""
- property var pluginInstance: null
- property bool isCompoundPill: false
- property bool isSmallToggle: false
-
- readonly property bool hasDetail: pluginInstance?.ccDetailContent !== null
- readonly property string iconName: pluginInstance?.ccWidgetIcon || "extension"
- readonly property string primaryText: pluginInstance?.ccWidgetPrimaryText || "Plugin"
- readonly property string secondaryText: pluginInstance?.ccWidgetSecondaryText || ""
- readonly property bool isActive: pluginInstance?.ccWidgetIsActive || false
- readonly property Component detailContent: pluginInstance?.ccDetailContent || null
- readonly property real detailHeight: pluginInstance?.ccDetailHeight || 250
-
- signal toggled
- signal expanded
-
- Component.onCompleted: {
- if (pluginInstance) {
- pluginInstance.ccWidgetToggled.connect(toggled);
- pluginInstance.ccWidgetExpanded.connect(expanded);
- }
- }
-
- function invokeToggle() {
- if (pluginInstance) {
- pluginInstance.ccWidgetToggled();
- }
- }
-
- function invokeExpand() {
- if (pluginInstance) {
- pluginInstance.ccWidgetExpanded();
- }
- }
-}
diff --git a/quickshell/Modules/Plugins/SelectionSetting.qml b/quickshell/Modules/Plugins/SelectionSetting.qml
index 0f8fac08e..7f9d6b8d6 100644
--- a/quickshell/Modules/Plugins/SelectionSetting.qml
+++ b/quickshell/Modules/Plugins/SelectionSetting.qml
@@ -1,6 +1,7 @@
import QtQuick
import qs.Common
import qs.Widgets
+import "../../Common/QmlUtils.js" as QmlUtils
Column {
id: root
@@ -16,68 +17,57 @@ Column {
spacing: Theme.spacingS
function loadValue() {
- const settings = findSettings()
+ const settings = QmlUtils.findSettings(root.parent);
if (settings && settings.pluginService) {
- value = settings.loadValue(settingKey, defaultValue)
+ value = settings.loadValue(settingKey, defaultValue);
}
}
Component.onCompleted: {
- loadValue()
+ loadValue();
}
readonly property var optionLabels: {
- const labels = []
+ const labels = [];
for (let i = 0; i < options.length; i++) {
- labels.push(options[i].label || options[i])
+ labels.push(options[i].label || options[i]);
}
- return labels
+ return labels;
}
readonly property var valueToLabel: {
- const map = {}
+ const map = {};
for (let i = 0; i < options.length; i++) {
- const opt = options[i]
+ const opt = options[i];
if (typeof opt === 'object') {
- map[opt.value] = opt.label
+ map[opt.value] = opt.label;
} else {
- map[opt] = opt
+ map[opt] = opt;
}
}
- return map
+ return map;
}
readonly property var labelToValue: {
- const map = {}
+ const map = {};
for (let i = 0; i < options.length; i++) {
- const opt = options[i]
+ const opt = options[i];
if (typeof opt === 'object') {
- map[opt.label] = opt.value
+ map[opt.label] = opt.value;
} else {
- map[opt] = opt
+ map[opt] = opt;
}
}
- return map
+ return map;
}
onValueChanged: {
- const settings = findSettings()
+ const settings = QmlUtils.findSettings(root.parent);
if (settings) {
- settings.saveValue(settingKey, value)
+ settings.saveValue(settingKey, value);
}
}
- function findSettings() {
- let item = parent
- while (item) {
- if (item.saveValue !== undefined && item.loadValue !== undefined) {
- return item
- }
- item = item.parent
- }
- return null
- }
-
DankDropdown {
width: parent.width
text: root.label
@@ -85,7 +75,7 @@ Column {
currentValue: root.valueToLabel[root.value] || root.value
options: root.optionLabels
onValueChanged: newValue => {
- root.value = root.labelToValue[newValue] || newValue
+ root.value = root.labelToValue[newValue] || newValue;
}
}
}
diff --git a/quickshell/Modules/Plugins/SliderSetting.qml b/quickshell/Modules/Plugins/SliderSetting.qml
index a349a475f..c2936993f 100644
--- a/quickshell/Modules/Plugins/SliderSetting.qml
+++ b/quickshell/Modules/Plugins/SliderSetting.qml
@@ -1,6 +1,7 @@
import QtQuick
import qs.Common
import qs.Widgets
+import "../../Common/QmlUtils.js" as QmlUtils
Column {
id: root
@@ -20,7 +21,7 @@ Column {
spacing: Theme.spacingS
function loadValue() {
- const settings = findSettings();
+ const settings = QmlUtils.findSettings(root.parent);
if (settings && settings.pluginService) {
value = settings.loadValue(settingKey, defaultValue);
}
@@ -31,23 +32,12 @@ Column {
}
onValueChanged: {
- const settings = findSettings();
+ const settings = QmlUtils.findSettings(root.parent);
if (settings) {
settings.saveValue(settingKey, value);
}
}
- function findSettings() {
- let item = parent;
- while (item) {
- if (item.saveValue !== undefined && item.loadValue !== undefined) {
- return item;
- }
- item = item.parent;
- }
- return null;
- }
-
StyledText {
text: root.label
font.pixelSize: Theme.fontSizeMedium
diff --git a/quickshell/Modules/Plugins/StringSetting.qml b/quickshell/Modules/Plugins/StringSetting.qml
index 849807b95..9c15b7f44 100644
--- a/quickshell/Modules/Plugins/StringSetting.qml
+++ b/quickshell/Modules/Plugins/StringSetting.qml
@@ -1,6 +1,7 @@
import QtQuick
import qs.Common
import qs.Widgets
+import "../../Common/QmlUtils.js" as QmlUtils
Column {
id: root
@@ -18,7 +19,7 @@ Column {
property bool isInitialized: false
function loadValue() {
- const settings = findSettings();
+ const settings = QmlUtils.findSettings(root.parent);
if (settings && settings.pluginService) {
const loadedValue = settings.loadValue(settingKey, defaultValue);
if (textField.activeFocus && isInitialized)
@@ -39,22 +40,11 @@ Column {
if (textField.text === value)
return;
value = textField.text;
- const settings = findSettings();
+ const settings = QmlUtils.findSettings(root.parent);
if (settings)
settings.saveValue(settingKey, value);
}
- function findSettings() {
- let item = parent;
- while (item) {
- if (item.saveValue !== undefined && item.loadValue !== undefined) {
- return item;
- }
- item = item.parent;
- }
- return null;
- }
-
StyledText {
text: root.label
font.pixelSize: Theme.fontSizeMedium
diff --git a/quickshell/Modules/Plugins/ToggleSetting.qml b/quickshell/Modules/Plugins/ToggleSetting.qml
index 51b9125ad..6d9b61665 100644
--- a/quickshell/Modules/Plugins/ToggleSetting.qml
+++ b/quickshell/Modules/Plugins/ToggleSetting.qml
@@ -1,6 +1,7 @@
import QtQuick
import qs.Common
import qs.Widgets
+import "../../Common/QmlUtils.js" as QmlUtils
Row {
id: root
@@ -17,7 +18,7 @@ Row {
property bool isInitialized: false
function loadValue() {
- const settings = findSettings();
+ const settings = QmlUtils.findSettings(root.parent);
if (settings && settings.pluginService) {
const loadedValue = settings.loadValue(settingKey, defaultValue);
value = loadedValue;
@@ -32,23 +33,12 @@ Row {
onValueChanged: {
if (!isInitialized)
return;
- const settings = findSettings();
+ const settings = QmlUtils.findSettings(root.parent);
if (settings) {
settings.saveValue(settingKey, value);
}
}
- function findSettings() {
- let item = parent;
- while (item) {
- if (item.saveValue !== undefined && item.loadValue !== undefined) {
- return item;
- }
- item = item.parent;
- }
- return null;
- }
-
Column {
width: parent.width - toggle.width - Theme.spacingM
spacing: Theme.spacingXS
diff --git a/quickshell/Modules/ProcessList/DisksView.qml b/quickshell/Modules/ProcessList/DisksView.qml
index fb8b04f07..485121e11 100644
--- a/quickshell/Modules/ProcessList/DisksView.qml
+++ b/quickshell/Modules/ProcessList/DisksView.qml
@@ -3,20 +3,11 @@ import QtQuick.Layouts
import qs.Common
import qs.Services
import qs.Widgets
+import "../../Common/Format.js" as Format
Item {
id: root
- function formatSpeed(bytesPerSec) {
- if (bytesPerSec < 1024)
- return bytesPerSec.toFixed(0) + " B/s";
- if (bytesPerSec < 1024 * 1024)
- return (bytesPerSec / 1024).toFixed(1) + " KB/s";
- if (bytesPerSec < 1024 * 1024 * 1024)
- return (bytesPerSec / (1024 * 1024)).toFixed(1) + " MB/s";
- return (bytesPerSec / (1024 * 1024 * 1024)).toFixed(2) + " GB/s";
- }
-
Component.onCompleted: {
DgopService.addRef(["disk", "diskmounts"]);
}
@@ -76,7 +67,7 @@ Item {
}
StyledText {
- text: root.formatSpeed(DgopService.diskReadRate)
+ text: Format.formatRate(DgopService.diskReadRate)
font.pixelSize: Theme.fontSizeSmall
font.family: SettingsData.monoFontFamily
font.weight: Font.Bold
@@ -94,7 +85,7 @@ Item {
}
StyledText {
- text: root.formatSpeed(DgopService.diskWriteRate)
+ text: Format.formatRate(DgopService.diskWriteRate)
font.pixelSize: Theme.fontSizeSmall
font.family: SettingsData.monoFontFamily
font.weight: Font.Bold
diff --git a/quickshell/Modules/ProcessList/PerformanceView.qml b/quickshell/Modules/ProcessList/PerformanceView.qml
index a847107c7..244474ac8 100644
--- a/quickshell/Modules/ProcessList/PerformanceView.qml
+++ b/quickshell/Modules/ProcessList/PerformanceView.qml
@@ -4,6 +4,7 @@ import Quickshell
import qs.Common
import qs.Services
import qs.Widgets
+import "../../Common/Format.js" as Format
Item {
id: root
@@ -17,31 +18,13 @@ Item {
property var diskReadHistory: []
property var diskWriteHistory: []
- function formatBytes(bytes) {
- if (bytes < 1024)
- return bytes.toFixed(0) + " B/s";
- if (bytes < 1024 * 1024)
- return (bytes / 1024).toFixed(1) + " KB/s";
- if (bytes < 1024 * 1024 * 1024)
- return (bytes / (1024 * 1024)).toFixed(1) + " MB/s";
- return (bytes / (1024 * 1024 * 1024)).toFixed(2) + " GB/s";
- }
-
- function addToHistory(arr, val) {
- const newArr = arr.slice();
- newArr.push(val);
- if (newArr.length > historySize)
- newArr.shift();
- return newArr;
- }
-
function sampleData() {
- cpuHistory = addToHistory(cpuHistory, DgopService.cpuUsage);
- memoryHistory = addToHistory(memoryHistory, DgopService.memoryUsage);
- networkRxHistory = addToHistory(networkRxHistory, DgopService.networkRxRate);
- networkTxHistory = addToHistory(networkTxHistory, DgopService.networkTxRate);
- diskReadHistory = addToHistory(diskReadHistory, DgopService.diskReadRate);
- diskWriteHistory = addToHistory(diskWriteHistory, DgopService.diskWriteRate);
+ cpuHistory = Format.addToHistory(cpuHistory, DgopService.cpuUsage, historySize);
+ memoryHistory = Format.addToHistory(memoryHistory, DgopService.memoryUsage, historySize);
+ networkRxHistory = Format.addToHistory(networkRxHistory, DgopService.networkRxRate, historySize);
+ networkTxHistory = Format.addToHistory(networkTxHistory, DgopService.networkTxRate, historySize);
+ diskReadHistory = Format.addToHistory(diskReadHistory, DgopService.diskReadRate, historySize);
+ diskWriteHistory = Format.addToHistory(diskWriteHistory, DgopService.diskWriteRate, historySize);
}
Component.onCompleted: {
@@ -111,8 +94,8 @@ Item {
Layout.fillHeight: true
title: I18n.tr("Network")
icon: "swap_horiz"
- value: "↓ " + root.formatBytes(DgopService.networkRxRate)
- subtitle: "↑ " + root.formatBytes(DgopService.networkTxRate)
+ value: "↓ " + Format.formatRate(DgopService.networkRxRate)
+ subtitle: "↑ " + Format.formatRate(DgopService.networkTxRate)
accentColor: Theme.info
history: root.networkRxHistory
history2: root.networkTxHistory
@@ -127,8 +110,8 @@ Item {
Layout.fillHeight: true
title: I18n.tr("Disk")
icon: "storage"
- value: "R: " + root.formatBytes(DgopService.diskReadRate)
- subtitle: "W: " + root.formatBytes(DgopService.diskWriteRate)
+ value: "R: " + Format.formatRate(DgopService.diskReadRate)
+ subtitle: "W: " + Format.formatRate(DgopService.diskWriteRate)
accentColor: Theme.warning
history: root.diskReadHistory
history2: root.diskWriteHistory
diff --git a/quickshell/Modules/ProcessList/ProcessesView.qml b/quickshell/Modules/ProcessList/ProcessesView.qml
index 1c9bb703f..efa6015f7 100644
--- a/quickshell/Modules/ProcessList/ProcessesView.qml
+++ b/quickshell/Modules/ProcessList/ProcessesView.qml
@@ -36,6 +36,36 @@ Item {
cachedProcesses = filteredProcesses;
}
+ function getProcessIcon(command) {
+ const cmd = command.toLowerCase();
+ if (cmd.includes("firefox") || cmd.includes("chrome") || cmd.includes("browser") || cmd.includes("chromium"))
+ return "web";
+ if (cmd.includes("code") || cmd.includes("editor") || cmd.includes("vim"))
+ return "code";
+ if (cmd.includes("terminal") || cmd.includes("bash") || cmd.includes("zsh"))
+ return "terminal";
+ if (cmd.includes("music") || cmd.includes("audio") || cmd.includes("spotify"))
+ return "music_note";
+ if (cmd.includes("video") || cmd.includes("vlc") || cmd.includes("mpv"))
+ return "play_circle";
+ if (cmd.includes("systemd") || cmd.includes("elogind") || cmd.includes("kernel") || cmd.includes("kthread") || cmd.includes("kworker"))
+ return "settings";
+ return "memory";
+ }
+
+ function formatCpuUsage(cpu) {
+ return (cpu || 0).toFixed(1) + "%";
+ }
+
+ function formatMemoryUsage(memoryKB) {
+ const mem = memoryKB || 0;
+ if (mem < 1024)
+ return mem.toFixed(0) + " KB";
+ if (mem < 1024 * 1024)
+ return (mem / 1024).toFixed(1) + " MB";
+ return (mem / (1024 * 1024)).toFixed(1) + " GB";
+ }
+
readonly property var filteredProcesses: {
if (!DgopService.allProcesses || DgopService.allProcesses.length === 0)
return [];
@@ -521,7 +551,7 @@ Item {
spacing: Theme.spacingS
DankIcon {
- name: DgopService.getProcessIcon(processItemRoot.processCmd)
+ name: root.getProcessIcon(processItemRoot.processCmd)
size: Theme.iconSize - 4
color: {
if (processItemRoot.processCpu > 80)
@@ -566,7 +596,7 @@ Item {
StyledText {
anchors.centerIn: parent
- text: DgopService.formatCpuUsage(processItemRoot.processCpu)
+ text: root.formatCpuUsage(processItemRoot.processCpu)
font.pixelSize: Theme.fontSizeSmall
font.family: SettingsData.monoFontFamily
font.weight: Font.Bold
@@ -600,7 +630,7 @@ Item {
StyledText {
anchors.centerIn: parent
- text: DgopService.formatMemoryUsage(processItemRoot.processMemKB)
+ text: root.formatMemoryUsage(processItemRoot.processMemKB)
font.pixelSize: Theme.fontSizeSmall
font.family: SettingsData.monoFontFamily
font.weight: Font.Bold
diff --git a/quickshell/Modules/Settings/DisplayConfig/DisplayConfigState.qml b/quickshell/Modules/Settings/DisplayConfig/DisplayConfigState.qml
index b36475211..0c013b2d3 100644
--- a/quickshell/Modules/Settings/DisplayConfig/DisplayConfigState.qml
+++ b/quickshell/Modules/Settings/DisplayConfig/DisplayConfigState.qml
@@ -387,7 +387,6 @@ Singleton {
});
}
- // Extract neutral per-output config from current live state
function extractOutputNeutralConfig(outputName, outputData, niriSettings, hyprlandSettings) {
const modeData = (outputData.modes && outputData.current_mode !== undefined) ? outputData.modes[outputData.current_mode] : null;
const modeStr = modeData ? modeData.width + "x" + modeData.height + "@" + (modeData.refresh_rate / 1000).toFixed(3) : null;
@@ -421,7 +420,6 @@ Singleton {
return cfg;
}
- // Convert monitors.json config entry → internal outputsData map
function profileKeyMatchesOutput(outputId, output, name) {
if (name === outputId || getOutputIdentifier(output, name) === outputId)
return true;
@@ -438,7 +436,6 @@ Singleton {
const cfgOutputs = configEntry.outputs || {};
for (const outputId in cfgOutputs) {
const cfg = cfgOutputs[outputId];
- // Find matching live output to get modes list
let liveOutput = null;
for (const name in outputs) {
if (profileKeyMatchesOutput(outputId, outputs[name], name)) {
@@ -476,7 +473,6 @@ Singleton {
return result;
}
- // Extract niri settings map from a neutral config entry.
function getNiriSettingsFromConfig(configEntry) {
const result = {};
for (const outputId in (configEntry.outputs || {})) {
@@ -490,7 +486,6 @@ Singleton {
return result;
}
- // Extract hyprland settings map from neutral config entry
function getHyprlandSettingsFromConfig(configEntry) {
const result = {};
for (const outputId in (configEntry.outputs || {})) {
@@ -537,7 +532,6 @@ Singleton {
return true;
}
- // Write compositor config from a neutral config entry and optionally reload
function applyConfigEntry(configEntry, configId, profileName, isManual) {
if (CompositorService.isHyprland && readOnly) {
if (isManual) {
@@ -580,8 +574,6 @@ Singleton {
});
}
- // ── Profile management ─────────────────────────────────────────────────
-
function validateProfiles() {
log.info("Validating profiles against current outputs...");
readMonitorsJson(data => {
@@ -2072,7 +2064,6 @@ Singleton {
return pending !== undefined ? pending : originalValue;
}
- // Returns true if the given output can currently be disabled.
// Prevents disabling all outputs and prevents disabling the only output
// in a single-display configuration.
function canDisableOutput() {
diff --git a/quickshell/Modules/Settings/GammaControlTab.qml b/quickshell/Modules/Settings/GammaControlTab.qml
index 1482d5f87..60c4df67a 100644
--- a/quickshell/Modules/Settings/GammaControlTab.qml
+++ b/quickshell/Modules/Settings/GammaControlTab.qml
@@ -3,23 +3,11 @@ import qs.Common
import qs.Services
import qs.Widgets
import qs.Modules.Settings.Widgets
+import "../../Common/Format.js" as Format
Item {
id: root
- function formatGammaTime(isoString) {
- if (!isoString)
- return "";
- try {
- const date = new Date(isoString);
- if (isNaN(date.getTime()))
- return "";
- return date.toLocaleTimeString(Qt.locale(), "HH:mm");
- } catch (e) {
- return "";
- }
- }
-
DankFlickable {
anchors.fill: parent
clip: true
@@ -195,7 +183,7 @@ Item {
}
onTabClicked: index => {
- DisplayService.setNightModeAutomationMode(index === 1 ? "location" : "time");
+ SessionData.setNightModeAutoMode(index === 1 ? "location" : "time");
currentIndex = index;
}
@@ -562,7 +550,7 @@ Item {
}
StyledText {
- text: root.formatGammaTime(DisplayService.gammaSunriseTime)
+ text: Format.formatIsoTime(DisplayService.gammaSunriseTime)
font.pixelSize: Theme.fontSizeLarge
font.weight: Font.Medium
color: Theme.surfaceText
@@ -598,7 +586,7 @@ Item {
}
StyledText {
- text: root.formatGammaTime(DisplayService.gammaSunsetTime)
+ text: Format.formatIsoTime(DisplayService.gammaSunsetTime)
font.pixelSize: Theme.fontSizeLarge
font.weight: Font.Medium
color: Theme.surfaceText
@@ -645,7 +633,7 @@ Item {
}
StyledText {
- text: root.formatGammaTime(DisplayService.gammaNextTransition)
+ text: Format.formatIsoTime(DisplayService.gammaNextTransition)
font.pixelSize: Theme.fontSizeMedium
font.weight: Font.Medium
color: Theme.surfaceText
diff --git a/quickshell/Widgets/KeybindItem.qml b/quickshell/Modules/Settings/KeybindItem.qml
similarity index 99%
rename from quickshell/Widgets/KeybindItem.qml
rename to quickshell/Modules/Settings/KeybindItem.qml
index d0aa6dcac..94147626e 100644
--- a/quickshell/Widgets/KeybindItem.qml
+++ b/quickshell/Modules/Settings/KeybindItem.qml
@@ -6,8 +6,8 @@ import Quickshell.Wayland
import qs.Common
import qs.Services
import qs.Widgets
-import "../Common/KeyUtils.js" as KeyUtils
-import "../Common/KeybindActions.js" as Actions
+import "../../Common/KeyUtils.js" as KeyUtils
+import "../../Common/KeybindActions.js" as Actions
Item {
id: root
diff --git a/quickshell/Modules/Settings/NetworkVpnTab.qml b/quickshell/Modules/Settings/NetworkVpnTab.qml
index c00bf21e9..05e89878c 100644
--- a/quickshell/Modules/Settings/NetworkVpnTab.qml
+++ b/quickshell/Modules/Settings/NetworkVpnTab.qml
@@ -240,7 +240,7 @@ Item {
required property var modelData
required property int index
- readonly property bool isActive: DMSNetworkService.isActiveUuid(modelData.uuid)
+ readonly property bool isActive: DMSNetworkService.isActiveVpnUuid(modelData.uuid)
readonly property bool isTransient: !!modelData.transient
readonly property bool canExpand: modelData.canExpand !== false
readonly property bool canDelete: modelData.canDelete !== false
diff --git a/quickshell/Modules/Settings/NetworkWifiTab.qml b/quickshell/Modules/Settings/NetworkWifiTab.qml
index 6567dc373..bbde650a4 100644
--- a/quickshell/Modules/Settings/NetworkWifiTab.qml
+++ b/quickshell/Modules/Settings/NetworkWifiTab.qml
@@ -9,6 +9,7 @@ import qs.Modules.Settings.Widgets
import qs.Modals.Common
import qs.Services
import qs.Widgets
+import "../../Common/QmlUtils.js" as QmlUtils
Item {
id: networkWifiTab
@@ -46,22 +47,14 @@ Item {
property string expandedSavedWifiSsid: ""
property int maxPinnedWifiNetworks: 3
- function normalizePinList(value) {
- if (Array.isArray(value))
- return value.filter(v => v);
- if (typeof value === "string" && value.length > 0)
- return [value];
- return [];
- }
-
function getPinnedWifiNetworks() {
const pins = CacheData.wifiNetworkPins || {};
- return normalizePinList(pins["preferredWifi"]);
+ return QmlUtils.normalizePinList(pins["preferredWifi"]);
}
function toggleWifiPin(ssid) {
const pins = JSON.parse(JSON.stringify(CacheData.wifiNetworkPins || {}));
- let pinnedList = normalizePinList(pins["preferredWifi"]);
+ let pinnedList = QmlUtils.normalizePinList(pins["preferredWifi"]);
const pinIndex = pinnedList.indexOf(ssid);
if (pinIndex !== -1) {
diff --git a/quickshell/Modules/Settings/NotificationsTab.qml b/quickshell/Modules/Settings/NotificationsTab.qml
index 8c93482fa..46f544fdd 100644
--- a/quickshell/Modules/Settings/NotificationsTab.qml
+++ b/quickshell/Modules/Settings/NotificationsTab.qml
@@ -1,5 +1,6 @@
import QtQuick
import qs.Common
+import qs.Services
import qs.Widgets
import qs.Modules.Settings.Widgets
@@ -215,7 +216,7 @@ Item {
currentValue: (SettingsData.notificationSummaryFontSize || I18n.tr("Unset")).toString()
onValueChanged: value => {
SettingsData.set("notificationSummaryFontSize", Number(value === I18n.tr("Unset") ? 0 : value));
- SettingsData.sendTestNotifications();
+ NotificationService.sendTestNotifications();
}
}
@@ -228,7 +229,7 @@ Item {
currentValue: (SettingsData.notificationBodyFontSize || I18n.tr("Unset")).toString()
onValueChanged: value => {
SettingsData.set("notificationBodyFontSize", Number(value === I18n.tr("Unset") ? 0 : value));
- SettingsData.sendTestNotifications();
+ NotificationService.sendTestNotifications();
}
}
@@ -277,7 +278,7 @@ Item {
SettingsData.set("notificationPopupPosition", SettingsData.Position.Bottom);
break;
}
- SettingsData.sendTestNotifications();
+ NotificationService.sendTestNotifications();
}
}
diff --git a/quickshell/Modules/Settings/ThemeColorsTab.qml b/quickshell/Modules/Settings/ThemeColorsTab.qml
index b49d45fd3..356df8d66 100644
--- a/quickshell/Modules/Settings/ThemeColorsTab.qml
+++ b/quickshell/Modules/Settings/ThemeColorsTab.qml
@@ -8,6 +8,7 @@ import qs.Services
import qs.Widgets
import qs.Modules.Settings.Widgets
import "../../Common/ConfigIncludeResolve.js" as ConfigIncludeResolve
+import "../../Common/Format.js" as Format
Item {
id: themeColorsTab
@@ -215,19 +216,6 @@ Item {
ToastService.showError(I18n.tr("Missing Environment Variables", "qt theme env error title"), I18n.tr("You need to set either:\nQT_QPA_PLATFORMTHEME=gtk3 OR\nQT_QPA_PLATFORMTHEME=qt6ct\nas environment variables, and then restart the shell.\n\nqt6ct requires qt6ct-kde to be installed.", "qt theme env error body"));
}
- function formatThemeAutoTime(isoString) {
- if (!isoString)
- return "";
- try {
- const date = new Date(isoString);
- if (isNaN(date.getTime()))
- return "";
- return date.toLocaleTimeString(Qt.locale(), "HH:mm");
- } catch (e) {
- return "";
- }
- }
-
function refreshMatugenSchemePreviews() {
if (!Theme.matugenAvailable)
return;
@@ -1566,7 +1554,7 @@ Item {
}
StyledText {
- text: themeColorsTab.formatThemeAutoTime(SessionData.themeModeNextTransition)
+ text: Format.formatIsoTime(SessionData.themeModeNextTransition)
font.pixelSize: Theme.fontSizeMedium
font.weight: Font.Medium
color: Theme.surfaceText
diff --git a/quickshell/Modules/Settings/Widgets/SettingsButtonGroupRow.qml b/quickshell/Modules/Settings/Widgets/SettingsButtonGroupRow.qml
index b435bed38..8e128bbe3 100644
--- a/quickshell/Modules/Settings/Widgets/SettingsButtonGroupRow.qml
+++ b/quickshell/Modules/Settings/Widgets/SettingsButtonGroupRow.qml
@@ -4,6 +4,7 @@ import QtQuick
import qs.Common
import qs.Services
import qs.Widgets
+import "../../../Common/QmlUtils.js" as QmlUtils
Item {
id: root
@@ -20,16 +21,6 @@ Item {
readonly property bool isHighlighted: settingKey !== "" && SettingsSearchService.highlightSection === settingKey
- function findParentFlickable() {
- let p = root.parent;
- while (p) {
- if (p.hasOwnProperty("contentY") && p.hasOwnProperty("contentItem"))
- return p;
- p = p.parent;
- }
- return null;
- }
-
Component.onCompleted: {
if (!settingKey)
return;
@@ -37,7 +28,7 @@ Item {
Qt.callLater(() => {
if (!root.parent)
return;
- var flickable = findParentFlickable();
+ var flickable = QmlUtils.findParentFlickable(root.parent);
if (flickable)
SettingsSearchService.registerCard(key, root, flickable);
});
diff --git a/quickshell/Modules/Settings/Widgets/SettingsCard.qml b/quickshell/Modules/Settings/Widgets/SettingsCard.qml
index 6e3d69090..40fbf103e 100644
--- a/quickshell/Modules/Settings/Widgets/SettingsCard.qml
+++ b/quickshell/Modules/Settings/Widgets/SettingsCard.qml
@@ -4,6 +4,7 @@ import QtQuick
import qs.Common
import qs.Services
import qs.Widgets
+import "../../../Common/QmlUtils.js" as QmlUtils
StyledRect {
id: root
@@ -43,17 +44,6 @@ StyledRect {
readonly property bool hasHeader: root.title !== "" || root.iconName !== ""
property bool userToggledCollapse: false
- function findParentFlickable() {
- let p = root.parent;
- while (p) {
- if (p.hasOwnProperty("contentY") && p.hasOwnProperty("contentItem")) {
- return p;
- }
- p = p.parent;
- }
- return null;
- }
-
Component.onCompleted: {
if (!settingKey)
return;
@@ -61,7 +51,7 @@ StyledRect {
Qt.callLater(() => {
if (!root.parent)
return;
- var flickable = findParentFlickable();
+ var flickable = QmlUtils.findParentFlickable(root.parent);
if (flickable)
SettingsSearchService.registerCard(key, root, flickable);
});
diff --git a/quickshell/Modules/Settings/Widgets/SettingsDropdownRow.qml b/quickshell/Modules/Settings/Widgets/SettingsDropdownRow.qml
index 3b622a210..e57cd27ac 100644
--- a/quickshell/Modules/Settings/Widgets/SettingsDropdownRow.qml
+++ b/quickshell/Modules/Settings/Widgets/SettingsDropdownRow.qml
@@ -4,6 +4,7 @@ import QtQuick
import qs.Common
import qs.Services
import qs.Widgets
+import "../../../Common/QmlUtils.js" as QmlUtils
DankDropdown {
id: root
@@ -35,16 +36,6 @@ DankDropdown {
}
}
- function findParentFlickable() {
- let p = root.parent;
- while (p) {
- if (p.hasOwnProperty("contentY") && p.hasOwnProperty("contentItem"))
- return p;
- p = p.parent;
- }
- return null;
- }
-
Component.onCompleted: {
if (!settingKey)
return;
@@ -52,7 +43,7 @@ DankDropdown {
Qt.callLater(() => {
if (!root.parent)
return;
- var flickable = findParentFlickable();
+ var flickable = QmlUtils.findParentFlickable(root.parent);
if (flickable)
SettingsSearchService.registerCard(key, root, flickable);
});
diff --git a/quickshell/Modules/Settings/Widgets/SettingsSliderCard.qml b/quickshell/Modules/Settings/Widgets/SettingsSliderCard.qml
index 3ae7800c1..e1a08c4b8 100644
--- a/quickshell/Modules/Settings/Widgets/SettingsSliderCard.qml
+++ b/quickshell/Modules/Settings/Widgets/SettingsSliderCard.qml
@@ -4,6 +4,7 @@ import QtQuick
import qs.Common
import qs.Services
import qs.Widgets
+import "../../../Common/QmlUtils.js" as QmlUtils
StyledRect {
id: root
@@ -36,16 +37,6 @@ StyledRect {
radius: Theme.cornerRadius
color: Theme.surfaceContainerHigh
- function findParentFlickable() {
- let p = root.parent;
- while (p) {
- if (p.hasOwnProperty("contentY") && p.hasOwnProperty("contentItem"))
- return p;
- p = p.parent;
- }
- return null;
- }
-
Component.onCompleted: {
if (!settingKey)
return;
@@ -53,7 +44,7 @@ StyledRect {
Qt.callLater(() => {
if (!root.parent)
return;
- const flickable = findParentFlickable();
+ const flickable = QmlUtils.findParentFlickable(root.parent);
if (flickable)
SettingsSearchService.registerCard(key, root, flickable);
});
diff --git a/quickshell/Modules/Settings/Widgets/SettingsSliderRow.qml b/quickshell/Modules/Settings/Widgets/SettingsSliderRow.qml
index fb09f43e4..efac69ad6 100644
--- a/quickshell/Modules/Settings/Widgets/SettingsSliderRow.qml
+++ b/quickshell/Modules/Settings/Widgets/SettingsSliderRow.qml
@@ -4,6 +4,7 @@ import QtQuick
import qs.Common
import qs.Services
import qs.Widgets
+import "../../../Common/QmlUtils.js" as QmlUtils
Item {
id: root
@@ -20,16 +21,6 @@ Item {
readonly property bool isHighlighted: settingKey !== "" && SettingsSearchService.highlightSection === settingKey
- function findParentFlickable() {
- let p = root.parent;
- while (p) {
- if (p.hasOwnProperty("contentY") && p.hasOwnProperty("contentItem"))
- return p;
- p = p.parent;
- }
- return null;
- }
-
Component.onCompleted: {
if (!settingKey)
return;
@@ -37,7 +28,7 @@ Item {
Qt.callLater(() => {
if (!root.parent)
return;
- var flickable = findParentFlickable();
+ var flickable = QmlUtils.findParentFlickable(root.parent);
if (flickable)
SettingsSearchService.registerCard(key, root, flickable);
});
diff --git a/quickshell/Modules/Settings/Widgets/SettingsToggleCard.qml b/quickshell/Modules/Settings/Widgets/SettingsToggleCard.qml
index aa7ed6580..94c55dd8b 100644
--- a/quickshell/Modules/Settings/Widgets/SettingsToggleCard.qml
+++ b/quickshell/Modules/Settings/Widgets/SettingsToggleCard.qml
@@ -4,6 +4,7 @@ import QtQuick
import qs.Common
import qs.Services
import qs.Widgets
+import "../../../Common/QmlUtils.js" as QmlUtils
StyledRect {
id: root
@@ -30,16 +31,6 @@ StyledRect {
radius: Theme.cornerRadius
color: Theme.surfaceContainerHigh
- function findParentFlickable() {
- let p = root.parent;
- while (p) {
- if (p.hasOwnProperty("contentY") && p.hasOwnProperty("contentItem"))
- return p;
- p = p.parent;
- }
- return null;
- }
-
Component.onCompleted: {
if (!settingKey)
return;
@@ -47,7 +38,7 @@ StyledRect {
Qt.callLater(() => {
if (!root.parent)
return;
- const flickable = findParentFlickable();
+ const flickable = QmlUtils.findParentFlickable(root.parent);
if (flickable)
SettingsSearchService.registerCard(key, root, flickable);
});
diff --git a/quickshell/Modules/Settings/Widgets/SettingsToggleRow.qml b/quickshell/Modules/Settings/Widgets/SettingsToggleRow.qml
index aa7969b74..a54589496 100644
--- a/quickshell/Modules/Settings/Widgets/SettingsToggleRow.qml
+++ b/quickshell/Modules/Settings/Widgets/SettingsToggleRow.qml
@@ -4,6 +4,7 @@ import QtQuick
import qs.Common
import qs.Services
import qs.Widgets
+import "../../../Common/QmlUtils.js" as QmlUtils
DankToggle {
id: root
@@ -19,16 +20,6 @@ DankToggle {
width: parent?.width ?? 0
- function findParentFlickable() {
- let p = root.parent;
- while (p) {
- if (p.hasOwnProperty("contentY") && p.hasOwnProperty("contentItem"))
- return p;
- p = p.parent;
- }
- return null;
- }
-
Component.onCompleted: {
if (!settingKey)
return;
@@ -36,7 +27,7 @@ DankToggle {
Qt.callLater(() => {
if (!root.parent)
return;
- var flickable = findParentFlickable();
+ var flickable = QmlUtils.findParentFlickable(root.parent);
if (flickable)
SettingsSearchService.registerCard(key, root, flickable);
});
diff --git a/quickshell/Modules/Settings/WorkspacesTab.qml b/quickshell/Modules/Settings/WorkspacesTab.qml
index 2b0143857..94277838a 100644
--- a/quickshell/Modules/Settings/WorkspacesTab.qml
+++ b/quickshell/Modules/Settings/WorkspacesTab.qml
@@ -207,7 +207,7 @@ Item {
iconName: "label"
title: I18n.tr("Named Workspace Icons")
settingKey: "workspaceIcons"
- visible: SettingsData.hasNamedWorkspaces()
+ visible: NiriService.hasNamedWorkspaces()
StyledText {
width: parent.width
@@ -218,7 +218,7 @@ Item {
}
Repeater {
- model: SettingsData.getNamedWorkspaces()
+ model: NiriService.getNamedWorkspaces()
Rectangle {
width: parent.width
diff --git a/quickshell/Modules/WallpaperBackground.qml b/quickshell/Modules/WallpaperBackground.qml
index 151369c7a..a8ee0fe28 100644
--- a/quickshell/Modules/WallpaperBackground.qml
+++ b/quickshell/Modules/WallpaperBackground.qml
@@ -114,7 +114,7 @@ Variants {
}
property real transitionProgress: 0
- property real shaderFillMode: getFillMode(SessionData.getMonitorWallpaperFillMode(modelData.name))
+ property real shaderFillMode: Theme.getFillMode(SessionData.getMonitorWallpaperFillMode(modelData.name))
property vector4d fillColor: Qt.vector4d(0, 0, 0, 1)
property real edgeSmoothness: 0.1
@@ -321,31 +321,6 @@ Variants {
Qt.callLater(() => root.changeWallpaper(pending, true));
}
- function getFillMode(modeName) {
- switch (modeName) {
- case "Scrolling":
- return Image.PreserveAspectCrop;
- case "Stretch":
- return Image.Stretch;
- case "Fit":
- case "PreserveAspectFit":
- return Image.PreserveAspectFit;
- case "Fill":
- case "PreserveAspectCrop":
- return Image.PreserveAspectCrop;
- case "Tile":
- return Image.Tile;
- case "TileVertically":
- return Image.TileVertically;
- case "TileHorizontally":
- return Image.TileHorizontally;
- case "Pad":
- return Image.Pad;
- default:
- return Image.PreserveAspectCrop;
- }
- }
-
function updateWorkspaceData() {
if (!scrollingEnabled)
return;
@@ -740,7 +715,7 @@ Variants {
cache: true
sourceSize: Qt.size(root.textureWidth, root.textureHeight)
- fillMode: root.getFillMode(SessionData.getMonitorWallpaperFillMode(modelData.name))
+ fillMode: Theme.getFillMode(SessionData.getMonitorWallpaperFillMode(modelData.name))
onStatusChanged: {
if (status === Image.Error) {
@@ -772,7 +747,7 @@ Variants {
cache: true
sourceSize: Qt.size(root.textureWidth, root.textureHeight)
- fillMode: root.getFillMode(SessionData.getMonitorWallpaperFillMode(modelData.name))
+ fillMode: Theme.getFillMode(SessionData.getMonitorWallpaperFillMode(modelData.name))
onStatusChanged: {
if (status === Image.Error) {
diff --git a/quickshell/Modules/WorkspaceOverlays/NiriOverviewOverlay.qml b/quickshell/Modules/WorkspaceOverlays/NiriOverviewOverlay.qml
index 6897f5a4d..4454b96aa 100644
--- a/quickshell/Modules/WorkspaceOverlays/NiriOverviewOverlay.qml
+++ b/quickshell/Modules/WorkspaceOverlays/NiriOverviewOverlay.qml
@@ -218,13 +218,21 @@ Scope {
}
if (event.key === Qt.Key_Up) {
- NiriService.moveWorkspaceUp();
+ NiriService.send({
+ "Action": {
+ "FocusWorkspaceUp": {}
+ }
+ });
event.accepted = true;
return;
}
if (event.key === Qt.Key_Down) {
- NiriService.moveWorkspaceDown();
+ NiriService.send({
+ "Action": {
+ "FocusWorkspaceDown": {}
+ }
+ });
event.accepted = true;
return;
}
diff --git a/quickshell/Services/AppSearchService.qml b/quickshell/Services/AppSearchService.qml
index 4f066a71d..22c2a5073 100644
--- a/quickshell/Services/AppSearchService.qml
+++ b/quickshell/Services/AppSearchService.qml
@@ -791,23 +791,6 @@ Singleton {
return null;
}
- function getAllPluginItems() {
- if (typeof PluginService === "undefined") {
- return [];
- }
-
- let allItems = [];
- const launchers = PluginService.getLauncherPlugins();
-
- for (const pluginId in launchers) {
- const categoryName = launchers[pluginId].name || pluginId;
- const items = getPluginItems(categoryName, "");
- allItems = allItems.concat(items);
- }
-
- return allItems;
- }
-
function getPluginItems(category, query) {
if (typeof PluginService === "undefined")
return [];
@@ -915,21 +898,6 @@ Singleton {
return false;
}
- function getPluginPasteText(pluginId, item) {
- if (typeof PluginService === "undefined")
- return null;
-
- const instance = PluginService.pluginInstances[pluginId];
- if (!instance)
- return null;
-
- if (typeof instance.getPasteText === "function") {
- return instance.getPasteText(item);
- }
-
- return null;
- }
-
function getPluginPasteArgs(pluginId, item) {
if (typeof PluginService === "undefined")
return null;
@@ -950,21 +918,6 @@ Singleton {
return null;
}
- function searchPluginItems(query) {
- if (typeof PluginService === "undefined")
- return [];
-
- let allItems = [];
- const launchers = PluginService.getLauncherPlugins();
-
- for (const pluginId in launchers) {
- const items = getPluginItemsForPlugin(pluginId, query);
- allItems = allItems.concat(items);
- }
-
- return allItems;
- }
-
function getPluginLauncherCategories(pluginId) {
if (typeof PluginService === "undefined")
return [];
@@ -1001,15 +954,4 @@ Singleton {
log.warn("Error setting category on plugin", pluginId, ":", e);
}
}
-
- function pluginHasCategories(pluginId) {
- if (typeof PluginService === "undefined")
- return false;
-
- const instance = PluginService.pluginInstances[pluginId];
- if (!instance)
- return false;
-
- return typeof instance.getCategories === "function";
- }
}
diff --git a/quickshell/Services/AudioService.qml b/quickshell/Services/AudioService.qml
index 2116c5213..f01f20e6f 100644
--- a/quickshell/Services/AudioService.qml
+++ b/quickshell/Services/AudioService.qml
@@ -85,7 +85,6 @@ Singleton {
}
}
- // Used in playLoginSoundIfApplicable()
Process {
id: loginSoundChecker
onExited: exitCode => {
@@ -720,15 +719,10 @@ EOFCONFIG
return "";
}
- // FIRST: Check if we have a custom alias in our deviceAliases map
- // This ensures we always show the user's custom name, regardless of
- // whether WirePlumber has applied it to the node properties yet
if (node.name && deviceAliases[node.name]) {
return deviceAliases[node.name];
}
- // Check node.properties["node.description"] for WirePlumber-applied aliases
- // This is the live property updated by WirePlumber rules
if (node.properties && node.properties["node.description"]) {
const desc = node.properties["node.description"];
if (desc !== node.name) {
@@ -736,22 +730,18 @@ EOFCONFIG
}
}
- // Check cached description as fallback
if (node.description && node.description !== node.name) {
return node.description;
}
- // Fallback to device description property
if (node.properties && node.properties["device.description"]) {
return node.properties["device.description"];
}
- // Fallback to nickname
if (node.nickname && node.nickname !== node.name) {
return node.nickname;
}
- // Fallback to friendly names based on node name patterns
if (node.name.includes("analog-stereo")) {
return "Built-in Audio Analog Stereo";
}
@@ -773,10 +763,6 @@ EOFCONFIG
return "";
}
- // Get the original name without checking for custom aliases
- // Check pattern-based friendly names FIRST (before device.description)
- // This ensures we show user-friendly names like "Built-in Audio Analog Stereo"
- // instead of hardware chip names like "ALC274 Analog"
if (node.name.includes("analog-stereo")) {
return "Built-in Audio Analog Stereo";
}
@@ -790,19 +776,16 @@ EOFCONFIG
return "HDMI Audio";
}
if (node.name.includes("raop_sink")) {
- // Extract friendly name from RAOP node name
const match = node.name.match(/raop_sink\.([^.]+)/);
if (match) {
return match[1].replace(/-/g, " ");
}
}
- // Fallback to device.description property
if (node.properties && node.properties["device.description"]) {
return node.properties["device.description"];
}
- // Fallback to nickname
if (node.nickname && node.nickname !== node.name) {
return node.nickname;
}
@@ -889,28 +872,6 @@ EOFCONFIG
return root.sink.audio.muted ? "Audio muted" : "Audio unmuted";
}
- function handleNodeVolumeWheel(node, wheelEvent) {
- if (!node?.audio)
- return;
-
- SessionData.suppressOSDTemporarily();
- const delta = wheelEvent.angleDelta.y;
- if (delta === 0)
- return;
-
- const current = Math.round(node.audio.volume * 100);
- const maxVol = getMaxVolumePercent(node);
- const newVolume = delta > 0 ? Math.min(maxVol, current + root.wheelVolumeStep) : Math.max(0, current - root.wheelVolumeStep);
-
- node.audio.muted = false;
- node.audio.volume = newVolume / 100;
-
- if (node === sink) {
- playVolumeChangeSoundIfEnabled();
- }
- wheelEvent.accepted = true;
- }
-
function setMicVolume(percentage) {
if (!root.source?.audio) {
return "No audio source available";
diff --git a/quickshell/Services/BluetoothService.qml b/quickshell/Services/BluetoothService.qml
index f18ed3c1d..47ca13bd6 100644
--- a/quickshell/Services/BluetoothService.qml
+++ b/quickshell/Services/BluetoothService.qml
@@ -354,28 +354,6 @@ Singleton {
return "Very Poor";
}
- function getSignalIcon(device) {
- if (!device || device.signalStrength === undefined || device.signalStrength <= 0) {
- return "signal_cellular_null";
- }
-
- const signal = device.signalStrength;
- if (signal >= 80) {
- return "signal_cellular_4_bar";
- }
- if (signal >= 60) {
- return "signal_cellular_3_bar";
- }
- if (signal >= 40) {
- return "signal_cellular_2_bar";
- }
- if (signal >= 20) {
- return "signal_cellular_1_bar";
- }
-
- return "signal_cellular_0_bar";
- }
-
function isDeviceBusy(device) {
if (!device) {
return false;
@@ -622,40 +600,6 @@ Singleton {
});
}
- function getCurrentCodec(device, callback) {
- if (!device || !device.connected || !isAudioDevice(device)) {
- callback("");
- return;
- }
-
- whenCodecBackendReady(() => {
- if (root.wpexecAvailable) {
- root.queryCardProfiles(device, (codecs, current) => {
- if (current) {
- callback(current);
- return;
- }
- if (!root.dbusBridgeAvailable) {
- callback("");
- return;
- }
- root.queryBluezCodecState(device, (bluezCodecs, bluezCurrent) => {
- callback(bluezCurrent || "");
- });
- });
- return;
- }
-
- if (!root.dbusBridgeAvailable) {
- callback("");
- return;
- }
- root.queryBluezCodecState(device, (codecs, current) => {
- callback(current || "");
- });
- });
- }
-
function getAvailableCodecs(device, callback) {
if (!device || !device.connected || !isAudioDevice(device)) {
callback([], "");
diff --git a/quickshell/Services/BlurService.qml b/quickshell/Services/BlurService.qml
index 4fd484455..41a37a4b1 100644
--- a/quickshell/Services/BlurService.qml
+++ b/quickshell/Services/BlurService.qml
@@ -41,6 +41,12 @@ Singleton {
return Theme.withAlpha(baseColor, hoverAlpha ?? 0.15);
}
+ Binding {
+ target: Theme
+ property: "blurLayersActive"
+ value: root.enabled
+ }
+
Process {
id: blurProbe
running: false
diff --git a/quickshell/Services/CompositorService.qml b/quickshell/Services/CompositorService.qml
index ccfecbf95..e00fa0b33 100644
--- a/quickshell/Services/CompositorService.qml
+++ b/quickshell/Services/CompositorService.qml
@@ -1087,4 +1087,58 @@ Singleton {
}
log.warn("Cannot power on monitors, unknown compositor");
}
+ function escapeSwayWorkspaceName(name) {
+ return String(name ?? "").replace(/\\/g, "\\\\").replace(/"/g, "\\\"");
+ }
+
+ function dispatchSwayWorkspace(ws) {
+ if (!ws)
+ return;
+ try {
+ if (ws.num !== undefined && ws.num !== -1) {
+ I3.dispatch(`workspace number ${ws.num}`);
+ } else if (ws.name) {
+ I3.dispatch(`workspace "${escapeSwayWorkspaceName(ws.name)}"`);
+ }
+ } catch (_) {}
+ }
+
+ Binding {
+ target: SettingsData
+ property: "activeCompositor"
+ value: root.compositor
+ }
+
+ Connections {
+ target: SettingsData
+
+ function onCompositorLayoutRefreshNeeded(frame) {
+ if (root.isNiri && typeof NiriService !== "undefined")
+ NiriService.generateNiriLayoutConfig(frame);
+ if (root.isHyprland && typeof HyprlandService !== "undefined")
+ HyprlandService.generateLayoutConfig(frame);
+ if (!frame && root.isMango && typeof MangoService !== "undefined")
+ MangoService.generateLayoutConfig();
+ }
+
+ function onCompositorInputRefreshNeeded() {
+ if (root.isNiri && typeof NiriService !== "undefined")
+ NiriService.generateNiriInputConfig();
+ }
+
+ function onCompositorCursorRefreshNeeded() {
+ if (root.isNiri && typeof NiriService !== "undefined") {
+ NiriService.generateNiriCursorConfig();
+ return;
+ }
+ if (root.isHyprland && typeof HyprlandService !== "undefined") {
+ HyprlandService.generateCursorConfig();
+ return;
+ }
+ if (root.isMango && typeof MangoService !== "undefined") {
+ MangoService.generateCursorConfig();
+ return;
+ }
+ }
+ }
}
diff --git a/quickshell/Services/CupsService.qml b/quickshell/Services/CupsService.qml
index 57d774411..962fc8474 100644
--- a/quickshell/Services/CupsService.qml
+++ b/quickshell/Services/CupsService.qml
@@ -349,14 +349,6 @@ Singleton {
return getPrinterStateTranslation(printer.state) + " (" + getPrinterStateReasonTranslation(printer.stateReason) + ")";
}
- function getCurrentPrinterStatePretty() {
- if (!cupsAvailable || !selectedPrinter)
- return "";
-
- var printer = printers[selectedPrinter];
- return getPrinterStateTranslation(printer.state) + " (" + I18n.tr("Reason") + ": " + getPrinterStateReasonTranslation(printer.stateReason) + ")";
- }
-
function getCurrentPrinterJobs() {
if (!cupsAvailable || !selectedPrinter)
return [];
@@ -372,14 +364,6 @@ Singleton {
return printer.jobs;
}
- function getJobsNum(printerName) {
- if (!cupsAvailable)
- return 0;
-
- var printer = printers[printerName];
- return printer.jobs.length;
- }
-
function pausePrinter(printerName) {
if (!cupsAvailable)
return;
@@ -579,57 +563,6 @@ Singleton {
});
}
- function setPrinterShared(printerName, shared) {
- if (!cupsAvailable)
- return;
- const params = {
- "printerName": printerName,
- "shared": shared
- };
-
- DMSService.sendRequest("cups.setPrinterShared", params, response => {
- if (response.error) {
- ToastService.showError(I18n.tr("Failed to update sharing"), response.error);
- } else {
- getState();
- }
- });
- }
-
- function setPrinterLocation(printerName, location) {
- if (!cupsAvailable)
- return;
- const params = {
- "printerName": printerName,
- "location": location
- };
-
- DMSService.sendRequest("cups.setPrinterLocation", params, response => {
- if (response.error) {
- ToastService.showError(I18n.tr("Failed to update location"), response.error);
- } else {
- getState();
- }
- });
- }
-
- function setPrinterInfo(printerName, info) {
- if (!cupsAvailable)
- return;
- const params = {
- "printerName": printerName,
- "info": info
- };
-
- DMSService.sendRequest("cups.setPrinterInfo", params, response => {
- if (response.error) {
- ToastService.showError(I18n.tr("Failed to update description"), response.error);
- } else {
- getState();
- }
- });
- }
-
function printTestPage(printerName) {
if (!cupsAvailable)
return;
@@ -647,23 +580,6 @@ Singleton {
});
}
- function moveJob(jobID, destPrinter) {
- if (!cupsAvailable)
- return;
- const params = {
- "jobID": jobID,
- "destPrinter": destPrinter
- };
-
- DMSService.sendRequest("cups.moveJob", params, response => {
- if (response.error) {
- ToastService.showError(I18n.tr("Failed to move job"), response.error);
- } else {
- fetchAllJobs();
- }
- });
- }
-
function restartJob(jobID) {
if (!cupsAvailable)
return;
@@ -699,40 +615,6 @@ Singleton {
});
}
- function addPrinterToClass(className, printerName) {
- if (!cupsAvailable)
- return;
- const params = {
- "className": className,
- "printerName": printerName
- };
-
- DMSService.sendRequest("cups.addPrinterToClass", params, response => {
- if (response.error) {
- ToastService.showError(I18n.tr("Failed to add printer to class"), response.error);
- } else {
- getClasses();
- }
- });
- }
-
- function removePrinterFromClass(className, printerName) {
- if (!cupsAvailable)
- return;
- const params = {
- "className": className,
- "printerName": printerName
- };
-
- DMSService.sendRequest("cups.removePrinterFromClass", params, response => {
- if (response.error) {
- ToastService.showError(I18n.tr("Failed to remove printer from class"), response.error);
- } else {
- getClasses();
- }
- });
- }
-
function deleteClass(className) {
if (!cupsAvailable)
return;
diff --git a/quickshell/Services/DMSNetworkService.qml b/quickshell/Services/DMSNetworkService.qml
index 95c84d8d0..524cf330a 100644
--- a/quickshell/Services/DMSNetworkService.qml
+++ b/quickshell/Services/DMSNetworkService.qml
@@ -1049,10 +1049,6 @@ Singleton {
return activeUuids && activeUuids.indexOf(uuid) !== -1;
}
- function isActiveUuid(uuid) {
- return isActiveVpnUuid(uuid);
- }
-
function refreshNetworkState() {
if (networkAvailable) {
getState();
diff --git a/quickshell/Services/DMSService.qml b/quickshell/Services/DMSService.qml
index 339ab4b22..3e936f4ad 100644
--- a/quickshell/Services/DMSService.qml
+++ b/quickshell/Services/DMSService.qml
@@ -301,20 +301,6 @@ Singleton {
}
}
- function subscribeAll() {
- subscribe(["all"]);
- }
-
- function subscribeAllExcept(excludeServices) {
- if (!Array.isArray(excludeServices)) {
- excludeServices = [excludeServices];
- }
-
- const allServices = ["network", "loginctl", "freedesktop", "gamma", "theme.auto", "bluetooth", "cups", "brightness", "browser", "dbus", "location"];
- const filtered = allServices.filter(s => !excludeServices.includes(s));
- subscribe(filtered);
- }
-
function handleSubscriptionEvent(response) {
if (response.error) {
if (response.error.includes("unknown method") && response.error.includes("subscribe")) {
@@ -453,10 +439,6 @@ Singleton {
}
}
- function ping(callback) {
- sendRequest("ping", null, callback);
- }
-
function listPlugins(callback) {
sendRequest("plugins.list", null, response => {
if (response.result) {
@@ -481,30 +463,6 @@ Singleton {
});
}
- function search(query, category, compositor, capability, callback) {
- const params = {
- "query": query
- };
- if (category) {
- params.category = category;
- }
- if (compositor) {
- params.compositor = compositor;
- }
- if (capability) {
- params.capability = capability;
- }
-
- sendRequest("plugins.search", params, response => {
- if (response.result) {
- searchResultsReceived(response.result);
- }
- if (callback) {
- callback(response);
- }
- });
- }
-
function install(pluginName, callback) {
sendRequest("plugins.install", {
"name": pluginName
@@ -568,19 +526,6 @@ Singleton {
});
}
- function searchThemes(query, callback) {
- sendRequest("themes.search", {
- "query": query
- }, response => {
- if (response.result) {
- themeSearchResultsReceived(response.result);
- }
- if (callback) {
- callback(response);
- }
- });
- }
-
function installTheme(themeName, callback) {
sendRequest("themes.install", {
"name": themeName
@@ -607,19 +552,6 @@ Singleton {
});
}
- function updateTheme(themeName, callback) {
- sendRequest("themes.update", {
- "name": themeName
- }, response => {
- if (callback) {
- callback(response);
- }
- if (!response.error) {
- listInstalledThemes();
- }
- });
- }
-
function lockSession(callback) {
sendRequest("loginctl.lock", null, callback);
}
@@ -640,30 +572,12 @@ Singleton {
}, callback);
}
- function bluetoothConnect(devicePath, callback) {
- sendRequest("bluetooth.connect", {
- "device": devicePath
- }, callback);
- }
-
- function bluetoothDisconnect(devicePath, callback) {
- sendRequest("bluetooth.disconnect", {
- "device": devicePath
- }, callback);
- }
-
function bluetoothRemove(devicePath, callback) {
sendRequest("bluetooth.remove", {
"device": devicePath
}, callback);
}
- function bluetoothTrust(devicePath, callback) {
- sendRequest("bluetooth.trust", {
- "device": devicePath
- }, callback);
- }
-
function bluetoothSubmitPairing(token, secrets, accept, callback) {
sendRequest("bluetooth.pairing.submit", {
"token": token,
diff --git a/quickshell/Services/DesktopService.qml b/quickshell/Services/DesktopService.qml
index d54d0309f..23b832655 100644
--- a/quickshell/Services/DesktopService.qml
+++ b/quickshell/Services/DesktopService.qml
@@ -18,7 +18,10 @@ Singleton {
property bool systemdAutostartTargetChecked: false
readonly property bool autostartAvailable: root.systemdAutostartTargetChecked && (!root.isSystemd || root.systemdAutostartTargetActive)
- Component.onCompleted: initSystemCheckProcess.running = true
+ Component.onCompleted: {
+ Paths.desktopIconResolver = name => resolveIconPath(name);
+ initSystemCheckProcess.running = true;
+ }
Process {
id: initSystemCheckProcess
diff --git a/quickshell/Services/DgopService.qml b/quickshell/Services/DgopService.qml
index 99a7b4451..406bdf4cc 100644
--- a/quickshell/Services/DgopService.qml
+++ b/quickshell/Services/DgopService.qml
@@ -156,10 +156,6 @@ Singleton {
}
}
- function setGpuPciIds(pciIds) {
- gpuPciIds = Array.isArray(pciIds) ? pciIds : [];
- }
-
function addGpuPciId(pciId) {
const currentCount = gpuPciIdRefCounts[pciId] || 0;
gpuPciIdRefCounts[pciId] = currentCount + 1;
@@ -203,12 +199,6 @@ Singleton {
gpuPciIdRefCounts = Object.assign({}, gpuPciIdRefCounts);
}
- function setProcessOptions(limit = 20, sort = "cpu", disableCpu = false) {
- processLimit = limit;
- processSort = sort;
- noCpu = disableCpu;
- }
-
function updateAllStats() {
if (dgopAvailable && refCount > 0 && enabledModules.length > 0) {
isUpdating = true;
@@ -476,44 +466,6 @@ Singleton {
}
}
- function getProcessIcon(command) {
- const cmd = command.toLowerCase();
- if (cmd.includes("firefox") || cmd.includes("chrome") || cmd.includes("browser") || cmd.includes("chromium")) {
- return "web";
- }
- if (cmd.includes("code") || cmd.includes("editor") || cmd.includes("vim")) {
- return "code";
- }
- if (cmd.includes("terminal") || cmd.includes("bash") || cmd.includes("zsh")) {
- return "terminal";
- }
- if (cmd.includes("music") || cmd.includes("audio") || cmd.includes("spotify")) {
- return "music_note";
- }
- if (cmd.includes("video") || cmd.includes("vlc") || cmd.includes("mpv")) {
- return "play_circle";
- }
- if (cmd.includes("systemd") || cmd.includes("elogind") || cmd.includes("kernel") || cmd.includes("kthread") || cmd.includes("kworker")) {
- return "settings";
- }
- return "memory";
- }
-
- function formatCpuUsage(cpu) {
- return (cpu || 0).toFixed(1) + "%";
- }
-
- function formatMemoryUsage(memoryKB) {
- const mem = memoryKB || 0;
- if (mem < 1024) {
- return mem.toFixed(0) + " KB";
- } else if (mem < 1024 * 1024) {
- return (mem / 1024).toFixed(1) + " MB";
- } else {
- return (mem / (1024 * 1024)).toFixed(1) + " GB";
- }
- }
-
function formatSystemMemory(memoryKB) {
const mem = memoryKB || 0;
if (mem === 0) {
@@ -526,12 +478,6 @@ Singleton {
}
}
- function killProcess(pid) {
- if (pid > 0) {
- Quickshell.execDetached("kill", [pid.toString()]);
- }
- }
-
function updateUptime() {
if (!bootTime) {
uptime = "";
diff --git a/quickshell/Services/DisplayService.qml b/quickshell/Services/DisplayService.qml
index 88c5d611d..8ad163acc 100644
--- a/quickshell/Services/DisplayService.qml
+++ b/quickshell/Services/DisplayService.qml
@@ -1228,10 +1228,6 @@ Singleton {
});
}
- function setNightModeAutomationMode(mode) {
- SessionData.setNightModeAutoMode(mode);
- }
-
function evaluateNightMode() {
if (!nightModeEnabled) {
return;
@@ -1355,6 +1351,14 @@ Singleton {
brightnessChanged();
}
+ Connections {
+ target: SessionData
+
+ function onBrightnessDisplayHintChanged(deviceName) {
+ root.updateDeviceBrightnessDisplay(deviceName);
+ }
+ }
+
Timer {
id: osdSuppressTimer
interval: 2000
diff --git a/quickshell/Services/IconThemeService.qml b/quickshell/Services/IconThemeService.qml
index b44129562..2541a5daa 100644
--- a/quickshell/Services/IconThemeService.qml
+++ b/quickshell/Services/IconThemeService.qml
@@ -37,7 +37,10 @@ Singleton {
}
onManagedThemeChanged: _rebuild()
- Component.onCompleted: _rebuild()
+ Component.onCompleted: {
+ Paths.iconResolver = name => resolve(name);
+ _rebuild();
+ }
function _bumpRevision() {
if (_bumpPending)
diff --git a/quickshell/Services/KeybindsService.qml b/quickshell/Services/KeybindsService.qml
index 5f9b8c720..0ea0bad74 100644
--- a/quickshell/Services/KeybindsService.qml
+++ b/quickshell/Services/KeybindsService.qml
@@ -574,18 +574,6 @@ Singleton {
bindRemoved(key);
}
- function isDmsAction(action) {
- return Actions.isDmsAction(action);
- }
-
- function isValidAction(action) {
- return Actions.isValidAction(action);
- }
-
- function getActionType(action) {
- return Actions.getActionType(action);
- }
-
function getActionLabel(action) {
return Actions.getActionLabel(action, currentProvider);
}
@@ -601,24 +589,4 @@ Singleton {
function getDmsActions() {
return Actions.getDmsActions(CompositorService.isNiri, CompositorService.isHyprland);
}
-
- function buildSpawnAction(command, args) {
- return Actions.buildSpawnAction(command, args);
- }
-
- function buildShellAction(shellCmd, shell) {
- return Actions.buildShellAction(currentProvider, shellCmd, shell);
- }
-
- function getShellFromAction(action) {
- return Actions.getShellFromAction(action);
- }
-
- function parseSpawnCommand(action) {
- return Actions.parseSpawnCommand(action);
- }
-
- function parseShellCommand(action) {
- return Actions.parseShellCommand(action);
- }
}
diff --git a/quickshell/Services/MangoService.qml b/quickshell/Services/MangoService.qml
index ba82ed2a0..d3b50c4f2 100644
--- a/quickshell/Services/MangoService.qml
+++ b/quickshell/Services/MangoService.qml
@@ -48,7 +48,6 @@ Singleton {
// windowsChanged is auto-generated by the `windows` property's change signal.
signal stateChanged
- // ── State sockets ──────────────────────────────────────────────────────
// One connection per watch target; mango streams a fresh full snapshot on
// every change, so each line is treated as the complete state.
@@ -272,8 +271,6 @@ Singleton {
root.windows = data.clients;
}
- // ── Tag API (dwl-style tag model) ──────────────────────────────────────
-
function getOutputState(outputName) {
return (outputs && outputs[outputName]) ? outputs[outputName] : null;
}
@@ -296,20 +293,6 @@ Singleton {
return at.length === 0 || at.every(t => t === 0);
}
- function getTagsWithClients(outputName) {
- const output = getOutputState(outputName);
- if (!output || !output.tags)
- return [];
- return output.tags.filter(tag => tag.clients > 0).map(tag => tag.tag);
- }
-
- function getUrgentTags(outputName) {
- const output = getOutputState(outputName);
- if (!output || !output.tags)
- return [];
- return output.tags.filter(tag => tag.state === 2).map(tag => tag.tag);
- }
-
function getVisibleTags(outputName) {
const output = getOutputState(outputName);
if (!output || !output.tags)
@@ -328,7 +311,6 @@ Singleton {
return displayScales[outputName];
}
- // ── Window list ↔ wlr toplevel matching (per-tag sort/filter) ──────────
// Match mango clients to wlr foreign-toplevels by appId+title to enrich them
// with owning tags/monitor for per-tag filtering and stable ordering.
@@ -452,8 +434,6 @@ Singleton {
return _matchAndEnrich(toplevels, clients);
}
- // ── Commands (mango verb IPC: dispatch