osd: add mic volume

Disabled by default
This commit is contained in:
2 * r + 2 * t 2025-08-27 15:52:18 +10:00
parent cdfc260f39
commit 4b9643a802
7 changed files with 116 additions and 39 deletions

View file

@ -326,6 +326,7 @@ default, you must create it manually.
"osd": {
"enabled": true,
"enableBrightness": true,
"enableMicrophone": false,
"hideDelay": 2000
},
"paths": {

View file

@ -4,6 +4,7 @@ JsonObject {
property bool enabled: true
property int hideDelay: 2000
property bool enableBrightness: true
property bool enableMicrophone: false
property Sizes sizes: Sizes {}
component Sizes: JsonObject {

View file

@ -33,7 +33,7 @@ Item {
clip: root.visibilities.session
screen: root.screen
visibility: root.visibilities.osd
visibilities: root.visibilities
anchors.verticalCenter: parent.verticalCenter
anchors.right: parent.right

View file

@ -1,73 +1,123 @@
pragma ComponentBehavior: Bound
import qs.components
import qs.components.controls
import qs.services
import qs.config
import qs.utils
import QtQuick
import QtQuick.Layouts
Column {
Item {
id: root
required property Brightness.Monitor monitor
padding: Appearance.padding.large
required property var visibilities
anchors.verticalCenter: parent.verticalCenter
anchors.left: parent.left
spacing: Appearance.spacing.normal
implicitWidth: layout.implicitWidth + Appearance.padding.large * 2
implicitHeight: layout.implicitHeight + Appearance.padding.large * 2
// Speaker volume
CustomMouseArea {
implicitWidth: Config.osd.sizes.sliderWidth
implicitHeight: Config.osd.sizes.sliderHeight
ColumnLayout {
id: layout
onWheel: event => {
if (event.angleDelta.y > 0)
Audio.incrementVolume();
else if (event.angleDelta.y < 0)
Audio.decrementVolume();
}
anchors.centerIn: parent
spacing: Appearance.spacing.normal
FilledSlider {
anchors.fill: parent
icon: Icons.getVolumeIcon(value, Audio.muted)
value: Audio.volume
onMoved: Audio.setVolume(value)
}
}
// Brightness
WrappedLoader {
active: Config.osd.enableBrightness
sourceComponent: CustomMouseArea {
// Speaker volume
CustomMouseArea {
implicitWidth: Config.osd.sizes.sliderWidth
implicitHeight: Config.osd.sizes.sliderHeight
onWheel: event => {
const monitor = root.monitor;
if (!monitor)
return;
if (event.angleDelta.y > 0)
monitor.setBrightness(monitor.brightness + 0.1);
Audio.incrementVolume();
else if (event.angleDelta.y < 0)
monitor.setBrightness(monitor.brightness - 0.1);
Audio.decrementVolume();
}
FilledSlider {
anchors.fill: parent
icon: `brightness_${(Math.round(value * 6) + 1)}`
value: root.monitor?.brightness ?? 0
onMoved: root.monitor?.setBrightness(value)
icon: Icons.getVolumeIcon(value, Audio.muted)
value: Audio.volume
onMoved: Audio.setVolume(value)
}
}
// Microphone volume
WrappedLoader {
shouldBeActive: Config.osd.enableMicrophone && (!Config.osd.enableBrightness || !root.visibilities.session)
sourceComponent: CustomMouseArea {
implicitWidth: Config.osd.sizes.sliderWidth
implicitHeight: Config.osd.sizes.sliderHeight
onWheel: event => {
if (event.angleDelta.y > 0)
Audio.incrementSourceVolume();
else if (event.angleDelta.y < 0)
Audio.decrementSourceVolume();
}
FilledSlider {
anchors.fill: parent
icon: Icons.getMicVolumeIcon(value, Audio.sourceMuted)
value: Audio.sourceVolume
onMoved: Audio.setSourceVolume(value)
}
}
}
// Brightness
WrappedLoader {
shouldBeActive: Config.osd.enableBrightness
sourceComponent: CustomMouseArea {
implicitWidth: Config.osd.sizes.sliderWidth
implicitHeight: Config.osd.sizes.sliderHeight
onWheel: event => {
const monitor = root.monitor;
if (!monitor)
return;
if (event.angleDelta.y > 0)
monitor.setBrightness(monitor.brightness + 0.1);
else if (event.angleDelta.y < 0)
monitor.setBrightness(monitor.brightness - 0.1);
}
FilledSlider {
anchors.fill: parent
icon: `brightness_${(Math.round(value * 6) + 1)}`
value: root.monitor?.brightness ?? 0
onMoved: root.monitor?.setBrightness(value)
}
}
}
}
component WrappedLoader: Loader {
required property bool shouldBeActive
Layout.preferredHeight: shouldBeActive ? Config.osd.sizes.sliderHeight : 0
opacity: shouldBeActive ? 1 : 0
active: opacity > 0
asynchronous: true
visible: active
Behavior on Layout.preferredHeight {
Anim {
easing.bezierCurve: Appearance.anim.curves.emphasized
}
}
Behavior on opacity {
Anim {}
}
}
}

View file

@ -8,7 +8,7 @@ Item {
id: root
required property ShellScreen screen
required property bool visibility
required property var visibilities
visible: width > 0
implicitWidth: 0
@ -16,7 +16,7 @@ Item {
states: State {
name: "visible"
when: root.visibility && Config.osd.enabled
when: root.visibilities.osd && Config.osd.enabled
PropertyChanges {
root.implicitWidth: content.implicitWidth
@ -50,5 +50,6 @@ Item {
id: content
monitor: Brightness.getMonitorForScreen(root.screen)
visibilities: root.visibilities
}
}

View file

@ -29,6 +29,9 @@ Singleton {
readonly property bool muted: !!sink?.audio?.muted
readonly property real volume: sink?.audio?.volume ?? 0
readonly property bool sourceMuted: !!source?.audio?.muted
readonly property real sourceVolume: source?.audio?.volume ?? 0
function setVolume(newVolume: real): void {
if (sink?.ready && sink?.audio) {
sink.audio.muted = false;
@ -44,6 +47,21 @@ Singleton {
setVolume(volume - (amount || Config.services.audioIncrement));
}
function setSourceVolume(newVolume: real): void {
if (source?.ready && source?.audio) {
source.audio.muted = false;
source.audio.volume = Math.max(0, Math.min(1, newVolume));
}
}
function incrementSourceVolume(amount: real): void {
setSourceVolume(sourceVolume + (amount || Config.services.audioIncrement));
}
function decrementSourceVolume(amount: real): void {
setSourceVolume(sourceVolume - (amount || Config.services.audioIncrement));
}
function setAudioSink(newSink: PwNode): void {
Pipewire.preferredDefaultAudioSink = newSink;
}

View file

@ -184,6 +184,12 @@ Singleton {
return "volume_mute";
}
function getMicVolumeIcon(volume: real, isMuted: bool): string {
if (!isMuted && volume > 0)
return "mic";
return "mic_off";
}
function getSpecialWsIcon(name: string): string {
name = name.toLowerCase().slice("special:".length);
if (name === "special")