added quickshell

This commit is contained in:
2026-08-10 23:57:26 +02:00
parent cbaf55cfc5
commit 47ff5233bd
69 changed files with 6945 additions and 80 deletions
+99
View File
@@ -0,0 +1,99 @@
pragma Singleton
import QtQuick
import Quickshell
import Quickshell.Services.Pipewire
// Live audio state straight from PipeWire, so the bar and OSD react the instant
// a volume key is pressed rather than on the next poll.
Singleton {
id: root
readonly property PwNode sink: Pipewire.defaultAudioSink
readonly property PwNode source: Pipewire.defaultAudioSource
// Binding node.audio.* only works while the node is tracked.
PwObjectTracker {
objects: [root.sink, root.source].filter(n => n !== null)
}
readonly property bool ready: sink !== null && sink.audio !== null
readonly property real volume: sink?.audio?.volume ?? 0
readonly property bool muted: sink?.audio?.muted ?? true
readonly property int volumePercent: Math.round(volume * 100)
readonly property string sinkName: sink?.nickname || sink?.description || sink?.name || "No output"
readonly property real micVolume: source?.audio?.volume ?? 0
readonly property bool micMuted: source?.audio?.muted ?? true
readonly property int micPercent: Math.round(micVolume * 100)
readonly property string sourceName: source?.nickname || source?.description || source?.name || "No input"
// Every sink on the box, for the output picker in the audio popout.
readonly property var sinks: Pipewire.nodes.values.filter(n =>
n.isSink && !n.isStream && n.audio !== null)
// Application streams, for the per-app mixer.
readonly property var streams: Pipewire.nodes.values.filter(n =>
n.isStream && n.audio !== null && n.isSink === false)
PwObjectTracker {
objects: root.streams
}
signal volumeBumped()
signal micBumped()
onVolumePercentChanged: volumeBumped()
onMutedChanged: volumeBumped()
onMicPercentChanged: micBumped()
onMicMutedChanged: micBumped()
function setVolume(v: real) {
if (!sink?.audio) return;
sink.audio.volume = Math.max(0, Math.min(1.5, v));
}
function changeVolume(delta: real) {
setVolume(volume + delta);
}
function toggleMute() {
if (!sink?.audio) return;
sink.audio.muted = !sink.audio.muted;
}
function setMicVolume(v: real) {
if (!source?.audio) return;
source.audio.volume = Math.max(0, Math.min(1.5, v));
}
function toggleMicMute() {
if (!source?.audio) return;
source.audio.muted = !source.audio.muted;
}
function setSink(node: PwNode) {
Pipewire.preferredDefaultAudioSink = node;
}
function streamName(node: PwNode): string {
if (!node) return "";
return node.properties?.["application.name"]
|| node.properties?.["media.name"]
|| node.nickname
|| node.description
|| node.name;
}
// Nerd Font glyph for the current output level.
readonly property string icon: {
if (muted) return "󰝟";
if (volumePercent === 0) return "󰕿";
if (volumePercent < 34) return "󰕿";
if (volumePercent < 67) return "󰖀";
return "󰕾";
}
readonly property string micIcon: micMuted ? "󰍭" : "󰍬"
}