From 42080db61bf8b9ecac5942a38cb54b08beef00cb Mon Sep 17 00:00:00 2001 From: haikalllp <152383572+haikalllp@users.noreply.github.com> Date: Thu, 27 Aug 2026 21:40:23 +0800 Subject: [PATCH] feat(theme): add support for matugen smart mode and type (#3109) * feat(theme): add support for matugen smart mode and type * add another dry run to get smart type preview * fix too much keywords in search index * revert dont extract translations * feat gate "Auto From Wallpaper" toggle row and the available matugen scheme for the pallete drop down * re-extract settings search index * make portal sync no longer disables matugen smart mode * make sure cosmic theme mode also follow change from smart mode * add translator context for Auto (Light Mode) / Auto (Dark Mode) / Auto From Wallpaper * fix: gate matugen smart mode checks on the dynamic theme so stock themes keep portal sync and fix tile labels in control center --- core/cmd/dms/commands_matugen.go | 8 +- core/internal/matugen/matugen.go | 98 +++++++++++++++++-- core/internal/matugen/matugen_test.go | 93 ++++++++++++++++++ core/internal/server/matugen_handler.go | 5 +- docs/CUSTOM_THEMES.md | 1 + quickshell/Common/SessionData.qml | 5 +- quickshell/Common/SettingsData.qml | 7 ++ quickshell/Common/Theme.qml | 18 +++- quickshell/Common/settings/SettingsSpec.js | 1 + .../ControlCenter/Components/DragDropGrid.qml | 5 +- .../Modules/Settings/ThemeColorsTab.qml | 36 ++++++- quickshell/Services/DMSService.qml | 9 ++ quickshell/Services/PortalService.qml | 2 + quickshell/matugen/templates/dank.json | 1 + .../translations/settings_search_index.json | 36 +++++++ 15 files changed, 305 insertions(+), 20 deletions(-) diff --git a/core/cmd/dms/commands_matugen.go b/core/cmd/dms/commands_matugen.go index ce7b20190..b9b5427c7 100644 --- a/core/cmd/dms/commands_matugen.go +++ b/core/cmd/dms/commands_matugen.go @@ -63,9 +63,9 @@ func init() { cmd.Flags().String("config-dir", "", "User config directory") cmd.Flags().String("kind", "image", "Source type: image or hex") cmd.Flags().String("value", "", "Wallpaper path or hex color") - cmd.Flags().String("mode", "dark", "Color mode: dark or light") + cmd.Flags().String("mode", "dark", "Color mode: dark, light, or smart (smart requires matugen 4.2+ and an image source)") cmd.Flags().String("icon-theme", "System Default", "Icon theme name") - cmd.Flags().String("matugen-type", "scheme-tonal-spot", "Matugen scheme type") + cmd.Flags().String("matugen-type", "scheme-tonal-spot", "Matugen scheme type (supports scheme-smart on matugen 4.2+)") cmd.Flags().Bool("run-user-templates", true, "Run user matugen templates") cmd.Flags().String("stock-colors", "", "Stock theme colors JSON") cmd.Flags().Bool("sync-mode-with-portal", false, "Sync color scheme with GNOME portal") @@ -77,6 +77,7 @@ func init() { matugenQueueCmd.Flags().Bool("wait", true, "Wait for completion") matugenQueueCmd.Flags().Duration("timeout", 90*time.Second, "Timeout for waiting") matugenPreviewCmd.Flags().String("source-color", "", "Source color used to generate previews") + matugenPreviewCmd.Flags().String("image", "", "Wallpaper image used to resolve the scheme-smart preview") matugenPreviewCmd.Flags().Float64("contrast", 0, "Contrast value from -1 to 1 (0 = standard)") matugenQtengineCmd.Flags().String("config-dir", "", "User config directory") matugenQtengineCmd.Flags().String("icon-theme", "", "Icon theme name") @@ -221,8 +222,9 @@ func runMatugenCheck(cmd *cobra.Command, args []string) { func runMatugenPreview(cmd *cobra.Command, args []string) { sourceColor, _ := cmd.Flags().GetString("source-color") + imagePath, _ := cmd.Flags().GetString("image") contrast, _ := cmd.Flags().GetFloat64("contrast") - previews, err := matugen.PreviewSchemes(sourceColor, contrast) + previews, err := matugen.PreviewSchemes(sourceColor, contrast, imagePath) if err != nil { log.Fatalf("Failed to generate Matugen previews: %v", err) } diff --git a/core/internal/matugen/matugen.go b/core/internal/matugen/matugen.go index 45a6503e7..c4d94521e 100644 --- a/core/internal/matugen/matugen.go +++ b/core/internal/matugen/matugen.go @@ -29,6 +29,7 @@ type ColorMode string const ( ColorModeDark ColorMode = "dark" ColorModeLight ColorMode = "light" + ColorModeSmart ColorMode = "smart" ) type TemplateKind int @@ -94,6 +95,7 @@ var ( matugenVersionOK bool matugenSupportsCOE bool matugenIsV4 bool + matugenIsV42 bool ) type Options struct { @@ -139,16 +141,17 @@ var previewSchemeTypes = []string{ "scheme-rainbow", } -func PreviewSchemes(sourceColor string, contrast float64) (map[string]SchemePreview, error) { +func PreviewSchemes(sourceColor string, contrast float64, imagePath string) (map[string]SchemePreview, error) { if sourceColor == "" { return nil, fmt.Errorf("source color is required") } - previews := make(map[string]SchemePreview, len(previewSchemeTypes)) + previews := make(map[string]SchemePreview, len(previewSchemeTypes)+1) for _, schemeType := range previewSchemeTypes { output, err := runMatugenDryRun(&Options{ Kind: "hex", Value: sourceColor, + Mode: ColorModeDark, MatugenType: schemeType, Contrast: contrast, }) @@ -163,9 +166,39 @@ func PreviewSchemes(sourceColor string, contrast float64) (map[string]SchemePrev } previews[schemeType] = SchemePreview{Dark: dark, Light: light} } + + previews["scheme-smart"] = smartSchemePreview(previews["scheme-tonal-spot"], contrast, imagePath) return previews, nil } +func smartSchemePreview(fallback SchemePreview, contrast float64, imagePath string) SchemePreview { + if imagePath == "" { + return fallback + } + flags, err := detectMatugenVersion() + if err != nil || !flags.isV42 { + return fallback + } + output, err := runMatugenDryRun(&Options{ + Kind: "image", + Value: imagePath, + Mode: ColorModeDark, + MatugenType: "scheme-smart", + Contrast: contrast, + }) + if err != nil { + log.Warnf("Smart scheme preview failed falling back to tonal-spot: %v", err) + return fallback + } + dark := extractMatugenColor(output, "primary", "dark") + light := extractMatugenColor(output, "primary", "light") + if dark == "" || light == "" { + log.Warn("Smart scheme preview failed falling back to tonal-spot: primary colors missing from matugen output") + return fallback + } + return SchemePreview{Dark: dark, Light: light} +} + func (o *Options) ColorsOutput() string { return filepath.Join(o.StateDir, "dms-colors.json") } @@ -280,6 +313,14 @@ func Run(opts Options) error { func buildOnce(opts *Options) (bool, error) { defer os.Remove(opts.colorsStaging()) + flags, err := detectMatugenVersion() + if err != nil { + return false, err + } + if err := resolveSmartMode(opts, flags); err != nil { + return false, err + } + cfgFile, err := os.CreateTemp("", "matugen-config-*.toml") if err != nil { return false, fmt.Errorf("failed to create temp config: %w", err) @@ -757,6 +798,7 @@ func extractTOMLSection(content, startMarker, endMarker string) string { type matugenFlags struct { supportsCOE bool isV4 bool + isV42 bool } func detectMatugenVersion() (matugenFlags, error) { @@ -764,12 +806,17 @@ func detectMatugenVersion() (matugenFlags, error) { defer matugenVersionMu.Unlock() if matugenVersionOK { - return matugenFlags{matugenSupportsCOE, matugenIsV4}, nil + return matugenFlags{matugenSupportsCOE, matugenIsV4, matugenIsV42}, nil } return detectMatugenVersionLocked() } +func SupportsSmart() bool { + flags, err := detectMatugenVersion() + return err == nil && flags.isV42 +} + func redetectMatugenVersion(old matugenFlags) (matugenFlags, bool) { matugenVersionMu.Lock() defer matugenVersionMu.Unlock() @@ -779,7 +826,7 @@ func redetectMatugenVersion(old matugenFlags) (matugenFlags, bool) { if err != nil { return old, false } - changed := flags.supportsCOE != old.supportsCOE || flags.isV4 != old.isV4 + changed := flags.supportsCOE != old.supportsCOE || flags.isV4 != old.isV4 || flags.isV42 != old.isV42 return flags, changed } @@ -811,6 +858,7 @@ func detectMatugenVersionLocked() (matugenFlags, error) { matugenSupportsCOE = major > 3 || (major == 3 && minor >= 1) matugenIsV4 = major >= 4 + matugenIsV42 = major > 4 || (major == 4 && minor >= 2) matugenVersionOK = true if matugenSupportsCOE { @@ -819,7 +867,7 @@ func detectMatugenVersionLocked() (matugenFlags, error) { if matugenIsV4 { log.Debugf("Matugen %s detected: using v4 compatibility flags", versionStr) } - return matugenFlags{matugenSupportsCOE, matugenIsV4}, nil + return matugenFlags{matugenSupportsCOE, matugenIsV4, matugenIsV42}, nil } func buildMatugenArgs(baseArgs []string, flags matugenFlags) []string { @@ -896,7 +944,7 @@ func execDryRun(opts *Options, flags matugenFlags) (string, error) { default: baseArgs = []string{opts.Kind, opts.Value} } - baseArgs = append(baseArgs, "-m", "dark", "-t", opts.MatugenType, "--json", "hex", "--dry-run") + baseArgs = append(baseArgs, "-m", string(opts.Mode), "-t", opts.MatugenType, "--json", "hex", "--dry-run") baseArgs = appendContrastArg(baseArgs, opts.Contrast) if flags.isV4 { baseArgs = append(baseArgs, "--source-color-index", "0", "--old-json-output") @@ -940,6 +988,44 @@ func extractMatugenColor(jsonStr, colorName, variant string) string { return variantData } +func extractTopLevelString(jsonStr, key string) string { + var data map[string]any + if err := json.Unmarshal([]byte(jsonStr), &data); err != nil { + return "" + } + if val, ok := data[key].(string); ok { + return val + } + return "" +} + +func resolveSmartMode(opts *Options, flags matugenFlags) error { + if opts.MatugenType == "scheme-smart" && !flags.isV42 { + return fmt.Errorf("scheme-smart requires matugen 4.2+") + } + if opts.Mode != ColorModeSmart { + return nil + } + if !flags.isV42 { + return fmt.Errorf("smart mode requires matugen 4.2+") + } + if opts.Kind != "image" || opts.StockColors != "" { + opts.Mode = ColorModeDark + return nil + } + output, err := runMatugenDryRun(opts) + if err != nil { + return fmt.Errorf("smart mode resolution failed: %w", err) + } + resolved := extractTopLevelString(output, "mode") + if resolved != string(ColorModeLight) && resolved != string(ColorModeDark) { + return fmt.Errorf("smart mode resolution returned unexpected mode %q", resolved) + } + log.Infof("Smart mode resolved to %s", resolved) + opts.Mode = ColorMode(resolved) + return nil +} + func extractNestedColor(jsonStr, colorName, variant string) string { var data map[string]any if err := json.Unmarshal([]byte(jsonStr), &data); err != nil { diff --git a/core/internal/matugen/matugen_test.go b/core/internal/matugen/matugen_test.go index e03a42264..dff0ec185 100644 --- a/core/internal/matugen/matugen_test.go +++ b/core/internal/matugen/matugen_test.go @@ -970,3 +970,96 @@ func TestSyncQtengineConfigBumpsMtime(t *testing.T) { } assert.Equal(t, string(before), string(after)) } + +func TestExtractTopLevelString(t *testing.T) { + tests := []struct { + name string + jsonStr string + key string + want string + }{ + {name: "returns top-level string key", jsonStr: `{"mode":"light","colors":{}}`, key: "mode", want: "light"}, + {name: "empty for missing key", jsonStr: `{"mode":"dark"}`, key: "is_dark_mode", want: ""}, + {name: "empty for non-string value", jsonStr: `{"mode":42}`, key: "mode", want: ""}, + {name: "empty for invalid json", jsonStr: `{mode`, key: "mode", want: ""}, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + assert.Equal(t, tc.want, extractTopLevelString(tc.jsonStr, tc.key)) + }) + } +} + +func TestResolveSmartMode(t *testing.T) { + tests := []struct { + name string + mode ColorMode + matugenType string + kind string + stockColors string + isV42 bool + wantErr string + wantMode ColorMode + }{ + { + name: "concrete mode passes through untouched", + mode: ColorModeLight, + kind: "image", + isV42: true, + wantMode: ColorModeLight, + }, + { + name: "smart mode errors on old matugen", + mode: ColorModeSmart, + kind: "image", + isV42: false, + wantErr: "smart mode requires matugen 4.2+", + }, + { + name: "scheme-smart errors on old matugen", + mode: ColorModeDark, + matugenType: "scheme-smart", + kind: "image", + isV42: false, + wantErr: "scheme-smart requires matugen 4.2+", + }, + { + name: "smart falls back to dark for non-image kind", + mode: ColorModeSmart, + kind: "hex", + isV42: true, + wantMode: ColorModeDark, + }, + { + name: "smart falls back to dark for stock colors", + mode: ColorModeSmart, + kind: "image", + stockColors: `{"primary":{}}`, + isV42: true, + wantMode: ColorModeDark, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + opts := &Options{ + Mode: tc.mode, + MatugenType: tc.matugenType, + Kind: tc.kind, + StockColors: tc.stockColors, + } + if opts.MatugenType == "" { + opts.MatugenType = "scheme-tonal-spot" + } + + err := resolveSmartMode(opts, matugenFlags{isV42: tc.isV42}) + if tc.wantErr != "" { + assert.ErrorContains(t, err, tc.wantErr) + return + } + assert.NoError(t, err) + assert.Equal(t, tc.wantMode, opts.Mode) + }) + } +} diff --git a/core/internal/server/matugen_handler.go b/core/internal/server/matugen_handler.go index 0c84cd491..2c0b02258 100644 --- a/core/internal/server/matugen_handler.go +++ b/core/internal/server/matugen_handler.go @@ -72,7 +72,8 @@ func handleMatugenQueue(conn *models.Conn, req models.Request) { func handleMatugenStatus(conn *models.Conn, req models.Request) { queue := matugen.GetQueue() models.Respond(conn, req.ID, map[string]bool{ - "running": queue.IsRunning(), - "pending": queue.HasPending(), + "running": queue.IsRunning(), + "pending": queue.HasPending(), + "smartSupported": matugen.SupportsSmart(), }) } diff --git a/docs/CUSTOM_THEMES.md b/docs/CUSTOM_THEMES.md index 30d8c4cef..f40979d71 100644 --- a/docs/CUSTOM_THEMES.md +++ b/docs/CUSTOM_THEMES.md @@ -141,6 +141,7 @@ While the core colors above are required, you can also customize these optional - `scheme-monochrome` - Monochromatic color schemes - `scheme-neutral` - Neutral, subdued color schemes - `scheme-rainbow` - Rainbow-like color schemes + - `scheme-smart` - Automatically picks the variant based on the wallpaper (requires matugen 4.2+) - `scheme-tonal-spot` - Tonal spot color schemes (default) ## Setting Custom Theme diff --git a/quickshell/Common/SessionData.qml b/quickshell/Common/SessionData.qml index 39372f91d..85317871b 100644 --- a/quickshell/Common/SessionData.qml +++ b/quickshell/Common/SessionData.qml @@ -611,9 +611,10 @@ Singleton { } } - function setLightMode(lightMode) { + function setLightMode(lightMode, skipWallpaperSync) { isSwitchingMode = true; - syncWallpaperForCurrentMode(lightMode); + if (!skipWallpaperSync) + syncWallpaperForCurrentMode(lightMode); isLightMode = lightMode; saveSettings(); Qt.callLater(() => { diff --git a/quickshell/Common/SettingsData.qml b/quickshell/Common/SettingsData.qml index 6e7b82a9e..33cbf53db 100644 --- a/quickshell/Common/SettingsData.qml +++ b/quickshell/Common/SettingsData.qml @@ -165,6 +165,7 @@ Singleton { property string customThemeFile: "" property var registryThemeVariants: ({}) property string matugenScheme: "scheme-tonal-spot" + property bool matugenSmartMode: false property real matugenContrast: 0 property bool runUserMatugenTemplates: true property string matugenTargetMonitor: "" @@ -2732,6 +2733,12 @@ Singleton { } } + function setMatugenSmartMode(enabled) { + if (matugenSmartMode === enabled) + return; + set("matugenSmartMode", enabled); + } + function setMatugenContrast(value) { if (matugenContrast === value) return; diff --git a/quickshell/Common/Theme.qml b/quickshell/Common/Theme.qml index d40e60bb3..65203c12b 100644 --- a/quickshell/Common/Theme.qml +++ b/quickshell/Common/Theme.qml @@ -361,6 +361,10 @@ Singleton { "value": "scheme-rainbow", "label": I18n.tr("Rainbow", "matugen color scheme option"), "description": I18n.tr("Diverse palette spanning the full spectrum.") + }), ({ + "value": "scheme-smart", + "label": I18n.tr("Smart", "matugen color scheme option"), + "description": I18n.tr("Automatically picks the scheme variant based on the wallpaper.") })] function getMatugenScheme(value) { @@ -1198,6 +1202,11 @@ Singleton { } function setLightMode(light, savePrefs = true, enableTransition = false) { + if (typeof SettingsData !== "undefined" && SettingsData.matugenSmartMode) { + SettingsData.matugenSmartMode = false; + SettingsData.saveSettings(); + } + if (enableTransition) { screenTransition(); lightModeTransitionTimer.lightMode = light; @@ -1525,7 +1534,7 @@ Singleton { const desired = { "kind": kind, "value": value, - "mode": isLight ? "light" : "dark", + "mode": (typeof SettingsData !== "undefined" && SettingsData.matugenSmartMode && kind === "image" && !stockColors) ? "smart" : (isLight ? "light" : "dark"), "iconTheme": iconTheme || "System Default", "matugenType": matugenType || "scheme-tonal-spot", "runUserTemplates": (typeof SettingsData !== "undefined") ? SettingsData.runUserMatugenTemplates : true @@ -2110,6 +2119,13 @@ Singleton { const colorsText = dynamicColorsFileView.text(); if (colorsText) { root.matugenColors = JSON.parse(colorsText); + if (typeof SettingsData !== "undefined" && SettingsData.matugenSmartMode && currentTheme === dynamic && root.matugenColors && root.matugenColors.mode && typeof SessionData !== "undefined" && !SessionData.isSwitchingMode) { + const resolvedLight = root.matugenColors.mode === "light"; + if (SessionData.isLightMode !== resolvedLight) { + SessionData.setLightMode(resolvedLight, true); + SettingsData.updateCosmicThemeMode(resolvedLight); + } + } if (typeof ToastService !== "undefined") { ToastService.clearWallpaperError(); } diff --git a/quickshell/Common/settings/SettingsSpec.js b/quickshell/Common/settings/SettingsSpec.js index 082cd30e7..00be17950 100644 --- a/quickshell/Common/settings/SettingsSpec.js +++ b/quickshell/Common/settings/SettingsSpec.js @@ -13,6 +13,7 @@ var SPEC = { customThemeFile: { def: "" }, registryThemeVariants: { def: {} }, matugenScheme: { def: "scheme-tonal-spot", onChange: "regenSystemThemes" }, + matugenSmartMode: { def: false, onChange: "regenSystemThemes" }, matugenContrast: { def: 0, onChange: "regenSystemThemes" }, runUserMatugenTemplates: { def: true, onChange: "regenSystemThemes" }, matugenTargetMonitor: { def: "", onChange: "regenSystemThemes" }, diff --git a/quickshell/Modules/ControlCenter/Components/DragDropGrid.qml b/quickshell/Modules/ControlCenter/Components/DragDropGrid.qml index abbf2e9a0..87c4d0974 100644 --- a/quickshell/Modules/ControlCenter/Components/DragDropGrid.qml +++ b/quickshell/Modules/ControlCenter/Components/DragDropGrid.qml @@ -743,8 +743,11 @@ Column { switch (widgetData.id || "") { case "nightMode": return I18n.tr("Night Mode"); - case "darkMode": + case "darkMode": { + if (SettingsData.matugenSmartMode && Theme.currentTheme === Theme.dynamic) + return SessionData.isLightMode ? I18n.tr("Auto (Light Mode)", "dark mode toggle label when matugen smart mode resolved light") : I18n.tr("Auto (Dark Mode)", "dark mode toggle label when matugen smart mode resolved dark"); return I18n.tr("Dark Mode"); + } case "idleInhibitor": return SessionService.idleInhibited ? I18n.tr("Keeping Awake") : I18n.tr("Keep Awake"); default: diff --git a/quickshell/Modules/Settings/ThemeColorsTab.qml b/quickshell/Modules/Settings/ThemeColorsTab.qml index 14972acbe..7ba28abb9 100644 --- a/quickshell/Modules/Settings/ThemeColorsTab.qml +++ b/quickshell/Modules/Settings/ThemeColorsTab.qml @@ -20,9 +20,11 @@ Item { readonly property bool frameModeActive: SettingsData.frameEnabled property var cachedIconThemes: SettingsData.availableIconThemes property var cachedCursorThemes: SettingsData.availableCursorThemes - property var cachedMatugenSchemes: Theme.availableMatugenSchemes.map(option => option.label) + readonly property bool matugenSmartCapable: Theme.matugenAvailable && DMSService.matugenSmartSupported + property var cachedMatugenSchemes: Theme.availableMatugenSchemes.filter(option => DMSService.matugenSmartSupported || option.value !== "scheme-smart").map(option => option.label) property var matugenSchemePreviews: ({}) property string matugenPreviewSource: "" + property string matugenPreviewImage: "" property real matugenPreviewContrast: 0 property string matugenPreviewRequestKey: "" property var installedRegistryThemes: [] @@ -34,7 +36,7 @@ Item { const mode = SessionData.isLightMode ? "light" : "dark"; for (var i = 0; i < Theme.availableMatugenSchemes.length; i++) { const option = Theme.availableMatugenSchemes[i]; - const preview = matugenSchemePreviews[option.value]; + const preview = matugenSchemePreviews[option.value] || matugenSchemePreviews["scheme-tonal-spot"]; if (preview?.[mode]) map[option.label] = preview[mode]; } @@ -284,14 +286,18 @@ Item { return; const sourceColor = Theme.getMatugenColor("source_color", Theme.primary).toString(); const contrast = SettingsData.matugenContrast ?? 0; - const requestKey = sourceColor + "|" + contrast; - if (sourceColor === matugenPreviewSource && contrast === matugenPreviewContrast && Object.keys(matugenSchemePreviews).length > 0) + const imagePath = (Theme.rawWallpaperPath && !Theme.rawWallpaperPath.startsWith("#")) ? Theme.rawWallpaperPath : ""; + const requestKey = sourceColor + "|" + contrast + "|" + imagePath; + if (sourceColor === matugenPreviewSource && contrast === matugenPreviewContrast && imagePath === matugenPreviewImage && Object.keys(matugenSchemePreviews).length > 0) return; if (requestKey === matugenPreviewRequestKey) return; matugenPreviewRequestKey = requestKey; - Proc.runCommand("", [Proc.dmsBin, "matugen", "preview", "--source-color", sourceColor, "--contrast", contrast.toString()], (output, exitCode) => { + const args = [Proc.dmsBin, "matugen", "preview", "--source-color", sourceColor, "--contrast", contrast.toString()]; + if (imagePath) + args.push("--image", imagePath); + Proc.runCommand("", args, (output, exitCode) => { if (requestKey !== themeColorsTab.matugenPreviewRequestKey) return; if (exitCode !== 0) { @@ -301,6 +307,7 @@ Item { try { themeColorsTab.matugenSchemePreviews = JSON.parse(output.trim()); themeColorsTab.matugenPreviewSource = sourceColor; + themeColorsTab.matugenPreviewImage = imagePath; themeColorsTab.matugenPreviewContrast = contrast; } catch (e) { themeColorsTab.matugenPreviewRequestKey = ""; @@ -1313,12 +1320,31 @@ Item { } } + SettingsToggleRow { + tab: "theme" + tags: ["matugen", "smart", "wallpaper", "auto", "mode"] + settingKey: "matugenSmartMode" + text: I18n.tr("Auto From Wallpaper", "toggle that lets wallpaper brightness decide light/dark mode") + description: I18n.tr("Dark or light mode follows the wallpaper brightness") + checked: SettingsData.matugenSmartMode + visible: matugenSmartCapable + enabled: Theme.currentTheme === Theme.dynamic + opacity: enabled ? 1 : 0.4 + onToggled: checked => { + if (checked && SessionData.themeModeAutoEnabled) + SessionData.setThemeModeAutoEnabled(false); + SettingsData.setMatugenSmartMode(checked); + } + } + DankToggle { id: themeModeAutoToggle width: parent.width text: I18n.tr("Automatic Control") checked: SessionData.themeModeAutoEnabled onToggled: checked => { + if (checked && SettingsData.matugenSmartMode) + SettingsData.setMatugenSmartMode(false); SessionData.setThemeModeAutoEnabled(checked); } diff --git a/quickshell/Services/DMSService.qml b/quickshell/Services/DMSService.qml index 62233155f..50b5e689d 100644 --- a/quickshell/Services/DMSService.qml +++ b/quickshell/Services/DMSService.qml @@ -24,6 +24,7 @@ Singleton { property bool isConnected: false readonly property bool isConnecting: requestSocket.connected && !requestSocket.linkUp property bool subscribeConnected: false + property bool matugenSmartSupported: false readonly property string socketPath: Quickshell.env("DMS_SOCKET") @@ -150,6 +151,7 @@ Singleton { root.isConnected = true; root.connectionStateChanged(); subscribeSocket.connected = true; + root.refreshMatugenStatus(); return; } root.isConnected = false; @@ -212,6 +214,13 @@ Singleton { } } + function refreshMatugenStatus() { + sendRequest("matugen.status", null, response => { + if (!response.error && response.result) + matugenSmartSupported = response.result.smartSupported === true; + }); + } + function sendSubscribeRequest() { const request = { "method": "subscribe", diff --git a/quickshell/Services/PortalService.qml b/quickshell/Services/PortalService.qml index cdf743b91..2b86cddf4 100644 --- a/quickshell/Services/PortalService.qml +++ b/quickshell/Services/PortalService.qml @@ -91,6 +91,8 @@ Singleton { return false; if (typeof SessionData !== "undefined" && SessionData.themeModeAutoEnabled) return false; + if (SettingsData.matugenSmartMode && typeof Theme !== "undefined" && Theme.currentTheme === Theme.dynamic) + return false; return typeof Theme !== "undefined"; } diff --git a/quickshell/matugen/templates/dank.json b/quickshell/matugen/templates/dank.json index 15e5a77dc..33c7666ee 100644 --- a/quickshell/matugen/templates/dank.json +++ b/quickshell/matugen/templates/dank.json @@ -17,6 +17,7 @@ "color14": {"dark": "{{dank16.color14.dark.hex}}", "light": "{{dank16.color14.light.hex}}", "default": "{{dank16.color14.default.hex}}"}, "color15": {"dark": "{{dank16.color15.dark.hex}}", "light": "{{dank16.color15.light.hex}}", "default": "{{dank16.color15.default.hex}}"} }, + "mode": "{{mode}}", "colors": { "dark": { "background": "{{colors.background.dark.hex}}", diff --git a/quickshell/translations/settings_search_index.json b/quickshell/translations/settings_search_index.json index 5dee4a130..28d1345d2 100644 --- a/quickshell/translations/settings_search_index.json +++ b/quickshell/translations/settings_search_index.json @@ -3564,6 +3564,42 @@ "icon": "apps", "description": "Sync dark mode with settings portals for system-wide theme hints" }, + { + "section": "matugenSmartMode", + "label": "Auto From Wallpaper", + "tabIndex": 10, + "category": "Theme & Colors", + "keywords": [ + "appearance", + "auto", + "background", + "backlight", + "bg", + "bright", + "brightness", + "colors", + "dark", + "dark mode", + "day", + "desktop", + "dim", + "follows", + "image", + "light", + "light mode", + "look", + "matugen", + "mode", + "night", + "picture", + "scheme", + "smart", + "style", + "theme", + "wallpaper" + ], + "description": "Dark or light mode follows the wallpaper brightness" + }, { "section": "cursorHideAfterInactive", "label": "Auto-Hide Timeout",