mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-01-29 07:52:50 -05:00
fix bluetooth codec modal
This commit is contained in:
@@ -10,6 +10,7 @@ import qs.Common
|
|||||||
import qs.Modules.ControlCenter
|
import qs.Modules.ControlCenter
|
||||||
import qs.Modules.ControlCenter.Widgets
|
import qs.Modules.ControlCenter.Widgets
|
||||||
import qs.Modules.ControlCenter.Details
|
import qs.Modules.ControlCenter.Details
|
||||||
|
import qs.Modules.ControlCenter.Details 1.0 as Details
|
||||||
import qs.Services
|
import qs.Services
|
||||||
import qs.Widgets
|
import qs.Widgets
|
||||||
|
|
||||||
@@ -74,43 +75,48 @@ DankPopout {
|
|||||||
}
|
}
|
||||||
|
|
||||||
content: Component {
|
content: Component {
|
||||||
Rectangle {
|
Item {
|
||||||
id: controlContent
|
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()
|
color: Theme.popupBackground()
|
||||||
radius: Theme.cornerRadius
|
radius: Theme.cornerRadius
|
||||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g,
|
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g,
|
||||||
Theme.outline.b, 0.08)
|
Theme.outline.b, 0.08)
|
||||||
border.width: 1
|
border.width: 1
|
||||||
antialiasing: true
|
antialiasing: true
|
||||||
smooth: true
|
smooth: true
|
||||||
focus: true
|
focus: true
|
||||||
|
|
||||||
Component.onCompleted: {
|
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() {
|
|
||||||
if (root.shouldBeVisible)
|
if (root.shouldBeVisible)
|
||||||
Qt.callLater(function () {
|
forceActiveFocus()
|
||||||
controlContent.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 {
|
Column {
|
||||||
id: mainColumn
|
id: mainColumn
|
||||||
@@ -808,7 +814,13 @@ DankPopout {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Details.BluetoothCodecSelector {
|
||||||
|
id: bluetoothCodecSelector
|
||||||
|
anchors.fill: parent
|
||||||
|
z: 10000
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -819,7 +831,17 @@ DankPopout {
|
|||||||
|
|
||||||
Component {
|
Component {
|
||||||
id: bluetoothDetailComponent
|
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 {
|
Component {
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import qs.Common
|
|||||||
import qs.Services
|
import qs.Services
|
||||||
import qs.Widgets
|
import qs.Widgets
|
||||||
|
|
||||||
Rectangle {
|
Item {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
property var device: null
|
property var device: null
|
||||||
@@ -15,7 +15,8 @@ Rectangle {
|
|||||||
property var availableCodecs: []
|
property var availableCodecs: []
|
||||||
property string currentCodec: ""
|
property string currentCodec: ""
|
||||||
property bool isLoading: false
|
property bool isLoading: false
|
||||||
property bool parsingTargetCard: false
|
|
||||||
|
signal codecSelected(string deviceAddress, string codecName)
|
||||||
|
|
||||||
function show(bluetoothDevice) {
|
function show(bluetoothDevice) {
|
||||||
device = bluetoothDevice;
|
device = bluetoothDevice;
|
||||||
@@ -39,75 +40,68 @@ Rectangle {
|
|||||||
|
|
||||||
function queryCodecs() {
|
function queryCodecs() {
|
||||||
if (!device)
|
if (!device)
|
||||||
return ;
|
return;
|
||||||
|
|
||||||
codecQueryProcess.cardName = BluetoothService.getCardName(device);
|
BluetoothService.getAvailableCodecs(device, function(codecs, current) {
|
||||||
codecQueryProcess.running = true;
|
availableCodecs = codecs;
|
||||||
|
currentCodec = current;
|
||||||
|
isLoading = false;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function selectCodec(profileName) {
|
function selectCodec(profileName) {
|
||||||
if (!device || isLoading)
|
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;
|
isLoading = true;
|
||||||
codecSwitchProcess.cardName = BluetoothService.getCardName(device);
|
BluetoothService.switchCodec(device, profileName, function(success, message) {
|
||||||
codecSwitchProcess.profile = profileName;
|
isLoading = false;
|
||||||
codecSwitchProcess.running = true;
|
if (success) {
|
||||||
}
|
ToastService.showToast(message, ToastService.levelInfo);
|
||||||
|
Qt.callLater(root.hide);
|
||||||
function parseCodecLine(line) {
|
} else {
|
||||||
if (!codecQueryProcess.cardName)
|
ToastService.showToast(message, ToastService.levelError);
|
||||||
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 ;
|
|
||||||
}
|
}
|
||||||
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
|
visible: false
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
color: "transparent"
|
|
||||||
z: 2000
|
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 {
|
FocusScope {
|
||||||
id: focusScope
|
id: focusScope
|
||||||
@@ -116,17 +110,14 @@ Rectangle {
|
|||||||
focus: root.visible
|
focus: root.visible
|
||||||
enabled: root.visible
|
enabled: root.visible
|
||||||
|
|
||||||
MouseArea {
|
Keys.onEscapePressed: {
|
||||||
anchors.fill: parent
|
root.hide()
|
||||||
onClicked: root.hide()
|
event.accepted = true
|
||||||
onWheel: (wheel) => {
|
|
||||||
return wheel.accepted = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
|
id: modalContent
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
width: 320
|
width: 320
|
||||||
height: Math.min(contentColumn.implicitHeight + Theme.spacingL * 2, 400)
|
height: Math.min(contentColumn.implicitHeight + Theme.spacingL * 2, 400)
|
||||||
@@ -139,8 +130,12 @@ Rectangle {
|
|||||||
|
|
||||||
MouseArea {
|
MouseArea {
|
||||||
anchors.fill: parent
|
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 {
|
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
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,19 @@ Rectangle {
|
|||||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
|
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
|
||||||
border.width: 1
|
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 {
|
Row {
|
||||||
id: headerRow
|
id: headerRow
|
||||||
@@ -131,9 +143,17 @@ Rectangle {
|
|||||||
required property var modelData
|
required property var modelData
|
||||||
required property int index
|
required property int index
|
||||||
|
|
||||||
|
property string currentCodec: BluetoothService.deviceCodecs[modelData.address] || ""
|
||||||
|
|
||||||
width: parent.width
|
width: parent.width
|
||||||
height: 50
|
height: 50
|
||||||
radius: Theme.cornerRadius
|
radius: Theme.cornerRadius
|
||||||
|
|
||||||
|
Component.onCompleted: {
|
||||||
|
if (modelData.connected && BluetoothService.isAudioDevice(modelData)) {
|
||||||
|
BluetoothService.refreshDeviceCodec(modelData)
|
||||||
|
}
|
||||||
|
}
|
||||||
color: {
|
color: {
|
||||||
if (modelData.state === BluetoothDeviceState.Connecting)
|
if (modelData.state === BluetoothDeviceState.Connecting)
|
||||||
return Qt.rgba(Theme.warning.r, Theme.warning.g, Theme.warning.b, 0.12)
|
return Qt.rgba(Theme.warning.r, Theme.warning.g, Theme.warning.b, 0.12)
|
||||||
@@ -189,8 +209,13 @@ Rectangle {
|
|||||||
text: {
|
text: {
|
||||||
if (modelData.state === BluetoothDeviceState.Connecting)
|
if (modelData.state === BluetoothDeviceState.Connecting)
|
||||||
return "Connecting..."
|
return "Connecting..."
|
||||||
if (modelData.connected)
|
if (modelData.connected) {
|
||||||
return "Connected"
|
let status = "Connected"
|
||||||
|
if (currentCodec) {
|
||||||
|
status += " • " + currentCodec
|
||||||
|
}
|
||||||
|
return status
|
||||||
|
}
|
||||||
return "Paired"
|
return "Paired"
|
||||||
}
|
}
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
@@ -484,8 +509,8 @@ Rectangle {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onTriggered: {
|
onTriggered: {
|
||||||
if (bluetoothCodecModalRef && bluetoothContextMenu.currentDevice) {
|
if (bluetoothContextMenu.currentDevice) {
|
||||||
bluetoothCodecModalRef.show(bluetoothContextMenu.currentDevice)
|
showCodecSelector(bluetoothContextMenu.currentDevice)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -515,9 +540,4 @@ Rectangle {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
BluetoothCodecSelector {
|
|
||||||
id: bluetoothCodecModal
|
|
||||||
anchors.fill: parent
|
|
||||||
z: 3000
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -3,6 +3,7 @@ pragma ComponentBehavior: Bound
|
|||||||
|
|
||||||
import QtQuick
|
import QtQuick
|
||||||
import Quickshell
|
import Quickshell
|
||||||
|
import Quickshell.Io
|
||||||
import Quickshell.Bluetooth
|
import Quickshell.Bluetooth
|
||||||
|
|
||||||
Singleton {
|
Singleton {
|
||||||
@@ -221,4 +222,255 @@ Singleton {
|
|||||||
qualityColor: "#9E9E9E"
|
qualityColor: "#9E9E9E"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
property var deviceCodecs: ({})
|
||||||
|
|
||||||
|
function updateDeviceCodec(deviceAddress, codec) {
|
||||||
|
deviceCodecs[deviceAddress] = codec
|
||||||
|
deviceCodecsChanged()
|
||||||
|
}
|
||||||
|
|
||||||
|
function refreshDeviceCodec(device) {
|
||||||
|
if (!device || !device.connected || !isAudioDevice(device)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let cardName = getCardName(device)
|
||||||
|
codecQueryProcess.cardName = cardName
|
||||||
|
codecQueryProcess.deviceAddress = device.address
|
||||||
|
codecQueryProcess.availableCodecs = []
|
||||||
|
codecQueryProcess.parsingTargetCard = false
|
||||||
|
codecQueryProcess.detectedCodec = ""
|
||||||
|
codecQueryProcess.running = true
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCurrentCodec(device, callback) {
|
||||||
|
if (!device || !device.connected || !isAudioDevice(device)) {
|
||||||
|
callback("")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let cardName = getCardName(device)
|
||||||
|
codecQueryProcess.cardName = cardName
|
||||||
|
codecQueryProcess.callback = callback
|
||||||
|
codecQueryProcess.availableCodecs = []
|
||||||
|
codecQueryProcess.parsingTargetCard = false
|
||||||
|
codecQueryProcess.detectedCodec = ""
|
||||||
|
codecQueryProcess.running = true
|
||||||
|
}
|
||||||
|
|
||||||
|
function getAvailableCodecs(device, callback) {
|
||||||
|
if (!device || !device.connected || !isAudioDevice(device)) {
|
||||||
|
callback([], "")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let cardName = getCardName(device)
|
||||||
|
codecFullQueryProcess.cardName = cardName
|
||||||
|
codecFullQueryProcess.callback = callback
|
||||||
|
codecFullQueryProcess.availableCodecs = []
|
||||||
|
codecFullQueryProcess.parsingTargetCard = false
|
||||||
|
codecFullQueryProcess.detectedCodec = ""
|
||||||
|
codecFullQueryProcess.running = true
|
||||||
|
}
|
||||||
|
|
||||||
|
function switchCodec(device, profileName, callback) {
|
||||||
|
if (!device || !isAudioDevice(device)) {
|
||||||
|
callback(false, "Invalid device")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let cardName = getCardName(device)
|
||||||
|
codecSwitchProcess.cardName = cardName
|
||||||
|
codecSwitchProcess.profile = profileName
|
||||||
|
codecSwitchProcess.callback = callback
|
||||||
|
codecSwitchProcess.running = true
|
||||||
|
}
|
||||||
|
|
||||||
|
Process {
|
||||||
|
id: codecQueryProcess
|
||||||
|
|
||||||
|
property string cardName: ""
|
||||||
|
property string deviceAddress: ""
|
||||||
|
property var callback: null
|
||||||
|
property bool parsingTargetCard: false
|
||||||
|
property string detectedCodec: ""
|
||||||
|
property var availableCodecs: []
|
||||||
|
|
||||||
|
command: ["pactl", "list", "cards"]
|
||||||
|
|
||||||
|
onExited: function(exitCode, exitStatus) {
|
||||||
|
if (exitCode === 0 && detectedCodec) {
|
||||||
|
if (deviceAddress) {
|
||||||
|
root.updateDeviceCodec(deviceAddress, detectedCodec)
|
||||||
|
}
|
||||||
|
if (callback) {
|
||||||
|
callback(detectedCodec)
|
||||||
|
}
|
||||||
|
} else if (callback) {
|
||||||
|
callback("")
|
||||||
|
}
|
||||||
|
|
||||||
|
parsingTargetCard = false
|
||||||
|
detectedCodec = ""
|
||||||
|
availableCodecs = []
|
||||||
|
deviceAddress = ""
|
||||||
|
callback = null
|
||||||
|
}
|
||||||
|
|
||||||
|
stdout: SplitParser {
|
||||||
|
splitMarker: "\n"
|
||||||
|
onRead: (data) => {
|
||||||
|
let line = data.trim()
|
||||||
|
|
||||||
|
if (line.includes(`Name: ${codecQueryProcess.cardName}`)) {
|
||||||
|
codecQueryProcess.parsingTargetCard = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (codecQueryProcess.parsingTargetCard && line.startsWith("Name: ") && !line.includes(codecQueryProcess.cardName)) {
|
||||||
|
codecQueryProcess.parsingTargetCard = false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (codecQueryProcess.parsingTargetCard) {
|
||||||
|
if (line.startsWith("Active Profile:")) {
|
||||||
|
let profile = line.split(": ")[1] || ""
|
||||||
|
let activeCodec = codecQueryProcess.availableCodecs.find((c) => {
|
||||||
|
return c.profile === profile
|
||||||
|
})
|
||||||
|
if (activeCodec) {
|
||||||
|
codecQueryProcess.detectedCodec = activeCodec.name
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
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 = root.getCodecInfo(codecName)
|
||||||
|
if (codecInfo && !codecQueryProcess.availableCodecs.some((c) => {
|
||||||
|
return c.profile === profile
|
||||||
|
})) {
|
||||||
|
let newCodecs = codecQueryProcess.availableCodecs.slice()
|
||||||
|
newCodecs.push({
|
||||||
|
"name": codecInfo.name,
|
||||||
|
"profile": profile,
|
||||||
|
"description": codecInfo.description,
|
||||||
|
"qualityColor": codecInfo.qualityColor
|
||||||
|
})
|
||||||
|
codecQueryProcess.availableCodecs = newCodecs
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Process {
|
||||||
|
id: codecFullQueryProcess
|
||||||
|
|
||||||
|
property string cardName: ""
|
||||||
|
property var callback: null
|
||||||
|
property bool parsingTargetCard: false
|
||||||
|
property string detectedCodec: ""
|
||||||
|
property var availableCodecs: []
|
||||||
|
|
||||||
|
command: ["pactl", "list", "cards"]
|
||||||
|
|
||||||
|
onExited: function(exitCode, exitStatus) {
|
||||||
|
if (callback) {
|
||||||
|
callback(exitCode === 0 ? availableCodecs : [], exitCode === 0 ? detectedCodec : "")
|
||||||
|
}
|
||||||
|
parsingTargetCard = false
|
||||||
|
detectedCodec = ""
|
||||||
|
availableCodecs = []
|
||||||
|
callback = null
|
||||||
|
}
|
||||||
|
|
||||||
|
stdout: SplitParser {
|
||||||
|
splitMarker: "\n"
|
||||||
|
onRead: (data) => {
|
||||||
|
let line = data.trim()
|
||||||
|
|
||||||
|
if (line.includes(`Name: ${codecFullQueryProcess.cardName}`)) {
|
||||||
|
codecFullQueryProcess.parsingTargetCard = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (codecFullQueryProcess.parsingTargetCard && line.startsWith("Name: ") && !line.includes(codecFullQueryProcess.cardName)) {
|
||||||
|
codecFullQueryProcess.parsingTargetCard = false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (codecFullQueryProcess.parsingTargetCard) {
|
||||||
|
if (line.startsWith("Active Profile:")) {
|
||||||
|
let profile = line.split(": ")[1] || ""
|
||||||
|
let activeCodec = codecFullQueryProcess.availableCodecs.find((c) => {
|
||||||
|
return c.profile === profile
|
||||||
|
})
|
||||||
|
if (activeCodec) {
|
||||||
|
codecFullQueryProcess.detectedCodec = activeCodec.name
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
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 = root.getCodecInfo(codecName)
|
||||||
|
if (codecInfo && !codecFullQueryProcess.availableCodecs.some((c) => {
|
||||||
|
return c.profile === profile
|
||||||
|
})) {
|
||||||
|
let newCodecs = codecFullQueryProcess.availableCodecs.slice()
|
||||||
|
newCodecs.push({
|
||||||
|
"name": codecInfo.name,
|
||||||
|
"profile": profile,
|
||||||
|
"description": codecInfo.description,
|
||||||
|
"qualityColor": codecInfo.qualityColor
|
||||||
|
})
|
||||||
|
codecFullQueryProcess.availableCodecs = newCodecs
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Process {
|
||||||
|
id: codecSwitchProcess
|
||||||
|
|
||||||
|
property string cardName: ""
|
||||||
|
property string profile: ""
|
||||||
|
property var callback: null
|
||||||
|
|
||||||
|
command: ["pactl", "set-card-profile", cardName, profile]
|
||||||
|
|
||||||
|
onExited: function(exitCode, exitStatus) {
|
||||||
|
if (callback) {
|
||||||
|
callback(exitCode === 0, exitCode === 0 ? "Codec switched successfully" : "Failed to switch codec")
|
||||||
|
}
|
||||||
|
|
||||||
|
// If successful, refresh the codec for this device
|
||||||
|
if (exitCode === 0) {
|
||||||
|
if (root.adapter && root.adapter.devices) {
|
||||||
|
root.adapter.devices.values.forEach(device => {
|
||||||
|
if (device && root.getCardName(device) === cardName) {
|
||||||
|
Qt.callLater(() => root.refreshDeviceCodec(device))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
callback = null
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ Singleton {
|
|||||||
property list<int> values: Array(6)
|
property list<int> values: Array(6)
|
||||||
property int refCount: 0
|
property int refCount: 0
|
||||||
property bool cavaAvailable: false
|
property bool cavaAvailable: false
|
||||||
property string monitorSource: AudioService.sink && AudioService.sink.name ? AudioService.sink.name + ".monitor" : ""
|
|
||||||
|
|
||||||
Process {
|
Process {
|
||||||
id: cavaCheck
|
id: cavaCheck
|
||||||
@@ -31,9 +30,7 @@ Singleton {
|
|||||||
id: cavaProcess
|
id: cavaProcess
|
||||||
|
|
||||||
running: root.cavaAvailable && root.refCount > 0
|
running: root.cavaAvailable && root.refCount > 0
|
||||||
command: ["sh", "-c", root.monitorSource
|
command: ["sh", "-c", `printf '[general]\\nmode=normal\\nframerate=25\\nautosens=0\\nsensitivity=30\\nbars=6\\nlower_cutoff_freq=50\\nhigher_cutoff_freq=12000\\n[output]\\nmethod=raw\\nraw_target=/dev/stdout\\ndata_format=ascii\\nchannels=mono\\nmono_option=average\\n[smoothing]\\nnoise_reduction=35\\nintegral=90\\ngravity=95\\nignore=2\\nmonstercat=1.5' | cava -p /dev/stdin`]
|
||||||
? `printf '[general]\\nmode=normal\\nframerate=25\\nautosens=0\\nsensitivity=30\\nbars=6\\nlower_cutoff_freq=50\\nhigher_cutoff_freq=12000\\n[input]\\nmethod=pulse\\nsource=${root.monitorSource}\\n[output]\\nmethod=raw\\nraw_target=/dev/stdout\\ndata_format=ascii\\nchannels=mono\\nmono_option=average\\n[smoothing]\\nnoise_reduction=35\\nintegral=90\\ngravity=95\\nignore=2\\nmonstercat=1.5' | cava -p /dev/stdin`
|
|
||||||
: `printf '[general]\\nmode=normal\\nframerate=25\\nautosens=0\\nsensitivity=30\\nbars=6\\nlower_cutoff_freq=50\\nhigher_cutoff_freq=12000\\n[output]\\nmethod=raw\\nraw_target=/dev/stdout\\ndata_format=ascii\\nchannels=mono\\nmono_option=average\\n[smoothing]\\nnoise_reduction=35\\nintegral=90\\ngravity=95\\nignore=2\\nmonstercat=1.5' | cava -p /dev/stdin`]
|
|
||||||
|
|
||||||
onRunningChanged: {
|
onRunningChanged: {
|
||||||
if (!running) {
|
if (!running) {
|
||||||
|
|||||||
Reference in New Issue
Block a user