From 51eb4d2ee751624727cfb20881cacad86b9c0ede Mon Sep 17 00:00:00 2001 From: zodiac3k Date: Mon, 10 Nov 2025 14:03:42 +0530 Subject: [PATCH 1/6] chore: Update .gitignore to include .pnpm-lock.yaml --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 9b0e2292..c05c3121 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,5 @@ prepare **/.claude/settings.local.json .direnv/ + +.pnpm-lock.yaml From bd571e92a3ca5c3fd6d22df9f114fd909c010302 Mon Sep 17 00:00:00 2001 From: zodiac3k Date: Mon, 10 Nov 2025 14:03:59 +0530 Subject: [PATCH 2/6] feat: Add support for importing GIF files in TypeScript declarations --- env.d.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/env.d.ts b/env.d.ts index 665d2c35..7f421209 100644 --- a/env.d.ts +++ b/env.d.ts @@ -20,3 +20,8 @@ declare module '*.css' { const content: string; export default content; } + +declare module '*.gif' { + const content: string; + export default content; +} \ No newline at end of file From 6a98d4e09306af87095c6a5c466452eaa943f188 Mon Sep 17 00:00:00 2001 From: zodiac3k Date: Mon, 10 Nov 2025 14:04:26 +0530 Subject: [PATCH 3/6] chore: Update .gitignore to remove leading dot from pnpm-lock.yaml --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index c05c3121..a72168b1 100644 --- a/.gitignore +++ b/.gitignore @@ -8,4 +8,4 @@ prepare .direnv/ -.pnpm-lock.yaml +pnpm-lock.yaml From 9cf4a868f98a4166ad86ce9b6f51e02599153f65 Mon Sep 17 00:00:00 2001 From: zodiac3k Date: Mon, 10 Nov 2025 16:31:43 +0530 Subject: [PATCH 4/6] feat: Enhance Matugen color generation with improved error handling and path normalization - Added normalization of wallpaper paths to ensure valid image files are processed. - Improved error messages and notifications for better user feedback when Matugen is disabled or when errors occur during color generation. - Updated the color mapping logic to handle different color formats and ensure only hex strings are returned. --- src/services/matugen/index.ts | 202 ++++++++++++++++++++++++++++++++-- 1 file changed, 194 insertions(+), 8 deletions(-) diff --git a/src/services/matugen/index.ts b/src/services/matugen/index.ts index 5d533d01..ac578ad4 100644 --- a/src/services/matugen/index.ts +++ b/src/services/matugen/index.ts @@ -5,6 +5,7 @@ import { SystemUtilities } from 'src/core/system/SystemUtilities'; import options from 'src/configuration'; import { isAnImage } from 'src/lib/validation/images'; import { defaultColorMap } from './defaults'; +import { normalizeToAbsolutePath } from 'src/lib/path/helpers'; const MATUGEN_ENABLED = options.theme.matugen; const MATUGEN_SETTINGS = options.theme.matugen_settings; @@ -46,19 +47,42 @@ export class MatugenService { * @returns The generated color palette or undefined if generation fails */ public async generateMatugenColors(): Promise { - if (!MATUGEN_ENABLED.get() || !SystemUtilities.checkDependencies('matugen')) { + if (!MATUGEN_ENABLED.get()) { + console.warn('[Matugen] Matugen is disabled in settings'); + return; + } + + if (!SystemUtilities.checkDependencies('matugen')) { + console.error('[Matugen] matugen command not found. Please install matugen.'); + SystemUtilities.notify({ + summary: 'Matugen Failed', + body: 'matugen command not found. Please install matugen.', + iconName: icons.ui.warning, + }); return; } const wallpaperPath = options.wallpaper.image.get(); - if (!wallpaperPath || !isAnImage(wallpaperPath)) { + if (!wallpaperPath) { SystemUtilities.notify({ summary: 'Matugen Failed', body: "Please select a wallpaper in 'Theming > General' first.", iconName: icons.ui.warning, }); + return; + } + // Check if it's a valid image file + const normalizedPath = normalizeToAbsolutePath(wallpaperPath); + + if (!isAnImage(normalizedPath)) { + console.warn(`[Matugen] Invalid wallpaper path or not an image: ${normalizedPath}`); + SystemUtilities.notify({ + summary: 'Matugen Failed', + body: `Invalid wallpaper path: ${wallpaperPath}`, + iconName: icons.ui.warning, + }); return; } @@ -67,20 +91,178 @@ export class MatugenService { const schemeType = MATUGEN_SETTINGS.scheme_type.get(); const mode = MATUGEN_SETTINGS.mode.get(); - const baseCommand = `matugen image -q "${wallpaperPath}" -t scheme-${schemeType} --contrast ${normalizedContrast}`; + const baseCommand = `matugen image -q "${normalizedPath}" -t scheme-${schemeType} --mode ${mode} --contrast ${normalizedContrast}`; const jsonResult = await SystemUtilities.bash(`${baseCommand} --dry-run --json hex`); + + if (!jsonResult || jsonResult.trim().length === 0) { + console.error('[Matugen] Matugen returned empty output'); + throw new Error('Matugen returned empty output'); + } + await SystemUtilities.bash(baseCommand); const parsedResult = JSON.parse(jsonResult); - return parsedResult?.colors?.[mode]; + + if (!parsedResult?.colors) { + console.error('[Matugen] Parsed result missing colors:', parsedResult); + throw new Error('Matugen result missing colors'); + } + + // Matugen returns colors as objects with mode-based properties when using --json hex + // Structure before destructuring: + // { + // colors: { + // background: { dark: "#0f1512", default: "#0f1512", light: "#f5fbf6" }, + // error: { dark: "#ba1a1a", default: "#ba1a1a", light: "#de3730" }, + // error_container: { dark: "#93000a", default: "#93000a", light: "#ffdad6" }, + // inverse_on_surface: { dark: "#1a1c19", default: "#1a1c19", light: "#f0f0f0" }, + // inverse_primary: { dark: "#006c4c", default: "#006c4c", light: "#4dd0a6" }, + // inverse_surface: { dark: "#1a1c19", default: "#1a1c19", light: "#f0f0f0" }, + // on_background: { dark: "#e1e3de", default: "#e1e3de", light: "#1a1c19" }, + // on_error: { dark: "#ffffff", default: "#ffffff", light: "#ffffff" }, + // on_error_container: { dark: "#ffdad6", default: "#ffdad6", light: "#410002" }, + // on_primary: { dark: "#003829", default: "#003829", light: "#ffffff" }, + // on_primary_container: { dark: "#00513c", default: "#00513c", light: "#002114" }, + // on_primary_fixed: { dark: "#002114", default: "#002114", light: "#002114" }, + // on_primary_fixed_variant: { dark: "#00513c", default: "#00513c", light: "#00513c" }, + // on_secondary: { dark: "#1a3529", default: "#1a3529", light: "#ffffff" }, + // on_secondary_container: { dark: "#1e4d3a", default: "#1e4d3a", light: "#002114" }, + // on_secondary_fixed: { dark: "#002114", default: "#002114", light: "#002114" }, + // on_secondary_fixed_variant: { dark: "#1e4d3a", default: "#1e4d3a", light: "#1e4d3a" }, + // on_surface: { dark: "#e1e3de", default: "#e1e3de", light: "#1a1c19" }, + // on_surface_variant: { dark: "#c1c9c0", default: "#c1c9c0", light: "#414942" }, + // on_tertiary: { dark: "#1a2836", default: "#1a2836", light: "#ffffff" }, + // on_tertiary_container: { dark: "#1e3a52", default: "#1e3a52", light: "#001e2e" }, + // on_tertiary_fixed: { dark: "#001e2e", default: "#001e2e", light: "#001e2e" }, + // on_tertiary_fixed_variant: { dark: "#1e3a52", default: "#1e3a52", light: "#1e3a52" }, + // outline: { dark: "#8b9389", default: "#8b9389", light: "#71796f" }, + // outline_variant: { dark: "#414942", default: "#414942", light: "#c1c9c0" }, + // primary: { dark: "#4dd0a6", default: "#4dd0a6", light: "#006c4c" }, + // primary_container: { dark: "#00513c", default: "#00513c", light: "#4dd0a6" }, + // primary_fixed: { dark: "#4dd0a6", default: "#4dd0a6", light: "#4dd0a6" }, + // primary_fixed_dim: { dark: "#2fb58a", default: "#2fb58a", light: "#2fb58a" }, + // scrim: { dark: "#000000", default: "#000000", light: "#000000" }, + // secondary: { dark: "#2fb58a", default: "#2fb58a", light: "#4dd0a6" }, + // secondary_container: { dark: "#1e4d3a", default: "#1e4d3a", light: "#2fb58a" }, + // secondary_fixed: { dark: "#2fb58a", default: "#2fb58a", light: "#2fb58a" }, + // secondary_fixed_dim: { dark: "#119a6f", default: "#119a6f", light: "#119a6f" }, + // shadow: { dark: "#000000", default: "#000000", light: "#000000" }, + // surface: { dark: "#111411", default: "#111411", light: "#f7faf7" }, + // surface_bright: { dark: "#373a37", default: "#373a37", light: "#f7faf7" }, + // surface_container: { dark: "#1b1e1b", default: "#1b1e1b", light: "#ecf0ec" }, + // surface_container_high: { dark: "#252825", default: "#252825", light: "#e6ebe6" }, + // surface_container_highest: { dark: "#303330", default: "#303330", light: "#e0e5e0" }, + // surface_container_low: { dark: "#161916", default: "#161916", light: "#f2f6f2" }, + // surface_container_lowest: { dark: "#0c0f0c", default: "#0c0f0c", light: "#ffffff" }, + // surface_dim: { dark: "#111411", default: "#111411", light: "#d8dbd7" }, + // surface_variant: { dark: "#414942", default: "#414942", light: "#c1c9c0" }, + // tertiary: { dark: "#7ab3d9", default: "#7ab3d9", light: "#005882" }, + // tertiary_container: { dark: "#1e3a52", default: "#1e3a52", light: "#7ab3d9" }, + // tertiary_fixed: { dark: "#7ab3d9", default: "#7ab3d9", light: "#7ab3d9" }, + // tertiary_fixed_dim: { dark: "#5499c2", default: "#5499c2", light: "#5499c2" } + // } + // } + // + // After destructuring based on selected mode (dark/light), we extract the hex value: + // { + // background: "#0f1512", // extracted from obj[mode] or obj.default + // error: "#ba1a1a", + // error_container: "#93000a", + // inverse_on_surface: "#1a1c19", + // inverse_primary: "#006c4c", + // inverse_surface: "#1a1c19", + // on_background: "#e1e3de", + // on_error: "#ffffff", + // on_error_container: "#ffdad6", + // on_primary: "#003829", + // on_primary_container: "#00513c", + // on_primary_fixed: "#002114", + // on_primary_fixed_variant: "#00513c", + // on_secondary: "#1a3529", + // on_secondary_container: "#1e4d3a", + // on_secondary_fixed: "#002114", + // on_secondary_fixed_variant: "#1e4d3a", + // on_surface: "#e1e3de", + // on_surface_variant: "#c1c9c0", + // on_tertiary: "#1a2836", + // on_tertiary_container: "#1e3a52", + // on_tertiary_fixed: "#001e2e", + // on_tertiary_fixed_variant: "#1e3a52", + // outline: "#8b9389", + // outline_variant: "#414942", + // primary: "#4dd0a6", + // primary_container: "#00513c", + // primary_fixed: "#4dd0a6", + // primary_fixed_dim: "#2fb58a", + // scrim: "#000000", + // secondary: "#2fb58a", + // secondary_container: "#1e4d3a", + // secondary_fixed: "#2fb58a", + // secondary_fixed_dim: "#119a6f", + // shadow: "#000000", + // surface: "#111411", + // surface_bright: "#373a37", + // surface_container: "#1b1e1b", + // surface_container_high: "#252825", + // surface_container_highest: "#303330", + // surface_container_low: "#161916", + // surface_container_lowest: "#0c0f0c", + // surface_dim: "#111411", + // surface_variant: "#414942", + // tertiary: "#7ab3d9", + // tertiary_container: "#1e3a52", + // tertiary_fixed: "#7ab3d9", + // tertiary_fixed_dim: "#5499c2" + // } + // + // This is then returned as MatugenColors type + let colors = parsedResult.colors; + + const processedColors: Record = {}; + for (const [key, value] of Object.entries(colors)) { + if (typeof value === 'string') { + // Already a string, use it directly + processedColors[key] = value; + } else if (typeof value === 'object' && value !== null) { + // Extract hex from mode-based color object + const obj = value as Record; + + // Check for mode-based colors (dark, default, light) + if (typeof obj[mode] === 'string') { + processedColors[key] = obj[mode] as string; + } else if (typeof obj.default === 'string') { + processedColors[key] = obj.default as string; + } else if (typeof obj.hex === 'string') { + processedColors[key] = obj.hex; + } else if ( + typeof obj.r === 'number' && + typeof obj.g === 'number' && + typeof obj.b === 'number' + ) { + // Convert RGB to hex + const hex = `#${[obj.r, obj.g, obj.b].map((x) => Math.round(x).toString(16).padStart(2, '0')).join('')}`; + processedColors[key] = hex; + } else { + console.error(`[Matugen] Cannot extract hex from color object for ${key}:`, obj); + throw new Error(`Cannot extract hex color from object for ${key}`); + } + } else { + console.error(`[Matugen] Unexpected color value type for ${key}: ${typeof value}`); + throw new Error(`Unexpected color value type for ${key}`); + } + } + colors = processedColors; + + return colors as MatugenColors; } catch (error) { + const errorMessage = error instanceof Error ? error.message : String(error); + console.error(`[Matugen] Error generating colors: ${errorMessage}`, error); SystemUtilities.notify({ summary: 'Matugen Error', - body: `An error occurred: ${error}`, + body: `An error occurred: ${errorMessage}`, iconName: icons.ui.info, }); - console.error(`An error occurred while generating matugen colors: ${error}`); return; } } @@ -99,7 +281,7 @@ export class MatugenService { * Maps a default color hex value to its Matugen-generated equivalent * * @param incomingHex - The original hex color to map - * @param matugenColors - The Matugen color palette to use for mapping + * @param matugenColors - The Matugen color palette to use for mapping (must be destructured hex strings, not objects) * @returns The mapped hex color or original if no mapping exists */ public getMatugenHex(incomingHex: HexColor, matugenColors?: MatugenColors): HexColor { @@ -107,6 +289,9 @@ export class MatugenService { return incomingHex; } + // matugenColors must contain hex strings (e.g., primary: "#4dd0a6") + // NOT objects (e.g., primary: { dark: "#4dd0a6", default: "#4dd0a6", light: "#006c4c" }) + // The destructuring happens in generateMatugenColors() before this is called const variation = MATUGEN_SETTINGS.variation.get(); const matugenVariation = getMatugenVariations(matugenColors, variation); @@ -117,7 +302,8 @@ export class MatugenService { const colorValue = defaultColorMap[colorKey]; if (colorValue === incomingHex) { - return matugenVariation[colorKey] ?? incomingHex; + const mappedColor = matugenVariation[colorKey] ?? incomingHex; + return mappedColor; } } From 269c6865627b3209d240768c11a097e0a677cb2f Mon Sep 17 00:00:00 2001 From: zodiac3k Date: Mon, 10 Nov 2025 16:44:27 +0530 Subject: [PATCH 5/6] fix: Cast wallpaperService to GObject.Object for type safety - Updated the connection method for wallpaperService to ensure it is treated as a GObject.Object, enhancing type safety and preventing potential runtime errors. --- src/style/optionsTrackers.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/style/optionsTrackers.ts b/src/style/optionsTrackers.ts index b7f3160f..10fa8eaf 100644 --- a/src/style/optionsTrackers.ts +++ b/src/style/optionsTrackers.ts @@ -1,3 +1,4 @@ +import GObject from 'astal/gobject'; import options from 'src/configuration'; import { normalizeToAbsolutePath } from 'src/lib/path/helpers'; import icons from '../lib/icons/icons'; @@ -27,7 +28,7 @@ export const initializeTrackers = (resetCssFunc: () => void): void => { ensureMatugenWallpaper(); }); - wallpaperService.connect('changed', () => { + (wallpaperService as GObject.Object).connect('changed', () => { console.info('Wallpaper changed, regenerating Matugen colors...'); if (options.theme.matugen.get()) { resetCssFunc(); From 8f5ba0a05fdc48cb2439cce9ae27b5d93f4c71f7 Mon Sep 17 00:00:00 2001 From: zodiac3k Date: Mon, 10 Nov 2025 17:18:18 +0530 Subject: [PATCH 6/6] refactor: Simplify color extraction logic in MatugenService - Streamlined the process of extracting hex values from mode-based color objects by removing redundant checks and directly using the mode or default values. - Improved code readability and maintainability while ensuring consistent handling of color formats. --- src/services/matugen/index.ts | 35 +++++------------------------------ 1 file changed, 5 insertions(+), 30 deletions(-) diff --git a/src/services/matugen/index.ts b/src/services/matugen/index.ts index ac578ad4..b4b027f5 100644 --- a/src/services/matugen/index.ts +++ b/src/services/matugen/index.ts @@ -219,38 +219,13 @@ export class MatugenService { // This is then returned as MatugenColors type let colors = parsedResult.colors; + // Extract hex values from mode-based color objects + // Matugen always returns objects with {dark, default, light} structure where values are hex strings const processedColors: Record = {}; for (const [key, value] of Object.entries(colors)) { - if (typeof value === 'string') { - // Already a string, use it directly - processedColors[key] = value; - } else if (typeof value === 'object' && value !== null) { - // Extract hex from mode-based color object - const obj = value as Record; - - // Check for mode-based colors (dark, default, light) - if (typeof obj[mode] === 'string') { - processedColors[key] = obj[mode] as string; - } else if (typeof obj.default === 'string') { - processedColors[key] = obj.default as string; - } else if (typeof obj.hex === 'string') { - processedColors[key] = obj.hex; - } else if ( - typeof obj.r === 'number' && - typeof obj.g === 'number' && - typeof obj.b === 'number' - ) { - // Convert RGB to hex - const hex = `#${[obj.r, obj.g, obj.b].map((x) => Math.round(x).toString(16).padStart(2, '0')).join('')}`; - processedColors[key] = hex; - } else { - console.error(`[Matugen] Cannot extract hex from color object for ${key}:`, obj); - throw new Error(`Cannot extract hex color from object for ${key}`); - } - } else { - console.error(`[Matugen] Unexpected color value type for ${key}: ${typeof value}`); - throw new Error(`Unexpected color value type for ${key}`); - } + const obj = value as Record; + // Extract hex from mode-based color object {dark, default, light} + processedColors[key] = obj[mode] ?? obj.default; } colors = processedColors;