Revision control
Copy as Markdown
// Copyright (C) 2025 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#include "qpipewire_instance_p.h"
#include <QtMultimedia/private/qpipewire_portal_p.h>
#include <QtMultimedia/private/qpipewire_propertydict_p.h>
#include <QtMultimedia/private/qpipewire_support_p.h>
#include <QtMultimedia/private/qtmultimediaglobal_p.h>
#if QT_CONFIG(pipewire_symbolloader)
# include <QtMultimedia/private/qpipewire_symbolloader_p.h>
#endif
#include <QtCore/qcoreapplication.h>
#include <QtCore/qmutex.h>
#include <spa/utils/names.h>
#include <array>
#include <mutex>
QT_BEGIN_NAMESPACE
namespace QtPipeWire {
using namespace Qt::Literals;
Q_LOGGING_CATEGORY(lcPipewire, "qt.multimedia.pipewire");
namespace {
struct InstanceHolder
{
QMutex mutex;
std::weak_ptr<QPipeWireInstance> instance;
};
Q_GLOBAL_STATIC(InstanceHolder, s_pipeWireInstance);
} // namespace
QPipewireInitializer::QPipewireInitializer()
{
pw_init(nullptr, nullptr);
qCDebug(lcPipewire) << "PipeWire initialized: compiled against" << pw_get_headers_version()
<< " running " << pw_get_library_version();
}
QPipewireInitializer::~QPipewireInitializer()
{
if (pw_check_library_version(0, 3, 49))
pw_deinit();
}
std::shared_ptr<QPipeWireInstance> QPipeWireInstance::instance()
{
std::lock_guard guard{ s_pipeWireInstance->mutex };
std::shared_ptr<QPipeWireInstance> ret = s_pipeWireInstance->instance.lock();
if (!ret) {
ret = QPipeWireInstance::create();
if (ret)
s_pipeWireInstance->instance = ret;
}
return ret;
}
bool QPipeWireInstance::isLoaded()
{
#if QT_CONFIG(pipewire_symbolloader)
return qPipewireIsLoaded();
#else
return 1;
#endif
}
q23::expected<void, std::error_code> QPipeWireInstance::hasSPAFactory(const char *factoryName)
{
std::array<spa_support, 32> support{};
uint32_t n_support = pw_get_support(support.data(), uint32_t(support.size()));
SpaHandleHandle handle{
pw_load_spa_handle(/*lib=*/nullptr, factoryName, /*info=*/nullptr, n_support,
support.data()),
};
if (!handle)
return q23::unexpected{ make_error_code() };
return {};
}
bool QPipeWireInstance::isInPwThreadLoop() const
{
return m_eventLoop.isInThread();
}
pw_loop *QPipeWireInstance::eventLoop() const
{
return m_eventLoop.loop();
}
bool QPipeWireInstance::hasScreenCastPortal()
{
#if QT_CONFIG(pipewire_screencapture)
if (!m_hasScreenCastPortal)
m_hasScreenCastPortal =
portalInterfaceVersion("org.freedesktop.portal.ScreenCast"_L1).has_value();
return *m_hasScreenCastPortal;
#else
return false;
#endif
}
q23::expected<PwCoreConnectionHandle, std::error_code> QPipeWireInstance::connectToDaemon()
{
auto connection = PwCoreConnectionHandle{
pw_context_connect(m_context.get(), /*props=*/nullptr,
/*user_data_size=*/0),
};
if (!connection)
return q23::unexpected(make_error_code(errno));
return connection;
}
q23::expected<PwCoreConnectionHandle, std::error_code> QPipeWireInstance::connectToPortal(int fd)
{
auto connection = PwCoreConnectionHandle{
pw_context_connect_fd(m_context.get(), fd, /*props=*/nullptr,
/*user_data_size=*/0),
};
if (!connection)
return q23::unexpected(make_error_code(errno));
return connection;
}
std::unique_ptr<QPipeWireInstance> QPipeWireInstance::create()
{
auto instance = std::unique_ptr<QPipeWireInstance>(new QPipeWireInstance);
if (!instance->m_eventLoop || !instance->m_context)
return nullptr;
return instance;
}
QPipeWireInstance::QPipeWireInstance()
{
if (!m_eventLoop)
return;
int status = m_eventLoop.start();
if (status < 0)
qFatal() << "Failed to start event loop" << make_error_code(-status).message();
m_context = runWithEventLoopLock([&] {
auto applicationName = qApp->applicationName().toUtf8();
PwPropertiesHandle props = makeProperties({
{ PW_KEY_APP_NAME, applicationName.data() },
});
return PwContextHandle{
pw_context_new(m_eventLoop.loop(), props.release(), /*user_data_size=*/0),
};
});
}
QPipeWireInstance::~QPipeWireInstance()
{
runWithEventLoopLock([&] { m_context = {}; });
m_eventLoop.stop();
}
} // namespace QtPipeWire
QT_END_NAMESPACE