Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package pulseaudio-qt for openSUSE:Factory checked in at 2024-09-15 12:33:16 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/pulseaudio-qt (Old) and /work/SRC/openSUSE:Factory/.pulseaudio-qt.new.29891 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "pulseaudio-qt" Sun Sep 15 12:33:16 2024 rev:7 rq:1200840 version:1.6.0 Changes: -------- --- /work/SRC/openSUSE:Factory/pulseaudio-qt/pulseaudio-qt.changes 2024-05-28 17:29:20.681162742 +0200 +++ /work/SRC/openSUSE:Factory/.pulseaudio-qt.new.29891/pulseaudio-qt.changes 2024-09-15 12:33:54.950308426 +0200 @@ -1,0 +2,11 @@ +Thu Sep 12 17:49:26 UTC 2024 - Fabian Vogt <fab...@ritter-vogt.de> + +- Update to 1.6.0: + * context: model states & auto-(re)connect + * context: refine autoconnect behavior + * verbosity-- + * server: detect if wireplumber is running + * Add since version to new API + * Bump version to 1.6.0 + +------------------------------------------------------------------- Old: ---- pulseaudio-qt-1.5.0.tar.xz pulseaudio-qt-1.5.0.tar.xz.sig New: ---- pulseaudio-qt-1.6.0.tar.xz pulseaudio-qt-1.6.0.tar.xz.sig ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ pulseaudio-qt.spec ++++++ --- /var/tmp/diff_new_pack.QfgSqD/_old 2024-09-15 12:33:55.810344055 +0200 +++ /var/tmp/diff_new_pack.QfgSqD/_new 2024-09-15 12:33:55.810344055 +0200 @@ -32,7 +32,7 @@ %define rname pulseaudio-qt %bcond_without released Name: pulseaudio-qt%{?pkg_suffix} -Version: 1.5.0 +Version: 1.6.0 Release: 0 Summary: Qt bindings for PulseAudio License: LGPL-2.1-or-later ++++++ pulseaudio-qt-1.5.0.tar.xz -> pulseaudio-qt-1.6.0.tar.xz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pulseaudio-qt-1.5.0/CMakeLists.txt new/pulseaudio-qt-1.6.0/CMakeLists.txt --- old/pulseaudio-qt-1.5.0/CMakeLists.txt 2024-05-24 19:11:26.000000000 +0200 +++ new/pulseaudio-qt-1.6.0/CMakeLists.txt 2024-09-12 17:15:02.000000000 +0200 @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.16) -set(PROJECT_VERSION 1.5.0) +set(PROJECT_VERSION 1.6.0) project(PulseAudioQt VERSION ${PROJECT_VERSION}) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pulseaudio-qt-1.5.0/src/context.cpp new/pulseaudio-qt-1.6.0/src/context.cpp --- old/pulseaudio-qt-1.5.0/src/context.cpp 2024-05-24 19:11:26.000000000 +0200 +++ new/pulseaudio-qt-1.6.0/src/context.cpp 2024-09-12 17:15:02.000000000 +0200 @@ -249,6 +249,10 @@ : QObject(parent) , d(new ContextPrivate(this)) { + connect(this, &Context::stateChanged, this, [this] { + qCDebug(PULSEAUDIOQT) << "context state changed:" << d->m_state; + }); + d->m_server = new Server(this); d->m_context = nullptr; d->m_mainloop = nullptr; @@ -262,6 +266,7 @@ }); connect(&d->m_connectTimer, &QTimer::timeout, this, [this] { + d->forceDisconnect(); d->connectToDaemon(); d->checkConnectTries(); }); @@ -447,11 +452,35 @@ void ContextPrivate::contextStateCallback(pa_context *c) { - qCDebug(PULSEAUDIOQT) << "state callback"; pa_context_state_t state = pa_context_get_state(c); + qCDebug(PULSEAUDIOQT) << "state callback" << state; + + m_state = [state]() -> Context::State { + switch (state) { + case PA_CONTEXT_UNCONNECTED: + return Context::State::Unconnected; + case PA_CONTEXT_CONNECTING: + return Context::State::Connecting; + case PA_CONTEXT_AUTHORIZING: + return Context::State::Authorizing; + case PA_CONTEXT_SETTING_NAME: + return Context::State::SettingName; + case PA_CONTEXT_READY: + return Context::State::Ready; + case PA_CONTEXT_FAILED: + return Context::State::Failed; + case PA_CONTEXT_TERMINATED: + return Context::State::Terminated; + } + return Context::State::Unconnected; + }(); + // Queue state changes to avoid race conditions with changes going on below in the code. It's not time critical anyway. + QMetaObject::invokeMethod(q, &Context::stateChanged, Qt::QueuedConnection); + if (state == PA_CONTEXT_READY) { qCDebug(PULSEAUDIOQT) << "ready, stopping connect timer"; m_connectTimer.stop(); + Q_EMIT q->autoConnectingChanged(); // 1. Register for the stream changes (except during probe) if (m_context == c) { @@ -521,9 +550,12 @@ pa_context_unref(m_context); m_context = nullptr; } - reset(); - qCDebug(PULSEAUDIOQT) << "Starting connect timer"; - m_connectTimer.start(std::chrono::seconds(5)); + if (!m_connectTimer.isActive() && hasConnectionTriesLeft()) { + reset(); + qCDebug(PULSEAUDIOQT) << "Starting connect timer"; + m_connectTimer.start(std::chrono::seconds(5)); + Q_EMIT q->autoConnectingChanged(); + } } } @@ -650,6 +682,11 @@ return; } + qCDebug(PULSEAUDIOQT) << "Connecting to daemon."; + + m_state = Context::State::Connecting; + Q_EMIT q->stateChanged(); + // We require a glib event loop if (!QByteArray(QAbstractEventDispatcher::instance()->metaObject()->className()).contains("Glib")) { qCWarning(PULSEAUDIOQT) << "Disabling PulseAudio integration for lack of GLib event loop"; @@ -678,10 +715,14 @@ Q_ASSERT(m_context); if (pa_context_connect(m_context, NULL, PA_CONTEXT_NOFAIL, nullptr) < 0) { + qCWarning(PULSEAUDIOQT) << "Failed to connect context"; pa_context_unref(m_context); pa_glib_mainloop_free(m_mainloop); + // Don't reset() here, it'd reset the retry count and possibly lead to infinite retries. m_context = nullptr; m_mainloop = nullptr; + m_state = Context::State::Unconnected; + Q_EMIT q->stateChanged(); return; } pa_context_set_state_callback(m_context, &context_state_callback, this); @@ -689,9 +730,10 @@ void ContextPrivate::checkConnectTries() { - if (++m_connectTries == 5) { + if (++m_connectTries; !hasConnectionTriesLeft()) { qCWarning(PULSEAUDIOQT) << "Giving up after" << m_connectTries << "tries to connect"; m_connectTimer.stop(); + Q_EMIT q->autoConnectingChanged(); } } @@ -707,6 +749,8 @@ m_streamRestores.reset(); m_server->reset(); m_connectTries = 0; + m_state = Context::State::Unconnected; + Q_EMIT q->stateChanged(); } bool Context::isValid() @@ -861,4 +905,44 @@ return d->m_context; } +Context::State Context::state() const +{ + return d->m_state; +} + +bool Context::isAutoConnecting() const +{ + return d->m_connectTimer.isActive(); +} + +void Context::reconnectDaemon() +{ + if (isAutoConnecting()) { // must not be in the dptr; the dptr function is called by the auto connecting logic + qCDebug(PULSEAUDIOQT) << "Already in the process of auto connecting. Not connecting again."; + return; + } + + d->forceDisconnect(); + return d->connectToDaemon(); +} + +void ContextPrivate::forceDisconnect() +{ + if (m_context) { + pa_context_unref(m_context); + m_context = nullptr; + } + + if (m_mainloop) { + pa_glib_mainloop_free(m_mainloop); + m_mainloop = nullptr; + } +} + +bool ContextPrivate::hasConnectionTriesLeft() const +{ + constexpr auto maxTries = 5; + return m_connectTries < maxTries; +} + } // PulseAudioQt diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pulseaudio-qt-1.5.0/src/context.h new/pulseaudio-qt-1.6.0/src/context.h --- old/pulseaudio-qt-1.5.0/src/context.h 2024-05-24 19:11:26.000000000 +0200 +++ new/pulseaudio-qt-1.6.0/src/context.h 2024-09-12 17:15:02.000000000 +0200 @@ -49,8 +49,36 @@ class PULSEAUDIOQT_EXPORT Context : public QObject { Q_OBJECT + /** + * The state of the Context. This is further augmented by the autoConnecting property. + * + * @since 1.6 + */ + Q_PROPERTY(State state READ state NOTIFY stateChanged) + /** + * Regardless of the state, this property indicates whether the Context is presently trying to + * automatically establish a connection. When this is false it may be useful to give the + * user a manual way to trigger a connection attempt. AutoConnecting is subject to internal + * timeouts that when hit will prevent further auto connecting until the Context managed to + * connect. + */ + Q_PROPERTY(bool autoConnecting READ isAutoConnecting NOTIFY autoConnectingChanged) public: + /** + * @since 1.6 + */ + enum class State { + Unconnected = 0, + Connecting, + Authorizing, + SettingName, + Ready, + Failed, + Terminated, + }; + Q_ENUM(State) + ~Context() override; static Context *instance(); @@ -131,6 +159,28 @@ void setDefaultSink(const QString &name); void setDefaultSource(const QString &name); + /** + * @returns the state of the context. + * + * @since 1.6 + */ + [[nodiscard]] State state() const; + + /** + * @returns whether the Context is currently trying to auto-connect to the daemon + * + * @since 1.6 + */ + [[nodiscard]] bool isAutoConnecting() const; + +public Q_SLOTS: + /** + * When the Context is not auto-connecting this may be used to give the user a manual trigger (e.g. a button) + * + * @since 1.6 + */ + void reconnectDaemon(); + Q_SIGNALS: /** * Indicates that sink was added. @@ -212,6 +262,20 @@ */ void streamRestoreRemoved(PulseAudioQt::StreamRestore *streamRestore); + /** + * Context state changed. + * + * @since 1.6 + */ + void stateChanged(); + + /** + * Indicates that autoConnecting changed. + * + * @since 1.6 + */ + void autoConnectingChanged(); + private: explicit Context(QObject *parent = nullptr); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pulseaudio-qt-1.5.0/src/context_p.h new/pulseaudio-qt-1.6.0/src/context_p.h --- old/pulseaudio-qt-1.5.0/src/context_p.h 2024-05-24 19:11:26.000000000 +0200 +++ new/pulseaudio-qt-1.6.0/src/context_p.h 2024-09-12 17:15:02.000000000 +0200 @@ -6,6 +6,7 @@ #ifndef CONTEXT_P_H #define CONTEXT_P_H +#include "context.h" #include "maps.h" #include "operation.h" #include <QTimer> @@ -45,6 +46,7 @@ QTimer m_connectTimer; int m_connectTries; + Context::State m_state = Context::State::Unconnected; static QString s_applicationId; @@ -80,6 +82,8 @@ void reset(); void connectToDaemon(); void checkConnectTries(); + void forceDisconnect(); + [[nodiscard]] bool hasConnectionTriesLeft() const; Context *q; }; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pulseaudio-qt-1.5.0/src/models.cpp new/pulseaudio-qt-1.6.0/src/models.cpp --- old/pulseaudio-qt-1.5.0/src/models.cpp 2024-05-24 19:11:26.000000000 +0200 +++ new/pulseaudio-qt-1.6.0/src/models.cpp 2024-09-12 17:15:02.000000000 +0200 @@ -65,7 +65,6 @@ QHash<int, QByteArray> AbstractModel::roleNames() const { if (!d->m_roles.empty()) { - qCDebug(PULSEAUDIOQT) << "returning roles" << d->m_roles; return d->m_roles; } Q_UNREACHABLE(); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pulseaudio-qt-1.5.0/src/server.cpp new/pulseaudio-qt-1.6.0/src/server.cpp --- old/pulseaudio-qt-1.5.0/src/server.cpp 2024-05-24 19:11:26.000000000 +0200 +++ new/pulseaudio-qt-1.6.0/src/server.cpp 2024-09-12 17:15:02.000000000 +0200 @@ -13,6 +13,8 @@ #include "sink.h" #include "source.h" +using namespace std::chrono_literals; + namespace PulseAudioQt { Server::Server(Context *context) @@ -25,6 +27,17 @@ connect(&context->d->m_sinks, &MapBaseQObject::removed, this, &Server::updateDefaultDevices); connect(&context->d->m_sources, &MapBaseQObject::added, this, &Server::updateDefaultDevices); connect(&context->d->m_sources, &MapBaseQObject::removed, this, &Server::updateDefaultDevices); + + // WirePlumber detection works based on connected clients. + // Since we act on individual client changes let's compress them otherwise we may be switching state multiple times + // for no reason. + d->m_wirePlumberFindTimer.setInterval(250ms); // arbitrary compression time + d->m_wirePlumberFindTimer.setSingleShot(true); + connect(&d->m_wirePlumberFindTimer, &QTimer::timeout, this, [this] { + d->findWirePlumber(); + }); + connect(&context->d->m_clients, &MapBaseQObject::added, &d->m_wirePlumberFindTimer, qOverload<>(&QTimer::start)); + connect(&context->d->m_clients, &MapBaseQObject::removed, &d->m_wirePlumberFindTimer, qOverload<>(&QTimer::start)); } Server::~Server() @@ -136,4 +149,28 @@ return d->m_isPipeWire; } +void ServerPrivate::findWirePlumber() +{ + if (!m_isPipeWire) { + return; + } + + const auto clients = Context::instance()->clients(); + for (const auto &client : clients) { + if (client->properties().value(QStringLiteral("wireplumber.daemon")) == QLatin1String("true")) { + m_hasWirePlumber = true; + Q_EMIT q->hasWirePlumberChanged(); + return; + } + } + + // Found no plumber, mark false + m_hasWirePlumber = false; + Q_EMIT q->hasWirePlumberChanged(); +} + +bool Server::hasWirePlumber() const +{ + return d->m_hasWirePlumber; +} } // PulseAudioQt diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pulseaudio-qt-1.5.0/src/server.h new/pulseaudio-qt-1.6.0/src/server.h --- old/pulseaudio-qt-1.5.0/src/server.h 2024-05-24 19:11:26.000000000 +0200 +++ new/pulseaudio-qt-1.6.0/src/server.h 2024-09-12 17:15:02.000000000 +0200 @@ -26,6 +26,8 @@ Q_PROPERTY(Source *defaultSource READ defaultSource NOTIFY defaultSourceChanged) /// Whether the connected Server is PipeWire (rather than PulseAudio) Q_PROPERTY(bool isPipeWire READ isPipeWire NOTIFY isPipeWireChanged) + /// Whether the connected PipeWire is driven by WirePlumber + Q_PROPERTY(bool hasWirePlumber READ hasWirePlumber NOTIFY hasWirePlumberChanged) public: ~Server() override; @@ -40,11 +42,19 @@ */ bool isPipeWire() const; + /** + * Whether WirePlumber is running in addition to PipeWire. + * + * @since 1.6 + */ + [[nodiscard]] bool hasWirePlumber() const; + Q_SIGNALS: void defaultSinkChanged(PulseAudioQt::Sink *sink); void defaultSourceChanged(PulseAudioQt::Source *source); void isPipeWireChanged(); void updated(); + void hasWirePlumberChanged(); private: explicit Server(Context *context); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pulseaudio-qt-1.5.0/src/server_p.h new/pulseaudio-qt-1.6.0/src/server_p.h --- old/pulseaudio-qt-1.5.0/src/server_p.h 2024-05-24 19:11:26.000000000 +0200 +++ new/pulseaudio-qt-1.6.0/src/server_p.h 2024-09-12 17:15:02.000000000 +0200 @@ -7,6 +7,8 @@ #include "server.h" #include <pulse/introspect.h> +#include <QTimer> + namespace PulseAudioQt { class ServerPrivate @@ -22,7 +24,10 @@ Sink *m_defaultSink; Source *m_defaultSource; bool m_isPipeWire = false; + bool m_hasWirePlumber = false; + QTimer m_wirePlumberFindTimer; void update(const pa_server_info *info); + void findWirePlumber(); }; }