diff options
| author | nippy <you@example.com> | 2026-04-18 13:49:56 +0200 |
|---|---|---|
| committer | nippy <you@example.com> | 2026-04-18 13:49:56 +0200 |
| commit | 5d94c0a7d44a2255b81815a52a7056a94a39842d (patch) | |
| tree | 759bdea9645c6a62f9e1e4c001f7d81cccd120d2 /raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Common/Theme.qml | |
| parent | e79cdf210b267f21a186255ce1a4d50029439d54 (diff) | |
| download | RaveOS-PKGBUILD-5d94c0a7d44a2255b81815a52a7056a94a39842d.tar.gz RaveOS-PKGBUILD-5d94c0a7d44a2255b81815a52a7056a94a39842d.zip | |
update Raveos themes
Diffstat (limited to 'raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Common/Theme.qml')
| -rw-r--r-- | raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Common/Theme.qml | 2410 |
1 files changed, 2410 insertions, 0 deletions
diff --git a/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Common/Theme.qml b/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Common/Theme.qml new file mode 100644 index 0000000..a50b688 --- /dev/null +++ b/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Common/Theme.qml @@ -0,0 +1,2410 @@ +pragma Singleton +pragma ComponentBehavior: Bound + +import QtCore +import QtQuick +import Quickshell +import Quickshell.Io +import qs.Common +import qs.Services +import qs.Modules.Greetd +import "StockThemes.js" as StockThemes + +Singleton { + id: root + + readonly property string stateDir: Paths.strip(StandardPaths.writableLocation(StandardPaths.GenericCacheLocation).toString()) + "/DankMaterialShell" + readonly property bool envDisableMatugen: Quickshell.env("DMS_DISABLE_MATUGEN") === "1" || Quickshell.env("DMS_DISABLE_MATUGEN") === "true" + readonly property string defaultFontFamily: "Inter Variable" + readonly property string defaultMonoFontFamily: "Fira Code" + + readonly property real popupDistance: { + if (typeof SettingsData === "undefined") + return 4; + const defaultBar = SettingsData.barConfigs[0] || SettingsData.getBarConfig("default"); + if (!defaultBar) + return 4; + const useAuto = defaultBar.popupGapsAuto ?? true; + const manualValue = defaultBar.popupGapsManual ?? 4; + const spacing = defaultBar.spacing ?? 4; + return useAuto ? Math.max(4, spacing) : manualValue; + } + + property string currentTheme: "raveos" + property string currentThemeCategory: "generic" + property bool isLightMode: typeof SessionData !== "undefined" ? SessionData.isLightMode : false + property bool colorsFileLoadFailed: false + + readonly property string dynamic: "dynamic" + readonly property string custom: "custom" + + readonly property string homeDir: Paths.strip(StandardPaths.writableLocation(StandardPaths.HomeLocation)) + readonly property string configDir: Paths.strip(StandardPaths.writableLocation(StandardPaths.ConfigLocation)) + readonly property string shellDir: Paths.strip(Qt.resolvedUrl(".").toString()).replace("/Common/", "") + readonly property string wallpaperPath: { + if (typeof SessionData === "undefined") + return ""; + + var monitors = SessionData.monitorWallpapers; + if (SessionData.perMonitorWallpaper) { + var screens = Quickshell.screens; + if (screens.length > 0) { + var s = screens[0]; + return monitors[s.name] || (s.model ? monitors[s.model] : "") || SessionData.wallpaperPath; + } + } + + return SessionData.wallpaperPath; + } + readonly property string rawWallpaperPath: { + if (typeof SessionData === "undefined") + return ""; + + var monitors = SessionData.monitorWallpapers; + if (SessionData.perMonitorWallpaper) { + var screens = Quickshell.screens; + if (screens.length > 0) { + var targetMonitor = (typeof SettingsData !== "undefined" && SettingsData.matugenTargetMonitor && SettingsData.matugenTargetMonitor !== "") ? SettingsData.matugenTargetMonitor : screens[0].name; + + var targetMonitorExists = false; + for (var i = 0; i < screens.length; i++) { + if (screens[i].name === targetMonitor) { + targetMonitorExists = true; + break; + } + } + + if (!targetMonitorExists) + targetMonitor = screens[0].name; + + var s = null; + for (var j = 0; j < screens.length; j++) { + if (screens[j].name === targetMonitor) { + s = screens[j]; + break; + } + } + + if (s) + return monitors[s.name] || (s.model ? monitors[s.model] : "") || SessionData.wallpaperPath; + return monitors[targetMonitor] || SessionData.wallpaperPath; + } + } + + return SessionData.wallpaperPath; + } + + property bool matugenAvailable: false + property bool gtkThemingEnabled: typeof SettingsData !== "undefined" ? SettingsData.gtkAvailable : false + property bool qtThemingEnabled: typeof SettingsData !== "undefined" ? (SettingsData.qt5ctAvailable || SettingsData.qt6ctAvailable) : false + property var workerRunning: false + property var pendingThemeRequest: null + + signal matugenCompleted(string mode, string result) + property var matugenColors: ({}) + property var _pendingGenerateParams: null + + property bool themeModeAutomationActive: false + property bool dmsServiceWasDisconnected: true + + readonly property var dank16: { + const raw = matugenColors?.dank16; + if (!raw) + return null; + + const dark = {}; + const light = {}; + const def = {}; + + for (let i = 0; i < 16; i++) { + const key = "color" + i; + const c = raw[key]; + if (!c) + continue; + dark[key] = c.dark; + light[key] = c.light; + def[key] = c.default; + } + + return { + dark, + light, + "default": def + }; + } + property var customThemeData: null + property var customThemeRawData: null + readonly property var currentThemeVariants: customThemeRawData?.variants || null + readonly property string currentThemeId: customThemeRawData?.id || "" + + Component.onCompleted: { + Quickshell.execDetached(["mkdir", "-p", stateDir]); + Proc.runCommand("matugenCheck", ["which", "matugen"], (output, code) => { + matugenAvailable = (code === 0) && !envDisableMatugen; + const isGreeterMode = (typeof SessionData !== "undefined" && SessionData.isGreeterMode); + + if (!matugenAvailable || isGreeterMode) { + return; + } + + if (colorsFileLoadFailed && currentTheme === dynamic && rawWallpaperPath) { + console.info("Theme: Matugen now available, regenerating colors for dynamic theme"); + const isLight = (typeof SessionData !== "undefined" && SessionData.isLightMode); + const iconTheme = (typeof SettingsData !== "undefined" && SettingsData.iconTheme) ? SettingsData.iconTheme : "System Default"; + const selectedMatugenType = (typeof SettingsData !== "undefined" && SettingsData.matugenScheme) ? SettingsData.matugenScheme : "scheme-tonal-spot"; + if (rawWallpaperPath.startsWith("#")) { + setDesiredTheme("hex", rawWallpaperPath, isLight, iconTheme, selectedMatugenType); + } else { + setDesiredTheme("image", rawWallpaperPath, isLight, iconTheme, selectedMatugenType); + } + return; + } + + const isLight = (typeof SessionData !== "undefined" && SessionData.isLightMode); + const iconTheme = (typeof SettingsData !== "undefined" && SettingsData.iconTheme) ? SettingsData.iconTheme : "System Default"; + + if (currentTheme === dynamic) { + if (rawWallpaperPath) { + const selectedMatugenType = (typeof SettingsData !== "undefined" && SettingsData.matugenScheme) ? SettingsData.matugenScheme : "scheme-tonal-spot"; + if (rawWallpaperPath.startsWith("#")) { + setDesiredTheme("hex", rawWallpaperPath, isLight, iconTheme, selectedMatugenType); + } else { + setDesiredTheme("image", rawWallpaperPath, isLight, iconTheme, selectedMatugenType); + } + } + } else if (currentTheme !== "custom") { + const darkTheme = StockThemes.getThemeByName(currentTheme, false); + const lightTheme = StockThemes.getThemeByName(currentTheme, true); + if (darkTheme && darkTheme.primary) { + const stockColors = buildMatugenColorsFromTheme(darkTheme, lightTheme); + const themeData = isLight ? lightTheme : darkTheme; + setDesiredTheme("hex", themeData.primary, isLight, iconTheme, themeData.matugen_type, stockColors); + } + } + }, 0); + if (typeof SessionData !== "undefined") { + SessionData.isLightModeChanged.connect(root.onLightModeChanged); + } + + if (typeof SettingsData !== "undefined" && SettingsData.currentThemeName) { + switchTheme(SettingsData.currentThemeName, false, false); + const currentIsLight = (typeof SessionData !== "undefined") ? SessionData.isLightMode : false; + SettingsData.updateCosmicThemeMode(currentIsLight); + } + + if (typeof SessionData !== "undefined" && SessionData.themeModeAutoEnabled) { + startThemeModeAutomation(); + } + } + + Connections { + target: SessionData + enabled: typeof SessionData !== "undefined" + + function onThemeModeAutoEnabledChanged() { + if (SessionData.themeModeAutoEnabled) { + root.startThemeModeAutomation(); + } else { + root.stopThemeModeAutomation(); + } + } + + function onThemeModeAutoModeChanged() { + if (root.themeModeAutomationActive) { + root.evaluateThemeMode(); + root.syncTimeThemeSchedule(); + root.syncLocationThemeSchedule(); + } + } + + function onThemeModeStartHourChanged() { + if (root.themeModeAutomationActive && !SessionData.themeModeShareGammaSettings) { + root.evaluateThemeMode(); + root.syncTimeThemeSchedule(); + } + } + + function onThemeModeStartMinuteChanged() { + if (root.themeModeAutomationActive && !SessionData.themeModeShareGammaSettings) { + root.evaluateThemeMode(); + root.syncTimeThemeSchedule(); + } + } + + function onThemeModeEndHourChanged() { + if (root.themeModeAutomationActive && !SessionData.themeModeShareGammaSettings) { + root.evaluateThemeMode(); + root.syncTimeThemeSchedule(); + } + } + + function onThemeModeEndMinuteChanged() { + if (root.themeModeAutomationActive && !SessionData.themeModeShareGammaSettings) { + root.evaluateThemeMode(); + root.syncTimeThemeSchedule(); + } + } + + function onThemeModeShareGammaSettingsChanged() { + if (root.themeModeAutomationActive) { + root.evaluateThemeMode(); + root.syncTimeThemeSchedule(); + root.syncLocationThemeSchedule(); + } + } + + function onNightModeStartHourChanged() { + if (root.themeModeAutomationActive && SessionData.themeModeShareGammaSettings) { + root.evaluateThemeMode(); + root.syncTimeThemeSchedule(); + } + } + + function onNightModeStartMinuteChanged() { + if (root.themeModeAutomationActive && SessionData.themeModeShareGammaSettings) { + root.evaluateThemeMode(); + root.syncTimeThemeSchedule(); + } + } + + function onNightModeEndHourChanged() { + if (root.themeModeAutomationActive && SessionData.themeModeShareGammaSettings) { + root.evaluateThemeMode(); + root.syncTimeThemeSchedule(); + } + } + + function onNightModeEndMinuteChanged() { + if (root.themeModeAutomationActive && SessionData.themeModeShareGammaSettings) { + root.evaluateThemeMode(); + root.syncTimeThemeSchedule(); + } + } + + function onLatitudeChanged() { + if (root.themeModeAutomationActive && SessionData.themeModeAutoMode === "location") { + if (!SessionData.nightModeUseIPLocation && SessionData.latitude !== 0.0 && SessionData.longitude !== 0.0 && typeof DMSService !== "undefined") { + DMSService.sendRequest("wayland.gamma.setLocation", { + "latitude": SessionData.latitude, + "longitude": SessionData.longitude + }); + } + root.evaluateThemeMode(); + root.syncLocationThemeSchedule(); + } + } + + function onLongitudeChanged() { + if (root.themeModeAutomationActive && SessionData.themeModeAutoMode === "location") { + if (!SessionData.nightModeUseIPLocation && SessionData.latitude !== 0.0 && SessionData.longitude !== 0.0 && typeof DMSService !== "undefined") { + DMSService.sendRequest("wayland.gamma.setLocation", { + "latitude": SessionData.latitude, + "longitude": SessionData.longitude + }); + } + root.evaluateThemeMode(); + root.syncLocationThemeSchedule(); + } + } + + function onNightModeUseIPLocationChanged() { + if (root.themeModeAutomationActive && SessionData.themeModeAutoMode === "location") { + if (typeof DMSService !== "undefined") { + DMSService.sendRequest("wayland.gamma.setUseIPLocation", { + "use": SessionData.nightModeUseIPLocation + }, response => { + if (!response.error && !SessionData.nightModeUseIPLocation && SessionData.latitude !== 0.0 && SessionData.longitude !== 0.0) { + DMSService.sendRequest("wayland.gamma.setLocation", { + "latitude": SessionData.latitude, + "longitude": SessionData.longitude + }); + } + }); + } + root.evaluateThemeMode(); + root.syncLocationThemeSchedule(); + } + } + } + + // React to gamma backend's isDay state changes for location-based mode + Connections { + target: DisplayService + enabled: typeof DisplayService !== "undefined" && typeof SessionData !== "undefined" && SessionData.themeModeAutoEnabled && SessionData.themeModeAutoMode === "location" && !themeAutoBackendAvailable() + + function onGammaIsDayChanged() { + if (root.isLightMode !== DisplayService.gammaIsDay) { + root.setLightMode(DisplayService.gammaIsDay, true, true); + } + } + } + + Connections { + target: DMSService + enabled: typeof DMSService !== "undefined" && typeof SessionData !== "undefined" + + function onLoginctlEvent(event) { + if (!SessionData.themeModeAutoEnabled) + return; + if (event.event === "unlock" || event.event === "resume") { + if (!themeAutoBackendAvailable()) { + root.evaluateThemeMode(); + return; + } + DMSService.sendRequest("theme.auto.trigger", {}); + } + } + + function onThemeAutoStateUpdate(data) { + if (!SessionData.themeModeAutoEnabled) { + return; + } + applyThemeAutoState(data); + } + + function onConnectionStateChanged() { + if (DMSService.isConnected && SessionData.themeModeAutoMode === "time") { + root.syncTimeThemeSchedule(); + } + + if (DMSService.isConnected && SessionData.themeModeAutoMode === "location") { + root.syncLocationThemeSchedule(); + } + + if (themeAutoBackendAvailable() && SessionData.themeModeAutoEnabled) { + DMSService.sendRequest("theme.auto.getState", null, response => { + if (response && response.result) { + applyThemeAutoState(response.result); + } + }); + } + + if (!SessionData.themeModeAutoEnabled) { + return; + } + + if (DMSService.isConnected && SessionData.themeModeAutoMode === "location") { + if (SessionData.nightModeUseIPLocation) { + DMSService.sendRequest("wayland.gamma.setUseIPLocation", { + "use": true + }, response => { + if (!response.error) { + console.info("Theme automation: IP location enabled after connection"); + } + }); + } else if (SessionData.latitude !== 0.0 && SessionData.longitude !== 0.0) { + DMSService.sendRequest("wayland.gamma.setUseIPLocation", { + "use": false + }, response => { + if (!response.error) { + DMSService.sendRequest("wayland.gamma.setLocation", { + "latitude": SessionData.latitude, + "longitude": SessionData.longitude + }, locationResponse => { + if (locationResponse?.error) { + console.warn("Theme automation: Failed to set location", locationResponse.error); + } + }); + } + }); + } else { + console.warn("Theme automation: No location configured"); + } + } + } + } + + function applyGreeterTheme(themeName) { + switchTheme(themeName, false, false); + if (themeName === dynamic && dynamicColorsFileView.path) { + dynamicColorsFileView.reload(); + } + } + + function getMatugenColor(path, fallback) { + const colorMode = (typeof SessionData !== "undefined" && SessionData.isLightMode) ? "light" : "dark"; + let cur = matugenColors && matugenColors.colors && matugenColors.colors[colorMode]; + for (const part of path.split(".")) { + if (!cur || typeof cur !== "object" || !(part in cur)) + return fallback; + cur = cur[part]; + } + return cur || fallback; + } + + readonly property var currentThemeData: { + if (currentTheme === "custom") { + return customThemeData || StockThemes.getThemeByName("purple", isLightMode); + } else if (currentTheme === dynamic) { + return { + "primary": getMatugenColor("primary", "#42a5f5"), + "primaryText": getMatugenColor("on_primary", "#ffffff"), + "primaryContainer": getMatugenColor("primary_container", "#1976d2"), + "secondary": getMatugenColor("secondary", "#8ab4f8"), + "surface": getMatugenColor("surface", "#1a1c1e"), + "surfaceText": getMatugenColor("on_background", "#e3e8ef"), + "surfaceVariant": getMatugenColor("surface_variant", "#44464f"), + "surfaceVariantText": getMatugenColor("on_surface_variant", "#c4c7c5"), + "surfaceTint": getMatugenColor("surface_tint", "#8ab4f8"), + "background": getMatugenColor("background", "#1a1c1e"), + "backgroundText": getMatugenColor("on_background", "#e3e8ef"), + "outline": getMatugenColor("outline", "#8e918f"), + "surfaceContainer": getMatugenColor("surface_container", "#1e2023"), + "surfaceContainerHigh": getMatugenColor("surface_container_high", "#292b2f"), + "surfaceContainerHighest": getMatugenColor("surface_container_highest", "#343740"), + "error": "#F2B8B5", + "warning": "#FF9800", + "info": "#2196F3", + "success": "#4CAF50" + }; + } else { + return StockThemes.getThemeByName(currentTheme, isLightMode); + } + } + + readonly property var availableMatugenSchemes: [({ + "value": "scheme-tonal-spot", + "label": I18n.tr("Tonal Spot", "matugen color scheme option"), + "description": I18n.tr("Balanced palette with focused accents (default).") + }), ({ + "value": "scheme-vibrant", + "label": I18n.tr("Vibrant", "matugen color scheme option"), + "description": I18n.tr("Lively palette with saturated accents.") + }), ({ + "value": "scheme-content", + "label": I18n.tr("Content", "matugen color scheme option"), + "description": I18n.tr("Derives colors that closely match the underlying image.") + }), ({ + "value": "scheme-expressive", + "label": I18n.tr("Expressive", "matugen color scheme option"), + "description": I18n.tr("Vibrant palette with playful saturation.") + }), ({ + "value": "scheme-fidelity", + "label": I18n.tr("Fidelity", "matugen color scheme option"), + "description": I18n.tr("High-fidelity palette that preserves source hues.") + }), ({ + "value": "scheme-fruit-salad", + "label": I18n.tr("Fruit Salad", "matugen color scheme option"), + "description": I18n.tr("Colorful mix of bright contrasting accents.") + }), ({ + "value": "scheme-monochrome", + "label": I18n.tr("Monochrome", "matugen color scheme option"), + "description": I18n.tr("Minimal palette built around a single hue.") + }), ({ + "value": "scheme-neutral", + "label": I18n.tr("Neutral", "matugen color scheme option"), + "description": I18n.tr("Muted palette with subdued, calming tones.") + }), ({ + "value": "scheme-rainbow", + "label": I18n.tr("Rainbow", "matugen color scheme option"), + "description": I18n.tr("Diverse palette spanning the full spectrum.") + })] + + function getMatugenScheme(value) { + const schemes = availableMatugenSchemes; + for (var i = 0; i < schemes.length; i++) { + if (schemes[i].value === value) + return schemes[i]; + } + return schemes[0]; + } + + property color primary: currentThemeData.primary + property color primaryText: currentThemeData.primaryText + property color primaryContainer: currentThemeData.primaryContainer + property color secondary: currentThemeData.secondary + property color surface: currentThemeData.surface + property color surfaceText: currentThemeData.surfaceText + property color surfaceVariant: currentThemeData.surfaceVariant + property color surfaceVariantText: currentThemeData.surfaceVariantText + property color surfaceTint: currentThemeData.surfaceTint + property color background: currentThemeData.background + property color backgroundText: currentThemeData.backgroundText + property color outline: currentThemeData.outline + property color outlineVariant: currentThemeData.outlineVariant || Qt.rgba(outline.r, outline.g, outline.b, 0.6) + property color surfaceContainer: currentThemeData.surfaceContainer + property color surfaceContainerHigh: currentThemeData.surfaceContainerHigh + property color surfaceContainerHighest: currentThemeData.surfaceContainerHighest || surfaceContainerHigh + + property color onSurface: surfaceText + property color onSurfaceVariant: surfaceVariantText + property color onPrimary: primaryText + property color onSurface_12: Qt.rgba(onSurface.r, onSurface.g, onSurface.b, 0.12) + property color onSurface_38: Qt.rgba(onSurface.r, onSurface.g, onSurface.b, 0.38) + property color onSurfaceVariant_30: Qt.rgba(onSurfaceVariant.r, onSurfaceVariant.g, onSurfaceVariant.b, 0.30) + + property color error: currentThemeData.error || "#F2B8B5" + property color warning: currentThemeData.warning || "#FF9800" + property color info: currentThemeData.info || "#2196F3" + property color tempWarning: "#ff9933" + property color tempDanger: "#ff5555" + property color success: currentThemeData.success || "#4CAF50" + + property color primaryHover: Qt.rgba(primary.r, primary.g, primary.b, 0.12) + property color primaryHoverLight: Qt.rgba(primary.r, primary.g, primary.b, 0.08) + property color primaryPressed: Qt.rgba(primary.r, primary.g, primary.b, 0.16) + property color primarySelected: Qt.rgba(primary.r, primary.g, primary.b, 0.3) + property color primaryBackground: Qt.rgba(primary.r, primary.g, primary.b, 0.04) + + property color secondaryHover: Qt.rgba(secondary.r, secondary.g, secondary.b, 0.08) + + property color surfaceHover: Qt.rgba(surfaceVariant.r, surfaceVariant.g, surfaceVariant.b, 0.08) + property color surfacePressed: Qt.rgba(surfaceVariant.r, surfaceVariant.g, surfaceVariant.b, 0.12) + property color surfaceSelected: Qt.rgba(surfaceVariant.r, surfaceVariant.g, surfaceVariant.b, 0.15) + property color surfaceLight: Qt.rgba(surfaceVariant.r, surfaceVariant.g, surfaceVariant.b, 0.1) + property color surfaceVariantAlpha: Qt.rgba(surfaceVariant.r, surfaceVariant.g, surfaceVariant.b, 0.2) + property color surfaceTextHover: Qt.rgba(surfaceText.r, surfaceText.g, surfaceText.b, 0.08) + property color surfaceTextAlpha: Qt.rgba(surfaceText.r, surfaceText.g, surfaceText.b, 0.3) + property color surfaceTextLight: Qt.rgba(surfaceText.r, surfaceText.g, surfaceText.b, 0.06) + property color surfaceTextMedium: Qt.rgba(surfaceText.r, surfaceText.g, surfaceText.b, 0.7) + + property color outlineButton: Qt.rgba(outline.r, outline.g, outline.b, 0.5) + property color outlineLight: Qt.rgba(outline.r, outline.g, outline.b, 0.05) + property color outlineMedium: Qt.rgba(outline.r, outline.g, outline.b, 0.08) + property color outlineStrong: Qt.rgba(outline.r, outline.g, outline.b, 0.12) + + property color errorHover: Qt.rgba(error.r, error.g, error.b, 0.12) + property color errorPressed: Qt.rgba(error.r, error.g, error.b, 0.16) + + readonly property color ccTileActiveBg: { + switch (SettingsData.controlCenterTileColorMode) { + case "primaryContainer": + return primaryContainer; + case "secondary": + return secondary; + case "surfaceVariant": + return surfaceVariant; + default: + return primary; + } + } + + readonly property color ccTileActiveText: { + switch (SettingsData.controlCenterTileColorMode) { + case "primaryContainer": + return primary; + case "secondary": + return surfaceText; + case "surfaceVariant": + return surfaceText; + default: + return primaryText; + } + } + + readonly property color ccTileInactiveIcon: { + switch (SettingsData.controlCenterTileColorMode) { + case "primaryContainer": + return primary; + case "secondary": + return secondary; + case "surfaceVariant": + return surfaceText; + default: + return primary; + } + } + + readonly property color ccTileRing: { + switch (SettingsData.controlCenterTileColorMode) { + case "primaryContainer": + return Qt.rgba(primary.r, primary.g, primary.b, 0.22); + case "secondary": + return Qt.rgba(surfaceText.r, surfaceText.g, surfaceText.b, 0.22); + case "surfaceVariant": + return Qt.rgba(surfaceText.r, surfaceText.g, surfaceText.b, 0.22); + default: + return Qt.rgba(primaryText.r, primaryText.g, primaryText.b, 0.22); + } + } + + readonly property color buttonBg: { + switch (SettingsData.buttonColorMode) { + case "primaryContainer": + return primaryContainer; + case "secondary": + return secondary; + case "surfaceVariant": + return surfaceVariant; + default: + return primary; + } + } + + readonly property color buttonText: { + switch (SettingsData.buttonColorMode) { + case "primaryContainer": + return primary; + case "secondary": + return surfaceText; + case "surfaceVariant": + return surfaceText; + default: + return primaryText; + } + } + + readonly property color buttonHover: { + switch (SettingsData.buttonColorMode) { + case "primaryContainer": + return Qt.rgba(primary.r, primary.g, primary.b, 0.12); + case "secondary": + return Qt.rgba(surfaceText.r, surfaceText.g, surfaceText.b, 0.12); + case "surfaceVariant": + return Qt.rgba(surfaceText.r, surfaceText.g, surfaceText.b, 0.12); + default: + return primaryHover; + } + } + + readonly property color buttonPressed: { + switch (SettingsData.buttonColorMode) { + case "primaryContainer": + return Qt.rgba(primary.r, primary.g, primary.b, 0.16); + case "secondary": + return Qt.rgba(surfaceText.r, surfaceText.g, surfaceText.b, 0.16); + case "surfaceVariant": + return Qt.rgba(surfaceText.r, surfaceText.g, surfaceText.b, 0.16); + default: + return primaryPressed; + } + } + + property color shadowMedium: Qt.rgba(0, 0, 0, 0.08) + property color shadowStrong: Qt.rgba(0, 0, 0, 0.3) + + readonly property bool elevationEnabled: typeof SettingsData !== "undefined" && (SettingsData.m3ElevationEnabled ?? true) + readonly property real elevationBlurMax: typeof SettingsData !== "undefined" && SettingsData.m3ElevationIntensity !== undefined ? Math.min(128, Math.max(32, SettingsData.m3ElevationIntensity * 2)) : 64 + + readonly property real _elevMult: typeof SettingsData !== "undefined" && SettingsData.m3ElevationIntensity !== undefined ? SettingsData.m3ElevationIntensity / 12 : 1 + readonly property real _opMult: typeof SettingsData !== "undefined" && SettingsData.m3ElevationOpacity !== undefined ? SettingsData.m3ElevationOpacity / 60 : 1 + function normalizeElevationDirection(direction) { + switch (direction) { + case "top": + case "topLeft": + case "topRight": + case "bottom": + case "bottomLeft": + case "bottomRight": + case "left": + case "right": + case "autoBar": + return direction; + default: + return "top"; + } + } + + readonly property string elevationLightDirection: { + if (typeof SettingsData === "undefined" || !SettingsData.m3ElevationLightDirection) + return "top"; + switch (SettingsData.m3ElevationLightDirection) { + case "autoBar": + case "top": + case "topLeft": + case "topRight": + case "bottom": + return SettingsData.m3ElevationLightDirection; + default: + return "top"; + } + } + readonly property real _elevDiagRatio: 0.55 + readonly property string _globalElevationDirForTokens: { + const normalized = normalizeElevationDirection(elevationLightDirection); + return normalized === "autoBar" ? "top" : normalized; + } + readonly property real _elevDirX: { + switch (_globalElevationDirForTokens) { + case "topLeft": + case "bottomLeft": + case "left": + return 1; + case "topRight": + case "bottomRight": + case "right": + return -1; + default: + return 0; + } + } + readonly property real _elevDirY: { + switch (_globalElevationDirForTokens) { + case "bottom": + case "bottomLeft": + case "bottomRight": + return -1; + case "left": + case "right": + return 0; + default: + return 1; + } + } + readonly property real _elevDirXScale: (_globalElevationDirForTokens === "left" || _globalElevationDirForTokens === "right") ? 1 : _elevDiagRatio + + readonly property var elevationLevel1: ({ + blurPx: 4 * _elevMult, + offsetX: 1 * _elevMult * _elevDirXScale * _elevDirX, + offsetY: 1 * _elevMult * _elevDirY, + spreadPx: 0, + alpha: 0.2 * _opMult + }) + readonly property var elevationLevel2: ({ + blurPx: 8 * _elevMult, + offsetX: 4 * _elevMult * _elevDirXScale * _elevDirX, + offsetY: 4 * _elevMult * _elevDirY, + spreadPx: 0, + alpha: 0.25 * _opMult + }) + readonly property var elevationLevel3: ({ + blurPx: 12 * _elevMult, + offsetX: 6 * _elevMult * _elevDirXScale * _elevDirX, + offsetY: 6 * _elevMult * _elevDirY, + spreadPx: 0, + alpha: 0.3 * _opMult + }) + readonly property var elevationLevel4: ({ + blurPx: 16 * _elevMult, + offsetX: 8 * _elevMult * _elevDirXScale * _elevDirX, + offsetY: 8 * _elevMult * _elevDirY, + spreadPx: 0, + alpha: 0.3 * _opMult + }) + readonly property var elevationLevel5: ({ + blurPx: 20 * _elevMult, + offsetX: 10 * _elevMult * _elevDirXScale * _elevDirX, + offsetY: 10 * _elevMult * _elevDirY, + spreadPx: 0, + alpha: 0.3 * _opMult + }) + + function elevationOffsetMagnitude(level, fallback, direction) { + if (!level) { + return fallback !== undefined ? Math.abs(fallback) : 0; + } + const yMag = Math.abs(level.offsetY !== undefined ? level.offsetY : 0); + if (yMag > 0) + return yMag; + const xMag = Math.abs(level.offsetX !== undefined ? level.offsetX : 0); + if (xMag > 0) { + if (direction === "left" || direction === "right") + return xMag; + return xMag / _elevDiagRatio; + } + return fallback !== undefined ? Math.abs(fallback) : 0; + } + + function elevationOffsetXFor(level, direction, fallback) { + const dir = normalizeElevationDirection(direction || elevationLightDirection); + const mag = elevationOffsetMagnitude(level, fallback, dir); + switch (dir) { + case "topLeft": + case "bottomLeft": + return mag * _elevDiagRatio; + case "topRight": + case "bottomRight": + return -mag * _elevDiagRatio; + case "left": + return mag; + case "right": + return -mag; + default: + return 0; + } + } + + function elevationOffsetYFor(level, direction, fallback) { + const dir = normalizeElevationDirection(direction || elevationLightDirection); + const mag = elevationOffsetMagnitude(level, fallback, dir); + switch (dir) { + case "bottom": + case "bottomLeft": + case "bottomRight": + return -mag; + case "left": + case "right": + return 0; + default: + return mag; + } + } + + function elevationOffsetX(level, fallback) { + return elevationOffsetXFor(level, elevationLightDirection, fallback); + } + + function elevationOffsetY(level, fallback) { + return elevationOffsetYFor(level, elevationLightDirection, fallback); + } + + function elevationRenderPadding(level, direction, fallbackOffset, extraPadding, minPadding) { + const dir = direction !== undefined ? direction : elevationLightDirection; + const blur = (level && level.blurPx !== undefined) ? Math.max(0, level.blurPx) : 0; + const spread = (level && level.spreadPx !== undefined) ? Math.max(0, level.spreadPx) : 0; + const fallback = fallbackOffset !== undefined ? fallbackOffset : 0; + const extra = extraPadding !== undefined ? extraPadding : 8; + const minPad = minPadding !== undefined ? minPadding : 16; + const offsetX = Math.abs(elevationOffsetXFor(level, dir, fallback)); + const offsetY = Math.abs(elevationOffsetYFor(level, dir, fallback)); + return Math.max(minPad, blur + spread + Math.max(offsetX, offsetY) + extra); + } + + function elevationShadowColor(level) { + const alpha = (level && level.alpha !== undefined) ? level.alpha : 0.3; + let r = 0; + let g = 0; + let b = 0; + + if (typeof SettingsData !== "undefined") { + const mode = SettingsData.m3ElevationColorMode || "default"; + if (mode === "default") { + r = 0; + g = 0; + b = 0; + } else if (mode === "text") { + r = surfaceText.r; + g = surfaceText.g; + b = surfaceText.b; + } else if (mode === "primary") { + r = primary.r; + g = primary.g; + b = primary.b; + } else if (mode === "surfaceVariant") { + r = surfaceVariant.r; + g = surfaceVariant.g; + b = surfaceVariant.b; + } else if (mode === "custom" && SettingsData.m3ElevationCustomColor) { + const c = Qt.color(SettingsData.m3ElevationCustomColor); + r = c.r; + g = c.g; + b = c.b; + } + } + return Qt.rgba(r, g, b, alpha); + } + function elevationTintOpacity(level) { + if (!level) + return 0; + if (level === elevationLevel1) + return 0.05; + if (level === elevationLevel2) + return 0.08; + if (level === elevationLevel3) + return 0.11; + if (level === elevationLevel4) + return 0.12; + if (level === elevationLevel5) + return 0.14; + return 0.08; + } + + readonly property var animationDurations: [ + { + "shorter": 0, + "short": 0, + "medium": 0, + "long": 0, + "extraLong": 0 + }, + { + "shorter": 50, + "short": 75, + "medium": 150, + "long": 250, + "extraLong": 500 + }, + { + "shorter": 100, + "short": 150, + "medium": 300, + "long": 500, + "extraLong": 1000 + }, + { + "shorter": 150, + "short": 225, + "medium": 450, + "long": 750, + "extraLong": 1500 + }, + { + "shorter": 200, + "short": 300, + "medium": 600, + "long": 1000, + "extraLong": 2000 + } + ] + + readonly property int currentAnimationSpeed: typeof SettingsData !== "undefined" ? SettingsData.animationSpeed : SettingsData.AnimationSpeed.Short + readonly property var currentDurations: animationDurations[currentAnimationSpeed] || animationDurations[SettingsData.AnimationSpeed.Short] + + readonly property int shorterDuration: (typeof SettingsData !== "undefined" && SettingsData.animationSpeed === SettingsData.AnimationSpeed.Custom) ? SettingsData.customAnimationDuration : currentDurations.shorter + readonly property int shortDuration: (typeof SettingsData !== "undefined" && SettingsData.animationSpeed === SettingsData.AnimationSpeed.Custom) ? SettingsData.customAnimationDuration : currentDurations.short + readonly property int mediumDuration: (typeof SettingsData !== "undefined" && SettingsData.animationSpeed === SettingsData.AnimationSpeed.Custom) ? SettingsData.customAnimationDuration : currentDurations.medium + readonly property int longDuration: (typeof SettingsData !== "undefined" && SettingsData.animationSpeed === SettingsData.AnimationSpeed.Custom) ? SettingsData.customAnimationDuration : currentDurations.long + readonly property int extraLongDuration: (typeof SettingsData !== "undefined" && SettingsData.animationSpeed === SettingsData.AnimationSpeed.Custom) ? SettingsData.customAnimationDuration : currentDurations.extraLong + readonly property int standardEasing: Easing.OutCubic + readonly property int emphasizedEasing: Easing.OutQuart + + readonly property var expressiveCurves: { + "emphasized": [0.05, 0, 2 / 15, 0.06, 1 / 6, 0.4, 5 / 24, 0.82, 0.25, 1, 1, 1], + "emphasizedAccel": [0.3, 0, 0.8, 0.15, 1, 1], + "emphasizedDecel": [0.05, 0.7, 0.1, 1, 1, 1], + "standard": [0.2, 0, 0, 1, 1, 1], + "standardAccel": [0.3, 0, 1, 1, 1, 1], + "standardDecel": [0, 0, 0, 1, 1, 1], + "expressiveFastSpatial": [0.42, 1.67, 0.21, 0.9, 1, 1], + "expressiveDefaultSpatial": [0.38, 1.21, 0.22, 1, 1, 1], + "expressiveEffects": [0.34, 0.8, 0.34, 1, 1, 1] + } + + readonly property var animationPresetDurations: { + "none": 0, + "short": 250, + "medium": 500, + "long": 750 + } + + readonly property int currentAnimationBaseDuration: { + if (typeof SettingsData === "undefined") + return 500; + + if (SettingsData.animationSpeed === SettingsData.AnimationSpeed.Custom) { + return SettingsData.customAnimationDuration; + } + + const presetMap = [0, 250, 500, 750]; + return presetMap[SettingsData.animationSpeed] !== undefined ? presetMap[SettingsData.animationSpeed] : 500; + } + + readonly property var expressiveDurations: { + if (typeof SettingsData === "undefined") { + return { + "fast": 200, + "normal": 400, + "large": 600, + "extraLarge": 1000, + "expressiveFastSpatial": 350, + "expressiveDefaultSpatial": 500, + "expressiveEffects": 200 + }; + } + + const baseDuration = currentAnimationBaseDuration; + return { + "fast": baseDuration * 0.4, + "normal": baseDuration * 0.8, + "large": baseDuration * 1.2, + "extraLarge": baseDuration * 2.0, + "expressiveFastSpatial": baseDuration * 0.7, + "expressiveDefaultSpatial": baseDuration, + "expressiveEffects": baseDuration * 0.4 + }; + } + + readonly property int notificationAnimationBaseDuration: { + if (typeof SettingsData === "undefined") + return 200; + if (SettingsData.notificationAnimationSpeed === SettingsData.AnimationSpeed.None) + return 0; + if (SettingsData.notificationAnimationSpeed === SettingsData.AnimationSpeed.Custom) + return SettingsData.notificationCustomAnimationDuration; + const presetMap = [0, 200, 400, 600]; + return presetMap[SettingsData.notificationAnimationSpeed] ?? 200; + } + + readonly property int notificationEnterDuration: { + const base = notificationAnimationBaseDuration; + return base === 0 ? 0 : Math.round(base * 0.875); + } + + readonly property int notificationExitDuration: { + const base = notificationAnimationBaseDuration; + return base === 0 ? 0 : Math.round(base * 0.75); + } + + readonly property int notificationExpandDuration: { + const base = notificationAnimationBaseDuration; + return base === 0 ? 0 : Math.round(base * 1.0); + } + + readonly property int notificationCollapseDuration: { + const base = notificationAnimationBaseDuration; + return base === 0 ? 0 : Math.round(base * 0.85); + } + + readonly property real notificationIconSizeNormal: 56 + readonly property real notificationIconSizeCompact: 48 + readonly property real notificationExpandedIconSizeNormal: 48 + readonly property real notificationExpandedIconSizeCompact: 40 + readonly property real notificationActionMinWidth: 48 + readonly property real notificationButtonCornerRadius: cornerRadius / 2 + readonly property real notificationHoverRevealMargin: spacingXL + readonly property real notificationContentSpacing: spacingXS + readonly property real notificationCardPadding: spacingM + readonly property real notificationCardPaddingCompact: spacingS + + readonly property real stateLayerHover: 0.08 + readonly property real stateLayerFocus: 0.12 + readonly property real stateLayerPressed: 0.12 + readonly property real stateLayerDrag: 0.16 + + readonly property int popoutAnimationDuration: { + if (typeof SettingsData === "undefined") + return 150; + if (SettingsData.syncComponentAnimationSpeeds) { + return Math.min(currentAnimationBaseDuration, 1000); + } + const presetMap = [0, 150, 300, 500]; + if (SettingsData.popoutAnimationSpeed === SettingsData.AnimationSpeed.Custom) + return SettingsData.popoutCustomAnimationDuration; + return presetMap[SettingsData.popoutAnimationSpeed] ?? 150; + } + + readonly property int modalAnimationDuration: { + if (typeof SettingsData === "undefined") + return 150; + if (SettingsData.syncComponentAnimationSpeeds) { + return Math.min(currentAnimationBaseDuration, 1000); + } + const presetMap = [0, 150, 300, 500]; + if (SettingsData.modalAnimationSpeed === SettingsData.AnimationSpeed.Custom) + return SettingsData.modalCustomAnimationDuration; + return presetMap[SettingsData.modalAnimationSpeed] ?? 150; + } + + property real cornerRadius: { + if (typeof SessionData !== "undefined" && SessionData.isGreeterMode && typeof GreetdSettings !== "undefined") { + return GreetdSettings.cornerRadius; + } + return typeof SettingsData !== "undefined" ? SettingsData.cornerRadius : 12; + } + + property string fontFamily: { + if (typeof SessionData !== "undefined" && SessionData.isGreeterMode && typeof GreetdSettings !== "undefined") { + return GreetdSettings.getEffectiveFontFamily(); + } + return typeof SettingsData !== "undefined" ? SettingsData.fontFamily : "Inter Variable"; + } + + property string monoFontFamily: { + if (typeof SessionData !== "undefined" && SessionData.isGreeterMode && typeof GreetdSettings !== "undefined") { + return GreetdSettings.monoFontFamily; + } + return typeof SettingsData !== "undefined" ? SettingsData.monoFontFamily : "Fira Code"; + } + + property int fontWeight: { + if (typeof SessionData !== "undefined" && SessionData.isGreeterMode && typeof GreetdSettings !== "undefined") { + return GreetdSettings.fontWeight; + } + return typeof SettingsData !== "undefined" ? SettingsData.fontWeight : Font.Normal; + } + + property real fontScale: { + if (typeof SessionData !== "undefined" && SessionData.isGreeterMode && typeof GreetdSettings !== "undefined") { + return GreetdSettings.fontScale; + } + return typeof SettingsData !== "undefined" ? SettingsData.fontScale : 1.0; + } + + property real spacingXS: 4 + property real spacingS: 8 + property real spacingM: 12 + property real spacingL: 16 + property real spacingXL: 24 + property real fontSizeSmall: Math.round(fontScale * 12) + property real fontSizeMedium: Math.round(fontScale * 14) + property real fontSizeLarge: Math.round(fontScale * 16) + property real fontSizeXLarge: Math.round(fontScale * 20) + property real barHeight: 48 + property real iconSize: 24 + property real iconSizeSmall: 16 + property real iconSizeLarge: 32 + + property real panelTransparency: 0.85 + property real popupTransparency: typeof SettingsData !== "undefined" && SettingsData.popupTransparency !== undefined ? SettingsData.popupTransparency : 1.0 + + function screenTransition() { + if (CompositorService.isNiri) { + NiriService.doScreenTransition(); + } + } + + function switchTheme(themeName, savePrefs = true, enableTransition = true) { + if (enableTransition) { + screenTransition(); + themeTransitionTimer.themeName = themeName; + themeTransitionTimer.savePrefs = savePrefs; + themeTransitionTimer.restart(); + return; + } + + if (themeName === dynamic) { + currentTheme = dynamic; + if (currentThemeCategory !== "registry") + currentThemeCategory = dynamic; + } else if (themeName === custom) { + currentTheme = custom; + if (currentThemeCategory !== "registry") + currentThemeCategory = custom; + if (typeof SettingsData !== "undefined" && SettingsData.customThemeFile) { + loadCustomThemeFromFile(SettingsData.customThemeFile); + } + } else if (themeName === "" && currentThemeCategory === "registry") { + // Registry category selected but no theme chosen yet + } else { + currentTheme = themeName; + if (currentThemeCategory !== "registry") { + currentThemeCategory = "generic"; + } + } + const isGreeterMode = (typeof SessionData !== "undefined" && SessionData.isGreeterMode); + if (savePrefs && typeof SettingsData !== "undefined" && !isGreeterMode) { + SettingsData.set("currentThemeCategory", currentThemeCategory); + SettingsData.set("currentThemeName", currentTheme); + } + + if (!isGreeterMode) { + generateSystemThemesFromCurrentTheme(); + } + } + + function setLightMode(light, savePrefs = true, enableTransition = false) { + if (enableTransition) { + screenTransition(); + lightModeTransitionTimer.lightMode = light; + lightModeTransitionTimer.savePrefs = savePrefs; + lightModeTransitionTimer.restart(); + return; + } + + const isGreeterMode = (typeof SessionData !== "undefined" && SessionData.isGreeterMode); + if (savePrefs && typeof SessionData !== "undefined" && !isGreeterMode) { + SessionData.setLightMode(light); + } + + if (!isGreeterMode) { + if (!matugenAvailable) { + PortalService.setLightMode(light); + } + if (typeof SettingsData !== "undefined") { + SettingsData.updateCosmicThemeMode(light); + } + generateSystemThemesFromCurrentTheme(); + } + } + + function toggleLightMode(savePrefs = true) { + setLightMode(!isLightMode, savePrefs, true); + } + + function forceGenerateSystemThemes() { + if (!matugenAvailable) { + return; + } + generateSystemThemesFromCurrentTheme(); + } + + function getAvailableThemes() { + return StockThemes.getAllThemeNames(); + } + + function getThemeDisplayName(themeName) { + const themeData = StockThemes.getThemeByName(themeName, isLightMode); + return themeData.name; + } + + function getThemeColors(themeName) { + if (themeName === "custom" && customThemeData) { + return customThemeData; + } + return StockThemes.getThemeByName(themeName, isLightMode); + } + + function switchThemeCategory(category, defaultTheme) { + screenTransition(); + themeCategoryTransitionTimer.category = category; + themeCategoryTransitionTimer.defaultTheme = defaultTheme; + themeCategoryTransitionTimer.restart(); + } + + function loadCustomTheme(themeData) { + customThemeRawData = themeData; + const colorMode = (typeof SessionData !== "undefined" && SessionData.isLightMode) ? "light" : "dark"; + + var baseColors = {}; + if (themeData.dark || themeData.light) { + baseColors = themeData[colorMode] || themeData.dark || themeData.light || {}; + } else { + baseColors = themeData; + } + + if (themeData.variants) { + const themeId = themeData.id || ""; + + if (themeData.variants.type === "multi" && themeData.variants.flavors && themeData.variants.accents) { + const defaults = themeData.variants.defaults || {}; + const modeDefaults = defaults[colorMode] || defaults.dark || {}; + const isGreeterMode = typeof SessionData !== "undefined" && SessionData.isGreeterMode; + const stored = isGreeterMode ? (GreetdSettings.registryThemeVariants[themeId]?.[colorMode] || modeDefaults) : (typeof SettingsData !== "undefined" ? SettingsData.getRegistryThemeMultiVariant(themeId, modeDefaults, colorMode) : modeDefaults); + var flavorId = stored.flavor || modeDefaults.flavor || ""; + const accentId = stored.accent || modeDefaults.accent || ""; + var flavor = findVariant(themeData.variants.flavors, flavorId); + if (flavor) { + const hasCurrentModeColors = flavor[colorMode] && (flavor[colorMode].primary || flavor[colorMode].surface); + if (!hasCurrentModeColors) { + flavorId = modeDefaults.flavor || ""; + flavor = findVariant(themeData.variants.flavors, flavorId); + } + } + const accent = findAccent(themeData.variants.accents, accentId); + if (flavor) { + const flavorColors = flavor[colorMode] || flavor.dark || flavor.light || {}; + baseColors = mergeColors(baseColors, flavorColors); + } + if (accent && flavor) { + const accentColors = accent[flavor.id] || {}; + baseColors = mergeColors(baseColors, accentColors); + } + customThemeData = baseColors; + generateSystemThemesFromCurrentTheme(); + return; + } + + if (themeData.variants.options && themeData.variants.options.length > 0) { + const isGreeterMode = typeof SessionData !== "undefined" && SessionData.isGreeterMode; + const selectedVariantId = isGreeterMode ? (typeof GreetdSettings.registryThemeVariants[themeId] === "string" ? GreetdSettings.registryThemeVariants[themeId] : themeData.variants.default) : (typeof SettingsData !== "undefined" ? SettingsData.getRegistryThemeVariant(themeId, themeData.variants.default) : themeData.variants.default); + const variant = findVariant(themeData.variants.options, selectedVariantId); + if (variant) { + const variantColors = variant[colorMode] || variant.dark || variant.light || {}; + customThemeData = mergeColors(baseColors, variantColors); + generateSystemThemesFromCurrentTheme(); + return; + } + } + } + + customThemeData = baseColors; + generateSystemThemesFromCurrentTheme(); + } + + function findVariant(options, variantId) { + if (!variantId || !options) + return null; + for (var i = 0; i < options.length; i++) { + if (options[i].id === variantId) + return options[i]; + } + return options[0] || null; + } + + function findAccent(accents, accentId) { + if (!accentId || !accents) + return null; + for (var i = 0; i < accents.length; i++) { + if (accents[i].id === accentId) + return accents[i]; + } + return accents[0] || null; + } + + function mergeColors(base, overlay) { + var result = JSON.parse(JSON.stringify(base)); + for (var key in overlay) { + if (overlay[key]) + result[key] = overlay[key]; + } + return result; + } + + function loadCustomThemeFromFile(filePath) { + customThemeFileView.path = filePath; + } + + function reloadCustomThemeVariant() { + if (currentTheme !== "custom" || !customThemeRawData) + return; + loadCustomTheme(customThemeRawData); + } + + property alias availableThemeNames: root._availableThemeNames + readonly property var _availableThemeNames: StockThemes.getAllThemeNames() + property string currentThemeName: currentTheme + + function panelBackground() { + return Qt.rgba(surfaceContainer.r, surfaceContainer.g, surfaceContainer.b, panelTransparency); + } + + property real notepadTransparency: SettingsData.notepadTransparencyOverride >= 0 ? SettingsData.notepadTransparencyOverride : popupTransparency + + property bool widgetBackgroundHasAlpha: { + const colorMode = typeof SettingsData !== "undefined" ? SettingsData.widgetBackgroundColor : "sch"; + return colorMode === "sth"; + } + + property var widgetBaseBackgroundColor: { + const colorMode = typeof SettingsData !== "undefined" ? SettingsData.widgetBackgroundColor : "sch"; + switch (colorMode) { + case "s": + return surface; + case "sc": + return surfaceContainer; + case "sch": + return surfaceContainerHigh; + case "sth": + default: + return surfaceTextHover; + } + } + + property color widgetBaseHoverColor: { + const blended = blend(widgetBaseBackgroundColor, primary, 0.1); + return withAlpha(blended, Math.max(0.3, blended.a)); + } + + property color widgetIconColor: { + if (typeof SettingsData === "undefined") { + return surfaceText; + } + + switch (SettingsData.widgetColorMode) { + case "colorful": + return surfaceText; + case "default": + default: + return surfaceText; + } + } + + property color widgetTextColor: { + if (typeof SettingsData === "undefined") { + return surfaceText; + } + + switch (SettingsData.widgetColorMode) { + case "colorful": + return primary; + case "default": + default: + return surfaceText; + } + } + + function isColorDark(c) { + return (0.299 * c.r + 0.587 * c.g + 0.114 * c.b) < 0.5; + } + + function barIconSize(barThickness, offset, maximizeIcon, iconScale) { + const defaultOffset = offset !== undefined ? offset : -6; + const size = (maximizeIcon ?? false) ? iconSizeLarge : iconSize; + const s = iconScale !== undefined ? iconScale : 1.0; + + return Math.round((barThickness / 48) * (size + defaultOffset) * s); + } + + function barTextSize(barThickness, fontScale, maximizeText) { + const scale = barThickness / 48; + const dankBarScale = fontScale !== undefined ? fontScale : 1.0; + const maxScale = (maximizeText ?? false) ? 1.5 : 1.0; + if (scale <= 0.75) + return Math.round(fontSizeSmall * 0.9 * dankBarScale * maxScale); + if (scale >= 1.25) + return Math.round(fontSizeMedium * dankBarScale * maxScale); + return Math.round(fontSizeSmall * dankBarScale * maxScale); + } + + function getBatteryIcon(level, isCharging, batteryAvailable) { + if (!batteryAvailable) + return "battery_std"; + + if (isCharging) { + if (level >= 90) + return "battery_charging_full"; + if (level >= 80) + return "battery_charging_90"; + if (level >= 60) + return "battery_charging_80"; + if (level >= 50) + return "battery_charging_60"; + if (level >= 30) + return "battery_charging_50"; + if (level >= 20) + return "battery_charging_30"; + return "battery_charging_20"; + } else { + if (level >= 95) + return "battery_full"; + if (level >= 85) + return "battery_6_bar"; + if (level >= 70) + return "battery_5_bar"; + if (level >= 55) + return "battery_4_bar"; + if (level >= 40) + return "battery_3_bar"; + if (level >= 25) + return "battery_2_bar"; + if (level >= 10) + return "battery_1_bar"; + return "battery_alert"; + } + } + + function getPowerProfileIcon(profile) { + switch (profile) { + case 0: + return "battery_saver"; + case 1: + return "battery_std"; + case 2: + return "flash_on"; + default: + return "settings"; + } + } + + function getPowerProfileLabel(profile) { + switch (profile) { + case 0: + return I18n.tr("Power Saver", "power profile option"); + case 1: + return I18n.tr("Balanced", "power profile option"); + case 2: + return I18n.tr("Performance", "power profile option"); + default: + return I18n.tr("Unknown", "power profile option"); + } + } + + function getPowerProfileDescription(profile) { + switch (profile) { + case 0: + return I18n.tr("Extend battery life", "power profile description"); + case 1: + return I18n.tr("Balance power and performance", "power profile description"); + case 2: + return I18n.tr("Prioritize performance", "power profile description"); + default: + return I18n.tr("Custom power profile", "power profile description"); + } + } + + function onLightModeChanged() { + if (currentTheme === "custom" && customThemeFileView.path) { + customThemeFileView.reload(); + } + } + + function setDesiredTheme(kind, value, isLight, iconTheme, matugenType, stockColors) { + if (!matugenAvailable) { + console.warn("Theme: matugen not available or disabled - cannot set system theme"); + return; + } + + if (workerRunning) { + console.info("Theme: Worker already running, queueing request"); + pendingThemeRequest = { + kind, + value, + isLight, + iconTheme, + matugenType, + stockColors + }; + return; + } + + console.info("Theme: Setting desired theme -", kind, "mode:", isLight ? "light" : "dark", stockColors ? "(stock colors)" : "(dynamic)"); + + if (typeof NiriService !== "undefined" && CompositorService.isNiri) { + NiriService.suppressNextToast(); + } + + const desired = { + "kind": kind, + "value": value, + "mode": isLight ? "light" : "dark", + "iconTheme": iconTheme || "System Default", + "matugenType": matugenType || "scheme-tonal-spot", + "runUserTemplates": (typeof SettingsData !== "undefined") ? SettingsData.runUserMatugenTemplates : true + }; + + console.log("Theme: Starting matugen worker"); + workerRunning = true; + + const args = ["dms", "matugen", "queue", "--state-dir", stateDir, "--shell-dir", shellDir, "--config-dir", configDir, "--kind", desired.kind, "--value", desired.value, "--mode", desired.mode, "--icon-theme", desired.iconTheme, "--matugen-type", desired.matugenType,]; + + if (!desired.runUserTemplates) { + args.push("--run-user-templates=false"); + } + if (stockColors) { + args.push("--stock-colors", JSON.stringify(stockColors)); + } + if (typeof SettingsData !== "undefined" && SettingsData.syncModeWithPortal) { + args.push("--sync-mode-with-portal"); + } + if (typeof SettingsData !== "undefined" && SettingsData.terminalsAlwaysDark) { + args.push("--terminals-always-dark"); + } + if (typeof SettingsData !== "undefined" && SettingsData.matugenContrast !== 0) { + args.push("--contrast", SettingsData.matugenContrast.toString()); + } + + if (typeof SettingsData !== "undefined") { + const skipTemplates = []; + if (!SettingsData.runDmsMatugenTemplates) { + skipTemplates.push("gtk", "nvim", "niri", "qt5ct", "qt6ct", "firefox", "pywalfox", "zenbrowser", "vesktop", "equibop", "ghostty", "kitty", "foot", "alacritty", "wezterm", "dgop", "kcolorscheme", "vscode", "emacs", "zed"); + } else { + if (!SettingsData.matugenTemplateGtk) + skipTemplates.push("gtk"); + if (!SettingsData.matugenTemplateNiri) + skipTemplates.push("niri"); + if (!SettingsData.matugenTemplateHyprland) + skipTemplates.push("hyprland"); + if (!SettingsData.matugenTemplateMangowc) + skipTemplates.push("mangowc"); + if (!SettingsData.matugenTemplateQt5ct) + skipTemplates.push("qt5ct"); + if (!SettingsData.matugenTemplateQt6ct) + skipTemplates.push("qt6ct"); + if (!SettingsData.matugenTemplateFirefox) + skipTemplates.push("firefox"); + if (!SettingsData.matugenTemplatePywalfox) + skipTemplates.push("pywalfox"); + if (!SettingsData.matugenTemplateZenBrowser) + skipTemplates.push("zenbrowser"); + if (!SettingsData.matugenTemplateVesktop) + skipTemplates.push("vesktop"); + if (!SettingsData.matugenTemplateEquibop) + skipTemplates.push("equibop"); + if (!SettingsData.matugenTemplateGhostty) + skipTemplates.push("ghostty"); + if (!SettingsData.matugenTemplateKitty) + skipTemplates.push("kitty"); + if (!SettingsData.matugenTemplateFoot) + skipTemplates.push("foot"); + if (!SettingsData.matugenTemplateNeovim) + skipTemplates.push("nvim"); + if (!SettingsData.matugenTemplateAlacritty) + skipTemplates.push("alacritty"); + if (!SettingsData.matugenTemplateWezterm) + skipTemplates.push("wezterm"); + if (!SettingsData.matugenTemplateDgop) + skipTemplates.push("dgop"); + if (!SettingsData.matugenTemplateKcolorscheme) + skipTemplates.push("kcolorscheme"); + if (!SettingsData.matugenTemplateVscode) + skipTemplates.push("vscode"); + if (!SettingsData.matugenTemplateEmacs) + skipTemplates.push("emacs"); + if (!SettingsData.matugenTemplateZed) + skipTemplates.push("zed"); + } + if (skipTemplates.length > 0) { + args.push("--skip-templates", skipTemplates.join(",")); + } + } + + systemThemeGenerator.command = args; + systemThemeGenerator.running = true; + } + + function generateSystemThemesFromCurrentTheme() { + const isGreeterMode = (typeof SessionData !== "undefined" && SessionData.isGreeterMode); + if (!matugenAvailable || isGreeterMode) + return; + + _pendingGenerateParams = true; + _themeGenerateDebounce.restart(); + } + + function _executeThemeGeneration() { + if (!_pendingGenerateParams) + return; + _pendingGenerateParams = null; + + const isLight = (typeof SessionData !== "undefined" && SessionData.isLightMode); + const iconTheme = (typeof SettingsData !== "undefined" && SettingsData.iconTheme) ? SettingsData.iconTheme : "System Default"; + + if (currentTheme === dynamic) { + if (!rawWallpaperPath) + return; + const selectedMatugenType = (typeof SettingsData !== "undefined" && SettingsData.matugenScheme) ? SettingsData.matugenScheme : "scheme-tonal-spot"; + const kind = rawWallpaperPath.startsWith("#") ? "hex" : "image"; + setDesiredTheme(kind, rawWallpaperPath, isLight, iconTheme, selectedMatugenType, null); + return; + } + + let darkTheme, lightTheme; + if (currentTheme === "custom") { + if (customThemeRawData && (customThemeRawData.dark || customThemeRawData.light)) { + darkTheme = customThemeRawData.dark || customThemeRawData.light; + lightTheme = customThemeRawData.light || customThemeRawData.dark; + + if (customThemeRawData.variants) { + const themeId = customThemeRawData.id || ""; + + if (customThemeRawData.variants.type === "multi" && customThemeRawData.variants.flavors && customThemeRawData.variants.accents) { + const defaults = customThemeRawData.variants.defaults || {}; + const darkDefaults = defaults.dark || {}; + const lightDefaults = defaults.light || defaults.dark || {}; + const isGreeterMode = typeof SessionData !== "undefined" && SessionData.isGreeterMode; + const storedDark = isGreeterMode ? (GreetdSettings.registryThemeVariants[themeId]?.dark || darkDefaults) : (typeof SettingsData !== "undefined" ? SettingsData.getRegistryThemeMultiVariant(themeId, darkDefaults, "dark") : darkDefaults); + const storedLight = isGreeterMode ? (GreetdSettings.registryThemeVariants[themeId]?.light || lightDefaults) : (typeof SettingsData !== "undefined" ? SettingsData.getRegistryThemeMultiVariant(themeId, lightDefaults, "light") : lightDefaults); + const darkFlavorId = storedDark.flavor || darkDefaults.flavor || ""; + const lightFlavorId = storedLight.flavor || lightDefaults.flavor || ""; + const accentId = storedDark.accent || darkDefaults.accent || ""; + const darkFlavor = findVariant(customThemeRawData.variants.flavors, darkFlavorId); + const lightFlavor = findVariant(customThemeRawData.variants.flavors, lightFlavorId); + const accent = findAccent(customThemeRawData.variants.accents, accentId); + if (darkFlavor) { + darkTheme = mergeColors(darkTheme, darkFlavor.dark || {}); + if (accent) + darkTheme = mergeColors(darkTheme, accent[darkFlavor.id] || {}); + } + if (lightFlavor) { + lightTheme = mergeColors(lightTheme, lightFlavor.light || {}); + if (accent) + lightTheme = mergeColors(lightTheme, accent[lightFlavor.id] || {}); + } + } else if (customThemeRawData.variants.options) { + const isGreeterMode = typeof SessionData !== "undefined" && SessionData.isGreeterMode; + const selectedVariantId = isGreeterMode ? (typeof GreetdSettings.registryThemeVariants[themeId] === "string" ? GreetdSettings.registryThemeVariants[themeId] : customThemeRawData.variants.default) : (typeof SettingsData !== "undefined" ? SettingsData.getRegistryThemeVariant(themeId, customThemeRawData.variants.default) : customThemeRawData.variants.default); + const variant = findVariant(customThemeRawData.variants.options, selectedVariantId); + if (variant) { + darkTheme = mergeColors(darkTheme, variant.dark || {}); + lightTheme = mergeColors(lightTheme, variant.light || {}); + } + } + } + } else { + darkTheme = customThemeData; + lightTheme = customThemeData; + } + } else { + darkTheme = StockThemes.getThemeByName(currentTheme, false); + lightTheme = StockThemes.getThemeByName(currentTheme, true); + } + + if (!darkTheme || !darkTheme.primary) { + console.warn("Theme data not available for:", currentTheme); + return; + } + + const stockColors = buildMatugenColorsFromTheme(darkTheme, lightTheme); + const themeData = isLight ? lightTheme : darkTheme; + setDesiredTheme("hex", themeData.primary, isLight, iconTheme, themeData.matugen_type, stockColors); + } + + function buildMatugenColorsFromTheme(darkTheme, lightTheme) { + const colors = {}; + const isLight = SessionData !== "undefined" && SessionData.isLightMode; + + function addColor(matugenKey, darkVal, lightVal) { + if (!darkVal && !lightVal) + return; + colors[matugenKey] = { + "dark": { + "color": String(darkVal || lightVal) + }, + "light": { + "color": String(lightVal || darkVal) + }, + "default": { + "color": String((isLight && lightVal) ? lightVal : darkVal) + } + }; + } + + function get(theme, key, fallback) { + return theme[key] || fallback; + } + + addColor("primary", darkTheme.primary, lightTheme.primary); + addColor("on_primary", darkTheme.primaryText, lightTheme.primaryText); + addColor("primary_container", darkTheme.primaryContainer, lightTheme.primaryContainer); + addColor("on_primary_container", darkTheme.primaryContainerText || darkTheme.surfaceText, lightTheme.primaryContainerText || lightTheme.surfaceText); + addColor("secondary", darkTheme.secondary, lightTheme.secondary); + addColor("on_secondary", darkTheme.secondaryText || darkTheme.primaryText, lightTheme.secondaryText || lightTheme.primaryText); + addColor("secondary_container", darkTheme.secondaryContainer || darkTheme.surfaceContainerHigh, lightTheme.secondaryContainer || lightTheme.surfaceContainerHigh); + addColor("on_secondary_container", darkTheme.secondaryContainerText || darkTheme.surfaceText, lightTheme.secondaryContainerText || lightTheme.surfaceText); + addColor("tertiary", darkTheme.tertiary || darkTheme.secondary, lightTheme.tertiary || lightTheme.secondary); + addColor("on_tertiary", darkTheme.tertiaryText || darkTheme.secondaryText || darkTheme.primaryText, lightTheme.tertiaryText || lightTheme.secondaryText || lightTheme.primaryText); + addColor("tertiary_container", darkTheme.tertiaryContainer || darkTheme.secondaryContainer || darkTheme.surfaceContainerHigh, lightTheme.tertiaryContainer || lightTheme.secondaryContainer || lightTheme.surfaceContainerHigh); + addColor("on_tertiary_container", darkTheme.tertiaryContainerText || darkTheme.surfaceText, lightTheme.tertiaryContainerText || lightTheme.surfaceText); + addColor("error", darkTheme.error || "#F2B8B5", lightTheme.error || "#B3261E"); + addColor("on_error", darkTheme.errorText || "#601410", lightTheme.errorText || "#FFFFFF"); + addColor("error_container", darkTheme.errorContainer || "#8C1D18", lightTheme.errorContainer || "#F9DEDC"); + addColor("on_error_container", darkTheme.errorContainerText || "#F9DEDC", lightTheme.errorContainerText || "#410E0B"); + addColor("surface", darkTheme.surface, lightTheme.surface); + addColor("on_surface", darkTheme.surfaceText, lightTheme.surfaceText); + addColor("surface_variant", darkTheme.surfaceVariant, lightTheme.surfaceVariant); + addColor("on_surface_variant", darkTheme.surfaceVariantText, lightTheme.surfaceVariantText); + addColor("surface_tint", darkTheme.surfaceTint, lightTheme.surfaceTint); + addColor("background", darkTheme.background, lightTheme.background); + addColor("on_background", darkTheme.backgroundText, lightTheme.backgroundText); + addColor("outline", darkTheme.outline, lightTheme.outline); + addColor("outline_variant", darkTheme.outlineVariant || darkTheme.surfaceVariant, lightTheme.outlineVariant || lightTheme.surfaceVariant); + addColor("surface_container", darkTheme.surfaceContainer, lightTheme.surfaceContainer); + addColor("surface_container_high", darkTheme.surfaceContainerHigh, lightTheme.surfaceContainerHigh); + addColor("surface_container_highest", darkTheme.surfaceContainerHighest || darkTheme.surfaceContainerHigh, lightTheme.surfaceContainerHighest || lightTheme.surfaceContainerHigh); + addColor("surface_container_low", darkTheme.surfaceContainerLow || darkTheme.surface, lightTheme.surfaceContainerLow || lightTheme.surface); + addColor("surface_container_lowest", darkTheme.surfaceContainerLowest || darkTheme.background, lightTheme.surfaceContainerLowest || lightTheme.background); + addColor("surface_bright", darkTheme.surfaceBright || darkTheme.surfaceContainerHighest || darkTheme.surfaceContainerHigh, lightTheme.surfaceBright || lightTheme.surface); + addColor("surface_dim", darkTheme.surfaceDim || darkTheme.background, lightTheme.surfaceDim || lightTheme.surfaceContainer); + addColor("inverse_surface", darkTheme.inverseSurface || lightTheme.surface, lightTheme.inverseSurface || darkTheme.surface); + addColor("inverse_on_surface", darkTheme.inverseOnSurface || lightTheme.surfaceText, lightTheme.inverseOnSurface || darkTheme.surfaceText); + addColor("inverse_primary", darkTheme.inversePrimary || lightTheme.primary, lightTheme.inversePrimary || darkTheme.primary); + addColor("scrim", darkTheme.scrim || "#000000", lightTheme.scrim || "#000000"); + addColor("shadow", darkTheme.shadow || "#000000", lightTheme.shadow || "#000000"); + addColor("source_color", darkTheme.primary, lightTheme.primary); + addColor("primary_fixed", darkTheme.primaryFixed || darkTheme.primaryContainer, lightTheme.primaryFixed || lightTheme.primaryContainer); + addColor("primary_fixed_dim", darkTheme.primaryFixedDim || darkTheme.primary, lightTheme.primaryFixedDim || lightTheme.primary); + addColor("on_primary_fixed", darkTheme.onPrimaryFixed || darkTheme.primaryText, lightTheme.onPrimaryFixed || lightTheme.primaryText); + addColor("on_primary_fixed_variant", darkTheme.onPrimaryFixedVariant || darkTheme.primaryText, lightTheme.onPrimaryFixedVariant || lightTheme.primaryText); + addColor("secondary_fixed", darkTheme.secondaryFixed || darkTheme.secondary, lightTheme.secondaryFixed || lightTheme.secondary); + addColor("secondary_fixed_dim", darkTheme.secondaryFixedDim || darkTheme.secondary, lightTheme.secondaryFixedDim || lightTheme.secondary); + addColor("on_secondary_fixed", darkTheme.onSecondaryFixed || darkTheme.primaryText, lightTheme.onSecondaryFixed || lightTheme.primaryText); + addColor("on_secondary_fixed_variant", darkTheme.onSecondaryFixedVariant || darkTheme.primaryText, lightTheme.onSecondaryFixedVariant || lightTheme.primaryText); + addColor("tertiary_fixed", darkTheme.tertiaryFixed || darkTheme.tertiary || darkTheme.secondary, lightTheme.tertiaryFixed || lightTheme.tertiary || lightTheme.secondary); + addColor("tertiary_fixed_dim", darkTheme.tertiaryFixedDim || darkTheme.tertiary || darkTheme.secondary, lightTheme.tertiaryFixedDim || lightTheme.tertiary || lightTheme.secondary); + addColor("on_tertiary_fixed", darkTheme.onTertiaryFixed || darkTheme.primaryText, lightTheme.onTertiaryFixed || lightTheme.primaryText); + addColor("on_tertiary_fixed_variant", darkTheme.onTertiaryFixedVariant || darkTheme.primaryText, lightTheme.onTertiaryFixedVariant || lightTheme.primaryText); + + return colors; + } + + function applyGtkColors() { + if (!matugenAvailable) { + if (typeof ToastService !== "undefined") { + ToastService.showError("matugen not available or disabled - cannot apply GTK colors"); + } + return; + } + + const isLight = (typeof SessionData !== "undefined" && SessionData.isLightMode) ? "true" : "false"; + Proc.runCommand("gtkApplier", [shellDir + "/scripts/gtk.sh", configDir, isLight, shellDir], (output, exitCode) => { + if (exitCode === 0) { + if (typeof ToastService !== "undefined" && typeof NiriService !== "undefined" && !NiriService.matugenSuppression) { + ToastService.showInfo("GTK colors applied successfully"); + } + } else { + if (typeof ToastService !== "undefined") { + ToastService.showError("Failed to apply GTK colors"); + } + } + }); + } + + function applyQtColors() { + if (!matugenAvailable) { + if (typeof ToastService !== "undefined") { + ToastService.showError("matugen not available or disabled - cannot apply Qt colors"); + } + return; + } + + Proc.runCommand("qtApplier", [shellDir + "/scripts/qt.sh", configDir], (output, exitCode) => { + if (exitCode === 0) { + if (typeof ToastService !== "undefined") { + ToastService.showInfo("Qt colors applied successfully"); + } + } else { + if (typeof ToastService !== "undefined") { + ToastService.showError("Failed to apply Qt colors"); + } + } + }); + } + + function withAlpha(c, a) { + return Qt.rgba(c.r, c.g, c.b, a); + } + + function blendAlpha(c, a) { + return Qt.rgba(c.r, c.g, c.b, c.a * a); + } + + function blend(c1, c2, r) { + return Qt.rgba(c1.r * (1 - r) + c2.r * r, c1.g * (1 - r) + c2.g * r, c1.b * (1 - r) + c2.b * r, c1.a * (1 - r) + c2.a * r); + } + + function getFillMode(modeName) { + switch (modeName) { + case "Stretch": + return Image.Stretch; + case "Fit": + case "PreserveAspectFit": + return Image.PreserveAspectFit; + case "Fill": + case "PreserveAspectCrop": + return Image.PreserveAspectCrop; + case "Tile": + return Image.Tile; + case "TileVertically": + return Image.TileVertically; + case "TileHorizontally": + return Image.TileHorizontally; + case "Pad": + return Image.Pad; + default: + return Image.PreserveAspectCrop; + } + } + + function snap(value, dpr) { + const s = dpr || 1; + return Math.round(value * s) / s; + } + + function px(value, dpr) { + const s = dpr || 1; + return Math.round(value * s) / s; + } + + function hairline(dpr) { + return 1 / (dpr || 1); + } + + function invertHex(hex) { + hex = hex.replace('#', ''); + + if (!/^[0-9A-Fa-f]{6}$/.test(hex)) { + return hex; + } + + const r = parseInt(hex.substr(0, 2), 16); + const g = parseInt(hex.substr(2, 2), 16); + const b = parseInt(hex.substr(4, 2), 16); + + const invR = (255 - r).toString(16).padStart(2, '0'); + const invG = (255 - g).toString(16).padStart(2, '0'); + const invB = (255 - b).toString(16).padStart(2, '0'); + + return `#${invR}${invG}${invB}`; + } + + property var baseLogoColor: { + if (typeof SettingsData === "undefined") + return ""; + const colorOverride = SettingsData.launcherLogoColorOverride; + if (!colorOverride || colorOverride === "") + return ""; + if (colorOverride === "primary") + return primary; + if (colorOverride === "surface") + return surfaceText; + return colorOverride; + } + + property var effectiveLogoColor: { + if (typeof SettingsData === "undefined") + return ""; + + const colorOverride = SettingsData.launcherLogoColorOverride; + if (!colorOverride || colorOverride === "") + return ""; + + if (colorOverride === "primary") + return primary; + if (colorOverride === "surface") + return surfaceText; + + if (!SettingsData.launcherLogoColorInvertOnMode) { + return colorOverride; + } + + if (isLightMode) { + return invertHex(colorOverride); + } + + return colorOverride; + } + + Process { + id: systemThemeGenerator + running: false + stdout: SplitParser { + onRead: data => console.info("Theme worker:", data) + } + stderr: SplitParser { + onRead: data => console.warn("Theme worker:", data) + } + + onExited: exitCode => { + workerRunning = false; + const currentMode = (typeof SessionData !== "undefined" && SessionData.isLightMode) ? "light" : "dark"; + + switch (exitCode) { + case 0: + console.info("Theme: Matugen worker completed successfully"); + root.matugenCompleted(currentMode, "success"); + break; + case 2: + console.log("Theme: Matugen worker completed with code 2 (no changes needed)"); + root.matugenCompleted(currentMode, "no-changes"); + break; + default: + if (typeof ToastService !== "undefined") { + ToastService.showError("Theme worker failed (" + exitCode + ")"); + } + console.warn("Theme: Matugen worker failed with exit code:", exitCode); + root.matugenCompleted(currentMode, "error"); + } + + if (!pendingThemeRequest) + return; + + const req = pendingThemeRequest; + pendingThemeRequest = null; + console.info("Theme: Processing queued theme request"); + setDesiredTheme(req.kind, req.value, req.isLight, req.iconTheme, req.matugenType, req.stockColors); + } + } + + FileView { + id: customThemeFileView + watchChanges: currentTheme === "custom" + + function parseAndLoadTheme() { + try { + var themeData = JSON.parse(customThemeFileView.text()); + loadCustomTheme(themeData); + } catch (e) { + ToastService.showError("Invalid JSON format: " + e.message); + } + } + + onLoaded: { + parseAndLoadTheme(); + } + + onFileChanged: { + customThemeFileView.reload(); + } + + onLoadFailed: function (error) { + if (typeof ToastService !== "undefined") { + ToastService.showError("Failed to read theme file: " + error); + } + } + } + + FileView { + id: dynamicColorsFileView + path: { + const greetCfgDir = Quickshell.env("DMS_GREET_CFG_DIR") || "/var/cache/dms-greeter"; + const colorsPath = SessionData.isGreeterMode ? greetCfgDir + "/colors.json" : stateDir + "/dms-colors.json"; + return colorsPath; + } + blockLoading: false + watchChanges: !SessionData.isGreeterMode + + function parseAndLoadColors() { + try { + const colorsText = dynamicColorsFileView.text(); + if (colorsText) { + root.matugenColors = JSON.parse(colorsText); + if (typeof ToastService !== "undefined") { + ToastService.clearWallpaperError(); + } + } + } catch (e) { + console.error("Theme: Failed to parse dynamic colors:", e); + if (typeof ToastService !== "undefined") { + ToastService.wallpaperErrorStatus = "error"; + ToastService.showError("Dynamic colors parse error: " + e.message); + } + } + } + + onLoaded: { + if (currentTheme === dynamic) + colorsFileLoadFailed = false; + parseAndLoadColors(); + } + + onFileChanged: { + dynamicColorsFileView.reload(); + } + + onLoadFailed: function (error) { + if (currentTheme === dynamic) { + console.warn("Theme: Dynamic colors file load failed, marking for regeneration"); + colorsFileLoadFailed = true; + const isGreeterMode = (typeof SessionData !== "undefined" && SessionData.isGreeterMode); + if (!isGreeterMode && matugenAvailable && rawWallpaperPath) { + console.log("Theme: Matugen available, triggering immediate regeneration"); + generateSystemThemesFromCurrentTheme(); + } + } + } + + onPathChanged: { + colorsFileLoadFailed = false; + } + } + + IpcHandler { + target: "theme" + + function toggle(): string { + root.toggleLightMode(); + return root.isLightMode ? "dark" : "light"; + } + + function light(): string { + root.setLightMode(true, true, true); + return "light"; + } + + function dark(): string { + root.setLightMode(false, true, true); + return "dark"; + } + + function getMode(): string { + return root.isLightMode ? "light" : "dark"; + } + } + + Timer { + id: _themeGenerateDebounce + interval: 100 + repeat: false + onTriggered: root._executeThemeGeneration() + } + + // These timers are for screen transitions, since sometimes QML still beats the niri call + Timer { + id: themeTransitionTimer + interval: 50 + repeat: false + property string themeName: "" + property bool savePrefs: true + onTriggered: root.switchTheme(themeName, savePrefs, false) + } + + Timer { + id: lightModeTransitionTimer + interval: 100 + repeat: false + property bool lightMode: false + property bool savePrefs: true + onTriggered: root.setLightMode(lightMode, savePrefs, false) + } + + Timer { + id: themeCategoryTransitionTimer + interval: 50 + repeat: false + property string category: "" + property string defaultTheme: "" + onTriggered: { + root.currentThemeCategory = category; + root.switchTheme(defaultTheme, true, false); + } + } + + // Theme mode automation functions + function themeAutoBackendAvailable() { + return typeof DMSService !== "undefined" && DMSService.isConnected && Array.isArray(DMSService.capabilities) && DMSService.capabilities.includes("theme.auto"); + } + + function applyThemeAutoState(state) { + if (!state) { + return; + } + if (state.config && state.config.mode && state.config.mode !== SessionData.themeModeAutoMode) { + return; + } + if (typeof SessionData !== "undefined" && state.nextTransition !== undefined) { + SessionData.themeModeNextTransition = state.nextTransition || ""; + } + if (state.isLight !== undefined && root.isLightMode !== state.isLight) { + root.setLightMode(state.isLight, true, true); + } + } + + function syncTimeThemeSchedule() { + if (typeof SessionData === "undefined" || typeof DMSService === "undefined") { + return; + } + + if (!DMSService.isConnected) { + return; + } + + const timeModeActive = SessionData.themeModeAutoEnabled && SessionData.themeModeAutoMode === "time"; + + if (!timeModeActive) { + return; + } + + DMSService.sendRequest("theme.auto.setMode", { + "mode": "time" + }); + + const shareSettings = SessionData.themeModeShareGammaSettings; + const startHour = shareSettings ? SessionData.nightModeStartHour : SessionData.themeModeStartHour; + const startMinute = shareSettings ? SessionData.nightModeStartMinute : SessionData.themeModeStartMinute; + const endHour = shareSettings ? SessionData.nightModeEndHour : SessionData.themeModeEndHour; + const endMinute = shareSettings ? SessionData.nightModeEndMinute : SessionData.themeModeEndMinute; + + DMSService.sendRequest("theme.auto.setSchedule", { + "startHour": startHour, + "startMinute": startMinute, + "endHour": endHour, + "endMinute": endMinute + }, response => { + if (response && response.error) { + console.error("Theme automation: Failed to sync time schedule:", response.error); + } + }); + + DMSService.sendRequest("theme.auto.setEnabled", { + "enabled": true + }); + DMSService.sendRequest("theme.auto.trigger", {}); + } + + function syncLocationThemeSchedule() { + if (typeof SessionData === "undefined" || typeof DMSService === "undefined") { + return; + } + + if (!DMSService.isConnected) { + return; + } + + const locationModeActive = SessionData.themeModeAutoEnabled && SessionData.themeModeAutoMode === "location"; + + if (!locationModeActive) { + return; + } + + DMSService.sendRequest("theme.auto.setMode", { + "mode": "location" + }); + + if (SessionData.nightModeUseIPLocation) { + DMSService.sendRequest("theme.auto.setUseIPLocation", { + "use": true + }); + } else { + DMSService.sendRequest("theme.auto.setUseIPLocation", { + "use": false + }); + if (SessionData.latitude !== 0.0 && SessionData.longitude !== 0.0) { + DMSService.sendRequest("theme.auto.setLocation", { + "latitude": SessionData.latitude, + "longitude": SessionData.longitude + }); + } + } + + DMSService.sendRequest("theme.auto.setEnabled", { + "enabled": true + }); + DMSService.sendRequest("theme.auto.trigger", {}); + } + + function evaluateThemeMode() { + if (typeof SessionData === "undefined" || !SessionData.themeModeAutoEnabled) { + return; + } + + if (themeAutoBackendAvailable()) { + DMSService.sendRequest("theme.auto.getState", null, response => { + if (response && response.result) { + applyThemeAutoState(response.result); + } + }); + return; + } + + const mode = SessionData.themeModeAutoMode; + + if (mode === "location") { + evaluateLocationBasedThemeMode(); + } else { + evaluateTimeBasedThemeMode(); + } + } + + function evaluateLocationBasedThemeMode() { + if (typeof DisplayService !== "undefined") { + const shouldBeLight = DisplayService.gammaIsDay; + if (root.isLightMode !== shouldBeLight) { + root.setLightMode(shouldBeLight, true, true); + } + return; + } + + if (!SessionData.nightModeUseIPLocation && SessionData.latitude !== 0.0 && SessionData.longitude !== 0.0) { + const shouldBeLight = calculateIsDaytime(SessionData.latitude, SessionData.longitude); + if (root.isLightMode !== shouldBeLight) { + root.setLightMode(shouldBeLight, true, true); + } + return; + } + + if (root.themeModeAutomationActive) { + if (SessionData.nightModeUseIPLocation) { + console.warn("Theme automation: Waiting for IP location from backend"); + } else { + console.warn("Theme automation: Location mode requires coordinates"); + } + } + } + + function evaluateTimeBasedThemeMode() { + const shareSettings = SessionData.themeModeShareGammaSettings; + + const startHour = shareSettings ? SessionData.nightModeStartHour : SessionData.themeModeStartHour; + const startMinute = shareSettings ? SessionData.nightModeStartMinute : SessionData.themeModeStartMinute; + const endHour = shareSettings ? SessionData.nightModeEndHour : SessionData.themeModeEndHour; + const endMinute = shareSettings ? SessionData.nightModeEndMinute : SessionData.themeModeEndMinute; + + const now = new Date(); + const currentMinutes = now.getHours() * 60 + now.getMinutes(); + const startMinutes = startHour * 60 + startMinute; + const endMinutes = endHour * 60 + endMinute; + + let shouldBeLight; + if (startMinutes < endMinutes) { + shouldBeLight = currentMinutes < startMinutes || currentMinutes >= endMinutes; + } else { + shouldBeLight = currentMinutes >= endMinutes && currentMinutes < startMinutes; + } + + if (root.isLightMode !== shouldBeLight) { + root.setLightMode(shouldBeLight, true, true); + } + } + + function calculateIsDaytime(lat, lng) { + const now = new Date(); + const start = new Date(now.getFullYear(), 0, 0); + const diff = now - start; + const dayOfYear = Math.floor(diff / 86400000); + const latRad = lat * Math.PI / 180; + + const declination = 23.45 * Math.sin((360 / 365) * (dayOfYear - 81) * Math.PI / 180); + const declinationRad = declination * Math.PI / 180; + + const cosHourAngle = -Math.tan(latRad) * Math.tan(declinationRad); + + if (cosHourAngle > 1) { + return false; // Polar night + } + if (cosHourAngle < -1) { + return true; // Midnight sun + } + + const hourAngle = Math.acos(cosHourAngle); + const hourAngleDeg = hourAngle * 180 / Math.PI; + + const sunriseHour = 12 - hourAngleDeg / 15; + const sunsetHour = 12 + hourAngleDeg / 15; + + const timeZoneOffset = now.getTimezoneOffset() / 60; + const localSunrise = sunriseHour - lng / 15 - timeZoneOffset; + const localSunset = sunsetHour - lng / 15 - timeZoneOffset; + + const currentHour = now.getHours() + now.getMinutes() / 60; + + const normalizeSunrise = ((localSunrise % 24) + 24) % 24; + const normalizeSunset = ((localSunset % 24) + 24) % 24; + + return currentHour >= normalizeSunrise && currentHour < normalizeSunset; + } + + // Helper function to send location to backend + function sendLocationToBackend() { + if (typeof SessionData === "undefined" || typeof DMSService === "undefined") { + return false; + } + + if (!DMSService.isConnected) { + return false; + } + + if (SessionData.nightModeUseIPLocation) { + DMSService.sendRequest("wayland.gamma.setUseIPLocation", { + "use": true + }, response => { + if (response?.error) { + console.warn("Theme automation: Failed to enable IP location", response.error); + } + }); + return true; + } else if (SessionData.latitude !== 0.0 && SessionData.longitude !== 0.0) { + DMSService.sendRequest("wayland.gamma.setUseIPLocation", { + "use": false + }, response => { + if (!response.error) { + DMSService.sendRequest("wayland.gamma.setLocation", { + "latitude": SessionData.latitude, + "longitude": SessionData.longitude + }, locResp => { + if (locResp?.error) { + console.warn("Theme automation: Failed to set location", locResp.error); + } + }); + } + }); + return true; + } + return false; + } + + Timer { + id: locationRetryTimer + interval: 1000 + repeat: true + running: false + property int retryCount: 0 + + onTriggered: { + if (root.sendLocationToBackend()) { + stop(); + retryCount = 0; + root.evaluateThemeMode(); + } else { + retryCount++; + if (retryCount >= 10) { + stop(); + retryCount = 0; + } + } + } + } + + function startThemeModeAutomation() { + root.themeModeAutomationActive = true; + + root.syncTimeThemeSchedule(); + root.syncLocationThemeSchedule(); + + const sent = root.sendLocationToBackend(); + + if (!sent && typeof SessionData !== "undefined" && SessionData.themeModeAutoMode === "location") { + locationRetryTimer.start(); + } else { + root.evaluateThemeMode(); + } + } + + function stopThemeModeAutomation() { + root.themeModeAutomationActive = false; + if (typeof DMSService !== "undefined" && DMSService.isConnected) { + DMSService.sendRequest("theme.auto.setEnabled", { + "enabled": false + }); + } + } +} |