Cleaned up ColorSchemeTab, added program checks, added firefox template

Matugen: added firefox (pywalfox) template
SidePanelToggle: use ProgramCheckerService for gpu-screen-recorder
ColorSchemeTab: use NCollapsible for matugen templates, use
ProgramCheckerService to detect available programs (for matugen
templates)
NCollapsible: create collapsible category
This commit is contained in:
Ly-sec 2025-09-15 07:44:31 +02:00
parent c0d6780c3d
commit fa838ecdb1
8 changed files with 514 additions and 148 deletions

View file

@ -70,6 +70,12 @@ Singleton {
lines.push('input_path = "' + Quickshell.shellDir + '/Assets/Matugen/templates/vesktop.css"')
lines.push('output_path = "~/.config/vesktop/themes/noctalia.theme.css"')
}
if (Settings.data.matugen.pywalfox) {
lines.push("\n[templates.pywalfox]")
lines.push('input_path = "' + Quickshell.shellDir + '/Assets/Matugen/templates/pywalfox.json"')
lines.push('output_path = "~/.cache/wal/colors.json"')
lines.push('post_hook = "pywalfox update"')
}
return lines.join("\n") + "\n"
}

View file

@ -0,0 +1,22 @@
{
"wallpaper": "{{image}}",
"alpha": "100",
"colors": {
"color0": "{{colors.background.default.hex}}",
"color1": "",
"color2": "",
"color3": "",
"color4": "",
"color5": "",
"color6": "",
"color7": "",
"color8": "",
"color9": "",
"color10": "{{colors.primary.default.hex}}",
"color11": "",
"color12": "",
"color13": "{{colors.surface_bright.default.hex}}",
"color14": "",
"color15": "{{colors.on_surface.default.hex}}"
}
}

View file

@ -449,6 +449,7 @@ Singleton {
property bool foot: false
property bool fuzzel: false
property bool vesktop: false
property bool pywalfox: false
property bool enableUserTemplates: false
}

View file

@ -39,7 +39,7 @@ NIconButton {
colorFg: Color.mOnSurface
colorBgHover: useDistroLogo ? Color.mSurfaceVariant : Color.mTertiary
colorBorder: Color.transparent
colorBorderHover: useDistroLogo ? Color.mTertiary : Color.transparent
colorBorderHover: useDistroLogo ? Color.mTertiary : Color.transparent
onClicked: PanelService.getPanel("sidePanel")?.toggle(this)
onRightClicked: PanelService.getPanel("settingsPanel")?.toggle()

View file

@ -318,146 +318,187 @@ ColumnLayout {
visible: Settings.data.colorSchemes.useWallpaperColors
}
// Matugen template toggles (moved from MatugenTab)
// Matugen template toggles organized by category
ColumnLayout {
spacing: Style.marginL * scaling
Layout.fillWidth: true
visible: Settings.data.colorSchemes.useWallpaperColors
spacing: Style.marginL * scaling
ColumnLayout {
spacing: Style.marginS * scaling
// UI Components
NCollapsible {
Layout.fillWidth: true
label: "UI"
description: "Desktop environment and UI toolkit theming."
defaultExpanded: false
NText {
text: "Matugen Templates"
font.pointSize: Style.fontSizeXXL * scaling
font.weight: Style.fontWeightBold
color: Color.mSecondary
NCheckbox {
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()
}
}
NText {
text: "Select which external components Matugen should apply theming to."
font.pointSize: Style.fontSizeM * scaling
color: Color.mOnSurfaceVariant
Layout.fillWidth: true
wrapMode: Text.WordWrap
NCheckbox {
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()
}
}
NCheckbox {
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()
}
}
NCheckbox {
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()
}
}
}
NCheckbox {
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()
}
}
NCheckbox {
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()
}
}
NCheckbox {
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()
}
}
NCheckbox {
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()
}
}
NCheckbox {
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()
}
}
NCheckbox {
label: "Ghostty"
description: "Write ~/.config/ghostty/themes/noctalia and reload"
checked: Settings.data.matugen.ghostty
onToggled: checked => {
Settings.data.matugen.ghostty = checked
if (Settings.data.colorSchemes.useWallpaperColors)
MatugenService.generateFromWallpaper()
}
}
NCheckbox {
label: "Foot"
description: "Write ~/.config/foot/themes/noctalia and reload"
checked: Settings.data.matugen.foot
onToggled: checked => {
Settings.data.matugen.foot = checked
if (Settings.data.colorSchemes.useWallpaperColors)
MatugenService.generateFromWallpaper()
}
}
NCheckbox {
label: "Fuzzel"
description: "Write ~/.config/fuzzel/themes/noctalia and reload"
checked: Settings.data.matugen.fuzzel
onToggled: checked => {
Settings.data.matugen.fuzzel = checked
if (Settings.data.colorSchemes.useWallpaperColors)
MatugenService.generateFromWallpaper()
}
}
NCheckbox {
label: "Vesktop"
description: "Write ~/.config/vesktop/themes/noctalia.theme.css"
checked: Settings.data.matugen.vesktop
onToggled: checked => {
Settings.data.matugen.vesktop = checked
if (Settings.data.colorSchemes.useWallpaperColors)
MatugenService.generateFromWallpaper()
}
}
NDivider {
// Terminal Emulators
NCollapsible {
Layout.fillWidth: true
Layout.topMargin: Style.marginM * scaling
Layout.bottomMargin: Style.marginM * scaling
label: "Terminal"
description: "Terminal emulator theming."
defaultExpanded: false
NCheckbox {
label: "Kitty"
description: ProgramCheckerService.kittyAvailable ? "Write ~/.config/kitty/themes/noctalia.conf and reload" : "Requires kitty terminal to be installed"
checked: Settings.data.matugen.kitty
enabled: ProgramCheckerService.kittyAvailable
opacity: ProgramCheckerService.kittyAvailable ? 1.0 : 0.6
onToggled: checked => {
if (ProgramCheckerService.kittyAvailable) {
Settings.data.matugen.kitty = checked
if (Settings.data.colorSchemes.useWallpaperColors)
MatugenService.generateFromWallpaper()
}
}
}
NCheckbox {
label: "Ghostty"
description: ProgramCheckerService.ghosttyAvailable ? "Write ~/.config/ghostty/themes/noctalia and reload" : "Requires ghostty terminal to be installed"
checked: Settings.data.matugen.ghostty
enabled: ProgramCheckerService.ghosttyAvailable
opacity: ProgramCheckerService.ghosttyAvailable ? 1.0 : 0.6
onToggled: checked => {
if (ProgramCheckerService.ghosttyAvailable) {
Settings.data.matugen.ghostty = checked
if (Settings.data.colorSchemes.useWallpaperColors)
MatugenService.generateFromWallpaper()
}
}
}
NCheckbox {
label: "Foot"
description: ProgramCheckerService.footAvailable ? "Write ~/.config/foot/themes/noctalia and reload" : "Requires foot terminal to be installed"
checked: Settings.data.matugen.foot
enabled: ProgramCheckerService.footAvailable
opacity: ProgramCheckerService.footAvailable ? 1.0 : 0.6
onToggled: checked => {
if (ProgramCheckerService.footAvailable) {
Settings.data.matugen.foot = checked
if (Settings.data.colorSchemes.useWallpaperColors)
MatugenService.generateFromWallpaper()
}
}
}
}
NCheckbox {
label: "User Templates"
description: "Enable user-defined Matugen config from ~/.config/matugen/config.toml"
checked: Settings.data.matugen.enableUserTemplates
onToggled: checked => {
Settings.data.matugen.enableUserTemplates = checked
if (Settings.data.colorSchemes.useWallpaperColors)
MatugenService.generateFromWallpaper()
}
// Applications
NCollapsible {
Layout.fillWidth: true
label: "Programs"
description: "Application-specific theming."
defaultExpanded: false
NCheckbox {
label: "Fuzzel"
description: ProgramCheckerService.fuzzelAvailable ? "Write ~/.config/fuzzel/themes/noctalia and reload" : "Requires fuzzel launcher to be installed"
checked: Settings.data.matugen.fuzzel
enabled: ProgramCheckerService.fuzzelAvailable
opacity: ProgramCheckerService.fuzzelAvailable ? 1.0 : 0.6
onToggled: checked => {
if (ProgramCheckerService.fuzzelAvailable) {
Settings.data.matugen.fuzzel = checked
if (Settings.data.colorSchemes.useWallpaperColors)
MatugenService.generateFromWallpaper()
}
}
}
NCheckbox {
label: "Vesktop"
description: ProgramCheckerService.vesktopAvailable ? "Write ~/.config/vesktop/themes/noctalia.theme.css" : "Requires vesktop Discord client to be installed"
checked: Settings.data.matugen.vesktop
enabled: ProgramCheckerService.vesktopAvailable
opacity: ProgramCheckerService.vesktopAvailable ? 1.0 : 0.6
onToggled: checked => {
if (ProgramCheckerService.vesktopAvailable) {
Settings.data.matugen.vesktop = checked
if (Settings.data.colorSchemes.useWallpaperColors)
MatugenService.generateFromWallpaper()
}
}
}
NCheckbox {
label: "Pywalfox (Firefox)"
description: ProgramCheckerService.pywalfoxAvailable ? "Write ~/.cache/wal/colors.json and run pywalfox update" : "Requires pywalfox package to be installed"
checked: Settings.data.matugen.pywalfox
enabled: ProgramCheckerService.pywalfoxAvailable
opacity: ProgramCheckerService.pywalfoxAvailable ? 1.0 : 0.6
onToggled: checked => {
if (ProgramCheckerService.pywalfoxAvailable) {
Settings.data.matugen.pywalfox = checked
if (Settings.data.colorSchemes.useWallpaperColors)
MatugenService.generateFromWallpaper()
}
}
}
}
// Miscellaneous
NCollapsible {
Layout.fillWidth: true
label: "Misc"
description: "Additional configuration options."
defaultExpanded: false
NCheckbox {
label: "User Templates"
description: "Enable user-defined Matugen config from ~/.config/matugen/config.toml"
checked: Settings.data.matugen.enableUserTemplates
onToggled: checked => {
Settings.data.matugen.enableUserTemplates = checked
if (Settings.data.colorSchemes.useWallpaperColors)
MatugenService.generateFromWallpaper()
}
}
}
}
}

View file

@ -0,0 +1,118 @@
pragma Singleton
import QtQuick
import Quickshell
import Quickshell.Io
import qs.Commons
// Service to check if various programs are available on the system
Singleton {
id: root
// Program availability properties
property bool matugenAvailable: false
property bool pywalfoxAvailable: false
property bool kittyAvailable: false
property bool ghosttyAvailable: false
property bool footAvailable: false
property bool fuzzelAvailable: false
property bool vesktopAvailable: false
property bool gpuScreenRecorderAvailable: false
// Signal emitted when all checks are complete
signal checksCompleted
// Programs to check - maps property names to commands
readonly property var programsToCheck: ({
"matugenAvailable": ["which", "matugen"],
"pywalfoxAvailable": ["which", "pywalfox"],
"kittyAvailable": ["which", "kitty"],
"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')"]
})
// Internal tracking
property int completedChecks: 0
property int totalChecks: Object.keys(programsToCheck).length
// Single reusable Process object
Process {
id: checker
running: false
property string currentProperty: ""
onExited: function (exitCode) {
// Set the availability property
root[currentProperty] = (exitCode === 0)
// Stop the process to free resources
running = false
// Track completion
root.completedChecks++
// Check next program or emit completion signal
if (root.completedChecks >= root.totalChecks) {
root.checksCompleted()
} else {
root.checkNextProgram()
}
}
stdout: StdioCollector {}
stderr: StdioCollector {}
}
// Queue of programs to check
property var checkQueue: []
property int currentCheckIndex: 0
// Function to check the next program in the queue
function checkNextProgram() {
if (currentCheckIndex >= checkQueue.length)
return
var propertyName = checkQueue[currentCheckIndex]
var command = programsToCheck[propertyName]
checker.currentProperty = propertyName
checker.command = command
checker.running = true
currentCheckIndex++
}
// Function to run all program checks
function checkAllPrograms() {
// Reset state
completedChecks = 0
currentCheckIndex = 0
checkQueue = Object.keys(programsToCheck)
// Start first check
if (checkQueue.length > 0) {
checkNextProgram()
}
}
// Function to check a specific program
function checkProgram(programProperty) {
if (!programsToCheck.hasOwnProperty(programProperty)) {
Logger.warn("ProgramChecker", "Unknown program property:", programProperty)
return
}
checker.currentProperty = programProperty
checker.command = programsToCheck[programProperty]
checker.running = true
}
// Initialize checks when service is created
Component.onCompleted: {
checkAllPrograms()
}
}

View file

@ -13,16 +13,13 @@ Singleton {
property bool isRecording: false
property bool isPending: false
property string outputPath: ""
property bool isAvailable: false
property bool isAvailable: ProgramCheckerService.gpuScreenRecorderAvailable
Component.onCompleted: {
checkAvailability()
}
function checkAvailability() {
// Detect native or Flatpak gpu-screen-recorder
availabilityCheckProcess.command = ["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')"]
availabilityCheckProcess.running = true
// Update availability when ProgramCheckerService completes its checks
Connections {
target: ProgramCheckerService
function onChecksCompleted() {// Availability is now automatically updated via property binding
}
}
// Start or Stop recording
@ -101,18 +98,6 @@ Singleton {
}
}
// Availability check process
Process {
id: availabilityCheckProcess
command: ["sh", "-c", "true"]
onExited: function (exitCode, exitStatus) {
// exitCode 0 means available, non-zero means unavailable
root.isAvailable = (exitCode === 0)
}
stdout: StdioCollector {}
stderr: StdioCollector {}
}
Timer {
id: pendingTimer
interval: 2000 // Wait 2 seconds to see if process stays alive

193
Widgets/NCollapsible.qml Normal file
View file

@ -0,0 +1,193 @@
import QtQuick
import QtQuick.Layouts
import qs.Commons
ColumnLayout {
id: root
property string label: ""
property string description: ""
property bool expanded: false
property bool defaultExpanded: false
property real contentSpacing: Style.marginM * scaling
signal toggled(bool expanded)
Layout.fillWidth: true
spacing: 0
// Default property to accept children
default property alias content: contentLayout.children
// Header with clickable area
Rectangle {
id: headerContainer
Layout.fillWidth: true
Layout.preferredHeight: headerContent.implicitHeight + (Style.marginL * scaling * 2)
// Material 3 style background
color: root.expanded ? Color.mSecondary : Color.mSurfaceVariant
radius: Style.radiusL * scaling
// Subtle border
border.color: root.expanded ? Color.mOnSecondary : Color.mOutline
border.width: Math.max(1, Style.borderS * scaling)
// Smooth color transitions
Behavior on color {
ColorAnimation {
duration: Style.animationNormal
easing.type: Easing.OutCubic
}
}
Behavior on border.color {
ColorAnimation {
duration: Style.animationNormal
easing.type: Easing.OutCubic
}
}
MouseArea {
id: headerArea
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
hoverEnabled: true
onClicked: {
root.expanded = !root.expanded
root.toggled(root.expanded)
}
// Hover effect overlay
Rectangle {
anchors.fill: parent
color: headerArea.containsMouse ? Color.mOnSurface : Color.transparent
opacity: headerArea.containsMouse ? 0.08 : 0
radius: headerContainer.radius // Reference the container's radius directly
Behavior on opacity {
NumberAnimation {
duration: Style.animationFast
}
}
}
}
RowLayout {
id: headerContent
anchors.fill: parent
anchors.margins: Style.marginL * scaling
spacing: Style.marginM * scaling
// Expand/collapse icon with rotation animation
NIcon {
id: chevronIcon
icon: "chevron-right"
font.pointSize: Style.fontSizeL * scaling
color: root.expanded ? Color.mOnSecondary : Color.mOnSurfaceVariant
Layout.alignment: Qt.AlignVCenter
rotation: root.expanded ? 90 : 0
Behavior on rotation {
NumberAnimation {
duration: Style.animationNormal
easing.type: Easing.OutCubic
}
}
Behavior on color {
ColorAnimation {
duration: Style.animationNormal
}
}
}
// Header text content - properly contained
ColumnLayout {
Layout.fillWidth: true
Layout.alignment: Qt.AlignVCenter
spacing: Style.marginXXS * scaling
NText {
text: root.label
font.pointSize: Style.fontSizeL * scaling
font.weight: Style.fontWeightSemiBold
color: root.expanded ? Color.mOnSecondary : Color.mOnSurface
Layout.fillWidth: true
wrapMode: Text.WordWrap
Behavior on color {
ColorAnimation {
duration: Style.animationNormal
}
}
}
NText {
text: root.description
font.pointSize: Style.fontSizeS * scaling
font.weight: Style.fontWeightRegular
color: root.expanded ? Color.mOnSecondary : Color.mOnSurfaceVariant
Layout.fillWidth: true
wrapMode: Text.WordWrap
visible: root.description !== ""
opacity: 0.87
Behavior on color {
ColorAnimation {
duration: Style.animationNormal
}
}
}
}
}
}
// Collapsible content with Material 3 styling
Rectangle {
id: contentContainer
Layout.fillWidth: true
Layout.topMargin: Style.marginS * scaling
visible: root.expanded
color: Color.mSurface
radius: Style.radiusL * scaling
border.color: Color.mOutline
border.width: Math.max(1, Style.borderS * scaling)
// Dynamic height based on content
Layout.preferredHeight: visible ? contentLayout.implicitHeight + (Style.marginL * scaling * 2) : 0
// Smooth height animation
Behavior on Layout.preferredHeight {
NumberAnimation {
duration: Style.animationNormal
easing.type: Easing.OutCubic
}
}
// Content layout
ColumnLayout {
id: contentLayout
anchors.fill: parent
anchors.margins: Style.marginL * scaling
spacing: root.contentSpacing
// Clip content during animation
clip: true
}
// Fade in animation for content
opacity: root.expanded ? 1.0 : 0.0
Behavior on opacity {
NumberAnimation {
duration: Style.animationNormal
easing.type: Easing.OutCubic
}
}
}
// Initialize expanded state
Component.onCompleted: {
root.expanded = root.defaultExpanded
}
}