{ config, pkgs, lib, ... }: let shellQml = pkgs.writeText "shell.qml" '' import Quickshell import QtQuick import QtQuick.Layouts import Quickshell.Wayland import Quickshell.Hyprland import Quickshell.Services.Mpris import Quickshell.Services.Pipewire import Quickshell.Services.SystemTray import Quickshell.Services.UPower import Quickshell.Bluetooth import "widgets" as W ShellRoot { id: root property int barHeight: 34 property int paddingX: 10 property int spacing: 10 property real backgroundOpacity: 0.45 property int radius: 10 property int borderWidth: 1 SystemClock { id: clock; precision: SystemClock.Minutes } Variants { model: Quickshell.screens PanelWindow { id: win required property var modelData screen: modelData anchors { top: true left: true right: true } implicitHeight: root.barHeight exclusiveZone: implicitHeight aboveWindows: true focusable: false color: "transparent" Rectangle { anchors.fill: parent radius: root.radius border.width: root.borderWidth border.color: Qt.rgba(1, 1, 1, 0.15) color: Qt.rgba(0, 0, 0, root.backgroundOpacity) } RowLayout { anchors.fill: parent anchors.leftMargin: root.paddingX anchors.rightMargin: root.paddingX spacing: root.spacing RowLayout { spacing: root.spacing Layout.alignment: Qt.AlignLeft W.DashboardButton { } W.Workspaces { } W.Separator { } W.WindowTitle { screen: modelData } } Item { Layout.fillWidth: true } RowLayout { spacing: root.spacing Layout.alignment: Qt.AlignHCenter W.Media { } } Item { Layout.fillWidth: true } RowLayout { spacing: root.spacing Layout.alignment: Qt.AlignRight W.Volume { } W.Network { } W.BluetoothIndicator { } W.Battery { } W.Separator { } W.Tray { parentWindow: win } W.KeyboardLayout { } W.Clock { clock: clock } W.Notifications { } } } } } } ''; barButtonQml = pkgs.writeText "BarButton.qml" '' import QtQuick import QtQuick.Layouts Item { id: root property string label: "" property string iconSource: "" property bool emphasized: false signal clicked() signal rightClicked() implicitHeight: 24 implicitWidth: content.implicitWidth + 14 Rectangle { anchors.fill: parent radius: 8 color: emphasized ? Qt.rgba(1, 1, 1, 0.18) : Qt.rgba(1, 1, 1, 0.10) border.width: 0 } RowLayout { id: content anchors.centerIn: parent spacing: 6 Image { visible: root.iconSource !== "" source: root.iconSource width: 16 height: 16 fillMode: Image.PreserveAspectFit smooth: true } Text { text: root.label font.weight: 300 color: "white" elide: Text.ElideRight } } MouseArea { anchors.fill: parent acceptedButtons: Qt.LeftButton | Qt.RightButton onClicked: function(mouse) { if (mouse.button === Qt.LeftButton) root.clicked(); if (mouse.button === Qt.RightButton) root.rightClicked(); } } } ''; separatorQml = pkgs.writeText "Separator.qml" '' import QtQuick import QtQuick.Layouts Rectangle { Layout.preferredWidth: 1 Layout.preferredHeight: 16 radius: 1 color: Qt.rgba(1, 1, 1, 0.18) } ''; dashboardQml = pkgs.writeText "DashboardButton.qml" '' import QtQuick import Quickshell.Io import "." BarButton { label: "≡" Process { id: proc } function run(cmd) { proc.running = false proc.command = ["sh", "-lc", cmd] proc.running = true } onClicked: run("command -v fuzzel >/dev/null && fuzzel || true") onRightClicked: run("command -v wlogout >/dev/null && wlogout || true") } ''; workspacesQml = pkgs.writeText "Workspaces.qml" '' import QtQuick import QtQuick.Layouts import Quickshell.Hyprland import Quickshell.Io import "." RowLayout { spacing: 6 Process { id: hypr } function dispatchWorkspace(id) { hypr.running = false hypr.command = ["sh", "-lc", "hyprctl dispatch workspace " + id] hypr.running = true } Repeater { model: Hyprland.workspaces delegate: BarButton { label: (modelData.name && modelData.name !== "") ? modelData.name : ("" + modelData.id) emphasized: modelData.focused onClicked: { if (modelData.activate) modelData.activate() else dispatchWorkspace(modelData.id) } } } } ''; windowTitleQml = pkgs.writeText "WindowTitle.qml" '' import QtQuick import Quickshell.Wayland Item { id: root property var screen implicitHeight: 24 implicitWidth: 320 Text { anchors.verticalCenter: parent.verticalCenter width: parent.implicitWidth color: "white" font.weight: 300 elide: Text.ElideRight text: { const t = ToplevelManager.activeToplevel; // singleton property :contentReference[oaicite:1]{index=1} if (!t) return ""; // опционально: фильтр по монитору if (root.screen && t.screens && t.screens.indexOf && t.screens.indexOf(root.screen) === -1) return ""; return t.title || ""; } } } ''; mediaQml = pkgs.writeText "Media.qml" '' import QtQuick import Quickshell.Services.Mpris Item { implicitHeight: 24 implicitWidth: 360 function pickPlayer() { const list = Mpris.players.values || []; if (list.length === 0) return null; for (let i = 0; i < list.length; i++) { if (list[i].playbackState === MprisPlaybackState.Playing) return list[i]; } return list[0]; } Text { anchors.verticalCenter: parent.verticalCenter width: parent.implicitWidth color: "white" font.weight: 300 elide: Text.ElideRight text: { const p = pickPlayer(); if (!p) return ""; const title = p.trackTitle || ""; const artist = p.trackArtist || ""; if (title === "" && artist === "") return ""; return "\"" + (title || "Unknown Title") + "\" by " + (artist || "Unknown Artist"); } } } ''; volumeQml = pkgs.writeText "Volume.qml" '' import QtQuick import Quickshell.Services.Pipewire import "." BarButton { id: root PwObjectTracker { objects: [ Pipewire.defaultAudioSink ] } function clamp(x, lo, hi) { return Math.max(lo, Math.min(hi, x)); } label: { const s = Pipewire.defaultAudioSink; if (!s || !s.audio) return "Vol: --"; return "Vol: " + Math.round(s.audio.volume * 100) + "%"; } WheelHandler { onWheel: function(event) { const s = Pipewire.defaultAudioSink; if (!s || !s.audio) return; const step = 0.05; const dir = event.angleDelta.y > 0 ? -1 : 1; s.audio.volume = root.clamp(s.audio.volume + (dir * step), 0.0, 1.0); event.accepted = true; } } } ''; networkQml = pkgs.writeText "Network.qml" '' import QtQuick import Quickshell.Io import "." BarButton { id: root property string ssid: "" property int signal: -1 Process { id: proc stdout: StdioCollector { waitForEnd: true onStreamFinished: { const text = this.text || ""; const lines = text.split("\n"); for (let i = 0; i < lines.length; i++) { const parts = lines[i].split(":"); if (parts.length >= 3 && parts[0] === "yes") { root.ssid = parts[1] || ""; root.signal = parseInt(parts[2]) || -1; return; } } root.ssid = ""; root.signal = -1; } } } Timer { interval: 5000 repeat: true running: true triggeredOnStart: true onTriggered: { proc.exec(["sh", "-lc", "nmcli -t -f active,ssid,signal dev wifi 2>/dev/null || true"]); } } function trunc(s, n) { return (s.length > n) ? (s.slice(0, n) + "…") : s; } label: (ssid === "") ? "WiFi: --" : ("WiFi: " + trunc(ssid, 13)) } ''; btQml = pkgs.writeText "BluetoothIndicator.qml" '' import QtQuick import Quickshell.Bluetooth import "." BarButton { label: { const n = (Bluetooth.devices && Bluetooth.devices.values) ? Bluetooth.devices.values.length : 0; return n > 0 ? ("BT: " + n) : "BT: --"; } } ''; batteryQml = pkgs.writeText "Battery.qml" '' import QtQuick import Quickshell.Services.UPower import "." BarButton { label: { const d = UPower.displayDevice; if (!d || !d.ready) return "Bat: --"; return "Bat: " + Math.round(d.percentage) + "%"; } } ''; trayQml = pkgs.writeText "Tray.qml" '' import QtQuick import QtQuick.Layouts import Quickshell.Services.SystemTray RowLayout { id: root property var parentWindow spacing: 6 Repeater { model: SystemTray.items delegate: Item { implicitWidth: 18 implicitHeight: 18 Image { anchors.fill: parent source: modelData.icon fillMode: Image.PreserveAspectFit smooth: true } MouseArea { anchors.fill: parent acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton onClicked: function(mouse) { if (mouse.button === Qt.LeftButton) { if (!modelData.onlyMenu) modelData.activate(); else if (root.parentWindow) modelData.display(root.parentWindow, mouse.x, mouse.y); } else if (mouse.button === Qt.RightButton) { if (root.parentWindow && modelData.hasMenu) modelData.display(root.parentWindow, mouse.x, mouse.y); else modelData.secondaryActivate(); } else if (mouse.button === Qt.MiddleButton) { modelData.secondaryActivate(); } } } } } } ''; kbQml = pkgs.writeText "KeyboardLayout.qml" '' import QtQuick import Quickshell.Io import "." BarButton { id: root property string layout: "--" Process { id: proc stdout: StdioCollector { waitForEnd: true onStreamFinished: { try { const obj = JSON.parse(this.text || "{}"); const kbs = obj.keyboards || []; let active = null; for (let i = 0; i < kbs.length; i++) { if (kbs[i].main === true) { active = kbs[i]; break; } } if (!active && kbs.length > 0) active = kbs[0]; const km = active ? (active.active_keymap || active.keymap || "") : ""; root.layout = km !== "" ? km : "--"; } catch (e) { root.layout = "--"; } } } } Timer { interval: 3000 repeat: true running: true triggeredOnStart: true onTriggered: { proc.exec(["sh", "-lc", "hyprctl devices -j 2>/dev/null || echo '{}'"]); } } label: "KB: " + layout } ''; clockQml = pkgs.writeText "Clock.qml" '' import QtQuick import "." BarButton { property var clock label: clock ? Qt.formatDateTime(clock.date, "ddd dd HH:mm") : "--" } ''; notifQml = pkgs.writeText "Notifications.qml" '' import QtQuick import Quickshell.Io import "." BarButton { label: "Notif" Process { id: proc } function run(cmd) { proc.running = false proc.command = ["sh", "-lc", cmd] proc.running = true } onClicked: run( "command -v swaync-client >/dev/null && swaync-client -t || " + "command -v dunstctl >/dev/null && dunstctl history-pop || true" ) } ''; quickshellDefault = pkgs.runCommand "quickshell-default-config" { } '' mkdir -p "$out/widgets" install -m644 ${shellQml} "$out/shell.qml" install -m644 ${barButtonQml} "$out/widgets/BarButton.qml" install -m644 ${separatorQml} "$out/widgets/Separator.qml" install -m644 ${dashboardQml} "$out/widgets/DashboardButton.qml" install -m644 ${workspacesQml} "$out/widgets/Workspaces.qml" install -m644 ${windowTitleQml} "$out/widgets/WindowTitle.qml" install -m644 ${mediaQml} "$out/widgets/Media.qml" install -m644 ${volumeQml} "$out/widgets/Volume.qml" install -m644 ${networkQml} "$out/widgets/Network.qml" install -m644 ${btQml} "$out/widgets/BluetoothIndicator.qml" install -m644 ${batteryQml} "$out/widgets/Battery.qml" install -m644 ${trayQml} "$out/widgets/Tray.qml" install -m644 ${kbQml} "$out/widgets/KeyboardLayout.qml" install -m644 ${clockQml} "$out/widgets/Clock.qml" install -m644 ${notifQml} "$out/widgets/Notifications.qml" ''; in { home.packages = with pkgs; [ quickshell networkmanager ]; # Важно: default должен быть обычной writable директорией в $HOME. home.file.".config/quickshell/default" = { source = quickshellDefault; recursive = true; force = true; }; }