mirror of
https://bitbucket.org/smil3y/kde-workspace.git
synced 2025-02-24 10:52:51 +00:00
890 lines
27 KiB
C++
890 lines
27 KiB
C++
/* This file is part of the KDE project
|
|
Copyright (C) 2010 Colin Guthrie <cguthrie@mandriva.org>
|
|
Copyright (C) 2011 Harald Sitter <sitter@kde.org>
|
|
|
|
This program is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License version 2
|
|
as published by the Free Software Foundation.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program; if not, write to the Free Software
|
|
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
|
02110-1301, USA.
|
|
*/
|
|
|
|
#include "audiosetup.h"
|
|
|
|
#include <QtCore/QAbstractEventDispatcher>
|
|
#include <QtCore/QDebug>
|
|
#include <QtCore/QDir>
|
|
#include <QtCore/QTimer>
|
|
#include <QtDBus/QDBusConnection>
|
|
#include <QtDBus/QDBusInterface>
|
|
#include <QLabel>
|
|
|
|
#include <kaboutdata.h>
|
|
#include <kconfiggroup.h>
|
|
#include <kdebug.h>
|
|
#include <kicon.h>
|
|
#include <kuser.h>
|
|
|
|
#include <glib.h>
|
|
#include <pulse/xmalloc.h>
|
|
#include <pulse/glib-mainloop.h>
|
|
|
|
#include "testspeakerwidget.h"
|
|
|
|
#define SS_DEFAULT_ICON "audio-card"
|
|
|
|
#define THAT(userdata) Q_ASSERT(userdata); AudioSetup *ss = static_cast<AudioSetup *>(userdata)
|
|
|
|
static pa_glib_mainloop *s_mainloop = NULL;
|
|
static pa_context *s_context = NULL;
|
|
|
|
QMap<quint32, cardInfo> s_Cards;
|
|
QMap<quint32, deviceInfo> s_Sinks;
|
|
QMap<quint32, deviceInfo> s_Sources;
|
|
|
|
static void card_cb(pa_context *c, const pa_card_info *i, int eol, void *userdata) {
|
|
Q_ASSERT(c);
|
|
THAT(userdata);
|
|
|
|
if (eol < 0) {
|
|
if (pa_context_errno(c) == PA_ERR_NOENTITY)
|
|
return;
|
|
|
|
kDebug() << "Card callback failure";
|
|
return;
|
|
}
|
|
|
|
if (eol > 0) {
|
|
ss->updateFromPulse();
|
|
return;
|
|
}
|
|
|
|
Q_ASSERT(i);
|
|
ss->updateCard(i);
|
|
}
|
|
|
|
static void sink_cb(pa_context *c, const pa_sink_info *i, int eol, void *userdata) {
|
|
Q_ASSERT(c);
|
|
THAT(userdata);
|
|
|
|
if (eol < 0) {
|
|
if (pa_context_errno(c) == PA_ERR_NOENTITY)
|
|
return;
|
|
kDebug() << "Sink callback failure";
|
|
return;
|
|
}
|
|
|
|
if (eol > 0) {
|
|
ss->updateIndependantDevices();
|
|
ss->updateFromPulse();
|
|
return;
|
|
}
|
|
|
|
Q_ASSERT(i);
|
|
ss->updateSink(i);
|
|
}
|
|
|
|
static void source_cb(pa_context *c, const pa_source_info *i, int eol, void *userdata) {
|
|
Q_ASSERT(c);
|
|
THAT(userdata);
|
|
|
|
if (eol < 0) {
|
|
if (pa_context_errno(c) == PA_ERR_NOENTITY)
|
|
return;
|
|
|
|
kDebug() << "Source callback failure";
|
|
return;
|
|
}
|
|
|
|
if (eol > 0) {
|
|
ss->updateIndependantDevices();
|
|
ss->updateFromPulse();
|
|
return;
|
|
}
|
|
|
|
Q_ASSERT(i);
|
|
ss->updateSource(i);
|
|
}
|
|
|
|
static void subscribe_cb(pa_context *c, pa_subscription_event_type_t t, uint32_t index, void *userdata) {
|
|
Q_ASSERT(c);
|
|
THAT(userdata);
|
|
|
|
switch (t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) {
|
|
case PA_SUBSCRIPTION_EVENT_CARD:
|
|
if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
|
|
ss->removeCard(index);
|
|
} else {
|
|
pa_operation *operation =
|
|
pa_context_get_card_info_by_index(c, index, card_cb, ss);
|
|
if (!operation) {
|
|
kDebug() << "pa_context_get_card_info_by_index() failed";
|
|
return;
|
|
}
|
|
pa_operation_unref(operation);
|
|
}
|
|
break;
|
|
|
|
case PA_SUBSCRIPTION_EVENT_SINK:
|
|
if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
|
|
ss->removeSink(index);
|
|
} else {
|
|
pa_operation *operation =
|
|
pa_context_get_sink_info_by_index(c, index, sink_cb, ss);
|
|
if (!operation) {
|
|
kDebug() << "pa_context_get_sink_info_by_index() failed";
|
|
return;
|
|
}
|
|
pa_operation_unref(operation);
|
|
}
|
|
break;
|
|
|
|
case PA_SUBSCRIPTION_EVENT_SOURCE:
|
|
if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
|
|
ss->removeSource(index);
|
|
} else {
|
|
pa_operation *o;
|
|
if (!(o = pa_context_get_source_info_by_index(c, index, source_cb, ss))) {
|
|
kDebug() << "pa_context_get_source_info_by_index() failed";
|
|
return;
|
|
}
|
|
pa_operation_unref(o);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void context_state_callback(pa_context *c, void *userdata)
|
|
{
|
|
Q_ASSERT(c);
|
|
THAT(userdata);
|
|
|
|
kDebug() << "context_state_callback" << pa_context_get_state(c);
|
|
pa_context_state_t state = pa_context_get_state(c);
|
|
if (state == PA_CONTEXT_READY) {
|
|
// Attempt to load things up
|
|
pa_operation *o;
|
|
|
|
pa_context_set_subscribe_callback(c, subscribe_cb, ss);
|
|
|
|
if (!(o = pa_context_subscribe(c, (pa_subscription_mask_t)
|
|
(PA_SUBSCRIPTION_MASK_CARD|
|
|
PA_SUBSCRIPTION_MASK_SINK|
|
|
PA_SUBSCRIPTION_MASK_SOURCE), NULL, NULL))) {
|
|
kDebug() << "pa_context_subscribe() failed";
|
|
return;
|
|
}
|
|
pa_operation_unref(o);
|
|
|
|
if (!(o = pa_context_get_card_info_list(c, card_cb, ss))) {
|
|
kDebug() << "pa_context_get_card_info_list() failed";
|
|
return;
|
|
}
|
|
pa_operation_unref(o);
|
|
|
|
if (!(o = pa_context_get_sink_info_list(c, sink_cb, ss))) {
|
|
kDebug() << "pa_context_get_sink_info_list() failed";
|
|
return;
|
|
}
|
|
pa_operation_unref(o);
|
|
|
|
if (!(o = pa_context_get_source_info_list(c, source_cb, ss))) {
|
|
kDebug() << "pa_context_get_source_info_list() failed";
|
|
return;
|
|
}
|
|
pa_operation_unref(o);
|
|
|
|
ss->load();
|
|
|
|
} else if (!PA_CONTEXT_IS_GOOD(state)) {
|
|
// If this is our probe phase, exit our context immediately
|
|
if (s_context != c)
|
|
pa_context_disconnect(c);
|
|
else {
|
|
kWarning() << "PulseAudio context lost. Scheduling reconnect in eventloop.";
|
|
pa_context_unref(s_context);
|
|
s_context = 0;
|
|
QMetaObject::invokeMethod(ss, "connectToDaemon", Qt::QueuedConnection);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void suspended_callback(pa_stream *s, void *userdata) {
|
|
THAT(userdata);
|
|
|
|
if (pa_stream_is_suspended(s))
|
|
ss->updateVUMeter(-1);
|
|
}
|
|
|
|
static void read_callback(pa_stream *s, size_t length, void *userdata) {
|
|
THAT(userdata);
|
|
|
|
const void *data;
|
|
int v;
|
|
if (pa_stream_peek(s, &data, &length) < 0) {
|
|
kDebug() << "Failed to read data from stream";
|
|
return;
|
|
}
|
|
|
|
Q_ASSERT(length > 0);
|
|
Q_ASSERT(length % sizeof(float) == 0);
|
|
|
|
v = ((const float*) data)[length / sizeof(float) -1] * 100;
|
|
|
|
pa_stream_drop(s);
|
|
|
|
if (v < 0)
|
|
v = 0;
|
|
if (v > 100)
|
|
v = 100;
|
|
|
|
ss->updateVUMeter(v);
|
|
}
|
|
|
|
|
|
AudioSetup::AudioSetup(QWidget *parent)
|
|
: QWidget(parent)
|
|
, m_OutstandingRequests(3)
|
|
, m_Canberra(0)
|
|
, m_VUStream(0)
|
|
, m_VURealValue(0)
|
|
{
|
|
setupUi(this);
|
|
|
|
cardLabel->setEnabled(false);
|
|
cardBox->setEnabled(false);
|
|
profileLabel->setVisible(false);
|
|
profileBox->setVisible(false);
|
|
|
|
deviceLabel->setEnabled(false);
|
|
deviceBox->setEnabled(false);
|
|
portLabel->setVisible(false);
|
|
portBox->setVisible(false);
|
|
|
|
for (int i = 0; i < 5; ++i)
|
|
placementGrid->setColumnStretch(i, 1);
|
|
for (int i = 0; i < 3; ++i)
|
|
placementGrid->setRowStretch(i, 1);
|
|
|
|
m_icon = new QLabel(this);
|
|
m_icon->setPixmap(QPixmap(KUser().faceIconPath()));
|
|
if (m_icon->pixmap()->isNull())
|
|
m_icon->setPixmap(KIcon("system-users").pixmap(KIconLoader::SizeHuge, KIconLoader::SizeHuge));
|
|
placementGrid->addWidget(m_icon, 1, 2, Qt::AlignCenter);
|
|
|
|
update();
|
|
connect(cardBox, SIGNAL(currentIndexChanged(int)), SLOT(cardChanged()));
|
|
connect(profileBox, SIGNAL(currentIndexChanged(int)), SLOT(profileChanged()));
|
|
connect(deviceBox, SIGNAL(currentIndexChanged(int)), SLOT(deviceChanged()));
|
|
connect(portBox, SIGNAL(currentIndexChanged(int)), SLOT(portChanged()));
|
|
|
|
m_VUTimer = new QTimer(this);
|
|
m_VUTimer->setInterval(10);
|
|
connect(m_VUTimer, SIGNAL(timeout()), this, SLOT(reallyUpdateVUMeter()));
|
|
|
|
// We require a glib event loop
|
|
const QByteArray eventDispatcher(
|
|
QAbstractEventDispatcher::instance()->metaObject()->className());
|
|
if (!eventDispatcher.contains("EventDispatcherGlib")) {
|
|
kDebug() << "Disabling PulseAudio integration for lack of GLib event loop.";
|
|
return;
|
|
}
|
|
|
|
int ret = ca_context_create(&m_Canberra);
|
|
if (ret < 0) {
|
|
kDebug() << "Disabling PulseAudio integration. Canberra context failed.";
|
|
return;
|
|
}
|
|
|
|
s_mainloop = pa_glib_mainloop_new(NULL);
|
|
if (!s_mainloop) {
|
|
kDebug() << "Disabling PulseAudio integration for lack of working GLib event loop.";
|
|
ca_context_destroy(m_Canberra);
|
|
m_Canberra = 0;
|
|
return;
|
|
}
|
|
|
|
connectToDaemon();
|
|
}
|
|
|
|
AudioSetup::~AudioSetup()
|
|
{
|
|
if (m_Canberra)
|
|
ca_context_destroy(m_Canberra);
|
|
if (s_context) {
|
|
pa_context_unref(s_context);
|
|
s_context = 0;
|
|
}
|
|
if (s_mainloop) {
|
|
pa_glib_mainloop_free(s_mainloop);
|
|
s_mainloop = 0;
|
|
}
|
|
s_Cards.clear();
|
|
s_Sinks.clear();
|
|
s_Sources.clear();
|
|
}
|
|
|
|
void AudioSetup::load()
|
|
{
|
|
}
|
|
|
|
void AudioSetup::save()
|
|
{
|
|
}
|
|
|
|
void AudioSetup::defaults()
|
|
{
|
|
}
|
|
|
|
void AudioSetup::updateCard(const pa_card_info *pInfo)
|
|
{
|
|
cardInfo info;
|
|
info.index = pInfo->index;
|
|
|
|
const char *description = pa_proplist_gets(pInfo->proplist, PA_PROP_DEVICE_DESCRIPTION);
|
|
if(description)
|
|
info.name = QString::fromUtf8(description);
|
|
else
|
|
info.name = QString::fromUtf8(pInfo->name);
|
|
|
|
const char *icon = pa_proplist_gets(pInfo->proplist, PA_PROP_DEVICE_ICON_NAME);
|
|
if (icon)
|
|
info.icon = QString::fromUtf8(icon);
|
|
else
|
|
info.icon = QString::fromUtf8(SS_DEFAULT_ICON);
|
|
|
|
for (quint32 i = 0; i < pInfo->n_profiles; ++i) {
|
|
const pa_card_profile_info *profile = &(pInfo->profiles[i]);
|
|
const quint32 priority = profile->priority;
|
|
const QPair<QString, QString> name(profile->name ? QString::fromUtf8(profile->name) : QString(),
|
|
profile->description ? QString::fromUtf8(profile->description) : QString());
|
|
info.profiles.insertMulti(priority, name);
|
|
}
|
|
|
|
if (pInfo->active_profile)
|
|
info.activeProfile = pInfo->active_profile->name;
|
|
|
|
cardBox->blockSignals(true);
|
|
if (s_Cards.contains(pInfo->index)) {
|
|
int idx = cardBox->findData(pInfo->index);
|
|
if (idx >= 0) {
|
|
cardBox->setItemIcon(idx, KIcon(info.icon));
|
|
cardBox->setItemText(idx, info.name);
|
|
}
|
|
} else {
|
|
cardBox->addItem(KIcon(info.icon), info.name, pInfo->index);
|
|
}
|
|
cardBox->blockSignals(false);
|
|
|
|
s_Cards[pInfo->index] = info;
|
|
|
|
cardChanged();
|
|
kDebug() << "Got info about card" << info.name;
|
|
}
|
|
|
|
void AudioSetup::removeCard(uint32_t index)
|
|
{
|
|
s_Cards.remove(index);
|
|
updateFromPulse();
|
|
const int idx = cardBox->findData(index);
|
|
if (idx >= 0)
|
|
cardBox->removeItem(idx);
|
|
}
|
|
|
|
void AudioSetup::updateSink(const pa_sink_info* i)
|
|
{
|
|
deviceInfo info;
|
|
info.index = i->index;
|
|
info.cardIndex = i->card;
|
|
info.name = QString::fromUtf8(i->description);
|
|
|
|
const char *icon = pa_proplist_gets(i->proplist, PA_PROP_DEVICE_ICON_NAME);
|
|
info.icon = icon ? icon : SS_DEFAULT_ICON;
|
|
|
|
info.channelMap = i->channel_map;
|
|
|
|
for (uint32_t j = 0; j < i->n_ports; ++j)
|
|
info.ports[i->ports[j]->priority] = QPair<QString,QString>(i->ports[j]->name, QString::fromUtf8(i->ports[j]->description));
|
|
if (i->active_port)
|
|
info.activePort = i->active_port->name;
|
|
|
|
s_Sinks[i->index] = info;
|
|
|
|
// Need to update the currently displayed port if this sink is the currently displayed one.
|
|
if (info.ports.size()) {
|
|
int idx = deviceBox->currentIndex();
|
|
if (idx >= 0) {
|
|
qint64 index = deviceBox->itemData(idx).toInt();
|
|
if (index >= 0 && index == i->index) {
|
|
portBox->blockSignals(true);
|
|
portBox->setCurrentIndex(portBox->findData(info.activePort));
|
|
portBox->blockSignals(false);
|
|
}
|
|
}
|
|
}
|
|
|
|
kDebug() << "Got info about sink" << info.name;
|
|
}
|
|
|
|
void AudioSetup::removeSink(uint32_t index)
|
|
{
|
|
s_Sinks.remove(index);
|
|
updateIndependantDevices();
|
|
updateFromPulse();
|
|
int idx = deviceBox->findData(index);
|
|
if (idx >= 0)
|
|
deviceBox->removeItem(idx);
|
|
}
|
|
|
|
void AudioSetup::updateSource(const pa_source_info* i)
|
|
{
|
|
if (i->monitor_of_sink != PA_INVALID_INDEX)
|
|
return;
|
|
|
|
deviceInfo info;
|
|
info.index = i->index;
|
|
info.cardIndex = i->card;
|
|
info.name = QString::fromUtf8(i->description);
|
|
|
|
const char* icon = pa_proplist_gets(i->proplist, PA_PROP_DEVICE_ICON_NAME);
|
|
info.icon = icon ? icon : SS_DEFAULT_ICON;
|
|
|
|
info.channelMap = i->channel_map;
|
|
|
|
for (uint32_t j = 0; j < i->n_ports; ++j)
|
|
info.ports[i->ports[j]->priority] = QPair<QString,QString>(i->ports[j]->name, QString::fromUtf8(i->ports[j]->description));
|
|
if (i->active_port)
|
|
info.activePort = i->active_port->name;
|
|
|
|
s_Sources[i->index] = info;
|
|
|
|
// Need to update the currently displayed port if this source is the currently displayed one.
|
|
if (false && info.ports.size()) {
|
|
int idx = deviceBox->currentIndex();
|
|
if (idx >= 0) {
|
|
qint64 index = deviceBox->itemData(idx).toInt();
|
|
if (index < 0 && ((-1*index) - 1) == i->index) {
|
|
portBox->blockSignals(true);
|
|
portBox->setCurrentIndex(portBox->findData(info.activePort));
|
|
portBox->blockSignals(false);
|
|
}
|
|
}
|
|
}
|
|
|
|
kDebug() << "Got info about source" << info.name;
|
|
}
|
|
|
|
void AudioSetup::removeSource(uint32_t index)
|
|
{
|
|
s_Sources.remove(index);
|
|
updateIndependantDevices();
|
|
updateFromPulse();
|
|
int idx = deviceBox->findData(index);
|
|
if (false && idx >= 0)
|
|
deviceBox->removeItem(idx);
|
|
}
|
|
|
|
void AudioSetup::updateFromPulse()
|
|
{
|
|
bool setupReady = false;
|
|
if (m_OutstandingRequests > 0) {
|
|
if (0 == --m_OutstandingRequests) {
|
|
// Work out which seclector to pick by default (we want to choose a real Card if possible)
|
|
if (s_Cards.size() != cardBox->count())
|
|
cardBox->setCurrentIndex(1);
|
|
setupReady = true;
|
|
}
|
|
}
|
|
|
|
if (!m_OutstandingRequests) {
|
|
if (!s_Cards.size() && !s_Sinks.size()) {
|
|
cardLabel->setEnabled(false);
|
|
cardBox->setEnabled(false);
|
|
profileLabel->setVisible(false);
|
|
profileBox->setVisible(false);
|
|
|
|
deviceLabel->setEnabled(false);
|
|
deviceBox->setEnabled(false);
|
|
portLabel->setVisible(false);
|
|
portBox->setVisible(false);
|
|
}
|
|
if (s_Cards.size() && !cardBox->isEnabled()) {
|
|
cardLabel->setEnabled(true);
|
|
cardBox->setEnabled(true);
|
|
cardChanged();
|
|
}
|
|
if (s_Sinks.size() && !deviceBox->isEnabled()) {
|
|
deviceLabel->setEnabled(true);
|
|
deviceBox->setEnabled(true);
|
|
deviceChanged();
|
|
}
|
|
|
|
if (setupReady) {
|
|
emit ready();
|
|
}
|
|
}
|
|
}
|
|
|
|
void AudioSetup::cardChanged()
|
|
{
|
|
int idx = cardBox->currentIndex();
|
|
if (idx < 0) {
|
|
profileLabel->setVisible(false);
|
|
profileBox->setVisible(false);
|
|
return;
|
|
}
|
|
|
|
uint32_t card_index = cardBox->itemData(idx).toUInt();
|
|
Q_ASSERT(PA_INVALID_INDEX == card_index || s_Cards.contains(card_index));
|
|
bool show_profiles = (PA_INVALID_INDEX != card_index && s_Cards[card_index].profiles.size());
|
|
if (show_profiles) {
|
|
cardInfo &card_info = s_Cards[card_index];
|
|
profileBox->blockSignals(true);
|
|
profileBox->clear();
|
|
QMap<quint32, QPair<QString, QString> >::const_iterator it;
|
|
for (it = card_info.profiles.constBegin(); it != card_info.profiles.constEnd(); ++it)
|
|
profileBox->insertItem(0, it.value().second, it.value().first);
|
|
profileBox->setCurrentIndex(profileBox->findData(card_info.activeProfile));
|
|
profileBox->blockSignals(false);
|
|
}
|
|
profileLabel->setVisible(show_profiles);
|
|
profileBox->setVisible(show_profiles);
|
|
|
|
deviceBox->blockSignals(true);
|
|
deviceBox->clear();
|
|
QMap<quint32, deviceInfo>::const_iterator it;
|
|
for (it = s_Sinks.constBegin(); it != s_Sinks.constEnd(); ++it) {
|
|
if (it->cardIndex == card_index)
|
|
deviceBox->addItem(KIcon(it->icon), i18n("Playback (%1)", it->name), it->index);
|
|
}
|
|
for (it = s_Sources.constBegin(); it != s_Sources.constEnd(); ++it) {
|
|
if (it->cardIndex == card_index)
|
|
deviceBox->addItem(KIcon(it->icon), i18n("Recording (%1)", it->name), ((-1*it->index) - 1));
|
|
}
|
|
deviceBox->blockSignals(false);
|
|
|
|
deviceGroupBox->setEnabled(!!deviceBox->count());
|
|
|
|
deviceChanged();
|
|
|
|
kDebug() << "Doing update" << cardBox->currentIndex();
|
|
|
|
emit changed();
|
|
}
|
|
|
|
void AudioSetup::profileChanged()
|
|
{
|
|
quint32 card_index = cardBox->itemData(cardBox->currentIndex()).toUInt();
|
|
Q_ASSERT(PA_INVALID_INDEX != card_index);
|
|
|
|
QString profile = profileBox->itemData(profileBox->currentIndex()).toString();
|
|
kDebug() << "Changing profile to" << profile;
|
|
|
|
Q_ASSERT(s_Cards[card_index].profiles.size());
|
|
|
|
pa_operation *operation =
|
|
pa_context_set_card_profile_by_index(s_context,
|
|
card_index,
|
|
qPrintable(profile),
|
|
NULL,
|
|
NULL);
|
|
if (!operation)
|
|
kDebug() << "pa_context_set_card_profile_by_name() failed";
|
|
else
|
|
pa_operation_unref(operation);
|
|
|
|
emit changed();
|
|
}
|
|
|
|
void AudioSetup::updateIndependantDevices()
|
|
{
|
|
// Should we display the "Independent Devices" drop down?
|
|
// Count all the sinks without cards
|
|
bool showID = false;
|
|
QMap<quint32, deviceInfo>::const_iterator it;
|
|
for (it = s_Sinks.constBegin(); it != s_Sinks.constEnd(); ++it) {
|
|
if (PA_INVALID_INDEX == it->cardIndex) {
|
|
showID = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
bool haveID = (PA_INVALID_INDEX == cardBox->itemData(0).toUInt());
|
|
|
|
kDebug() << QString("Want ID: %1; Have ID: %2").arg(showID?"Yes":"No").arg(haveID?"Yes":"No");
|
|
|
|
cardBox->blockSignals(true);
|
|
if (haveID && !showID)
|
|
cardBox->removeItem(0);
|
|
else if (!haveID && showID)
|
|
cardBox->insertItem(0, KIcon(SS_DEFAULT_ICON), i18n("Independent Devices"), PA_INVALID_INDEX);
|
|
cardBox->blockSignals(false);
|
|
}
|
|
|
|
void AudioSetup::updateVUMeter(int vol)
|
|
{
|
|
if (vol < 0) {
|
|
inputLevels->setEnabled(false);
|
|
inputLevels->setValue(0);
|
|
m_VURealValue = 0;
|
|
} else {
|
|
inputLevels->setEnabled(true);
|
|
if (vol > inputLevels->value())
|
|
inputLevels->setValue(vol);
|
|
m_VURealValue = vol;
|
|
}
|
|
}
|
|
|
|
void AudioSetup::reallyUpdateVUMeter()
|
|
{
|
|
int val = inputLevels->value();
|
|
if (val > m_VURealValue)
|
|
inputLevels->setValue(val-1);
|
|
}
|
|
|
|
bool AudioSetup::connectToDaemon()
|
|
{
|
|
pa_mainloop_api *api = pa_glib_mainloop_get_api(s_mainloop);
|
|
|
|
s_context = pa_context_new(api, i18n("KDE Audio Hardware Setup").toUtf8().constData());
|
|
if (pa_context_connect(s_context, NULL, PA_CONTEXT_NOFAIL, 0) < 0) {
|
|
kDebug() << "Disabling PulseAudio integration. Context connection failed: " << pa_strerror(pa_context_errno(s_context));
|
|
pa_context_unref(s_context);
|
|
s_context = 0;
|
|
pa_glib_mainloop_free(s_mainloop);
|
|
s_mainloop = 0;
|
|
ca_context_destroy(m_Canberra);
|
|
m_Canberra = 0;
|
|
setEnabled(false);
|
|
return false;
|
|
}
|
|
|
|
pa_context_set_state_callback(s_context, &context_state_callback, this);
|
|
setEnabled(true);
|
|
return true;
|
|
}
|
|
|
|
static deviceInfo &getDeviceInfo(qint64 index)
|
|
{
|
|
if (index >= 0) {
|
|
Q_ASSERT(s_Sinks.contains(index));
|
|
return s_Sinks[index];
|
|
}
|
|
|
|
index = (-1 * index) - 1;
|
|
Q_ASSERT(s_Sources.contains(index));
|
|
return s_Sources[index];
|
|
}
|
|
|
|
void AudioSetup::deviceChanged()
|
|
{
|
|
int idx = deviceBox->currentIndex();
|
|
if (idx < 0) {
|
|
portLabel->setVisible(false);
|
|
portBox->setVisible(false);
|
|
_updatePlacementTester();
|
|
return;
|
|
}
|
|
qint64 index = deviceBox->itemData(idx).toInt();
|
|
deviceInfo &device_info = getDeviceInfo(index);
|
|
|
|
kDebug() << QString("Updating ports for device '%1' (%2 ports available)")
|
|
.arg(device_info.name)
|
|
.arg(device_info.ports.size());
|
|
|
|
bool showPorts = !!device_info.ports.size();
|
|
if (showPorts) {
|
|
portBox->blockSignals(true);
|
|
portBox->clear();
|
|
QMap<quint32, QPair<QString, QString> >::const_iterator it;
|
|
for (it = device_info.ports.constBegin(); it != device_info.ports.constEnd(); ++it)
|
|
portBox->insertItem(0, it.value().second, it.value().first);
|
|
portBox->setCurrentIndex(portBox->findData(device_info.activePort));
|
|
portBox->blockSignals(false);
|
|
}
|
|
portLabel->setVisible(showPorts);
|
|
portBox->setVisible(showPorts);
|
|
|
|
if (deviceBox->currentIndex() >= 0) {
|
|
if (index < 0)
|
|
_createMonitorStreamForSource((-1*index) - 1);
|
|
else if (m_VUStream) {
|
|
pa_stream_disconnect(m_VUStream);
|
|
m_VUStream = NULL;
|
|
}
|
|
|
|
_updatePlacementTester();
|
|
}
|
|
|
|
emit changed();
|
|
}
|
|
|
|
void AudioSetup::portChanged()
|
|
{
|
|
qint64 index = deviceBox->itemData(deviceBox->currentIndex()).toInt();
|
|
|
|
QString port = portBox->itemData(portBox->currentIndex()).toString();
|
|
kDebug() << "Changing port to" << port;
|
|
|
|
#ifndef QT_NO_DEBUG
|
|
deviceInfo &device_info = getDeviceInfo(index);
|
|
Q_ASSERT(device_info.ports.size());
|
|
#endif /* QT_NO_DEBUG */
|
|
|
|
pa_operation *o;
|
|
if (index >= 0) {
|
|
if (!(o = pa_context_set_sink_port_by_index(s_context, (uint32_t)index, port.toAscii().constData(), NULL, NULL)))
|
|
kDebug() << "pa_context_set_sink_port_by_index() failed";
|
|
else
|
|
pa_operation_unref(o);
|
|
} else {
|
|
if (!(o = pa_context_set_source_port_by_index(s_context, (uint32_t)((-1*index) - 1), port.toAscii().constData(), NULL, NULL)))
|
|
kDebug() << "pa_context_set_source_port_by_index() failed";
|
|
else
|
|
pa_operation_unref(o);
|
|
}
|
|
|
|
emit changed();
|
|
}
|
|
|
|
void AudioSetup::_updatePlacementTester()
|
|
{
|
|
static const int position_table[] = {
|
|
/* Position, X, Y */
|
|
PA_CHANNEL_POSITION_FRONT_LEFT, 0, 0,
|
|
PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER, 1, 0,
|
|
PA_CHANNEL_POSITION_FRONT_CENTER, 2, 0,
|
|
PA_CHANNEL_POSITION_MONO, 2, 0,
|
|
PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER, 3, 0,
|
|
PA_CHANNEL_POSITION_FRONT_RIGHT, 4, 0,
|
|
PA_CHANNEL_POSITION_SIDE_LEFT, 0, 1,
|
|
PA_CHANNEL_POSITION_SIDE_RIGHT, 4, 1,
|
|
PA_CHANNEL_POSITION_REAR_LEFT, 0, 2,
|
|
PA_CHANNEL_POSITION_REAR_CENTER, 2, 2,
|
|
PA_CHANNEL_POSITION_REAR_RIGHT, 4, 2,
|
|
PA_CHANNEL_POSITION_LFE, 3, 2
|
|
};
|
|
|
|
QLayoutItem* w;
|
|
while ((w = placementGrid->takeAt(0))) {
|
|
if (w->widget() != m_icon) {
|
|
if (w->widget())
|
|
delete w->widget();
|
|
delete w;
|
|
}
|
|
}
|
|
placementGrid->addWidget(m_icon, 1, 2, Qt::AlignCenter);
|
|
int idx = deviceBox->currentIndex();
|
|
if (idx < 0)
|
|
return;
|
|
|
|
qint64 index = deviceBox->itemData(idx).toInt();
|
|
deviceInfo& sink_info = getDeviceInfo(index);
|
|
|
|
if (index < 0) {
|
|
playbackOrCaptureStack->setCurrentIndex(1);
|
|
m_VUTimer->start();
|
|
return;
|
|
}
|
|
|
|
playbackOrCaptureStack->setCurrentIndex(0);
|
|
m_VUTimer->stop();
|
|
|
|
for (int i = 0; i < 36; i += 3) {
|
|
pa_channel_position_t pos = (pa_channel_position_t)position_table[i];
|
|
// Check to see if we have this item in our current channel map.
|
|
bool have = false;
|
|
for (uint32_t j = 0; j < sink_info.channelMap.channels; ++j) {
|
|
if (sink_info.channelMap.map[j] == pos) {
|
|
have = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!have) {
|
|
continue;
|
|
}
|
|
|
|
KPushButton *btn = new TestSpeakerWidget(pos, m_Canberra, this);
|
|
placementGrid->addWidget(btn, position_table[i+2], position_table[i+1], Qt::AlignCenter);
|
|
}
|
|
}
|
|
|
|
void AudioSetup::_createMonitorStreamForSource(uint32_t source_idx)
|
|
{
|
|
if (m_VUStream) {
|
|
pa_stream_disconnect(m_VUStream);
|
|
m_VUStream = NULL;
|
|
}
|
|
|
|
char t[16];
|
|
pa_buffer_attr attr;
|
|
pa_sample_spec ss;
|
|
|
|
ss.channels = 1;
|
|
ss.format = PA_SAMPLE_FLOAT32;
|
|
ss.rate = 25;
|
|
|
|
memset(&attr, 0, sizeof(attr));
|
|
attr.fragsize = sizeof(float);
|
|
attr.maxlength = static_cast<quint32>(-1);
|
|
|
|
snprintf(t, sizeof(t), "%u", source_idx);
|
|
|
|
m_VUStream = pa_stream_new(s_context, "Peak detect", &ss, NULL);
|
|
if (!m_VUStream) {
|
|
kDebug() << "Failed to create monitoring stream";
|
|
return;
|
|
}
|
|
|
|
pa_stream_set_read_callback(m_VUStream, read_callback, this);
|
|
pa_stream_set_suspended_callback(m_VUStream, suspended_callback, this);
|
|
|
|
if (pa_stream_connect_record(m_VUStream, t, &attr, (pa_stream_flags_t) (PA_STREAM_DONT_MOVE|PA_STREAM_PEAK_DETECT|PA_STREAM_ADJUST_LATENCY)) < 0) {
|
|
kDebug() << "Failed to connect monitoring stream";
|
|
pa_stream_unref(m_VUStream);
|
|
m_VUStream = NULL;
|
|
}
|
|
}
|
|
|
|
quint32 AudioSetup::getCurrentSinkIndex()
|
|
{
|
|
int idx = deviceBox->currentIndex();
|
|
if (idx < 0)
|
|
return PA_INVALID_INDEX;
|
|
|
|
qint64 index = deviceBox->itemData(idx).toInt();
|
|
if (index >= 0)
|
|
return static_cast<quint32>(index);
|
|
|
|
return PA_INVALID_INDEX;
|
|
}
|
|
|
|
QDebug operator<<(QDebug dbg, const pa_context_state_t &state)
|
|
{
|
|
QString name;
|
|
switch (state) {
|
|
case PA_CONTEXT_UNCONNECTED: name = QLatin1String("Unconnected");
|
|
case PA_CONTEXT_CONNECTING: name = QLatin1String("Connecting");
|
|
case PA_CONTEXT_AUTHORIZING: name = QLatin1String("Authorizing");
|
|
case PA_CONTEXT_SETTING_NAME: name = QLatin1String("Setting Name");
|
|
case PA_CONTEXT_READY: name = QLatin1String("Ready");
|
|
case PA_CONTEXT_FAILED: name = QLatin1String("Failed");
|
|
case PA_CONTEXT_TERMINATED: name = QLatin1String("Terminated");
|
|
}
|
|
|
|
if (name.isEmpty())
|
|
name = QString("Unknown state(%0)").arg(state);
|
|
|
|
dbg.nospace() << name;
|
|
return dbg;
|
|
}
|
|
|
|
#include "audiosetup.moc"
|