From 96ae2c0d6fe26a64244659c8654535c553e9c45e Mon Sep 17 00:00:00 2001 From: Ly-sec Date: Fri, 14 Nov 2025 07:40:15 +0100 Subject: [PATCH] Matugen/Code: add VSCodium support --- Assets/Translations/de.json | 2 +- Assets/Translations/en.json | 2 +- Assets/Translations/es.json | 2 +- Assets/Translations/fr.json | 2 +- Assets/Translations/nl.json | 2 +- Assets/Translations/pt.json | 2 +- Assets/Translations/ru.json | 2 +- Assets/Translations/tr.json | 2 +- Assets/Translations/uk-UA.json | 2 +- Assets/Translations/zh-CN.json | 2 +- .../Panels/Settings/Tabs/ColorSchemeTab.qml | 49 +++++++++++--- Services/System/ProgramCheckerService.qml | 66 ++++++++++++++++++- Services/Theming/TemplateProcessor.qml | 59 +++++++++++++++++ Services/Theming/TemplateRegistry.qml | 35 +++++++++- 14 files changed, 206 insertions(+), 23 deletions(-) diff --git a/Assets/Translations/de.json b/Assets/Translations/de.json index a40c3374..0474b777 100644 --- a/Assets/Translations/de.json +++ b/Assets/Translations/de.json @@ -880,7 +880,7 @@ "programs": { "code": { "description": "Schreibe {Dateipfad}. Das Hyprluna-Theme muss manuell installiert und aktiviert werden", - "description-missing": "Benötigt die Installation von {app}" + "description-missing": "Kein Code-Client erkannt. Installieren Sie VSCode oder VSCodium." }, "description": "Anwendungsspezifisches Theming.", "discord": { diff --git a/Assets/Translations/en.json b/Assets/Translations/en.json index 5c4ab9f1..b92c800e 100644 --- a/Assets/Translations/en.json +++ b/Assets/Translations/en.json @@ -880,7 +880,7 @@ "programs": { "code": { "description": "Write {filepath}. Hyprluna theme needs to be installed and activated manually.", - "description-missing": "Requires {app} to be installed" + "description-missing": "No Code client detected. Install VSCode or VSCodium." }, "description": "Application-specific theming.", "discord": { diff --git a/Assets/Translations/es.json b/Assets/Translations/es.json index 76decb25..9076a78d 100644 --- a/Assets/Translations/es.json +++ b/Assets/Translations/es.json @@ -880,7 +880,7 @@ "programs": { "code": { "description": "Escribe {filepath}. El tema Hyprluna debe ser instalado y activado manualmente.", - "description-missing": "Requiere que {app} esté instalado/a." + "description-missing": "No se detectó cliente de Code. Instala VSCode o VSCodium." }, "description": "Tematización específica de aplicaciones.", "discord": { diff --git a/Assets/Translations/fr.json b/Assets/Translations/fr.json index 6f0a1bae..10e96498 100644 --- a/Assets/Translations/fr.json +++ b/Assets/Translations/fr.json @@ -880,7 +880,7 @@ "programs": { "code": { "description": "Écrire {filepath}. Le thème Hyprluna doit être installé et activé manuellement.", - "description-missing": "Nécessite l'installation de {app}" + "description-missing": "Aucun client Code détecté. Installez VSCode ou VSCodium." }, "description": "Thématisation spécifique aux applications.", "discord": { diff --git a/Assets/Translations/nl.json b/Assets/Translations/nl.json index f1f74974..0568efee 100644 --- a/Assets/Translations/nl.json +++ b/Assets/Translations/nl.json @@ -880,7 +880,7 @@ "programs": { "code": { "description": "Schrijf {filepath}. Het Hyprluna-thema moet handmatig worden geïnstalleerd en geactiveerd.", - "description-missing": "Vereist dat {app} is geïnstalleerd." + "description-missing": "Geen Code-client gedetecteerd. Installeer VSCode of VSCodium." }, "description": "Toepassingsspecifieke theming.", "discord": { diff --git a/Assets/Translations/pt.json b/Assets/Translations/pt.json index fac180ae..7d62b4ae 100644 --- a/Assets/Translations/pt.json +++ b/Assets/Translations/pt.json @@ -880,7 +880,7 @@ "programs": { "code": { "description": "Escreva em {filepath}. O tema Hyprluna precisa ser instalado e ativado manualmente.", - "description-missing": "Requer que o {app} esteja instalado." + "description-missing": "Nenhum cliente Code detectado. Instale VSCode ou VSCodium." }, "description": "Tematização específica de aplicativos.", "discord": { diff --git a/Assets/Translations/ru.json b/Assets/Translations/ru.json index 45bb6fe6..64648c1f 100644 --- a/Assets/Translations/ru.json +++ b/Assets/Translations/ru.json @@ -880,7 +880,7 @@ "programs": { "code": { "description": "Записать {filepath}. Тему Hyprluna нужно установить и активировать вручную.", - "description-missing": "Требуется установка {app}" + "description-missing": "Клиент Code не обнаружен. Установите VSCode или VSCodium." }, "description": "Тематика для конкретных приложений.", "discord": { diff --git a/Assets/Translations/tr.json b/Assets/Translations/tr.json index 9fafd582..30335a5c 100644 --- a/Assets/Translations/tr.json +++ b/Assets/Translations/tr.json @@ -880,7 +880,7 @@ "programs": { "code": { "description": "{filepath} dosyasına yaz. Hyprluna temasının kurulu ve manuel olarak etkinleştirilmiş olması gerekir.", - "description-missing": "Kurulum için {app} gereklidir" + "description-missing": "Code istemcisi tespit edilmedi. VSCode veya VSCodium kurun." }, "description": "Uygulamaya özel temalandırma.", "discord": { diff --git a/Assets/Translations/uk-UA.json b/Assets/Translations/uk-UA.json index e8eb376a..ef948d3e 100644 --- a/Assets/Translations/uk-UA.json +++ b/Assets/Translations/uk-UA.json @@ -880,7 +880,7 @@ "programs": { "code": { "description": "Записати {filepath}. Тему Hyprluna потрібно встановити та активувати вручну.", - "description-missing": "Потрібна установка {app}" + "description-missing": "Клієнт Code не виявлено. Встановіть VSCode або VSCodium." }, "description": "Оформлення окремих програм.", "discord": { diff --git a/Assets/Translations/zh-CN.json b/Assets/Translations/zh-CN.json index c72c6728..8f16bd51 100644 --- a/Assets/Translations/zh-CN.json +++ b/Assets/Translations/zh-CN.json @@ -880,7 +880,7 @@ "programs": { "code": { "description": "写入 {filepath}。Hyprluna 主题需要手动安装和激活。", - "description-missing": "需要安装 {app}" + "description-missing": "未检测到 Code 客户端。请安装 VSCode 或 VSCodium。" }, "description": "应用程序特定主题。", "discord": { diff --git a/Modules/Panels/Settings/Tabs/ColorSchemeTab.qml b/Modules/Panels/Settings/Tabs/ColorSchemeTab.qml index d11e6e68..dc8e59df 100644 --- a/Modules/Panels/Settings/Tabs/ColorSchemeTab.qml +++ b/Modules/Panels/Settings/Tabs/ColorSchemeTab.qml @@ -740,19 +740,48 @@ ColumnLayout { } } + // Code clients - single toggle with dynamic description NCheckbox { + id: codeToggle label: "Code" - description: ProgramCheckerService.codeAvailable ? I18n.tr("settings.color-scheme.templates.programs.code.description", { - "filepath": "~/.vscode/extensions/hyprluna.hyprluna-theme-1.0.2/themes/hyprluna.json" - }) : I18n.tr("settings.color-scheme.templates.programs.code.description-missing", { - "app": "code" - }) - checked: Settings.data.templates.code - enabled: ProgramCheckerService.codeAvailable - opacity: ProgramCheckerService.codeAvailable ? 1.0 : 0.6 + description: { + if (ProgramCheckerService.availableCodeClients.length === 0) { + return I18n.tr("settings.color-scheme.templates.programs.code.description-missing") + } else { + // Show detected clients + var clientInfo = [] + for (var i = 0; i < ProgramCheckerService.availableCodeClients.length; i++) { + var client = ProgramCheckerService.availableCodeClients[i] + // Capitalize first letter and format nicely + var clientName = client.name === "code" ? "VSCode" : "VSCodium" + clientInfo.push(clientName) + } + return "Detected: " + clientInfo.join(", ") + } + } + Layout.fillWidth: true + Layout.preferredWidth: -1 + checked: { + // Check if any Code client template is enabled + var anyEnabled = false + for (var i = 0; i < ProgramCheckerService.availableCodeClients.length; i++) { + var client = ProgramCheckerService.availableCodeClients[i] + if (Settings.data.templates["code_" + client.name]) { + anyEnabled = true + break + } + } + return anyEnabled + } + enabled: ProgramCheckerService.availableCodeClients.length > 0 + opacity: ProgramCheckerService.availableCodeClients.length > 0 ? 1.0 : 0.6 onToggled: checked => { - if (ProgramCheckerService.codeAvailable) { - Settings.data.templates.code = checked + // Enable/disable all detected Code clients + for (var i = 0; i < ProgramCheckerService.availableCodeClients.length; i++) { + var client = ProgramCheckerService.availableCodeClients[i] + Settings.data.templates["code_" + client.name] = checked + } + if (ProgramCheckerService.availableCodeClients.length > 0) { AppThemeService.generate() } } diff --git a/Services/System/ProgramCheckerService.qml b/Services/System/ProgramCheckerService.qml index 10a2eff0..17dd6983 100644 --- a/Services/System/ProgramCheckerService.qml +++ b/Services/System/ProgramCheckerService.qml @@ -31,6 +31,9 @@ Singleton { // Discord client auto-detection property var availableDiscordClients: [] + // Code client auto-detection + property var availableCodeClients: [] + // Signal emitted when all checks are complete signal checksCompleted @@ -97,6 +100,66 @@ Singleton { stderr: StdioCollector {} } + // Function to detect Code client by checking config directories + function detectCodeClient() { + // Build shell script to check each client + var scriptParts = ["available_clients=\"\";"] + + for (var i = 0; i < TemplateRegistry.codeClients.length; i++) { + var client = TemplateRegistry.codeClients[i] + var clientName = client.name + var configPath = client.configPath + + // Check if the config directory exists + scriptParts.push("if [ -d \"$HOME" + configPath.substring(1) + "\" ]; then available_clients=\"$available_clients " + clientName + "\"; fi;") + } + + scriptParts.push("echo \"$available_clients\"") + + // Use a Process to check directory existence for all clients + codeDetector.command = ["sh", "-c", scriptParts.join(" ")] + codeDetector.running = true + } + + // Process to detect Code client directories + Process { + id: codeDetector + running: false + + onExited: function (exitCode) { + availableCodeClients = [] + + if (exitCode === 0) { + var detectedClients = stdout.text.trim().split(/\s+/).filter(function (client) { + return client.length > 0 + }) + + if (detectedClients.length > 0) { + // Build list of available clients + for (var i = 0; i < detectedClients.length; i++) { + var clientName = detectedClients[i] + for (var j = 0; j < TemplateRegistry.codeClients.length; j++) { + var client = TemplateRegistry.codeClients[j] + if (client.name === clientName) { + availableCodeClients.push(client) + break + } + } + } + + Logger.i("ProgramChecker", "Detected Code clients:", detectedClients.join(", ")) + } + } + + if (availableCodeClients.length === 0) { + Logger.d("ProgramChecker", "No Code clients detected") + } + } + + stdout: StdioCollector {} + stderr: StdioCollector {} + } + // Programs to check - maps property names to commands readonly property var programsToCheck: ({ "matugenAvailable": ["which", "matugen"], @@ -140,8 +203,9 @@ Singleton { // Check next program or emit completion signal if (root.completedChecks >= root.totalChecks) { - // Run Discord client detection after all checks are complete + // Run Discord and Code client detection after all checks are complete root.detectDiscordClient() + root.detectCodeClient() root.checksCompleted() } else { root.checkNextProgram() diff --git a/Services/Theming/TemplateProcessor.qml b/Services/Theming/TemplateProcessor.qml index 63e059ed..9992bf1b 100644 --- a/Services/Theming/TemplateProcessor.qml +++ b/Services/Theming/TemplateProcessor.qml @@ -114,6 +114,18 @@ Singleton { } }) } + } else if (app.id === "code") { + // Handle Code clients specially + if (Settings.data.templates.code) { + app.clients.forEach(client => { + // Check if this specific client is detected + if (isCodeClientEnabled(client.name)) { + lines.push(`\n[templates.code_${client.name}]`) + lines.push(`input_path = "${Quickshell.shellDir}/Assets/MatugenTemplates/${app.input}"`) + lines.push(`output_path = "${client.path}"`) + } + }) + } } else { // Handle regular apps if (Settings.data.templates[app.id]) { @@ -140,6 +152,16 @@ Singleton { return false } + function isCodeClientEnabled(clientName) { + // Check ProgramCheckerService to see if client is detected + for (var i = 0; i < ProgramCheckerService.availableCodeClients.length; i++) { + if (ProgramCheckerService.availableCodeClients[i].name === clientName) { + return true + } + } + return false + } + function buildMatugenScript(content, wallpaper, mode) { const delimiter = "MATUGEN_CONFIG_EOF_" + Math.random().toString(36).substr(2, 9) const pathEsc = dynamicConfigPath.replace(/'/g, "'\\''") @@ -163,6 +185,10 @@ Singleton { if (Settings.data.templates.discord) { script += processDiscordClients(app, colors, mode, homeDir) } + } else if (app.id === "code") { + if (Settings.data.templates.code) { + script += processCodeClients(app, colors, mode, homeDir) + } } else { if (Settings.data.templates[app.id]) { script += processTemplate(app, colors, mode, homeDir) @@ -198,6 +224,39 @@ Singleton { return script } + function processCodeClients(codeApp, colors, mode, homeDir) { + let script = "" + const palette = ColorPaletteGenerator.generatePalette(colors, Settings.data.colorSchemes.darkMode, false) + + codeApp.clients.forEach(client => { + if (!isCodeClientEnabled(client.name)) + return + + const templatePath = `${Quickshell.shellDir}/Assets/MatugenTemplates/${codeApp.input}` + const outputPath = client.path.replace("~", homeDir) + const outputDir = outputPath.substring(0, outputPath.lastIndexOf('/')) + + // Extract base config directory for checking + var baseConfigDir = "" + if (client.name === "code") { + baseConfigDir = "~/.vscode".replace("~", homeDir) + } else if (client.name === "codium") { + baseConfigDir = "~/.vscode-oss".replace("~", homeDir) + } + + script += `\n` + script += `if [ -d "${baseConfigDir}" ]; then\n` + script += ` mkdir -p ${outputDir}\n` + script += ` cp '${templatePath}' '${outputPath}'\n` + script += ` ${replaceColorsInFile(outputPath, palette)}` + script += `else\n` + script += ` echo "Code client ${client.name} not found at ${baseConfigDir}, skipping"\n` + script += `fi\n` + }) + + return script + } + function processTemplate(app, colors, mode, homeDir) { const palette = ColorPaletteGenerator.generatePalette(colors, Settings.data.colorSchemes.darkMode, app.strict || false) let script = "" diff --git a/Services/Theming/TemplateRegistry.qml b/Services/Theming/TemplateRegistry.qml index ebb67a01..b810f78e 100644 --- a/Services/Theming/TemplateRegistry.qml +++ b/Services/Theming/TemplateRegistry.qml @@ -142,14 +142,18 @@ Singleton { "path": "~/.config/discord", "requiresThemesFolder": true }] - }, // VSCode with hardcoded path (requirement #5) + }, { "id": "code", "name": "VSCode", "category": "applications", "input": "code.json", - "outputs": [{ + "clients": [{ + "name": "code", "path": "~/.vscode/extensions/hyprluna.hyprluna-theme-1.0.2/themes/hyprluna.json" + }, { + "name": "codium", + "path": "~/.vscode-oss/extensions/hyprluna.hyprluna-theme-1.0.2/themes/hyprluna.json" }] }, { "id": "spicetify", @@ -179,6 +183,33 @@ Singleton { return clients } + // Extract Code clients for ProgramCheckerService compatibility + readonly property var codeClients: { + var clients = [] + var codeApp = applications.find(app => app.id === "code") + if (codeApp && codeApp.clients) { + codeApp.clients.forEach(client => { + // Extract base config directory from theme path + var themePath = client.path + var baseConfigDir = "" + if (client.name === "code") { + // For VSCode: ~/.vscode/extensions/... -> ~/.vscode + baseConfigDir = "~/.vscode" + } else if (client.name === "codium") { + // For VSCodium: ~/.vscode-oss/extensions/... -> ~/.vscode-oss + baseConfigDir = "~/.vscode-oss" + } + clients.push({ + "name": client.name, + "configPath": baseConfigDir, + "themePath": themePath, + "requiresThemesFolder": false + }) + }) + } + return clients + } + // Build user templates TOML content function buildUserTemplatesToml() { var lines = []