quickshell: complete native PulseAudio source support
This commit is contained in:
parent
63a39ab5d0
commit
dfd494c5f1
4 changed files with 251 additions and 43 deletions
|
|
@ -45,6 +45,7 @@ modules/ii/sidebarLeft/SidebarLeft.qml souveraine/modules/ii/sidebarLeft/Side
|
||||||
modules/ii/sidebarLeft/AiChat.qml souveraine/modules/ii/sidebarLeft/AiChat.qml
|
modules/ii/sidebarLeft/AiChat.qml souveraine/modules/ii/sidebarLeft/AiChat.qml
|
||||||
modules/ii/sidebarRight/SidebarRight.qml souveraine/modules/ii/sidebarRight/SidebarRight.qml
|
modules/ii/sidebarRight/SidebarRight.qml souveraine/modules/ii/sidebarRight/SidebarRight.qml
|
||||||
modules/ii/sidebarRight/volumeMixer/VolumeDialogContent.qml souveraine/modules/ii/sidebarRight/volumeMixer/VolumeDialogContent.qml
|
modules/ii/sidebarRight/volumeMixer/VolumeDialogContent.qml souveraine/modules/ii/sidebarRight/volumeMixer/VolumeDialogContent.qml
|
||||||
|
modules/ii/bar/UtilButtons.qml souveraine/modules/ii/bar/UtilButtons.qml
|
||||||
modules/common/Config.qml souveraine/modules/common/Config.qml
|
modules/common/Config.qml souveraine/modules/common/Config.qml
|
||||||
modules/common/ShellModel.qml souveraine/modules/common/ShellModel.qml
|
modules/common/ShellModel.qml souveraine/modules/common/ShellModel.qml
|
||||||
modules/common/functions/Session.qml souveraine/modules/common/functions/Session.qml
|
modules/common/functions/Session.qml souveraine/modules/common/functions/Session.qml
|
||||||
|
|
|
||||||
158
surfaces/quickshell/modules/ii/bar/UtilButtons.qml
Normal file
158
surfaces/quickshell/modules/ii/bar/UtilButtons.qml
Normal file
|
|
@ -0,0 +1,158 @@
|
||||||
|
import qs
|
||||||
|
import qs.services
|
||||||
|
import qs.modules.common
|
||||||
|
import qs.modules.common.widgets
|
||||||
|
import QtQuick
|
||||||
|
import QtQuick.Layouts
|
||||||
|
import Quickshell
|
||||||
|
import Quickshell.Hyprland
|
||||||
|
import Quickshell.Services.UPower
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: root
|
||||||
|
property bool borderless: Config.options.bar.borderless
|
||||||
|
implicitWidth: rowLayout.implicitWidth + rowLayout.spacing * 2
|
||||||
|
implicitHeight: rowLayout.implicitHeight
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
id: rowLayout
|
||||||
|
|
||||||
|
spacing: 4
|
||||||
|
anchors.centerIn: parent
|
||||||
|
|
||||||
|
Loader {
|
||||||
|
active: Config.options.bar.utilButtons.showScreenSnip
|
||||||
|
visible: Config.options.bar.utilButtons.showScreenSnip
|
||||||
|
sourceComponent: CircleUtilButton {
|
||||||
|
Layout.alignment: Qt.AlignVCenter
|
||||||
|
onClicked: Quickshell.execDetached(["qs", "-p", Quickshell.shellPath(""), "ipc", "call", "region", "screenshot"])
|
||||||
|
MaterialSymbol {
|
||||||
|
horizontalAlignment: Qt.AlignHCenter
|
||||||
|
fill: 1
|
||||||
|
text: "screenshot_region"
|
||||||
|
iconSize: Appearance.font.pixelSize.large
|
||||||
|
color: Appearance.colors.colOnLayer2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Loader {
|
||||||
|
active: Config.options.bar.utilButtons.showScreenRecord
|
||||||
|
visible: Config.options.bar.utilButtons.showScreenRecord
|
||||||
|
sourceComponent: CircleUtilButton {
|
||||||
|
Layout.alignment: Qt.AlignVCenter
|
||||||
|
onClicked: Quickshell.execDetached([Directories.recordScriptPath])
|
||||||
|
MaterialSymbol {
|
||||||
|
horizontalAlignment: Qt.AlignHCenter
|
||||||
|
fill: 1
|
||||||
|
text: "videocam"
|
||||||
|
iconSize: Appearance.font.pixelSize.large
|
||||||
|
color: Appearance.colors.colOnLayer2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Loader {
|
||||||
|
active: Config.options.bar.utilButtons.showColorPicker
|
||||||
|
visible: Config.options.bar.utilButtons.showColorPicker
|
||||||
|
sourceComponent: CircleUtilButton {
|
||||||
|
Layout.alignment: Qt.AlignVCenter
|
||||||
|
onClicked: Quickshell.execDetached(["hyprpicker", "-a"])
|
||||||
|
MaterialSymbol {
|
||||||
|
horizontalAlignment: Qt.AlignHCenter
|
||||||
|
fill: 1
|
||||||
|
text: "colorize"
|
||||||
|
iconSize: Appearance.font.pixelSize.large
|
||||||
|
color: Appearance.colors.colOnLayer2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Loader {
|
||||||
|
active: Config.options.bar.utilButtons.showKeyboardToggle
|
||||||
|
visible: Config.options.bar.utilButtons.showKeyboardToggle
|
||||||
|
sourceComponent: CircleUtilButton {
|
||||||
|
Layout.alignment: Qt.AlignVCenter
|
||||||
|
onClicked: GlobalStates.oskOpen = !GlobalStates.oskOpen
|
||||||
|
MaterialSymbol {
|
||||||
|
horizontalAlignment: Qt.AlignHCenter
|
||||||
|
fill: 0
|
||||||
|
text: "keyboard"
|
||||||
|
iconSize: Appearance.font.pixelSize.large
|
||||||
|
color: Appearance.colors.colOnLayer2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Loader {
|
||||||
|
active: Config.options.bar.utilButtons.showMicToggle
|
||||||
|
visible: Config.options.bar.utilButtons.showMicToggle
|
||||||
|
sourceComponent: CircleUtilButton {
|
||||||
|
Layout.alignment: Qt.AlignVCenter
|
||||||
|
onClicked: Audio.toggleMicMute()
|
||||||
|
MaterialSymbol {
|
||||||
|
horizontalAlignment: Qt.AlignHCenter
|
||||||
|
fill: 0
|
||||||
|
text: Audio.source?.audio?.muted ? "mic_off" : "mic"
|
||||||
|
iconSize: Appearance.font.pixelSize.large
|
||||||
|
color: Appearance.colors.colOnLayer2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Loader {
|
||||||
|
active: Config.options.bar.utilButtons.showDarkModeToggle
|
||||||
|
visible: Config.options.bar.utilButtons.showDarkModeToggle
|
||||||
|
sourceComponent: CircleUtilButton {
|
||||||
|
Layout.alignment: Qt.AlignVCenter
|
||||||
|
onClicked: event => {
|
||||||
|
if (Appearance.m3colors.darkmode) {
|
||||||
|
Quickshell.execDetached(["bash", "-c", `${Directories.wallpaperSwitchScriptPath} --mode light --noswitch`])
|
||||||
|
} else {
|
||||||
|
Quickshell.execDetached(["bash", "-c", `${Directories.wallpaperSwitchScriptPath} --mode dark --noswitch`])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
MaterialSymbol {
|
||||||
|
horizontalAlignment: Qt.AlignHCenter
|
||||||
|
fill: 0
|
||||||
|
text: Appearance.m3colors.darkmode ? "light_mode" : "dark_mode"
|
||||||
|
iconSize: Appearance.font.pixelSize.large
|
||||||
|
color: Appearance.colors.colOnLayer2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Loader {
|
||||||
|
active: Config.options.bar.utilButtons.showPerformanceProfileToggle
|
||||||
|
visible: Config.options.bar.utilButtons.showPerformanceProfileToggle
|
||||||
|
sourceComponent: CircleUtilButton {
|
||||||
|
Layout.alignment: Qt.AlignVCenter
|
||||||
|
onClicked: event => {
|
||||||
|
if (PowerProfiles.hasPerformanceProfile) {
|
||||||
|
switch(PowerProfiles.profile) {
|
||||||
|
case PowerProfile.PowerSaver: PowerProfiles.profile = PowerProfile.Balanced
|
||||||
|
break
|
||||||
|
case PowerProfile.Balanced: PowerProfiles.profile = PowerProfile.Performance
|
||||||
|
break
|
||||||
|
case PowerProfile.Performance: PowerProfiles.profile = PowerProfile.PowerSaver
|
||||||
|
break
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
PowerProfiles.profile = PowerProfiles.profile == PowerProfile.Balanced ? PowerProfile.PowerSaver : PowerProfile.Balanced
|
||||||
|
}
|
||||||
|
}
|
||||||
|
MaterialSymbol {
|
||||||
|
horizontalAlignment: Qt.AlignHCenter
|
||||||
|
fill: 0
|
||||||
|
text: switch(PowerProfiles.profile) {
|
||||||
|
case PowerProfile.PowerSaver: return "energy_savings_leaf"
|
||||||
|
case PowerProfile.Balanced: return "airwave"
|
||||||
|
case PowerProfile.Performance: return "local_fire_department"
|
||||||
|
}
|
||||||
|
iconSize: Appearance.font.pixelSize.large
|
||||||
|
color: Appearance.colors.colOnLayer2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -4,16 +4,15 @@ import qs.modules.common.widgets
|
||||||
import QtQuick
|
import QtQuick
|
||||||
import QtQuick.Layouts
|
import QtQuick.Layouts
|
||||||
|
|
||||||
// Native PulseAudio has one verified board endpoint today: the UCM Speaker
|
// Native PulseAudio publishes the Pixel 3 board endpoints directly: playback
|
||||||
// route published as alsa_output.hw_0_0. ii's original dialog only renders
|
// on hw:0,0 and UCM handset capture on hw:0,1. Present both without depending
|
||||||
// PipeWire nodes, so it presented an empty application list as though no
|
// on PipeWire's node model.
|
||||||
// device existed. Keep the device state explicit until capture and alternate
|
|
||||||
// routes are real PulseAudio endpoints.
|
|
||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
id: root
|
id: root
|
||||||
required property bool isSink
|
required property bool isSink
|
||||||
readonly property bool outputAvailable: Audio.ready && Audio.sink?.name.length > 0
|
readonly property bool outputAvailable: Audio.ready && Audio.sink?.name.length > 0
|
||||||
readonly property bool inputAvailable: Audio.source?.audio !== undefined && Audio.source?.audio !== null
|
readonly property bool inputAvailable: Audio.sourceReady && Audio.source?.name.length > 0
|
||||||
|
readonly property var currentNode: root.isSink ? Audio.sink : Audio.source
|
||||||
spacing: 16
|
spacing: 16
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
|
|
@ -45,7 +44,7 @@ ColumnLayout {
|
||||||
color: Appearance.colors.colOnLayer2
|
color: Appearance.colors.colOnLayer2
|
||||||
text: root.isSink
|
text: root.isSink
|
||||||
? (root.outputAvailable ? Audio.friendlyDeviceName(Audio.sink) : Translation.tr("Connecting to PulseAudio…"))
|
? (root.outputAvailable ? Audio.friendlyDeviceName(Audio.sink) : Translation.tr("Connecting to PulseAudio…"))
|
||||||
: (root.inputAvailable ? Audio.friendlyDeviceName(Audio.source) : Translation.tr("No microphone source yet"))
|
: (root.inputAvailable ? Audio.friendlyDeviceName(Audio.source) : Translation.tr("Connecting to PulseAudio…"))
|
||||||
}
|
}
|
||||||
|
|
||||||
StyledText {
|
StyledText {
|
||||||
|
|
@ -55,7 +54,9 @@ ColumnLayout {
|
||||||
color: Appearance.m3colors.m3outline
|
color: Appearance.m3colors.m3outline
|
||||||
text: root.isSink
|
text: root.isSink
|
||||||
? Translation.tr("Default output • Pixel 3 internal stereo speakers")
|
? Translation.tr("Default output • Pixel 3 internal stereo speakers")
|
||||||
: Translation.tr("The handset DMIC route is still under capture validation")
|
: (Audio.micActive
|
||||||
|
? Translation.tr("Default input • recording in progress")
|
||||||
|
: Translation.tr("Default input • Pixel 3 handset microphone"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -63,20 +64,20 @@ ColumnLayout {
|
||||||
|
|
||||||
StyledSlider {
|
StyledSlider {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
visible: root.isSink && root.outputAvailable
|
visible: root.isSink ? root.outputAvailable : root.inputAvailable
|
||||||
value: Audio.sink?.audio?.volume ?? 0
|
value: root.currentNode?.audio?.volume ?? 0
|
||||||
onMoved: Audio.sink.audio.volume = value
|
onMoved: root.currentNode.audio.volume = value
|
||||||
configuration: StyledSlider.Configuration.M
|
configuration: StyledSlider.Configuration.M
|
||||||
}
|
}
|
||||||
|
|
||||||
StyledText {
|
StyledText {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
visible: root.isSink && root.outputAvailable
|
visible: root.isSink ? root.outputAvailable : root.inputAvailable
|
||||||
horizontalAlignment: Text.AlignHCenter
|
horizontalAlignment: Text.AlignHCenter
|
||||||
color: Appearance.colors.colSubtext
|
color: Appearance.colors.colSubtext
|
||||||
text: Audio.sink?.audio?.muted
|
text: root.currentNode?.audio?.muted
|
||||||
? Translation.tr("Muted")
|
? Translation.tr("Muted")
|
||||||
: `${Math.round((Audio.sink?.audio?.volume ?? 0) * 100)}%`
|
: `${Math.round((root.currentNode?.audio?.volume ?? 0) * 100)}%`
|
||||||
}
|
}
|
||||||
|
|
||||||
Item { Layout.fillHeight: true }
|
Item { Layout.fillHeight: true }
|
||||||
|
|
|
||||||
|
|
@ -18,8 +18,10 @@ Singleton {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
property bool ready: false
|
property bool ready: false
|
||||||
|
property bool sourceReady: false
|
||||||
property bool syncing: false
|
property bool syncing: false
|
||||||
property bool autoMuted: false
|
property bool autoMuted: false
|
||||||
|
property bool micActive: false
|
||||||
readonly property real hardMaxValue: 1.00
|
readonly property real hardMaxValue: 1.00
|
||||||
property string audioTheme: Config.options.sounds.theme
|
property string audioTheme: Config.options.sounds.theme
|
||||||
readonly property real value: sink.audio.volume
|
readonly property real value: sink.audio.volume
|
||||||
|
|
@ -47,28 +49,34 @@ Singleton {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// A capture source is intentionally absent until the ADSP capture route
|
// Mirror PulseAudio's default source into the same node-shaped API used
|
||||||
// produces real samples. Stock ii consumers (e.g. QuickSliders.qml) do
|
// for output. This is the handset UCM capture endpoint (hw:0,1), not a
|
||||||
// `Audio.source.audio.volume` with no null-guard — safe against ii's own
|
// placeholder: QS must be able to control and report the real microphone
|
||||||
// PipeWire Audio, which always populates source as an object. Give this
|
// even while the lower-level zero-sample fault is being diagnosed.
|
||||||
// a real stub (mirroring sink's shape) instead of null so those
|
|
||||||
// consumers work unmodified; mic volume just reads 0 and setting it is
|
|
||||||
// a no-op until real capture exists.
|
|
||||||
property QtObject source: QtObject {
|
property QtObject source: QtObject {
|
||||||
id: sourceNode
|
id: sourceNode
|
||||||
property string id: ""
|
property string id: name
|
||||||
property string name: ""
|
property string name: ""
|
||||||
property string description: Translation.tr("Microphone")
|
property string description: Translation.tr("Internal microphone")
|
||||||
property string nickname: description
|
property string nickname: description
|
||||||
property bool isSink: false
|
property bool isSink: false
|
||||||
property var properties: ({})
|
property var properties: ({ "node.name": name })
|
||||||
property QtObject audio: QtObject {
|
property QtObject audio: QtObject {
|
||||||
property real volume: 0
|
property real volume: 0
|
||||||
property bool muted: false
|
property bool muted: false
|
||||||
|
|
||||||
|
onVolumeChanged: {
|
||||||
|
if (root.sourceReady && !root.syncing)
|
||||||
|
root.setSourceVolume(volume)
|
||||||
|
}
|
||||||
|
onMutedChanged: {
|
||||||
|
if (root.sourceReady && !root.syncing)
|
||||||
|
root.setSourceMuted(muted)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
readonly property list<var> outputDevices: sink.name.length > 0 ? [sink] : []
|
readonly property list<var> outputDevices: sink.name.length > 0 ? [sink] : []
|
||||||
readonly property list<var> inputDevices: []
|
readonly property list<var> inputDevices: source.name.length > 0 ? [source] : []
|
||||||
readonly property list<var> outputAppNodes: []
|
readonly property list<var> outputAppNodes: []
|
||||||
readonly property list<var> inputAppNodes: []
|
readonly property list<var> inputAppNodes: []
|
||||||
|
|
||||||
|
|
@ -95,8 +103,9 @@ Singleton {
|
||||||
}
|
}
|
||||||
|
|
||||||
function toggleMicMute() {
|
function toggleMicMute() {
|
||||||
if (source?.audio)
|
if (!sourceReady)
|
||||||
source.audio.muted = !source.audio.muted
|
return
|
||||||
|
setSourceMuted(!source.audio.muted)
|
||||||
}
|
}
|
||||||
|
|
||||||
function incrementVolume() {
|
function incrementVolume() {
|
||||||
|
|
@ -122,6 +131,21 @@ Singleton {
|
||||||
refreshSoon.restart()
|
refreshSoon.restart()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function setSourceVolume(nextVolume) {
|
||||||
|
if (!sourceReady || !isFinite(nextVolume))
|
||||||
|
return
|
||||||
|
const bounded = Math.max(0, Math.min(hardMaxValue, Number(nextVolume)))
|
||||||
|
sourceVolumeProcess.exec(["pactl", "set-source-volume", "@DEFAULT_SOURCE@", `${Math.round(bounded * 100)}%`])
|
||||||
|
refreshSoon.restart()
|
||||||
|
}
|
||||||
|
|
||||||
|
function setSourceMuted(muted) {
|
||||||
|
if (!sourceReady)
|
||||||
|
return
|
||||||
|
sourceMuteProcess.exec(["pactl", "set-source-mute", "@DEFAULT_SOURCE@", muted ? "1" : "0"])
|
||||||
|
refreshSoon.restart()
|
||||||
|
}
|
||||||
|
|
||||||
function setDefaultSink(node) {
|
function setDefaultSink(node) {
|
||||||
if (!node?.name)
|
if (!node?.name)
|
||||||
return
|
return
|
||||||
|
|
@ -136,41 +160,65 @@ Singleton {
|
||||||
refreshSoon.restart()
|
refreshSoon.restart()
|
||||||
}
|
}
|
||||||
|
|
||||||
// All three status reads must be from the same PulseAudio default sink.
|
// Read the default sink and source in one transaction so the UI never
|
||||||
|
// mixes state from different PulseAudio generations.
|
||||||
// The tagged output avoids relying on locale-sensitive pactl labels.
|
// The tagged output avoids relying on locale-sensitive pactl labels.
|
||||||
Process {
|
Process {
|
||||||
id: statusProcess
|
id: statusProcess
|
||||||
command: ["sh", "-c", "printf 'name='; pactl get-default-sink; printf 'volume='; pactl get-sink-volume @DEFAULT_SINK@; printf 'mute='; pactl get-sink-mute @DEFAULT_SINK@"]
|
command: ["sh", "-c", "printf 'sink_name='; pactl get-default-sink; printf 'sink_volume='; pactl get-sink-volume @DEFAULT_SINK@; printf 'sink_mute='; pactl get-sink-mute @DEFAULT_SINK@; printf 'source_name='; pactl get-default-source; printf 'source_volume='; pactl get-source-volume @DEFAULT_SOURCE@; printf 'source_mute='; pactl get-source-mute @DEFAULT_SOURCE@; printf 'source_outputs='; pactl list short source-outputs | wc -l"]
|
||||||
environment: ({ LANG: "C", LC_ALL: "C" })
|
environment: ({ LANG: "C", LC_ALL: "C" })
|
||||||
stdout: StdioCollector {
|
stdout: StdioCollector {
|
||||||
onStreamFinished: {
|
onStreamFinished: {
|
||||||
const text = this.text
|
const text = this.text
|
||||||
const name = /^name=(.+)$/m.exec(text)?.[1]?.trim()
|
const sinkName = /^sink_name=(.+)$/m.exec(text)?.[1]?.trim()
|
||||||
const volume = /^volume=.*?(\d+)%/m.exec(text)?.[1]
|
const sinkVolume = /^sink_volume=.*?(\d+)%/m.exec(text)?.[1]
|
||||||
const muted = /^mute=Mute:\s*(yes|no)$/m.exec(text)?.[1]
|
const sinkMuted = /^sink_mute=Mute:\s*(yes|no)$/m.exec(text)?.[1]
|
||||||
if (!name || volume === undefined || muted === undefined) {
|
const sourceName = /^source_name=(.+)$/m.exec(text)?.[1]?.trim()
|
||||||
|
const sourceVolume = /^source_volume=.*?(\d+)%/m.exec(text)?.[1]
|
||||||
|
const sourceMuted = /^source_mute=Mute:\s*(yes|no)$/m.exec(text)?.[1]
|
||||||
|
const sourceOutputs = /^source_outputs=(\d+)$/m.exec(text)?.[1]
|
||||||
|
if (!sinkName || sinkVolume === undefined || sinkMuted === undefined) {
|
||||||
root.ready = false
|
root.ready = false
|
||||||
return
|
} else {
|
||||||
|
root.syncing = true
|
||||||
|
sinkNode.name = sinkName
|
||||||
|
sinkNode.description = sinkName.includes("hw_0_0")
|
||||||
|
? Translation.tr("Internal speakers") : sinkName
|
||||||
|
sinkNode.audio.volume = Math.max(0, Math.min(root.hardMaxValue, Number(sinkVolume) / 100))
|
||||||
|
sinkNode.audio.muted = sinkMuted === "yes"
|
||||||
|
root.syncing = false
|
||||||
|
root.ready = true
|
||||||
}
|
}
|
||||||
|
|
||||||
root.syncing = true
|
if (!sourceName || sourceVolume === undefined || sourceMuted === undefined) {
|
||||||
sinkNode.name = name
|
root.sourceReady = false
|
||||||
sinkNode.description = name.includes("hw_0_0")
|
root.micActive = false
|
||||||
? Translation.tr("Internal speakers") : name
|
} else {
|
||||||
sinkNode.audio.volume = Math.max(0, Math.min(root.hardMaxValue, Number(volume) / 100))
|
root.syncing = true
|
||||||
sinkNode.audio.muted = muted === "yes"
|
sourceNode.name = sourceName
|
||||||
root.syncing = false
|
sourceNode.description = sourceName.includes("hw_0_1")
|
||||||
root.ready = true
|
? Translation.tr("Internal microphone") : sourceName
|
||||||
|
sourceNode.audio.volume = Math.max(0, Math.min(root.hardMaxValue, Number(sourceVolume) / 100))
|
||||||
|
sourceNode.audio.muted = sourceMuted === "yes"
|
||||||
|
root.syncing = false
|
||||||
|
root.sourceReady = true
|
||||||
|
root.micActive = Number(sourceOutputs ?? 0) > 0
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
onExited: exitCode => {
|
onExited: exitCode => {
|
||||||
if (exitCode !== 0)
|
if (exitCode !== 0) {
|
||||||
root.ready = false
|
root.ready = false
|
||||||
|
root.sourceReady = false
|
||||||
|
root.micActive = false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Process { id: volumeProcess }
|
Process { id: volumeProcess }
|
||||||
Process { id: muteProcess }
|
Process { id: muteProcess }
|
||||||
|
Process { id: sourceVolumeProcess }
|
||||||
|
Process { id: sourceMuteProcess }
|
||||||
Process { id: defaultSinkProcess }
|
Process { id: defaultSinkProcess }
|
||||||
Process { id: defaultSourceProcess }
|
Process { id: defaultSourceProcess }
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue