mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-04-16 10:42:06 -04:00
audio: defensive checks on PwNode objects
This commit is contained in:
@@ -260,7 +260,7 @@ Column {
|
|||||||
}
|
}
|
||||||
case "audioOutput":
|
case "audioOutput":
|
||||||
{
|
{
|
||||||
if (!AudioService.sink)
|
if (!AudioService.sink?.audio)
|
||||||
return "volume_off";
|
return "volume_off";
|
||||||
let volume = AudioService.sink.audio.volume;
|
let volume = AudioService.sink.audio.volume;
|
||||||
let muted = AudioService.sink.audio.muted;
|
let muted = AudioService.sink.audio.muted;
|
||||||
@@ -276,7 +276,7 @@ Column {
|
|||||||
}
|
}
|
||||||
case "audioInput":
|
case "audioInput":
|
||||||
{
|
{
|
||||||
if (!AudioService.source)
|
if (!AudioService.source?.audio)
|
||||||
return "mic_off";
|
return "mic_off";
|
||||||
let muted = AudioService.source.audio.muted;
|
let muted = AudioService.source.audio.muted;
|
||||||
return muted ? "mic_off" : "mic";
|
return muted ? "mic_off" : "mic";
|
||||||
@@ -369,7 +369,7 @@ Column {
|
|||||||
}
|
}
|
||||||
case "audioOutput":
|
case "audioOutput":
|
||||||
{
|
{
|
||||||
if (!AudioService.sink)
|
if (!AudioService.sink?.audio)
|
||||||
return I18n.tr("Select device", "audio status");
|
return I18n.tr("Select device", "audio status");
|
||||||
if (AudioService.sink.audio.muted)
|
if (AudioService.sink.audio.muted)
|
||||||
return I18n.tr("Muted", "audio status");
|
return I18n.tr("Muted", "audio status");
|
||||||
@@ -380,7 +380,7 @@ Column {
|
|||||||
}
|
}
|
||||||
case "audioInput":
|
case "audioInput":
|
||||||
{
|
{
|
||||||
if (!AudioService.source)
|
if (!AudioService.source?.audio)
|
||||||
return I18n.tr("Select device", "audio status");
|
return I18n.tr("Select device", "audio status");
|
||||||
if (AudioService.source.audio.muted)
|
if (AudioService.source.audio.muted)
|
||||||
return I18n.tr("Muted", "audio status");
|
return I18n.tr("Muted", "audio status");
|
||||||
@@ -412,9 +412,9 @@ Column {
|
|||||||
case "bluetooth":
|
case "bluetooth":
|
||||||
return !!(BluetoothService.available && BluetoothService.adapter && BluetoothService.adapter.enabled);
|
return !!(BluetoothService.available && BluetoothService.adapter && BluetoothService.adapter.enabled);
|
||||||
case "audioOutput":
|
case "audioOutput":
|
||||||
return !!(AudioService.sink && !AudioService.sink.audio.muted);
|
return !!(AudioService.sink?.audio && !AudioService.sink.audio.muted);
|
||||||
case "audioInput":
|
case "audioInput":
|
||||||
return !!(AudioService.source && !AudioService.source.audio.muted);
|
return !!(AudioService.source?.audio && !AudioService.source.audio.muted);
|
||||||
default:
|
default:
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -351,8 +351,8 @@ Rectangle {
|
|||||||
deviceRipple.trigger(mapped.x, mapped.y);
|
deviceRipple.trigger(mapped.x, mapped.y);
|
||||||
}
|
}
|
||||||
onClicked: {
|
onClicked: {
|
||||||
if (modelData) {
|
if (modelData && modelData.name) {
|
||||||
Pipewire.preferredDefaultAudioSource = modelData;
|
AudioService.setDefaultSourceByName(modelData.name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -355,8 +355,8 @@ Rectangle {
|
|||||||
deviceRipple.trigger(mapped.x, mapped.y);
|
deviceRipple.trigger(mapped.x, mapped.y);
|
||||||
}
|
}
|
||||||
onClicked: {
|
onClicked: {
|
||||||
if (modelData) {
|
if (modelData && modelData.name) {
|
||||||
Pipewire.preferredDefaultAudioSink = modelData;
|
AudioService.setDefaultSinkByName(modelData.name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ Row {
|
|||||||
cursorShape: Qt.PointingHandCursor
|
cursorShape: Qt.PointingHandCursor
|
||||||
onPressed: mouse => iconRipple.trigger(mouse.x, mouse.y)
|
onPressed: mouse => iconRipple.trigger(mouse.x, mouse.y)
|
||||||
onClicked: {
|
onClicked: {
|
||||||
if (defaultSink) {
|
if (defaultSink?.audio) {
|
||||||
SessionData.suppressOSDTemporarily();
|
SessionData.suppressOSDTemporarily();
|
||||||
defaultSink.audio.muted = !defaultSink.audio.muted;
|
defaultSink.audio.muted = !defaultSink.audio.muted;
|
||||||
}
|
}
|
||||||
@@ -45,7 +45,7 @@ Row {
|
|||||||
DankIcon {
|
DankIcon {
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
name: {
|
name: {
|
||||||
if (!defaultSink)
|
if (!defaultSink?.audio)
|
||||||
return "volume_off";
|
return "volume_off";
|
||||||
|
|
||||||
let volume = defaultSink.audio.volume;
|
let volume = defaultSink.audio.volume;
|
||||||
@@ -62,18 +62,18 @@ Row {
|
|||||||
return "volume_up";
|
return "volume_up";
|
||||||
}
|
}
|
||||||
size: Theme.iconSize
|
size: Theme.iconSize
|
||||||
color: defaultSink && !defaultSink.audio.muted && defaultSink.audio.volume > 0 ? Theme.primary : Theme.surfaceText
|
color: defaultSink?.audio && !defaultSink.audio.muted && defaultSink.audio.volume > 0 ? Theme.primary : Theme.surfaceText
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
DankSlider {
|
DankSlider {
|
||||||
id: volumeSlider
|
id: volumeSlider
|
||||||
|
|
||||||
readonly property real actualVolumePercent: defaultSink ? Math.round(defaultSink.audio.volume * 100) : 0
|
readonly property real actualVolumePercent: defaultSink?.audio ? Math.round(defaultSink.audio.volume * 100) : 0
|
||||||
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
width: parent.width - (Theme.iconSize + Theme.spacingS * 2)
|
width: parent.width - (Theme.iconSize + Theme.spacingS * 2)
|
||||||
enabled: defaultSink !== null
|
enabled: defaultSink?.audio != null
|
||||||
minimum: 0
|
minimum: 0
|
||||||
maximum: AudioService.sinkMaxVolume
|
maximum: AudioService.sinkMaxVolume
|
||||||
showValue: true
|
showValue: true
|
||||||
@@ -83,7 +83,7 @@ Row {
|
|||||||
trackColor: root.sliderTrackColor.a > 0 ? root.sliderTrackColor : Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
|
trackColor: root.sliderTrackColor.a > 0 ? root.sliderTrackColor : Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
|
||||||
|
|
||||||
onSliderValueChanged: function (newValue) {
|
onSliderValueChanged: function (newValue) {
|
||||||
if (defaultSink) {
|
if (defaultSink?.audio) {
|
||||||
SessionData.suppressOSDTemporarily();
|
SessionData.suppressOSDTemporarily();
|
||||||
defaultSink.audio.volume = newValue / 100.0;
|
defaultSink.audio.volume = newValue / 100.0;
|
||||||
if (newValue > 0 && defaultSink.audio.muted) {
|
if (newValue > 0 && defaultSink.audio.muted) {
|
||||||
@@ -97,7 +97,7 @@ Row {
|
|||||||
Binding {
|
Binding {
|
||||||
target: volumeSlider
|
target: volumeSlider
|
||||||
property: "value"
|
property: "value"
|
||||||
value: defaultSink ? Math.min(AudioService.sinkMaxVolume, Math.round(defaultSink.audio.volume * 100)) : 0
|
value: defaultSink?.audio ? Math.min(AudioService.sinkMaxVolume, Math.round(defaultSink.audio.volume * 100)) : 0
|
||||||
when: !volumeSlider.isDragging
|
when: !volumeSlider.isDragging
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ Row {
|
|||||||
cursorShape: Qt.PointingHandCursor
|
cursorShape: Qt.PointingHandCursor
|
||||||
onPressed: mouse => iconRipple.trigger(mouse.x, mouse.y)
|
onPressed: mouse => iconRipple.trigger(mouse.x, mouse.y)
|
||||||
onClicked: {
|
onClicked: {
|
||||||
if (defaultSource) {
|
if (defaultSource?.audio) {
|
||||||
SessionData.suppressOSDTemporarily();
|
SessionData.suppressOSDTemporarily();
|
||||||
defaultSource.audio.muted = !defaultSource.audio.muted;
|
defaultSource.audio.muted = !defaultSource.audio.muted;
|
||||||
}
|
}
|
||||||
@@ -45,7 +45,7 @@ Row {
|
|||||||
DankIcon {
|
DankIcon {
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
name: {
|
name: {
|
||||||
if (!defaultSource)
|
if (!defaultSource?.audio)
|
||||||
return "mic_off";
|
return "mic_off";
|
||||||
|
|
||||||
let volume = defaultSource.audio.volume;
|
let volume = defaultSource.audio.volume;
|
||||||
@@ -56,26 +56,26 @@ Row {
|
|||||||
return "mic";
|
return "mic";
|
||||||
}
|
}
|
||||||
size: Theme.iconSize
|
size: Theme.iconSize
|
||||||
color: defaultSource && !defaultSource.audio.muted && defaultSource.audio.volume > 0 ? Theme.primary : Theme.surfaceText
|
color: defaultSource?.audio && !defaultSource.audio.muted && defaultSource.audio.volume > 0 ? Theme.primary : Theme.surfaceText
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
DankSlider {
|
DankSlider {
|
||||||
readonly property real actualVolumePercent: defaultSource ? Math.round(defaultSource.audio.volume * 100) : 0
|
readonly property real actualVolumePercent: defaultSource?.audio ? Math.round(defaultSource.audio.volume * 100) : 0
|
||||||
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
width: parent.width - (Theme.iconSize + Theme.spacingS * 2)
|
width: parent.width - (Theme.iconSize + Theme.spacingS * 2)
|
||||||
enabled: defaultSource !== null
|
enabled: defaultSource?.audio != null
|
||||||
minimum: 0
|
minimum: 0
|
||||||
maximum: 100
|
maximum: 100
|
||||||
value: defaultSource ? Math.min(100, Math.round(defaultSource.audio.volume * 100)) : 0
|
value: defaultSource?.audio ? Math.min(100, Math.round(defaultSource.audio.volume * 100)) : 0
|
||||||
showValue: true
|
showValue: true
|
||||||
unit: "%"
|
unit: "%"
|
||||||
valueOverride: actualVolumePercent
|
valueOverride: actualVolumePercent
|
||||||
thumbOutlineColor: Theme.surfaceContainer
|
thumbOutlineColor: Theme.surfaceContainer
|
||||||
trackColor: root.sliderTrackColor.a > 0 ? root.sliderTrackColor : Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
|
trackColor: root.sliderTrackColor.a > 0 ? root.sliderTrackColor : Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
|
||||||
onSliderValueChanged: function (newValue) {
|
onSliderValueChanged: function (newValue) {
|
||||||
if (defaultSource) {
|
if (defaultSource?.audio) {
|
||||||
SessionData.suppressOSDTemporarily();
|
SessionData.suppressOSDTemporarily();
|
||||||
defaultSource.audio.volume = newValue / 100.0;
|
defaultSource.audio.volume = newValue / 100.0;
|
||||||
if (newValue > 0 && defaultSource.audio.muted) {
|
if (newValue > 0 && defaultSource.audio.muted) {
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import QtQuick
|
import QtQuick
|
||||||
import QtQuick.Effects
|
|
||||||
import Quickshell.Services.Pipewire
|
import Quickshell.Services.Pipewire
|
||||||
import qs.Common
|
import qs.Common
|
||||||
import qs.Services
|
import qs.Services
|
||||||
@@ -25,7 +24,7 @@ Item {
|
|||||||
}
|
}
|
||||||
property bool usePlayerVolume: activePlayer && activePlayer.volumeSupported && !__isChromeBrowser
|
property bool usePlayerVolume: activePlayer && activePlayer.volumeSupported && !__isChromeBrowser
|
||||||
property real currentVolume: usePlayerVolume ? activePlayer.volume : (AudioService.sink?.audio?.volume ?? 0)
|
property real currentVolume: usePlayerVolume ? activePlayer.volume : (AudioService.sink?.audio?.volume ?? 0)
|
||||||
property bool volumeAvailable: (activePlayer && activePlayer.volumeSupported && !__isChromeBrowser) || (AudioService.sink && AudioService.sink.audio)
|
property bool volumeAvailable: !!((activePlayer && activePlayer.volumeSupported && !__isChromeBrowser) || (AudioService.sink && AudioService.sink.audio))
|
||||||
property var availableDevices: {
|
property var availableDevices: {
|
||||||
const hidden = SessionData.hiddenOutputDeviceNames ?? [];
|
const hidden = SessionData.hiddenOutputDeviceNames ?? [];
|
||||||
return Pipewire.nodes.values.filter(node => {
|
return Pipewire.nodes.values.filter(node => {
|
||||||
@@ -336,8 +335,8 @@ Item {
|
|||||||
hoverEnabled: true
|
hoverEnabled: true
|
||||||
cursorShape: Qt.PointingHandCursor
|
cursorShape: Qt.PointingHandCursor
|
||||||
onClicked: {
|
onClicked: {
|
||||||
if (modelData) {
|
if (modelData && modelData.name) {
|
||||||
Pipewire.preferredDefaultAudioSink = modelData;
|
AudioService.setDefaultSinkByName(modelData.name);
|
||||||
root.deviceSelected(modelData);
|
root.deviceSelected(modelData);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ Item {
|
|||||||
const id = activePlayer.identity.toLowerCase();
|
const id = activePlayer.identity.toLowerCase();
|
||||||
return id.includes("chrome") || id.includes("chromium");
|
return id.includes("chrome") || id.includes("chromium");
|
||||||
}
|
}
|
||||||
readonly property bool volumeAvailable: (activePlayer && activePlayer.volumeSupported && !__isChromeBrowser) || (AudioService.sink && AudioService.sink.audio)
|
readonly property bool volumeAvailable: !!((activePlayer && activePlayer.volumeSupported && !__isChromeBrowser) || (AudioService.sink && AudioService.sink.audio))
|
||||||
readonly property bool usePlayerVolume: activePlayer && activePlayer.volumeSupported && !__isChromeBrowser
|
readonly property bool usePlayerVolume: activePlayer && activePlayer.volumeSupported && !__isChromeBrowser
|
||||||
readonly property real currentVolume: usePlayerVolume ? activePlayer.volume : (AudioService.sink?.audio?.volume ?? 0)
|
readonly property real currentVolume: usePlayerVolume ? activePlayer.volume : (AudioService.sink?.audio?.volume ?? 0)
|
||||||
|
|
||||||
|
|||||||
@@ -71,18 +71,48 @@ Singleton {
|
|||||||
// Used in playLoginSoundIfApplicable()
|
// Used in playLoginSoundIfApplicable()
|
||||||
Process {
|
Process {
|
||||||
id: loginSoundChecker
|
id: loginSoundChecker
|
||||||
onExited: (exitCode) => {
|
onExited: exitCode => {
|
||||||
if (exitCode === 0) {
|
if (exitCode === 0) {
|
||||||
playLoginSound();
|
playLoginSound();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getAvailableSinks() {
|
function getAvailableSinks() {
|
||||||
const hidden = SessionData.hiddenOutputDeviceNames ?? [];
|
const hidden = SessionData.hiddenOutputDeviceNames ?? [];
|
||||||
return Pipewire.nodes.values.filter(node => node.audio && node.isSink && !node.isStream && !hidden.includes(node.name));
|
return Pipewire.nodes.values.filter(node => node.audio && node.isSink && !node.isStream && !hidden.includes(node.name));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Resolve a PwNode by name from the live typed list and assign it as the
|
||||||
|
// default sink. Going through Pipewire.nodes.values directly (no .filter
|
||||||
|
// / spread / .sort / property var) avoids QML type erasure to QObject*,
|
||||||
|
// which newer quickshell rejects when assigning to preferredDefaultAudioSink.
|
||||||
|
function setDefaultSinkByName(name) {
|
||||||
|
if (!name)
|
||||||
|
return false;
|
||||||
|
for (let i = 0; i < Pipewire.nodes.values.length; i++) {
|
||||||
|
const node = Pipewire.nodes.values[i];
|
||||||
|
if (node && node.name === name && node.audio && node.isSink && !node.isStream) {
|
||||||
|
Pipewire.preferredDefaultAudioSink = node;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setDefaultSourceByName(name) {
|
||||||
|
if (!name)
|
||||||
|
return false;
|
||||||
|
for (let i = 0; i < Pipewire.nodes.values.length; i++) {
|
||||||
|
const node = Pipewire.nodes.values[i];
|
||||||
|
if (node && node.name === name && node.audio && !node.isSink && !node.isStream) {
|
||||||
|
Pipewire.preferredDefaultAudioSource = node;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
function cycleAudioOutput() {
|
function cycleAudioOutput() {
|
||||||
const sinks = getAvailableSinks();
|
const sinks = getAvailableSinks();
|
||||||
if (sinks.length < 2)
|
if (sinks.length < 2)
|
||||||
@@ -92,6 +122,7 @@ Singleton {
|
|||||||
const currentIndex = sinks.findIndex(s => s.name === currentName);
|
const currentIndex = sinks.findIndex(s => s.name === currentName);
|
||||||
const nextIndex = (currentIndex + 1) % sinks.length;
|
const nextIndex = (currentIndex + 1) % sinks.length;
|
||||||
const nextSink = sinks[nextIndex];
|
const nextSink = sinks[nextIndex];
|
||||||
|
if (!setDefaultSinkByName(nextSink.name))
|
||||||
Pipewire.preferredDefaultAudioSink = nextSink;
|
Pipewire.preferredDefaultAudioSink = nextSink;
|
||||||
const name = displayName(nextSink);
|
const name = displayName(nextSink);
|
||||||
audioOutputCycled(name, sinkIcon(nextSink));
|
audioOutputCycled(name, sinkIcon(nextSink));
|
||||||
@@ -650,7 +681,6 @@ EOFCONFIG
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
`, root, "AudioService.LoginSound");
|
`, root, "AudioService.LoginSound");
|
||||||
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.warn("AudioService: Error creating sound players:", e);
|
console.warn("AudioService: Error creating sound players:", e);
|
||||||
}
|
}
|
||||||
@@ -704,7 +734,8 @@ EOFCONFIG
|
|||||||
const runtimeDir = Quickshell.env("XDG_RUNTIME_DIR");
|
const runtimeDir = Quickshell.env("XDG_RUNTIME_DIR");
|
||||||
const sessionId = Quickshell.env("XDG_SESSION_ID") || "0";
|
const sessionId = Quickshell.env("XDG_SESSION_ID") || "0";
|
||||||
|
|
||||||
if (!runtimeDir) return;
|
if (!runtimeDir)
|
||||||
|
return;
|
||||||
|
|
||||||
const loginFile = `${runtimeDir}/danklinux.login-${sessionId}`;
|
const loginFile = `${runtimeDir}/danklinux.login-${sessionId}`;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user