Add Audio Settings, split NotificationHistory

This commit is contained in:
Ly-sec 2025-08-13 14:00:06 +02:00
parent 0babca7b56
commit faa6bcd222
10 changed files with 555 additions and 209 deletions

View file

@ -0,0 +1,308 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Quickshell.Services.Pipewire
import qs.Modules.Settings
import qs.Widgets
import qs.Services
ColumnLayout {
id: root
spacing: 0
ScrollView {
id: scrollView
Layout.fillWidth: true
Layout.fillHeight: true
padding: Style.marginMedium * scaling
clip: true
ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
ScrollBar.vertical.policy: ScrollBar.AsNeeded
ColumnLayout {
width: scrollView.availableWidth
spacing: 0
Item {
Layout.fillWidth: true
Layout.preferredHeight: 0
}
ColumnLayout {
spacing: Style.marginTiny * scaling
Layout.fillWidth: true
NText {
text: "Audio"
font.pointSize: Style.fontSizeXL * scaling
font.weight: Style.fontWeightBold
color: Colors.textPrimary
Layout.bottomMargin: Style.marginSmall * scaling
}
// Volume Controls
ColumnLayout {
spacing: Style.marginSmall * scaling
Layout.fillWidth: true
Layout.topMargin: Style.marginSmall * scaling
// Master Volume
ColumnLayout {
spacing: Style.marginSmall * scaling
Layout.fillWidth: true
NText {
text: "Master Volume"
font.weight: Style.fontWeightBold
color: Colors.textPrimary
}
NText {
text: "System-wide volume level"
font.pointSize: Style.fontSizeSmall * scaling
color: Colors.textSecondary
wrapMode: Text.WordWrap
Layout.fillWidth: true
}
RowLayout {
NSlider {
id: masterVolumeSlider
Layout.fillWidth: true
from: 0
to: allowOverdrive.value ? 200 : 100
value: (Audio.volume || 0) * 100
stepSize: 5
onValueChanged: {
Audio.volumeSet(value / 100)
}
}
NText {
text: Math.round(masterVolumeSlider.value) + "%"
Layout.alignment: Qt.AlignVCenter
color: Colors.textSecondary
}
}
NToggle {
id: allowOverdrive
label: "Allow Volume Overdrive"
description: "Enable volume levels above 100% (up to 200%)"
value: Settings.data.audio ? Settings.data.audio.volumeOverdrive : false
onToggled: function (checked) {
Settings.data.audio.volumeOverdrive = checked
// If overdrive is disabled and current volume is above 100%, cap it
if (!checked && Audio.volume > 1.0) {
Audio.volumeSet(1.0)
}
}
}
}
// Mute Toggle
ColumnLayout {
spacing: Style.marginSmall * scaling
Layout.fillWidth: true
Layout.topMargin: Style.marginMedium * scaling
NToggle {
label: "Mute Audio"
description: "Mute or unmute the default audio output"
value: Audio.muted
onToggled: function (newValue) {
if (Audio.sink && Audio.sink.audio) {
Audio.sink.audio.muted = newValue
}
}
}
}
}
NDivider {
Layout.fillWidth: true
Layout.topMargin: Style.marginLarge * 2 * scaling
Layout.bottomMargin: Style.marginLarge * scaling
}
// Audio Devices
ColumnLayout {
spacing: Style.marginLarge * scaling
Layout.fillWidth: true
NText {
text: "Audio Devices"
font.pointSize: Style.fontSizeXL * scaling
font.weight: Style.fontWeightBold
color: Colors.textPrimary
Layout.bottomMargin: Style.marginSmall * scaling
}
// Output Device
NComboBox {
id: outputDeviceCombo
label: "Output Device"
description: "Default audio output device"
optionsKeys: outputDeviceKeys
optionsLabels: outputDeviceLabels
currentKey: Audio.sink ? Audio.sink.id.toString() : ""
onSelected: function (key) {
// Find the node by ID and set it as preferred
for (let i = 0; i < Pipewire.nodes.count; i++) {
let node = Pipewire.nodes.get(i)
if (node.id.toString() === key && node.isSink) {
Pipewire.preferredDefaultAudioSink = node
break
}
}
}
}
// Input Device
NComboBox {
id: inputDeviceCombo
label: "Input Device"
description: "Default audio input device"
optionsKeys: inputDeviceKeys
optionsLabels: inputDeviceLabels
currentKey: Audio.source ? Audio.source.id.toString() : ""
onSelected: function (key) {
// Find the node by ID and set it as preferred
for (let i = 0; i < Pipewire.nodes.count; i++) {
let node = Pipewire.nodes.get(i)
if (node.id.toString() === key && !node.isSink) {
Pipewire.preferredDefaultAudioSource = node
break
}
}
}
}
}
// Divider
NDivider {
Layout.fillWidth: true
Layout.topMargin: Style.marginLarge * scaling
Layout.bottomMargin: Style.marginMedium * scaling
}
// Audio Visualizer Category
ColumnLayout {
spacing: Style.marginSmall * scaling
Layout.fillWidth: true
NText {
text: "Audio Visualizer"
font.pointSize: Style.fontSizeXL * scaling
font.weight: Style.fontWeightBold
color: Colors.textPrimary
Layout.bottomMargin: Style.marginSmall * scaling
}
// Audio Visualizer section
NComboBox {
id: audioVisualizerCombo
label: "Visualization Type"
description: "Choose a visualization type for media playback"
optionsKeys: ["radial", "bars", "wave"]
optionsLabels: ["Radial", "Bars", "Wave"]
currentKey: Settings.data.audio ? Settings.data.audio.audioVisualizer.type : "radial"
onSelected: function (key) {
if (!Settings.data.audio) {
Settings.data.audio = {}
}
if (!Settings.data.audio.audioVisualizer) {
Settings.data.audio.audioVisualizer = {}
}
Settings.data.audio.audioVisualizer.type = key
}
}
}
}
}
}
// Device list properties
property var outputDeviceKeys: ["default"]
property var outputDeviceLabels: ["Default Output"]
property var inputDeviceKeys: ["default"]
property var inputDeviceLabels: ["Default Input"]
// Bind Pipewire nodes
PwObjectTracker {
id: nodeTracker
objects: [Pipewire.nodes]
}
// Update device lists when component is completed
Component.onCompleted: {
updateDeviceLists()
}
// Timer to check if pipewire is ready and update device lists
Timer {
id: deviceUpdateTimer
interval: 100
repeat: true
running: !(Pipewire && Pipewire.ready)
onTriggered: {
if (Pipewire && Pipewire.ready) {
updateDeviceLists()
running = false
}
}
}
// Update device lists when nodes change
Connections {
target: nodeTracker
function onObjectsChanged() {
updateDeviceLists()
}
}
Repeater {
id: nodesRepeater
model: Pipewire.nodes
delegate: Item {
Component.onCompleted: {
if (modelData && modelData.isSink && modelData.audio) {
// Add to output devices
let key = modelData.id.toString()
if (!outputDeviceKeys.includes(key)) {
outputDeviceKeys.push(key)
outputDeviceLabels.push(modelData.description || modelData.name || "Unknown Device")
}
} else if (modelData && !modelData.isSink && modelData.audio) {
// Add to input devices
let key = modelData.id.toString()
if (!inputDeviceKeys.includes(key)) {
inputDeviceKeys.push(key)
inputDeviceLabels.push(modelData.description || modelData.name || "Unknown Device")
}
}
}
}
}
function updateDeviceLists() {
if (Pipewire && Pipewire.ready) {
// Update comboboxes
if (outputDeviceCombo) {
outputDeviceCombo.optionsKeys = outputDeviceKeys
outputDeviceCombo.optionsLabels = outputDeviceLabels
}
if (inputDeviceCombo) {
inputDeviceCombo.optionsKeys = inputDeviceKeys
inputDeviceCombo.optionsLabels = inputDeviceLabels
}
}
}
}

View file

@ -40,17 +40,7 @@ ColumnLayout {
Layout.bottomMargin: Style.marginSmall * scaling
}
// Audio Visualizer section
NComboBox {
label: "Audio Visualizer"
description: "Choose a visualization type"
optionsKeys: ["radial", "bars", "wave"]
optionsLabels: ["Radial", "Bars", "Wave"]
currentKey: Settings.data.audioVisualizer.type
onSelected: function (key) {
Settings.data.audioVisualizer.type = key
}
}
}
}
}