forked from Shinonome/dots-hyprland
right sidebar: move audio controls to dialogs
This commit is contained in:
@@ -139,6 +139,7 @@ Singleton {
|
|||||||
property string networkEthernet: "kcmshell6 kcm_networkmanagement"
|
property string networkEthernet: "kcmshell6 kcm_networkmanagement"
|
||||||
property string taskManager: "plasma-systemmonitor --page-name Processes"
|
property string taskManager: "plasma-systemmonitor --page-name Processes"
|
||||||
property string terminal: "kitty -1" // This is only for shell actions
|
property string terminal: "kitty -1" // This is only for shell actions
|
||||||
|
property string volumeMixer: `~/.config/hypr/hyprland/scripts/launch_first_available.sh "pavucontrol-qt" "pavucontrol"`
|
||||||
}
|
}
|
||||||
|
|
||||||
property JsonObject background: JsonObject {
|
property JsonObject background: JsonObject {
|
||||||
|
|||||||
@@ -165,7 +165,7 @@ Slider {
|
|||||||
TrackDot {
|
TrackDot {
|
||||||
required property real modelData
|
required property real modelData
|
||||||
value: modelData
|
value: modelData
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent?.verticalCenter
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import QtQuick
|
||||||
|
import Quickshell
|
||||||
|
import qs.modules.common
|
||||||
|
import qs.modules.common.functions
|
||||||
|
import qs.modules.common.widgets
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: "Section"
|
||||||
|
font {
|
||||||
|
pixelSize: Appearance.font.pixelSize.large
|
||||||
|
family: Appearance.font.family.title
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -13,70 +13,8 @@ Rectangle {
|
|||||||
radius: Appearance.rounding.normal
|
radius: Appearance.rounding.normal
|
||||||
color: Appearance.colors.colLayer1
|
color: Appearance.colors.colLayer1
|
||||||
|
|
||||||
property int selectedTab: 0
|
NotificationList {
|
||||||
property var tabButtonList: [
|
|
||||||
{"icon": "notifications", "name": Translation.tr("Notifications")},
|
|
||||||
{"icon": "volume_up", "name": Translation.tr("Audio")}
|
|
||||||
]
|
|
||||||
|
|
||||||
Keys.onPressed: (event) => {
|
|
||||||
if (event.key === Qt.Key_PageDown || event.key === Qt.Key_PageUp) {
|
|
||||||
if (event.key === Qt.Key_PageDown) {
|
|
||||||
root.selectedTab = Math.min(root.selectedTab + 1, root.tabButtonList.length - 1)
|
|
||||||
} else if (event.key === Qt.Key_PageUp) {
|
|
||||||
root.selectedTab = Math.max(root.selectedTab - 1, 0)
|
|
||||||
}
|
|
||||||
event.accepted = true;
|
|
||||||
}
|
|
||||||
if (event.modifiers === Qt.ControlModifier) {
|
|
||||||
if (event.key === Qt.Key_Tab) {
|
|
||||||
root.selectedTab = (root.selectedTab + 1) % root.tabButtonList.length
|
|
||||||
} else if (event.key === Qt.Key_Backtab) {
|
|
||||||
root.selectedTab = (root.selectedTab - 1 + root.tabButtonList.length) % root.tabButtonList.length
|
|
||||||
}
|
|
||||||
event.accepted = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ColumnLayout {
|
|
||||||
anchors.margins: 5
|
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
spacing: 0
|
anchors.margins: 5
|
||||||
|
|
||||||
PrimaryTabBar {
|
|
||||||
id: tabBar
|
|
||||||
tabButtonList: root.tabButtonList
|
|
||||||
externalTrackedTab: root.selectedTab
|
|
||||||
|
|
||||||
function onCurrentIndexChanged(currentIndex) {
|
|
||||||
root.selectedTab = currentIndex
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SwipeView {
|
|
||||||
id: swipeView
|
|
||||||
Layout.topMargin: 5
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.fillHeight: true
|
|
||||||
spacing: 10
|
|
||||||
currentIndex: root.selectedTab
|
|
||||||
onCurrentIndexChanged: {
|
|
||||||
tabBar.enableIndicatorAnimation = true
|
|
||||||
root.selectedTab = currentIndex
|
|
||||||
}
|
|
||||||
|
|
||||||
clip: true
|
|
||||||
layer.enabled: true
|
|
||||||
layer.effect: OpacityMask {
|
|
||||||
maskSource: Rectangle {
|
|
||||||
width: swipeView.width
|
|
||||||
height: swipeView.height
|
|
||||||
radius: Appearance.rounding.small
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
NotificationList {}
|
|
||||||
VolumeMixer {}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import "./quickToggles/"
|
|||||||
import "./quickToggles/classicStyle/"
|
import "./quickToggles/classicStyle/"
|
||||||
import "./wifiNetworks/"
|
import "./wifiNetworks/"
|
||||||
import "./bluetoothDevices/"
|
import "./bluetoothDevices/"
|
||||||
|
import "./volumeMixer/"
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
id: root
|
id: root
|
||||||
@@ -21,6 +22,8 @@ Item {
|
|||||||
property string settingsQmlPath: Quickshell.shellPath("settings.qml")
|
property string settingsQmlPath: Quickshell.shellPath("settings.qml")
|
||||||
property bool showWifiDialog: false
|
property bool showWifiDialog: false
|
||||||
property bool showBluetoothDialog: false
|
property bool showBluetoothDialog: false
|
||||||
|
property bool showAudioOutputDialog: false
|
||||||
|
property bool showAudioInputDialog: false
|
||||||
property bool editMode: false
|
property bool editMode: false
|
||||||
|
|
||||||
Connections {
|
Connections {
|
||||||
@@ -29,6 +32,8 @@ Item {
|
|||||||
if (!GlobalStates.sidebarRightOpen) {
|
if (!GlobalStates.sidebarRightOpen) {
|
||||||
root.showWifiDialog = false;
|
root.showWifiDialog = false;
|
||||||
root.showBluetoothDialog = false;
|
root.showBluetoothDialog = false;
|
||||||
|
root.showAudioOutputDialog = false;
|
||||||
|
root.showAudioInputDialog = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -102,53 +107,71 @@ Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onShowWifiDialogChanged: if (showWifiDialog) wifiDialogLoader.active = true;
|
ToggleDialog {
|
||||||
Loader {
|
|
||||||
id: wifiDialogLoader
|
id: wifiDialogLoader
|
||||||
anchors.fill: parent
|
shownPropertyString: "showWifiDialog"
|
||||||
|
dialog: WifiDialog {}
|
||||||
active: root.showWifiDialog || item.visible
|
onShownChanged: {
|
||||||
onActiveChanged: {
|
if (!shown) return;
|
||||||
if (active) {
|
Network.enableWifi();
|
||||||
item.show = true;
|
Network.rescanWifi();
|
||||||
item.forceActiveFocus();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sourceComponent: WifiDialog {
|
|
||||||
onDismiss: {
|
|
||||||
show = false
|
|
||||||
root.showWifiDialog = false
|
|
||||||
}
|
|
||||||
onVisibleChanged: {
|
|
||||||
if (!visible && !root.showWifiDialog) wifiDialogLoader.active = false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onShowBluetoothDialogChanged: {
|
ToggleDialog {
|
||||||
if (showBluetoothDialog) bluetoothDialogLoader.active = true;
|
|
||||||
else Bluetooth.defaultAdapter.discovering = false;
|
|
||||||
}
|
|
||||||
Loader {
|
|
||||||
id: bluetoothDialogLoader
|
id: bluetoothDialogLoader
|
||||||
|
shownPropertyString: "showBluetoothDialog"
|
||||||
|
dialog: BluetoothDialog {}
|
||||||
|
onShownChanged: {
|
||||||
|
if (!shown) {
|
||||||
|
Bluetooth.defaultAdapter.discovering = false;
|
||||||
|
} else {
|
||||||
|
Bluetooth.defaultAdapter.enabled = true;
|
||||||
|
Bluetooth.defaultAdapter.discovering = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ToggleDialog {
|
||||||
|
id: audioOutputDialogLoader
|
||||||
|
shownPropertyString: "showAudioOutputDialog"
|
||||||
|
dialog: VolumeDialog {
|
||||||
|
isSink: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ToggleDialog {
|
||||||
|
id: audioInputDialogLoader
|
||||||
|
shownPropertyString: "showAudioInputDialog"
|
||||||
|
dialog: VolumeDialog {
|
||||||
|
isSink: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
component ToggleDialog: Loader {
|
||||||
|
id: toggleDialogLoader
|
||||||
|
required property string shownPropertyString
|
||||||
|
property alias dialog: toggleDialogLoader.sourceComponent
|
||||||
|
readonly property bool shown: root[shownPropertyString]
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
|
|
||||||
active: root.showBluetoothDialog || item.visible
|
onShownChanged: if (shown) toggleDialogLoader.active = true;
|
||||||
|
active: shown
|
||||||
onActiveChanged: {
|
onActiveChanged: {
|
||||||
if (active) {
|
if (active) {
|
||||||
item.show = true;
|
item.show = true;
|
||||||
item.forceActiveFocus();
|
item.forceActiveFocus();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Connections {
|
||||||
sourceComponent: BluetoothDialog {
|
target: toggleDialogLoader.item
|
||||||
onDismiss: {
|
function onDismiss() {
|
||||||
show = false
|
toggleDialogLoader.item.show = false
|
||||||
root.showBluetoothDialog = false
|
root[toggleDialogLoader.shownPropertyString] = false;
|
||||||
}
|
}
|
||||||
onVisibleChanged: {
|
function onVisibleChanged() {
|
||||||
if (!visible && !root.showBluetoothDialog) bluetoothDialogLoader.active = false;
|
if (!toggleDialogLoader.item.visible && !root[toggleDialogLoader.shownPropertyString]) toggleDialogLoader.active = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -163,15 +186,17 @@ Item {
|
|||||||
Connections {
|
Connections {
|
||||||
target: quickPanelImplLoader.item
|
target: quickPanelImplLoader.item
|
||||||
function onOpenWifiDialog() {
|
function onOpenWifiDialog() {
|
||||||
Network.enableWifi();
|
|
||||||
Network.rescanWifi();
|
|
||||||
root.showWifiDialog = true;
|
root.showWifiDialog = true;
|
||||||
}
|
}
|
||||||
function onOpenBluetoothDialog() {
|
function onOpenBluetoothDialog() {
|
||||||
Bluetooth.defaultAdapter.enabled = true;
|
|
||||||
Bluetooth.defaultAdapter.discovering = true;
|
|
||||||
root.showBluetoothDialog = true;
|
root.showBluetoothDialog = true;
|
||||||
}
|
}
|
||||||
|
function onOpenAudioOutputDialog() {
|
||||||
|
root.showAudioOutputDialog = true;
|
||||||
|
}
|
||||||
|
function onOpenAudioInputDialog() {
|
||||||
|
root.showAudioInputDialog = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,4 +9,6 @@ Rectangle {
|
|||||||
|
|
||||||
signal openWifiDialog()
|
signal openWifiDialog()
|
||||||
signal openBluetoothDialog()
|
signal openBluetoothDialog()
|
||||||
|
signal openAudioOutputDialog()
|
||||||
|
signal openAudioInputDialog()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -101,6 +101,8 @@ AbstractQuickPanel {
|
|||||||
spacing: root.spacing
|
spacing: root.spacing
|
||||||
onOpenWifiDialog: root.openWifiDialog()
|
onOpenWifiDialog: root.openWifiDialog()
|
||||||
onOpenBluetoothDialog: root.openBluetoothDialog()
|
onOpenBluetoothDialog: root.openBluetoothDialog()
|
||||||
|
onOpenAudioOutputDialog: root.openAudioOutputDialog()
|
||||||
|
onOpenAudioInputDialog: root.openAudioInputDialog()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+6
-2
@@ -8,7 +8,7 @@ import Quickshell
|
|||||||
AndroidQuickToggleButton {
|
AndroidQuickToggleButton {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
name: Translation.tr("Audio")
|
name: Translation.tr("Audio output")
|
||||||
statusText: toggled ? Translation.tr("Unmuted") : Translation.tr("Muted")
|
statusText: toggled ? Translation.tr("Unmuted") : Translation.tr("Muted")
|
||||||
toggled: !Audio.sink?.audio?.muted
|
toggled: !Audio.sink?.audio?.muted
|
||||||
buttonIcon: Audio.sink?.audio?.muted ? "volume_off" : "volume_up"
|
buttonIcon: Audio.sink?.audio?.muted ? "volume_off" : "volume_up"
|
||||||
@@ -16,7 +16,11 @@ AndroidQuickToggleButton {
|
|||||||
Audio.sink.audio.muted = !Audio.sink.audio.muted
|
Audio.sink.audio.muted = !Audio.sink.audio.muted
|
||||||
}
|
}
|
||||||
|
|
||||||
|
altAction: () => {
|
||||||
|
root.openMenu()
|
||||||
|
}
|
||||||
|
|
||||||
StyledToolTip {
|
StyledToolTip {
|
||||||
text: Translation.tr("Audio")
|
text: Translation.tr("Audio output | Right-click for volume mixer & device selector")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-1
@@ -17,11 +17,14 @@ AndroidQuickToggleButton {
|
|||||||
onClicked: {
|
onClicked: {
|
||||||
Bluetooth.defaultAdapter.enabled = !Bluetooth.defaultAdapter?.enabled
|
Bluetooth.defaultAdapter.enabled = !Bluetooth.defaultAdapter?.enabled
|
||||||
}
|
}
|
||||||
|
altAction: () => {
|
||||||
|
root.openMenu()
|
||||||
|
}
|
||||||
StyledToolTip {
|
StyledToolTip {
|
||||||
text: Translation.tr("%1 | Right-click to configure").arg(
|
text: Translation.tr("%1 | Right-click to configure").arg(
|
||||||
(BluetoothStatus.firstActiveDevice?.name ?? Translation.tr("Bluetooth"))
|
(BluetoothStatus.firstActiveDevice?.name ?? Translation.tr("Bluetooth"))
|
||||||
+ (BluetoothStatus.activeDeviceCount > 1 ? ` +${BluetoothStatus.activeDeviceCount - 1}` : "")
|
+ (BluetoothStatus.activeDeviceCount > 1 ? ` +${BluetoothStatus.activeDeviceCount - 1}` : "")
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+6
-2
@@ -8,7 +8,7 @@ import Quickshell
|
|||||||
AndroidQuickToggleButton {
|
AndroidQuickToggleButton {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
name: Translation.tr("Microphone")
|
name: Translation.tr("Audio input")
|
||||||
statusText: toggled ? Translation.tr("Enabled") : Translation.tr("Muted")
|
statusText: toggled ? Translation.tr("Enabled") : Translation.tr("Muted")
|
||||||
toggled: !Audio.source?.audio?.muted
|
toggled: !Audio.source?.audio?.muted
|
||||||
buttonIcon: Audio.source?.audio?.muted ? "mic_off" : "mic"
|
buttonIcon: Audio.source?.audio?.muted ? "mic_off" : "mic"
|
||||||
@@ -16,7 +16,11 @@ AndroidQuickToggleButton {
|
|||||||
Audio.source.audio.muted = !Audio.source.audio.muted
|
Audio.source.audio.muted = !Audio.source.audio.muted
|
||||||
}
|
}
|
||||||
|
|
||||||
|
altAction: () => {
|
||||||
|
root.openMenu()
|
||||||
|
}
|
||||||
|
|
||||||
StyledToolTip {
|
StyledToolTip {
|
||||||
text: Translation.tr("Microphone")
|
text: Translation.tr("Audio input | Right-click for volume mixer & device selector")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+10
-4
@@ -7,8 +7,6 @@ import QtQuick.Layouts
|
|||||||
import Quickshell
|
import Quickshell
|
||||||
import Quickshell.Bluetooth
|
import Quickshell.Bluetooth
|
||||||
|
|
||||||
import "./androidStyle/"
|
|
||||||
|
|
||||||
DelegateChooser {
|
DelegateChooser {
|
||||||
id: root
|
id: root
|
||||||
property bool editMode: false
|
property bool editMode: false
|
||||||
@@ -18,6 +16,8 @@ DelegateChooser {
|
|||||||
required property int startingIndex
|
required property int startingIndex
|
||||||
signal openWifiDialog()
|
signal openWifiDialog()
|
||||||
signal openBluetoothDialog()
|
signal openBluetoothDialog()
|
||||||
|
signal openAudioOutputDialog()
|
||||||
|
signal openAudioInputDialog()
|
||||||
|
|
||||||
role: "type"
|
role: "type"
|
||||||
|
|
||||||
@@ -32,7 +32,7 @@ DelegateChooser {
|
|||||||
baseCellHeight: root.baseCellHeight
|
baseCellHeight: root.baseCellHeight
|
||||||
cellSpacing: root.spacing
|
cellSpacing: root.spacing
|
||||||
cellSize: modelData.size
|
cellSize: modelData.size
|
||||||
altAction: () => {
|
onOpenMenu: {
|
||||||
root.openWifiDialog()
|
root.openWifiDialog()
|
||||||
}
|
}
|
||||||
} }
|
} }
|
||||||
@@ -48,7 +48,7 @@ DelegateChooser {
|
|||||||
baseCellHeight: root.baseCellHeight
|
baseCellHeight: root.baseCellHeight
|
||||||
cellSpacing: root.spacing
|
cellSpacing: root.spacing
|
||||||
cellSize: modelData.size
|
cellSize: modelData.size
|
||||||
altAction: () => {
|
onOpenMenu: {
|
||||||
root.openBluetoothDialog()
|
root.openBluetoothDialog()
|
||||||
}
|
}
|
||||||
} }
|
} }
|
||||||
@@ -181,6 +181,9 @@ DelegateChooser {
|
|||||||
baseCellHeight: root.baseCellHeight
|
baseCellHeight: root.baseCellHeight
|
||||||
cellSpacing: root.spacing
|
cellSpacing: root.spacing
|
||||||
cellSize: modelData.size
|
cellSize: modelData.size
|
||||||
|
onOpenMenu: {
|
||||||
|
root.openAudioInputDialog()
|
||||||
|
}
|
||||||
} }
|
} }
|
||||||
|
|
||||||
DelegateChoice { roleValue: "audio"; AndroidAudioToggle {
|
DelegateChoice { roleValue: "audio"; AndroidAudioToggle {
|
||||||
@@ -194,6 +197,9 @@ DelegateChooser {
|
|||||||
baseCellHeight: root.baseCellHeight
|
baseCellHeight: root.baseCellHeight
|
||||||
cellSpacing: root.spacing
|
cellSpacing: root.spacing
|
||||||
cellSize: modelData.size
|
cellSize: modelData.size
|
||||||
|
onOpenMenu: {
|
||||||
|
root.openAudioOutputDialog()
|
||||||
|
}
|
||||||
} }
|
} }
|
||||||
|
|
||||||
DelegateChoice { roleValue: "notifications"; AndroidNotificationToggle {
|
DelegateChoice { roleValue: "notifications"; AndroidNotificationToggle {
|
||||||
|
|||||||
@@ -0,0 +1,136 @@
|
|||||||
|
pragma ComponentBehavior: Bound
|
||||||
|
import qs
|
||||||
|
import qs.services
|
||||||
|
import qs.modules.common
|
||||||
|
import qs.modules.common.widgets
|
||||||
|
import QtQuick
|
||||||
|
import QtQuick.Layouts
|
||||||
|
import Quickshell
|
||||||
|
import Quickshell.Services.Pipewire
|
||||||
|
|
||||||
|
WindowDialog {
|
||||||
|
id: root
|
||||||
|
property bool isSink: true
|
||||||
|
function correctType(node) {
|
||||||
|
return (node.isSink === root.isSink) && node.audio
|
||||||
|
}
|
||||||
|
readonly property list<var> appPwNodes: Pipewire.nodes.values.filter((node) => { // Should be list<PwNode> but it breaks ScriptModel
|
||||||
|
return root.correctType(node) && node.isStream
|
||||||
|
})
|
||||||
|
readonly property bool hasApps: appPwNodes.length > 0
|
||||||
|
backgroundHeight: 700
|
||||||
|
|
||||||
|
WindowDialogTitle {
|
||||||
|
text: root.isSink ? Translation.tr("Audio output") : Translation.tr("Audio input")
|
||||||
|
}
|
||||||
|
|
||||||
|
WindowDialogSectionHeader {
|
||||||
|
visible: root.hasApps
|
||||||
|
text: Translation.tr("Applications")
|
||||||
|
}
|
||||||
|
|
||||||
|
WindowDialogSeparator {
|
||||||
|
visible: root.hasApps
|
||||||
|
Layout.topMargin: -22
|
||||||
|
Layout.leftMargin: 0
|
||||||
|
Layout.rightMargin: 0
|
||||||
|
}
|
||||||
|
|
||||||
|
DialogSectionListView {
|
||||||
|
visible: root.hasApps
|
||||||
|
Layout.fillHeight: true
|
||||||
|
|
||||||
|
model: ScriptModel {
|
||||||
|
values: root.appPwNodes
|
||||||
|
}
|
||||||
|
delegate: VolumeMixerEntry {
|
||||||
|
anchors {
|
||||||
|
left: parent?.left
|
||||||
|
right: parent?.right
|
||||||
|
}
|
||||||
|
required property var modelData
|
||||||
|
node: modelData
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
WindowDialogSectionHeader {
|
||||||
|
text: Translation.tr("Devices")
|
||||||
|
}
|
||||||
|
|
||||||
|
WindowDialogSeparator {
|
||||||
|
Layout.topMargin: -22
|
||||||
|
Layout.leftMargin: 0
|
||||||
|
Layout.rightMargin: 0
|
||||||
|
}
|
||||||
|
|
||||||
|
DialogSectionListView {
|
||||||
|
Layout.fillHeight: !root.hasApps
|
||||||
|
Layout.preferredHeight: 180
|
||||||
|
|
||||||
|
model: ScriptModel {
|
||||||
|
values: Pipewire.nodes.values.filter(node => {
|
||||||
|
return root.correctType(node) && !node.isStream
|
||||||
|
})
|
||||||
|
}
|
||||||
|
delegate: StyledRadioButton {
|
||||||
|
id: radioButton
|
||||||
|
required property var modelData
|
||||||
|
anchors {
|
||||||
|
left: parent?.left
|
||||||
|
right: parent?.right
|
||||||
|
}
|
||||||
|
|
||||||
|
description: modelData.description
|
||||||
|
checked: modelData.id === (root.isSink ? Pipewire.preferredDefaultAudioSink?.id : Pipewire.preferredDefaultAudioSource?.id)
|
||||||
|
|
||||||
|
onCheckedChanged: {
|
||||||
|
if (!checked) return;
|
||||||
|
if (root.isSink) {
|
||||||
|
Pipewire.preferredDefaultAudioSink = modelData
|
||||||
|
} else {
|
||||||
|
Pipewire.preferredDefaultAudioSource = modelData
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
WindowDialogSeparator {
|
||||||
|
Layout.leftMargin: 0
|
||||||
|
Layout.rightMargin: 0
|
||||||
|
}
|
||||||
|
|
||||||
|
WindowDialogButtonRow {
|
||||||
|
DialogButton {
|
||||||
|
buttonText: Translation.tr("Details")
|
||||||
|
onClicked: {
|
||||||
|
Quickshell.execDetached(["bash", "-c", `${Config.options.apps.volumeMixer}`]);
|
||||||
|
GlobalStates.sidebarRightOpen = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
}
|
||||||
|
|
||||||
|
DialogButton {
|
||||||
|
buttonText: Translation.tr("Done")
|
||||||
|
onClicked: root.dismiss()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
component DialogSectionListView: StyledListView {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.topMargin: -22
|
||||||
|
Layout.bottomMargin: -16
|
||||||
|
Layout.leftMargin: -Appearance.rounding.large
|
||||||
|
Layout.rightMargin: -Appearance.rounding.large
|
||||||
|
topMargin: 12
|
||||||
|
bottomMargin: 12
|
||||||
|
leftMargin: 20
|
||||||
|
rightMargin: 20
|
||||||
|
|
||||||
|
clip: true
|
||||||
|
spacing: 4
|
||||||
|
animateAppearance: false
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,275 +0,0 @@
|
|||||||
import qs.modules.common
|
|
||||||
import qs.modules.common.widgets
|
|
||||||
import qs.services
|
|
||||||
import Qt5Compat.GraphicalEffects
|
|
||||||
import QtQuick
|
|
||||||
import QtQuick.Controls
|
|
||||||
import QtQuick.Layouts
|
|
||||||
import Quickshell
|
|
||||||
import Quickshell.Services.Pipewire
|
|
||||||
|
|
||||||
|
|
||||||
Item {
|
|
||||||
id: root
|
|
||||||
property bool showDeviceSelector: false
|
|
||||||
property bool deviceSelectorInput
|
|
||||||
property int dialogMargins: 16
|
|
||||||
property PwNode selectedDevice
|
|
||||||
readonly property list<PwNode> appPwNodes: Pipewire.nodes.values.filter((node) => {
|
|
||||||
// return node.type == "21" // Alternative, not as clean
|
|
||||||
return node.isSink && node.isStream
|
|
||||||
})
|
|
||||||
|
|
||||||
function showDeviceSelectorDialog(input: bool) {
|
|
||||||
root.selectedDevice = null
|
|
||||||
root.showDeviceSelector = true
|
|
||||||
root.deviceSelectorInput = input
|
|
||||||
}
|
|
||||||
|
|
||||||
Keys.onPressed: (event) => {
|
|
||||||
// Close dialog on pressing Esc if open
|
|
||||||
if (event.key === Qt.Key_Escape && root.showDeviceSelector) {
|
|
||||||
root.showDeviceSelector = false
|
|
||||||
event.accepted = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ColumnLayout {
|
|
||||||
anchors.fill: parent
|
|
||||||
Item {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.fillHeight: true
|
|
||||||
StyledListView {
|
|
||||||
id: listView
|
|
||||||
model: root.appPwNodes
|
|
||||||
clip: true
|
|
||||||
anchors {
|
|
||||||
fill: parent
|
|
||||||
topMargin: 10
|
|
||||||
bottomMargin: 10
|
|
||||||
}
|
|
||||||
spacing: 6
|
|
||||||
|
|
||||||
delegate: VolumeMixerEntry {
|
|
||||||
// Layout.fillWidth: true
|
|
||||||
anchors {
|
|
||||||
left: parent.left
|
|
||||||
right: parent.right
|
|
||||||
leftMargin: 10
|
|
||||||
rightMargin: 10
|
|
||||||
}
|
|
||||||
required property var modelData
|
|
||||||
node: modelData
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Placeholder when list is empty
|
|
||||||
Item {
|
|
||||||
anchors.fill: listView
|
|
||||||
|
|
||||||
visible: opacity > 0
|
|
||||||
opacity: (root.appPwNodes.length === 0) ? 1 : 0
|
|
||||||
|
|
||||||
Behavior on opacity {
|
|
||||||
NumberAnimation {
|
|
||||||
duration: Appearance.animation.menuDecel.duration
|
|
||||||
easing.type: Appearance.animation.menuDecel.type
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ColumnLayout {
|
|
||||||
anchors.centerIn: parent
|
|
||||||
spacing: 5
|
|
||||||
|
|
||||||
MaterialSymbol {
|
|
||||||
Layout.alignment: Qt.AlignHCenter
|
|
||||||
iconSize: 55
|
|
||||||
color: Appearance.m3colors.m3outline
|
|
||||||
text: "brand_awareness"
|
|
||||||
}
|
|
||||||
StyledText {
|
|
||||||
Layout.alignment: Qt.AlignHCenter
|
|
||||||
font.pixelSize: Appearance.font.pixelSize.normal
|
|
||||||
color: Appearance.m3colors.m3outline
|
|
||||||
horizontalAlignment: Text.AlignHCenter
|
|
||||||
text: Translation.tr("No audio source")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Device selector
|
|
||||||
RowLayout {
|
|
||||||
id: deviceSelectorRowLayout
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.fillHeight: false
|
|
||||||
uniformCellSizes: true
|
|
||||||
|
|
||||||
AudioDeviceSelectorButton {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
input: false
|
|
||||||
downAction: () => root.showDeviceSelectorDialog(input)
|
|
||||||
}
|
|
||||||
AudioDeviceSelectorButton {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
input: true
|
|
||||||
downAction: () => root.showDeviceSelectorDialog(input)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Device selector dialog
|
|
||||||
Item {
|
|
||||||
anchors.fill: parent
|
|
||||||
z: 9999
|
|
||||||
|
|
||||||
visible: opacity > 0
|
|
||||||
opacity: root.showDeviceSelector ? 1 : 0
|
|
||||||
Behavior on opacity {
|
|
||||||
NumberAnimation {
|
|
||||||
duration: Appearance.animation.elementMoveFast.duration
|
|
||||||
easing.type: Appearance.animation.elementMoveFast.type
|
|
||||||
easing.bezierCurve: Appearance.animation.elementMoveFast.bezierCurve
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle { // Scrim
|
|
||||||
id: scrimOverlay
|
|
||||||
anchors.fill: parent
|
|
||||||
radius: Appearance.rounding.small
|
|
||||||
color: Appearance.colors.colScrim
|
|
||||||
MouseArea {
|
|
||||||
hoverEnabled: true
|
|
||||||
anchors.fill: parent
|
|
||||||
preventStealing: true
|
|
||||||
propagateComposedEvents: false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle { // The dialog
|
|
||||||
id: dialog
|
|
||||||
color: Appearance.colors.colSurfaceContainerHigh
|
|
||||||
radius: Appearance.rounding.normal
|
|
||||||
anchors.left: parent.left
|
|
||||||
anchors.right: parent.right
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
anchors.margins: 30
|
|
||||||
implicitHeight: dialogColumnLayout.implicitHeight
|
|
||||||
|
|
||||||
ColumnLayout {
|
|
||||||
id: dialogColumnLayout
|
|
||||||
anchors.fill: parent
|
|
||||||
spacing: 16
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
id: dialogTitle
|
|
||||||
Layout.topMargin: dialogMargins
|
|
||||||
Layout.leftMargin: dialogMargins
|
|
||||||
Layout.rightMargin: dialogMargins
|
|
||||||
Layout.alignment: Qt.AlignLeft
|
|
||||||
color: Appearance.m3colors.m3onSurface
|
|
||||||
font.pixelSize: Appearance.font.pixelSize.larger
|
|
||||||
text: root.deviceSelectorInput ? Translation.tr("Select input device") : Translation.tr("Select output device")
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
color: Appearance.m3colors.m3outline
|
|
||||||
implicitHeight: 1
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.leftMargin: dialogMargins
|
|
||||||
Layout.rightMargin: dialogMargins
|
|
||||||
}
|
|
||||||
|
|
||||||
StyledFlickable {
|
|
||||||
id: dialogFlickable
|
|
||||||
Layout.fillWidth: true
|
|
||||||
clip: true
|
|
||||||
implicitHeight: Math.min(scrimOverlay.height - dialogMargins * 8 - dialogTitle.height - dialogButtonsRowLayout.height, devicesColumnLayout.implicitHeight)
|
|
||||||
|
|
||||||
contentHeight: devicesColumnLayout.implicitHeight
|
|
||||||
|
|
||||||
ColumnLayout {
|
|
||||||
id: devicesColumnLayout
|
|
||||||
anchors.fill: parent
|
|
||||||
Layout.fillWidth: true
|
|
||||||
spacing: 0
|
|
||||||
|
|
||||||
Repeater {
|
|
||||||
model: ScriptModel {
|
|
||||||
values: Pipewire.nodes.values.filter(node => {
|
|
||||||
return !node.isStream && node.isSink !== root.deviceSelectorInput && node.audio
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// This could and should be refractored, but all data becomes null when passed wtf
|
|
||||||
delegate: StyledRadioButton {
|
|
||||||
id: radioButton
|
|
||||||
required property var modelData
|
|
||||||
Layout.leftMargin: root.dialogMargins
|
|
||||||
Layout.rightMargin: root.dialogMargins
|
|
||||||
Layout.fillWidth: true
|
|
||||||
|
|
||||||
description: modelData.description
|
|
||||||
checked: modelData.id === Pipewire.defaultAudioSink?.id
|
|
||||||
|
|
||||||
Connections {
|
|
||||||
target: root
|
|
||||||
function onShowDeviceSelectorChanged() {
|
|
||||||
if(!root.showDeviceSelector) return;
|
|
||||||
radioButton.checked = (modelData.id === Pipewire.defaultAudioSink?.id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onCheckedChanged: {
|
|
||||||
if (checked) {
|
|
||||||
root.selectedDevice = modelData
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Item {
|
|
||||||
implicitHeight: dialogMargins
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
color: Appearance.m3colors.m3outline
|
|
||||||
implicitHeight: 1
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.leftMargin: dialogMargins
|
|
||||||
Layout.rightMargin: dialogMargins
|
|
||||||
}
|
|
||||||
|
|
||||||
RowLayout {
|
|
||||||
id: dialogButtonsRowLayout
|
|
||||||
Layout.bottomMargin: dialogMargins
|
|
||||||
Layout.leftMargin: dialogMargins
|
|
||||||
Layout.rightMargin: dialogMargins
|
|
||||||
Layout.alignment: Qt.AlignRight
|
|
||||||
|
|
||||||
DialogButton {
|
|
||||||
buttonText: Translation.tr("Cancel")
|
|
||||||
onClicked: {
|
|
||||||
root.showDeviceSelector = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
DialogButton {
|
|
||||||
buttonText: Translation.tr("OK")
|
|
||||||
onClicked: {
|
|
||||||
root.showDeviceSelector = false
|
|
||||||
if (root.selectedDevice) {
|
|
||||||
if (root.deviceSelectorInput) {
|
|
||||||
Pipewire.preferredDefaultAudioSource = root.selectedDevice
|
|
||||||
} else {
|
|
||||||
Pipewire.preferredDefaultAudioSink = root.selectedDevice
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -21,7 +21,7 @@ Item {
|
|||||||
spacing: 6
|
spacing: 6
|
||||||
|
|
||||||
Image {
|
Image {
|
||||||
property real size: slider.height * 0.9
|
property real size: 36
|
||||||
Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
|
Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
|
||||||
visible: source != ""
|
visible: source != ""
|
||||||
sourceSize.width: size
|
sourceSize.width: size
|
||||||
@@ -57,6 +57,7 @@ Item {
|
|||||||
id: slider
|
id: slider
|
||||||
value: root.node.audio.volume
|
value: root.node.audio.volume
|
||||||
onMoved: root.node.audio.volume = value
|
onMoved: root.node.audio.volume = value
|
||||||
|
configuration: StyledSlider.Configuration.S
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user