Merge pull request #11 from Zacharias-Brohn/main
merge main
This commit was merged in pull request #11.
This commit is contained in:
+151
-217
@@ -14,8 +14,11 @@ import qs.Helpers
|
||||
Item {
|
||||
id: root
|
||||
|
||||
implicitWidth: layout.implicitWidth + 10 * 2
|
||||
implicitHeight: layout.implicitHeight + 10 * 2
|
||||
implicitWidth: layout.implicitWidth + 5 * 2
|
||||
implicitHeight: layout.implicitHeight + 5 * 2
|
||||
|
||||
readonly property int topMargin: 0
|
||||
readonly property int rounding: 6
|
||||
|
||||
required property var wrapper
|
||||
|
||||
@@ -38,7 +41,7 @@ Item {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: tabBar.tabHeight
|
||||
|
||||
color: stack.currentIndex === 0 ? DynamicColors.tPalette.m3primaryContainer : "transparent"
|
||||
color: stack.currentIndex === 0 ? DynamicColors.palette.m3primary : DynamicColors.tPalette.m3surfaceContainer
|
||||
|
||||
StateLayer {
|
||||
|
||||
@@ -49,6 +52,7 @@ Item {
|
||||
CustomText {
|
||||
text: qsTr("Volumes")
|
||||
anchors.centerIn: parent
|
||||
color: stack.currentIndex === 0 ? DynamicColors.palette.m3onPrimary : DynamicColors.palette.m3primary
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -58,7 +62,7 @@ Item {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: tabBar.tabHeight
|
||||
|
||||
color: stack.currentIndex === 1 ? DynamicColors.tPalette.m3primaryContainer : "transparent"
|
||||
color: stack.currentIndex === 1 ? DynamicColors.palette.m3primary : DynamicColors.tPalette.m3surfaceContainer
|
||||
|
||||
StateLayer {
|
||||
|
||||
@@ -69,6 +73,7 @@ Item {
|
||||
CustomText {
|
||||
text: qsTr("Devices")
|
||||
anchors.centerIn: parent
|
||||
color: stack.currentIndex === 1 ? DynamicColors.palette.m3onPrimary : DynamicColors.palette.m3primary
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -126,113 +131,143 @@ Item {
|
||||
component VolumesTab: ColumnLayout {
|
||||
spacing: 12
|
||||
|
||||
RowLayout {
|
||||
Layout.topMargin: 10
|
||||
spacing: 15
|
||||
Rectangle {
|
||||
Layout.preferredWidth: 40
|
||||
Layout.preferredHeight: 40
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
color: DynamicColors.tPalette.m3primaryContainer
|
||||
radius: 1000
|
||||
MaterialIcon {
|
||||
anchors.centerIn: parent
|
||||
color: DynamicColors.palette.m3onPrimaryContainer
|
||||
text: "volume_up"
|
||||
font.pointSize: 22
|
||||
CustomRect {
|
||||
Layout.topMargin: root.topMargin
|
||||
Layout.preferredHeight: 42 + Appearance.spacing.smaller * 2
|
||||
Layout.fillWidth: true
|
||||
color: DynamicColors.tPalette.m3surfaceContainer
|
||||
|
||||
radius: root.rounding
|
||||
|
||||
RowLayout {
|
||||
id: outputVolume
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.margins: Appearance.spacing.smaller
|
||||
spacing: 15
|
||||
CustomRect {
|
||||
Layout.preferredWidth: 40
|
||||
Layout.preferredHeight: 40
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
color: DynamicColors.palette.m3primary
|
||||
radius: 1000
|
||||
MaterialIcon {
|
||||
anchors.centerIn: parent
|
||||
color: DynamicColors.palette.m3onPrimary
|
||||
text: "speaker"
|
||||
font.pointSize: 22
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
RowLayout {
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
|
||||
CustomText {
|
||||
text: "Output Volume"
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft
|
||||
|
||||
CustomText {
|
||||
text: "Output Volume"
|
||||
Layout.fillWidth: true
|
||||
Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft
|
||||
}
|
||||
|
||||
CustomText {
|
||||
text: qsTr("%1").arg(Audio.muted ? qsTr("Muted") : `${Math.round(Audio.volume * 100)}%`);
|
||||
font.bold: true
|
||||
Layout.alignment: Qt.AlignVCenter | Qt.AlignRight
|
||||
}
|
||||
}
|
||||
|
||||
CustomText {
|
||||
text: qsTr("%1").arg(Audio.muted ? qsTr("Muted") : `${Math.round(Audio.volume * 100)}%`);
|
||||
font.bold: true
|
||||
Layout.alignment: Qt.AlignVCenter | Qt.AlignRight
|
||||
}
|
||||
}
|
||||
CustomMouseArea {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 10
|
||||
Layout.bottomMargin: 5
|
||||
|
||||
CustomMouseArea {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 10
|
||||
Layout.bottomMargin: 5
|
||||
CustomSlider {
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
implicitHeight: 10
|
||||
value: Audio.volume
|
||||
onMoved: Audio.setVolume(value)
|
||||
|
||||
CustomSlider {
|
||||
anchors.fill: parent
|
||||
value: Audio.volume
|
||||
onMoved: Audio.setVolume(value)
|
||||
|
||||
Behavior on value { Anim {} }
|
||||
Behavior on value { Anim {} }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
RowLayout {
|
||||
Layout.topMargin: 10
|
||||
spacing: 15
|
||||
Rectangle {
|
||||
Layout.preferredWidth: 40
|
||||
Layout.preferredHeight: 40
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
color: DynamicColors.tPalette.m3primaryContainer
|
||||
radius: 1000
|
||||
MaterialIcon {
|
||||
anchors.centerIn: parent
|
||||
anchors.alignWhenCentered: false
|
||||
color: DynamicColors.palette.m3onPrimaryContainer
|
||||
text: "mic"
|
||||
font.pointSize: 22
|
||||
CustomRect {
|
||||
Layout.topMargin: root.topMargin
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 42 + Appearance.spacing.smaller * 2
|
||||
color: DynamicColors.tPalette.m3surfaceContainer
|
||||
|
||||
radius: root.rounding
|
||||
|
||||
RowLayout {
|
||||
id: inputVolume
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.margins: Appearance.spacing.smaller
|
||||
spacing: 15
|
||||
Rectangle {
|
||||
Layout.preferredWidth: 40
|
||||
Layout.preferredHeight: 40
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
color: DynamicColors.palette.m3primary
|
||||
radius: 1000
|
||||
MaterialIcon {
|
||||
anchors.centerIn: parent
|
||||
anchors.alignWhenCentered: false
|
||||
color: DynamicColors.palette.m3onPrimary
|
||||
text: "mic"
|
||||
font.pointSize: 22
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
RowLayout {
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
|
||||
CustomText {
|
||||
text: "Input Volume"
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft
|
||||
Layout.fillHeight: true
|
||||
|
||||
CustomText {
|
||||
text: "Input Volume"
|
||||
Layout.fillWidth: true
|
||||
Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft
|
||||
}
|
||||
|
||||
CustomText {
|
||||
text: qsTr("%1").arg(Audio.sourceMuted ? qsTr("Muted") : `${Math.round(Audio.sourceVolume * 100)}%`);
|
||||
font.bold: true
|
||||
Layout.alignment: Qt.AlignVCenter | Qt.AlignRight
|
||||
}
|
||||
}
|
||||
|
||||
CustomText {
|
||||
text: qsTr("%1").arg(Audio.sourceMuted ? qsTr("Muted") : `${Math.round(Audio.sourceVolume * 100)}%`);
|
||||
font.bold: true
|
||||
Layout.alignment: Qt.AlignVCenter | Qt.AlignRight
|
||||
}
|
||||
}
|
||||
CustomMouseArea {
|
||||
Layout.fillWidth: true
|
||||
Layout.bottomMargin: 5
|
||||
implicitHeight: 10
|
||||
|
||||
CustomMouseArea {
|
||||
Layout.fillWidth: true
|
||||
Layout.bottomMargin: 5
|
||||
implicitHeight: 10
|
||||
CustomSlider {
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
implicitHeight: 10
|
||||
value: Audio.sourceVolume
|
||||
onMoved: Audio.setSourceVolume(value)
|
||||
|
||||
CustomSlider {
|
||||
anchors.fill: parent
|
||||
value: Audio.sourceVolume
|
||||
onMoved: Audio.setSourceVolume(value)
|
||||
|
||||
Behavior on value { Anim {} }
|
||||
Behavior on value { Anim {} }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
Layout.topMargin: 10
|
||||
Layout.topMargin: root.topMargin
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 1
|
||||
|
||||
@@ -240,147 +275,47 @@ Item {
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: Audio.appStreams
|
||||
model: Audio.streams.filter(s => s.isSink)
|
||||
|
||||
Item {
|
||||
CustomRect {
|
||||
id: appBox
|
||||
|
||||
Layout.topMargin: 10
|
||||
Layout.topMargin: root.topMargin
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 42
|
||||
visible: !isCaptureStream
|
||||
required property PwNode modelData
|
||||
Layout.preferredHeight: 42 + Appearance.spacing.smaller * 2
|
||||
color: DynamicColors.tPalette.m3surfaceContainer
|
||||
|
||||
function isValidMatch(searchTerm, entry) {
|
||||
if (!entry)
|
||||
return false;
|
||||
var search = searchTerm.toLowerCase();
|
||||
var id = (entry.id || "").toLowerCase();
|
||||
var name = (entry.name || "").toLowerCase();
|
||||
var icon = (entry.icon || "").toLowerCase();
|
||||
// Match is valid if search term appears in entry or entry appears in search
|
||||
return id.includes(search) || name.includes(search) || icon.includes(search) || search.includes(id.split('.').pop()) || search.includes(name.replace(/\s+/g, ''));
|
||||
}
|
||||
radius: root.rounding
|
||||
|
||||
readonly property string appName: {
|
||||
if (!modelData)
|
||||
return "Unknown App";
|
||||
|
||||
var props = modelData.properties;
|
||||
var desc = modelData.description || "";
|
||||
var name = modelData.name || "";
|
||||
var mediaName = props["media.name"] || "";
|
||||
|
||||
if ( mediaName !== "playStream" ) {
|
||||
return mediaName;
|
||||
}
|
||||
|
||||
if (!props) {
|
||||
if (desc)
|
||||
return desc;
|
||||
if (name) {
|
||||
var nameParts = name.split(/[-_]/);
|
||||
if (nameParts.length > 0 && nameParts[0])
|
||||
return nameParts[0].charAt(0).toUpperCase() + nameParts[0].slice(1);
|
||||
return name;
|
||||
}
|
||||
return "Unknown App";
|
||||
}
|
||||
|
||||
var binaryName = props["application.process.binary"] || "";
|
||||
|
||||
// Try binary name first (fixes Electron apps like vesktop)
|
||||
if (binaryName) {
|
||||
var binParts = binaryName.split("/");
|
||||
if (binParts.length > 0) {
|
||||
var binName = binParts[binParts.length - 1].toLowerCase();
|
||||
var entry = ThemeIcons.findAppEntry(binName);
|
||||
// Only use entry if it's actually related to binary name
|
||||
if (entry && entry.name && isValidMatch(binName, entry))
|
||||
return entry.name;
|
||||
}
|
||||
}
|
||||
|
||||
var computedAppName = props["application.name"] || "";
|
||||
var mediaName = props["media.name"] || "";
|
||||
var appId = props["application.id"] || "";
|
||||
|
||||
if (appId) {
|
||||
var entry = ThemeIcons.findAppEntry(appId);
|
||||
if (entry && entry.name && isValidMatch(appId, entry))
|
||||
return entry.name;
|
||||
if (!computedAppName) {
|
||||
var parts = appId.split(".");
|
||||
if (parts.length > 0 && parts[0])
|
||||
computedAppName = parts[0].charAt(0).toUpperCase() + parts[0].slice(1);
|
||||
}
|
||||
}
|
||||
|
||||
if (!computedAppName && binaryName) {
|
||||
var binParts = binaryName.split("/");
|
||||
if (binParts.length > 0 && binParts[binParts.length - 1])
|
||||
computedAppName = binParts[binParts.length - 1].charAt(0).toUpperCase() + binParts[binParts.length - 1].slice(1);
|
||||
}
|
||||
|
||||
var result = computedAppName || mediaTitle || mediaName || binaryName || desc || name;
|
||||
|
||||
if (!result || result === "" || result === "Unknown App") {
|
||||
if (name) {
|
||||
var nameParts = name.split(/[-_]/);
|
||||
if (nameParts.length > 0 && nameParts[0])
|
||||
result = nameParts[0].charAt(0).toUpperCase() + nameParts[0].slice(1);
|
||||
}
|
||||
}
|
||||
|
||||
return result || "Unknown App";
|
||||
}
|
||||
|
||||
PwObjectTracker {
|
||||
objects: appBox.modelData ? [appBox.modelData] : []
|
||||
}
|
||||
|
||||
PwNodePeakMonitor {
|
||||
id: peak
|
||||
node: appBox.modelData
|
||||
}
|
||||
|
||||
readonly property bool isCaptureStream: {
|
||||
if (!modelData || !modelData.properties)
|
||||
return false;
|
||||
const props = modelData.properties;
|
||||
// Exclude capture streams - check for stream.capture.sink property
|
||||
if (props["stream.capture.sink"] !== undefined) {
|
||||
return true;
|
||||
}
|
||||
const mediaClass = props["media.class"] || "";
|
||||
// Exclude Stream/Input (capture) but allow Stream/Output (playback)
|
||||
if (mediaClass.includes("Capture") || mediaClass === "Stream/Input" || mediaClass === "Stream/Input/Audio") {
|
||||
return true;
|
||||
}
|
||||
const mediaRole = props["media.role"] || "";
|
||||
if (mediaRole === "Capture") {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
required property var modelData
|
||||
required property int index
|
||||
|
||||
RowLayout {
|
||||
id: layoutVolume
|
||||
anchors.fill: parent
|
||||
anchors.margins: Appearance.spacing.smaller
|
||||
spacing: 15
|
||||
|
||||
IconImage {
|
||||
id: icon
|
||||
property string iconPath1: Quickshell.iconPath(DesktopEntries.byId(appBox.modelData.name).icon);
|
||||
property string iconPath2: Quickshell.iconPath(DesktopEntries.byId(appBox.appName).icon);
|
||||
source: iconPath1 !== "" ? iconPath1 : iconPath2
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
implicitSize: 42
|
||||
|
||||
StateLayer {
|
||||
radius: 1000
|
||||
onClicked: {
|
||||
appBox.modelData.audio.muted = !appBox.modelData.audio.muted;
|
||||
CustomRect {
|
||||
Layout.preferredWidth: 40
|
||||
Layout.preferredHeight: 40
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
color: DynamicColors.palette.m3primary
|
||||
radius: 1000
|
||||
MaterialIcon {
|
||||
id: icon
|
||||
anchors.centerIn: parent
|
||||
text: "volume_up"
|
||||
font.pointSize: 22
|
||||
color: DynamicColors.palette.m3onPrimary
|
||||
|
||||
StateLayer {
|
||||
radius: 1000
|
||||
onClicked: {
|
||||
appBox.modelData.audio.muted = !appBox.modelData.audio.muted;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -391,7 +326,7 @@ Item {
|
||||
|
||||
TextMetrics {
|
||||
id: metrics
|
||||
text: appBox.appName
|
||||
text: Audio.getStreamName(appBox.modelData)
|
||||
elide: Text.ElideRight
|
||||
elideWidth: root.width - 50
|
||||
}
|
||||
@@ -419,15 +354,14 @@ Item {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft
|
||||
Layout.bottomMargin: 5
|
||||
implicitHeight: 10
|
||||
CustomAudioSlider {
|
||||
anchors.fill: parent
|
||||
CustomSlider {
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
implicitHeight: 10
|
||||
value: appBox.modelData.audio.volume
|
||||
peak: peak.peak
|
||||
onMoved: {
|
||||
Audio.setAppAudioVolume(appBox.modelData, value)
|
||||
console.log(icon.iconPath1, icon.iconPath2)
|
||||
Audio.setStreamVolume(appBox.modelData, value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+9
-16
@@ -2,6 +2,7 @@ import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import Quickshell.Io
|
||||
import Quickshell.Services.Pipewire
|
||||
import qs.Daemons
|
||||
import qs.Modules
|
||||
import qs.Config
|
||||
import qs.Components
|
||||
@@ -23,14 +24,6 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
PwObjectTracker {
|
||||
objects: [ Pipewire.defaultAudioSink ]
|
||||
}
|
||||
|
||||
PwObjectTracker {
|
||||
objects: [ Pipewire.defaultAudioSource ]
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.left: parent.left
|
||||
@@ -63,8 +56,8 @@ Item {
|
||||
MaterialIcon {
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
font.pixelSize: 18
|
||||
text: "volume_up"
|
||||
color: Pipewire.defaultAudioSink?.audio.muted ? DynamicColors.palette.m3error : root.textColor
|
||||
text: Audio.muted ? "volume_off" : "volume_up"
|
||||
color: Audio.muted ? DynamicColors.palette.m3error : root.textColor
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
@@ -82,9 +75,9 @@ Item {
|
||||
bottom: parent.bottom
|
||||
}
|
||||
|
||||
implicitWidth: parent.width * ( Pipewire.defaultAudioSink?.audio.volume ?? 0 )
|
||||
implicitWidth: parent.width * ( Audio.volume ?? 0 )
|
||||
radius: parent.radius
|
||||
color: Pipewire.defaultAudioSink?.audio.muted ? DynamicColors.palette.m3error : root.barColor
|
||||
color: Audio.muted ? DynamicColors.palette.m3error : root.barColor
|
||||
Behavior on color {
|
||||
CAnim {}
|
||||
}
|
||||
@@ -94,8 +87,8 @@ Item {
|
||||
MaterialIcon {
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
font.pixelSize: 18
|
||||
text: Pipewire.defaultAudioSource?.audio.muted ? "mic_off" : "mic"
|
||||
color: ( Pipewire.defaultAudioSource?.audio.muted ?? false ) ? DynamicColors.palette.m3error : root.textColor
|
||||
text: Audio.sourceMuted ? "mic_off" : "mic"
|
||||
color: ( Audio.sourceMuted ?? false ) ? DynamicColors.palette.m3error : root.textColor
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
@@ -113,9 +106,9 @@ Item {
|
||||
bottom: parent.bottom
|
||||
}
|
||||
|
||||
implicitWidth: parent.width * ( Pipewire.defaultAudioSource?.audio.volume ?? 0 )
|
||||
implicitWidth: parent.width * ( Audio.sourceVolume ?? 0 )
|
||||
radius: parent.radius
|
||||
color: ( Pipewire.defaultAudioSource?.audio.muted ?? false ) ? DynamicColors.palette.m3error : root.barColor
|
||||
color: ( Audio.sourceMuted ?? false ) ? DynamicColors.palette.m3error : root.barColor
|
||||
|
||||
Behavior on color {
|
||||
CAnim {}
|
||||
|
||||
+1
-9
@@ -7,6 +7,7 @@ import qs.Helpers as Helpers
|
||||
import qs.Components
|
||||
|
||||
Item {
|
||||
id: root
|
||||
required property PersistentProperties visibilities
|
||||
required property Wrapper popouts
|
||||
required property RowLayout loader
|
||||
@@ -38,15 +39,6 @@ Item {
|
||||
acceptedButtons: Qt.LeftButton
|
||||
onClicked: {
|
||||
root.visibilities.dashboard = !root.visibilities.dashboard;
|
||||
if ( root.visibilities.sidebar || root.popouts.hasCurrent ) {
|
||||
// Helpers.Calendar.displayYear = new Date().getFullYear();
|
||||
// Helpers.Calendar.displayMonth = new Date().getMonth();
|
||||
// root.popouts.currentName = "calendar";
|
||||
// root.popouts.currentCenter = Qt.binding( () => item.mapToItem( root.loader, root.implicitWidth / 2, 0 ).x );
|
||||
// root.popouts.hasCurrent = true;
|
||||
root.popouts.hasCurrent = false;
|
||||
root.visibilities.sidebar = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,144 +0,0 @@
|
||||
pragma Singleton
|
||||
|
||||
import qs.Config
|
||||
import ZShell.Services
|
||||
import ZShell
|
||||
import Quickshell
|
||||
import Quickshell.Services.Pipewire
|
||||
import QtQuick
|
||||
|
||||
Singleton {
|
||||
id: root
|
||||
|
||||
property string previousSinkName: ""
|
||||
property string previousSourceName: ""
|
||||
|
||||
readonly property var nodes: Pipewire.nodes.values.reduce((acc, node) => {
|
||||
if (!node.isStream) {
|
||||
if (node.isSink)
|
||||
acc.sinks.push(node);
|
||||
else if (node.audio)
|
||||
acc.sources.push(node);
|
||||
} else if (node.isStream && node.audio) {
|
||||
// Application streams (output streams)
|
||||
acc.streams.push(node);
|
||||
}
|
||||
return acc;
|
||||
}, {
|
||||
sources: [],
|
||||
sinks: [],
|
||||
streams: []
|
||||
})
|
||||
|
||||
readonly property list<PwNode> sinks: nodes.sinks
|
||||
readonly property list<PwNode> sources: nodes.sources
|
||||
readonly property list<PwNode> streams: nodes.streams
|
||||
|
||||
readonly property PwNode sink: Pipewire.defaultAudioSink
|
||||
readonly property PwNode source: Pipewire.defaultAudioSource
|
||||
|
||||
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
|
||||
|
||||
readonly property alias beatTracker: beatTracker
|
||||
|
||||
function setVolume(newVolume: real): void {
|
||||
if (sink?.ready && sink?.audio) {
|
||||
sink.audio.muted = false;
|
||||
sink.audio.volume = Math.max(0, Math.min(Config.services.maxVolume, newVolume));
|
||||
}
|
||||
}
|
||||
|
||||
function incrementVolume(amount: real): void {
|
||||
setVolume(volume + (amount || Config.services.audioIncrement));
|
||||
}
|
||||
|
||||
function decrementVolume(amount: real): void {
|
||||
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(Config.services.maxVolume, 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;
|
||||
}
|
||||
|
||||
function setAudioSource(newSource: PwNode): void {
|
||||
Pipewire.preferredDefaultAudioSource = newSource;
|
||||
}
|
||||
|
||||
function setStreamVolume(stream: PwNode, newVolume: real): void {
|
||||
if (stream?.ready && stream?.audio) {
|
||||
stream.audio.muted = false;
|
||||
stream.audio.volume = Math.max(0, Math.min(Config.services.maxVolume, newVolume));
|
||||
}
|
||||
}
|
||||
|
||||
function setStreamMuted(stream: PwNode, muted: bool): void {
|
||||
if (stream?.ready && stream?.audio) {
|
||||
stream.audio.muted = muted;
|
||||
}
|
||||
}
|
||||
|
||||
function getStreamVolume(stream: PwNode): real {
|
||||
return stream?.audio?.volume ?? 0;
|
||||
}
|
||||
|
||||
function getStreamMuted(stream: PwNode): bool {
|
||||
return !!stream?.audio?.muted;
|
||||
}
|
||||
|
||||
function getStreamName(stream: PwNode): string {
|
||||
if (!stream)
|
||||
return qsTr("Unknown");
|
||||
// Try application name first, then description, then name
|
||||
return stream.applicationName || stream.description || stream.name || qsTr("Unknown Application");
|
||||
}
|
||||
|
||||
onSinkChanged: {
|
||||
if (!sink?.ready)
|
||||
return;
|
||||
|
||||
const newSinkName = sink.description || sink.name || qsTr("Unknown Device");
|
||||
|
||||
previousSinkName = newSinkName;
|
||||
}
|
||||
|
||||
onSourceChanged: {
|
||||
if (!source?.ready)
|
||||
return;
|
||||
|
||||
const newSourceName = source.description || source.name || qsTr("Unknown Device");
|
||||
|
||||
previousSourceName = newSourceName;
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
previousSinkName = sink?.description || sink?.name || qsTr("Unknown Device");
|
||||
previousSourceName = source?.description || source?.name || qsTr("Unknown Device");
|
||||
}
|
||||
|
||||
PwObjectTracker {
|
||||
objects: [...root.sinks, ...root.sources, ...root.streams]
|
||||
}
|
||||
|
||||
BeatTracker {
|
||||
id: beatTracker
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
import ZShell.Services
|
||||
import QtQuick
|
||||
import QtQuick.Shapes
|
||||
import qs.Daemons
|
||||
import qs.Components
|
||||
import qs.Config
|
||||
import qs.Helpers
|
||||
|
||||
@@ -42,10 +42,6 @@ Item {
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
root.visibilities.sidebar = !root.visibilities.sidebar;
|
||||
if ( root.visibilities.dashboard || root.popouts.hasCurrent ) {
|
||||
root.popouts.hasCurrent = false;
|
||||
root.visibilities.dashboard = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+11
-2
@@ -5,7 +5,7 @@ import QtQuick.Layouts
|
||||
import qs.Components
|
||||
import qs.Helpers
|
||||
import qs.Config
|
||||
import qs.Modules.Dashboard.Dash
|
||||
import qs.Daemons
|
||||
import qs.Modules as Modules
|
||||
|
||||
Item {
|
||||
@@ -47,6 +47,7 @@ Item {
|
||||
icon: Icons.getVolumeIcon(value, root.muted)
|
||||
value: root.volume
|
||||
to: Config.services.maxVolume
|
||||
color: Audio.muted ? DynamicColors.palette.m3error : DynamicColors.palette.m3secondary
|
||||
onMoved: Audio.setVolume(value)
|
||||
}
|
||||
}
|
||||
@@ -101,7 +102,15 @@ Item {
|
||||
|
||||
icon: `brightness_${(Math.round(value * 6) + 1)}`
|
||||
value: root.brightness
|
||||
onMoved: root.monitor?.setBrightness(value)
|
||||
onMoved: {
|
||||
if ( Config.osd.allMonBrightness ) {
|
||||
root.monitor?.setBrightness(value)
|
||||
} else {
|
||||
for (const mon of Brightness.monitors) {
|
||||
mon.setBrightness(value)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import qs.Components
|
||||
import qs.Helpers
|
||||
import qs.Config
|
||||
import qs.Modules as Modules
|
||||
import qs.Modules.Dashboard.Dash
|
||||
import qs.Daemons
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
Reference in New Issue
Block a user