Create separate matugen toggles, add MatugenService

Matugen: add Matugen.qml as central place for templates, add
MatugenService to take care of .toml generation
Notification: possible fix for "children of null"
This commit is contained in:
Ly-sec 2025-08-28 13:27:49 +02:00
parent 85b92d9c6f
commit f510c1922d
10 changed files with 610 additions and 501 deletions

View file

@ -0,0 +1,52 @@
pragma Singleton
import QtQuick
import Quickshell
import qs.Commons
// Central place to define which templates we generate and where they write.
// Users can extend it by dropping additional templates into:
// - Assets/Matugen/templates/
Singleton {
id: root
// Build the base TOML using current settings
function buildConfigToml() {
var lines = []
lines.push("[config]")
// Always include noctalia colors output for the shell
lines.push("[templates.noctalia]")
lines.push('input_path = "' + Quickshell.shellDir + '/Assets/Matugen/templates/noctalia.json"')
lines.push('output_path = "' + Settings.configDir + 'colors.json"')
if (Settings.data.matugen.gtk4) {
lines.push("\n[templates.gtk4]")
lines.push('input_path = "' + Quickshell.shellDir + '/Assets/Matugen/templates/gtk4.css"')
lines.push('output_path = "~/.config/gtk-4.0/gtk.css"')
}
if (Settings.data.matugen.gtk3) {
lines.push("\n[templates.gtk3]")
lines.push('input_path = "' + Quickshell.shellDir + '/Assets/Matugen/templates/gtk3.css"')
lines.push('output_path = "~/.config/gtk-3.0/gtk.css"')
}
if (Settings.data.matugen.qt6) {
lines.push("\n[templates.qt6]")
lines.push('input_path = "' + Quickshell.shellDir + '/Assets/Matugen/templates/qtct.conf"')
lines.push('output_path = "~/.config/qt6ct/colors/noctalia.conf"')
}
if (Settings.data.matugen.qt5) {
lines.push("\n[templates.qt5]")
lines.push('input_path = "' + Quickshell.shellDir + '/Assets/Matugen/templates/qtct.conf"')
lines.push('output_path = "~/.config/qt5ct/colors/noctalia.conf"')
}
if (Settings.data.matugen.kitty) {
lines.push("\n[templates.kitty]")
lines.push('input_path = "' + Quickshell.shellDir + '/Assets/Matugen/templates/kitty.conf"')
lines.push('output_path = "~/.config/kitty/themes/noctalia.conf"')
lines.push("post_hook = 'kitty +kitten themes --reload-in=all noctalia'")
}
return lines.join("\n") + "\n"
}
}

View file

@ -1,6 +0,0 @@
# Base: only write Noctalia colors.json for the shell
[config]
[templates.noctalia]
input_path = "templates/noctalia.json"
output_path = "~/.config/noctalia/colors.json"

View file

@ -1,31 +0,0 @@
# This file configures how matugen generates colors from wallpapers for Noctalia
[config]
[templates.noctalia]
input_path = "templates/noctalia.json"
output_path = "~/.config/noctalia/colors.json"
# GTK 4 (libadwaita) variables override
[templates.gtk4]
input_path = "templates/gtk4.css"
output_path = "~/.config/gtk-4.0/gtk.css"
# GTK 3 named-colors fallback for legacy apps
[templates.gtk3]
input_path = "templates/gtk3.css"
output_path = "~/.config/gtk-3.0/gtk.css"
# Qt6ct color scheme (can also be used by qt5ct in many distros)
[templates.qt6]
input_path = "templates/qtct.conf"
output_path = "~/.config/qt6ct/colors/noctalia.conf"
[templates.qt5]
input_path = "templates/qtct.conf"
output_path = "~/.config/qt5ct/colors/noctalia.conf"
[templates.kitty]
input_path = "templates/kitty.conf"
output_path = "~/.config/kitty/themes/noctalia.conf"
post_hook = 'kitty +kitten themes --reload-in=all noctalia'

View file

@ -256,8 +256,16 @@ Singleton {
property bool useWallpaperColors: false
property string predefinedScheme: ""
property bool darkMode: true
// External app theming (GTK & Qt)
property bool themeApps: false
}
// matugen templates toggles
property JsonObject matugen: JsonObject {
// Per-template flags to control dynamic config generation
property bool gtk4: false
property bool gtk3: false
property bool qt6: false
property bool qt5: false
property bool kitty: false
}
// night light

View file

@ -25,7 +25,9 @@ Variants {
property var removingNotifications: ({})
// If no notification display activated in settings, then show them all
active: Settings.isLoaded && modelData && (NotificationService.notificationModel.count > 0) ? (Settings.data.notifications.monitors.includes(modelData.name)
active: Settings.isLoaded && modelData
&& (NotificationService.notificationModel.count > 0) ? (Settings.data.notifications.monitors.includes(
modelData.name)
|| (Settings.data.notifications.monitors.length === 0)) : false
visible: (NotificationService.notificationModel.count > 0)
@ -51,7 +53,7 @@ Variants {
NotificationService.animateAndRemove.connect(function (notification, index) {
// Prefer lookup by identity to avoid index mismatches
var delegate = null
if (notificationStack.children && notificationStack.children.length > 0) {
if (notificationStack && notificationStack.children && notificationStack.children.length > 0) {
for (var i = 0; i < notificationStack.children.length; i++) {
var child = notificationStack.children[i]
if (child && child.model && child.model.rawNotification === notification) {
@ -62,7 +64,7 @@ Variants {
}
// Fallback to index if identity lookup failed
if (!delegate && notificationStack.children && notificationStack.children[index]) {
if (!delegate && notificationStack && notificationStack.children && notificationStack.children[index]) {
delegate = notificationStack.children[index]
}

View file

@ -37,7 +37,6 @@ ColumnLayout {
}
}
NText {
text: "Noctalia Shell"
font.pointSize: Style.fontSizeXXXL * scaling
@ -209,8 +208,7 @@ ColumnLayout {
}
NText {
text: (modelData.contributions || 0) + " " + ((modelData.contributions
|| 0) === 1 ? "commit" : "commits")
text: (modelData.contributions || 0) + " " + ((modelData.contributions || 0) === 1 ? "commit" : "commits")
font.pointSize: Style.fontSizeXS * scaling
color: contributorArea.containsMouse ? Color.mSurface : Color.mOnSurface
}
@ -232,4 +230,3 @@ ColumnLayout {
}
}
}

View file

@ -104,7 +104,6 @@ ColumnLayout {
}
ColumnLayout {
width: scrollView.availableWidth
spacing: 0
Item {
@ -125,7 +124,7 @@ ColumnLayout {
onToggled: checked => {
Settings.data.colorSchemes.darkMode = checked
if (Settings.data.colorSchemes.useWallpaperColors) {
ColorSchemeService.changedWallpaper()
MatugenService.generateFromWallpaper()
} else if (Settings.data.colorSchemes.predefinedScheme) {
// Re-apply current scheme to pick the right variant
ColorSchemeService.applyScheme(Settings.data.colorSchemes.predefinedScheme)
@ -137,19 +136,6 @@ ColumnLayout {
}
}
// App theming
NToggle {
label: "Theme external apps (GTK, Qt & kitty)"
description: "Writes GTK (gtk.css), Qt5/6 (noctalia.conf) and Kitty (noctalia.conf) themes based on your colors."
checked: Settings.data.colorSchemes.themeApps
onToggled: checked => {
Settings.data.colorSchemes.themeApps = checked
if (Settings.data.colorSchemes.useWallpaperColors) {
ColorSchemeService.changedWallpaper()
}
}
}
// Use Matugen
NToggle {
label: "Enable Matugen"
@ -348,5 +334,84 @@ ColumnLayout {
Layout.fillWidth: true
Layout.topMargin: Style.marginXL * scaling
Layout.bottomMargin: Style.marginXL * scaling
visible: Settings.data.colorSchemes.useWallpaperColors
}
// Matugen template toggles (moved from MatugenTab)
ColumnLayout {
spacing: Style.marginL * scaling
Layout.fillWidth: true
visible: Settings.data.colorSchemes.useWallpaperColors
NText {
text: "Matugen Templates"
font.pointSize: Style.fontSizeXXL * scaling
font.weight: Style.fontWeightBold
color: Color.mSecondary
Layout.bottomMargin: Style.marginS * scaling
}
NText {
text: "Choose which external components should be themed by Matugen."
font.pointSize: Style.fontSizeM * scaling
color: Color.mOnSurfaceVariant
Layout.fillWidth: true
wrapMode: Text.WordWrap
}
NToggle {
label: "GTK 4 (libadwaita)"
description: "Write ~/.config/gtk-4.0/gtk.css"
checked: Settings.data.matugen.gtk4
onToggled: checked => {
Settings.data.matugen.gtk4 = checked
if (Settings.data.colorSchemes.useWallpaperColors)
MatugenService.generateFromWallpaper()
}
}
NToggle {
label: "GTK 3"
description: "Write ~/.config/gtk-3.0/gtk.css"
checked: Settings.data.matugen.gtk3
onToggled: checked => {
Settings.data.matugen.gtk3 = checked
if (Settings.data.colorSchemes.useWallpaperColors)
MatugenService.generateFromWallpaper()
}
}
NToggle {
label: "Qt6ct"
description: "Write ~/.config/qt6ct/colors/noctalia.conf"
checked: Settings.data.matugen.qt6
onToggled: checked => {
Settings.data.matugen.qt6 = checked
if (Settings.data.colorSchemes.useWallpaperColors)
MatugenService.generateFromWallpaper()
}
}
NToggle {
label: "Qt5ct"
description: "Write ~/.config/qt5ct/colors/noctalia.conf"
checked: Settings.data.matugen.qt5
onToggled: checked => {
Settings.data.matugen.qt5 = checked
if (Settings.data.colorSchemes.useWallpaperColors)
MatugenService.generateFromWallpaper()
}
}
NToggle {
label: "Kitty"
description: "Write ~/.config/kitty/themes/noctalia.conf and reload"
checked: Settings.data.matugen.kitty
onToggled: checked => {
Settings.data.matugen.kitty = checked
if (Settings.data.colorSchemes.useWallpaperColors)
MatugenService.generateFromWallpaper()
}
}
}
}

View file

@ -43,7 +43,6 @@ ColumnLayout {
})
}
NText {
text: "Monitor-specific configuration"
font.pointSize: Style.fontSizeL * scaling
@ -123,8 +122,8 @@ ColumnLayout {
Settings.data.notifications.monitors = addMonitor(Settings.data.notifications.monitors,
modelData.name)
} else {
Settings.data.notifications.monitors = removeMonitor(
Settings.data.notifications.monitors, modelData.name)
Settings.data.notifications.monitors = removeMonitor(Settings.data.notifications.monitors,
modelData.name)
}
}
}
@ -327,7 +326,6 @@ ColumnLayout {
}
}
NDivider {
Layout.fillWidth: true
Layout.topMargin: Style.marginXL * scaling

View file

@ -5,6 +5,7 @@ import Qt.labs.folderlistmodel
import Quickshell
import Quickshell.Io
import qs.Commons
import qs.Services
Singleton {
id: root
@ -37,7 +38,7 @@ Singleton {
function changedWallpaper() {
if (Settings.data.colorSchemes.useWallpaperColors) {
Logger.log("ColorScheme", "Starting color generation from wallpaper")
generateColorsProcess.running = true
MatugenService.generateFromWallpaper()
// Invalidate potential predefined scheme
Settings.data.colorSchemes.predefinedScheme = ""
}
@ -137,35 +138,5 @@ Singleton {
colorsWriter.writeAdapter()
}
Process {
id: generateColorsProcess
command: {
// Choose config based on external theming toggles
var cfg = Quickshell.shellDir + "/Assets/Matugen/matugen.toml"
if (!Settings.data.colorSchemes.themeApps) {
cfg = Quickshell.shellDir + "/Assets/Matugen/matugen.base.toml"
}
var cmd = ["matugen", "image", WallpaperService.currentWallpaper, "--config", cfg]
if (!Settings.data.colorSchemes.darkMode) {
cmd.push("--mode", "light")
} else {
cmd.push("--mode", "dark")
}
return cmd
}
workingDirectory: Quickshell.shellDir
running: false
stdout: StdioCollector {
onStreamFinished: {
Logger.log("ColorScheme", "Completed colors generation")
}
}
stderr: StdioCollector {
onStreamFinished: {
if (this.text !== "") {
Logger.error(this.text)
}
}
}
}
// Matugen generation moved to MatugenService
}

View file

@ -0,0 +1,53 @@
pragma Singleton
import QtQuick
import Quickshell
import Quickshell.Io
import qs.Commons
import qs.Assets.Matugen
import qs.Services
Singleton {
id: root
property string dynamicConfigPath: Settings.cacheDir + "matugen.dynamic.toml"
// Build TOML content based on settings
function buildConfigToml() {
return Matugen.buildConfigToml()
}
// Generate colors using current wallpaper and settings
function generateFromWallpaper() {
// Ensure cache dir exists
Quickshell.execDetached(["mkdir", "-p", Settings.cacheDir])
var content = buildConfigToml()
var mode = Settings.data.colorSchemes.darkMode ? "dark" : "light"
var wp = WallpaperService.currentWallpaper.replace(/'/g, "'\\''")
var pathEsc = dynamicConfigPath.replace(/'/g, "'\\''")
var extraRepo = (Quickshell.shellDir + "/Assets/Matugen/extra").replace(/'/g, "'\\''")
var extraUser = (Settings.configDir + "matugen.d").replace(/'/g, "'\\''")
var script = "cat > '" + pathEsc + "' << 'EOF'\n" + content + "EOF\n" + "for d in '" + extraRepo + "' '" + extraUser
+ "'; do\n" + " if [ -d \"$d\" ]; then\n"
+ " for f in \"$d\"/*.toml; do\n" + " [ -f \"$f\" ] && { echo; echo \"# extra: $f\"; cat \"$f\"; } >> '"
+ pathEsc + "'\n" + " done\n" + " fi\n" + "done\n" + "matugen image '" + wp + "' --config '" + pathEsc + "' --mode " + mode
generateProcess.command = ["bash", "-lc", script]
generateProcess.running = true
}
Process {
id: generateProcess
workingDirectory: Quickshell.shellDir
running: false
stdout: StdioCollector {
onStreamFinished: Logger.log("Matugen", "Completed colors generation")
}
stderr: StdioCollector {
onStreamFinished: if (this.text !== "")
Logger.error(this.text)
}
}
// No separate writer; the write happens inline via bash heredoc
}