mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-01-24 21:42:51 -05:00
Use integrated pipewire from quickshell
This commit is contained in:
@@ -68,7 +68,6 @@ Singleton {
|
|||||||
|
|
||||||
function parseSettings(content) {
|
function parseSettings(content) {
|
||||||
try {
|
try {
|
||||||
console.log("Settings file content:", content)
|
|
||||||
if (content && content.trim()) {
|
if (content && content.trim()) {
|
||||||
var settings = JSON.parse(content)
|
var settings = JSON.parse(content)
|
||||||
themeIndex = settings.themeIndex !== undefined ? settings.themeIndex : 0
|
themeIndex = settings.themeIndex !== undefined ? settings.themeIndex : 0
|
||||||
|
|||||||
@@ -1,332 +1,264 @@
|
|||||||
import QtQuick
|
import QtQuick
|
||||||
import Quickshell
|
import Quickshell
|
||||||
import Quickshell.Io
|
import Quickshell.Services.Pipewire
|
||||||
pragma Singleton
|
pragma Singleton
|
||||||
pragma ComponentBehavior: Bound
|
pragma ComponentBehavior: Bound
|
||||||
|
|
||||||
Singleton {
|
Singleton {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
|
readonly property PwNode sink: Pipewire.defaultAudioSink
|
||||||
|
readonly property PwNode source: Pipewire.defaultAudioSource
|
||||||
|
|
||||||
|
readonly property bool sinkMuted: sink?.audio?.muted ?? false
|
||||||
|
readonly property bool sourceMuted: source?.audio?.muted ?? false
|
||||||
|
readonly property real volumeLevel: (sink?.audio?.volume ?? 0) * 100
|
||||||
|
readonly property real micLevel: (source?.audio?.volume ?? 0) * 100
|
||||||
|
|
||||||
|
signal audioVolumeChanged(real volume)
|
||||||
|
signal audioMicLevelChanged(real level)
|
||||||
|
signal audioMuteChanged(bool muted)
|
||||||
|
signal audioMicMuteChanged(bool muted)
|
||||||
|
signal audioDeviceChanged()
|
||||||
|
|
||||||
property int volumeLevel: 50
|
onVolumeLevelChanged: audioVolumeChanged(volumeLevel)
|
||||||
|
onMicLevelChanged: audioMicLevelChanged(micLevel)
|
||||||
|
onSinkMutedChanged: audioMuteChanged(sinkMuted)
|
||||||
|
onSourceMutedChanged: audioMicMuteChanged(sourceMuted)
|
||||||
|
onSinkChanged: {
|
||||||
|
audioDeviceChanged()
|
||||||
|
}
|
||||||
|
onSourceChanged: {
|
||||||
|
audioDeviceChanged()
|
||||||
|
}
|
||||||
|
|
||||||
property var audioSinks: []
|
property var audioSinks: []
|
||||||
property string currentAudioSink: ""
|
|
||||||
|
|
||||||
property int micLevel: 50
|
|
||||||
property var audioSources: []
|
property var audioSources: []
|
||||||
property string currentAudioSource: ""
|
|
||||||
|
Component.onCompleted: {
|
||||||
property bool deviceScanningEnabled: false
|
Qt.callLater(updateDevices)
|
||||||
property bool initialScanComplete: false
|
}
|
||||||
Process {
|
|
||||||
id: volumeChecker
|
function updateDevices() {
|
||||||
command: ["bash", "-c", "pactl get-sink-volume @DEFAULT_SINK@ | grep -o '[0-9]*%' | head -1 | tr -d '%'"]
|
updateAudioSinks()
|
||||||
running: true
|
updateAudioSources()
|
||||||
|
}
|
||||||
stdout: SplitParser {
|
|
||||||
splitMarker: "\n"
|
Connections {
|
||||||
onRead: (data) => {
|
target: Pipewire
|
||||||
if (data.trim()) {
|
function onReadyChanged() {
|
||||||
root.volumeLevel = Math.min(100, parseInt(data.trim()) || 50)
|
if (Pipewire.ready) {
|
||||||
}
|
updateAudioSinks()
|
||||||
|
updateAudioSources()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
function onDefaultAudioSinkChanged() {
|
||||||
|
updateAudioSinks()
|
||||||
Process {
|
}
|
||||||
id: micLevelChecker
|
function onDefaultAudioSourceChanged() {
|
||||||
command: ["bash", "-c", "pactl get-source-volume @DEFAULT_SOURCE@ | grep -o '[0-9]*%' | head -1 | tr -d '%'"]
|
updateAudioSources()
|
||||||
running: true
|
|
||||||
|
|
||||||
stdout: SplitParser {
|
|
||||||
splitMarker: "\n"
|
|
||||||
onRead: (data) => {
|
|
||||||
if (data.trim()) {
|
|
||||||
root.micLevel = Math.min(100, parseInt(data.trim()) || 50)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Process {
|
// Timer to check for node changes since ObjectModel doesn't expose change signals
|
||||||
id: audioSinkLister
|
|
||||||
command: ["pactl", "list", "sinks"]
|
|
||||||
running: false
|
|
||||||
|
|
||||||
stdout: StdioCollector {
|
|
||||||
onStreamFinished: {
|
|
||||||
if (text.trim()) {
|
|
||||||
let sinks = []
|
|
||||||
let lines = text.trim().split('\n')
|
|
||||||
|
|
||||||
let currentSink = null
|
|
||||||
|
|
||||||
for (let line of lines) {
|
|
||||||
line = line.trim()
|
|
||||||
|
|
||||||
if (line.startsWith('Sink #')) {
|
|
||||||
if (currentSink && currentSink.name && currentSink.id) {
|
|
||||||
sinks.push(currentSink)
|
|
||||||
}
|
|
||||||
|
|
||||||
let sinkId = line.replace('Sink #', '').trim()
|
|
||||||
currentSink = {
|
|
||||||
id: sinkId,
|
|
||||||
name: "",
|
|
||||||
displayName: "",
|
|
||||||
description: "",
|
|
||||||
nick: "",
|
|
||||||
active: false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (line.startsWith('Name: ') && currentSink) {
|
|
||||||
currentSink.name = line.replace('Name: ', '').trim()
|
|
||||||
}
|
|
||||||
else if (line.startsWith('Description: ') && currentSink) {
|
|
||||||
currentSink.description = line.replace('Description: ', '').trim()
|
|
||||||
}
|
|
||||||
else if (line.includes('device.description = ') && currentSink && !currentSink.description) {
|
|
||||||
currentSink.description = line.replace('device.description = ', '').replace(/"/g, '').trim()
|
|
||||||
}
|
|
||||||
else if (line.includes('node.nick = ') && currentSink && !currentSink.description) {
|
|
||||||
currentSink.nick = line.replace('node.nick = ', '').replace(/"/g, '').trim()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (currentSink && currentSink.name && currentSink.id) {
|
|
||||||
sinks.push(currentSink)
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let sink of sinks) {
|
|
||||||
let displayName = sink.description
|
|
||||||
|
|
||||||
if (!displayName || displayName === sink.name) {
|
|
||||||
displayName = sink.nick
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!displayName || displayName === sink.name) {
|
|
||||||
if (sink.name.includes("analog-stereo")) displayName = "Built-in Speakers"
|
|
||||||
else if (sink.name.includes("bluez")) displayName = "Bluetooth Audio"
|
|
||||||
else if (sink.name.includes("usb")) displayName = "USB Audio"
|
|
||||||
else if (sink.name.includes("hdmi")) displayName = "HDMI Audio"
|
|
||||||
else if (sink.name.includes("easyeffects")) displayName = "EasyEffects"
|
|
||||||
else displayName = sink.name
|
|
||||||
}
|
|
||||||
|
|
||||||
sink.displayName = displayName
|
|
||||||
}
|
|
||||||
|
|
||||||
root.audioSinks = sinks
|
|
||||||
defaultSinkChecker.running = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Process {
|
|
||||||
id: audioSourceLister
|
|
||||||
command: ["pactl", "list", "sources"]
|
|
||||||
running: false
|
|
||||||
|
|
||||||
stdout: StdioCollector {
|
|
||||||
onStreamFinished: {
|
|
||||||
if (text.trim()) {
|
|
||||||
let sources = []
|
|
||||||
let lines = text.trim().split('\n')
|
|
||||||
|
|
||||||
let currentSource = null
|
|
||||||
|
|
||||||
for (let line of lines) {
|
|
||||||
line = line.trim()
|
|
||||||
|
|
||||||
if (line.startsWith('Source #')) {
|
|
||||||
if (currentSource && currentSource.name && currentSource.id) {
|
|
||||||
sources.push(currentSource)
|
|
||||||
}
|
|
||||||
currentSource = {
|
|
||||||
id: line.replace('Source #', '').replace(':', ''),
|
|
||||||
name: '',
|
|
||||||
displayName: '',
|
|
||||||
active: false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (line.startsWith('Name: ') && currentSource) {
|
|
||||||
currentSource.name = line.replace('Name: ', '')
|
|
||||||
}
|
|
||||||
else if (line.startsWith('Description: ') && currentSource) {
|
|
||||||
let desc = line.replace('Description: ', '')
|
|
||||||
currentSource.displayName = desc
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (currentSource && currentSource.name && currentSource.id) {
|
|
||||||
sources.push(currentSource)
|
|
||||||
}
|
|
||||||
|
|
||||||
sources = sources.filter(source => !source.name.includes('.monitor'))
|
|
||||||
|
|
||||||
root.audioSources = sources
|
|
||||||
defaultSourceChecker.running = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Process {
|
|
||||||
id: defaultSinkChecker
|
|
||||||
command: ["pactl", "get-default-sink"]
|
|
||||||
running: false
|
|
||||||
|
|
||||||
stdout: SplitParser {
|
|
||||||
splitMarker: "\n"
|
|
||||||
onRead: (data) => {
|
|
||||||
if (data.trim()) {
|
|
||||||
root.currentAudioSink = data.trim()
|
|
||||||
|
|
||||||
let updatedSinks = []
|
|
||||||
for (let sink of root.audioSinks) {
|
|
||||||
updatedSinks.push({
|
|
||||||
id: sink.id,
|
|
||||||
name: sink.name,
|
|
||||||
displayName: sink.displayName,
|
|
||||||
active: sink.name === root.currentAudioSink
|
|
||||||
})
|
|
||||||
}
|
|
||||||
root.audioSinks = updatedSinks
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Process {
|
|
||||||
id: defaultSourceChecker
|
|
||||||
command: ["pactl", "get-default-source"]
|
|
||||||
running: false
|
|
||||||
|
|
||||||
stdout: SplitParser {
|
|
||||||
splitMarker: "\n"
|
|
||||||
onRead: (data) => {
|
|
||||||
if (data.trim()) {
|
|
||||||
root.currentAudioSource = data.trim()
|
|
||||||
|
|
||||||
let updatedSources = []
|
|
||||||
for (let source of root.audioSources) {
|
|
||||||
updatedSources.push({
|
|
||||||
id: source.id,
|
|
||||||
name: source.name,
|
|
||||||
displayName: source.displayName,
|
|
||||||
active: source.name === root.currentAudioSource
|
|
||||||
})
|
|
||||||
}
|
|
||||||
root.audioSources = updatedSources
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function setVolume(percentage) {
|
|
||||||
let volumeSetProcess = Qt.createQmlObject('
|
|
||||||
import Quickshell.Io
|
|
||||||
Process {
|
|
||||||
command: ["pactl", "set-sink-volume", "@DEFAULT_SINK@", "' + percentage + '%"]
|
|
||||||
running: true
|
|
||||||
onExited: volumeChecker.running = true
|
|
||||||
}
|
|
||||||
', root)
|
|
||||||
}
|
|
||||||
|
|
||||||
function setMicLevel(percentage) {
|
|
||||||
let micSetProcess = Qt.createQmlObject('
|
|
||||||
import Quickshell.Io
|
|
||||||
Process {
|
|
||||||
command: ["pactl", "set-source-volume", "@DEFAULT_SOURCE@", "' + percentage + '%"]
|
|
||||||
running: true
|
|
||||||
onExited: micLevelChecker.running = true
|
|
||||||
}
|
|
||||||
', root)
|
|
||||||
}
|
|
||||||
|
|
||||||
function setAudioSink(sinkName) {
|
|
||||||
console.log("Setting audio sink to:", sinkName)
|
|
||||||
|
|
||||||
sinkSetProcess.command = ["pactl", "set-default-sink", sinkName]
|
|
||||||
sinkSetProcess.running = true
|
|
||||||
}
|
|
||||||
|
|
||||||
Process {
|
|
||||||
id: sinkSetProcess
|
|
||||||
running: false
|
|
||||||
|
|
||||||
onExited: (exitCode) => {
|
|
||||||
console.log("Audio sink change exit code:", exitCode)
|
|
||||||
if (exitCode === 0) {
|
|
||||||
console.log("Audio sink changed successfully")
|
|
||||||
defaultSinkChecker.running = true
|
|
||||||
if (root.deviceScanningEnabled) {
|
|
||||||
audioSinkLister.running = true
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
console.error("Failed to change audio sink")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function setAudioSource(sourceName) {
|
|
||||||
console.log("Setting audio source to:", sourceName)
|
|
||||||
|
|
||||||
sourceSetProcess.command = ["pactl", "set-default-source", sourceName]
|
|
||||||
sourceSetProcess.running = true
|
|
||||||
}
|
|
||||||
|
|
||||||
Process {
|
|
||||||
id: sourceSetProcess
|
|
||||||
running: false
|
|
||||||
|
|
||||||
onExited: (exitCode) => {
|
|
||||||
console.log("Audio source change exit code:", exitCode)
|
|
||||||
if (exitCode === 0) {
|
|
||||||
console.log("Audio source changed successfully")
|
|
||||||
defaultSourceChecker.running = true
|
|
||||||
if (root.deviceScanningEnabled) {
|
|
||||||
audioSourceLister.running = true
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
console.error("Failed to change audio source")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Timer {
|
Timer {
|
||||||
interval: 5000
|
interval: 2000
|
||||||
running: root.deviceScanningEnabled && root.initialScanComplete
|
running: Pipewire.ready
|
||||||
repeat: true
|
repeat: true
|
||||||
onTriggered: {
|
onTriggered: {
|
||||||
if (root.deviceScanningEnabled) {
|
if (Pipewire.nodes && Pipewire.nodes.values) {
|
||||||
audioSinkLister.running = true
|
let currentCount = Pipewire.nodes.values.length
|
||||||
audioSourceLister.running = true
|
if (currentCount !== lastNodeCount) {
|
||||||
|
lastNodeCount = currentCount
|
||||||
|
updateAudioSinks()
|
||||||
|
updateAudioSources()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
property int lastNodeCount: 0
|
||||||
|
|
||||||
|
function updateAudioSinks() {
|
||||||
|
if (!Pipewire.ready || !Pipewire.nodes) return
|
||||||
|
|
||||||
|
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 = []
|
||||||
|
|
||||||
|
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.AudioSource) === PwNodeType.AudioSource && !node.isStream && !node.name.includes('.monitor')) {
|
||||||
|
sources.push({
|
||||||
|
id: node.id.toString(),
|
||||||
|
name: node.name,
|
||||||
|
displayName: getDisplayName(node),
|
||||||
|
subtitle: getDeviceSubtitle(node.name),
|
||||||
|
active: node === root.source,
|
||||||
|
node: node
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
audioSources = sources
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDisplayName(node) {
|
||||||
|
// Check properties first (this is key for Bluetooth devices!)
|
||||||
|
if (node.properties && node.properties["device.description"]) {
|
||||||
|
return node.properties["device.description"]
|
||||||
|
}
|
||||||
|
|
||||||
|
if (node.description && node.description !== node.name) {
|
||||||
|
return node.description
|
||||||
|
}
|
||||||
|
|
||||||
|
if (node.nickname && node.nickname !== node.name) {
|
||||||
|
return node.nickname
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback to name processing
|
||||||
|
if (node.name.includes("analog-stereo")) return "Built-in Speakers"
|
||||||
|
else if (node.name.includes("bluez")) return "Bluetooth Audio"
|
||||||
|
else if (node.name.includes("usb")) return "USB Audio"
|
||||||
|
else if (node.name.includes("hdmi")) return "HDMI Audio"
|
||||||
|
|
||||||
|
return node.name
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDeviceSubtitle(nodeName) {
|
||||||
|
if (!nodeName) return ""
|
||||||
|
|
||||||
|
// Simple subtitle based on node name patterns
|
||||||
|
if (nodeName.includes('usb-')) {
|
||||||
|
if (nodeName.includes('SteelSeries')) {
|
||||||
|
return "USB Gaming Headset"
|
||||||
|
} else if (nodeName.includes('Generic')) {
|
||||||
|
return "USB Audio Device"
|
||||||
|
}
|
||||||
|
return "USB Audio"
|
||||||
|
} else if (nodeName.includes('pci-')) {
|
||||||
|
if (nodeName.includes('01_00.1') || nodeName.includes('01:00.1')) {
|
||||||
|
return "NVIDIA GPU Audio"
|
||||||
|
}
|
||||||
|
return "PCI Audio"
|
||||||
|
} else if (nodeName.includes('bluez')) {
|
||||||
|
return "Bluetooth Audio"
|
||||||
|
} else if (nodeName.includes('analog')) {
|
||||||
|
return "Built-in Audio"
|
||||||
|
}
|
||||||
|
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
readonly property string currentAudioSink: sink?.name ?? ""
|
||||||
|
readonly property string currentAudioSource: source?.name ?? ""
|
||||||
|
|
||||||
Component.onCompleted: {
|
readonly property string currentSinkDisplayName: {
|
||||||
console.log("AudioService: Starting initialization...")
|
if (!sink) return ""
|
||||||
audioSinkLister.running = true
|
|
||||||
audioSourceLister.running = true
|
for (let sinkInfo of audioSinks) {
|
||||||
initialScanComplete = true
|
if (sinkInfo.node === sink) {
|
||||||
console.log("AudioService: Initialization complete")
|
return sinkInfo.displayName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return sink.description || sink.name
|
||||||
}
|
}
|
||||||
|
|
||||||
function enableDeviceScanning(enabled) {
|
readonly property string currentSourceDisplayName: {
|
||||||
console.log("AudioService: Device scanning", enabled ? "enabled" : "disabled")
|
if (!source) return ""
|
||||||
root.deviceScanningEnabled = enabled
|
|
||||||
if (enabled && root.initialScanComplete) {
|
for (let sourceInfo of audioSources) {
|
||||||
audioSinkLister.running = true
|
if (sourceInfo.node === source) {
|
||||||
audioSourceLister.running = true
|
return sourceInfo.displayName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return source.description || source.name
|
||||||
|
}
|
||||||
|
|
||||||
|
function setVolume(percentage) {
|
||||||
|
if (sink?.ready && sink?.audio) {
|
||||||
|
sink.audio.muted = false
|
||||||
|
sink.audio.volume = percentage / 100
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function refreshDevices() {
|
function setMicLevel(percentage) {
|
||||||
console.log("AudioService: Manual device refresh triggered")
|
if (source?.ready && source?.audio) {
|
||||||
audioSinkLister.running = true
|
source.audio.muted = false
|
||||||
audioSourceLister.running = true
|
source.audio.volume = percentage / 100
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleMute() {
|
||||||
|
if (sink?.ready && sink?.audio) {
|
||||||
|
sink.audio.muted = !sink.audio.muted
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleMicMute() {
|
||||||
|
if (source?.ready && source?.audio) {
|
||||||
|
source.audio.muted = !source.audio.muted
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function setAudioSink(sinkName) {
|
||||||
|
if (Pipewire.nodes.values) {
|
||||||
|
for (let i = 0; i < Pipewire.nodes.values.length; i++) {
|
||||||
|
let node = Pipewire.nodes.values[i]
|
||||||
|
if (node && node.name === sinkName && (node.type & PwNodeType.AudioSink) === PwNodeType.AudioSink && !node.isStream) {
|
||||||
|
Pipewire.preferredDefaultAudioSink = node
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function setAudioSource(sourceName) {
|
||||||
|
if (Pipewire.nodes.values) {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PwObjectTracker {
|
||||||
|
id: nodeTracker
|
||||||
|
objects: [Pipewire.defaultAudioSink, Pipewire.defaultAudioSource]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -11,13 +11,14 @@ Item {
|
|||||||
|
|
||||||
property int audioSubTab: 0 // 0: Output, 1: Input
|
property int audioSubTab: 0 // 0: Output, 1: Input
|
||||||
|
|
||||||
// These should be bound from parent
|
readonly property real volumeLevel: AudioService.volumeLevel
|
||||||
property real volumeLevel: 50
|
readonly property real micLevel: AudioService.micLevel
|
||||||
property real micLevel: 50
|
readonly property bool volumeMuted: AudioService.sinkMuted
|
||||||
property string currentAudioSink: ""
|
readonly property bool micMuted: AudioService.sourceMuted
|
||||||
property string currentAudioSource: ""
|
readonly property string currentAudioSink: AudioService.currentAudioSink
|
||||||
property var audioSinks: []
|
readonly property string currentAudioSource: AudioService.currentAudioSource
|
||||||
property var audioSources: []
|
readonly property var audioSinks: AudioService.audioSinks
|
||||||
|
readonly property var audioSources: AudioService.audioSources
|
||||||
|
|
||||||
Column {
|
Column {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
@@ -102,11 +103,18 @@ Item {
|
|||||||
spacing: Theme.spacingM
|
spacing: Theme.spacingM
|
||||||
|
|
||||||
Text {
|
Text {
|
||||||
text: "volume_down"
|
text: audioTab.volumeMuted ? "volume_off" : "volume_down"
|
||||||
font.family: Theme.iconFont
|
font.family: Theme.iconFont
|
||||||
font.pixelSize: Theme.iconSize
|
font.pixelSize: Theme.iconSize
|
||||||
color: Theme.surfaceText
|
color: audioTab.volumeMuted ? Theme.error : Theme.surfaceText
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: AudioService.toggleMute()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
@@ -179,13 +187,6 @@ Item {
|
|||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Text {
|
|
||||||
text: audioTab.volumeLevel + "%"
|
|
||||||
font.pixelSize: Theme.fontSizeMedium
|
|
||||||
color: Theme.surfaceText
|
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Output Devices
|
// Output Devices
|
||||||
@@ -224,14 +225,7 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Text {
|
Text {
|
||||||
text: "Current: " + (function() {
|
text: "Current: " + (AudioService.currentSinkDisplayName || "None")
|
||||||
for (let sink of audioTab.audioSinks) {
|
|
||||||
if (sink.name === audioTab.currentAudioSink) {
|
|
||||||
return sink.displayName
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return audioTab.currentAudioSink
|
|
||||||
})()
|
|
||||||
font.pixelSize: Theme.fontSizeMedium
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
color: Theme.primary
|
color: Theme.primary
|
||||||
font.weight: Font.Medium
|
font.weight: Font.Medium
|
||||||
@@ -283,10 +277,16 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Text {
|
Text {
|
||||||
text: modelData.active ? "Selected" : ""
|
text: {
|
||||||
|
if (modelData.subtitle && modelData.subtitle !== "") {
|
||||||
|
return modelData.subtitle + (modelData.active ? " • Selected" : "")
|
||||||
|
} else {
|
||||||
|
return modelData.active ? "Selected" : ""
|
||||||
|
}
|
||||||
|
}
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
color: Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.8)
|
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
|
||||||
visible: modelData.active
|
visible: text !== ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -298,8 +298,6 @@ Item {
|
|||||||
cursorShape: Qt.PointingHandCursor
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
|
||||||
onClicked: {
|
onClicked: {
|
||||||
console.log("Clicked audio device:", JSON.stringify(modelData))
|
|
||||||
console.log("Device name to set:", modelData.name)
|
|
||||||
AudioService.setAudioSink(modelData.name)
|
AudioService.setAudioSink(modelData.name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -337,11 +335,18 @@ Item {
|
|||||||
spacing: Theme.spacingM
|
spacing: Theme.spacingM
|
||||||
|
|
||||||
Text {
|
Text {
|
||||||
text: "mic"
|
text: audioTab.micMuted ? "mic_off" : "mic"
|
||||||
font.family: Theme.iconFont
|
font.family: Theme.iconFont
|
||||||
font.pixelSize: Theme.iconSize
|
font.pixelSize: Theme.iconSize
|
||||||
color: Theme.surfaceText
|
color: audioTab.micMuted ? Theme.error : Theme.surfaceText
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: AudioService.toggleMicMute()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
@@ -415,12 +420,6 @@ Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Text {
|
|
||||||
text: audioTab.micLevel + "%"
|
|
||||||
font.pixelSize: Theme.fontSizeMedium
|
|
||||||
color: Theme.surfaceText
|
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Input Devices
|
// Input Devices
|
||||||
@@ -459,14 +458,7 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Text {
|
Text {
|
||||||
text: "Current: " + (function() {
|
text: "Current: " + (AudioService.currentSourceDisplayName || "None")
|
||||||
for (let source of audioTab.audioSources) {
|
|
||||||
if (source.name === audioTab.currentAudioSource) {
|
|
||||||
return source.displayName
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return audioTab.currentAudioSource
|
|
||||||
})()
|
|
||||||
font.pixelSize: Theme.fontSizeMedium
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
color: Theme.primary
|
color: Theme.primary
|
||||||
font.weight: Font.Medium
|
font.weight: Font.Medium
|
||||||
@@ -517,10 +509,16 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Text {
|
Text {
|
||||||
text: modelData.active ? "Selected" : ""
|
text: {
|
||||||
|
if (modelData.subtitle && modelData.subtitle !== "") {
|
||||||
|
return modelData.subtitle + (modelData.active ? " • Selected" : "")
|
||||||
|
} else {
|
||||||
|
return modelData.active ? "Selected" : ""
|
||||||
|
}
|
||||||
|
}
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
color: Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.8)
|
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
|
||||||
visible: modelData.active
|
visible: text !== ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -532,8 +530,6 @@ Item {
|
|||||||
cursorShape: Qt.PointingHandCursor
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
|
||||||
onClicked: {
|
onClicked: {
|
||||||
console.log("Clicked audio source:", JSON.stringify(modelData))
|
|
||||||
console.log("Source name to set:", modelData.name)
|
|
||||||
AudioService.setAudioSource(modelData.name)
|
AudioService.setAudioSource(modelData.name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -676,14 +676,6 @@ PanelWindow {
|
|||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
anchors.margins: Theme.spacingM
|
anchors.margins: Theme.spacingM
|
||||||
visible: controlCenterPopup.currentTab === "audio"
|
visible: controlCenterPopup.currentTab === "audio"
|
||||||
|
|
||||||
// Bind properties from root
|
|
||||||
volumeLevel: root.volumeLevel
|
|
||||||
micLevel: root.micLevel
|
|
||||||
currentAudioSink: root.currentAudioSink
|
|
||||||
currentAudioSource: root.currentAudioSource
|
|
||||||
audioSinks: root.audioSinks
|
|
||||||
audioSources: root.audioSources
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Bluetooth Tab
|
// Bluetooth Tab
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ Rectangle {
|
|||||||
property string networkStatus: "disconnected"
|
property string networkStatus: "disconnected"
|
||||||
property string wifiSignalStrength: "good"
|
property string wifiSignalStrength: "good"
|
||||||
property int volumeLevel: 50
|
property int volumeLevel: 50
|
||||||
|
property bool volumeMuted: false
|
||||||
property bool bluetoothAvailable: false
|
property bool bluetoothAvailable: false
|
||||||
property bool bluetoothEnabled: false
|
property bool bluetoothEnabled: false
|
||||||
property bool isActive: false
|
property bool isActive: false
|
||||||
@@ -62,7 +63,7 @@ Rectangle {
|
|||||||
|
|
||||||
// Audio Icon
|
// Audio Icon
|
||||||
Text {
|
Text {
|
||||||
text: root.volumeLevel === 0 ? "volume_off" :
|
text: root.volumeMuted ? "volume_off" :
|
||||||
root.volumeLevel < 33 ? "volume_down" : "volume_up"
|
root.volumeLevel < 33 ? "volume_down" : "volume_up"
|
||||||
font.family: Theme.iconFont
|
font.family: Theme.iconFont
|
||||||
font.pixelSize: Theme.iconSize - 8
|
font.pixelSize: Theme.iconSize - 8
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ PanelWindow {
|
|||||||
property string networkStatus: "disconnected"
|
property string networkStatus: "disconnected"
|
||||||
property string wifiSignalStrength: "good"
|
property string wifiSignalStrength: "good"
|
||||||
property int volumeLevel: 50
|
property int volumeLevel: 50
|
||||||
|
property bool volumeMuted: false
|
||||||
property bool bluetoothAvailable: false
|
property bool bluetoothAvailable: false
|
||||||
property bool bluetoothEnabled: false
|
property bool bluetoothEnabled: false
|
||||||
|
|
||||||
@@ -321,6 +322,7 @@ PanelWindow {
|
|||||||
networkStatus: topBar.networkStatus
|
networkStatus: topBar.networkStatus
|
||||||
wifiSignalStrength: topBar.wifiSignalStrength
|
wifiSignalStrength: topBar.wifiSignalStrength
|
||||||
volumeLevel: topBar.volumeLevel
|
volumeLevel: topBar.volumeLevel
|
||||||
|
volumeMuted: topBar.volumeMuted
|
||||||
bluetoothAvailable: topBar.bluetoothAvailable
|
bluetoothAvailable: topBar.bluetoothAvailable
|
||||||
bluetoothEnabled: topBar.bluetoothEnabled
|
bluetoothEnabled: topBar.bluetoothEnabled
|
||||||
isActive: topBar.shellRoot ? topBar.shellRoot.controlCenterVisible : false
|
isActive: topBar.shellRoot ? topBar.shellRoot.controlCenterVisible : false
|
||||||
|
|||||||
@@ -82,6 +82,7 @@ ShellRoot {
|
|||||||
|
|
||||||
// Audio properties from AudioService
|
// Audio properties from AudioService
|
||||||
property int volumeLevel: AudioService.volumeLevel
|
property int volumeLevel: AudioService.volumeLevel
|
||||||
|
property bool volumeMuted: AudioService.sinkMuted
|
||||||
property var audioSinks: AudioService.audioSinks
|
property var audioSinks: AudioService.audioSinks
|
||||||
property string currentAudioSink: AudioService.currentAudioSink
|
property string currentAudioSink: AudioService.currentAudioSink
|
||||||
|
|
||||||
@@ -320,6 +321,7 @@ ShellRoot {
|
|||||||
networkStatus: root.networkStatus
|
networkStatus: root.networkStatus
|
||||||
wifiSignalStrength: root.wifiSignalStrength
|
wifiSignalStrength: root.wifiSignalStrength
|
||||||
volumeLevel: root.volumeLevel
|
volumeLevel: root.volumeLevel
|
||||||
|
volumeMuted: root.volumeMuted
|
||||||
bluetoothAvailable: root.bluetoothAvailable
|
bluetoothAvailable: root.bluetoothAvailable
|
||||||
bluetoothEnabled: root.bluetoothEnabled
|
bluetoothEnabled: root.bluetoothEnabled
|
||||||
shellRoot: root
|
shellRoot: root
|
||||||
|
|||||||
Reference in New Issue
Block a user