mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-06-07 19:59:14 -04:00
feat: implement interactive microphone volume OSD and IPC controls (#2406)
* feat: implement interactive microphone volume OSD and persistence Addresses #2388 * refactor: reduce scope to interactive microphone OSD and IPC controls only
This commit is contained in:
@@ -66,7 +66,7 @@ const DMS_ACTIONS = [
|
|||||||
{ id: "spawn dms ipc call mpris increment 5", label: "Player Volume Up (5%)" },
|
{ id: "spawn dms ipc call mpris increment 5", label: "Player Volume Up (5%)" },
|
||||||
{ id: "spawn dms ipc call mpris decrement 5", label: "Player Volume Down (5%)" },
|
{ id: "spawn dms ipc call mpris decrement 5", label: "Player Volume Down (5%)" },
|
||||||
{ id: "spawn dms ipc call audio mute", label: "Volume Mute Toggle" },
|
{ id: "spawn dms ipc call audio mute", label: "Volume Mute Toggle" },
|
||||||
{ id: "spawn dms ipc call audio micmute", label: "Microphone Mute Toggle" },
|
{ id: "spawn dms ipc call mic mute", label: "Microphone Mute Toggle" },
|
||||||
{ id: "spawn dms ipc call audio cycleoutput", label: "Audio Output: Cycle" },
|
{ id: "spawn dms ipc call audio cycleoutput", label: "Audio Output: Cycle" },
|
||||||
{ id: "spawn dms ipc call brightness increment 5 \"\"", label: "Brightness Up" },
|
{ id: "spawn dms ipc call brightness increment 5 \"\"", label: "Brightness Up" },
|
||||||
{ id: "spawn dms ipc call brightness increment 1 \"\"", label: "Brightness Up (1%)" },
|
{ id: "spawn dms ipc call brightness increment 1 \"\"", label: "Brightness Up (1%)" },
|
||||||
|
|||||||
@@ -708,6 +708,7 @@ Singleton {
|
|||||||
property bool osdBrightnessEnabled: true
|
property bool osdBrightnessEnabled: true
|
||||||
property bool osdIdleInhibitorEnabled: true
|
property bool osdIdleInhibitorEnabled: true
|
||||||
property bool osdMicMuteEnabled: true
|
property bool osdMicMuteEnabled: true
|
||||||
|
property bool osdMicVolumeEnabled: true
|
||||||
property bool osdCapsLockEnabled: true
|
property bool osdCapsLockEnabled: true
|
||||||
property bool osdPowerProfileEnabled: true
|
property bool osdPowerProfileEnabled: true
|
||||||
property bool osdAudioOutputEnabled: true
|
property bool osdAudioOutputEnabled: true
|
||||||
|
|||||||
@@ -1114,7 +1114,7 @@ Item {
|
|||||||
Variants {
|
Variants {
|
||||||
model: SettingsData.getFilteredScreens("osd")
|
model: SettingsData.getFilteredScreens("osd")
|
||||||
|
|
||||||
delegate: MicMuteOSD {
|
delegate: MicVolumeOSD {
|
||||||
modelData: item
|
modelData: item
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1794,6 +1794,36 @@ Item {
|
|||||||
target: "outputs"
|
target: "outputs"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
IpcHandler {
|
||||||
|
target: "mic"
|
||||||
|
|
||||||
|
function setvolume(percentage: string): string {
|
||||||
|
return AudioService.setMicVolume(parseInt(percentage));
|
||||||
|
}
|
||||||
|
|
||||||
|
function increment(step: string): string {
|
||||||
|
return AudioService.incrementMicVolume(step);
|
||||||
|
}
|
||||||
|
|
||||||
|
function decrement(step: string): string {
|
||||||
|
return AudioService.decrementMicVolume(step);
|
||||||
|
}
|
||||||
|
|
||||||
|
function mute(): string {
|
||||||
|
return AudioService.toggleMicMute();
|
||||||
|
}
|
||||||
|
|
||||||
|
function status(): string {
|
||||||
|
if (!AudioService.source || !AudioService.source.audio) {
|
||||||
|
return "No audio source available";
|
||||||
|
}
|
||||||
|
|
||||||
|
const volume = Math.round(AudioService.source.audio.volume * 100);
|
||||||
|
const muteStatus = AudioService.source.audio.muted ? " (muted)" : "";
|
||||||
|
return `Microphone: ${volume}%${muteStatus}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
IpcHandler {
|
IpcHandler {
|
||||||
function findTrayItem(itemId: string): var {
|
function findTrayItem(itemId: string): var {
|
||||||
if (!itemId)
|
if (!itemId)
|
||||||
|
|||||||
@@ -1,29 +0,0 @@
|
|||||||
import QtQuick
|
|
||||||
import qs.Common
|
|
||||||
import qs.Services
|
|
||||||
import qs.Widgets
|
|
||||||
|
|
||||||
DankOSD {
|
|
||||||
id: root
|
|
||||||
|
|
||||||
osdWidth: Theme.iconSize + Theme.spacingS * 2
|
|
||||||
osdHeight: Theme.iconSize + Theme.spacingS * 2
|
|
||||||
autoHideInterval: 2000
|
|
||||||
enableMouseInteraction: false
|
|
||||||
|
|
||||||
Connections {
|
|
||||||
target: AudioService
|
|
||||||
function onMicMuteChanged() {
|
|
||||||
if (SettingsData.osdMicMuteEnabled) {
|
|
||||||
root.show()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
content: DankIcon {
|
|
||||||
anchors.centerIn: parent
|
|
||||||
name: AudioService.source && AudioService.source.audio && AudioService.source.audio.muted ? "mic_off" : "mic"
|
|
||||||
size: Theme.iconSize
|
|
||||||
color: AudioService.source && AudioService.source.audio && AudioService.source.audio.muted ? Theme.error : Theme.primary
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,253 @@
|
|||||||
|
import QtQuick
|
||||||
|
import qs.Common
|
||||||
|
import qs.Services
|
||||||
|
import qs.Widgets
|
||||||
|
|
||||||
|
DankOSD {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
readonly property bool useVertical: isVerticalLayout
|
||||||
|
property int _displayVolume: 0
|
||||||
|
|
||||||
|
function _syncVolume() {
|
||||||
|
if (!AudioService.source?.audio)
|
||||||
|
return;
|
||||||
|
_displayVolume = Math.round(AudioService.source.audio.volume * 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
osdWidth: useVertical ? (40 + Theme.spacingS * 2) : Math.min(260, Screen.width - Theme.spacingM * 2)
|
||||||
|
osdHeight: useVertical ? Math.min(260, Screen.height - Theme.spacingM * 2) : (40 + Theme.spacingS * 2)
|
||||||
|
autoHideInterval: 3000
|
||||||
|
enableMouseInteraction: true
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: AudioService.source?.audio ?? null
|
||||||
|
|
||||||
|
function onVolumeChanged() {
|
||||||
|
root._syncVolume();
|
||||||
|
if (SettingsData.osdMicVolumeEnabled)
|
||||||
|
root.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
function onMutedChanged() {
|
||||||
|
if (SettingsData.osdMicMuteEnabled)
|
||||||
|
root.show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: AudioService
|
||||||
|
|
||||||
|
function onSourceChanged() {
|
||||||
|
root._syncVolume();
|
||||||
|
if (root.shouldBeVisible && SettingsData.osdMicVolumeEnabled)
|
||||||
|
root.show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
content: Loader {
|
||||||
|
anchors.fill: parent
|
||||||
|
sourceComponent: useVertical ? verticalContent : horizontalContent
|
||||||
|
}
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: horizontalContent
|
||||||
|
|
||||||
|
Item {
|
||||||
|
property int gap: Theme.spacingS
|
||||||
|
|
||||||
|
anchors.centerIn: parent
|
||||||
|
width: parent.width - Theme.spacingS * 2
|
||||||
|
height: 40
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
width: Theme.iconSize
|
||||||
|
height: Theme.iconSize
|
||||||
|
radius: Theme.iconSize / 2
|
||||||
|
color: "transparent"
|
||||||
|
x: parent.gap
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
name: AudioService.source?.audio?.muted ? "mic_off" : "mic"
|
||||||
|
size: Theme.iconSize
|
||||||
|
color: muteButton.containsMouse ? Theme.primary : (AudioService.source?.audio?.muted ? Theme.error : Theme.surfaceText)
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: muteButton
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: AudioService.toggleMicMute()
|
||||||
|
onContainsMouseChanged: setChildHovered(containsMouse || volumeSlider.containsMouse)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DankSlider {
|
||||||
|
id: volumeSlider
|
||||||
|
|
||||||
|
width: parent.width - Theme.iconSize - parent.gap * 3
|
||||||
|
height: 40
|
||||||
|
x: parent.gap * 2 + Theme.iconSize
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
minimum: 0
|
||||||
|
maximum: 100
|
||||||
|
enabled: AudioService.source?.audio ?? false
|
||||||
|
showValue: true
|
||||||
|
unit: "%"
|
||||||
|
thumbOutlineColor: Theme.surfaceContainer
|
||||||
|
valueOverride: root._displayVolume
|
||||||
|
alwaysShowValue: SettingsData.osdAlwaysShowValue
|
||||||
|
|
||||||
|
Component.onCompleted: {
|
||||||
|
root._syncVolume();
|
||||||
|
value = root._displayVolume;
|
||||||
|
}
|
||||||
|
|
||||||
|
onSliderValueChanged: newValue => {
|
||||||
|
if (!AudioService.source?.audio)
|
||||||
|
return;
|
||||||
|
SessionData.suppressOSDTemporarily();
|
||||||
|
AudioService.source.audio.volume = newValue / 100;
|
||||||
|
resetHideTimer();
|
||||||
|
}
|
||||||
|
|
||||||
|
onContainsMouseChanged: setChildHovered(containsMouse || muteButton.containsMouse)
|
||||||
|
|
||||||
|
Binding on value {
|
||||||
|
value: root._displayVolume
|
||||||
|
when: !volumeSlider.pressed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: verticalContent
|
||||||
|
|
||||||
|
Item {
|
||||||
|
anchors.fill: parent
|
||||||
|
property int gap: Theme.spacingS
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
width: Theme.iconSize
|
||||||
|
height: Theme.iconSize
|
||||||
|
radius: Theme.iconSize / 2
|
||||||
|
color: "transparent"
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
y: gap
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
name: AudioService.source?.audio?.muted ? "mic_off" : "mic"
|
||||||
|
size: Theme.iconSize
|
||||||
|
color: muteButtonVert.containsMouse ? Theme.primary : (AudioService.source?.audio?.muted ? Theme.error : Theme.surfaceText)
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: muteButtonVert
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: AudioService.toggleMicMute()
|
||||||
|
onContainsMouseChanged: setChildHovered(containsMouse || vertSliderArea.containsMouse)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: vertSlider
|
||||||
|
width: 12
|
||||||
|
height: parent.height - Theme.iconSize - gap * 3 - 24
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
y: gap * 2 + Theme.iconSize
|
||||||
|
|
||||||
|
property bool dragging: false
|
||||||
|
property int value: root._displayVolume
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: vertTrack
|
||||||
|
width: parent.width
|
||||||
|
height: parent.height
|
||||||
|
anchors.centerIn: parent
|
||||||
|
color: Theme.outline
|
||||||
|
radius: Theme.cornerRadius
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: vertFill
|
||||||
|
width: parent.width
|
||||||
|
height: (vertSlider.value / 100) * parent.height
|
||||||
|
anchors.bottom: parent.bottom
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
color: AudioService.source?.audio?.muted ? Theme.error : Theme.primary
|
||||||
|
radius: Theme.cornerRadius
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: vertHandle
|
||||||
|
width: 24
|
||||||
|
height: 8
|
||||||
|
radius: Theme.cornerRadius
|
||||||
|
y: {
|
||||||
|
const ratio = vertSlider.value / 100;
|
||||||
|
const travel = parent.height - height;
|
||||||
|
return Math.max(0, Math.min(travel, travel * (1 - ratio)));
|
||||||
|
}
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
color: AudioService.source?.audio?.muted ? Theme.error : Theme.primary
|
||||||
|
border.width: 3
|
||||||
|
border.color: Theme.surfaceContainer
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: vertSliderArea
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: -12
|
||||||
|
enabled: AudioService.source?.audio ?? false
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
|
||||||
|
onContainsMouseChanged: setChildHovered(containsMouse || muteButtonVert.containsMouse)
|
||||||
|
|
||||||
|
onPressed: mouse => {
|
||||||
|
vertSlider.dragging = true;
|
||||||
|
updateVolume(mouse);
|
||||||
|
}
|
||||||
|
|
||||||
|
onReleased: vertSlider.dragging = false
|
||||||
|
|
||||||
|
onPositionChanged: mouse => {
|
||||||
|
if (pressed)
|
||||||
|
updateVolume(mouse);
|
||||||
|
}
|
||||||
|
|
||||||
|
onClicked: mouse => updateVolume(mouse)
|
||||||
|
|
||||||
|
function updateVolume(mouse) {
|
||||||
|
if (!AudioService.source?.audio)
|
||||||
|
return;
|
||||||
|
const ratio = 1.0 - (mouse.y / height);
|
||||||
|
const volume = Math.max(0, Math.min(100, Math.round(ratio * 100)));
|
||||||
|
SessionData.suppressOSDTemporarily();
|
||||||
|
AudioService.source.audio.volume = volume / 100;
|
||||||
|
resetHideTimer();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
anchors.bottom: parent.bottom
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
anchors.bottomMargin: gap
|
||||||
|
text: vertSlider.value + "%"
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: Theme.surfaceText
|
||||||
|
visible: SettingsData.osdAlwaysShowValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -397,6 +397,14 @@ EOFCONFIG
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: root.source?.audio ?? null
|
||||||
|
|
||||||
|
function onMutedChanged() {
|
||||||
|
root.micMuteChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function checkGsettings() {
|
function checkGsettings() {
|
||||||
Proc.runCommand("checkGsettings", ["sh", "-c", "gsettings get org.gnome.desktop.sound theme-name 2>/dev/null"], (output, exitCode) => {
|
Proc.runCommand("checkGsettings", ["sh", "-c", "gsettings get org.gnome.desktop.sound theme-name 2>/dev/null"], (output, exitCode) => {
|
||||||
gsettingsAvailable = (exitCode === 0);
|
gsettingsAvailable = (exitCode === 0);
|
||||||
@@ -844,6 +852,36 @@ EOFCONFIG
|
|||||||
return root.source.audio.muted ? "Microphone muted" : "Microphone unmuted";
|
return root.source.audio.muted ? "Microphone muted" : "Microphone unmuted";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function incrementMicVolume(step) {
|
||||||
|
if (!root.source?.audio)
|
||||||
|
return "No audio source available";
|
||||||
|
|
||||||
|
if (root.source.audio.muted)
|
||||||
|
root.source.audio.muted = false;
|
||||||
|
|
||||||
|
const currentVolume = Math.round(root.source.audio.volume * 100);
|
||||||
|
const stepValue = parseInt(step || "5");
|
||||||
|
const newVolume = Math.max(0, Math.min(100, currentVolume + stepValue));
|
||||||
|
|
||||||
|
root.source.audio.volume = newVolume / 100;
|
||||||
|
return `Microphone volume increased to ${newVolume}%`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function decrementMicVolume(step) {
|
||||||
|
if (!root.source?.audio)
|
||||||
|
return "No audio source available";
|
||||||
|
|
||||||
|
if (root.source.audio.muted)
|
||||||
|
root.source.audio.muted = false;
|
||||||
|
|
||||||
|
const currentVolume = Math.round(root.source.audio.volume * 100);
|
||||||
|
const stepValue = parseInt(step || "5");
|
||||||
|
const newVolume = Math.max(0, Math.min(100, currentVolume - stepValue));
|
||||||
|
|
||||||
|
root.source.audio.volume = newVolume / 100;
|
||||||
|
return `Microphone volume decreased to ${newVolume}%`;
|
||||||
|
}
|
||||||
|
|
||||||
IpcHandler {
|
IpcHandler {
|
||||||
target: "audio"
|
target: "audio"
|
||||||
|
|
||||||
@@ -892,9 +930,7 @@ EOFCONFIG
|
|||||||
}
|
}
|
||||||
|
|
||||||
function micmute(): string {
|
function micmute(): string {
|
||||||
const result = root.toggleMicMute();
|
return root.toggleMicMute();
|
||||||
root.micMuteChanged();
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function status(): string {
|
function status(): string {
|
||||||
@@ -957,7 +993,6 @@ EOFCONFIG
|
|||||||
return `Switched to: ${result}`;
|
return `Switched to: ${result}`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Connections {
|
Connections {
|
||||||
target: SettingsData
|
target: SettingsData
|
||||||
function onUseSystemSoundThemeChanged() {
|
function onUseSystemSoundThemeChanged() {
|
||||||
|
|||||||
Reference in New Issue
Block a user