diff --git a/Services/MatugenService.qml b/Services/MatugenService.qml index 81b83769..76b097f5 100644 --- a/Services/MatugenService.qml +++ b/Services/MatugenService.qml @@ -11,14 +11,69 @@ Singleton { id: root readonly property string colorsApplyScript: Quickshell.shellDir + '/Bin/colors-apply.sh' + readonly property string dynamicConfigPath: Settings.cacheDir + "matugen.dynamic.toml" - property string dynamicConfigPath: Settings.cacheDir + "matugen.dynamic.toml" + readonly property var templateConfigs: ({ + "gtk": { + "input": "gtk.css", + "outputs": [{ + "path": "~/.config/gtk-3.0/gtk.css" + }, { + "path": "~/.config/gtk-4.0/gtk.css" + }], + "postProcess": mode => `gsettings set org.gnome.desktop.interface color-scheme prefer-${mode}\n` + }, + "qt": { + "input": "qtct.conf", + "outputs": [{ + "path": "~/.config/qt5ct/colors/noctalia.conf" + }, { + "path": "~/.config/qt6ct/colors/noctalia.conf" + }] + }, + "fuzzel": { + "input": "fuzzel.conf", + "outputs": [{ + "path": "~/.config/fuzzel/themes/noctalia" + }], + "postProcess": () => `${colorsApplyScript} fuzzel\n` + }, + "pywalfox": { + "input": "pywalfox.json", + "outputs": [{ + "path": "~/.cache/wal/colors.json" + }], + "postProcess": () => `${colorsApplyScript} pywalfox\n` + }, + "vesktop": { + "input": "vesktop.css", + "outputs": [{ + "path": "~/.config/vesktop/themes/noctalia.theme.css" + }] + } + }) - // External state management + readonly property var terminalPaths: ({ + "foot": "~/.config/foot/themes/noctalia", + "ghostty": "~/.config/ghostty/themes/noctalia", + "kitty": "~/.config/kitty/themes/noctalia.conf" + }) + + readonly property var schemeNameMap: ({ + "Noctalia (default)": "Noctalia-default", + "Noctalia (legacy)": "Noctalia-legacy", + "Tokyo Night": "Tokyo-Night" + }) + + // ===== Lifecycle ===== + function init() { + Logger.log("Matugen", "Service started") + } + + // ===== External Connections ===== Connections { target: WallpaperService function onWallpaperChanged(screenName, path) { - // Only detect changes on main screen if (screenName === Screen.name && Settings.data.colorSchemes.useWallpaperColors) { generateFromWallpaper() } @@ -30,582 +85,234 @@ Singleton { function onDarkModeChanged() { Logger.log("Matugen", "Detected dark mode change") if (Settings.data.colorSchemes.useWallpaperColors) { - MatugenService.generateFromWallpaper() + generateFromWallpaper() } } } - // -------------------------------- - function init() { - // does nothing but ensure the singleton is created - // do not remove - Logger.log("Matugen", "Service started") - } - - // -------------------------------- - // Generate colors using current wallpaper and settings + // ===== Wallpaper Generation ===== function generateFromWallpaper() { Logger.log("Matugen", "Generating from wallpaper on screen:", Screen.name) - var wp = WallpaperService.getWallpaper(Screen.name).replace(/'/g, "'\\''") - if (wp === "") { - Logger.error("Matugen", "No wallpaper was found") + const wp = WallpaperService.getWallpaper(Screen.name).replace(/'/g, "'\\''") + if (!wp) { + Logger.error("Matugen", "No wallpaper found") return } - var content = MatugenTemplates.buildConfigToml() - if (content === "") { + const content = MatugenTemplates.buildConfigToml() + if (!content) return - } - var mode = Settings.data.colorSchemes.darkMode ? "dark" : "light" - var pathEsc = dynamicConfigPath.replace(/'/g, "'\\''") - var script = buildMatugenScript(content, pathEsc, wp, mode) + const mode = Settings.data.colorSchemes.darkMode ? "dark" : "light" + const script = buildMatugenScript(content, wp, mode) + generateProcess.command = ["bash", "-lc", script] generateProcess.running = true } - // -------------------------------- - function buildMatugenScript(content, pathEsc, wallpaper, mode) { - // Use a unique delimiter to avoid conflicts with config content - var delimiter = "MATUGEN_CONFIG_EOF_" + Math.random().toString(36).substr(2, 9) - var script = "cat > '" + pathEsc + "' << '" + delimiter + "'\n" + content + "\n" + delimiter + "\n" + function buildMatugenScript(content, wallpaper, mode) { + const delimiter = "MATUGEN_CONFIG_EOF_" + Math.random().toString(36).substr(2, 9) + const pathEsc = dynamicConfigPath.replace(/'/g, "'\\''") - // Main matugen command - script += "matugen image '" + wallpaper + "' --config '" + pathEsc + "' --mode " + mode + " --type " + Settings.data.colorSchemes.matugenSchemeType - - // Add user template execution if enabled - script += addUserTemplateExecution(wallpaper, mode) + let script = `cat > '${pathEsc}' << '${delimiter}'\n${content}\n${delimiter}\n` + script += `matugen image '${wallpaper}' --config '${pathEsc}' --mode ${mode} --type ${Settings.data.colorSchemes.matugenSchemeType}` + script += buildUserTemplateCommand(wallpaper, mode) return script + "\n" } - // -------------------------------- - function addUserTemplateExecution(input, mode) { - if (!Settings.data.templates.enableUserTemplates) { - return "" - } - - var userConfigPath = getUserConfigPath() - var script = "\n# Execute user config if it exists\n" - script += "if [ -f '" + userConfigPath + "' ]; then\n" - script += " matugen image '" + input + "' --config '" + userConfigPath + "' --mode " + mode + " --type " + Settings.data.colorSchemes.matugenSchemeType + "\n" - script += "fi" - - return script - } - - // -------------------------------- - function getUserConfigPath() { - return (Quickshell.env("HOME") + "/.config/matugen/config.toml").replace(/'/g, "'\\''") - } - - // -------------------------------- - function selectVibrantColor(schemeData, mode) { - var colors = [] - colors.push(schemeData[mode]["mPrimary"]) - colors.push(schemeData[mode]["mSecondary"]) - colors.push(schemeData[mode]["mTertiary"]) - - var bestScore = 0 - var bestScoreIndex = -1 - for (var i = 0; i < colors.length; i++) { - var hsl = ColorsConvert.hexToHSL(colors[i]) - - var score = hsl['s'] - if (score > bestScore) { - bestScore = score - bestScoreIndex = i - } - } - - return colors[bestScoreIndex] - } - - // -------------------------------- - // Generate templates from predefined color scheme + // ===== Predefined Scheme Generation ===== function generateFromPredefinedScheme(schemeData) { Logger.log("Matugen", "Generating templates from predefined color scheme") - // Handle terminal theme copying for predefined schemes handleTerminalThemes() - var content = MatugenTemplates.buildConfigToml() - if (content === "") { - return - } + const mode = Settings.data.colorSchemes.darkMode ? "dark" : "light" + const colors = schemeData[mode] || schemeData.dark || schemeData.light + const matugenColors = buildMatugenColorObject(colors) - var mode = Settings.data.colorSchemes.darkMode ? "dark" : "light" - var pathEsc = dynamicConfigPath.replace(/'/g, "'\\''") + const script = processAllTemplates(matugenColors, mode) - // Use the full predefined color scheme instead of generating from a single color - var script = buildPredefinedSchemeScriptWithFullColors(content, pathEsc, schemeData, mode) generateProcess.command = ["bash", "-lc", script] generateProcess.running = true } - // -------------------------------- - function buildPredefinedSchemeScriptWithFullColors(content, pathEsc, schemeData, mode) { - // Instead of using matugen color generation, directly process templates with predefined colors - var colors = schemeData[mode] || schemeData.dark || schemeData.light - var script = "" + function buildMatugenColorObject(colors) { + // Helper with fallback support + const c = (color, fallback) => ({ + "default": { + "hex": colors[color] || colors[fallback] || "#000000" + } + }) - // Process each enabled template directly with predefined colors - script += processTemplatesWithPredefinedColors(colors, mode) - - // Add user template execution if enabled - script += addUserTemplateExecutionForPredefinedColors(colors, mode) - - return script - } - - // -------------------------------- - function processTemplatesWithPredefinedColors(colors, mode) { - var script = "" - - // Create a colors object that matches matugen's expected format - var matugenColors = { - "primary": { - "default": { - "hex": colors.mPrimary - } - }, - "on_primary": { - "default": { - "hex": colors.mOnPrimary - } - }, - "primary_container": { - "default": { - "hex": colors.mPrimary - } - }, - "on_primary_container": { - "default": { - "hex": colors.mOnPrimary - } - }, - "secondary": { - "default": { - "hex": colors.mSecondary - } - }, - "on_secondary": { - "default": { - "hex": colors.mOnSecondary - } - }, - "secondary_container": { - "default": { - "hex": colors.mSecondary - } - }, - "on_secondary_container": { - "default": { - "hex": colors.mOnSecondary - } - }, - "tertiary": { - "default": { - "hex": colors.mTertiary - } - }, - "on_tertiary": { - "default": { - "hex": colors.mOnTertiary - } - }, - "tertiary_container": { - "default": { - "hex": colors.mTertiary - } - }, - "on_tertiary_container": { - "default": { - "hex": colors.mOnTertiary - } - }, - "error": { - "default": { - "hex": colors.mError - } - }, - "on_error": { - "default": { - "hex": colors.mOnError - } - }, - "error_container": { - "default": { - "hex": colors.mError - } - }, - "on_error_container": { - "default": { - "hex": colors.mOnError - } - }, - "background": { - "default": { - "hex": colors.mSurface - } - }, - "on_background": { - "default": { - "hex": colors.mOnSurface - } - }, - "surface": { - "default": { - "hex": colors.mSurface - } - }, - "on_surface": { - "default": { - "hex": colors.mOnSurface - } - }, - "surface_variant": { - "default": { - "hex": colors.mSurfaceVariant - } - }, - "on_surface_variant": { - "default": { - "hex": colors.mOnSurfaceVariant - } - }, - "outline": { - "default": { - "hex": colors.mOutline - } - }, - "outline_variant": { - "default": { - "hex": colors.mOutline - } - }, - "shadow": { - "default": { - "hex": colors.mShadow - } - }, - "surface_container": { - "default": { - "hex": colors.mSurfaceVariant - } - }, - "surface_container_low": { - "default": { - "hex": colors.mSurface - } - }, - "surface_container_lowest": { - "default": { - "hex": colors.mSurface - } - }, - "surface_container_high": { - "default": { - "hex": colors.mSurfaceVariant - } - }, - "surface_container_highest": { - "default": { - "hex": colors.mOutline - } - } + return { + "primary": c("mPrimary"), + "on_primary": c("mOnPrimary"), + "primary_container": c("mPrimaryContainer", "mPrimary"), + "on_primary_container": c("mOnPrimaryContainer", "mOnPrimary"), + "secondary": c("mSecondary"), + "on_secondary": c("mOnSecondary"), + "secondary_container": c("mSecondaryContainer", "mSecondary"), + "on_secondary_container": c("mOnSecondaryContainer", "mOnSecondary"), + "tertiary": c("mTertiary"), + "on_tertiary": c("mOnTertiary"), + "tertiary_container": c("mTertiaryContainer", "mTertiary"), + "on_tertiary_container": c("mOnTertiaryContainer", "mOnTertiary"), + "error": c("mError"), + "on_error": c("mOnError"), + "error_container": c("mErrorContainer", "mError"), + "on_error_container": c("mOnErrorContainer", "mOnError"), + "background": c("mBackground", "mSurface"), + "on_background": c("mOnBackground", "mOnSurface"), + "surface": c("mSurface"), + "on_surface": c("mOnSurface"), + "surface_variant": c("mSurfaceVariant", "mSurface"), + "on_surface_variant": c("mOnSurfaceVariant", "mOnSurface"), + "surface_container_lowest": c("mSurfaceContainerLowest", "mSurface"), + "surface_container_low": c("mSurfaceContainerLow", "mSurface"), + "surface_container": c("mSurfaceContainer", "mSurfaceVariant"), + "surface_container_high": c("mSurfaceContainerHigh", "mSurfaceVariant"), + "surface_container_highest": c("mSurfaceContainerHighest", "mOutline"), + "outline": c("mOutline"), + "outline_variant": c("mOutlineVariant", "mOutline"), + "shadow": c("mShadow") } + } - // Process each enabled template using the same structure as MatugenTemplates - var applications = [{ - "name": "gtk", - "templates": [{ - "version": "gtk3", - "output": "gtk3" - }, { - "version": "gtk4", - "output": "gtk4" - }], - "input": "gtk.css" - }, { - "name": "qt", - "templates": [{ - "version": "qt5", - "output": "qt5" - }, { - "version": "qt6", - "output": "qt6" - }], - "input": "qtct.conf" - }, { - "name": "fuzzel", - "templates": [{ - "version": "fuzzel", - "output": "fuzzel" - }], - "input": "fuzzel.conf" - }, { - "name": "pywalfox", - "templates": [{ - "version": "pywalfox", - "output": "pywalfox" - }], - "input": "pywalfox.json" - }, { - "name": "vesktop", - "templates": [{ - "version": "vesktop", - "output": "vesktop" - }], - "input": "vesktop.css" - }] + function processAllTemplates(colors, mode) { + let script = "" + const homeDir = Quickshell.env("HOME") - applications.forEach(function (app) { - if (Settings.data.templates[app.name]) { - script += processTemplateForApp(app.name, matugenColors, mode) - } - }) + Object.keys(templateConfigs).forEach(appName => { + if (Settings.data.templates[appName]) { + script += processTemplate(appName, colors, mode, homeDir) + } + }) return script } - // -------------------------------- - function processTemplateForApp(appName, colors, mode) { - var script = "" + function processTemplate(appName, colors, mode, homeDir) { + const config = templateConfigs[appName] + const templatePath = `${Quickshell.shellDir}/Assets/MatugenTemplates/${config.input}` + let script = "" - switch (appName) { - case "gtk": - script += processGtkTemplate(colors, mode) - break - case "qt": - script += processQtTemplate(colors, mode) - break - case "fuzzel": - script += processFuzzelTemplate(colors, mode) - break - case "pywalfox": - script += processPywalfoxTemplate(colors, mode) - break - case "vesktop": - script += processVesktopTemplate(colors, mode) - break + config.outputs.forEach(output => { + const outputPath = output.path.replace("~", homeDir) + const outputDir = outputPath.substring(0, outputPath.lastIndexOf('/')) + + script += `mkdir -p ${outputDir}\n` + script += `cp '${templatePath}' '${outputPath}'\n` + script += replaceColorsInFile(outputPath, colors) + }) + + if (config.postProcess) { + script += config.postProcess(mode) } return script } - // -------------------------------- - function processGtkTemplate(colors, mode) { - var script = "" - var templatePath = Quickshell.shellDir + "/Assets/MatugenTemplates/gtk.css" - var homeDir = Quickshell.env("HOME") - var outputPath3 = homeDir + "/.config/gtk-3.0/gtk.css" - var outputPath4 = homeDir + "/.config/gtk-4.0/gtk.css" - - // Process GTK3 template - script += "mkdir -p " + homeDir + "/.config/gtk-3.0\n" - script += "cp '" + templatePath + "' '" + outputPath3 + "'\n" - script += replaceColorsInFile(outputPath3, colors) - - // Process GTK4 template - script += "mkdir -p " + homeDir + "/.config/gtk-4.0\n" - script += "cp '" + templatePath + "' '" + outputPath4 + "'\n" - script += replaceColorsInFile(outputPath4, colors) - - script += "gsettings set org.gnome.desktop.interface color-scheme prefer-" + mode + "\n" - - return script - } - - // -------------------------------- - function processQtTemplate(colors, mode) { - var script = "" - var templatePath = Quickshell.shellDir + "/Assets/MatugenTemplates/qtct.conf" - var homeDir = Quickshell.env("HOME") - var outputPath5 = homeDir + "/.config/qt5ct/colors/noctalia.conf" - var outputPath6 = homeDir + "/.config/qt6ct/colors/noctalia.conf" - - // Process Qt5 template - script += "mkdir -p " + homeDir + "/.config/qt5ct/colors\n" - script += "cp '" + templatePath + "' '" + outputPath5 + "'\n" - script += replaceColorsInFile(outputPath5, colors) - - // Process Qt6 template - script += "mkdir -p " + homeDir + "/.config/qt6ct/colors\n" - script += "cp '" + templatePath + "' '" + outputPath6 + "'\n" - script += replaceColorsInFile(outputPath6, colors) - - return script - } - - // -------------------------------- - function processFuzzelTemplate(colors, mode) { - var script = "" - var templatePath = Quickshell.shellDir + "/Assets/MatugenTemplates/fuzzel.conf" - var homeDir = Quickshell.env("HOME") - var outputPath = homeDir + "/.config/fuzzel/themes/noctalia" - - script += "mkdir -p " + homeDir + "/.config/fuzzel/themes\n" - script += "cp '" + templatePath + "' '" + outputPath + "'\n" - script += replaceColorsInFile(outputPath, colors) - script += MatugenService.colorsApplyScript + " fuzzel\n" - - return script - } - - // -------------------------------- - function processPywalfoxTemplate(colors, mode) { - var script = "" - var templatePath = Quickshell.shellDir + "/Assets/MatugenTemplates/pywalfox.json" - var homeDir = Quickshell.env("HOME") - var outputPath = homeDir + "/.cache/wal/colors.json" - - script += "mkdir -p " + homeDir + "/.cache/wal\n" - script += "cp '" + templatePath + "' '" + outputPath + "'\n" - script += replaceColorsInFile(outputPath, colors) - script += MatugenService.colorsApplyScript + " pywalfox\n" - - return script - } - - // -------------------------------- - function processVesktopTemplate(colors, mode) { - var script = "" - var templatePath = Quickshell.shellDir + "/Assets/MatugenTemplates/vesktop.css" - var homeDir = Quickshell.env("HOME") - var outputPath = homeDir + "/.config/vesktop/themes/noctalia.theme.css" - - script += "mkdir -p " + homeDir + "/.config/vesktop/themes\n" - script += "cp '" + templatePath + "' '" + outputPath + "'\n" - script += replaceColorsInFile(outputPath, colors) - - return script - } - - // -------------------------------- function replaceColorsInFile(filePath, colors) { - var script = "" - - // Replace all color placeholders with actual colors - Object.keys(colors).forEach(function (colorKey) { - var colorValue = colors[colorKey].default.hex - // Escape special characters in the color value for sed - var escapedColor = colorValue.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') - script += "sed -i 's/{{colors\\." + colorKey + "\\.default\\.hex}}/" + escapedColor + "/g' '" + filePath + "'\n" - }) - + let script = "" + Object.keys(colors).forEach(colorKey => { + const colorValue = colors[colorKey].default.hex + const escapedColor = colorValue.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') + script += `sed -i 's/{{colors\\.${colorKey}\\.default\\.hex}}/${escapedColor}/g' '${filePath}'\n` + }) return script } - // -------------------------------- - function addUserTemplateExecutionForPredefinedColors(colors, mode) { - if (!Settings.data.templates.enableUserTemplates) { - return "" - } - - var userConfigPath = getUserConfigPath() - var script = "\n# Execute user config if it exists\n" - script += "if [ -f '" + userConfigPath + "' ]; then\n" - script += " # Process user templates with predefined colors\n" - script += " echo 'User templates processing not implemented for predefined colors yet'\n" - script += "fi" - - return script - } - - // -------------------------------- - function buildPredefinedSchemeScript(content, pathEsc, color, mode) { - // Use a unique delimiter to avoid conflicts with config content - var delimiter = "MATUGEN_CONFIG_EOF_" + Math.random().toString(36).substr(2, 9) - var script = "cat > '" + pathEsc + "' << '" + delimiter + "'\n" + content + "\n" + delimiter + "\n\n" - script += "matugen color hex '" + color + "' --config '" + pathEsc + "' --mode " + mode + "\n" - - // Add user template execution if enabled - script += addUserTemplateExecutionForColor(color, mode) - - return script - } - - // -------------------------------- - function addUserTemplateExecutionForColor(color, mode) { - if (!Settings.data.templates.enableUserTemplates) { - return "" - } - - var userConfigPath = getUserConfigPath() - var script = "\n# Execute user config if it exists\n" - script += "if [ -f '" + userConfigPath + "' ]; then\n" - script += " matugen color hex '" + color + "' --config '" + userConfigPath + "' --mode " + mode + "\n" - script += "fi" - - return script - } - - // -------------------------------- + // ===== Terminal Themes ===== function handleTerminalThemes() { - var terminals = { - "foot": "~/.config/foot/themes/noctalia", - "ghostty": "~/.config/ghostty/themes/noctalia", - "kitty": "~/.config/kitty/themes/noctalia.conf" - } + const commands = [] - var copyCmd = Object.keys(terminals).filter(function (terminal) { - return Settings.data.templates[terminal] - }).map(function (terminal) { - var colorsPath = terminals[terminal] - var colorsPathParent = colorsPath.replace(/[^\/]*$/, "") - var terminalColorsTemplate = getTerminalColorsTemplate(terminal) - return ['mkdir -p ' + colorsPathParent, 'cp -f ' + terminalColorsTemplate + ' ' + colorsPath, colorsApplyScript + ' ' + terminal] - }).reduce(function (arr1, arr2) { - return arr1.concat(arr2) - }, []).join('; ') + Object.keys(terminalPaths).forEach(terminal => { + if (Settings.data.templates[terminal]) { + const outputPath = terminalPaths[terminal] + const outputDir = outputPath.substring(0, outputPath.lastIndexOf('/')) + const templatePath = getTerminalColorsTemplate(terminal) - if (copyCmd !== "") { - copyProcess.command = ["bash", "-lc", copyCmd] + commands.push(`mkdir -p ${outputDir}`) + commands.push(`cp -f ${templatePath} ${outputPath}`) + commands.push(`${colorsApplyScript} ${terminal}`) + } + }) + + if (commands.length > 0) { + copyProcess.command = ["bash", "-lc", commands.join('; ')] copyProcess.running = true } } - // -------------------------------- function getTerminalColorsTemplate(terminal) { - var colorScheme = Settings.data.colorSchemes.predefinedScheme - const darkLight = Settings.data.colorSchemes.darkMode ? 'dark' : 'light' + let colorScheme = Settings.data.colorSchemes.predefinedScheme + const mode = Settings.data.colorSchemes.darkMode ? 'dark' : 'light' - // Convert display names back to folder names - var schemeMap = { - "Noctalia (default)": "Noctalia-default", - "Noctalia (legacy)": "Noctalia-legacy", - "Tokyo Night": "Tokyo-Night" - } + colorScheme = schemeNameMap[colorScheme] || colorScheme + const extension = terminal === 'kitty' ? ".conf" : "" - colorScheme = schemeMap[colorScheme] || colorScheme - var extension = terminal === 'kitty' ? ".conf" : "" - - return `${Quickshell.shellDir}/Assets/ColorScheme/${colorScheme}/terminal/${terminal}/${colorScheme}-${darkLight}${extension}` + return `${Quickshell.shellDir}/Assets/ColorScheme/${colorScheme}/terminal/${terminal}/${colorScheme}-${mode}${extension}` } - // -------------------------------- + // ===== User Templates ===== + function buildUserTemplateCommand(input, mode) { + if (!Settings.data.templates.enableUserTemplates) { + return "" + } + + const userConfigPath = getUserConfigPath() + let script = "\n# Execute user config if it exists\n" + script += `if [ -f '${userConfigPath}' ]; then\n` + script += ` matugen image '${input}' --config '${userConfigPath}' --mode ${mode} --type ${Settings.data.colorSchemes.matugenSchemeType}\n` + script += "fi" + + return script + } + + function getUserConfigPath() { + return (Quickshell.env("HOME") + "/.config/matugen/config.toml").replace(/'/g, "'\\''") + } + + // ===== Utilities ===== + function selectVibrantColor(schemeData, mode) { + const colors = [schemeData[mode]["mPrimary"], schemeData[mode]["mSecondary"], schemeData[mode]["mTertiary"]] + + let bestScore = 0 + let bestIndex = 0 + + colors.forEach((color, i) => { + const hsl = ColorsConvert.hexToHSL(color) + if (hsl.s > bestScore) { + bestScore = hsl.s + bestIndex = i + } + }) + + return colors[bestIndex] + } + + // ===== Processes ===== Process { id: generateProcess workingDirectory: Quickshell.shellDir running: false stderr: StdioCollector { onStreamFinished: { - if (this.text !== "") { + if (this.text) { Logger.warn("MatugenService", "GenerateProcess stderr:", this.text) } } } } - // -------------------------------- Process { id: copyProcess running: false stderr: StdioCollector { onStreamFinished: { - if (this.text !== "") { + if (this.text) { Logger.warn("MatugenService", "CopyProcess stderr:", this.text) } }