diff --git a/.gitignore b/.gitignore index 9b0e22921..a72168b17 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,5 @@ prepare **/.claude/settings.local.json .direnv/ + +pnpm-lock.yaml diff --git a/env.d.ts b/env.d.ts index 665d2c35e..7f4212097 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 diff --git a/src/services/matugen/index.ts b/src/services/matugen/index.ts index 5d533d01d..b4b027f5d 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,153 @@ 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; + + // 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)) { + const obj = value as Record; + // Extract hex from mode-based color object {dark, default, light} + processedColors[key] = obj[mode] ?? obj.default; + } + 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 +256,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 +264,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 +277,8 @@ export class MatugenService { const colorValue = defaultColorMap[colorKey]; if (colorValue === incomingHex) { - return matugenVariation[colorKey] ?? incomingHex; + const mappedColor = matugenVariation[colorKey] ?? incomingHex; + return mappedColor; } } diff --git a/src/style/optionsTrackers.ts b/src/style/optionsTrackers.ts index b7f3160f7..10fa8eafd 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();