mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2025-12-06 05:25:41 -05:00
Use integrated pipewire from quickshell
This commit is contained in:
@@ -68,7 +68,6 @@ Singleton {
|
||||
|
||||
function parseSettings(content) {
|
||||
try {
|
||||
console.log("Settings file content:", content)
|
||||
if (content && content.trim()) {
|
||||
var settings = JSON.parse(content)
|
||||
themeIndex = settings.themeIndex !== undefined ? settings.themeIndex : 0
|
||||
|
||||
@@ -1,332 +1,264 @@
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import Quickshell.Services.Pipewire
|
||||
pragma Singleton
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
Singleton {
|
||||
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 string currentAudioSink: ""
|
||||
|
||||
property int micLevel: 50
|
||||
property var audioSources: []
|
||||
property string currentAudioSource: ""
|
||||
|
||||
property bool deviceScanningEnabled: false
|
||||
property bool initialScanComplete: false
|
||||
Process {
|
||||
id: volumeChecker
|
||||
command: ["bash", "-c", "pactl get-sink-volume @DEFAULT_SINK@ | grep -o '[0-9]*%' | head -1 | tr -d '%'"]
|
||||
running: true
|
||||
|
||||
stdout: SplitParser {
|
||||
splitMarker: "\n"
|
||||
onRead: (data) => {
|
||||
if (data.trim()) {
|
||||
root.volumeLevel = Math.min(100, parseInt(data.trim()) || 50)
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
Qt.callLater(updateDevices)
|
||||
}
|
||||
|
||||
function updateDevices() {
|
||||
updateAudioSinks()
|
||||
updateAudioSources()
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: Pipewire
|
||||
function onReadyChanged() {
|
||||
if (Pipewire.ready) {
|
||||
updateAudioSinks()
|
||||
updateAudioSources()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: micLevelChecker
|
||||
command: ["bash", "-c", "pactl get-source-volume @DEFAULT_SOURCE@ | grep -o '[0-9]*%' | head -1 | tr -d '%'"]
|
||||
running: true
|
||||
|
||||
stdout: SplitParser {
|
||||
splitMarker: "\n"
|
||||
onRead: (data) => {
|
||||
if (data.trim()) {
|
||||
root.micLevel = Math.min(100, parseInt(data.trim()) || 50)
|
||||
}
|
||||
}
|
||||
function onDefaultAudioSinkChanged() {
|
||||
updateAudioSinks()
|
||||
}
|
||||
function onDefaultAudioSourceChanged() {
|
||||
updateAudioSources()
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
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 to check for node changes since ObjectModel doesn't expose change signals
|
||||
Timer {
|
||||
interval: 5000
|
||||
running: root.deviceScanningEnabled && root.initialScanComplete
|
||||
interval: 2000
|
||||
running: Pipewire.ready
|
||||
repeat: true
|
||||
onTriggered: {
|
||||
if (root.deviceScanningEnabled) {
|
||||
audioSinkLister.running = true
|
||||
audioSourceLister.running = true
|
||||
if (Pipewire.nodes && Pipewire.nodes.values) {
|
||||
let currentCount = Pipewire.nodes.values.length
|
||||
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: {
|
||||
console.log("AudioService: Starting initialization...")
|
||||
audioSinkLister.running = true
|
||||
audioSourceLister.running = true
|
||||
initialScanComplete = true
|
||||
console.log("AudioService: Initialization complete")
|
||||
readonly property string currentSinkDisplayName: {
|
||||
if (!sink) return ""
|
||||
|
||||
for (let sinkInfo of audioSinks) {
|
||||
if (sinkInfo.node === sink) {
|
||||
return sinkInfo.displayName
|
||||
}
|
||||
}
|
||||
|
||||
return sink.description || sink.name
|
||||
}
|
||||
|
||||
function enableDeviceScanning(enabled) {
|
||||
console.log("AudioService: Device scanning", enabled ? "enabled" : "disabled")
|
||||
root.deviceScanningEnabled = enabled
|
||||
if (enabled && root.initialScanComplete) {
|
||||
audioSinkLister.running = true
|
||||
audioSourceLister.running = true
|
||||
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) {
|
||||
sink.audio.muted = false
|
||||
sink.audio.volume = percentage / 100
|
||||
}
|
||||
}
|
||||
|
||||
function refreshDevices() {
|
||||
console.log("AudioService: Manual device refresh triggered")
|
||||
audioSinkLister.running = true
|
||||
audioSourceLister.running = true
|
||||
|
||||
function setMicLevel(percentage) {
|
||||
if (source?.ready && source?.audio) {
|
||||
source.audio.muted = false
|
||||
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
|
||||
|
||||
// These should be bound from parent
|
||||
property real volumeLevel: 50
|
||||
property real micLevel: 50
|
||||
property string currentAudioSink: ""
|
||||
property string currentAudioSource: ""
|
||||
property var audioSinks: []
|
||||
property var audioSources: []
|
||||
readonly property real volumeLevel: AudioService.volumeLevel
|
||||
readonly property real micLevel: AudioService.micLevel
|
||||
readonly property bool volumeMuted: AudioService.sinkMuted
|
||||
readonly property bool micMuted: AudioService.sourceMuted
|
||||
readonly property string currentAudioSink: AudioService.currentAudioSink
|
||||
readonly property string currentAudioSource: AudioService.currentAudioSource
|
||||
readonly property var audioSinks: AudioService.audioSinks
|
||||
readonly property var audioSources: AudioService.audioSources
|
||||
|
||||
Column {
|
||||
anchors.fill: parent
|
||||
@@ -102,11 +103,18 @@ Item {
|
||||
spacing: Theme.spacingM
|
||||
|
||||
Text {
|
||||
text: "volume_down"
|
||||
text: audioTab.volumeMuted ? "volume_off" : "volume_down"
|
||||
font.family: Theme.iconFont
|
||||
font.pixelSize: Theme.iconSize
|
||||
color: Theme.surfaceText
|
||||
color: audioTab.volumeMuted ? Theme.error : Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: AudioService.toggleMute()
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
@@ -179,13 +187,6 @@ Item {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
text: audioTab.volumeLevel + "%"
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.surfaceText
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
}
|
||||
|
||||
// Output Devices
|
||||
@@ -224,14 +225,7 @@ Item {
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "Current: " + (function() {
|
||||
for (let sink of audioTab.audioSinks) {
|
||||
if (sink.name === audioTab.currentAudioSink) {
|
||||
return sink.displayName
|
||||
}
|
||||
}
|
||||
return audioTab.currentAudioSink
|
||||
})()
|
||||
text: "Current: " + (AudioService.currentSinkDisplayName || "None")
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.primary
|
||||
font.weight: Font.Medium
|
||||
@@ -283,10 +277,16 @@ Item {
|
||||
}
|
||||
|
||||
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
|
||||
color: Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.8)
|
||||
visible: modelData.active
|
||||
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
|
||||
visible: text !== ""
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -298,8 +298,6 @@ Item {
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
|
||||
onClicked: {
|
||||
console.log("Clicked audio device:", JSON.stringify(modelData))
|
||||
console.log("Device name to set:", modelData.name)
|
||||
AudioService.setAudioSink(modelData.name)
|
||||
}
|
||||
}
|
||||
@@ -337,11 +335,18 @@ Item {
|
||||
spacing: Theme.spacingM
|
||||
|
||||
Text {
|
||||
text: "mic"
|
||||
text: audioTab.micMuted ? "mic_off" : "mic"
|
||||
font.family: Theme.iconFont
|
||||
font.pixelSize: Theme.iconSize
|
||||
color: Theme.surfaceText
|
||||
color: audioTab.micMuted ? Theme.error : Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: AudioService.toggleMicMute()
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
@@ -415,12 +420,6 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
text: audioTab.micLevel + "%"
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.surfaceText
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
}
|
||||
|
||||
// Input Devices
|
||||
@@ -459,14 +458,7 @@ Item {
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "Current: " + (function() {
|
||||
for (let source of audioTab.audioSources) {
|
||||
if (source.name === audioTab.currentAudioSource) {
|
||||
return source.displayName
|
||||
}
|
||||
}
|
||||
return audioTab.currentAudioSource
|
||||
})()
|
||||
text: "Current: " + (AudioService.currentSourceDisplayName || "None")
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.primary
|
||||
font.weight: Font.Medium
|
||||
@@ -517,10 +509,16 @@ Item {
|
||||
}
|
||||
|
||||
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
|
||||
color: Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.8)
|
||||
visible: modelData.active
|
||||
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
|
||||
visible: text !== ""
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -532,8 +530,6 @@ Item {
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
|
||||
onClicked: {
|
||||
console.log("Clicked audio source:", JSON.stringify(modelData))
|
||||
console.log("Source name to set:", modelData.name)
|
||||
AudioService.setAudioSource(modelData.name)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -676,14 +676,6 @@ PanelWindow {
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacingM
|
||||
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
|
||||
|
||||
@@ -8,6 +8,7 @@ Rectangle {
|
||||
property string networkStatus: "disconnected"
|
||||
property string wifiSignalStrength: "good"
|
||||
property int volumeLevel: 50
|
||||
property bool volumeMuted: false
|
||||
property bool bluetoothAvailable: false
|
||||
property bool bluetoothEnabled: false
|
||||
property bool isActive: false
|
||||
@@ -62,7 +63,7 @@ Rectangle {
|
||||
|
||||
// Audio Icon
|
||||
Text {
|
||||
text: root.volumeLevel === 0 ? "volume_off" :
|
||||
text: root.volumeMuted ? "volume_off" :
|
||||
root.volumeLevel < 33 ? "volume_down" : "volume_up"
|
||||
font.family: Theme.iconFont
|
||||
font.pixelSize: Theme.iconSize - 8
|
||||
|
||||
@@ -41,6 +41,7 @@ PanelWindow {
|
||||
property string networkStatus: "disconnected"
|
||||
property string wifiSignalStrength: "good"
|
||||
property int volumeLevel: 50
|
||||
property bool volumeMuted: false
|
||||
property bool bluetoothAvailable: false
|
||||
property bool bluetoothEnabled: false
|
||||
|
||||
@@ -321,6 +322,7 @@ PanelWindow {
|
||||
networkStatus: topBar.networkStatus
|
||||
wifiSignalStrength: topBar.wifiSignalStrength
|
||||
volumeLevel: topBar.volumeLevel
|
||||
volumeMuted: topBar.volumeMuted
|
||||
bluetoothAvailable: topBar.bluetoothAvailable
|
||||
bluetoothEnabled: topBar.bluetoothEnabled
|
||||
isActive: topBar.shellRoot ? topBar.shellRoot.controlCenterVisible : false
|
||||
|
||||
@@ -82,6 +82,7 @@ ShellRoot {
|
||||
|
||||
// Audio properties from AudioService
|
||||
property int volumeLevel: AudioService.volumeLevel
|
||||
property bool volumeMuted: AudioService.sinkMuted
|
||||
property var audioSinks: AudioService.audioSinks
|
||||
property string currentAudioSink: AudioService.currentAudioSink
|
||||
|
||||
@@ -320,6 +321,7 @@ ShellRoot {
|
||||
networkStatus: root.networkStatus
|
||||
wifiSignalStrength: root.wifiSignalStrength
|
||||
volumeLevel: root.volumeLevel
|
||||
volumeMuted: root.volumeMuted
|
||||
bluetoothAvailable: root.bluetoothAvailable
|
||||
bluetoothEnabled: root.bluetoothEnabled
|
||||
shellRoot: root
|
||||
|
||||
Reference in New Issue
Block a user