mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-10-18 09:14:01 -04:00
audio: add mono audio toggle with support detection (#3539)
This commit is contained in:
@@ -258,6 +258,7 @@ Singleton {
|
||||
property string calendarBackend: Spec.SPEC.calendarBackend.def
|
||||
property string defaultTaskCalendarId: Spec.SPEC.defaultTaskCalendarId.def
|
||||
property bool audioShowStreamDevices: Spec.SPEC.audioShowStreamDevices.def
|
||||
property bool audioMono: Spec.SPEC.audioMono.def
|
||||
property string clockFormat: Spec.SPEC.clockFormat.def
|
||||
readonly property bool localeUses24Hour: {
|
||||
const fmt = Qt.locale().timeFormat(Locale.ShortFormat).replace(/'[^']*'/g, "");
|
||||
|
||||
@@ -339,6 +339,9 @@ var LOCAL_SPEC = {
|
||||
audioShowStreamDevices: {
|
||||
def: false
|
||||
},
|
||||
audioMono: {
|
||||
def: false
|
||||
},
|
||||
windSpeedUnit: {
|
||||
def: "kmh"
|
||||
},
|
||||
|
||||
@@ -8,6 +8,8 @@ import qs.Modules.Settings.Widgets
|
||||
Item {
|
||||
id: root
|
||||
|
||||
readonly property var log: Log.scoped("AudioTab")
|
||||
|
||||
LayoutMirroring.enabled: I18n.isRtl
|
||||
LayoutMirroring.childrenInherit: true
|
||||
|
||||
@@ -119,6 +121,32 @@ Item {
|
||||
SettingsPage {
|
||||
id: mainColumn
|
||||
|
||||
SettingsCard {
|
||||
tab: "audio"
|
||||
tags: ["audio", "accessibility", "mono"]
|
||||
title: I18n.tr("Mono Audio", "Audio settings: mono audio toggle")
|
||||
settingKey: "audioMono"
|
||||
iconName: "a11y"
|
||||
|
||||
SettingsToggleRow {
|
||||
tab: "audio"
|
||||
tags: ["audio", "mono"]
|
||||
settingKey: "audioMono"
|
||||
text: I18n.tr("Mono Audio", "Audio settings: mono audio toggle")
|
||||
description: I18n.tr("Mix all stereo content into a single channel", "Audio settings mono description")
|
||||
enabled: AudioService.monoSettingSupported
|
||||
checked: SettingsData.audioMono
|
||||
onToggled: checked => {
|
||||
SettingsData.set("audioMono", checked);
|
||||
AudioService.setMonoSetting(checked, (ok, message) => {
|
||||
if (!ok) {
|
||||
SettingsData.set("audioMono", !checked);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SettingsCard {
|
||||
tab: "audio"
|
||||
tags: ["audio", "device", "output", "speaker"]
|
||||
|
||||
@@ -62,6 +62,9 @@ Singleton {
|
||||
readonly property int sourceVolumePercent: Math.round((source?.audio?.volume ?? 0) * 100)
|
||||
readonly property int wheelVolumeStep: SettingsData.audioWheelScrollAmount
|
||||
|
||||
property bool monoSettingSupported: false
|
||||
property string monoSettingBackend: ""
|
||||
|
||||
signal micMuteChanged
|
||||
signal micVolumeChanged
|
||||
signal audioOutputCycled(string deviceName, string deviceIcon)
|
||||
@@ -134,6 +137,15 @@ Singleton {
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: SettingsData
|
||||
function onAudioMonoChanged() {
|
||||
if (root.monoSettingSupported) {
|
||||
root.setMonoSetting(SettingsData.audioMono);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function setSink(node: PwNode): bool {
|
||||
if (!node)
|
||||
return false;
|
||||
@@ -1276,6 +1288,78 @@ EOFCONFIG
|
||||
return root.sink.audio.muted ? "Audio muted" : "Audio unmuted";
|
||||
}
|
||||
|
||||
// Mono audio toggle
|
||||
property bool _monoQueryInProgress: false
|
||||
|
||||
function queryMonoSetting(callback) {
|
||||
if (root._monoQueryInProgress) {
|
||||
return;
|
||||
}
|
||||
root._monoQueryInProgress = true;
|
||||
// Try pactl first (PipeWire ≥1.4.10 /1.6.0)
|
||||
Proc.runCommand("audio-query-mono", ["env", "LC_ALL=C", "pactl", "send-message", "/core", "pipewire-pulse:force-mono-output", ""], (output, exitCode) => {
|
||||
if (exitCode === 0 && output) {
|
||||
const trimmed = output.trim();
|
||||
if (trimmed === "true" || trimmed === "false") {
|
||||
root.monoSettingSupported = true;
|
||||
root.monoSettingBackend = "pactl";
|
||||
root._monoQueryInProgress = false;
|
||||
callback(true, trimmed === "true");
|
||||
return;
|
||||
}
|
||||
}
|
||||
// Fallback to wpctl settings
|
||||
Proc.runCommand("audio-query-mono-wpctl", ["env", "LC_ALL=C", "wpctl", "settings", "node.features.audio.mono"], (output2, exitCode2) => {
|
||||
if (exitCode2 === 0 && output2) {
|
||||
const m = output2.match(/\b(true|false)\b/);
|
||||
if (m) {
|
||||
root.monoSettingSupported = true;
|
||||
root.monoSettingBackend = "wpctl";
|
||||
root._monoQueryInProgress = false;
|
||||
callback(true, m[1] === "true");
|
||||
return;
|
||||
}
|
||||
}
|
||||
root.monoSettingSupported = false;
|
||||
root.monoSettingBackend = "";
|
||||
root._monoQueryInProgress = false;
|
||||
callback(false, false);
|
||||
});
|
||||
}, 0);
|
||||
}
|
||||
|
||||
// Re-apply the saved preference: the live PipeWire value is runtime-only and resets on restart
|
||||
function applyMonoStartupPreference() {
|
||||
if (!SettingsData._hasLoaded)
|
||||
return;
|
||||
queryMonoSetting((supported, currentValue) => {
|
||||
if (supported && currentValue !== SettingsData.audioMono)
|
||||
root.setMonoSetting(SettingsData.audioMono);
|
||||
});
|
||||
}
|
||||
|
||||
function setMonoSetting(enabled, callback) {
|
||||
const boolStr = enabled ? "true" : "false";
|
||||
const backend = root.monoSettingBackend;
|
||||
if (backend === "pactl") {
|
||||
Proc.runCommand("audio-set-mono", ["env", "LC_ALL=C", "pactl", "send-message", "/core", "pipewire-pulse:force-mono-output", boolStr], (output, exitCode) => {
|
||||
const ok = exitCode === 0;
|
||||
if (callback) {
|
||||
callback(ok, ok ? "Mono audio enabled" : (output || "Failed to set mono audio"));
|
||||
}
|
||||
}, 0);
|
||||
} else if (backend === "wpctl") {
|
||||
Proc.runCommand("audio-set-mono-wpctl", ["env", "LC_ALL=C", "wpctl", "settings", "node.features.audio.mono", boolStr], (output, exitCode) => {
|
||||
const ok = exitCode === 0;
|
||||
if (callback) {
|
||||
callback(ok, ok ? "Mono audio enabled" : (output || "Failed to set mono audio"));
|
||||
}
|
||||
}, 0);
|
||||
} else if (callback) {
|
||||
callback(false, "Mono audio not supported");
|
||||
}
|
||||
}
|
||||
|
||||
function setMicVolume(percentage) {
|
||||
if (!root.source?.audio) {
|
||||
return "No audio source available";
|
||||
@@ -1363,6 +1447,27 @@ EOFCONFIG
|
||||
return root.toggleMicMute();
|
||||
}
|
||||
|
||||
function setmono(status: string): string {
|
||||
const enabled = status === "true";
|
||||
if (!root.monoSettingSupported) {
|
||||
return "Mono audio not supported";
|
||||
}
|
||||
// Async: persist setting only on success
|
||||
root.setMonoSetting(enabled, (ok, message) => {
|
||||
if (ok) {
|
||||
SettingsData.set("audioMono", enabled);
|
||||
}
|
||||
});
|
||||
return "Mono audio toggle sent";
|
||||
}
|
||||
|
||||
function getmono(): string {
|
||||
if (!root.monoSettingSupported) {
|
||||
return "Mono audio not supported";
|
||||
}
|
||||
return `Mono Audio: ${root.monoSettingBackend} ${SettingsData.audioMono ? "enabled" : "disabled"}`;
|
||||
}
|
||||
|
||||
function status(): string {
|
||||
let result = "Audio Status:\n";
|
||||
|
||||
@@ -1436,10 +1541,17 @@ EOFCONFIG
|
||||
checkSoundThemeSupport();
|
||||
}
|
||||
|
||||
readonly property bool settingsLoaded: SettingsData._hasLoaded
|
||||
onSettingsLoadedChanged: {
|
||||
if (settingsLoaded)
|
||||
root.applyMonoStartupPreference();
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
rebuildTypedNodeLists();
|
||||
loadDeviceAliases();
|
||||
if (SettingsData.soundsEnabled && SettingsData.useSystemSoundTheme)
|
||||
getCurrentSoundTheme();
|
||||
root.applyMonoStartupPreference();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11284,6 +11284,31 @@
|
||||
"parentLabel": "Sound & media",
|
||||
"icon": "mic"
|
||||
},
|
||||
{
|
||||
"section": "audioMono",
|
||||
"label": "Mono Audio",
|
||||
"tabIndex": 29,
|
||||
"category": "Audio",
|
||||
"keywords": [
|
||||
"accessibility",
|
||||
"audio",
|
||||
"channel",
|
||||
"content",
|
||||
"headphones",
|
||||
"media",
|
||||
"microphone",
|
||||
"mono",
|
||||
"pipewire",
|
||||
"single",
|
||||
"sound",
|
||||
"speaker",
|
||||
"stereo",
|
||||
"volume"
|
||||
],
|
||||
"parentLabel": "Sound & media",
|
||||
"icon": "a11y",
|
||||
"description": "Mix all stereo content into a single channel"
|
||||
},
|
||||
{
|
||||
"section": "audioOutputDevices",
|
||||
"label": "Output devices",
|
||||
|
||||
Reference in New Issue
Block a user