Index: runtime/phonon/kcm/testspeakerwidget.cpp =================================================================== --- runtime/phonon/kcm/testspeakerwidget.cpp (revision 0) +++ runtime/phonon/kcm/testspeakerwidget.cpp (revision 1154776) @@ -0,0 +1,194 @@ +/* This file is part of the KDE project + Copyright (C) 2010 Colin Guthrie <cguthrie@mandriva.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 "testspeakerwidget.h" +#include "speakersetup.h" +#include <stdio.h> + +TestSpeakerWidget::TestSpeakerWidget(const pa_channel_position_t pos, ca_context *canberra, SpeakerSetup* ss) + : KPushButton(KIcon("preferences-desktop-sound"), "Test", ss) + , m_Ss(ss) + , m_Pos(pos) + , m_Canberra(canberra) +{ + setText(_positionName()); + connect(this, SIGNAL(clicked()), SLOT(clicked())); +} + +void TestSpeakerWidget::clicked() +{ + uint32_t sink_index = m_Ss->getCurrentSinkIndex(); + char dev[64]; + snprintf(dev, sizeof(dev), "%lu", (unsigned long) sink_index); + ca_context_change_device(m_Canberra, dev); + + const char* sound_name = _positionSoundName(); + ca_proplist* proplist; + ca_proplist_create(&proplist); + + ca_proplist_sets(proplist, CA_PROP_MEDIA_ROLE, "test"); + ca_proplist_sets(proplist, CA_PROP_MEDIA_NAME, _positionName().toAscii().constData()); + ca_proplist_sets(proplist, CA_PROP_CANBERRA_FORCE_CHANNEL, _positionAsString()); + ca_proplist_sets(proplist, CA_PROP_CANBERRA_ENABLE, "1"); + + ca_proplist_sets(proplist, CA_PROP_EVENT_ID, sound_name); + if (ca_context_play_full(m_Canberra, 0, proplist, NULL, NULL) < 0) { + // Try a different sound name. + ca_proplist_sets(proplist, CA_PROP_EVENT_ID, "audio-test-signal"); + if (ca_context_play_full(m_Canberra, 0, proplist, NULL, NULL) < 0) { + // Finaly try this... if this doesn't work, then stuff it. + ca_proplist_sets(proplist, CA_PROP_EVENT_ID, "bell-window-system"); + } + } + + ca_context_change_device(m_Canberra, NULL); + ca_proplist_destroy(proplist); +} + +const char* TestSpeakerWidget::_positionAsString() +{ + switch (m_Pos) + { + case PA_CHANNEL_POSITION_FRONT_LEFT: + return "front-left"; + + case PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER: + return "front-left-of-center"; + + case PA_CHANNEL_POSITION_FRONT_CENTER: + return "front-center"; + + case PA_CHANNEL_POSITION_MONO: + return "mono"; + + case PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER: + return "front-right-of-center"; + + case PA_CHANNEL_POSITION_FRONT_RIGHT: + return "front-right"; + + case PA_CHANNEL_POSITION_SIDE_LEFT: + return "side-left"; + + case PA_CHANNEL_POSITION_SIDE_RIGHT: + return "side-right"; + + case PA_CHANNEL_POSITION_REAR_LEFT: + return "rear-left"; + + case PA_CHANNEL_POSITION_REAR_CENTER: + return "rear-center"; + + case PA_CHANNEL_POSITION_REAR_RIGHT: + return "rear-right"; + + case PA_CHANNEL_POSITION_LFE: + return "lfe"; + + default: + break; + } + return "invalid"; +} + +QString TestSpeakerWidget::_positionName() +{ + switch (m_Pos) + { + case PA_CHANNEL_POSITION_FRONT_LEFT: + return i18n("Front Left"); + + case PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER: + return i18n("Front Left of Center"); + + case PA_CHANNEL_POSITION_FRONT_CENTER: + return i18n("Front Center"); + + case PA_CHANNEL_POSITION_MONO: + return i18n("Mono"); + + case PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER: + return i18n("Front Right of Center"); + + case PA_CHANNEL_POSITION_FRONT_RIGHT: + return i18n("Front Right"); + + case PA_CHANNEL_POSITION_SIDE_LEFT: + return i18n("Side Left"); + + case PA_CHANNEL_POSITION_SIDE_RIGHT: + return i18n("Side Right"); + + case PA_CHANNEL_POSITION_REAR_LEFT: + return i18n("Rear Left"); + + case PA_CHANNEL_POSITION_REAR_CENTER: + return i18n("Rear Center"); + + case PA_CHANNEL_POSITION_REAR_RIGHT: + return i18n("Rear Right"); + + case PA_CHANNEL_POSITION_LFE: + return i18n("Subwoofer"); + + default: + break; + } + return i18n("Unknown Channel"); +} + +const char* TestSpeakerWidget::_positionSoundName() +{ + switch (m_Pos) + { + case PA_CHANNEL_POSITION_FRONT_LEFT: + return "audio-channel-front-left"; + + case PA_CHANNEL_POSITION_FRONT_RIGHT: + return "audio-channel-front-right"; + + case PA_CHANNEL_POSITION_FRONT_CENTER: + return "audio-channel-front-center"; + + case PA_CHANNEL_POSITION_REAR_LEFT: + return "audio-channel-rear-left"; + + case PA_CHANNEL_POSITION_REAR_RIGHT: + return "audio-channel-rear-right"; + + case PA_CHANNEL_POSITION_REAR_CENTER: + return "audio-channel-rear-center"; + + case PA_CHANNEL_POSITION_LFE: + return "audio-channel-lfe"; + + case PA_CHANNEL_POSITION_SIDE_LEFT: + return "audio-channel-side-left"; + + case PA_CHANNEL_POSITION_SIDE_RIGHT: + return "audio-channel-side-right"; + default: + break; + } + return NULL; +} + + +#include "testspeakerwidget.moc" +// vim: sw=4 sts=4 et tw=100 Property changes on: runtime/phonon/kcm/testspeakerwidget.cpp ___________________________________________________________________ Added: svn:mime-type + text/plain Added: svn:eol-style + native Index: runtime/phonon/kcm/testspeakerwidget.h =================================================================== --- runtime/phonon/kcm/testspeakerwidget.h (revision 0) +++ runtime/phonon/kcm/testspeakerwidget.h (revision 1154776) @@ -0,0 +1,50 @@ +/* This file is part of the KDE project + Copyright (C) 2010 Colin Guthrie <cguthrie@mandriva.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. + +*/ + +#ifndef PHONON_TESTSPEAKERWIDGET_H +#define PHONON_TESTSPEAKERWIDGET_H + +#include <kpushbutton.h> + +#include <canberra.h> +#include <pulse/pulseaudio.h> + + +class SpeakerSetup; + +class TestSpeakerWidget: public KPushButton +{ + Q_OBJECT + public: + TestSpeakerWidget(const pa_channel_position_t pos, ca_context *canberra, SpeakerSetup* ss); + + private slots: + void clicked(); + + private: + QString _positionName(); + const char* _positionAsString(); + const char* _positionSoundName(); + + SpeakerSetup* m_Ss; + pa_channel_position_t m_Pos; + ca_context* m_Canberra; +}; + +#endif // PHONON_TESTSPEAKERWIDGET_H Property changes on: runtime/phonon/kcm/testspeakerwidget.h ___________________________________________________________________ Added: svn:eol-style + native Index: runtime/phonon/kcm/speakersetup.ui =================================================================== --- runtime/phonon/kcm/speakersetup.ui (revision 0) +++ runtime/phonon/kcm/speakersetup.ui (revision 1154776) @@ -0,0 +1,142 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <author>Matthias Kretz <kretz@kde.org</author> + <class>SpeakerSetup</class> + <widget class="QWidget" name="SpeakerSetup"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>537</width> + <height>465</height> + </rect> + </property> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <widget class="QGroupBox" name="hardwareGroupBox"> + <property name="title"> + <string>Hardware</string> + </property> + <layout class="QGridLayout" name="_3"> + <item row="3" column="1"> + <widget class="QComboBox" name="profileBox"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + </widget> + </item> + <item row="2" column="1"> + <widget class="KComboBox" name="cardBox"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + </widget> + </item> + <item row="3" column="0"> + <widget class="QLabel" name="profileLabel"> + <property name="text"> + <string>Profile</string> + </property> + <property name="buddy"> + <cstring>profileBox</cstring> + </property> + </widget> + </item> + <item row="2" column="0"> + <widget class="QLabel" name="cardLabel"> + <property name="text"> + <string>Sound Card</string> + </property> + <property name="buddy"> + <cstring>cardBox</cstring> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QGroupBox" name="outputGroupBox"> + <property name="title"> + <string>Output</string> + </property> + <layout class="QGridLayout" name="_2"> + <item row="0" column="1"> + <widget class="KComboBox" name="sinkBox"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + </widget> + </item> + <item row="1" column="0"> + <widget class="QLabel" name="portLabel"> + <property name="text"> + <string>Connector</string> + </property> + <property name="buddy"> + <cstring>portBox</cstring> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="QComboBox" name="portBox"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + </widget> + </item> + <item row="0" column="0"> + <widget class="QLabel" name="sinkLabel"> + <property name="text"> + <string>Sound Output</string> + </property> + <property name="buddy"> + <cstring>sinkBox</cstring> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QGroupBox" name="groupBox"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Expanding"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="title"> + <string>Speaker Placement and Testing</string> + </property> + <layout class="QVBoxLayout" name="_4"> + <item> + <layout class="QGridLayout" name="placementGrid"/> + </item> + </layout> + </widget> + </item> + </layout> + </widget> + <customwidgets> + <customwidget> + <class>KComboBox</class> + <extends>QComboBox</extends> + <header>kcombobox.h</header> + </customwidget> + </customwidgets> + <resources/> + <connections/> +</ui> Index: runtime/phonon/kcm/speakersetup.cpp =================================================================== --- runtime/phonon/kcm/speakersetup.cpp (revision 0) +++ runtime/phonon/kcm/speakersetup.cpp (revision 1154776) @@ -0,0 +1,664 @@ +/* This file is part of the KDE project + Copyright (C) 2010 Colin Guthrie <cguthrie@mandriva.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 "speakersetup.h" +#include "testspeakerwidget.h" +#include <kgenericfactory.h> +#include <kconfiggroup.h> +#include <kaboutdata.h> +#include <kicon.h> + +#include <glib.h> +#include <pulse/xmalloc.h> +#include <pulse/glib-mainloop.h> +#include <QtCore/QAbstractEventDispatcher> +#include <QtCore/QFileInfo> +#include <QtCore/QDir> +#include <QtDBus/QDBusConnection> +#include <QtDBus/QDBusInterface> + +#define SS_DEFAULT_ICON "audio-card" + + +static pa_glib_mainloop *s_mainloop = NULL; +static pa_context *s_context = NULL; + +typedef struct { + uint32_t index; + QString name; + QString icon; + QMap<uint32_t,QPair<QString,QString> > profiles; + QString activeProfile; +} cardInfo; + +QMap<uint32_t,cardInfo> s_Cards; + +typedef struct { + uint32_t index; + uint32_t cardIndex; + QString name; + QString icon; + pa_channel_map channelMap; + QMap<uint32_t,QPair<QString,QString> > ports; + QString activePort; +} sinkInfo; + +QMap<uint32_t,sinkInfo> s_Sinks; + +static int debugLevel() { + static int level = -1; + if (level < 1) { + level = 0; + QString pulseenv = qgetenv("PHONON_PULSEAUDIO_DEBUG"); + int l = pulseenv.toInt(); + if (l > 0) + level = (l > 2 ? 2 : l); + } + return level; +} + +static void logMessage(const QString &message, int priority = 2, QObject *obj=0); +static void logMessage(const QString &message, int priority, QObject *obj) +{ + if (debugLevel() > 0) { + QString output; + if (obj) { + // Strip away namespace from className + QString className(obj->metaObject()->className()); + int nameLength = className.length() - className.lastIndexOf(':') - 1; + className = className.right(nameLength); + output.sprintf("%s %s (%s %p)", message.toLatin1().constData(), + obj->objectName().toLatin1().constData(), + className.toLatin1().constData(), obj); + } + else { + output = message; + } + if (priority <= debugLevel()) { + qDebug() << QString("PulseSupport(%1): %2").arg(priority).arg(output); + } + } +} + + +static void card_cb(pa_context *c, const pa_card_info *i, int eol, void *userdata) { + Q_ASSERT(c); + Q_ASSERT(userdata); + + SpeakerSetup* ss = static_cast<SpeakerSetup*>(userdata); + + if (eol < 0) { + if (pa_context_errno(c) == PA_ERR_NOENTITY) + return; + + logMessage(QString("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); + Q_ASSERT(userdata); + + SpeakerSetup* ss = static_cast<SpeakerSetup*>(userdata); + + if (eol < 0) { + if (pa_context_errno(c) == PA_ERR_NOENTITY) + return; + + logMessage(QString("Sink callback failure")); + return; + } + + if (eol > 0) { + ss->updateIndependantDevices(); + ss->updateFromPulse(); + return; + } + + Q_ASSERT(i); + ss->updateSink(i); +} + +static void subscribe_cb(pa_context *c, pa_subscription_event_type_t t, uint32_t index, void *userdata) { + Q_ASSERT(c); + Q_ASSERT(userdata); + + SpeakerSetup* ss = static_cast<SpeakerSetup*>(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 *o; + if (!(o = pa_context_get_card_info_by_index(c, index, card_cb, ss))) { + logMessage(QString("pa_context_get_card_info_by_index() failed")); + return; + } + pa_operation_unref(o); + } + break; + + case PA_SUBSCRIPTION_EVENT_SINK: + if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) { + ss->removeSink(index); + } else { + pa_operation *o; + if (!(o = pa_context_get_sink_info_by_index(c, index, sink_cb, ss))) { + logMessage(QString("pa_context_get_sink_info_by_index() failed")); + return; + } + pa_operation_unref(o); + } + break; + } +} + + +static const char* statename(pa_context_state_t state) +{ + switch (state) + { + case PA_CONTEXT_UNCONNECTED: return "Unconnected"; + case PA_CONTEXT_CONNECTING: return "Connecting"; + case PA_CONTEXT_AUTHORIZING: return "Authorizing"; + case PA_CONTEXT_SETTING_NAME: return "Setting Name"; + case PA_CONTEXT_READY: return "Ready"; + case PA_CONTEXT_FAILED: return "Failed"; + case PA_CONTEXT_TERMINATED: return "Terminated"; + } + + static QString unknown; + unknown = QString("Unknown state: %0").arg(state); + return unknown.toAscii().constData(); +} + +static void context_state_callback(pa_context *c, void *userdata) +{ + Q_ASSERT(c); + Q_ASSERT(userdata); + + SpeakerSetup* ss = static_cast<SpeakerSetup*>(userdata); + + logMessage(QString("context_state_callback %1").arg(statename(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), NULL, NULL))) { + logMessage(QString("pa_context_subscribe() failed")); + return; + } + pa_operation_unref(o); + + if (!(o = pa_context_get_card_info_list(c, card_cb, ss))) { + logMessage(QString("pa_context_get_card_info_list() failed")); + return; + } + pa_operation_unref(o); + + if (!(o = pa_context_get_sink_info_list(c, sink_cb, ss))) { + logMessage(QString("pa_context_get_sink_info_list() failed")); + return; + } + pa_operation_unref(o); + + ss->load(); + + } else if (!PA_CONTEXT_IS_GOOD(state)) { + /// @todo Deal with reconnection... + //logMessage(QString("Connection to PulseAudio lost: %1").arg(pa_strerror(pa_context_errno(c)))); + + // If this is our probe phase, exit our context immediately + if (s_context != c) + pa_context_disconnect(c); + else { + pa_context_unref(s_context); + s_context = NULL; + //QTimer::singleShot(50, PulseSupport::getInstance(), SLOT(connectToDaemon())); + } + } +} + + +SpeakerSetup::SpeakerSetup(QWidget *parent) + : QWidget(parent) + , m_OutstandingRequests(2) + , m_Canberra(NULL) +{ + setupUi(this); + + cardLabel->setEnabled(false); + cardBox->setEnabled(false); + profileLabel->setVisible(false); + profileBox->setVisible(false); + + sinkLabel->setEnabled(false); + sinkBox->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); + const QFileInfo fi(QDir(QDir::homePath()), ".face.icon"); + if (fi.exists()) + m_Icon->setPixmap(QPixmap(fi.absoluteFilePath()).scaled(KIconLoader::SizeHuge, KIconLoader::SizeHuge, Qt::KeepAspectRatio)); + else + 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(sinkBox, SIGNAL(currentIndexChanged(int)), SLOT(sinkChanged())); + connect(portBox, SIGNAL(currentIndexChanged(int)), SLOT(portChanged())); + + // We require a glib event loop + if (QLatin1String(QAbstractEventDispatcher::instance()->metaObject()->className()) + != "QGuiEventDispatcherGlib") { + logMessage("Disabling PulseAudio integration for lack of GLib event loop."); + return; + } + + s_mainloop = pa_glib_mainloop_new(NULL); + Q_ASSERT(s_mainloop); + + pa_mainloop_api *api = pa_glib_mainloop_get_api(s_mainloop); + + s_context = pa_context_new(api, "kspeakersetup"); + int rv; + rv = pa_context_connect(s_context, NULL, PA_CONTEXT_NOFAIL, 0); + Q_ASSERT(rv >= 0); + + pa_context_set_state_callback(s_context, &context_state_callback, this); + + rv = ca_context_create(&m_Canberra); + Q_ASSERT(rv >= 0); +} + +SpeakerSetup::~SpeakerSetup() +{ + ca_context_destroy(m_Canberra); + pa_context_unref(s_context); + s_context = NULL; + pa_glib_mainloop_free(s_mainloop); + s_mainloop = NULL; +} + +void SpeakerSetup::load() +{ +} + +void SpeakerSetup::save() +{ +} + +void SpeakerSetup::defaults() +{ +} + +void SpeakerSetup::updateCard(const pa_card_info* i) +{ + cardInfo info; + info.index = i->index; + + const char* description = pa_proplist_gets(i->proplist, PA_PROP_DEVICE_DESCRIPTION); + info.name = description ? QString::fromUtf8(description) : i->name; + + const char* icon = pa_proplist_gets(i->proplist, PA_PROP_DEVICE_ICON_NAME); + info.icon = icon ? icon : SS_DEFAULT_ICON; + + for (uint32_t j = 0; j < i->n_profiles; ++j) + info.profiles[i->profiles[j].priority] = QPair<QString,QString>(i->profiles[j].name, QString::fromUtf8(i->profiles[j].description)); + if (i->active_profile) + info.activeProfile = i->active_profile->name; + + + bool bs = cardBox->blockSignals(true); + if (s_Cards.contains(i->index)) { + int idx = cardBox->findData(i->index); + if (idx >= 0) { + cardBox->setItemIcon(idx, KIcon(info.icon)); + cardBox->setItemText(idx, info.name); + } + } + else + cardBox->addItem(KIcon(info.icon), info.name, i->index); + cardBox->blockSignals(bs); + + s_Cards[i->index] = info; + + cardChanged(); + + logMessage(QString("Got info about card %1").arg(info.name)); +} + +void SpeakerSetup::removeCard(uint32_t index) +{ + s_Cards.remove(index); + updateFromPulse(); + int idx = cardBox->findData(index); + if (idx >= 0) + cardBox->removeItem(idx); +} + +void SpeakerSetup::updateSink(const pa_sink_info* i) +{ + sinkInfo 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 = sinkBox->currentIndex(); + if (idx >= 0) { + uint32_t sink_index = sinkBox->itemData(idx).toUInt(); + if (sink_index == i->index) { + bool bs = portBox->blockSignals(true); + portBox->setCurrentIndex(portBox->findData(info.activePort)); + portBox->blockSignals(bs); + } + } + } + + logMessage(QString("Got info about sink %1").arg(info.name)); +} + +void SpeakerSetup::removeSink(uint32_t index) +{ + s_Sinks.remove(index); + updateIndependantDevices(); + updateFromPulse(); + int idx = sinkBox->findData(index); + if (idx >= 0) + sinkBox->removeItem(idx); +} + +void SpeakerSetup::updateFromPulse() +{ + 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); + emit ready(); + } + } + + if (!m_OutstandingRequests) { + if (!s_Cards.size() && !s_Sinks.size()) { + cardLabel->setEnabled(false); + cardBox->setEnabled(false); + profileLabel->setVisible(false); + profileBox->setVisible(false); + + sinkLabel->setEnabled(false); + sinkBox->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() && !sinkBox->isEnabled()) { + sinkLabel->setEnabled(true); + sinkBox->setEnabled(true); + sinkChanged(); + } + } +} + +void SpeakerSetup::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]; + bool bs = profileBox->blockSignals(true); + profileBox->clear(); + for (QMap<uint32_t, QPair<QString,QString> >::iterator it = card_info.profiles.begin(); it != card_info.profiles.end(); ++it) + profileBox->insertItem(0, it.value().second, it.value().first); + profileBox->setCurrentIndex(profileBox->findData(card_info.activeProfile)); + profileBox->blockSignals(bs); + } + profileLabel->setVisible(show_profiles); + profileBox->setVisible(show_profiles); + + + bool bs = sinkBox->blockSignals(true); + sinkBox->clear(); + for (QMap<uint32_t,sinkInfo>::iterator it = s_Sinks.begin(); it != s_Sinks.end(); ++it) { + if (it->cardIndex == card_index) + sinkBox->addItem(KIcon(it->icon), it->name, it->index); + } + sinkBox->blockSignals(bs); + + outputGroupBox->setEnabled(!!sinkBox->count()); + + sinkChanged(); + + logMessage(QString("Doing update %1").arg(cardBox->currentIndex())); + + emit changed(); +} + +void SpeakerSetup::profileChanged() +{ + uint32_t card_index = cardBox->itemData(cardBox->currentIndex()).toUInt(); + Q_ASSERT(PA_INVALID_INDEX != card_index); + + QString profile = profileBox->itemData(profileBox->currentIndex()).toString(); + logMessage(QString("Changing profile to %1").arg(profile)); + + cardInfo &card_info = s_Cards[card_index]; + Q_ASSERT(card_info.profiles.size()); + + pa_operation *o; + if (!(o = pa_context_set_card_profile_by_index(s_context, card_index, profile.toAscii().constData(), NULL, NULL))) + logMessage(QString("pa_context_set_card_profile_by_name() failed")); + else + pa_operation_unref(o); + + emit changed(); +} + +void SpeakerSetup::updateIndependantDevices() +{ + // Should we display the "Independant Devices" drop down? + // Count all the sinks without cards + bool showID = false; + for (QMap<uint32_t,sinkInfo>::iterator it = s_Sinks.begin(); it != s_Sinks.end(); ++it) { + if (PA_INVALID_INDEX == it->cardIndex) { + showID = true; + break; + } + } + + bool haveID = (PA_INVALID_INDEX == cardBox->itemData(0).toUInt()); + + logMessage(QString("Want ID: %1; Have ID: %2").arg(showID?"Yes":"No").arg(haveID?"Yes":"No")); + + bool bs = cardBox->blockSignals(true); + if (haveID && !showID) + cardBox->removeItem(0); + else if (!haveID && showID) + cardBox->insertItem(0, KIcon(SS_DEFAULT_ICON), "Independent Devices", PA_INVALID_INDEX); + cardBox->blockSignals(bs); +} + +void SpeakerSetup::sinkChanged() +{ + int idx = sinkBox->currentIndex(); + if (idx < 0) { + portLabel->setVisible(false); + portBox->setVisible(false); + _updatePlacementTester(); + return; + } + uint32_t sink_index = sinkBox->itemData(idx).toUInt(); + Q_ASSERT(s_Sinks.contains(sink_index)); + sinkInfo &sink_info = s_Sinks[sink_index]; + logMessage(QString("Updating ports for sink '%1' (%2 ports available)").arg(sink_info.name).arg(sink_info.ports.size())); + + bool show_ports = !!sink_info.ports.size(); + if (show_ports) { + bool bs = portBox->blockSignals(true); + portBox->clear(); + for (QMap<uint32_t, QPair<QString,QString> >::iterator it = sink_info.ports.begin(); it != sink_info.ports.end(); ++it) + portBox->insertItem(0, it.value().second, it.value().first); + portBox->setCurrentIndex(portBox->findData(sink_info.activePort)); + portBox->blockSignals(bs); + } + portLabel->setVisible(show_ports); + portBox->setVisible(show_ports); + + if (sinkBox->currentIndex() >= 0) + _updatePlacementTester(); + + emit changed(); +} + +void SpeakerSetup::portChanged() +{ + uint32_t sink_index = sinkBox->itemData(sinkBox->currentIndex()).toUInt(); + Q_ASSERT(PA_INVALID_INDEX != sink_index); + + QString port = portBox->itemData(portBox->currentIndex()).toString(); + logMessage(QString("Changing port to %1").arg(port)); + + sinkInfo &sink_info = s_Sinks[sink_index]; + Q_ASSERT(sink_info.ports.size()); + + pa_operation *o; + if (!(o = pa_context_set_sink_port_by_index(s_context, sink_index, port.toAscii().constData(), NULL, NULL))) + logMessage(QString("pa_context_set_sink_port_by_index() failed")); + else + pa_operation_unref(o); + + emit changed(); +} + +void SpeakerSetup::_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 = sinkBox->currentIndex(); + if (idx < 0) + return; + + uint32_t sink_index = sinkBox->itemData(idx).toUInt(); + Q_ASSERT(s_Sinks.contains(sink_index)); + sinkInfo& sink_info = s_Sinks[sink_index]; + + 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);//KPushButton(KIcon("audio-card"), (name ? name : "Unknown Channel"), this); + placementGrid->addWidget(btn, position_table[i+2], position_table[i+1], Qt::AlignCenter); + } + +} + +uint32_t SpeakerSetup::getCurrentSinkIndex() +{ + int idx = sinkBox->currentIndex(); + if (idx < 0) + return PA_INVALID_INDEX; + + return sinkBox->itemData(idx).toUInt(); +} + + +#include "speakersetup.moc" +// vim: sw=4 sts=4 et tw=100 Property changes on: runtime/phonon/kcm/speakersetup.cpp ___________________________________________________________________ Added: svn:mime-type + text/plain Added: svn:eol-style + native Index: runtime/phonon/kcm/speakersetup.h =================================================================== --- runtime/phonon/kcm/speakersetup.h (revision 0) +++ runtime/phonon/kcm/speakersetup.h (revision 1154776) @@ -0,0 +1,70 @@ +/* This file is part of the KDE project + Copyright (C) 2010 Colin Guthrie <cguthrie@mandriva.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. + +*/ + +#ifndef PHONON_SPEAKERSETUP_H +#define PHONON_SPEAKERSETUP_H + +#include <canberra.h> +#include <pulse/pulseaudio.h> + +#include "ui_speakersetup.h" +#define KDE3_SUPPORT +#include <kcmodule.h> +#undef KDE3_SUPPORT +#include <kconfig.h> + +//class QLabel; + +class SpeakerSetup : public QWidget, private Ui::SpeakerSetup +{ + Q_OBJECT + public: + SpeakerSetup(QWidget *parent = 0); + ~SpeakerSetup(); + + void load(); + void save(); + void defaults(); + uint32_t getCurrentSinkIndex(); + void updateCard(const pa_card_info*); + void removeCard(uint32_t idx); + void updateSink(const pa_sink_info*); + void removeSink(uint32_t idx); + void updateFromPulse(); + void updateIndependantDevices(); + + public Q_SLOTS: + void cardChanged(); + void profileChanged(); + void sinkChanged(); + void portChanged(); + + Q_SIGNALS: + void changed(); + void ready(); + + private: + void _updatePlacementTester(); + + QLabel* m_Icon; + int m_OutstandingRequests; + ca_context* m_Canberra; +}; + +#endif // PHONON_SPEAKERSETUP_H Property changes on: runtime/phonon/kcm/speakersetup.h ___________________________________________________________________ Added: svn:eol-style + native Index: runtime/phonon/kcm/main.cpp =================================================================== --- runtime/phonon/kcm/main.cpp (revision 1154775) +++ runtime/phonon/kcm/main.cpp (revision 1154776) @@ -1,5 +1,6 @@ /* This file is part of the KDE project Copyright (C) 2006-2007 Matthias Kretz <kretz@kde.org> + Copyright (C) 2010 Colin Guthrie <cguthrie@mandriva.org> This program is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public @@ -28,6 +29,10 @@ #include "devicepreference.h" #include "backendselection.h" +#ifdef HAVE_PULSEAUDIO +# include "speakersetup.h" +#endif + K_PLUGIN_FACTORY(PhononKcmFactory, registerPlugin<PhononKcm>();) K_EXPORT_PLUGIN(PhononKcmFactory("kcm_phonon")) @@ -39,23 +44,33 @@ KDE_VERSION_STRING, KLocalizedString(), KAboutData::License_GPL, ki18n("Copyright 2006 Matthias Kretz")); about->addAuthor(ki18n("Matthias Kretz"), KLocalizedString(), "kretz@kde.org"); + about->addAuthor(ki18n("Colin Guthrie"), KLocalizedString(), "cguthrie@mandriva.org"); setAboutData(about); setLayout(new QHBoxLayout); layout()->setMargin(0); layout()->setSpacing(0); - KTabWidget *tabs = new KTabWidget(this); - layout()->addWidget(tabs); + m_tabs = new KTabWidget(this); + layout()->addWidget(m_tabs); m_devicePreferenceWidget = new DevicePreference(this); - tabs->addTab(m_devicePreferenceWidget, i18n("Device Preference")); + m_tabs->addTab(m_devicePreferenceWidget, i18n("Device Preference")); m_backendSelection = new BackendSelection(this); - tabs->addTab(m_backendSelection, i18n("Backend")); + m_tabs->addTab(m_backendSelection, i18n("Backend")); + load(); connect(m_backendSelection, SIGNAL(changed()), SLOT(changed())); connect(m_devicePreferenceWidget, SIGNAL(changed()), SLOT(changed())); + setButtons( KCModule::Default|KCModule::Apply|KCModule::Help ); + +#ifdef HAVE_PULSEAUDIO + m_speakerSetup = new SpeakerSetup(this); + m_speakerSetup->setVisible(false); + connect(m_speakerSetup, SIGNAL(ready()), SLOT(speakerSetupReady())); + connect(m_speakerSetup, SIGNAL(changed()), SLOT(changed())); +#endif } void PhononKcm::load() @@ -76,5 +91,13 @@ m_backendSelection->defaults(); } +#ifdef HAVE_PULSEAUDIO +void PhononKcm::speakerSetupReady() +{ + m_tabs->insertTab(1, m_speakerSetup, i18n("Speaker Setup")); + emit changed(); +} +#endif + #include "main.moc" // vim: ts=4 Index: runtime/phonon/kcm/main.h =================================================================== --- runtime/phonon/kcm/main.h (revision 1154775) +++ runtime/phonon/kcm/main.h (revision 1154776) @@ -25,6 +25,11 @@ class DevicePreference; class BackendSelection; +#ifdef HAVE_PULSEAUDIO +class SpeakerSetup; +#endif +class KTabWidget; + class PhononKcm : public KCModule { Q_OBJECT @@ -35,9 +40,18 @@ void save(); void defaults(); +#ifdef HAVE_PULSEAUDIO + private Q_SLOTS: + void speakerSetupReady(); +#endif + private: + KTabWidget* m_tabs; DevicePreference *m_devicePreferenceWidget; BackendSelection *m_backendSelection; +#ifdef HAVE_PULSEAUDIO + SpeakerSetup* m_speakerSetup; +#endif }; #endif // MAIN_H Index: runtime/phonon/kcm/CMakeLists.txt =================================================================== --- runtime/phonon/kcm/CMakeLists.txt (revision 1154775) +++ runtime/phonon/kcm/CMakeLists.txt (revision 1154776) @@ -1,3 +1,11 @@ +set(PULSEAUDIO_MINIMUM_VERSION "0.9.16") +macro_optional_find_package(PulseAudio) +macro_log_feature(PULSEAUDIO_FOUND "PulseAudio" "PulseAudio Audio Server" "http://www.pulseaudio.org/" FALSE "0.9.16" "libpulse is needed for speaker setup GUI") +find_package(GLIB2) + +pkg_check_modules(CANBERRA libcanberra) +macro_log_feature(CANBERRA_FOUND "libcanberra" "libcanberra audio library" "http://0pointer.de/lennart/projects/libcanberra/" FALSE "" "libcanberra is needed for speaker setup GUI") + add_subdirectory(xine) set(kcmphonon_SRCS main.cpp devicepreference.cpp backendselection.cpp) @@ -2,6 +10,17 @@ kde4_add_ui_files(kcmphonon_SRCS devicepreference.ui backendselection.ui) +set(kcmphonon_LIBS ${KDE4_PHONON_LIBS} ${KDE4_KCMUTILS_LIBS} ${KDE4_KIO_LIBS}) +if(GLIB2_FOUND AND PULSEAUDIO_FOUND AND CANBERRA_FOUND) + add_definitions(-DHAVE_PULSEAUDIO) + + set(kcmphonon_SRCS ${kcmphonon_SRCS} speakersetup.cpp testspeakerwidget.cpp) + kde4_add_ui_files(kcmphonon_SRCS speakersetup.ui) + + include_directories(${GLIB2_INCLUDE_DIR} ${PULSEAUDIO_INCLUDE_DIR} ${CANBERRA_INCLUDE_DIRS}) + + set(kcmphonon_LIBS ${kcmphonon_LIBS} ${GLIB2_LIBRARIES} ${PULSEAUDIO_LIBRARY} ${PULSEAUDIO_MAINLOOP_LIBRARY} ${CANBERRA_LIBRARIES}) +endif(GLIB2_FOUND AND PULSEAUDIO_FOUND AND CANBERRA_FOUND) + kde4_add_plugin(kcm_phonon ${kcmphonon_SRCS}) -target_link_libraries(kcm_phonon ${KDE4_PHONON_LIBS} ${KDE4_KCMUTILS_LIBS} - ${KDE4_KIO_LIBS}) +target_link_libraries(kcm_phonon ${kcmphonon_LIBS})