import QtQuick import QtQuick.Effects import Quickshell.Services.Pipewire import "." as M Row { id: root spacing: M.Theme.moduleSpacing // Only detect active client streams, not hardware sources/devices readonly property bool _videoCapture: { if (!Pipewire.nodes) return false; for (const node of Pipewire.nodes.values) { if (!node.isStream) continue; const mc = node.properties?.["media.class"] ?? ""; if (mc === "Stream/Input/Video" || mc === "Stream/Output/Video") return true; } return false; } readonly property bool _audioIn: { if (!Pipewire.nodes) return false; for (const node of Pipewire.nodes.values) { if (!node.isStream) continue; const mc = node.properties?.["media.class"] ?? ""; if (mc === "Stream/Input/Audio") return true; } return false; } visible: root._videoCapture || root._audioIn // Screenshare indicator Text { visible: root._videoCapture text: "\uF03D" color: M.Theme.base08 font.pixelSize: M.Theme.fontSize + 2 font.family: M.Theme.iconFontFamily anchors.verticalCenter: parent.verticalCenter layer.enabled: true layer.effect: MultiEffect { shadowEnabled: true shadowColor: M.Theme.base08 shadowBlur: 0.8 shadowVerticalOffset: 0 shadowHorizontalOffset: 0 } SequentialAnimation on opacity { running: root._videoCapture loops: Animation.Infinite NumberAnimation { to: 0.4; duration: 600; easing.type: Easing.InOutQuad } NumberAnimation { to: 1; duration: 600; easing.type: Easing.InOutQuad } } } // Microphone indicator Text { visible: root._audioIn text: "\uF130" color: M.Theme.base0B font.pixelSize: M.Theme.fontSize + 2 font.family: M.Theme.iconFontFamily anchors.verticalCenter: parent.verticalCenter layer.enabled: true layer.effect: MultiEffect { shadowEnabled: true shadowColor: M.Theme.base0B shadowBlur: 0.8 shadowVerticalOffset: 0 shadowHorizontalOffset: 0 } SequentialAnimation on opacity { running: root._audioIn loops: Animation.Infinite NumberAnimation { to: 0.4; duration: 600; easing.type: Easing.InOutQuad } NumberAnimation { to: 1; duration: 600; easing.type: Easing.InOutQuad } } } }