diff options
| author | AlexanderCurl <alexc@alexc.hu> | 2026-04-18 17:46:06 +0100 |
|---|---|---|
| committer | AlexanderCurl <alexc@alexc.hu> | 2026-04-18 17:46:06 +0100 |
| commit | 70ca3fef77ee8bdc6e3ac28589a6fa08c024cc69 (patch) | |
| tree | ab1123d4067c1b086dd6faa7ee4ea643236b565a /raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Common | |
| parent | 5d94c0a7d44a2255b81815a52a7056a94a39842d (diff) | |
| download | RaveOS-PKGBUILD-70ca3fef77ee8bdc6e3ac28589a6fa08c024cc69.tar.gz RaveOS-PKGBUILD-70ca3fef77ee8bdc6e3ac28589a6fa08c024cc69.zip | |
Replaced file structures for themes in the correct format
Diffstat (limited to 'raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Common')
33 files changed, 13511 insertions, 0 deletions
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Common/Anims.qml b/raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Common/Anims.qml new file mode 100644 index 0000000..349e991 --- /dev/null +++ b/raveos-hyprland-theme/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-hyprland-theme/theme-data/DankMaterialShell/quickshell/Common/AppUsageHistoryData.qml b/raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Common/AppUsageHistoryData.qml new file mode 100644 index 0000000..1077f38 --- /dev/null +++ b/raveos-hyprland-theme/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-hyprland-theme/theme-data/DankMaterialShell/quickshell/Common/Appearance.qml b/raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Common/Appearance.qml new file mode 100644 index 0000000..3c22566 --- /dev/null +++ b/raveos-hyprland-theme/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-hyprland-theme/theme-data/DankMaterialShell/quickshell/Common/CacheData.qml b/raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Common/CacheData.qml new file mode 100644 index 0000000..6de7a8b --- /dev/null +++ b/raveos-hyprland-theme/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-hyprland-theme/theme-data/DankMaterialShell/quickshell/Common/CacheUtils.qml b/raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Common/CacheUtils.qml new file mode 100644 index 0000000..1f03f0e --- /dev/null +++ b/raveos-hyprland-theme/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-hyprland-theme/theme-data/DankMaterialShell/quickshell/Common/DankAnim.qml b/raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Common/DankAnim.qml new file mode 100644 index 0000000..63d0c1c --- /dev/null +++ b/raveos-hyprland-theme/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-hyprland-theme/theme-data/DankMaterialShell/quickshell/Common/DankColorAnim.qml b/raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Common/DankColorAnim.qml new file mode 100644 index 0000000..5bd5a9d --- /dev/null +++ b/raveos-hyprland-theme/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-hyprland-theme/theme-data/DankMaterialShell/quickshell/Common/DankSocket.qml b/raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Common/DankSocket.qml new file mode 100644 index 0000000..93471ba --- /dev/null +++ b/raveos-hyprland-theme/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-hyprland-theme/theme-data/DankMaterialShell/quickshell/Common/ElevationShadow.qml b/raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Common/ElevationShadow.qml new file mode 100644 index 0000000..dab80cd --- /dev/null +++ b/raveos-hyprland-theme/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-hyprland-theme/theme-data/DankMaterialShell/quickshell/Common/I18n.qml b/raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Common/I18n.qml new file mode 100644 index 0000000..aae0009 --- /dev/null +++ b/raveos-hyprland-theme/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-hyprland-theme/theme-data/DankMaterialShell/quickshell/Common/KeyUtils.js b/raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Common/KeyUtils.js new file mode 100644 index 0000000..eaf4520 --- /dev/null +++ b/raveos-hyprland-theme/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-hyprland-theme/theme-data/DankMaterialShell/quickshell/Common/KeybindActions.js b/raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Common/KeybindActions.js new file mode 100644 index 0000000..25b1ed0 --- /dev/null +++ b/raveos-hyprland-theme/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-hyprland-theme/theme-data/DankMaterialShell/quickshell/Common/ListViewTransitions.qml b/raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Common/ListViewTransitions.qml new file mode 100644 index 0000000..2ccefc8 --- /dev/null +++ b/raveos-hyprland-theme/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-hyprland-theme/theme-data/DankMaterialShell/quickshell/Common/ModalManager.qml b/raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Common/ModalManager.qml new file mode 100644 index 0000000..75b11f7 --- /dev/null +++ b/raveos-hyprland-theme/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-hyprland-theme/theme-data/DankMaterialShell/quickshell/Common/OSDManager.qml b/raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Common/OSDManager.qml new file mode 100644 index 0000000..06bc07f --- /dev/null +++ b/raveos-hyprland-theme/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-hyprland-theme/theme-data/DankMaterialShell/quickshell/Common/Paths.qml b/raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Common/Paths.qml new file mode 100644 index 0000000..19a4b16 --- /dev/null +++ b/raveos-hyprland-theme/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-hyprland-theme/theme-data/DankMaterialShell/quickshell/Common/PopoutManager.qml b/raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Common/PopoutManager.qml new file mode 100644 index 0000000..47b4eda --- /dev/null +++ b/raveos-hyprland-theme/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-hyprland-theme/theme-data/DankMaterialShell/quickshell/Common/Proc.qml b/raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Common/Proc.qml new file mode 100644 index 0000000..813f938 --- /dev/null +++ b/raveos-hyprland-theme/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-hyprland-theme/theme-data/DankMaterialShell/quickshell/Common/Ref.qml b/raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Common/Ref.qml new file mode 100644 index 0000000..406b50c --- /dev/null +++ b/raveos-hyprland-theme/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-hyprland-theme/theme-data/DankMaterialShell/quickshell/Common/SessionData.qml b/raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Common/SessionData.qml new file mode 100644 index 0000000..13b1359 --- /dev/null +++ b/raveos-hyprland-theme/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-hyprland-theme/theme-data/DankMaterialShell/quickshell/Common/SettingsData.qml b/raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Common/SettingsData.qml new file mode 100644 index 0000000..0790ea4 --- /dev/null +++ b/raveos-hyprland-theme/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-hyprland-theme/theme-data/DankMaterialShell/quickshell/Common/StockThemes.js b/raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Common/StockThemes.js new file mode 100644 index 0000000..311c27f --- /dev/null +++ b/raveos-hyprland-theme/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-hyprland-theme/theme-data/DankMaterialShell/quickshell/Common/Theme.qml b/raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Common/Theme.qml new file mode 100644 index 0000000..a50b688 --- /dev/null +++ b/raveos-hyprland-theme/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-hyprland-theme/theme-data/DankMaterialShell/quickshell/Common/TrayMenuManager.qml b/raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Common/TrayMenuManager.qml new file mode 100644 index 0000000..560d757 --- /dev/null +++ b/raveos-hyprland-theme/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-hyprland-theme/theme-data/DankMaterialShell/quickshell/Common/fzf.js b/raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Common/fzf.js new file mode 100644 index 0000000..94946e6 --- /dev/null +++ b/raveos-hyprland-theme/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-hyprland-theme/theme-data/DankMaterialShell/quickshell/Common/markdown2html.js b/raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Common/markdown2html.js new file mode 100644 index 0000000..04a9b52 --- /dev/null +++ b/raveos-hyprland-theme/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, '&') + .replace(/</g, '<') + .replace(/>/g, '>'); + 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, '&') + .replace(/</g, '<') + .replace(/>/g, '>'); + 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, '&') + .replace(/</g, '<') + .replace(/>/g, '>'); + + // 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, '&').replace(/</g, '<').replace(/>/g, '>'); + 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-hyprland-theme/theme-data/DankMaterialShell/quickshell/Common/settings/Lists.qml b/raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Common/settings/Lists.qml new file mode 100644 index 0000000..57b5f2d --- /dev/null +++ b/raveos-hyprland-theme/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-hyprland-theme/theme-data/DankMaterialShell/quickshell/Common/settings/Processes.qml b/raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Common/settings/Processes.qml new file mode 100644 index 0000000..a11abae --- /dev/null +++ b/raveos-hyprland-theme/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-hyprland-theme/theme-data/DankMaterialShell/quickshell/Common/settings/SessionSpec.js b/raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Common/settings/SessionSpec.js new file mode 100644 index 0000000..e2bb4fe --- /dev/null +++ b/raveos-hyprland-theme/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-hyprland-theme/theme-data/DankMaterialShell/quickshell/Common/settings/SessionStore.js b/raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Common/settings/SessionStore.js new file mode 100644 index 0000000..c1e2190 --- /dev/null +++ b/raveos-hyprland-theme/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-hyprland-theme/theme-data/DankMaterialShell/quickshell/Common/settings/SettingsSpec.js b/raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Common/settings/SettingsSpec.js new file mode 100644 index 0000000..056545e --- /dev/null +++ b/raveos-hyprland-theme/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-hyprland-theme/theme-data/DankMaterialShell/quickshell/Common/settings/SettingsStore.js b/raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Common/settings/SettingsStore.js new file mode 100644 index 0000000..f88bb03 --- /dev/null +++ b/raveos-hyprland-theme/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-hyprland-theme/theme-data/DankMaterialShell/quickshell/Common/suncalc.js b/raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Common/suncalc.js new file mode 100644 index 0000000..db55b9a --- /dev/null +++ b/raveos-hyprland-theme/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; +}; |