services/pipewire: expose node type

This commit is contained in:
nydragon 2025-06-07 03:26:55 -07:00 committed by outfoxxed
parent 6b3d64e32a
commit ee570ec623
Signed by untrusted user: outfoxxed
GPG key ID: 4C88A185FB89301E
5 changed files with 100 additions and 23 deletions

View file

@ -146,7 +146,7 @@ void PwDefaultTracker::onNodeDestroyed(QObject* node) {
void PwDefaultTracker::changeConfiguredSink(PwNode* node) {
if (node != nullptr) {
if (!node->isSink) {
if (!node->type.testFlags(PwNodeType::AudioSink)) {
qCCritical(logDefaults) << "Cannot change default sink to a node that is not a sink.";
return;
}
@ -168,7 +168,7 @@ void PwDefaultTracker::changeConfiguredSinkName(const QString& sink) {
void PwDefaultTracker::changeConfiguredSource(PwNode* node) {
if (node != nullptr) {
if (node->isSink) {
if (!node->type.testFlags(PwNodeType::AudioSource)) {
qCCritical(logDefaults) << "Cannot change default source to a node that is not a source.";
return;
}

View file

@ -11,6 +11,7 @@
#include <qlogging.h>
#include <qloggingcategory.h>
#include <qobject.h>
#include <qstringliteral.h>
#include <qtmetamacros.h>
#include <qtypes.h>
#include <spa/node/keys.h>
@ -85,6 +86,20 @@ QString PwAudioChannel::toString(Enum value) {
}
}
QString PwNodeType::toString(PwNodeType::Flags type) {
switch (type) {
case PwNodeType::VideoSource: return QStringLiteral("VideoSource");
case PwNodeType::VideoSink: return QStringLiteral("VideoSink");
case PwNodeType::AudioSource: return QStringLiteral("AudioSource");
case PwNodeType::AudioSink: return QStringLiteral("AudioSink");
case PwNodeType::AudioDuplex: return QStringLiteral("AudioDuplex");
case PwNodeType::AudioOutStream: return QStringLiteral("AudioOutStream");
case PwNodeType::AudioInStream: return QStringLiteral("AudioInStream");
case PwNodeType::Untracked: return QStringLiteral("Untracked");
default: return QStringLiteral("Invalid");
}
}
void PwNode::bindHooks() {
// Bind the device first as pw is in order, meaning the device should be bound before
// we want to do anything with it.
@ -116,21 +131,19 @@ void PwNode::unbindHooks() {
void PwNode::initProps(const spa_dict* props) {
if (const auto* mediaClass = spa_dict_lookup(props, SPA_KEY_MEDIA_CLASS)) {
if (strcmp(mediaClass, "Audio/Sink") == 0) {
this->type = PwNodeType::Audio;
this->isSink = true;
this->isStream = false;
this->type = PwNodeType::AudioSink;
} else if (strcmp(mediaClass, "Audio/Source") == 0) {
this->type = PwNodeType::Audio;
this->isSink = false;
this->isStream = false;
this->type = PwNodeType::AudioSource;
} else if (strcmp(mediaClass, "Audio/Duplex") == 0) {
this->type = PwNodeType::AudioDuplex;
} else if (strcmp(mediaClass, "Stream/Output/Audio") == 0) {
this->type = PwNodeType::Audio;
this->isSink = false;
this->isStream = true;
this->type = PwNodeType::AudioOutStream;
} else if (strcmp(mediaClass, "Stream/Input/Audio") == 0) {
this->type = PwNodeType::Audio;
this->isSink = true;
this->isStream = true;
this->type = PwNodeType::AudioInStream;
} else if (strcmp(mediaClass, "Video/Sink") == 0) {
this->type = PwNodeType::VideoSink;
} else if (strcmp(mediaClass, "Video/Source") == 0) {
this->type = PwNodeType::VideoSource;
}
}
@ -164,7 +177,7 @@ void PwNode::initProps(const spa_dict* props) {
}
}
if (this->type == PwNodeType::Audio) {
if (this->type.testFlags(PwNodeType::Audio)) {
this->boundData = new PwNodeBoundAudio(this);
}
}

View file

@ -4,6 +4,7 @@
#include <pipewire/node.h>
#include <pipewire/type.h>
#include <qcontainerfwd.h>
#include <qflags.h>
#include <qmap.h>
#include <qobject.h>
#include <qqmlintegration.h>
@ -86,12 +87,71 @@ public:
/// including aux and custom channel ranges.
Q_INVOKABLE static QString toString(qs::service::pipewire::PwAudioChannel::Enum value);
};
///! The type of a pipewire node.
/// Use bitwise comparisons to filter for audio, video, sink, source or stream nodes
class PwNodeType: public QObject {
Q_OBJECT;
QML_ELEMENT;
QML_SINGLETON;
enum class PwNodeType : quint8 {
Untracked,
Audio,
public:
enum Flag : quint8 {
// A Pipewire node which is not being managed.
Untracked = 0b0,
// This flag is set when this node is an Audio node.
Audio = 0b1,
// This flag is set when this node is an Video node.
Video = 0b10,
// This flag is set when this node is a stream node.
Stream = 0b100,
// This flag is set when this node is producing some form of data,
// such as a microphone, screenshare or webcam.
Source = 0b1000,
// This flag is set when this node is receiving data.
Sink = 0b10000,
// A sink for audio samples, like an audio card.
//
// This is equivalent to the media class `Video/Source` and is
// composed of the @@PwNodeType.Audio and @@PwNodeType.Sink flags.
AudioSink = Audio | Sink,
// A source of audio samples like a microphone.
//
// This is quivalent to the media class `Video/Sink` and is composed
// of the @@PwNodeType.Audio and @@PwNodeType.Source flags.
AudioSource = Audio | Source,
// A node that is both a sink and a source.
//
// This is equivalent to the media class `Audio/Duplex` and is composed of the
// @@PwNodeType.Audio, @@PwNodeType.Source and @@PwNodeType.Sink flags.
AudioDuplex = Audio | Sink | Source,
// A playback stream.
//
// This is equivalent to the media class `Stream/Output/Audio` and is composed
// of the @@PwNodeType.Audio, @@PwNodeType.Sink and @@PwNodeType.Stream flags.
AudioOutStream = Audio | Sink | Stream,
// A capture stream.
//
// This is equivalent to the media class `Stream/Input/Audio` and is composed
// of the @@PwNodeType.Audio, @@PwNodeType.Source and @@PwNodeType.Stream flags.
AudioInStream = Audio | Source | Stream,
// A producer of video, like a webcam or a screenshare.
//
// This is equivalent to the media class `Video/Source` and is composed
// of the @@PwNodeType.Video and @@PwNodeType.Source flags.
VideoSource = Video | Source,
// A consumer of video, such as a program that is recieving a video stream.
//
// This is equivalent to the media class `Video/Sink` and is composed of the
// @@PwNodeType.Video and @@PwNodeType.Sink flags.
VideoSink = Video | Sink,
};
Q_ENUM(Flag)
Q_DECLARE_FLAGS(Flags, Flag)
Q_INVOKABLE static QString toString(qs::service::pipewire::PwNodeType::Flags type);
};
Q_DECLARE_OPERATORS_FOR_FLAGS(PwNodeType::Flags)
class PwNode;
struct PwVolumeProps {
@ -169,9 +229,8 @@ public:
QString nick;
QMap<QString, QString> properties;
PwNodeType type = PwNodeType::Untracked;
bool isSink = false;
bool isStream = false;
PwNodeType::Flags type = PwNodeType::Untracked;
bool ready = false;
PwNodeBoundData* boundData = nullptr;

View file

@ -328,12 +328,14 @@ QString PwNodeIface::description() const { return this->mNode->description; }
QString PwNodeIface::nickname() const { return this->mNode->nick; }
bool PwNodeIface::isSink() const { return this->mNode->isSink; }
bool PwNodeIface::isSink() const { return this->mNode->type.testFlags(PwNodeType::Sink); }
bool PwNodeIface::isStream() const { return this->mNode->isStream; }
bool PwNodeIface::isStream() const { return this->mNode->type.testFlags(PwNodeType::Stream); }
bool PwNodeIface::isReady() const { return this->mNode->ready; }
PwNodeType::Flags PwNodeIface::type() const { return this->mNode->type; };
QVariantMap PwNodeIface::properties() const {
auto map = QVariantMap();
for (auto [k, v]: this->mNode->properties.asKeyValueRange()) {

View file

@ -287,6 +287,8 @@ class PwNodeIface: public PwObjectIface {
/// If `true` then the node is likely to be a program, if `false` it is likely to be
/// a hardware device.
Q_PROPERTY(bool isStream READ isStream CONSTANT);
/// The type of this node. Reflects Pipewire's [media.class](https://docs.pipewire.org/page_man_pipewire-props_7.html).
Q_PROPERTY(qs::service::pipewire::PwNodeType::Flags type READ type CONSTANT);
/// The property set present on the node, as an object containing key-value pairs.
/// You can inspect this directly with `pw-cli i <id>`.
///
@ -324,6 +326,7 @@ public:
[[nodiscard]] bool isSink() const;
[[nodiscard]] bool isStream() const;
[[nodiscard]] bool isReady() const;
[[nodiscard]] PwNodeType::Flags type() const;
[[nodiscard]] QVariantMap properties() const;
[[nodiscard]] PwNodeAudioIface* audio() const;