From 940ee3e9885e0a3905b51d734d58ff81170528f2 Mon Sep 17 00:00:00 2001 From: Janis Hutz Date: Sun, 14 Jun 2026 14:30:09 +0200 Subject: [PATCH] feat: basic quickshell setup --- config/matugen/config.toml | 4 + config/quickshell/README.md | 3 + config/quickshell/bar/Bar.qml | 35 ++++++ config/quickshell/bar/WidgetWrapper.qml | 63 ++++++++++ config/quickshell/bar/qmldir | 2 + config/quickshell/bar/widgets/Application.qml | 4 + config/quickshell/bar/widgets/Clock.qml | 10 ++ .../quickshell/bar/widgets/ClockHandler.qml | 30 +++++ .../quickshell/bar/widgets/CurrentWindow.qml | 0 config/quickshell/bar/widgets/Mode.qml | 6 + config/quickshell/bar/widgets/Status.qml | 0 config/quickshell/bar/widgets/Workspace.qml | 0 config/quickshell/bar/widgets/qmldir | 2 + config/quickshell/config/Appearance.qml | 114 ++++++++++++++++++ config/quickshell/config/Paths.qml | 14 +++ config/quickshell/config/qmldir | 2 + config/quickshell/osd/Volume.qml | 95 +++++++++++++++ config/quickshell/osd/qmldir | 1 + config/quickshell/shell.qml | 15 +++ config/quickshell/utils/FileUtils.qml | 72 +++++++++++ config/quickshell/utils/MaterialSymbols.qml | 0 config/quickshell/utils/qmldir | 1 + quickshell-test.sh | 4 + 23 files changed, 477 insertions(+) create mode 100644 config/quickshell/bar/Bar.qml create mode 100644 config/quickshell/bar/WidgetWrapper.qml create mode 100644 config/quickshell/bar/qmldir create mode 100644 config/quickshell/bar/widgets/Application.qml create mode 100644 config/quickshell/bar/widgets/Clock.qml create mode 100644 config/quickshell/bar/widgets/ClockHandler.qml create mode 100644 config/quickshell/bar/widgets/CurrentWindow.qml create mode 100644 config/quickshell/bar/widgets/Mode.qml create mode 100644 config/quickshell/bar/widgets/Status.qml create mode 100644 config/quickshell/bar/widgets/Workspace.qml create mode 100644 config/quickshell/bar/widgets/qmldir create mode 100644 config/quickshell/config/Appearance.qml create mode 100644 config/quickshell/config/Paths.qml create mode 100644 config/quickshell/config/qmldir create mode 100644 config/quickshell/osd/Volume.qml create mode 100644 config/quickshell/osd/qmldir create mode 100644 config/quickshell/shell.qml create mode 100644 config/quickshell/utils/FileUtils.qml create mode 100644 config/quickshell/utils/MaterialSymbols.qml create mode 100644 config/quickshell/utils/qmldir create mode 100755 quickshell-test.sh diff --git a/config/matugen/config.toml b/config/matugen/config.toml index d8edf7c..9604eba 100644 --- a/config/matugen/config.toml +++ b/config/matugen/config.toml @@ -20,3 +20,7 @@ output_path = '~/.config/gtk-3.0/gtk.css' [templates.gtk4] input_path = '~/.config/matugen/templates/gtk-4.0/gtk.css' output_path = '~/.config/gtk-4.0/gtk.css' + +[templates.wlogout] +input_path = '~/.config/matugen/templates/wlogout/style.css' +output_path = '~/.config/wlogout/style.css' diff --git a/config/quickshell/README.md b/config/quickshell/README.md index f08a525..4ea1f19 100644 --- a/config/quickshell/README.md +++ b/config/quickshell/README.md @@ -1 +1,4 @@ # Quickshell Status bar +Many of the components were taken and adapted from the [End-4 configs](https://github.com/end-4/dots-hyprland). + +All of the files in `utils/` are taken from the End-4 configs diff --git a/config/quickshell/bar/Bar.qml b/config/quickshell/bar/Bar.qml new file mode 100644 index 0000000..3ad8eab --- /dev/null +++ b/config/quickshell/bar/Bar.qml @@ -0,0 +1,35 @@ +import Quickshell +import QtQuick +import QtQuick.Layouts +import "./widgets/" +import "../config" + +Scope { + Variants { + model: Quickshell.screens + + PanelWindow { + id: barRoot + property var modelData + screen: modelData + + anchors { + top: true + left: true + right: true + } + + color: Appearance.colors.m3background + + implicitHeight: 40 + + RowLayout { + anchors.fill: parent + + spacing: 0 + + Clock {} + } + } + } +} diff --git a/config/quickshell/bar/WidgetWrapper.qml b/config/quickshell/bar/WidgetWrapper.qml new file mode 100644 index 0000000..fdc1f8a --- /dev/null +++ b/config/quickshell/bar/WidgetWrapper.qml @@ -0,0 +1,63 @@ +import QtQuick +import QtQuick.Layouts +import "../config/" + +// Mostly from here: https://github.com/ChrisTitusTech/quickshell/blob/main/bar/BarBlock.qml +Rectangle { + id: root + radius: Appearance.rounding.barItems + + Layout.preferredWidth: contentContainer.implicitWidth + 20 + Layout.preferredHeight: 30 + + property Item content: Text { + text: "No content" + } + property bool hoverEnabled: false + property Item mouseArea: mouseArea + + property bool dim: false + property var onClicked: function () {} + property int leftPadding + property int rightPadding + + // Background color + color: { + if (mouseArea.containsMouse) + return Appearance.colors.m3primary; + return Appearance.colors.m3background; + } + + states: [ + State { + when: mouseArea.containsMouse + PropertyChanges { + target: root + } + } + ] + + Behavior on color { + ColorAnimation { + duration: 200 + } + } + + Item { + // Contents of the bar block + id: contentContainer + implicitWidth: content.implicitWidth + implicitHeight: content.implicitHeight + anchors.centerIn: parent + children: content + } + + MouseArea { + enabled: root.hoverEnabled + id: mouseArea + anchors.fill: root + hoverEnabled: true + acceptedButtons: Qt.LeftButton + onClicked: root.onClicked() + } +} diff --git a/config/quickshell/bar/qmldir b/config/quickshell/bar/qmldir new file mode 100644 index 0000000..efc0cde --- /dev/null +++ b/config/quickshell/bar/qmldir @@ -0,0 +1,2 @@ +Bar 1.0 Bar.qml +WidgetWrapper 1.0 WidgetWrapper.qml diff --git a/config/quickshell/bar/widgets/Application.qml b/config/quickshell/bar/widgets/Application.qml new file mode 100644 index 0000000..3fac4db --- /dev/null +++ b/config/quickshell/bar/widgets/Application.qml @@ -0,0 +1,4 @@ +import QtQuick +import Quickshell + +Scope {} diff --git a/config/quickshell/bar/widgets/Clock.qml b/config/quickshell/bar/widgets/Clock.qml new file mode 100644 index 0000000..41a72bf --- /dev/null +++ b/config/quickshell/bar/widgets/Clock.qml @@ -0,0 +1,10 @@ +import QtQuick +import QtQuick.Layouts +import "." +import "../../config" + +Text { + text: `${ClockHandler.date}, ${ClockHandler.time}` + color: Appearance.colors.m3onBackground + Layout.leftMargin: 10 +} diff --git a/config/quickshell/bar/widgets/ClockHandler.qml b/config/quickshell/bar/widgets/ClockHandler.qml new file mode 100644 index 0000000..b08c1dd --- /dev/null +++ b/config/quickshell/bar/widgets/ClockHandler.qml @@ -0,0 +1,30 @@ +pragma Singleton + +import Quickshell +import Quickshell.Io +import QtQuick + +Singleton { + property string time + property string date + + Process { + id: dateProc + command: ["date", "+%a %e %b|%T"] + running: true + + stdout: SplitParser { + onRead: data => { + date = data.split("|")[0]; + time = data.split("|")[1]; + } + } + } + + Timer { + interval: 1000 + running: true + repeat: true + onTriggered: dateProc.running = true + } +} diff --git a/config/quickshell/bar/widgets/CurrentWindow.qml b/config/quickshell/bar/widgets/CurrentWindow.qml new file mode 100644 index 0000000..e69de29 diff --git a/config/quickshell/bar/widgets/Mode.qml b/config/quickshell/bar/widgets/Mode.qml new file mode 100644 index 0000000..66be12d --- /dev/null +++ b/config/quickshell/bar/widgets/Mode.qml @@ -0,0 +1,6 @@ +import Quickshell +import QtQuick + +Scope { + id: root +} diff --git a/config/quickshell/bar/widgets/Status.qml b/config/quickshell/bar/widgets/Status.qml new file mode 100644 index 0000000..e69de29 diff --git a/config/quickshell/bar/widgets/Workspace.qml b/config/quickshell/bar/widgets/Workspace.qml new file mode 100644 index 0000000..e69de29 diff --git a/config/quickshell/bar/widgets/qmldir b/config/quickshell/bar/widgets/qmldir new file mode 100644 index 0000000..9e30ce4 --- /dev/null +++ b/config/quickshell/bar/widgets/qmldir @@ -0,0 +1,2 @@ +Clock 1.0 Clock.qml +singleton ClockHandler 1.0 ClockHandler.qml diff --git a/config/quickshell/config/Appearance.qml b/config/quickshell/config/Appearance.qml new file mode 100644 index 0000000..313803c --- /dev/null +++ b/config/quickshell/config/Appearance.qml @@ -0,0 +1,114 @@ +pragma Singleton + +import QtQuick +import Quickshell +import Quickshell.Io + +Singleton { + id: root + property QtObject colors + property QtObject fonts + property QtObject fontsizes + property QtObject rounding + + // From end-4 configs + colors: QtObject { + property color m3background: "#141313" + property color m3onBackground: "#e6e1e1" + property color m3surface: "#141313" + property color m3surfaceDim: "#141313" + property color m3surfaceBright: "#3a3939" + property color m3surfaceContainerLowest: "#0f0e0e" + property color m3surfaceContainerLow: "#1c1b1c" + property color m3surfaceContainer: "#201f20" + property color m3surfaceContainerHigh: "#2b2a2a" + property color m3surfaceContainerHighest: "#363435" + property color m3onSurface: "#e6e1e1" + property color m3surfaceVariant: "#49464a" + property color m3onSurfaceVariant: "#cbc5ca" + property color m3inverseSurface: "#e6e1e1" + property color m3inverseOnSurface: "#313030" + property color m3outline: "#948f94" + property color m3outlineVariant: "#49464a" + property color m3shadow: "#000000" + property color m3scrim: "#000000" + property color m3surfaceTint: "#cbc4cb" + property color m3primary: "#cbc4cb" + property color m3onPrimary: "#322f34" + property color m3primaryContainer: "#2d2a2f" + property color m3onPrimaryContainer: "#bcb6bc" + property color m3inversePrimary: "#615d63" + property color m3secondary: "#cac5c8" + property color m3onSecondary: "#323032" + property color m3secondaryContainer: "#4d4b4d" + property color m3onSecondaryContainer: "#ece6e9" + property color m3tertiary: "#d1c3c6" + property color m3onTertiary: "#372e30" + property color m3tertiaryContainer: "#31292b" + property color m3onTertiaryContainer: "#c1b4b7" + property color m3error: "#ffb4ab" + property color m3onError: "#690005" + property color m3errorContainer: "#93000a" + property color m3onErrorContainer: "#ffdad6" + property color m3primaryFixed: "#e7e0e7" + property color m3primaryFixedDim: "#cbc4cb" + property color m3onPrimaryFixed: "#1d1b1f" + property color m3onPrimaryFixedVariant: "#49454b" + property color m3secondaryFixed: "#e6e1e4" + property color m3secondaryFixedDim: "#cac5c8" + property color m3onSecondaryFixed: "#1d1b1d" + property color m3onSecondaryFixedVariant: "#484648" + property color m3tertiaryFixed: "#eddfe1" + property color m3tertiaryFixedDim: "#d1c3c6" + property color m3onTertiaryFixed: "#211a1c" + property color m3onTertiaryFixedVariant: "#4e4447" + property color m3success: "#B5CCBA" + property color m3onSuccess: "#213528" + property color m3successContainer: "#374B3E" + property color m3onSuccessContainer: "#D1E9D6" + } + + rounding: QtObject { + property int barItems: 10 + } + + function reapplyTheme() { + themeFileView.reload(); + } + + function applyTheme(fileContent) { + const json = JSON.parse(fileContent); + for (const key in json) { + if (json.hasOwnProperty(key)) { + // Convert snake_case to CamelCase + const camelCaseKey = key.replace(/_([a-z])/g, g => g[1].toUpperCase()); + const m3Key = `${camelCaseKey}`; + root.colors[m3Key] = json[key]; + } + } + } + + Timer { + id: delayedFileRead + interval: 1000 + repeat: false + running: false + onTriggered: { + root.applyTheme(themeFileView.text()); + } + } + + FileView { + id: themeFileView + path: Qt.resolvedUrl(Paths.materialColorsPath) + watchChanges: true + onFileChanged: { + this.reload(); + delayedFileRead.start(); + } + onLoadedChanged: { + const fileContent = themeFileView.text(); + root.applyTheme(fileContent); + } + } +} diff --git a/config/quickshell/config/Paths.qml b/config/quickshell/config/Paths.qml new file mode 100644 index 0000000..383c8a1 --- /dev/null +++ b/config/quickshell/config/Paths.qml @@ -0,0 +1,14 @@ +pragma Singleton + +import Quickshell +import QtCore +import "../utils/" + +Singleton { + readonly property string home: StandardPaths.standardLocations(StandardPaths.HomeLocation)[0] + readonly property string config: StandardPaths.standardLocations(StandardPaths.ConfigLocation)[0] + readonly property string state: StandardPaths.standardLocations(StandardPaths.StateLocation)[0] + readonly property string cache: StandardPaths.standardLocations(StandardPaths.CacheLocation)[0] + + property string materialColorsPath: FileUtils.trimFileProtocol(`${Paths.state}/user/generated/colors.json`) +} diff --git a/config/quickshell/config/qmldir b/config/quickshell/config/qmldir new file mode 100644 index 0000000..63a7fad --- /dev/null +++ b/config/quickshell/config/qmldir @@ -0,0 +1,2 @@ +singleton Appearance 1.0 Appearance.qml +singleton Paths 1.0 Paths.qml diff --git a/config/quickshell/osd/Volume.qml b/config/quickshell/osd/Volume.qml new file mode 100644 index 0000000..badce9a --- /dev/null +++ b/config/quickshell/osd/Volume.qml @@ -0,0 +1,95 @@ +// From https://github.com/quickshell-mirror/quickshell-examples/blob/master/volume-osd/shell.qml +import QtQuick +import QtQuick.Layouts +import Quickshell +import Quickshell.Services.Pipewire +import Quickshell.Widgets +import "../config/" + +Scope { + id: root + + // Bind the pipewire node so its volume will be tracked + PwObjectTracker { + objects: [Pipewire.defaultAudioSink] + } + + Connections { + target: Pipewire.defaultAudioSink?.audio + + function onVolumeChanged() { + root.shouldShowOsd = true; + hideTimer.restart(); + } + } + + property bool shouldShowOsd: false + + Timer { + id: hideTimer + interval: 1000 + onTriggered: root.shouldShowOsd = false + } + + // The OSD window will be created and destroyed based on shouldShowOsd. + // PanelWindow.visible could be set instead of using a loader, but using + // a loader will reduce the memory overhead when the window isn't open. + LazyLoader { + active: root.shouldShowOsd + + PanelWindow { + // Since the panel's screen is unset, it will be picked by the compositor + // when the window is created. Most compositors pick the current active monitor. + + anchors.bottom: true + margins.bottom: screen.height / 5 + exclusiveZone: 0 + + implicitWidth: 300 + implicitHeight: 50 + color: "transparent" + + // An empty click mask prevents the window from blocking mouse events. + mask: Region {} + + Rectangle { + anchors.fill: parent + radius: height / 2 + color: Appearance.colors.m3background + + RowLayout { + anchors { + fill: parent + leftMargin: 10 + rightMargin: 15 + } + + IconImage { + implicitSize: 30 + source: Quickshell.iconPath("audio-volume-high-symbolic") + } + + Rectangle { + // Stretches to fill all left-over space + Layout.fillWidth: true + + implicitHeight: 10 + radius: 20 + color: "#50ffffff" + + Rectangle { + anchors { + left: parent.left + top: parent.top + bottom: parent.bottom + } + + implicitWidth: parent.width * (Pipewire.defaultAudioSink?.audio.volume ?? 0) + radius: parent.radius + } + } + } + } + } + } +} diff --git a/config/quickshell/osd/qmldir b/config/quickshell/osd/qmldir new file mode 100644 index 0000000..3e70892 --- /dev/null +++ b/config/quickshell/osd/qmldir @@ -0,0 +1 @@ +Volume 1.0 Volume.qml diff --git a/config/quickshell/shell.qml b/config/quickshell/shell.qml new file mode 100644 index 0000000..2e77ffb --- /dev/null +++ b/config/quickshell/shell.qml @@ -0,0 +1,15 @@ +import Quickshell +import QtQuick +import "config" +import "bar" +import "osd" + +ShellRoot { + Component.onCompleted: { + // Load themes + Appearance.reapplyTheme(); + } + + Bar {} + Volume {} +} diff --git a/config/quickshell/utils/FileUtils.qml b/config/quickshell/utils/FileUtils.qml new file mode 100644 index 0000000..f24746e --- /dev/null +++ b/config/quickshell/utils/FileUtils.qml @@ -0,0 +1,72 @@ +pragma Singleton + +import Quickshell + +Singleton { + id: root + + /** + * Trims the File protocol off the input string + * @param {string} str + * @returns {string} + */ + function trimFileProtocol(str) { + let s = str; + if (typeof s !== "string") s = str.toString(); // Convert to string if it's an url or whatever + return s.startsWith("file://") ? s.slice(7) : s; + } + + /** + * Extracts the file name from a file path + * @param {string} str + * @returns {string} + */ + function fileNameForPath(str) { + if (typeof str !== "string") return ""; + const trimmed = trimFileProtocol(str); + return trimmed.split(/[\\/]/).pop(); + } + + /** + * Extracts the folder name from a directory path + * @param {string} str + * @returns {string} + */ + function folderNameForPath(str) { + if (typeof str !== "string") return ""; + const trimmed = trimFileProtocol(str); + // Remove trailing slash if present + const noTrailing = trimmed.endsWith("/") ? trimmed.slice(0, -1) : trimmed; + if (!noTrailing) return ""; + return noTrailing.split(/[\\/]/).pop(); + } + + /** + * Removes the file extension from a file path or name + * @param {string} str + * @returns {string} + */ + function trimFileExt(str) { + if (typeof str !== "string") return ""; + const trimmed = trimFileProtocol(str); + const lastDot = trimmed.lastIndexOf("."); + if (lastDot > -1 && lastDot > trimmed.lastIndexOf("/")) { + return trimmed.slice(0, lastDot); + } + return trimmed; + } + + /** + * Returns the parent directory of a given file path + * @param {string} str + * @returns {string} + */ + function parentDirectory(str) { + if (typeof str !== "string") return ""; + const trimmed = trimFileProtocol(str); + const parts = trimmed.split(/[\\/]/); + if (parts.length <= 1) return ""; + parts.pop(); + return parts.join("/"); + } +} diff --git a/config/quickshell/utils/MaterialSymbols.qml b/config/quickshell/utils/MaterialSymbols.qml new file mode 100644 index 0000000..e69de29 diff --git a/config/quickshell/utils/qmldir b/config/quickshell/utils/qmldir new file mode 100644 index 0000000..771c05c --- /dev/null +++ b/config/quickshell/utils/qmldir @@ -0,0 +1 @@ +singleton FileUtils 1.0 FileUtils.qml diff --git a/quickshell-test.sh b/quickshell-test.sh new file mode 100755 index 0000000..aeaa0dd --- /dev/null +++ b/quickshell-test.sh @@ -0,0 +1,4 @@ +#!/bin/sh + +mkdir ~/.config/quickshell | true +cp -r ./config/quickshell/* ~/.config/quickshell/