diff options
Diffstat (limited to 'volume-osd/VolumeOsd.qml')
| -rw-r--r-- | volume-osd/VolumeOsd.qml | 302 |
1 files changed, 0 insertions, 302 deletions
diff --git a/volume-osd/VolumeOsd.qml b/volume-osd/VolumeOsd.qml deleted file mode 100644 index f739aec..0000000 --- a/volume-osd/VolumeOsd.qml +++ /dev/null @@ -1,302 +0,0 @@ -// Copyright (C) 2026 Danilo M. <danix@danix.xyz> -// -// This program is free software; you can redistribute it and/or modify -// it under the terms of the GNU General Public License version 2 as -// published by the Free Software Foundation. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -import Quickshell -import Quickshell.Wayland -import Quickshell.Services.Pipewire -import QtQuick - -Scope { - id: root - - // Milliseconds the OSD stays up after the last change. - property int timeout: 1500 - - readonly property PwNode sink: Pipewire.defaultAudioSink - readonly property PwNode source: Pipewire.defaultAudioSource - - // Which one to draw: set by whichever node changed last. - property PwNode active: null - property bool isInput: false - - // Keeping the nodes bound is what makes volume/muted actually update. - PwObjectTracker { objects: [root.sink, root.source].filter(n => n !== null) } - - // A node reports its initial volume while binding, before `ready` goes - // true, so the `ready` check alone suppresses the startup values. Nothing - // else may be swallowed: the next signal after that is the user's first - // keypress, and eating it costs the OSD its first appearance. - function show(node, input) { - if (!node?.ready || !node.audio) return; - root.active = node; - root.isInput = input; - hideTimer.restart(); - } - - Connections { - target: root.sink?.audio ?? null - function onVolumeChanged() { root.show(root.sink, false); } - function onMutedChanged() { root.show(root.sink, false); } - } - - // A track change shows the OSD as well, so the row is not something you - // only see when you happen to touch the volume. - Connections { - target: Player.current ?? null - function onTrackTitleChanged() { if (Player.title) root.showTrack(); } - function onPlaybackStateChanged() { root.showTrack(); } - } - - Connections { - target: root.source?.audio ?? null - function onVolumeChanged() { root.show(root.source, true); } - function onMutedChanged() { root.show(root.source, true); } - } - - function showTrack() { - if (!Player.active) return; - root.active = root.sink; - root.isInput = false; - hideTimer.restart(); - } - - // Hovering freezes the countdown so the transport buttons can be clicked; - // leaving starts it again. Without a hover the OSD behaves exactly as it - // did before there was anything clickable on it. - property bool hovered: false - - // Only counts down while the OSD is up and the pointer is elsewhere. - Timer { - id: hideTimer - running: root.active !== null && !root.hovered - interval: root.timeout - onTriggered: root.active = null - } - - // Quickshell exits once no window is visible, and the OSD is hidden most - // of the time, so a 1x1 transparent window holds the process open. Its - // empty mask makes it click-through, so it cannot catch a stray click. - PanelWindow { - visible: true - implicitWidth: 1 - implicitHeight: 1 - color: "transparent" - exclusionMode: ExclusionMode.Ignore - mask: Region {} - WlrLayershell.keyboardFocus: WlrKeyboardFocus.None - } - - PanelWindow { - id: win - - visible: root.active !== null - - readonly property PwNode node: root.active - readonly property real volume: node?.audio?.volume ?? 0 - readonly property bool muted: node?.audio?.muted ?? false - - // Bottom centre. Move the anchor to relocate. - anchors.bottom: true - margins.bottom: 120 - - // Grows to fit the track row; the volume-only size is unchanged. - implicitWidth: 360 - implicitHeight: Player.active ? 150 : 72 - color: "transparent" - - exclusionMode: ExclusionMode.Ignore - WlrLayershell.layer: WlrLayer.Overlay - WlrLayershell.namespace: "quickshell-volume-osd" - // Still no keyboard focus: the transport buttons are pointer targets, - // and the OSD must never take keys from the window being typed in. - WlrLayershell.keyboardFocus: WlrKeyboardFocus.None - - Rectangle { - anchors.fill: parent - radius: 12 - // Translucent so the compositor's blur shows through. The frosting - // itself is Hyprland's, applied by layerrule to this window's - // namespace: see the README. - color: Qt.alpha(Theme.base, 0.65) - border.width: 1 - border.color: Qt.alpha(Theme.text, 0.12) - - // Tracks the pointer over the whole panel so the fade can pause. - HoverHandler { - onHoveredChanged: root.hovered = hovered - } - - Column { - anchors.fill: parent - anchors.margins: 16 - spacing: 12 - - Loader { - active: Player.active - width: parent.width - sourceComponent: trackRow - } - - Rectangle { - visible: Player.active - width: parent.width - height: 1 - color: Qt.alpha(Theme.text, 0.1) - } - - Row { - width: parent.width - spacing: 14 - - Text { - anchors.verticalCenter: parent.verticalCenter - width: 30 - horizontalAlignment: Text.AlignHCenter - font.family: Theme.fontFamily - font.pixelSize: 24 - color: win.muted ? Theme.red : Theme.accent - text: { - if (root.isInput) return win.muted ? "\uf131" : "\uf130"; - if (win.muted || win.volume <= 0) return "\uf026"; - return win.volume < 0.5 ? "\uf027" : "\uf028"; - } - } - - Column { - anchors.verticalCenter: parent.verticalCenter - width: parent.width - 30 - parent.spacing - spacing: 8 - - Item { - width: parent.width - height: label.implicitHeight - - Text { - id: label - anchors.left: parent.left - font.family: Theme.fontFamily - font.pixelSize: Theme.fontSize - color: Theme.subtext - text: root.isInput ? "Input" : "Output" - } - - Text { - anchors.right: parent.right - font.family: Theme.fontFamily - font.pixelSize: Theme.fontSize - color: Theme.text - text: win.muted ? "muted" : Math.round(win.volume * 100) + "%" - } - } - - Rectangle { - width: parent.width - height: 6 - radius: 3 - color: Theme.surface - - Rectangle { - height: parent.height - radius: parent.radius - // Volume can exceed 1.0; the bar stops at full. - width: parent.width * Math.min(win.volume, 1) - color: win.muted ? Theme.red : Theme.accent - opacity: win.muted ? 0.5 : 1 - Behavior on width { NumberAnimation { duration: 100 } } - } - } - } - } - } - } - } - - Component { - id: trackRow - - Row { - id: trackLine - // A Row sizes to its children, so the panel width has to be - // pushed in: the text column below subtracts from it. - width: parent ? parent.width : 0 - spacing: 12 - - // Players that extract embedded art reuse one temp path, so the - // source carries a per-track suffix and caching is off. - Rectangle { - width: 46; height: 46; radius: 6 - color: Qt.alpha(Theme.surface, 0.8) - clip: true - - Image { - anchors.fill: parent - source: Player.artUrl - cache: false - asynchronous: true - fillMode: Image.PreserveAspectCrop - visible: status === Image.Ready - } - Text { - anchors.centerIn: parent - visible: Player.artUrl === "" || parent.children[0].status !== Image.Ready - text: "" - font { family: Theme.fontFamily; pixelSize: 20 } - color: Theme.overlay - } - } - - Column { - anchors.verticalCenter: parent.verticalCenter - // Whatever the art and transport buttons leave: a fixed width - // here overflowed the panel and pushed `next` past its edge. - width: trackLine.width - 46 - transport.width - 2 * trackLine.spacing - spacing: 3 - - Text { - width: parent.width - elide: Text.ElideRight - text: Player.title || "Nothing playing" - font { family: Theme.fontFamily; pixelSize: Theme.fontSize - 1; bold: true } - color: Theme.text - } - Text { - width: parent.width - elide: Text.ElideRight - visible: Player.artist !== "" - text: Player.artist - font { family: Theme.fontFamily; pixelSize: Theme.fontSize - 3 } - color: Theme.subtext - } - } - - Row { - id: transport - anchors.verticalCenter: parent.verticalCenter - spacing: 2 - TransportButton { - glyph: "" - enabled: Player.current?.canGoPrevious ?? false - onClicked: Player.current?.previous() - } - TransportButton { - glyph: Player.playing ? "" : "" - enabled: Player.current?.canTogglePlaying ?? false - onClicked: Player.current?.togglePlaying() - } - TransportButton { - glyph: "" - enabled: Player.current?.canGoNext ?? false - onClicked: Player.current?.next() - } - } - } - } -} |
