forked from Shinonome/dots-hyprland
earbang protection
This commit is contained in:
@@ -12,6 +12,14 @@ Singleton {
|
|||||||
property int fakeScreenRounding: 1 // 0: None | 1: Always | 2: When not fullscreen
|
property int fakeScreenRounding: 1 // 0: None | 1: Always | 2: When not fullscreen
|
||||||
}
|
}
|
||||||
|
|
||||||
|
property QtObject audio: QtObject { // Values in %
|
||||||
|
property QtObject protection: QtObject { // Prevent sudden bangs
|
||||||
|
property bool enable: true
|
||||||
|
property real maxAllowedIncrease: 10
|
||||||
|
property real maxAllowed: 90 // Realistically should already provide some protection when it's 99...
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
property QtObject apps: QtObject {
|
property QtObject apps: QtObject {
|
||||||
property string bluetooth: "better-control --bluetooth"
|
property string bluetooth: "better-control --bluetooth"
|
||||||
property string imageViewer: "loupe"
|
property string imageViewer: "loupe"
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ Scope {
|
|||||||
item: osdValuesWrapper
|
item: osdValuesWrapper
|
||||||
}
|
}
|
||||||
|
|
||||||
implicitWidth: Appearance.sizes.osdWidth
|
implicitWidth: columnLayout.implicitWidth
|
||||||
implicitHeight: columnLayout.implicitHeight
|
implicitHeight: columnLayout.implicitHeight
|
||||||
visible: osdLoader.active
|
visible: osdLoader.active
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import Quickshell.Hyprland
|
|||||||
Scope {
|
Scope {
|
||||||
id: root
|
id: root
|
||||||
property bool showOsdValues: false
|
property bool showOsdValues: false
|
||||||
|
property string protectionMessage: ""
|
||||||
property var focusedScreen: Quickshell.screens.find(s => s.name === Hyprland.focusedMonitor?.name)
|
property var focusedScreen: Quickshell.screens.find(s => s.name === Hyprland.focusedMonitor?.name)
|
||||||
|
|
||||||
function triggerOsd() {
|
function triggerOsd() {
|
||||||
@@ -25,7 +26,8 @@ Scope {
|
|||||||
repeat: false
|
repeat: false
|
||||||
running: false
|
running: false
|
||||||
onTriggered: {
|
onTriggered: {
|
||||||
showOsdValues = false
|
root.showOsdValues = false
|
||||||
|
root.protectionMessage = ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -36,7 +38,7 @@ Scope {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Connections {
|
Connections { // Listen to volume changes
|
||||||
target: Audio.sink?.audio ?? null
|
target: Audio.sink?.audio ?? null
|
||||||
function onVolumeChanged() {
|
function onVolumeChanged() {
|
||||||
if (!Audio.ready) return
|
if (!Audio.ready) return
|
||||||
@@ -48,6 +50,14 @@ Scope {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Connections { // Listen to protection triggers
|
||||||
|
target: Audio
|
||||||
|
function onSinkProtectionTriggered(reason) {
|
||||||
|
root.protectionMessage = reason;
|
||||||
|
root.triggerOsd()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Loader {
|
Loader {
|
||||||
id: osdLoader
|
id: osdLoader
|
||||||
active: showOsdValues
|
active: showOsdValues
|
||||||
@@ -75,7 +85,7 @@ Scope {
|
|||||||
item: osdValuesWrapper
|
item: osdValuesWrapper
|
||||||
}
|
}
|
||||||
|
|
||||||
implicitWidth: Appearance.sizes.osdWidth
|
implicitWidth: columnLayout.implicitWidth
|
||||||
implicitHeight: columnLayout.implicitHeight
|
implicitHeight: columnLayout.implicitHeight
|
||||||
visible: osdLoader.active
|
visible: osdLoader.active
|
||||||
|
|
||||||
@@ -85,8 +95,8 @@ Scope {
|
|||||||
Item {
|
Item {
|
||||||
id: osdValuesWrapper
|
id: osdValuesWrapper
|
||||||
// Extra space for shadow
|
// Extra space for shadow
|
||||||
implicitHeight: osdValues.implicitHeight + Appearance.sizes.elevationMargin * 2
|
implicitHeight: contentColumnLayout.implicitHeight + Appearance.sizes.elevationMargin * 2
|
||||||
implicitWidth: osdValues.implicitWidth
|
implicitWidth: contentColumnLayout.implicitWidth
|
||||||
clip: true
|
clip: true
|
||||||
|
|
||||||
MouseArea {
|
MouseArea {
|
||||||
@@ -95,20 +105,63 @@ Scope {
|
|||||||
onEntered: root.showOsdValues = false
|
onEntered: root.showOsdValues = false
|
||||||
}
|
}
|
||||||
|
|
||||||
Behavior on implicitHeight {
|
ColumnLayout {
|
||||||
NumberAnimation {
|
id: contentColumnLayout
|
||||||
duration: Appearance.animation.menuDecel.duration
|
anchors {
|
||||||
easing.type: Appearance.animation.menuDecel.type
|
top: parent.top
|
||||||
|
left: parent.left
|
||||||
|
right: parent.right
|
||||||
|
leftMargin: Appearance.sizes.elevationMargin
|
||||||
|
rightMargin: Appearance.sizes.elevationMargin
|
||||||
}
|
}
|
||||||
}
|
spacing: 0
|
||||||
|
|
||||||
OsdValueIndicator {
|
OsdValueIndicator {
|
||||||
id: osdValues
|
id: osdValues
|
||||||
anchors.fill: parent
|
Layout.fillWidth: true
|
||||||
anchors.margins: Appearance.sizes.elevationMargin
|
value: Audio.sink?.audio.volume ?? 0
|
||||||
value: Audio.sink?.audio.volume ?? 0
|
icon: Audio.sink?.audio.muted ? "volume_off" : "volume_up"
|
||||||
icon: Audio.sink?.audio.muted ? "volume_off" : "volume_up"
|
name: qsTr("Volume")
|
||||||
name: qsTr("Volume")
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: protectionMessageWrapper
|
||||||
|
implicitHeight: protectionMessageBackground.implicitHeight
|
||||||
|
implicitWidth: protectionMessageBackground.implicitWidth
|
||||||
|
Layout.alignment: Qt.AlignHCenter
|
||||||
|
opacity: root.protectionMessage !== "" ? 1 : 0
|
||||||
|
|
||||||
|
StyledRectangularShadow {
|
||||||
|
target: protectionMessageBackground
|
||||||
|
}
|
||||||
|
Rectangle {
|
||||||
|
id: protectionMessageBackground
|
||||||
|
anchors.centerIn: parent
|
||||||
|
color: Appearance.m3colors.m3error
|
||||||
|
property real padding: 10
|
||||||
|
implicitHeight: protectionMessageRowLayout.implicitHeight + padding * 2
|
||||||
|
implicitWidth: protectionMessageRowLayout.implicitWidth + padding * 2
|
||||||
|
radius: Appearance.rounding.normal
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
id: protectionMessageRowLayout
|
||||||
|
anchors.centerIn: parent
|
||||||
|
MaterialSymbol {
|
||||||
|
id: protectionMessageIcon
|
||||||
|
text: "dangerous"
|
||||||
|
iconSize: Appearance.font.pixelSize.hugeass
|
||||||
|
color: Appearance.m3colors.m3onError
|
||||||
|
}
|
||||||
|
StyledText {
|
||||||
|
id: protectionMessageTextWidget
|
||||||
|
horizontalAlignment: Text.AlignHCenter
|
||||||
|
color: Appearance.m3colors.m3onError
|
||||||
|
wrapMode: Text.Wrap
|
||||||
|
text: root.protectionMessage
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import "root:/modules/common"
|
||||||
import QtQuick
|
import QtQuick
|
||||||
import Quickshell
|
import Quickshell
|
||||||
import Quickshell.Services.Pipewire
|
import Quickshell.Services.Pipewire
|
||||||
@@ -11,11 +12,43 @@ Singleton {
|
|||||||
id: root
|
id: root
|
||||||
|
|
||||||
property bool ready: Pipewire.defaultAudioSink?.ready ?? false
|
property bool ready: Pipewire.defaultAudioSink?.ready ?? false
|
||||||
property var sink: Pipewire.defaultAudioSink
|
property PwNode sink: Pipewire.defaultAudioSink
|
||||||
property var source: Pipewire.defaultAudioSource
|
property PwNode source: Pipewire.defaultAudioSource
|
||||||
|
|
||||||
|
signal sinkProtectionTriggered(string reason);
|
||||||
|
|
||||||
PwObjectTracker {
|
PwObjectTracker {
|
||||||
objects: [sink, source]
|
objects: [sink, source]
|
||||||
|
Component.onCompleted: {
|
||||||
|
sink.audio.volume = sink.audio.volume; // Trigger initial volume change
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Connections { // Protection against sudden volume changes
|
||||||
|
target: sink?.audio ?? null
|
||||||
|
property bool lastReady: false
|
||||||
|
property real lastVolume: 0
|
||||||
|
function onVolumeChanged() {
|
||||||
|
if (!ConfigOptions.audio.protection.enable) return;
|
||||||
|
if (!lastReady) {
|
||||||
|
lastVolume = sink.audio.volume;
|
||||||
|
lastReady = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const newVolume = sink.audio.volume;
|
||||||
|
const maxAllowedIncrease = ConfigOptions.audio.protection.maxAllowedIncrease / 100;
|
||||||
|
const maxAllowed = ConfigOptions.audio.protection.maxAllowed / 100;
|
||||||
|
|
||||||
|
if (newVolume - lastVolume > maxAllowedIncrease) {
|
||||||
|
sink.audio.volume = lastVolume;
|
||||||
|
root.sinkProtectionTriggered("Illegal increment");
|
||||||
|
} else if (newVolume > maxAllowed) {
|
||||||
|
sink.audio.volume = lastVolume;
|
||||||
|
root.sinkProtectionTriggered("Exceeded max allowed");
|
||||||
|
}
|
||||||
|
lastVolume = sink.audio.volume;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user