a BUNCH of qml which I havent reviewed yet
This commit is contained in:
@@ -0,0 +1,234 @@
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import Quickshell.Services.Pipewire
|
||||
|
||||
Scope {
|
||||
id: osd
|
||||
|
||||
readonly property PwNode sink: Pipewire.defaultAudioSink
|
||||
readonly property PwNode source: Pipewire.defaultAudioSource
|
||||
|
||||
property string label: ""
|
||||
property real level: 0
|
||||
property bool muted: false
|
||||
property bool shown: false
|
||||
property string warning: ""
|
||||
|
||||
// going past 100% : at 100%, it takes 'unlockPresses' separate presses of
|
||||
// volume-up (the first shows a warning); above 100% there is no limit
|
||||
readonly property real step: 0.10
|
||||
readonly property int unlockPresses: 3
|
||||
readonly property int repeatGuardMs: 150 // key-repeat events don't count toward unlocking
|
||||
property int pressesAtMax: 0
|
||||
property real lastUpPress: 0
|
||||
|
||||
IpcHandler {
|
||||
target: "volume"
|
||||
function up(): void { osd.volumeUp() }
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: unlockReset
|
||||
interval: 3000
|
||||
onTriggered: osd.pressesAtMax = 0
|
||||
}
|
||||
|
||||
function volumeUp(): void {
|
||||
const audio = sink?.audio
|
||||
if (!audio) return
|
||||
|
||||
const now = Date.now()
|
||||
const isRepeat = now - lastUpPress < repeatGuardMs
|
||||
lastUpPress = now
|
||||
|
||||
const percent = Math.round(audio.volume * 100)
|
||||
|
||||
if (percent < 100) { // normal range; stop exactly at 100%
|
||||
pressesAtMax = 0
|
||||
audio.volume = Math.min(1.0, audio.volume + step)
|
||||
return
|
||||
}
|
||||
|
||||
if (percent > 100) { // already past 100% : no limit
|
||||
audio.volume = audio.volume + step
|
||||
return
|
||||
}
|
||||
|
||||
// exactly 100%
|
||||
if (!isRepeat) {
|
||||
pressesAtMax += 1
|
||||
unlockReset.restart()
|
||||
}
|
||||
|
||||
if (pressesAtMax >= unlockPresses) {
|
||||
pressesAtMax = 0
|
||||
audio.volume = audio.volume + step
|
||||
return
|
||||
}
|
||||
|
||||
if (isRepeat && shown) { // holding the key at 100% : just keep the warning up
|
||||
hideTimer.restart()
|
||||
return
|
||||
}
|
||||
|
||||
const remaining = unlockPresses - pressesAtMax
|
||||
show("volume", sink, "volume-max", `press ${remaining} more to go past 100%`)
|
||||
}
|
||||
|
||||
// sound effects : names from $DOTWRYN/config/sfx.map, files in DESKTOP__SFX_PATH
|
||||
property string sfxPath: ""
|
||||
property var sfxFiles: ({})
|
||||
|
||||
// default devices must be tracked for their volume / mute to update
|
||||
PwObjectTracker { objects: [osd.sink, osd.source] }
|
||||
|
||||
Process { // DESKTOP__SFX_PATH lives in the scwrypts environment; read it once
|
||||
running: true
|
||||
command: ["scwrypts", "--group", "scwrypts", "--type", "zsh", "--name", "scwrypts/environment/getenv", "--", "DESKTOP__SFX_PATH"]
|
||||
environment: ({ SCWRYPTS_LOG_LEVEL: "0" })
|
||||
stdout: StdioCollector {
|
||||
onStreamFinished: osd.sfxPath = this.text.trim().replace(/^\$HOME|^~/, Quickshell.env("HOME"))
|
||||
}
|
||||
}
|
||||
|
||||
FileView {
|
||||
path: Quickshell.env("DOTWRYN") + "/config/sfx.map"
|
||||
watchChanges: true
|
||||
onFileChanged: reload()
|
||||
onLoaded: {
|
||||
const files = {}
|
||||
for (const line of text().split("\n")) {
|
||||
const [name, file] = line.trim().split(/\s+/)
|
||||
if (name && file && !name.startsWith("#")) files[name] = file
|
||||
}
|
||||
osd.sfxFiles = files
|
||||
}
|
||||
}
|
||||
|
||||
function playSfx(name: string): void {
|
||||
if (sfxPath === "" || !sfxFiles[name]) return
|
||||
Quickshell.execDetached(["canberra-gtk-play", "-f", `${sfxPath}/${sfxFiles[name]}`])
|
||||
}
|
||||
|
||||
function show(label: string, node: PwNode, sfx: string, warning: string): void {
|
||||
if (!node?.audio) return
|
||||
osd.warning = warning ?? ""
|
||||
osd.label = label
|
||||
osd.level = node.audio.volume
|
||||
osd.muted = node.audio.muted
|
||||
osd.shown = true
|
||||
hideTimer.restart()
|
||||
playSfx(sfx)
|
||||
}
|
||||
|
||||
// ignore the initial values reported while the devices bind at startup
|
||||
property bool armed: false
|
||||
Timer { running: true; interval: 1500; onTriggered: osd.armed = true }
|
||||
|
||||
Connections {
|
||||
target: osd.sink?.audio ?? null
|
||||
ignoreUnknownSignals: true
|
||||
function onVolumeChanged() { if (osd.armed) osd.show("volume", osd.sink, "volume", "") }
|
||||
function onMutedChanged() { if (osd.armed) osd.show("volume", osd.sink, "mute", "") }
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: osd.source?.audio ?? null
|
||||
ignoreUnknownSignals: true
|
||||
function onVolumeChanged() { if (osd.armed) osd.show("mic", osd.source, "volume", "") }
|
||||
function onMutedChanged() { if (osd.armed) osd.show("mic", osd.source, "mute", "") }
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: hideTimer
|
||||
interval: 1500
|
||||
onTriggered: osd.shown = false
|
||||
}
|
||||
|
||||
PanelWindow {
|
||||
visible: osd.shown
|
||||
|
||||
anchors.bottom: true
|
||||
margins.bottom: 120
|
||||
exclusiveZone: 0
|
||||
|
||||
readonly property bool boosted: osd.level > 1.005
|
||||
|
||||
implicitWidth: 400
|
||||
implicitHeight: osd.warning !== "" ? 84 : 56
|
||||
color: "transparent"
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
color: Colors.background
|
||||
border.width: 2
|
||||
border.color: osd.muted || parent.boosted || osd.warning !== "" ? Colors.alert : Colors.secondary
|
||||
|
||||
TextMetrics {
|
||||
id: percentSlot
|
||||
font.family: Colors.font
|
||||
font.pointSize: Colors.fontSize
|
||||
font.bold: true
|
||||
text: "1000%"
|
||||
}
|
||||
|
||||
Text { // past-100% warning; sits above the row so the row never moves
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.bottom: row.top
|
||||
anchors.bottomMargin: 8
|
||||
visible: osd.warning !== ""
|
||||
text: osd.warning
|
||||
color: Colors.alert
|
||||
font.family: Colors.font
|
||||
font.pointSize: Colors.fontSize - 1
|
||||
font.bold: true
|
||||
}
|
||||
|
||||
Row {
|
||||
id: row
|
||||
|
||||
// pinned to the bottom edge : the popup is anchored to the screen
|
||||
// bottom, so it grows upward for the warning and the bar stays put
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.bottomMargin: 18
|
||||
spacing: 14
|
||||
|
||||
Text {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
width: 64
|
||||
text: osd.muted ? `${osd.label} \u2298` : osd.label
|
||||
color: osd.muted ? Colors.alert : Colors.foreground
|
||||
font.family: Colors.font
|
||||
font.pointSize: Colors.fontSize
|
||||
font.bold: true
|
||||
}
|
||||
|
||||
Rectangle { // level bar
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
width: 200
|
||||
height: 10
|
||||
color: Colors.disabled
|
||||
|
||||
Rectangle {
|
||||
width: parent.width * Math.min(osd.level, 1)
|
||||
height: parent.height
|
||||
color: osd.muted || osd.level > 1.005 ? Colors.alert : Colors.secondary
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
width: percentSlot.advanceWidth
|
||||
horizontalAlignment: Text.AlignRight
|
||||
text: `${Math.round(osd.level * 100)}%`
|
||||
color: osd.level > 1.005 ? Colors.alert : Colors.foreground
|
||||
font.family: Colors.font
|
||||
font.pointSize: Colors.fontSize
|
||||
font.bold: true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user