summaryrefslogtreecommitdiff
path: root/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Common
diff options
context:
space:
mode:
Diffstat (limited to 'raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Common')
-rw-r--r--raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Common/Anims.qml25
-rw-r--r--raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Common/AppUsageHistoryData.qml123
-rw-r--r--raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Common/Appearance.qml66
-rw-r--r--raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Common/CacheData.qml227
-rw-r--r--raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Common/CacheUtils.qml40
-rw-r--r--raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Common/DankAnim.qml9
-rw-r--r--raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Common/DankColorAnim.qml9
-rw-r--r--raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Common/DankSocket.qml62
-rw-r--r--raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Common/ElevationShadow.qml54
-rw-r--r--raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Common/I18n.qml138
-rw-r--r--raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Common/KeyUtils.js178
-rw-r--r--raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Common/KeybindActions.js1329
-rw-r--r--raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Common/ListViewTransitions.qml46
-rw-r--r--raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Common/ModalManager.qml35
-rw-r--r--raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Common/OSDManager.qml46
-rw-r--r--raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Common/Paths.qml131
-rw-r--r--raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Common/PopoutManager.qml186
-rw-r--r--raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Common/Proc.qml141
-rw-r--r--raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Common/Ref.qml9
-rw-r--r--raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Common/SessionData.qml1437
-rw-r--r--raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Common/SettingsData.qml2840
-rw-r--r--raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Common/StockThemes.js468
-rw-r--r--raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Common/Theme.qml2410
-rw-r--r--raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Common/TrayMenuManager.qml36
-rw-r--r--raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Common/fzf.js1308
-rw-r--r--raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Common/markdown2html.js117
-rw-r--r--raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Common/settings/Lists.qml94
-rw-r--r--raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Common/settings/Processes.qml602
-rw-r--r--raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Common/settings/SessionSpec.js109
-rw-r--r--raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Common/settings/SessionStore.js100
-rw-r--r--raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Common/settings/SettingsSpec.js553
-rw-r--r--raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Common/settings/SettingsStore.js275
-rw-r--r--raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Common/suncalc.js308
33 files changed, 13511 insertions, 0 deletions
diff --git a/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Common/Anims.qml b/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Common/Anims.qml
new file mode 100644
index 0000000..349e991
--- /dev/null
+++ b/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Common/Anims.qml
@@ -0,0 +1,25 @@
+pragma Singleton
+pragma ComponentBehavior: Bound
+
+import QtQuick
+import Quickshell
+
+Singleton {
+ id: root
+
+ readonly property int durShort: 200
+ readonly property int durMed: 450
+ readonly property int durLong: 600
+
+ readonly property int slidePx: 80
+
+ readonly property var emphasized: [0.05, 0.00, 0.133333, 0.06, 0.166667, 0.40, 0.208333, 0.82, 0.25, 1.00, 1.00, 1.00]
+
+ readonly property var emphasizedDecel: [0.05, 0.70, 0.10, 1.00, 1.00, 1.00]
+
+ readonly property var emphasizedAccel: [0.30, 0.00, 0.80, 0.15, 1.00, 1.00]
+
+ readonly property var standard: [0.20, 0.00, 0.00, 1.00, 1.00, 1.00]
+ readonly property var standardDecel: [0.00, 0.00, 0.00, 1.00, 1.00, 1.00]
+ readonly property var standardAccel: [0.30, 0.00, 1.00, 1.00, 1.00, 1.00]
+}
diff --git a/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Common/AppUsageHistoryData.qml b/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Common/AppUsageHistoryData.qml
new file mode 100644
index 0000000..1077f38
--- /dev/null
+++ b/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Common/AppUsageHistoryData.qml
@@ -0,0 +1,123 @@
+pragma Singleton
+pragma ComponentBehavior: Bound
+
+import QtCore
+import QtQuick
+import Quickshell
+import Quickshell.Io
+
+Singleton {
+ id: root
+
+ property var appUsageRanking: {}
+ property bool _saving: false
+
+ Component.onCompleted: {
+ loadSettings();
+ }
+
+ function loadSettings() {
+ parseSettings(settingsFile.text());
+ }
+
+ function parseSettings(content) {
+ try {
+ if (content && content.trim()) {
+ var settings = JSON.parse(content);
+ appUsageRanking = settings.appUsageRanking || {};
+ }
+ } catch (e) {}
+ }
+
+ function saveSettings() {
+ settingsFile.setText(JSON.stringify({
+ "appUsageRanking": appUsageRanking
+ }, null, 2));
+ }
+
+ function addAppUsage(app) {
+ if (!app)
+ return;
+ var appId = app.id || (app.execString || app.exec || "");
+ if (!appId)
+ return;
+ var currentRanking = Object.assign({}, appUsageRanking);
+
+ if (currentRanking[appId]) {
+ currentRanking[appId].usageCount = (currentRanking[appId].usageCount || 1) + 1;
+ currentRanking[appId].lastUsed = Date.now();
+ currentRanking[appId].icon = app.icon ? String(app.icon) : (currentRanking[appId].icon || "application-x-executable");
+ currentRanking[appId].name = app.name || currentRanking[appId].name || "";
+ } else {
+ currentRanking[appId] = {
+ "name": app.name || "",
+ "exec": app.execString || app.exec || "",
+ "icon": app.icon ? String(app.icon) : "application-x-executable",
+ "comment": app.comment || "",
+ "usageCount": 1,
+ "lastUsed": Date.now()
+ };
+ }
+
+ appUsageRanking = currentRanking;
+ _saving = true;
+ saveSettings();
+ _saving = false;
+ }
+
+ function getRankedApps() {
+ var apps = [];
+ for (var appId in appUsageRanking) {
+ var appData = appUsageRanking[appId];
+ apps.push({
+ "id": appId,
+ "name": appData.name,
+ "exec": appData.exec,
+ "icon": appData.icon,
+ "comment": appData.comment,
+ "usageCount": appData.usageCount,
+ "lastUsed": appData.lastUsed
+ });
+ }
+
+ return apps.sort(function (a, b) {
+ if (a.usageCount !== b.usageCount)
+ return b.usageCount - a.usageCount;
+ return a.name.localeCompare(b.name);
+ });
+ }
+
+ function cleanupAppUsageRanking(availableAppIds) {
+ var currentRanking = Object.assign({}, appUsageRanking);
+ var hasChanges = false;
+
+ for (var appId in currentRanking) {
+ if (availableAppIds.indexOf(appId) === -1) {
+ delete currentRanking[appId];
+ hasChanges = true;
+ }
+ }
+
+ if (hasChanges) {
+ appUsageRanking = currentRanking;
+ _saving = true;
+ saveSettings();
+ _saving = false;
+ }
+ }
+
+ FileView {
+ id: settingsFile
+
+ path: StandardPaths.writableLocation(StandardPaths.GenericStateLocation) + "/DankMaterialShell/appusage.json"
+ blockLoading: true
+ blockWrites: true
+ watchChanges: true
+ onLoaded: {
+ if (root._saving)
+ return;
+ parseSettings(settingsFile.text());
+ }
+ onLoadFailed: error => {}
+ }
+}
diff --git a/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Common/Appearance.qml b/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Common/Appearance.qml
new file mode 100644
index 0000000..3c22566
--- /dev/null
+++ b/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Common/Appearance.qml
@@ -0,0 +1,66 @@
+pragma Singleton
+pragma ComponentBehavior: Bound
+
+import QtQuick
+import Quickshell
+
+Singleton {
+ id: root
+
+ readonly property Rounding rounding: Rounding {}
+ readonly property Spacing spacing: Spacing {}
+ readonly property FontSize fontSize: FontSize {}
+ readonly property Anim anim: Anim {}
+
+ component Rounding: QtObject {
+ readonly property int small: 8
+ readonly property int normal: 12
+ readonly property int large: 16
+ readonly property int extraLarge: 24
+ readonly property int full: 1000
+ }
+
+ component Spacing: QtObject {
+ readonly property int small: 4
+ readonly property int normal: 8
+ readonly property int large: 12
+ readonly property int extraLarge: 16
+ readonly property int huge: 24
+ }
+
+ component FontSize: QtObject {
+ readonly property int small: 12
+ readonly property int normal: 14
+ readonly property int large: 16
+ readonly property int extraLarge: 20
+ readonly property int huge: 24
+ }
+
+ component AnimCurves: QtObject {
+ readonly property list<real> standard: [0.2, 0, 0, 1, 1, 1]
+ readonly property list<real> standardAccel: [0.3, 0, 1, 1, 1, 1]
+ readonly property list<real> standardDecel: [0, 0, 0, 1, 1, 1]
+ readonly property list<real> emphasized: [0.05, 0, 2 / 15, 0.06, 1
+ / 6, 0.4, 5 / 24, 0.82, 0.25, 1, 1, 1]
+ readonly property list<real> emphasizedAccel: [0.3, 0, 0.8, 0.15, 1, 1]
+ readonly property list<real> emphasizedDecel: [0.05, 0.7, 0.1, 1, 1, 1]
+ readonly property list<real> expressiveFastSpatial: [0.42, 1.67, 0.21, 0.9, 1, 1]
+ readonly property list<real> expressiveDefaultSpatial: [0.38, 1.21, 0.22, 1, 1, 1]
+ readonly property list<real> expressiveEffects: [0.34, 0.8, 0.34, 1, 1, 1]
+ }
+
+ component AnimDurations: QtObject {
+ readonly property int quick: 150
+ readonly property int normal: 300
+ readonly property int slow: 500
+ readonly property int extraSlow: 1000
+ readonly property int expressiveFastSpatial: 350
+ readonly property int expressiveDefaultSpatial: 500
+ readonly property int expressiveEffects: 200
+ }
+
+ component Anim: QtObject {
+ readonly property AnimCurves curves: AnimCurves {}
+ readonly property AnimDurations durations: AnimDurations {}
+ }
+}
diff --git a/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Common/CacheData.qml b/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Common/CacheData.qml
new file mode 100644
index 0000000..6de7a8b
--- /dev/null
+++ b/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Common/CacheData.qml
@@ -0,0 +1,227 @@
+pragma Singleton
+pragma ComponentBehavior: Bound
+
+import QtCore
+import QtQuick
+import Quickshell
+import Quickshell.Io
+
+Singleton {
+ id: root
+
+ readonly property int cacheConfigVersion: 1
+
+ readonly property bool isGreeterMode: Quickshell.env("DMS_RUN_GREETER") === "1" || Quickshell.env("DMS_RUN_GREETER") === "true"
+
+ readonly property string _stateUrl: StandardPaths.writableLocation(StandardPaths.GenericCacheLocation)
+ readonly property string _stateDir: Paths.strip(_stateUrl)
+
+ property bool _loading: false
+
+ property string wallpaperLastPath: ""
+ property string profileLastPath: ""
+
+ property var fileBrowserSettings: ({
+ "wallpaper": {
+ "lastPath": "",
+ "viewMode": "grid",
+ "sortBy": "name",
+ "sortAscending": true,
+ "iconSizeIndex": 1,
+ "showSidebar": true
+ },
+ "profile": {
+ "lastPath": "",
+ "viewMode": "grid",
+ "sortBy": "name",
+ "sortAscending": true,
+ "iconSizeIndex": 1,
+ "showSidebar": true
+ },
+ "notepad_save": {
+ "lastPath": "",
+ "viewMode": "list",
+ "sortBy": "name",
+ "sortAscending": true,
+ "iconSizeIndex": 1,
+ "showSidebar": true
+ },
+ "notepad_load": {
+ "lastPath": "",
+ "viewMode": "list",
+ "sortBy": "name",
+ "sortAscending": true,
+ "iconSizeIndex": 1,
+ "showSidebar": true
+ },
+ "generic": {
+ "lastPath": "",
+ "viewMode": "list",
+ "sortBy": "name",
+ "sortAscending": true,
+ "iconSizeIndex": 1,
+ "showSidebar": true
+ },
+ "default": {
+ "lastPath": "",
+ "viewMode": "list",
+ "sortBy": "name",
+ "sortAscending": true,
+ "iconSizeIndex": 1,
+ "showSidebar": true
+ }
+ })
+
+ Component.onCompleted: {
+ if (!isGreeterMode) {
+ loadCache();
+ }
+ }
+
+ function loadCache() {
+ _loading = true;
+ parseCache(cacheFile.text());
+ _loading = false;
+ }
+
+ function parseCache(content) {
+ _loading = true;
+ try {
+ if (content && content.trim()) {
+ const cache = JSON.parse(content);
+
+ wallpaperLastPath = cache.wallpaperLastPath !== undefined ? cache.wallpaperLastPath : "";
+ profileLastPath = cache.profileLastPath !== undefined ? cache.profileLastPath : "";
+
+ if (cache.fileBrowserSettings !== undefined) {
+ fileBrowserSettings = cache.fileBrowserSettings;
+ } else if (cache.fileBrowserViewMode !== undefined) {
+ fileBrowserSettings = {
+ "wallpaper": {
+ "lastPath": cache.wallpaperLastPath || "",
+ "viewMode": cache.fileBrowserViewMode || "grid",
+ "sortBy": cache.fileBrowserSortBy || "name",
+ "sortAscending": cache.fileBrowserSortAscending !== undefined ? cache.fileBrowserSortAscending : true,
+ "iconSizeIndex": cache.fileBrowserIconSizeIndex !== undefined ? cache.fileBrowserIconSizeIndex : 1,
+ "showSidebar": cache.fileBrowserShowSidebar !== undefined ? cache.fileBrowserShowSidebar : true
+ },
+ "profile": {
+ "lastPath": cache.profileLastPath || "",
+ "viewMode": cache.fileBrowserViewMode || "grid",
+ "sortBy": cache.fileBrowserSortBy || "name",
+ "sortAscending": cache.fileBrowserSortAscending !== undefined ? cache.fileBrowserSortAscending : true,
+ "iconSizeIndex": cache.fileBrowserIconSizeIndex !== undefined ? cache.fileBrowserIconSizeIndex : 1,
+ "showSidebar": cache.fileBrowserShowSidebar !== undefined ? cache.fileBrowserShowSidebar : true
+ },
+ "file": {
+ "lastPath": "",
+ "viewMode": "list",
+ "sortBy": "name",
+ "sortAscending": true,
+ "iconSizeIndex": 1,
+ "showSidebar": true
+ }
+ };
+ }
+
+ if (cache.configVersion === undefined) {
+ migrateFromUndefinedToV1(cache);
+ cleanupUnusedKeys();
+ saveCache();
+ }
+ }
+ } catch (e) {
+ console.warn("CacheData: Failed to parse cache:", e.message);
+ } finally {
+ _loading = false;
+ }
+ }
+
+ function saveCache() {
+ if (_loading)
+ return;
+ cacheFile.setText(JSON.stringify({
+ "wallpaperLastPath": wallpaperLastPath,
+ "profileLastPath": profileLastPath,
+ "fileBrowserSettings": fileBrowserSettings,
+ "configVersion": cacheConfigVersion
+ }, null, 2));
+ }
+
+ function migrateFromUndefinedToV1(cache) {
+ console.info("CacheData: Migrating configuration from undefined to version 1");
+ }
+
+ function cleanupUnusedKeys() {
+ const validKeys = ["wallpaperLastPath", "profileLastPath", "fileBrowserSettings", "configVersion"];
+
+ try {
+ const content = cacheFile.text();
+ if (!content || !content.trim())
+ return;
+ const cache = JSON.parse(content);
+ let needsSave = false;
+
+ for (const key in cache) {
+ if (!validKeys.includes(key)) {
+ console.log("CacheData: Removing unused key:", key);
+ delete cache[key];
+ needsSave = true;
+ }
+ }
+
+ if (needsSave) {
+ cacheFile.setText(JSON.stringify(cache, null, 2));
+ }
+ } catch (e) {
+ console.warn("CacheData: Failed to cleanup unused keys:", e.message);
+ }
+ }
+
+ function loadLauncherCache() {
+ try {
+ var content = launcherCacheFile.text();
+ if (content && content.trim())
+ return JSON.parse(content);
+ } catch (e) {
+ console.warn("CacheData: Failed to parse launcher cache:", e.message);
+ }
+ return null;
+ }
+
+ function saveLauncherCache(sections) {
+ if (_loading)
+ return;
+ launcherCacheFile.setText(JSON.stringify(sections));
+ }
+
+ FileView {
+ id: launcherCacheFile
+
+ path: isGreeterMode ? "" : _stateDir + "/DankMaterialShell/launcher_cache.json"
+ blockLoading: true
+ blockWrites: true
+ atomicWrites: true
+ watchChanges: false
+ }
+
+ FileView {
+ id: cacheFile
+
+ path: isGreeterMode ? "" : _stateDir + "/DankMaterialShell/cache.json"
+ blockLoading: true
+ blockWrites: true
+ atomicWrites: true
+ watchChanges: !isGreeterMode
+ onLoaded: {
+ if (!isGreeterMode) {
+ parseCache(cacheFile.text());
+ }
+ }
+ onLoadFailed: error => {
+ if (!isGreeterMode) {
+ console.info("CacheData: No cache file found, starting fresh");
+ }
+ }
+ }
+}
diff --git a/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Common/CacheUtils.qml b/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Common/CacheUtils.qml
new file mode 100644
index 0000000..1f03f0e
--- /dev/null
+++ b/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Common/CacheUtils.qml
@@ -0,0 +1,40 @@
+pragma Singleton
+pragma ComponentBehavior: Bound
+import Quickshell
+
+Singleton {
+ id: root
+
+ // Clear all image cache
+ function clearImageCache() {
+ Quickshell.execDetached(["rm", "-rf", Paths.stringify(Paths.imagecache)]);
+ Paths.mkdir(Paths.imagecache);
+ }
+
+ // Clear cache older than specified minutes
+ function clearOldCache(ageInMinutes) {
+ Quickshell.execDetached(["find", Paths.stringify(Paths.imagecache), "-name", "*.png", "-mmin", `+${ageInMinutes}`, "-delete"]);
+ }
+
+ // Clear cache for specific size
+ function clearCacheForSize(size) {
+ Quickshell.execDetached(["find", Paths.stringify(Paths.imagecache), "-name", `*@${size}x${size}.png`, "-delete"]);
+ }
+
+ // Get cache size in MB
+ function getCacheSize(callback) {
+ var process = Qt.createQmlObject(`
+ import Quickshell.Io
+ Process {
+ command: ["du", "-sm", "${Paths.stringify(Paths.imagecache)}"]
+ running: true
+ stdout: StdioCollector {
+ onStreamFinished: {
+ var sizeMB = parseInt(text.split("\\t")[0]) || 0
+ callback(sizeMB)
+ }
+ }
+ }
+ `, root);
+ }
+}
diff --git a/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Common/DankAnim.qml b/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Common/DankAnim.qml
new file mode 100644
index 0000000..63d0c1c
--- /dev/null
+++ b/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Common/DankAnim.qml
@@ -0,0 +1,9 @@
+import QtQuick
+import qs.Common
+
+// Reusable NumberAnimation wrapper
+NumberAnimation {
+ duration: Theme.expressiveDurations.normal
+ easing.type: Easing.BezierSpline
+ easing.bezierCurve: Theme.expressiveCurves.standard
+}
diff --git a/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Common/DankColorAnim.qml b/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Common/DankColorAnim.qml
new file mode 100644
index 0000000..5bd5a9d
--- /dev/null
+++ b/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Common/DankColorAnim.qml
@@ -0,0 +1,9 @@
+import QtQuick
+import qs.Common
+
+// Reusable ColorAnimation wrapper
+ColorAnimation {
+ duration: Theme.expressiveDurations.normal
+ easing.type: Easing.BezierSpline
+ easing.bezierCurve: Theme.expressiveCurves.standard
+}
diff --git a/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Common/DankSocket.qml b/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Common/DankSocket.qml
new file mode 100644
index 0000000..93471ba
--- /dev/null
+++ b/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Common/DankSocket.qml
@@ -0,0 +1,62 @@
+import QtQuick
+import Quickshell.Io
+
+Item {
+ id: root
+
+ property alias path: socket.path
+ property alias parser: socket.parser
+ property bool connected: false
+
+ property int reconnectBaseMs: 400
+ property int reconnectMaxMs: 15000
+
+ property int _reconnectAttempt: 0
+
+ signal connectionStateChanged()
+
+ onConnectedChanged: {
+ socket.connected = connected
+ }
+
+ Socket {
+ id: socket
+
+ onConnectionStateChanged: {
+ root.connectionStateChanged()
+ if (connected) {
+ root._reconnectAttempt = 0
+ return
+ }
+ if (root.connected) {
+ root._scheduleReconnect()
+ }
+ }
+ }
+
+ Timer {
+ id: reconnectTimer
+ interval: 0
+ repeat: false
+ onTriggered: {
+ socket.connected = false
+ Qt.callLater(() => socket.connected = true)
+ }
+ }
+
+ function send(data) {
+ const json = typeof data === "string" ? data : JSON.stringify(data)
+ const message = json.endsWith("\n") ? json : json + "\n"
+ socket.write(message)
+ socket.flush()
+ }
+
+ function _scheduleReconnect() {
+ const pow = Math.min(_reconnectAttempt, 10)
+ const base = Math.min(reconnectBaseMs * Math.pow(2, pow), reconnectMaxMs)
+ const jitter = Math.floor(Math.random() * Math.floor(base / 4))
+ reconnectTimer.interval = base + jitter
+ reconnectTimer.restart()
+ _reconnectAttempt++
+ }
+}
diff --git a/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Common/ElevationShadow.qml b/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Common/ElevationShadow.qml
new file mode 100644
index 0000000..dab80cd
--- /dev/null
+++ b/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Common/ElevationShadow.qml
@@ -0,0 +1,54 @@
+pragma ComponentBehavior: Bound
+
+import QtQuick
+import QtQuick.Effects
+import qs.Common
+
+Item {
+ id: root
+
+ property var level: Theme.elevationLevel2
+ property string direction: Theme.elevationLightDirection
+ property real fallbackOffset: 4
+
+ property color targetColor: "white"
+ property real targetRadius: Theme.cornerRadius
+ property color borderColor: "transparent"
+ property real borderWidth: 0
+
+ property bool shadowEnabled: Theme.elevationEnabled
+ property real shadowBlurPx: level && level.blurPx !== undefined ? level.blurPx : 0
+ property real shadowSpreadPx: level && level.spreadPx !== undefined ? level.spreadPx : 0
+ property real shadowOffsetX: Theme.elevationOffsetXFor(level, direction, fallbackOffset)
+ property real shadowOffsetY: Theme.elevationOffsetYFor(level, direction, fallbackOffset)
+ property color shadowColor: Theme.elevationShadowColor(level)
+ property real shadowOpacity: 1
+ property real blurMax: Theme.elevationBlurMax
+
+ property alias sourceRect: sourceRect
+
+ layer.enabled: shadowEnabled
+
+ layer.effect: MultiEffect {
+ autoPaddingEnabled: true
+ shadowEnabled: true
+ blurEnabled: false
+ maskEnabled: false
+ shadowBlur: Math.max(0, Math.min(1, root.shadowBlurPx / Math.max(1, root.blurMax)))
+ shadowScale: 1 + (2 * root.shadowSpreadPx) / Math.max(1, Math.min(root.width, root.height))
+ shadowHorizontalOffset: root.shadowOffsetX
+ shadowVerticalOffset: root.shadowOffsetY
+ blurMax: root.blurMax
+ shadowColor: root.shadowColor
+ shadowOpacity: root.shadowOpacity
+ }
+
+ Rectangle {
+ id: sourceRect
+ anchors.fill: parent
+ radius: root.targetRadius
+ color: root.targetColor
+ border.color: root.borderColor
+ border.width: root.borderWidth
+ }
+}
diff --git a/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Common/I18n.qml b/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Common/I18n.qml
new file mode 100644
index 0000000..aae0009
--- /dev/null
+++ b/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Common/I18n.qml
@@ -0,0 +1,138 @@
+pragma Singleton
+pragma ComponentBehavior: Bound
+
+import QtQuick
+import Qt.labs.folderlistmodel
+import Quickshell
+import Quickshell.Io
+
+Singleton {
+ id: root
+
+ property string _resolvedLocale: "en"
+
+ readonly property string _rawLocale: SessionData.locale === "" ? Qt.locale().name : SessionData.locale
+ readonly property string _lang: _rawLocale.split(/[_-]/)[0]
+ readonly property var _candidates: {
+ const fullUnderscore = _rawLocale;
+ const fullHyphen = _rawLocale.replace("_", "-");
+ return [fullUnderscore, fullHyphen, _lang].filter(c => c && c !== "en");
+ }
+
+ readonly property var _rtlLanguages: ["ar", "he", "iw", "fa", "ur", "ps", "sd", "dv", "yi", "ku"]
+ readonly property bool isRtl: _rtlLanguages.includes(_lang)
+
+ readonly property url translationsFolder: Qt.resolvedUrl("../translations/poexports")
+
+ readonly property alias folder: dir.folder
+ property var presentLocales: ({
+ "en": Qt.locale("en")
+ })
+ property var translations: ({})
+ property bool translationsLoaded: false
+
+ property url _selectedPath: ""
+
+ FolderListModel {
+ id: dir
+ folder: root.translationsFolder
+ nameFilters: ["*.json"]
+ showDirs: false
+ showDotAndDotDot: false
+
+ onStatusChanged: if (status === FolderListModel.Ready) {
+ root._loadPresentLocales();
+ root._pickTranslation();
+ }
+ }
+
+ FileView {
+ id: translationLoader
+ path: root._selectedPath
+
+ onLoaded: {
+ try {
+ root.translations = JSON.parse(text());
+ root.translationsLoaded = true;
+ console.info(`I18n: Loaded translations for '${root._resolvedLocale}' (${Object.keys(root.translations).length} contexts)`);
+ } catch (e) {
+ console.warn(`I18n: Error parsing '${root._resolvedLocale}':`, e, "- falling back to English");
+ root._fallbackToEnglish();
+ }
+ }
+
+ onLoadFailed: error => {
+ console.warn(`I18n: Failed to load '${root._resolvedLocale}' (${error}), ` + "falling back to English");
+ root._fallbackToEnglish();
+ }
+ }
+
+ function locale() {
+ if (SessionData.timeLocale)
+ return Qt.locale(SessionData.timeLocale);
+ return Qt.locale();
+ }
+
+ function _loadPresentLocales() {
+ if (Object.keys(presentLocales).length > 1) {
+ return; // already loaded
+ }
+ for (let i = 0; i < dir.count; i++) {
+ const name = dir.get(i, "fileName"); // e.g. "zh_CN.json"
+ if (name && name.endsWith(".json")) {
+ const shortName = name.slice(0, -5);
+ presentLocales[shortName] = Qt.locale(shortName);
+ }
+ }
+ }
+
+ function _pickTranslation() {
+ for (let i = 0; i < _candidates.length; i++) {
+ const cand = _candidates[i];
+ if (presentLocales[cand] === undefined)
+ continue;
+ _resolvedLocale = cand;
+ useLocale(cand, cand.startsWith("en") ? "" : translationsFolder + "/" + cand + ".json");
+ return;
+ }
+
+ _resolvedLocale = "en";
+ _fallbackToEnglish();
+ }
+
+ function useLocale(localeTag, fileUrl) {
+ _resolvedLocale = localeTag || "en";
+ _selectedPath = fileUrl;
+ translationsLoaded = false;
+ translations = ({});
+ console.info(`I18n: Using locale '${localeTag}' from ${fileUrl}`);
+ }
+
+ function _fallbackToEnglish() {
+ _selectedPath = "";
+ translationsLoaded = false;
+ translations = ({});
+ console.warn("I18n: Falling back to built-in English strings");
+ }
+
+ function tr(term, context) {
+ if (!translationsLoaded || !translations)
+ return term;
+ const ctx = context || term;
+ if (translations[ctx] && translations[ctx][term])
+ return translations[ctx][term];
+ for (const c in translations) {
+ if (translations[c] && translations[c][term])
+ return translations[c][term];
+ }
+ return term;
+ }
+
+ function trContext(context, term) {
+ if (!translationsLoaded || !translations)
+ return term;
+ if (translations[context] && translations[context][term])
+ return translations[context][term];
+ return term;
+ }
+}
diff --git a/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Common/KeyUtils.js b/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Common/KeyUtils.js
new file mode 100644
index 0000000..eaf4520
--- /dev/null
+++ b/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Common/KeyUtils.js
@@ -0,0 +1,178 @@
+.pragma library
+
+const KEY_MAP = {
+ 16777234: "Left",
+ 16777236: "Right",
+ 16777235: "Up",
+ 16777237: "Down",
+ 44: "Comma",
+ 46: "Period",
+ 47: "Slash",
+ 59: "Semicolon",
+ 39: "Apostrophe",
+ 91: "BracketLeft",
+ 93: "BracketRight",
+ 92: "Backslash",
+ 45: "Minus",
+ 61: "Equal",
+ 96: "grave",
+ 32: "space",
+ 16777225: "Print",
+ 16777226: "Print",
+ 16777220: "Return",
+ 16777221: "Return",
+ 16777217: "Tab",
+ 16777219: "BackSpace",
+ 16777223: "Delete",
+ 16777222: "Insert",
+ 16777232: "Home",
+ 16777233: "End",
+ 16777238: "Page_Up",
+ 16777239: "Page_Down",
+ 16777216: "Escape",
+ 16777252: "Caps_Lock",
+ 16777253: "Num_Lock",
+ 16777254: "Scroll_Lock",
+ 16777224: "Pause",
+ 16777330: "XF86AudioRaiseVolume",
+ 16777328: "XF86AudioLowerVolume",
+ 16777329: "XF86AudioMute",
+ 16842808: "XF86AudioMicMute",
+ 16777344: "XF86AudioPlay",
+ 16777345: "XF86AudioStop",
+ 16777346: "XF86AudioPrev",
+ 16777347: "XF86AudioNext",
+ 16777348: "XF86AudioPause",
+ 16777349: "XF86AudioMedia",
+ 16777350: "XF86AudioRecord",
+ 16842798: "XF86MonBrightnessUp",
+ 16777394: "XF86MonBrightnessUp",
+ 16842797: "XF86MonBrightnessDown",
+ 16777395: "XF86MonBrightnessDown",
+ 16842800: "XF86KbdBrightnessUp",
+ 16842799: "XF86KbdBrightnessDown",
+ 16842796: "XF86PowerOff",
+ 16842803: "XF86Sleep",
+ 16842804: "XF86WakeUp",
+ 16842802: "XF86Eject",
+ 16842791: "XF86Calculator",
+ 16842806: "XF86Explorer",
+ 16842794: "XF86HomePage",
+ 16777426: "XF86Search",
+ 16777427: "XF86Mail",
+ 16777442: "XF86Launch0",
+ 16777443: "XF86Launch1",
+ 33: "1",
+ 64: "2",
+ 35: "3",
+ 36: "4",
+ 37: "5",
+ 94: "6",
+ 38: "7",
+ 42: "8",
+ 40: "9",
+ 41: "0",
+ 60: "Comma",
+ 62: "Period",
+ 63: "Slash",
+ 58: "Semicolon",
+ 34: "Apostrophe",
+ 123: "BracketLeft",
+ 125: "BracketRight",
+ 124: "Backslash",
+ 95: "Minus",
+ 43: "Equal",
+ 126: "grave",
+ 196: "Adiaeresis",
+ 214: "Odiaeresis",
+ 220: "Udiaeresis",
+ 228: "adiaeresis",
+ 246: "odiaeresis",
+ 252: "udiaeresis",
+ 223: "ssharp",
+ 201: "Eacute",
+ 233: "eacute",
+ 200: "Egrave",
+ 232: "egrave",
+ 202: "Ecircumflex",
+ 234: "ecircumflex",
+ 203: "Ediaeresis",
+ 235: "ediaeresis",
+ 192: "Agrave",
+ 224: "agrave",
+ 194: "Acircumflex",
+ 226: "acircumflex",
+ 199: "Ccedilla",
+ 231: "ccedilla",
+ 206: "Icircumflex",
+ 238: "icircumflex",
+ 207: "Idiaeresis",
+ 239: "idiaeresis",
+ 212: "Ocircumflex",
+ 244: "ocircumflex",
+ 217: "Ugrave",
+ 249: "ugrave",
+ 219: "Ucircumflex",
+ 251: "ucircumflex",
+ 209: "Ntilde",
+ 241: "ntilde",
+ 191: "questiondown",
+ 161: "exclamdown"
+};
+
+function xkbKeyFromQtKey(qk) {
+ if (qk >= 65 && qk <= 90)
+ return String.fromCharCode(qk);
+ if (qk >= 97 && qk <= 122)
+ return String.fromCharCode(qk - 32);
+ if (qk >= 48 && qk <= 57)
+ return String.fromCharCode(qk);
+ if (qk >= 16777264 && qk <= 16777298)
+ return "F" + (qk - 16777264 + 1);
+ return KEY_MAP[qk] || "";
+}
+
+function modsFromEvent(mods) {
+ var result = [];
+ if (mods & 0x10000000)
+ result.push("Super");
+ if (mods & 0x08000000)
+ result.push("Alt");
+ if (mods & 0x04000000)
+ result.push("Ctrl");
+ if (mods & 0x02000000)
+ result.push("Shift");
+ return result;
+}
+
+function formatToken(mods, key) {
+ return (mods.length ? mods.join("+") + "+" : "") + key;
+}
+
+function normalizeKeyCombo(keyCombo) {
+ if (!keyCombo)
+ return "";
+ return keyCombo.toLowerCase().replace(/\bmod\b/g, "super").replace(/\bsuper\b/g, "super");
+}
+
+function getConflictingBinds(keyCombo, currentAction, allBinds) {
+ if (!keyCombo)
+ return [];
+ var conflicts = [];
+ var normalizedKey = normalizeKeyCombo(keyCombo);
+ for (var i = 0; i < allBinds.length; i++) {
+ var bind = allBinds[i];
+ if (bind.action === currentAction)
+ continue;
+ for (var k = 0; k < bind.keys.length; k++) {
+ if (normalizeKeyCombo(bind.keys[k].key) === normalizedKey) {
+ conflicts.push({
+ action: bind.action,
+ desc: bind.desc || bind.action
+ });
+ break;
+ }
+ }
+ }
+ return conflicts;
+}
diff --git a/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Common/KeybindActions.js b/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Common/KeybindActions.js
new file mode 100644
index 0000000..25b1ed0
--- /dev/null
+++ b/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Common/KeybindActions.js
@@ -0,0 +1,1329 @@
+.pragma library
+
+const ACTION_TYPES = [
+ { id: "dms", label: "DMS Action", icon: "widgets" },
+ { id: "compositor", label: "Compositor", icon: "desktop_windows" },
+ { id: "spawn", label: "Run Command", icon: "terminal" },
+ { id: "shell", label: "Shell Command", icon: "code" }
+];
+
+const DMS_ACTIONS = [
+ { id: "spawn dms ipc call spotlight toggle", label: "App Launcher: Toggle" },
+ { id: "spawn dms ipc call spotlight open", label: "App Launcher: Open" },
+ { id: "spawn dms ipc call spotlight close", label: "App Launcher: Close" },
+ { id: "spawn dms ipc call clipboard toggle", label: "Clipboard: Toggle" },
+ { id: "spawn dms ipc call clipboard open", label: "Clipboard: Open" },
+ { id: "spawn dms ipc call clipboard close", label: "Clipboard: Close" },
+ { id: "spawn dms ipc call notifications toggle", label: "Notifications: Toggle" },
+ { id: "spawn dms ipc call notifications open", label: "Notifications: Open" },
+ { id: "spawn dms ipc call notifications close", label: "Notifications: Close" },
+ { id: "spawn dms ipc call processlist toggle", label: "Task Manager: Toggle" },
+ { id: "spawn dms ipc call processlist open", label: "Task Manager: Open" },
+ { id: "spawn dms ipc call processlist close", label: "Task Manager: Close" },
+ { id: "spawn dms ipc call processlist focusOrToggle", label: "Task Manager: Focus or Toggle" },
+ { id: "spawn dms ipc call settings toggle", label: "Settings: Toggle" },
+ { id: "spawn dms ipc call settings open", label: "Settings: Open" },
+ { id: "spawn dms ipc call settings close", label: "Settings: Close" },
+ { id: "spawn dms ipc call settings focusOrToggle", label: "Settings: Focus or Toggle" },
+ { id: "spawn dms ipc call powermenu toggle", label: "Power Menu: Toggle" },
+ { id: "spawn dms ipc call powermenu open", label: "Power Menu: Open" },
+ { id: "spawn dms ipc call powermenu close", label: "Power Menu: Close" },
+ { id: "spawn dms ipc call control-center toggle", label: "Control Center: Toggle" },
+ { id: "spawn dms ipc call control-center open", label: "Control Center: Open" },
+ { id: "spawn dms ipc call control-center close", label: "Control Center: Close" },
+ { id: "spawn dms ipc call notepad toggle", label: "Notepad: Toggle" },
+ { id: "spawn dms ipc call notepad open", label: "Notepad: Open" },
+ { id: "spawn dms ipc call notepad close", label: "Notepad: Close" },
+ { id: "spawn dms ipc call dash toggle \"\"", label: "Dashboard: Toggle" },
+ { id: "spawn dms ipc call dash open overview", label: "Dashboard: Overview" },
+ { id: "spawn dms ipc call dash open media", label: "Dashboard: Media" },
+ { id: "spawn dms ipc call dash open weather", label: "Dashboard: Weather" },
+ { id: "spawn dms ipc call dankdash wallpaper", label: "Wallpaper Browser" },
+ { id: "spawn dms ipc call file browse wallpaper", label: "File: Browse Wallpaper" },
+ { id: "spawn dms ipc call file browse profile", label: "File: Browse Profile" },
+ { id: "spawn dms ipc call keybinds toggle niri", label: "Keybinds Cheatsheet: Toggle", compositor: "niri" },
+ { id: "spawn dms ipc call keybinds open niri", label: "Keybinds Cheatsheet: Open", compositor: "niri" },
+ { id: "spawn dms ipc call keybinds close", label: "Keybinds Cheatsheet: Close" },
+ { id: "spawn dms ipc call lock lock", label: "Lock Screen" },
+ { id: "spawn dms ipc call lock demo", label: "Lock Screen: Demo" },
+ { id: "spawn dms ipc call inhibit toggle", label: "Idle Inhibit: Toggle" },
+ { id: "spawn dms ipc call inhibit enable", label: "Idle Inhibit: Enable" },
+ { id: "spawn dms ipc call inhibit disable", label: "Idle Inhibit: Disable" },
+ { id: "spawn dms ipc call audio increment 5", label: "Volume Up" },
+ { id: "spawn dms ipc call audio increment 1", label: "Volume Up (1%)" },
+ { id: "spawn dms ipc call audio increment 5", label: "Volume Up (5%)" },
+ { id: "spawn dms ipc call audio increment 10", label: "Volume Up (10%)" },
+ { id: "spawn dms ipc call audio decrement 5", label: "Volume Down" },
+ { id: "spawn dms ipc call audio decrement 1", label: "Volume Down (1%)" },
+ { id: "spawn dms ipc call audio decrement 5", label: "Volume Down (5%)" },
+ { id: "spawn dms ipc call audio decrement 10", label: "Volume Down (10%)" },
+ { id: "spawn dms ipc call mpris increment 5", label: "Player Volume Up (5%)" },
+ { id: "spawn dms ipc call mpris decrement 5", label: "Player Volume Down (5%)" },
+ { id: "spawn dms ipc call audio mute", label: "Volume Mute Toggle" },
+ { id: "spawn dms ipc call audio micmute", label: "Microphone Mute Toggle" },
+ { id: "spawn dms ipc call audio cycleoutput", label: "Audio Output: Cycle" },
+ { id: "spawn dms ipc call brightness increment 5 \"\"", label: "Brightness Up" },
+ { id: "spawn dms ipc call brightness increment 1 \"\"", label: "Brightness Up (1%)" },
+ { id: "spawn dms ipc call brightness increment 5 \"\"", label: "Brightness Up (5%)" },
+ { id: "spawn dms ipc call brightness increment 10 \"\"", label: "Brightness Up (10%)" },
+ { id: "spawn dms ipc call brightness decrement 5 \"\"", label: "Brightness Down" },
+ { id: "spawn dms ipc call brightness decrement 1 \"\"", label: "Brightness Down (1%)" },
+ { id: "spawn dms ipc call brightness decrement 5 \"\"", label: "Brightness Down (5%)" },
+ { id: "spawn dms ipc call brightness decrement 10 \"\"", label: "Brightness Down (10%)" },
+ { id: "spawn dms ipc call brightness toggleExponential \"\"", label: "Brightness: Toggle Exponential" },
+ { id: "spawn dms ipc call theme toggle", label: "Theme: Toggle Light/Dark" },
+ { id: "spawn dms ipc call theme light", label: "Theme: Light Mode" },
+ { id: "spawn dms ipc call theme dark", label: "Theme: Dark Mode" },
+ { id: "spawn dms ipc call night toggle", label: "Night Mode: Toggle" },
+ { id: "spawn dms ipc call night enable", label: "Night Mode: Enable" },
+ { id: "spawn dms ipc call night disable", label: "Night Mode: Disable" },
+ { id: "spawn dms ipc call bar toggle index 0", label: "Bar: Toggle (Primary)" },
+ { id: "spawn dms ipc call bar reveal index 0", label: "Bar: Reveal (Primary)" },
+ { id: "spawn dms ipc call bar hide index 0", label: "Bar: Hide (Primary)" },
+ { id: "spawn dms ipc call bar toggleAutoHide index 0", label: "Bar: Toggle Auto-Hide (Primary)" },
+ { id: "spawn dms ipc call bar autoHide index 0", label: "Bar: Enable Auto-Hide (Primary)" },
+ { id: "spawn dms ipc call bar manualHide index 0", label: "Bar: Disable Auto-Hide (Primary)" },
+ { id: "spawn dms ipc call dock toggle", label: "Dock: Toggle" },
+ { id: "spawn dms ipc call dock reveal", label: "Dock: Reveal" },
+ { id: "spawn dms ipc call dock hide", label: "Dock: Hide" },
+ { id: "spawn dms ipc call dock toggleAutoHide", label: "Dock: Toggle Auto-Hide" },
+ { id: "spawn dms ipc call dock autoHide", label: "Dock: Enable Auto-Hide" },
+ { id: "spawn dms ipc call dock manualHide", label: "Dock: Disable Auto-Hide" },
+ { id: "spawn dms ipc call mpris playPause", label: "Media: Play/Pause" },
+ { id: "spawn dms ipc call mpris play", label: "Media: Play" },
+ { id: "spawn dms ipc call mpris pause", label: "Media: Pause" },
+ { id: "spawn dms ipc call mpris previous", label: "Media: Previous Track" },
+ { id: "spawn dms ipc call mpris next", label: "Media: Next Track" },
+ { id: "spawn dms ipc call mpris stop", label: "Media: Stop" },
+ { id: "spawn dms ipc call niri screenshot", label: "Screenshot: Interactive", compositor: "niri" },
+ { id: "spawn dms ipc call niri screenshotScreen", label: "Screenshot: Full Screen", compositor: "niri" },
+ { id: "spawn dms ipc call niri screenshotWindow", label: "Screenshot: Window", compositor: "niri" },
+ { id: "spawn dms ipc call hypr toggleOverview", label: "Hyprland: Toggle Overview", compositor: "hyprland" },
+ { id: "spawn dms ipc call hypr openOverview", label: "Hyprland: Open Overview", compositor: "hyprland" },
+ { id: "spawn dms ipc call hypr closeOverview", label: "Hyprland: Close Overview", compositor: "hyprland" },
+ { id: "spawn dms ipc call wallpaper next", label: "Wallpaper: Next" },
+ { id: "spawn dms ipc call wallpaper prev", label: "Wallpaper: Previous" },
+ { id: "spawn dms ipc call workspace-rename open", label: "Workspace: Rename" }
+];
+
+const NIRI_ACTIONS = {
+ "Window": [
+ { id: "close-window", label: "Close Window" },
+ { id: "fullscreen-window", label: "Fullscreen" },
+ { id: "maximize-column", label: "Maximize Column" },
+ { id: "center-column", label: "Center Column" },
+ { id: "center-visible-columns", label: "Center Visible Columns" },
+ { id: "toggle-window-floating", label: "Toggle Floating" },
+ { id: "switch-focus-between-floating-and-tiling", label: "Switch Floating/Tiling Focus" },
+ { id: "switch-preset-column-width", label: "Cycle Column Width" },
+ { id: "switch-preset-window-height", label: "Cycle Window Height" },
+ { id: "set-column-width", label: "Set Column Width" },
+ { id: "set-window-height", label: "Set Window Height" },
+ { id: "reset-window-height", label: "Reset Window Height" },
+ { id: "expand-column-to-available-width", label: "Expand to Available Width" },
+ { id: "consume-or-expel-window-left", label: "Consume/Expel Left" },
+ { id: "consume-or-expel-window-right", label: "Consume/Expel Right" },
+ { id: "toggle-column-tabbed-display", label: "Toggle Tabbed" },
+ { id: "toggle-window-rule-opacity", label: "Toggle Window Opacity" }
+ ],
+ "Focus": [
+ { id: "focus-column-left", label: "Focus Left" },
+ { id: "focus-column-right", label: "Focus Right" },
+ { id: "focus-window-down", label: "Focus Down" },
+ { id: "focus-window-up", label: "Focus Up" },
+ { id: "focus-column-first", label: "Focus First Column" },
+ { id: "focus-column-last", label: "Focus Last Column" }
+ ],
+ "Move": [
+ { id: "move-column-left", label: "Move Left" },
+ { id: "move-column-right", label: "Move Right" },
+ { id: "move-window-down", label: "Move Down" },
+ { id: "move-window-up", label: "Move Up" },
+ { id: "move-column-to-first", label: "Move to First" },
+ { id: "move-column-to-last", label: "Move to Last" }
+ ],
+ "Workspace": [
+ { id: "focus-workspace-down", label: "Focus Workspace Down" },
+ { id: "focus-workspace-up", label: "Focus Workspace Up" },
+ { id: "focus-workspace-previous", label: "Focus Previous Workspace" },
+ { id: "focus-workspace", label: "Focus Workspace (by index)" },
+ { id: "move-column-to-workspace-down", label: "Move to Workspace Down" },
+ { id: "move-column-to-workspace-up", label: "Move to Workspace Up" },
+ { id: "move-column-to-workspace", label: "Move to Workspace (by index)" },
+ { id: "move-workspace-down", label: "Move Workspace Down" },
+ { id: "move-workspace-up", label: "Move Workspace Up" }
+ ],
+ "Monitor": [
+ { id: "focus-monitor-left", label: "Focus Monitor Left" },
+ { id: "focus-monitor-right", label: "Focus Monitor Right" },
+ { id: "focus-monitor-down", label: "Focus Monitor Down" },
+ { id: "focus-monitor-up", label: "Focus Monitor Up" },
+ { id: "move-column-to-monitor-left", label: "Move to Monitor Left" },
+ { id: "move-column-to-monitor-right", label: "Move to Monitor Right" },
+ { id: "move-column-to-monitor-down", label: "Move to Monitor Down" },
+ { id: "move-column-to-monitor-up", label: "Move to Monitor Up" }
+ ],
+ "Screenshot": [
+ { id: "screenshot", label: "Screenshot (Interactive)" },
+ { id: "screenshot-screen", label: "Screenshot Screen" },
+ { id: "screenshot-window", label: "Screenshot Window" }
+ ],
+ "System": [
+ { id: "toggle-overview", label: "Toggle Overview" },
+ { id: "show-hotkey-overlay", label: "Show Hotkey Overlay" },
+ { id: "do-screen-transition", label: "Screen Transition" },
+ { id: "power-off-monitors", label: "Power Off Monitors" },
+ { id: "power-on-monitors", label: "Power On Monitors" },
+ { id: "toggle-keyboard-shortcuts-inhibit", label: "Toggle Shortcuts Inhibit" },
+ { id: "quit", label: "Quit Niri" },
+ { id: "suspend", label: "Suspend" }
+ ],
+ "Alt-Tab": [
+ { id: "next-window", label: "Next Window" },
+ { id: "previous-window", label: "Previous Window" }
+ ]
+};
+
+const MANGOWC_ACTIONS = {
+ "Window": [
+ { id: "killclient", label: "Close Window" },
+ { id: "focuslast", label: "Focus Last Window" },
+ { id: "focusstack next", label: "Focus Next in Stack" },
+ { id: "focusstack prev", label: "Focus Previous in Stack" },
+ { id: "focusdir left", label: "Focus Left" },
+ { id: "focusdir right", label: "Focus Right" },
+ { id: "focusdir up", label: "Focus Up" },
+ { id: "focusdir down", label: "Focus Down" },
+ { id: "exchange_client left", label: "Swap Left" },
+ { id: "exchange_client right", label: "Swap Right" },
+ { id: "exchange_client up", label: "Swap Up" },
+ { id: "exchange_client down", label: "Swap Down" },
+ { id: "exchange_stack_client next", label: "Swap Next in Stack" },
+ { id: "exchange_stack_client prev", label: "Swap Previous in Stack" },
+ { id: "togglefloating", label: "Toggle Floating" },
+ { id: "togglefullscreen", label: "Toggle Fullscreen" },
+ { id: "togglefakefullscreen", label: "Toggle Fake Fullscreen" },
+ { id: "togglemaximizescreen", label: "Toggle Maximize" },
+ { id: "toggleglobal", label: "Toggle Global (Sticky)" },
+ { id: "toggleoverlay", label: "Toggle Overlay" },
+ { id: "minimized", label: "Minimize Window" },
+ { id: "restore_minimized", label: "Restore Minimized" },
+ { id: "toggle_render_border", label: "Toggle Border" },
+ { id: "centerwin", label: "Center Window" },
+ { id: "zoom", label: "Swap with Master" }
+ ],
+ "Move/Resize": [
+ { id: "smartmovewin left", label: "Smart Move Left" },
+ { id: "smartmovewin right", label: "Smart Move Right" },
+ { id: "smartmovewin up", label: "Smart Move Up" },
+ { id: "smartmovewin down", label: "Smart Move Down" },
+ { id: "smartresizewin left", label: "Smart Resize Left" },
+ { id: "smartresizewin right", label: "Smart Resize Right" },
+ { id: "smartresizewin up", label: "Smart Resize Up" },
+ { id: "smartresizewin down", label: "Smart Resize Down" },
+ { id: "movewin", label: "Move Window (x,y)" },
+ { id: "resizewin", label: "Resize Window (w,h)" }
+ ],
+ "Tags": [
+ { id: "view", label: "View Tag" },
+ { id: "viewtoleft", label: "View Left Tag" },
+ { id: "viewtoright", label: "View Right Tag" },
+ { id: "viewtoleft_have_client", label: "View Left (with client)" },
+ { id: "viewtoright_have_client", label: "View Right (with client)" },
+ { id: "viewcrossmon", label: "View Cross-Monitor" },
+ { id: "tag", label: "Move to Tag" },
+ { id: "tagsilent", label: "Move to Tag (silent)" },
+ { id: "tagtoleft", label: "Move to Left Tag" },
+ { id: "tagtoright", label: "Move to Right Tag" },
+ { id: "tagcrossmon", label: "Move Cross-Monitor" },
+ { id: "toggletag", label: "Toggle Tag on Window" },
+ { id: "toggleview", label: "Toggle Tag View" },
+ { id: "comboview", label: "Combo View Tags" }
+ ],
+ "Layout": [
+ { id: "setlayout", label: "Set Layout" },
+ { id: "switch_layout", label: "Cycle Layouts" },
+ { id: "set_proportion", label: "Set Proportion" },
+ { id: "switch_proportion_preset", label: "Cycle Proportion Presets" },
+ { id: "incnmaster +1", label: "Increase Masters" },
+ { id: "incnmaster -1", label: "Decrease Masters" },
+ { id: "setmfact", label: "Set Master Factor" },
+ { id: "incgaps", label: "Adjust Gaps" },
+ { id: "togglegaps", label: "Toggle Gaps" }
+ ],
+ "Monitor": [
+ { id: "focusmon left", label: "Focus Monitor Left" },
+ { id: "focusmon right", label: "Focus Monitor Right" },
+ { id: "focusmon up", label: "Focus Monitor Up" },
+ { id: "focusmon down", label: "Focus Monitor Down" },
+ { id: "tagmon left", label: "Move to Monitor Left" },
+ { id: "tagmon right", label: "Move to Monitor Right" },
+ { id: "tagmon up", label: "Move to Monitor Up" },
+ { id: "tagmon down", label: "Move to Monitor Down" },
+ { id: "disable_monitor", label: "Disable Monitor" },
+ { id: "enable_monitor", label: "Enable Monitor" },
+ { id: "toggle_monitor", label: "Toggle Monitor" },
+ { id: "create_virtual_output", label: "Create Virtual Output" },
+ { id: "destroy_all_virtual_output", label: "Destroy Virtual Outputs" }
+ ],
+ "Scratchpad": [
+ { id: "toggle_scratchpad", label: "Toggle Scratchpad" },
+ { id: "toggle_name_scratchpad", label: "Toggle Named Scratchpad" }
+ ],
+ "Overview": [
+ { id: "toggleoverview", label: "Toggle Overview" }
+ ],
+ "System": [
+ { id: "reload_config", label: "Reload Config" },
+ { id: "quit", label: "Quit MangoWC" },
+ { id: "setkeymode", label: "Set Keymode" },
+ { id: "switch_keyboard_layout", label: "Switch Keyboard Layout" },
+ { id: "setoption", label: "Set Option" },
+ { id: "toggle_trackpad_enable", label: "Toggle Trackpad" }
+ ]
+};
+
+const HYPRLAND_ACTIONS = {
+ "Window": [
+ { id: "killactive", label: "Close Window" },
+ { id: "forcekillactive", label: "Force Kill Window" },
+ { id: "closewindow", label: "Close Window (by selector)" },
+ { id: "killwindow", label: "Kill Window (by selector)" },
+ { id: "togglefloating", label: "Toggle Floating" },
+ { id: "setfloating", label: "Set Floating" },
+ { id: "settiled", label: "Set Tiled" },
+ { id: "fullscreen", label: "Toggle Fullscreen" },
+ { id: "fullscreenstate", label: "Set Fullscreen State" },
+ { id: "pin", label: "Pin Window" },
+ { id: "centerwindow", label: "Center Window" },
+ { id: "resizeactive", label: "Resize Active Window" },
+ { id: "moveactive", label: "Move Active Window" },
+ { id: "resizewindowpixel", label: "Resize Window (pixels)" },
+ { id: "movewindowpixel", label: "Move Window (pixels)" },
+ { id: "alterzorder", label: "Change Z-Order" },
+ { id: "bringactivetotop", label: "Bring to Top" },
+ { id: "setprop", label: "Set Window Property" },
+ { id: "toggleswallow", label: "Toggle Swallow" }
+ ],
+ "Focus": [
+ { id: "movefocus l", label: "Focus Left" },
+ { id: "movefocus r", label: "Focus Right" },
+ { id: "movefocus u", label: "Focus Up" },
+ { id: "movefocus d", label: "Focus Down" },
+ { id: "movefocus", label: "Move Focus (direction)" },
+ { id: "cyclenext", label: "Cycle Next Window" },
+ { id: "cyclenext prev", label: "Cycle Previous Window" },
+ { id: "focuswindow", label: "Focus Window (by selector)" },
+ { id: "focuscurrentorlast", label: "Focus Current or Last" },
+ { id: "focusurgentorlast", label: "Focus Urgent or Last" }
+ ],
+ "Move": [
+ { id: "movewindow l", label: "Move Window Left" },
+ { id: "movewindow r", label: "Move Window Right" },
+ { id: "movewindow u", label: "Move Window Up" },
+ { id: "movewindow d", label: "Move Window Down" },
+ { id: "movewindow", label: "Move Window (direction)" },
+ { id: "swapwindow l", label: "Swap Left" },
+ { id: "swapwindow r", label: "Swap Right" },
+ { id: "swapwindow u", label: "Swap Up" },
+ { id: "swapwindow d", label: "Swap Down" },
+ { id: "swapwindow", label: "Swap Window (direction)" },
+ { id: "swapnext", label: "Swap with Next" },
+ { id: "swapnext prev", label: "Swap with Previous" },
+ { id: "movecursortocorner", label: "Move Cursor to Corner" },
+ { id: "movecursor", label: "Move Cursor (x,y)" }
+ ],
+ "Workspace": [
+ { id: "workspace", label: "Focus Workspace" },
+ { id: "workspace +1", label: "Next Workspace" },
+ { id: "workspace -1", label: "Previous Workspace" },
+ { id: "workspace e+1", label: "Next Open Workspace" },
+ { id: "workspace e-1", label: "Previous Open Workspace" },
+ { id: "workspace previous", label: "Previous Visited Workspace" },
+ { id: "workspace previous_per_monitor", label: "Previous on Monitor" },
+ { id: "workspace empty", label: "First Empty Workspace" },
+ { id: "movetoworkspace", label: "Move to Workspace" },
+ { id: "movetoworkspace +1", label: "Move to Next Workspace" },
+ { id: "movetoworkspace -1", label: "Move to Previous Workspace" },
+ { id: "movetoworkspacesilent", label: "Move to Workspace (silent)" },
+ { id: "movetoworkspacesilent +1", label: "Move to Next (silent)" },
+ { id: "movetoworkspacesilent -1", label: "Move to Previous (silent)" },
+ { id: "togglespecialworkspace", label: "Toggle Special Workspace" },
+ { id: "focusworkspaceoncurrentmonitor", label: "Focus Workspace on Current Monitor" },
+ { id: "renameworkspace", label: "Rename Workspace" }
+ ],
+ "Monitor": [
+ { id: "focusmonitor l", label: "Focus Monitor Left" },
+ { id: "focusmonitor r", label: "Focus Monitor Right" },
+ { id: "focusmonitor u", label: "Focus Monitor Up" },
+ { id: "focusmonitor d", label: "Focus Monitor Down" },
+ { id: "focusmonitor +1", label: "Focus Next Monitor" },
+ { id: "focusmonitor -1", label: "Focus Previous Monitor" },
+ { id: "focusmonitor", label: "Focus Monitor (by selector)" },
+ { id: "movecurrentworkspacetomonitor", label: "Move Workspace to Monitor" },
+ { id: "moveworkspacetomonitor", label: "Move Specific Workspace to Monitor" },
+ { id: "swapactiveworkspaces", label: "Swap Active Workspaces" }
+ ],
+ "Groups": [
+ { id: "togglegroup", label: "Toggle Group" },
+ { id: "changegroupactive f", label: "Next in Group" },
+ { id: "changegroupactive b", label: "Previous in Group" },
+ { id: "changegroupactive", label: "Change Active in Group" },
+ { id: "moveintogroup l", label: "Move into Group Left" },
+ { id: "moveintogroup r", label: "Move into Group Right" },
+ { id: "moveintogroup u", label: "Move into Group Up" },
+ { id: "moveintogroup d", label: "Move into Group Down" },
+ { id: "moveoutofgroup", label: "Move out of Group" },
+ { id: "movewindoworgroup l", label: "Move Window/Group Left" },
+ { id: "movewindoworgroup r", label: "Move Window/Group Right" },
+ { id: "movewindoworgroup u", label: "Move Window/Group Up" },
+ { id: "movewindoworgroup d", label: "Move Window/Group Down" },
+ { id: "movegroupwindow f", label: "Swap Forward in Group" },
+ { id: "movegroupwindow b", label: "Swap Backward in Group" },
+ { id: "lockgroups lock", label: "Lock All Groups" },
+ { id: "lockgroups unlock", label: "Unlock All Groups" },
+ { id: "lockgroups toggle", label: "Toggle Groups Lock" },
+ { id: "lockactivegroup lock", label: "Lock Active Group" },
+ { id: "lockactivegroup unlock", label: "Unlock Active Group" },
+ { id: "lockactivegroup toggle", label: "Toggle Active Group Lock" },
+ { id: "denywindowfromgroup on", label: "Deny Window from Group" },
+ { id: "denywindowfromgroup off", label: "Allow Window in Group" },
+ { id: "denywindowfromgroup toggle", label: "Toggle Deny from Group" },
+ { id: "setignoregrouplock on", label: "Ignore Group Lock" },
+ { id: "setignoregrouplock off", label: "Respect Group Lock" },
+ { id: "setignoregrouplock toggle", label: "Toggle Ignore Group Lock" }
+ ],
+ "Layout": [
+ { id: "splitratio", label: "Adjust Split Ratio" }
+ ],
+ "System": [
+ { id: "exit", label: "Exit Hyprland" },
+ { id: "forcerendererreload", label: "Force Renderer Reload" },
+ { id: "dpms on", label: "DPMS On" },
+ { id: "dpms off", label: "DPMS Off" },
+ { id: "dpms toggle", label: "DPMS Toggle" },
+ { id: "forceidle", label: "Force Idle" },
+ { id: "submap", label: "Enter Submap" },
+ { id: "submap reset", label: "Reset Submap" },
+ { id: "global", label: "Global Shortcut" },
+ { id: "event", label: "Emit Custom Event" }
+ ],
+ "Pass-through": [
+ { id: "pass", label: "Pass Key to Window" },
+ { id: "sendshortcut", label: "Send Shortcut to Window" },
+ { id: "sendkeystate", label: "Send Key State" }
+ ]
+};
+
+const COMPOSITOR_ACTIONS = {
+ niri: NIRI_ACTIONS,
+ mangowc: MANGOWC_ACTIONS,
+ hyprland: HYPRLAND_ACTIONS
+};
+
+const CATEGORY_ORDER = ["DMS", "Execute", "Workspace", "Tags", "Window", "Move/Resize", "Focus", "Move", "Layout", "Groups", "Monitor", "Scratchpad", "Screenshot", "System", "Pass-through", "Overview", "Alt-Tab", "Other"];
+
+const NIRI_ACTION_ARGS = {
+ "quit": {
+ args: [{ name: "skip-confirmation", type: "bool", label: "Skip confirmation" }]
+ },
+ "do-screen-transition": {
+ args: [{ name: "delay-ms", type: "number", label: "Delay (ms)", placeholder: "250" }]
+ },
+ "set-column-width": {
+ args: [{ name: "value", type: "text", label: "Width", placeholder: "+10%, -10%, 50%" }]
+ },
+ "set-window-height": {
+ args: [{ name: "value", type: "text", label: "Height", placeholder: "+10%, -10%, 50%" }]
+ },
+ "focus-workspace": {
+ args: [{ name: "index", type: "number", label: "Workspace", placeholder: "1, 2, 3..." }]
+ },
+ "move-column-to-workspace": {
+ args: [
+ { name: "index", type: "number", label: "Workspace", placeholder: "1, 2, 3..." },
+ { name: "focus", type: "bool", label: "Follow focus", default: false }
+ ]
+ },
+ "move-column-to-workspace-down": {
+ args: [{ name: "focus", type: "bool", label: "Follow focus", default: false }]
+ },
+ "move-column-to-workspace-up": {
+ args: [{ name: "focus", type: "bool", label: "Follow focus", default: false }]
+ },
+ "screenshot": {
+ args: [{ name: "show-pointer", type: "bool", label: "Show pointer" }]
+ },
+ "screenshot-screen": {
+ args: [
+ { name: "show-pointer", type: "bool", label: "Show pointer" },
+ { name: "write-to-disk", type: "bool", label: "Save to disk" }
+ ]
+ },
+ "screenshot-window": {
+ args: [{ name: "write-to-disk", type: "bool", label: "Save to disk" }]
+ }
+};
+
+const MANGOWC_ACTION_ARGS = {
+ "view": {
+ args: [
+ { name: "tag", type: "number", label: "Tag", placeholder: "1-9" },
+ { name: "monitor", type: "number", label: "Monitor", placeholder: "0", default: "0" }
+ ]
+ },
+ "tag": {
+ args: [
+ { name: "tag", type: "number", label: "Tag", placeholder: "1-9" },
+ { name: "monitor", type: "number", label: "Monitor", placeholder: "0", default: "0" }
+ ]
+ },
+ "tagsilent": {
+ args: [
+ { name: "tag", type: "number", label: "Tag", placeholder: "1-9" },
+ { name: "monitor", type: "number", label: "Monitor", placeholder: "0", default: "0" }
+ ]
+ },
+ "toggletag": {
+ args: [
+ { name: "tag", type: "number", label: "Tag", placeholder: "1-9" },
+ { name: "monitor", type: "number", label: "Monitor", placeholder: "0", default: "0" }
+ ]
+ },
+ "toggleview": {
+ args: [
+ { name: "tag", type: "number", label: "Tag", placeholder: "1-9" },
+ { name: "monitor", type: "number", label: "Monitor", placeholder: "0", default: "0" }
+ ]
+ },
+ "comboview": {
+ args: [{ name: "tags", type: "text", label: "Tags", placeholder: "1,2,3" }]
+ },
+ "setlayout": {
+ args: [{ name: "layout", type: "text", label: "Layout", placeholder: "tile, monocle, grid, deck" }]
+ },
+ "set_proportion": {
+ args: [{ name: "value", type: "text", label: "Proportion", placeholder: "0.5, +0.1, -0.1" }]
+ },
+ "setmfact": {
+ args: [{ name: "value", type: "text", label: "Factor", placeholder: "+0.05, -0.05" }]
+ },
+ "incgaps": {
+ args: [{ name: "value", type: "number", label: "Amount", placeholder: "+5, -5" }]
+ },
+ "movewin": {
+ args: [{ name: "value", type: "text", label: "Position", placeholder: "x,y or +10,+10" }]
+ },
+ "resizewin": {
+ args: [{ name: "value", type: "text", label: "Size", placeholder: "w,h or +10,+10" }]
+ },
+ "setkeymode": {
+ args: [{ name: "mode", type: "text", label: "Mode", placeholder: "default, custom" }]
+ },
+ "setoption": {
+ args: [{ name: "option", type: "text", label: "Option", placeholder: "option_name value" }]
+ },
+ "toggle_name_scratchpad": {
+ args: [{ name: "name", type: "text", label: "Name", placeholder: "scratchpad name" }]
+ },
+ "incnmaster": {
+ args: [{ name: "value", type: "number", label: "Amount", placeholder: "+1, -1" }]
+ }
+};
+
+const HYPRLAND_ACTION_ARGS = {
+ "workspace": {
+ args: [{ name: "value", type: "text", label: "Workspace", placeholder: "1, +1, -1, name:..." }]
+ },
+ "movetoworkspace": {
+ args: [
+ { name: "workspace", type: "text", label: "Workspace", placeholder: "1, +1, special:name" },
+ { name: "window", type: "text", label: "Window (optional)", placeholder: "class:^(app)$" }
+ ]
+ },
+ "movetoworkspacesilent": {
+ args: [
+ { name: "workspace", type: "text", label: "Workspace", placeholder: "1, +1, special:name" },
+ { name: "window", type: "text", label: "Window (optional)", placeholder: "class:^(app)$" }
+ ]
+ },
+ "focusworkspaceoncurrentmonitor": {
+ args: [{ name: "value", type: "text", label: "Workspace", placeholder: "1, +1, name:..." }]
+ },
+ "togglespecialworkspace": {
+ args: [{ name: "name", type: "text", label: "Name (optional)", placeholder: "scratchpad" }]
+ },
+ "focusmonitor": {
+ args: [{ name: "value", type: "text", label: "Monitor", placeholder: "l, r, +1, DP-1" }]
+ },
+ "movecurrentworkspacetomonitor": {
+ args: [{ name: "monitor", type: "text", label: "Monitor", placeholder: "l, r, DP-1" }]
+ },
+ "moveworkspacetomonitor": {
+ args: [
+ { name: "workspace", type: "text", label: "Workspace", placeholder: "1, name:..." },
+ { name: "monitor", type: "text", label: "Monitor", placeholder: "DP-1" }
+ ]
+ },
+ "swapactiveworkspaces": {
+ args: [
+ { name: "monitor1", type: "text", label: "Monitor 1", placeholder: "DP-1" },
+ { name: "monitor2", type: "text", label: "Monitor 2", placeholder: "DP-2" }
+ ]
+ },
+ "renameworkspace": {
+ args: [
+ { name: "id", type: "number", label: "Workspace ID", placeholder: "1" },
+ { name: "name", type: "text", label: "New Name", placeholder: "work" }
+ ]
+ },
+ "fullscreen": {
+ args: [{ name: "mode", type: "text", label: "Mode", placeholder: "0=full, 1=max, 2=fake" }]
+ },
+ "fullscreenstate": {
+ args: [
+ { name: "internal", type: "text", label: "Internal", placeholder: "-1, 0, 1, 2, 3" },
+ { name: "client", type: "text", label: "Client", placeholder: "-1, 0, 1, 2, 3" }
+ ]
+ },
+ "resizeactive": {
+ args: [{ name: "value", type: "text", label: "Size", placeholder: "10 -10, 20% 0" }]
+ },
+ "moveactive": {
+ args: [{ name: "value", type: "text", label: "Position", placeholder: "10 -10, exact 100 100" }]
+ },
+ "resizewindowpixel": {
+ args: [
+ { name: "size", type: "text", label: "Size", placeholder: "100 100" },
+ { name: "window", type: "text", label: "Window", placeholder: "class:^(app)$" }
+ ]
+ },
+ "movewindowpixel": {
+ args: [
+ { name: "position", type: "text", label: "Position", placeholder: "100 100" },
+ { name: "window", type: "text", label: "Window", placeholder: "class:^(app)$" }
+ ]
+ },
+ "splitratio": {
+ args: [{ name: "value", type: "text", label: "Ratio", placeholder: "+0.1, -0.1, exact 0.5" }]
+ },
+ "closewindow": {
+ args: [{ name: "window", type: "text", label: "Window", placeholder: "class:^(app)$" }]
+ },
+ "killwindow": {
+ args: [{ name: "window", type: "text", label: "Window", placeholder: "class:^(app)$" }]
+ },
+ "focuswindow": {
+ args: [{ name: "window", type: "text", label: "Window", placeholder: "class:^(app)$" }]
+ },
+ "tagwindow": {
+ args: [
+ { name: "tag", type: "text", label: "Tag", placeholder: "+mytag, -mytag" },
+ { name: "window", type: "text", label: "Window (optional)", placeholder: "class:^(app)$" }
+ ]
+ },
+ "alterzorder": {
+ args: [
+ { name: "zheight", type: "text", label: "Z-Height", placeholder: "top, bottom" },
+ { name: "window", type: "text", label: "Window (optional)", placeholder: "class:^(app)$" }
+ ]
+ },
+ "setprop": {
+ args: [
+ { name: "window", type: "text", label: "Window", placeholder: "class:^(app)$" },
+ { name: "property", type: "text", label: "Property", placeholder: "opaque, alpha..." },
+ { name: "value", type: "text", label: "Value", placeholder: "1, toggle" }
+ ]
+ },
+ "signal": {
+ args: [{ name: "signal", type: "number", label: "Signal", placeholder: "9" }]
+ },
+ "signalwindow": {
+ args: [
+ { name: "window", type: "text", label: "Window", placeholder: "class:^(app)$" },
+ { name: "signal", type: "number", label: "Signal", placeholder: "9" }
+ ]
+ },
+ "submap": {
+ args: [{ name: "name", type: "text", label: "Submap Name", placeholder: "resize, reset" }]
+ },
+ "global": {
+ args: [{ name: "name", type: "text", label: "Shortcut Name", placeholder: "app:action" }]
+ },
+ "event": {
+ args: [{ name: "data", type: "text", label: "Event Data", placeholder: "custom data" }]
+ },
+ "pass": {
+ args: [{ name: "window", type: "text", label: "Window", placeholder: "class:^(app)$" }]
+ },
+ "sendshortcut": {
+ args: [
+ { name: "mod", type: "text", label: "Modifier", placeholder: "SUPER, ALT" },
+ { name: "key", type: "text", label: "Key", placeholder: "F4" },
+ { name: "window", type: "text", label: "Window (optional)", placeholder: "class:^(app)$" }
+ ]
+ },
+ "sendkeystate": {
+ args: [
+ { name: "mod", type: "text", label: "Modifier", placeholder: "SUPER" },
+ { name: "key", type: "text", label: "Key", placeholder: "a" },
+ { name: "state", type: "text", label: "State", placeholder: "down, repeat, up" },
+ { name: "window", type: "text", label: "Window", placeholder: "class:^(app)$" }
+ ]
+ },
+ "forceidle": {
+ args: [{ name: "seconds", type: "number", label: "Seconds", placeholder: "300" }]
+ },
+ "movecursortocorner": {
+ args: [{ name: "corner", type: "number", label: "Corner", placeholder: "0-3 (BL, BR, TR, TL)" }]
+ },
+ "movecursor": {
+ args: [
+ { name: "x", type: "number", label: "X", placeholder: "100" },
+ { name: "y", type: "number", label: "Y", placeholder: "100" }
+ ]
+ },
+ "changegroupactive": {
+ args: [{ name: "direction", type: "text", label: "Direction/Index", placeholder: "f, b, or index" }]
+ },
+ "movefocus": {
+ args: [{ name: "direction", type: "text", label: "Direction", placeholder: "l, r, u, d" }]
+ },
+ "movewindow": {
+ args: [{ name: "direction", type: "text", label: "Direction/Monitor", placeholder: "l, r, mon:DP-1" }]
+ },
+ "swapwindow": {
+ args: [{ name: "direction", type: "text", label: "Direction", placeholder: "l, r, u, d" }]
+ },
+ "moveintogroup": {
+ args: [{ name: "direction", type: "text", label: "Direction", placeholder: "l, r, u, d" }]
+ },
+ "movewindoworgroup": {
+ args: [{ name: "direction", type: "text", label: "Direction", placeholder: "l, r, u, d" }]
+ },
+ "cyclenext": {
+ args: [{ name: "options", type: "text", label: "Options", placeholder: "prev, tiled, floating" }]
+ }
+};
+
+const ACTION_ARGS = {
+ niri: NIRI_ACTION_ARGS,
+ mangowc: MANGOWC_ACTION_ARGS,
+ hyprland: HYPRLAND_ACTION_ARGS
+};
+
+const DMS_ACTION_ARGS = {
+ "audio increment": {
+ base: "spawn dms ipc call audio increment",
+ args: [{ name: "amount", type: "number", label: "Amount %", placeholder: "5", default: "5" }]
+ },
+ "audio decrement": {
+ base: "spawn dms ipc call audio decrement",
+ args: [{ name: "amount", type: "number", label: "Amount %", placeholder: "5", default: "5" }]
+ },
+ "player increment": {
+ base: "spawn dms ipc call mpris increment",
+ args: [{ name: "amount", type: "number", label: "Amount %", placeholder: "5", default: "5" }]
+ },
+ "player decrement": {
+ base: "spawn dms ipc call mpris decrement",
+ args: [{ name: "amount", type: "number", label: "Amount %", placeholder: "5", default: "5" }]
+ },
+ "brightness increment": {
+ base: "spawn dms ipc call brightness increment",
+ args: [
+ { name: "amount", type: "number", label: "Amount %", placeholder: "5", default: "5" },
+ { name: "device", type: "text", label: "Device", placeholder: "leave empty for default", default: "" }
+ ]
+ },
+ "brightness decrement": {
+ base: "spawn dms ipc call brightness decrement",
+ args: [
+ { name: "amount", type: "number", label: "Amount %", placeholder: "5", default: "5" },
+ { name: "device", type: "text", label: "Device", placeholder: "leave empty for default", default: "" }
+ ]
+ },
+ "brightness toggleExponential": {
+ base: "spawn dms ipc call brightness toggleExponential",
+ args: [
+ { name: "device", type: "text", label: "Device", placeholder: "leave empty for default", default: "" }
+ ]
+ },
+ "dash toggle": {
+ base: "spawn dms ipc call dash toggle",
+ args: [
+ { name: "tab", type: "text", label: "Tab", placeholder: "overview, media, wallpaper, weather", default: "" }
+ ]
+ }
+};
+
+function getActionTypes() {
+ return ACTION_TYPES;
+}
+
+function getDmsActionArgs() {
+ return DMS_ACTION_ARGS;
+}
+
+function getDmsActions(isNiri, isHyprland) {
+ const result = [];
+ for (let i = 0; i < DMS_ACTIONS.length; i++) {
+ const action = DMS_ACTIONS[i];
+ if (!action.compositor) {
+ result.push(action);
+ continue;
+ }
+ switch (action.compositor) {
+ case "niri":
+ if (isNiri)
+ result.push(action);
+ break;
+ case "hyprland":
+ if (isHyprland)
+ result.push(action);
+ break;
+ }
+ }
+ return result;
+}
+
+function getCompositorCategories(compositor) {
+ var actions = COMPOSITOR_ACTIONS[compositor];
+ if (!actions)
+ return [];
+ return Object.keys(actions);
+}
+
+function getCompositorActions(compositor, category) {
+ var actions = COMPOSITOR_ACTIONS[compositor];
+ if (!actions)
+ return [];
+ return actions[category] || [];
+}
+
+function getCategoryOrder() {
+ return CATEGORY_ORDER;
+}
+
+function findDmsAction(actionId) {
+ for (let i = 0; i < DMS_ACTIONS.length; i++) {
+ if (DMS_ACTIONS[i].id === actionId)
+ return DMS_ACTIONS[i];
+ }
+ return null;
+}
+
+function findCompositorAction(compositor, actionId) {
+ var actions = COMPOSITOR_ACTIONS[compositor];
+ if (!actions)
+ return null;
+ for (const cat in actions) {
+ const acts = actions[cat];
+ for (let i = 0; i < acts.length; i++) {
+ if (acts[i].id === actionId)
+ return acts[i];
+ }
+ }
+ return null;
+}
+
+function getActionLabel(action, compositor) {
+ if (!action)
+ return "";
+
+ var dmsAct = findDmsAction(action);
+ if (dmsAct)
+ return dmsAct.label;
+
+ if (compositor) {
+ var compAct = findCompositorAction(compositor, action);
+ if (compAct)
+ return compAct.label;
+ var base = action.split(" ")[0];
+ compAct = findCompositorAction(compositor, base);
+ if (compAct)
+ return compAct.label;
+ }
+
+ if (action.startsWith("spawn sh -c "))
+ return action.slice(12).replace(/^["']|["']$/g, "");
+ if (action.startsWith("spawn "))
+ return action.slice(6);
+ return action;
+}
+
+function getActionType(action) {
+ if (!action)
+ return "compositor";
+ if (action.startsWith("spawn dms ipc call "))
+ return "dms";
+ if (/^spawn \w+ -c /.test(action) || action.startsWith("spawn_shell "))
+ return "shell";
+ if (action.startsWith("spawn "))
+ return "spawn";
+ return "compositor";
+}
+
+function isDmsAction(action) {
+ if (!action)
+ return false;
+ return action.startsWith("spawn dms ipc call ");
+}
+
+function isValidAction(action) {
+ if (!action)
+ return false;
+ switch (action) {
+ case "spawn":
+ case "spawn ":
+ case "spawn sh -c \"\"":
+ case "spawn sh -c ''":
+ case "spawn_shell":
+ case "spawn_shell ":
+ return false;
+ }
+ return true;
+}
+
+function isKnownCompositorAction(compositor, action) {
+ if (!action || !compositor)
+ return false;
+ var found = findCompositorAction(compositor, action);
+ if (found)
+ return true;
+ var base = action.split(" ")[0];
+ return findCompositorAction(compositor, base) !== null;
+}
+
+function buildSpawnAction(command, args) {
+ if (!command)
+ return "";
+ let parts = [command];
+ if (args && args.length > 0)
+ parts = parts.concat(args.filter(function (a) { return a; }));
+ return "spawn " + parts.join(" ");
+}
+
+function buildShellAction(compositor, shellCmd, shell) {
+ if (!shellCmd)
+ return "";
+ if (compositor === "mangowc")
+ return "spawn_shell " + shellCmd;
+ var shellBin = shell || "sh";
+ return "spawn " + shellBin + " -c \"" + shellCmd.replace(/"/g, "\\\"") + "\"";
+}
+
+function parseSpawnCommand(action) {
+ if (!action || !action.startsWith("spawn "))
+ return { command: "", args: [] };
+ const rest = action.slice(6);
+ const parts = rest.split(" ").filter(function (p) { return p; });
+ return {
+ command: parts[0] || "",
+ args: parts.slice(1)
+ };
+}
+
+function parseShellCommand(action) {
+ if (!action)
+ return "";
+ var match = action.match(/^spawn (\w+) -c (.+)$/);
+ if (match) {
+ var content = match[2];
+ if ((content.startsWith('"') && content.endsWith('"')) || (content.startsWith("'") && content.endsWith("'")))
+ content = content.slice(1, -1);
+ return content.replace(/\\"/g, "\"");
+ }
+ if (action.startsWith("spawn_shell "))
+ return action.slice(12);
+ return "";
+}
+
+function getShellFromAction(action) {
+ if (!action)
+ return "sh";
+ var match = action.match(/^spawn (\w+) -c /);
+ return match ? match[1] : "sh";
+}
+
+function getActionArgConfig(compositor, action) {
+ if (!action)
+ return null;
+
+ var baseAction = action.split(" ")[0];
+ var compositorArgs = ACTION_ARGS[compositor];
+ if (compositorArgs && compositorArgs[baseAction])
+ return { type: "compositor", base: baseAction, config: compositorArgs[baseAction] };
+
+ for (var key in DMS_ACTION_ARGS) {
+ if (action.startsWith(DMS_ACTION_ARGS[key].base))
+ return { type: "dms", base: key, config: DMS_ACTION_ARGS[key] };
+ }
+
+ return null;
+}
+
+function parseCompositorActionArgs(compositor, action) {
+ if (!action)
+ return { base: "", args: {} };
+
+ var parts = action.split(" ");
+ var base = parts[0];
+ var args = {};
+
+ var compositorArgs = ACTION_ARGS[compositor];
+ if (!compositorArgs || !compositorArgs[base])
+ return { base: action, args: {} };
+
+ var argConfig = compositorArgs[base];
+ var argParts = parts.slice(1);
+
+ switch (compositor) {
+ case "niri":
+ switch (base) {
+ case "move-column-to-workspace":
+ for (var i = 0; i < argParts.length; i++) {
+ if (argParts[i] === "focus=true" || argParts[i] === "focus=false") {
+ args.focus = argParts[i] === "focus=true";
+ } else if (!args.index) {
+ args.index = argParts[i];
+ }
+ }
+ break;
+ case "move-column-to-workspace-down":
+ case "move-column-to-workspace-up":
+ for (var k = 0; k < argParts.length; k++) {
+ if (argParts[k] === "focus=true" || argParts[k] === "focus=false")
+ args.focus = argParts[k] === "focus=true";
+ }
+ break;
+ default:
+ for (var j = 0; j < argParts.length; j++) {
+ var kv = argParts[j].split("=");
+ if (kv.length === 2) {
+ switch (kv[1]) {
+ case "true":
+ args[kv[0]] = true;
+ break;
+ case "false":
+ args[kv[0]] = false;
+ break;
+ default:
+ args[kv[0]] = kv[1];
+ }
+ } else {
+ args.value = args.value ? (args.value + " " + argParts[j]) : argParts[j];
+ }
+ }
+ }
+ break;
+ case "mangowc":
+ if (argConfig.args && argConfig.args.length > 0 && argParts.length > 0) {
+ var paramStr = argParts.join(" ");
+ var paramValues = paramStr.split(",");
+ for (var m = 0; m < argConfig.args.length && m < paramValues.length; m++) {
+ args[argConfig.args[m].name] = paramValues[m];
+ }
+ }
+ break;
+ case "hyprland":
+ if (argConfig.args && argConfig.args.length > 0) {
+ switch (base) {
+ case "resizewindowpixel":
+ case "movewindowpixel":
+ var commaIdx = argParts.join(" ").indexOf(",");
+ if (commaIdx !== -1) {
+ var fullStr = argParts.join(" ");
+ args[argConfig.args[0].name] = fullStr.substring(0, commaIdx);
+ args[argConfig.args[1].name] = fullStr.substring(commaIdx + 1);
+ } else if (argParts.length > 0) {
+ args[argConfig.args[0].name] = argParts.join(" ");
+ }
+ break;
+ case "movetoworkspace":
+ case "movetoworkspacesilent":
+ case "tagwindow":
+ case "alterzorder":
+ if (argParts.length >= 2) {
+ args[argConfig.args[0].name] = argParts[0];
+ args[argConfig.args[1].name] = argParts.slice(1).join(" ");
+ } else if (argParts.length === 1) {
+ args[argConfig.args[0].name] = argParts[0];
+ }
+ break;
+ case "moveworkspacetomonitor":
+ case "swapactiveworkspaces":
+ case "renameworkspace":
+ case "fullscreenstate":
+ case "movecursor":
+ if (argParts.length >= 2) {
+ args[argConfig.args[0].name] = argParts[0];
+ args[argConfig.args[1].name] = argParts[1];
+ } else if (argParts.length === 1) {
+ args[argConfig.args[0].name] = argParts[0];
+ }
+ break;
+ case "setprop":
+ if (argParts.length >= 3) {
+ args.window = argParts[0];
+ args.property = argParts[1];
+ args.value = argParts.slice(2).join(" ");
+ } else if (argParts.length === 2) {
+ args.window = argParts[0];
+ args.property = argParts[1];
+ }
+ break;
+ case "sendshortcut":
+ if (argParts.length >= 3) {
+ args.mod = argParts[0];
+ args.key = argParts[1];
+ args.window = argParts.slice(2).join(" ");
+ } else if (argParts.length >= 2) {
+ args.mod = argParts[0];
+ args.key = argParts[1];
+ }
+ break;
+ case "sendkeystate":
+ if (argParts.length >= 4) {
+ args.mod = argParts[0];
+ args.key = argParts[1];
+ args.state = argParts[2];
+ args.window = argParts.slice(3).join(" ");
+ }
+ break;
+ case "signalwindow":
+ if (argParts.length >= 2) {
+ args.window = argParts[0];
+ args.signal = argParts[1];
+ }
+ break;
+ default:
+ if (argParts.length > 0) {
+ if (argConfig.args.length === 1) {
+ args[argConfig.args[0].name] = argParts.join(" ");
+ } else {
+ args.value = argParts.join(" ");
+ }
+ }
+ }
+ }
+ break;
+ default:
+ if (argParts.length > 0)
+ args.value = argParts.join(" ");
+ }
+
+ return { base: base, args: args };
+}
+
+function buildCompositorAction(compositor, base, args) {
+ if (!base)
+ return "";
+
+ var parts = [base];
+
+ if (!args || Object.keys(args).length === 0)
+ return base;
+
+ switch (compositor) {
+ case "niri":
+ switch (base) {
+ case "move-column-to-workspace":
+ if (args.index)
+ parts.push(args.index);
+ if (args.focus === false)
+ parts.push("focus=false");
+ break;
+ case "move-column-to-workspace-down":
+ case "move-column-to-workspace-up":
+ if (args.focus === false)
+ parts.push("focus=false");
+ break;
+ default:
+ if (args.value)
+ parts.push(args.value);
+ else if (args.index)
+ parts.push(args.index);
+ for (var prop in args) {
+ switch (prop) {
+ case "value":
+ case "index":
+ continue;
+ }
+ var val = args[prop];
+ if (val === true)
+ parts.push(prop + "=true");
+ else if (val === false)
+ parts.push(prop + "=false");
+ else if (val !== undefined && val !== null && val !== "")
+ parts.push(prop + "=" + val);
+ }
+ }
+ break;
+ case "mangowc":
+ var compositorArgs = ACTION_ARGS.mangowc;
+ if (compositorArgs && compositorArgs[base] && compositorArgs[base].args) {
+ var argConfig = compositorArgs[base].args;
+ var argValues = [];
+ for (var i = 0; i < argConfig.length; i++) {
+ var argDef = argConfig[i];
+ var val = args[argDef.name];
+ if (val === undefined || val === "")
+ val = argDef.default || "";
+ if (val === "" && argValues.length === 0)
+ continue;
+ argValues.push(val);
+ }
+ if (argValues.length > 0)
+ parts.push(argValues.join(","));
+ } else if (args.value) {
+ parts.push(args.value);
+ }
+ break;
+ case "hyprland":
+ var hyprArgs = ACTION_ARGS.hyprland;
+ if (hyprArgs && hyprArgs[base] && hyprArgs[base].args) {
+ var hyprConfig = hyprArgs[base].args;
+ switch (base) {
+ case "resizewindowpixel":
+ case "movewindowpixel":
+ if (args[hyprConfig[0].name])
+ parts.push(args[hyprConfig[0].name]);
+ if (args[hyprConfig[1].name])
+ parts[parts.length - 1] += "," + args[hyprConfig[1].name];
+ break;
+ case "setprop":
+ if (args.window)
+ parts.push(args.window);
+ if (args.property)
+ parts.push(args.property);
+ if (args.value)
+ parts.push(args.value);
+ break;
+ case "sendshortcut":
+ if (args.mod)
+ parts.push(args.mod);
+ if (args.key)
+ parts.push(args.key);
+ if (args.window)
+ parts.push(args.window);
+ break;
+ case "sendkeystate":
+ if (args.mod)
+ parts.push(args.mod);
+ if (args.key)
+ parts.push(args.key);
+ if (args.state)
+ parts.push(args.state);
+ if (args.window)
+ parts.push(args.window);
+ break;
+ case "signalwindow":
+ if (args.window)
+ parts.push(args.window);
+ if (args.signal)
+ parts.push(args.signal);
+ break;
+ default:
+ for (var j = 0; j < hyprConfig.length; j++) {
+ var hVal = args[hyprConfig[j].name];
+ if (hVal !== undefined && hVal !== "")
+ parts.push(hVal);
+ }
+ }
+ } else if (args.value) {
+ parts.push(args.value);
+ }
+ break;
+ default:
+ if (args.value)
+ parts.push(args.value);
+ }
+
+ return parts.join(" ");
+}
+
+function parseDmsActionArgs(action) {
+ if (!action)
+ return { base: "", args: {} };
+
+ for (var key in DMS_ACTION_ARGS) {
+ var config = DMS_ACTION_ARGS[key];
+ if (!action.startsWith(config.base))
+ continue;
+
+ var rest = action.slice(config.base.length).trim();
+ var result = { base: key, args: {} };
+
+ if (!rest)
+ return result;
+
+ var tokens = [];
+ var current = "";
+ var inQuotes = false;
+ var hadQuotes = false;
+ for (var i = 0; i < rest.length; i++) {
+ var c = rest[i];
+ switch (c) {
+ case '"':
+ inQuotes = !inQuotes;
+ hadQuotes = true;
+ break;
+ case ' ':
+ if (inQuotes) {
+ current += c;
+ } else if (current || hadQuotes) {
+ tokens.push(current);
+ current = "";
+ hadQuotes = false;
+ }
+ break;
+ default:
+ current += c;
+ break;
+ }
+ }
+ if (current || hadQuotes)
+ tokens.push(current);
+
+ for (var j = 0; j < config.args.length && j < tokens.length; j++) {
+ result.args[config.args[j].name] = tokens[j];
+ }
+
+ return result;
+ }
+
+ return { base: action, args: {} };
+}
+
+function buildDmsAction(baseKey, args) {
+ var config = DMS_ACTION_ARGS[baseKey];
+ if (!config)
+ return "";
+
+ var parts = [config.base];
+
+ for (var i = 0; i < config.args.length; i++) {
+ var argDef = config.args[i];
+ var value = args?.[argDef.name];
+ if (value === undefined || value === null)
+ value = argDef.default ?? "";
+
+ if (argDef.type === "text" && value === "") {
+ parts.push('""');
+ } else if (value !== "") {
+ parts.push(value);
+ } else {
+ break;
+ }
+ }
+
+ 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/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Common/ListViewTransitions.qml b/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Common/ListViewTransitions.qml
new file mode 100644
index 0000000..2ccefc8
--- /dev/null
+++ b/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Common/ListViewTransitions.qml
@@ -0,0 +1,46 @@
+pragma Singleton
+pragma ComponentBehavior: Bound
+
+import QtQuick
+import Quickshell
+import qs.Common
+
+// Reusable ListView/GridView transitions
+Singleton {
+ id: root
+
+ readonly property Transition add: Transition {
+ DankAnim {
+ property: "opacity"
+ from: 0
+ to: 1
+ duration: Theme.expressiveDurations.expressiveEffects
+ easing.bezierCurve: Theme.expressiveCurves.emphasizedDecel
+ }
+ }
+
+ readonly property Transition remove: Transition {
+ DankAnim {
+ property: "opacity"
+ to: 0
+ duration: Theme.expressiveDurations.fast
+ easing.bezierCurve: Theme.expressiveCurves.emphasizedAccel
+ }
+ }
+
+ readonly property Transition displaced: Transition {
+ DankAnim {
+ property: "y"
+ duration: Theme.expressiveDurations.normal
+ easing.bezierCurve: Theme.expressiveCurves.expressiveEffects
+ }
+ }
+
+ readonly property Transition move: Transition {
+ DankAnim {
+ property: "y"
+ duration: Theme.expressiveDurations.normal
+ easing.bezierCurve: Theme.expressiveCurves.expressiveEffects
+ }
+ }
+}
diff --git a/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Common/ModalManager.qml b/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Common/ModalManager.qml
new file mode 100644
index 0000000..75b11f7
--- /dev/null
+++ b/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Common/ModalManager.qml
@@ -0,0 +1,35 @@
+pragma Singleton
+pragma ComponentBehavior: Bound
+
+import Quickshell
+import QtQuick
+
+Singleton {
+ id: modalManager
+
+ signal closeAllModalsExcept(var excludedModal)
+ signal modalChanged
+
+ property var currentModalsByScreen: ({})
+
+ function openModal(modal) {
+ const screenName = modal.effectiveScreen?.name ?? "unknown";
+ currentModalsByScreen[screenName] = modal;
+ modalChanged();
+ Qt.callLater(() => {
+ if (!modal.allowStacking)
+ closeAllModalsExcept(modal);
+ if (!modal.keepPopoutsOpen)
+ PopoutManager.closeAllPopouts();
+ TrayMenuManager.closeAllMenus();
+ });
+ }
+
+ function closeModal(modal) {
+ const screenName = modal.effectiveScreen?.name ?? "unknown";
+ if (currentModalsByScreen[screenName] === modal) {
+ delete currentModalsByScreen[screenName];
+ modalChanged();
+ }
+ }
+}
diff --git a/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Common/OSDManager.qml b/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Common/OSDManager.qml
new file mode 100644
index 0000000..06bc07f
--- /dev/null
+++ b/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Common/OSDManager.qml
@@ -0,0 +1,46 @@
+pragma Singleton
+pragma ComponentBehavior: Bound
+
+import Quickshell
+import QtQuick
+
+Singleton {
+ id: osdManager
+
+ property var currentOSDsByScreen: ({})
+
+ Connections {
+ target: Quickshell
+ function onScreensChanged() {
+ const activeNames = {};
+ for (let i = 0; i < Quickshell.screens.length; i++)
+ activeNames[Quickshell.screens[i].name] = true;
+ for (const screenName in osdManager.currentOSDsByScreen) {
+ if (activeNames[screenName])
+ continue;
+ osdManager.currentOSDsByScreen[screenName] = null;
+ }
+ }
+ }
+
+ function showOSD(osd) {
+ if (!osd || !osd.screen)
+ return;
+ const screenName = osd.screen.name;
+ const currentOSD = currentOSDsByScreen[screenName];
+
+ if (currentOSD && currentOSD !== osd) {
+ if (typeof currentOSD.hide === "function") {
+ try {
+ currentOSD.hide();
+ } catch (e) {
+ currentOSDsByScreen[screenName] = null;
+ }
+ } else {
+ currentOSDsByScreen[screenName] = null;
+ }
+ }
+
+ currentOSDsByScreen[screenName] = osd;
+ }
+}
diff --git a/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Common/Paths.qml b/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Common/Paths.qml
new file mode 100644
index 0000000..19a4b16
--- /dev/null
+++ b/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Common/Paths.qml
@@ -0,0 +1,131 @@
+pragma Singleton
+pragma ComponentBehavior: Bound
+
+import Quickshell
+import QtCore
+import qs.Services
+
+Singleton {
+ id: root
+
+ readonly property url home: StandardPaths.standardLocations(StandardPaths.HomeLocation)[0]
+ readonly property url pictures: StandardPaths.standardLocations(StandardPaths.PicturesLocation)[0]
+ readonly property url xdgCache: StandardPaths.standardLocations(StandardPaths.GenericCacheLocation)[0]
+
+ readonly property url data: `${StandardPaths.standardLocations(StandardPaths.GenericDataLocation)[0]}/DankMaterialShell`
+ readonly property url state: `${StandardPaths.standardLocations(StandardPaths.GenericStateLocation)[0]}/DankMaterialShell`
+ readonly property url cache: `${StandardPaths.standardLocations(StandardPaths.GenericCacheLocation)[0]}/DankMaterialShell`
+ readonly property url config: `${StandardPaths.standardLocations(StandardPaths.GenericConfigLocation)[0]}/DankMaterialShell`
+
+ readonly property url imagecache: `${cache}/imagecache`
+
+ function stringify(path: url): string {
+ return path.toString().replace(/%20/g, " ");
+ }
+
+ function expandTilde(path: string): string {
+ return strip(path.replace("~", stringify(root.home)));
+ }
+
+ function shortenHome(path: string): string {
+ return path.replace(strip(root.home), "~");
+ }
+
+ function strip(path: url): string {
+ return stringify(path).replace("file://", "");
+ }
+
+ function toFileUrl(path: string): string {
+ return path.startsWith("file://") ? path : "file://" + path;
+ }
+
+ function mkdir(path: url): void {
+ Quickshell.execDetached(["mkdir", "-p", strip(path)]);
+ }
+
+ function copy(from: url, to: url): void {
+ Quickshell.execDetached(["cp", strip(from), strip(to)]);
+ }
+
+ function isSteamApp(appId: string): bool {
+ return appId && /^steam_app_\d+$/.test(appId);
+ }
+
+ function moddedAppId(appId: string): string {
+ const subs = SettingsData.appIdSubstitutions || [];
+ for (let i = 0; i < subs.length; i++) {
+ const sub = subs[i];
+ if (sub.type === "exact" && appId === sub.pattern) {
+ return sub.replacement;
+ } else if (sub.type === "contains" && appId.includes(sub.pattern)) {
+ return sub.replacement;
+ } else if (sub.type === "regex") {
+ const match = appId.match(new RegExp(sub.pattern));
+ if (match) {
+ return sub.replacement.replace(/\$(\d+)/g, (_, n) => match[n] || "");
+ }
+ }
+ }
+ const steamMatch = appId.match(/^steam_app_(\d+)$/);
+ if (steamMatch)
+ return `steam_icon_${steamMatch[1]}`;
+ return appId;
+ }
+
+ function resolveIconPath(iconName: string): string {
+ if (!iconName)
+ return "";
+ const moddedId = moddedAppId(iconName);
+ if (moddedId !== iconName) {
+ if (moddedId.startsWith("~") || moddedId.startsWith("/"))
+ return toFileUrl(expandTilde(moddedId));
+ if (moddedId.startsWith("file://"))
+ return moddedId;
+ return Quickshell.iconPath(moddedId, true);
+ }
+ return Quickshell.iconPath(iconName, true) || DesktopService.resolveIconPath(iconName);
+ }
+
+ function resolveIconUrl(iconName: string): string {
+ if (!iconName)
+ return "";
+ const moddedId = moddedAppId(iconName);
+ if (moddedId !== iconName) {
+ if (moddedId.startsWith("~") || moddedId.startsWith("/"))
+ return toFileUrl(expandTilde(moddedId));
+ if (moddedId.startsWith("file://"))
+ return moddedId;
+ return "image://icon/" + moddedId;
+ }
+ return "image://icon/" + iconName;
+ }
+
+ function getAppIcon(appId: string, desktopEntry: var): string {
+ // ! TODO - after QS 0.3, we can install our icon properly
+ if (appId === "org.quickshell" || appId === "com.danklinux.dms") {
+ return Qt.resolvedUrl("../assets/danklogo.svg");
+ }
+
+ const moddedId = moddedAppId(appId);
+ if (moddedId !== appId)
+ return resolveIconPath(appId);
+
+ if (desktopEntry && desktopEntry.icon) {
+ return Quickshell.iconPath(desktopEntry.icon, true);
+ }
+
+ const icon = Quickshell.iconPath(appId, true);
+ if (icon && icon !== "")
+ return icon;
+
+ return DesktopService.resolveIconPath(appId);
+ }
+
+ function getAppName(appId: string, desktopEntry: var): string {
+ if (appId === "org.quickshell" || appId === "com.danklinux.dms") {
+ return "dms";
+ }
+
+ return desktopEntry && desktopEntry.name ? desktopEntry.name : appId;
+ }
+}
diff --git a/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Common/PopoutManager.qml b/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Common/PopoutManager.qml
new file mode 100644
index 0000000..47b4eda
--- /dev/null
+++ b/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Common/PopoutManager.qml
@@ -0,0 +1,186 @@
+pragma Singleton
+pragma ComponentBehavior: Bound
+
+import Quickshell
+import QtQuick
+
+Singleton {
+ id: root
+
+ property var currentPopoutsByScreen: ({})
+ property var currentPopoutTriggers: ({})
+
+ signal popoutOpening
+ signal popoutChanged
+
+ function _closePopout(popout) {
+ try {
+ switch (true) {
+ case popout.dashVisible !== undefined:
+ popout.dashVisible = false;
+ return;
+ case popout.notificationHistoryVisible !== undefined:
+ popout.notificationHistoryVisible = false;
+ return;
+ default:
+ if (typeof popout.close !== "function")
+ return;
+ popout.close();
+ }
+ } catch (e) {
+ return;
+ }
+ }
+
+ function _isStale(popout) {
+ try {
+ if (!popout || !("shouldBeVisible" in popout))
+ return true;
+ if (!popout.screen)
+ return true;
+ return false;
+ } catch (e) {
+ return true;
+ }
+ }
+
+ function showPopout(popout) {
+ if (!popout || !popout.screen)
+ return;
+ popoutOpening();
+
+ const screenName = popout.screen.name;
+
+ for (const otherScreenName in currentPopoutsByScreen) {
+ const otherPopout = currentPopoutsByScreen[otherScreenName];
+ if (!otherPopout || otherPopout === popout)
+ continue;
+ if (_isStale(otherPopout)) {
+ currentPopoutsByScreen[otherScreenName] = null;
+ continue;
+ }
+ _closePopout(otherPopout);
+ }
+
+ currentPopoutsByScreen[screenName] = popout;
+ popoutChanged();
+ ModalManager.closeAllModalsExcept(null);
+ }
+
+ function hidePopout(popout) {
+ if (!popout || !popout.screen)
+ return;
+ const screenName = popout.screen.name;
+ if (currentPopoutsByScreen[screenName] === popout) {
+ currentPopoutsByScreen[screenName] = null;
+ currentPopoutTriggers[screenName] = null;
+ popoutChanged();
+ }
+ }
+
+ function closeAllPopouts() {
+ for (const screenName in currentPopoutsByScreen) {
+ const popout = currentPopoutsByScreen[screenName];
+ if (!popout || _isStale(popout))
+ continue;
+ _closePopout(popout);
+ }
+ currentPopoutsByScreen = {};
+ }
+
+ function getActivePopout(screen) {
+ if (!screen)
+ return null;
+ return currentPopoutsByScreen[screen.name] || null;
+ }
+
+ function requestPopout(popout, tabIndex, triggerSource) {
+ if (!popout || !popout.screen)
+ return;
+ const screenName = popout.screen.name;
+ const currentPopout = currentPopoutsByScreen[screenName];
+ const triggerId = triggerSource !== undefined ? triggerSource : tabIndex;
+
+ const willOpen = !(currentPopout === popout && popout.shouldBeVisible && triggerId !== undefined && currentPopoutTriggers[screenName] === triggerId);
+ if (willOpen) {
+ popoutOpening();
+ }
+
+ let movedFromOtherScreen = false;
+ for (const otherScreenName in currentPopoutsByScreen) {
+ if (otherScreenName === screenName)
+ continue;
+ const otherPopout = currentPopoutsByScreen[otherScreenName];
+ if (!otherPopout)
+ continue;
+
+ if (_isStale(otherPopout)) {
+ currentPopoutsByScreen[otherScreenName] = null;
+ currentPopoutTriggers[otherScreenName] = null;
+ continue;
+ }
+
+ if (otherPopout === popout) {
+ movedFromOtherScreen = true;
+ currentPopoutsByScreen[otherScreenName] = null;
+ currentPopoutTriggers[otherScreenName] = null;
+ continue;
+ }
+
+ _closePopout(otherPopout);
+ }
+
+ if (currentPopout && currentPopout !== popout) {
+ if (_isStale(currentPopout)) {
+ currentPopoutsByScreen[screenName] = null;
+ currentPopoutTriggers[screenName] = null;
+ } else {
+ _closePopout(currentPopout);
+ }
+ }
+
+ if (currentPopout === popout && popout.shouldBeVisible && !movedFromOtherScreen) {
+ if (triggerId !== undefined && currentPopoutTriggers[screenName] === triggerId) {
+ _closePopout(popout);
+ return;
+ }
+
+ if (triggerId === undefined) {
+ _closePopout(popout);
+ return;
+ }
+
+ if (tabIndex !== undefined && popout.currentTabIndex !== undefined) {
+ popout.currentTabIndex = tabIndex;
+ }
+ if (popout.updateSurfacePosition)
+ popout.updateSurfacePosition();
+ currentPopoutTriggers[screenName] = triggerId;
+ return;
+ }
+
+ currentPopoutTriggers[screenName] = triggerId;
+ currentPopoutsByScreen[screenName] = popout;
+ popoutChanged();
+
+ if (tabIndex !== undefined && popout.currentTabIndex !== undefined) {
+ popout.currentTabIndex = tabIndex;
+ }
+
+ if (currentPopout !== popout) {
+ ModalManager.closeAllModalsExcept(null);
+ }
+
+ if (movedFromOtherScreen) {
+ popout.open();
+ } else {
+ if (popout.dashVisible !== undefined) {
+ popout.dashVisible = true;
+ } else if (popout.notificationHistoryVisible !== undefined) {
+ popout.notificationHistoryVisible = true;
+ } else {
+ popout.open();
+ }
+ }
+ }
+}
diff --git a/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Common/Proc.qml b/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Common/Proc.qml
new file mode 100644
index 0000000..813f938
--- /dev/null
+++ b/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Common/Proc.qml
@@ -0,0 +1,141 @@
+pragma Singleton
+pragma ComponentBehavior: Bound
+
+import QtQuick
+import Quickshell
+
+Singleton {
+ id: root
+
+ readonly property int noTimeout: -1
+ property int defaultDebounceMs: 50
+ property int defaultTimeoutMs: 10000
+ property var _procDebouncers: ({})
+
+ function runCommand(id, command, callback, debounceMs, timeoutMs) {
+ const wait = (typeof debounceMs === "number" && debounceMs >= 0) ? debounceMs : defaultDebounceMs;
+ const timeout = (typeof timeoutMs === "number") ? timeoutMs : defaultTimeoutMs;
+ let procId = id ? id : Math.random();
+ const isRandomId = !id;
+
+ if (!_procDebouncers[procId]) {
+ const t = Qt.createQmlObject('import QtQuick; Timer { repeat: false }', root);
+ t.triggered.connect(function () {
+ _launchProc(procId, isRandomId);
+ });
+ _procDebouncers[procId] = {
+ timer: t,
+ command: command,
+ callback: callback,
+ waitMs: wait,
+ timeoutMs: timeout,
+ isRandomId: isRandomId
+ };
+ } else {
+ _procDebouncers[procId].command = command;
+ _procDebouncers[procId].callback = callback;
+ _procDebouncers[procId].waitMs = wait;
+ _procDebouncers[procId].timeoutMs = timeout;
+ }
+
+ const entry = _procDebouncers[procId];
+ entry.timer.interval = entry.waitMs;
+ entry.timer.restart();
+ }
+
+ function _launchProc(id, isRandomId) {
+ const entry = _procDebouncers[id];
+ if (!entry)
+ return;
+ const proc = Qt.createQmlObject('import Quickshell.Io; Process { running: false }', root);
+ const out = Qt.createQmlObject('import Quickshell.Io; StdioCollector {}', proc);
+ const err = Qt.createQmlObject('import Quickshell.Io; StdioCollector {}', proc);
+ const timeoutTimer = Qt.createQmlObject('import QtQuick; Timer { repeat: false }', root);
+
+ proc.stdout = out;
+ proc.stderr = err;
+ proc.command = entry.command;
+
+ let capturedOut = "";
+ let capturedErr = "";
+ let exitSeen = false;
+ let exitCodeValue = -1;
+ let outSeen = false;
+ let errSeen = false;
+ let timedOut = false;
+
+ timeoutTimer.interval = entry.timeoutMs;
+ timeoutTimer.triggered.connect(function () {
+ if (!exitSeen) {
+ timedOut = true;
+ proc.running = false;
+ exitSeen = true;
+ exitCodeValue = 124;
+ maybeComplete();
+ }
+ });
+
+ out.streamFinished.connect(function () {
+ try {
+ capturedOut = out.text || "";
+ } catch (e) {
+ capturedOut = "";
+ }
+ outSeen = true;
+ maybeComplete();
+ });
+
+ err.streamFinished.connect(function () {
+ try {
+ capturedErr = err.text || "";
+ } catch (e) {
+ capturedErr = "";
+ }
+ errSeen = true;
+ maybeComplete();
+ });
+
+ proc.exited.connect(function (code) {
+ timeoutTimer.stop();
+ exitSeen = true;
+ exitCodeValue = code;
+ maybeComplete();
+ });
+
+ function maybeComplete() {
+ if (!exitSeen || !outSeen || !errSeen)
+ return;
+ timeoutTimer.stop();
+ if (entry && entry.callback && typeof entry.callback === "function") {
+ try {
+ const safeOutput = capturedOut !== null && capturedOut !== undefined ? capturedOut : "";
+ const safeExitCode = exitCodeValue !== null && exitCodeValue !== undefined ? exitCodeValue : -1;
+ entry.callback(safeOutput, safeExitCode);
+ } catch (e) {
+ console.warn("runCommand callback error for command:", entry.command, "Error:", e);
+ }
+ }
+ try {
+ proc.destroy();
+ } catch (_) {}
+ try {
+ timeoutTimer.destroy();
+ } catch (_) {}
+
+ if (isRandomId || entry.isRandomId) {
+ Qt.callLater(function () {
+ if (_procDebouncers[id]) {
+ try {
+ _procDebouncers[id].timer.destroy();
+ } catch (_) {}
+ delete _procDebouncers[id];
+ }
+ });
+ }
+ }
+
+ proc.running = true;
+ if (entry.timeoutMs !== noTimeout)
+ timeoutTimer.start();
+ }
+}
diff --git a/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Common/Ref.qml b/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Common/Ref.qml
new file mode 100644
index 0000000..406b50c
--- /dev/null
+++ b/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Common/Ref.qml
@@ -0,0 +1,9 @@
+import QtQuick
+import Quickshell
+
+QtObject {
+ required property Singleton service
+
+ Component.onCompleted: service.refCount++
+ Component.onDestruction: service.refCount--
+}
diff --git a/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Common/SessionData.qml b/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Common/SessionData.qml
new file mode 100644
index 0000000..13b1359
--- /dev/null
+++ b/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Common/SessionData.qml
@@ -0,0 +1,1437 @@
+pragma Singleton
+pragma ComponentBehavior: Bound
+
+import QtCore
+import QtQuick
+import Quickshell
+import Quickshell.Io
+import qs.Common
+import qs.Services
+import "settings/SessionSpec.js" as Spec
+import "settings/SessionStore.js" as Store
+
+Singleton {
+ id: root
+
+ readonly property int sessionConfigVersion: 3
+
+ readonly property bool isGreeterMode: Quickshell.env("DMS_RUN_GREETER") === "1" || Quickshell.env("DMS_RUN_GREETER") === "true"
+ property bool _parseError: false
+ property bool _hasLoaded: false
+ property bool _isReadOnly: false
+ property bool _hasUnsavedChanges: false
+ property var _loadedSessionSnapshot: null
+ readonly property var _hooks: ({
+ "updateLocale": updateLocale
+ })
+ readonly property string _stateUrl: StandardPaths.writableLocation(StandardPaths.GenericStateLocation)
+ readonly property string _stateDir: Paths.strip(_stateUrl)
+
+ property bool isLightMode: false
+ property bool doNotDisturb: false
+ property bool isSwitchingMode: false
+ property bool suppressOSD: true
+
+ Timer {
+ id: osdSuppressTimer
+ interval: 2000
+ running: true
+ onTriggered: root.suppressOSD = false
+ }
+
+ function suppressOSDTemporarily() {
+ suppressOSD = true;
+ osdSuppressTimer.restart();
+ }
+
+ Connections {
+ target: SessionService
+ function onSessionResumed() {
+ root.suppressOSD = true;
+ osdSuppressTimer.restart();
+ }
+ }
+
+ property string wallpaperPath: "/usr/share/raveos/hyprland-theme/theme-data/background"
+ property bool perMonitorWallpaper: false
+ property var monitorWallpapers: ({})
+ property bool perModeWallpaper: false
+ property string wallpaperPathLight: ""
+ property string wallpaperPathDark: ""
+ property var monitorWallpapersLight: ({})
+ property var monitorWallpapersDark: ({})
+ property var monitorWallpaperFillModes: ({})
+ property string wallpaperTransition: "fade"
+ readonly property var availableWallpaperTransitions: ["none", "fade", "wipe", "disc", "stripes", "iris bloom", "pixelate", "portal"]
+ property var includedTransitions: availableWallpaperTransitions.filter(t => t !== "none")
+
+ property bool wallpaperCyclingEnabled: false
+ property string wallpaperCyclingMode: "interval"
+ property int wallpaperCyclingInterval: 300
+ property string wallpaperCyclingTime: "06:00"
+ property var monitorCyclingSettings: ({})
+
+ property bool nightModeEnabled: false
+ property int nightModeTemperature: 4500
+ property int nightModeHighTemperature: 6500
+ property bool nightModeAutoEnabled: false
+ property string nightModeAutoMode: "time"
+ property int nightModeStartHour: 18
+ property int nightModeStartMinute: 0
+ property int nightModeEndHour: 6
+ property int nightModeEndMinute: 0
+ property real latitude: 0.0
+ property real longitude: 0.0
+ property bool nightModeUseIPLocation: false
+ property string nightModeLocationProvider: ""
+
+ property bool themeModeAutoEnabled: false
+ property string themeModeAutoMode: "time"
+ property int themeModeStartHour: 18
+ property int themeModeStartMinute: 0
+ property int themeModeEndHour: 6
+ property int themeModeEndMinute: 0
+ property bool themeModeShareGammaSettings: true
+ property string themeModeNextTransition: ""
+
+ property var pinnedApps: []
+ property var barPinnedApps: []
+ property int dockLauncherPosition: 0
+ property var hiddenTrayIds: []
+ property var trayItemOrder: []
+ property var recentColors: []
+ property bool showThirdPartyPlugins: false
+ property string launchPrefix: ""
+ property string lastBrightnessDevice: ""
+ property var brightnessExponentialDevices: ({})
+ property var brightnessUserSetValues: ({})
+ property var brightnessExponentValues: ({})
+
+ property int selectedGpuIndex: 0
+ property bool nvidiaGpuTempEnabled: false
+ property bool nonNvidiaGpuTempEnabled: false
+ property var enabledGpuPciIds: []
+
+ property string wifiDeviceOverride: ""
+ property bool weatherHourlyDetailed: true
+
+ property string weatherLocation: "New York, NY"
+ property string weatherCoordinates: "40.7128,-74.0060"
+
+ property var hiddenApps: []
+ property var appOverrides: ({})
+ property bool searchAppActions: true
+
+ property string vpnLastConnected: ""
+
+ property string lastPlayerIdentity: ""
+
+ property var deviceMaxVolumes: ({})
+ property var hiddenOutputDeviceNames: []
+ property var hiddenInputDeviceNames: []
+
+ property string locale: ""
+ property string timeLocale: ""
+
+ property string launcherLastMode: "all"
+ property string launcherLastQuery: ""
+ property var launcherQueryHistory: []
+ property string appDrawerLastMode: "apps"
+ property string niriOverviewLastMode: "apps"
+ property string settingsSidebarExpandedIds: ","
+ property string settingsSidebarCollapsedIds: ","
+
+ Component.onCompleted: {
+ if (!isGreeterMode) {
+ loadSettings();
+ }
+ }
+
+ property var _pendingMigration: null
+
+ function loadSettings() {
+ _hasUnsavedChanges = false;
+ _pendingMigration = null;
+
+ if (isGreeterMode) {
+ parseSettings(greeterSessionFile.text());
+ return;
+ }
+
+ try {
+ const txt = settingsFile.text();
+ let obj = (txt && txt.trim()) ? JSON.parse(txt) : null;
+
+ if (obj?.brightnessLogarithmicDevices && !obj?.brightnessExponentialDevices)
+ obj.brightnessExponentialDevices = obj.brightnessLogarithmicDevices;
+
+ if (obj?.nightModeStartTime !== undefined) {
+ const parts = obj.nightModeStartTime.split(":");
+ obj.nightModeStartHour = parseInt(parts[0]) || 18;
+ obj.nightModeStartMinute = parseInt(parts[1]) || 0;
+ }
+ if (obj?.nightModeEndTime !== undefined) {
+ const parts = obj.nightModeEndTime.split(":");
+ obj.nightModeEndHour = parseInt(parts[0]) || 6;
+ obj.nightModeEndMinute = parseInt(parts[1]) || 0;
+ }
+
+ const oldVersion = obj?.configVersion ?? 0;
+ if (obj && oldVersion === 0)
+ migrateFromUndefinedToV1(obj);
+
+ if (obj && oldVersion < sessionConfigVersion) {
+ const settingsDataRef = (typeof SettingsData !== "undefined") ? SettingsData : null;
+ const migrated = Store.migrateToVersion(obj, sessionConfigVersion, settingsDataRef);
+ if (migrated) {
+ _pendingMigration = migrated;
+ obj = migrated;
+ }
+ }
+
+ Store.parse(root, obj);
+
+ _loadedSessionSnapshot = getCurrentSessionJson();
+ _hasLoaded = true;
+
+ if (!isGreeterMode && typeof Theme !== "undefined")
+ Theme.generateSystemThemesFromCurrentTheme();
+
+ if (typeof WallpaperCyclingService !== "undefined")
+ WallpaperCyclingService.updateCyclingState();
+
+ _checkSessionWritable();
+ } catch (e) {
+ _parseError = true;
+ const msg = e.message;
+ console.error("SessionData: Failed to parse session.json - file will not be overwritten.");
+ Qt.callLater(() => ToastService.showError(I18n.tr("Failed to parse session.json"), msg));
+ }
+ }
+
+ function _checkSessionWritable() {
+ sessionWritableCheckProcess.running = true;
+ }
+
+ function _onWritableCheckComplete(writable) {
+ const wasReadOnly = _isReadOnly;
+ _isReadOnly = !writable;
+ if (_isReadOnly) {
+ _hasUnsavedChanges = _checkForUnsavedChanges();
+ } else {
+ _loadedSessionSnapshot = getCurrentSessionJson();
+ _hasUnsavedChanges = false;
+ if (wasReadOnly && _pendingMigration)
+ settingsFile.setText(JSON.stringify(_pendingMigration, null, 2));
+ }
+ _pendingMigration = null;
+ }
+
+ function _checkForUnsavedChanges() {
+ if (!_hasLoaded || !_loadedSessionSnapshot)
+ return false;
+ const current = getCurrentSessionJson();
+ return current !== _loadedSessionSnapshot;
+ }
+
+ function getCurrentSessionJson() {
+ return JSON.stringify(Store.toJson(root), null, 2);
+ }
+
+ function parseSettings(content) {
+ _parseError = false;
+ try {
+ let obj = (content && content.trim()) ? JSON.parse(content) : null;
+
+ if (obj?.brightnessLogarithmicDevices && !obj?.brightnessExponentialDevices)
+ obj.brightnessExponentialDevices = obj.brightnessLogarithmicDevices;
+
+ if (obj?.nightModeStartTime !== undefined) {
+ const parts = obj.nightModeStartTime.split(":");
+ obj.nightModeStartHour = parseInt(parts[0]) || 18;
+ obj.nightModeStartMinute = parseInt(parts[1]) || 0;
+ }
+ if (obj?.nightModeEndTime !== undefined) {
+ const parts = obj.nightModeEndTime.split(":");
+ obj.nightModeEndHour = parseInt(parts[0]) || 6;
+ obj.nightModeEndMinute = parseInt(parts[1]) || 0;
+ }
+
+ const oldVersion = obj?.configVersion ?? 0;
+ if (obj && oldVersion === 0)
+ migrateFromUndefinedToV1(obj);
+
+ if (obj && oldVersion < sessionConfigVersion) {
+ const settingsDataRef = (typeof SettingsData !== "undefined") ? SettingsData : null;
+ const migrated = Store.migrateToVersion(obj, sessionConfigVersion, settingsDataRef);
+ if (migrated) {
+ _pendingMigration = migrated;
+ obj = migrated;
+ }
+ }
+
+ Store.parse(root, obj);
+
+ _loadedSessionSnapshot = getCurrentSessionJson();
+ _hasLoaded = true;
+
+ if (!isGreeterMode && typeof Theme !== "undefined")
+ Theme.generateSystemThemesFromCurrentTheme();
+
+ if (typeof WallpaperCyclingService !== "undefined")
+ WallpaperCyclingService.updateCyclingState();
+ } catch (e) {
+ _parseError = true;
+ const msg = e.message;
+ console.error("SessionData: Failed to parse session.json - file will not be overwritten.");
+ Qt.callLater(() => ToastService.showError(I18n.tr("Failed to parse session.json"), msg));
+ }
+ }
+
+ function saveSettings() {
+ if (isGreeterMode || _parseError || !_hasLoaded)
+ return;
+ settingsFile.setText(getCurrentSessionJson());
+ if (_isReadOnly)
+ _checkSessionWritable();
+ }
+
+ function set(key, value) {
+ Spec.set(root, key, value, saveSettings, _hooks);
+ }
+
+ function migrateFromUndefinedToV1(settings) {
+ if (typeof SettingsData !== "undefined") {
+ if (settings.acMonitorTimeout !== undefined) {
+ SettingsData.set("acMonitorTimeout", settings.acMonitorTimeout);
+ }
+ if (settings.acLockTimeout !== undefined) {
+ SettingsData.set("acLockTimeout", settings.acLockTimeout);
+ }
+ if (settings.acSuspendTimeout !== undefined) {
+ SettingsData.set("acSuspendTimeout", settings.acSuspendTimeout);
+ }
+ if (settings.acHibernateTimeout !== undefined) {
+ SettingsData.set("acHibernateTimeout", settings.acHibernateTimeout);
+ }
+ if (settings.batteryMonitorTimeout !== undefined) {
+ SettingsData.set("batteryMonitorTimeout", settings.batteryMonitorTimeout);
+ }
+ if (settings.batteryLockTimeout !== undefined) {
+ SettingsData.set("batteryLockTimeout", settings.batteryLockTimeout);
+ }
+ if (settings.batterySuspendTimeout !== undefined) {
+ SettingsData.set("batterySuspendTimeout", settings.batterySuspendTimeout);
+ }
+ if (settings.batteryHibernateTimeout !== undefined) {
+ SettingsData.set("batteryHibernateTimeout", settings.batteryHibernateTimeout);
+ }
+ if (settings.lockBeforeSuspend !== undefined) {
+ SettingsData.set("lockBeforeSuspend", settings.lockBeforeSuspend);
+ }
+ if (settings.loginctlLockIntegration !== undefined) {
+ SettingsData.set("loginctlLockIntegration", settings.loginctlLockIntegration);
+ }
+ if (settings.launchPrefix !== undefined) {
+ SettingsData.set("launchPrefix", settings.launchPrefix);
+ }
+ }
+ if (typeof CacheData !== "undefined") {
+ if (settings.wallpaperLastPath !== undefined) {
+ CacheData.wallpaperLastPath = settings.wallpaperLastPath;
+ }
+ if (settings.profileLastPath !== undefined) {
+ CacheData.profileLastPath = settings.profileLastPath;
+ }
+ CacheData.saveCache();
+ }
+ }
+
+ function setLightMode(lightMode) {
+ isSwitchingMode = true;
+ syncWallpaperForCurrentMode(lightMode);
+ isLightMode = lightMode;
+ saveSettings();
+ Qt.callLater(() => {
+ isSwitchingMode = false;
+ });
+ }
+
+ function setDoNotDisturb(enabled) {
+ doNotDisturb = enabled;
+ saveSettings();
+ }
+
+ function setWallpaperPath(path) {
+ wallpaperPath = path;
+ saveSettings();
+ }
+
+ function setWallpaper(imagePath) {
+ wallpaperPath = imagePath;
+ if (perModeWallpaper) {
+ if (isLightMode) {
+ wallpaperPathLight = imagePath;
+ } else {
+ wallpaperPathDark = imagePath;
+ }
+ }
+ saveSettings();
+
+ if (typeof Theme !== "undefined") {
+ Theme.generateSystemThemesFromCurrentTheme();
+ }
+ }
+
+ function setWallpaperColor(color) {
+ wallpaperPath = color;
+ if (perModeWallpaper) {
+ if (isLightMode) {
+ wallpaperPathLight = color;
+ } else {
+ wallpaperPathDark = color;
+ }
+ }
+ saveSettings();
+
+ if (typeof Theme !== "undefined") {
+ Theme.generateSystemThemesFromCurrentTheme();
+ }
+ }
+
+ function clearWallpaper() {
+ wallpaperPath = "";
+ saveSettings();
+
+ if (typeof Theme !== "undefined") {
+ if (typeof SettingsData !== "undefined" && SettingsData.theme) {
+ Theme.switchTheme(SettingsData.theme);
+ } else {
+ Theme.switchTheme("purple");
+ }
+ }
+ }
+
+ function setPerMonitorWallpaper(enabled) {
+ perMonitorWallpaper = enabled;
+ if (enabled && perModeWallpaper) {
+ syncWallpaperForCurrentMode();
+ }
+ saveSettings();
+
+ if (typeof Theme !== "undefined") {
+ Theme.generateSystemThemesFromCurrentTheme();
+ }
+ }
+
+ function setPerModeWallpaper(enabled) {
+ if (enabled && wallpaperCyclingEnabled) {
+ setWallpaperCyclingEnabled(false);
+ }
+ if (enabled && perMonitorWallpaper) {
+ var monitorCyclingAny = false;
+ for (var key in monitorCyclingSettings) {
+ if (monitorCyclingSettings[key].enabled) {
+ monitorCyclingAny = true;
+ break;
+ }
+ }
+ if (monitorCyclingAny) {
+ var newSettings = Object.assign({}, monitorCyclingSettings);
+ for (var screenName in newSettings) {
+ newSettings[screenName].enabled = false;
+ }
+ monitorCyclingSettings = newSettings;
+ }
+ }
+
+ perModeWallpaper = enabled;
+ if (enabled) {
+ if (perMonitorWallpaper) {
+ monitorWallpapersLight = Object.assign({}, monitorWallpapers);
+ monitorWallpapersDark = Object.assign({}, monitorWallpapers);
+ } else {
+ wallpaperPathLight = wallpaperPath;
+ wallpaperPathDark = wallpaperPath;
+ }
+ } else {
+ syncWallpaperForCurrentMode();
+ }
+ saveSettings();
+
+ if (typeof Theme !== "undefined") {
+ Theme.generateSystemThemesFromCurrentTheme();
+ }
+ }
+
+ function setMonitorWallpaper(screenName, path) {
+ var screen = null;
+ var screens = Quickshell.screens;
+ for (var i = 0; i < screens.length; i++) {
+ if (screens[i].name === screenName) {
+ screen = screens[i];
+ break;
+ }
+ }
+
+ if (!screen) {
+ console.warn("SessionData: Screen not found");
+ return;
+ }
+
+ var identifier = typeof SettingsData !== "undefined" ? SettingsData.getScreenDisplayName(screen) : screen.name;
+
+ var newMonitorWallpapers = {};
+ for (var key in monitorWallpapers) {
+ var isThisScreen = key === screen.name || (screen.model && key === screen.model);
+ if (!isThisScreen) {
+ newMonitorWallpapers[key] = monitorWallpapers[key];
+ }
+ }
+
+ if (path && path !== "") {
+ newMonitorWallpapers[identifier] = path;
+ }
+
+ monitorWallpapers = newMonitorWallpapers;
+
+ if (perModeWallpaper) {
+ if (isLightMode) {
+ var newLight = {};
+ for (var key in monitorWallpapersLight) {
+ var isThisScreen = key === screen.name || (screen.model && key === screen.model);
+ if (!isThisScreen) {
+ newLight[key] = monitorWallpapersLight[key];
+ }
+ }
+ if (path && path !== "") {
+ newLight[identifier] = path;
+ }
+ monitorWallpapersLight = newLight;
+ } else {
+ var newDark = {};
+ for (var key in monitorWallpapersDark) {
+ var isThisScreen = key === screen.name || (screen.model && key === screen.model);
+ if (!isThisScreen) {
+ newDark[key] = monitorWallpapersDark[key];
+ }
+ }
+ if (path && path !== "") {
+ newDark[identifier] = path;
+ }
+ monitorWallpapersDark = newDark;
+ }
+ }
+
+ saveSettings();
+
+ if (typeof Theme !== "undefined" && typeof Quickshell !== "undefined" && typeof SettingsData !== "undefined") {
+ var screens = Quickshell.screens;
+ if (screens.length > 0) {
+ var targetMonitor = (SettingsData.matugenTargetMonitor && SettingsData.matugenTargetMonitor !== "") ? SettingsData.matugenTargetMonitor : screens[0].name;
+ if (screenName === targetMonitor) {
+ Theme.generateSystemThemesFromCurrentTheme();
+ }
+ }
+ }
+ }
+
+ function setWallpaperTransition(transition) {
+ wallpaperTransition = transition;
+ saveSettings();
+ }
+
+ function setWallpaperCyclingEnabled(enabled) {
+ wallpaperCyclingEnabled = enabled;
+ saveSettings();
+ }
+
+ function setWallpaperCyclingMode(mode) {
+ wallpaperCyclingMode = mode;
+ saveSettings();
+ }
+
+ function setWallpaperCyclingInterval(interval) {
+ wallpaperCyclingInterval = interval;
+ saveSettings();
+ }
+
+ function setWallpaperCyclingTime(time) {
+ wallpaperCyclingTime = time;
+ saveSettings();
+ }
+
+ function setMonitorCyclingEnabled(screenName, enabled) {
+ var screen = null;
+ var screens = Quickshell.screens;
+ for (var i = 0; i < screens.length; i++) {
+ if (screens[i].name === screenName) {
+ screen = screens[i];
+ break;
+ }
+ }
+
+ if (!screen) {
+ console.warn("SessionData: Screen not found");
+ return;
+ }
+
+ var identifier = typeof SettingsData !== "undefined" ? SettingsData.getScreenDisplayName(screen) : screen.name;
+
+ var newSettings = {};
+ for (var key in monitorCyclingSettings) {
+ var isThisScreen = key === screen.name || (screen.model && key === screen.model);
+ if (!isThisScreen) {
+ newSettings[key] = monitorCyclingSettings[key];
+ }
+ }
+
+ newSettings[identifier] = getMonitorCyclingSettings(screenName);
+ newSettings[identifier].enabled = enabled;
+ monitorCyclingSettings = newSettings;
+ saveSettings();
+ }
+
+ function setMonitorCyclingMode(screenName, mode) {
+ var screen = null;
+ var screens = Quickshell.screens;
+ for (var i = 0; i < screens.length; i++) {
+ if (screens[i].name === screenName) {
+ screen = screens[i];
+ break;
+ }
+ }
+
+ if (!screen) {
+ console.warn("SessionData: Screen not found");
+ return;
+ }
+
+ var identifier = typeof SettingsData !== "undefined" ? SettingsData.getScreenDisplayName(screen) : screen.name;
+
+ var newSettings = {};
+ for (var key in monitorCyclingSettings) {
+ var isThisScreen = key === screen.name || (screen.model && key === screen.model);
+ if (!isThisScreen) {
+ newSettings[key] = monitorCyclingSettings[key];
+ }
+ }
+
+ newSettings[identifier] = getMonitorCyclingSettings(screenName);
+ newSettings[identifier].mode = mode;
+ monitorCyclingSettings = newSettings;
+ saveSettings();
+ }
+
+ function setMonitorCyclingInterval(screenName, interval) {
+ var screen = null;
+ var screens = Quickshell.screens;
+ for (var i = 0; i < screens.length; i++) {
+ if (screens[i].name === screenName) {
+ screen = screens[i];
+ break;
+ }
+ }
+
+ if (!screen) {
+ console.warn("SessionData: Screen not found");
+ return;
+ }
+
+ var identifier = typeof SettingsData !== "undefined" ? SettingsData.getScreenDisplayName(screen) : screen.name;
+
+ var newSettings = {};
+ for (var key in monitorCyclingSettings) {
+ var isThisScreen = key === screen.name || (screen.model && key === screen.model);
+ if (!isThisScreen) {
+ newSettings[key] = monitorCyclingSettings[key];
+ }
+ }
+
+ newSettings[identifier] = getMonitorCyclingSettings(screenName);
+ newSettings[identifier].interval = interval;
+ monitorCyclingSettings = newSettings;
+ saveSettings();
+ }
+
+ function setMonitorCyclingTime(screenName, time) {
+ var screen = null;
+ var screens = Quickshell.screens;
+ for (var i = 0; i < screens.length; i++) {
+ if (screens[i].name === screenName) {
+ screen = screens[i];
+ break;
+ }
+ }
+
+ if (!screen) {
+ console.warn("SessionData: Screen not found");
+ return;
+ }
+
+ var identifier = typeof SettingsData !== "undefined" ? SettingsData.getScreenDisplayName(screen) : screen.name;
+
+ var newSettings = {};
+ for (var key in monitorCyclingSettings) {
+ var isThisScreen = key === screen.name || (screen.model && key === screen.model);
+ if (!isThisScreen) {
+ newSettings[key] = monitorCyclingSettings[key];
+ }
+ }
+
+ newSettings[identifier] = getMonitorCyclingSettings(screenName);
+ newSettings[identifier].time = time;
+ monitorCyclingSettings = newSettings;
+ saveSettings();
+ }
+
+ function setNightModeEnabled(enabled) {
+ nightModeEnabled = enabled;
+ saveSettings();
+ }
+
+ function setNightModeTemperature(temperature) {
+ nightModeTemperature = temperature;
+ saveSettings();
+ }
+
+ function setNightModeHighTemperature(temperature) {
+ nightModeHighTemperature = temperature;
+ saveSettings();
+ }
+
+ function setNightModeAutoEnabled(enabled) {
+ nightModeAutoEnabled = enabled;
+ saveSettings();
+ }
+
+ function setNightModeAutoMode(mode) {
+ nightModeAutoMode = mode;
+ saveSettings();
+ }
+
+ function setNightModeStartHour(hour) {
+ nightModeStartHour = hour;
+ saveSettings();
+ }
+
+ function setNightModeStartMinute(minute) {
+ nightModeStartMinute = minute;
+ saveSettings();
+ }
+
+ function setNightModeEndHour(hour) {
+ nightModeEndHour = hour;
+ saveSettings();
+ }
+
+ function setNightModeEndMinute(minute) {
+ nightModeEndMinute = minute;
+ saveSettings();
+ }
+
+ function setNightModeUseIPLocation(use) {
+ nightModeUseIPLocation = use;
+ saveSettings();
+ }
+
+ function setLatitude(lat) {
+ latitude = lat;
+ saveSettings();
+ }
+
+ function setLongitude(lng) {
+ longitude = lng;
+ saveSettings();
+ }
+
+ function setNightModeLocationProvider(provider) {
+ nightModeLocationProvider = provider;
+ saveSettings();
+ }
+
+ function setThemeModeAutoEnabled(enabled) {
+ themeModeAutoEnabled = enabled;
+ saveSettings();
+ }
+
+ function setThemeModeAutoMode(mode) {
+ themeModeAutoMode = mode;
+ saveSettings();
+ }
+
+ function setThemeModeStartHour(hour) {
+ themeModeStartHour = hour;
+ saveSettings();
+ }
+
+ function setThemeModeStartMinute(minute) {
+ themeModeStartMinute = minute;
+ saveSettings();
+ }
+
+ function setThemeModeEndHour(hour) {
+ themeModeEndHour = hour;
+ saveSettings();
+ }
+
+ function setThemeModeEndMinute(minute) {
+ themeModeEndMinute = minute;
+ saveSettings();
+ }
+
+ function setThemeModeShareGammaSettings(share) {
+ themeModeShareGammaSettings = share;
+ saveSettings();
+ }
+
+ function setPinnedApps(apps) {
+ pinnedApps = apps;
+ saveSettings();
+ }
+
+ function setDockLauncherPosition(position) {
+ dockLauncherPosition = position;
+ saveSettings();
+ }
+
+ function addPinnedApp(appId) {
+ if (!appId)
+ return;
+ var currentPinned = [...pinnedApps];
+ if (currentPinned.indexOf(appId) === -1) {
+ currentPinned.push(appId);
+ setPinnedApps(currentPinned);
+ }
+ }
+
+ function removePinnedApp(appId) {
+ if (!appId)
+ return;
+ var currentPinned = pinnedApps.filter(id => id !== appId);
+ setPinnedApps(currentPinned);
+ }
+
+ function isPinnedApp(appId) {
+ return appId && pinnedApps.indexOf(appId) !== -1;
+ }
+
+ function setBarPinnedApps(apps) {
+ barPinnedApps = apps;
+ saveSettings();
+ }
+
+ function addBarPinnedApp(appId) {
+ if (!appId)
+ return;
+ var currentPinned = [...barPinnedApps];
+ if (currentPinned.indexOf(appId) === -1) {
+ currentPinned.push(appId);
+ setBarPinnedApps(currentPinned);
+ }
+ }
+
+ function removeBarPinnedApp(appId) {
+ if (!appId)
+ return;
+ var currentPinned = barPinnedApps.filter(id => id !== appId);
+ setBarPinnedApps(currentPinned);
+ }
+
+ function isBarPinnedApp(appId) {
+ return appId && barPinnedApps.indexOf(appId) !== -1;
+ }
+
+ function hideTrayId(trayId) {
+ if (!trayId)
+ return;
+ const current = [...hiddenTrayIds];
+ if (current.indexOf(trayId) === -1) {
+ current.push(trayId);
+ hiddenTrayIds = current;
+ saveSettings();
+ }
+ }
+
+ function showTrayId(trayId) {
+ if (!trayId)
+ return;
+ hiddenTrayIds = hiddenTrayIds.filter(id => id !== trayId);
+ saveSettings();
+ }
+
+ function isHiddenTrayId(trayId) {
+ return trayId && hiddenTrayIds.indexOf(trayId) !== -1;
+ }
+
+ function setTrayItemOrder(order) {
+ trayItemOrder = order;
+ saveSettings();
+ }
+
+ function addRecentColor(color) {
+ const colorStr = color.toString();
+ let recent = recentColors.slice();
+ recent = recent.filter(c => c !== colorStr);
+ recent.unshift(colorStr);
+ if (recent.length > 5)
+ recent = recent.slice(0, 5);
+ recentColors = recent;
+ saveSettings();
+ }
+
+ function setShowThirdPartyPlugins(enabled) {
+ showThirdPartyPlugins = enabled;
+ saveSettings();
+ }
+
+ function setLaunchPrefix(prefix) {
+ launchPrefix = prefix;
+ saveSettings();
+ }
+
+ function setLastBrightnessDevice(device) {
+ lastBrightnessDevice = device;
+ saveSettings();
+ }
+
+ function setBrightnessExponential(deviceName, enabled) {
+ var newSettings = Object.assign({}, brightnessExponentialDevices);
+ if (enabled) {
+ newSettings[deviceName] = true;
+ } else {
+ delete newSettings[deviceName];
+ }
+ brightnessExponentialDevices = newSettings;
+ saveSettings();
+
+ if (typeof DisplayService !== "undefined") {
+ DisplayService.updateDeviceBrightnessDisplay(deviceName);
+ }
+ }
+
+ function getBrightnessExponential(deviceName) {
+ return brightnessExponentialDevices[deviceName] === true;
+ }
+
+ function setBrightnessUserSetValue(deviceName, value) {
+ var newValues = Object.assign({}, brightnessUserSetValues);
+ newValues[deviceName] = value;
+ brightnessUserSetValues = newValues;
+ saveSettings();
+ }
+
+ function getBrightnessUserSetValue(deviceName) {
+ return brightnessUserSetValues[deviceName];
+ }
+
+ function clearBrightnessUserSetValue(deviceName) {
+ var newValues = Object.assign({}, brightnessUserSetValues);
+ delete newValues[deviceName];
+ brightnessUserSetValues = newValues;
+ saveSettings();
+ }
+
+ function setBrightnessExponent(deviceName, exponent) {
+ var newValues = Object.assign({}, brightnessExponentValues);
+ if (exponent !== undefined && exponent !== null) {
+ newValues[deviceName] = exponent;
+ } else {
+ delete newValues[deviceName];
+ }
+ brightnessExponentValues = newValues;
+ saveSettings();
+ }
+
+ function getBrightnessExponent(deviceName) {
+ const value = brightnessExponentValues[deviceName];
+ 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();
+ }
+
+ function setWifiDeviceOverride(device) {
+ wifiDeviceOverride = device || "";
+ saveSettings();
+ }
+
+ function setWeatherHourlyDetailed(detailed) {
+ weatherHourlyDetailed = detailed;
+ saveSettings();
+ }
+
+ function setWeatherLocation(displayName, coordinates) {
+ weatherLocation = displayName;
+ weatherCoordinates = coordinates;
+ saveSettings();
+ }
+
+ function hideApp(appId) {
+ if (!appId)
+ return;
+ const current = [...hiddenApps];
+ if (current.indexOf(appId) === -1) {
+ current.push(appId);
+ hiddenApps = current;
+ saveSettings();
+ }
+ }
+
+ function showApp(appId) {
+ if (!appId)
+ return;
+ hiddenApps = hiddenApps.filter(id => id !== appId);
+ saveSettings();
+ }
+
+ function isAppHidden(appId) {
+ return appId && hiddenApps.indexOf(appId) !== -1;
+ }
+
+ function setAppOverride(appId, overrides) {
+ if (!appId)
+ return;
+ const newOverrides = Object.assign({}, appOverrides);
+ if (!overrides || Object.keys(overrides).length === 0) {
+ delete newOverrides[appId];
+ } else {
+ newOverrides[appId] = overrides;
+ }
+ appOverrides = newOverrides;
+ saveSettings();
+ }
+
+ function getAppOverride(appId) {
+ if (!appId)
+ return null;
+ return appOverrides[appId] || null;
+ }
+
+ function clearAppOverride(appId) {
+ if (!appId)
+ return;
+ const newOverrides = Object.assign({}, appOverrides);
+ delete newOverrides[appId];
+ appOverrides = newOverrides;
+ saveSettings();
+ }
+
+ function setSearchAppActions(enabled) {
+ searchAppActions = enabled;
+ saveSettings();
+ }
+
+ function setVpnLastConnected(uuid) {
+ vpnLastConnected = uuid || "";
+ saveSettings();
+ }
+
+ function setDeviceMaxVolume(nodeName, maxPercent) {
+ if (!nodeName)
+ return;
+ const updated = Object.assign({}, deviceMaxVolumes);
+ const clamped = Math.max(100, Math.min(200, Math.round(maxPercent)));
+ if (clamped === 100) {
+ delete updated[nodeName];
+ } else {
+ updated[nodeName] = clamped;
+ }
+ deviceMaxVolumes = updated;
+ saveSettings();
+ }
+
+ function setHiddenOutputDeviceNames(deviceNames) {
+ if (!Array.isArray(deviceNames))
+ return;
+ hiddenOutputDeviceNames = deviceNames;
+ saveSettings();
+ }
+
+ function setHiddenInputDeviceNames(deviceNames) {
+ if (!Array.isArray(deviceNames))
+ return;
+ hiddenInputDeviceNames = deviceNames;
+ saveSettings();
+ }
+
+ function getDeviceMaxVolume(nodeName) {
+ if (!nodeName)
+ return 100;
+ 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();
+ return;
+ }
+ I18n.useLocale(locale, locale.startsWith("en") ? "" : I18n.folder + "/" + locale + ".json");
+ }
+
+ function setLauncherLastMode(mode) {
+ launcherLastMode = mode;
+ saveSettings();
+ }
+
+ function setLauncherLastQuery(query) {
+ launcherLastQuery = query;
+ saveSettings();
+ }
+
+ function addLauncherHistory(query) {
+ let q = query.trim();
+
+ setLauncherLastQuery(q);
+
+ if (!q)
+ return;
+
+ if (launcherQueryHistory.length > 0 && launcherQueryHistory[0] === q) {
+ return;
+ }
+
+ let history = [...launcherQueryHistory];
+
+ let idx = history.indexOf(q);
+ if (idx !== -1)
+ history.splice(idx, 1);
+
+ history.unshift(q);
+ if (history.length > 50)
+ history = history.slice(0, 50);
+
+ launcherQueryHistory = history;
+ saveSettings();
+ }
+
+ function clearLauncherHistory() {
+ launcherLastQuery = "";
+ launcherSearchHistory = [];
+ saveSettings();
+ }
+
+ function setAppDrawerLastMode(mode) {
+ appDrawerLastMode = mode;
+ saveSettings();
+ }
+
+ function setNiriOverviewLastMode(mode) {
+ niriOverviewLastMode = mode;
+ saveSettings();
+ }
+
+ function setSettingsSidebarState(expandedIds, collapsedIds) {
+ settingsSidebarExpandedIds = expandedIds;
+ settingsSidebarCollapsedIds = collapsedIds;
+ saveSettings();
+ }
+
+ function syncWallpaperForCurrentMode(mode) {
+ if (!perModeWallpaper)
+ return;
+ var light = (mode !== undefined) ? mode : isLightMode;
+ if (perMonitorWallpaper) {
+ monitorWallpapers = light ? Object.assign({}, monitorWallpapersLight) : Object.assign({}, monitorWallpapersDark);
+ return;
+ }
+
+ wallpaperPath = light ? wallpaperPathLight : wallpaperPathDark;
+ }
+
+ function _findMonitorValue(map, screenName) {
+ var screen = null;
+ var screens = Quickshell.screens;
+ for (var i = 0; i < screens.length; i++) {
+ if (screens[i].name === screenName) {
+ screen = screens[i];
+ break;
+ }
+ }
+
+ if (!screen)
+ return map[screenName];
+
+ if (map[screen.name] !== undefined)
+ return map[screen.name];
+ if (screen.model && map[screen.model] !== undefined)
+ return map[screen.model];
+ if (typeof SettingsData !== "undefined") {
+ var displayName = SettingsData.getScreenDisplayName(screen);
+ if (displayName && map[displayName] !== undefined)
+ return map[displayName];
+ }
+ return undefined;
+ }
+
+ function getMonitorWallpaper(screenName) {
+ if (!perMonitorWallpaper)
+ return wallpaperPath;
+ var value = _findMonitorValue(monitorWallpapers, screenName);
+ return value !== undefined ? value : wallpaperPath;
+ }
+
+ function getMonitorWallpaperFillMode(screenName) {
+ var globalFillMode = (typeof SettingsData !== "undefined") ? SettingsData.wallpaperFillMode : "Fill";
+ if (!perMonitorWallpaper)
+ return globalFillMode;
+ var value = _findMonitorValue(monitorWallpaperFillModes, screenName);
+ return value !== undefined ? value : globalFillMode;
+ }
+
+ function setMonitorWallpaperFillMode(screenName, mode) {
+ var screen = null;
+ var screens = Quickshell.screens;
+ for (var i = 0; i < screens.length; i++) {
+ if (screens[i].name === screenName) {
+ screen = screens[i];
+ break;
+ }
+ }
+
+ if (!screen)
+ return;
+
+ var identifier = typeof SettingsData !== "undefined" ? SettingsData.getScreenDisplayName(screen) : screen.name;
+
+ var newModes = {};
+ for (var key in monitorWallpaperFillModes) {
+ var isThisScreen = key === screen.name || (screen.model && key === screen.model);
+ if (!isThisScreen)
+ newModes[key] = monitorWallpaperFillModes[key];
+ }
+
+ newModes[identifier] = mode;
+ monitorWallpaperFillModes = newModes;
+ saveSettings();
+ }
+
+ function getMonitorCyclingSettings(screenName) {
+ var defaults = {
+ "enabled": false,
+ "mode": "interval",
+ "interval": 300,
+ "time": "06:00"
+ };
+ var value = _findMonitorValue(monitorCyclingSettings, screenName);
+ return Object.assign({}, defaults, value !== undefined ? value : {});
+ }
+
+ FileView {
+ id: settingsFile
+
+ path: isGreeterMode ? "" : StandardPaths.writableLocation(StandardPaths.GenericStateLocation) + "/DankMaterialShell/session.json"
+ blockLoading: true
+ blockWrites: true
+ atomicWrites: true
+ watchChanges: !isGreeterMode
+ onLoaded: {
+ if (!isGreeterMode) {
+ _hasUnsavedChanges = false;
+ parseSettings(settingsFile.text());
+ }
+ }
+ onSaveFailed: error => {
+ root._isReadOnly = true;
+ root._hasUnsavedChanges = root._checkForUnsavedChanges();
+ }
+ }
+
+ FileView {
+ id: greeterSessionFile
+
+ path: {
+ const greetCfgDir = Quickshell.env("DMS_GREET_CFG_DIR") || "/var/cache/dms-greeter";
+ return greetCfgDir + "/session.json";
+ }
+ preload: isGreeterMode
+ blockLoading: false
+ blockWrites: true
+ watchChanges: false
+ printErrors: true
+ onLoaded: {
+ if (isGreeterMode) {
+ parseSettings(greeterSessionFile.text());
+ }
+ }
+ }
+
+ Process {
+ id: sessionWritableCheckProcess
+
+ property string sessionPath: Paths.strip(settingsFile.path)
+
+ command: ["sh", "-c", "[ ! -f \"" + sessionPath + "\" ] || [ -w \"" + sessionPath + "\" ] && echo 'writable' || echo 'readonly'"]
+ running: false
+
+ stdout: StdioCollector {
+ onStreamFinished: {
+ const result = text.trim();
+ root._onWritableCheckComplete(result === "writable");
+ }
+ }
+ }
+
+ 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/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Common/SettingsData.qml b/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Common/SettingsData.qml
new file mode 100644
index 0000000..0790ea4
--- /dev/null
+++ b/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Common/SettingsData.qml
@@ -0,0 +1,2840 @@
+pragma Singleton
+pragma ComponentBehavior: Bound
+
+import QtCore
+import QtQuick
+import Quickshell
+import Quickshell.Io
+import qs.Common
+import qs.Common.settings
+import qs.Services
+import "settings/SettingsSpec.js" as Spec
+import "settings/SettingsStore.js" as Store
+
+Singleton {
+ id: root
+
+ readonly property int settingsConfigVersion: 5
+
+ readonly property bool isGreeterMode: Quickshell.env("DMS_RUN_GREETER") === "1" || Quickshell.env("DMS_RUN_GREETER") === "true"
+
+ enum Position {
+ Top,
+ Bottom,
+ Left,
+ Right,
+ TopCenter,
+ BottomCenter,
+ LeftCenter,
+ RightCenter
+ }
+
+ enum AnimationSpeed {
+ None,
+ Short,
+ Medium,
+ Long,
+ Custom
+ }
+
+ enum SuspendBehavior {
+ Suspend,
+ Hibernate,
+ SuspendThenHibernate
+ }
+
+ enum WidgetColorMode {
+ Default,
+ Colorful
+ }
+
+ readonly property string _homeUrl: StandardPaths.writableLocation(StandardPaths.HomeLocation)
+ readonly property string _configUrl: StandardPaths.writableLocation(StandardPaths.ConfigLocation)
+ readonly property string _configDir: Paths.strip(_configUrl)
+ readonly property string pluginSettingsPath: _configDir + "/DankMaterialShell/plugin_settings.json"
+
+ property bool _loading: false
+ property bool _pluginSettingsLoading: false
+ property bool _parseError: false
+ property bool _pluginParseError: false
+ property bool _hasLoaded: false
+ property bool _isReadOnly: false
+ property bool _hasUnsavedChanges: false
+ property bool _selfWrite: false
+ property var _loadedSettingsSnapshot: null
+ property var pluginSettings: ({})
+ property var builtInPluginSettings: ({})
+
+ function getBuiltInPluginSetting(pluginId, key, defaultValue) {
+ if (!builtInPluginSettings[pluginId])
+ return defaultValue;
+ return builtInPluginSettings[pluginId][key] !== undefined ? builtInPluginSettings[pluginId][key] : defaultValue;
+ }
+
+ function setBuiltInPluginSetting(pluginId, key, value) {
+ const updated = JSON.parse(JSON.stringify(builtInPluginSettings));
+ if (!updated[pluginId])
+ updated[pluginId] = {};
+ updated[pluginId][key] = value;
+ builtInPluginSettings = updated;
+ saveSettings();
+ }
+
+ property bool clipboardEnterToPaste: false
+
+ property var launcherPluginVisibility: ({})
+
+ function getPluginAllowWithoutTrigger(pluginId) {
+ if (!launcherPluginVisibility[pluginId])
+ return true;
+ return launcherPluginVisibility[pluginId].allowWithoutTrigger !== false;
+ }
+
+ function setPluginAllowWithoutTrigger(pluginId, allow) {
+ const updated = JSON.parse(JSON.stringify(launcherPluginVisibility));
+ if (!updated[pluginId])
+ updated[pluginId] = {};
+ updated[pluginId].allowWithoutTrigger = allow;
+ launcherPluginVisibility = updated;
+ saveSettings();
+ }
+
+ property var launcherPluginOrder: []
+ onLauncherPluginOrderChanged: saveSettings()
+
+ function setLauncherPluginOrder(order) {
+ launcherPluginOrder = order;
+ }
+
+ function getOrderedLauncherPlugins(allPlugins) {
+ if (!launcherPluginOrder || launcherPluginOrder.length === 0)
+ return allPlugins;
+ const orderMap = {};
+ for (let i = 0; i < launcherPluginOrder.length; i++)
+ orderMap[launcherPluginOrder[i]] = i;
+ return allPlugins.slice().sort((a, b) => {
+ const aOrder = orderMap[a.id] ?? 9999;
+ const bOrder = orderMap[b.id] ?? 9999;
+ if (aOrder !== bOrder)
+ return aOrder - bOrder;
+ return a.name.localeCompare(b.name);
+ });
+ }
+
+ property alias dankBarLeftWidgetsModel: leftWidgetsModel
+ property alias dankBarCenterWidgetsModel: centerWidgetsModel
+ property alias dankBarRightWidgetsModel: rightWidgetsModel
+
+ property string currentThemeName: "purple"
+ property string currentThemeCategory: "generic"
+ property string customThemeFile: ""
+ property var registryThemeVariants: ({})
+ property string matugenScheme: "scheme-tonal-spot"
+ property real matugenContrast: 0
+ property bool runUserMatugenTemplates: true
+ property string matugenTargetMonitor: ""
+ property real popupTransparency: 1.0
+ property real dockTransparency: 1
+ property string widgetBackgroundColor: "sch"
+ property string widgetColorMode: "default"
+ property string controlCenterTileColorMode: "primary"
+ property string buttonColorMode: "primary"
+ property real cornerRadius: 12
+ property int niriLayoutGapsOverride: -1
+ property int niriLayoutRadiusOverride: -1
+ property int niriLayoutBorderSize: -1
+ property int hyprlandLayoutGapsOverride: -1
+ property int hyprlandLayoutRadiusOverride: -1
+ property int hyprlandLayoutBorderSize: -1
+ property int mangoLayoutGapsOverride: -1
+ property int mangoLayoutRadiusOverride: -1
+ property int mangoLayoutBorderSize: -1
+
+ property int firstDayOfWeek: -1
+ property bool showWeekNumber: false
+ property bool use24HourClock: true
+ property bool showSeconds: false
+ property bool padHours12Hour: false
+ property bool useFahrenheit: false
+ property string windSpeedUnit: "kmh"
+ property bool nightModeEnabled: false
+ property int animationSpeed: SettingsData.AnimationSpeed.Short
+ property int customAnimationDuration: 500
+ property bool syncComponentAnimationSpeeds: true
+ onSyncComponentAnimationSpeedsChanged: saveSettings()
+ property int popoutAnimationSpeed: SettingsData.AnimationSpeed.Short
+ property int popoutCustomAnimationDuration: 150
+ property int modalAnimationSpeed: SettingsData.AnimationSpeed.Short
+ property int modalCustomAnimationDuration: 150
+ property bool enableRippleEffects: true
+ onEnableRippleEffectsChanged: saveSettings()
+ property bool m3ElevationEnabled: true
+ onM3ElevationEnabledChanged: saveSettings()
+ property int m3ElevationIntensity: 12
+ onM3ElevationIntensityChanged: saveSettings()
+ property int m3ElevationOpacity: 30
+ onM3ElevationOpacityChanged: saveSettings()
+ property string m3ElevationColorMode: "default"
+ onM3ElevationColorModeChanged: saveSettings()
+ property string m3ElevationLightDirection: "top"
+ onM3ElevationLightDirectionChanged: saveSettings()
+ property string m3ElevationCustomColor: "#000000"
+ onM3ElevationCustomColorChanged: saveSettings()
+ property bool modalElevationEnabled: true
+ onModalElevationEnabledChanged: saveSettings()
+ property bool popoutElevationEnabled: true
+ onPopoutElevationEnabledChanged: saveSettings()
+ property bool barElevationEnabled: true
+ onBarElevationEnabledChanged: saveSettings()
+ property bool blurEnabled: false
+ onBlurEnabledChanged: saveSettings()
+ property string blurBorderColor: "outline"
+ onBlurBorderColorChanged: saveSettings()
+ property string blurBorderCustomColor: "#ffffff"
+ onBlurBorderCustomColorChanged: saveSettings()
+ property real blurBorderOpacity: 1.0
+ onBlurBorderOpacityChanged: saveSettings()
+ property string wallpaperFillMode: "Fill"
+ property bool blurredWallpaperLayer: false
+ property bool blurWallpaperOnOverview: false
+
+ property bool showLauncherButton: true
+ property bool showWorkspaceSwitcher: true
+ property bool showFocusedWindow: true
+ property bool showWeather: true
+ property bool showMusic: true
+ property bool showClipboard: true
+ property bool showCpuUsage: true
+ property bool showMemUsage: true
+ property bool showCpuTemp: true
+ property bool showGpuTemp: true
+ property int selectedGpuIndex: 0
+ property var enabledGpuPciIds: []
+ property bool showSystemTray: true
+ property bool showClock: true
+ property bool showNotificationButton: true
+ property bool showBattery: true
+ property bool showControlCenterButton: true
+ property bool showCapsLockIndicator: true
+
+ property bool controlCenterShowNetworkIcon: true
+ property bool controlCenterShowBluetoothIcon: true
+ property bool controlCenterShowAudioIcon: true
+ property bool controlCenterShowAudioPercent: false
+ property bool controlCenterShowVpnIcon: true
+ property bool controlCenterShowBrightnessIcon: false
+ property bool controlCenterShowBrightnessPercent: false
+ property bool controlCenterShowMicIcon: false
+ property bool controlCenterShowMicPercent: true
+ property bool controlCenterShowBatteryIcon: false
+ property bool controlCenterShowPrinterIcon: false
+ property bool controlCenterShowScreenSharingIcon: true
+ property bool showPrivacyButton: true
+ property bool privacyShowMicIcon: false
+ property bool privacyShowCameraIcon: false
+ property bool privacyShowScreenShareIcon: false
+
+ property var controlCenterWidgets: [
+ {
+ "id": "volumeSlider",
+ "enabled": true,
+ "width": 50
+ },
+ {
+ "id": "brightnessSlider",
+ "enabled": true,
+ "width": 50
+ },
+ {
+ "id": "wifi",
+ "enabled": true,
+ "width": 50
+ },
+ {
+ "id": "bluetooth",
+ "enabled": true,
+ "width": 50
+ },
+ {
+ "id": "audioOutput",
+ "enabled": true,
+ "width": 50
+ },
+ {
+ "id": "audioInput",
+ "enabled": true,
+ "width": 50
+ },
+ {
+ "id": "nightMode",
+ "enabled": true,
+ "width": 50
+ },
+ {
+ "id": "darkMode",
+ "enabled": true,
+ "width": 50
+ }
+ ]
+
+ property bool showWorkspaceIndex: false
+ property bool showWorkspaceName: false
+ property bool showWorkspacePadding: false
+ property bool workspaceScrolling: false
+ property bool showWorkspaceApps: false
+ property bool workspaceDragReorder: true
+ property bool groupWorkspaceApps: true
+ property int maxWorkspaceIcons: 3
+ property int workspaceAppIconSizeOffset: 0
+ property bool workspaceFollowFocus: false
+ property bool showOccupiedWorkspacesOnly: false
+ property bool reverseScrolling: false
+ property bool dwlShowAllTags: false
+ property bool workspaceActiveAppHighlightEnabled: false
+ property string workspaceColorMode: "default"
+ property string workspaceOccupiedColorMode: "none"
+ property string workspaceUnfocusedColorMode: "default"
+ property string workspaceUrgentColorMode: "default"
+ property bool workspaceFocusedBorderEnabled: false
+ property string workspaceFocusedBorderColor: "primary"
+ property int workspaceFocusedBorderThickness: 2
+ property var workspaceNameIcons: ({})
+ property bool waveProgressEnabled: true
+ property bool scrollTitleEnabled: true
+ property bool mediaAdaptiveWidthEnabled: true
+ property bool audioVisualizerEnabled: true
+ property string audioScrollMode: "volume"
+ property int audioWheelScrollAmount: 5
+ property bool clockCompactMode: false
+ property bool focusedWindowCompactMode: false
+ property bool runningAppsCompactMode: true
+ property int barMaxVisibleApps: 0
+ property int barMaxVisibleRunningApps: 0
+ property bool barShowOverflowBadge: true
+ property bool appsDockHideIndicators: false
+ property bool appsDockColorizeActive: false
+ property string appsDockActiveColorMode: "primary"
+ property bool appsDockEnlargeOnHover: false
+ property int appsDockEnlargePercentage: 125
+ property int appsDockIconSizePercentage: 100
+ property bool keyboardLayoutNameCompactMode: false
+ property bool runningAppsCurrentWorkspace: true
+ property bool runningAppsGroupByApp: false
+ property bool runningAppsCurrentMonitor: false
+ property var appIdSubstitutions: []
+ property string centeringMode: "index"
+ property string clockDateFormat: ""
+ property string lockDateFormat: ""
+ property bool greeterRememberLastSession: true
+ property bool greeterRememberLastUser: true
+ property bool greeterEnableFprint: false
+ property bool greeterEnableU2f: false
+ property string greeterWallpaperPath: ""
+ property bool greeterUse24HourClock: true
+ property bool greeterShowSeconds: false
+ property bool greeterPadHours12Hour: false
+ property string greeterLockDateFormat: ""
+ property string greeterFontFamily: ""
+ property string greeterWallpaperFillMode: ""
+ property int mediaSize: 1
+
+ property string appLauncherViewMode: "list"
+ property string spotlightModalViewMode: "list"
+ property string browserPickerViewMode: "grid"
+ property var browserUsageHistory: ({})
+ property string appPickerViewMode: "grid"
+ property var filePickerUsageHistory: ({})
+ property bool sortAppsAlphabetically: false
+ property int appLauncherGridColumns: 4
+ property bool spotlightCloseNiriOverview: true
+ property bool rememberLastQuery: false
+ property var spotlightSectionViewModes: ({})
+ onSpotlightSectionViewModesChanged: saveSettings()
+ property var appDrawerSectionViewModes: ({})
+ onAppDrawerSectionViewModesChanged: saveSettings()
+ property bool niriOverviewOverlayEnabled: true
+ property string dankLauncherV2Size: "compact"
+ property bool dankLauncherV2BorderEnabled: false
+ property int dankLauncherV2BorderThickness: 2
+ property string dankLauncherV2BorderColor: "primary"
+ property bool dankLauncherV2ShowFooter: true
+ property bool dankLauncherV2UnloadOnClose: false
+ property bool dankLauncherV2IncludeFilesInAll: false
+ property bool dankLauncherV2IncludeFoldersInAll: false
+
+ property string _legacyWeatherLocation: "New York, NY"
+ property string _legacyWeatherCoordinates: "40.7128,-74.0060"
+ property string _legacyVpnLastConnected: ""
+ readonly property string weatherLocation: SessionData.weatherLocation
+ readonly property string weatherCoordinates: SessionData.weatherCoordinates
+ property bool useAutoLocation: false
+ property bool weatherEnabled: true
+
+ property string networkPreference: "auto"
+
+ property string iconTheme: "System Default"
+ property var availableIconThemes: ["System Default"]
+ property string systemDefaultIconTheme: ""
+ property bool qt5ctAvailable: false
+ property bool qt6ctAvailable: false
+ property bool gtkAvailable: false
+
+ property var cursorSettings: ({
+ "theme": "System Default",
+ "size": 24,
+ "niri": {
+ "hideWhenTyping": false,
+ "hideAfterInactiveMs": 0
+ },
+ "hyprland": {
+ "hideOnKeyPress": false,
+ "hideOnTouch": false,
+ "inactiveTimeout": 0
+ },
+ "dwl": {
+ "cursorHideTimeout": 0
+ }
+ })
+ property var availableCursorThemes: ["System Default"]
+ property string systemDefaultCursorTheme: ""
+
+ property string launcherLogoMode: "apps"
+ property string launcherLogoCustomPath: ""
+ property string launcherLogoColorOverride: ""
+ property bool launcherLogoColorInvertOnMode: false
+ property real launcherLogoBrightness: 0.5
+ property real launcherLogoContrast: 1
+ property int launcherLogoSizeOffset: 0
+
+ property string fontFamily: "Inter Variable"
+ property string monoFontFamily: "Fira Code"
+ property int fontWeight: Font.Normal
+ property real fontScale: 1.0
+ property real dankBarFontScale: 1.0
+
+ property bool notepadUseMonospace: true
+ property string notepadFontFamily: ""
+ property real notepadFontSize: 14
+ property bool notepadShowLineNumbers: false
+ property real notepadTransparencyOverride: -1
+ property real notepadLastCustomTransparency: 0.7
+
+ onNotepadUseMonospaceChanged: saveSettings()
+ onNotepadFontFamilyChanged: saveSettings()
+ onNotepadFontSizeChanged: saveSettings()
+ onNotepadShowLineNumbersChanged: saveSettings()
+ // onCenteringModeChanged: saveSettings()
+ onNotepadTransparencyOverrideChanged: {
+ if (notepadTransparencyOverride > 0) {
+ notepadLastCustomTransparency = notepadTransparencyOverride;
+ }
+ saveSettings();
+ }
+ onNotepadLastCustomTransparencyChanged: saveSettings()
+
+ property bool soundsEnabled: true
+ property bool useSystemSoundTheme: false
+ property bool soundNewNotification: true
+ property bool soundVolumeChanged: true
+ property bool soundPluggedIn: true
+ property bool soundLogin: false
+
+ property int acMonitorTimeout: 0
+ property int acLockTimeout: 0
+ property int acSuspendTimeout: 0
+ property int acSuspendBehavior: SettingsData.SuspendBehavior.Suspend
+ property string acProfileName: ""
+ property int batteryMonitorTimeout: 0
+ property int batteryLockTimeout: 0
+ property int batterySuspendTimeout: 0
+ property int batterySuspendBehavior: SettingsData.SuspendBehavior.Suspend
+ property string batteryProfileName: ""
+ property int batteryChargeLimit: 100
+ property bool lockBeforeSuspend: false
+ property bool loginctlLockIntegration: true
+ property bool fadeToLockEnabled: true
+ property int fadeToLockGracePeriod: 5
+ property bool fadeToDpmsEnabled: true
+ property int fadeToDpmsGracePeriod: 5
+ property string launchPrefix: ""
+ property var brightnessDevicePins: ({})
+ property var wifiNetworkPins: ({})
+ property var bluetoothDevicePins: ({})
+ property var audioInputDevicePins: ({})
+ property var audioOutputDevicePins: ({})
+
+ property bool gtkThemingEnabled: false
+ property bool qtThemingEnabled: false
+ property bool syncModeWithPortal: true
+ property bool terminalsAlwaysDark: false
+
+ property string muxType: "tmux"
+ property bool muxUseCustomCommand: false
+ property string muxCustomCommand: ""
+ property string muxSessionFilter: ""
+
+ property bool runDmsMatugenTemplates: true
+ property bool matugenTemplateGtk: true
+ property bool matugenTemplateNiri: true
+ property bool matugenTemplateHyprland: true
+ property bool matugenTemplateMangowc: true
+ property bool matugenTemplateQt5ct: true
+ property bool matugenTemplateQt6ct: true
+ property bool matugenTemplateFirefox: true
+ property bool matugenTemplatePywalfox: true
+ property bool matugenTemplateZenBrowser: true
+ property bool matugenTemplateVesktop: true
+ property bool matugenTemplateEquibop: true
+ property bool matugenTemplateGhostty: true
+ property bool matugenTemplateKitty: true
+ property bool matugenTemplateFoot: true
+ property bool matugenTemplateNeovim: false
+ property bool matugenTemplateAlacritty: true
+ property bool matugenTemplateWezterm: true
+ property bool matugenTemplateDgop: true
+ property bool matugenTemplateKcolorscheme: true
+ property bool matugenTemplateVscode: true
+ property bool matugenTemplateEmacs: true
+ property bool matugenTemplateZed: true
+
+ property var matugenTemplateNeovimSettings: ({
+ "dark": {
+ "baseTheme": "github_dark",
+ "harmony": 0.5
+ },
+ "light": {
+ "baseTheme": "github_light",
+ "harmony": 0.5
+ }
+ })
+ property bool matugenTemplateNeovimSetBackground: true
+
+ property bool showDock: false
+ property bool dockAutoHide: false
+ property bool dockSmartAutoHide: false
+ property bool dockGroupByApp: false
+ property bool dockRestoreSpecialWorkspaceOnClick: false
+ property bool dockOpenOnOverview: false
+ property int dockPosition: SettingsData.Position.Bottom
+ property real dockSpacing: 4
+ property real dockBottomGap: 0
+ property real dockMargin: 0
+ property real dockIconSize: 40
+ property string dockIndicatorStyle: "circle"
+ property bool dockBorderEnabled: false
+ property string dockBorderColor: "surfaceText"
+ property real dockBorderOpacity: 1.0
+ property int dockBorderThickness: 1
+ property bool dockIsolateDisplays: false
+ property bool dockLauncherEnabled: false
+ property string dockLauncherLogoMode: "apps"
+ property string dockLauncherLogoCustomPath: ""
+ property string dockLauncherLogoColorOverride: ""
+ property int dockLauncherLogoSizeOffset: 0
+ property real dockLauncherLogoBrightness: 0.5
+ property real dockLauncherLogoContrast: 1
+ property int dockMaxVisibleApps: 0
+ property int dockMaxVisibleRunningApps: 0
+ property bool dockShowOverflowBadge: true
+
+ property bool notificationOverlayEnabled: false
+ property bool notificationPopupShadowEnabled: true
+ property bool notificationPopupPrivacyMode: false
+ property int overviewRows: 2
+ property int overviewColumns: 5
+ property real overviewScale: 0.16
+
+ property bool modalDarkenBackground: true
+
+ property bool lockScreenShowPowerActions: true
+ property bool lockScreenShowSystemIcons: true
+ property bool lockScreenShowTime: true
+ property bool lockScreenShowDate: true
+ property bool lockScreenShowProfileImage: true
+ property bool lockScreenShowPasswordField: true
+ property bool lockScreenShowMediaPlayer: true
+ property bool lockScreenPowerOffMonitorsOnLock: false
+ property bool lockAtStartup: false
+
+ property bool enableFprint: false
+ property int maxFprintTries: 15
+ readonly property bool fprintdAvailable: Processes.fprintdAvailable
+ readonly property bool lockFingerprintCanEnable: Processes.lockFingerprintCanEnable
+ readonly property bool lockFingerprintReady: Processes.lockFingerprintReady
+ readonly property string lockFingerprintReason: Processes.lockFingerprintReason
+ readonly property bool greeterFingerprintCanEnable: Processes.greeterFingerprintCanEnable
+ readonly property bool greeterFingerprintReady: Processes.greeterFingerprintReady
+ readonly property string greeterFingerprintReason: Processes.greeterFingerprintReason
+ readonly property string greeterFingerprintSource: Processes.greeterFingerprintSource
+ property bool enableU2f: false
+ property string u2fMode: "or"
+ readonly property bool u2fAvailable: Processes.u2fAvailable
+ readonly property bool lockU2fCanEnable: Processes.lockU2fCanEnable
+ readonly property bool lockU2fReady: Processes.lockU2fReady
+ readonly property string lockU2fReason: Processes.lockU2fReason
+ readonly property bool greeterU2fCanEnable: Processes.greeterU2fCanEnable
+ readonly property bool greeterU2fReady: Processes.greeterU2fReady
+ readonly property string greeterU2fReason: Processes.greeterU2fReason
+ readonly property string greeterU2fSource: Processes.greeterU2fSource
+ property string lockScreenActiveMonitor: "all"
+ property string lockScreenInactiveColor: "#000000"
+ property int lockScreenNotificationMode: 0
+ property bool lockScreenVideoEnabled: false
+ property string lockScreenVideoPath: ""
+ property bool lockScreenVideoCycling: false
+ property bool hideBrightnessSlider: false
+
+ property int notificationTimeoutLow: 5000
+ property int notificationTimeoutNormal: 5000
+ property int notificationTimeoutCritical: 0
+ property bool notificationCompactMode: false
+ property int notificationPopupPosition: SettingsData.Position.Top
+ property int notificationAnimationSpeed: SettingsData.AnimationSpeed.Short
+ property int notificationCustomAnimationDuration: 400
+ property bool notificationHistoryEnabled: true
+ property int notificationHistoryMaxCount: 50
+ property int notificationHistoryMaxAgeDays: 7
+ property bool notificationHistorySaveLow: true
+ property bool notificationHistorySaveNormal: true
+ property bool notificationHistorySaveCritical: true
+ property var notificationRules: []
+ property bool notificationFocusedMonitor: false
+
+ property bool osdAlwaysShowValue: false
+ property int osdPosition: SettingsData.Position.BottomCenter
+ property bool osdVolumeEnabled: true
+ property bool osdMediaVolumeEnabled: true
+ property bool osdMediaPlaybackEnabled: false
+ property bool osdBrightnessEnabled: true
+ property bool osdIdleInhibitorEnabled: true
+ property bool osdMicMuteEnabled: true
+ property bool osdCapsLockEnabled: true
+ property bool osdPowerProfileEnabled: true
+ property bool osdAudioOutputEnabled: true
+
+ property bool powerActionConfirm: true
+ property real powerActionHoldDuration: 0.5
+ property var powerMenuActions: ["reboot", "logout", "poweroff", "lock", "suspend", "restart"]
+ property string powerMenuDefaultAction: "logout"
+ property bool powerMenuGridLayout: false
+ property string customPowerActionLock: ""
+ property string customPowerActionLogout: ""
+ property string customPowerActionSuspend: ""
+ property string customPowerActionHibernate: ""
+ property string customPowerActionReboot: ""
+ property string customPowerActionPowerOff: ""
+
+ property bool updaterHideWidget: false
+ property bool updaterUseCustomCommand: false
+ property string updaterCustomCommand: ""
+ property string updaterTerminalAdditionalParams: ""
+
+ property string displayNameMode: "system"
+ property var screenPreferences: ({})
+ property var showOnLastDisplay: ({})
+ property var niriOutputSettings: ({})
+ property var hyprlandOutputSettings: ({})
+ property var displayProfiles: ({})
+ property var activeDisplayProfile: ({})
+ property bool displayProfileAutoSelect: false
+ property bool displayShowDisconnected: false
+ property bool displaySnapToEdge: true
+
+ property var barConfigs: [
+ {
+ "id": "default",
+ "name": "Main Bar",
+ "enabled": true,
+ "position": 0,
+ "screenPreferences": ["all"],
+ "showOnLastDisplay": true,
+ "leftWidgets": ["launcherButton", "workspaceSwitcher", "focusedWindow"],
+ "centerWidgets": ["music", "clock", "weather"],
+ "rightWidgets": ["systemTray", "clipboard", "cpuUsage", "memUsage", "notificationButton", "battery", "controlCenterButton"],
+ "spacing": 4,
+ "innerPadding": 4,
+ "bottomGap": 0,
+ "transparency": 1.0,
+ "widgetTransparency": 1.0,
+ "squareCorners": false,
+ "noBackground": false,
+ "maximizeWidgetIcons": false,
+ "maximizeWidgetText": false,
+ "removeWidgetPadding": false,
+ "widgetPadding": 8,
+ "gothCornersEnabled": false,
+ "gothCornerRadiusOverride": false,
+ "gothCornerRadiusValue": 12,
+ "borderEnabled": false,
+ "borderColor": "surfaceText",
+ "borderOpacity": 1.0,
+ "borderThickness": 1,
+ "widgetOutlineEnabled": false,
+ "widgetOutlineColor": "primary",
+ "widgetOutlineOpacity": 1.0,
+ "widgetOutlineThickness": 1,
+ "fontScale": 1.0,
+ "iconScale": 1.0,
+ "autoHide": false,
+ "autoHideDelay": 250,
+ "showOnWindowsOpen": false,
+ "openOnOverview": false,
+ "visible": true,
+ "popupGapsAuto": true,
+ "popupGapsManual": 4,
+ "maximizeDetection": true,
+ "scrollEnabled": true,
+ "scrollXBehavior": "column",
+ "scrollYBehavior": "workspace",
+ "shadowIntensity": 0,
+ "shadowOpacity": 60,
+ "shadowColorMode": "default",
+ "shadowCustomColor": "#000000",
+ "clickThrough": false
+ }
+ ]
+
+ property bool desktopClockEnabled: false
+ property string desktopClockStyle: "analog"
+ property real desktopClockTransparency: 0.8
+ property string desktopClockColorMode: "primary"
+ property color desktopClockCustomColor: "#ffffff"
+ property bool desktopClockShowDate: true
+ property bool desktopClockShowAnalogNumbers: false
+ property bool desktopClockShowAnalogSeconds: true
+ property real desktopClockX: -1
+ property real desktopClockY: -1
+ property real desktopClockWidth: 280
+ property real desktopClockHeight: 180
+ property var desktopClockDisplayPreferences: ["all"]
+
+ property bool systemMonitorEnabled: false
+ property bool systemMonitorShowHeader: true
+ property real systemMonitorTransparency: 0.8
+ property string systemMonitorColorMode: "primary"
+ property color systemMonitorCustomColor: "#ffffff"
+ property bool systemMonitorShowCpu: true
+ property bool systemMonitorShowCpuGraph: true
+ property bool systemMonitorShowCpuTemp: true
+ property bool systemMonitorShowGpuTemp: false
+ property string systemMonitorGpuPciId: ""
+ property bool systemMonitorShowMemory: true
+ property bool systemMonitorShowMemoryGraph: true
+ property bool systemMonitorShowNetwork: true
+ property bool systemMonitorShowNetworkGraph: true
+ property bool systemMonitorShowDisk: true
+ property bool systemMonitorShowTopProcesses: false
+ property int systemMonitorTopProcessCount: 3
+ property string systemMonitorTopProcessSortBy: "cpu"
+ property string systemMonitorLayoutMode: "auto"
+ property int systemMonitorGraphInterval: 60
+ property real systemMonitorX: -1
+ property real systemMonitorY: -1
+ property real systemMonitorWidth: 320
+ property real systemMonitorHeight: 480
+ property var systemMonitorDisplayPreferences: ["all"]
+ property var systemMonitorVariants: []
+ property var desktopWidgetPositions: ({})
+ property var desktopWidgetGridSettings: ({})
+ property var desktopWidgetInstances: []
+ property var desktopWidgetGroups: []
+
+ function getDesktopWidgetGridSetting(screenKey, property, defaultValue) {
+ const val = desktopWidgetGridSettings?.[screenKey]?.[property];
+ return val !== undefined ? val : defaultValue;
+ }
+
+ function setDesktopWidgetGridSetting(screenKey, property, value) {
+ const allSettings = JSON.parse(JSON.stringify(desktopWidgetGridSettings || {}));
+ if (!allSettings[screenKey])
+ allSettings[screenKey] = {};
+ allSettings[screenKey][property] = value;
+ desktopWidgetGridSettings = allSettings;
+ saveSettings();
+ }
+
+ function getDesktopWidgetPosition(pluginId, screenKey, property, defaultValue) {
+ const pos = desktopWidgetPositions?.[pluginId]?.[screenKey]?.[property];
+ return pos !== undefined ? pos : defaultValue;
+ }
+
+ function updateDesktopWidgetPosition(pluginId, screenKey, updates) {
+ const allPositions = JSON.parse(JSON.stringify(desktopWidgetPositions || {}));
+ if (!allPositions[pluginId])
+ allPositions[pluginId] = {};
+ allPositions[pluginId][screenKey] = Object.assign({}, allPositions[pluginId][screenKey] || {}, updates);
+ desktopWidgetPositions = allPositions;
+ saveSettings();
+ }
+
+ function getSystemMonitorVariants() {
+ return systemMonitorVariants || [];
+ }
+
+ function createSystemMonitorVariant(name, config) {
+ const id = "sysmon_" + Date.now() + "_" + Math.random().toString(36).substr(2, 9);
+ const variant = {
+ id: id,
+ name: name,
+ config: config || getDefaultSystemMonitorConfig()
+ };
+ const variants = JSON.parse(JSON.stringify(systemMonitorVariants || []));
+ variants.push(variant);
+ systemMonitorVariants = variants;
+ saveSettings();
+ return variant;
+ }
+
+ function updateSystemMonitorVariant(variantId, updates) {
+ const variants = JSON.parse(JSON.stringify(systemMonitorVariants || []));
+ const idx = variants.findIndex(v => v.id === variantId);
+ if (idx === -1)
+ return;
+ Object.assign(variants[idx], updates);
+ systemMonitorVariants = variants;
+ saveSettings();
+ }
+
+ function removeSystemMonitorVariant(variantId) {
+ const variants = (systemMonitorVariants || []).filter(v => v.id !== variantId);
+ systemMonitorVariants = variants;
+ saveSettings();
+ }
+
+ function getSystemMonitorVariant(variantId) {
+ return (systemMonitorVariants || []).find(v => v.id === variantId) || null;
+ }
+
+ function getDefaultSystemMonitorConfig() {
+ return {
+ showHeader: true,
+ transparency: 0.8,
+ colorMode: "primary",
+ customColor: "#ffffff",
+ showCpu: true,
+ showCpuGraph: true,
+ showCpuTemp: true,
+ showGpuTemp: false,
+ gpuPciId: "",
+ showMemory: true,
+ showMemoryGraph: true,
+ showNetwork: true,
+ showNetworkGraph: true,
+ showDisk: true,
+ showTopProcesses: false,
+ topProcessCount: 3,
+ topProcessSortBy: "cpu",
+ layoutMode: "auto",
+ graphInterval: 60,
+ x: -1,
+ y: -1,
+ width: 320,
+ height: 480,
+ displayPreferences: ["all"]
+ };
+ }
+
+ function createDesktopWidgetInstance(widgetType, name, config) {
+ const id = "dw_" + Date.now() + "_" + Math.random().toString(36).substr(2, 9);
+ const instance = {
+ id: id,
+ widgetType: widgetType,
+ name: name || widgetType,
+ enabled: true,
+ config: config || {},
+ positions: {}
+ };
+ const instances = JSON.parse(JSON.stringify(desktopWidgetInstances || []));
+ instances.push(instance);
+ desktopWidgetInstances = instances;
+ saveSettings();
+ return instance;
+ }
+
+ function updateDesktopWidgetInstance(instanceId, updates) {
+ const instances = JSON.parse(JSON.stringify(desktopWidgetInstances || []));
+ const idx = instances.findIndex(inst => inst.id === instanceId);
+ if (idx === -1)
+ return;
+ Object.assign(instances[idx], updates);
+ desktopWidgetInstances = instances;
+ saveSettings();
+ }
+
+ function updateDesktopWidgetInstanceConfig(instanceId, configUpdates) {
+ const instances = JSON.parse(JSON.stringify(desktopWidgetInstances || []));
+ const idx = instances.findIndex(inst => inst.id === instanceId);
+ if (idx === -1)
+ return;
+ instances[idx].config = Object.assign({}, instances[idx].config || {}, configUpdates);
+ desktopWidgetInstances = instances;
+ saveSettings();
+ }
+
+ function updateDesktopWidgetInstancePosition(instanceId, screenKey, positionUpdates) {
+ const instances = JSON.parse(JSON.stringify(desktopWidgetInstances || []));
+ const idx = instances.findIndex(inst => inst.id === instanceId);
+ if (idx === -1)
+ return;
+ if (!instances[idx].positions)
+ instances[idx].positions = {};
+ instances[idx].positions[screenKey] = Object.assign({}, instances[idx].positions[screenKey] || {}, positionUpdates);
+ desktopWidgetInstances = instances;
+ saveSettings();
+ }
+
+ function removeDesktopWidgetInstance(instanceId) {
+ const instances = (desktopWidgetInstances || []).filter(inst => inst.id !== instanceId);
+ desktopWidgetInstances = instances;
+ saveSettings();
+ }
+
+ function syncDesktopWidgetPositionToAllScreens(instanceId) {
+ const instances = JSON.parse(JSON.stringify(desktopWidgetInstances || []));
+ const idx = instances.findIndex(inst => inst.id === instanceId);
+ if (idx === -1)
+ return;
+ const positions = instances[idx].positions || {};
+ const screenKeys = Object.keys(positions).filter(k => k !== "_synced");
+ if (screenKeys.length === 0)
+ return;
+ const sourceKey = screenKeys[0];
+ const sourcePos = positions[sourceKey];
+ if (!sourcePos)
+ return;
+ const screen = Array.from(Quickshell.screens.values()).find(s => getScreenDisplayName(s) === sourceKey);
+ if (!screen)
+ return;
+ const screenW = screen.width;
+ const screenH = screen.height;
+ const synced = {};
+ if (sourcePos.x !== undefined)
+ synced.x = sourcePos.x / screenW;
+ if (sourcePos.y !== undefined)
+ synced.y = sourcePos.y / screenH;
+ if (sourcePos.width !== undefined)
+ synced.width = sourcePos.width;
+ if (sourcePos.height !== undefined)
+ synced.height = sourcePos.height;
+ instances[idx].positions["_synced"] = synced;
+ desktopWidgetInstances = instances;
+ saveSettings();
+ }
+
+ function duplicateDesktopWidgetInstance(instanceId) {
+ const source = getDesktopWidgetInstance(instanceId);
+ if (!source)
+ return null;
+ const newId = "dw_" + Date.now() + "_" + Math.random().toString(36).substr(2, 9);
+ const instance = {
+ id: newId,
+ widgetType: source.widgetType,
+ name: source.name + " (Copy)",
+ enabled: source.enabled,
+ config: JSON.parse(JSON.stringify(source.config || {})),
+ positions: {}
+ };
+ const instances = JSON.parse(JSON.stringify(desktopWidgetInstances || []));
+ instances.push(instance);
+ desktopWidgetInstances = instances;
+ saveSettings();
+ return instance;
+ }
+
+ function getDesktopWidgetInstance(instanceId) {
+ return (desktopWidgetInstances || []).find(inst => inst.id === instanceId) || null;
+ }
+
+ function getDesktopWidgetInstancesOfType(widgetType) {
+ return (desktopWidgetInstances || []).filter(inst => inst.widgetType === widgetType);
+ }
+
+ function getEnabledDesktopWidgetInstances() {
+ return (desktopWidgetInstances || []).filter(inst => inst.enabled);
+ }
+
+ function moveDesktopWidgetInstance(instanceId, direction) {
+ const instances = JSON.parse(JSON.stringify(desktopWidgetInstances || []));
+ const idx = instances.findIndex(inst => inst.id === instanceId);
+ if (idx === -1)
+ return false;
+ const targetIdx = direction === "up" ? idx - 1 : idx + 1;
+ if (targetIdx < 0 || targetIdx >= instances.length)
+ return false;
+ const temp = instances[idx];
+ instances[idx] = instances[targetIdx];
+ instances[targetIdx] = temp;
+ desktopWidgetInstances = instances;
+ saveSettings();
+ return true;
+ }
+
+ function reorderDesktopWidgetInstance(instanceId, newIndex) {
+ const instances = JSON.parse(JSON.stringify(desktopWidgetInstances || []));
+ const idx = instances.findIndex(inst => inst.id === instanceId);
+ if (idx === -1 || newIndex < 0 || newIndex >= instances.length)
+ return false;
+ const [item] = instances.splice(idx, 1);
+ instances.splice(newIndex, 0, item);
+ desktopWidgetInstances = instances;
+ saveSettings();
+ return true;
+ }
+
+ function reorderDesktopWidgetInstanceInGroup(instanceId, groupId, newIndexInGroup) {
+ const instances = JSON.parse(JSON.stringify(desktopWidgetInstances || []));
+ const groups = desktopWidgetGroups || [];
+ const groupMatches = inst => {
+ if (groupId === null)
+ return !inst.group || !groups.some(g => g.id === inst.group);
+ return inst.group === groupId;
+ };
+ const groupInstances = instances.filter(groupMatches);
+ const currentGroupIdx = groupInstances.findIndex(inst => inst.id === instanceId);
+ if (currentGroupIdx === -1 || currentGroupIdx === newIndexInGroup)
+ return false;
+ if (newIndexInGroup < 0 || newIndexInGroup >= groupInstances.length)
+ return false;
+ const globalIdx = instances.findIndex(inst => inst.id === instanceId);
+ if (globalIdx === -1)
+ return false;
+ const [item] = instances.splice(globalIdx, 1);
+ const targetInstance = groupInstances[newIndexInGroup];
+ let targetGlobalIdx = instances.findIndex(inst => inst.id === targetInstance.id);
+ if (newIndexInGroup > currentGroupIdx)
+ targetGlobalIdx++;
+ instances.splice(targetGlobalIdx, 0, item);
+ desktopWidgetInstances = instances;
+ saveSettings();
+ return true;
+ }
+
+ function createDesktopWidgetGroup(name) {
+ const id = "dwg_" + Date.now() + "_" + Math.random().toString(36).substr(2, 9);
+ const group = {
+ id: id,
+ name: name,
+ collapsed: false
+ };
+ const groups = JSON.parse(JSON.stringify(desktopWidgetGroups || []));
+ groups.push(group);
+ desktopWidgetGroups = groups;
+ saveSettings();
+ return group;
+ }
+
+ function updateDesktopWidgetGroup(groupId, updates) {
+ const groups = JSON.parse(JSON.stringify(desktopWidgetGroups || []));
+ const idx = groups.findIndex(g => g.id === groupId);
+ if (idx === -1)
+ return;
+ Object.assign(groups[idx], updates);
+ desktopWidgetGroups = groups;
+ saveSettings();
+ }
+
+ function removeDesktopWidgetGroup(groupId) {
+ const instances = JSON.parse(JSON.stringify(desktopWidgetInstances || []));
+ for (let i = 0; i < instances.length; i++) {
+ if (instances[i].group === groupId)
+ instances[i].group = null;
+ }
+ desktopWidgetInstances = instances;
+ const groups = (desktopWidgetGroups || []).filter(g => g.id !== groupId);
+ desktopWidgetGroups = groups;
+ saveSettings();
+ }
+
+ function getDesktopWidgetGroup(groupId) {
+ return (desktopWidgetGroups || []).find(g => g.id === groupId) || null;
+ }
+
+ function getDesktopWidgetInstancesByGroup(groupId) {
+ return (desktopWidgetInstances || []).filter(inst => inst.group === groupId);
+ }
+
+ function getUngroupedDesktopWidgetInstances() {
+ return (desktopWidgetInstances || []).filter(inst => !inst.group);
+ }
+
+ signal forceDankBarLayoutRefresh
+ signal forceDockLayoutRefresh
+ signal widgetDataChanged
+ signal workspaceIconsUpdated
+
+ function refreshAuthAvailability() {
+ if (isGreeterMode)
+ return;
+ Processes.detectAuthCapabilities();
+ }
+
+ Component.onCompleted: {
+ if (!isGreeterMode) {
+ Processes.settingsRoot = root;
+ loadSettings();
+ initializeListModels();
+ refreshAuthAvailability();
+ Processes.checkPluginSettings();
+ }
+ }
+
+ function applyStoredTheme() {
+ if (typeof Theme !== "undefined") {
+ Theme.currentThemeCategory = currentThemeCategory;
+ Theme.switchTheme(currentThemeName, false, false);
+ } else {
+ Qt.callLater(function () {
+ if (typeof Theme !== "undefined") {
+ Theme.currentThemeCategory = currentThemeCategory;
+ Theme.switchTheme(currentThemeName, false, false);
+ }
+ });
+ }
+ }
+
+ function regenSystemThemes() {
+ if (typeof Theme !== "undefined") {
+ Theme.generateSystemThemesFromCurrentTheme();
+ }
+ }
+
+ function updateCompositorLayout() {
+ if (typeof CompositorService === "undefined")
+ return;
+ if (CompositorService.isNiri && typeof NiriService !== "undefined")
+ NiriService.generateNiriLayoutConfig();
+ if (CompositorService.isHyprland && typeof HyprlandService !== "undefined")
+ HyprlandService.generateLayoutConfig();
+ if (CompositorService.isDwl && typeof DwlService !== "undefined")
+ DwlService.generateLayoutConfig();
+ }
+
+ function applyStoredIconTheme() {
+ updateGtkIconTheme();
+ updateQtIconTheme();
+ updateCosmicIconTheme();
+ }
+
+ function updateCosmicIconTheme() {
+ let cosmicThemeName = (iconTheme === "System Default") ? systemDefaultIconTheme : iconTheme;
+ if (!cosmicThemeName || cosmicThemeName === "System Default") {
+ const detectScript = `if command -v gsettings >/dev/null 2>&1; then
+ gsettings get org.gnome.desktop.interface icon-theme 2>/dev/null | sed "s/'//g"
+ elif command -v dconf >/dev/null 2>&1; then
+ dconf read /org/gnome/desktop/interface/icon-theme 2>/dev/null | sed "s/'//g"
+ fi`;
+
+ Proc.runCommand("detectCosmicIconTheme", ["sh", "-c", detectScript], (output, exitCode) => {
+ if (exitCode !== 0)
+ return;
+ const detected = (output || "").trim();
+ if (!detected || detected === "System Default")
+ return;
+ const detectedEscaped = detected.replace(/'/g, "'\\''");
+ const writeScript = `mkdir -p ${_configDir}/cosmic/com.system76.CosmicTk/v1
+ printf '"%s"\\n' '${detectedEscaped}' > ${_configDir}/cosmic/com.system76.CosmicTk/v1/icon_theme 2>/dev/null || true`;
+ Quickshell.execDetached(["sh", "-lc", writeScript]);
+ });
+ return;
+ }
+
+ const cosmicThemeNameEscaped = cosmicThemeName.replace(/'/g, "'\\''");
+ const script = `mkdir -p ${_configDir}/cosmic/com.system76.CosmicTk/v1
+ printf '"%s"\\n' '${cosmicThemeNameEscaped}' > ${_configDir}/cosmic/com.system76.CosmicTk/v1/icon_theme 2>/dev/null || true`;
+ Quickshell.execDetached(["sh", "-lc", script]);
+ }
+
+ function updateCosmicThemeMode(isLightMode) {
+ const isDark = isLightMode ? "false" : "true";
+ const script = `mkdir -p ${_configDir}/cosmic/com.system76.CosmicTheme.Mode/v1
+ printf '%s\\n' ${isDark} > ${_configDir}/cosmic/com.system76.CosmicTheme.Mode/v1/is_dark 2>/dev/null || true`;
+ Quickshell.execDetached(["sh", "-lc", script]);
+ }
+
+ function updateGtkIconTheme() {
+ const gtkThemeName = (iconTheme === "System Default") ? systemDefaultIconTheme : iconTheme;
+ if (gtkThemeName === "System Default" || gtkThemeName === "")
+ return;
+ if (typeof DMSService !== "undefined" && DMSService.apiVersion >= 3 && typeof PortalService !== "undefined") {
+ PortalService.setSystemIconTheme(gtkThemeName);
+ }
+
+ const configScript = `mkdir -p ${_configDir}/gtk-3.0 ${_configDir}/gtk-4.0
+
+ for config_dir in ${_configDir}/gtk-3.0 ${_configDir}/gtk-4.0; do
+ settings_file="$config_dir/settings.ini"
+ [ -f "$settings_file" ] && [ ! -w "$settings_file" ] && continue
+ if [ -f "$settings_file" ]; then
+ if grep -q "^gtk-icon-theme-name=" "$settings_file"; then
+ sed -i 's/^gtk-icon-theme-name=.*/gtk-icon-theme-name=${gtkThemeName}/' "$settings_file"
+ else
+ if grep -q "\\[Settings\\]" "$settings_file"; then
+ sed -i '/\\[Settings\\]/a gtk-icon-theme-name=${gtkThemeName}' "$settings_file"
+ else
+ echo -e '\\n[Settings]\\ngtk-icon-theme-name=${gtkThemeName}' >> "$settings_file"
+ fi
+ fi
+ else
+ echo -e '[Settings]\\ngtk-icon-theme-name=${gtkThemeName}' > "$settings_file"
+ fi
+ done
+
+ pkill -HUP -f 'gtk' 2>/dev/null || true`;
+
+ Quickshell.execDetached(["sh", "-lc", configScript]);
+ }
+
+ function updateQtIconTheme() {
+ const qtThemeName = (iconTheme === "System Default") ? "" : iconTheme;
+ if (!qtThemeName)
+ return;
+ const home = _homeUrl.replace("file://", "").replace(/'/g, "'\\''");
+ const qtThemeNameEscaped = qtThemeName.replace(/'/g, "'\\''");
+
+ const script = `mkdir -p ${_configDir}/qt5ct ${_configDir}/qt6ct ${_configDir}/environment.d 2>/dev/null || true
+ update_qt_icon_theme() {
+ local config_file="$1"
+ local theme_name="$2"
+ if [ -f "$config_file" ]; then
+ if grep -q "^\\[Appearance\\]" "$config_file"; then
+ if grep -q "^icon_theme=" "$config_file"; then
+ sed -i "s/^icon_theme=.*/icon_theme=$theme_name/" "$config_file"
+ else
+ sed -i "/^\\[Appearance\\]/a icon_theme=$theme_name" "$config_file"
+ fi
+ else
+ printf "\\n[Appearance]\\nicon_theme=%s\\n" "$theme_name" >> "$config_file"
+ fi
+ else
+ printf "[Appearance]\\nicon_theme=%s\\n" "$theme_name" > "$config_file"
+ fi
+ }
+ update_qt_icon_theme ${_configDir}/qt5ct/qt5ct.conf '${qtThemeNameEscaped}'
+ update_qt_icon_theme ${_configDir}/qt6ct/qt6ct.conf '${qtThemeNameEscaped}'`;
+
+ Quickshell.execDetached(["sh", "-lc", script]);
+ }
+
+ function scheduleAuthApply() {
+ if (isGreeterMode)
+ return;
+ Qt.callLater(() => {
+ Processes.settingsRoot = root;
+ Processes.scheduleAuthApply();
+ });
+ }
+
+ readonly property var _hooks: ({
+ "applyStoredTheme": applyStoredTheme,
+ "regenSystemThemes": regenSystemThemes,
+ "updateCompositorLayout": updateCompositorLayout,
+ "applyStoredIconTheme": applyStoredIconTheme,
+ "updateBarConfigs": updateBarConfigs,
+ "updateCompositorCursor": updateCompositorCursor,
+ "scheduleAuthApply": scheduleAuthApply
+ })
+
+ function set(key, value) {
+ Spec.set(root, key, value, saveSettings, _hooks);
+ }
+
+ function loadSettings() {
+ _loading = true;
+ _parseError = false;
+ _hasUnsavedChanges = false;
+ _pendingMigration = null;
+
+ try {
+ const txt = settingsFile.text();
+ let obj = (txt && txt.trim()) ? JSON.parse(txt) : null;
+
+ const oldVersion = obj?.configVersion ?? 0;
+ if (oldVersion < settingsConfigVersion) {
+ const migrated = Store.migrateToVersion(obj, settingsConfigVersion);
+ if (migrated) {
+ _pendingMigration = migrated;
+ obj = migrated;
+ }
+ }
+
+ Store.parse(root, obj);
+
+ if (obj?.weatherLocation !== undefined)
+ _legacyWeatherLocation = obj.weatherLocation;
+ if (obj?.weatherCoordinates !== undefined)
+ _legacyWeatherCoordinates = obj.weatherCoordinates;
+ if (obj?.vpnLastConnected !== undefined && obj.vpnLastConnected !== "") {
+ _legacyVpnLastConnected = obj.vpnLastConnected;
+ SessionData.vpnLastConnected = _legacyVpnLastConnected;
+ SessionData.saveSettings();
+ }
+
+ _loadedSettingsSnapshot = JSON.stringify(Store.toJson(root));
+ _hasLoaded = true;
+ applyStoredTheme();
+ updateCompositorCursor();
+ Processes.detectQtTools();
+
+ _checkSettingsWritable();
+ } catch (e) {
+ _parseError = true;
+ const msg = e.message;
+ console.error("SettingsData: Failed to parse settings.json - file will not be overwritten. Error:", msg);
+ Qt.callLater(() => ToastService.showError(I18n.tr("Failed to parse settings.json"), msg));
+ applyStoredTheme();
+ } finally {
+ _loading = false;
+ }
+ loadPluginSettings();
+ }
+
+ property var _pendingMigration: null
+
+ function _checkSettingsWritable() {
+ settingsWritableCheckProcess.running = true;
+ }
+
+ function _onWritableCheckComplete(writable) {
+ const wasReadOnly = _isReadOnly;
+ _isReadOnly = !writable;
+ if (_isReadOnly) {
+ _hasUnsavedChanges = _checkForUnsavedChanges();
+ if (!wasReadOnly)
+ console.info("SettingsData: settings.json is now read-only");
+ } else {
+ _loadedSettingsSnapshot = JSON.stringify(Store.toJson(root));
+ _hasUnsavedChanges = false;
+ if (wasReadOnly)
+ console.info("SettingsData: settings.json is now writable");
+ if (_pendingMigration)
+ settingsFile.setText(JSON.stringify(_pendingMigration, null, 2));
+ }
+ _pendingMigration = null;
+ }
+
+ function _checkForUnsavedChanges() {
+ if (!_hasLoaded || !_loadedSettingsSnapshot)
+ return false;
+ const current = JSON.stringify(Store.toJson(root));
+ return current !== _loadedSettingsSnapshot;
+ }
+
+ function getCurrentSettingsJson() {
+ return JSON.stringify(Store.toJson(root), null, 2);
+ }
+
+ function _resetPluginSettings() {
+ _pluginParseError = false;
+ pluginSettings = {};
+ }
+
+ function _pluginSettingsErrorCode(error) {
+ if (typeof error === "number")
+ return error;
+ if (error && typeof error === "object") {
+ if (typeof error.code === "number")
+ return error.code;
+ if (typeof error.errno === "number")
+ return error.errno;
+ }
+
+ const msg = String(error || "").trim();
+ if (/^\d+$/.test(msg))
+ return Number(msg);
+
+ return -1;
+ }
+
+ function _isMissingPluginSettingsError(error) {
+ if (_pluginSettingsErrorCode(error) === 2)
+ return true;
+
+ const msg = String(error || "").toLowerCase();
+ return msg.indexOf("file does not exist") !== -1 || msg.indexOf("no such file") !== -1 || msg.indexOf("enoent") !== -1;
+ }
+
+ function loadPluginSettings() {
+ try {
+ parsePluginSettings(pluginSettingsFile.text());
+ } catch (e) {
+ const msg = e.message || String(e);
+ if (!_isMissingPluginSettingsError(e))
+ console.warn("SettingsData: Failed to load plugin_settings.json. Error:", msg);
+ _resetPluginSettings();
+ }
+ }
+
+ function parsePluginSettings(content) {
+ _pluginSettingsLoading = true;
+ _pluginParseError = false;
+ try {
+ if (content && content.trim()) {
+ pluginSettings = JSON.parse(content);
+ } else {
+ pluginSettings = {};
+ }
+ } catch (e) {
+ _pluginParseError = true;
+ const msg = e.message;
+ console.error("SettingsData: Failed to parse plugin_settings.json - file will not be overwritten. Error:", msg);
+ Qt.callLater(() => ToastService.showError(I18n.tr("Failed to parse plugin_settings.json"), msg));
+ pluginSettings = {};
+ } finally {
+ _pluginSettingsLoading = false;
+ }
+ }
+
+ function saveSettings() {
+ if (_loading || _parseError || !_hasLoaded)
+ return;
+ _selfWrite = true;
+ settingsFile.setText(JSON.stringify(Store.toJson(root), null, 2));
+ if (_isReadOnly)
+ _checkSettingsWritable();
+ }
+
+ function savePluginSettings() {
+ if (_pluginSettingsLoading || _pluginParseError)
+ return;
+ pluginSettingsFile.setText(JSON.stringify(pluginSettings, null, 2));
+ }
+
+ function detectAvailableIconThemes() {
+ const xdgDataDirs = Quickshell.env("XDG_DATA_DIRS") || "";
+ const localData = Paths.strip(StandardPaths.writableLocation(StandardPaths.GenericDataLocation));
+ const homeDir = Paths.strip(StandardPaths.writableLocation(StandardPaths.HomeLocation));
+
+ const dataDirs = xdgDataDirs.trim() !== "" ? xdgDataDirs.split(":").concat([localData]) : ["/usr/share", "/usr/local/share", localData];
+
+ const iconPaths = dataDirs.map(d => d + "/icons").concat([homeDir + "/.icons"]);
+ const pathsArg = iconPaths.join(" ");
+
+ const script = `
+ echo "SYSDEFAULT:$(gsettings get org.gnome.desktop.interface icon-theme 2>/dev/null | sed "s/'//g" || echo '')"
+ for dir in ${pathsArg}; do
+ [ -d "$dir" ] || continue
+ for theme in "$dir"/*/; do
+ [ -d "$theme" ] || continue
+ basename "$theme"
+ done
+ done | grep -v '^icons$' | grep -v '^default$' | grep -v '^hicolor$' | grep -v '^locolor$' | sort -u
+ `;
+
+ Proc.runCommand("detectIconThemes", ["sh", "-c", script], (output, exitCode) => {
+ const themes = ["System Default"];
+ if (output && output.trim()) {
+ const lines = output.trim().split('\n');
+ for (let i = 0; i < lines.length; i++) {
+ const line = lines[i].trim();
+ if (line.startsWith("SYSDEFAULT:")) {
+ systemDefaultIconTheme = line.substring(11).trim();
+ continue;
+ }
+ if (line)
+ themes.push(line);
+ }
+ }
+ availableIconThemes = themes;
+ });
+ }
+
+ function detectAvailableCursorThemes() {
+ const xdgDataDirs = Quickshell.env("XDG_DATA_DIRS") || "";
+ const localData = Paths.strip(StandardPaths.writableLocation(StandardPaths.GenericDataLocation));
+ const homeDir = Paths.strip(StandardPaths.writableLocation(StandardPaths.HomeLocation));
+
+ const dataDirs = xdgDataDirs.trim() !== "" ? xdgDataDirs.split(":").concat([localData]) : ["/usr/share", "/usr/local/share", localData];
+
+ const cursorPaths = dataDirs.map(d => d + "/icons").concat([homeDir + "/.icons", homeDir + "/.local/share/icons"]);
+ const pathsArg = cursorPaths.join(" ");
+
+ const script = `
+ echo "SYSDEFAULT:$(gsettings get org.gnome.desktop.interface cursor-theme 2>/dev/null | sed "s/'//g" || echo '')"
+ for dir in ${pathsArg}; do
+ [ -d "$dir" ] || continue
+ for theme in "$dir"/*/; do
+ [ -d "$theme" ] || continue
+ [ -d "$theme/cursors" ] || continue
+ basename "$theme"
+ done
+ done | grep -v '^icons$' | grep -v '^default$' | sort -u
+ `;
+
+ Proc.runCommand("detectCursorThemes", ["sh", "-c", script], (output, exitCode) => {
+ const themes = ["System Default"];
+ if (output && output.trim()) {
+ const lines = output.trim().split('\n');
+ for (let i = 0; i < lines.length; i++) {
+ const line = lines[i].trim();
+ if (line.startsWith("SYSDEFAULT:")) {
+ systemDefaultCursorTheme = line.substring(11).trim();
+ continue;
+ }
+ if (line)
+ themes.push(line);
+ }
+ }
+ availableCursorThemes = themes;
+ });
+ }
+
+ function getEffectiveTimeFormat() {
+ if (use24HourClock)
+ return showSeconds ? "hh:mm:ss" : "hh:mm";
+ if (padHours12Hour)
+ return showSeconds ? "hh:mm:ss AP" : "hh:mm AP";
+ return showSeconds ? "h:mm:ss AP" : "h:mm AP";
+ }
+
+ function getEffectiveClockDateFormat() {
+ return clockDateFormat && clockDateFormat.length > 0 ? clockDateFormat : "ddd d";
+ }
+
+ function getEffectiveLockDateFormat() {
+ return lockDateFormat && lockDateFormat.length > 0 ? lockDateFormat : Locale.LongFormat;
+ }
+
+ function initializeListModels() {
+ const defaultBar = barConfigs[0] || getBarConfig("default");
+ if (defaultBar) {
+ Lists.init(leftWidgetsModel, centerWidgetsModel, rightWidgetsModel, defaultBar.leftWidgets, defaultBar.centerWidgets, defaultBar.rightWidgets);
+ }
+ }
+
+ function updateListModel(listModel, order) {
+ Lists.update(listModel, order);
+ widgetDataChanged();
+ }
+
+ function hasNamedWorkspaces() {
+ if (typeof NiriService === "undefined" || !CompositorService.isNiri)
+ return false;
+
+ for (var i = 0; i < NiriService.allWorkspaces.length; i++) {
+ var ws = NiriService.allWorkspaces[i];
+ if (ws.name && ws.name.trim() !== "")
+ return true;
+ }
+ return false;
+ }
+
+ function getNamedWorkspaces() {
+ var namedWorkspaces = [];
+ if (typeof NiriService === "undefined" || !CompositorService.isNiri)
+ return namedWorkspaces;
+
+ for (const ws of NiriService.allWorkspaces) {
+ if (ws.name && ws.name.trim() !== "") {
+ namedWorkspaces.push(ws.name);
+ }
+ }
+ return namedWorkspaces;
+ }
+
+ function getPopupYPosition(barHeight) {
+ const defaultBar = barConfigs[0] || getBarConfig("default");
+ const gothOffset = defaultBar?.gothCornersEnabled ? Theme.cornerRadius : 0;
+ const spacing = defaultBar?.spacing ?? 4;
+ const bottomGap = defaultBar?.bottomGap ?? 0;
+ return barHeight + spacing + bottomGap - gothOffset + Theme.popupDistance;
+ }
+
+ function getPopupTriggerPosition(pos, screen, barThickness, widgetWidth, barSpacing, barPosition, barConfig) {
+ const relativeX = pos.x;
+ const relativeY = pos.y;
+ const defaultBar = barConfigs[0] || getBarConfig("default");
+ const spacing = barSpacing !== undefined ? barSpacing : (defaultBar?.spacing ?? 4);
+ const position = barPosition !== undefined ? barPosition : (defaultBar?.position ?? SettingsData.Position.Top);
+ const rawBottomGap = barConfig ? (barConfig.bottomGap !== undefined ? barConfig.bottomGap : (defaultBar?.bottomGap ?? 0)) : (defaultBar?.bottomGap ?? 0);
+ const bottomGap = Math.max(0, rawBottomGap);
+
+ const useAutoGaps = (barConfig && barConfig.popupGapsAuto !== undefined) ? barConfig.popupGapsAuto : (defaultBar?.popupGapsAuto ?? true);
+ const manualGapValue = (barConfig && barConfig.popupGapsManual !== undefined) ? barConfig.popupGapsManual : (defaultBar?.popupGapsManual ?? 4);
+ const popupGap = useAutoGaps ? Math.max(4, spacing) : manualGapValue;
+
+ switch (position) {
+ case SettingsData.Position.Left:
+ return {
+ "x": barThickness + spacing + popupGap,
+ "y": relativeY,
+ "width": widgetWidth
+ };
+ case SettingsData.Position.Right:
+ return {
+ "x": (screen?.width || 0) - (barThickness + spacing + popupGap),
+ "y": relativeY,
+ "width": widgetWidth
+ };
+ case SettingsData.Position.Bottom:
+ return {
+ "x": relativeX,
+ "y": (screen?.height || 0) - (barThickness + spacing + bottomGap + popupGap),
+ "width": widgetWidth
+ };
+ default:
+ return {
+ "x": relativeX,
+ "y": barThickness + spacing + bottomGap + popupGap,
+ "width": widgetWidth
+ };
+ }
+ }
+
+ function getAdjacentBarInfo(screen, barPosition, barConfig) {
+ if (!screen || !barConfig) {
+ return {
+ "topBar": 0,
+ "bottomBar": 0,
+ "leftBar": 0,
+ "rightBar": 0
+ };
+ }
+
+ if (barConfig.autoHide) {
+ return {
+ "topBar": 0,
+ "bottomBar": 0,
+ "leftBar": 0,
+ "rightBar": 0
+ };
+ }
+
+ const enabledBars = getEnabledBarConfigs();
+ const defaultBar = barConfigs[0] || getBarConfig("default");
+ const position = barPosition !== undefined ? barPosition : (defaultBar?.position ?? SettingsData.Position.Top);
+ let topBar = 0;
+ let bottomBar = 0;
+ let leftBar = 0;
+ let rightBar = 0;
+
+ for (var i = 0; i < enabledBars.length; i++) {
+ const other = enabledBars[i];
+ if (other.id === barConfig.id)
+ continue;
+ if (other.autoHide)
+ continue;
+ const otherScreens = other.screenPreferences || ["all"];
+ const barScreens = barConfig.screenPreferences || ["all"];
+ const onSameScreen = otherScreens.includes("all") || barScreens.includes("all") || otherScreens.some(s => isScreenInPreferences(screen, [s]));
+
+ if (!onSameScreen)
+ continue;
+ const otherSpacing = other.spacing !== undefined ? other.spacing : (defaultBar?.spacing ?? 4);
+ const otherPadding = other.innerPadding !== undefined ? other.innerPadding : (defaultBar?.innerPadding ?? 4);
+ const otherThickness = Math.max(26 + otherPadding * 0.6, Theme.barHeight - 4 - (8 - otherPadding)) + otherSpacing;
+
+ const useAutoGaps = other.popupGapsAuto !== undefined ? other.popupGapsAuto : (defaultBar?.popupGapsAuto ?? true);
+ const manualGap = other.popupGapsManual !== undefined ? other.popupGapsManual : (defaultBar?.popupGapsManual ?? 4);
+ const popupGap = useAutoGaps ? Math.max(4, otherSpacing) : manualGap;
+
+ switch (other.position) {
+ case SettingsData.Position.Top:
+ topBar = Math.max(topBar, otherThickness + popupGap);
+ break;
+ case SettingsData.Position.Bottom:
+ bottomBar = Math.max(bottomBar, otherThickness + popupGap);
+ break;
+ case SettingsData.Position.Left:
+ leftBar = Math.max(leftBar, otherThickness + popupGap);
+ break;
+ case SettingsData.Position.Right:
+ rightBar = Math.max(rightBar, otherThickness + popupGap);
+ break;
+ }
+ }
+
+ return {
+ "topBar": topBar,
+ "bottomBar": bottomBar,
+ "leftBar": leftBar,
+ "rightBar": rightBar
+ };
+ }
+
+ function getBarBounds(screen, barThickness, barPosition, barConfig) {
+ if (!screen) {
+ return {
+ "x": 0,
+ "y": 0,
+ "width": 0,
+ "height": 0,
+ "wingSize": 0
+ };
+ }
+
+ const defaultBar = barConfigs[0] || getBarConfig("default");
+ const wingRadius = (defaultBar?.gothCornerRadiusOverride ?? false) ? (defaultBar?.gothCornerRadiusValue ?? 12) : Theme.cornerRadius;
+ const wingSize = (defaultBar?.gothCornersEnabled ?? false) ? Math.max(0, wingRadius) : 0;
+ const screenWidth = screen.width;
+ const screenHeight = screen.height;
+ const position = barPosition !== undefined ? barPosition : (defaultBar?.position ?? SettingsData.Position.Top);
+ const bottomGap = barConfig ? (barConfig.bottomGap !== undefined ? barConfig.bottomGap : (defaultBar?.bottomGap ?? 0)) : (defaultBar?.bottomGap ?? 0);
+
+ let topOffset = 0;
+ let bottomOffset = 0;
+ let leftOffset = 0;
+ let rightOffset = 0;
+
+ if (barConfig) {
+ const enabledBars = getEnabledBarConfigs();
+ for (var i = 0; i < enabledBars.length; i++) {
+ const other = enabledBars[i];
+ if (other.id === barConfig.id)
+ continue;
+ const otherScreens = other.screenPreferences || ["all"];
+ const barScreens = barConfig.screenPreferences || ["all"];
+ const onSameScreen = otherScreens.includes("all") || barScreens.includes("all") || otherScreens.some(s => isScreenInPreferences(screen, [s]));
+
+ if (!onSameScreen)
+ continue;
+ const otherSpacing = other.spacing !== undefined ? other.spacing : (defaultBar?.spacing ?? 4);
+ const otherPadding = other.innerPadding !== undefined ? other.innerPadding : (defaultBar?.innerPadding ?? 4);
+ const otherThickness = Math.max(26 + otherPadding * 0.6, Theme.barHeight - 4 - (8 - otherPadding)) + otherSpacing + wingSize;
+ const otherBottomGap = other.bottomGap !== undefined ? other.bottomGap : (defaultBar?.bottomGap ?? 0);
+
+ switch (other.position) {
+ case SettingsData.Position.Top:
+ if (position === SettingsData.Position.Top && other.id < barConfig.id) {
+ topOffset += otherThickness; // Simple stacking for same pos
+ } else if (position === SettingsData.Position.Left || position === SettingsData.Position.Right) {
+ topOffset = Math.max(topOffset, otherThickness);
+ }
+ break;
+ case SettingsData.Position.Bottom:
+ if (position === SettingsData.Position.Bottom && other.id < barConfig.id) {
+ bottomOffset += (otherThickness + otherBottomGap);
+ } else if (position === SettingsData.Position.Left || position === SettingsData.Position.Right) {
+ bottomOffset = Math.max(bottomOffset, otherThickness + otherBottomGap);
+ }
+ break;
+ case SettingsData.Position.Left:
+ if (position === SettingsData.Position.Top || position === SettingsData.Position.Bottom) {
+ leftOffset = Math.max(leftOffset, otherThickness);
+ } else if (position === SettingsData.Position.Left && other.id < barConfig.id) {
+ leftOffset += otherThickness;
+ }
+ break;
+ case SettingsData.Position.Right:
+ if (position === SettingsData.Position.Top || position === SettingsData.Position.Bottom) {
+ rightOffset = Math.max(rightOffset, otherThickness);
+ } else if (position === SettingsData.Position.Right && other.id < barConfig.id) {
+ rightOffset += otherThickness;
+ }
+ break;
+ }
+ }
+ }
+
+ switch (position) {
+ case SettingsData.Position.Top:
+ return {
+ "x": leftOffset,
+ "y": topOffset + bottomGap,
+ "width": screenWidth - leftOffset - rightOffset,
+ "height": barThickness + wingSize,
+ "wingSize": wingSize
+ };
+ case SettingsData.Position.Bottom:
+ return {
+ "x": leftOffset,
+ "y": screenHeight - barThickness - wingSize - bottomGap - bottomOffset,
+ "width": screenWidth - leftOffset - rightOffset,
+ "height": barThickness + wingSize,
+ "wingSize": wingSize
+ };
+ case SettingsData.Position.Left:
+ return {
+ "x": 0,
+ "y": topOffset,
+ "width": barThickness + wingSize,
+ "height": screenHeight - topOffset - bottomOffset,
+ "wingSize": wingSize
+ };
+ case SettingsData.Position.Right:
+ return {
+ "x": screenWidth - barThickness - wingSize,
+ "y": topOffset,
+ "width": barThickness + wingSize,
+ "height": screenHeight - topOffset - bottomOffset,
+ "wingSize": wingSize
+ };
+ }
+
+ return {
+ "x": 0,
+ "y": 0,
+ "width": 0,
+ "height": 0,
+ "wingSize": 0
+ };
+ }
+
+ function updateBarConfigs() {
+ barConfigsChanged();
+ saveSettings();
+ }
+
+ function getBarConfig(barId) {
+ return barConfigs.find(cfg => cfg.id === barId) || null;
+ }
+
+ function addBarConfig(config) {
+ const configs = JSON.parse(JSON.stringify(barConfigs));
+ configs.push(config);
+ barConfigs = configs;
+ updateBarConfigs();
+ }
+
+ function updateBarConfig(barId, updates) {
+ const configs = JSON.parse(JSON.stringify(barConfigs));
+ const index = configs.findIndex(cfg => cfg.id === barId);
+ if (index === -1)
+ return;
+ const positionChanged = updates.position !== undefined && configs[index].position !== updates.position;
+
+ Object.assign(configs[index], updates);
+ barConfigs = configs;
+ updateBarConfigs();
+
+ if (positionChanged) {
+ NotificationService.dismissAllPopups();
+ }
+ }
+
+ function checkBarCollisions(barId) {
+ const bar = getBarConfig(barId);
+ if (!bar || !bar.enabled)
+ return [];
+
+ const conflicts = [];
+ const enabledBars = getEnabledBarConfigs();
+
+ for (var i = 0; i < enabledBars.length; i++) {
+ const other = enabledBars[i];
+ if (other.id === barId)
+ continue;
+ const samePosition = bar.position === other.position;
+ if (!samePosition)
+ continue;
+ const barScreens = bar.screenPreferences || ["all"];
+ const otherScreens = other.screenPreferences || ["all"];
+
+ const hasAll = barScreens.includes("all") || otherScreens.includes("all");
+ if (hasAll) {
+ conflicts.push({
+ "barId": other.id,
+ "barName": other.name,
+ "reason": "Same position on all screens"
+ });
+ continue;
+ }
+
+ const overlapping = barScreens.some(screen => otherScreens.includes(screen));
+ if (overlapping) {
+ conflicts.push({
+ "barId": other.id,
+ "barName": other.name,
+ "reason": "Same position on overlapping screens"
+ });
+ }
+ }
+
+ return conflicts;
+ }
+
+ function deleteBarConfig(barId) {
+ if (barId === "default")
+ return;
+ const configs = barConfigs.filter(cfg => cfg.id !== barId);
+ barConfigs = configs;
+ updateBarConfigs();
+ }
+
+ function getEnabledBarConfigs() {
+ return barConfigs.filter(cfg => cfg.enabled);
+ }
+
+ function getScreensSortedByPosition() {
+ const screens = [];
+ for (var i = 0; i < Quickshell.screens.length; i++) {
+ screens.push(Quickshell.screens[i]);
+ }
+ screens.sort((a, b) => {
+ if (a.x !== b.x)
+ return a.x - b.x;
+ return a.y - b.y;
+ });
+ return screens;
+ }
+
+ function getScreenModelIndex(screen) {
+ if (!screen || !screen.model)
+ return -1;
+ const sorted = getScreensSortedByPosition();
+ let modelCount = 0;
+ let screenIndex = -1;
+ for (var i = 0; i < sorted.length; i++) {
+ if (sorted[i].model === screen.model) {
+ if (sorted[i].name === screen.name) {
+ screenIndex = modelCount;
+ }
+ modelCount++;
+ }
+ }
+ if (modelCount <= 1)
+ return -1;
+ return screenIndex;
+ }
+
+ function getScreenDisplayName(screen) {
+ if (!screen)
+ return "";
+ if (displayNameMode === "model" && screen.model) {
+ const modelIndex = getScreenModelIndex(screen);
+ if (modelIndex >= 0) {
+ return screen.model + "-" + modelIndex;
+ }
+ return screen.model;
+ }
+ return screen.name;
+ }
+
+ function isScreenInPreferences(screen, prefs) {
+ if (!screen)
+ return false;
+
+ const screenDisplayName = getScreenDisplayName(screen);
+
+ return prefs.some(pref => {
+ if (typeof pref === "string") {
+ if (pref === "all" || pref === screen.name)
+ return true;
+ if (displayNameMode === "model") {
+ return pref === screenDisplayName;
+ }
+ return pref === screen.model;
+ }
+
+ if (displayNameMode === "model") {
+ if (pref.model && screen.model) {
+ if (pref.modelIndex !== undefined) {
+ const screenModelIndex = getScreenModelIndex(screen);
+ return pref.model === screen.model && pref.modelIndex === screenModelIndex;
+ }
+ return pref.model === screen.model;
+ }
+ return false;
+ }
+ return pref.name === screen.name;
+ });
+ }
+
+ function getFilteredScreens(componentId) {
+ var prefs = screenPreferences && screenPreferences[componentId] || ["all"];
+ if (prefs.includes("all") || (typeof prefs[0] === "string" && prefs[0] === "all")) {
+ return Quickshell.screens;
+ }
+ var filtered = Quickshell.screens.filter(screen => isScreenInPreferences(screen, prefs));
+ if (filtered.length === 0 && showOnLastDisplay && showOnLastDisplay[componentId] && Quickshell.screens.length === 1) {
+ return Quickshell.screens;
+ }
+ return filtered;
+ }
+
+ function sendTestNotifications() {
+ NotificationService.dismissAllPopups();
+ sendTestNotification(0);
+ testNotifTimer1.start();
+ testNotifTimer2.start();
+ }
+
+ function sendTestNotification(index) {
+ const notifications = [["Notification Position Test", "DMS test notification 1 of 3 ~ Hi there!", "preferences-system"], ["Second Test", "DMS Notification 2 of 3 ~ Check it out!", "applications-graphics"], ["Third Test", "DMS notification 3 of 3 ~ Enjoy!", "face-smile"]];
+
+ if (index < 0 || index >= notifications.length) {
+ return;
+ }
+
+ const notif = notifications[index];
+ testNotificationProcess.command = ["notify-send", "-h", "int:transient:1", "-a", "DMS", "-i", notif[2], notif[0], notif[1]];
+ testNotificationProcess.running = true;
+ }
+
+ function setMatugenScheme(scheme) {
+ var normalized = scheme || "scheme-tonal-spot";
+ if (matugenScheme === normalized)
+ return;
+ set("matugenScheme", normalized);
+ if (typeof Theme !== "undefined") {
+ Theme.generateSystemThemesFromCurrentTheme();
+ }
+ }
+
+ function setMatugenContrast(value) {
+ if (matugenContrast === value)
+ return;
+ set("matugenContrast", value);
+ }
+
+ function setRunUserMatugenTemplates(enabled) {
+ if (runUserMatugenTemplates === enabled)
+ return;
+ set("runUserMatugenTemplates", enabled);
+ if (typeof Theme !== "undefined") {
+ Theme.generateSystemThemesFromCurrentTheme();
+ }
+ }
+
+ function setMatugenTargetMonitor(monitorName) {
+ if (matugenTargetMonitor === monitorName)
+ return;
+ set("matugenTargetMonitor", monitorName);
+ if (typeof Theme !== "undefined") {
+ Theme.generateSystemThemesFromCurrentTheme();
+ }
+ }
+
+ function setCornerRadius(radius) {
+ set("cornerRadius", radius);
+ updateCompositorLayout();
+ }
+
+ function setWeatherLocation(displayName, coordinates) {
+ SessionData.setWeatherLocation(displayName, coordinates);
+ }
+
+ function setIconTheme(themeName) {
+ iconTheme = themeName;
+ updateGtkIconTheme();
+ updateQtIconTheme();
+ updateCosmicIconTheme();
+ saveSettings();
+ if (typeof Theme !== "undefined" && Theme.currentTheme === Theme.dynamic)
+ Theme.generateSystemThemesFromCurrentTheme();
+ }
+
+ function setCursorTheme(themeName) {
+ const updated = JSON.parse(JSON.stringify(cursorSettings));
+ if (updated.theme === themeName)
+ return;
+ updated.theme = themeName;
+ cursorSettings = updated;
+ saveSettings();
+ updateXResources();
+ updateCompositorCursor();
+ }
+
+ function setCursorSize(size) {
+ const updated = JSON.parse(JSON.stringify(cursorSettings));
+ if (updated.size === size)
+ return;
+ updated.size = size;
+ cursorSettings = updated;
+ saveSettings();
+ updateXResources();
+ updateCompositorCursor();
+ }
+
+ // This solution for xwayland cursor themes is from the xwls discussion:
+ // https://github.com/Supreeeme/xwayland-satellite/issues/104
+ // no idea if this matters on other compositors but we also set XCURSOR stuff in the launcher
+ function updateCompositorCursor() {
+ if (typeof CompositorService === "undefined")
+ return;
+ if (CompositorService.isNiri && typeof NiriService !== "undefined") {
+ NiriService.generateNiriCursorConfig();
+ return;
+ }
+ if (CompositorService.isHyprland && typeof HyprlandService !== "undefined") {
+ HyprlandService.generateCursorConfig();
+ return;
+ }
+ if (CompositorService.isDwl && typeof DwlService !== "undefined") {
+ DwlService.generateCursorConfig();
+ return;
+ }
+ }
+
+ function updateXResources() {
+ const homeDir = Paths.strip(StandardPaths.writableLocation(StandardPaths.HomeLocation));
+ const xresourcesPath = homeDir + "/.Xresources";
+ const themeName = cursorSettings.theme === "System Default" ? systemDefaultCursorTheme : cursorSettings.theme;
+ const size = cursorSettings.size || 24;
+
+ if (!themeName)
+ return;
+
+ const script = `
+ xresources_file="${xresourcesPath}"
+ [ -f "$xresources_file" ] && [ ! -w "$xresources_file" ] && exit 0
+ theme_name="${themeName}"
+ cursor_size="${size}"
+
+ current_theme=""
+ current_size=""
+ if [ -f "$xresources_file" ]; then
+ current_theme=$(grep -E '^[[:space:]]*Xcursor\\.theme:' "$xresources_file" 2>/dev/null | sed 's/.*:[[:space:]]*//' | head -1)
+ current_size=$(grep -E '^[[:space:]]*Xcursor\\.size:' "$xresources_file" 2>/dev/null | sed 's/.*:[[:space:]]*//' | head -1)
+ fi
+
+ [ "$current_theme" = "$theme_name" ] && [ "$current_size" = "$cursor_size" ] && exit 0
+
+ if [ -f "$xresources_file" ]; then
+ cp "$xresources_file" "\${xresources_file}.backup$(date +%s)"
+ fi
+
+ temp_file="\${xresources_file}.tmp.$$"
+ if [ -f "$xresources_file" ]; then
+ grep -v '^[[:space:]]*Xcursor\\.theme:' "$xresources_file" | grep -v '^[[:space:]]*Xcursor\\.size:' > "$temp_file" 2>/dev/null || true
+ else
+ touch "$temp_file"
+ fi
+
+ echo "Xcursor.theme: $theme_name" >> "$temp_file"
+ echo "Xcursor.size: $cursor_size" >> "$temp_file"
+ mv "$temp_file" "$xresources_file"
+ xrdb -merge "$xresources_file" 2>/dev/null || true
+ `;
+
+ Quickshell.execDetached(["sh", "-c", script]);
+ }
+
+ function getCursorEnvironment() {
+ const isSystemDefault = cursorSettings.theme === "System Default";
+ const isDefaultSize = !cursorSettings.size || cursorSettings.size === 24;
+ if (isSystemDefault && isDefaultSize)
+ return {};
+
+ const themeName = isSystemDefault ? "" : cursorSettings.theme;
+ const size = String(cursorSettings.size || 24);
+ const env = {};
+
+ if (!isDefaultSize) {
+ env["XCURSOR_SIZE"] = size;
+ env["HYPRCURSOR_SIZE"] = size;
+ }
+ if (themeName) {
+ env["XCURSOR_THEME"] = themeName;
+ env["HYPRCURSOR_THEME"] = themeName;
+ }
+ return env;
+ }
+
+ function setGtkThemingEnabled(enabled) {
+ set("gtkThemingEnabled", enabled);
+ if (enabled && typeof Theme !== "undefined") {
+ Theme.generateSystemThemesFromCurrentTheme();
+ }
+ }
+
+ function setQtThemingEnabled(enabled) {
+ set("qtThemingEnabled", enabled);
+ if (enabled && typeof Theme !== "undefined") {
+ Theme.generateSystemThemesFromCurrentTheme();
+ }
+ }
+
+ function setShowDock(enabled) {
+ showDock = enabled;
+ const defaultBar = barConfigs[0] || getBarConfig("default");
+ const barPos = defaultBar?.position ?? SettingsData.Position.Top;
+ if (enabled && dockPosition === barPos) {
+ if (barPos === SettingsData.Position.Top) {
+ setDockPosition(SettingsData.Position.Bottom);
+ return;
+ }
+ if (barPos === SettingsData.Position.Bottom) {
+ setDockPosition(SettingsData.Position.Top);
+ return;
+ }
+ if (barPos === SettingsData.Position.Left) {
+ setDockPosition(SettingsData.Position.Right);
+ return;
+ }
+ if (barPos === SettingsData.Position.Right) {
+ setDockPosition(SettingsData.Position.Left);
+ return;
+ }
+ }
+ saveSettings();
+ }
+
+ function setDockPosition(position) {
+ dockPosition = position;
+ const defaultBar = barConfigs[0] || getBarConfig("default");
+ const barPos = defaultBar?.position ?? SettingsData.Position.Top;
+ if (position === SettingsData.Position.Bottom && barPos === SettingsData.Position.Bottom && showDock) {
+ setDankBarPosition(SettingsData.Position.Top);
+ }
+ if (position === SettingsData.Position.Top && barPos === SettingsData.Position.Top && showDock) {
+ setDankBarPosition(SettingsData.Position.Bottom);
+ }
+ if (position === SettingsData.Position.Left && barPos === SettingsData.Position.Left && showDock) {
+ setDankBarPosition(SettingsData.Position.Right);
+ }
+ if (position === SettingsData.Position.Right && barPos === SettingsData.Position.Right && showDock) {
+ setDankBarPosition(SettingsData.Position.Left);
+ }
+ saveSettings();
+ Qt.callLater(() => forceDockLayoutRefresh());
+ }
+
+ function setDankBarSpacing(spacing) {
+ const defaultBar = barConfigs[0] || getBarConfig("default");
+ if (defaultBar) {
+ updateBarConfig(defaultBar.id, {
+ "spacing": spacing
+ });
+ }
+ updateCompositorLayout();
+ }
+
+ function setDankBarPosition(position) {
+ const defaultBar = barConfigs[0] || getBarConfig("default");
+ if (!defaultBar)
+ return;
+ if (position === SettingsData.Position.Bottom && dockPosition === SettingsData.Position.Bottom && showDock) {
+ setDockPosition(SettingsData.Position.Top);
+ return;
+ }
+ if (position === SettingsData.Position.Top && dockPosition === SettingsData.Position.Top && showDock) {
+ setDockPosition(SettingsData.Position.Bottom);
+ return;
+ }
+ if (position === SettingsData.Position.Left && dockPosition === SettingsData.Position.Left && showDock) {
+ setDockPosition(SettingsData.Position.Right);
+ return;
+ }
+ if (position === SettingsData.Position.Right && dockPosition === SettingsData.Position.Right && showDock) {
+ setDockPosition(SettingsData.Position.Left);
+ return;
+ }
+ updateBarConfig(defaultBar.id, {
+ "position": position
+ });
+ }
+
+ function setDankBarLeftWidgets(order) {
+ const defaultBar = barConfigs[0] || getBarConfig("default");
+ if (defaultBar) {
+ updateBarConfig(defaultBar.id, {
+ "leftWidgets": order
+ });
+ updateListModel(leftWidgetsModel, order);
+ }
+ }
+
+ function setDankBarCenterWidgets(order) {
+ const defaultBar = barConfigs[0] || getBarConfig("default");
+ if (defaultBar) {
+ updateBarConfig(defaultBar.id, {
+ "centerWidgets": order
+ });
+ updateListModel(centerWidgetsModel, order);
+ }
+ }
+
+ function setDankBarRightWidgets(order) {
+ const defaultBar = barConfigs[0] || getBarConfig("default");
+ if (defaultBar) {
+ updateBarConfig(defaultBar.id, {
+ "rightWidgets": order
+ });
+ updateListModel(rightWidgetsModel, order);
+ }
+ }
+
+ function resetDankBarWidgetsToDefault() {
+ var defaultLeft = ["launcherButton", "workspaceSwitcher", "focusedWindow"];
+ var defaultCenter = ["music", "clock", "weather"];
+ var defaultRight = ["systemTray", "clipboard", "notificationButton", "battery", "controlCenterButton"];
+ const defaultBar = barConfigs[0] || getBarConfig("default");
+ if (defaultBar) {
+ updateBarConfig(defaultBar.id, {
+ "leftWidgets": defaultLeft,
+ "centerWidgets": defaultCenter,
+ "rightWidgets": defaultRight
+ });
+ }
+ updateListModel(leftWidgetsModel, defaultLeft);
+ updateListModel(centerWidgetsModel, defaultCenter);
+ updateListModel(rightWidgetsModel, defaultRight);
+ showLauncherButton = true;
+ showWorkspaceSwitcher = true;
+ showFocusedWindow = true;
+ showWeather = true;
+ showMusic = true;
+ showClipboard = true;
+ showCpuUsage = true;
+ showMemUsage = true;
+ showCpuTemp = true;
+ showGpuTemp = true;
+ showSystemTray = true;
+ showClock = true;
+ showNotificationButton = true;
+ showBattery = true;
+ showControlCenterButton = true;
+ showCapsLockIndicator = true;
+ }
+
+ function setWorkspaceNameIcon(workspaceName, iconData) {
+ var iconMap = JSON.parse(JSON.stringify(workspaceNameIcons));
+ iconMap[workspaceName] = iconData;
+ workspaceNameIcons = iconMap;
+ saveSettings();
+ workspaceIconsUpdated();
+ }
+
+ function removeWorkspaceNameIcon(workspaceName) {
+ var iconMap = JSON.parse(JSON.stringify(workspaceNameIcons));
+ delete iconMap[workspaceName];
+ workspaceNameIcons = iconMap;
+ saveSettings();
+ workspaceIconsUpdated();
+ }
+
+ function getWorkspaceNameIcon(workspaceName) {
+ return workspaceNameIcons[workspaceName] || null;
+ }
+
+ function addAppIdSubstitution(pattern, replacement, type) {
+ var subs = JSON.parse(JSON.stringify(appIdSubstitutions));
+ subs.push({
+ pattern: pattern,
+ replacement: replacement,
+ type: type
+ });
+ appIdSubstitutions = subs;
+ saveSettings();
+ }
+
+ function updateAppIdSubstitution(index, pattern, replacement, type) {
+ var subs = JSON.parse(JSON.stringify(appIdSubstitutions));
+ if (index < 0 || index >= subs.length)
+ return;
+ subs[index] = {
+ pattern: pattern,
+ replacement: replacement,
+ type: type
+ };
+ appIdSubstitutions = subs;
+ saveSettings();
+ }
+
+ function removeAppIdSubstitution(index) {
+ var subs = JSON.parse(JSON.stringify(appIdSubstitutions));
+ if (index < 0 || index >= subs.length)
+ return;
+ subs.splice(index, 1);
+ appIdSubstitutions = subs;
+ saveSettings();
+ }
+
+ property bool _pendingExpandNotificationRules: false
+ property int _pendingNotificationRuleIndex: -1
+
+ function addNotificationRule() {
+ var rules = JSON.parse(JSON.stringify(notificationRules || []));
+ rules.push({
+ enabled: true,
+ field: "appName",
+ pattern: "",
+ matchType: "contains",
+ action: "default",
+ urgency: "default"
+ });
+ notificationRules = rules;
+ saveSettings();
+ }
+
+ function addNotificationRuleForNotification(appName, desktopEntry) {
+ var rules = JSON.parse(JSON.stringify(notificationRules || []));
+ var pattern = (desktopEntry && desktopEntry !== "") ? desktopEntry : (appName || "");
+ var field = (desktopEntry && desktopEntry !== "") ? "desktopEntry" : "appName";
+ var rule = {
+ enabled: true,
+ field: pattern ? field : "appName",
+ pattern: pattern || "",
+ matchType: pattern ? "exact" : "contains",
+ action: "default",
+ urgency: "default"
+ };
+ rules.push(rule);
+ notificationRules = rules;
+ saveSettings();
+ var index = rules.length - 1;
+ _pendingExpandNotificationRules = true;
+ _pendingNotificationRuleIndex = index;
+ return index;
+ }
+
+ function addMuteRuleForApp(appName, desktopEntry) {
+ var rules = JSON.parse(JSON.stringify(notificationRules || []));
+ var pattern = (desktopEntry && desktopEntry !== "") ? desktopEntry : (appName || "");
+ var field = (desktopEntry && desktopEntry !== "") ? "desktopEntry" : "appName";
+ if (pattern === "")
+ return;
+ rules.push({
+ enabled: true,
+ field: field,
+ pattern: pattern,
+ matchType: "exact",
+ action: "mute",
+ urgency: "default"
+ });
+ notificationRules = rules;
+ saveSettings();
+ }
+
+ function isAppMuted(appName, desktopEntry) {
+ const rules = notificationRules || [];
+ const pat = (desktopEntry && desktopEntry !== "" ? desktopEntry : appName || "").toString().toLowerCase();
+ if (!pat)
+ return false;
+ for (let i = 0; i < rules.length; i++) {
+ const r = rules[i];
+ if ((r.action || "").toString().toLowerCase() !== "mute" || r.enabled === false)
+ continue;
+ const field = (r.field || "appName").toString().toLowerCase();
+ const rulePat = (r.pattern || "").toString().toLowerCase();
+ if (!rulePat)
+ continue;
+ const useDesktop = field === "desktopentry";
+ const matches = (useDesktop && desktopEntry) ? (desktopEntry.toString().toLowerCase() === rulePat) : (appName && appName.toString().toLowerCase() === rulePat);
+ if (matches)
+ return true;
+ if (rulePat === pat)
+ return true;
+ }
+ return false;
+ }
+
+ function removeMuteRuleForApp(appName, desktopEntry) {
+ var rules = JSON.parse(JSON.stringify(notificationRules || []));
+ const app = (appName || "").toString().toLowerCase();
+ const desktop = (desktopEntry || "").toString().toLowerCase();
+ if (!app && !desktop)
+ return;
+ for (let i = rules.length - 1; i >= 0; i--) {
+ const r = rules[i];
+ if ((r.action || "").toString().toLowerCase() !== "mute")
+ continue;
+ const rulePat = (r.pattern || "").toString().toLowerCase();
+ if (!rulePat)
+ continue;
+ if (rulePat === app || rulePat === desktop) {
+ rules.splice(i, 1);
+ notificationRules = rules;
+ saveSettings();
+ return;
+ }
+ }
+ }
+
+ function updateNotificationRule(index, ruleData) {
+ var rules = JSON.parse(JSON.stringify(notificationRules || []));
+ if (index < 0 || index >= rules.length)
+ return;
+ var existing = rules[index] || {};
+ rules[index] = Object.assign({}, existing, ruleData || {});
+ notificationRules = rules;
+ saveSettings();
+ }
+
+ function updateNotificationRuleField(index, key, value) {
+ if (key === undefined || key === null || key === "")
+ return;
+ var patch = {};
+ patch[key] = value;
+ updateNotificationRule(index, patch);
+ }
+
+ function removeNotificationRule(index) {
+ var rules = JSON.parse(JSON.stringify(notificationRules || []));
+ if (index < 0 || index >= rules.length)
+ return;
+ rules.splice(index, 1);
+ notificationRules = rules;
+ saveSettings();
+ }
+
+ function getDefaultNotificationRules() {
+ return Spec.SPEC.notificationRules.def;
+ }
+
+ function resetNotificationRules() {
+ notificationRules = JSON.parse(JSON.stringify(Spec.SPEC.notificationRules.def));
+ saveSettings();
+ }
+
+ function getDefaultAppIdSubstitutions() {
+ return Spec.SPEC.appIdSubstitutions.def;
+ }
+
+ function resetAppIdSubstitutions() {
+ appIdSubstitutions = JSON.parse(JSON.stringify(Spec.SPEC.appIdSubstitutions.def));
+ saveSettings();
+ }
+
+ function getRegistryThemeVariant(themeId, defaultVariant) {
+ var stored = registryThemeVariants[themeId];
+ if (typeof stored === "string")
+ return stored || defaultVariant || "";
+ return defaultVariant || "";
+ }
+
+ function setRegistryThemeVariant(themeId, variantId) {
+ var variants = JSON.parse(JSON.stringify(registryThemeVariants));
+ variants[themeId] = variantId;
+ registryThemeVariants = variants;
+ saveSettings();
+ if (typeof Theme !== "undefined")
+ Theme.reloadCustomThemeVariant();
+ }
+
+ function getRegistryThemeMultiVariant(themeId, defaults, mode) {
+ var stored = registryThemeVariants[themeId];
+ if (!stored || typeof stored !== "object")
+ return defaults || {};
+ if ((stored.dark && typeof stored.dark === "object") || (stored.light && typeof stored.light === "object")) {
+ if (!mode)
+ return stored.dark || stored.light || defaults || {};
+ var modeData = stored[mode];
+ if (modeData && typeof modeData === "object")
+ return modeData;
+ return defaults || {};
+ }
+ return stored;
+ }
+
+ function setRegistryThemeMultiVariant(themeId, flavor, accent, mode) {
+ var variants = JSON.parse(JSON.stringify(registryThemeVariants));
+ var existing = variants[themeId];
+ var perMode = {};
+ if (existing && typeof existing === "object") {
+ if ((existing.dark && typeof existing.dark === "object") || (existing.light && typeof existing.light === "object")) {
+ perMode = existing;
+ } else if (typeof existing.flavor === "string") {
+ perMode.dark = {
+ flavor: existing.flavor,
+ accent: existing.accent || ""
+ };
+ }
+ }
+ perMode[mode || "dark"] = {
+ flavor: flavor,
+ accent: accent
+ };
+ variants[themeId] = perMode;
+ registryThemeVariants = variants;
+ saveSettings();
+ if (typeof Theme !== "undefined")
+ Theme.reloadCustomThemeVariant();
+ }
+
+ function toggleDankBarVisible() {
+ const defaultBar = barConfigs[0] || getBarConfig("default");
+ if (defaultBar) {
+ updateBarConfig(defaultBar.id, {
+ "visible": !defaultBar.visible
+ });
+ }
+ }
+
+ function toggleShowDock() {
+ setShowDock(!showDock);
+ }
+
+ function getPluginSetting(pluginId, key, defaultValue) {
+ if (!pluginSettings[pluginId]) {
+ return defaultValue;
+ }
+ return pluginSettings[pluginId][key] !== undefined ? pluginSettings[pluginId][key] : defaultValue;
+ }
+
+ function setPluginSetting(pluginId, key, value) {
+ const updated = JSON.parse(JSON.stringify(pluginSettings));
+ if (!updated[pluginId]) {
+ updated[pluginId] = {};
+ }
+ updated[pluginId][key] = value;
+ pluginSettings = updated;
+ savePluginSettings();
+ }
+
+ function removePluginSettings(pluginId) {
+ if (pluginSettings[pluginId]) {
+ delete pluginSettings[pluginId];
+ savePluginSettings();
+ }
+ }
+
+ function getPluginSettingsForPlugin(pluginId) {
+ const settings = pluginSettings[pluginId];
+ return settings ? JSON.parse(JSON.stringify(settings)) : {};
+ }
+
+ function getNiriOutputSetting(outputId, key, defaultValue) {
+ if (!niriOutputSettings[outputId])
+ return defaultValue;
+ return niriOutputSettings[outputId][key] !== undefined ? niriOutputSettings[outputId][key] : defaultValue;
+ }
+
+ function setNiriOutputSetting(outputId, key, value) {
+ const updated = JSON.parse(JSON.stringify(niriOutputSettings));
+ if (!updated[outputId])
+ updated[outputId] = {};
+ updated[outputId][key] = value;
+ niriOutputSettings = updated;
+ saveSettings();
+ }
+
+ function getNiriOutputSettings(outputId) {
+ const settings = niriOutputSettings[outputId];
+ return settings ? JSON.parse(JSON.stringify(settings)) : {};
+ }
+
+ function setNiriOutputSettings(outputId, settings) {
+ const updated = JSON.parse(JSON.stringify(niriOutputSettings));
+ updated[outputId] = settings;
+ niriOutputSettings = updated;
+ saveSettings();
+ }
+
+ function removeNiriOutputSettings(outputId) {
+ if (!niriOutputSettings[outputId])
+ return;
+ const updated = JSON.parse(JSON.stringify(niriOutputSettings));
+ delete updated[outputId];
+ niriOutputSettings = updated;
+ saveSettings();
+ }
+
+ function getHyprlandOutputSetting(outputId, key, defaultValue) {
+ if (!hyprlandOutputSettings[outputId])
+ return defaultValue;
+ return hyprlandOutputSettings[outputId][key] !== undefined ? hyprlandOutputSettings[outputId][key] : defaultValue;
+ }
+
+ function setHyprlandOutputSetting(outputId, key, value) {
+ const updated = JSON.parse(JSON.stringify(hyprlandOutputSettings));
+ if (!updated[outputId])
+ updated[outputId] = {};
+ updated[outputId][key] = value;
+ hyprlandOutputSettings = updated;
+ saveSettings();
+ }
+
+ function removeHyprlandOutputSetting(outputId, key) {
+ if (!hyprlandOutputSettings[outputId] || !(key in hyprlandOutputSettings[outputId]))
+ return;
+ const updated = JSON.parse(JSON.stringify(hyprlandOutputSettings));
+ delete updated[outputId][key];
+ hyprlandOutputSettings = updated;
+ saveSettings();
+ }
+
+ function getHyprlandOutputSettings(outputId) {
+ const settings = hyprlandOutputSettings[outputId];
+ return settings ? JSON.parse(JSON.stringify(settings)) : {};
+ }
+
+ function setHyprlandOutputSettings(outputId, settings) {
+ const updated = JSON.parse(JSON.stringify(hyprlandOutputSettings));
+ updated[outputId] = settings;
+ hyprlandOutputSettings = updated;
+ saveSettings();
+ }
+
+ function removeHyprlandOutputSettings(outputId) {
+ if (!hyprlandOutputSettings[outputId])
+ return;
+ const updated = JSON.parse(JSON.stringify(hyprlandOutputSettings));
+ delete updated[outputId];
+ hyprlandOutputSettings = updated;
+ saveSettings();
+ }
+
+ function getDisplayProfiles(compositor) {
+ return displayProfiles[compositor] || {};
+ }
+
+ function setDisplayProfile(compositor, profileId, data) {
+ const updated = JSON.parse(JSON.stringify(displayProfiles));
+ if (!updated[compositor])
+ updated[compositor] = {};
+ updated[compositor][profileId] = data;
+ displayProfiles = updated;
+ saveSettings();
+ }
+
+ function removeDisplayProfile(compositor, profileId) {
+ if (!displayProfiles[compositor] || !displayProfiles[compositor][profileId])
+ return;
+ const updated = JSON.parse(JSON.stringify(displayProfiles));
+ delete updated[compositor][profileId];
+ displayProfiles = updated;
+ saveSettings();
+ }
+
+ function getActiveDisplayProfile(compositor) {
+ return activeDisplayProfile[compositor] || "";
+ }
+
+ function setActiveDisplayProfile(compositor, profileId) {
+ const updated = JSON.parse(JSON.stringify(activeDisplayProfile));
+ updated[compositor] = profileId;
+ activeDisplayProfile = updated;
+ saveSettings();
+ }
+
+ ListModel {
+ id: leftWidgetsModel
+ }
+
+ ListModel {
+ id: centerWidgetsModel
+ }
+
+ ListModel {
+ id: rightWidgetsModel
+ }
+
+ property Process testNotificationProcess
+
+ testNotificationProcess: Process {
+ command: []
+ running: false
+ }
+
+ property Timer testNotifTimer1
+
+ testNotifTimer1: Timer {
+ interval: 400
+ repeat: false
+ onTriggered: sendTestNotification(1)
+ }
+
+ property Timer testNotifTimer2
+
+ testNotifTimer2: Timer {
+ interval: 800
+ repeat: false
+ onTriggered: sendTestNotification(2)
+ }
+
+ property alias settingsFile: settingsFile
+
+ Timer {
+ id: settingsFileReloadDebounce
+ interval: 50
+ onTriggered: settingsFile.reload()
+ repeat: false
+ }
+
+ FileView {
+ id: settingsFile
+
+ path: isGreeterMode ? "" : StandardPaths.writableLocation(StandardPaths.ConfigLocation) + "/DankMaterialShell/settings.json"
+ blockLoading: true
+ blockWrites: true
+ atomicWrites: true
+ watchChanges: true
+ onFileChanged: {
+ if (_selfWrite) {
+ _selfWrite = false;
+ return;
+ }
+ settingsFileReloadDebounce.restart();
+ }
+ onLoaded: {
+ if (isGreeterMode)
+ return;
+ _loading = true;
+ _hasUnsavedChanges = false;
+ try {
+ const txt = settingsFile.text();
+ if (!txt || !txt.trim()) {
+ _parseError = true;
+ return;
+ }
+ const obj = JSON.parse(txt);
+ _parseError = false;
+ Store.parse(root, obj);
+
+ if (obj.weatherLocation !== undefined)
+ _legacyWeatherLocation = obj.weatherLocation;
+ if (obj.weatherCoordinates !== undefined)
+ _legacyWeatherCoordinates = obj.weatherCoordinates;
+ if (obj.vpnLastConnected !== undefined && obj.vpnLastConnected !== "") {
+ _legacyVpnLastConnected = obj.vpnLastConnected;
+ SessionData.vpnLastConnected = _legacyVpnLastConnected;
+ SessionData.saveSettings();
+ }
+
+ _loadedSettingsSnapshot = JSON.stringify(Store.toJson(root));
+ _hasLoaded = true;
+ applyStoredTheme();
+ updateCompositorCursor();
+ } catch (e) {
+ _parseError = true;
+ const msg = e.message;
+ console.error("SettingsData: Failed to reload settings.json - file will not be overwritten. Error:", msg);
+ Qt.callLater(() => ToastService.showError(I18n.tr("Failed to parse settings.json"), msg));
+ } finally {
+ _loading = false;
+ }
+ }
+ onLoadFailed: error => {
+ if (!isGreeterMode) {
+ applyStoredTheme();
+ }
+ }
+ onSaveFailed: error => {
+ root._isReadOnly = true;
+ root._hasUnsavedChanges = root._checkForUnsavedChanges();
+ }
+ }
+
+ FileView {
+ id: pluginSettingsFile
+
+ path: isGreeterMode ? "" : pluginSettingsPath
+ blockLoading: true
+ blockWrites: true
+ atomicWrites: true
+ printErrors: false
+ watchChanges: !isGreeterMode
+ onLoaded: {
+ if (!isGreeterMode) {
+ parsePluginSettings(pluginSettingsFile.text());
+ }
+ }
+ onLoadFailed: error => {
+ if (!isGreeterMode) {
+ const msg = String(error || "");
+ if (!_isMissingPluginSettingsError(error))
+ console.warn("SettingsData: Failed to load plugin_settings.json. Error:", msg);
+ _resetPluginSettings();
+ }
+ }
+ }
+
+ property bool pluginSettingsFileExists: false
+
+ Process {
+ id: settingsWritableCheckProcess
+
+ property string settingsPath: Paths.strip(settingsFile.path)
+
+ command: ["sh", "-c", "[ ! -f \"" + settingsPath + "\" ] || [ -w \"" + settingsPath + "\" ] && echo 'writable' || echo 'readonly'"]
+ running: false
+
+ stdout: StdioCollector {
+ onStreamFinished: {
+ const result = text.trim();
+ root._onWritableCheckComplete(result === "writable");
+ }
+ }
+ }
+}
diff --git a/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Common/StockThemes.js b/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Common/StockThemes.js
new file mode 100644
index 0000000..311c27f
--- /dev/null
+++ b/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Common/StockThemes.js
@@ -0,0 +1,468 @@
+// Stock theme definitions for DankMaterialShell
+// Separated from Theme.qml to keep that file clean
+
+const StockThemes = {
+ DARK: {
+ blue: {
+ name: "Blue",
+ primary: "#42a5f5",
+ primaryText: "#000000",
+ primaryContainer: "#0d47a1",
+ secondary: "#8ab4f8",
+ surface: "#101418",
+ surfaceText: "#e0e2e8",
+ surfaceVariant: "#42474e",
+ surfaceVariantText: "#c2c7cf",
+ surfaceTint: "#8ab4f8",
+ background: "#101418",
+ backgroundText: "#e0e2e8",
+ outline: "#8c9199",
+ surfaceContainer: "#1d2024",
+ surfaceContainerHigh: "#272a2f",
+ surfaceContainerHighest: "#32353a",
+ },
+ purple: {
+ name: "Purple",
+ primary: "#D0BCFF",
+ primaryText: "#381E72",
+ primaryContainer: "#4F378B",
+ secondary: "#CCC2DC",
+ surface: "#141218",
+ surfaceText: "#e6e0e9",
+ surfaceVariant: "#49454e",
+ surfaceVariantText: "#cac4cf",
+ surfaceTint: "#D0BCFF",
+ background: "#141218",
+ backgroundText: "#e6e0e9",
+ outline: "#948f99",
+ surfaceContainer: "#211f24",
+ surfaceContainerHigh: "#2b292f",
+ surfaceContainerHighest: "#36343a",
+ },
+ green: {
+ name: "Green",
+ primary: "#4caf50",
+ primaryText: "#000000",
+ primaryContainer: "#1b5e20",
+ secondary: "#81c995",
+ surface: "#10140f",
+ surfaceText: "#e0e4db",
+ surfaceVariant: "#424940",
+ surfaceVariantText: "#c2c9bd",
+ surfaceTint: "#81c995",
+ background: "#10140f",
+ backgroundText: "#e0e4db",
+ outline: "#8c9388",
+ surfaceContainer: "#1d211b",
+ surfaceContainerHigh: "#272b25",
+ surfaceContainerHighest: "#323630",
+ },
+ orange: {
+ name: "Orange",
+ primary: "#ff6d00",
+ primaryText: "#000000",
+ primaryContainer: "#3e2723",
+ secondary: "#ffb74d",
+ surface: "#1a120e",
+ surfaceText: "#f0dfd8",
+ surfaceVariant: "#52443d",
+ surfaceVariantText: "#d7c2b9",
+ surfaceTint: "#ffb74d",
+ background: "#1a120e",
+ backgroundText: "#f0dfd8",
+ outline: "#a08d85",
+ surfaceContainer: "#271e1a",
+ surfaceContainerHigh: "#322824",
+ surfaceContainerHighest: "#3d332e",
+ },
+ red: {
+ name: "Red",
+ primary: "#f44336",
+ primaryText: "#000000",
+ primaryContainer: "#4a0e0e",
+ secondary: "#f28b82",
+ surface: "#1a1110",
+ surfaceText: "#f1dedc",
+ surfaceVariant: "#534341",
+ surfaceVariantText: "#d8c2be",
+ surfaceTint: "#f28b82",
+ background: "#1a1110",
+ backgroundText: "#f1dedc",
+ outline: "#a08c89",
+ surfaceContainer: "#271d1c",
+ surfaceContainerHigh: "#322826",
+ surfaceContainerHighest: "#3d3231",
+ },
+ cyan: {
+ name: "Cyan",
+ primary: "#00bcd4",
+ primaryText: "#000000",
+ primaryContainer: "#004d5c",
+ secondary: "#4dd0e1",
+ surface: "#0e1416",
+ surfaceText: "#dee3e5",
+ surfaceVariant: "#3f484a",
+ surfaceVariantText: "#bfc8ca",
+ surfaceTint: "#4dd0e1",
+ background: "#0e1416",
+ backgroundText: "#dee3e5",
+ outline: "#899295",
+ surfaceContainer: "#1b2122",
+ surfaceContainerHigh: "#252b2c",
+ surfaceContainerHighest: "#303637",
+ },
+ pink: {
+ name: "Pink",
+ primary: "#e91e63",
+ primaryText: "#000000",
+ primaryContainer: "#4a0e2f",
+ secondary: "#f8bbd9",
+ surface: "#191112",
+ surfaceText: "#f0dee0",
+ surfaceVariant: "#524345",
+ surfaceVariantText: "#d6c2c3",
+ surfaceTint: "#f8bbd9",
+ background: "#191112",
+ backgroundText: "#f0dee0",
+ outline: "#9f8c8e",
+ surfaceContainer: "#261d1e",
+ surfaceContainerHigh: "#312829",
+ surfaceContainerHighest: "#3c3233",
+ },
+ amber: {
+ name: "Amber",
+ primary: "#ffc107",
+ primaryText: "#000000",
+ primaryContainer: "#4a3c00",
+ secondary: "#ffd54f",
+ surface: "#17130b",
+ surfaceText: "#ebe1d4",
+ surfaceVariant: "#4d4639",
+ surfaceVariantText: "#d0c5b4",
+ surfaceTint: "#ffd54f",
+ background: "#17130b",
+ backgroundText: "#ebe1d4",
+ outline: "#998f80",
+ surfaceContainer: "#231f17",
+ surfaceContainerHigh: "#2e2921",
+ surfaceContainerHighest: "#39342b",
+ },
+ coral: {
+ name: "Coral",
+ primary: "#ffb4ab",
+ primaryText: "#000000",
+ primaryContainer: "#8c1d18",
+ secondary: "#f9dedc",
+ surface: "#1a1110",
+ surfaceText: "#f1dedc",
+ surfaceVariant: "#534341",
+ surfaceVariantText: "#d8c2bf",
+ surfaceTint: "#ffb4ab",
+ background: "#1a1110",
+ backgroundText: "#f1dedc",
+ outline: "#a08c8a",
+ surfaceContainer: "#271d1c",
+ surfaceContainerHigh: "#322826",
+ surfaceContainerHighest: "#3d3231",
+ },
+ raveos: {
+ name: "RaveOS",
+ primary: "#3d7839",
+ primaryText: "#ffffff",
+ primaryContainer: "#1e3c1c",
+ secondary: "#5c8a58",
+ surface: "#0e140d",
+ surfaceText: "#e0e4db",
+ surfaceVariant: "#40493e",
+ surfaceVariantText: "#c0c9bc",
+ surfaceTint: "#5c8a58",
+ background: "#0e140d",
+ backgroundText: "#e0e4db",
+ outline: "#8a9386",
+ surfaceContainer: "#1a2119",
+ surfaceContainerHigh: "#242b22",
+ surfaceContainerHighest: "#2f362d",
+ },
+ monochrome: {
+ name: "Monochrome",
+ primary: "#ffffff",
+ primaryText: "#2b303c",
+ primaryContainer: "#424753",
+ secondary: "#c4c6d0",
+ surface: "#2a2a2a",
+ surfaceText: "#e4e2e3",
+ surfaceVariant: "#474648",
+ surfaceVariantText: "#c8c6c7",
+ surfaceTint: "#c2c6d6",
+ background: "#131315",
+ backgroundText: "#e4e2e3",
+ outline: "#929092",
+ surfaceContainer: "#353535",
+ surfaceContainerHigh: "#424242",
+ surfaceContainerHighest: "#505050",
+ error: "#ffb4ab",
+ warning: "#3f4759",
+ info: "#595e6c",
+ matugen_type: "scheme-monochrome",
+ },
+ },
+ LIGHT: {
+ blue: {
+ name: "Blue Light",
+ primary: "#1976d2",
+ primaryText: "#ffffff",
+ primaryContainer: "#e3f2fd",
+ secondary: "#42a5f5",
+ surface: "#f7f9ff",
+ surfaceText: "#181c20",
+ surfaceVariant: "#dee3eb",
+ surfaceVariantText: "#42474e",
+ surfaceTint: "#1976d2",
+ background: "#f7f9ff",
+ backgroundText: "#181c20",
+ outline: "#72777f",
+ surfaceContainer: "#eceef4",
+ surfaceContainerHigh: "#e6e8ee",
+ surfaceContainerHighest: "#e0e2e8",
+ },
+ purple: {
+ name: "Purple Light",
+ primary: "#6750A4",
+ primaryText: "#ffffff",
+ primaryContainer: "#EADDFF",
+ secondary: "#625B71",
+ surface: "#fef7ff",
+ surfaceText: "#1d1b20",
+ surfaceVariant: "#e7e0eb",
+ surfaceVariantText: "#49454e",
+ surfaceTint: "#6750A4",
+ background: "#fef7ff",
+ backgroundText: "#1d1b20",
+ outline: "#7a757f",
+ surfaceContainer: "#f2ecf4",
+ surfaceContainerHigh: "#ece6ee",
+ surfaceContainerHighest: "#e6e0e9",
+ },
+ green: {
+ name: "Green Light",
+ primary: "#2e7d32",
+ primaryText: "#ffffff",
+ primaryContainer: "#e8f5e8",
+ secondary: "#4caf50",
+ surface: "#f7fbf1",
+ surfaceText: "#191d17",
+ surfaceVariant: "#dee5d8",
+ surfaceVariantText: "#424940",
+ surfaceTint: "#2e7d32",
+ background: "#f7fbf1",
+ backgroundText: "#191d17",
+ outline: "#72796f",
+ surfaceContainer: "#ecefe6",
+ surfaceContainerHigh: "#e6e9e0",
+ surfaceContainerHighest: "#e0e4db",
+ },
+ orange: {
+ name: "Orange Light",
+ primary: "#e65100",
+ primaryText: "#ffffff",
+ primaryContainer: "#ffecb3",
+ secondary: "#ff9800",
+ surface: "#fff8f6",
+ surfaceText: "#221a16",
+ surfaceVariant: "#f4ded5",
+ surfaceVariantText: "#52443d",
+ surfaceTint: "#e65100",
+ background: "#fff8f6",
+ backgroundText: "#221a16",
+ outline: "#85736c",
+ surfaceContainer: "#fceae3",
+ surfaceContainerHigh: "#f6e5de",
+ surfaceContainerHighest: "#f0dfd8",
+ },
+ red: {
+ name: "Red Light",
+ primary: "#d32f2f",
+ primaryText: "#ffffff",
+ primaryContainer: "#ffebee",
+ secondary: "#f44336",
+ surface: "#fff8f7",
+ surfaceText: "#231918",
+ surfaceVariant: "#f5ddda",
+ surfaceVariantText: "#534341",
+ surfaceTint: "#d32f2f",
+ background: "#fff8f7",
+ backgroundText: "#231918",
+ outline: "#857370",
+ surfaceContainer: "#fceae7",
+ surfaceContainerHigh: "#f7e4e1",
+ surfaceContainerHighest: "#f1dedc",
+ },
+ cyan: {
+ name: "Cyan Light",
+ primary: "#0097a7",
+ primaryText: "#ffffff",
+ primaryContainer: "#e0f2f1",
+ secondary: "#00bcd4",
+ surface: "#f5fafc",
+ surfaceText: "#171d1e",
+ surfaceVariant: "#dbe4e6",
+ surfaceVariantText: "#3f484a",
+ surfaceTint: "#0097a7",
+ background: "#f5fafc",
+ backgroundText: "#171d1e",
+ outline: "#6f797b",
+ surfaceContainer: "#e9eff0",
+ surfaceContainerHigh: "#e3e9eb",
+ surfaceContainerHighest: "#dee3e5",
+ },
+ pink: {
+ name: "Pink Light",
+ primary: "#c2185b",
+ primaryText: "#ffffff",
+ primaryContainer: "#fce4ec",
+ secondary: "#e91e63",
+ surface: "#fff8f7",
+ surfaceText: "#22191a",
+ surfaceVariant: "#f3dddf",
+ surfaceVariantText: "#524345",
+ surfaceTint: "#c2185b",
+ background: "#fff8f7",
+ backgroundText: "#22191a",
+ outline: "#847375",
+ surfaceContainer: "#fbeaeb",
+ surfaceContainerHigh: "#f5e4e5",
+ surfaceContainerHighest: "#f0dee0",
+ },
+ amber: {
+ name: "Amber Light",
+ primary: "#ff8f00",
+ primaryText: "#000000",
+ primaryContainer: "#fff8e1",
+ secondary: "#ffc107",
+ surface: "#fff8f2",
+ surfaceText: "#1f1b13",
+ surfaceVariant: "#ede1cf",
+ surfaceVariantText: "#4d4639",
+ surfaceTint: "#ff8f00",
+ background: "#fff8f2",
+ backgroundText: "#1f1b13",
+ outline: "#7f7667",
+ surfaceContainer: "#f6ecdf",
+ surfaceContainerHigh: "#f1e7d9",
+ surfaceContainerHighest: "#ebe1d4",
+ },
+ coral: {
+ name: "Coral Light",
+ primary: "#8c1d18",
+ primaryText: "#ffffff",
+ primaryContainer: "#ffdad6",
+ secondary: "#ff5449",
+ surface: "#fff8f7",
+ surfaceText: "#231918",
+ surfaceVariant: "#f5ddda",
+ surfaceVariantText: "#534341",
+ surfaceTint: "#8c1d18",
+ background: "#fff8f7",
+ backgroundText: "#231918",
+ outline: "#857371",
+ surfaceContainer: "#fceae7",
+ surfaceContainerHigh: "#f6e4e2",
+ surfaceContainerHighest: "#f1dedc",
+ },
+ raveos: {
+ name: "RaveOS Light",
+ primary: "#3d7839",
+ primaryText: "#ffffff",
+ primaryContainer: "#c1e9bb",
+ secondary: "#3d7839",
+ surface: "#f7fbf1",
+ surfaceText: "#191d17",
+ surfaceVariant: "#dee5d8",
+ surfaceVariantText: "#424940",
+ surfaceTint: "#3d7839",
+ background: "#f7fbf1",
+ backgroundText: "#191d17",
+ outline: "#72796f",
+ surfaceContainer: "#ecefe6",
+ surfaceContainerHigh: "#e6e9e0",
+ surfaceContainerHighest: "#e0e4db",
+ },
+ monochrome: {
+ name: "Monochrome Light",
+ primary: "#2b303c",
+ primaryText: "#ffffff",
+ primaryContainer: "#d6d7dc",
+ secondary: "#4a4d56",
+ surface: "#f5f5f6",
+ surfaceText: "#2a2a2a",
+ surfaceVariant: "#e0e0e2",
+ surfaceVariantText: "#424242",
+ surfaceTint: "#5a5f6e",
+ background: "#ffffff",
+ backgroundText: "#1a1a1a",
+ outline: "#757577",
+ surfaceContainer: "#e8e8ea",
+ surfaceContainerHigh: "#dcdcde",
+ surfaceContainerHighest: "#d0d0d2",
+ error: "#ba1a1a",
+ warning: "#f9e79f",
+ info: "#5d6475",
+ matugen_type: "scheme-monochrome",
+ },
+ },
+};
+
+const ThemeCategories = {
+ GENERIC: {
+ name: "Generic",
+ variants: [
+ "raveos",
+ "blue",
+ "purple",
+ "green",
+ "orange",
+ "red",
+ "cyan",
+ "pink",
+ "amber",
+ "coral",
+ "monochrome",
+ ],
+ },
+};
+
+const ThemeNames = {
+ RAVEOS: "raveos",
+ BLUE: "blue",
+ PURPLE: "purple",
+ GREEN: "green",
+ ORANGE: "orange",
+ RED: "red",
+ CYAN: "cyan",
+ PINK: "pink",
+ AMBER: "amber",
+ CORAL: "coral",
+ MONOCHROME: "monochrome",
+ DYNAMIC: "dynamic",
+};
+
+function isStockTheme(themeName) {
+ return Object.keys(StockThemes.DARK).includes(themeName);
+}
+
+function getAvailableThemes(isLight = false) {
+ return isLight ? StockThemes.LIGHT : StockThemes.DARK;
+}
+
+function getThemeByName(themeName, isLight = false) {
+ const themes = getAvailableThemes(isLight);
+ return themes[themeName] || themes.blue;
+}
+
+function getAllThemeNames() {
+ return Object.keys(StockThemes.DARK);
+}
+
+function getThemeCategories() {
+ return ThemeCategories;
+}
diff --git a/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Common/Theme.qml b/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Common/Theme.qml
new file mode 100644
index 0000000..a50b688
--- /dev/null
+++ b/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Common/Theme.qml
@@ -0,0 +1,2410 @@
+pragma Singleton
+pragma ComponentBehavior: Bound
+
+import QtCore
+import QtQuick
+import Quickshell
+import Quickshell.Io
+import qs.Common
+import qs.Services
+import qs.Modules.Greetd
+import "StockThemes.js" as StockThemes
+
+Singleton {
+ id: root
+
+ readonly property string stateDir: Paths.strip(StandardPaths.writableLocation(StandardPaths.GenericCacheLocation).toString()) + "/DankMaterialShell"
+ readonly property bool envDisableMatugen: Quickshell.env("DMS_DISABLE_MATUGEN") === "1" || Quickshell.env("DMS_DISABLE_MATUGEN") === "true"
+ readonly property string defaultFontFamily: "Inter Variable"
+ readonly property string defaultMonoFontFamily: "Fira Code"
+
+ readonly property real popupDistance: {
+ if (typeof SettingsData === "undefined")
+ return 4;
+ const defaultBar = SettingsData.barConfigs[0] || SettingsData.getBarConfig("default");
+ if (!defaultBar)
+ return 4;
+ const useAuto = defaultBar.popupGapsAuto ?? true;
+ const manualValue = defaultBar.popupGapsManual ?? 4;
+ const spacing = defaultBar.spacing ?? 4;
+ return useAuto ? Math.max(4, spacing) : manualValue;
+ }
+
+ property string currentTheme: "raveos"
+ property string currentThemeCategory: "generic"
+ property bool isLightMode: typeof SessionData !== "undefined" ? SessionData.isLightMode : false
+ property bool colorsFileLoadFailed: false
+
+ readonly property string dynamic: "dynamic"
+ readonly property string custom: "custom"
+
+ readonly property string homeDir: Paths.strip(StandardPaths.writableLocation(StandardPaths.HomeLocation))
+ readonly property string configDir: Paths.strip(StandardPaths.writableLocation(StandardPaths.ConfigLocation))
+ readonly property string shellDir: Paths.strip(Qt.resolvedUrl(".").toString()).replace("/Common/", "")
+ readonly property string wallpaperPath: {
+ if (typeof SessionData === "undefined")
+ return "";
+
+ var monitors = SessionData.monitorWallpapers;
+ if (SessionData.perMonitorWallpaper) {
+ var screens = Quickshell.screens;
+ if (screens.length > 0) {
+ var s = screens[0];
+ return monitors[s.name] || (s.model ? monitors[s.model] : "") || SessionData.wallpaperPath;
+ }
+ }
+
+ return SessionData.wallpaperPath;
+ }
+ readonly property string rawWallpaperPath: {
+ if (typeof SessionData === "undefined")
+ return "";
+
+ var monitors = SessionData.monitorWallpapers;
+ if (SessionData.perMonitorWallpaper) {
+ var screens = Quickshell.screens;
+ if (screens.length > 0) {
+ var targetMonitor = (typeof SettingsData !== "undefined" && SettingsData.matugenTargetMonitor && SettingsData.matugenTargetMonitor !== "") ? SettingsData.matugenTargetMonitor : screens[0].name;
+
+ var targetMonitorExists = false;
+ for (var i = 0; i < screens.length; i++) {
+ if (screens[i].name === targetMonitor) {
+ targetMonitorExists = true;
+ break;
+ }
+ }
+
+ if (!targetMonitorExists)
+ targetMonitor = screens[0].name;
+
+ var s = null;
+ for (var j = 0; j < screens.length; j++) {
+ if (screens[j].name === targetMonitor) {
+ s = screens[j];
+ break;
+ }
+ }
+
+ if (s)
+ return monitors[s.name] || (s.model ? monitors[s.model] : "") || SessionData.wallpaperPath;
+ return monitors[targetMonitor] || SessionData.wallpaperPath;
+ }
+ }
+
+ return SessionData.wallpaperPath;
+ }
+
+ property bool matugenAvailable: false
+ property bool gtkThemingEnabled: typeof SettingsData !== "undefined" ? SettingsData.gtkAvailable : false
+ property bool qtThemingEnabled: typeof SettingsData !== "undefined" ? (SettingsData.qt5ctAvailable || SettingsData.qt6ctAvailable) : false
+ property var workerRunning: false
+ property var pendingThemeRequest: null
+
+ signal matugenCompleted(string mode, string result)
+ property var matugenColors: ({})
+ property var _pendingGenerateParams: null
+
+ property bool themeModeAutomationActive: false
+ property bool dmsServiceWasDisconnected: true
+
+ readonly property var dank16: {
+ const raw = matugenColors?.dank16;
+ if (!raw)
+ return null;
+
+ const dark = {};
+ const light = {};
+ const def = {};
+
+ for (let i = 0; i < 16; i++) {
+ const key = "color" + i;
+ const c = raw[key];
+ if (!c)
+ continue;
+ dark[key] = c.dark;
+ light[key] = c.light;
+ def[key] = c.default;
+ }
+
+ return {
+ dark,
+ light,
+ "default": def
+ };
+ }
+ property var customThemeData: null
+ property var customThemeRawData: null
+ readonly property var currentThemeVariants: customThemeRawData?.variants || null
+ readonly property string currentThemeId: customThemeRawData?.id || ""
+
+ Component.onCompleted: {
+ Quickshell.execDetached(["mkdir", "-p", stateDir]);
+ Proc.runCommand("matugenCheck", ["which", "matugen"], (output, code) => {
+ matugenAvailable = (code === 0) && !envDisableMatugen;
+ const isGreeterMode = (typeof SessionData !== "undefined" && SessionData.isGreeterMode);
+
+ if (!matugenAvailable || isGreeterMode) {
+ return;
+ }
+
+ if (colorsFileLoadFailed && currentTheme === dynamic && rawWallpaperPath) {
+ console.info("Theme: Matugen now available, regenerating colors for dynamic theme");
+ const isLight = (typeof SessionData !== "undefined" && SessionData.isLightMode);
+ const iconTheme = (typeof SettingsData !== "undefined" && SettingsData.iconTheme) ? SettingsData.iconTheme : "System Default";
+ const selectedMatugenType = (typeof SettingsData !== "undefined" && SettingsData.matugenScheme) ? SettingsData.matugenScheme : "scheme-tonal-spot";
+ if (rawWallpaperPath.startsWith("#")) {
+ setDesiredTheme("hex", rawWallpaperPath, isLight, iconTheme, selectedMatugenType);
+ } else {
+ setDesiredTheme("image", rawWallpaperPath, isLight, iconTheme, selectedMatugenType);
+ }
+ return;
+ }
+
+ const isLight = (typeof SessionData !== "undefined" && SessionData.isLightMode);
+ const iconTheme = (typeof SettingsData !== "undefined" && SettingsData.iconTheme) ? SettingsData.iconTheme : "System Default";
+
+ if (currentTheme === dynamic) {
+ if (rawWallpaperPath) {
+ const selectedMatugenType = (typeof SettingsData !== "undefined" && SettingsData.matugenScheme) ? SettingsData.matugenScheme : "scheme-tonal-spot";
+ if (rawWallpaperPath.startsWith("#")) {
+ setDesiredTheme("hex", rawWallpaperPath, isLight, iconTheme, selectedMatugenType);
+ } else {
+ setDesiredTheme("image", rawWallpaperPath, isLight, iconTheme, selectedMatugenType);
+ }
+ }
+ } else if (currentTheme !== "custom") {
+ const darkTheme = StockThemes.getThemeByName(currentTheme, false);
+ const lightTheme = StockThemes.getThemeByName(currentTheme, true);
+ if (darkTheme && darkTheme.primary) {
+ const stockColors = buildMatugenColorsFromTheme(darkTheme, lightTheme);
+ const themeData = isLight ? lightTheme : darkTheme;
+ setDesiredTheme("hex", themeData.primary, isLight, iconTheme, themeData.matugen_type, stockColors);
+ }
+ }
+ }, 0);
+ if (typeof SessionData !== "undefined") {
+ SessionData.isLightModeChanged.connect(root.onLightModeChanged);
+ }
+
+ if (typeof SettingsData !== "undefined" && SettingsData.currentThemeName) {
+ switchTheme(SettingsData.currentThemeName, false, false);
+ 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
+ enabled: typeof DMSService !== "undefined" && typeof SessionData !== "undefined"
+
+ function onLoginctlEvent(event) {
+ if (!SessionData.themeModeAutoEnabled)
+ return;
+ if (event.event === "unlock" || event.event === "resume") {
+ if (!themeAutoBackendAvailable()) {
+ root.evaluateThemeMode();
+ return;
+ }
+ DMSService.sendRequest("theme.auto.trigger", {});
+ }
+ }
+
+ 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) {
+ console.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) {
+ console.warn("Theme automation: Failed to set location", locationResponse.error);
+ }
+ });
+ }
+ });
+ } else {
+ console.warn("Theme automation: No location configured");
+ }
+ }
+ }
+ }
+
+ function applyGreeterTheme(themeName) {
+ switchTheme(themeName, false, false);
+ if (themeName === dynamic && dynamicColorsFileView.path) {
+ dynamicColorsFileView.reload();
+ }
+ }
+
+ function getMatugenColor(path, fallback) {
+ const colorMode = (typeof SessionData !== "undefined" && SessionData.isLightMode) ? "light" : "dark";
+ let cur = matugenColors && matugenColors.colors && matugenColors.colors[colorMode];
+ for (const part of path.split(".")) {
+ if (!cur || typeof cur !== "object" || !(part in cur))
+ return fallback;
+ cur = cur[part];
+ }
+ return cur || fallback;
+ }
+
+ readonly property var currentThemeData: {
+ if (currentTheme === "custom") {
+ return customThemeData || StockThemes.getThemeByName("purple", isLightMode);
+ } else if (currentTheme === dynamic) {
+ return {
+ "primary": getMatugenColor("primary", "#42a5f5"),
+ "primaryText": getMatugenColor("on_primary", "#ffffff"),
+ "primaryContainer": getMatugenColor("primary_container", "#1976d2"),
+ "secondary": getMatugenColor("secondary", "#8ab4f8"),
+ "surface": getMatugenColor("surface", "#1a1c1e"),
+ "surfaceText": getMatugenColor("on_background", "#e3e8ef"),
+ "surfaceVariant": getMatugenColor("surface_variant", "#44464f"),
+ "surfaceVariantText": getMatugenColor("on_surface_variant", "#c4c7c5"),
+ "surfaceTint": getMatugenColor("surface_tint", "#8ab4f8"),
+ "background": getMatugenColor("background", "#1a1c1e"),
+ "backgroundText": getMatugenColor("on_background", "#e3e8ef"),
+ "outline": getMatugenColor("outline", "#8e918f"),
+ "surfaceContainer": getMatugenColor("surface_container", "#1e2023"),
+ "surfaceContainerHigh": getMatugenColor("surface_container_high", "#292b2f"),
+ "surfaceContainerHighest": getMatugenColor("surface_container_highest", "#343740"),
+ "error": "#F2B8B5",
+ "warning": "#FF9800",
+ "info": "#2196F3",
+ "success": "#4CAF50"
+ };
+ } else {
+ return StockThemes.getThemeByName(currentTheme, isLightMode);
+ }
+ }
+
+ readonly property var availableMatugenSchemes: [({
+ "value": "scheme-tonal-spot",
+ "label": I18n.tr("Tonal Spot", "matugen color scheme option"),
+ "description": I18n.tr("Balanced palette with focused accents (default).")
+ }), ({
+ "value": "scheme-vibrant",
+ "label": I18n.tr("Vibrant", "matugen color scheme option"),
+ "description": I18n.tr("Lively palette with saturated accents.")
+ }), ({
+ "value": "scheme-content",
+ "label": I18n.tr("Content", "matugen color scheme option"),
+ "description": I18n.tr("Derives colors that closely match the underlying image.")
+ }), ({
+ "value": "scheme-expressive",
+ "label": I18n.tr("Expressive", "matugen color scheme option"),
+ "description": I18n.tr("Vibrant palette with playful saturation.")
+ }), ({
+ "value": "scheme-fidelity",
+ "label": I18n.tr("Fidelity", "matugen color scheme option"),
+ "description": I18n.tr("High-fidelity palette that preserves source hues.")
+ }), ({
+ "value": "scheme-fruit-salad",
+ "label": I18n.tr("Fruit Salad", "matugen color scheme option"),
+ "description": I18n.tr("Colorful mix of bright contrasting accents.")
+ }), ({
+ "value": "scheme-monochrome",
+ "label": I18n.tr("Monochrome", "matugen color scheme option"),
+ "description": I18n.tr("Minimal palette built around a single hue.")
+ }), ({
+ "value": "scheme-neutral",
+ "label": I18n.tr("Neutral", "matugen color scheme option"),
+ "description": I18n.tr("Muted palette with subdued, calming tones.")
+ }), ({
+ "value": "scheme-rainbow",
+ "label": I18n.tr("Rainbow", "matugen color scheme option"),
+ "description": I18n.tr("Diverse palette spanning the full spectrum.")
+ })]
+
+ function getMatugenScheme(value) {
+ const schemes = availableMatugenSchemes;
+ for (var i = 0; i < schemes.length; i++) {
+ if (schemes[i].value === value)
+ return schemes[i];
+ }
+ return schemes[0];
+ }
+
+ property color primary: currentThemeData.primary
+ property color primaryText: currentThemeData.primaryText
+ property color primaryContainer: currentThemeData.primaryContainer
+ property color secondary: currentThemeData.secondary
+ property color surface: currentThemeData.surface
+ property color surfaceText: currentThemeData.surfaceText
+ property color surfaceVariant: currentThemeData.surfaceVariant
+ property color surfaceVariantText: currentThemeData.surfaceVariantText
+ property color surfaceTint: currentThemeData.surfaceTint
+ property color background: currentThemeData.background
+ property color backgroundText: currentThemeData.backgroundText
+ property color outline: currentThemeData.outline
+ property color outlineVariant: currentThemeData.outlineVariant || Qt.rgba(outline.r, outline.g, outline.b, 0.6)
+ property color surfaceContainer: currentThemeData.surfaceContainer
+ property color surfaceContainerHigh: currentThemeData.surfaceContainerHigh
+ property color surfaceContainerHighest: currentThemeData.surfaceContainerHighest || surfaceContainerHigh
+
+ property color onSurface: surfaceText
+ property color onSurfaceVariant: surfaceVariantText
+ property color onPrimary: primaryText
+ property color onSurface_12: Qt.rgba(onSurface.r, onSurface.g, onSurface.b, 0.12)
+ property color onSurface_38: Qt.rgba(onSurface.r, onSurface.g, onSurface.b, 0.38)
+ property color onSurfaceVariant_30: Qt.rgba(onSurfaceVariant.r, onSurfaceVariant.g, onSurfaceVariant.b, 0.30)
+
+ property color error: currentThemeData.error || "#F2B8B5"
+ property color warning: currentThemeData.warning || "#FF9800"
+ property color info: currentThemeData.info || "#2196F3"
+ property color tempWarning: "#ff9933"
+ property color tempDanger: "#ff5555"
+ property color success: currentThemeData.success || "#4CAF50"
+
+ property color primaryHover: Qt.rgba(primary.r, primary.g, primary.b, 0.12)
+ property color primaryHoverLight: Qt.rgba(primary.r, primary.g, primary.b, 0.08)
+ property color primaryPressed: Qt.rgba(primary.r, primary.g, primary.b, 0.16)
+ property color primarySelected: Qt.rgba(primary.r, primary.g, primary.b, 0.3)
+ property color primaryBackground: Qt.rgba(primary.r, primary.g, primary.b, 0.04)
+
+ property color secondaryHover: Qt.rgba(secondary.r, secondary.g, secondary.b, 0.08)
+
+ property color surfaceHover: Qt.rgba(surfaceVariant.r, surfaceVariant.g, surfaceVariant.b, 0.08)
+ property color surfacePressed: Qt.rgba(surfaceVariant.r, surfaceVariant.g, surfaceVariant.b, 0.12)
+ property color surfaceSelected: Qt.rgba(surfaceVariant.r, surfaceVariant.g, surfaceVariant.b, 0.15)
+ property color surfaceLight: Qt.rgba(surfaceVariant.r, surfaceVariant.g, surfaceVariant.b, 0.1)
+ property color surfaceVariantAlpha: Qt.rgba(surfaceVariant.r, surfaceVariant.g, surfaceVariant.b, 0.2)
+ property color surfaceTextHover: Qt.rgba(surfaceText.r, surfaceText.g, surfaceText.b, 0.08)
+ property color surfaceTextAlpha: Qt.rgba(surfaceText.r, surfaceText.g, surfaceText.b, 0.3)
+ property color surfaceTextLight: Qt.rgba(surfaceText.r, surfaceText.g, surfaceText.b, 0.06)
+ property color surfaceTextMedium: Qt.rgba(surfaceText.r, surfaceText.g, surfaceText.b, 0.7)
+
+ property color outlineButton: Qt.rgba(outline.r, outline.g, outline.b, 0.5)
+ property color outlineLight: Qt.rgba(outline.r, outline.g, outline.b, 0.05)
+ property color outlineMedium: Qt.rgba(outline.r, outline.g, outline.b, 0.08)
+ property color outlineStrong: Qt.rgba(outline.r, outline.g, outline.b, 0.12)
+
+ property color errorHover: Qt.rgba(error.r, error.g, error.b, 0.12)
+ property color errorPressed: Qt.rgba(error.r, error.g, error.b, 0.16)
+
+ readonly property color ccTileActiveBg: {
+ switch (SettingsData.controlCenterTileColorMode) {
+ case "primaryContainer":
+ return primaryContainer;
+ case "secondary":
+ return secondary;
+ case "surfaceVariant":
+ return surfaceVariant;
+ default:
+ return primary;
+ }
+ }
+
+ readonly property color ccTileActiveText: {
+ switch (SettingsData.controlCenterTileColorMode) {
+ case "primaryContainer":
+ return primary;
+ case "secondary":
+ return surfaceText;
+ case "surfaceVariant":
+ return surfaceText;
+ default:
+ return primaryText;
+ }
+ }
+
+ readonly property color ccTileInactiveIcon: {
+ switch (SettingsData.controlCenterTileColorMode) {
+ case "primaryContainer":
+ return primary;
+ case "secondary":
+ return secondary;
+ case "surfaceVariant":
+ return surfaceText;
+ default:
+ return primary;
+ }
+ }
+
+ readonly property color ccTileRing: {
+ switch (SettingsData.controlCenterTileColorMode) {
+ case "primaryContainer":
+ return Qt.rgba(primary.r, primary.g, primary.b, 0.22);
+ case "secondary":
+ return Qt.rgba(surfaceText.r, surfaceText.g, surfaceText.b, 0.22);
+ case "surfaceVariant":
+ return Qt.rgba(surfaceText.r, surfaceText.g, surfaceText.b, 0.22);
+ default:
+ return Qt.rgba(primaryText.r, primaryText.g, primaryText.b, 0.22);
+ }
+ }
+
+ readonly property color buttonBg: {
+ switch (SettingsData.buttonColorMode) {
+ case "primaryContainer":
+ return primaryContainer;
+ case "secondary":
+ return secondary;
+ case "surfaceVariant":
+ return surfaceVariant;
+ default:
+ return primary;
+ }
+ }
+
+ readonly property color buttonText: {
+ switch (SettingsData.buttonColorMode) {
+ case "primaryContainer":
+ return primary;
+ case "secondary":
+ return surfaceText;
+ case "surfaceVariant":
+ return surfaceText;
+ default:
+ return primaryText;
+ }
+ }
+
+ readonly property color buttonHover: {
+ switch (SettingsData.buttonColorMode) {
+ case "primaryContainer":
+ return Qt.rgba(primary.r, primary.g, primary.b, 0.12);
+ case "secondary":
+ return Qt.rgba(surfaceText.r, surfaceText.g, surfaceText.b, 0.12);
+ case "surfaceVariant":
+ return Qt.rgba(surfaceText.r, surfaceText.g, surfaceText.b, 0.12);
+ default:
+ return primaryHover;
+ }
+ }
+
+ readonly property color buttonPressed: {
+ switch (SettingsData.buttonColorMode) {
+ case "primaryContainer":
+ return Qt.rgba(primary.r, primary.g, primary.b, 0.16);
+ case "secondary":
+ return Qt.rgba(surfaceText.r, surfaceText.g, surfaceText.b, 0.16);
+ case "surfaceVariant":
+ return Qt.rgba(surfaceText.r, surfaceText.g, surfaceText.b, 0.16);
+ default:
+ return primaryPressed;
+ }
+ }
+
+ property color shadowMedium: Qt.rgba(0, 0, 0, 0.08)
+ property color shadowStrong: Qt.rgba(0, 0, 0, 0.3)
+
+ readonly property bool elevationEnabled: typeof SettingsData !== "undefined" && (SettingsData.m3ElevationEnabled ?? true)
+ readonly property real elevationBlurMax: typeof SettingsData !== "undefined" && SettingsData.m3ElevationIntensity !== undefined ? Math.min(128, Math.max(32, SettingsData.m3ElevationIntensity * 2)) : 64
+
+ readonly property real _elevMult: typeof SettingsData !== "undefined" && SettingsData.m3ElevationIntensity !== undefined ? SettingsData.m3ElevationIntensity / 12 : 1
+ readonly property real _opMult: typeof SettingsData !== "undefined" && SettingsData.m3ElevationOpacity !== undefined ? SettingsData.m3ElevationOpacity / 60 : 1
+ function normalizeElevationDirection(direction) {
+ switch (direction) {
+ case "top":
+ case "topLeft":
+ case "topRight":
+ case "bottom":
+ case "bottomLeft":
+ case "bottomRight":
+ case "left":
+ case "right":
+ case "autoBar":
+ return direction;
+ default:
+ return "top";
+ }
+ }
+
+ readonly property string elevationLightDirection: {
+ if (typeof SettingsData === "undefined" || !SettingsData.m3ElevationLightDirection)
+ return "top";
+ switch (SettingsData.m3ElevationLightDirection) {
+ case "autoBar":
+ case "top":
+ case "topLeft":
+ case "topRight":
+ case "bottom":
+ return SettingsData.m3ElevationLightDirection;
+ default:
+ return "top";
+ }
+ }
+ readonly property real _elevDiagRatio: 0.55
+ readonly property string _globalElevationDirForTokens: {
+ const normalized = normalizeElevationDirection(elevationLightDirection);
+ return normalized === "autoBar" ? "top" : normalized;
+ }
+ readonly property real _elevDirX: {
+ switch (_globalElevationDirForTokens) {
+ case "topLeft":
+ case "bottomLeft":
+ case "left":
+ return 1;
+ case "topRight":
+ case "bottomRight":
+ case "right":
+ return -1;
+ default:
+ return 0;
+ }
+ }
+ readonly property real _elevDirY: {
+ switch (_globalElevationDirForTokens) {
+ case "bottom":
+ case "bottomLeft":
+ case "bottomRight":
+ return -1;
+ case "left":
+ case "right":
+ return 0;
+ default:
+ return 1;
+ }
+ }
+ readonly property real _elevDirXScale: (_globalElevationDirForTokens === "left" || _globalElevationDirForTokens === "right") ? 1 : _elevDiagRatio
+
+ readonly property var elevationLevel1: ({
+ blurPx: 4 * _elevMult,
+ offsetX: 1 * _elevMult * _elevDirXScale * _elevDirX,
+ offsetY: 1 * _elevMult * _elevDirY,
+ spreadPx: 0,
+ alpha: 0.2 * _opMult
+ })
+ readonly property var elevationLevel2: ({
+ blurPx: 8 * _elevMult,
+ offsetX: 4 * _elevMult * _elevDirXScale * _elevDirX,
+ offsetY: 4 * _elevMult * _elevDirY,
+ spreadPx: 0,
+ alpha: 0.25 * _opMult
+ })
+ readonly property var elevationLevel3: ({
+ blurPx: 12 * _elevMult,
+ offsetX: 6 * _elevMult * _elevDirXScale * _elevDirX,
+ offsetY: 6 * _elevMult * _elevDirY,
+ spreadPx: 0,
+ alpha: 0.3 * _opMult
+ })
+ readonly property var elevationLevel4: ({
+ blurPx: 16 * _elevMult,
+ offsetX: 8 * _elevMult * _elevDirXScale * _elevDirX,
+ offsetY: 8 * _elevMult * _elevDirY,
+ spreadPx: 0,
+ alpha: 0.3 * _opMult
+ })
+ readonly property var elevationLevel5: ({
+ blurPx: 20 * _elevMult,
+ offsetX: 10 * _elevMult * _elevDirXScale * _elevDirX,
+ offsetY: 10 * _elevMult * _elevDirY,
+ spreadPx: 0,
+ alpha: 0.3 * _opMult
+ })
+
+ function elevationOffsetMagnitude(level, fallback, direction) {
+ if (!level) {
+ return fallback !== undefined ? Math.abs(fallback) : 0;
+ }
+ const yMag = Math.abs(level.offsetY !== undefined ? level.offsetY : 0);
+ if (yMag > 0)
+ return yMag;
+ const xMag = Math.abs(level.offsetX !== undefined ? level.offsetX : 0);
+ if (xMag > 0) {
+ if (direction === "left" || direction === "right")
+ return xMag;
+ return xMag / _elevDiagRatio;
+ }
+ return fallback !== undefined ? Math.abs(fallback) : 0;
+ }
+
+ function elevationOffsetXFor(level, direction, fallback) {
+ const dir = normalizeElevationDirection(direction || elevationLightDirection);
+ const mag = elevationOffsetMagnitude(level, fallback, dir);
+ switch (dir) {
+ case "topLeft":
+ case "bottomLeft":
+ return mag * _elevDiagRatio;
+ case "topRight":
+ case "bottomRight":
+ return -mag * _elevDiagRatio;
+ case "left":
+ return mag;
+ case "right":
+ return -mag;
+ default:
+ return 0;
+ }
+ }
+
+ function elevationOffsetYFor(level, direction, fallback) {
+ const dir = normalizeElevationDirection(direction || elevationLightDirection);
+ const mag = elevationOffsetMagnitude(level, fallback, dir);
+ switch (dir) {
+ case "bottom":
+ case "bottomLeft":
+ case "bottomRight":
+ return -mag;
+ case "left":
+ case "right":
+ return 0;
+ default:
+ return mag;
+ }
+ }
+
+ function elevationOffsetX(level, fallback) {
+ return elevationOffsetXFor(level, elevationLightDirection, fallback);
+ }
+
+ function elevationOffsetY(level, fallback) {
+ return elevationOffsetYFor(level, elevationLightDirection, fallback);
+ }
+
+ function elevationRenderPadding(level, direction, fallbackOffset, extraPadding, minPadding) {
+ const dir = direction !== undefined ? direction : elevationLightDirection;
+ const blur = (level && level.blurPx !== undefined) ? Math.max(0, level.blurPx) : 0;
+ const spread = (level && level.spreadPx !== undefined) ? Math.max(0, level.spreadPx) : 0;
+ const fallback = fallbackOffset !== undefined ? fallbackOffset : 0;
+ const extra = extraPadding !== undefined ? extraPadding : 8;
+ const minPad = minPadding !== undefined ? minPadding : 16;
+ const offsetX = Math.abs(elevationOffsetXFor(level, dir, fallback));
+ const offsetY = Math.abs(elevationOffsetYFor(level, dir, fallback));
+ return Math.max(minPad, blur + spread + Math.max(offsetX, offsetY) + extra);
+ }
+
+ function elevationShadowColor(level) {
+ const alpha = (level && level.alpha !== undefined) ? level.alpha : 0.3;
+ let r = 0;
+ let g = 0;
+ let b = 0;
+
+ if (typeof SettingsData !== "undefined") {
+ const mode = SettingsData.m3ElevationColorMode || "default";
+ if (mode === "default") {
+ r = 0;
+ g = 0;
+ b = 0;
+ } else if (mode === "text") {
+ r = surfaceText.r;
+ g = surfaceText.g;
+ b = surfaceText.b;
+ } else if (mode === "primary") {
+ r = primary.r;
+ g = primary.g;
+ b = primary.b;
+ } else if (mode === "surfaceVariant") {
+ r = surfaceVariant.r;
+ g = surfaceVariant.g;
+ b = surfaceVariant.b;
+ } else if (mode === "custom" && SettingsData.m3ElevationCustomColor) {
+ const c = Qt.color(SettingsData.m3ElevationCustomColor);
+ r = c.r;
+ g = c.g;
+ b = c.b;
+ }
+ }
+ return Qt.rgba(r, g, b, alpha);
+ }
+ 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,
+ "short": 0,
+ "medium": 0,
+ "long": 0,
+ "extraLong": 0
+ },
+ {
+ "shorter": 50,
+ "short": 75,
+ "medium": 150,
+ "long": 250,
+ "extraLong": 500
+ },
+ {
+ "shorter": 100,
+ "short": 150,
+ "medium": 300,
+ "long": 500,
+ "extraLong": 1000
+ },
+ {
+ "shorter": 150,
+ "short": 225,
+ "medium": 450,
+ "long": 750,
+ "extraLong": 1500
+ },
+ {
+ "shorter": 200,
+ "short": 300,
+ "medium": 600,
+ "long": 1000,
+ "extraLong": 2000
+ }
+ ]
+
+ readonly property int currentAnimationSpeed: typeof SettingsData !== "undefined" ? SettingsData.animationSpeed : SettingsData.AnimationSpeed.Short
+ readonly property var currentDurations: animationDurations[currentAnimationSpeed] || animationDurations[SettingsData.AnimationSpeed.Short]
+
+ readonly property int shorterDuration: (typeof SettingsData !== "undefined" && SettingsData.animationSpeed === SettingsData.AnimationSpeed.Custom) ? SettingsData.customAnimationDuration : currentDurations.shorter
+ readonly property int shortDuration: (typeof SettingsData !== "undefined" && SettingsData.animationSpeed === SettingsData.AnimationSpeed.Custom) ? SettingsData.customAnimationDuration : currentDurations.short
+ readonly property int mediumDuration: (typeof SettingsData !== "undefined" && SettingsData.animationSpeed === SettingsData.AnimationSpeed.Custom) ? SettingsData.customAnimationDuration : currentDurations.medium
+ readonly property int longDuration: (typeof SettingsData !== "undefined" && SettingsData.animationSpeed === SettingsData.AnimationSpeed.Custom) ? SettingsData.customAnimationDuration : currentDurations.long
+ readonly property int extraLongDuration: (typeof SettingsData !== "undefined" && SettingsData.animationSpeed === SettingsData.AnimationSpeed.Custom) ? SettingsData.customAnimationDuration : currentDurations.extraLong
+ readonly property int standardEasing: Easing.OutCubic
+ readonly property int emphasizedEasing: Easing.OutQuart
+
+ readonly property var expressiveCurves: {
+ "emphasized": [0.05, 0, 2 / 15, 0.06, 1 / 6, 0.4, 5 / 24, 0.82, 0.25, 1, 1, 1],
+ "emphasizedAccel": [0.3, 0, 0.8, 0.15, 1, 1],
+ "emphasizedDecel": [0.05, 0.7, 0.1, 1, 1, 1],
+ "standard": [0.2, 0, 0, 1, 1, 1],
+ "standardAccel": [0.3, 0, 1, 1, 1, 1],
+ "standardDecel": [0, 0, 0, 1, 1, 1],
+ "expressiveFastSpatial": [0.42, 1.67, 0.21, 0.9, 1, 1],
+ "expressiveDefaultSpatial": [0.38, 1.21, 0.22, 1, 1, 1],
+ "expressiveEffects": [0.34, 0.8, 0.34, 1, 1, 1]
+ }
+
+ readonly property var animationPresetDurations: {
+ "none": 0,
+ "short": 250,
+ "medium": 500,
+ "long": 750
+ }
+
+ readonly property int currentAnimationBaseDuration: {
+ if (typeof SettingsData === "undefined")
+ return 500;
+
+ if (SettingsData.animationSpeed === SettingsData.AnimationSpeed.Custom) {
+ return SettingsData.customAnimationDuration;
+ }
+
+ const presetMap = [0, 250, 500, 750];
+ return presetMap[SettingsData.animationSpeed] !== undefined ? presetMap[SettingsData.animationSpeed] : 500;
+ }
+
+ readonly property var expressiveDurations: {
+ if (typeof SettingsData === "undefined") {
+ return {
+ "fast": 200,
+ "normal": 400,
+ "large": 600,
+ "extraLarge": 1000,
+ "expressiveFastSpatial": 350,
+ "expressiveDefaultSpatial": 500,
+ "expressiveEffects": 200
+ };
+ }
+
+ const baseDuration = currentAnimationBaseDuration;
+ return {
+ "fast": baseDuration * 0.4,
+ "normal": baseDuration * 0.8,
+ "large": baseDuration * 1.2,
+ "extraLarge": baseDuration * 2.0,
+ "expressiveFastSpatial": baseDuration * 0.7,
+ "expressiveDefaultSpatial": baseDuration,
+ "expressiveEffects": baseDuration * 0.4
+ };
+ }
+
+ readonly property int notificationAnimationBaseDuration: {
+ if (typeof SettingsData === "undefined")
+ return 200;
+ if (SettingsData.notificationAnimationSpeed === SettingsData.AnimationSpeed.None)
+ return 0;
+ if (SettingsData.notificationAnimationSpeed === SettingsData.AnimationSpeed.Custom)
+ return SettingsData.notificationCustomAnimationDuration;
+ const presetMap = [0, 200, 400, 600];
+ return presetMap[SettingsData.notificationAnimationSpeed] ?? 200;
+ }
+
+ readonly property int notificationEnterDuration: {
+ const base = notificationAnimationBaseDuration;
+ return base === 0 ? 0 : Math.round(base * 0.875);
+ }
+
+ readonly property int notificationExitDuration: {
+ const base = notificationAnimationBaseDuration;
+ return base === 0 ? 0 : Math.round(base * 0.75);
+ }
+
+ readonly property int notificationExpandDuration: {
+ const base = notificationAnimationBaseDuration;
+ return base === 0 ? 0 : Math.round(base * 1.0);
+ }
+
+ readonly property int notificationCollapseDuration: {
+ const base = notificationAnimationBaseDuration;
+ return base === 0 ? 0 : Math.round(base * 0.85);
+ }
+
+ readonly property real notificationIconSizeNormal: 56
+ readonly property real notificationIconSizeCompact: 48
+ readonly property real notificationExpandedIconSizeNormal: 48
+ readonly property real notificationExpandedIconSizeCompact: 40
+ readonly property real notificationActionMinWidth: 48
+ readonly property real notificationButtonCornerRadius: cornerRadius / 2
+ readonly property real notificationHoverRevealMargin: spacingXL
+ readonly property real notificationContentSpacing: spacingXS
+ readonly property real notificationCardPadding: spacingM
+ readonly property real notificationCardPaddingCompact: spacingS
+
+ readonly property real stateLayerHover: 0.08
+ readonly property real stateLayerFocus: 0.12
+ readonly property real stateLayerPressed: 0.12
+ readonly property real stateLayerDrag: 0.16
+
+ readonly property int popoutAnimationDuration: {
+ if (typeof SettingsData === "undefined")
+ return 150;
+ if (SettingsData.syncComponentAnimationSpeeds) {
+ return Math.min(currentAnimationBaseDuration, 1000);
+ }
+ const presetMap = [0, 150, 300, 500];
+ if (SettingsData.popoutAnimationSpeed === SettingsData.AnimationSpeed.Custom)
+ return SettingsData.popoutCustomAnimationDuration;
+ return presetMap[SettingsData.popoutAnimationSpeed] ?? 150;
+ }
+
+ readonly property int modalAnimationDuration: {
+ if (typeof SettingsData === "undefined")
+ return 150;
+ if (SettingsData.syncComponentAnimationSpeeds) {
+ return Math.min(currentAnimationBaseDuration, 1000);
+ }
+ const presetMap = [0, 150, 300, 500];
+ if (SettingsData.modalAnimationSpeed === SettingsData.AnimationSpeed.Custom)
+ return SettingsData.modalCustomAnimationDuration;
+ return presetMap[SettingsData.modalAnimationSpeed] ?? 150;
+ }
+
+ property real cornerRadius: {
+ if (typeof SessionData !== "undefined" && SessionData.isGreeterMode && typeof GreetdSettings !== "undefined") {
+ return GreetdSettings.cornerRadius;
+ }
+ return typeof SettingsData !== "undefined" ? SettingsData.cornerRadius : 12;
+ }
+
+ property string fontFamily: {
+ if (typeof SessionData !== "undefined" && SessionData.isGreeterMode && typeof GreetdSettings !== "undefined") {
+ return GreetdSettings.getEffectiveFontFamily();
+ }
+ return typeof SettingsData !== "undefined" ? SettingsData.fontFamily : "Inter Variable";
+ }
+
+ property string monoFontFamily: {
+ if (typeof SessionData !== "undefined" && SessionData.isGreeterMode && typeof GreetdSettings !== "undefined") {
+ return GreetdSettings.monoFontFamily;
+ }
+ return typeof SettingsData !== "undefined" ? SettingsData.monoFontFamily : "Fira Code";
+ }
+
+ property int fontWeight: {
+ if (typeof SessionData !== "undefined" && SessionData.isGreeterMode && typeof GreetdSettings !== "undefined") {
+ return GreetdSettings.fontWeight;
+ }
+ return typeof SettingsData !== "undefined" ? SettingsData.fontWeight : Font.Normal;
+ }
+
+ property real fontScale: {
+ if (typeof SessionData !== "undefined" && SessionData.isGreeterMode && typeof GreetdSettings !== "undefined") {
+ return GreetdSettings.fontScale;
+ }
+ return typeof SettingsData !== "undefined" ? SettingsData.fontScale : 1.0;
+ }
+
+ property real spacingXS: 4
+ property real spacingS: 8
+ property real spacingM: 12
+ property real spacingL: 16
+ property real spacingXL: 24
+ property real fontSizeSmall: Math.round(fontScale * 12)
+ property real fontSizeMedium: Math.round(fontScale * 14)
+ property real fontSizeLarge: Math.round(fontScale * 16)
+ property real fontSizeXLarge: Math.round(fontScale * 20)
+ property real barHeight: 48
+ property real iconSize: 24
+ property real iconSizeSmall: 16
+ property real iconSizeLarge: 32
+
+ property real panelTransparency: 0.85
+ property real popupTransparency: typeof SettingsData !== "undefined" && SettingsData.popupTransparency !== undefined ? SettingsData.popupTransparency : 1.0
+
+ function screenTransition() {
+ if (CompositorService.isNiri) {
+ NiriService.doScreenTransition();
+ }
+ }
+
+ function switchTheme(themeName, savePrefs = true, enableTransition = true) {
+ if (enableTransition) {
+ screenTransition();
+ themeTransitionTimer.themeName = themeName;
+ themeTransitionTimer.savePrefs = savePrefs;
+ themeTransitionTimer.restart();
+ return;
+ }
+
+ if (themeName === dynamic) {
+ currentTheme = dynamic;
+ if (currentThemeCategory !== "registry")
+ currentThemeCategory = dynamic;
+ } else if (themeName === custom) {
+ currentTheme = custom;
+ if (currentThemeCategory !== "registry")
+ currentThemeCategory = custom;
+ if (typeof SettingsData !== "undefined" && SettingsData.customThemeFile) {
+ loadCustomThemeFromFile(SettingsData.customThemeFile);
+ }
+ } else if (themeName === "" && currentThemeCategory === "registry") {
+ // Registry category selected but no theme chosen yet
+ } else {
+ currentTheme = themeName;
+ if (currentThemeCategory !== "registry") {
+ currentThemeCategory = "generic";
+ }
+ }
+ const isGreeterMode = (typeof SessionData !== "undefined" && SessionData.isGreeterMode);
+ if (savePrefs && typeof SettingsData !== "undefined" && !isGreeterMode) {
+ SettingsData.set("currentThemeCategory", currentThemeCategory);
+ SettingsData.set("currentThemeName", currentTheme);
+ }
+
+ if (!isGreeterMode) {
+ generateSystemThemesFromCurrentTheme();
+ }
+ }
+
+ function setLightMode(light, savePrefs = true, enableTransition = false) {
+ if (enableTransition) {
+ screenTransition();
+ lightModeTransitionTimer.lightMode = light;
+ lightModeTransitionTimer.savePrefs = savePrefs;
+ lightModeTransitionTimer.restart();
+ return;
+ }
+
+ const isGreeterMode = (typeof SessionData !== "undefined" && SessionData.isGreeterMode);
+ if (savePrefs && typeof SessionData !== "undefined" && !isGreeterMode) {
+ SessionData.setLightMode(light);
+ }
+
+ if (!isGreeterMode) {
+ if (!matugenAvailable) {
+ PortalService.setLightMode(light);
+ }
+ if (typeof SettingsData !== "undefined") {
+ SettingsData.updateCosmicThemeMode(light);
+ }
+ generateSystemThemesFromCurrentTheme();
+ }
+ }
+
+ function toggleLightMode(savePrefs = true) {
+ 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;
+ }
+ return StockThemes.getThemeByName(themeName, isLightMode);
+ }
+
+ function switchThemeCategory(category, defaultTheme) {
+ screenTransition();
+ themeCategoryTransitionTimer.category = category;
+ themeCategoryTransitionTimer.defaultTheme = defaultTheme;
+ themeCategoryTransitionTimer.restart();
+ }
+
+ function loadCustomTheme(themeData) {
+ customThemeRawData = themeData;
+ const colorMode = (typeof SessionData !== "undefined" && SessionData.isLightMode) ? "light" : "dark";
+
+ var baseColors = {};
+ if (themeData.dark || themeData.light) {
+ baseColors = themeData[colorMode] || themeData.dark || themeData.light || {};
+ } else {
+ baseColors = themeData;
+ }
+
+ if (themeData.variants) {
+ const themeId = themeData.id || "";
+
+ if (themeData.variants.type === "multi" && themeData.variants.flavors && themeData.variants.accents) {
+ const defaults = themeData.variants.defaults || {};
+ const modeDefaults = defaults[colorMode] || defaults.dark || {};
+ const isGreeterMode = typeof SessionData !== "undefined" && SessionData.isGreeterMode;
+ const stored = isGreeterMode ? (GreetdSettings.registryThemeVariants[themeId]?.[colorMode] || modeDefaults) : (typeof SettingsData !== "undefined" ? SettingsData.getRegistryThemeMultiVariant(themeId, modeDefaults, colorMode) : modeDefaults);
+ var flavorId = stored.flavor || modeDefaults.flavor || "";
+ const accentId = stored.accent || modeDefaults.accent || "";
+ var flavor = findVariant(themeData.variants.flavors, flavorId);
+ if (flavor) {
+ const hasCurrentModeColors = flavor[colorMode] && (flavor[colorMode].primary || flavor[colorMode].surface);
+ if (!hasCurrentModeColors) {
+ flavorId = modeDefaults.flavor || "";
+ flavor = findVariant(themeData.variants.flavors, flavorId);
+ }
+ }
+ const accent = findAccent(themeData.variants.accents, accentId);
+ if (flavor) {
+ const flavorColors = flavor[colorMode] || flavor.dark || flavor.light || {};
+ baseColors = mergeColors(baseColors, flavorColors);
+ }
+ if (accent && flavor) {
+ const accentColors = accent[flavor.id] || {};
+ baseColors = mergeColors(baseColors, accentColors);
+ }
+ customThemeData = baseColors;
+ generateSystemThemesFromCurrentTheme();
+ return;
+ }
+
+ if (themeData.variants.options && themeData.variants.options.length > 0) {
+ const isGreeterMode = typeof SessionData !== "undefined" && SessionData.isGreeterMode;
+ const selectedVariantId = isGreeterMode ? (typeof GreetdSettings.registryThemeVariants[themeId] === "string" ? GreetdSettings.registryThemeVariants[themeId] : themeData.variants.default) : (typeof SettingsData !== "undefined" ? SettingsData.getRegistryThemeVariant(themeId, themeData.variants.default) : themeData.variants.default);
+ const variant = findVariant(themeData.variants.options, selectedVariantId);
+ if (variant) {
+ const variantColors = variant[colorMode] || variant.dark || variant.light || {};
+ customThemeData = mergeColors(baseColors, variantColors);
+ generateSystemThemesFromCurrentTheme();
+ return;
+ }
+ }
+ }
+
+ customThemeData = baseColors;
+ generateSystemThemesFromCurrentTheme();
+ }
+
+ function findVariant(options, variantId) {
+ if (!variantId || !options)
+ return null;
+ for (var i = 0; i < options.length; i++) {
+ if (options[i].id === variantId)
+ return options[i];
+ }
+ return options[0] || null;
+ }
+
+ function findAccent(accents, accentId) {
+ if (!accentId || !accents)
+ return null;
+ for (var i = 0; i < accents.length; i++) {
+ if (accents[i].id === accentId)
+ return accents[i];
+ }
+ return accents[0] || null;
+ }
+
+ function mergeColors(base, overlay) {
+ var result = JSON.parse(JSON.stringify(base));
+ for (var key in overlay) {
+ if (overlay[key])
+ result[key] = overlay[key];
+ }
+ return result;
+ }
+
+ function loadCustomThemeFromFile(filePath) {
+ customThemeFileView.path = filePath;
+ }
+
+ function reloadCustomThemeVariant() {
+ if (currentTheme !== "custom" || !customThemeRawData)
+ return;
+ loadCustomTheme(customThemeRawData);
+ }
+
+ property alias availableThemeNames: root._availableThemeNames
+ readonly property var _availableThemeNames: StockThemes.getAllThemeNames()
+ property string currentThemeName: currentTheme
+
+ function panelBackground() {
+ return Qt.rgba(surfaceContainer.r, surfaceContainer.g, surfaceContainer.b, panelTransparency);
+ }
+
+ property real notepadTransparency: SettingsData.notepadTransparencyOverride >= 0 ? SettingsData.notepadTransparencyOverride : popupTransparency
+
+ property bool widgetBackgroundHasAlpha: {
+ const colorMode = typeof SettingsData !== "undefined" ? SettingsData.widgetBackgroundColor : "sch";
+ return colorMode === "sth";
+ }
+
+ property var widgetBaseBackgroundColor: {
+ const colorMode = typeof SettingsData !== "undefined" ? SettingsData.widgetBackgroundColor : "sch";
+ switch (colorMode) {
+ case "s":
+ return surface;
+ case "sc":
+ return surfaceContainer;
+ case "sch":
+ return surfaceContainerHigh;
+ case "sth":
+ default:
+ return surfaceTextHover;
+ }
+ }
+
+ property color widgetBaseHoverColor: {
+ const blended = blend(widgetBaseBackgroundColor, primary, 0.1);
+ return withAlpha(blended, Math.max(0.3, blended.a));
+ }
+
+ property color widgetIconColor: {
+ if (typeof SettingsData === "undefined") {
+ return surfaceText;
+ }
+
+ switch (SettingsData.widgetColorMode) {
+ case "colorful":
+ return surfaceText;
+ case "default":
+ default:
+ return surfaceText;
+ }
+ }
+
+ property color widgetTextColor: {
+ if (typeof SettingsData === "undefined") {
+ return surfaceText;
+ }
+
+ switch (SettingsData.widgetColorMode) {
+ case "colorful":
+ return primary;
+ case "default":
+ default:
+ return surfaceText;
+ }
+ }
+
+ 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;
+ const s = iconScale !== undefined ? iconScale : 1.0;
+
+ return Math.round((barThickness / 48) * (size + defaultOffset) * s);
+ }
+
+ function barTextSize(barThickness, fontScale, maximizeText) {
+ const scale = barThickness / 48;
+ const dankBarScale = fontScale !== undefined ? fontScale : 1.0;
+ const maxScale = (maximizeText ?? false) ? 1.5 : 1.0;
+ if (scale <= 0.75)
+ return Math.round(fontSizeSmall * 0.9 * dankBarScale * maxScale);
+ if (scale >= 1.25)
+ return Math.round(fontSizeMedium * dankBarScale * maxScale);
+ return Math.round(fontSizeSmall * dankBarScale * maxScale);
+ }
+
+ function getBatteryIcon(level, isCharging, batteryAvailable) {
+ if (!batteryAvailable)
+ return "battery_std";
+
+ if (isCharging) {
+ if (level >= 90)
+ return "battery_charging_full";
+ if (level >= 80)
+ return "battery_charging_90";
+ if (level >= 60)
+ return "battery_charging_80";
+ if (level >= 50)
+ return "battery_charging_60";
+ if (level >= 30)
+ return "battery_charging_50";
+ if (level >= 20)
+ return "battery_charging_30";
+ return "battery_charging_20";
+ } else {
+ if (level >= 95)
+ return "battery_full";
+ if (level >= 85)
+ return "battery_6_bar";
+ if (level >= 70)
+ return "battery_5_bar";
+ if (level >= 55)
+ return "battery_4_bar";
+ if (level >= 40)
+ return "battery_3_bar";
+ if (level >= 25)
+ return "battery_2_bar";
+ if (level >= 10)
+ return "battery_1_bar";
+ return "battery_alert";
+ }
+ }
+
+ function getPowerProfileIcon(profile) {
+ switch (profile) {
+ case 0:
+ return "battery_saver";
+ case 1:
+ return "battery_std";
+ case 2:
+ return "flash_on";
+ default:
+ return "settings";
+ }
+ }
+
+ function getPowerProfileLabel(profile) {
+ switch (profile) {
+ case 0:
+ return I18n.tr("Power Saver", "power profile option");
+ case 1:
+ return I18n.tr("Balanced", "power profile option");
+ case 2:
+ return I18n.tr("Performance", "power profile option");
+ default:
+ return I18n.tr("Unknown", "power profile option");
+ }
+ }
+
+ 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();
+ }
+ }
+
+ function setDesiredTheme(kind, value, isLight, iconTheme, matugenType, stockColors) {
+ if (!matugenAvailable) {
+ console.warn("Theme: matugen not available or disabled - cannot set system theme");
+ return;
+ }
+
+ if (workerRunning) {
+ console.info("Theme: Worker already running, queueing request");
+ pendingThemeRequest = {
+ kind,
+ value,
+ isLight,
+ iconTheme,
+ matugenType,
+ stockColors
+ };
+ return;
+ }
+
+ console.info("Theme: Setting desired theme -", kind, "mode:", isLight ? "light" : "dark", stockColors ? "(stock colors)" : "(dynamic)");
+
+ if (typeof NiriService !== "undefined" && CompositorService.isNiri) {
+ NiriService.suppressNextToast();
+ }
+
+ const desired = {
+ "kind": kind,
+ "value": value,
+ "mode": isLight ? "light" : "dark",
+ "iconTheme": iconTheme || "System Default",
+ "matugenType": matugenType || "scheme-tonal-spot",
+ "runUserTemplates": (typeof SettingsData !== "undefined") ? SettingsData.runUserMatugenTemplates : true
+ };
+
+ console.log("Theme: Starting matugen worker");
+ workerRunning = true;
+
+ const args = ["dms", "matugen", "queue", "--state-dir", stateDir, "--shell-dir", shellDir, "--config-dir", configDir, "--kind", desired.kind, "--value", desired.value, "--mode", desired.mode, "--icon-theme", desired.iconTheme, "--matugen-type", desired.matugenType,];
+
+ if (!desired.runUserTemplates) {
+ args.push("--run-user-templates=false");
+ }
+ if (stockColors) {
+ args.push("--stock-colors", JSON.stringify(stockColors));
+ }
+ if (typeof SettingsData !== "undefined" && SettingsData.syncModeWithPortal) {
+ args.push("--sync-mode-with-portal");
+ }
+ if (typeof SettingsData !== "undefined" && SettingsData.terminalsAlwaysDark) {
+ args.push("--terminals-always-dark");
+ }
+ if (typeof SettingsData !== "undefined" && SettingsData.matugenContrast !== 0) {
+ args.push("--contrast", SettingsData.matugenContrast.toString());
+ }
+
+ if (typeof SettingsData !== "undefined") {
+ const skipTemplates = [];
+ if (!SettingsData.runDmsMatugenTemplates) {
+ skipTemplates.push("gtk", "nvim", "niri", "qt5ct", "qt6ct", "firefox", "pywalfox", "zenbrowser", "vesktop", "equibop", "ghostty", "kitty", "foot", "alacritty", "wezterm", "dgop", "kcolorscheme", "vscode", "emacs", "zed");
+ } else {
+ if (!SettingsData.matugenTemplateGtk)
+ skipTemplates.push("gtk");
+ if (!SettingsData.matugenTemplateNiri)
+ skipTemplates.push("niri");
+ if (!SettingsData.matugenTemplateHyprland)
+ skipTemplates.push("hyprland");
+ if (!SettingsData.matugenTemplateMangowc)
+ skipTemplates.push("mangowc");
+ if (!SettingsData.matugenTemplateQt5ct)
+ skipTemplates.push("qt5ct");
+ if (!SettingsData.matugenTemplateQt6ct)
+ skipTemplates.push("qt6ct");
+ if (!SettingsData.matugenTemplateFirefox)
+ skipTemplates.push("firefox");
+ if (!SettingsData.matugenTemplatePywalfox)
+ skipTemplates.push("pywalfox");
+ if (!SettingsData.matugenTemplateZenBrowser)
+ skipTemplates.push("zenbrowser");
+ if (!SettingsData.matugenTemplateVesktop)
+ skipTemplates.push("vesktop");
+ if (!SettingsData.matugenTemplateEquibop)
+ skipTemplates.push("equibop");
+ if (!SettingsData.matugenTemplateGhostty)
+ skipTemplates.push("ghostty");
+ if (!SettingsData.matugenTemplateKitty)
+ skipTemplates.push("kitty");
+ if (!SettingsData.matugenTemplateFoot)
+ skipTemplates.push("foot");
+ if (!SettingsData.matugenTemplateNeovim)
+ skipTemplates.push("nvim");
+ if (!SettingsData.matugenTemplateAlacritty)
+ skipTemplates.push("alacritty");
+ if (!SettingsData.matugenTemplateWezterm)
+ skipTemplates.push("wezterm");
+ if (!SettingsData.matugenTemplateDgop)
+ skipTemplates.push("dgop");
+ if (!SettingsData.matugenTemplateKcolorscheme)
+ skipTemplates.push("kcolorscheme");
+ if (!SettingsData.matugenTemplateVscode)
+ skipTemplates.push("vscode");
+ if (!SettingsData.matugenTemplateEmacs)
+ skipTemplates.push("emacs");
+ if (!SettingsData.matugenTemplateZed)
+ skipTemplates.push("zed");
+ }
+ if (skipTemplates.length > 0) {
+ args.push("--skip-templates", skipTemplates.join(","));
+ }
+ }
+
+ systemThemeGenerator.command = args;
+ systemThemeGenerator.running = true;
+ }
+
+ function generateSystemThemesFromCurrentTheme() {
+ const isGreeterMode = (typeof SessionData !== "undefined" && SessionData.isGreeterMode);
+ if (!matugenAvailable || isGreeterMode)
+ return;
+
+ _pendingGenerateParams = true;
+ _themeGenerateDebounce.restart();
+ }
+
+ function _executeThemeGeneration() {
+ if (!_pendingGenerateParams)
+ return;
+ _pendingGenerateParams = null;
+
+ const isLight = (typeof SessionData !== "undefined" && SessionData.isLightMode);
+ const iconTheme = (typeof SettingsData !== "undefined" && SettingsData.iconTheme) ? SettingsData.iconTheme : "System Default";
+
+ if (currentTheme === dynamic) {
+ if (!rawWallpaperPath)
+ return;
+ const selectedMatugenType = (typeof SettingsData !== "undefined" && SettingsData.matugenScheme) ? SettingsData.matugenScheme : "scheme-tonal-spot";
+ const kind = rawWallpaperPath.startsWith("#") ? "hex" : "image";
+ setDesiredTheme(kind, rawWallpaperPath, isLight, iconTheme, selectedMatugenType, null);
+ return;
+ }
+
+ let darkTheme, lightTheme;
+ if (currentTheme === "custom") {
+ if (customThemeRawData && (customThemeRawData.dark || customThemeRawData.light)) {
+ darkTheme = customThemeRawData.dark || customThemeRawData.light;
+ lightTheme = customThemeRawData.light || customThemeRawData.dark;
+
+ if (customThemeRawData.variants) {
+ const themeId = customThemeRawData.id || "";
+
+ if (customThemeRawData.variants.type === "multi" && customThemeRawData.variants.flavors && customThemeRawData.variants.accents) {
+ const defaults = customThemeRawData.variants.defaults || {};
+ const darkDefaults = defaults.dark || {};
+ const lightDefaults = defaults.light || defaults.dark || {};
+ const isGreeterMode = typeof SessionData !== "undefined" && SessionData.isGreeterMode;
+ const storedDark = isGreeterMode ? (GreetdSettings.registryThemeVariants[themeId]?.dark || darkDefaults) : (typeof SettingsData !== "undefined" ? SettingsData.getRegistryThemeMultiVariant(themeId, darkDefaults, "dark") : darkDefaults);
+ const storedLight = isGreeterMode ? (GreetdSettings.registryThemeVariants[themeId]?.light || lightDefaults) : (typeof SettingsData !== "undefined" ? SettingsData.getRegistryThemeMultiVariant(themeId, lightDefaults, "light") : lightDefaults);
+ const darkFlavorId = storedDark.flavor || darkDefaults.flavor || "";
+ const lightFlavorId = storedLight.flavor || lightDefaults.flavor || "";
+ const accentId = storedDark.accent || darkDefaults.accent || "";
+ const darkFlavor = findVariant(customThemeRawData.variants.flavors, darkFlavorId);
+ const lightFlavor = findVariant(customThemeRawData.variants.flavors, lightFlavorId);
+ const accent = findAccent(customThemeRawData.variants.accents, accentId);
+ if (darkFlavor) {
+ darkTheme = mergeColors(darkTheme, darkFlavor.dark || {});
+ if (accent)
+ darkTheme = mergeColors(darkTheme, accent[darkFlavor.id] || {});
+ }
+ if (lightFlavor) {
+ lightTheme = mergeColors(lightTheme, lightFlavor.light || {});
+ if (accent)
+ lightTheme = mergeColors(lightTheme, accent[lightFlavor.id] || {});
+ }
+ } else if (customThemeRawData.variants.options) {
+ const isGreeterMode = typeof SessionData !== "undefined" && SessionData.isGreeterMode;
+ const selectedVariantId = isGreeterMode ? (typeof GreetdSettings.registryThemeVariants[themeId] === "string" ? GreetdSettings.registryThemeVariants[themeId] : customThemeRawData.variants.default) : (typeof SettingsData !== "undefined" ? SettingsData.getRegistryThemeVariant(themeId, customThemeRawData.variants.default) : customThemeRawData.variants.default);
+ const variant = findVariant(customThemeRawData.variants.options, selectedVariantId);
+ if (variant) {
+ darkTheme = mergeColors(darkTheme, variant.dark || {});
+ lightTheme = mergeColors(lightTheme, variant.light || {});
+ }
+ }
+ }
+ } else {
+ darkTheme = customThemeData;
+ lightTheme = customThemeData;
+ }
+ } else {
+ darkTheme = StockThemes.getThemeByName(currentTheme, false);
+ lightTheme = StockThemes.getThemeByName(currentTheme, true);
+ }
+
+ if (!darkTheme || !darkTheme.primary) {
+ console.warn("Theme data not available for:", currentTheme);
+ return;
+ }
+
+ const stockColors = buildMatugenColorsFromTheme(darkTheme, lightTheme);
+ const themeData = isLight ? lightTheme : darkTheme;
+ setDesiredTheme("hex", themeData.primary, isLight, iconTheme, themeData.matugen_type, stockColors);
+ }
+
+ function buildMatugenColorsFromTheme(darkTheme, lightTheme) {
+ const colors = {};
+ const isLight = SessionData !== "undefined" && SessionData.isLightMode;
+
+ function addColor(matugenKey, darkVal, lightVal) {
+ if (!darkVal && !lightVal)
+ return;
+ colors[matugenKey] = {
+ "dark": {
+ "color": String(darkVal || lightVal)
+ },
+ "light": {
+ "color": String(lightVal || darkVal)
+ },
+ "default": {
+ "color": String((isLight && lightVal) ? lightVal : darkVal)
+ }
+ };
+ }
+
+ function get(theme, key, fallback) {
+ return theme[key] || fallback;
+ }
+
+ addColor("primary", darkTheme.primary, lightTheme.primary);
+ addColor("on_primary", darkTheme.primaryText, lightTheme.primaryText);
+ addColor("primary_container", darkTheme.primaryContainer, lightTheme.primaryContainer);
+ addColor("on_primary_container", darkTheme.primaryContainerText || darkTheme.surfaceText, lightTheme.primaryContainerText || lightTheme.surfaceText);
+ addColor("secondary", darkTheme.secondary, lightTheme.secondary);
+ addColor("on_secondary", darkTheme.secondaryText || darkTheme.primaryText, lightTheme.secondaryText || lightTheme.primaryText);
+ addColor("secondary_container", darkTheme.secondaryContainer || darkTheme.surfaceContainerHigh, lightTheme.secondaryContainer || lightTheme.surfaceContainerHigh);
+ addColor("on_secondary_container", darkTheme.secondaryContainerText || darkTheme.surfaceText, lightTheme.secondaryContainerText || lightTheme.surfaceText);
+ addColor("tertiary", darkTheme.tertiary || darkTheme.secondary, lightTheme.tertiary || lightTheme.secondary);
+ addColor("on_tertiary", darkTheme.tertiaryText || darkTheme.secondaryText || darkTheme.primaryText, lightTheme.tertiaryText || lightTheme.secondaryText || lightTheme.primaryText);
+ addColor("tertiary_container", darkTheme.tertiaryContainer || darkTheme.secondaryContainer || darkTheme.surfaceContainerHigh, lightTheme.tertiaryContainer || lightTheme.secondaryContainer || lightTheme.surfaceContainerHigh);
+ addColor("on_tertiary_container", darkTheme.tertiaryContainerText || darkTheme.surfaceText, lightTheme.tertiaryContainerText || lightTheme.surfaceText);
+ addColor("error", darkTheme.error || "#F2B8B5", lightTheme.error || "#B3261E");
+ addColor("on_error", darkTheme.errorText || "#601410", lightTheme.errorText || "#FFFFFF");
+ addColor("error_container", darkTheme.errorContainer || "#8C1D18", lightTheme.errorContainer || "#F9DEDC");
+ addColor("on_error_container", darkTheme.errorContainerText || "#F9DEDC", lightTheme.errorContainerText || "#410E0B");
+ addColor("surface", darkTheme.surface, lightTheme.surface);
+ addColor("on_surface", darkTheme.surfaceText, lightTheme.surfaceText);
+ addColor("surface_variant", darkTheme.surfaceVariant, lightTheme.surfaceVariant);
+ addColor("on_surface_variant", darkTheme.surfaceVariantText, lightTheme.surfaceVariantText);
+ addColor("surface_tint", darkTheme.surfaceTint, lightTheme.surfaceTint);
+ addColor("background", darkTheme.background, lightTheme.background);
+ addColor("on_background", darkTheme.backgroundText, lightTheme.backgroundText);
+ addColor("outline", darkTheme.outline, lightTheme.outline);
+ addColor("outline_variant", darkTheme.outlineVariant || darkTheme.surfaceVariant, lightTheme.outlineVariant || lightTheme.surfaceVariant);
+ addColor("surface_container", darkTheme.surfaceContainer, lightTheme.surfaceContainer);
+ addColor("surface_container_high", darkTheme.surfaceContainerHigh, lightTheme.surfaceContainerHigh);
+ addColor("surface_container_highest", darkTheme.surfaceContainerHighest || darkTheme.surfaceContainerHigh, lightTheme.surfaceContainerHighest || lightTheme.surfaceContainerHigh);
+ addColor("surface_container_low", darkTheme.surfaceContainerLow || darkTheme.surface, lightTheme.surfaceContainerLow || lightTheme.surface);
+ addColor("surface_container_lowest", darkTheme.surfaceContainerLowest || darkTheme.background, lightTheme.surfaceContainerLowest || lightTheme.background);
+ addColor("surface_bright", darkTheme.surfaceBright || darkTheme.surfaceContainerHighest || darkTheme.surfaceContainerHigh, lightTheme.surfaceBright || lightTheme.surface);
+ addColor("surface_dim", darkTheme.surfaceDim || darkTheme.background, lightTheme.surfaceDim || lightTheme.surfaceContainer);
+ addColor("inverse_surface", darkTheme.inverseSurface || lightTheme.surface, lightTheme.inverseSurface || darkTheme.surface);
+ addColor("inverse_on_surface", darkTheme.inverseOnSurface || lightTheme.surfaceText, lightTheme.inverseOnSurface || darkTheme.surfaceText);
+ addColor("inverse_primary", darkTheme.inversePrimary || lightTheme.primary, lightTheme.inversePrimary || darkTheme.primary);
+ addColor("scrim", darkTheme.scrim || "#000000", lightTheme.scrim || "#000000");
+ addColor("shadow", darkTheme.shadow || "#000000", lightTheme.shadow || "#000000");
+ addColor("source_color", darkTheme.primary, lightTheme.primary);
+ addColor("primary_fixed", darkTheme.primaryFixed || darkTheme.primaryContainer, lightTheme.primaryFixed || lightTheme.primaryContainer);
+ addColor("primary_fixed_dim", darkTheme.primaryFixedDim || darkTheme.primary, lightTheme.primaryFixedDim || lightTheme.primary);
+ addColor("on_primary_fixed", darkTheme.onPrimaryFixed || darkTheme.primaryText, lightTheme.onPrimaryFixed || lightTheme.primaryText);
+ addColor("on_primary_fixed_variant", darkTheme.onPrimaryFixedVariant || darkTheme.primaryText, lightTheme.onPrimaryFixedVariant || lightTheme.primaryText);
+ addColor("secondary_fixed", darkTheme.secondaryFixed || darkTheme.secondary, lightTheme.secondaryFixed || lightTheme.secondary);
+ addColor("secondary_fixed_dim", darkTheme.secondaryFixedDim || darkTheme.secondary, lightTheme.secondaryFixedDim || lightTheme.secondary);
+ addColor("on_secondary_fixed", darkTheme.onSecondaryFixed || darkTheme.primaryText, lightTheme.onSecondaryFixed || lightTheme.primaryText);
+ addColor("on_secondary_fixed_variant", darkTheme.onSecondaryFixedVariant || darkTheme.primaryText, lightTheme.onSecondaryFixedVariant || lightTheme.primaryText);
+ addColor("tertiary_fixed", darkTheme.tertiaryFixed || darkTheme.tertiary || darkTheme.secondary, lightTheme.tertiaryFixed || lightTheme.tertiary || lightTheme.secondary);
+ addColor("tertiary_fixed_dim", darkTheme.tertiaryFixedDim || darkTheme.tertiary || darkTheme.secondary, lightTheme.tertiaryFixedDim || lightTheme.tertiary || lightTheme.secondary);
+ addColor("on_tertiary_fixed", darkTheme.onTertiaryFixed || darkTheme.primaryText, lightTheme.onTertiaryFixed || lightTheme.primaryText);
+ addColor("on_tertiary_fixed_variant", darkTheme.onTertiaryFixedVariant || darkTheme.primaryText, lightTheme.onTertiaryFixedVariant || lightTheme.primaryText);
+
+ return colors;
+ }
+
+ function applyGtkColors() {
+ if (!matugenAvailable) {
+ if (typeof ToastService !== "undefined") {
+ ToastService.showError("matugen not available or disabled - cannot apply GTK colors");
+ }
+ return;
+ }
+
+ const isLight = (typeof SessionData !== "undefined" && SessionData.isLightMode) ? "true" : "false";
+ Proc.runCommand("gtkApplier", [shellDir + "/scripts/gtk.sh", configDir, isLight, shellDir], (output, exitCode) => {
+ if (exitCode === 0) {
+ if (typeof ToastService !== "undefined" && typeof NiriService !== "undefined" && !NiriService.matugenSuppression) {
+ ToastService.showInfo("GTK colors applied successfully");
+ }
+ } else {
+ if (typeof ToastService !== "undefined") {
+ ToastService.showError("Failed to apply GTK colors");
+ }
+ }
+ });
+ }
+
+ function applyQtColors() {
+ if (!matugenAvailable) {
+ if (typeof ToastService !== "undefined") {
+ ToastService.showError("matugen not available or disabled - cannot apply Qt colors");
+ }
+ return;
+ }
+
+ Proc.runCommand("qtApplier", [shellDir + "/scripts/qt.sh", configDir], (output, exitCode) => {
+ if (exitCode === 0) {
+ if (typeof ToastService !== "undefined") {
+ ToastService.showInfo("Qt colors applied successfully");
+ }
+ } else {
+ if (typeof ToastService !== "undefined") {
+ ToastService.showError("Failed to apply Qt colors");
+ }
+ }
+ });
+ }
+
+ function withAlpha(c, a) {
+ return Qt.rgba(c.r, c.g, c.b, a);
+ }
+
+ function blendAlpha(c, a) {
+ return Qt.rgba(c.r, c.g, c.b, c.a * a);
+ }
+
+ 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);
+ }
+
+ 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;
+ }
+ }
+
+ function snap(value, dpr) {
+ const s = dpr || 1;
+ return Math.round(value * s) / s;
+ }
+
+ function px(value, dpr) {
+ const s = dpr || 1;
+ return Math.round(value * s) / s;
+ }
+
+ function hairline(dpr) {
+ return 1 / (dpr || 1);
+ }
+
+ function invertHex(hex) {
+ hex = hex.replace('#', '');
+
+ if (!/^[0-9A-Fa-f]{6}$/.test(hex)) {
+ return hex;
+ }
+
+ const r = parseInt(hex.substr(0, 2), 16);
+ const g = parseInt(hex.substr(2, 2), 16);
+ const b = parseInt(hex.substr(4, 2), 16);
+
+ const invR = (255 - r).toString(16).padStart(2, '0');
+ const invG = (255 - g).toString(16).padStart(2, '0');
+ const invB = (255 - b).toString(16).padStart(2, '0');
+
+ return `#${invR}${invG}${invB}`;
+ }
+
+ property var baseLogoColor: {
+ if (typeof SettingsData === "undefined")
+ return "";
+ const colorOverride = SettingsData.launcherLogoColorOverride;
+ if (!colorOverride || colorOverride === "")
+ return "";
+ if (colorOverride === "primary")
+ return primary;
+ if (colorOverride === "surface")
+ return surfaceText;
+ return colorOverride;
+ }
+
+ property var effectiveLogoColor: {
+ if (typeof SettingsData === "undefined")
+ return "";
+
+ const colorOverride = SettingsData.launcherLogoColorOverride;
+ if (!colorOverride || colorOverride === "")
+ return "";
+
+ if (colorOverride === "primary")
+ return primary;
+ if (colorOverride === "surface")
+ return surfaceText;
+
+ if (!SettingsData.launcherLogoColorInvertOnMode) {
+ return colorOverride;
+ }
+
+ if (isLightMode) {
+ return invertHex(colorOverride);
+ }
+
+ return colorOverride;
+ }
+
+ Process {
+ id: systemThemeGenerator
+ running: false
+ stdout: SplitParser {
+ onRead: data => console.info("Theme worker:", data)
+ }
+ stderr: SplitParser {
+ onRead: data => console.warn("Theme worker:", data)
+ }
+
+ onExited: exitCode => {
+ workerRunning = false;
+ const currentMode = (typeof SessionData !== "undefined" && SessionData.isLightMode) ? "light" : "dark";
+
+ switch (exitCode) {
+ case 0:
+ console.info("Theme: Matugen worker completed successfully");
+ root.matugenCompleted(currentMode, "success");
+ break;
+ case 2:
+ console.log("Theme: Matugen worker completed with code 2 (no changes needed)");
+ root.matugenCompleted(currentMode, "no-changes");
+ break;
+ default:
+ if (typeof ToastService !== "undefined") {
+ ToastService.showError("Theme worker failed (" + exitCode + ")");
+ }
+ console.warn("Theme: Matugen worker failed with exit code:", exitCode);
+ root.matugenCompleted(currentMode, "error");
+ }
+
+ if (!pendingThemeRequest)
+ return;
+
+ const req = pendingThemeRequest;
+ pendingThemeRequest = null;
+ console.info("Theme: Processing queued theme request");
+ setDesiredTheme(req.kind, req.value, req.isLight, req.iconTheme, req.matugenType, req.stockColors);
+ }
+ }
+
+ FileView {
+ id: customThemeFileView
+ watchChanges: currentTheme === "custom"
+
+ function parseAndLoadTheme() {
+ try {
+ var themeData = JSON.parse(customThemeFileView.text());
+ loadCustomTheme(themeData);
+ } catch (e) {
+ ToastService.showError("Invalid JSON format: " + e.message);
+ }
+ }
+
+ onLoaded: {
+ parseAndLoadTheme();
+ }
+
+ onFileChanged: {
+ customThemeFileView.reload();
+ }
+
+ onLoadFailed: function (error) {
+ if (typeof ToastService !== "undefined") {
+ ToastService.showError("Failed to read theme file: " + error);
+ }
+ }
+ }
+
+ FileView {
+ id: dynamicColorsFileView
+ path: {
+ const greetCfgDir = Quickshell.env("DMS_GREET_CFG_DIR") || "/var/cache/dms-greeter";
+ const colorsPath = SessionData.isGreeterMode ? greetCfgDir + "/colors.json" : stateDir + "/dms-colors.json";
+ return colorsPath;
+ }
+ blockLoading: false
+ watchChanges: !SessionData.isGreeterMode
+
+ function parseAndLoadColors() {
+ try {
+ const colorsText = dynamicColorsFileView.text();
+ if (colorsText) {
+ root.matugenColors = JSON.parse(colorsText);
+ if (typeof ToastService !== "undefined") {
+ ToastService.clearWallpaperError();
+ }
+ }
+ } catch (e) {
+ console.error("Theme: Failed to parse dynamic colors:", e);
+ if (typeof ToastService !== "undefined") {
+ ToastService.wallpaperErrorStatus = "error";
+ ToastService.showError("Dynamic colors parse error: " + e.message);
+ }
+ }
+ }
+
+ onLoaded: {
+ if (currentTheme === dynamic)
+ colorsFileLoadFailed = false;
+ parseAndLoadColors();
+ }
+
+ onFileChanged: {
+ dynamicColorsFileView.reload();
+ }
+
+ onLoadFailed: function (error) {
+ if (currentTheme === dynamic) {
+ console.warn("Theme: Dynamic colors file load failed, marking for regeneration");
+ colorsFileLoadFailed = true;
+ const isGreeterMode = (typeof SessionData !== "undefined" && SessionData.isGreeterMode);
+ if (!isGreeterMode && matugenAvailable && rawWallpaperPath) {
+ console.log("Theme: Matugen available, triggering immediate regeneration");
+ generateSystemThemesFromCurrentTheme();
+ }
+ }
+ }
+
+ onPathChanged: {
+ colorsFileLoadFailed = false;
+ }
+ }
+
+ IpcHandler {
+ target: "theme"
+
+ function toggle(): string {
+ root.toggleLightMode();
+ return root.isLightMode ? "dark" : "light";
+ }
+
+ function light(): string {
+ root.setLightMode(true, true, true);
+ return "light";
+ }
+
+ function dark(): string {
+ root.setLightMode(false, true, true);
+ return "dark";
+ }
+
+ function getMode(): string {
+ return root.isLightMode ? "light" : "dark";
+ }
+ }
+
+ Timer {
+ id: _themeGenerateDebounce
+ interval: 100
+ repeat: false
+ onTriggered: root._executeThemeGeneration()
+ }
+
+ // These timers are for screen transitions, since sometimes QML still beats the niri call
+ Timer {
+ id: themeTransitionTimer
+ interval: 50
+ repeat: false
+ property string themeName: ""
+ property bool savePrefs: true
+ onTriggered: root.switchTheme(themeName, savePrefs, false)
+ }
+
+ Timer {
+ id: lightModeTransitionTimer
+ interval: 100
+ repeat: false
+ property bool lightMode: false
+ property bool savePrefs: true
+ onTriggered: root.setLightMode(lightMode, savePrefs, false)
+ }
+
+ Timer {
+ id: themeCategoryTransitionTimer
+ interval: 50
+ repeat: false
+ property string category: ""
+ property string defaultTheme: ""
+ onTriggered: {
+ root.currentThemeCategory = category;
+ 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) {
+ console.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) {
+ console.warn("Theme automation: Waiting for IP location from backend");
+ } else {
+ console.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) {
+ console.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) {
+ console.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/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Common/TrayMenuManager.qml b/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Common/TrayMenuManager.qml
new file mode 100644
index 0000000..560d757
--- /dev/null
+++ b/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Common/TrayMenuManager.qml
@@ -0,0 +1,36 @@
+pragma Singleton
+
+import Quickshell
+import QtQuick
+
+Singleton {
+ id: root
+
+ property var activeTrayMenus: ({})
+
+ function registerMenu(screenName, menu) {
+ if (!screenName || !menu) return
+ const newMenus = Object.assign({}, activeTrayMenus)
+ newMenus[screenName] = menu
+ activeTrayMenus = newMenus
+ }
+
+ function unregisterMenu(screenName) {
+ if (!screenName) return
+ const newMenus = Object.assign({}, activeTrayMenus)
+ delete newMenus[screenName]
+ activeTrayMenus = newMenus
+ }
+
+ function closeAllMenus() {
+ for (const screenName in activeTrayMenus) {
+ const menu = activeTrayMenus[screenName]
+ if (!menu) continue
+ if (typeof menu.close === "function") {
+ menu.close()
+ } else if (menu.showMenu !== undefined) {
+ menu.showMenu = false
+ }
+ }
+ }
+}
diff --git a/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Common/fzf.js b/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Common/fzf.js
new file mode 100644
index 0000000..94946e6
--- /dev/null
+++ b/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Common/fzf.js
@@ -0,0 +1,1308 @@
+.pragma library
+
+/*
+https://github.com/ajitid/fzf-for-js
+
+BSD 3-Clause License
+
+Copyright (c) 2021, Ajit
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+3. Neither the name of the copyright holder nor the names of its
+ contributors may be used to endorse or promote products derived from
+ this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+const normalized = {
+ 216: "O",
+ 223: "s",
+ 248: "o",
+ 273: "d",
+ 295: "h",
+ 305: "i",
+ 320: "l",
+ 322: "l",
+ 359: "t",
+ 383: "s",
+ 384: "b",
+ 385: "B",
+ 387: "b",
+ 390: "O",
+ 392: "c",
+ 393: "D",
+ 394: "D",
+ 396: "d",
+ 398: "E",
+ 400: "E",
+ 402: "f",
+ 403: "G",
+ 407: "I",
+ 409: "k",
+ 410: "l",
+ 412: "M",
+ 413: "N",
+ 414: "n",
+ 415: "O",
+ 421: "p",
+ 427: "t",
+ 429: "t",
+ 430: "T",
+ 434: "V",
+ 436: "y",
+ 438: "z",
+ 477: "e",
+ 485: "g",
+ 544: "N",
+ 545: "d",
+ 549: "z",
+ 564: "l",
+ 565: "n",
+ 566: "t",
+ 567: "j",
+ 570: "A",
+ 571: "C",
+ 572: "c",
+ 573: "L",
+ 574: "T",
+ 575: "s",
+ 576: "z",
+ 579: "B",
+ 580: "U",
+ 581: "V",
+ 582: "E",
+ 583: "e",
+ 584: "J",
+ 585: "j",
+ 586: "Q",
+ 587: "q",
+ 588: "R",
+ 589: "r",
+ 590: "Y",
+ 591: "y",
+ 592: "a",
+ 593: "a",
+ 595: "b",
+ 596: "o",
+ 597: "c",
+ 598: "d",
+ 599: "d",
+ 600: "e",
+ 603: "e",
+ 604: "e",
+ 605: "e",
+ 606: "e",
+ 607: "j",
+ 608: "g",
+ 609: "g",
+ 610: "G",
+ 613: "h",
+ 614: "h",
+ 616: "i",
+ 618: "I",
+ 619: "l",
+ 620: "l",
+ 621: "l",
+ 623: "m",
+ 624: "m",
+ 625: "m",
+ 626: "n",
+ 627: "n",
+ 628: "N",
+ 629: "o",
+ 633: "r",
+ 634: "r",
+ 635: "r",
+ 636: "r",
+ 637: "r",
+ 638: "r",
+ 639: "r",
+ 640: "R",
+ 641: "R",
+ 642: "s",
+ 647: "t",
+ 648: "t",
+ 649: "u",
+ 651: "v",
+ 652: "v",
+ 653: "w",
+ 654: "y",
+ 655: "Y",
+ 656: "z",
+ 657: "z",
+ 663: "c",
+ 665: "B",
+ 666: "e",
+ 667: "G",
+ 668: "H",
+ 669: "j",
+ 670: "k",
+ 671: "L",
+ 672: "q",
+ 686: "h",
+ 867: "a",
+ 868: "e",
+ 869: "i",
+ 870: "o",
+ 871: "u",
+ 872: "c",
+ 873: "d",
+ 874: "h",
+ 875: "m",
+ 876: "r",
+ 877: "t",
+ 878: "v",
+ 879: "x",
+ 7424: "A",
+ 7427: "B",
+ 7428: "C",
+ 7429: "D",
+ 7431: "E",
+ 7432: "e",
+ 7433: "i",
+ 7434: "J",
+ 7435: "K",
+ 7436: "L",
+ 7437: "M",
+ 7438: "N",
+ 7439: "O",
+ 7440: "O",
+ 7441: "o",
+ 7442: "o",
+ 7443: "o",
+ 7446: "o",
+ 7447: "o",
+ 7448: "P",
+ 7449: "R",
+ 7450: "R",
+ 7451: "T",
+ 7452: "U",
+ 7453: "u",
+ 7454: "u",
+ 7455: "m",
+ 7456: "V",
+ 7457: "W",
+ 7458: "Z",
+ 7522: "i",
+ 7523: "r",
+ 7524: "u",
+ 7525: "v",
+ 7834: "a",
+ 7835: "s",
+ 8305: "i",
+ 8341: "h",
+ 8342: "k",
+ 8343: "l",
+ 8344: "m",
+ 8345: "n",
+ 8346: "p",
+ 8347: "s",
+ 8348: "t",
+ 8580: "c"
+};
+for (let i = "\u0300".codePointAt(0); i <= "\u036F".codePointAt(0); ++i) {
+ const diacritic = String.fromCodePoint(i);
+ for (const asciiChar of "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz") {
+ const withDiacritic = (asciiChar + diacritic).normalize();
+ const withDiacriticCodePoint = withDiacritic.codePointAt(0);
+ if (withDiacriticCodePoint > 126) {
+ normalized[withDiacriticCodePoint] = asciiChar;
+ }
+ }
+}
+const ranges = {
+ a: [7844, 7863],
+ e: [7870, 7879],
+ o: [7888, 7907],
+ u: [7912, 7921]
+};
+for (const lowerChar of Object.keys(ranges)) {
+ const upperChar = lowerChar.toUpperCase();
+ for (let i = ranges[lowerChar][0]; i <= ranges[lowerChar][1]; ++i) {
+ normalized[i] = i % 2 === 0 ? upperChar : lowerChar;
+ }
+}
+function normalizeRune(rune) {
+ if (rune < 192 || rune > 8580) {
+ return rune;
+ }
+ const normalizedChar = normalized[rune];
+ if (normalizedChar !== void 0)
+ return normalizedChar.codePointAt(0);
+ return rune;
+}
+function toShort(number) {
+ return number;
+}
+function toInt(number) {
+ return number;
+}
+function maxInt16(num1, num2) {
+ return num1 > num2 ? num1 : num2;
+}
+const strToRunes = (str) => str.split("").map((s) => s.codePointAt(0));
+const runesToStr = (runes) => runes.map((r) => String.fromCodePoint(r)).join("");
+const whitespaceRunes = new Set(
+ " \f\n\r \v\xA0\u1680\u2028\u2029\u202F\u205F\u3000\uFEFF".split("").map((v) => v.codePointAt(0))
+);
+for (let codePoint = "\u2000".codePointAt(0); codePoint <= "\u200A".codePointAt(0); codePoint++) {
+ whitespaceRunes.add(codePoint);
+}
+const isWhitespace = (rune) => whitespaceRunes.has(rune);
+const whitespacesAtStart = (runes) => {
+ let whitespaces = 0;
+ for (const rune of runes) {
+ if (isWhitespace(rune))
+ whitespaces++;
+ else
+ break;
+ }
+ return whitespaces;
+};
+const whitespacesAtEnd = (runes) => {
+ let whitespaces = 0;
+ for (let i = runes.length - 1; i >= 0; i--) {
+ if (isWhitespace(runes[i]))
+ whitespaces++;
+ else
+ break;
+ }
+ return whitespaces;
+};
+const MAX_ASCII = "\x7F".codePointAt(0);
+const CAPITAL_A_RUNE = "A".codePointAt(0);
+const CAPITAL_Z_RUNE = "Z".codePointAt(0);
+const SMALL_A_RUNE = "a".codePointAt(0);
+const SMALL_Z_RUNE = "z".codePointAt(0);
+const NUMERAL_ZERO_RUNE = "0".codePointAt(0);
+const NUMERAL_NINE_RUNE = "9".codePointAt(0);
+function indexAt(index, max, forward) {
+ if (forward) {
+ return index;
+ }
+ return max - index - 1;
+}
+const SCORE_MATCH = 16, SCORE_GAP_START = -3, SCORE_GAP_EXTENTION = -1, BONUS_BOUNDARY = SCORE_MATCH / 2, BONUS_NON_WORD = SCORE_MATCH / 2, BONUS_CAMEL_123 = BONUS_BOUNDARY + SCORE_GAP_EXTENTION, BONUS_CONSECUTIVE = -(SCORE_GAP_START + SCORE_GAP_EXTENTION), BONUS_FIRST_CHAR_MULTIPLIER = 2;
+function createPosSet(withPos) {
+ if (withPos) {
+ return /* @__PURE__ */ new Set();
+ }
+ return null;
+}
+function alloc16(offset, slab2, size) {
+ if (slab2 !== null && slab2.i16.length > offset + size) {
+ const subarray = slab2.i16.subarray(offset, offset + size);
+ return [offset + size, subarray];
+ }
+ return [offset, new Int16Array(size)];
+}
+function alloc32(offset, slab2, size) {
+ if (slab2 !== null && slab2.i32.length > offset + size) {
+ const subarray = slab2.i32.subarray(offset, offset + size);
+ return [offset + size, subarray];
+ }
+ return [offset, new Int32Array(size)];
+}
+function charClassOfAscii(rune) {
+ if (rune >= SMALL_A_RUNE && rune <= SMALL_Z_RUNE) {
+ return 1;
+ } else if (rune >= CAPITAL_A_RUNE && rune <= CAPITAL_Z_RUNE) {
+ return 2;
+ } else if (rune >= NUMERAL_ZERO_RUNE && rune <= NUMERAL_NINE_RUNE) {
+ return 4;
+ } else {
+ return 0;
+ }
+}
+function charClassOfNonAscii(rune) {
+ const char = String.fromCodePoint(rune);
+ if (char !== char.toUpperCase()) {
+ return 1;
+ } else if (char !== char.toLowerCase()) {
+ return 2;
+ } else if (char.match(/\p{Number}/gu) !== null) {
+ return 4;
+ } else if (char.match(/\p{Letter}/gu) !== null) {
+ return 3;
+ }
+ return 0;
+}
+function charClassOf(rune) {
+ if (rune <= MAX_ASCII) {
+ return charClassOfAscii(rune);
+ }
+ return charClassOfNonAscii(rune);
+}
+function bonusFor(prevClass, currClass) {
+ if (prevClass === 0 && currClass !== 0) {
+ return BONUS_BOUNDARY;
+ } else if (prevClass === 1 && currClass === 2 || prevClass !== 4 && currClass === 4) {
+ return BONUS_CAMEL_123;
+ } else if (currClass === 0) {
+ return BONUS_NON_WORD;
+ }
+ return 0;
+}
+function bonusAt(input, idx) {
+ if (idx === 0) {
+ return BONUS_BOUNDARY;
+ }
+ return bonusFor(charClassOf(input[idx - 1]), charClassOf(input[idx]));
+}
+function trySkip(input, caseSensitive, char, from) {
+ let rest = input.slice(from);
+ let idx = rest.indexOf(char);
+ if (idx === 0) {
+ return from;
+ }
+ if (!caseSensitive && char >= SMALL_A_RUNE && char <= SMALL_Z_RUNE) {
+ if (idx > 0) {
+ rest = rest.slice(0, idx);
+ }
+ const uidx = rest.indexOf(char - 32);
+ if (uidx >= 0) {
+ idx = uidx;
+ }
+ }
+ if (idx < 0) {
+ return -1;
+ }
+ return from + idx;
+}
+function isAscii(runes) {
+ for (const rune of runes) {
+ if (rune >= 128) {
+ return false;
+ }
+ }
+ return true;
+}
+function asciiFuzzyIndex(input, pattern, caseSensitive) {
+ if (!isAscii(input)) {
+ return 0;
+ }
+ if (!isAscii(pattern)) {
+ return -1;
+ }
+ let firstIdx = 0, idx = 0;
+ for (let pidx = 0; pidx < pattern.length; pidx++) {
+ idx = trySkip(input, caseSensitive, pattern[pidx], idx);
+ if (idx < 0) {
+ return -1;
+ }
+ if (pidx === 0 && idx > 0) {
+ firstIdx = idx - 1;
+ }
+ idx++;
+ }
+ return firstIdx;
+}
+const fuzzyMatchV2 = (caseSensitive, normalize, forward, input, pattern, withPos, slab2) => {
+ const M = pattern.length;
+ if (M === 0) {
+ return [{ start: 0, end: 0, score: 0 }, createPosSet(withPos)];
+ }
+ const N = input.length;
+ if (slab2 !== null && N * M > slab2.i16.length) {
+ return fuzzyMatchV1(caseSensitive, normalize, forward, input, pattern, withPos);
+ }
+ const idx = asciiFuzzyIndex(input, pattern, caseSensitive);
+ if (idx < 0) {
+ return [{ start: -1, end: -1, score: 0 }, null];
+ }
+ let offset16 = 0, offset32 = 0, H0 = null, C0 = null, B = null, F = null;
+ [offset16, H0] = alloc16(offset16, slab2, N);
+ [offset16, C0] = alloc16(offset16, slab2, N);
+ [offset16, B] = alloc16(offset16, slab2, N);
+ [offset32, F] = alloc32(offset32, slab2, M);
+ const [, T] = alloc32(offset32, slab2, N);
+ for (let i = 0; i < T.length; i++) {
+ T[i] = input[i];
+ }
+ let maxScore = toShort(0), maxScorePos = 0;
+ let pidx = 0, lastIdx = 0;
+ const pchar0 = pattern[0];
+ let pchar = pattern[0], prevH0 = toShort(0), prevCharClass = 0, inGap = false;
+ let Tsub = T.subarray(idx);
+ let H0sub = H0.subarray(idx).subarray(0, Tsub.length), C0sub = C0.subarray(idx).subarray(0, Tsub.length), Bsub = B.subarray(idx).subarray(0, Tsub.length);
+ for (let [off, char] of Tsub.entries()) {
+ let charClass = null;
+ if (char <= MAX_ASCII) {
+ charClass = charClassOfAscii(char);
+ if (!caseSensitive && charClass === 2) {
+ char += 32;
+ }
+ } else {
+ charClass = charClassOfNonAscii(char);
+ if (!caseSensitive && charClass === 2) {
+ char = String.fromCodePoint(char).toLowerCase().codePointAt(0);
+ }
+ if (normalize) {
+ char = normalizeRune(char);
+ }
+ }
+ Tsub[off] = char;
+ const bonus = bonusFor(prevCharClass, charClass);
+ Bsub[off] = bonus;
+ prevCharClass = charClass;
+ if (char === pchar) {
+ if (pidx < M) {
+ F[pidx] = toInt(idx + off);
+ pidx++;
+ pchar = pattern[Math.min(pidx, M - 1)];
+ }
+ lastIdx = idx + off;
+ }
+ if (char === pchar0) {
+ const score = SCORE_MATCH + bonus * BONUS_FIRST_CHAR_MULTIPLIER;
+ H0sub[off] = score;
+ C0sub[off] = 1;
+ if (M === 1 && (forward && score > maxScore || !forward && score >= maxScore)) {
+ maxScore = score;
+ maxScorePos = idx + off;
+ if (forward && bonus === BONUS_BOUNDARY) {
+ break;
+ }
+ }
+ inGap = false;
+ } else {
+ if (inGap) {
+ H0sub[off] = maxInt16(prevH0 + SCORE_GAP_EXTENTION, 0);
+ } else {
+ H0sub[off] = maxInt16(prevH0 + SCORE_GAP_START, 0);
+ }
+ C0sub[off] = 0;
+ inGap = true;
+ }
+ prevH0 = H0sub[off];
+ }
+ if (pidx !== M) {
+ return [{ start: -1, end: -1, score: 0 }, null];
+ }
+ if (M === 1) {
+ const result = {
+ start: maxScorePos,
+ end: maxScorePos + 1,
+ score: maxScore
+ };
+ if (!withPos) {
+ return [result, null];
+ }
+ const pos2 = /* @__PURE__ */ new Set();
+ pos2.add(maxScorePos);
+ return [result, pos2];
+ }
+ const f0 = F[0];
+ const width = lastIdx - f0 + 1;
+ let H = null;
+ [offset16, H] = alloc16(offset16, slab2, width * M);
+ {
+ const toCopy = H0.subarray(f0, lastIdx + 1);
+ for (const [i, v] of toCopy.entries()) {
+ H[i] = v;
+ }
+ }
+ let [, C] = alloc16(offset16, slab2, width * M);
+ {
+ const toCopy = C0.subarray(f0, lastIdx + 1);
+ for (const [i, v] of toCopy.entries()) {
+ C[i] = v;
+ }
+ }
+ const Fsub = F.subarray(1);
+ const Psub = pattern.slice(1).slice(0, Fsub.length);
+ for (const [off, f] of Fsub.entries()) {
+ let inGap2 = false;
+ const pchar2 = Psub[off], pidx2 = off + 1, row = pidx2 * width, Tsub2 = T.subarray(f, lastIdx + 1), Bsub2 = B.subarray(f).subarray(0, Tsub2.length), Csub = C.subarray(row + f - f0).subarray(0, Tsub2.length), Cdiag = C.subarray(row + f - f0 - 1 - width).subarray(0, Tsub2.length), Hsub = H.subarray(row + f - f0).subarray(0, Tsub2.length), Hdiag = H.subarray(row + f - f0 - 1 - width).subarray(0, Tsub2.length), Hleft = H.subarray(row + f - f0 - 1).subarray(0, Tsub2.length);
+ Hleft[0] = 0;
+ for (const [off2, char] of Tsub2.entries()) {
+ const col = off2 + f;
+ let s1 = 0, s2 = 0, consecutive = 0;
+ if (inGap2) {
+ s2 = Hleft[off2] + SCORE_GAP_EXTENTION;
+ } else {
+ s2 = Hleft[off2] + SCORE_GAP_START;
+ }
+ if (pchar2 === char) {
+ s1 = Hdiag[off2] + SCORE_MATCH;
+ let b = Bsub2[off2];
+ consecutive = Cdiag[off2] + 1;
+ if (b === BONUS_BOUNDARY) {
+ consecutive = 1;
+ } else if (consecutive > 1) {
+ b = maxInt16(b, maxInt16(BONUS_CONSECUTIVE, B[col - consecutive + 1]));
+ }
+ if (s1 + b < s2) {
+ s1 += Bsub2[off2];
+ consecutive = 0;
+ } else {
+ s1 += b;
+ }
+ }
+ Csub[off2] = consecutive;
+ inGap2 = s1 < s2;
+ const score = maxInt16(maxInt16(s1, s2), 0);
+ if (pidx2 === M - 1 && (forward && score > maxScore || !forward && score >= maxScore)) {
+ maxScore = score;
+ maxScorePos = col;
+ }
+ Hsub[off2] = score;
+ }
+ }
+ const pos = createPosSet(withPos);
+ let j = f0;
+ if (withPos && pos !== null) {
+ let i = M - 1;
+ j = maxScorePos;
+ let preferMatch = true;
+ while (true) {
+ const I = i * width, j0 = j - f0, s = H[I + j0];
+ let s1 = 0, s2 = 0;
+ if (i > 0 && j >= F[i]) {
+ s1 = H[I - width + j0 - 1];
+ }
+ if (j > F[i]) {
+ s2 = H[I + j0 - 1];
+ }
+ if (s > s1 && (s > s2 || s === s2 && preferMatch)) {
+ pos.add(j);
+ if (i === 0) {
+ break;
+ }
+ i--;
+ }
+ preferMatch = C[I + j0] > 1 || I + width + j0 + 1 < C.length && C[I + width + j0 + 1] > 0;
+ j--;
+ }
+ }
+ return [{ start: j, end: maxScorePos + 1, score: maxScore }, pos];
+};
+function calculateScore(caseSensitive, normalize, text, pattern, sidx, eidx, withPos) {
+ let pidx = 0, score = 0, inGap = false, consecutive = 0, firstBonus = toShort(0);
+ const pos = createPosSet(withPos);
+ let prevCharClass = 0;
+ if (sidx > 0) {
+ prevCharClass = charClassOf(text[sidx - 1]);
+ }
+ for (let idx = sidx; idx < eidx; idx++) {
+ let rune = text[idx];
+ const charClass = charClassOf(rune);
+ if (!caseSensitive) {
+ if (rune >= CAPITAL_A_RUNE && rune <= CAPITAL_Z_RUNE) {
+ rune += 32;
+ } else if (rune > MAX_ASCII) {
+ rune = String.fromCodePoint(rune).toLowerCase().codePointAt(0);
+ }
+ }
+ if (normalize) {
+ rune = normalizeRune(rune);
+ }
+ if (rune === pattern[pidx]) {
+ if (withPos && pos !== null) {
+ pos.add(idx);
+ }
+ score += SCORE_MATCH;
+ let bonus = bonusFor(prevCharClass, charClass);
+ if (consecutive === 0) {
+ firstBonus = bonus;
+ } else {
+ if (bonus === BONUS_BOUNDARY) {
+ firstBonus = bonus;
+ }
+ bonus = maxInt16(maxInt16(bonus, firstBonus), BONUS_CONSECUTIVE);
+ }
+ if (pidx === 0) {
+ score += bonus * BONUS_FIRST_CHAR_MULTIPLIER;
+ } else {
+ score += bonus;
+ }
+ inGap = false;
+ consecutive++;
+ pidx++;
+ } else {
+ if (inGap) {
+ score += SCORE_GAP_EXTENTION;
+ } else {
+ score += SCORE_GAP_START;
+ }
+ inGap = true;
+ consecutive = 0;
+ firstBonus = 0;
+ }
+ prevCharClass = charClass;
+ }
+ return [score, pos];
+}
+function fuzzyMatchV1(caseSensitive, normalize, forward, text, pattern, withPos, slab2) {
+ if (pattern.length === 0) {
+ return [{ start: 0, end: 0, score: 0 }, null];
+ }
+ if (asciiFuzzyIndex(text, pattern, caseSensitive) < 0) {
+ return [{ start: -1, end: -1, score: 0 }, null];
+ }
+ let pidx = 0, sidx = -1, eidx = -1;
+ const lenRunes = text.length;
+ const lenPattern = pattern.length;
+ for (let index = 0; index < lenRunes; index++) {
+ let rune = text[indexAt(index, lenRunes, forward)];
+ if (!caseSensitive) {
+ if (rune >= CAPITAL_A_RUNE && rune <= CAPITAL_Z_RUNE) {
+ rune += 32;
+ } else if (rune > MAX_ASCII) {
+ rune = String.fromCodePoint(rune).toLowerCase().codePointAt(0);
+ }
+ }
+ if (normalize) {
+ rune = normalizeRune(rune);
+ }
+ const pchar = pattern[indexAt(pidx, lenPattern, forward)];
+ if (rune === pchar) {
+ if (sidx < 0) {
+ sidx = index;
+ }
+ pidx++;
+ if (pidx === lenPattern) {
+ eidx = index + 1;
+ break;
+ }
+ }
+ }
+ if (sidx >= 0 && eidx >= 0) {
+ pidx--;
+ for (let index = eidx - 1; index >= sidx; index--) {
+ const tidx = indexAt(index, lenRunes, forward);
+ let rune = text[tidx];
+ if (!caseSensitive) {
+ if (rune >= CAPITAL_A_RUNE && rune <= CAPITAL_Z_RUNE) {
+ rune += 32;
+ } else if (rune > MAX_ASCII) {
+ rune = String.fromCodePoint(rune).toLowerCase().codePointAt(0);
+ }
+ }
+ const pidx_ = indexAt(pidx, lenPattern, forward);
+ const pchar = pattern[pidx_];
+ if (rune === pchar) {
+ pidx--;
+ if (pidx < 0) {
+ sidx = index;
+ break;
+ }
+ }
+ }
+ if (!forward) {
+ const sidxTemp = sidx;
+ sidx = lenRunes - eidx;
+ eidx = lenRunes - sidxTemp;
+ }
+ const [score, pos] = calculateScore(
+ caseSensitive,
+ normalize,
+ text,
+ pattern,
+ sidx,
+ eidx,
+ withPos
+ );
+ return [{ start: sidx, end: eidx, score }, pos];
+ }
+ return [{ start: -1, end: -1, score: 0 }, null];
+};
+const exactMatchNaive = (caseSensitive, normalize, forward, text, pattern, withPos, slab2) => {
+ if (pattern.length === 0) {
+ return [{ start: 0, end: 0, score: 0 }, null];
+ }
+ const lenRunes = text.length;
+ const lenPattern = pattern.length;
+ if (lenRunes < lenPattern) {
+ return [{ start: -1, end: -1, score: 0 }, null];
+ }
+ if (asciiFuzzyIndex(text, pattern, caseSensitive) < 0) {
+ return [{ start: -1, end: -1, score: 0 }, null];
+ }
+ let pidx = 0;
+ let bestPos = -1, bonus = toShort(0), bestBonus = toShort(-1);
+ for (let index = 0; index < lenRunes; index++) {
+ const index_ = indexAt(index, lenRunes, forward);
+ let rune = text[index_];
+ if (!caseSensitive) {
+ if (rune >= CAPITAL_A_RUNE && rune <= CAPITAL_Z_RUNE) {
+ rune += 32;
+ } else if (rune > MAX_ASCII) {
+ rune = String.fromCodePoint(rune).toLowerCase().codePointAt(0);
+ }
+ }
+ if (normalize) {
+ rune = normalizeRune(rune);
+ }
+ const pidx_ = indexAt(pidx, lenPattern, forward);
+ const pchar = pattern[pidx_];
+ if (pchar === rune) {
+ if (pidx_ === 0) {
+ bonus = bonusAt(text, index_);
+ }
+ pidx++;
+ if (pidx === lenPattern) {
+ if (bonus > bestBonus) {
+ bestPos = index;
+ bestBonus = bonus;
+ }
+ if (bonus === BONUS_BOUNDARY) {
+ break;
+ }
+ index -= pidx - 1;
+ pidx = 0;
+ bonus = 0;
+ }
+ } else {
+ index -= pidx;
+ pidx = 0;
+ bonus = 0;
+ }
+ }
+ if (bestPos >= 0) {
+ let sidx = 0, eidx = 0;
+ if (forward) {
+ sidx = bestPos - lenPattern + 1;
+ eidx = bestPos + 1;
+ } else {
+ sidx = lenRunes - (bestPos + 1);
+ eidx = lenRunes - (bestPos - lenPattern + 1);
+ }
+ const [score] = calculateScore(caseSensitive, normalize, text, pattern, sidx, eidx, false);
+ return [{ start: sidx, end: eidx, score }, null];
+ }
+ return [{ start: -1, end: -1, score: 0 }, null];
+};
+const prefixMatch = (caseSensitive, normalize, forward, text, pattern, withPos, slab2) => {
+ if (pattern.length === 0) {
+ return [{ start: 0, end: 0, score: 0 }, null];
+ }
+ let trimmedLen = 0;
+ if (!isWhitespace(pattern[0])) {
+ trimmedLen = whitespacesAtStart(text);
+ }
+ if (text.length - trimmedLen < pattern.length) {
+ return [{ start: -1, end: -1, score: 0 }, null];
+ }
+ for (const [index, r] of pattern.entries()) {
+ let rune = text[trimmedLen + index];
+ if (!caseSensitive) {
+ rune = String.fromCodePoint(rune).toLowerCase().codePointAt(0);
+ }
+ if (normalize) {
+ rune = normalizeRune(rune);
+ }
+ if (rune !== r) {
+ return [{ start: -1, end: -1, score: 0 }, null];
+ }
+ }
+ const lenPattern = pattern.length;
+ const [score] = calculateScore(
+ caseSensitive,
+ normalize,
+ text,
+ pattern,
+ trimmedLen,
+ trimmedLen + lenPattern,
+ false
+ );
+ return [{ start: trimmedLen, end: trimmedLen + lenPattern, score }, null];
+};
+const suffixMatch = (caseSensitive, normalize, forward, text, pattern, withPos, slab2) => {
+ const lenRunes = text.length;
+ let trimmedLen = lenRunes;
+ if (pattern.length === 0 || !isWhitespace(pattern[pattern.length - 1])) {
+ trimmedLen -= whitespacesAtEnd(text);
+ }
+ if (pattern.length === 0) {
+ return [{ start: trimmedLen, end: trimmedLen, score: 0 }, null];
+ }
+ const diff = trimmedLen - pattern.length;
+ if (diff < 0) {
+ return [{ start: -1, end: -1, score: 0 }, null];
+ }
+ for (const [index, r] of pattern.entries()) {
+ let rune = text[index + diff];
+ if (!caseSensitive) {
+ rune = String.fromCodePoint(rune).toLowerCase().codePointAt(0);
+ }
+ if (normalize) {
+ rune = normalizeRune(rune);
+ }
+ if (rune !== r) {
+ return [{ start: -1, end: -1, score: 0 }, null];
+ }
+ }
+ const lenPattern = pattern.length;
+ const sidx = trimmedLen - lenPattern;
+ const eidx = trimmedLen;
+ const [score] = calculateScore(caseSensitive, normalize, text, pattern, sidx, eidx, false);
+ return [{ start: sidx, end: eidx, score }, null];
+};
+const equalMatch = (caseSensitive, normalize, forward, text, pattern, withPos, slab2) => {
+ const lenPattern = pattern.length;
+ if (lenPattern === 0) {
+ return [{ start: -1, end: -1, score: 0 }, null];
+ }
+ let trimmedLen = 0;
+ if (!isWhitespace(pattern[0])) {
+ trimmedLen = whitespacesAtStart(text);
+ }
+ let trimmedEndLen = 0;
+ if (!isWhitespace(pattern[lenPattern - 1])) {
+ trimmedEndLen = whitespacesAtEnd(text);
+ }
+ if (text.length - trimmedLen - trimmedEndLen != lenPattern) {
+ return [{ start: -1, end: -1, score: 0 }, null];
+ }
+ let match = true;
+ if (normalize) {
+ const runes = text;
+ for (const [idx, pchar] of pattern.entries()) {
+ let rune = runes[trimmedLen + idx];
+ if (!caseSensitive) {
+ rune = String.fromCodePoint(rune).toLowerCase().codePointAt(0);
+ }
+ if (normalizeRune(pchar) !== normalizeRune(rune)) {
+ match = false;
+ break;
+ }
+ }
+ } else {
+ let runesStr = runesToStr(text).substring(trimmedLen, text.length - trimmedEndLen);
+ if (!caseSensitive) {
+ runesStr = runesStr.toLowerCase();
+ }
+ match = runesStr === runesToStr(pattern);
+ }
+ if (match) {
+ return [
+ {
+ start: trimmedLen,
+ end: trimmedLen + lenPattern,
+ score: (SCORE_MATCH + BONUS_BOUNDARY) * lenPattern + (BONUS_FIRST_CHAR_MULTIPLIER - 1) * BONUS_BOUNDARY
+ },
+ null
+ ];
+ }
+ return [{ start: -1, end: -1, score: 0 }, null];
+};
+const SLAB_16_SIZE = 100 * 1024;
+const SLAB_32_SIZE = 2048;
+function makeSlab(size16, size32) {
+ return {
+ i16: new Int16Array(size16),
+ i32: new Int32Array(size32)
+ };
+}
+const slab = makeSlab(SLAB_16_SIZE, SLAB_32_SIZE);
+var TermType = /* @__PURE__ */ ((TermType2) => {
+ TermType2[TermType2["Fuzzy"] = 0] = "Fuzzy";
+ TermType2[TermType2["Exact"] = 1] = "Exact";
+ TermType2[TermType2["Prefix"] = 2] = "Prefix";
+ TermType2[TermType2["Suffix"] = 3] = "Suffix";
+ TermType2[TermType2["Equal"] = 4] = "Equal";
+ return TermType2;
+})(TermType || {});
+const termTypeMap = {
+ [0]: fuzzyMatchV2,
+ [1]: exactMatchNaive,
+ [2]: prefixMatch,
+ [3]: suffixMatch,
+ [4]: equalMatch
+};
+function buildPatternForExtendedMatch(fuzzy, caseMode, normalize, str) {
+ let cacheable = true;
+ str = str.trimLeft();
+ {
+ const trimmedAtRightStr = str.trimRight();
+ if (trimmedAtRightStr.endsWith("\\") && str[trimmedAtRightStr.length] === " ") {
+ str = trimmedAtRightStr + " ";
+ } else {
+ str = trimmedAtRightStr;
+ }
+ }
+ let sortable = false;
+ let termSets = [];
+ termSets = parseTerms(fuzzy, caseMode, normalize, str);
+ Loop:
+ for (const termSet of termSets) {
+ for (const [idx, term] of termSet.entries()) {
+ if (!term.inv) {
+ sortable = true;
+ }
+ if (!cacheable || idx > 0 || term.inv || fuzzy && term.typ !== 0 || !fuzzy && term.typ !== 1) {
+ cacheable = false;
+ if (sortable) {
+ break Loop;
+ }
+ }
+ }
+ }
+ return {
+ str,
+ termSets,
+ sortable,
+ cacheable,
+ fuzzy
+ };
+}
+function parseTerms(fuzzy, caseMode, normalize, str) {
+ str = str.replace(/\\ /g, " ");
+ const tokens = str.split(/ +/);
+ const sets = [];
+ let set = [];
+ let switchSet = false;
+ let afterBar = false;
+ for (const token of tokens) {
+ let typ = 0, inv = false, text = token.replace(/\t/g, " ");
+ const lowerText = text.toLowerCase();
+ const caseSensitive = caseMode === "case-sensitive" || caseMode === "smart-case" && text !== lowerText;
+ const normalizeTerm = normalize && lowerText === runesToStr(strToRunes(lowerText).map(normalizeRune));
+ if (!caseSensitive) {
+ text = lowerText;
+ }
+ if (!fuzzy) {
+ typ = 1;
+ }
+ if (set.length > 0 && !afterBar && text === "|") {
+ switchSet = false;
+ afterBar = true;
+ continue;
+ }
+ afterBar = false;
+ if (text.startsWith("!")) {
+ inv = true;
+ typ = 1;
+ text = text.substring(1);
+ }
+ if (text !== "$" && text.endsWith("$")) {
+ typ = 3;
+ text = text.substring(0, text.length - 1);
+ }
+ if (text.startsWith("'")) {
+ if (fuzzy && !inv) {
+ typ = 1;
+ } else {
+ typ = 0;
+ }
+ text = text.substring(1);
+ } else if (text.startsWith("^")) {
+ if (typ === 3) {
+ typ = 4;
+ } else {
+ typ = 2;
+ }
+ text = text.substring(1);
+ }
+ if (text.length > 0) {
+ if (switchSet) {
+ sets.push(set);
+ set = [];
+ }
+ let textRunes = strToRunes(text);
+ if (normalizeTerm) {
+ textRunes = textRunes.map(normalizeRune);
+ }
+ set.push({
+ typ,
+ inv,
+ text: textRunes,
+ caseSensitive,
+ normalize: normalizeTerm
+ });
+ switchSet = true;
+ }
+ }
+ if (set.length > 0) {
+ sets.push(set);
+ }
+ return sets;
+}
+const buildPatternForBasicMatch = (query, casing, normalize) => {
+ let caseSensitive = false;
+ switch (casing) {
+ case "smart-case":
+ if (query.toLowerCase() !== query) {
+ caseSensitive = true;
+ }
+ break;
+ case "case-sensitive":
+ caseSensitive = true;
+ break;
+ case "case-insensitive":
+ query = query.toLowerCase();
+ caseSensitive = false;
+ break;
+ }
+ let queryRunes = strToRunes(query);
+ if (normalize) {
+ queryRunes = queryRunes.map(normalizeRune);
+ }
+ return {
+ queryRunes,
+ caseSensitive
+ };
+};
+function iter(algoFn, tokens, caseSensitive, normalize, forward, pattern, slab2) {
+ for (const part of tokens) {
+ const [res, pos] = algoFn(caseSensitive, normalize, forward, part.text, pattern, true, slab2);
+ if (res.start >= 0) {
+ const sidx = res.start + part.prefixLength;
+ const eidx = res.end + part.prefixLength;
+ if (pos !== null) {
+ const newPos = /* @__PURE__ */ new Set();
+ pos.forEach((v) => newPos.add(part.prefixLength + v));
+ return [[sidx, eidx], res.score, newPos];
+ }
+ return [[sidx, eidx], res.score, pos];
+ }
+ }
+ return [[-1, -1], 0, null];
+}
+function computeExtendedMatch(text, pattern, fuzzyAlgo, forward) {
+ const input = [
+ {
+ text,
+ prefixLength: 0
+ }
+ ];
+ const offsets = [];
+ let totalScore = 0;
+ const allPos = /* @__PURE__ */ new Set();
+ for (const termSet of pattern.termSets) {
+ let offset = [0, 0];
+ let currentScore = 0;
+ let matched = false;
+ for (const term of termSet) {
+ let algoFn = termTypeMap[term.typ];
+ if (term.typ === TermType.Fuzzy) {
+ algoFn = fuzzyAlgo;
+ }
+ const [off, score, pos] = iter(
+ algoFn,
+ input,
+ term.caseSensitive,
+ term.normalize,
+ forward,
+ term.text,
+ slab
+ );
+ const sidx = off[0];
+ if (sidx >= 0) {
+ if (term.inv) {
+ continue;
+ }
+ offset = off;
+ currentScore = score;
+ matched = true;
+ if (pos !== null) {
+ pos.forEach((v) => allPos.add(v));
+ } else {
+ for (let idx = off[0]; idx < off[1]; ++idx) {
+ allPos.add(idx);
+ }
+ }
+ break;
+ } else if (term.inv) {
+ offset = [0, 0];
+ currentScore = 0;
+ matched = true;
+ continue;
+ }
+ }
+ if (matched) {
+ offsets.push(offset);
+ totalScore += currentScore;
+ }
+ }
+ return { offsets, totalScore, allPos };
+}
+function getResultFromScoreMap(scoreMap, limit) {
+ const scoresInDesc = Object.keys(scoreMap).map((v) => parseInt(v, 10)).sort((a, b) => b - a);
+ let result = [];
+ for (const score of scoresInDesc) {
+ result = result.concat(scoreMap[score]);
+ if (result.length >= limit) {
+ break;
+ }
+ }
+ return result;
+}
+function getBasicMatchIter(scoreMap, queryRunes, caseSensitive) {
+ return (idx) => {
+ const itemRunes = this.runesList[idx];
+ if (queryRunes.length > itemRunes.length)
+ return;
+ let [match, positions] = this.algoFn(
+ caseSensitive,
+ this.opts.normalize,
+ this.opts.forward,
+ itemRunes,
+ queryRunes,
+ true,
+ slab
+ );
+ if (match.start === -1)
+ return;
+ if (this.opts.fuzzy === false) {
+ positions = /* @__PURE__ */ new Set();
+ for (let position = match.start; position < match.end; ++position) {
+ positions.add(position);
+ }
+ }
+ const scoreKey = this.opts.sort ? match.score : 0;
+ if (scoreMap[scoreKey] === void 0) {
+ scoreMap[scoreKey] = [];
+ }
+ scoreMap[scoreKey].push(Object.assign({
+ item: this.items[idx],
+ positions: positions != null ? positions : /* @__PURE__ */ new Set()
+ }, match));
+ };
+}
+function getExtendedMatchIter(scoreMap, pattern) {
+ return (idx) => {
+ const runes = this.runesList[idx];
+ const match = computeExtendedMatch(runes, pattern, this.algoFn, this.opts.forward);
+ if (match.offsets.length !== pattern.termSets.length)
+ return;
+ let sidx = -1, eidx = -1;
+ if (match.allPos.size > 0) {
+ sidx = Math.min(...match.allPos);
+ eidx = Math.max(...match.allPos) + 1;
+ }
+ const scoreKey = this.opts.sort ? match.totalScore : 0;
+ if (scoreMap[scoreKey] === void 0) {
+ scoreMap[scoreKey] = [];
+ }
+ scoreMap[scoreKey].push({
+ score: match.totalScore,
+ item: this.items[idx],
+ positions: match.allPos,
+ start: sidx,
+ end: eidx
+ });
+ };
+}
+function basicMatch(query) {
+ const { queryRunes, caseSensitive } = buildPatternForBasicMatch(
+ query,
+ this.opts.casing,
+ this.opts.normalize
+ );
+ const scoreMap = {};
+ const iter2 = getBasicMatchIter.bind(this)(
+ scoreMap,
+ queryRunes,
+ caseSensitive
+ );
+ for (let i = 0, len = this.runesList.length; i < len; ++i) {
+ iter2(i);
+ }
+ return getResultFromScoreMap(scoreMap, this.opts.limit);
+}
+function extendedMatch(query) {
+ const pattern = buildPatternForExtendedMatch(
+ Boolean(this.opts.fuzzy),
+ this.opts.casing,
+ this.opts.normalize,
+ query
+ );
+ const scoreMap = {};
+ const iter2 = getExtendedMatchIter.bind(this)(scoreMap, pattern);
+ for (let i = 0, len = this.runesList.length; i < len; ++i) {
+ iter2(i);
+ }
+ return getResultFromScoreMap(scoreMap, this.opts.limit);
+}
+const defaultOpts = {
+ limit: Infinity,
+ selector: (v) => v,
+ casing: "smart-case",
+ normalize: true,
+ fuzzy: "v2",
+ tiebreakers: [],
+ sort: true,
+ forward: true,
+ match: basicMatch
+};
+class Finder {
+ constructor(list, ...optionsTuple) {
+ this.opts = Object.assign({}, defaultOpts, optionsTuple[0]);
+ this.items = list;
+ this.runesList = list.map((item) => strToRunes(this.opts.selector(item).normalize()));
+ this.algoFn = exactMatchNaive;
+ switch (this.opts.fuzzy) {
+ case "v2":
+ this.algoFn = fuzzyMatchV2;
+ break;
+ case "v1":
+ this.algoFn = fuzzyMatchV1;
+ break;
+ }
+ }
+ find(query) {
+ if (query.length === 0 || this.items.length === 0)
+ return this.items.slice(0, this.opts.limit).map(createResultItemWithEmptyPos);
+ query = query.normalize();
+ let result = this.opts.match.bind(this)(query);
+ return postProcessResultItems(result, this.opts);
+ }
+}
+function createResultItemWithEmptyPos(item) {
+ return ({
+ item,
+ start: -1,
+ end: -1,
+ score: 0,
+ positions: /* @__PURE__ */ new Set()
+ })
+};
+function postProcessResultItems(result, opts) {
+ if (opts.sort) {
+ const { selector } = opts;
+ result.sort((a, b) => {
+ if (a.score !== b.score) {
+ return b.score - a.score;
+ }
+ for (const tiebreaker of opts.tiebreakers) {
+ const diff = tiebreaker(a, b, selector);
+ if (diff !== 0) {
+ return diff;
+ }
+ }
+ return 0;
+ });
+ }
+ if (Number.isFinite(opts.limit)) {
+ result.splice(opts.limit);
+ }
+ return result;
+}
+function byLengthAsc(a, b, selector) {
+ return selector(a.item).length - selector(b.item).length;
+}
+function byStartAsc(a, b) {
+ return a.start - b.start;
+}
diff --git a/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Common/markdown2html.js b/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Common/markdown2html.js
new file mode 100644
index 0000000..04a9b52
--- /dev/null
+++ b/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Common/markdown2html.js
@@ -0,0 +1,117 @@
+.pragma library
+// This exists only because I haven't been able to get linkColor to work with MarkdownText
+// May not be necessary if that's possible tbh.
+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, '&amp;')
+ .replace(/</g, '&lt;')
+ .replace(/>/g, '&gt;');
+ codeBlocks.push(`<pre><code>${escapedCode}</code></pre>`);
+ 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, '&amp;')
+ .replace(/</g, '&lt;')
+ .replace(/>/g, '&gt;');
+ inlineCode.push(`<code>${escapedCode}</code>`);
+ return `\x00INLINECODE${inlineIndex++}\x00`;
+ });
+
+ // Extract plain URLs before escaping so & in query strings is preserved
+ const urls = [];
+ let urlIndex = 0;
+ html = html.replace(/(^|[\s])((?:https?|file):\/\/[^\s]+)/gm, (match, prefix, url) => {
+ urls.push(url);
+ return prefix + `\x00URL${urlIndex++}\x00`;
+ });
+
+ // Escape HTML entities (but not in code blocks or URLs)
+ html = html.replace(/&/g, '&amp;')
+ .replace(/</g, '&lt;')
+ .replace(/>/g, '&gt;');
+
+ // Headers
+ html = html.replace(/^### (.*?)$/gm, '<h3>$1</h3>');
+ html = html.replace(/^## (.*?)$/gm, '<h2>$1</h2>');
+ html = html.replace(/^# (.*?)$/gm, '<h1>$1</h1>');
+
+ // Bold and italic (order matters!)
+ html = html.replace(/\*\*\*(.*?)\*\*\*/g, '<b><i>$1</i></b>');
+ html = html.replace(/\*\*(.*?)\*\*/g, '<b>$1</b>');
+ html = html.replace(/\*(.*?)\*/g, '<i>$1</i>');
+ html = html.replace(/___(.*?)___/g, '<b><i>$1</i></b>');
+ html = html.replace(/__(.*?)__/g, '<b>$1</b>');
+ html = html.replace(/_(.*?)_/g, '<i>$1</i>');
+
+ // Links
+ html = html.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2">$1</a>');
+
+ // Lists
+ html = html.replace(/^\* (.*?)$/gm, '<li>$1</li>');
+ html = html.replace(/^- (.*?)$/gm, '<li>$1</li>');
+ html = html.replace(/^\d+\. (.*?)$/gm, '<li>$1</li>');
+
+ // Wrap consecutive list items in ul/ol tags
+ html = html.replace(/(<li>[\s\S]*?<\/li>\s*)+/g, function(match) {
+ return '<ul>' + match + '</ul>';
+ });
+
+ // 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, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
+ return `<a href="${url}">${display}</a>`;
+ });
+
+ // Restore code blocks and inline code BEFORE line break processing
+ html = html.replace(/\x00CODEBLOCK(\d+)\x00/g, (match, index) => {
+ return codeBlocks[parseInt(index)];
+ });
+
+ html = html.replace(/\x00INLINECODE(\d+)\x00/g, (match, index) => {
+ return inlineCode[parseInt(index)];
+ });
+
+ // Line breaks (after code blocks are restored)
+ html = html.replace(/\n\n/g, '</p><p>');
+ html = html.replace(/\n/g, '<br/>');
+
+ // Wrap in paragraph tags if not already wrapped
+ if (!html.startsWith('<')) {
+ html = '<p>' + html + '</p>';
+ }
+
+ // Clean up the final HTML
+ // Remove <br/> tags immediately before block elements
+ html = html.replace(/<br\/>\s*<pre>/g, '<pre>');
+ html = html.replace(/<br\/>\s*<ul>/g, '<ul>');
+ html = html.replace(/<br\/>\s*<h[1-6]>/g, '<h$1>');
+
+ // Remove empty paragraphs
+ html = html.replace(/<p>\s*<\/p>/g, '');
+ html = html.replace(/<p>\s*<br\/>\s*<\/p>/g, '');
+
+ // Remove excessive line breaks
+ html = html.replace(/(<br\/>){3,}/g, '<br/><br/>'); // Max 2 consecutive line breaks
+ html = html.replace(/(<\/p>)\s*(<p>)/g, '$1$2'); // Remove whitespace between paragraphs
+
+ // Remove leading/trailing whitespace
+ html = html.trim();
+
+ return html;
+}
diff --git a/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Common/settings/Lists.qml b/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Common/settings/Lists.qml
new file mode 100644
index 0000000..57b5f2d
--- /dev/null
+++ b/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Common/settings/Lists.qml
@@ -0,0 +1,94 @@
+pragma Singleton
+pragma ComponentBehavior: Bound
+
+import Quickshell
+import QtQuick
+
+Singleton {
+ id: root
+
+ function init(leftModel, centerModel, rightModel, left, center, right) {
+ const dummy = {
+ widgetId: "dummy",
+ enabled: true,
+ size: 20,
+ selectedGpuIndex: 0,
+ pciId: "",
+ mountPath: "/",
+ minimumWidth: true,
+ showSwap: false,
+ mediaSize: 1,
+ showNetworkIcon: true,
+ showBluetoothIcon: true,
+ showAudioIcon: true,
+ showAudioPercent: true,
+ showVpnIcon: true,
+ showBrightnessIcon: false,
+ showBrightnessPercent: false,
+ showMicIcon: false,
+ showMicPercent: true,
+ showBatteryIcon: false,
+ showPrinterIcon: false,
+ showScreenSharingIcon: true
+ };
+ leftModel.append(dummy);
+ centerModel.append(dummy);
+ rightModel.append(dummy);
+
+ update(leftModel, left);
+ update(centerModel, center);
+ update(rightModel, right);
+ }
+
+ function update(model, order) {
+ model.clear();
+ for (var i = 0; i < order.length; i++) {
+ var isObj = typeof order[i] !== "string";
+ var widgetId = isObj ? order[i].id : order[i];
+ var item = {
+ widgetId: widgetId,
+ enabled: isObj ? order[i].enabled : true
+ };
+ if (isObj && order[i].size !== undefined)
+ item.size = order[i].size;
+ if (isObj && order[i].selectedGpuIndex !== undefined)
+ item.selectedGpuIndex = order[i].selectedGpuIndex;
+ if (isObj && order[i].pciId !== undefined)
+ item.pciId = order[i].pciId;
+ if (isObj && order[i].mountPath !== undefined)
+ item.mountPath = order[i].mountPath;
+ if (isObj && order[i].minimumWidth !== undefined)
+ item.minimumWidth = order[i].minimumWidth;
+ if (isObj && order[i].showSwap !== undefined)
+ item.showSwap = order[i].showSwap;
+ if (isObj && order[i].mediaSize !== undefined)
+ item.mediaSize = order[i].mediaSize;
+ if (isObj && order[i].showNetworkIcon !== undefined)
+ item.showNetworkIcon = order[i].showNetworkIcon;
+ if (isObj && order[i].showBluetoothIcon !== undefined)
+ item.showBluetoothIcon = order[i].showBluetoothIcon;
+ if (isObj && order[i].showAudioIcon !== undefined)
+ item.showAudioIcon = order[i].showAudioIcon;
+ if (isObj && order[i].showAudioPercent !== undefined)
+ item.showAudioPercent = order[i].showAudioPercent;
+ if (isObj && order[i].showVpnIcon !== undefined)
+ item.showVpnIcon = order[i].showVpnIcon;
+ if (isObj && order[i].showBrightnessIcon !== undefined)
+ item.showBrightnessIcon = order[i].showBrightnessIcon;
+ if (isObj && order[i].showBrightnessPercent !== undefined)
+ item.showBrightnessPercent = order[i].showBrightnessPercent;
+ if (isObj && order[i].showMicIcon !== undefined)
+ item.showMicIcon = order[i].showMicIcon;
+ if (isObj && order[i].showMicPercent !== undefined)
+ item.showMicPercent = order[i].showMicPercent;
+ if (isObj && order[i].showBatteryIcon !== undefined)
+ item.showBatteryIcon = order[i].showBatteryIcon;
+ if (isObj && order[i].showPrinterIcon !== undefined)
+ item.showPrinterIcon = order[i].showPrinterIcon;
+ if (isObj && order[i].showScreenSharingIcon !== undefined)
+ item.showScreenSharingIcon = order[i].showScreenSharingIcon;
+
+ model.append(item);
+ }
+ }
+}
diff --git a/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Common/settings/Processes.qml b/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Common/settings/Processes.qml
new file mode 100644
index 0000000..a11abae
--- /dev/null
+++ b/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Common/settings/Processes.qml
@@ -0,0 +1,602 @@
+pragma Singleton
+pragma ComponentBehavior: Bound
+
+import QtQuick
+import Quickshell
+import Quickshell.Io
+import qs.Common
+import qs.Services
+
+Singleton {
+ id: root
+
+ property var settingsRoot: null
+
+ property string greetdPamText: ""
+ property string systemAuthPamText: ""
+ property string commonAuthPamText: ""
+ property string passwordAuthPamText: ""
+ property string systemLoginPamText: ""
+ property string systemLocalLoginPamText: ""
+ property string commonAuthPcPamText: ""
+ property string loginPamText: ""
+ property string dankshellU2fPamText: ""
+ property string u2fKeysText: ""
+
+ property string fingerprintProbeOutput: ""
+ property int fingerprintProbeExitCode: 0
+ property bool fingerprintProbeFinalized: false
+
+ property string pamProbeOutput: ""
+ property bool pamProbeFinalized: false
+
+ readonly property string homeDir: Quickshell.env("HOME") || ""
+ readonly property string u2fKeysPath: homeDir ? homeDir + "/.config/Yubico/u2f_keys" : ""
+ readonly property bool homeU2fKeysDetected: u2fKeysPath !== "" && u2fKeysWatcher.loaded && u2fKeysText.trim() !== ""
+ readonly property bool lockU2fCustomConfigDetected: pamModuleEnabled(dankshellU2fPamText, "pam_u2f")
+ readonly property bool greeterPamHasFprint: greeterPamStackHasModule("pam_fprintd")
+ readonly property bool greeterPamHasU2f: greeterPamStackHasModule("pam_u2f")
+
+ function envFlag(name) {
+ const value = (Quickshell.env(name) || "").trim().toLowerCase();
+ if (value === "1" || value === "true" || value === "yes" || value === "on")
+ return true;
+ if (value === "0" || value === "false" || value === "no" || value === "off")
+ return false;
+ return null;
+ }
+
+ readonly property var forcedFprintAvailable: envFlag("DMS_FORCE_FPRINT_AVAILABLE")
+ readonly property var forcedU2fAvailable: envFlag("DMS_FORCE_U2F_AVAILABLE")
+
+ // --- Derived auth probe state ---
+
+ readonly property bool pamFprintSupportDetected: pamProbeFinalized && pamProbeOutput.includes("pam_fprintd.so:true")
+ readonly property bool pamU2fSupportDetected: pamProbeFinalized && pamProbeOutput.includes("pam_u2f.so:true")
+
+ readonly property string fingerprintProbeState: {
+ if (forcedFprintAvailable !== null)
+ return forcedFprintAvailable ? "ready" : "probe_failed";
+ if (!fingerprintProbeFinalized)
+ return "probe_failed";
+ return parseFingerprintProbe(fingerprintProbeExitCode, fingerprintProbeOutput, pamFprintSupportDetected);
+ }
+
+ // --- Lock fingerprint capabilities ---
+
+ readonly property bool lockFingerprintCanEnable: {
+ if (forcedFprintAvailable !== null)
+ return forcedFprintAvailable;
+ switch (fingerprintProbeState) {
+ case "ready":
+ case "missing_enrollment":
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ readonly property bool lockFingerprintReady: {
+ if (forcedFprintAvailable !== null)
+ return forcedFprintAvailable;
+ return fingerprintProbeState === "ready";
+ }
+
+ readonly property string lockFingerprintReason: {
+ if (forcedFprintAvailable !== null)
+ return forcedFprintAvailable ? "ready" : "probe_failed";
+ return fingerprintProbeState;
+ }
+
+ // --- Greeter fingerprint capabilities ---
+
+ readonly property bool greeterFingerprintCanEnable: {
+ if (forcedFprintAvailable !== null)
+ return forcedFprintAvailable;
+ if (greeterPamHasFprint)
+ return fingerprintProbeState !== "missing_reader";
+ switch (fingerprintProbeState) {
+ case "ready":
+ case "missing_enrollment":
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ readonly property bool greeterFingerprintReady: {
+ if (forcedFprintAvailable !== null)
+ return forcedFprintAvailable;
+ return fingerprintProbeState === "ready";
+ }
+
+ readonly property string greeterFingerprintReason: {
+ if (forcedFprintAvailable !== null)
+ return forcedFprintAvailable ? "ready" : "probe_failed";
+ if (greeterPamHasFprint) {
+ switch (fingerprintProbeState) {
+ case "ready":
+ return "configured_externally";
+ case "missing_enrollment":
+ return "missing_enrollment";
+ case "missing_reader":
+ return "missing_reader";
+ default:
+ return "probe_failed";
+ }
+ }
+ return fingerprintProbeState;
+ }
+
+ readonly property string greeterFingerprintSource: {
+ if (forcedFprintAvailable !== null)
+ return forcedFprintAvailable ? "dms" : "none";
+ if (greeterPamHasFprint)
+ return "pam";
+ switch (fingerprintProbeState) {
+ case "ready":
+ case "missing_enrollment":
+ return "dms";
+ default:
+ return "none";
+ }
+ }
+
+ // --- Lock U2F capabilities ---
+
+ readonly property bool lockU2fReady: {
+ if (forcedU2fAvailable !== null)
+ return forcedU2fAvailable;
+ return lockU2fCustomConfigDetected || homeU2fKeysDetected;
+ }
+
+ readonly property bool lockU2fCanEnable: {
+ if (forcedU2fAvailable !== null)
+ return forcedU2fAvailable;
+ return lockU2fReady || pamU2fSupportDetected;
+ }
+
+ readonly property string lockU2fReason: {
+ if (forcedU2fAvailable !== null)
+ return forcedU2fAvailable ? "ready" : "probe_failed";
+ if (lockU2fReady)
+ return "ready";
+ if (lockU2fCanEnable)
+ return "missing_key_registration";
+ return "missing_pam_support";
+ }
+
+ // --- Greeter U2F capabilities ---
+
+ readonly property bool greeterU2fReady: {
+ if (forcedU2fAvailable !== null)
+ return forcedU2fAvailable;
+ if (greeterPamHasU2f)
+ return true;
+ return homeU2fKeysDetected;
+ }
+
+ readonly property bool greeterU2fCanEnable: {
+ if (forcedU2fAvailable !== null)
+ return forcedU2fAvailable;
+ if (greeterPamHasU2f)
+ return true;
+ return greeterU2fReady || pamU2fSupportDetected;
+ }
+
+ readonly property string greeterU2fReason: {
+ if (forcedU2fAvailable !== null)
+ return forcedU2fAvailable ? "ready" : "probe_failed";
+ if (greeterPamHasU2f)
+ return "configured_externally";
+ if (greeterU2fReady)
+ return "ready";
+ if (greeterU2fCanEnable)
+ return "missing_key_registration";
+ return "missing_pam_support";
+ }
+
+ readonly property string greeterU2fSource: {
+ if (forcedU2fAvailable !== null)
+ return forcedU2fAvailable ? "dms" : "none";
+ if (greeterPamHasU2f)
+ return "pam";
+ if (greeterU2fCanEnable)
+ return "dms";
+ return "none";
+ }
+
+ // --- Aggregates ---
+
+ readonly property bool fprintdAvailable: lockFingerprintReady || greeterFingerprintReady
+ readonly property bool u2fAvailable: lockU2fReady || greeterU2fReady
+
+ // --- Auth detection ---
+
+ readonly property var _fprintProbeCommand: ["sh", "-c", "if command -v fprintd-list >/dev/null 2>&1; then fprintd-list \"${USER:-$(id -un)}\" 2>&1; else printf '__missing_command__\\n'; exit 127; fi"]
+ readonly property var _pamProbeCommand: ["sh", "-c", "for module in pam_fprintd.so pam_u2f.so; do found=false; for dir in /usr/lib64/security /usr/lib/security /lib/security /lib/x86_64-linux-gnu/security /usr/lib/x86_64-linux-gnu/security /usr/lib/aarch64-linux-gnu/security /run/current-system/sw/lib/security; do if [ -f \"$dir/$module\" ]; then found=true; break; fi; done; printf '%s:%s\\n' \"$module\" \"$found\"; done"]
+
+ function detectAuthCapabilities() {
+ if (forcedFprintAvailable === null) {
+ fingerprintProbeFinalized = false;
+ Proc.runCommand("fprint-probe", _fprintProbeCommand, (output, exitCode) => {
+ fingerprintProbeOutput = output || "";
+ fingerprintProbeExitCode = exitCode;
+ fingerprintProbeFinalized = true;
+ }, 0);
+ }
+
+ pamProbeFinalized = false;
+ Proc.runCommand("pam-probe", _pamProbeCommand, (output, _exitCode) => {
+ pamProbeOutput = output || "";
+ pamProbeFinalized = true;
+ }, 0);
+ }
+
+ function detectFprintd() {
+ detectAuthCapabilities();
+ }
+
+ function detectU2f() {
+ detectAuthCapabilities();
+ }
+
+ // --- Auth apply pipeline ---
+
+ property bool authApplyRunning: false
+ property bool authApplyQueued: false
+ property bool authApplyRerunRequested: false
+ property bool authApplyTerminalFallbackFromPrecheck: false
+ property string authApplyStdout: ""
+ property string authApplyStderr: ""
+ property string authApplySudoProbeStderr: ""
+ property string authApplyTerminalFallbackStderr: ""
+
+ function scheduleAuthApply() {
+ if (!settingsRoot || settingsRoot.isGreeterMode)
+ return;
+
+ authApplyQueued = true;
+ if (authApplyRunning) {
+ authApplyRerunRequested = true;
+ return;
+ }
+
+ authApplyDebounce.restart();
+ }
+
+ function beginAuthApply() {
+ if (!authApplyQueued || authApplyRunning || !settingsRoot || settingsRoot.isGreeterMode)
+ return;
+
+ authApplyQueued = false;
+ authApplyRerunRequested = false;
+ authApplyStdout = "";
+ authApplyStderr = "";
+ authApplySudoProbeStderr = "";
+ authApplyTerminalFallbackStderr = "";
+ authApplyTerminalFallbackFromPrecheck = false;
+ authApplyRunning = true;
+ authApplySudoProbeProcess.running = true;
+ }
+
+ 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");
+ authApplyTerminalFallbackStderr = "";
+ authApplyTerminalFallbackProcess.running = true;
+ }
+
+ function finishAuthApply() {
+ const shouldRerun = authApplyQueued || authApplyRerunRequested;
+ authApplyRunning = false;
+ authApplyRerunRequested = false;
+ if (shouldRerun)
+ authApplyDebounce.restart();
+ }
+
+ // --- PAM parsing helpers ---
+
+ function stripPamComment(line) {
+ if (!line)
+ return "";
+ const trimmed = line.trim();
+ if (!trimmed || trimmed.startsWith("#"))
+ return "";
+ const hashIdx = trimmed.indexOf("#");
+ if (hashIdx >= 0)
+ return trimmed.substring(0, hashIdx).trim();
+ return trimmed;
+ }
+
+ function pamModuleEnabled(pamText, moduleName) {
+ if (!pamText || !moduleName)
+ return false;
+ const lines = pamText.split(/\r?\n/);
+ for (let i = 0; i < lines.length; i++) {
+ const line = stripPamComment(lines[i]);
+ if (!line)
+ continue;
+ if (line.includes(moduleName))
+ return true;
+ }
+ return false;
+ }
+
+ function pamTextIncludesFile(pamText, filename) {
+ if (!pamText || !filename)
+ return false;
+ const lines = pamText.split(/\r?\n/);
+ for (let i = 0; i < lines.length; i++) {
+ const line = stripPamComment(lines[i]);
+ if (!line)
+ continue;
+ if (line.includes(filename) && (line.includes("include") || line.includes("substack") || line.startsWith("@include")))
+ return true;
+ }
+ return false;
+ }
+
+ function greeterPamStackHasModule(moduleName) {
+ if (pamModuleEnabled(greetdPamText, moduleName))
+ return true;
+ const includedPamStacks = [["system-auth", systemAuthPamText], ["common-auth", commonAuthPamText], ["password-auth", passwordAuthPamText], ["system-login", systemLoginPamText], ["system-local-login", systemLocalLoginPamText], ["common-auth-pc", commonAuthPcPamText], ["login", loginPamText]];
+ for (let i = 0; i < includedPamStacks.length; i++) {
+ const stack = includedPamStacks[i];
+ if (pamTextIncludesFile(greetdPamText, stack[0]) && pamModuleEnabled(stack[1], moduleName))
+ return true;
+ }
+ return false;
+ }
+
+ // --- Fingerprint probe output parsing ---
+
+ function hasEnrolledFingerprintOutput(output) {
+ const lower = (output || "").toLowerCase();
+ if (lower.includes("has fingers enrolled") || lower.includes("has fingerprints enrolled"))
+ return true;
+ const lines = lower.split(/\r?\n/);
+ for (let i = 0; i < lines.length; i++) {
+ const trimmed = lines[i].trim();
+ if (trimmed.startsWith("finger:"))
+ return true;
+ if (trimmed.startsWith("- ") && trimmed.includes("finger"))
+ return true;
+ }
+ return false;
+ }
+
+ function hasMissingFingerprintEnrollmentOutput(output) {
+ const lower = (output || "").toLowerCase();
+ return lower.includes("no fingers enrolled") || lower.includes("no fingerprints enrolled") || lower.includes("no prints enrolled");
+ }
+
+ function hasMissingFingerprintReaderOutput(output) {
+ const lower = (output || "").toLowerCase();
+ return lower.includes("no devices available") || lower.includes("no device available") || lower.includes("no devices found") || lower.includes("list_devices failed") || lower.includes("no device");
+ }
+
+ function parseFingerprintProbe(exitCode, output, pamFprintDetected) {
+ if (hasEnrolledFingerprintOutput(output))
+ return "ready";
+ if (hasMissingFingerprintEnrollmentOutput(output))
+ return "missing_enrollment";
+ if (hasMissingFingerprintReaderOutput(output))
+ return "missing_reader";
+ if (exitCode === 0)
+ return "missing_enrollment";
+ if (exitCode === 127 || (output || "").includes("__missing_command__"))
+ return "probe_failed";
+ return pamFprintDetected ? "probe_failed" : "missing_pam_support";
+ }
+
+ // --- Qt tools detection ---
+
+ function detectQtTools() {
+ qtToolsDetectionProcess.running = true;
+ }
+
+ function checkPluginSettings() {
+ pluginSettingsCheckProcess.running = true;
+ }
+
+ property var qtToolsDetectionProcess: Process {
+ command: ["sh", "-c", "echo -n 'qt5ct:'; command -v qt5ct >/dev/null && echo 'true' || echo 'false'; echo -n 'qt6ct:'; command -v qt6ct >/dev/null && echo 'true' || echo 'false'; echo -n 'gtk:'; (command -v gsettings >/dev/null || command -v dconf >/dev/null) && echo 'true' || echo 'false'"]
+ running: false
+
+ stdout: StdioCollector {
+ onStreamFinished: {
+ if (!settingsRoot)
+ return;
+ if (text && text.trim()) {
+ const lines = text.trim().split("\n");
+ for (let i = 0; i < lines.length; i++) {
+ const line = lines[i];
+ if (line.startsWith("qt5ct:")) {
+ settingsRoot.qt5ctAvailable = line.split(":")[1] === "true";
+ } else if (line.startsWith("qt6ct:")) {
+ settingsRoot.qt6ctAvailable = line.split(":")[1] === "true";
+ } else if (line.startsWith("gtk:")) {
+ settingsRoot.gtkAvailable = line.split(":")[1] === "true";
+ }
+ }
+ }
+ }
+ }
+ }
+
+ Timer {
+ id: authApplyDebounce
+ interval: 300
+ repeat: false
+ onTriggered: root.beginAuthApply()
+ }
+
+ property var authApplyProcess: Process {
+ command: ["dms", "auth", "sync", "--yes"]
+ running: false
+
+ stdout: StdioCollector {
+ onStreamFinished: root.authApplyStdout = text || ""
+ }
+
+ stderr: StdioCollector {
+ onStreamFinished: root.authApplyStderr = text || ""
+ }
+
+ onExited: exitCode => {
+ const out = (root.authApplyStdout || "").trim();
+ const err = (root.authApplyStderr || "").trim();
+
+ if (exitCode === 0) {
+ 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.detectAuthCapabilities();
+ root.finishAuthApply();
+ return;
+ }
+
+ let details = "";
+ if (out !== "")
+ 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.launchAuthApplyTerminalFallback(false, "");
+ }
+ }
+
+ property var authApplySudoProbeProcess: Process {
+ command: ["sudo", "-n", "true"]
+ running: false
+
+ stderr: StdioCollector {
+ onStreamFinished: root.authApplySudoProbeStderr = text || ""
+ }
+
+ onExited: exitCode => {
+ const err = (root.authApplySudoProbeStderr || "").trim();
+ if (exitCode === 0) {
+ ToastService.showInfo(I18n.tr("Applying authentication changes…"), "", "", "auth-sync");
+ root.authApplyProcess.running = true;
+ return;
+ }
+
+ root.launchAuthApplyTerminalFallback(true, err);
+ }
+ }
+
+ property var authApplyTerminalFallbackProcess: Process {
+ command: ["dms", "auth", "sync", "--terminal", "--yes"]
+ running: false
+
+ stderr: StdioCollector {
+ onStreamFinished: root.authApplyTerminalFallbackStderr = text || ""
+ }
+
+ onExited: exitCode => {
+ if (exitCode === 0) {
+ const message = root.authApplyTerminalFallbackFromPrecheck ? I18n.tr("Terminal opened. Complete authentication setup there; it will close automatically when done.") : I18n.tr("Terminal fallback opened. Complete authentication setup there; it will close automatically when done.");
+ ToastService.showInfo(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.finishAuthApply();
+ }
+ }
+
+ FileView {
+ id: greetdPamWatcher
+ path: "/etc/pam.d/greetd"
+ printErrors: false
+ onLoaded: root.greetdPamText = text()
+ onLoadFailed: root.greetdPamText = ""
+ }
+
+ FileView {
+ id: systemAuthPamWatcher
+ path: "/etc/pam.d/system-auth"
+ printErrors: false
+ onLoaded: root.systemAuthPamText = text()
+ onLoadFailed: root.systemAuthPamText = ""
+ }
+
+ FileView {
+ id: commonAuthPamWatcher
+ path: "/etc/pam.d/common-auth"
+ printErrors: false
+ onLoaded: root.commonAuthPamText = text()
+ onLoadFailed: root.commonAuthPamText = ""
+ }
+
+ FileView {
+ id: passwordAuthPamWatcher
+ path: "/etc/pam.d/password-auth"
+ printErrors: false
+ onLoaded: root.passwordAuthPamText = text()
+ onLoadFailed: root.passwordAuthPamText = ""
+ }
+
+ FileView {
+ id: systemLoginPamWatcher
+ path: "/etc/pam.d/system-login"
+ printErrors: false
+ onLoaded: root.systemLoginPamText = text()
+ onLoadFailed: root.systemLoginPamText = ""
+ }
+
+ FileView {
+ id: systemLocalLoginPamWatcher
+ path: "/etc/pam.d/system-local-login"
+ printErrors: false
+ onLoaded: root.systemLocalLoginPamText = text()
+ onLoadFailed: root.systemLocalLoginPamText = ""
+ }
+
+ FileView {
+ id: commonAuthPcPamWatcher
+ path: "/etc/pam.d/common-auth-pc"
+ printErrors: false
+ onLoaded: root.commonAuthPcPamText = text()
+ onLoadFailed: root.commonAuthPcPamText = ""
+ }
+
+ FileView {
+ id: loginPamWatcher
+ path: "/etc/pam.d/login"
+ printErrors: false
+ onLoaded: root.loginPamText = text()
+ onLoadFailed: root.loginPamText = ""
+ }
+
+ FileView {
+ id: dankshellU2fPamWatcher
+ path: "/etc/pam.d/dankshell-u2f"
+ printErrors: false
+ onLoaded: root.dankshellU2fPamText = text()
+ onLoadFailed: root.dankshellU2fPamText = ""
+ }
+
+ FileView {
+ id: u2fKeysWatcher
+ path: root.u2fKeysPath
+ printErrors: false
+ onLoaded: root.u2fKeysText = text()
+ onLoadFailed: root.u2fKeysText = ""
+ }
+
+ property var pluginSettingsCheckProcess: Process {
+ command: ["test", "-f", settingsRoot?.pluginSettingsPath || ""]
+ running: false
+
+ onExited: function (exitCode) {
+ if (!settingsRoot)
+ return;
+ settingsRoot.pluginSettingsFileExists = (exitCode === 0);
+ }
+ }
+}
diff --git a/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Common/settings/SessionSpec.js b/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Common/settings/SessionSpec.js
new file mode 100644
index 0000000..e2bb4fe
--- /dev/null
+++ b/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Common/settings/SessionSpec.js
@@ -0,0 +1,109 @@
+.pragma library
+
+var SPEC = {
+ isLightMode: { def: false },
+ doNotDisturb: { def: false },
+
+ wallpaperPath: { def: "" },
+ perMonitorWallpaper: { def: false },
+ monitorWallpapers: { def: {} },
+ perModeWallpaper: { def: false },
+ wallpaperPathLight: { def: "" },
+ wallpaperPathDark: { def: "" },
+ monitorWallpapersLight: { def: {} },
+ monitorWallpapersDark: { def: {} },
+ monitorWallpaperFillModes: { def: {} },
+ wallpaperTransition: { def: "fade" },
+ includedTransitions: { def: ["fade", "wipe", "disc", "stripes", "iris bloom", "pixelate", "portal"] },
+
+ wallpaperCyclingEnabled: { def: false },
+ wallpaperCyclingMode: { def: "interval" },
+ wallpaperCyclingInterval: { def: 300 },
+ wallpaperCyclingTime: { def: "06:00" },
+ monitorCyclingSettings: { def: {} },
+
+ nightModeEnabled: { def: false },
+ nightModeTemperature: { def: 4500 },
+ nightModeHighTemperature: { def: 6500 },
+ nightModeAutoEnabled: { def: false },
+ nightModeAutoMode: { def: "time" },
+ nightModeStartHour: { def: 18 },
+ nightModeStartMinute: { def: 0 },
+ nightModeEndHour: { def: 6 },
+ nightModeEndMinute: { def: 0 },
+ latitude: { def: 0.0 },
+ longitude: { def: 0.0 },
+ nightModeUseIPLocation: { def: false },
+ nightModeLocationProvider: { def: "" },
+
+ themeModeAutoEnabled: { def: false },
+ themeModeAutoMode: { def: "time" },
+ themeModeStartHour: { def: 18 },
+ themeModeStartMinute: { def: 0 },
+ themeModeEndHour: { def: 6 },
+ themeModeEndMinute: { def: 0 },
+ themeModeShareGammaSettings: { def: true },
+
+ weatherLocation: { def: "New York, NY" },
+ weatherCoordinates: { def: "40.7128,-74.0060" },
+
+ pinnedApps: { def: [] },
+ barPinnedApps: { def: [] },
+ dockLauncherPosition: { def: 0 },
+ hiddenTrayIds: { def: [] },
+ trayItemOrder: { def: [] },
+ recentColors: { def: [] },
+ showThirdPartyPlugins: { def: false },
+ launchPrefix: { def: "" },
+ lastBrightnessDevice: { def: "" },
+
+ brightnessExponentialDevices: { def: {} },
+ brightnessUserSetValues: { def: {} },
+ brightnessExponentValues: { def: {} },
+
+ selectedGpuIndex: { def: 0 },
+ nvidiaGpuTempEnabled: { def: false },
+ nonNvidiaGpuTempEnabled: { def: false },
+ enabledGpuPciIds: { def: [] },
+
+ wifiDeviceOverride: { def: "" },
+ weatherHourlyDetailed: { def: true },
+
+ hiddenApps: { def: [] },
+ appOverrides: { def: {} },
+ searchAppActions: { def: true },
+
+ vpnLastConnected: { def: "" },
+
+ lastPlayerIdentity: { def: "" },
+
+ deviceMaxVolumes: { def: {} },
+ hiddenOutputDeviceNames: { def: [] },
+ hiddenInputDeviceNames: { def: [] },
+
+ locale: { def: "", onChange: "updateLocale" },
+ timeLocale: { def: "" },
+
+ launcherLastMode: { def: "all" },
+ launcherLastQuery: { def: "" },
+ launcherQueryHistory: { def: [] },
+ appDrawerLastMode: { def: "apps" },
+ niriOverviewLastMode: { def: "apps" },
+
+ settingsSidebarExpandedIds: { def: "," },
+ settingsSidebarCollapsedIds: { def: "," }
+};
+
+function getValidKeys() {
+ return Object.keys(SPEC).concat(["configVersion"]);
+}
+
+function set(root, key, value, saveFn, hooks) {
+ if (!(key in SPEC)) return;
+ root[key] = value;
+ var hookName = SPEC[key].onChange;
+ if (hookName && hooks && hooks[hookName]) {
+ hooks[hookName](root);
+ }
+ saveFn();
+}
diff --git a/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Common/settings/SessionStore.js b/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Common/settings/SessionStore.js
new file mode 100644
index 0000000..c1e2190
--- /dev/null
+++ b/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Common/settings/SessionStore.js
@@ -0,0 +1,100 @@
+.pragma library
+
+ .import "./SessionSpec.js" as SpecModule
+
+function parse(root, jsonObj) {
+ var SPEC = SpecModule.SPEC;
+
+ if (!jsonObj) return;
+
+ for (var k in SPEC) {
+ if (!(k in jsonObj)) {
+ root[k] = SPEC[k].def;
+ }
+ }
+
+ for (var k in jsonObj) {
+ if (!SPEC[k]) continue;
+ var raw = jsonObj[k];
+ var spec = SPEC[k];
+ var coerce = spec.coerce;
+ root[k] = coerce ? (coerce(raw) !== undefined ? coerce(raw) : root[k]) : raw;
+ }
+}
+
+function toJson(root) {
+ var SPEC = SpecModule.SPEC;
+ var out = {};
+ for (var k in SPEC) {
+ if (SPEC[k].persist === false) continue;
+ out[k] = root[k];
+ }
+ out.configVersion = root.sessionConfigVersion;
+ return out;
+}
+
+function migrateToVersion(obj, targetVersion, settingsData) {
+ if (!obj) return null;
+
+ var session = JSON.parse(JSON.stringify(obj));
+ var currentVersion = session.configVersion || 0;
+
+ if (currentVersion >= targetVersion) {
+ return null;
+ }
+
+ if (currentVersion < 2) {
+ console.info("SessionData: Migrating session from version", currentVersion, "to version 2");
+ console.info("SessionData: Importing weather location and coordinates from settings");
+
+ if (settingsData && typeof settingsData !== "undefined") {
+ if (session.weatherLocation === undefined || session.weatherLocation === "New York, NY") {
+ var settingsWeatherLocation = settingsData._legacyWeatherLocation;
+ if (settingsWeatherLocation && settingsWeatherLocation !== "New York, NY") {
+ session.weatherLocation = settingsWeatherLocation;
+ console.info("SessionData: Migrated weatherLocation:", settingsWeatherLocation);
+ }
+ }
+
+ if (session.weatherCoordinates === undefined || session.weatherCoordinates === "40.7128,-74.0060") {
+ var settingsWeatherCoordinates = settingsData._legacyWeatherCoordinates;
+ if (settingsWeatherCoordinates && settingsWeatherCoordinates !== "40.7128,-74.0060") {
+ session.weatherCoordinates = settingsWeatherCoordinates;
+ console.info("SessionData: Migrated weatherCoordinates:", settingsWeatherCoordinates);
+ }
+ }
+ }
+
+ session.configVersion = 2;
+ }
+
+ if (currentVersion < 3) {
+ console.info("SessionData: Migrating session to version 3");
+ session.configVersion = 3;
+ }
+
+ 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/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Common/settings/SettingsSpec.js b/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Common/settings/SettingsSpec.js
new file mode 100644
index 0000000..056545e
--- /dev/null
+++ b/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Common/settings/SettingsSpec.js
@@ -0,0 +1,553 @@
+.pragma library
+
+function percentToUnit(v) {
+ if (v === undefined || v === null) return undefined;
+ return v > 1 ? v / 100 : v;
+}
+
+var SPEC = {
+ currentThemeName: { def: "raveos", onChange: "applyStoredTheme" },
+ currentThemeCategory: { def: "generic" },
+ customThemeFile: { def: "" },
+ registryThemeVariants: { def: {} },
+ matugenScheme: { def: "scheme-tonal-spot", onChange: "regenSystemThemes" },
+ matugenContrast: { def: 0, onChange: "regenSystemThemes" },
+ runUserMatugenTemplates: { def: true, onChange: "regenSystemThemes" },
+ matugenTargetMonitor: { def: "", onChange: "regenSystemThemes" },
+
+ popupTransparency: { def: 1.0, coerce: percentToUnit },
+ dockTransparency: { def: 1.0, coerce: percentToUnit },
+
+ widgetBackgroundColor: { def: "sch" },
+ widgetColorMode: { def: "default" },
+ controlCenterTileColorMode: { def: "primary" },
+ buttonColorMode: { def: "primary" },
+ cornerRadius: { def: 16, onChange: "updateCompositorLayout" },
+ niriLayoutGapsOverride: { def: -1, onChange: "updateCompositorLayout" },
+ niriLayoutRadiusOverride: { def: -1, onChange: "updateCompositorLayout" },
+ niriLayoutBorderSize: { def: -1, onChange: "updateCompositorLayout" },
+ hyprlandLayoutGapsOverride: { def: -1, onChange: "updateCompositorLayout" },
+ hyprlandLayoutRadiusOverride: { def: -1, onChange: "updateCompositorLayout" },
+ hyprlandLayoutBorderSize: { def: -1, onChange: "updateCompositorLayout" },
+ mangoLayoutGapsOverride: { def: -1, onChange: "updateCompositorLayout" },
+ mangoLayoutRadiusOverride: { def: -1, onChange: "updateCompositorLayout" },
+ mangoLayoutBorderSize: { def: -1, onChange: "updateCompositorLayout" },
+
+ firstDayOfWeek: { def: -1 },
+ showWeekNumber: { def: false },
+ use24HourClock: { def: true },
+ showSeconds: { def: false },
+ padHours12Hour: { def: false },
+ useFahrenheit: { def: false },
+ windSpeedUnit: { def: "kmh" },
+ nightModeEnabled: { def: false },
+ animationSpeed: { def: 1 },
+ customAnimationDuration: { def: 500 },
+ syncComponentAnimationSpeeds: { def: true },
+ popoutAnimationSpeed: { def: 1 },
+ popoutCustomAnimationDuration: { def: 150 },
+ modalAnimationSpeed: { def: 1 },
+ modalCustomAnimationDuration: { def: 150 },
+ enableRippleEffects: { def: true },
+ m3ElevationEnabled: { def: true },
+ m3ElevationIntensity: { def: 12 },
+ m3ElevationOpacity: { def: 30 },
+ m3ElevationColorMode: { def: "default" },
+ m3ElevationLightDirection: { def: "top" },
+ m3ElevationCustomColor: { def: "#000000" },
+ modalElevationEnabled: { def: true },
+ popoutElevationEnabled: { def: true },
+ barElevationEnabled: { def: true },
+ blurEnabled: { def: false },
+ blurBorderColor: { def: "outline" },
+ blurBorderCustomColor: { def: "#ffffff" },
+ blurBorderOpacity: { def: 1.0, coerce: percentToUnit },
+ wallpaperFillMode: { def: "Fill" },
+ blurredWallpaperLayer: { def: false },
+ blurWallpaperOnOverview: { def: false },
+
+ showLauncherButton: { def: true },
+ showWorkspaceSwitcher: { def: true },
+ showFocusedWindow: { def: true },
+ showWeather: { def: true },
+ showMusic: { def: true },
+ showClipboard: { def: true },
+ showCpuUsage: { def: true },
+ showMemUsage: { def: true },
+ showCpuTemp: { def: true },
+ showGpuTemp: { def: true },
+ selectedGpuIndex: { def: 0 },
+ enabledGpuPciIds: { def: [] },
+ showSystemTray: { def: true },
+ showClock: { def: true },
+ showNotificationButton: { def: true },
+ showBattery: { def: true },
+ showControlCenterButton: { def: true },
+ showCapsLockIndicator: { def: true },
+
+ controlCenterShowNetworkIcon: { def: true },
+ controlCenterShowBluetoothIcon: { def: true },
+ controlCenterShowAudioIcon: { def: true },
+ controlCenterShowAudioPercent: { def: false },
+ controlCenterShowVpnIcon: { def: true },
+ controlCenterShowBrightnessIcon: { def: false },
+ controlCenterShowBrightnessPercent: { def: false },
+ controlCenterShowMicIcon: { def: false },
+ controlCenterShowMicPercent: { def: false },
+ controlCenterShowBatteryIcon: { def: false },
+ controlCenterShowPrinterIcon: { def: false },
+ controlCenterShowScreenSharingIcon: { def: true },
+
+ showPrivacyButton: { def: true },
+ privacyShowMicIcon: { def: false },
+ privacyShowCameraIcon: { def: false },
+ privacyShowScreenShareIcon: { def: false },
+
+ controlCenterWidgets: {
+ def: [
+ { id: "volumeSlider", enabled: true, width: 50 },
+ { id: "brightnessSlider", enabled: true, width: 50 },
+ { id: "wifi", enabled: true, width: 50 },
+ { id: "bluetooth", enabled: true, width: 50 },
+ { id: "audioOutput", enabled: true, width: 50 },
+ { id: "audioInput", enabled: true, width: 50 },
+ { id: "nightMode", enabled: true, width: 50 },
+ { id: "darkMode", enabled: true, width: 50 }
+ ]
+ },
+
+ showWorkspaceIndex: { def: false },
+ showWorkspaceName: { def: false },
+ showWorkspacePadding: { def: false },
+ workspaceScrolling: { def: false },
+ showWorkspaceApps: { def: false },
+ workspaceDragReorder: { def: true },
+ maxWorkspaceIcons: { def: 3 },
+ workspaceAppIconSizeOffset: { def: 0 },
+ groupWorkspaceApps: { def: true },
+ workspaceFollowFocus: { def: false },
+ showOccupiedWorkspacesOnly: { def: false },
+ reverseScrolling: { def: false },
+ dwlShowAllTags: { def: false },
+ workspaceActiveAppHighlightEnabled: { def: false },
+ workspaceColorMode: { def: "default" },
+ workspaceOccupiedColorMode: { def: "none" },
+ workspaceUnfocusedColorMode: { def: "default" },
+ workspaceUrgentColorMode: { def: "default" },
+ workspaceFocusedBorderEnabled: { def: false },
+ workspaceFocusedBorderColor: { def: "primary" },
+ workspaceFocusedBorderThickness: { def: 2 },
+ workspaceNameIcons: { def: {} },
+ waveProgressEnabled: { def: true },
+ scrollTitleEnabled: { def: true },
+ mediaAdaptiveWidthEnabled: { def: true },
+ audioVisualizerEnabled: { def: true },
+ audioScrollMode: { def: "volume" },
+ audioWheelScrollAmount: { def: 5 },
+ clockCompactMode: { def: false },
+ focusedWindowCompactMode: { def: false },
+ runningAppsCompactMode: { def: true },
+ barMaxVisibleApps: { def: 0 },
+ barMaxVisibleRunningApps: { def: 0 },
+ barShowOverflowBadge: { def: true },
+ appsDockHideIndicators: { def: false },
+ appsDockColorizeActive: { def: false },
+ appsDockActiveColorMode: { def: "primary" },
+ appsDockEnlargeOnHover: { def: false },
+ appsDockEnlargePercentage: { def: 125 },
+ appsDockIconSizePercentage: { def: 100 },
+ keyboardLayoutNameCompactMode: { def: false },
+ runningAppsCurrentWorkspace: { def: true },
+ runningAppsGroupByApp: { def: false },
+ runningAppsCurrentMonitor: { def: false },
+ appIdSubstitutions: {
+ def: [
+ { pattern: "Spotify", replacement: "spotify", type: "exact" },
+ { pattern: "beepertexts", replacement: "beeper", type: "exact" },
+ { pattern: "home assistant desktop", replacement: "homeassistant-desktop", type: "exact" },
+ { pattern: "com.transmissionbt.transmission", replacement: "transmission-gtk", type: "contains" },
+ { pattern: "^steam_app_(\\d+)$", replacement: "steam_icon_$1", type: "regex" }
+ ]
+ },
+ centeringMode: { def: "index" },
+ clockDateFormat: { def: "" },
+ lockDateFormat: { def: "" },
+ greeterRememberLastSession: { def: true },
+ greeterRememberLastUser: { def: true },
+ greeterEnableFprint: { def: false, onChange: "scheduleAuthApply" },
+ greeterEnableU2f: { def: false, onChange: "scheduleAuthApply" },
+ greeterWallpaperPath: { def: "" },
+ greeterUse24HourClock: { def: true },
+ greeterShowSeconds: { def: false },
+ greeterPadHours12Hour: { def: false },
+ greeterLockDateFormat: { def: "" },
+ greeterFontFamily: { def: "" },
+ greeterWallpaperFillMode: { def: "" },
+ mediaSize: { def: 1 },
+
+ appLauncherViewMode: { def: "list" },
+ spotlightModalViewMode: { def: "list" },
+ browserPickerViewMode: { def: "grid" },
+ browserUsageHistory: { def: {} },
+ appPickerViewMode: { def: "grid" },
+ filePickerUsageHistory: { def: {} },
+ sortAppsAlphabetically: { def: false },
+ appLauncherGridColumns: { def: 4 },
+ spotlightCloseNiriOverview: { def: true },
+ rememberLastQuery: { def: false },
+ spotlightSectionViewModes: { def: {} },
+ appDrawerSectionViewModes: { def: {} },
+ niriOverviewOverlayEnabled: { def: true },
+ dankLauncherV2Size: { def: "compact" },
+ dankLauncherV2BorderEnabled: { def: false },
+ dankLauncherV2BorderThickness: { def: 2 },
+ dankLauncherV2BorderColor: { def: "primary" },
+ dankLauncherV2ShowFooter: { def: true },
+ dankLauncherV2UnloadOnClose: { def: false },
+ dankLauncherV2IncludeFilesInAll: { def: false },
+ dankLauncherV2IncludeFoldersInAll: { def: false },
+
+ useAutoLocation: { def: false },
+ weatherEnabled: { def: true },
+
+ networkPreference: { def: "auto" },
+
+ iconTheme: { def: "System Default", onChange: "applyStoredIconTheme" },
+ availableIconThemes: { def: ["System Default"], persist: false },
+ systemDefaultIconTheme: { def: "", persist: false },
+ qt5ctAvailable: { def: false, persist: false },
+ qt6ctAvailable: { def: false, persist: false },
+ gtkAvailable: { def: false, persist: false },
+
+ cursorSettings: { def: { theme: "System Default", size: 24, niri: { hideWhenTyping: false, hideAfterInactiveMs: 0 }, hyprland: { hideOnKeyPress: false, hideOnTouch: false, inactiveTimeout: 0 }, dwl: { cursorHideTimeout: 0 } }, onChange: "updateCompositorCursor" },
+ availableCursorThemes: { def: ["System Default"], persist: false },
+ systemDefaultCursorTheme: { def: "", persist: false },
+
+ launcherLogoMode: { def: "os" },
+ launcherLogoCustomPath: { def: "" },
+ launcherLogoColorOverride: { def: "" },
+ launcherLogoColorInvertOnMode: { def: false },
+ launcherLogoBrightness: { def: 0.5 },
+ launcherLogoContrast: { def: 1 },
+ launcherLogoSizeOffset: { def: 0 },
+
+ fontFamily: { def: "Inter Variable" },
+ monoFontFamily: { def: "Fira Code" },
+ fontWeight: { def: 400 },
+ fontScale: { def: 1.0 },
+
+ notepadUseMonospace: { def: true },
+ notepadFontFamily: { def: "" },
+ notepadFontSize: { def: 14 },
+ notepadShowLineNumbers: { def: false },
+ notepadTransparencyOverride: { def: -1 },
+ notepadLastCustomTransparency: { def: 0.7 },
+
+ soundsEnabled: { def: true },
+ useSystemSoundTheme: { def: false },
+ soundLogin: { def: false },
+ soundNewNotification: { def: true },
+ soundVolumeChanged: { def: true },
+ soundPluggedIn: { def: true },
+
+ acMonitorTimeout: { def: 0 },
+ acLockTimeout: { def: 0 },
+ acSuspendTimeout: { def: 0 },
+ acSuspendBehavior: { def: 0 },
+ acProfileName: { def: "" },
+ batteryMonitorTimeout: { def: 0 },
+ batteryLockTimeout: { def: 0 },
+ batterySuspendTimeout: { def: 0 },
+ batterySuspendBehavior: { def: 0 },
+ batteryProfileName: { def: "" },
+ batteryChargeLimit: { def: 100 },
+ lockBeforeSuspend: { def: false },
+ loginctlLockIntegration: { def: true },
+ fadeToLockEnabled: { def: true },
+ fadeToLockGracePeriod: { def: 5 },
+ fadeToDpmsEnabled: { def: true },
+ fadeToDpmsGracePeriod: { def: 5 },
+ launchPrefix: { def: "" },
+ brightnessDevicePins: { def: {} },
+ wifiNetworkPins: { def: {} },
+ bluetoothDevicePins: { def: {} },
+ audioInputDevicePins: { def: {} },
+ audioOutputDevicePins: { def: {} },
+
+ gtkThemingEnabled: { def: false, onChange: "regenSystemThemes" },
+ qtThemingEnabled: { def: false, onChange: "regenSystemThemes" },
+ syncModeWithPortal: { def: true },
+ terminalsAlwaysDark: { def: false, onChange: "regenSystemThemes" },
+
+ muxType: { def: "tmux" },
+ muxUseCustomCommand: { def: false },
+ muxCustomCommand: { def: "" },
+ muxSessionFilter: { def: "" },
+
+ runDmsMatugenTemplates: { def: true },
+ matugenTemplateGtk: { def: true },
+ matugenTemplateNiri: { def: true },
+ matugenTemplateHyprland: { def: true },
+ matugenTemplateMangowc: { def: true },
+ matugenTemplateQt5ct: { def: true },
+ matugenTemplateQt6ct: { def: true },
+ matugenTemplateFirefox: { def: true },
+ matugenTemplatePywalfox: { def: true },
+ matugenTemplateZenBrowser: { def: true },
+ matugenTemplateVesktop: { def: true },
+ matugenTemplateEquibop: { def: true },
+ matugenTemplateGhostty: { def: true },
+ matugenTemplateKitty: { def: true },
+ matugenTemplateFoot: { def: true },
+ matugenTemplateAlacritty: { def: true },
+ matugenTemplateNeovim: { def: false },
+ matugenTemplateWezterm: { def: true },
+ matugenTemplateDgop: { def: true },
+ matugenTemplateKcolorscheme: { def: true },
+ matugenTemplateVscode: { def: true },
+ matugenTemplateEmacs: { def: true },
+ matugenTemplateZed: { def: true },
+
+ matugenTemplateNeovimSettings: {
+ def: {
+ dark: { baseTheme: "github_dark", harmony: 0.5 },
+ light: { baseTheme: "github_light", harmony: 0.5 }
+ }
+ },
+ matugenTemplateNeovimSetBackground: { def: true },
+
+ showDock: { def: false },
+ dockAutoHide: { def: false },
+ dockSmartAutoHide: { def: false },
+ dockGroupByApp: { def: false },
+ dockRestoreSpecialWorkspaceOnClick: { def: false },
+ dockOpenOnOverview: { def: false },
+ dockPosition: { def: 1 },
+ dockSpacing: { def: 4 },
+ dockBottomGap: { def: 0 },
+ dockMargin: { def: 0 },
+ dockIconSize: { def: 40 },
+ dockIndicatorStyle: { def: "circle" },
+ dockBorderEnabled: { def: false },
+ dockBorderColor: { def: "surfaceText" },
+ dockBorderOpacity: { def: 1.0, coerce: percentToUnit },
+ dockBorderThickness: { def: 1 },
+ dockIsolateDisplays: { def: false },
+ dockLauncherEnabled: { def: false },
+ dockLauncherLogoMode: { def: "apps" },
+ dockLauncherLogoCustomPath: { def: "" },
+ dockLauncherLogoColorOverride: { def: "" },
+ dockLauncherLogoSizeOffset: { def: 0 },
+ dockLauncherLogoBrightness: { def: 0.5, coerce: percentToUnit },
+ dockLauncherLogoContrast: { def: 1, coerce: percentToUnit },
+ dockMaxVisibleApps: { def: 0 },
+ dockMaxVisibleRunningApps: { def: 0 },
+ dockShowOverflowBadge: { def: true },
+
+ notificationOverlayEnabled: { def: false },
+ notificationPopupShadowEnabled: { def: true },
+ notificationPopupPrivacyMode: { def: false },
+ overviewRows: { def: 2, persist: false },
+ overviewColumns: { def: 5, persist: false },
+ overviewScale: { def: 0.16, persist: false },
+
+ modalDarkenBackground: { def: true },
+
+ lockScreenShowPowerActions: { def: true },
+ lockScreenShowSystemIcons: { def: true },
+ lockScreenShowTime: { def: true },
+ lockScreenShowDate: { def: true },
+ lockScreenShowProfileImage: { def: true },
+ lockScreenShowPasswordField: { def: true },
+ lockScreenShowMediaPlayer: { def: true },
+ lockScreenPowerOffMonitorsOnLock: { def: false },
+ lockAtStartup: { def: false },
+ enableFprint: { def: false, onChange: "scheduleAuthApply" },
+ maxFprintTries: { def: 15 },
+ enableU2f: { def: false, onChange: "scheduleAuthApply" },
+ u2fMode: { def: "or" },
+ lockScreenActiveMonitor: { def: "all" },
+ lockScreenInactiveColor: { def: "#000000" },
+ lockScreenNotificationMode: { def: 0 },
+ lockScreenVideoEnabled: { def: false },
+ lockScreenVideoPath: { def: "" },
+ lockScreenVideoCycling: { def: false },
+ hideBrightnessSlider: { def: false },
+
+ notificationTimeoutLow: { def: 5000 },
+ notificationTimeoutNormal: { def: 5000 },
+ notificationTimeoutCritical: { def: 0 },
+ notificationCompactMode: { def: false },
+ notificationPopupPosition: { def: 0 },
+ notificationAnimationSpeed: { def: 1 },
+ notificationCustomAnimationDuration: { def: 400 },
+ notificationHistoryEnabled: { def: true },
+ notificationHistoryMaxCount: { def: 50 },
+ notificationHistoryMaxAgeDays: { def: 7 },
+ notificationHistorySaveLow: { def: true },
+ notificationHistorySaveNormal: { def: true },
+ notificationHistorySaveCritical: { def: true },
+ notificationRules: { def: [] },
+ notificationFocusedMonitor: { def: false },
+
+ osdAlwaysShowValue: { def: false },
+ osdPosition: { def: 5 },
+ osdVolumeEnabled: { def: true },
+ osdMediaVolumeEnabled: { def: true },
+ osdMediaPlaybackEnabled: { def: false },
+ osdBrightnessEnabled: { def: true },
+ osdIdleInhibitorEnabled: { def: true },
+ osdMicMuteEnabled: { def: true },
+ osdCapsLockEnabled: { def: true },
+ osdPowerProfileEnabled: { def: false },
+ osdAudioOutputEnabled: { def: true },
+
+ powerActionConfirm: { def: true },
+ powerActionHoldDuration: { def: 0.5 },
+ powerMenuActions: { def: ["reboot", "logout", "poweroff", "lock", "suspend", "restart"] },
+ powerMenuDefaultAction: { def: "logout" },
+ powerMenuGridLayout: { def: false },
+ customPowerActionLock: { def: "" },
+ customPowerActionLogout: { def: "" },
+ customPowerActionSuspend: { def: "" },
+ customPowerActionHibernate: { def: "" },
+ customPowerActionReboot: { def: "" },
+ customPowerActionPowerOff: { def: "" },
+
+ updaterHideWidget: { def: false },
+ updaterUseCustomCommand: { def: false },
+ updaterCustomCommand: { def: "" },
+ updaterTerminalAdditionalParams: { def: "" },
+
+ displayNameMode: { def: "system" },
+ screenPreferences: { def: {} },
+ showOnLastDisplay: { def: {} },
+ niriOutputSettings: { def: {} },
+ hyprlandOutputSettings: { def: {} },
+ displayProfiles: { def: {} },
+ activeDisplayProfile: { def: {} },
+ displayProfileAutoSelect: { def: false },
+ displayShowDisconnected: { def: false },
+ displaySnapToEdge: { def: true },
+
+ barConfigs: {
+ def: [{
+ id: "default",
+ name: "Main Bar",
+ enabled: true,
+ position: 0,
+ screenPreferences: ["all"],
+ showOnLastDisplay: true,
+ leftWidgets: ["launcherButton", "workspaceSwitcher", "focusedWindow"],
+ centerWidgets: ["music", "clock", "weather"],
+ rightWidgets: ["systemTray", "clipboard", "cpuUsage", "memUsage", "notificationButton", "battery", "controlCenterButton"],
+ spacing: 4,
+ innerPadding: 4,
+ bottomGap: 0,
+ transparency: 1.0,
+ widgetTransparency: 1.0,
+ squareCorners: false,
+ noBackground: false,
+ maximizeWidgetIcons: false,
+ maximizeWidgetText: false,
+ removeWidgetPadding: false,
+ widgetPadding: 8,
+ gothCornersEnabled: false,
+ gothCornerRadiusOverride: false,
+ gothCornerRadiusValue: 12,
+ borderEnabled: false,
+ borderColor: "surfaceText",
+ borderOpacity: 1.0,
+ borderThickness: 1,
+ widgetOutlineEnabled: false,
+ widgetOutlineColor: "primary",
+ widgetOutlineOpacity: 1.0,
+ widgetOutlineThickness: 1,
+ fontScale: 1.0,
+ iconScale: 1.0,
+ autoHide: false,
+ autoHideDelay: 250,
+ showOnWindowsOpen: false,
+ openOnOverview: false,
+ visible: true,
+ popupGapsAuto: true,
+ popupGapsManual: 4,
+ maximizeDetection: true,
+ scrollEnabled: true,
+ scrollXBehavior: "column",
+ scrollYBehavior: "workspace",
+ shadowIntensity: 0,
+ shadowOpacity: 60,
+ shadowColorMode: "default",
+ shadowCustomColor: "#000000",
+ clickThrough: false
+ }], onChange: "updateBarConfigs"
+ },
+
+ desktopClockEnabled: { def: false },
+ desktopClockStyle: { def: "analog" },
+ desktopClockTransparency: { def: 0.8, coerce: percentToUnit },
+ desktopClockColorMode: { def: "primary" },
+ desktopClockCustomColor: { def: "#ffffff" },
+ desktopClockShowDate: { def: true },
+ desktopClockShowAnalogNumbers: { def: false },
+ desktopClockShowAnalogSeconds: { def: true },
+ desktopClockX: { def: -1 },
+ desktopClockY: { def: -1 },
+ desktopClockWidth: { def: 280 },
+ desktopClockHeight: { def: 180 },
+ desktopClockDisplayPreferences: { def: ["all"] },
+
+ systemMonitorEnabled: { def: false },
+ systemMonitorShowHeader: { def: true },
+ systemMonitorTransparency: { def: 0.8, coerce: percentToUnit },
+ systemMonitorColorMode: { def: "primary" },
+ systemMonitorCustomColor: { def: "#ffffff" },
+ systemMonitorShowCpu: { def: true },
+ systemMonitorShowCpuGraph: { def: true },
+ systemMonitorShowCpuTemp: { def: true },
+ systemMonitorShowGpuTemp: { def: false },
+ systemMonitorGpuPciId: { def: "" },
+ systemMonitorShowMemory: { def: true },
+ systemMonitorShowMemoryGraph: { def: true },
+ systemMonitorShowNetwork: { def: true },
+ systemMonitorShowNetworkGraph: { def: true },
+ systemMonitorShowDisk: { def: true },
+ systemMonitorShowTopProcesses: { def: false },
+ systemMonitorTopProcessCount: { def: 3 },
+ systemMonitorTopProcessSortBy: { def: "cpu" },
+ systemMonitorGraphInterval: { def: 60 },
+ systemMonitorLayoutMode: { def: "auto" },
+ systemMonitorX: { def: -1 },
+ systemMonitorY: { def: -1 },
+ systemMonitorWidth: { def: 320 },
+ systemMonitorHeight: { def: 480 },
+ systemMonitorDisplayPreferences: { def: ["all"] },
+ systemMonitorVariants: { def: [] },
+ desktopWidgetPositions: { def: {} },
+ desktopWidgetGridSettings: { def: {} },
+
+ desktopWidgetInstances: { def: [] },
+
+ desktopWidgetGroups: { def: [] },
+
+ builtInPluginSettings: { def: {} },
+ clipboardEnterToPaste: { def: false },
+
+ launcherPluginVisibility: { def: {} },
+ launcherPluginOrder: { def: [] }
+};
+
+function getValidKeys() {
+ return Object.keys(SPEC).filter(function (k) { return SPEC[k].persist !== false; }).concat(["configVersion"]);
+}
+
+function set(root, key, value, saveFn, hooks) {
+ if (!(key in SPEC)) return;
+ root[key] = value;
+ var hookName = SPEC[key].onChange;
+ if (hookName && hooks && hooks[hookName]) {
+ hooks[hookName](root);
+ }
+ saveFn();
+}
diff --git a/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Common/settings/SettingsStore.js b/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Common/settings/SettingsStore.js
new file mode 100644
index 0000000..f88bb03
--- /dev/null
+++ b/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Common/settings/SettingsStore.js
@@ -0,0 +1,275 @@
+.pragma library
+
+ .import "./SettingsSpec.js" as SpecModule
+
+function parse(root, jsonObj) {
+ var SPEC = SpecModule.SPEC;
+
+ if (!jsonObj) return;
+
+ for (var k in SPEC) {
+ if (k === "pluginSettings") continue;
+ // Runtime-only keys are never in the JSON; resetting them here
+ // would wipe values set by detection processes on every reload.
+ if (SPEC[k].persist === false) continue;
+ if (!(k in jsonObj)) {
+ root[k] = SPEC[k].def;
+ }
+ }
+
+ for (var k in jsonObj) {
+ if (!SPEC[k]) continue;
+ if (k === "pluginSettings") continue;
+ var raw = jsonObj[k];
+ var spec = SPEC[k];
+ var coerce = spec.coerce;
+ root[k] = coerce ? (coerce(raw) !== undefined ? coerce(raw) : root[k]) : raw;
+ }
+}
+
+function toJson(root) {
+ var SPEC = SpecModule.SPEC;
+ var out = {};
+ for (var k in SPEC) {
+ if (SPEC[k].persist === false) continue;
+ if (k === "pluginSettings") continue;
+ out[k] = root[k];
+ }
+ out.configVersion = root.settingsConfigVersion;
+ return out;
+}
+
+function migrateToVersion(obj, targetVersion) {
+ if (!obj) return null;
+
+ var settings = JSON.parse(JSON.stringify(obj));
+ var currentVersion = settings.configVersion || 0;
+
+ if (currentVersion >= targetVersion) {
+ return null;
+ }
+
+ if (currentVersion < 2) {
+ console.info("Migrating settings from version", currentVersion, "to version 2");
+
+ if (settings.barConfigs === undefined) {
+ var position = 0;
+ if (settings.dankBarAtBottom !== undefined || settings.topBarAtBottom !== undefined) {
+ var atBottom = settings.dankBarAtBottom !== undefined ? settings.dankBarAtBottom : settings.topBarAtBottom;
+ position = atBottom ? 1 : 0;
+ } else if (settings.dankBarPosition !== undefined) {
+ position = settings.dankBarPosition;
+ }
+
+ var defaultConfig = {
+ id: "default",
+ name: "Main Bar",
+ enabled: true,
+ position: position,
+ screenPreferences: ["all"],
+ showOnLastDisplay: true,
+ leftWidgets: settings.dankBarLeftWidgets || ["launcherButton", "workspaceSwitcher", "focusedWindow"],
+ centerWidgets: settings.dankBarCenterWidgets || ["music", "clock", "weather"],
+ rightWidgets: settings.dankBarRightWidgets || ["systemTray", "clipboard", "cpuUsage", "memUsage", "notificationButton", "battery", "controlCenterButton"],
+ spacing: settings.dankBarSpacing !== undefined ? settings.dankBarSpacing : 4,
+ innerPadding: settings.dankBarInnerPadding !== undefined ? settings.dankBarInnerPadding : 4,
+ bottomGap: settings.dankBarBottomGap !== undefined ? settings.dankBarBottomGap : 0,
+ transparency: settings.dankBarTransparency !== undefined ? settings.dankBarTransparency : 1.0,
+ widgetTransparency: settings.dankBarWidgetTransparency !== undefined ? settings.dankBarWidgetTransparency : 1.0,
+ squareCorners: settings.dankBarSquareCorners !== undefined ? settings.dankBarSquareCorners : false,
+ noBackground: settings.dankBarNoBackground !== undefined ? settings.dankBarNoBackground : false,
+ gothCornersEnabled: settings.dankBarGothCornersEnabled !== undefined ? settings.dankBarGothCornersEnabled : false,
+ gothCornerRadiusOverride: settings.dankBarGothCornerRadiusOverride !== undefined ? settings.dankBarGothCornerRadiusOverride : false,
+ gothCornerRadiusValue: settings.dankBarGothCornerRadiusValue !== undefined ? settings.dankBarGothCornerRadiusValue : 12,
+ borderEnabled: settings.dankBarBorderEnabled !== undefined ? settings.dankBarBorderEnabled : false,
+ borderColor: settings.dankBarBorderColor || "surfaceText",
+ borderOpacity: settings.dankBarBorderOpacity !== undefined ? settings.dankBarBorderOpacity : 1.0,
+ borderThickness: settings.dankBarBorderThickness !== undefined ? settings.dankBarBorderThickness : 1,
+ fontScale: settings.dankBarFontScale !== undefined ? settings.dankBarFontScale : 1.0,
+ autoHide: settings.dankBarAutoHide !== undefined ? settings.dankBarAutoHide : false,
+ autoHideDelay: settings.dankBarAutoHideDelay !== undefined ? settings.dankBarAutoHideDelay : 250,
+ openOnOverview: settings.dankBarOpenOnOverview !== undefined ? settings.dankBarOpenOnOverview : false,
+ visible: settings.dankBarVisible !== undefined ? settings.dankBarVisible : true,
+ popupGapsAuto: settings.popupGapsAuto !== undefined ? settings.popupGapsAuto : true,
+ popupGapsManual: settings.popupGapsManual !== undefined ? settings.popupGapsManual : 4
+ };
+
+ settings.barConfigs = [defaultConfig];
+
+ var legacyKeys = [
+ "dankBarLeftWidgets", "dankBarCenterWidgets", "dankBarRightWidgets",
+ "dankBarWidgetOrder", "dankBarAutoHide", "dankBarAutoHideDelay",
+ "dankBarOpenOnOverview", "dankBarVisible", "dankBarSpacing",
+ "dankBarBottomGap", "dankBarInnerPadding", "dankBarPosition",
+ "dankBarSquareCorners", "dankBarNoBackground", "dankBarGothCornersEnabled",
+ "dankBarGothCornerRadiusOverride", "dankBarGothCornerRadiusValue",
+ "dankBarBorderEnabled", "dankBarBorderColor", "dankBarBorderOpacity",
+ "dankBarBorderThickness", "popupGapsAuto", "popupGapsManual",
+ "dankBarAtBottom", "topBarAtBottom", "dankBarTransparency", "dankBarWidgetTransparency"
+ ];
+
+ for (var i = 0; i < legacyKeys.length; i++) {
+ delete settings[legacyKeys[i]];
+ }
+
+ console.info("Migrated single bar settings to barConfigs");
+ }
+
+ settings.configVersion = 2;
+ }
+
+ if (currentVersion < 3) {
+ console.info("Migrating settings from version", currentVersion, "to version 3");
+ console.info("Per-widget controlCenterButton config now supported via widgetData properties");
+ settings.configVersion = 3;
+ }
+
+ if (currentVersion < 4) {
+ console.info("Migrating settings from version", currentVersion, "to version 4");
+ console.info("Migrating desktop widgets to unified desktopWidgetInstances");
+
+ var instances = [];
+
+ if (settings.desktopClockEnabled) {
+ var clockPositions = {};
+ if (settings.desktopClockX !== undefined && settings.desktopClockX >= 0) {
+ clockPositions["default"] = {
+ x: settings.desktopClockX,
+ y: settings.desktopClockY,
+ width: settings.desktopClockWidth || 280,
+ height: settings.desktopClockHeight || 180
+ };
+ }
+
+ instances.push({
+ id: "dw_clock_primary",
+ widgetType: "desktopClock",
+ name: "Desktop Clock",
+ enabled: true,
+ config: {
+ style: settings.desktopClockStyle || "analog",
+ transparency: settings.desktopClockTransparency !== undefined ? settings.desktopClockTransparency : 0.8,
+ colorMode: settings.desktopClockColorMode || "primary",
+ customColor: settings.desktopClockCustomColor || "#ffffff",
+ showDate: settings.desktopClockShowDate !== false,
+ showAnalogNumbers: settings.desktopClockShowAnalogNumbers || false,
+ showAnalogSeconds: settings.desktopClockShowAnalogSeconds !== false,
+ displayPreferences: settings.desktopClockDisplayPreferences || ["all"]
+ },
+ positions: clockPositions
+ });
+ }
+
+ if (settings.systemMonitorEnabled) {
+ var sysmonPositions = {};
+ if (settings.systemMonitorX !== undefined && settings.systemMonitorX >= 0) {
+ sysmonPositions["default"] = {
+ x: settings.systemMonitorX,
+ y: settings.systemMonitorY,
+ width: settings.systemMonitorWidth || 320,
+ height: settings.systemMonitorHeight || 480
+ };
+ }
+
+ instances.push({
+ id: "dw_sysmon_primary",
+ widgetType: "systemMonitor",
+ name: "System Monitor",
+ enabled: true,
+ config: {
+ showHeader: settings.systemMonitorShowHeader !== false,
+ transparency: settings.systemMonitorTransparency !== undefined ? settings.systemMonitorTransparency : 0.8,
+ colorMode: settings.systemMonitorColorMode || "primary",
+ customColor: settings.systemMonitorCustomColor || "#ffffff",
+ showCpu: settings.systemMonitorShowCpu !== false,
+ showCpuGraph: settings.systemMonitorShowCpuGraph !== false,
+ showCpuTemp: settings.systemMonitorShowCpuTemp !== false,
+ showGpuTemp: settings.systemMonitorShowGpuTemp || false,
+ gpuPciId: settings.systemMonitorGpuPciId || "",
+ showMemory: settings.systemMonitorShowMemory !== false,
+ showMemoryGraph: settings.systemMonitorShowMemoryGraph !== false,
+ showNetwork: settings.systemMonitorShowNetwork !== false,
+ showNetworkGraph: settings.systemMonitorShowNetworkGraph !== false,
+ showDisk: settings.systemMonitorShowDisk !== false,
+ showTopProcesses: settings.systemMonitorShowTopProcesses || false,
+ topProcessCount: settings.systemMonitorTopProcessCount || 3,
+ topProcessSortBy: settings.systemMonitorTopProcessSortBy || "cpu",
+ layoutMode: settings.systemMonitorLayoutMode || "auto",
+ graphInterval: settings.systemMonitorGraphInterval || 60,
+ displayPreferences: settings.systemMonitorDisplayPreferences || ["all"]
+ },
+ positions: sysmonPositions
+ });
+ }
+
+ var variants = settings.systemMonitorVariants || [];
+ for (var i = 0; i < variants.length; i++) {
+ var v = variants[i];
+ instances.push({
+ id: v.id,
+ widgetType: "systemMonitor",
+ name: v.name || ("System Monitor " + (i + 2)),
+ enabled: true,
+ config: v.config || {},
+ positions: v.positions || {}
+ });
+ }
+
+ settings.desktopWidgetInstances = instances;
+ settings.configVersion = 4;
+ }
+
+ if (currentVersion < 5) {
+ console.info("Migrating settings from version", currentVersion, "to version 5");
+ console.info("Moving sensitive data (weather location, coordinates) to session.json");
+
+ delete settings.weatherLocation;
+ delete settings.weatherCoordinates;
+
+ settings.configVersion = 5;
+ }
+
+ if (currentVersion < 6) {
+ console.info("Migrating settings from version", currentVersion, "to version 6");
+
+ if (settings.barElevationEnabled === undefined) {
+ var legacyBars = Array.isArray(settings.barConfigs) ? settings.barConfigs : [];
+ var hadLegacyBarShadowEnabled = false;
+ for (var j = 0; j < legacyBars.length; j++) {
+ var legacyIntensity = Number(legacyBars[j] && legacyBars[j].shadowIntensity);
+ if (!isNaN(legacyIntensity) && legacyIntensity > 0) {
+ hadLegacyBarShadowEnabled = true;
+ break;
+ }
+ }
+ settings.barElevationEnabled = hadLegacyBarShadowEnabled;
+ }
+
+ settings.configVersion = 6;
+ }
+
+ 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/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Common/suncalc.js b/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Common/suncalc.js
new file mode 100644
index 0000000..db55b9a
--- /dev/null
+++ b/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Common/suncalc.js
@@ -0,0 +1,308 @@
+.pragma library
+// Copyright (c) 2025, Vladimir Agafonkin
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without modification, are
+// permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this list of
+// conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice, this list
+// of conditions and the following disclaimer in the documentation and/or other materials
+// provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
+// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+// COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
+// TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+// shortcuts for easier to read formulas
+
+const PI = Math.PI,
+ sin = Math.sin,
+ cos = Math.cos,
+ tan = Math.tan,
+ asin = Math.asin,
+ atan = Math.atan2,
+ acos = Math.acos,
+ rad = PI / 180;
+
+// sun calculations are based on https://aa.quae.nl/en/reken/zonpositie.html formulas
+
+// date/time constants and conversions
+
+const dayMs = 1000 * 60 * 60 * 24,
+ J1970 = 2440588,
+ J2000 = 2451545;
+
+function toJulian(date) { return date.valueOf() / dayMs - 0.5 + J1970; }
+function fromJulian(j) { return new Date((j + 0.5 - J1970) * dayMs); }
+function toDays(date) { return toJulian(date) - J2000; }
+
+
+// general calculations for position
+
+const e = rad * 23.4397; // obliquity of the Earth
+
+function rightAscension(l, b) { return atan(sin(l) * cos(e) - tan(b) * sin(e), cos(l)); }
+function declination(l, b) { return asin(sin(b) * cos(e) + cos(b) * sin(e) * sin(l)); }
+
+function azimuth(H, phi, dec) { return atan(sin(H), cos(H) * sin(phi) - tan(dec) * cos(phi)); }
+function altitude(H, phi, dec) { return asin(sin(phi) * sin(dec) + cos(phi) * cos(dec) * cos(H)); }
+
+function siderealTime(d, lw) { return rad * (280.16 + 360.9856235 * d) - lw; }
+
+function astroRefraction(h) {
+ if (h < 0) // the following formula works for positive altitudes only.
+ h = 0; // if h = -0.08901179 a div/0 would occur.
+
+ // formula 16.4 of "Astronomical Algorithms" 2nd edition by Jean Meeus (Willmann-Bell, Richmond) 1998.
+ // 1.02 / tan(h + 10.26 / (h + 5.10)) h in degrees, result in arc minutes -> converted to rad:
+ return 0.0002967 / Math.tan(h + 0.00312536 / (h + 0.08901179));
+}
+
+// general sun calculations
+
+function solarMeanAnomaly(d) { return rad * (357.5291 + 0.98560028 * d); }
+
+function eclipticLongitude(M) {
+
+ const C = rad * (1.9148 * sin(M) + 0.02 * sin(2 * M) + 0.0003 * sin(3 * M)), // equation of center
+ P = rad * 102.9372; // perihelion of the Earth
+
+ return M + C + P + PI;
+}
+
+function sunCoords(d) {
+
+ const M = solarMeanAnomaly(d),
+ L = eclipticLongitude(M);
+
+ return {
+ dec: declination(L, 0),
+ ra: rightAscension(L, 0)
+ };
+}
+
+
+// calculates sun position for a given date and latitude/longitude
+
+function getPosition(date, lat, lng) {
+
+ const lw = rad * -lng,
+ phi = rad * lat,
+ d = toDays(date),
+
+ c = sunCoords(d),
+ H = siderealTime(d, lw) - c.ra;
+
+ return {
+ azimuth: azimuth(H, phi, c.dec),
+ altitude: altitude(H, phi, c.dec)
+ };
+};
+
+
+// sun times configuration (angle, morning name, evening name)
+
+const times = [
+ [-0.833, 'sunrise', 'sunset'],
+ [-0.3, 'sunriseEnd', 'sunsetStart'],
+ [-6, 'dawn', 'dusk'],
+ [-12, 'nauticalDawn', 'nauticalDusk'],
+ [-18, 'nightEnd', 'night'],
+ [6, 'goldenHourEnd', 'goldenHour']
+];
+
+// 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;
+
+function julianCycle(d, lw) { return Math.round(d - J0 - lw / (2 * PI)); }
+
+function approxTransit(Ht, lw, n) { return J0 + (Ht + lw) / (2 * PI) + n; }
+function solarTransitJ(ds, M, L) { return J2000 + ds + 0.0053 * sin(M) - 0.0069 * sin(2 * L); }
+
+function hourAngle(h, phi, d) { return acos((sin(h) - sin(phi) * sin(d)) / (cos(phi) * cos(d))); }
+function observerAngle(height) { return -2.076 * Math.sqrt(height) / 60; }
+
+// returns set time for the given sun altitude
+function getSetJ(h, lw, phi, dec, n, M, L) {
+
+ const w = hourAngle(h, phi, dec),
+ a = approxTransit(w, lw, n);
+ return solarTransitJ(a, M, L);
+}
+
+
+// calculates sun times for a given date, latitude/longitude, and, optionally,
+// the observer height (in meters) relative to the horizon
+
+function getTimes(date, lat, lng, height) {
+
+ height = height || 0;
+
+ const lw = rad * -lng,
+ phi = rad * lat,
+ dh = observerAngle(height),
+ d = toDays(date),
+ n = julianCycle(d, lw),
+ ds = approxTransit(0, lw, n),
+ M = solarMeanAnomaly(ds),
+ L = eclipticLongitude(M),
+ dec = declination(L, 0),
+ Jnoon = solarTransitJ(ds, M, L);
+
+ const result = {
+ solarNoon: fromJulian(Jnoon),
+ nadir: fromJulian(Jnoon - 0.5)
+ };
+
+ for (const time of times) {
+ const h0 = (time[0] + dh) * rad;
+ const Jset = getSetJ(h0, lw, phi, dec, n, M, L);
+ const Jrise = Jnoon - (Jset - Jnoon);
+
+ result[time[1]] = fromJulian(Jrise);
+ result[time[2]] = fromJulian(Jset);
+ }
+
+ return result;
+};
+
+
+// moon calculations, based on http://aa.quae.nl/en/reken/hemelpositie.html formulas
+
+function moonCoords(d) { // geocentric ecliptic coordinates of the moon
+
+ const L = rad * (218.316 + 13.176396 * d), // ecliptic longitude
+ M = rad * (134.963 + 13.064993 * d), // mean anomaly
+ F = rad * (93.272 + 13.229350 * d), // mean distance
+
+ l = L + rad * 6.289 * sin(M), // longitude
+ b = rad * 5.128 * sin(F), // latitude
+ dt = 385001 - 20905 * cos(M); // distance to the moon in km
+
+ return {
+ ra: rightAscension(l, b),
+ dec: declination(l, b),
+ dist: dt
+ };
+}
+
+function getMoonPosition(date, lat, lng) {
+
+ const lw = rad * -lng,
+ phi = rad * lat,
+ d = toDays(date),
+ c = moonCoords(d),
+ H = siderealTime(d, lw) - c.ra,
+ h = altitude(H, phi, c.dec),
+ // formula 14.1 of "Astronomical Algorithms" 2nd edition by Jean Meeus (Willmann-Bell, Richmond) 1998.
+ pa = atan(sin(H), tan(phi) * cos(c.dec) - sin(c.dec) * cos(H));
+
+ return {
+ azimuth: azimuth(H, phi, c.dec),
+ altitude: h + astroRefraction(h), // altitude correction for refraction,
+ distance: c.dist,
+ parallacticAngle: pa
+ };
+};
+
+
+// calculations for illumination parameters of the moon,
+// based on http://idlastro.gsfc.nasa.gov/ftp/pro/astro/mphase.pro formulas and
+// Chapter 48 of "Astronomical Algorithms" 2nd edition by Jean Meeus (Willmann-Bell, Richmond) 1998.
+
+function getMoonIllumination(date) {
+
+ const d = toDays(date || new Date()),
+ s = sunCoords(d),
+ m = moonCoords(d),
+
+ sdist = 149598000, // distance from Earth to Sun in km
+
+ phi = acos(sin(s.dec) * sin(m.dec) + cos(s.dec) * cos(m.dec) * cos(s.ra - m.ra)),
+ inc = atan(sdist * sin(phi), m.dist - sdist * cos(phi)),
+ angle = atan(cos(s.dec) * sin(s.ra - m.ra), sin(s.dec) * cos(m.dec) -
+ cos(s.dec) * sin(m.dec) * cos(s.ra - m.ra));
+
+ return {
+ fraction: (1 + cos(inc)) / 2,
+ phase: 0.5 + 0.5 * inc * (angle < 0 ? -1 : 1) / Math.PI,
+ 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;
+};