fixes waybar, adds quickshell

This commit is contained in:
2026-05-04 15:32:47 +02:00
parent 5e55b51220
commit bd17b76c30
34 changed files with 995 additions and 1217 deletions
+1 -1
View File
@@ -5,7 +5,7 @@
source = ~/.config/hypr/config/defaults.conf source = ~/.config/hypr/config/defaults.conf
# Autostart wiki https://wiki.hyprland.org/0.45.0/Configuring/Keywords/#executing # # 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 = blueman-applet &
exec-once = fcitx5 -d & exec-once = fcitx5 -d &
exec-once = mako & exec-once = mako &
+2 -2
View File
@@ -7,8 +7,8 @@
monitor = eDP-2, highres@highrr, 0x0, 1, vrr, 1 monitor = eDP-2, highres@highrr, 0x0, 1, vrr, 1
#monitor = HDMI-A-1, highres@highrr, auto-right, 1, transform, 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 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 H4ZK111233, highres@highrr, 1920x0, 1#, transform, 1
# monitor = HDMI-A-1, highres@highrr, auto-right, 1, vrr, 0 # monitor = HDMI-A-1, highres@highrr, auto-right, 1, vrr, 0
+60
View File
@@ -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 {}
}
}
+23
View File
@@ -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()
}
}
}
+39
View File
@@ -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)
}
}
+38
View File
@@ -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
}
@@ -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
}
}
@@ -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
}
}
@@ -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
}
}
+91
View File
@@ -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
}
}
@@ -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()
}
}
}
+55
View File
@@ -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
}
}
@@ -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()
}
}
@@ -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
}
}
@@ -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)
}
}
}
}
}
}
}
@@ -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
}
}
@@ -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 }
}
}
}
}
}
@@ -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
}
}
@@ -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
}
}
@@ -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
}
}
@@ -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"))
}
}
}
}
}
+14
View File
@@ -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
}
}
}
-19
View File
@@ -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;
-445
View File
@@ -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": "<big>􏆲</big> {}",
"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": "<big>{:%Y %B}</big>\n<tt><small>{calendar}</small></tt>",
"today-format": "<b>{}</b>",
"on-click": "kitty -e calcure --class=\"float\" -T calcure",
},
"clock#date": {
"format": "󰥔 {:%H:%M \n %e %b}",
"tooltip-format": "<big>{:%Y %B}</big>\n<tt><small>{calendar}</small></tt>",
"today-format": "<b>{}</b>",
},
"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 <span color=\"darkgray\">| {usage}%</span>",
"max-length": 13,
"min-length": 13,
},
"mpd": {
"max-length": 25,
"format": "<span foreground='#bb9af7'></span> {title}",
"format-paused": " {title}",
"format-stopped": "<span foreground='#bb9af7'></span>",
"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": "<span foreground='#fab387'>{icon}</span> {volume}%",
"format-muted": " ",
"format-source": "",
"format-source-muted": "",
//"format-muted": "<span foreground='#fab387'> </span>",
//"format-icons": [ "<span foreground='#fab387'></span>" ]
"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",
},
},
}
-127
View File
@@ -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()
-42
View File
@@ -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)
-35
View File
@@ -1,35 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<object class="GtkMenu" id="menu">
<child>
<object class="GtkMenuItem" id="suspend">
<property name="label">Suspend</property>
</object>
</child>
<child>
<object class="GtkMenuItem" id="hibernat">
<property name="label">Hibernate</property>
</object>
</child>
<child>
<object class="GtkMenuItem" id="logout">
<property name="label">Logout</property>
</object>
</child>
<child>
<object class="GtkMenuItem" id="shutdown">
<property name="label">Shutdown</property>
</object>
</child>
<child>
<object class="GtkSeparatorMenuItem" id="delimiter1"/>
</child>
<child>
<object class="GtkMenuItem" id="reboot">
<property name="label">Reboot</property>
</object>
</child>
</object>
</interface>
-150
View File
@@ -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(
" <child>\n"
" <object class=\"GtkMenuItem\" id=\"none\">\n"
" <property name=\"label\">No profiles</property>\n"
" <property name=\"sensitive\">false</property>\n"
" </object>\n"
" </child>\n"
)
else:
for profile in profiles:
label = profile
if profile == current:
label = f"{profile} (current)"
items.append(
" <child>\n"
f" <object class=\"GtkMenuItem\" id=\"{profile}\">\n"
f" <property name=\"label\">{label}</property>\n"
" </object>\n"
" </child>\n"
)
content = (
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
"<interface>\n"
" <object class=\"GtkMenu\" id=\"menu\">\n"
+ "".join(items) +
" </object>\n"
"</interface>\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()
@@ -1,20 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<object class="GtkMenu" id="menu">
<child>
<object class="GtkMenuItem" id="performance">
<property name="label">performance</property>
</object>
</child>
<child>
<object class="GtkMenuItem" id="balanced">
<property name="label">balanced</property>
</object>
</child>
<child>
<object class="GtkMenuItem" id="power-saver">
<property name="label">power-saver (current)</property>
</object>
</child>
</object>
</interface>
-18
View File
@@ -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"\"}"
-24
View File
@@ -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"\"}"
}
'
-79
View File
@@ -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]}"\"}"
-245
View File
@@ -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;
}
-10
View File
@@ -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 &