1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2025-12-07 22:15:38 -05:00

fix bluetooth codec modal

This commit is contained in:
bbedward
2025-08-30 10:59:42 -04:00
parent bd39a92d16
commit 62c7202b33
5 changed files with 401 additions and 166 deletions

View File

@@ -10,6 +10,7 @@ import qs.Common
import qs.Modules.ControlCenter
import qs.Modules.ControlCenter.Widgets
import qs.Modules.ControlCenter.Details
import qs.Modules.ControlCenter.Details 1.0 as Details
import qs.Services
import qs.Widgets
@@ -74,43 +75,48 @@ DankPopout {
}
content: Component {
Rectangle {
id: controlContent
Item {
implicitHeight: controlContent.implicitHeight
property alias bluetoothCodecSelector: bluetoothCodecSelector
Rectangle {
id: controlContent
implicitHeight: mainColumn.implicitHeight + Theme.spacingM
anchors.fill: parent
implicitHeight: mainColumn.implicitHeight + Theme.spacingM
color: Theme.popupBackground()
radius: Theme.cornerRadius
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g,
Theme.outline.b, 0.08)
border.width: 1
antialiasing: true
smooth: true
focus: true
color: Theme.popupBackground()
radius: Theme.cornerRadius
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g,
Theme.outline.b, 0.08)
border.width: 1
antialiasing: true
smooth: true
focus: true
Component.onCompleted: {
if (root.shouldBeVisible)
forceActiveFocus()
}
Keys.onPressed: function (event) {
if (event.key === Qt.Key_Escape) {
root.close()
event.accepted = true
} else {
event.accepted = false
}
}
Connections {
function onShouldBeVisibleChanged() {
Component.onCompleted: {
if (root.shouldBeVisible)
Qt.callLater(function () {
controlContent.forceActiveFocus()
})
forceActiveFocus()
}
Keys.onPressed: function (event) {
if (event.key === Qt.Key_Escape) {
root.close()
event.accepted = true
} else {
event.accepted = false
}
}
Connections {
function onShouldBeVisibleChanged() {
if (root.shouldBeVisible)
Qt.callLater(function () {
controlContent.forceActiveFocus()
})
}
target: root
}
target: root
}
Column {
id: mainColumn
@@ -808,7 +814,13 @@ DankPopout {
}
}
}
}
Details.BluetoothCodecSelector {
id: bluetoothCodecSelector
anchors.fill: parent
z: 10000
}
}
}
@@ -819,7 +831,17 @@ DankPopout {
Component {
id: bluetoothDetailComponent
BluetoothDetail {}
BluetoothDetail {
id: bluetoothDetail
onShowCodecSelector: function(device) {
if (contentLoader.item && contentLoader.item.bluetoothCodecSelector) {
contentLoader.item.bluetoothCodecSelector.show(device)
contentLoader.item.bluetoothCodecSelector.codecSelected.connect(function(deviceAddress, codecName) {
bluetoothDetail.updateDeviceCodecDisplay(deviceAddress, codecName)
})
}
}
}
}
Component {

View File

@@ -6,7 +6,7 @@ import qs.Common
import qs.Services
import qs.Widgets
Rectangle {
Item {
id: root
property var device: null
@@ -15,7 +15,8 @@ Rectangle {
property var availableCodecs: []
property string currentCodec: ""
property bool isLoading: false
property bool parsingTargetCard: false
signal codecSelected(string deviceAddress, string codecName)
function show(bluetoothDevice) {
device = bluetoothDevice;
@@ -39,75 +40,68 @@ Rectangle {
function queryCodecs() {
if (!device)
return ;
return;
codecQueryProcess.cardName = BluetoothService.getCardName(device);
codecQueryProcess.running = true;
BluetoothService.getAvailableCodecs(device, function(codecs, current) {
availableCodecs = codecs;
currentCodec = current;
isLoading = false;
});
}
function selectCodec(profileName) {
if (!device || isLoading)
return ;
return;
let selectedCodec = availableCodecs.find(c => c.profile === profileName);
if (selectedCodec && device) {
BluetoothService.updateDeviceCodec(device.address, selectedCodec.name);
codecSelected(device.address, selectedCodec.name);
}
isLoading = true;
codecSwitchProcess.cardName = BluetoothService.getCardName(device);
codecSwitchProcess.profile = profileName;
codecSwitchProcess.running = true;
}
function parseCodecLine(line) {
if (!codecQueryProcess.cardName)
return ;
if (line.includes(`Name: ${codecQueryProcess.cardName}`)) {
parsingTargetCard = true;
return ;
}
if (parsingTargetCard && line.startsWith("Name: ") && !line.includes(codecQueryProcess.cardName)) {
parsingTargetCard = false;
return ;
}
if (parsingTargetCard) {
if (line.startsWith("Active Profile:")) {
let profile = line.split(": ")[1] || "";
let activeCodec = availableCodecs.find((c) => {
return c.profile === profile;
});
if (activeCodec)
currentCodec = activeCodec.name;
return ;
BluetoothService.switchCodec(device, profileName, function(success, message) {
isLoading = false;
if (success) {
ToastService.showToast(message, ToastService.levelInfo);
Qt.callLater(root.hide);
} else {
ToastService.showToast(message, ToastService.levelError);
}
if (line.includes("codec") && line.includes("available: yes")) {
let parts = line.split(": ");
if (parts.length >= 2) {
let profile = parts[0].trim();
let description = parts[1];
let codecMatch = description.match(/codec ([^\)\s]+)/i);
let codecName = codecMatch ? codecMatch[1].toUpperCase() : "UNKNOWN";
let codecInfo = BluetoothService.getCodecInfo(codecName);
if (codecInfo && !availableCodecs.some((c) => {
return c.profile === profile;
})) {
let newCodecs = availableCodecs.slice();
newCodecs.push({
"name": codecInfo.name,
"profile": profile,
"description": codecInfo.description,
"qualityColor": codecInfo.qualityColor
});
availableCodecs = newCodecs;
}
}
}
}
});
}
visible: false
anchors.fill: parent
color: "transparent"
z: 2000
opacity: modalVisible ? 1 : 0
MouseArea {
id: modalBlocker
anchors.fill: parent
visible: modalVisible
enabled: modalVisible
hoverEnabled: true
preventStealing: true
propagateComposedEvents: false
onClicked: root.hide()
onWheel: (wheel) => { wheel.accepted = true }
onPositionChanged: (mouse) => { mouse.accepted = true }
}
Rectangle {
id: modalBackground
anchors.fill: parent
color: Qt.rgba(0, 0, 0, 0.5)
opacity: modalVisible ? 1 : 0
Behavior on opacity {
NumberAnimation {
duration: Theme.mediumDuration
easing.type: Theme.emphasizedEasing
}
}
}
FocusScope {
id: focusScope
@@ -116,17 +110,14 @@ Rectangle {
focus: root.visible
enabled: root.visible
MouseArea {
anchors.fill: parent
onClicked: root.hide()
onWheel: (wheel) => {
return wheel.accepted = true;
}
Keys.onEscapePressed: {
root.hide()
event.accepted = true
}
}
Rectangle {
id: modalContent
anchors.centerIn: parent
width: 320
height: Math.min(contentColumn.implicitHeight + Theme.spacingL * 2, 400)
@@ -139,8 +130,12 @@ Rectangle {
MouseArea {
anchors.fill: parent
onClicked: {
}
hoverEnabled: true
preventStealing: true
propagateComposedEvents: false
onClicked: (mouse) => { mouse.accepted = true }
onWheel: (wheel) => { wheel.accepted = true }
onPositionChanged: (mouse) => { mouse.accepted = true }
}
Column {
@@ -309,55 +304,4 @@ Rectangle {
}
}
Process {
id: codecQueryProcess
property string cardName: ""
command: ["pactl", "list", "cards"]
onExited: function(exitCode, exitStatus) {
isLoading = false;
if (exitCode !== 0)
console.warn("Failed to query codecs:", exitCode);
}
stdout: SplitParser {
splitMarker: "\n"
onRead: (data) => {
return parseCodecLine(data.trim());
}
}
}
Process {
id: codecSwitchProcess
property string cardName: ""
property string profile: ""
command: ["pactl", "set-card-profile", cardName, profile]
onExited: function(exitCode, exitStatus) {
isLoading = false;
if (exitCode === 0) {
queryCodecs();
ToastService.showToast("Codec switched successfully", ToastService.levelInfo);
Qt.callLater(root.hide);
} else {
ToastService.showToast("Failed to switch codec", ToastService.levelError);
console.warn("Failed to switch codec:", exitCode);
}
}
}
Behavior on opacity {
NumberAnimation {
duration: Theme.mediumDuration
easing.type: Theme.emphasizedEasing
}
}
}

View File

@@ -13,7 +13,19 @@ Rectangle {
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
border.width: 1
property var bluetoothCodecModalRef: bluetoothCodecModal
property var bluetoothCodecModalRef: null
signal showCodecSelector(var device)
function updateDeviceCodecDisplay(deviceAddress, codecName) {
for (let i = 0; i < pairedRepeater.count; i++) {
let item = pairedRepeater.itemAt(i)
if (item && item.modelData && item.modelData.address === deviceAddress) {
item.currentCodec = codecName
break
}
}
}
Row {
id: headerRow
@@ -131,9 +143,17 @@ Rectangle {
required property var modelData
required property int index
property string currentCodec: BluetoothService.deviceCodecs[modelData.address] || ""
width: parent.width
height: 50
radius: Theme.cornerRadius
Component.onCompleted: {
if (modelData.connected && BluetoothService.isAudioDevice(modelData)) {
BluetoothService.refreshDeviceCodec(modelData)
}
}
color: {
if (modelData.state === BluetoothDeviceState.Connecting)
return Qt.rgba(Theme.warning.r, Theme.warning.g, Theme.warning.b, 0.12)
@@ -189,8 +209,13 @@ Rectangle {
text: {
if (modelData.state === BluetoothDeviceState.Connecting)
return "Connecting..."
if (modelData.connected)
return "Connected"
if (modelData.connected) {
let status = "Connected"
if (currentCodec) {
status += " • " + currentCodec
}
return status
}
return "Paired"
}
font.pixelSize: Theme.fontSizeSmall
@@ -484,8 +509,8 @@ Rectangle {
}
onTriggered: {
if (bluetoothCodecModalRef && bluetoothContextMenu.currentDevice) {
bluetoothCodecModalRef.show(bluetoothContextMenu.currentDevice)
if (bluetoothContextMenu.currentDevice) {
showCodecSelector(bluetoothContextMenu.currentDevice)
}
}
}
@@ -515,9 +540,4 @@ Rectangle {
}
}
BluetoothCodecSelector {
id: bluetoothCodecModal
anchors.fill: parent
z: 3000
}
}