1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2026-01-24 21:42:51 -05:00

audio: some cleanups to audio service

This commit is contained in:
bbedward
2025-07-17 09:08:59 -04:00
parent 6484ee0806
commit 944e0fe129
2 changed files with 130 additions and 167 deletions

View File

@@ -15,116 +15,139 @@ Singleton {
readonly property real volumeLevel: (sink?.audio?.volume ?? 0) * 100 readonly property real volumeLevel: (sink?.audio?.volume ?? 0) * 100
readonly property real micLevel: (source?.audio?.volume ?? 0) * 100 readonly property real micLevel: (source?.audio?.volume ?? 0) * 100
property ListModel audioSinksModel: ListModel {}
property ListModel audioSourcesModel: ListModel {}
property var audioSinks: [] property var audioSinks: []
property var audioSources: [] property var audioSources: []
property bool _refreshQueued: false Component.onCompleted: _rebuildModels()
Component.onCompleted: {
deferRefresh()
}
function deferRefresh() {
if (_refreshQueued) return
_refreshQueued = true
Qt.callLater(function () {
_refreshQueued = false
updateDevices()
})
}
function updateDevices() {
updateAudioSinks()
updateAudioSources()
}
Connections { Connections {
target: Pipewire target: Pipewire
function onReadyChanged() { function onReadyChanged() { _rebuildModels() }
if (Pipewire.ready) deferRefresh() function onDefaultAudioSinkChanged() { _rebuildModels() }
} function onDefaultAudioSourceChanged() { _rebuildModels() }
function onDefaultAudioSinkChanged() { function onNodeAdded() { _rebuildModels() }
deferRefresh() function onNodeRemoved() { _rebuildModels() }
}
function onDefaultAudioSourceChanged() {
deferRefresh()
}
} }
// Timer to check for node changes since ObjectModel doesn't expose change signals
Timer { Timer {
interval: 2000 interval: 2000
running: Pipewire.ready running: Pipewire.ready
repeat: true repeat: true
onTriggered: { onTriggered: _checkForNodeChanges()
if (Pipewire.nodes && Pipewire.nodes.values) { }
let currentCount = Pipewire.nodes.values.length
if (currentCount !== lastNodeCount) { property int _lastNodeCount: 0
lastNodeCount = currentCount
deferRefresh() function _checkForNodeChanges() {
} if (Pipewire.nodes?.values) {
let currentCount = Pipewire.nodes.values.length
if (currentCount !== _lastNodeCount) {
_lastNodeCount = currentCount
_rebuildModels()
} }
} }
} }
property int lastNodeCount: 0 readonly property string currentAudioSink: sink?.name ?? ""
readonly property string currentAudioSource: source?.name ?? ""
function updateAudioSinks() { readonly property string currentSinkDisplayName: {
if (!Pipewire.ready || !Pipewire.nodes) return if (!sink) return ""
for (let i = 0; i < audioSinksModel.count; i++) {
let item = audioSinksModel.get(i)
if (item.node === sink) {
return item.displayName
}
}
return _displayName(sink)
}
readonly property string currentSourceDisplayName: {
if (!source) return ""
for (let i = 0; i < audioSourcesModel.count; i++) {
let item = audioSourcesModel.get(i)
if (item.node === source) {
return item.displayName
}
}
return _displayName(source)
}
function setVolume(percentage) {
if (sink?.audio) {
sink.audio.muted = false
sink.audio.volume = percentage / 100
}
}
function setMicLevel(percentage) {
if (source?.audio) {
source.audio.muted = false
source.audio.volume = percentage / 100
}
}
function toggleMute() {
if (sink?.audio) {
sink.audio.muted = !sink.audio.muted
}
}
function toggleMicMute() {
if (source?.audio) {
source.audio.muted = !source.audio.muted
}
}
function setAudioSink(sinkName) {
_setPreferred(sinkName, PwNodeType.AudioSink)
}
function setAudioSource(sourceName) {
_setPreferred(sourceName, PwNodeType.AudioSource)
}
function _rebuildModels() {
audioSinksModel.clear()
audioSourcesModel.clear()
let sinks = [] let sinks = []
if (Pipewire.nodes.values) {
for (let i = 0; i < Pipewire.nodes.values.length; i++) {
let node = Pipewire.nodes.values[i]
if (!node) continue
if ((node.type & PwNodeType.AudioSink) === PwNodeType.AudioSink && !node.isStream) {
let displayName = getDisplayName(node)
sinks.push({
id: node.id.toString(),
name: node.name,
displayName: displayName,
subtitle: getDeviceSubtitle(node.name),
active: node === root.sink,
node: node
})
}
}
}
audioSinks = sinks
}
function updateAudioSources() {
if (!Pipewire.ready || !Pipewire.nodes) return
let sources = [] let sources = []
if (Pipewire.nodes.values) { if (!Pipewire.ready || !Pipewire.nodes?.values) return
for (let i = 0; i < Pipewire.nodes.values.length; i++) {
let node = Pipewire.nodes.values[i] for (let i = 0; i < Pipewire.nodes.values.length; i++) {
if (!node) continue let node = Pipewire.nodes.values[i]
if (!node || node.isStream) continue
if ((node.type & PwNodeType.AudioSource) === PwNodeType.AudioSource && !node.isStream && !node.name.includes('.monitor')) {
sources.push({ let entry = {
id: node.id.toString(), id: node.id.toString(),
name: node.name, name: node.name,
displayName: getDisplayName(node), displayName: _displayName(node),
subtitle: getDeviceSubtitle(node.name), subtitle: _subtitle(node.name),
active: node === root.source, active: node === sink || node === source,
node: node node: node
}) }
}
if ((node.type & PwNodeType.AudioSink) === PwNodeType.AudioSink) {
audioSinksModel.append(entry)
sinks.push(entry)
}
if ((node.type & PwNodeType.AudioSource) === PwNodeType.AudioSource && !node.name.includes(".monitor")) {
audioSourcesModel.append(entry)
sources.push(entry)
} }
} }
audioSinks = sinks
audioSources = sources audioSources = sources
} }
function getDisplayName(node) { function _displayName(node) {
// Check properties first (this is key for Bluetooth devices!) if (node.properties?.["device.description"]) {
if (node.properties && node.properties["device.description"]) {
return node.properties["device.description"] return node.properties["device.description"]
} }
@@ -136,7 +159,6 @@ Singleton {
return node.nickname return node.nickname
} }
// Fallback to name processing
if (node.name.includes("analog-stereo")) return "Built-in Speakers" if (node.name.includes("analog-stereo")) return "Built-in Speakers"
else if (node.name.includes("bluez")) return "Bluetooth Audio" else if (node.name.includes("bluez")) return "Bluetooth Audio"
else if (node.name.includes("usb")) return "USB Audio" else if (node.name.includes("usb")) return "USB Audio"
@@ -145,104 +167,49 @@ Singleton {
return node.name return node.name
} }
function getDeviceSubtitle(nodeName) { function _subtitle(name) {
if (!nodeName) return "" if (!name) return ""
// Simple subtitle based on node name patterns if (name.includes('usb-')) {
if (nodeName.includes('usb-')) { if (name.includes('SteelSeries')) {
if (nodeName.includes('SteelSeries')) {
return "USB Gaming Headset" return "USB Gaming Headset"
} else if (nodeName.includes('Generic')) { } else if (name.includes('Generic')) {
return "USB Audio Device" return "USB Audio Device"
} }
return "USB Audio" return "USB Audio"
} else if (nodeName.includes('pci-')) { } else if (name.includes('pci-')) {
if (nodeName.includes('01_00.1') || nodeName.includes('01:00.1')) { if (name.includes('01_00.1') || name.includes('01:00.1')) {
return "NVIDIA GPU Audio" return "NVIDIA GPU Audio"
} }
return "PCI Audio" return "PCI Audio"
} else if (nodeName.includes('bluez')) { } else if (name.includes('bluez')) {
return "Bluetooth Audio" return "Bluetooth Audio"
} else if (nodeName.includes('analog')) { } else if (name.includes('analog')) {
return "Built-in Audio" return "Built-in Audio"
} else if (name.includes('hdmi')) {
return "HDMI Audio"
} }
return "" return ""
} }
readonly property string currentAudioSink: sink?.name ?? "" function _setPreferred(name, kind) {
readonly property string currentAudioSource: source?.name ?? ""
readonly property string currentSinkDisplayName: {
if (!sink) return ""
for (let sinkInfo of audioSinks) {
if (sinkInfo.node === sink) {
return sinkInfo.displayName
}
}
return sink.description || sink.name
}
readonly property string currentSourceDisplayName: {
if (!source) return ""
for (let sourceInfo of audioSources) {
if (sourceInfo.node === source) {
return sourceInfo.displayName
}
}
return source.description || source.name
}
function setVolume(percentage) {
if (!sink?.ready || !sink?.audio) return
sink.audio.muted = false
sink.audio.volume = percentage / 100
}
function setMicLevel(percentage) {
if (!source?.ready || !source?.audio) return
source.audio.muted = false
source.audio.volume = percentage / 100
}
function toggleMute() {
if (!sink?.ready || !sink?.audio) return
sink.audio.muted = !sink.audio.muted
}
function toggleMicMute() {
if (!source?.ready || !source?.audio) return
source.audio.muted = !source.audio.muted
}
function setAudioSink(sinkName) {
if (!Pipewire.nodes?.values) return if (!Pipewire.nodes?.values) return
for (let i = 0; i < Pipewire.nodes.values.length; i++) { for (let i = 0; i < Pipewire.nodes.values.length; i++) {
let node = Pipewire.nodes.values[i] let node = Pipewire.nodes.values[i]
if (node && node.name === sinkName && (node.type & PwNodeType.AudioSink) === PwNodeType.AudioSink && !node.isStream) { if (node && node.name === name && !node.isStream && ((node.type & kind) === kind)) {
Pipewire.preferredDefaultAudioSink = node if (kind === PwNodeType.AudioSink) {
break Pipewire.preferredDefaultAudioSink = node
} } else if (kind === PwNodeType.AudioSource) {
} Pipewire.preferredDefaultAudioSource = node
} }
function setAudioSource(sourceName) {
if (!Pipewire.nodes?.values) return
for (let i = 0; i < Pipewire.nodes.values.length; i++) {
let node = Pipewire.nodes.values[i]
if (node && node.name === sourceName && (node.type & PwNodeType.AudioSource) === PwNodeType.AudioSource && !node.isStream) {
Pipewire.preferredDefaultAudioSource = node
break break
} }
} }
} }
PwObjectTracker { PwObjectTracker {
id: nodeTracker
objects: [Pipewire.defaultAudioSink, Pipewire.defaultAudioSource] objects: [Pipewire.defaultAudioSink, Pipewire.defaultAudioSource]
} }
} }

View File

@@ -49,10 +49,6 @@ ShellRoot {
onControlCenterVisibleChanged: { onControlCenterVisibleChanged: {
console.log("Control center", controlCenterVisible ? "opened" : "closed") console.log("Control center", controlCenterVisible ? "opened" : "closed")
BluetoothService.enableMonitoring(controlCenterVisible) BluetoothService.enableMonitoring(controlCenterVisible)
if (controlCenterVisible) {
// Refresh devices when opening control center
AudioService.updateDevices()
}
} }
property bool batteryPopupVisible: false property bool batteryPopupVisible: false
property bool powerMenuVisible: false property bool powerMenuVisible: false