summaryrefslogtreecommitdiff
path: root/raveos-hyprland-theme/theme-data/dms/Modules/ControlCenter/Details/AudioInputDetail.qml
diff options
context:
space:
mode:
authorNippy <nippy@rp1.hu>2026-05-08 19:50:49 +0200
committerNippy <nippy@rp1.hu>2026-05-08 19:50:49 +0200
commit3462bcc46ecf74f576ea4397f16492c16bd75b10 (patch)
treec5ab7aef4498647e057bda8d49c11e5f9dda74c9 /raveos-hyprland-theme/theme-data/dms/Modules/ControlCenter/Details/AudioInputDetail.qml
parent56616f693b4f263cbf0d47da43b67424efa6022d (diff)
downloadRaveOS-PKGBUILD-3462bcc46ecf74f576ea4397f16492c16bd75b10.tar.gz
RaveOS-PKGBUILD-3462bcc46ecf74f576ea4397f16492c16bd75b10.zip
raveos update
Diffstat (limited to 'raveos-hyprland-theme/theme-data/dms/Modules/ControlCenter/Details/AudioInputDetail.qml')
-rw-r--r--raveos-hyprland-theme/theme-data/dms/Modules/ControlCenter/Details/AudioInputDetail.qml363
1 files changed, 363 insertions, 0 deletions
diff --git a/raveos-hyprland-theme/theme-data/dms/Modules/ControlCenter/Details/AudioInputDetail.qml b/raveos-hyprland-theme/theme-data/dms/Modules/ControlCenter/Details/AudioInputDetail.qml
new file mode 100644
index 0000000..8fc2d46
--- /dev/null
+++ b/raveos-hyprland-theme/theme-data/dms/Modules/ControlCenter/Details/AudioInputDetail.qml
@@ -0,0 +1,363 @@
+import QtQuick
+import Quickshell
+import Quickshell.Services.Pipewire
+import qs.Common
+import qs.Services
+import qs.Widgets
+
+Rectangle {
+ id: root
+
+ LayoutMirroring.enabled: I18n.isRtl
+ LayoutMirroring.childrenInherit: true
+
+ property bool hasInputVolumeSliderInCC: {
+ const widgets = SettingsData.controlCenterWidgets || [];
+ return widgets.some(widget => widget.id === "inputVolumeSlider");
+ }
+
+ implicitHeight: headerRow.height + (hasInputVolumeSliderInCC ? 0 : volumeSlider.height) + audioContent.height + Theme.spacingM
+ radius: Theme.cornerRadius
+ color: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
+ border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
+ border.width: 0
+
+ Row {
+ id: headerRow
+ anchors.left: parent.left
+ anchors.right: parent.right
+ anchors.top: parent.top
+ anchors.leftMargin: Theme.spacingM
+ anchors.rightMargin: Theme.spacingM
+ anchors.topMargin: Theme.spacingS
+ height: 40
+
+ StyledText {
+ id: headerText
+ text: I18n.tr("Input Devices")
+ font.pixelSize: Theme.fontSizeLarge
+ color: Theme.surfaceText
+ font.weight: Font.Medium
+ anchors.verticalCenter: parent.verticalCenter
+ }
+
+ Item {
+ height: 1
+ width: parent.width - headerText.width - settingsButton.width
+ }
+
+ DankActionButton {
+ id: settingsButton
+ anchors.verticalCenter: parent.verticalCenter
+ iconName: "settings"
+ buttonSize: 28
+ iconSize: 16
+ iconColor: Theme.surfaceVariantText
+ onClicked: {
+ PopoutService.closeControlCenter();
+ PopoutService.openSettingsWithTab("audio");
+ }
+ }
+ }
+
+ Row {
+ id: volumeSlider
+ anchors.left: parent.left
+ anchors.right: parent.right
+ anchors.top: headerRow.bottom
+ anchors.leftMargin: Theme.spacingM
+ anchors.rightMargin: Theme.spacingM
+ anchors.topMargin: Theme.spacingXS
+ height: 35
+ spacing: 0
+ visible: !hasInputVolumeSliderInCC
+
+ Rectangle {
+ width: Theme.iconSize + Theme.spacingS * 2
+ height: Theme.iconSize + Theme.spacingS * 2
+ anchors.verticalCenter: parent.verticalCenter
+ radius: (Theme.iconSize + Theme.spacingS * 2) / 2
+ color: iconArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
+
+ DankRipple {
+ id: iconRipple
+ cornerRadius: parent.radius
+ }
+
+ MouseArea {
+ id: iconArea
+ anchors.fill: parent
+ hoverEnabled: true
+ cursorShape: Qt.PointingHandCursor
+ onPressed: mouse => iconRipple.trigger(mouse.x, mouse.y)
+ onClicked: {
+ if (AudioService.source && AudioService.source.audio) {
+ AudioService.source.audio.muted = !AudioService.source.audio.muted;
+ }
+ }
+ }
+
+ DankIcon {
+ anchors.centerIn: parent
+ name: {
+ if (!AudioService.source || !AudioService.source.audio)
+ return "mic_off";
+ let muted = AudioService.source.audio.muted;
+ return muted ? "mic_off" : "mic";
+ }
+ size: Theme.iconSize
+ color: AudioService.source && AudioService.source.audio && !AudioService.source.audio.muted && AudioService.source.audio.volume > 0 ? Theme.primary : Theme.surfaceText
+ }
+ }
+
+ DankSlider {
+ readonly property real actualVolumePercent: AudioService.source && AudioService.source.audio ? Math.round(AudioService.source.audio.volume * 100) : 0
+
+ anchors.verticalCenter: parent.verticalCenter
+ width: parent.width - (Theme.iconSize + Theme.spacingS * 2)
+ enabled: AudioService.source && AudioService.source.audio
+ minimum: 0
+ maximum: 100
+ value: AudioService.source && AudioService.source.audio ? Math.min(100, Math.round(AudioService.source.audio.volume * 100)) : 0
+ showValue: true
+ unit: "%"
+ valueOverride: actualVolumePercent
+ thumbOutlineColor: Theme.surfaceVariant
+
+ onSliderValueChanged: function (newValue) {
+ if (AudioService.source && AudioService.source.audio) {
+ AudioService.source.audio.volume = newValue / 100;
+ if (newValue > 0 && AudioService.source.audio.muted) {
+ AudioService.source.audio.muted = false;
+ }
+ }
+ }
+ }
+ }
+
+ DankFlickable {
+ id: audioContent
+ anchors.top: hasInputVolumeSliderInCC ? headerRow.bottom : volumeSlider.bottom
+ anchors.left: parent.left
+ anchors.right: parent.right
+ anchors.bottom: parent.bottom
+ anchors.margins: Theme.spacingM
+ anchors.topMargin: hasInputVolumeSliderInCC ? Theme.spacingM : Theme.spacingS
+ contentHeight: audioColumn.height
+ clip: true
+
+ property int maxPinnedInputs: 3
+
+ function normalizePinList(value) {
+ if (Array.isArray(value))
+ return value.filter(v => v);
+ if (typeof value === "string" && value.length > 0)
+ return [value];
+ return [];
+ }
+
+ function getPinnedInputs() {
+ const pins = SettingsData.audioInputDevicePins || {};
+ return normalizePinList(pins["preferredInput"]);
+ }
+
+ Column {
+ id: audioColumn
+ width: parent.width
+ spacing: Theme.spacingS
+
+ Repeater {
+ model: ScriptModel {
+ values: {
+ const hidden = SessionData.hiddenInputDeviceNames ?? [];
+ const nodes = Pipewire.nodes.values.filter(node => {
+ if (!node.audio || node.isSink || node.isStream)
+ return false;
+ return !hidden.includes(node.name);
+ });
+ const pinnedList = audioContent.getPinnedInputs();
+
+ let sorted = [...nodes];
+ sorted.sort((a, b) => {
+ // Pinned device first
+ const aPinnedIndex = pinnedList.indexOf(a.name);
+ const bPinnedIndex = pinnedList.indexOf(b.name);
+ if (aPinnedIndex !== -1 || bPinnedIndex !== -1) {
+ if (aPinnedIndex === -1)
+ return 1;
+ if (bPinnedIndex === -1)
+ return -1;
+ return aPinnedIndex - bPinnedIndex;
+ }
+ // Then active device
+ if (a === AudioService.source && b !== AudioService.source)
+ return -1;
+ if (b === AudioService.source && a !== AudioService.source)
+ return 1;
+ return 0;
+ });
+ return sorted;
+ }
+ }
+
+ delegate: Rectangle {
+ required property var modelData
+ required property int index
+
+ width: parent.width
+ height: 50
+ radius: Theme.cornerRadius
+ color: deviceMouseArea.containsMouse ? Theme.primaryHoverLight : Theme.surfaceLight
+ border.color: modelData === AudioService.source ? Theme.primary : Theme.outlineLight
+ border.width: modelData === AudioService.source ? 2 : 1
+
+ Row {
+ anchors.left: parent.left
+ anchors.verticalCenter: parent.verticalCenter
+ anchors.leftMargin: Theme.spacingM
+ spacing: Theme.spacingS
+
+ DankIcon {
+ name: {
+ if (modelData.name.includes("bluez"))
+ return "headset";
+ else if (modelData.name.includes("usb"))
+ return "headset";
+ else
+ return "mic";
+ }
+ size: Theme.iconSize - 4
+ color: modelData === AudioService.source ? Theme.primary : Theme.surfaceText
+ anchors.verticalCenter: parent.verticalCenter
+ }
+
+ Column {
+ anchors.verticalCenter: parent.verticalCenter
+ width: {
+ const iconWidth = Theme.iconSize;
+ const pinButtonWidth = pinInputRow.width + Theme.spacingS * 4 + Theme.spacingM;
+ return parent.parent.width - iconWidth - parent.spacing - pinButtonWidth - Theme.spacingM * 2;
+ }
+
+ StyledText {
+ text: AudioService.displayName(modelData)
+ font.pixelSize: Theme.fontSizeMedium
+ color: Theme.surfaceText
+ font.weight: modelData === AudioService.source ? Font.Medium : Font.Normal
+ elide: Text.ElideRight
+ width: parent.width
+ wrapMode: Text.NoWrap
+ horizontalAlignment: Text.AlignLeft
+ }
+
+ StyledText {
+ text: modelData === AudioService.source ? I18n.tr("Active") : I18n.tr("Available")
+ font.pixelSize: Theme.fontSizeSmall
+ color: Theme.surfaceVariantText
+ elide: Text.ElideRight
+ width: parent.width
+ wrapMode: Text.NoWrap
+ horizontalAlignment: Text.AlignLeft
+ }
+ }
+ }
+
+ Rectangle {
+ anchors.right: parent.right
+ anchors.rightMargin: Theme.spacingM
+ anchors.verticalCenter: parent.verticalCenter
+ width: pinInputRow.width + Theme.spacingS * 2
+ height: 28
+ radius: height / 2
+ color: {
+ const isThisDevicePinned = audioContent.getPinnedInputs().includes(modelData.name);
+ return isThisDevicePinned ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : Theme.withAlpha(Theme.surfaceText, 0.05);
+ }
+
+ Row {
+ id: pinInputRow
+ anchors.centerIn: parent
+ spacing: 4
+
+ DankIcon {
+ name: "push_pin"
+ size: 16
+ color: {
+ const isThisDevicePinned = audioContent.getPinnedInputs().includes(modelData.name);
+ return isThisDevicePinned ? Theme.primary : Theme.surfaceText;
+ }
+ anchors.verticalCenter: parent.verticalCenter
+ }
+
+ StyledText {
+ text: {
+ const isThisDevicePinned = audioContent.getPinnedInputs().includes(modelData.name);
+ return isThisDevicePinned ? I18n.tr("Pinned") : I18n.tr("Pin");
+ }
+ font.pixelSize: Theme.fontSizeSmall
+ color: {
+ const isThisDevicePinned = audioContent.getPinnedInputs().includes(modelData.name);
+ return isThisDevicePinned ? Theme.primary : Theme.surfaceText;
+ }
+ anchors.verticalCenter: parent.verticalCenter
+ }
+ }
+
+ DankRipple {
+ id: pinRipple
+ cornerRadius: parent.radius
+ }
+
+ MouseArea {
+ anchors.fill: parent
+ cursorShape: Qt.PointingHandCursor
+ onPressed: mouse => pinRipple.trigger(mouse.x, mouse.y)
+ onClicked: {
+ const pins = JSON.parse(JSON.stringify(SettingsData.audioInputDevicePins || {}));
+ let pinnedList = audioContent.normalizePinList(pins["preferredInput"]);
+ const pinIndex = pinnedList.indexOf(modelData.name);
+
+ if (pinIndex !== -1) {
+ pinnedList.splice(pinIndex, 1);
+ } else {
+ pinnedList.unshift(modelData.name);
+ if (pinnedList.length > audioContent.maxPinnedInputs)
+ pinnedList = pinnedList.slice(0, audioContent.maxPinnedInputs);
+ }
+
+ if (pinnedList.length > 0)
+ pins["preferredInput"] = pinnedList;
+ else
+ delete pins["preferredInput"];
+
+ SettingsData.set("audioInputDevicePins", pins);
+ }
+ }
+ }
+
+ DankRipple {
+ id: deviceRipple
+ cornerRadius: parent.radius
+ }
+
+ MouseArea {
+ id: deviceMouseArea
+ anchors.fill: parent
+ anchors.rightMargin: pinInputRow.width + Theme.spacingS * 4
+ hoverEnabled: true
+ cursorShape: Qt.PointingHandCursor
+ onPressed: mouse => {
+ let mapped = mapToItem(parent, mouse.x, mouse.y);
+ deviceRipple.trigger(mapped.x, mapped.y);
+ }
+ onClicked: {
+ if (modelData) {
+ Pipewire.preferredDefaultAudioSource = modelData;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}