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:
@@ -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]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user