mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2025-12-10 07:25:37 -05:00
audio: un-dumbify audio service
This commit is contained in:
@@ -11,142 +11,10 @@ Singleton {
|
|||||||
readonly property PwNode sink: Pipewire.defaultAudioSink
|
readonly property PwNode sink: Pipewire.defaultAudioSink
|
||||||
readonly property PwNode source: Pipewire.defaultAudioSource
|
readonly property PwNode source: Pipewire.defaultAudioSource
|
||||||
|
|
||||||
readonly property bool sinkMuted: sink?.audio?.muted ?? false
|
function displayName(node) {
|
||||||
readonly property bool sourceMuted: source?.audio?.muted ?? false
|
if (!node) return ""
|
||||||
readonly property real volumeLevel: (sink?.audio?.volume ?? 0) * 100
|
|
||||||
readonly property real micLevel: (source?.audio?.volume ?? 0) * 100
|
|
||||||
|
|
||||||
property ListModel audioSinksModel: ListModel {}
|
|
||||||
property ListModel audioSourcesModel: ListModel {}
|
|
||||||
|
|
||||||
property var audioSinks: []
|
|
||||||
property var audioSources: []
|
|
||||||
|
|
||||||
Component.onCompleted: _rebuildModels()
|
|
||||||
|
|
||||||
Connections {
|
|
||||||
target: Pipewire
|
|
||||||
function onReadyChanged() { _rebuildModels() }
|
|
||||||
function onDefaultAudioSinkChanged() { _rebuildModels() }
|
|
||||||
function onDefaultAudioSourceChanged() { _rebuildModels() }
|
|
||||||
}
|
|
||||||
|
|
||||||
Timer {
|
|
||||||
interval: 2000
|
|
||||||
running: Pipewire.ready
|
|
||||||
repeat: true
|
|
||||||
onTriggered: _checkForNodeChanges()
|
|
||||||
}
|
|
||||||
|
|
||||||
property int _lastNodeCount: 0
|
|
||||||
|
|
||||||
function _checkForNodeChanges() {
|
|
||||||
if (Pipewire.nodes?.values) {
|
|
||||||
let currentCount = Pipewire.nodes.values.length
|
|
||||||
if (currentCount !== _lastNodeCount) {
|
|
||||||
_lastNodeCount = currentCount
|
|
||||||
_rebuildModels()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
readonly property string currentAudioSink: sink?.name ?? ""
|
|
||||||
readonly property string currentAudioSource: source?.name ?? ""
|
|
||||||
|
|
||||||
readonly property string currentSinkDisplayName: {
|
|
||||||
if (!sink) return ""
|
|
||||||
for (let i = 0; i < audioSinksModel.count; i++) {
|
|
||||||
let item = audioSinksModel.get(i)
|
|
||||||
if (item.node === sink) {
|
|
||||||
return item.displayName
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return _displayName(sink)
|
|
||||||
}
|
|
||||||
|
|
||||||
readonly property string currentSourceDisplayName: {
|
|
||||||
if (!source) return ""
|
|
||||||
for (let i = 0; i < audioSourcesModel.count; i++) {
|
|
||||||
let item = audioSourcesModel.get(i)
|
|
||||||
if (item.node === source) {
|
|
||||||
return item.displayName
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return _displayName(source)
|
|
||||||
}
|
|
||||||
|
|
||||||
function setVolume(percentage) {
|
|
||||||
if (sink?.audio) {
|
|
||||||
sink.audio.muted = false
|
|
||||||
sink.audio.volume = percentage / 100
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function setMicLevel(percentage) {
|
|
||||||
if (source?.audio) {
|
|
||||||
source.audio.muted = false
|
|
||||||
source.audio.volume = percentage / 100
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function toggleMute() {
|
|
||||||
if (sink?.audio) {
|
|
||||||
sink.audio.muted = !sink.audio.muted
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function toggleMicMute() {
|
|
||||||
if (source?.audio) {
|
|
||||||
source.audio.muted = !source.audio.muted
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function setAudioSink(sinkName) {
|
|
||||||
_setPreferred(sinkName, PwNodeType.AudioSink)
|
|
||||||
}
|
|
||||||
|
|
||||||
function setAudioSource(sourceName) {
|
|
||||||
_setPreferred(sourceName, PwNodeType.AudioSource)
|
|
||||||
}
|
|
||||||
|
|
||||||
function _rebuildModels() {
|
|
||||||
audioSinksModel.clear()
|
|
||||||
audioSourcesModel.clear()
|
|
||||||
|
|
||||||
let sinks = []
|
if (node.properties && node.properties["device.description"]) {
|
||||||
let sources = []
|
|
||||||
|
|
||||||
if (!Pipewire.ready || !Pipewire.nodes?.values) return
|
|
||||||
|
|
||||||
for (let i = 0; i < Pipewire.nodes.values.length; i++) {
|
|
||||||
let node = Pipewire.nodes.values[i]
|
|
||||||
if (!node || node.isStream) continue
|
|
||||||
|
|
||||||
let entry = {
|
|
||||||
id: node.id.toString(),
|
|
||||||
name: node.name,
|
|
||||||
displayName: _displayName(node),
|
|
||||||
subtitle: _subtitle(node.name),
|
|
||||||
active: node === sink || node === source,
|
|
||||||
node: node
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((node.type & PwNodeType.AudioSink) === PwNodeType.AudioSink) {
|
|
||||||
audioSinksModel.append(entry)
|
|
||||||
sinks.push(entry)
|
|
||||||
}
|
|
||||||
if ((node.type & PwNodeType.AudioSource) === PwNodeType.AudioSource && !node.name.includes(".monitor")) {
|
|
||||||
audioSourcesModel.append(entry)
|
|
||||||
sources.push(entry)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
audioSinks = sinks
|
|
||||||
audioSources = sources
|
|
||||||
}
|
|
||||||
|
|
||||||
function _displayName(node) {
|
|
||||||
if (node.properties?.["device.description"]) {
|
|
||||||
return node.properties["device.description"]
|
return node.properties["device.description"]
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -166,7 +34,7 @@ Singleton {
|
|||||||
return node.name
|
return node.name
|
||||||
}
|
}
|
||||||
|
|
||||||
function _subtitle(name) {
|
function subtitle(name) {
|
||||||
if (!name) return ""
|
if (!name) return ""
|
||||||
|
|
||||||
if (name.includes('usb-')) {
|
if (name.includes('usb-')) {
|
||||||
@@ -192,22 +60,6 @@ Singleton {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
function _setPreferred(name, kind) {
|
|
||||||
if (!Pipewire.nodes?.values) return
|
|
||||||
|
|
||||||
for (let i = 0; i < Pipewire.nodes.values.length; i++) {
|
|
||||||
let node = Pipewire.nodes.values[i]
|
|
||||||
if (node && node.name === name && !node.isStream && ((node.type & kind) === kind)) {
|
|
||||||
if (kind === PwNodeType.AudioSink) {
|
|
||||||
Pipewire.preferredDefaultAudioSink = node
|
|
||||||
} else if (kind === PwNodeType.AudioSource) {
|
|
||||||
Pipewire.preferredDefaultAudioSource = node
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
PwObjectTracker {
|
PwObjectTracker {
|
||||||
objects: [Pipewire.defaultAudioSink, Pipewire.defaultAudioSource]
|
objects: [Pipewire.defaultAudioSink, Pipewire.defaultAudioSource]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ Singleton {
|
|||||||
return [];
|
return [];
|
||||||
|
|
||||||
return adapter.devices.values.filter((dev) => {
|
return adapter.devices.values.filter((dev) => {
|
||||||
return dev && dev.paired && isValidDevice(dev);
|
return dev && dev.paired;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
readonly property var allDevicesWithBattery: {
|
readonly property var allDevicesWithBattery: {
|
||||||
@@ -47,20 +47,6 @@ Singleton {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function isValidDevice(device) {
|
|
||||||
if (!device)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
var displayName = device.name || device.deviceName;
|
|
||||||
if (!displayName || displayName.length < 2)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if (displayName.startsWith('/org/bluez') || displayName.includes('hci0'))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
return displayName.length >= 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getDeviceIcon(device) {
|
function getDeviceIcon(device) {
|
||||||
if (!device)
|
if (!device)
|
||||||
return "bluetooth";
|
return "bluetooth";
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import QtQuick
|
|||||||
import QtQuick.Controls
|
import QtQuick.Controls
|
||||||
import Quickshell
|
import Quickshell
|
||||||
import Quickshell.Io
|
import Quickshell.Io
|
||||||
|
import Quickshell.Services.Pipewire
|
||||||
import Quickshell.Widgets
|
import Quickshell.Widgets
|
||||||
import qs.Common
|
import qs.Common
|
||||||
import qs.Services
|
import qs.Services
|
||||||
@@ -10,14 +11,12 @@ Item {
|
|||||||
id: audioTab
|
id: audioTab
|
||||||
|
|
||||||
property int audioSubTab: 0 // 0: Output, 1: Input
|
property int audioSubTab: 0 // 0: Output, 1: Input
|
||||||
readonly property real volumeLevel: AudioService.volumeLevel
|
readonly property real volumeLevel: (AudioService.sink && AudioService.sink.audio && AudioService.sink.audio.volume * 100) || 0
|
||||||
readonly property real micLevel: AudioService.micLevel
|
readonly property real micLevel: (AudioService.source && AudioService.source.audio && AudioService.source.audio.volume * 100) || 0
|
||||||
readonly property bool volumeMuted: AudioService.sinkMuted
|
readonly property bool volumeMuted: (AudioService.sink && AudioService.sink.audio && AudioService.sink.audio.muted) || false
|
||||||
readonly property bool micMuted: AudioService.sourceMuted
|
readonly property bool micMuted: (AudioService.source && AudioService.source.audio && AudioService.source.audio.muted) || false
|
||||||
readonly property string currentAudioSink: AudioService.currentAudioSink
|
readonly property string currentSinkDisplayName: AudioService.sink ? AudioService.displayName(AudioService.sink) : ""
|
||||||
readonly property string currentAudioSource: AudioService.currentAudioSource
|
readonly property string currentSourceDisplayName: AudioService.source ? AudioService.displayName(AudioService.source) : ""
|
||||||
readonly property var audioSinks: AudioService.audioSinks
|
|
||||||
readonly property var audioSources: AudioService.audioSources
|
|
||||||
|
|
||||||
Column {
|
Column {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
@@ -115,7 +114,11 @@ Item {
|
|||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
hoverEnabled: true
|
hoverEnabled: true
|
||||||
cursorShape: Qt.PointingHandCursor
|
cursorShape: Qt.PointingHandCursor
|
||||||
onClicked: AudioService.toggleMute()
|
onClicked: {
|
||||||
|
if (AudioService.sink && AudioService.sink.audio)
|
||||||
|
AudioService.sink.audio.muted = !AudioService.sink.audio.muted;
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -191,7 +194,10 @@ Item {
|
|||||||
isDragging = true;
|
isDragging = true;
|
||||||
let ratio = Math.max(0, Math.min(1, mouse.x / volumeSliderTrack.width));
|
let ratio = Math.max(0, Math.min(1, mouse.x / volumeSliderTrack.width));
|
||||||
let newVolume = Math.round(ratio * 100);
|
let newVolume = Math.round(ratio * 100);
|
||||||
AudioService.setVolume(newVolume);
|
if (AudioService.sink && AudioService.sink.audio) {
|
||||||
|
AudioService.sink.audio.muted = false;
|
||||||
|
AudioService.sink.audio.volume = newVolume / 100;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
onReleased: {
|
onReleased: {
|
||||||
isDragging = false;
|
isDragging = false;
|
||||||
@@ -200,13 +206,19 @@ Item {
|
|||||||
if (pressed && isDragging) {
|
if (pressed && isDragging) {
|
||||||
let ratio = Math.max(0, Math.min(1, mouse.x / volumeSliderTrack.width));
|
let ratio = Math.max(0, Math.min(1, mouse.x / volumeSliderTrack.width));
|
||||||
let newVolume = Math.round(ratio * 100);
|
let newVolume = Math.round(ratio * 100);
|
||||||
AudioService.setVolume(newVolume);
|
if (AudioService.sink && AudioService.sink.audio) {
|
||||||
|
AudioService.sink.audio.muted = false;
|
||||||
|
AudioService.sink.audio.volume = newVolume / 100;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
onClicked: (mouse) => {
|
onClicked: (mouse) => {
|
||||||
let ratio = Math.max(0, Math.min(1, mouse.x / volumeSliderTrack.width));
|
let ratio = Math.max(0, Math.min(1, mouse.x / volumeSliderTrack.width));
|
||||||
let newVolume = Math.round(ratio * 100);
|
let newVolume = Math.round(ratio * 100);
|
||||||
AudioService.setVolume(newVolume);
|
if (AudioService.sink && AudioService.sink.audio) {
|
||||||
|
AudioService.sink.audio.muted = false;
|
||||||
|
AudioService.sink.audio.volume = newVolume / 100;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -223,7 +235,10 @@ Item {
|
|||||||
let globalPos = mapToItem(volumeSliderTrack, mouse.x, mouse.y);
|
let globalPos = mapToItem(volumeSliderTrack, mouse.x, mouse.y);
|
||||||
let ratio = Math.max(0, Math.min(1, globalPos.x / volumeSliderTrack.width));
|
let ratio = Math.max(0, Math.min(1, globalPos.x / volumeSliderTrack.width));
|
||||||
let newVolume = Math.round(ratio * 100);
|
let newVolume = Math.round(ratio * 100);
|
||||||
AudioService.setVolume(newVolume);
|
if (AudioService.sink && AudioService.sink.audio) {
|
||||||
|
AudioService.sink.audio.muted = false;
|
||||||
|
AudioService.sink.audio.volume = newVolume / 100;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
onReleased: {
|
onReleased: {
|
||||||
@@ -265,7 +280,7 @@ Item {
|
|||||||
color: Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12)
|
color: Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12)
|
||||||
border.color: Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.3)
|
border.color: Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.3)
|
||||||
border.width: 1
|
border.width: 1
|
||||||
visible: audioTab.currentAudioSink !== ""
|
visible: AudioService.sink !== null
|
||||||
|
|
||||||
Row {
|
Row {
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
@@ -281,7 +296,7 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Text {
|
Text {
|
||||||
text: "Current: " + (AudioService.currentSinkDisplayName || "None")
|
text: "Current: " + (audioTab.currentSinkDisplayName || "None")
|
||||||
font.pixelSize: Theme.fontSizeMedium
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
color: Theme.primary
|
color: Theme.primary
|
||||||
font.weight: Font.Medium
|
font.weight: Font.Medium
|
||||||
@@ -293,14 +308,25 @@ Item {
|
|||||||
|
|
||||||
// Real audio devices
|
// Real audio devices
|
||||||
Repeater {
|
Repeater {
|
||||||
model: audioTab.audioSinks
|
model: {
|
||||||
|
if (!Pipewire.ready || !Pipewire.nodes || !Pipewire.nodes.values) return []
|
||||||
|
let sinks = []
|
||||||
|
for (let i = 0; i < Pipewire.nodes.values.length; i++) {
|
||||||
|
let node = Pipewire.nodes.values[i]
|
||||||
|
if (!node || node.isStream) continue
|
||||||
|
if ((node.type & PwNodeType.AudioSink) === PwNodeType.AudioSink) {
|
||||||
|
sinks.push(node)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sinks
|
||||||
|
}
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
width: parent.width
|
width: parent.width
|
||||||
height: 50
|
height: 50
|
||||||
radius: Theme.cornerRadius
|
radius: Theme.cornerRadius
|
||||||
color: deviceArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : (modelData.active ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.08))
|
color: deviceArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : (modelData === AudioService.sink ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.08))
|
||||||
border.color: modelData.active ? Theme.primary : "transparent"
|
border.color: modelData === AudioService.sink ? Theme.primary : "transparent"
|
||||||
border.width: 1
|
border.width: 1
|
||||||
|
|
||||||
Row {
|
Row {
|
||||||
@@ -322,7 +348,7 @@ Item {
|
|||||||
}
|
}
|
||||||
font.family: Theme.iconFont
|
font.family: Theme.iconFont
|
||||||
font.pixelSize: Theme.iconSize
|
font.pixelSize: Theme.iconSize
|
||||||
color: modelData.active ? Theme.primary : Theme.surfaceText
|
color: modelData === AudioService.sink ? Theme.primary : Theme.surfaceText
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -331,18 +357,18 @@ Item {
|
|||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
|
||||||
Text {
|
Text {
|
||||||
text: modelData.displayName
|
text: AudioService.displayName(modelData)
|
||||||
font.pixelSize: Theme.fontSizeMedium
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
color: modelData.active ? Theme.primary : Theme.surfaceText
|
color: modelData === AudioService.sink ? Theme.primary : Theme.surfaceText
|
||||||
font.weight: modelData.active ? Font.Medium : Font.Normal
|
font.weight: modelData === AudioService.sink ? Font.Medium : Font.Normal
|
||||||
}
|
}
|
||||||
|
|
||||||
Text {
|
Text {
|
||||||
text: {
|
text: {
|
||||||
if (modelData.subtitle && modelData.subtitle !== "")
|
if (AudioService.subtitle(modelData.name) && AudioService.subtitle(modelData.name) !== "")
|
||||||
return modelData.subtitle + (modelData.active ? " • Selected" : "");
|
return AudioService.subtitle(modelData.name) + (modelData === AudioService.sink ? " • Selected" : "");
|
||||||
else
|
else
|
||||||
return modelData.active ? "Selected" : "";
|
return modelData === AudioService.sink ? "Selected" : "";
|
||||||
}
|
}
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
|
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
|
||||||
@@ -360,7 +386,9 @@ Item {
|
|||||||
hoverEnabled: true
|
hoverEnabled: true
|
||||||
cursorShape: Qt.PointingHandCursor
|
cursorShape: Qt.PointingHandCursor
|
||||||
onClicked: {
|
onClicked: {
|
||||||
AudioService.setAudioSink(modelData.name);
|
if (modelData)
|
||||||
|
Pipewire.preferredDefaultAudioSink = modelData;
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -412,7 +440,11 @@ Item {
|
|||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
hoverEnabled: true
|
hoverEnabled: true
|
||||||
cursorShape: Qt.PointingHandCursor
|
cursorShape: Qt.PointingHandCursor
|
||||||
onClicked: AudioService.toggleMicMute()
|
onClicked: {
|
||||||
|
if (AudioService.source && AudioService.source.audio)
|
||||||
|
AudioService.source.audio.muted = !AudioService.source.audio.muted;
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -488,7 +520,10 @@ Item {
|
|||||||
isDragging = true;
|
isDragging = true;
|
||||||
let ratio = Math.max(0, Math.min(1, mouse.x / micSliderTrack.width));
|
let ratio = Math.max(0, Math.min(1, mouse.x / micSliderTrack.width));
|
||||||
let newMicLevel = Math.round(ratio * 100);
|
let newMicLevel = Math.round(ratio * 100);
|
||||||
AudioService.setMicLevel(newMicLevel);
|
if (AudioService.source && AudioService.source.audio) {
|
||||||
|
AudioService.source.audio.muted = false;
|
||||||
|
AudioService.source.audio.volume = newMicLevel / 100;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
onReleased: {
|
onReleased: {
|
||||||
isDragging = false;
|
isDragging = false;
|
||||||
@@ -497,13 +532,19 @@ Item {
|
|||||||
if (pressed && isDragging) {
|
if (pressed && isDragging) {
|
||||||
let ratio = Math.max(0, Math.min(1, mouse.x / micSliderTrack.width));
|
let ratio = Math.max(0, Math.min(1, mouse.x / micSliderTrack.width));
|
||||||
let newMicLevel = Math.round(ratio * 100);
|
let newMicLevel = Math.round(ratio * 100);
|
||||||
AudioService.setMicLevel(newMicLevel);
|
if (AudioService.source && AudioService.source.audio) {
|
||||||
|
AudioService.source.audio.muted = false;
|
||||||
|
AudioService.source.audio.volume = newMicLevel / 100;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
onClicked: (mouse) => {
|
onClicked: (mouse) => {
|
||||||
let ratio = Math.max(0, Math.min(1, mouse.x / micSliderTrack.width));
|
let ratio = Math.max(0, Math.min(1, mouse.x / micSliderTrack.width));
|
||||||
let newMicLevel = Math.round(ratio * 100);
|
let newMicLevel = Math.round(ratio * 100);
|
||||||
AudioService.setMicLevel(newMicLevel);
|
if (AudioService.source && AudioService.source.audio) {
|
||||||
|
AudioService.source.audio.muted = false;
|
||||||
|
AudioService.source.audio.volume = newMicLevel / 100;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -520,7 +561,10 @@ Item {
|
|||||||
let globalPos = mapToItem(micSliderTrack, mouse.x, mouse.y);
|
let globalPos = mapToItem(micSliderTrack, mouse.x, mouse.y);
|
||||||
let ratio = Math.max(0, Math.min(1, globalPos.x / micSliderTrack.width));
|
let ratio = Math.max(0, Math.min(1, globalPos.x / micSliderTrack.width));
|
||||||
let newMicLevel = Math.round(ratio * 100);
|
let newMicLevel = Math.round(ratio * 100);
|
||||||
AudioService.setMicLevel(newMicLevel);
|
if (AudioService.source && AudioService.source.audio) {
|
||||||
|
AudioService.source.audio.muted = false;
|
||||||
|
AudioService.source.audio.volume = newMicLevel / 100;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
onReleased: {
|
onReleased: {
|
||||||
@@ -562,7 +606,7 @@ Item {
|
|||||||
color: Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12)
|
color: Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12)
|
||||||
border.color: Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.3)
|
border.color: Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.3)
|
||||||
border.width: 1
|
border.width: 1
|
||||||
visible: audioTab.currentAudioSource !== ""
|
visible: AudioService.source !== null
|
||||||
|
|
||||||
Row {
|
Row {
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
@@ -578,7 +622,7 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Text {
|
Text {
|
||||||
text: "Current: " + (AudioService.currentSourceDisplayName || "None")
|
text: "Current: " + (audioTab.currentSourceDisplayName || "None")
|
||||||
font.pixelSize: Theme.fontSizeMedium
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
color: Theme.primary
|
color: Theme.primary
|
||||||
font.weight: Font.Medium
|
font.weight: Font.Medium
|
||||||
@@ -590,14 +634,25 @@ Item {
|
|||||||
|
|
||||||
// Real audio input devices
|
// Real audio input devices
|
||||||
Repeater {
|
Repeater {
|
||||||
model: audioTab.audioSources
|
model: {
|
||||||
|
if (!Pipewire.ready || !Pipewire.nodes || !Pipewire.nodes.values) return []
|
||||||
|
let sources = []
|
||||||
|
for (let i = 0; i < Pipewire.nodes.values.length; i++) {
|
||||||
|
let node = Pipewire.nodes.values[i]
|
||||||
|
if (!node || node.isStream) continue
|
||||||
|
if ((node.type & PwNodeType.AudioSource) === PwNodeType.AudioSource && !node.name.includes(".monitor")) {
|
||||||
|
sources.push(node)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sources
|
||||||
|
}
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
width: parent.width
|
width: parent.width
|
||||||
height: 50
|
height: 50
|
||||||
radius: Theme.cornerRadius
|
radius: Theme.cornerRadius
|
||||||
color: sourceArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : (modelData.active ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.08))
|
color: sourceArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : (modelData === AudioService.source ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.08))
|
||||||
border.color: modelData.active ? Theme.primary : "transparent"
|
border.color: modelData === AudioService.source ? Theme.primary : "transparent"
|
||||||
border.width: 1
|
border.width: 1
|
||||||
|
|
||||||
Row {
|
Row {
|
||||||
@@ -617,7 +672,7 @@ Item {
|
|||||||
}
|
}
|
||||||
font.family: Theme.iconFont
|
font.family: Theme.iconFont
|
||||||
font.pixelSize: Theme.iconSize
|
font.pixelSize: Theme.iconSize
|
||||||
color: modelData.active ? Theme.primary : Theme.surfaceText
|
color: modelData === AudioService.source ? Theme.primary : Theme.surfaceText
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -626,18 +681,18 @@ Item {
|
|||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
|
||||||
Text {
|
Text {
|
||||||
text: modelData.displayName
|
text: AudioService.displayName(modelData)
|
||||||
font.pixelSize: Theme.fontSizeMedium
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
color: modelData.active ? Theme.primary : Theme.surfaceText
|
color: modelData === AudioService.source ? Theme.primary : Theme.surfaceText
|
||||||
font.weight: modelData.active ? Font.Medium : Font.Normal
|
font.weight: modelData === AudioService.source ? Font.Medium : Font.Normal
|
||||||
}
|
}
|
||||||
|
|
||||||
Text {
|
Text {
|
||||||
text: {
|
text: {
|
||||||
if (modelData.subtitle && modelData.subtitle !== "")
|
if (AudioService.subtitle(modelData.name) && AudioService.subtitle(modelData.name) !== "")
|
||||||
return modelData.subtitle + (modelData.active ? " • Selected" : "");
|
return AudioService.subtitle(modelData.name) + (modelData === AudioService.source ? " • Selected" : "");
|
||||||
else
|
else
|
||||||
return modelData.active ? "Selected" : "";
|
return modelData === AudioService.source ? "Selected" : "";
|
||||||
}
|
}
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
|
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
|
||||||
@@ -655,7 +710,9 @@ Item {
|
|||||||
hoverEnabled: true
|
hoverEnabled: true
|
||||||
cursorShape: Qt.PointingHandCursor
|
cursorShape: Qt.PointingHandCursor
|
||||||
onClicked: {
|
onClicked: {
|
||||||
AudioService.setAudioSource(modelData.name);
|
if (modelData)
|
||||||
|
Pipewire.preferredDefaultAudioSource = modelData;
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -92,7 +92,7 @@ Item {
|
|||||||
|
|
||||||
Repeater {
|
Repeater {
|
||||||
model: BluetoothService.adapter && BluetoothService.adapter.devices ? BluetoothService.adapter.devices.values.filter((dev) => {
|
model: BluetoothService.adapter && BluetoothService.adapter.devices ? BluetoothService.adapter.devices.values.filter((dev) => {
|
||||||
return dev && dev.paired && BluetoothService.isValidDevice(dev);
|
return dev && dev.paired;
|
||||||
}) : []
|
}) : []
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
@@ -301,7 +301,7 @@ Item {
|
|||||||
return [];
|
return [];
|
||||||
|
|
||||||
var filtered = Bluetooth.devices.values.filter((dev) => {
|
var filtered = Bluetooth.devices.values.filter((dev) => {
|
||||||
return dev && !dev.paired && !dev.pairing && !dev.blocked && BluetoothService.isValidDevice(dev) && (dev.signalStrength === undefined || dev.signalStrength > 0);
|
return dev && !dev.paired && !dev.pairing && !dev.blocked && (dev.signalStrength === undefined || dev.signalStrength > 0);
|
||||||
});
|
});
|
||||||
return BluetoothService.sortDevices(filtered);
|
return BluetoothService.sortDevices(filtered);
|
||||||
}
|
}
|
||||||
@@ -500,7 +500,7 @@ Item {
|
|||||||
return false;
|
return false;
|
||||||
|
|
||||||
var availableCount = Bluetooth.devices.values.filter((dev) => {
|
var availableCount = Bluetooth.devices.values.filter((dev) => {
|
||||||
return dev && !dev.paired && !dev.pairing && !dev.blocked && BluetoothService.isValidDevice(dev) && (dev.signalStrength === undefined || dev.signalStrength > 0);
|
return dev && !dev.paired && !dev.pairing && !dev.blocked && (dev.signalStrength === undefined || dev.signalStrength > 0);
|
||||||
}).length;
|
}).length;
|
||||||
|
|
||||||
return availableCount === 0;
|
return availableCount === 0;
|
||||||
@@ -555,7 +555,7 @@ Item {
|
|||||||
return true;
|
return true;
|
||||||
|
|
||||||
var availableCount = Bluetooth.devices.values.filter((dev) => {
|
var availableCount = Bluetooth.devices.values.filter((dev) => {
|
||||||
return dev && !dev.paired && !dev.pairing && !dev.blocked && BluetoothService.isValidDevice(dev) && (dev.signalStrength === undefined || dev.signalStrength > 0);
|
return dev && !dev.paired && !dev.pairing && !dev.blocked && (dev.signalStrength === undefined || dev.signalStrength > 0);
|
||||||
}).length;
|
}).length;
|
||||||
|
|
||||||
return availableCount === 0 && !BluetoothService.adapter.discovering;
|
return availableCount === 0 && !BluetoothService.adapter.discovering;
|
||||||
|
|||||||
@@ -90,13 +90,16 @@ Rectangle {
|
|||||||
acceptedButtons: Qt.NoButton
|
acceptedButtons: Qt.NoButton
|
||||||
onWheel: function(wheelEvent) {
|
onWheel: function(wheelEvent) {
|
||||||
let delta = wheelEvent.angleDelta.y;
|
let delta = wheelEvent.angleDelta.y;
|
||||||
let currentVolume = AudioService.volumeLevel;
|
let currentVolume = (AudioService.sink && AudioService.sink.audio && AudioService.sink.audio.volume * 100) || 0;
|
||||||
let newVolume;
|
let newVolume;
|
||||||
if (delta > 0)
|
if (delta > 0)
|
||||||
newVolume = Math.min(100, currentVolume + 5);
|
newVolume = Math.min(100, currentVolume + 5);
|
||||||
else
|
else
|
||||||
newVolume = Math.max(0, currentVolume - 5);
|
newVolume = Math.max(0, currentVolume - 5);
|
||||||
AudioService.setVolume(newVolume);
|
if (AudioService.sink && AudioService.sink.audio) {
|
||||||
|
AudioService.sink.audio.muted = false;
|
||||||
|
AudioService.sink.audio.volume = newVolume / 100;
|
||||||
|
}
|
||||||
wheelEvent.accepted = true;
|
wheelEvent.accepted = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user