2023-03-16 07:21:01 +00:00
|
|
|
// SPDX-FileCopyrightText: 2023 Devin Lin <devin@kde.org>
|
|
|
|
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
|
|
|
|
2023-10-19 17:39:03 +00:00
|
|
|
pragma Singleton
|
|
|
|
|
|
2023-03-16 07:21:01 +00:00
|
|
|
import QtQuick
|
|
|
|
|
|
|
|
|
|
import org.kde.plasma.private.volume
|
|
|
|
|
|
|
|
|
|
QtObject {
|
2023-03-20 04:10:14 +00:00
|
|
|
id: root
|
|
|
|
|
|
2023-10-17 11:42:33 +00:00
|
|
|
property var config: GlobalConfig {}
|
|
|
|
|
|
2023-03-16 07:21:01 +00:00
|
|
|
property SinkModel paSinkModel: SinkModel {}
|
|
|
|
|
|
|
|
|
|
// whether the audio icon should be visible in the status bar
|
2024-06-24 23:43:43 +00:00
|
|
|
readonly property bool isVisible: PreferredDevice.sink
|
2023-03-16 07:21:01 +00:00
|
|
|
|
|
|
|
|
// the icon that should be displayed in the status bar
|
2024-06-24 23:43:43 +00:00
|
|
|
readonly property string icon: PreferredDevice.sink && !isDummyOutput(PreferredDevice.sink)
|
|
|
|
|
? iconName(PreferredDevice.sink.volume, PreferredDevice.sink.muted)
|
2023-03-16 07:21:01 +00:00
|
|
|
: iconName(0, true)
|
|
|
|
|
|
|
|
|
|
// the name of the audio device when it isn't valid
|
|
|
|
|
readonly property string dummyOutputName: "auto_null"
|
|
|
|
|
|
2023-10-17 11:42:33 +00:00
|
|
|
// the maximum volume amount (percentage)
|
|
|
|
|
readonly property int maxVolumePercent: config.raiseMaximumVolume ? 150 : 100
|
|
|
|
|
|
2023-03-16 07:21:01 +00:00
|
|
|
// the maximum volume amount
|
2023-10-17 11:42:33 +00:00
|
|
|
readonly property int maxVolumeValue: maxVolumePercent * PulseAudio.NormalVolume / 100
|
2023-03-16 07:21:01 +00:00
|
|
|
|
|
|
|
|
// step that increments when adjusting the volume
|
|
|
|
|
readonly property int volumeStep: Math.round(5 * PulseAudio.NormalVolume / 100.0)
|
|
|
|
|
|
2023-03-20 04:10:14 +00:00
|
|
|
// The current audio volume (updated by connecting to sinks)
|
|
|
|
|
property int volumeValue
|
|
|
|
|
|
2023-03-16 07:21:01 +00:00
|
|
|
function isDummyOutput(output) {
|
|
|
|
|
return output && output.name === dummyOutputName;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function boundVolume(volume) {
|
|
|
|
|
return Math.max(PulseAudio.MinimalVolume, Math.min(volume, maxVolumeValue));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function volumePercent(volume, max){
|
|
|
|
|
if (!max) {
|
|
|
|
|
max = PulseAudio.NormalVolume;
|
|
|
|
|
}
|
2023-10-17 11:42:33 +00:00
|
|
|
return Math.round(volume / max * maxVolumePercent);
|
2023-03-16 07:21:01 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function increaseVolume() {
|
2024-06-24 23:43:43 +00:00
|
|
|
if (!PreferredDevice.sink || isDummyOutput(PreferredDevice.sink)) {
|
2023-03-16 07:21:01 +00:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2024-06-24 23:43:43 +00:00
|
|
|
var volume = boundVolume(PreferredDevice.sink.volume + volumeStep);
|
2023-03-16 07:21:01 +00:00
|
|
|
var percent = volumePercent(volume, maxVolumeValue);
|
2024-06-24 23:43:43 +00:00
|
|
|
PreferredDevice.sink.muted = percent == 0;
|
|
|
|
|
PreferredDevice.sink.volume = volume;
|
2023-03-16 07:21:01 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function decreaseVolume() {
|
2024-06-24 23:43:43 +00:00
|
|
|
if (!PreferredDevice.sink || isDummyOutput(PreferredDevice.sink)) {
|
2023-03-16 07:21:01 +00:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2024-06-24 23:43:43 +00:00
|
|
|
var volume = boundVolume(PreferredDevice.sink.volume - volumeStep);
|
2023-03-16 07:21:01 +00:00
|
|
|
var percent = volumePercent(volume, maxVolumeValue);
|
2024-06-24 23:43:43 +00:00
|
|
|
PreferredDevice.sink.muted = percent == 0;
|
|
|
|
|
PreferredDevice.sink.volume = volume;
|
2023-03-16 07:21:01 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function muteVolume() {
|
2024-06-24 23:43:43 +00:00
|
|
|
if (!PreferredDevice.sink || isDummyOutput(PreferredDevice.sink)) {
|
2023-03-16 07:21:01 +00:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2024-06-24 23:43:43 +00:00
|
|
|
PreferredDevice.sink.muted = !PreferredDevice.sink.muted;
|
2023-03-16 07:21:01 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function iconName(volume, muted, prefix) {
|
|
|
|
|
if (!prefix) {
|
|
|
|
|
prefix = "audio-volume";
|
|
|
|
|
}
|
|
|
|
|
var icon = null;
|
|
|
|
|
var percent = volume / maxVolumeValue;
|
|
|
|
|
if (percent <= 0.0 || muted) {
|
|
|
|
|
icon = prefix + "-muted";
|
|
|
|
|
} else if (percent <= 0.25) {
|
|
|
|
|
icon = prefix + "-low";
|
|
|
|
|
} else if (percent <= 0.75) {
|
|
|
|
|
icon = prefix + "-medium";
|
|
|
|
|
} else {
|
|
|
|
|
icon = prefix + "-high";
|
|
|
|
|
}
|
|
|
|
|
return icon;
|
|
|
|
|
}
|
2023-03-20 04:10:14 +00:00
|
|
|
|
|
|
|
|
// emitted when the volume changed, but not due to sink switching
|
|
|
|
|
signal volumeChanged()
|
|
|
|
|
|
|
|
|
|
property var updateVolume: Connections {
|
2024-06-24 23:43:43 +00:00
|
|
|
target: root.paSinkModel ? (PreferredDevice.sink ? PreferredDevice.sink : null) : null
|
2023-03-20 04:10:14 +00:00
|
|
|
enabled: target !== null
|
|
|
|
|
|
|
|
|
|
function onVolumeChanged() {
|
2024-06-24 23:43:43 +00:00
|
|
|
root.volumeValue = root.volumePercent(PreferredDevice.sink.volume, root.maxVolumeValue);
|
2023-03-20 04:10:14 +00:00
|
|
|
root.volumeChanged();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function onMutedChanged() {
|
2024-06-24 23:43:43 +00:00
|
|
|
root.volumeValue = PreferredDevice.sink.muted ? 0 : root.volumePercent(PreferredDevice.sink.volume, root.maxVolumeValue);
|
2023-03-20 04:10:14 +00:00
|
|
|
root.volumeChanged();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
property var updateVolumeOnSinkChange: Connections {
|
|
|
|
|
target: root.paSinkModel ? root.paSinkModel : null
|
|
|
|
|
enabled: target !== null
|
|
|
|
|
|
|
|
|
|
function onPreferredSinkChanged() {
|
2024-06-24 23:43:43 +00:00
|
|
|
if (PreferredDevice.sink) {
|
|
|
|
|
root.volumeValue = root.volumePercent(PreferredDevice.sink.volume, root.maxVolumeValue);
|
2023-03-20 04:10:14 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-03-16 07:21:01 +00:00
|
|
|
}
|