From c67b1850765c0263e87973af8d81f54b89dfd1b4 Mon Sep 17 00:00:00 2001 From: bbedward Date: Sun, 26 Jul 2026 23:22:22 -0400 Subject: [PATCH] qs: large sweep of dead code removals --- dank-qml-common | 2 +- flake.lock | 6 +- quickshell/Common/CacheUtils.qml | 29 - .../Common/ConnectedSurfaceDescriptor.js | 20 - quickshell/Common/ConnectedSurfaceGeometry.js | 26 - quickshell/Common/ConnectorGeometry.js | 10 - quickshell/Common/Format.js | 72 ++ quickshell/Common/KeybindActions.js | 7 - quickshell/Common/Paths.qml | 20 +- quickshell/Common/QmlUtils.js | 27 + quickshell/Common/SessionData.qml | 209 +----- quickshell/Common/SettingsData.qml | 484 +------------- quickshell/Common/StockThemes.js | 26 - quickshell/Common/Theme.qml | 619 +----------------- quickshell/Common/markdown2html.js | 23 +- quickshell/Common/settings/Processes.qml | 28 +- quickshell/Common/settings/SessionStore.js | 23 - quickshell/Common/settings/SettingsStore.js | 23 - quickshell/Common/suncalc.js | 65 -- quickshell/DMSShell.qml | 5 + .../Modals/Common/DankModalConnected.qml | 22 +- .../DankLauncherV2ModalConnected.qml | 26 +- quickshell/Modals/DankLauncherV2/Scorer.js | 9 - quickshell/Modals/PowerProfileModal.qml | 15 +- quickshell/Modals/ProcessListModal.qml | 15 +- quickshell/Modules/BlurredWallpaperLive.qml | 27 +- .../SystemMonitorWidget.qml | 39 +- .../BuiltinPlugins/VpnWidget.qml | 2 +- .../ControlCenter/Components/ActionTile.qml | 131 ---- .../Components/DragDropDetailHost.qml | 91 --- .../ControlCenter/Components/PowerButton.qml | 55 -- .../Details/AudioInputDetail.qml | 13 +- .../Details/AudioOutputDetail.qml | 13 +- .../ControlCenter/Details/BluetoothDetail.qml | 13 +- .../Details/DoNotDisturbDetail.qml | 30 +- .../ControlCenter/Details/NetworkDetail.qml | 13 +- .../Details}/VpnDetailContent.qml | 0 .../Details}/VpnProfileDelegate.qml | 0 .../ControlCenter/Widgets/CompactSlider.qml | 72 -- .../ControlCenter/Widgets/CompoundPill.qml | 9 +- .../ControlCenter/Widgets/DetailView.qml | 23 - .../Widgets/SmallBatteryButton.qml | 7 +- .../Widgets/SmallColorPickerButton.qml | 7 +- .../Widgets/SmallCompoundButton.qml | 7 +- .../Widgets/SmallDiskUsageButton.qml | 7 +- .../Widgets/SmallToggleButton.qml | 7 +- .../ControlCenter/Widgets/ToggleButton.qml | 7 +- quickshell/Modules/DankBar/DankBarContent.qml | 18 +- .../Modules/DankBar/Popouts/VpnPopout.qml | 1 + .../DankBar/Widgets/NetworkMonitor.qml | 17 +- .../DankBar/Widgets/WorkspaceSwitcher.qml | 22 +- .../Modules/DankDash/MediaDropdownOverlay.qml | 19 +- .../Modules/DankDash/MediaPlayerTab.qml | 7 + .../DankDash/Overview/MediaOverviewCard.qml | 7 + quickshell/Modules/Dock/DockBody.qml | 12 +- quickshell/Modules/Dock/DockGeometry.qml | 6 +- .../Notifications/Center/DndDurationMenu.qml | 33 +- .../Center/HistoryNotificationCard.qml | 32 +- quickshell/Modules/Plugins/ColorSetting.qml | 16 +- quickshell/Modules/Plugins/ListSetting.qml | 16 +- .../Modules/Plugins/ListSettingWithInput.qml | 16 +- .../Plugins/PluginControlCenterWrapper.qml | 43 -- .../Modules/Plugins/SelectionSetting.qml | 50 +- quickshell/Modules/Plugins/SliderSetting.qml | 16 +- quickshell/Modules/Plugins/StringSetting.qml | 16 +- quickshell/Modules/Plugins/ToggleSetting.qml | 16 +- quickshell/Modules/ProcessList/DisksView.qml | 15 +- .../Modules/ProcessList/PerformanceView.qml | 39 +- .../Modules/ProcessList/ProcessesView.qml | 36 +- .../DisplayConfig/DisplayConfigState.qml | 9 - .../Modules/Settings/GammaControlTab.qml | 22 +- .../Settings}/KeybindItem.qml | 4 +- quickshell/Modules/Settings/NetworkVpnTab.qml | 2 +- .../Modules/Settings/NetworkWifiTab.qml | 13 +- .../Modules/Settings/NotificationsTab.qml | 7 +- .../Modules/Settings/ThemeColorsTab.qml | 16 +- .../Widgets/SettingsButtonGroupRow.qml | 13 +- .../Modules/Settings/Widgets/SettingsCard.qml | 14 +- .../Settings/Widgets/SettingsDropdownRow.qml | 13 +- .../Settings/Widgets/SettingsSliderCard.qml | 13 +- .../Settings/Widgets/SettingsSliderRow.qml | 13 +- .../Settings/Widgets/SettingsToggleCard.qml | 13 +- .../Settings/Widgets/SettingsToggleRow.qml | 13 +- quickshell/Modules/Settings/WorkspacesTab.qml | 4 +- quickshell/Modules/WallpaperBackground.qml | 31 +- .../WorkspaceOverlays/NiriOverviewOverlay.qml | 12 +- quickshell/Services/AppSearchService.qml | 58 -- quickshell/Services/AudioService.qml | 39 -- quickshell/Services/BluetoothService.qml | 56 -- quickshell/Services/BlurService.qml | 6 + quickshell/Services/CompositorService.qml | 54 ++ quickshell/Services/CupsService.qml | 118 ---- quickshell/Services/DMSNetworkService.qml | 4 - quickshell/Services/DMSService.qml | 86 --- quickshell/Services/DesktopService.qml | 5 +- quickshell/Services/DgopService.qml | 54 -- quickshell/Services/DisplayService.qml | 12 +- quickshell/Services/IconThemeService.qml | 5 +- quickshell/Services/KeybindsService.qml | 32 - quickshell/Services/MangoService.qml | 22 - quickshell/Services/NiriService.qml | 102 ++- quickshell/Services/NotificationService.qml | 80 ++- quickshell/Services/PluginService.qml | 9 - quickshell/Services/PortalService.qml | 15 +- quickshell/Services/ThemeAutoService.qml | 541 +++++++++++++++ quickshell/Services/ToastService.qml | 163 +++-- quickshell/Services/TrashService.qml | 1 + .../Services/WallpaperCyclingService.qml | 146 +++++ quickshell/Widgets/DankAlbumArt.qml | 22 +- quickshell/Widgets/DankPopoutStandalone.qml | 8 +- quickshell/Widgets/DankSeekbar.qml | 18 +- quickshell/Widgets/DankSlideout.qml | 8 +- quickshell/shell.qml | 8 + 113 files changed, 1467 insertions(+), 3254 deletions(-) delete mode 100644 quickshell/Common/CacheUtils.qml create mode 100644 quickshell/Common/Format.js create mode 100644 quickshell/Common/QmlUtils.js delete mode 100644 quickshell/Modules/ControlCenter/Components/ActionTile.qml delete mode 100644 quickshell/Modules/ControlCenter/Components/DragDropDetailHost.qml delete mode 100644 quickshell/Modules/ControlCenter/Components/PowerButton.qml rename quickshell/{Widgets => Modules/ControlCenter/Details}/VpnDetailContent.qml (100%) rename quickshell/{Widgets => Modules/ControlCenter/Details}/VpnProfileDelegate.qml (100%) delete mode 100644 quickshell/Modules/ControlCenter/Widgets/CompactSlider.qml delete mode 100644 quickshell/Modules/ControlCenter/Widgets/DetailView.qml delete mode 100644 quickshell/Modules/Plugins/PluginControlCenterWrapper.qml rename quickshell/{Widgets => Modules/Settings}/KeybindItem.qml (99%) create mode 100644 quickshell/Services/ThemeAutoService.qml 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, '

$1

'); html = html.replace(/^## (.*?)$/gm, '

$1

'); html = html.replace(/^# (.*?)$/gm, '

$1

'); @@ -58,20 +50,16 @@ function markdownToHtml(text) { html = html.replace(/__(.*?)__/g, '$1'); html = html.replace(/_(.*?)_/g, '$1'); - // Links html = html.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '$1'); - // Lists html = html.replace(/^\* (.*?)$/gm, '
  • $1
  • '); html = html.replace(/^- (.*?)$/gm, '
  • $1
  • '); html = html.replace(/^\d+\. (.*?)$/gm, '
  • $1
  • '); - // Wrap consecutive list items in ul/ol tags html = html.replace(/(
  • [\s\S]*?<\/li>\s*)+/g, function(match) { return ''; }); - // Restore extracted URLs as anchor tags (preserves raw & in href) html = html.replace(/\x00URL(\d+)\x00/g, (_, index) => { const url = urls[parseInt(index)]; const display = url.replace(/&/g, '&').replace(//g, '>'); @@ -87,30 +75,23 @@ function markdownToHtml(text) { return inlineCode[parseInt(index)]; }); - // Line breaks (after code blocks are restored) html = html.replace(/\n\n/g, '

    '); html = html.replace(/\n/g, '
    '); - // Wrap in paragraph tags if not already wrapped if (!html.startsWith('<')) { html = '

    ' + html + '

    '; } - // Clean up the final HTML - // Remove
    tags immediately before block elements html = html.replace(/\s*
    /g, '
    ');
         html = html.replace(/\s*
      /g, '
        '); html = html.replace(/\s*/g, ''); - // Remove empty paragraphs html = html.replace(/

        \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 , over the socket) ── - function suppressWatchedConfigReloads(ms) { root._ignoreWatchedReloadUntil = Math.max(root._ignoreWatchedReloadUntil, Date.now() + (ms || 1500)); } @@ -539,8 +519,6 @@ Singleton { } } - // ── Config generation (mango config fragments under ~/.config/mango/dms) ─ - Connections { target: SettingsData function onBarConfigsChanged() { diff --git a/quickshell/Services/NiriService.qml b/quickshell/Services/NiriService.qml index f390ce72a..eb43c2b88 100644 --- a/quickshell/Services/NiriService.qml +++ b/quickshell/Services/NiriService.qml @@ -76,6 +76,28 @@ Singleton { generateNiriInputConfig(); } + Connections { + target: Theme + + function onScreenTransitionNeeded() { + if (CompositorService.isNiri) { + root.doScreenTransition(); + } + } + + function onThemeGenerationStarting() { + if (CompositorService.isNiri) { + root.suppressNextToast(); + } + } + } + + Binding { + target: Theme + property: "matugenToastSuppressed" + value: root.matugenSuppression + } + Timer { id: suppressToastTimer interval: 3000 @@ -798,22 +820,6 @@ Singleton { }); } - function moveWorkspaceDown() { - return send({ - "Action": { - "FocusWorkspaceDown": {} - } - }); - } - - function moveWorkspaceUp() { - return send({ - "Action": { - "FocusWorkspaceUp": {} - } - }); - } - function switchToWorkspace(workspaceId) { return send({ "Action": { @@ -925,10 +931,6 @@ Singleton { }); } - function getCurrentOutputWorkspaceNumbers() { - return currentOutputWorkspaces.map(w => w.idx + 1); - } - function getCurrentOutputWorkspaces() { return currentOutputWorkspaces.slice(); } @@ -952,24 +954,6 @@ Singleton { suppressResetTimer.restart(); } - function findNiriWindow(toplevel) { - if (!toplevel.appId) - return null; - - for (var j = 0; j < windows.length; j++) { - const niriWindow = windows[j]; - if (niriWindow.app_id === toplevel.appId) { - if (!niriWindow.title || niriWindow.title === toplevel.title) { - return { - "niriIndex": j, - "niriWindow": niriWindow - }; - } - } - } - return null; - } - function sortToplevels(toplevels) { if (!toplevels || toplevels.length === 0 || !CompositorService.isNiri || windows.length === 0) { return [...toplevels]; @@ -1371,23 +1355,6 @@ window-rule { writeCursorProcess.running = true; } - function updateOutputPosition(outputName, x, y) { - if (!outputs || !outputs[outputName]) - return; - const updatedOutputs = {}; - for (const name in outputs) { - const output = outputs[name]; - if (name === outputName && output.logical) { - updatedOutputs[name] = JSON.parse(JSON.stringify(output)); - updatedOutputs[name].logical.x = x; - updatedOutputs[name].logical.y = y; - } else { - updatedOutputs[name] = output; - } - } - outputs = updatedOutputs; - } - function applyOutputConfig(outputName, config, callback) { if (!CompositorService.isNiri || !outputName) { if (callback) @@ -1786,6 +1753,31 @@ window-rule { writeInputProcess.running = true; } + function hasNamedWorkspaces() { + if (!CompositorService.isNiri) + return false; + + for (var i = 0; i < allWorkspaces.length; i++) { + var ws = allWorkspaces[i]; + if (ws.name && ws.name.trim() !== "") + return true; + } + return false; + } + + function getNamedWorkspaces() { + var namedWorkspaces = []; + if (!CompositorService.isNiri) + return namedWorkspaces; + + for (const ws of allWorkspaces) { + if (ws.name && ws.name.trim() !== "") { + namedWorkspaces.push(ws.name); + } + } + return namedWorkspaces; + } + IpcHandler { function screenshot(): string { if (!CompositorService.isNiri) { diff --git a/quickshell/Services/NotificationService.qml b/quickshell/Services/NotificationService.qml index 3f28f9bd1..613f1d9fd 100644 --- a/quickshell/Services/NotificationService.qml +++ b/quickshell/Services/NotificationService.qml @@ -259,36 +259,6 @@ Singleton { return historyList.filter(n => getHistoryTimeRange(n.timestamp) === range).length; } - function formatHistoryTime(timestamp) { - root.timeUpdateTick; - root.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; - } - } - function _nowSec() { return Date.now() / 1000.0; } @@ -929,6 +899,56 @@ Singleton { notificationQueue = []; } + Connections { + target: SettingsData + + function onNotificationPopupsInvalidated() { + root.dismissAllPopups(); + } + } + + function sendTestNotifications() { + 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; + } + + property Process testNotificationProcess + + testNotificationProcess: Process { + command: [] + running: false + } + + property Timer testNotifTimer1 + + testNotifTimer1: Timer { + interval: 400 + repeat: false + onTriggered: root.sendTestNotification(1) + } + + property Timer testNotifTimer2 + + testNotifTimer2: Timer { + interval: 800 + repeat: false + onTriggered: root.sendTestNotification(2) + } + function clearAllNotifications() { if (!notifications.length) { return; diff --git a/quickshell/Services/PluginService.qml b/quickshell/Services/PluginService.qml index 0ab4c184c..3705ca790 100644 --- a/quickshell/Services/PluginService.qml +++ b/quickshell/Services/PluginService.qml @@ -252,11 +252,6 @@ Singleton { return paths; } - function pluginHasSurface(pluginId, surface) { - const plugin = availablePlugins[pluginId]; - return !!(plugin && plugin.surfaces && plugin.surfaces.includes(surface)); - } - function _onManifestParsed(absPath, manifest, sourceTag, mtimeEpochMs) { if (!manifest || !manifest.id || !manifest.name || (!manifest.component && !manifest.components)) { log.error("invalid manifest fields:", absPath); @@ -839,10 +834,6 @@ Singleton { return plugin.pluginDirectory || ""; } - function saveAllPluginSettings() { - SettingsData.savePluginSettings(); - } - function getPluginStatePath(pluginId) { return Paths.strip(Paths.state) + "/plugins/" + pluginId + "_state.json"; } diff --git a/quickshell/Services/PortalService.qml b/quickshell/Services/PortalService.qml index 60783405a..cdf743b91 100644 --- a/quickshell/Services/PortalService.qml +++ b/quickshell/Services/PortalService.qml @@ -132,13 +132,6 @@ Singleton { } } - function setLightMode(isLightMode) { - if (typeof SettingsData !== "undefined" && SettingsData.syncModeWithPortal === false) { - return; - } - setSystemColorScheme(isLightMode); - } - function setSystemColorScheme(isLightMode) { if (typeof SettingsData !== "undefined" && SettingsData.syncModeWithPortal === false) { return; @@ -211,6 +204,14 @@ Singleton { colorSchemeDetector.running = true; } + Connections { + target: Theme + + function onIsLightModeChanged() { + root.setSystemColorScheme(Theme.isLightMode); + } + } + Connections { target: typeof SettingsData !== "undefined" ? SettingsData : null diff --git a/quickshell/Services/ThemeAutoService.qml b/quickshell/Services/ThemeAutoService.qml new file mode 100644 index 000000000..6a2f7e580 --- /dev/null +++ b/quickshell/Services/ThemeAutoService.qml @@ -0,0 +1,541 @@ +pragma Singleton +pragma ComponentBehavior: Bound + +import QtQuick +import Quickshell +import qs.Common +import qs.Services + +Singleton { + id: root + readonly property var log: Log.scoped("ThemeAutoService") + + property bool active: false + + Component.onCompleted: { + if (typeof SessionData !== "undefined" && SessionData.themeModeAutoEnabled) { + start(); + } + } + + Connections { + target: SessionData + enabled: typeof SessionData !== "undefined" + + function onThemeModeAutoEnabledChanged() { + if (SessionData.themeModeAutoEnabled) { + root.start(); + } else { + root.stop(); + } + } + + function onThemeModeAutoModeChanged() { + if (root.active) { + root.evaluate(); + root.syncTimeSchedule(); + root.syncLocationSchedule(); + } + } + + function onThemeModeStartHourChanged() { + if (root.active && !SessionData.themeModeShareGammaSettings) { + root.evaluate(); + root.syncTimeSchedule(); + } + } + + function onThemeModeStartMinuteChanged() { + if (root.active && !SessionData.themeModeShareGammaSettings) { + root.evaluate(); + root.syncTimeSchedule(); + } + } + + function onThemeModeEndHourChanged() { + if (root.active && !SessionData.themeModeShareGammaSettings) { + root.evaluate(); + root.syncTimeSchedule(); + } + } + + function onThemeModeEndMinuteChanged() { + if (root.active && !SessionData.themeModeShareGammaSettings) { + root.evaluate(); + root.syncTimeSchedule(); + } + } + + function onThemeModeShareGammaSettingsChanged() { + if (root.active) { + root.evaluate(); + root.syncTimeSchedule(); + root.syncLocationSchedule(); + } + } + + function onNightModeStartHourChanged() { + if (root.active && SessionData.themeModeShareGammaSettings) { + root.evaluate(); + root.syncTimeSchedule(); + } + } + + function onNightModeStartMinuteChanged() { + if (root.active && SessionData.themeModeShareGammaSettings) { + root.evaluate(); + root.syncTimeSchedule(); + } + } + + function onNightModeEndHourChanged() { + if (root.active && SessionData.themeModeShareGammaSettings) { + root.evaluate(); + root.syncTimeSchedule(); + } + } + + function onNightModeEndMinuteChanged() { + if (root.active && SessionData.themeModeShareGammaSettings) { + root.evaluate(); + root.syncTimeSchedule(); + } + } + + function onLatitudeChanged() { + if (root.active && 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.evaluate(); + root.syncLocationSchedule(); + } + } + + function onLongitudeChanged() { + if (root.active && 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.evaluate(); + root.syncLocationSchedule(); + } + } + + function onNightModeUseIPLocationChanged() { + if (root.active && 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.evaluate(); + root.syncLocationSchedule(); + } + } + } + + Connections { + target: DisplayService + enabled: typeof DisplayService !== "undefined" && typeof SessionData !== "undefined" && SessionData.themeModeAutoEnabled && SessionData.themeModeAutoMode === "location" && !root.backendAvailable() + + function onGammaIsDayChanged() { + if (Theme.isLightMode !== DisplayService.gammaIsDay) { + Theme.setLightMode(DisplayService.gammaIsDay, true, true); + } + } + } + + Connections { + target: DMSService + + function onThemeAutoStateUpdate(data) { + if (!SessionData.themeModeAutoEnabled) { + return; + } + root.applyBackendState(data); + } + + function onConnectionStateChanged() { + if (DMSService.isConnected && SessionData.themeModeAutoMode === "time") { + root.syncTimeSchedule(); + } + + if (DMSService.isConnected && SessionData.themeModeAutoMode === "location") { + root.syncLocationSchedule(); + } + + if (root.backendAvailable() && SessionData.themeModeAutoEnabled) { + DMSService.sendRequest("theme.auto.getState", null, response => { + if (response && response.result) { + root.applyBackendState(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) { + root.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) { + root.log.warn("Theme automation: Failed to set location", locationResponse.error); + } + }); + } + }); + } else { + root.log.warn("Theme automation: No location configured"); + } + } + } + } + + Connections { + target: SessionService + enabled: SessionData.themeModeAutoEnabled + + function onSessionUnlocked() { + root.refresh(); + } + + function onSessionResumed() { + root.refresh(); + } + } + + function refresh() { + if (!backendAvailable()) { + evaluate(); + return; + } + DMSService.sendRequest("theme.auto.trigger", {}); + } + + function backendAvailable() { + return typeof DMSService !== "undefined" && DMSService.isConnected && Array.isArray(DMSService.capabilities) && DMSService.capabilities.includes("theme.auto"); + } + + function applyBackendState(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 && Theme.isLightMode !== state.isLight) { + Theme.setLightMode(state.isLight, true, true); + } + } + + function syncTimeSchedule() { + 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 syncLocationSchedule() { + 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 evaluate() { + if (typeof SessionData === "undefined" || !SessionData.themeModeAutoEnabled) { + return; + } + + if (backendAvailable()) { + DMSService.sendRequest("theme.auto.getState", null, response => { + if (response && response.result) { + applyBackendState(response.result); + } + }); + return; + } + + const mode = SessionData.themeModeAutoMode; + + if (mode === "location") { + evaluateLocation(); + } else { + evaluateTime(); + } + } + + function evaluateLocation() { + if (typeof DisplayService !== "undefined") { + const shouldBeLight = DisplayService.gammaIsDay; + if (Theme.isLightMode !== shouldBeLight) { + Theme.setLightMode(shouldBeLight, true, true); + } + return; + } + + if (!SessionData.nightModeUseIPLocation && SessionData.latitude !== 0.0 && SessionData.longitude !== 0.0) { + const shouldBeLight = calculateIsDaytime(SessionData.latitude, SessionData.longitude); + if (Theme.isLightMode !== shouldBeLight) { + Theme.setLightMode(shouldBeLight, true, true); + } + return; + } + + if (root.active) { + if (SessionData.nightModeUseIPLocation) { + log.warn("Theme automation: Waiting for IP location from backend"); + } else { + log.warn("Theme automation: Location mode requires coordinates"); + } + } + } + + function evaluateTime() { + 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 (Theme.isLightMode !== shouldBeLight) { + Theme.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; + } + if (cosHourAngle < -1) { + return true; + } + + 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; + } + + 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.evaluate(); + } else { + retryCount++; + if (retryCount >= 10) { + stop(); + retryCount = 0; + } + } + } + } + + function start() { + root.active = true; + + syncTimeSchedule(); + syncLocationSchedule(); + + const sent = sendLocationToBackend(); + + if (!sent && typeof SessionData !== "undefined" && SessionData.themeModeAutoMode === "location") { + locationRetryTimer.start(); + } else { + evaluate(); + } + } + + function stop() { + root.active = false; + if (typeof DMSService !== "undefined" && DMSService.isConnected) { + DMSService.sendRequest("theme.auto.setEnabled", { + "enabled": false + }); + } + } +} diff --git a/quickshell/Services/ToastService.qml b/quickshell/Services/ToastService.qml index 67bd149bd..bca87e506 100644 --- a/quickshell/Services/ToastService.qml +++ b/quickshell/Services/ToastService.qml @@ -1,9 +1,10 @@ pragma Singleton - pragma ComponentBehavior: Bound import QtQuick import Quickshell +import qs.Common +import qs.Common.settings Singleton { id: root @@ -26,151 +27,149 @@ Singleton { readonly property var stickyCategories: ["greeter-autologin-sync"] function isStickyCategory(category) { - return category && stickyCategories.indexOf(category) >= 0 + return category && stickyCategories.indexOf(category) >= 0; } function showToast(message, level = levelInfo, details = "", command = "", category = "") { - const now = Date.now() - const messageKey = message + level + const now = Date.now(); + const messageKey = message + level; if (level === levelError) { - const lastTime = lastErrorTime[messageKey] || 0 + const lastTime = lastErrorTime[messageKey] || 0; if (now - lastTime < errorThrottleMs) { - return + return; } - lastErrorTime[messageKey] = now + lastErrorTime[messageKey] = now; } if (category && level === levelError) { if (currentCategory === category && toastVisible && currentLevel === levelError) { - currentMessage = message - currentDetails = details || "" - currentCommand = command || "" - hasDetails = currentDetails.length > 0 || currentCommand.length > 0 - resetToastState() + currentMessage = message; + currentDetails = details || ""; + currentCommand = command || ""; + hasDetails = currentDetails.length > 0 || currentCommand.length > 0; + resetToastState(); if (hasDetails) { - toastTimer.interval = 8000 + toastTimer.interval = 8000; } else { - toastTimer.interval = 5000 + toastTimer.interval = 5000; } - toastTimer.restart() - return + toastTimer.restart(); + return; } - toastQueue = toastQueue.filter(t => t.category !== category) + toastQueue = toastQueue.filter(t => t.category !== category); } - const isDuplicate = toastQueue.some(toast => - toast.message === message && toast.level === level - ) + const isDuplicate = toastQueue.some(toast => toast.message === message && toast.level === level); if (isDuplicate) { - return + return; } if (toastQueue.length >= maxQueueSize) { if (level === levelError) { - toastQueue = toastQueue.filter(t => t.level !== levelError).slice(0, maxQueueSize - 1) + toastQueue = toastQueue.filter(t => t.level !== levelError).slice(0, maxQueueSize - 1); } else { - return + return; } } toastQueue.push({ - "message": message, - "level": level, - "details": details, - "command": command, - "category": category - }) + "message": message, + "level": level, + "details": details, + "command": command, + "category": category + }); if (!toastVisible) { - processQueue() + processQueue(); } } function showInfo(message, details = "", command = "", category = "") { - showToast(message, levelInfo, details, command, category) + showToast(message, levelInfo, details, command, category); } function showWarning(message, details = "", command = "", category = "") { - showToast(message, levelWarn, details, command, category) + showToast(message, levelWarn, details, command, category); } function showError(message, details = "", command = "", category = "") { - showToast(message, levelError, details, command, category) + showToast(message, levelError, details, command, category); } function dismissCategory(category) { if (!category) { - return + return; } if (currentCategory === category && toastVisible) { - hideToast() - return + hideToast(); + return; } - toastQueue = toastQueue.filter(t => t.category !== category) + toastQueue = toastQueue.filter(t => t.category !== category); } function hideToast() { - toastVisible = false - currentMessage = "" - currentDetails = "" - currentCommand = "" - currentCategory = "" - hasDetails = false - currentLevel = levelInfo - toastTimer.stop() - resetToastState() + toastVisible = false; + currentMessage = ""; + currentDetails = ""; + currentCommand = ""; + currentCategory = ""; + hasDetails = false; + currentLevel = levelInfo; + toastTimer.stop(); + resetToastState(); if (toastQueue.length > 0) { - processQueue() + processQueue(); } } function processQueue() { if (toastQueue.length === 0) { - return + return; } - const toast = toastQueue.shift() - currentMessage = toast.message - currentLevel = toast.level - currentDetails = toast.details || "" - currentCommand = toast.command || "" - currentCategory = toast.category || "" - hasDetails = currentDetails.length > 0 || currentCommand.length > 0 - toastVisible = true - resetToastState() + const toast = toastQueue.shift(); + currentMessage = toast.message; + currentLevel = toast.level; + currentDetails = toast.details || ""; + currentCommand = toast.command || ""; + currentCategory = toast.category || ""; + hasDetails = currentDetails.length > 0 || currentCommand.length > 0; + toastVisible = true; + resetToastState(); if (isStickyCategory(toast.category)) { - toastTimer.stop() + toastTimer.stop(); } else if (toast.level === levelError && hasDetails) { - toastTimer.interval = 8000 - toastTimer.start() + toastTimer.interval = 8000; + toastTimer.start(); } else { - toastTimer.interval = toast.level === levelError ? 5000 : toast.level === levelWarn ? 3000 : 1500 - toastTimer.start() + toastTimer.interval = toast.level === levelError ? 5000 : toast.level === levelWarn ? 3000 : 1500; + toastTimer.start(); } } signal resetToastState function stopTimer() { - toastTimer.stop() + toastTimer.stop(); } function restartTimer() { if (isStickyCategory(currentCategory)) { - return + return; } if (hasDetails && currentLevel === levelError) { - toastTimer.interval = 8000 - toastTimer.restart() + toastTimer.interval = 8000; + toastTimer.restart(); } } function clearWallpaperError() { - wallpaperErrorStatus = "" + wallpaperErrorStatus = ""; } Timer { @@ -181,4 +180,34 @@ Singleton { repeat: false onTriggered: hideToast() } + + Connections { + target: SessionData + + function onLoadErrorOccurred(file, message) { + root.showError(I18n.tr("Failed to parse %1").arg(file), message); + } + } + + Connections { + target: Processes + + function onToastRequested(severity, title, body, command, category) { + switch (severity) { + case root.levelWarn: + root.showWarning(title, body, command, category); + break; + case root.levelError: + root.showError(title, body, command, category); + break; + default: + root.showInfo(title, body, command, category); + break; + } + } + + function onToastCategoryDismissed(category) { + root.dismissCategory(category); + } + } } diff --git a/quickshell/Services/TrashService.qml b/quickshell/Services/TrashService.qml index ea31d925c..4eed474af 100644 --- a/quickshell/Services/TrashService.qml +++ b/quickshell/Services/TrashService.qml @@ -53,6 +53,7 @@ Singleton { } Component.onCompleted: { + Paths.trashHandler = (path, callback) => trashPath(path, callback); detectProc.running = true; refreshCount(); } diff --git a/quickshell/Services/WallpaperCyclingService.qml b/quickshell/Services/WallpaperCyclingService.qml index 2d58230a3..db88b1e9a 100644 --- a/quickshell/Services/WallpaperCyclingService.qml +++ b/quickshell/Services/WallpaperCyclingService.qml @@ -1,6 +1,7 @@ pragma Singleton pragma ComponentBehavior: Bound +import QtCore import QtQuick import Quickshell import Quickshell.Io @@ -281,4 +282,149 @@ Singleton { onStreamFinished: root.applyCycledWallpaper(text, prevCyclingProcess.currentWallpaper, prevCyclingProcess.targetScreenName, prevCyclingProcess.goToPrevious) } } + + Connections { + target: SessionData + + function onLoaded() { + root.updateCyclingState(); + } + } + + IpcHandler { + target: "wallpaper" + + function get(): string { + if (SessionData.perMonitorWallpaper) { + return "ERROR: Per-monitor mode enabled. Use getFor(screenName) instead."; + } + return SessionData.wallpaperPath || ""; + } + + function set(path: string): string { + if (SessionData.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 { + SessionData.setWallpaper(absolutePath); + return "SUCCESS: Wallpaper set to " + absolutePath; + } catch (e) { + return "ERROR: Failed to set wallpaper: " + e.toString(); + } + } + + function clear(): string { + SessionData.setWallpaper(""); + SessionData.setPerMonitorWallpaper(false); + SessionData.monitorWallpapers = {}; + SessionData.saveSettings(); + return "SUCCESS: All wallpapers cleared"; + } + + function next(): string { + if (SessionData.perMonitorWallpaper) { + return "ERROR: Per-monitor mode enabled. Use nextFor(screenName) instead."; + } + + if (!SessionData.wallpaperPath) { + return "ERROR: No wallpaper set"; + } + + try { + root.cycleNextManually(); + return "SUCCESS: Cycling to next wallpaper"; + } catch (e) { + return "ERROR: Failed to cycle wallpaper: " + e.toString(); + } + } + + function prev(): string { + if (SessionData.perMonitorWallpaper) { + return "ERROR: Per-monitor mode enabled. Use prevFor(screenName) instead."; + } + + if (!SessionData.wallpaperPath) { + return "ERROR: No wallpaper set"; + } + + try { + root.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 SessionData.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 (!SessionData.perMonitorWallpaper) { + SessionData.setPerMonitorWallpaper(true); + } + SessionData.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 = SessionData.getMonitorWallpaper(screenName); + if (!currentWallpaper) { + return "ERROR: No wallpaper set for " + screenName; + } + + try { + root.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 = SessionData.getMonitorWallpaper(screenName); + if (!currentWallpaper) { + return "ERROR: No wallpaper set for " + screenName; + } + + try { + root.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/Widgets/DankAlbumArt.qml b/quickshell/Widgets/DankAlbumArt.qml index 351704484..67b90fe5e 100644 --- a/quickshell/Widgets/DankAlbumArt.qml +++ b/quickshell/Widgets/DankAlbumArt.qml @@ -1,13 +1,14 @@ import QtQuick import Quickshell.Services.Mpris import qs.Common -import qs.Services Item { id: root property MprisPlayer activePlayer - property string artUrl: TrackArtService.resolvedArtUrl + property string artUrl: "" + property color accentColor: Theme.primary + property var cavaService: null property string lastValidArtUrl: "" // Live mpris url — always valid for the current track; fallback so art is never blank. readonly property string rawArtUrl: { @@ -46,7 +47,7 @@ Item { readonly property real blobSpinSpeed: 0.03 readonly property bool onScreen: visible && (Window.window?.visible ?? false) - readonly property bool blobActive: CavaService.cavaAvailable && onScreen && activePlayer?.playbackState === MprisPlaybackState.Playing && showAnimation && albumArtStatus === Image.Ready + readonly property bool blobActive: (cavaService?.cavaAvailable ?? false) && onScreen && activePlayer?.playbackState === MprisPlaybackState.Playing && showAnimation && albumArtStatus === Image.Ready property var smoothedBands: [0, 0, 0, 0, 0, 0] property var slowBands: [0, 0, 0, 0, 0, 0] property var bandTargets: [0, 0, 0, 0, 0, 0] @@ -110,7 +111,7 @@ Item { } function updateBands() { - const vals = CavaService.values; + const vals = cavaService?.values; if (!vals || vals.length < 6) return; @@ -166,16 +167,16 @@ Item { } Loader { - active: root.onScreen && activePlayer?.playbackState === MprisPlaybackState.Playing && showAnimation + active: root.cavaService && root.onScreen && activePlayer?.playbackState === MprisPlaybackState.Playing && showAnimation sourceComponent: Component { Ref { - service: CavaService + service: root.cavaService } } } Connections { - target: CavaService + target: root.cavaService enabled: blobEffect.visible function onValuesChanged() { root.updateBands(); @@ -210,8 +211,7 @@ Item { property real energy: 0 property vector4d bandsA: Qt.vector4d(0, 0, 0, 0) property vector2d bandsB: Qt.vector2d(0, 0) - readonly property color accentColor: MediaAccentService.accent - property vector4d fillColor: Qt.vector4d(accentColor.r, accentColor.g, accentColor.b, accentColor.a) + property vector4d fillColor: Qt.vector4d(root.accentColor.r, root.accentColor.g, root.accentColor.b, root.accentColor.a) Behavior on activation { NumberAnimation { @@ -231,7 +231,7 @@ Item { z: 1 imageSource: root._mainSrc fallbackIcon: "album" - border.color: MediaAccentService.accent + border.color: root.accentColor border.width: 2 onImageStatusChanged: { @@ -251,7 +251,7 @@ Item { anchors.centerIn: parent z: 2 fallbackIcon: "" - border.color: MediaAccentService.accent + border.color: root.accentColor border.width: 2 opacity: 0 visible: opacity > 0 diff --git a/quickshell/Widgets/DankPopoutStandalone.qml b/quickshell/Widgets/DankPopoutStandalone.qml index bff5fe7bd..1c98270bd 100644 --- a/quickshell/Widgets/DankPopoutStandalone.qml +++ b/quickshell/Widgets/DankPopoutStandalone.qml @@ -93,14 +93,8 @@ Item { "label": "popouts" }) - function _frameEdgeInset(side) { - if (!screen) - return 0; - return SettingsData.frameEdgeInsetForSide(screen, side); - } - function _frameGapMargin(side) { - return _frameEdgeInset(side) + Theme.popupDistance; + return SettingsData.frameEdgeInsetForSide(screen, side) + Theme.popupDistance; } function _edgeClearance(side, popupGap, adjacentInset) { diff --git a/quickshell/Widgets/DankSeekbar.qml b/quickshell/Widgets/DankSeekbar.qml index e8250a2f7..17c1fd7f7 100644 --- a/quickshell/Widgets/DankSeekbar.qml +++ b/quickshell/Widgets/DankSeekbar.qml @@ -1,14 +1,16 @@ import QtQuick import Quickshell.Services.Mpris import qs.Common -import qs.Services import qs.Widgets Item { id: root property MprisPlayer activePlayer - readonly property real stableLength: MprisController.activePlayerStableLength + property real stableLength: 0 + property color accentColor: Theme.primary + property color accentTrackColor: Theme.withAlpha(accentColor, 0.28) + property color accentSubtleColor: Theme.withAlpha(accentColor, 0.55) property real seekPreviewRatio: -1 readonly property real playerValue: { @@ -142,10 +144,10 @@ Item { value: root.value actualValue: root.playerValue showActualPlaybackState: root.isSeeking - fillColor: MediaAccentService.accent - playheadColor: MediaAccentService.accent - trackColor: MediaAccentService.accentTrack - actualProgressColor: MediaAccentService.accentSubtle + fillColor: root.accentColor + playheadColor: root.accentColor + trackColor: root.accentTrackColor + actualProgressColor: root.accentSubtleColor isPlaying: activePlayer && activePlayer.playbackState === MprisPlaybackState.Playing onFrameTicked: { if (!root.isSeeking) @@ -186,8 +188,8 @@ Item { Item { property real lineWidth: 3 property color trackColor: Theme.withAlpha(Theme.surfaceVariant, 0.40) - property color fillColor: MediaAccentService.accent - property color playheadColor: MediaAccentService.accent + property color fillColor: root.accentColor + property color playheadColor: root.accentColor property color actualProgressColor: Theme.onSurface_38 readonly property real midY: height / 2 diff --git a/quickshell/Widgets/DankSlideout.qml b/quickshell/Widgets/DankSlideout.qml index 8b8697ddb..9a4c52917 100644 --- a/quickshell/Widgets/DankSlideout.qml +++ b/quickshell/Widgets/DankSlideout.qml @@ -177,7 +177,13 @@ PanelWindow { layer.textureSize: Qt.size(0, 0) opacity: 1 - readonly property color slideoutSurfaceColor: root.customTransparency >= 0 ? Theme.withAlpha(Theme.surfaceContainer, root.customTransparency) : Theme.popupLayerColor(Theme.surfaceContainer) + readonly property color slideoutSurfaceColor: { + if (root.customTransparency >= 0) + return Theme.withAlpha(Theme.surfaceContainer, root.customTransparency); + if (Theme.isConnectedEffect) + return Theme.connectedSurfaceColor; + return Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency); + } anchors.top: parent.top anchors.bottom: parent.bottom diff --git a/quickshell/shell.qml b/quickshell/shell.qml index 1ed0f0111..64a25d569 100644 --- a/quickshell/shell.qml +++ b/quickshell/shell.qml @@ -10,6 +10,7 @@ import QtQuick import Quickshell import qs.Common +import qs.DankCommon.Common as DC import qs.Modules import qs.Services @@ -20,6 +21,13 @@ ShellRoot { Component.onCompleted: { Quickshell.watchFiles = !disableHotReload; + DC.Style.theme = Theme; + DC.Style.settings = SettingsData; + DC.I18n.backend = I18n; + DC.Paths.backend = Paths; + DC.Log.backend = Log; + DC.Host.session = SessionService; + DC.Host.cache = CacheData; } Loader {