mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-04-08 14:52:08 -04:00
audio: add per-device max volume limit setting
This commit is contained in:
@@ -451,10 +451,11 @@ Column {
|
||||
if (!AudioService.sink || !AudioService.sink.audio)
|
||||
return;
|
||||
let delta = wheelEvent.angleDelta.y;
|
||||
let maxVol = AudioService.sinkMaxVolume;
|
||||
let currentVolume = AudioService.sink.audio.volume * 100;
|
||||
let newVolume;
|
||||
if (delta > 0)
|
||||
newVolume = Math.min(100, currentVolume + 5);
|
||||
newVolume = Math.min(maxVol, currentVolume + 5);
|
||||
else
|
||||
newVolume = Math.max(0, currentVolume - 5);
|
||||
AudioService.sink.audio.muted = false;
|
||||
|
||||
@@ -102,8 +102,8 @@ Rectangle {
|
||||
width: parent.width - (Theme.iconSize + Theme.spacingS * 2)
|
||||
enabled: AudioService.sink && AudioService.sink.audio
|
||||
minimum: 0
|
||||
maximum: 100
|
||||
value: AudioService.sink && AudioService.sink.audio ? Math.min(100, Math.round(AudioService.sink.audio.volume * 100)) : 0
|
||||
maximum: AudioService.sinkMaxVolume
|
||||
value: AudioService.sink && AudioService.sink.audio ? Math.min(AudioService.sinkMaxVolume, Math.round(AudioService.sink.audio.volume * 100)) : 0
|
||||
showValue: true
|
||||
unit: "%"
|
||||
valueOverride: actualVolumePercent
|
||||
@@ -136,15 +136,15 @@ Rectangle {
|
||||
|
||||
function normalizePinList(value) {
|
||||
if (Array.isArray(value))
|
||||
return value.filter(v => v)
|
||||
return value.filter(v => v);
|
||||
if (typeof value === "string" && value.length > 0)
|
||||
return [value]
|
||||
return []
|
||||
return [value];
|
||||
return [];
|
||||
}
|
||||
|
||||
function getPinnedOutputs() {
|
||||
const pins = SettingsData.audioOutputDevicePins || {}
|
||||
return normalizePinList(pins["preferredOutput"])
|
||||
const pins = SettingsData.audioOutputDevicePins || {};
|
||||
return normalizePinList(pins["preferredOutput"]);
|
||||
}
|
||||
|
||||
Column {
|
||||
@@ -163,14 +163,14 @@ Rectangle {
|
||||
let sorted = [...nodes];
|
||||
sorted.sort((a, b) => {
|
||||
// Pinned device first
|
||||
const aPinnedIndex = pinnedList.indexOf(a.name)
|
||||
const bPinnedIndex = pinnedList.indexOf(b.name)
|
||||
const aPinnedIndex = pinnedList.indexOf(a.name);
|
||||
const bPinnedIndex = pinnedList.indexOf(b.name);
|
||||
if (aPinnedIndex !== -1 || bPinnedIndex !== -1) {
|
||||
if (aPinnedIndex === -1)
|
||||
return 1
|
||||
return 1;
|
||||
if (bPinnedIndex === -1)
|
||||
return -1
|
||||
return aPinnedIndex - bPinnedIndex
|
||||
return -1;
|
||||
return aPinnedIndex - bPinnedIndex;
|
||||
}
|
||||
// Then active device
|
||||
if (a === AudioService.sink && b !== AudioService.sink)
|
||||
@@ -292,24 +292,24 @@ Rectangle {
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
const pins = JSON.parse(JSON.stringify(SettingsData.audioOutputDevicePins || {}))
|
||||
let pinnedList = audioContent.normalizePinList(pins["preferredOutput"])
|
||||
const pinIndex = pinnedList.indexOf(modelData.name)
|
||||
const pins = JSON.parse(JSON.stringify(SettingsData.audioOutputDevicePins || {}));
|
||||
let pinnedList = audioContent.normalizePinList(pins["preferredOutput"]);
|
||||
const pinIndex = pinnedList.indexOf(modelData.name);
|
||||
|
||||
if (pinIndex !== -1) {
|
||||
pinnedList.splice(pinIndex, 1)
|
||||
pinnedList.splice(pinIndex, 1);
|
||||
} else {
|
||||
pinnedList.unshift(modelData.name)
|
||||
pinnedList.unshift(modelData.name);
|
||||
if (pinnedList.length > audioContent.maxPinnedOutputs)
|
||||
pinnedList = pinnedList.slice(0, audioContent.maxPinnedOutputs)
|
||||
pinnedList = pinnedList.slice(0, audioContent.maxPinnedOutputs);
|
||||
}
|
||||
|
||||
if (pinnedList.length > 0)
|
||||
pins["preferredOutput"] = pinnedList
|
||||
pins["preferredOutput"] = pinnedList;
|
||||
else
|
||||
delete pins["preferredOutput"]
|
||||
delete pins["preferredOutput"];
|
||||
|
||||
SettingsData.set("audioOutputDevicePins", pins)
|
||||
SettingsData.set("audioOutputDevicePins", pins);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,7 +69,7 @@ Row {
|
||||
width: parent.width - (Theme.iconSize + Theme.spacingS * 2)
|
||||
enabled: defaultSink !== null
|
||||
minimum: 0
|
||||
maximum: 100
|
||||
maximum: AudioService.sinkMaxVolume
|
||||
showValue: true
|
||||
unit: "%"
|
||||
valueOverride: actualVolumePercent
|
||||
@@ -91,7 +91,7 @@ Row {
|
||||
Binding {
|
||||
target: volumeSlider
|
||||
property: "value"
|
||||
value: defaultSink ? Math.min(100, Math.round(defaultSink.audio.volume * 100)) : 0
|
||||
value: defaultSink ? Math.min(AudioService.sinkMaxVolume, Math.round(defaultSink.audio.volume * 100)) : 0
|
||||
when: !volumeSlider.isDragging
|
||||
}
|
||||
}
|
||||
|
||||
@@ -140,8 +140,9 @@ BasePill {
|
||||
volumeAccumulator = 0;
|
||||
}
|
||||
|
||||
const maxVol = AudioService.sinkMaxVolume;
|
||||
const currentVolume = AudioService.sink.audio.volume * 100;
|
||||
const newVolume = delta > 0 ? Math.min(100, currentVolume + step) : Math.max(0, currentVolume - step);
|
||||
const newVolume = delta > 0 ? Math.min(maxVol, currentVolume + step) : Math.max(0, currentVolume - step);
|
||||
AudioService.sink.audio.muted = false;
|
||||
AudioService.sink.audio.volume = newVolume / 100;
|
||||
AudioService.playVolumeChangeSoundIfEnabled();
|
||||
|
||||
@@ -201,8 +201,9 @@ Item {
|
||||
function adjustVolume(step) {
|
||||
if (!volumeAvailable)
|
||||
return;
|
||||
const maxVol = usePlayerVolume ? 100 : AudioService.sinkMaxVolume;
|
||||
const current = Math.round(currentVolume * 100);
|
||||
const newVolume = Math.min(100, Math.max(0, current + step));
|
||||
const newVolume = Math.min(maxVol, Math.max(0, current + step));
|
||||
|
||||
SessionData.suppressOSDTemporarily();
|
||||
if (usePlayerVolume) {
|
||||
@@ -778,7 +779,8 @@ Item {
|
||||
SessionData.suppressOSDTemporarily();
|
||||
const delta = wheelEvent.angleDelta.y;
|
||||
const current = (currentVolume * 100) || 0;
|
||||
const newVolume = delta > 0 ? Math.min(100, current + 5) : Math.max(0, current - 5);
|
||||
const maxVol = usePlayerVolume ? 100 : AudioService.sinkMaxVolume;
|
||||
const newVolume = delta > 0 ? Math.min(maxVol, current + 5) : Math.max(0, current - 5);
|
||||
|
||||
if (usePlayerVolume) {
|
||||
activePlayer.volume = newVolume / 100;
|
||||
|
||||
@@ -95,7 +95,7 @@ DankOSD {
|
||||
x: parent.gap * 2 + Theme.iconSize
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
minimum: 0
|
||||
maximum: 100
|
||||
maximum: AudioService.sinkMaxVolume
|
||||
enabled: AudioService.sink && AudioService.sink.audio
|
||||
showValue: true
|
||||
unit: "%"
|
||||
@@ -105,7 +105,7 @@ DankOSD {
|
||||
|
||||
Component.onCompleted: {
|
||||
if (AudioService.sink && AudioService.sink.audio) {
|
||||
value = Math.min(100, Math.round(AudioService.sink.audio.volume * 100));
|
||||
value = Math.min(AudioService.sinkMaxVolume, Math.round(AudioService.sink.audio.volume * 100));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -126,7 +126,7 @@ DankOSD {
|
||||
|
||||
function onVolumeChanged() {
|
||||
if (volumeSlider && !volumeSlider.pressed) {
|
||||
volumeSlider.value = Math.min(100, Math.round(AudioService.sink.audio.volume * 100));
|
||||
volumeSlider.value = Math.min(AudioService.sinkMaxVolume, Math.round(AudioService.sink.audio.volume * 100));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -179,7 +179,7 @@ DankOSD {
|
||||
y: gap * 2 + Theme.iconSize
|
||||
|
||||
property bool dragging: false
|
||||
property int value: AudioService.sink && AudioService.sink.audio ? Math.min(100, Math.round(AudioService.sink.audio.volume * 100)) : 0
|
||||
property int value: AudioService.sink && AudioService.sink.audio ? Math.min(AudioService.sinkMaxVolume, Math.round(AudioService.sink.audio.volume * 100)) : 0
|
||||
|
||||
Rectangle {
|
||||
id: vertTrack
|
||||
@@ -193,7 +193,7 @@ DankOSD {
|
||||
Rectangle {
|
||||
id: vertFill
|
||||
width: parent.width
|
||||
height: (vertSlider.value / 100) * parent.height
|
||||
height: (vertSlider.value / AudioService.sinkMaxVolume) * parent.height
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
color: Theme.primary
|
||||
@@ -206,7 +206,7 @@ DankOSD {
|
||||
height: 8
|
||||
radius: Theme.cornerRadius
|
||||
y: {
|
||||
const ratio = vertSlider.value / 100;
|
||||
const ratio = vertSlider.value / AudioService.sinkMaxVolume;
|
||||
const travel = parent.height - height;
|
||||
return Math.max(0, Math.min(travel, travel * (1 - ratio)));
|
||||
}
|
||||
@@ -249,8 +249,9 @@ DankOSD {
|
||||
|
||||
function updateVolume(mouse) {
|
||||
if (AudioService.sink && AudioService.sink.audio) {
|
||||
const maxVol = AudioService.sinkMaxVolume;
|
||||
const ratio = 1.0 - (mouse.y / height);
|
||||
const volume = Math.max(0, Math.min(100, Math.round(ratio * 100)));
|
||||
const volume = Math.max(0, Math.min(maxVol, Math.round(ratio * maxVol)));
|
||||
SessionData.suppressOSDTemporarily();
|
||||
AudioService.sink.audio.volume = volume / 100;
|
||||
resetHideTimer();
|
||||
@@ -262,7 +263,7 @@ DankOSD {
|
||||
target: AudioService.sink && AudioService.sink.audio ? AudioService.sink.audio : null
|
||||
|
||||
function onVolumeChanged() {
|
||||
vertSlider.value = Math.min(100, Math.round(AudioService.sink.audio.volume * 100));
|
||||
vertSlider.value = Math.min(AudioService.sinkMaxVolume, Math.round(AudioService.sink.audio.volume * 100));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -284,7 +285,7 @@ DankOSD {
|
||||
if (!useVertical) {
|
||||
const slider = contentLoader.item.item.children[0].children[1];
|
||||
if (slider && slider.value !== undefined) {
|
||||
slider.value = Math.min(100, Math.round(AudioService.sink.audio.volume * 100));
|
||||
slider.value = Math.min(AudioService.sinkMaxVolume, Math.round(AudioService.sink.audio.volume * 100));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import Quickshell.Services.Pipewire
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
@@ -131,21 +130,64 @@ Item {
|
||||
Repeater {
|
||||
model: root.outputDevices
|
||||
|
||||
delegate: DeviceAliasRow {
|
||||
delegate: Column {
|
||||
required property var modelData
|
||||
width: parent?.width ?? 0
|
||||
spacing: 0
|
||||
|
||||
deviceNode: modelData
|
||||
deviceType: "output"
|
||||
DeviceAliasRow {
|
||||
deviceNode: modelData
|
||||
deviceType: "output"
|
||||
|
||||
onEditRequested: device => {
|
||||
root.editingDevice = device;
|
||||
root.editingDeviceType = "output";
|
||||
root.newDeviceName = AudioService.displayName(device);
|
||||
root.showEditDialog = true;
|
||||
onEditRequested: device => {
|
||||
root.editingDevice = device;
|
||||
root.editingDeviceType = "output";
|
||||
root.newDeviceName = AudioService.displayName(device);
|
||||
root.showEditDialog = true;
|
||||
}
|
||||
|
||||
onResetRequested: device => {
|
||||
AudioService.removeDeviceAlias(device.name);
|
||||
}
|
||||
}
|
||||
|
||||
onResetRequested: device => {
|
||||
AudioService.removeDeviceAlias(device.name);
|
||||
Item {
|
||||
width: parent.width
|
||||
height: 36
|
||||
|
||||
StyledText {
|
||||
id: maxVolLabel
|
||||
text: I18n.tr("Max Volume", "Audio settings: maximum volume limit per device")
|
||||
x: Theme.spacingM + Theme.iconSize + Theme.spacingM
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
}
|
||||
|
||||
DankSlider {
|
||||
id: maxVolSlider
|
||||
anchors.left: maxVolLabel.right
|
||||
anchors.leftMargin: Theme.spacingS
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: Theme.spacingM
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
height: 36
|
||||
minimum: 100
|
||||
maximum: 200
|
||||
step: 5
|
||||
showValue: true
|
||||
unit: "%"
|
||||
onSliderValueChanged: newValue => {
|
||||
SessionData.setDeviceMaxVolume(modelData.name, newValue);
|
||||
}
|
||||
}
|
||||
|
||||
Binding {
|
||||
target: maxVolSlider
|
||||
property: "value"
|
||||
value: SessionData.deviceMaxVolumes[modelData.name] ?? 100
|
||||
when: !maxVolSlider.isDragging
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user