170 lines
6.2 KiB
Plaintext
170 lines
6.2 KiB
Plaintext
{ pkgs, ... }:
|
|
{
|
|
home.packages = with pkgs; [
|
|
quickshell jq playerctl pamixer networkmanager # nmcli
|
|
coreutils gnugrep gawk
|
|
];
|
|
|
|
wayland.windowManager.hyprland.settings.exec-once = [
|
|
"quickshell -c default"
|
|
];
|
|
|
|
xdg.configFile."quickshell/default/shell.qml".text = ''
|
|
import Quickshell
|
|
import QtQuick
|
|
import QtQuick.Layouts
|
|
|
|
// ===== Helpers =====
|
|
function run(cmd) {
|
|
const p = Qt.createQmlObject('import Quickshell; Process { }', root);
|
|
p.command = cmd; p.running = true; return p;
|
|
}
|
|
|
|
function pipe(cmdArr, cb) {
|
|
const p = Qt.createQmlObject('import Quickshell; Process { stdout: StdioCollector{} }', root);
|
|
p.command = cmdArr; p.running = true;
|
|
p.stdout.onStreamFinished.connect(function() { cb(p.stdout.text.trim()); });
|
|
}
|
|
|
|
// ===== Window per monitor =====
|
|
Variants {
|
|
id: root
|
|
model: Quickshell.screens
|
|
delegate: Component {
|
|
PanelWindow {
|
|
required property var modelData
|
|
screen: modelData
|
|
anchors.top: true; anchors.left: true; anchors.right: true
|
|
implicitHeight: 30
|
|
layer: "top"
|
|
exclusiveZone: implicitHeight
|
|
|
|
Rectangle { anchors.fill: parent; color: "#121212"; opacity: 0.95; radius: 0 }
|
|
|
|
// ===== Layout =====
|
|
RowLayout {
|
|
anchors.fill: parent
|
|
anchors.margins: 8
|
|
spacing: 12
|
|
|
|
// ---- LEFT: Workspaces ----
|
|
Row {
|
|
id: wsRow
|
|
Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft
|
|
property int activeWs: 1
|
|
Repeater {
|
|
model: 10
|
|
delegate: Rectangle {
|
|
property int idx: index + 1
|
|
height: 18; width: 22; radius: 5
|
|
color: (wsRow.activeWs === idx) ? "#2e7d32" : "#1f1f1f"
|
|
border.width: 1
|
|
border.color: (wsRow.activeWs === idx) ? "#4caf50" : "#333333"
|
|
Text {
|
|
anchors.centerIn: parent
|
|
text: idx; color: "#eaeaea"; font.pixelSize: 11
|
|
}
|
|
MouseArea {
|
|
anchors.fill: parent
|
|
onClicked: run(["hyprctl","dispatch","workspace", String(idx)])
|
|
}
|
|
}
|
|
}
|
|
|
|
// active workspace poll
|
|
Timer {
|
|
interval: 500; running: true; repeat: true
|
|
onTriggered: pipe(["bash","-lc","hyprctl activeworkspace -j | jq -r .id"], function(t){
|
|
if (t !== "" && !isNaN(parseInt(t))) wsRow.activeWs = parseInt(t)
|
|
})
|
|
}
|
|
}
|
|
|
|
// ---- CENTER: Active window title ----
|
|
Item { Layout.fillWidth: true }
|
|
Text {
|
|
id: titleText
|
|
Layout.alignment: Qt.AlignVCenter | Qt.AlignHCenter
|
|
elide: Text.ElideRight; horizontalAlignment: Text.AlignHCenter
|
|
font.pixelSize: 12; color: "#dcdcdc"; text: "—"
|
|
width: parent.width * 0.40
|
|
}
|
|
Timer {
|
|
interval: 500; running: true; repeat: true
|
|
onTriggered: pipe(["bash","-lc","hyprctl activewindow -j | jq -r .title"], function(t){
|
|
titleText.text = (t && t !== "null") ? t : "—"
|
|
})
|
|
}
|
|
Item { Layout.fillWidth: true }
|
|
|
|
// ---- RIGHT: Media / Volume / Net / Battery / Clock ----
|
|
Row {
|
|
Layout.alignment: Qt.AlignVCenter | Qt.AlignRight
|
|
spacing: 10
|
|
|
|
// Media (playerctl)
|
|
Text {
|
|
id: media
|
|
font.pixelSize: 12; color: "#c0c0c0"; text: ""
|
|
width: 220; elide: Text.ElideRight
|
|
}
|
|
Timer {
|
|
interval: 1000; running: true; repeat: true
|
|
onTriggered: pipe(["bash","-lc",
|
|
"playerctl metadata --format '{{status}} {{artist}} - {{title}}' 2>/dev/null || true"
|
|
], function(t){ media.text = t })
|
|
}
|
|
|
|
// Volume (pamixer)
|
|
Text {
|
|
id: vol; font.pixelSize: 12; color: "#e0e0e0"; text: ""
|
|
MouseArea { anchors.fill: parent; onWheel: (wheel.angleDelta.y>0)
|
|
? run(["pamixer","-i","3"]) : run(["pamixer","-d","3"]) }
|
|
onPressed: run(["pamixer","-t"])
|
|
}
|
|
Timer {
|
|
interval: 800; running: true; repeat: true
|
|
onTriggered: pipe(["bash","-lc",
|
|
"if pamixer --get-mute; then echo ' Mute'; else echo \" $(pamixer --get-volume)%\"; fi"
|
|
], function(t){ vol.text = t })
|
|
}
|
|
|
|
// Network (nmcli)
|
|
Text {
|
|
id: net; font.pixelSize: 12; color: "#b0b0b0"; text: ""
|
|
MouseArea { anchors.fill: parent; onClicked: run(["nm-connection-editor"]) }
|
|
}
|
|
Timer {
|
|
interval: 2500; running: true; repeat: true
|
|
onTriggered: pipe(["bash","-lc",
|
|
"nmcli -t -f NAME,TYPE,DEVICE,STATE connection show --active | awk -F: 'NR==1{print $1\" (\"$2\")\"}' || true"
|
|
], function(t){ net.text = t || "offline" })
|
|
}
|
|
|
|
// Battery (upower/sysfs)
|
|
Text { id: bat; font.pixelSize: 12; color: "#b0e08a"; text: "" }
|
|
Timer {
|
|
interval: 3000; running: true; repeat: true
|
|
onTriggered: pipe(["bash","-lc",
|
|
"cap=$(cat /sys/class/power_supply/BAT*/capacity 2>/dev/null | head -n1); " +
|
|
"stat=$(cat /sys/class/power_supply/BAT*/status 2>/dev/null | head -n1); " +
|
|
"if [ -n \"$cap\" ]; then echo \"$stat $cap%\"; fi"
|
|
], function(t){ bat.text = t || "" })
|
|
}
|
|
|
|
// Clock
|
|
Text {
|
|
id: clock; font.pixelSize: 12; color: "#eeeeee"
|
|
}
|
|
Timer {
|
|
interval: 1000; running: true; repeat: true
|
|
onTriggered: pipe(["date","+%a %b %d %H:%M:%S"], function(t){ clock.text = t })
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
'';
|
|
}
|