From bd17b76c30fecf66a0418ff9a7422c41002ef366 Mon Sep 17 00:00:00 2001 From: Michael Beck Date: Mon, 4 May 2026 15:32:47 +0200 Subject: [PATCH] fixes waybar, adds quickshell --- .config/hypr/config/autostart.conf | 2 +- .config/hypr/config/monitor.conf | 4 +- .config/quickshell/Bar.qml | 60 +++ .config/quickshell/Exec.qml | 23 + .config/quickshell/Pill.qml | 39 ++ .config/quickshell/Theme.qml | 38 ++ .config/quickshell/modules/AudioWidget.qml | 48 ++ .config/quickshell/modules/BatteryWidget.qml | 56 +++ .../quickshell/modules/BluetoothWidget.qml | 61 +++ .config/quickshell/modules/CavaWidget.qml | 91 ++++ .config/quickshell/modules/ClockWidget.qml | 30 ++ .config/quickshell/modules/CpuWidget.qml | 55 +++ .config/quickshell/modules/MediaWidget.qml | 54 +++ .config/quickshell/modules/MemoryWidget.qml | 43 ++ .../quickshell/modules/PowerMenuWidget.qml | 83 ++++ .../modules/PowerProfilesWidget.qml | 59 +++ .config/quickshell/modules/SysTrayWidget.qml | 63 +++ .../quickshell/modules/TemperatureWidget.qml | 43 ++ .config/quickshell/modules/WeatherWidget.qml | 34 ++ .../quickshell/modules/WindowTitleWidget.qml | 28 ++ .../quickshell/modules/WorkspacesWidget.qml | 70 +++ .config/quickshell/shell.qml | 14 + .config/waybar/colors-wallust.css | 19 - .config/waybar/config.jsonc | 445 ------------------ .config/waybar/mediaplayer.py | 127 ----- .config/waybar/modules/mail.py | 42 -- .config/waybar/modules/power_menu.xml | 35 -- .config/waybar/modules/power_profiles.py | 150 ------ .../waybar/modules/power_profiles_menu.xml | 20 - .config/waybar/modules/spotify.sh | 18 - .config/waybar/modules/storage.sh | 24 - .config/waybar/modules/weather.sh | 79 ---- .config/waybar/style.css | 245 ---------- .config/waybar/waybar.sh | 10 - 34 files changed, 995 insertions(+), 1217 deletions(-) create mode 100644 .config/quickshell/Bar.qml create mode 100644 .config/quickshell/Exec.qml create mode 100644 .config/quickshell/Pill.qml create mode 100644 .config/quickshell/Theme.qml create mode 100644 .config/quickshell/modules/AudioWidget.qml create mode 100644 .config/quickshell/modules/BatteryWidget.qml create mode 100644 .config/quickshell/modules/BluetoothWidget.qml create mode 100644 .config/quickshell/modules/CavaWidget.qml create mode 100644 .config/quickshell/modules/ClockWidget.qml create mode 100644 .config/quickshell/modules/CpuWidget.qml create mode 100644 .config/quickshell/modules/MediaWidget.qml create mode 100644 .config/quickshell/modules/MemoryWidget.qml create mode 100644 .config/quickshell/modules/PowerMenuWidget.qml create mode 100644 .config/quickshell/modules/PowerProfilesWidget.qml create mode 100644 .config/quickshell/modules/SysTrayWidget.qml create mode 100644 .config/quickshell/modules/TemperatureWidget.qml create mode 100644 .config/quickshell/modules/WeatherWidget.qml create mode 100644 .config/quickshell/modules/WindowTitleWidget.qml create mode 100644 .config/quickshell/modules/WorkspacesWidget.qml create mode 100644 .config/quickshell/shell.qml delete mode 100644 .config/waybar/colors-wallust.css delete mode 100644 .config/waybar/config.jsonc delete mode 100755 .config/waybar/mediaplayer.py delete mode 100755 .config/waybar/modules/mail.py delete mode 100644 .config/waybar/modules/power_menu.xml delete mode 100755 .config/waybar/modules/power_profiles.py delete mode 100644 .config/waybar/modules/power_profiles_menu.xml delete mode 100755 .config/waybar/modules/spotify.sh delete mode 100755 .config/waybar/modules/storage.sh delete mode 100755 .config/waybar/modules/weather.sh delete mode 100644 .config/waybar/style.css delete mode 100755 .config/waybar/waybar.sh diff --git a/.config/hypr/config/autostart.conf b/.config/hypr/config/autostart.conf index c107eb0..63d7bba 100644 --- a/.config/hypr/config/autostart.conf +++ b/.config/hypr/config/autostart.conf @@ -5,7 +5,7 @@ source = ~/.config/hypr/config/defaults.conf # Autostart wiki https://wiki.hyprland.org/0.45.0/Configuring/Keywords/#executing # -exec-once = /home/michaelb/.config/hypr/scripts/launch-waybar.sh & +exec-once = qs exec-once = blueman-applet & exec-once = fcitx5 -d & exec-once = mako & diff --git a/.config/hypr/config/monitor.conf b/.config/hypr/config/monitor.conf index 87dab07..ee68e57 100644 --- a/.config/hypr/config/monitor.conf +++ b/.config/hypr/config/monitor.conf @@ -7,8 +7,8 @@ monitor = eDP-2, highres@highrr, 0x0, 1, vrr, 1 #monitor = HDMI-A-1, highres@highrr, auto-right, 1, transform, 1 -monitor = desc:Samsung Electric Company S24F350 H4ZR302705, highres@highrr, auto-right, 1 -monitor = desc:Samsung Electric Company S24F350 H4ZK111233, highres@highrr, auto-right, 1, transform, 1 +#monitor = desc:Samsung Electric Company S24F350 H4ZR302705, highres@highrr, auto-right, 1 +monitor = desc:Samsung Electric Company S24F350 H4ZK111233, highres@highrr, 1920x0, 1#, transform, 1 # monitor = HDMI-A-1, highres@highrr, auto-right, 1, vrr, 0 diff --git a/.config/quickshell/Bar.qml b/.config/quickshell/Bar.qml new file mode 100644 index 0000000..9efc733 --- /dev/null +++ b/.config/quickshell/Bar.qml @@ -0,0 +1,60 @@ +// Bar.qml - top panel +// Neighbouring types (Pill, ClockWidget, Theme, etc.) are auto-imported by QuickShell. +import Quickshell +import Quickshell.Wayland +import QtQuick +import QtQuick.Layouts +import "./modules" + +PanelWindow { + id: root + + WlrLayershell.namespace: "quickshell-bar" + WlrLayershell.layer: WlrLayer.Top + WlrLayershell.keyboardFocus: WlrKeyboardFocus.None + + anchors { + top: true + left: true + right: true + } + + margins { + left: 2 + right: 2 + bottom: 1 + top: 3 + } + + implicitHeight: Theme.barHeight + exclusiveZone: Theme.barHeight + color: "transparent" + + RowLayout { + anchors.fill: parent + anchors.leftMargin: 4 + anchors.rightMargin: 4 + spacing: Theme.spacing + + // ─── LEFT ────────────────────────────────────────── + ClockWidget {} + WeatherWidget {} + SysTrayWidget {} + WorkspacesWidget { screen: root.screen } + MediaWidget {} + WindowTitleWidget { screen: root.screen } + + Item { Layout.fillWidth: true } + + // ─── RIGHT ───────────────────────────────────────── + CavaWidget {} + AudioWidget {} + MemoryWidget {} + CpuWidget {} + TemperatureWidget {} + BatteryWidget {} + BluetoothWidget {} + PowerProfilesWidget {} + PowerMenuWidget {} + } +} diff --git a/.config/quickshell/Exec.qml b/.config/quickshell/Exec.qml new file mode 100644 index 0000000..7f8b270 --- /dev/null +++ b/.config/quickshell/Exec.qml @@ -0,0 +1,23 @@ +// Exec.qml - fire-and-forget process launcher +// Usage from any file: Exec.run(["kitty", "-e", "btop"]) +pragma Singleton +import Quickshell +import Quickshell.Io +import QtQuick + +Singleton { + id: root + + function run(cmd) { + const proc = procPool.createObject(root, { command: cmd }); + proc.running = true; + } + + Component { + id: procPool + Process { + running: false + onExited: destroy() + } + } +} diff --git a/.config/quickshell/Pill.qml b/.config/quickshell/Pill.qml new file mode 100644 index 0000000..4432cf3 --- /dev/null +++ b/.config/quickshell/Pill.qml @@ -0,0 +1,39 @@ +// Pill.qml - styled module container +import QtQuick +import QtQuick.Layouts + +Rectangle { + id: root + + default property alias content: inner.data + + property bool hovered: mouseArea.containsMouse + signal clicked(var mouse) + signal scrolled(var wheel) + + property real leftPadding: Theme.pillPadH + property real rightPadding: Theme.pillPadH + implicitWidth: inner.implicitWidth + leftPadding + rightPadding + implicitHeight: Theme.barHeight + radius: Theme.radius + color: hovered ? Theme.pillHover : Theme.pill + + Behavior on color { ColorAnimation { duration: 150 } } + + RowLayout { + id: inner + anchors.verticalCenter: parent.verticalCenter + anchors.left: parent.left + anchors.leftMargin: root.leftPadding + spacing: 4 + } + + MouseArea { + id: mouseArea + anchors.fill: parent + hoverEnabled: true + acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton + onClicked: (m) => root.clicked(m) + onWheel: (w) => root.scrolled(w) + } +} diff --git a/.config/quickshell/Theme.qml b/.config/quickshell/Theme.qml new file mode 100644 index 0000000..faebb46 --- /dev/null +++ b/.config/quickshell/Theme.qml @@ -0,0 +1,38 @@ +// Theme.qml - global palette & dimensions +// QuickShell auto-discovers this; access from any file as `Theme.colorN` etc. +pragma Singleton +import Quickshell +import QtQuick + +Singleton { + // ── Wallust palette ────────────────────────────────────── + readonly property color background: "#252425" + readonly property color foreground: "#F9F1D9" + readonly property color color0: "#505051" + readonly property color color1: "#9C604E" + readonly property color color2: "#807A52" + readonly property color color3: "#908BAB" + readonly property color color4: "#B7815F" + readonly property color color5: "#B9BECA" + readonly property color color6: "#EED793" + readonly property color color7: "#EEE3C1" + readonly property color color8: "#A79F87" + + // ── Derived / semantic ──────────────────────────────────── + readonly property color pill: Qt.rgba(0.976, 0.945, 0.851, 0.15) + readonly property color pillHover: color2 + readonly property color wsActive: color3 + readonly property color wsUrgent: color1 + + // ── Typography ──────────────────────────────────────────── + readonly property string fontSans: "Fira Sans Condensed" + readonly property string fontMono: "FiraCode Nerd Font" + readonly property int fontSize: 12 + + // ── Bar geometry ───────────────────────────────────────── + readonly property int barHeight: 28 + readonly property int barPadding: 2 + readonly property int radius: 5 + readonly property int pillPadH: 10 + readonly property int spacing: 4 +} diff --git a/.config/quickshell/modules/AudioWidget.qml b/.config/quickshell/modules/AudioWidget.qml new file mode 100644 index 0000000..1c50431 --- /dev/null +++ b/.config/quickshell/modules/AudioWidget.qml @@ -0,0 +1,48 @@ +// modules/AudioWidget.qml - wireplumber volume, matches waybar style. +// Icon set mirrors waybar wireplumber format-icons (NerdFont). +import QtQuick +import QtQuick.Layouts +import Quickshell.Services.Pipewire +import ".." + +Pill { + id: root + + property var node: Pipewire.defaultAudioSink + property bool muted: node?.audio.muted ?? false + property real vol: node?.audio.volume ?? 0 + + property string icon: { + if (muted || vol === 0) return " " + if (vol < 0.34) return " " + if (vol < 0.67) return " " + return " " + } + + onClicked: (m) => { + if (m.button === Qt.LeftButton) Exec.run(["pavucontrol"]) + if (m.button === Qt.RightButton && node) + node.audio.muted = !node.audio.muted + } + + onScrolled: (w) => { + if (!node) return + var delta = w.angleDelta.y > 0 ? 0.04 : -0.04 + node.audio.volume = Math.max(0, Math.min(1.5, node.audio.volume + delta)) + } + + // Cava feeds into this on the left → right border only + leftPadding: 0 + rightPadding: Theme.pillPadH + + Text { + text: root.icon + font { family: Theme.fontMono; pixelSize: Theme.fontSize; } + color: "#fab387" // peach accent matching waybar foreground color for icon + } + Text { + text: root.muted ? "muted" : Math.round(root.vol * 100) + "%" + font { family: Theme.fontSans; pixelSize: Theme.fontSize } + color: Theme.foreground + } +} diff --git a/.config/quickshell/modules/BatteryWidget.qml b/.config/quickshell/modules/BatteryWidget.qml new file mode 100644 index 0000000..df9695e --- /dev/null +++ b/.config/quickshell/modules/BatteryWidget.qml @@ -0,0 +1,56 @@ +// BatteryWidget.qml - battery icon + percentage +import QtQuick +import Quickshell.Io +import ".." + +Pill { + id: root + property int capacity: 100 + property string status: "Unknown" + property bool charging: status === "Charging" + property bool plugged: status === "Full" || status === "Not charging" + property bool critical: capacity <= 15 && !charging + + property string icon: { + if (charging) return " " + if (plugged) return " " + if (capacity > 80) return "" + if (capacity > 60) return "" + if (capacity > 40) return "" + if (capacity > 20) return "" + return "" + } + + Text { + text: root.icon + " " + root.capacity + "%" + font.family: Theme.fontSans + font.pixelSize: Theme.fontSize + color: root.critical ? Theme.color1 : Theme.foreground + } + + Process { + id: batProc + running: false + command: ["bash", "-c", + "cat /sys/class/power_supply/BAT0/capacity 2>/dev/null; " + + "echo ---; " + + "cat /sys/class/power_supply/BAT0/status 2>/dev/null"] + stdout: StdioCollector { + onStreamFinished: { + const parts = this.text.split("---") + if (parts.length >= 2) { + const cap = parseInt(parts[0].trim()) + if (!isNaN(cap)) root.capacity = cap + root.status = parts[1].trim() + } + } + } + onExited: batProc.running = false + } + + Timer { + interval: 5000; running: true; repeat: true + triggeredOnStart: true + onTriggered: batProc.running = true + } +} diff --git a/.config/quickshell/modules/BluetoothWidget.qml b/.config/quickshell/modules/BluetoothWidget.qml new file mode 100644 index 0000000..e980844 --- /dev/null +++ b/.config/quickshell/modules/BluetoothWidget.qml @@ -0,0 +1,61 @@ +// modules/BluetoothWidget.qml - ᛒ status / device alias +// Uses bluetoothctl via a polled Process (no BlueZ QML bindings in QS yet). +import QtQuick +import Quickshell.Io +import ".." + +Pill { + id: root + property string btStatus: "off" // "off" | "on" | "connected" + property string devAlias: "" + + readonly property var bgColors: ({ + "off": Qt.rgba(0.565, 0.545, 0.671, 0.3), // alpha(@color3, 0.3) + "on": Theme.color2, + "connected": Theme.color4, + }) + // Override Pill's own color binding + color: bgColors[btStatus] ?? Theme.pill + + onClicked: (m) => { + if (m.button === Qt.LeftButton) + Exec.run(["blueman-manager"]) + } + + Text { + text: { + var s = root.btStatus + if (s === "connected") return "ᛒ " + (root.devAlias || "connected") + if (s === "on") return "ᛒ on" + return "ᛒ off" + } + font { family: Theme.fontSans; pixelSize: Theme.fontSize } + color: Theme.foreground + } + + // Poll bluetoothctl show + info every 5 s + Process { + id: btProc + running: false + command: ["bash", "-c", + "bluetoothctl show | grep -E 'Powered|Name'; " + + "bluetoothctl info 2>/dev/null | grep -E 'Name|Connected'"] + stdout: SplitParser { + onRead: (line) => { + if (/Powered:\s+no/i.test(line)) { root.btStatus = "off"; root.devAlias = "" } + if (/Powered:\s+yes/i.test(line)) { if (root.btStatus === "off") root.btStatus = "on" } + if (/Connected:\s+yes/i.test(line)) root.btStatus = "connected" + if (/Connected:\s+no/i.test(line)) { if (root.btStatus === "connected") { root.btStatus = "on"; root.devAlias = "" } } + var match = /^\s+Name:\s+(.+)/.exec(line) + if (match && root.btStatus === "connected") root.devAlias = match[1].trim() + } + } + onExited: btProc.running = false + } + + Timer { + interval: 5000; running: true; repeat: true + triggeredOnStart: true + onTriggered: btProc.running = true + } +} diff --git a/.config/quickshell/modules/CavaWidget.qml b/.config/quickshell/modules/CavaWidget.qml new file mode 100644 index 0000000..eb70f47 --- /dev/null +++ b/.config/quickshell/modules/CavaWidget.qml @@ -0,0 +1,91 @@ +// CavaWidget.qml - audio visualiser via cava raw output +import QtQuick +import QtQuick.Layouts +import Quickshell.Io +import ".." + +Rectangle { + id: root + + property var bars: Array(12).fill(0) + property bool silence: bars.every(v => v === 0) + + readonly property var blocks: [" ","▁","▂","▃","▄","▅","▆","▇","█"] + + implicitWidth: cavaRow.implicitWidth + Theme.pillPadH * 2 + implicitHeight: Theme.barHeight + radius: Theme.radius + color: cavaHover.containsMouse ? Theme.pillHover : Theme.pill + Behavior on color { ColorAnimation { duration: 150 } } + + RowLayout { + id: cavaRow + anchors.centerIn: parent + spacing: 1 + + Repeater { + model: root.bars.length + Text { + required property int index + text: root.silence + ? " " + : root.blocks[Math.min(Math.floor(root.bars[index] / 28.5), 8)] + font.family: Theme.fontMono + font.pixelSize: Theme.fontSize + 1 + color: Theme.foreground + } + } + } + + MouseArea { + id: cavaHover + anchors.fill: parent + hoverEnabled: true + onClicked: Exec.run(["pavucontrol"]) + } + + // Write the cava config once at startup, then run cava pointing at it. + Component.onCompleted: writeCfg.running = true + + Process { + id: writeCfg + running: false + // Plain double-quoted string - no JS interpolation, bash sees ${VAR} verbatim. + command: ["bash", "-c", + "mkdir -p /tmp/qs-cava && cat > /tmp/qs-cava/cava.ini <<'CFG'\n" + + "[general]\n" + + "framerate = 30\n" + + "bars = 12\n" + + "[input]\n" + + "method = pipewire\n" + + "source = auto\n" + + "[smoothing]\n" + + "noise_reduction = 77\n" + + "monstercat = 1\n" + + "[output]\n" + + "method = raw\n" + + "raw_target = /dev/stdout\n" + + "data_format = ascii\n" + + "ascii_max_range = 255\n" + + "bar_delimiter = 59\n" + + "CFG\n" + ] + onExited: cavaProc.running = true + } + + Process { + id: cavaProc + running: false + command: ["cava", "-p", "/tmp/qs-cava/cava.ini"] + stdout: SplitParser { + onRead: (line) => { + const parts = line.trim().replace(/;$/, "").split(";"); + if (parts.length >= root.bars.length) { + root.bars = parts.slice(0, root.bars.length) + .map(v => parseInt(v) || 0); + } + } + } + onExited: cavaProc.running = true + } +} diff --git a/.config/quickshell/modules/ClockWidget.qml b/.config/quickshell/modules/ClockWidget.qml new file mode 100644 index 0000000..8f6f227 --- /dev/null +++ b/.config/quickshell/modules/ClockWidget.qml @@ -0,0 +1,30 @@ +// modules/ClockWidget.qml - " HH:MM DD Mon" (matches waybar clock format) +import QtQuick +import QtQuick.Layouts +import Quickshell.Io +import ".." + +Pill { + onClicked: (m) => { + if (m.button === Qt.LeftButton) + Exec.run(["kitty", "-e", "calcure", "--class=float", "-T", "calcure"]) + } + + Text { + text: " " + Qt.formatDateTime(clock.now, "HH:mm") + + " " + Qt.formatDateTime(clock.now, "d MMM") + font { family: Theme.fontSans; pixelSize: Theme.fontSize } + color: Theme.foreground + } + + // Update every 10 s (no need for per-second ticks) + QtObject { + id: clock + property var now: new Date() + property var timer: Timer { + interval: 10000; running: true; repeat: true + triggeredOnStart: true + onTriggered: clock.now = new Date() + } + } +} diff --git a/.config/quickshell/modules/CpuWidget.qml b/.config/quickshell/modules/CpuWidget.qml new file mode 100644 index 0000000..d96c714 --- /dev/null +++ b/.config/quickshell/modules/CpuWidget.qml @@ -0,0 +1,55 @@ +// CpuWidget.qml - "X.XGHz | Y%" +import QtQuick +import Quickshell.Io +import ".." + +Pill { + id: root + property real freqGhz: 0 + property int usagePct: 0 + + property int prevIdle: 0 + property int prevTotal: 0 + + onClicked: (m) => { + if (m.button === Qt.LeftButton) Exec.run(["kitty", "-e", "btop"]) + } + + Text { + text: root.freqGhz.toFixed(1) + "GHz | " + root.usagePct + "%" + font.family: Theme.fontSans + font.pixelSize: Theme.fontSize + color: Theme.foreground + } + + // /proc/stat - first line is total CPU + Process { + id: statProc + running: false + command: ["bash", "-c", "head -1 /proc/stat && cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_cur_freq 2>/dev/null"] + stdout: StdioCollector { + onStreamFinished: { + const lines = this.text.split("\n") + // line 0: cpu user nice system idle iowait irq softirq steal + const nums = lines[0].replace(/^cpu\s+/, "").split(/\s+/).map(Number) + const idle = (nums[3] || 0) + (nums[4] || 0) + const total = nums.reduce((s, v) => s + v, 0) + const dIdle = idle - root.prevIdle + const dTotal = total - root.prevTotal + if (dTotal > 0) root.usagePct = Math.round((1 - dIdle / dTotal) * 100) + root.prevIdle = idle + root.prevTotal = total + // line 1: current frequency in kHz + const khz = parseInt(lines[1] || "0") + if (!isNaN(khz) && khz > 0) root.freqGhz = khz / 1e6 + } + } + onExited: statProc.running = false + } + + Timer { + interval: 1500; running: true; repeat: true + triggeredOnStart: true + onTriggered: statProc.running = true + } +} diff --git a/.config/quickshell/modules/MediaWidget.qml b/.config/quickshell/modules/MediaWidget.qml new file mode 100644 index 0000000..41d5f42 --- /dev/null +++ b/.config/quickshell/modules/MediaWidget.qml @@ -0,0 +1,54 @@ +// modules/MediaWidget.qml +// Mirrors waybar custom/spotify - uses MPRIS via Quickshell.Services.Mpris. +// Shows: artist - title + spotify icon. Click to play/pause, scroll to skip. +import QtQuick +import Quickshell.Services.Mpris +import ".." + +Pill { + id: root + + // Pick the first active player (prefer spotify) + property MprisPlayer activePlayer: { + var players = Mpris.players.values + for (var i = 0; i < players.length; i++) + if (players[i].identity.toLowerCase() === "spotify") return players[i] + return players.length > 0 ? players[0] : null + } + + property string trackText: { + if (!activePlayer) return "" + var p = activePlayer + var info = "" + if (p.trackArtists && p.trackTitle) + info = p.trackArtists.join(", ") + " - " + p.trackTitle + else if (p.trackTitle) + info = p.trackTitle + if (info.length > 45) info = info.substring(0, 45) + "..." + if (p.playbackState !== MprisPlaybackState.Playing && info) + info = " " + info + return info + " " // trailing Nerd Font Spotify icon + } + + visible: trackText !== "" + + Text { + text: root.trackText + font { family: Theme.fontSans; pixelSize: Theme.fontSize } + color: Theme.foreground + elide: Text.ElideRight + maximumLineCount: 1 + } + + onClicked: (m) => { + if (!root.activePlayer) return + if (m.button === Qt.LeftButton) + root.activePlayer.togglePlaying() + } + + onScrolled: (w) => { + if (!root.activePlayer) return + if (w.angleDelta.y > 0) root.activePlayer.next() + else root.activePlayer.previous() + } +} diff --git a/.config/quickshell/modules/MemoryWidget.qml b/.config/quickshell/modules/MemoryWidget.qml new file mode 100644 index 0000000..560d945 --- /dev/null +++ b/.config/quickshell/modules/MemoryWidget.qml @@ -0,0 +1,43 @@ +// MemoryWidget.qml - " X.XX / Y GB" +import QtQuick +import Quickshell.Io +import ".." + +Pill { + id: root + property real usedGb: 0 + property real totalGb: 0 + + onClicked: (m) => { + if (m.button === Qt.LeftButton) Exec.run(["kitty", "-e", "btop"]) + } + + Text { + text: " " + root.usedGb.toFixed(2) + " / " + root.totalGb.toFixed(0) + " GB" + font.family: Theme.fontSans + font.pixelSize: Theme.fontSize + color: Theme.foreground + } + + Process { + id: memProc + running: false + command: ["cat", "/proc/meminfo"] + stdout: StdioCollector { + onStreamFinished: { + const text = this.text + const total = parseInt((/MemTotal:\s+(\d+)/.exec(text) || [])[1] || "0") + const avail = parseInt((/MemAvailable:\s+(\d+)/.exec(text) || [])[1] || "0") + root.totalGb = total / 1048576 + root.usedGb = (total - avail) / 1048576 + } + } + onExited: memProc.running = false + } + + Timer { + interval: 5000; running: true; repeat: true + triggeredOnStart: true + onTriggered: memProc.running = true + } +} diff --git a/.config/quickshell/modules/PowerMenuWidget.qml b/.config/quickshell/modules/PowerMenuWidget.qml new file mode 100644 index 0000000..a7eb7f4 --- /dev/null +++ b/.config/quickshell/modules/PowerMenuWidget.qml @@ -0,0 +1,83 @@ +// modules/PowerMenuWidget.qml - ⏻ button with inline popup menu. +// Matches waybar custom/power with menu-actions. +import QtQuick +import QtQuick.Layouts +import Quickshell.Io +import ".." + +Pill { + id: root + onClicked: (m) => { + if (m.button === Qt.LeftButton) menu.visible = !menu.visible + } + + Text { + text: "⏻ " + font { family: Theme.fontSans; pixelSize: Theme.fontSize } + color: Theme.foreground + } + + // Inline drop-up popup - appears above the bar + Rectangle { + id: menu + visible: false + z: 100 + + width: 130 + height: menuCol.implicitHeight + 16 + radius: 5 + color: Qt.rgba(0.086, 0.075, 0.125, 0.85) + border.color: Qt.rgba(1, 1, 1, 0.06) + border.width: 1 + + // Anchor above the pill + parent: root.parent // reparent to bar so z-ordering works + x: root.x + root.width - width + y: root.y - height - 4 + + ColumnLayout { + id: menuCol + anchors { fill: parent; margins: 8 } + spacing: 2 + + Repeater { + model: [ + { label: "Suspend", cmd: ["systemctl", "suspend"] }, + { label: "Hibernate", cmd: ["systemctl", "hibernate"] }, + { label: "Logout", cmd: ["hyprctl", "dispatch", "exit"] }, + { label: "Reboot", cmd: ["reboot"] }, + { label: "Shutdown", cmd: ["shutdown", "now"] }, + ] + + delegate: Rectangle { + required property var modelData + Layout.fillWidth: true + height: 26 + radius: 5 + color: itemHover.containsMouse + ? Theme.pillHover + : "transparent" + Behavior on color { ColorAnimation { duration: 100 } } + + Text { + anchors { left: parent.left; verticalCenter: parent.verticalCenter; leftMargin: 8 } + text: modelData.label + font { family: Theme.fontSans; pixelSize: Theme.fontSize } + color: Theme.foreground + } + + MouseArea { + id: itemHover + anchors.fill: parent + hoverEnabled: true + onClicked: { + menu.visible = false + Exec.run(modelData.cmd) + } + } + } + } + } + + } +} diff --git a/.config/quickshell/modules/PowerProfilesWidget.qml b/.config/quickshell/modules/PowerProfilesWidget.qml new file mode 100644 index 0000000..1264606 --- /dev/null +++ b/.config/quickshell/modules/PowerProfilesWidget.qml @@ -0,0 +1,59 @@ +// PowerProfilesWidget.qml - ⚡/⚖/🔋 + click-to-cycle +import QtQuick +import Quickshell.Io +import ".." + +Pill { + id: root + property string profile: "balanced" + + readonly property var profileOrder: ["performance", "balanced", "power-saver"] + readonly property var icons: ({ + "performance": "⚡", + "balanced": "⚖", + "power-saver": "🔋", + }) + + onClicked: (m) => { + if (m.button !== Qt.LeftButton) return; + const i = profileOrder.indexOf(root.profile); + const next = profileOrder[(i + 1) % profileOrder.length]; + setProc.command = ["powerprofilesctl", "set", next]; + setProc.running = true; + root.profile = next; // optimistic update + } + + Text { + text: (root.icons[root.profile] ?? "⚡") + font.family: Theme.fontSans + font.pixelSize: Theme.fontSize + color: Theme.foreground + } + + // Read current profile periodically + Process { + id: readProc + running: false + command: ["powerprofilesctl", "get"] + stdout: SplitParser { + onRead: (line) => root.profile = line.trim() + } + onExited: readProc.running = false + } + + // Setter - command is rewritten on each click + Process { + id: setProc + running: false + command: ["true"] + onExited: setProc.running = false + } + + Timer { + interval: 2000 + running: true + repeat: true + triggeredOnStart: true + onTriggered: readProc.running = true + } +} diff --git a/.config/quickshell/modules/SysTrayWidget.qml b/.config/quickshell/modules/SysTrayWidget.qml new file mode 100644 index 0000000..083f218 --- /dev/null +++ b/.config/quickshell/modules/SysTrayWidget.qml @@ -0,0 +1,63 @@ +// modules/SysTrayWidget.qml - SNI system tray (nm-applet, blueman ...) +import QtQuick +import QtQuick.Layouts +import Quickshell.Services.SystemTray +import ".." + +Rectangle { + id: root + color: "transparent" + radius: Theme.radius + + implicitWidth: trayRow.implicitWidth + 6 + implicitHeight: Theme.barHeight + + RowLayout { + id: trayRow + anchors.centerIn: parent + spacing: Theme.spacing + + Repeater { + model: SystemTray.items + + Rectangle { + required property SystemTrayItem modelData + width: 22; height: 22 + radius: Theme.radius + color: trayHover.containsMouse + ? Theme.pillHover + : Qt.rgba(0.976, 0.945, 0.851, 0.15) + + Behavior on color { ColorAnimation { duration: 150 } } + + Image { + anchors { fill: parent; margins: 4 } + source: modelData.icon + fillMode: Image.PreserveAspectFit + smooth: true + } + + MouseArea { + id: trayHover + anchors.fill: parent + hoverEnabled: true + acceptedButtons: Qt.LeftButton | Qt.RightButton + onClicked: (m) => { + if (m.button === Qt.LeftButton) + modelData.activate() + else + modelData.contextMenu(mapToGlobal(mouseX, mouseY)) + } + } + + // Attention indicator dot + Rectangle { + visible: modelData.status === SystemTrayItem.NeedsAttention + width: 5; height: 5; radius: 2.5 + color: Theme.wsUrgent + anchors { bottom: parent.bottom; right: parent.right; margins: 1 } + } + } + } + } +} diff --git a/.config/quickshell/modules/TemperatureWidget.qml b/.config/quickshell/modules/TemperatureWidget.qml new file mode 100644 index 0000000..9df2ce0 --- /dev/null +++ b/.config/quickshell/modules/TemperatureWidget.qml @@ -0,0 +1,43 @@ +// TemperatureWidget.qml - CPU package temperature +import QtQuick +import Quickshell.Io +import ".." + +Pill { + id: root + property int tempC: 0 + property bool critical: tempC >= 80 + + property string icon: tempC < 50 ? "" : tempC < 70 ? "" : "" + + onClicked: (m) => { + if (m.button === Qt.LeftButton) Exec.run(["xsensors"]) + } + + Text { + text: root.icon + " " + root.tempC + "°C" + font.family: Theme.fontSans + font.pixelSize: Theme.fontSize + color: root.critical ? Theme.color1 : Theme.foreground + } + + // Read first available CPU package sensor - works regardless of hwmon number + Process { + id: tempProc + running: false + command: ["bash", "-c", "cat /sys/class/hwmon/hwmon*/temp1_input 2>/dev/null | head -1"] + stdout: SplitParser { + onRead: (line) => { + const raw = parseInt(line.trim()) + if (!isNaN(raw)) root.tempC = Math.round(raw / 1000) + } + } + onExited: tempProc.running = false + } + + Timer { + interval: 4000; running: true; repeat: true + triggeredOnStart: true + onTriggered: tempProc.running = true + } +} diff --git a/.config/quickshell/modules/WeatherWidget.qml b/.config/quickshell/modules/WeatherWidget.qml new file mode 100644 index 0000000..433569a --- /dev/null +++ b/.config/quickshell/modules/WeatherWidget.qml @@ -0,0 +1,34 @@ +// modules/WeatherWidget.qml - wttr.in one-liner, refreshed hourly +import QtQuick +import Quickshell.Io +import ".." + +Pill { + id: root + property string weatherText: "..." + + Text { + text: weatherText + font { family: Theme.fontSans; pixelSize: Theme.fontSize } + color: Theme.foreground + } + + // ── Fetch via curl ──────────────────────────────────────── + Process { + id: curl + command: ["curl", "-s", "--max-time", "8", "https://wttr.in/?format=1"] + running: false + stdout: SplitParser { + onRead: (line) => root.weatherText = line.trim() + } + onExited: curl.running = false + } + + Timer { + interval: 3600000 // 1 hour + running: true + repeat: true + triggeredOnStart: true + onTriggered: curl.running = true + } +} diff --git a/.config/quickshell/modules/WindowTitleWidget.qml b/.config/quickshell/modules/WindowTitleWidget.qml new file mode 100644 index 0000000..15818de --- /dev/null +++ b/.config/quickshell/modules/WindowTitleWidget.qml @@ -0,0 +1,28 @@ +// modules/WindowTitleWidget.qml - hyprland/window equivalent +import QtQuick +import Quickshell.Hyprland +import ".." + +Pill { + id: root + required property var screen + + property string title: { + var ws = Hyprland.focusedWorkspace + if (!ws) return "" + var win = ws.lastWindow + if (!win) return "" + var t = win.title ?? "" + return t.length > 60 ? t.substring(0, 60) + "..." : t + } + + visible: title !== "" + + Text { + text: root.title + font { family: Theme.fontSans; pixelSize: Theme.fontSize } + color: Theme.foreground + elide: Text.ElideRight + maximumLineCount: 1 + } +} diff --git a/.config/quickshell/modules/WorkspacesWidget.qml b/.config/quickshell/modules/WorkspacesWidget.qml new file mode 100644 index 0000000..29eb72c --- /dev/null +++ b/.config/quickshell/modules/WorkspacesWidget.qml @@ -0,0 +1,70 @@ +// modules/WorkspacesWidget.qml +// Kanji workspace labels, per-monitor, matches waybar hyprland/workspaces. +import QtQuick +import QtQuick.Layouts +import Quickshell.Hyprland +import ".." + +Rectangle { + id: root + required property var screen + + color: "transparent" + implicitWidth: wsRow.implicitWidth + implicitHeight: Theme.barHeight + + // Filter workspaces that belong to this screen's monitor + property string monitorName: { + for (var i = 0; i < Hyprland.monitors.values.length; i++) { + var m = Hyprland.monitors.values[i] + if (m.name === screen.name) return m.name + } + return "" + } + + RowLayout { + id: wsRow + anchors.verticalCenter: parent.verticalCenter + spacing: Theme.spacing + + Repeater { + model: { + // sort visible workspaces for this monitor + var all = Hyprland.workspaces.values + return all.filter(ws => ws.monitor && ws.monitor.name === root.monitorName) + .sort((a, b) => a.id - b.id) + } + + delegate: Rectangle { + required property var modelData + property bool isActive: modelData.id === (Hyprland.focusedWorkspace?.id ?? -1) + + width: 32 + height: Theme.barHeight - 4 + radius: Theme.radius + Layout.alignment: Qt.AlignVCenter + color: isActive + ? Theme.wsActive + : (wsBtn.containsMouse ? Theme.pillHover : Theme.pill) + + Behavior on color { ColorAnimation { duration: 200 } } + + Text { + anchors.centerIn: parent + text: Math.min(modelData.id - 1, 10 - 1) + font { family: Theme.fontSans; pixelSize: 11 } + color: Theme.foreground + } + + MouseArea { + id: wsBtn + anchors.fill: parent + hoverEnabled: true + onClicked: Hyprland.dispatch("workspace " + modelData.id) + onWheel: (w) => Hyprland.dispatch( + "workspace " + (w.angleDelta.y > 0 ? "e+1" : "e-1")) + } + } + } + } +} diff --git a/.config/quickshell/shell.qml b/.config/quickshell/shell.qml new file mode 100644 index 0000000..5d37a3c --- /dev/null +++ b/.config/quickshell/shell.qml @@ -0,0 +1,14 @@ +// shell.qml - entry point +// QuickShell scans this directory and auto-imports neighbours (Bar, Theme, Exec, ...). +// Do NOT create a qmldir file - QuickShell synthesises one automatically. +import Quickshell + +ShellRoot { + Variants { + model: Quickshell.screens + Bar { + required property var modelData + screen: modelData + } + } +} diff --git a/.config/waybar/colors-wallust.css b/.config/waybar/colors-wallust.css deleted file mode 100644 index 79de5dc..0000000 --- a/.config/waybar/colors-wallust.css +++ /dev/null @@ -1,19 +0,0 @@ -@define-color cursor #AAA27D; -@define-color background #252425; -@define-color foreground #F9F1D9; -@define-color color0 #505051; -@define-color color1 #9C604E; -@define-color color2 #807A52; -@define-color color3 #908BAB; -@define-color color4 #B7815F; -@define-color color5 #B9BECA; -@define-color color6 #EED793; -@define-color color7 #EEE3C1; -@define-color color8 #A79F87; -@define-color color9 #9C604E; -@define-color color10 #807A52; -@define-color color11 #908BAB; -@define-color color12 #B7815F; -@define-color color13 #B9BECA; -@define-color color14 #EED793; -@define-color color15 #EEE3C1; diff --git a/.config/waybar/config.jsonc b/.config/waybar/config.jsonc deleted file mode 100644 index 9cea439..0000000 --- a/.config/waybar/config.jsonc +++ /dev/null @@ -1,445 +0,0 @@ -{ - // ------------------------------------------------------------------------- - // Global configuration - // ------------------------------------------------------------------------- - - "layer": "top", - "swap-icon-label": true, - "position": "top", - - "height": 36, - - "margin-left": 2, - "margin-bottom": 1, - "margin-right": 2, - - "spacing": 1, // Gaps between modules (4px) - - "modules-left": [ - "clock", - "custom/weather", - "tray", - //"custom/rofi", - "hyprland/workspaces", - //"hyprland/submap", - //"idle_inhibitor", - //"mpd", - "custom/spotify", - "hyprland/window", - ], - // "modules-center": [ - // //"custom/gammastep" - // ], - "modules-right": [ - "cava", - "wireplumber", - //"wlr/taskbar", - //"custom/storage", - "memory", - "cpu", - "temperature", - "battery", - - //"pulseaudio", - //"backlight", - "bluetooth", - //"custom/screenshot_t", - "custom/power_profiles", - "custom/power", - ], - - // ------------------------------------------------------------------------- - // Modules - // ------------------------------------------------------------------------- - - "wlr/taskbar": { - "on-click": "activate", - "on-click-middle": "close", - "format": "{icon}", - "icon-size": 12, - "icon-theme": "Numix-Circle", - }, - - "custom/sp1": { - "format": " | ", - "tooltip": false, - }, - "custom/sp2": { - "format": " |", - "tooltip": false, - }, - - "custom/rofi": { - "format": "", - "tooltip": false, - "on-click-right": "nwg-drawer", - "on-click": "wofi --show run", - "on-click-middle": "pkill -9 wofi", - }, - "custom/screenshot_t": { - "format": " ", - "on-click": "~/.config/hypr/scripts/screenshot_full", - "on-click-right": "~/.config/hypr/scripts/screenshot_area", - }, - - "clock#1": { - "format": " {:%a}", - "tooltip": false, - "on-click": "kitty -e calcure --class=\"float\" -T calcure", - }, - "clock#2": { - "format": " {:%d-%h-%Y}", - "tooltip": false, - "on-click": "kitty -e calcure --class=\"float\" -T calcure", - }, - "clock#3": { - "format": " {:%H:%M:%S %p}", - "tooltip": false, - "on-click": "kitty -e calcure --class=\"float\" -T calcure", - }, - - "bluetooth": { - // "controller": "controller1", // specify the alias of the controller if there are more than 1 on the system - "format": "ᛒ {status}", - "format-connected": "ᛒ {device_alias}", - "format-disabled": "ᛒ off", - "tooltip-format": "{controller_alias}\t{controller_address}", - "tooltip-format-connected": "{controller_alias}\t{controller_address}\n\n{device_enumerate}", - "tooltip-format-enumerate-connected": "{device_alias}\t{device_address}", - "on-click": "blueman-manager", - }, - - "temperature": { - // "thermal-zone": 1, - "interval": 4, - //"hwmon-path": "/sys/class/hwmon/hwmon3/temp1_input", - "critical-threshold": 80, - // "format-critical": " {temperatureC}°C", - "format-critical": " {temperatureC}°C", - "format": "{icon} {temperatureC}°C", - "format-icons": ["", "", ""], - "max-length": 7, - "min-length": 7, - "on-click": "xsensors", - }, - - "memory": { - "interval": 30, - "format": " {used:0.2f} / {total:0.0f} GB", - "on-click": "kitty -e btop", - }, - - "battery": { - "interval": 2, - "states": { - "good": 95, - "warning": 30, - "critical": 15, - }, - "format": "{icon} {capacity}%", - "format-charging": " {capacity}%", - "format-plugged": " {capacity}%", - "format-icons": ["", "", "", "", ""], - }, - "network": { - "format-wifi": " {essid} ({signalStrength}%)", - "format-ethernet": "{ifname}: {ipaddr}/{cidr} ", - "format-linked": "{ifname} (No IP) ", - "format": "", - "format-disconnected": "", - "format-alt": "{ifname}: {ipaddr}/{cidr}", - "on-click": "wl-copy $(ip address show up scope global | grep inet | head -n1 | cut -d/ -f 1 | tr -d [:space:] | cut -c5-)", - "on-click-right": "wl-copy $(ip address show up scope global | grep inet6 | head -n1 | cut -d/ -f 1 | tr -d [:space:] | cut -c6-)", - "tooltip-format": " {bandwidthUpBits}  {bandwidthDownBits}\n{ifname}\n{ipaddr}/{cidr}\n", - "tooltip-format-wifi": " {essid} {frequency}MHz\nStrength: {signaldBm}dBm ({signalStrength}%)\nIP: {ipaddr}/{cidr}\n {bandwidthUpBits}  {bandwidthDownBits}", - "interval": 10, - }, - "custom/storage": { - "format": " {}", - "format-alt": "{percentage}% ", - "format-alt-click": "click-right", - "return-type": "json", - "interval": 60, - "exec": "~/.config/waybar/modules/storage.sh", - }, - - "backlight": { - "device": "intel_backlight", - "format": "{icon} {percent}%", - "format-alt": "{percent}% {icon}", - "format-alt-click": "click-right", - //"format-icons": ["", ""], - "format-icons": ["", ""], - "on-scroll-down": "brightnessctl s 5%-", - "on-scroll-up": "brightnessctl s +5%", - }, - "idle_inhibitor": { - "format": "{icon}", - "format-icons": { - "activated": "", - "deactivated": "", - }, - "tooltip": "true", - }, - "custom/weather": { - "format": "{}", - "format-alt": "{alt}: {}", - "format-alt-click": "click-right", - "interval": 3600, - "exec": "curl -s 'https://wttr.in/?format=1'", - //"return-type": "json", - //"exec": "~/.config/waybar/modules/weather.sh", - "exec-if": "ping wttr.in -c1", - }, - "custom/pacman": { - "format": "􏆲 {}", - "interval": 3600, // every hour - "exec": "checkupdates | wc -l", // # of updates - "exec-if": "exit 0", // always run; consider advanced run conditions - "on-click": "alacritty -e 'paru'; pkill -SIGRTMIN+8 waybar", // update system - "signal": 8, - "max-length": 5, - "min-length": 3, - }, - - "custom/spotify": { - "exec": "~/.config/waybar/mediaplayer.py --player spotify", - "format": "{} ", - "return-type": "json", - "on-click": "playerctl play-pause", - "on-scroll-up": "playerctl next", - "on-scroll-down": "playerctl previous", - }, - - "custom/media": { - "format": "{0} {1}", - "return-type": "json", - "max-length": 40, - "format-icons": { - "spotify": "", - "default": "🎜", - }, - "escape": true, - //"exec": "~/.config/waybar/mediaplayer.py" // Script in resources folder - // "exec": "~/.config/waybar/mediaplayer.py --player spotify 2> /dev/null" // Filter player based on name - }, - - "clock": { - "format": " {:%H:%M  %e %b}", - "tooltip-format": "{:%Y %B}\n{calendar}", - "today-format": "{}", - "on-click": "kitty -e calcure --class=\"float\" -T calcure", - }, - - "clock#date": { - "format": "󰥔 {:%H:%M \n %e %b}", - "tooltip-format": "{:%Y %B}\n{calendar}", - "today-format": "{}", - }, - - "custom/gammastep": { - "interval": 5, - "return-type": "json", - "exec": { - "pre": "if unit_status=\"$(systemctl --user is-active gammastep)\"; then\nstatus=\"$unit_status ($(journalctl --user -u gammastep.service -g 'Period: ' | tail -1 | cut -d ':' -f6 | xargs))\"\nelse\nstatus=\"$unit_status\"\nfi", - "alt": "${status:-inactive}", - "tooltip": "Gammastep is $status", - }, - "format": "{icon}", - "format-icons": { - "activating": "󰁪 ", - "deactivating": "󰁪 ", - "inactive": "? ", - "active (Night)": " ", - "active (Nighttime)": " ", - "active (Transition (Night)": " ", - "active (Transition (Nighttime)": " ", - "active (Day)": " ", - "active (Daytime)": " ", - "active (Transition (Day)": " ", - "active (Transition (Daytime)": " ", - }, - "on-click": "systemctl --user is-active gammastep && systemctl --user stop gammastep || systemctl --user start gammastep", - }, - - "cpu": { - "interval": 1, - //"format": " {}%", // Icon: microchip - "format": "{max_frequency}GHz | {usage}%", - "max-length": 13, - "min-length": 13, - }, - - "mpd": { - "max-length": 25, - "format": " {title}", - "format-paused": " {title}", - "format-stopped": "", - "format-disconnected": "", - "on-click": "mpc --quiet toggle", - "on-click-right": "mpc update; mpc ls | mpc add", - "on-click-middle": "alacritty -e ncmpcpp", - "on-scroll-up": "mpc --quiet prev", - "on-scroll-down": "mpc --quiet next", - "smooth-scrolling-threshold": 5, - "tooltip-format": "{title} - {artist} ({elapsedTime:%M:%S}/{totalTime:%H:%M:%S})", - }, - - "custom/title": { - "format": "{}", - "interval": 0, - "return-type": "json", - //"max-length": 35, - "tooltip": false, - }, - - "custom/title#name": { - "format": "{}", - "interval": 0, - "return-type": "json", - - "max-length": 35, - "exec": "$HOME/.scripts/title", - }, - - /*"custom/keyboard": { - "format": " {}", - "interval": 1, - "exec": "$HOME/.config/waybar/get_kbdlayout.sh" - },*/ - - "hyprland/workspaces": { - "all-outputs": false, - "format": "{name}", - "format-icons": { - "1": "一", - "2": "二", - "3": "三", - "4": "四", - "5": "五", - "6": "六", - "7": "七", - "8": "八", - "9": "九", - "10": "十", - }, - "on-scroll-up": "hyprctl dispatch workspace e+1 1>/dev/null", - "on-scroll-down": "hyprctl dispatch workspace e-1 1>/dev/null", - "sort-by-number": true, - "active-only": false, - }, - - "hyprland/window": { - "max-length": 200, - "separate-outputs": true, - }, - - "pulseaudio": { - "scroll-step": 3, // %, can be a float - "format": "{icon} {volume}% {format_source}", - "format-bluetooth": "{volume}% {icon} {format_source}", - "format-bluetooth-muted": " {icon} {format_source}", - "format-muted": " {format_source}", - //"format-source": "{volume}% ", - //"format-source-muted": "", - "format-source": "", - "format-source-muted": "", - "format-icons": { - "headphone": "", - "hands-free": "", - "headset": "", - "phone": "", - "portable": "", - "car": "", - "default": ["", "", ""], - }, - "on-click": "pavucontrol", - "on-click-right": "amixer sset Master toggle", - }, - - "wireplumber": { - "on-click": "pavucontrol", - "on-click-right": "amixer sset Master toggle 1>/dev/null", - //on-click: "${wpctl} set-mute @DEFAULT_AUDIO_SINK@ toggle"; - //on-scroll-down: "${wpctl} set-volume -l 1.0 @DEFAULT_AUDIO_SINK@ 0.04+"; - //on-scroll-up: "${wpctl} set-volume -l 1.0 @DEFAULT_AUDIO_SINK@ 0.04-"; - "format": "{icon} {volume}%", - "format-muted": " ", - "format-source": "", - "format-source-muted": "", - //"format-muted": "", - //"format-icons": [ "" ] - "format-icons": { - "headphone": " ", - "hands-free": " ", - "headset": " ", - "phone": " ", - "portable": " ", - "car": " ", - "default": [" ", " ", " "], - }, - }, - - "tray": { - "icon-size": 11, - "spacing": 5, - "show-passive-items": true, - }, - - "custom/power_profiles": { - "format": "{}", - "return-type": "json", - "interval": 2, - "exec": "~/.config/waybar/modules/power_profiles.py", - "menu": "on-click", - "menu-file": "~/.config/waybar/modules/power_profiles_menu.xml", - "menu-actions": { - "performance": "~/.config/waybar/modules/power_profiles.py --set performance", - "balanced": "~/.config/waybar/modules/power_profiles.py --set balanced", - "power-saver": "~/.config/waybar/modules/power_profiles.py --set power-saver", - }, - "tooltip": true, - }, - - "custom/power": { - "format": "⏻ ", - "tooltip": false, - "menu": "on-click", - "menu-file": "~/.config/waybar/modules/power_menu.xml", - "menu-actions": { - "suspend": "systemctl suspend", - "hibernate": "systemctl hibernate", - "logout": "hyprctl dispatch exit", - "shutdown": "shutdown", - "reboot": "reboot", - }, - }, - - "cava": { - "on-click": "pavucontrol", - // "cava_config": "$XDG_CONFIG_HOME/cava/cava.conf", - "framerate": 30, - "sensitivity": 3, - "autosens": 1, - "bars": 12, - "lower_cutoff_freq": 10, - "higher_cutoff_freq": 10000, - "hide_on_silence": false, - "format_silent": "quiet", - "method": "pipewire", - "source": "auto", - "stereo": false, - "reverse": false, - "bar_delimiter": 0, - "bar_spacing": 1, - "monstercat": true, - "waves": true, - "noise_reduction": 0.77, - "input_delay": 2, - "eq": { "1": 1.8, "2": 1.4, "3": 1.1, "4": 1.0, "5": 1.0 }, - "format-icons": ["", "▁", "▂", "▃", "▄", "▅", "▆", "▇", "█"], - "actions": { - "on-click-right": "mode", - }, - }, -} diff --git a/.config/waybar/mediaplayer.py b/.config/waybar/mediaplayer.py deleted file mode 100755 index 7404f48..0000000 --- a/.config/waybar/mediaplayer.py +++ /dev/null @@ -1,127 +0,0 @@ -#!/usr/bin/env python3 -import argparse -import logging -import sys -import signal -import gi -import json -gi.require_version('Playerctl', '2.0') -from gi.repository import Playerctl, GLib - -logger = logging.getLogger(__name__) - - -def write_output(text, player): - logger.info('Writing output') - - output = {'text': text, - 'class': 'custom-' + player.props.player_name, - 'alt': player.props.player_name} - - sys.stdout.write(json.dumps(output) + '\n') - sys.stdout.flush() - - -def on_play(player, status, manager): - logger.info('Received new playback status') - on_metadata(player, player.props.metadata, manager) - - -def on_metadata(player, metadata, manager): - logger.info('Received new metadata') - track_info = '' - - if player.props.player_name == 'spotify' and \ - 'mpris:trackid' in metadata.keys() and \ - ':ad:' in player.props.metadata['mpris:trackid']: - track_info = 'AD PLAYING' - elif player.get_artist() != '' and player.get_title() != '': - track_info = '{artist} - {title}'.format(artist=player.get_artist(), - title=player.get_title()) - else: - track_info = player.get_title() - - if player.props.status != 'Playing' and track_info: - track_info = ' ' + track_info - write_output(track_info, player) - - -def on_player_appeared(manager, player, selected_player=None): - if player is not None and (selected_player is None or player.name == selected_player): - init_player(manager, player) - else: - logger.debug("New player appeared, but it's not the selected player, skipping") - - -def on_player_vanished(manager, player): - logger.info('Player has vanished') - sys.stdout.write('\n') - sys.stdout.flush() - - -def init_player(manager, name): - logger.debug('Initialize player: {player}'.format(player=name.name)) - player = Playerctl.Player.new_from_name(name) - player.connect('playback-status', on_play, manager) - player.connect('metadata', on_metadata, manager) - manager.manage_player(player) - on_metadata(player, player.props.metadata, manager) - - -def signal_handler(sig, frame): - logger.debug('Received signal to stop, exiting') - sys.stdout.write('\n') - sys.stdout.flush() - # loop.quit() - sys.exit(0) - - -def parse_arguments(): - parser = argparse.ArgumentParser() - - # Increase verbosity with every occurance of -v - parser.add_argument('-v', '--verbose', action='count', default=0) - - # Define for which player we're listening - parser.add_argument('--player') - - return parser.parse_args() - - -def main(): - arguments = parse_arguments() - - # Initialize logging - logging.basicConfig(stream=sys.stderr, level=logging.DEBUG, - format='%(name)s %(levelname)s %(message)s') - - # Logging is set by default to WARN and higher. - # With every occurrence of -v it's lowered by one - logger.setLevel(max((3 - arguments.verbose) * 10, 0)) - - # Log the sent command line arguments - logger.debug('Arguments received {}'.format(vars(arguments))) - - manager = Playerctl.PlayerManager() - loop = GLib.MainLoop() - - manager.connect('name-appeared', lambda *args: on_player_appeared(*args, arguments.player)) - manager.connect('player-vanished', on_player_vanished) - - signal.signal(signal.SIGINT, signal_handler) - signal.signal(signal.SIGTERM, signal_handler) - - for player in manager.props.player_names: - if arguments.player is not None and arguments.player != player.name: - logger.debug('{player} is not the filtered player, skipping it' - .format(player=player.name) - ) - continue - - init_player(manager, player) - - loop.run() - - -if __name__ == '__main__': - main() diff --git a/.config/waybar/modules/mail.py b/.config/waybar/modules/mail.py deleted file mode 100755 index abc36ec..0000000 --- a/.config/waybar/modules/mail.py +++ /dev/null @@ -1,42 +0,0 @@ -#!/usr/bin/python - -import os -import imaplib - -import mailsecrets - -def getmails(username, password, server): - imap = imaplib.IMAP4_SSL(server, 993) - imap.login(username, password) - imap.select('INBOX') - ustatus, uresponse = imap.uid('search', None, 'UNSEEN') - if ustatus == 'OK': - unread_msg_nums = uresponse[0].split() - else: - unread_msg_nums = [] - - fstatus, fresponse = imap.uid('search', None, 'FLAGGED') - if fstatus == 'OK': - flagged_msg_nums = fresponse[0].split() - else: - flagged_msg_nums = [] - - return [len(unread_msg_nums), len(flagged_msg_nums)] - -ping = os.system("ping " + mailsecrets.server + " -c1 > /dev/null 2>&1") -if ping == 0: - mails = getmails(mailsecrets.username, mailsecrets.password, mailsecrets.server) - text = '' - alt = '' - - if mails[0] > 0: - text = alt = str(mails[0]) - if mails[1] > 0: - alt = str(mails[1]) + "  " + alt - else: - exit(1) - - print('{"text":"' + text + '", "alt": "' + alt + '"}') - -else: - exit(1) diff --git a/.config/waybar/modules/power_menu.xml b/.config/waybar/modules/power_menu.xml deleted file mode 100644 index a280e97..0000000 --- a/.config/waybar/modules/power_menu.xml +++ /dev/null @@ -1,35 +0,0 @@ - - - - - - - - Suspend - - - - - Hibernate - - - - - Logout - - - - - Shutdown - - - - - - - - Reboot - - - - diff --git a/.config/waybar/modules/power_profiles.py b/.config/waybar/modules/power_profiles.py deleted file mode 100755 index 20744e8..0000000 --- a/.config/waybar/modules/power_profiles.py +++ /dev/null @@ -1,150 +0,0 @@ -#!/usr/bin/env python3 -import subprocess -import json -import sys -from pathlib import Path - -def get_current_profile(): - """Get the current power profile""" - try: - result = subprocess.run( - ["powerprofilesctl", "get"], - capture_output=True, - text=True, - timeout=5 - ) - if result.returncode == 0: - return result.stdout.strip() - except Exception as e: - print(f"Error getting profile: {e}", file=sys.stderr) - return "unknown" - -def get_available_profiles(): - """Get list of available power profiles""" - try: - result = subprocess.run( - ["powerprofilesctl", "list"], - capture_output=True, - text=True, - timeout=5 - ) - if result.returncode == 0: - profiles = [] - for line in result.stdout.split('\n'): - line = line.strip() - if line and ':' in line and not line.startswith('Cpu') and not line.startswith('Platform') and not line.startswith('Degraded'): - profile_name = line.split(':')[0].strip() - if profile_name and not profile_name.startswith('*'): - profiles.append(profile_name) - elif profile_name.startswith('*'): - profiles.append(profile_name[1:].strip()) - return profiles - except Exception as e: - print(f"Error getting profiles: {e}", file=sys.stderr) - return [] - -def set_profile(profile): - """Set the power profile""" - try: - subprocess.run( - ["powerprofilesctl", "set", profile], - capture_output=True, - timeout=5 - ) - except Exception as e: - print(f"Error setting profile: {e}", file=sys.stderr) - -def write_menu_file(menu_path, profiles, current): - """Write a GTK menu XML file for Waybar.""" - items = [] - if not profiles: - items.append( - " \n" - " \n" - " No profiles\n" - " false\n" - " \n" - " \n" - ) - else: - for profile in profiles: - label = profile - if profile == current: - label = f"{profile} (current)" - items.append( - " \n" - f" \n" - f" {label}\n" - " \n" - " \n" - ) - - content = ( - "\n" - "\n" - " \n" - + "".join(items) + - " \n" - "\n" - ) - try: - menu_path.parent.mkdir(parents=True, exist_ok=True) - menu_path.write_text(content) - except Exception as e: - print(f"Error writing menu file: {e}", file=sys.stderr) - -def format_output(profile): - """Format output for waybar""" - # Icons for each profile - icons = { - "performance": "⚡", - "balanced": "⚖", - "power-saver": "🔋" - } - - icon = icons.get(profile, "⚡") - - output = { - "text": f"{icon}", - "alt": profile, - "class": f"power-profile-{profile}", - "tooltip": f"Current: {profile}" - } - - return json.dumps(output) - -def main(): - menu_path = Path("~/.config/waybar/modules/power_profiles_menu.xml").expanduser() - - if len(sys.argv) > 2 and sys.argv[1] == "--set": - target = sys.argv[2] - set_profile(target) - current = get_current_profile() - available = get_available_profiles() - write_menu_file(menu_path, available, current) - print(format_output(current)) - return - - if len(sys.argv) > 1 and sys.argv[1] == "--next": - # Cycle to next profile - current = get_current_profile() - available = get_available_profiles() - write_menu_file(menu_path, available, current) - - if current in available and available: - idx = available.index(current) - next_profile = available[(idx + 1) % len(available)] - set_profile(next_profile) - print(format_output(next_profile)) - else: - print(format_output(current)) - return - - # Just display current profile - current = get_current_profile() - available = get_available_profiles() - write_menu_file(menu_path, available, current) - print(format_output(current)) - -if __name__ == "__main__": - main() diff --git a/.config/waybar/modules/power_profiles_menu.xml b/.config/waybar/modules/power_profiles_menu.xml deleted file mode 100644 index 9f949be..0000000 --- a/.config/waybar/modules/power_profiles_menu.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - performance - - - - - balanced - - - - - power-saver (current) - - - - diff --git a/.config/waybar/modules/spotify.sh b/.config/waybar/modules/spotify.sh deleted file mode 100755 index c00622b..0000000 --- a/.config/waybar/modules/spotify.sh +++ /dev/null @@ -1,18 +0,0 @@ -#!/bin/sh - -class=$(playerctl metadata --player=spotify --format '{{lc(status)}}') -icon="" - -if [[ $class == "playing" ]]; then - info=$(playerctl metadata --player=spotify --format '{{artist}} - {{title}}') - if [[ ${#info} > 40 ]]; then - info=$(echo $info | cut -c1-40)"..." - fi - text=$info" "$icon -elif [[ $class == "paused" ]]; then - text=$icon -elif [[ $class == "stopped" ]]; then - text="" -fi - -echo -e "{\"text\":\""$text"\", \"class\":\""$class"\"}" diff --git a/.config/waybar/modules/storage.sh b/.config/waybar/modules/storage.sh deleted file mode 100755 index ae2a5ce..0000000 --- a/.config/waybar/modules/storage.sh +++ /dev/null @@ -1,24 +0,0 @@ -#!/bin/sh - -mount="/" -warning=20 -critical=10 - -df -h -P -l "$mount" | awk -v warning=$warning -v critical=$critical ' -/\/.*/ { - text=$4 - tooltip="Filesystem: "$1"\rSize: "$2"\rUsed: "$3"\rAvail: "$4"\rUse%: "$5"\rMounted on: "$6 - use=$5 - exit 0 -} -END { - class="" - gsub(/%$/,"",use) - if ((100 - use) < critical) { - class="critical" - } else if ((100 - use) < warning) { - class="warning" - } - print "{\"text\":\""text"\", \"percentage\":"use",\"tooltip\":\""tooltip"\", \"class\":\""class"\"}" -} -' diff --git a/.config/waybar/modules/weather.sh b/.config/waybar/modules/weather.sh deleted file mode 100755 index 06157ef..0000000 --- a/.config/waybar/modules/weather.sh +++ /dev/null @@ -1,79 +0,0 @@ -#!/bin/bash - -cachedir=~/.cache/rbn -cachefile=${0##*/}-$1 - -if [ ! -d $cachedir ]; then - mkdir -p $cachedir -fi - -if [ ! -f $cachedir/$cachefile ]; then - touch $cachedir/$cachefile -fi - -# Save current IFS -SAVEIFS=$IFS -# Change IFS to new line. -IFS=$'\n' - -cacheage=$(($(date +%s) - $(stat -c '%Y' "$cachedir/$cachefile"))) -if [ $cacheage -gt 1740 ] || [ ! -s $cachedir/$cachefile ]; then - data=($(curl -s https://en.wttr.in/$1\?0qnT 2>&1)) - echo ${data[0]} | cut -f1 -d, > $cachedir/$cachefile - echo ${data[1]} | sed -E 's/^.{15}//' >> $cachedir/$cachefile - echo ${data[2]} | sed -E 's/^.{15}//' >> $cachedir/$cachefile -fi - -weather=($(cat $cachedir/$cachefile)) - -# Restore IFSClear -IFS=$SAVEIFS - -temperature=$(echo ${weather[2]} | sed -E 's/([[:digit:]])+\.\./\1 to /g') - -#echo ${weather[1]##*,} - -# https://fontawesome.com/icons?s=solid&c=weather -case $(echo ${weather[1]##*,} | tr '[:upper:]' '[:lower:]') in -"clear" | "sunny") - condition="" - ;; -"partly cloudy") - condition="杖" - ;; -"cloudy") - condition="" - ;; -"overcast") - condition="" - ;; -"mist" | "fog" | "freezing fog") - condition="" - ;; -"patchy rain possible" | "patchy light drizzle" | "light drizzle" | "patchy light rain" | "light rain" | "light rain shower" | "rain") - condition="" - ;; -"moderate rain at times" | "moderate rain" | "heavy rain at times" | "heavy rain" | "moderate or heavy rain shower" | "torrential rain shower" | "rain shower") - condition="" - ;; -"patchy snow possible" | "patchy sleet possible" | "patchy freezing drizzle possible" | "freezing drizzle" | "heavy freezing drizzle" | "light freezing rain" | "moderate or heavy freezing rain" | "light sleet" | "ice pellets" | "light sleet showers" | "moderate or heavy sleet showers") - condition="ﭽ" - ;; -"blowing snow" | "moderate or heavy sleet" | "patchy light snow" | "light snow" | "light snow showers") - condition="流" - ;; -"blizzard" | "patchy moderate snow" | "moderate snow" | "patchy heavy snow" | "heavy snow" | "moderate or heavy snow with thunder" | "moderate or heavy snow showers") - condition="ﰕ" - ;; -"thundery outbreaks possible" | "patchy light rain with thunder" | "moderate or heavy rain with thunder" | "patchy light snow with thunder") - condition="" - ;; -*) - condition="" - echo -e "{\"text\":\""$condition"\", \"alt\":\""${weather[0]}"\", \"tooltip\":\""${weather[0]}: $temperature ${weather[1]}"\"}" - ;; -esac - -#echo $temp $condition - -echo -e "{\"text\":\""$temperature $condition"\", \"alt\":\""${weather[0]}"\", \"tooltip\":\""${weather[0]}: $temperature ${weather[1]}"\"}" diff --git a/.config/waybar/style.css b/.config/waybar/style.css deleted file mode 100644 index d097df9..0000000 --- a/.config/waybar/style.css +++ /dev/null @@ -1,245 +0,0 @@ -/* Import wallust colors */ -@import url("colors-wallust.css"); - -/* Global defaults */ -* { - font-family: - "Fira Sans Condensed", "Font Awesome 6 Free", FontAwesome, Roboto, - Helvetica, Arial, sans-serif; - font-size: 12px; - font-weight: 500; - margin: 0; - padding: 0; - border: none; - border-radius: 3px; - min-height: 0; - transition: background-color 0.5s; -} - -.modules-left, -.modules-right { - background-color: rgba(17, 24, 39, 0.1); - margin: 2px; - padding: 1px 3px; -} - -/* The whole bar */ -#waybar { - background-color: alpha(@background, 0); - color: @foreground; - border-radius: 0px; - margin: 0; -} - -window#waybar.hidden { - opacity: 0.2; -} - -window#waybar.empty #window { - background-color: transparent; -} - -/* --- Workspaces --- */ -#workspaces button { - padding: 3px 10px; - margin: 2px; - border-radius: 3px; - color: @foreground; - background-color: alpha(@foreground, 0.15); - transition: all 0.3s ease-in-out; - font-size: 9px; -} - -#workspaces button.active { - color: @foreground; - background: @color3; -} - -#workspaces button:hover { - background: @color2; -} - -#workspaces button.urgent { - background-color: @color9; -} - -#workspaces { - background-color: transparent; - margin: 0; - padding: 0; -} - -/* --- Tray --- */ -#tray { - margin: 2px; -} - -#tray > * { - background-color: alpha(@foreground, 0.15); - margin: 2px; -} - -#tray > * > image, -#tray > * image { - padding: 0px 10px; -} - -#tray > *:hover { - background: @color2; -} - -#tray > .needs-attention { - background-color: @color9; -} - -#window { - background-color: alpha(@background, 0.1); - color: @foreground; - border-radius: 3px; - padding: 1px 10px; - margin: 2px; -} - -/* --- Shared module styling --- */ -#clock, -#battery, -#cpu, -#memory, -#custom-storage, -#custom-weather, -#bluetooth, -#custom-media, -#custom-power_profiles, -#custom-power, -#temperature, -#backlight, -#network, -#pulseaudio, -#wireplumber, -#cava { - background-color: alpha(@foreground, 0.15); - color: @foreground; - padding: 0px 10px; - margin: 2px; -} - -#clock:hover, -#battery:hover, -#cpu:hover, -#memory:hover, -#custom-storage:hover, -#custom-weather:hover, -#bluetooth:hover, -#custom-media:hover, -#custom-power_profiles:hover, -#custom-power:hover, -#temperature:hover, -#backlight:hover, -#network:hover, -#wireplumber:hover, -#pulseaudio:hover, -#cava:hover { - background: @color2; -} - -#wireplumber { - border-radius: 0px 3px 3px 0px; - margin-left: 0px; -} - -#cava { - border-radius: 3px 0px 0px 3px; - margin-right: 0px; -} - -#bluetooth { - background-color: alpha(@color3, 0.3); -} - -#bluetooth.disabled { -} - -#bluetooth.on { - background-color: alpha(@color2, 1); -} - -#bluetooth.connected { - background-color: alpha(@color4, 1); -} - -#bluetooth.connected:hover, -#bluetooth.on:hover { - background-color: alpha(@color3, 0.3); -} - -#battery.critical:not(.charging) { - color: @color1; - background-color: alpha(@color2, 1); -} - -#temperature.critical { - background-color: alpha(@color2, 1); -} - -#idle_inhibitor { - background-color: rgba(45, 52, 54, 0.5); -} - -#idle_inhibitor.activated { - background-color: rgba(236, 240, 241, 0.5); /* bleibt gleich */ - color: rgba(45, 52, 54, 1); -} - -/* Language + keyboard state */ -#language, -#keyboard-state { - padding: 0 0px; - margin: 0 5px; - min-width: 16px; -} - -#language { - background: rgba(0, 176, 147, 0.5); - color: rgba(116, 8, 100, 0.5); -} - -#keyboard-state { - background: rgba(151, 225, 173, 0.5); - color: rgba(0, 0, 0, 0.5); -} - -#keyboard-state > label { - padding: 0 0px; -} - -#keyboard-state > label.locked { - background: rgba(0, 0, 0, 0.2); -} - -#taskbar { - padding: 0; -} - -#taskbar button { - padding: 0px 3px 0px 6px; - margin: 2px 1px; - border-radius: 6px; - color: @foreground; - transition: all 0.3s ease-in-out; -} - -#taskbar button.active { - background: alpha(@foreground, 0.2); - transition: all 0.4s ease-in-out; -} - -menu { - border-radius: 5px; - border: 0px; - background: rgba(22, 19, 32, 0.5); - color: #b5e8e0; - padding: 5px; -} -menuitem { - border-radius: 5px; -} diff --git a/.config/waybar/waybar.sh b/.config/waybar/waybar.sh deleted file mode 100755 index d03b9eb..0000000 --- a/.config/waybar/waybar.sh +++ /dev/null @@ -1,10 +0,0 @@ -#!/usr/bin/env sh - -# Terminate already running bar instances -killall -q waybar - -# Wait until the processes have been shut down -while pgrep -x waybar >/dev/null; do sleep 1; done - -# Launch main -waybar &