ColorSchemeTab: auto-detect themabale discord client

This commit is contained in:
lysec
2025-10-10 13:42:42 +02:00
parent 8c5968c721
commit 63881bf8a9
13 changed files with 260 additions and 105 deletions

View File

@@ -558,9 +558,9 @@
"description": "Schreibt {filepath} und lädt neu",
"description-missing": "Erfordert fuzzel Starter"
},
"vesktop": {
"description": "Schreibt {filepath}",
"description-missing": "Erfordert vesktop Discord-Client"
"discord": {
"description": "Schreibt {filepath} für {client}",
"description-missing": "Kein Discord-Client erkannt. Installieren Sie vesktop, webcord, armcord, equibop, lightcord oder dorion."
},
"pywalfox": {
"description": "Schreibt {filepath} und führt pywalfox update aus",

View File

@@ -560,9 +560,9 @@
"description": "Write {filepath} and reload",
"description-missing": "Requires {app} to be installed"
},
"vesktop": {
"description": "Write {filepath}",
"description-missing": "Requires {app} to be installed"
"discord": {
"description": "Write {filepath} for {client}",
"description-missing": "No Discord client detected. Install vesktop, webcord, armcord, equibop, lightcord, or dorion."
},
"pywalfox": {
"description": "Write {filepath} and run pywalfox update",

View File

@@ -554,9 +554,9 @@
"description": "Escribir {filepath} y recargar",
"description-missing": "Requiere que {app} esté instalado"
},
"vesktop": {
"description": "Escribir {filepath}",
"description-missing": "Requiere que {app} esté instalado"
"discord": {
"description": "Escribir {filepath} para {client}",
"description-missing": "No se detectó cliente de Discord. Instala vesktop, webcord, armcord, equibop, lightcord o dorion."
},
"pywalfox": {
"description": "Escribir {filepath} y ejecutar pywalfox update",

View File

@@ -554,9 +554,9 @@
"description": "Écrire ~/.config/fuzzel/themes/noctalia et recharger",
"description-missing": "Nécessite que le lanceur fuzzel soit installé"
},
"vesktop": {
"description": "Écrire ~/.config/vesktop/themes/noctalia.theme.css",
"description-missing": "Nécessite que le client Discord vesktop soit installé"
"discord": {
"description": "Écrire {filepath} pour {client}",
"description-missing": "Aucun client Discord détecté. Installez vesktop, webcord, armcord, equibop, lightcord ou dorion."
},
"pywalfox": {
"description": "Écrire ~/.cache/wal/colors.json et exécuter pywalfox update",

View File

@@ -520,9 +520,9 @@
"description": "Escrever {filepath} e recarregar",
"description-missing": "Requer que o {app} esteja instalado"
},
"vesktop": {
"description": "Escrever {filepath}",
"description-missing": "Requer que o {app} esteja instalado"
"discord": {
"description": "Escrever {filepath} para {client}",
"description-missing": "Nenhum cliente Discord detectado. Instale vesktop, webcord, armcord, equibop, lightcord ou dorion."
},
"pywalfox": {
"description": "Escrever {filepath} e executar pywalfox update",

View File

@@ -554,9 +554,9 @@
"description": "写入 {filepath} 并重新加载",
"description-missing": "需要安装 {app}"
},
"vesktop": {
"description": "写入 {filepath}",
"description-missing": "需要安装 {app}"
"discord": {
"description": "为 {client} 写入 {filepath}",
"description-missing": "未检测到 Discord 客户端。请安装 vesktop、webcord、armcord、equibop、lightcord 或 dorion。"
},
"pywalfox": {
"description": "写入 {filepath} 并运行 pywalfox update",

View File

@@ -176,7 +176,13 @@
"ghostty": false,
"foot": false,
"fuzzel": false,
"vesktop": false,
"discord": false,
"discord_vesktop": false,
"discord_webcord": false,
"discord_armcord": false,
"discord_equibop": false,
"discord_lightcord": false,
"discord_dorion": false,
"pywalfox": false,
"enableUserTemplates": false
},

View File

@@ -327,7 +327,13 @@ Singleton {
property bool ghostty: false
property bool foot: false
property bool fuzzel: false
property bool vesktop: false
property bool discord: false
property bool discord_vesktop: false
property bool discord_webcord: false
property bool discord_armcord: false
property bool discord_equibop: false
property bool discord_lightcord: false
property bool discord_dorion: false
property bool pywalfox: false
property bool enableUserTemplates: false
}

View File

@@ -494,22 +494,29 @@ ColumnLayout {
}
}
NCheckbox {
label: "Vesktop"
description: ProgramCheckerService.vesktopAvailable ? I18n.tr("settings.color-scheme.templates.programs.vesktop.description", {
"filepath": "~/.config/vesktop/themes/noctalia.theme.css"
}) : I18n.tr("settings.color-scheme.templates.programs.vesktop.description-missing", {
"app": "vesktop"
})
checked: Settings.data.templates.vesktop
enabled: ProgramCheckerService.vesktopAvailable
opacity: ProgramCheckerService.vesktopAvailable ? 1.0 : 0.6
onToggled: checked => {
if (ProgramCheckerService.vesktopAvailable) {
Settings.data.templates.vesktop = checked
// Show individual checkboxes for each detected Discord client
Repeater {
model: ProgramCheckerService.availableDiscordClients
delegate: NCheckbox {
label: modelData.name.charAt(0).toUpperCase() + modelData.name.slice(1)
description: I18n.tr("settings.color-scheme.templates.programs.discord.description", {
"client": modelData.name.charAt(0).toUpperCase() + modelData.name.slice(1),
"filepath": modelData.themePath
})
checked: Settings.data.templates["discord_" + modelData.name] || false
onToggled: checked => {
Settings.data.templates["discord_" + modelData.name] = checked
AppThemeService.generate()
}
}
}
}
// Show message if no Discord clients detected
NText {
visible: ProgramCheckerService.availableDiscordClients.length === 0
text: I18n.tr("settings.color-scheme.templates.programs.discord.description-missing")
color: Color.mOnSurfaceVariant
pointSize: Style.fontSizeS * scaling
}
NCheckbox {

View File

@@ -161,7 +161,8 @@ Singleton {
// Check if any Matugen templates are enabled
function hasEnabledMatugenTemplates() {
return Settings.data.templates.gtk || Settings.data.templates.qt || Settings.data.templates.kitty || Settings.data.templates.ghostty || Settings.data.templates.foot || Settings.data.templates.fuzzel || Settings.data.templates.vesktop || Settings.data.templates.pywalfox
return Settings.data.templates.gtk || Settings.data.templates.qt || Settings.data.templates.kitty || Settings.data.templates.ghostty || Settings.data.templates.foot || Settings.data.templates.fuzzel || Settings.data.templates.discord || Settings.data.templates.discord_vesktop || Settings.data.templates.discord_webcord
|| Settings.data.templates.discord_armcord || Settings.data.templates.discord_equibop || Settings.data.templates.discord_lightcord || Settings.data.templates.discord_dorion || Settings.data.templates.pywalfox
}
// Writer to colors.json using a JsonAdapter for safety

View File

@@ -69,65 +69,127 @@ Singleton {
})
}
// Applications configuration
readonly property var applications: [{
"name": "gtk",
"templates": [{
"version": "gtk3",
"output": "~/.config/gtk-3.0/gtk.css"
}, {
"version": "gtk4",
"output": "~/.config/gtk-4.0/gtk.css"
}],
"input": "gtk.css",
"postHook": "gsettings set org.gnome.desktop.interface color-scheme prefer-{mode}"
}, {
"name": "qt",
"templates": [{
"version": "qt5",
"output": "~/.config/qt5ct/colors/noctalia.conf"
}, {
"version": "qt6",
"output": "~/.config/qt6ct/colors/noctalia.conf"
}],
"input": "qtct.conf"
}, {
"name": "fuzzel",
"templates": [{
"version": "fuzzel",
"output": "~/.config/fuzzel/themes/noctalia"
}],
"input": "fuzzel.conf",
"postHook": AppThemeService.colorsApplyScript + " fuzzel"
}, {
"name": "pywalfox",
"templates": [{
"version": "pywalfox",
"output": "~/.cache/wal/colors.json"
}],
"input": "pywalfox.json",
"postHook": AppThemeService.colorsApplyScript + " pywalfox"
}, {
"name": "discord_vesktop",
"templates": [{
"version": "discord_vesktop",
"output": "~/.config/vesktop/themes/noctalia.theme.css"
}],
"input": "vesktop.css"
}, {
"name": "discord_webcord",
"templates": [{
"version": "discord_webcord",
"output": "~/.config/webcord/themes/noctalia.theme.css"
}],
"input": "vesktop.css"
}, {
"name": "discord_armcord",
"templates": [{
"version": "discord_armcord",
"output": "~/.config/armcord/themes/noctalia.theme.css"
}],
"input": "vesktop.css"
}, {
"name": "discord_equibop",
"templates": [{
"version": "discord_equibop",
"output": "~/.config/equibop/themes/noctalia.theme.css"
}],
"input": "vesktop.css"
}, {
"name": "discord_lightcord",
"templates": [{
"version": "discord_lightcord",
"output": "~/.config/lightcord/themes/noctalia.theme.css"
}],
"input": "vesktop.css"
}, {
"name": "discord_dorion",
"templates": [{
"version": "discord_dorion",
"output": "~/.config/dorion/themes/noctalia.theme.css"
}],
"input": "vesktop.css"
}]
// --------------------------------
function addApplicationTemplates(lines, mode) {
var applications = [{
"name": "gtk",
"templates": [{
"version": "gtk3",
"output": "~/.config/gtk-3.0/gtk.css"
}, {
"version": "gtk4",
"output": "~/.config/gtk-4.0/gtk.css"
}],
"input": "gtk.css",
"postHook": "gsettings set org.gnome.desktop.interface color-scheme prefer-" + mode
}, {
"name": "qt",
"templates": [{
"version": "qt5",
"output": "~/.config/qt5ct/colors/noctalia.conf"
}, {
"version": "qt6",
"output": "~/.config/qt6ct/colors/noctalia.conf"
}],
"input": "qtct.conf"
}, {
"name": "fuzzel",
"templates": [{
"version": "fuzzel",
"output": "~/.config/fuzzel/themes/noctalia"
}],
"input": "fuzzel.conf",
"postHook": AppThemeService.colorsApplyScript + " fuzzel"
}, {
"name": "pywalfox",
"templates": [{
"version": "pywalfox",
"output": "~/.cache/wal/colors.json"
}],
"input": "pywalfox.json",
"postHook": AppThemeService.colorsApplyScript + " pywalfox"
}, {
"name": "vesktop",
"templates": [{
"version": "vesktop",
"output": "~/.config/vesktop/themes/noctalia.theme.css"
}],
"input": "vesktop.css"
}]
applications.forEach(function (app) {
if (Settings.data.templates[app.name]) {
// Check if app has a condition and if it's met
var shouldInclude = true
if (app.condition !== undefined) {
shouldInclude = app.condition
}
if (Settings.data.templates[app.name] && shouldInclude) {
app.templates.forEach(function (template) {
lines.push("\n[templates." + template.version + "]")
lines.push('input_path = "' + Quickshell.shellDir + '/Assets/MatugenTemplates/' + app.input + '"')
lines.push('output_path = "' + template.output + '"')
if (app.postHook) {
lines.push('post_hook = "' + app.postHook + '"')
var postHook = app.postHook.replace("{mode}", mode)
lines.push('post_hook = "' + postHook + '"')
}
})
}
})
}
// Extract Discord clients from applications array
readonly property var discordClients: {
var clients = []
for (var i = 0; i < applications.length; i++) {
var app = applications[i]
if (app.name && app.name.startsWith("discord_")) {
var clientName = app.name.replace("discord_", "")
var themePath = app.templates[0].output
var configPath = themePath.replace("/themes/noctalia.theme.css", "")
clients.push({
"name": clientName,
"configPath": configPath,
"themePath": themePath
})
}
}
return clients
}
}

View File

@@ -61,7 +61,8 @@ Singleton {
if (title1) {
for (var j = 0; j < genericPlayers.length; j++) {
if (matchedGenericIndices[j]) continue
if (matchedGenericIndices[j])
continue
let genericPlayer = genericPlayers[j]
let title2 = String(genericPlayer.trackTitle || "").trim()
@@ -71,27 +72,29 @@ Singleton {
let scoreSpecific = (specificPlayer.trackArtUrl ? 1 : 0)
let scoreGeneric = (genericPlayer.trackArtUrl ? 1 : 0)
if(scoreSpecific > scoreGeneric){ dataPlayer = specificPlayer }
if (scoreSpecific > scoreGeneric) {
dataPlayer = specificPlayer
}
let virtualPlayer = {
identity: identityPlayer.identity,
desktopEntry: identityPlayer.desktopEntry,
trackTitle: dataPlayer.trackTitle,
trackArtist: dataPlayer.trackArtist,
trackAlbum: dataPlayer.trackAlbum,
trackArtUrl: dataPlayer.trackArtUrl,
length: dataPlayer.length || 0,
position: dataPlayer.position || 0,
playbackState: dataPlayer.playbackState,
isPlaying: dataPlayer.isPlaying || false,
canPlay: dataPlayer.canPlay || false,
canPause: dataPlayer.canPause || false,
canGoNext: dataPlayer.canGoNext || false,
canGoPrevious: dataPlayer.canGoPrevious || false,
canSeek: dataPlayer.canSeek || false,
canControl: dataPlayer.canControl || false,
_stateSource: dataPlayer,
_controlTarget: identityPlayer
"identity": identityPlayer.identity,
"desktopEntry": identityPlayer.desktopEntry,
"trackTitle": dataPlayer.trackTitle,
"trackArtist": dataPlayer.trackArtist,
"trackAlbum": dataPlayer.trackAlbum,
"trackArtUrl": dataPlayer.trackArtUrl,
"length": dataPlayer.length || 0,
"position": dataPlayer.position || 0,
"playbackState": dataPlayer.playbackState,
"isPlaying": dataPlayer.isPlaying || false,
"canPlay": dataPlayer.canPlay || false,
"canPause": dataPlayer.canPause || false,
"canGoNext": dataPlayer.canGoNext || false,
"canGoPrevious": dataPlayer.canGoPrevious || false,
"canSeek": dataPlayer.canSeek || false,
"canControl": dataPlayer.canControl || false,
"_stateSource": dataPlayer,
"_controlTarget": identityPlayer
}
finalPlayers.push(virtualPlayer)
matchedGenericIndices[j] = true

View File

@@ -16,13 +16,67 @@ Singleton {
property bool ghosttyAvailable: false
property bool footAvailable: false
property bool fuzzelAvailable: false
property bool vesktopAvailable: false
property bool gpuScreenRecorderAvailable: false
property bool wlsunsetAvailable: false
// Discord client auto-detection
property var availableDiscordClients: []
// Signal emitted when all checks are complete
signal checksCompleted
// Function to detect Discord client by checking config directories
function detectDiscordClient() {
// Build list of client names from MatugenTemplates
var clientNames = []
for (var i = 0; i < MatugenTemplates.discordClients.length; i++) {
clientNames.push(MatugenTemplates.discordClients[i].name)
}
// Use a Process to check directory existence for all clients
discordDetector.command = ["sh", "-c", "available_clients=\"\"; " + "for client in " + clientNames.join(" ") + "; do " + " if [ -d \"$HOME/.config/$client\" ]; then " + " available_clients=\"$available_clients $client\"; " + " fi; " + "done; " + "echo \"$available_clients\""]
discordDetector.running = true
}
// Process to detect Discord client directories
Process {
id: discordDetector
running: false
onExited: function (exitCode) {
availableDiscordClients = []
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 < MatugenTemplates.discordClients.length; j++) {
var client = MatugenTemplates.discordClients[j]
if (client.name === clientName) {
availableDiscordClients.push(client)
break
}
}
}
Logger.log("ProgramChecker", "Detected Discord clients:", detectedClients.join(", "))
}
}
if (availableDiscordClients.length === 0) {
Logger.log("ProgramChecker", "No Discord clients detected")
}
}
stdout: StdioCollector {}
stderr: StdioCollector {}
}
// Programs to check - maps property names to commands
readonly property var programsToCheck: ({
"matugenAvailable": ["which", "matugen"],
@@ -31,7 +85,6 @@ Singleton {
"ghosttyAvailable": ["which", "ghostty"],
"footAvailable": ["which", "foot"],
"fuzzelAvailable": ["which", "fuzzel"],
"vesktopAvailable": ["which", "vesktop"],
"gpuScreenRecorderAvailable": ["sh", "-c", "command -v gpu-screen-recorder >/dev/null 2>&1 || (command -v flatpak >/dev/null 2>&1 && flatpak list --app | grep -q 'com.dec05eba.gpu_screen_recorder')"],
"wlsunsetAvailable": ["which", "wlsunset"]
})
@@ -59,6 +112,8 @@ Singleton {
// Check next program or emit completion signal
if (root.completedChecks >= root.totalChecks) {
// Run Discord client detection after all checks are complete
root.detectDiscordClient()
root.checksCompleted()
} else {
root.checkNextProgram()
@@ -113,6 +168,21 @@ Singleton {
checker.running = true
}
// Manual function to test Discord detection (for debugging)
function testDiscordDetection() {
Logger.log("ProgramChecker", "Testing Discord detection...")
Logger.log("ProgramChecker", "HOME:", Quickshell.env("HOME"))
// Test each client directory
for (var i = 0; i < MatugenTemplates.discordClients.length; i++) {
var client = MatugenTemplates.discordClients[i]
var configDir = client.configPath.replace("~", Quickshell.env("HOME"))
Logger.log("ProgramChecker", "Checking:", configDir)
}
detectDiscordClient()
}
// Initialize checks when service is created
Component.onCompleted: {
checkAllPrograms()