From 487c3a0ade9c96f294acb4ae987a8996d2629743 Mon Sep 17 00:00:00 2001 From: Rex Dieter Date: Jan 11 2010 17:00:05 +0000 Subject: - respin kmix_pa patch --- diff --git a/kdemultimedia.spec b/kdemultimedia.spec index 428f46d..2bb7f4a 100644 --- a/kdemultimedia.spec +++ b/kdemultimedia.spec @@ -2,7 +2,7 @@ Name: kdemultimedia Epoch: 6 Version: 4.3.90 -Release: 1%{?dist} +Release: 2%{?dist} Summary: KDE Multimedia applications Group: Applications/Multimedia @@ -16,7 +16,7 @@ Patch1: kdemultimedia-4.3.75-nomplayerthumbs.patch Patch2: kdemultimedia-4.3.75-kscd_doc.patch # git clone git://colin.guthr.ie/kdemultimedia # git diff master..remotes/origin/pulse > kmix_pa-.patch -Patch3: kmix_pa-20100106.patch +Patch3: kmix_pa-20100111.patch ## upstream patches @@ -176,6 +176,9 @@ fi %changelog +* Mon Jan 11 2010 Rex Dieter - 4.3.90-2 +- respin kmix_pa patch + * Wed Jan 06 2010 Rex Dieter - 4.3.90-1 - kde-4.3.90 (4.4rc1) diff --git a/kmix_pa-20100106.patch b/kmix_pa-20100106.patch deleted file mode 100644 index 74f3cc4..0000000 --- a/kmix_pa-20100106.patch +++ /dev/null @@ -1,1410 +0,0 @@ -diff --git a/kmix/kmix-platforms.cpp b/kmix/kmix-platforms.cpp -index f7b8c9a..5f61989 100644 ---- a/kmix/kmix-platforms.cpp -+++ b/kmix/kmix-platforms.cpp -@@ -129,6 +129,10 @@ MixerFactory g_mixerFactories[] = { - { IRIX_getMixer, IRIX_getDriverName }, - #endif - -+#if defined(PULSE_MIXER) -+ { PULSE_getMixer, PULSE_getDriverName }, -+#endif -+ - #if defined(ALSA_MIXER) - { ALSA_getMixer, ALSA_getDriverName }, - #endif -@@ -145,10 +149,6 @@ MixerFactory g_mixerFactories[] = { - { HPUX_getMixer, HPUX_getDriverName }, - #endif - --#if defined(PULSE_MIXER) -- { PULSE_getMixer, PULSE_getDriverName }, --#endif -- - { 0, 0 } - }; - -diff --git a/kmix/kmix.cpp b/kmix/kmix.cpp -index 30cc5d4..b1d0743 100644 ---- a/kmix/kmix.cpp -+++ b/kmix/kmix.cpp -@@ -49,6 +49,7 @@ - #include - - // KMix -+#include "guiprofile.h" - #include "mixertoolbox.h" - #include "kmix.h" - #include "kmixdevicemanager.h" -@@ -399,11 +400,25 @@ void KMixWindow::recreateGUIwithoutSavingView() - */ - void KMixWindow::recreateGUI(bool saveConfig) - { -- saveViewConfig(); // save the state before recreating -+ // Find out which of the tabs is currently selected for restoration -+ int current_tab = -1; -+ if (m_wsMixers) -+ current_tab = m_wsMixers->currentIndex(); -+ -+ if (saveConfig) -+ saveViewConfig(); // save the state before recreating -+ -+ // Before clearing the mixer widgets, we must increase the refcount on the guiprof to save it deleting the ViewBase object. -+ if ( Mixer::mixers().count() > 0 ) -+ for (int i=0; iselectProfile((Mixer::mixers())[i])->increaseRefcount(); - clearMixerWidgets(); -+ - if ( Mixer::mixers().count() > 0 ) { - for (int i=0; iselectProfile(mixer)->decreaseRefcount(); - addMixerWidget(mixer->id()); - } - bool dockingSucceded = updateDocking(); -@@ -415,6 +430,10 @@ void KMixWindow::recreateGUI(bool saveConfig) - updateDocking(); // -<- removes the DockIcon - hide(); - } -+ -+ if (current_tab >= 0) { -+ m_wsMixers->setCurrentIndex(current_tab); -+ } - } - - -diff --git a/kmix/kmixerwidget.cpp b/kmix/kmixerwidget.cpp -index b7fdb2a..ad126b6 100644 ---- a/kmix/kmixerwidget.cpp -+++ b/kmix/kmixerwidget.cpp -@@ -92,6 +92,7 @@ void KMixerWidget::createLayout(ViewBase::ViewFlags vflags) - // delete old objects - if( m_balanceSlider ) { - delete m_balanceSlider; -+ m_balanceSlider = 0; - } - if( m_topLayout ) { - delete m_topLayout; -diff --git a/kmix/mixer.cpp b/kmix/mixer.cpp -index 82bfba4..caf056e 100644 ---- a/kmix/mixer.cpp -+++ b/kmix/mixer.cpp -@@ -150,6 +150,7 @@ bool Mixer::openIfValid() { - setLocalMasterMD(noMaster); // no master - } - connect( _mixerBackend, SIGNAL(controlChanged()), SLOT(controlChangedForwarder()) ); -+ connect( _mixerBackend, SIGNAL(controlsReconfigured(int)), SLOT(controlsReconfiguredForwarder(int)) ); - - m_dbusName = "/Mixer" + QString::number(_mixerBackend->m_devnum); - QDBusConnection::sessionBus().registerObject(m_dbusName, this); -@@ -163,6 +164,11 @@ void Mixer::controlChangedForwarder() - emit controlChanged(); - } - -+void Mixer::controlsReconfiguredForwarder(int mixerTabIndex) -+{ -+ emit controlsReconfigured(mixerTabIndex); -+} -+ - /** - * Closes the mixer. - * Also, stops the polling timer. -diff --git a/kmix/mixer.h b/kmix/mixer.h -index e0cdf4d..e193b16 100644 ---- a/kmix/mixer.h -+++ b/kmix/mixer.h -@@ -166,6 +166,7 @@ class Mixer : public QObject - signals: - void newBalance( Volume& ); - void controlChanged(void); -+ void controlsReconfigured(int mixerTabIndex); - - protected: - int m_balance; // from -100 (just left) to 100 (just right) -@@ -173,6 +174,7 @@ class Mixer : public QObject - - private slots: - void controlChangedForwarder(); -+ void controlsReconfiguredForwarder(int mixerTabIndex); - - public: - static QList& mixers(); -diff --git a/kmix/mixer_alsa9.cpp b/kmix/mixer_alsa9.cpp -index 5f12bdc..1799535 100644 ---- a/kmix/mixer_alsa9.cpp -+++ b/kmix/mixer_alsa9.cpp -@@ -420,10 +420,10 @@ Volume* Mixer_ALSA::addVolume(snd_mixer_elem_t *elem, bool capture) - } - - -- // Chek if this control has at least one volume control -+ // Check if this control has at least one volume control - bool hasVolume = (chn != Volume::MNONE); - -- // Chek if a appropriate switch is present (appropriate means, based o nthe "capture" parameer) -+ // Check if a appropriate switch is present (appropriate means, based o nthe "capture" parameer) - bool hasCommonSwitch = snd_mixer_selem_has_common_switch ( elem ); - - bool hasSwitch = hasCommonSwitch | -diff --git a/kmix/mixer_backend.h b/kmix/mixer_backend.h -index 26e9557..3a7b5d7 100644 ---- a/kmix/mixer_backend.h -+++ b/kmix/mixer_backend.h -@@ -131,6 +131,7 @@ protected: - - signals: - void controlChanged( void ); -+ void controlsReconfigured( int mixerTabIndex ); - - protected slots: - virtual void readSetFromHW(); -diff --git a/kmix/mixer_pulse.cpp b/kmix/mixer_pulse.cpp -index 694b9a9..62bf12a 100644 ---- a/kmix/mixer_pulse.cpp -+++ b/kmix/mixer_pulse.cpp -@@ -20,12 +20,658 @@ - */ - - #include -+#include - - #include "mixer_pulse.h" - #include "mixer.h" - -+#include -+#include -+ -+ -+#define KMIXPA_PLAYBACK 0 -+#define KMIXPA_CAPTURE 1 -+#define KMIXPA_APP_PLAYBACK 2 -+#define KMIXPA_APP_CAPTURE 3 -+#define KMIXPA_WIDGET_MAX KMIXPA_APP_CAPTURE -+ -+static unsigned int refcount = 0; - static pa_context *context = NULL; - static pa_glib_mainloop *mainloop = NULL; -+static QEventLoop *s_connectionEventloop = NULL; -+static enum { UNKNOWN, ACTIVE, INACTIVE } s_pulseActive = UNKNOWN; -+static int s_OutstandingRequests = 0; -+ -+QMap s_Mixers; -+ -+typedef QMap devmap; -+static devmap outputDevices; -+static devmap captureDevices; -+static QMap clients; -+static devmap outputStreams; -+static devmap captureStreams; -+static devmap outputRoles; -+ -+static void dec_outstanding() { -+ if (s_OutstandingRequests <= 0) -+ return; -+ -+ if (--s_OutstandingRequests <= 0) -+ { -+ s_pulseActive = ACTIVE; -+ if (s_connectionEventloop) { -+ s_connectionEventloop->exit(0); -+ s_connectionEventloop = NULL; -+ -+ // If we have no devices then we consider PA to be 'INACTIVE' -+ if (outputDevices.isEmpty() && captureDevices.isEmpty()) -+ s_pulseActive = INACTIVE; -+ else -+ s_pulseActive = ACTIVE; -+ } -+ } -+} -+ -+static void translateMasksAndMaps(devinfo& dev) -+{ -+ dev.chanMask = Volume::MNONE; -+ dev.chanIDs.clear(); -+ -+ if (dev.channel_map.channels != dev.volume.channels) { -+ kError() << "Hiddeous Channel mixup map says " << dev.channel_map.channels << ", volume says: " << dev.volume.channels; -+ return; -+ } -+ if (1 == dev.channel_map.channels && PA_CHANNEL_POSITION_MONO == dev.channel_map.map[0]) { -+ // We just use the left channel to represent this. -+ dev.chanMask = (Volume::ChannelMask)( dev.chanMask | Volume::MLEFT); -+ dev.chanIDs[0] = Volume::LEFT; -+ } else { -+ for (uint8_t i = 0; i < dev.channel_map.channels; ++i) { -+ switch (dev.channel_map.map[i]) { -+ case PA_CHANNEL_POSITION_MONO: -+ kWarning(67100) << "Channel Map contains a MONO element but has >1 channel - we can't handle this."; -+ return; -+ -+ case PA_CHANNEL_POSITION_FRONT_LEFT: -+ dev.chanMask = (Volume::ChannelMask)( dev.chanMask | Volume::MLEFT); -+ dev.chanIDs[i] = Volume::LEFT; -+ break; -+ case PA_CHANNEL_POSITION_FRONT_RIGHT: -+ dev.chanMask = (Volume::ChannelMask)( dev.chanMask | Volume::MRIGHT); -+ dev.chanIDs[i] = Volume::RIGHT; -+ break; -+ case PA_CHANNEL_POSITION_FRONT_CENTER: -+ dev.chanMask = (Volume::ChannelMask)( dev.chanMask | Volume::MCENTER); -+ dev.chanIDs[i] = Volume::CENTER; -+ break; -+ case PA_CHANNEL_POSITION_REAR_CENTER: -+ dev.chanMask = (Volume::ChannelMask)( dev.chanMask | Volume::MREARCENTER); -+ dev.chanIDs[i] = Volume::REARCENTER; -+ break; -+ case PA_CHANNEL_POSITION_REAR_LEFT: -+ dev.chanMask = (Volume::ChannelMask)( dev.chanMask | Volume::MSURROUNDLEFT); -+ dev.chanIDs[i] = Volume::SURROUNDLEFT; -+ break; -+ case PA_CHANNEL_POSITION_REAR_RIGHT: -+ dev.chanMask = (Volume::ChannelMask)( dev.chanMask | Volume::MSURROUNDRIGHT); -+ dev.chanIDs[i] = Volume::SURROUNDRIGHT; -+ break; -+ case PA_CHANNEL_POSITION_LFE: -+ dev.chanMask = (Volume::ChannelMask)( dev.chanMask | Volume::MWOOFER); -+ dev.chanIDs[i] = Volume::WOOFER; -+ break; -+ case PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER: -+ dev.chanMask = (Volume::ChannelMask)( dev.chanMask | Volume::MREARSIDELEFT); -+ dev.chanIDs[i] = Volume::REARSIDELEFT; -+ break; -+ case PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER: -+ dev.chanMask = (Volume::ChannelMask)( dev.chanMask | Volume::MREARSIDERIGHT); -+ dev.chanIDs[i] = Volume::REARSIDERIGHT; -+ break; -+ default: -+ kWarning(67100) << "Channel Map contains a pa_channel_position we cannot handle " << dev.channel_map.map[i]; -+ break; -+ } -+ } -+ } -+} -+ -+static void sink_cb(pa_context *c, const pa_sink_info *i, int eol, void *) { -+ -+ Q_ASSERT(c == context); -+ -+ if (eol < 0) { -+ if (pa_context_errno(c) == PA_ERR_NOENTITY) -+ return; -+ -+ kWarning(67100) << "Sink callback failure"; -+ return; -+ } -+ -+ if (eol > 0) { -+ dec_outstanding(); -+ if (s_Mixers.contains(KMIXPA_PLAYBACK)) -+ s_Mixers[KMIXPA_PLAYBACK]->triggerUpdate(); -+ return; -+ } -+ -+ devinfo s; -+ s.index = s.device_index = i->index; -+ s.restore.name = i->name; -+ s.restore.device = ""; -+ s.name = QString(i->name).replace(' ', '_'); -+ s.description = i->description; -+ s.volume = i->volume; -+ s.channel_map = i->channel_map; -+ s.mute = !!i->mute; -+ -+ translateMasksAndMaps(s); -+ -+ bool is_new = !outputDevices.contains(s.index); -+ outputDevices[s.index] = s; -+ kDebug(67100) << "Got some info about sink: " << s.description; -+ -+ if (is_new && s_Mixers.contains(KMIXPA_PLAYBACK)) -+ s_Mixers[KMIXPA_PLAYBACK]->addWidget(s.index); -+} -+ -+static void source_cb(pa_context *c, const pa_source_info *i, int eol, void *) { -+ -+ Q_ASSERT(c == context); -+ -+ if (eol < 0) { -+ if (pa_context_errno(c) == PA_ERR_NOENTITY) -+ return; -+ -+ kWarning(67100) << "Source callback failure"; -+ return; -+ } -+ -+ if (eol > 0) { -+ dec_outstanding(); -+ if (s_Mixers.contains(KMIXPA_CAPTURE)) -+ s_Mixers[KMIXPA_CAPTURE]->triggerUpdate(); -+ return; -+ } -+ -+ // Do something.... -+ if (PA_INVALID_INDEX != i->monitor_of_sink) -+ { -+ kDebug(67100) << "Ignoring Monitor Source: " << i->description; -+ return; -+ } -+ -+ devinfo s; -+ s.index = s.device_index = i->index; -+ s.restore.name = i->name; -+ s.restore.device = ""; -+ s.name = QString(i->name).replace(' ', '_'); -+ s.description = i->description; -+ s.volume = i->volume; -+ s.channel_map = i->channel_map; -+ s.mute = !!i->mute; -+ -+ translateMasksAndMaps(s); -+ -+ bool is_new = !captureDevices.contains(s.index); -+ captureDevices[s.index] = s; -+ kDebug(67100) << "Got some info about source: " << s.description; -+ -+ if (is_new && s_Mixers.contains(KMIXPA_CAPTURE)) -+ s_Mixers[KMIXPA_CAPTURE]->addWidget(s.index); -+} -+ -+static void client_cb(pa_context *c, const pa_client_info *i, int eol, void *) { -+ -+ Q_ASSERT(c == context); -+ -+ if (eol < 0) { -+ if (pa_context_errno(c) == PA_ERR_NOENTITY) -+ return; -+ -+ kWarning(67100) << "Client callback failure"; -+ return; -+ } -+ -+ if (eol > 0) { -+ dec_outstanding(); -+ return; -+ } -+ -+ clients[i->index] = i->name; -+ kDebug(67100) << "Got some info about client: " << i->name; -+} -+ -+static void sink_input_cb(pa_context *c, const pa_sink_input_info *i, int eol, void *) { -+ -+ Q_ASSERT(c == context); -+ -+ if (eol < 0) { -+ if (pa_context_errno(c) == PA_ERR_NOENTITY) -+ return; -+ -+ kWarning(67100) << "Sink Input callback failure"; -+ return; -+ } -+ -+ if (eol > 0) { -+ dec_outstanding(); -+ if (s_Mixers.contains(KMIXPA_APP_PLAYBACK)) -+ s_Mixers[KMIXPA_APP_PLAYBACK]->triggerUpdate(); -+ return; -+ } -+ -+ const char *t; -+ if ((t = pa_proplist_gets(i->proplist, "module-stream-restore.id"))) { -+ if (strcmp(t, "sink-input-by-media-role:event") == 0) { -+ kWarning(67100) << "Ignoring sink-input due to it being designated as an event and thus handled by the Event slider"; -+ return; -+ } -+ } -+ -+ QString prefix = QString("%1: ").arg(i18n("Unknown Application")); -+ if (clients.contains(i->client)) -+ prefix = QString("%1: ").arg(clients[i->client]); -+ -+ devinfo s; -+ s.index = i->index; -+ s.device_index = i->sink; -+ s.restore.name = i->name; -+ s.restore.device = ""; -+ s.description = prefix + i->name; -+ s.name = QString("stream:") + QString(s.description).replace(' ', '_'); -+ s.volume = i->volume; -+ s.channel_map = i->channel_map; -+ s.mute = !!i->mute; -+ -+ translateMasksAndMaps(s); -+ -+ bool is_new = !outputStreams.contains(s.index); -+ outputStreams[s.index] = s; -+ kDebug(67100) << "Got some info about sink input (playback stream): " << s.description; -+ -+ if (is_new && s_Mixers.contains(KMIXPA_APP_PLAYBACK)) -+ s_Mixers[KMIXPA_APP_PLAYBACK]->addWidget(s.index); -+} -+ -+static void source_output_cb(pa_context *c, const pa_source_output_info *i, int eol, void *) { -+ -+ Q_ASSERT(c == context); -+ -+ if (eol < 0) { -+ if (pa_context_errno(c) == PA_ERR_NOENTITY) -+ return; -+ -+ kWarning(67100) << "Source Output callback failure"; -+ return; -+ } -+ -+ if (eol > 0) { -+ dec_outstanding(); -+ if (s_Mixers.contains(KMIXPA_APP_CAPTURE)) -+ s_Mixers[KMIXPA_APP_CAPTURE]->triggerUpdate(); -+ return; -+ } -+ -+ /* NB Until Source Outputs support volumes, we just use the volume of the source itself */ -+ if (!captureDevices.contains(i->source)) { -+ kWarning(67100) << "Source Output refers to a Source we don't have any info for :s"; -+ return; -+ } -+ -+ QString prefix = QString("%1: ").arg(i18n("Unknown Application")); -+ if (clients.contains(i->client)) -+ prefix = QString("%1: ").arg(clients[i->client]); -+ -+ devinfo s; -+ s.index = i->index; -+ s.device_index = i->source; -+ s.restore.name = i->name; -+ s.restore.device = ""; -+ s.description = prefix + i->name; -+ s.name = QString("stream:") + QString(s.description).replace(' ', '_'); -+ //s.volume = i->volume; -+ s.volume = captureDevices[i->source].volume; -+ s.channel_map = i->channel_map; -+ //s.mute = !!i->mute; -+ s.mute = captureDevices[i->source].mute; -+ -+ translateMasksAndMaps(s); -+ -+ bool is_new = !captureStreams.contains(s.index); -+ captureStreams[s.index] = s; -+ kDebug(67100) << "Got some info about source output (capture stream): " << s.description; -+ -+ if (is_new && s_Mixers.contains(KMIXPA_APP_CAPTURE)) -+ s_Mixers[KMIXPA_APP_CAPTURE]->addWidget(s.index); -+} -+ -+ -+void ext_stream_restore_read_cb(pa_context *c, const pa_ext_stream_restore_info *i, int eol, void *) { -+ -+ Q_ASSERT(c == context); -+ -+ if (eol < 0) { -+ dec_outstanding(); -+ kWarning(67100) << "Failed to initialize stream_restore extension: " << pa_strerror(pa_context_errno(context)); -+ return; -+ } -+ -+ if (eol > 0) { -+ dec_outstanding(); -+ if (s_Mixers.contains(KMIXPA_APP_PLAYBACK)) -+ s_Mixers[KMIXPA_APP_PLAYBACK]->triggerUpdate(); -+ return; -+ } -+ -+ // We only want to know about Sound Events for now... -+ if (strcmp(i->name, "sink-input-by-media-role:event") != 0) -+ return; -+ -+ // Fake a Mono Device/Volume -+ pa_cvolume volume; -+ volume.channels = 1; -+ volume.values[0] = pa_cvolume_max(&i->volume); -+ pa_channel_map channel_map; -+ channel_map.channels = 1; -+ channel_map.map[0] = PA_CHANNEL_POSITION_MONO; -+ -+ devinfo s; -+ s.index = s.device_index = PA_INVALID_INDEX; -+ s.restore.name = i->name; -+ s.restore.device = i->device; -+ s.description = i18n("Event Sounds"); -+ s.name = QString("restore:") + i->name; -+ s.volume = volume; -+ s.channel_map = channel_map; -+ s.mute = !!i->mute; -+ -+ translateMasksAndMaps(s); -+ -+ outputRoles[s.index] = s; -+ kDebug(67100) << "Got some info about restore rule: " << s.description; -+} -+ -+static void ext_stream_restore_subscribe_cb(pa_context *c, void *) { -+ -+ Q_ASSERT(c == context); -+ -+ pa_operation *o; -+ if (!(o = pa_ext_stream_restore_read(c, ext_stream_restore_read_cb, NULL))) { -+ kWarning(67100) << "pa_ext_stream_restore_read() failed"; -+ return; -+ } -+ -+ pa_operation_unref(o); -+} -+ -+ -+static void subscribe_cb(pa_context *c, pa_subscription_event_type_t t, uint32_t index, void *) { -+ -+ Q_ASSERT(c == context); -+ -+ switch (t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) { -+ case PA_SUBSCRIPTION_EVENT_SINK: -+ if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) { -+ if (s_Mixers.contains(KMIXPA_PLAYBACK)) -+ s_Mixers[KMIXPA_PLAYBACK]->removeWidget(index); -+ } else { -+ pa_operation *o; -+ if (!(o = pa_context_get_sink_info_by_index(c, index, sink_cb, NULL))) { -+ kWarning(67100) << "pa_context_get_sink_info_by_index() failed"; -+ return; -+ } -+ pa_operation_unref(o); -+ } -+ break; -+ -+ case PA_SUBSCRIPTION_EVENT_SOURCE: -+ if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) { -+ if (s_Mixers.contains(KMIXPA_CAPTURE)) -+ s_Mixers[KMIXPA_CAPTURE]->removeWidget(index); -+ } else { -+ pa_operation *o; -+ if (!(o = pa_context_get_source_info_by_index(c, index, source_cb, NULL))) { -+ kWarning(67100) << "pa_context_get_source_info_by_index() failed"; -+ return; -+ } -+ pa_operation_unref(o); -+ } -+ break; -+ -+ case PA_SUBSCRIPTION_EVENT_SINK_INPUT: -+ if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) { -+ if (s_Mixers.contains(KMIXPA_APP_PLAYBACK)) -+ s_Mixers[KMIXPA_APP_PLAYBACK]->removeWidget(index); -+ } else { -+ pa_operation *o; -+ if (!(o = pa_context_get_sink_input_info(c, index, sink_input_cb, NULL))) { -+ kWarning(67100) << "pa_context_get_sink_input_info() failed"; -+ return; -+ } -+ pa_operation_unref(o); -+ } -+ break; -+ -+ case PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT: -+ if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) { -+ if (s_Mixers.contains(KMIXPA_APP_CAPTURE)) -+ s_Mixers[KMIXPA_APP_CAPTURE]->removeWidget(index); -+ } else { -+ pa_operation *o; -+ if (!(o = pa_context_get_source_output_info(c, index, source_output_cb, NULL))) { -+ kWarning(67100) << "pa_context_get_sink_input_info() failed"; -+ return; -+ } -+ pa_operation_unref(o); -+ } -+ break; -+ -+ case PA_SUBSCRIPTION_EVENT_CLIENT: -+ if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) { -+ clients.remove(index); -+ } else { -+ pa_operation *o; -+ if (!(o = pa_context_get_client_info(c, index, client_cb, NULL))) { -+ kWarning(67100) << "pa_context_get_client_info() failed"; -+ return; -+ } -+ pa_operation_unref(o); -+ } -+ break; -+ -+ } -+} -+ -+ -+static void context_state_callback(pa_context *c, void *) -+{ -+ Q_ASSERT(c == context); -+ -+ switch (pa_context_get_state(c)) { -+ case PA_CONTEXT_UNCONNECTED: -+ case PA_CONTEXT_CONNECTING: -+ case PA_CONTEXT_AUTHORIZING: -+ case PA_CONTEXT_SETTING_NAME: -+ break; -+ -+ case PA_CONTEXT_READY: -+ // Attempt to load things up -+ pa_operation *o; -+ -+ pa_context_set_subscribe_callback(c, subscribe_cb, NULL); -+ -+ if (!(o = pa_context_subscribe(c, (pa_subscription_mask_t) -+ (PA_SUBSCRIPTION_MASK_SINK| -+ PA_SUBSCRIPTION_MASK_SOURCE| -+ PA_SUBSCRIPTION_MASK_CLIENT| -+ PA_SUBSCRIPTION_MASK_SINK_INPUT| -+ PA_SUBSCRIPTION_MASK_SOURCE_OUTPUT), NULL, NULL))) { -+ kWarning(67100) << "pa_context_subscribe() failed"; -+ return; -+ } -+ pa_operation_unref(o); -+ -+ if (!(o = pa_context_get_sink_info_list(c, sink_cb, NULL))) { -+ kWarning(67100) << "pa_context_get_sink_info_list() failed"; -+ return; -+ } -+ pa_operation_unref(o); -+ s_OutstandingRequests++; -+ -+ if (!(o = pa_context_get_source_info_list(c, source_cb, NULL))) { -+ kWarning(67100) << "pa_context_get_source_info_list() failed"; -+ return; -+ } -+ pa_operation_unref(o); -+ s_OutstandingRequests++; -+ -+ -+ if (!(o = pa_context_get_client_info_list(c, client_cb, NULL))) { -+ kWarning(67100) << "pa_context_client_info_list() failed"; -+ return; -+ } -+ pa_operation_unref(o); -+ s_OutstandingRequests++; -+ -+ if (!(o = pa_context_get_sink_input_info_list(c, sink_input_cb, NULL))) { -+ kWarning(67100) << "pa_context_get_sink_input_info_list() failed"; -+ return; -+ } -+ pa_operation_unref(o); -+ s_OutstandingRequests++; -+ -+ if (!(o = pa_context_get_source_output_info_list(c, source_output_cb, NULL))) { -+ kWarning(67100) << "pa_context_get_source_output_info_list() failed"; -+ return; -+ } -+ pa_operation_unref(o); -+ s_OutstandingRequests++; -+ -+ /* These calls are not always supported */ -+ if ((o = pa_ext_stream_restore_read(c, ext_stream_restore_read_cb, NULL))) { -+ pa_operation_unref(o); -+ s_OutstandingRequests++; -+ -+ pa_ext_stream_restore_set_subscribe_cb(c, ext_stream_restore_subscribe_cb, NULL); -+ -+ if ((o = pa_ext_stream_restore_subscribe(c, 1, NULL, NULL))) -+ pa_operation_unref(o); -+ } else -+ kWarning(67100) << "Failed to initialize stream_restore extension: " << pa_strerror(pa_context_errno(context)); -+ -+ break; -+ -+ case PA_CONTEXT_FAILED: -+ s_pulseActive = INACTIVE; -+ if (s_connectionEventloop) { -+ s_connectionEventloop->exit(0); -+ s_connectionEventloop = NULL; -+ } -+ break; -+ -+ case PA_CONTEXT_TERMINATED: -+ default: -+ s_pulseActive = INACTIVE; -+ /// @todo Deal with reconnection... -+ break; -+ } -+} -+ -+static void setVolumeFromPulse(Volume& volume, const devinfo& dev) -+{ -+ chanIDMap::const_iterator iter; -+ for (iter = dev.chanIDs.begin(); iter != dev.chanIDs.end(); ++iter) -+ { -+ //kDebug(67100) << "Setting volume for channel " << iter.value() << " to " << (long)dev.volume.values[iter.key()] << " (" << ((100*(long)dev.volume.values[iter.key()]) / PA_VOLUME_NORM) << "%)"; -+ volume.setVolume(iter.value(), (long)dev.volume.values[iter.key()]); -+ } -+} -+ -+static pa_cvolume genVolumeForPulse(const devinfo& dev, Volume& volume) -+{ -+ pa_cvolume cvol = dev.volume; -+ -+ chanIDMap::const_iterator iter; -+ for (iter = dev.chanIDs.begin(); iter != dev.chanIDs.end(); ++iter) -+ { -+ cvol.values[iter.key()] = (uint32_t)volume.getVolume(iter.value()); -+ //kDebug(67100) << "Setting volume for channel " << iter.value() << " to " << cvol.values[iter.key()] << " (" << ((100*cvol.values[iter.key()]) / PA_VOLUME_NORM) << "%)"; -+ } -+ return cvol; -+} -+ -+static devmap* get_widget_map(int type) -+{ -+ Q_ASSERT(type >= 0 && type <= KMIXPA_WIDGET_MAX); -+ -+ if (KMIXPA_PLAYBACK == type) -+ return &outputDevices; -+ else if (KMIXPA_CAPTURE == type) -+ return &captureDevices; -+ else if (KMIXPA_APP_PLAYBACK == type) -+ return &outputStreams; -+ else if (KMIXPA_APP_CAPTURE == type) -+ return &captureStreams; -+ -+ Q_ASSERT(0); -+ return NULL; -+} -+ -+void Mixer_PULSE::addWidget(int index) -+{ -+ devmap* map = get_widget_map(m_devnum); -+ bool capture = (KMIXPA_CAPTURE == m_devnum || KMIXPA_APP_CAPTURE == m_devnum); -+ -+ if (!map->contains(index)) { -+ kWarning(67100) << "New " << m_devnum << " widget notified for index " << index << " but I cannot find it in my list :s"; -+ return; -+ } -+ addDevice((*map)[index], capture); -+ emit controlsReconfigured(m_devnum); -+} -+ -+void Mixer_PULSE::removeWidget(int index) -+{ -+ devmap* map = get_widget_map(m_devnum); -+ -+ if (!map->contains(index)) { -+ //kWarning(67100) << "Removing " << m_devnum << " widget notified for index " << index << " but I cannot find it in my list :s"; -+ // Sometimes we ignore things (e.g. event sounds) so don't be too noisy here. -+ return; -+ } -+ -+ QString id = (*map)[index].name; -+ map->remove(index); -+ -+ // We need to find the MixDevice that goes with this widget and remove it. -+ MixSet::iterator iter; -+ for (iter = m_mixDevices.begin(); iter != m_mixDevices.end(); ++iter) -+ { -+ if ((*iter)->id() == id) -+ { -+ delete *iter; -+ m_mixDevices.erase(iter); -+ emit controlsReconfigured(m_devnum); -+ return; -+ } -+ } -+} -+ -+void Mixer_PULSE::addDevice(devinfo& dev, bool capture) -+{ -+ if (dev.chanMask != Volume::MNONE) { -+ Volume v(dev.chanMask, PA_VOLUME_NORM, PA_VOLUME_MUTED, !capture/*mute switch*/, capture); -+ setVolumeFromPulse(v, dev); -+ MixDevice* md = new MixDevice( _mixer, dev.name, dev.description); -+ if (capture) -+ md->addCaptureVolume(v); -+ else -+ md->addPlaybackVolume(v); -+ md->setMuted(dev.mute); -+ m_mixDevices.append(md); -+ } -+} - - Mixer_Backend* PULSE_getMixer( Mixer *mixer, int devnum ) - { -@@ -39,203 +685,284 @@ Mixer_PULSE::Mixer_PULSE(Mixer *mixer, int devnum) : Mixer_Backend(mixer, devnum - { - if ( devnum == -1 ) - m_devnum = 0; -+ -+ QString pulseenv = qgetenv("KMIX_PULSEAUDIO_DISABLE"); -+ if (pulseenv.toInt()) -+ s_pulseActive = INACTIVE; -+ -+ ++refcount; -+ if (INACTIVE != s_pulseActive && 1 == refcount) -+ { -+ mainloop = pa_glib_mainloop_new(g_main_context_default()); -+ g_assert(mainloop); -+ -+ pa_mainloop_api *api = pa_glib_mainloop_get_api(mainloop); -+ g_assert(api); -+ -+ context = pa_context_new(api, "KMix KDE 4"); -+ g_assert(context); -+ -+ // We create a simple event loop to allow the glib loop -+ // to iterate until we've connected or not to the server. -+ s_connectionEventloop = new QEventLoop; -+ -+ // (cg) Convert to PA_CONTEXT_NOFLAGS when PulseAudio 0.9.19 is required -+ if (pa_context_connect(context, NULL, static_cast(0), 0) >= 0) { -+ pa_context_set_state_callback(context, &context_state_callback, s_connectionEventloop); -+ // Now we block until we connect or otherwise... -+ s_connectionEventloop->exec(); -+ } -+ } -+ -+ s_Mixers[m_devnum] = this; - } - - Mixer_PULSE::~Mixer_PULSE() - { -- close(); -+ s_Mixers.remove(m_devnum); -+ -+ if (refcount > 0) -+ { -+ --refcount; -+ if (0 == refcount) -+ { -+ if (context) { -+ pa_context_unref(context); -+ context = NULL; -+ } -+ -+ if (mainloop) { -+ pa_glib_mainloop_free(mainloop); -+ mainloop = NULL; -+ } -+ } -+ } - } - - int Mixer_PULSE::open() - { -- kDebug(67100) << "Trying Pulse sink"; -- mainloop = pa_glib_mainloop_new(g_main_context_default()); -- g_assert(mainloop); -- pa_mainloop_api *api = pa_glib_mainloop_get_api(mainloop); -- g_assert(api); -- -- context = pa_context_new(api, "KMix KDE 4"); -- g_assert(context); -- //return Mixer::ERR_OPEN; -- --/* -- // -- // Mixer is open. Now define all of the mix devices. -- // -- -- for ( int idx = 0; idx < numDevs; idx++ ) -- { -- Volume vol( 2, AUDIO_MAX_GAIN ); -- QString id; -- id.setNum(idx); -- MixDevice* md = new MixDevice( _mixer, id, -- QString(MixerDevNames[idx]), MixerChannelTypes[idx]); -- md->addPlaybackVolume(vol); -- md->setRecSource( isRecsrcHW( idx ) ); -- m_mixDevices.append( md ); -- } --*/ -- -- m_mixerName = "PULSE Audio Mixer"; -- -- m_isOpen = true; -+ //kDebug(67100) << "Trying Pulse sink"; - -+ if (ACTIVE == s_pulseActive && m_devnum <= KMIXPA_APP_CAPTURE) -+ { -+ devmap::iterator iter; -+ if (KMIXPA_PLAYBACK == m_devnum) -+ { -+ m_mixerName = i18n("Playback Devices"); -+ for (iter = outputDevices.begin(); iter != outputDevices.end(); ++iter) -+ addDevice(*iter, false); -+ } -+ else if (KMIXPA_CAPTURE == m_devnum) -+ { -+ m_mixerName = i18n("Capture Devices"); -+ for (iter = captureDevices.begin(); iter != captureDevices.end(); ++iter) -+ addDevice(*iter, true); -+ } -+ else if (KMIXPA_APP_PLAYBACK == m_devnum) -+ { -+ m_mixerName = i18n("Playback Streams"); -+ for (iter = outputRoles.begin(); iter != outputRoles.end(); ++iter) -+ addDevice(*iter, false); -+ for (iter = outputStreams.begin(); iter != outputStreams.end(); ++iter) -+ addDevice(*iter, false); -+ } -+ else if (KMIXPA_APP_CAPTURE == m_devnum) -+ { -+ m_mixerName = i18n("Capture Streams"); -+ for (iter = captureStreams.begin(); iter != captureStreams.end(); ++iter) -+ addDevice(*iter, true); -+ } -+ -+ kDebug(67100) << "Using PulseAudio for mixer: " << m_mixerName; -+ m_isOpen = true; -+ } -+ - return 0; - } - - int Mixer_PULSE::close() - { -- if (context) -- { -- pa_context_unref(context); -- context = NULL; -- } -- if (mainloop) -- { -- pa_glib_mainloop_free(mainloop); -- mainloop = NULL; -- } - return 1; - } - - int Mixer_PULSE::readVolumeFromHW( const QString& id, MixDevice *md ) - { --/* audio_info_t audioinfo; -- uint_t devMask = MixerSunPortMasks[devnum]; -- -- Volume& volume = md->playbackVolume(); -- int devnum = id2num(id); -- // -- // Read the current audio information from the driver -- // -- if ( ioctl( fd, AUDIO_GETINFO, &audioinfo ) < 0 ) -- { -- return( Mixer::ERR_READ ); -- } -- else -- { -- // -- // Extract the appropriate fields based on the requested device -- // -- switch ( devnum ) -- { -- case MIXERDEV_MASTER_VOLUME : -- volume.setSwitchActivated( audioinfo.output_muted ); -- GainBalanceToVolume( audioinfo.play.gain, -- audioinfo.play.balance, -- volume ); -- break; -- -- case MIXERDEV_RECORD_MONITOR : -- md->setMuted(false); -- volume.setAllVolumes( audioinfo.monitor_gain ); -- break; -- -- case MIXERDEV_INTERNAL_SPEAKER : -- case MIXERDEV_HEADPHONE : -- case MIXERDEV_LINE_OUT : -- md->setMuted( (audioinfo.play.port & devMask) ? false : true ); -- GainBalanceToVolume( audioinfo.play.gain, -- audioinfo.play.balance, -- volume ); -- break; -+ devmap *map = NULL; -+ Volume *vol = NULL; -+ -+ if (KMIXPA_PLAYBACK == m_devnum) { -+ map = &outputDevices; -+ vol = &md->playbackVolume(); -+ } else if (KMIXPA_CAPTURE == m_devnum) { -+ map = &captureDevices; -+ vol = &md->captureVolume(); -+ } else if (KMIXPA_APP_PLAYBACK == m_devnum) { -+ if (id.startsWith("stream:")) -+ map = &outputStreams; -+ else if (id.startsWith("restore:")) -+ map = &outputRoles; -+ vol = &md->playbackVolume(); -+ } else if (KMIXPA_APP_CAPTURE == m_devnum) { -+ map = &captureStreams; -+ vol = &md->captureVolume(); -+ } - -- case MIXERDEV_MICROPHONE : -- case MIXERDEV_LINE_IN : -- case MIXERDEV_CD : -- md->setMuted( (audioinfo.record.port & devMask) ? false : true ); -- GainBalanceToVolume( audioinfo.record.gain, -- audioinfo.record.balance, -- volume ); -+ Q_ASSERT(map); -+ Q_ASSERT(vol); -+ -+ devmap::iterator iter; -+ for (iter = map->begin(); iter != map->end(); ++iter) -+ { -+ if (iter->name == id) -+ { -+ setVolumeFromPulse(*vol, *iter); -+ md->setMuted(iter->mute); - break; -+ } -+ } - -- default : -- return Mixer::ERR_READ; -- } -- return 0; -- }*/ -- return 0; -+ return 0; - } - - int Mixer_PULSE::writeVolumeToHW( const QString& id, MixDevice *md ) - { --/* uint_t gain; -- uchar_t balance; -- uchar_t mute; -- -- Volume& volume = md->playbackVolume(); -- int devnum = id2num(id); -- // -- // Convert the Volume(left vol, right vol) to the Gain/Balance Sun uses -- // -- VolumeToGainBalance( volume, gain, balance ); -- mute = md->isMuted() ? 1 : 0; -- -- // -- // Read the current audio settings from the hardware -- // -- audio_info_t audioinfo; -- if ( ioctl( fd, AUDIO_GETINFO, &audioinfo ) < 0 ) -- { -- return( Mixer::ERR_READ ); -- } -+ devmap::iterator iter; -+ if (KMIXPA_PLAYBACK == m_devnum) -+ { -+ for (iter = outputDevices.begin(); iter != outputDevices.end(); ++iter) -+ { -+ if (iter->name == id) -+ { -+ pa_operation *o; -+ -+ pa_cvolume volume = genVolumeForPulse(*iter, md->playbackVolume()); -+ if (!(o = pa_context_set_sink_volume_by_index(context, iter->index, &volume, NULL, NULL))) { -+ kWarning(67100) << "pa_context_set_sink_volume_by_index() failed"; -+ return Mixer::ERR_READ; -+ } -+ pa_operation_unref(o); -+ -+ if (!(o = pa_context_set_sink_mute_by_index(context, iter->index, (md->isMuted() ? 1 : 0), NULL, NULL))) { -+ kWarning(67100) << "pa_context_set_sink_mute_by_index() failed"; -+ return Mixer::ERR_READ; -+ } -+ pa_operation_unref(o); -+ -+ return 0; -+ } -+ } -+ } -+ else if (KMIXPA_CAPTURE == m_devnum) -+ { -+ for (iter = captureDevices.begin(); iter != captureDevices.end(); ++iter) -+ { -+ if (iter->name == id) -+ { -+ pa_operation *o; -+ -+ pa_cvolume volume = genVolumeForPulse(*iter, md->captureVolume()); -+ if (!(o = pa_context_set_source_volume_by_index(context, iter->index, &volume, NULL, NULL))) { -+ kWarning(67100) << "pa_context_set_source_volume_by_index() failed"; -+ return Mixer::ERR_READ; -+ } -+ pa_operation_unref(o); -+ -+ if (!(o = pa_context_set_source_mute_by_index(context, iter->index, (md->isMuted() ? 1 : 0), NULL, NULL))) { -+ kWarning(67100) << "pa_context_set_source_mute_by_index() failed"; -+ return Mixer::ERR_READ; -+ } -+ pa_operation_unref(o); -+ -+ return 0; -+ } -+ } -+ } -+ else if (KMIXPA_APP_PLAYBACK == m_devnum) -+ { -+ if (id.startsWith("stream:")) -+ { -+ for (iter = outputStreams.begin(); iter != outputStreams.end(); ++iter) -+ { -+ if (iter->name == id) -+ { -+ pa_operation *o; -+ -+ pa_cvolume volume = genVolumeForPulse(*iter, md->playbackVolume()); -+ if (!(o = pa_context_set_sink_input_volume(context, iter->index, &volume, NULL, NULL))) { -+ kWarning(67100) << "pa_context_set_sink_input_volume() failed"; -+ return Mixer::ERR_READ; -+ } -+ pa_operation_unref(o); -+ -+ if (!(o = pa_context_set_sink_input_mute(context, iter->index, (md->isMuted() ? 1 : 0), NULL, NULL))) { -+ kWarning(67100) << "pa_context_set_sink_input_mute() failed"; -+ return Mixer::ERR_READ; -+ } -+ pa_operation_unref(o); -+ -+ return 0; -+ } -+ } -+ } -+ else if (id.startsWith("restore:")) -+ { -+ for (iter = outputRoles.begin(); iter != outputRoles.end(); ++iter) -+ { -+ if (iter->name == id) -+ { -+ pa_ext_stream_restore_info info; -+ info.name = iter->restore.name.toLatin1().constData(); -+ info.channel_map = iter->channel_map; -+ info.volume = genVolumeForPulse(*iter, md->playbackVolume()); -+ info.device = iter->restore.device.isEmpty() ? NULL : iter->restore.device.toLatin1().constData(); -+ info.mute = (md->isMuted() ? 1 : 0); -+ -+ pa_operation* o; -+ if (!(o = pa_ext_stream_restore_write(context, PA_UPDATE_REPLACE, &info, 1, TRUE, NULL, NULL))) { -+ kWarning(67100) << "pa_ext_stream_restore_write() failed" << info.channel_map.channels << info.volume.channels << info.name; -+ return Mixer::ERR_READ; -+ } -+ pa_operation_unref(o); -+ -+ return 0; -+ } -+ } -+ } -+ } -+ else if (KMIXPA_APP_CAPTURE == m_devnum) -+ { -+ for (iter = captureStreams.begin(); iter != captureStreams.end(); ++iter) -+ { -+ if (iter->name == id) -+ { -+ pa_operation *o; -+ -+ // NB Note that this is different from APP_PLAYBACK in that we set the volume on the source itself. -+ pa_cvolume volume = genVolumeForPulse(*iter, md->captureVolume()); -+ if (!(o = pa_context_set_source_volume_by_index(context, iter->device_index, &volume, NULL, NULL))) { -+ kWarning(67100) << "pa_context_set_source_volume_by_index() failed"; -+ return Mixer::ERR_READ; -+ } -+ pa_operation_unref(o); -+ -+ if (!(o = pa_context_set_source_mute_by_index(context, iter->device_index, (md->isMuted() ? 1 : 0), NULL, NULL))) { -+ kWarning(67100) << "pa_context_set_source_mute_by_index() failed"; -+ return Mixer::ERR_READ; -+ } -+ pa_operation_unref(o); -+ -+ return 0; -+ } -+ } -+ } - -- // -- // Now, based on the devnum that we are writing to, update the appropriate -- // volume field and twiddle the appropriate bitmask to enable/mute the -- // device as necessary. -- // -- switch ( devnum ) -- { -- case MIXERDEV_MASTER_VOLUME : -- audioinfo.play.gain = gain; -- audioinfo.play.balance = balance; -- audioinfo.output_muted = mute; -- break; -- -- case MIXERDEV_RECORD_MONITOR : -- audioinfo.monitor_gain = gain; -- // no mute or balance for record monitor -- break; -- -- case MIXERDEV_INTERNAL_SPEAKER : -- case MIXERDEV_HEADPHONE : -- case MIXERDEV_LINE_OUT : -- audioinfo.play.gain = gain; -- audioinfo.play.balance = balance; -- if ( mute ) -- audioinfo.play.port &= ~MixerSunPortMasks[devnum]; -- else -- audioinfo.play.port |= MixerSunPortMasks[devnum]; -- break; -- -- case MIXERDEV_MICROPHONE : -- case MIXERDEV_LINE_IN : -- case MIXERDEV_CD : -- audioinfo.record.gain = gain; -- audioinfo.record.balance = balance; -- if ( mute ) -- audioinfo.record.port &= ~MixerSunPortMasks[devnum]; -- else -- audioinfo.record.port |= MixerSunPortMasks[devnum]; -- break; -- -- default : -- return Mixer::ERR_READ; -- } -+ return 0; -+} - -- // -- // Now that we've updated the audioinfo struct, write it back to the hardware -- // -- if ( ioctl( fd, AUDIO_SETINFO, &audioinfo ) < 0 ) -- { -- return( Mixer::ERR_WRITE ); -- } -- else -- { -- return 0; -- }*/ -- return 0; -+void Mixer_PULSE::triggerUpdate() -+{ -+ readSetFromHWforceUpdate(); -+ readSetFromHW(); - } - - void Mixer_PULSE::setRecsrcHW( const QString& /*id*/, bool /* on */ ) -@@ -243,19 +970,8 @@ void Mixer_PULSE::setRecsrcHW( const QString& /*id*/, bool /* on */ ) - return; - } - --bool Mixer_PULSE::isRecsrcHW( const QString& id ) -+bool Mixer_PULSE::isRecsrcHW( const QString& /*id*/ ) - { --/* int devnum = id2num(id); -- switch ( devnum ) -- { -- case MIXERDEV_MICROPHONE : -- case MIXERDEV_LINE_IN : -- case MIXERDEV_CD : -- return true; -- -- default : -- return false; -- }*/ - return false; - } - -diff --git a/kmix/mixer_pulse.h b/kmix/mixer_pulse.h -index 6dcd68b..1760a72 100644 ---- a/kmix/mixer_pulse.h -+++ b/kmix/mixer_pulse.h -@@ -24,30 +24,55 @@ - - #include - -+#include "mixer_backend.h" - #include --#include --#include - --#include "mixer_backend.h" -+typedef QMap chanIDMap; -+typedef struct { -+ int index; -+ int device_index; -+ QString name; -+ QString description; -+ pa_cvolume volume; -+ pa_channel_map channel_map; -+ bool mute; -+ -+ struct { -+ QString name; -+ QString device; -+ } restore; -+ -+ Volume::ChannelMask chanMask; -+ chanIDMap chanIDs; -+} devinfo; - - class Mixer_PULSE : public Mixer_Backend - { --public: -- Mixer_PULSE(Mixer *mixer, int devnum); -- virtual ~Mixer_PULSE(); -+ public: -+ Mixer_PULSE(Mixer *mixer, int devnum); -+ virtual ~Mixer_PULSE(); -+ -+ virtual int readVolumeFromHW( const QString& id, MixDevice *md ); -+ virtual int writeVolumeToHW ( const QString& id, MixDevice *md ); -+ void setRecsrcHW ( const QString& id, bool on ); -+ bool isRecsrcHW ( const QString& id ); -+ -+ virtual QString getDriverName(); -+ virtual bool needsPolling() { return false; } -+ -+ void triggerUpdate(); -+ void addWidget(int index); -+ void removeWidget(int index); - -- virtual int readVolumeFromHW( const QString& id, MixDevice *md ); -- virtual int writeVolumeToHW ( const QString& id, MixDevice *md ); -- void setRecsrcHW ( const QString& id, bool on ); -- bool isRecsrcHW ( const QString& id ); -+ protected: -+ virtual int open(); -+ virtual int close(); - -- virtual QString getDriverName(); -+ int fd; - --protected: -- virtual int open(); -- virtual int close(); -+ private: -+ void addDevice(devinfo& dev, bool capture); - -- int fd; - }; - - #endif -diff --git a/kmix/viewbase.cpp b/kmix/viewbase.cpp -index b53ecd4..4ab41da 100644 ---- a/kmix/viewbase.cpp -+++ b/kmix/viewbase.cpp -@@ -76,6 +76,7 @@ ViewBase::ViewBase(QWidget* parent, const char* id, Mixer* mixer, Qt::WFlags f, - action->setText(i18n("&Channels")); - connect(action, SIGNAL(triggered(bool) ), SLOT(configureView())); - connect ( _mixer, SIGNAL(controlChanged()), this, SLOT(refreshVolumeLevels()) ); -+ connect ( _mixer, SIGNAL(controlsReconfigured(int)), this, SLOT(controlsReconfigured(int)) ); - } - - ViewBase::~ViewBase() { -@@ -203,6 +204,11 @@ void ViewBase::showContextMenu() - _popMenu->popup( pos ); - } - -+void ViewBase::controlsReconfigured(int mixerTabIndex) -+{ -+ Q_UNUSED(mixerTabIndex); -+ emit rebuildGUI(); -+} - - void ViewBase::refreshVolumeLevels() - { -diff --git a/kmix/viewbase.h b/kmix/viewbase.h -index 0470f18..35cccfc 100644 ---- a/kmix/viewbase.h -+++ b/kmix/viewbase.h -@@ -130,6 +130,7 @@ protected: - GUIProfile* _guiprof; - KActionCollection *_localActionColletion; - public slots: -+ virtual void controlsReconfigured(int mixerTabIndex); - virtual void refreshVolumeLevels(); - virtual void configureView(); - void toggleMenuBarSlot(); diff --git a/kmix_pa-20100111.patch b/kmix_pa-20100111.patch new file mode 100644 index 0000000..002906e --- /dev/null +++ b/kmix_pa-20100111.patch @@ -0,0 +1,1921 @@ +diff --git a/kmix/KMixApp.cpp b/kmix/KMixApp.cpp +index a5cf1d5..43ec696 100644 +--- a/kmix/KMixApp.cpp ++++ b/kmix/KMixApp.cpp +@@ -51,7 +51,8 @@ KMixApp::newInstance() + // There are 3 cases for a new instance + + //kDebug(67100) << "KMixApp::newInstance() isRestored()=" << isRestored() << "_keepVisibility=" << _keepVisibility; +- if ( m_kmix ) ++ static bool first = true; ++ if ( !first ) + { // There already exists an instance/window + + /* !!! @bug : _keepVisibilty has the wrong value here. +@@ -74,7 +75,11 @@ KMixApp::newInstance() + // starts it again, the KMix main window will be shown. + // If KMix is restored by SM or the --keepvisibilty is used, KMix will NOT + // explicitly be shown. +- m_kmix->show(); ++ if ( !m_kmix ) { ++ m_kmix->show(); ++ } else { ++ kWarning(67100) << "KMixApp::newInstance() Window has not finished constructing yet so ignoring the show() request."; ++ } + } + else { + // CASE 2: If KMix is running, AND ( session gets restored OR keepvisibilty command line switch ) +@@ -92,6 +97,11 @@ KMixApp::newInstance() + { + // CASE 3: KMix was not running yet => instanciate a new one + //kDebug(67100) << "KMixApp::newInstance() Instanciate: _keepVisibility=" << _keepVisibility ; ++ first = false; // NB See https://qa.mandriva.com/show_bug.cgi?id=56893#c3 ++ // It is important to track this via a separate variable and not ++ // based on m_kmix to handle this race condition. ++ // Specific protection for the activation-prior-to-full-construction ++ // case exists above in the 'already running case' + m_kmix = new KMixWindow(_keepVisibility); + //connect(this, SIGNAL(stopUpdatesOnVisibility()), m_kmix, SLOT(stopVisibilityUpdates())); + if ( isSessionRestored() && KMainWindow::canBeRestored(0) ) +diff --git a/kmix/kmix-platforms.cpp b/kmix/kmix-platforms.cpp +index f7b8c9a..5f61989 100644 +--- a/kmix/kmix-platforms.cpp ++++ b/kmix/kmix-platforms.cpp +@@ -129,6 +129,10 @@ MixerFactory g_mixerFactories[] = { + { IRIX_getMixer, IRIX_getDriverName }, + #endif + ++#if defined(PULSE_MIXER) ++ { PULSE_getMixer, PULSE_getDriverName }, ++#endif ++ + #if defined(ALSA_MIXER) + { ALSA_getMixer, ALSA_getDriverName }, + #endif +@@ -145,10 +149,6 @@ MixerFactory g_mixerFactories[] = { + { HPUX_getMixer, HPUX_getDriverName }, + #endif + +-#if defined(PULSE_MIXER) +- { PULSE_getMixer, PULSE_getDriverName }, +-#endif +- + { 0, 0 } + }; + +diff --git a/kmix/kmix.cpp b/kmix/kmix.cpp +index 30cc5d4..b1d0743 100644 +--- a/kmix/kmix.cpp ++++ b/kmix/kmix.cpp +@@ -49,6 +49,7 @@ + #include + + // KMix ++#include "guiprofile.h" + #include "mixertoolbox.h" + #include "kmix.h" + #include "kmixdevicemanager.h" +@@ -399,11 +400,25 @@ void KMixWindow::recreateGUIwithoutSavingView() + */ + void KMixWindow::recreateGUI(bool saveConfig) + { +- saveViewConfig(); // save the state before recreating ++ // Find out which of the tabs is currently selected for restoration ++ int current_tab = -1; ++ if (m_wsMixers) ++ current_tab = m_wsMixers->currentIndex(); ++ ++ if (saveConfig) ++ saveViewConfig(); // save the state before recreating ++ ++ // Before clearing the mixer widgets, we must increase the refcount on the guiprof to save it deleting the ViewBase object. ++ if ( Mixer::mixers().count() > 0 ) ++ for (int i=0; iselectProfile((Mixer::mixers())[i])->increaseRefcount(); + clearMixerWidgets(); ++ + if ( Mixer::mixers().count() > 0 ) { + for (int i=0; iselectProfile(mixer)->decreaseRefcount(); + addMixerWidget(mixer->id()); + } + bool dockingSucceded = updateDocking(); +@@ -415,6 +430,10 @@ void KMixWindow::recreateGUI(bool saveConfig) + updateDocking(); // -<- removes the DockIcon + hide(); + } ++ ++ if (current_tab >= 0) { ++ m_wsMixers->setCurrentIndex(current_tab); ++ } + } + + +diff --git a/kmix/kmixerwidget.cpp b/kmix/kmixerwidget.cpp +index b7fdb2a..ad126b6 100644 +--- a/kmix/kmixerwidget.cpp ++++ b/kmix/kmixerwidget.cpp +@@ -92,6 +92,7 @@ void KMixerWidget::createLayout(ViewBase::ViewFlags vflags) + // delete old objects + if( m_balanceSlider ) { + delete m_balanceSlider; ++ m_balanceSlider = 0; + } + if( m_topLayout ) { + delete m_topLayout; +diff --git a/kmix/kmixprefdlg.h b/kmix/kmixprefdlg.h +index 36aa942..64872cd 100644 +--- a/kmix/kmixprefdlg.h ++++ b/kmix/kmixprefdlg.h +@@ -25,7 +25,6 @@ + #include + + class KMixPrefWidget; +-class KMixApp; + class QCheckBox; + class QRadioButton; + +@@ -49,7 +48,6 @@ KMixPrefDlg : public KDialog + + private: + QFrame *m_generalTab; +- KMixApp *m_mixApp; + KMixPrefWidget *m_mixPrefTab; + + QCheckBox *m_dockingChk; +diff --git a/kmix/main.cpp b/kmix/main.cpp +index fee99e4..b585c13 100644 +--- a/kmix/main.cpp ++++ b/kmix/main.cpp +@@ -51,6 +51,7 @@ extern "C" KDE_EXPORT int kdemain(int argc, char *argv[]) + aboutData.addCredit(ki18n("Lennart Augustsson"), ki18n("*BSD fixes"), "augustss@cs.chalmers.se"); + aboutData.addCredit(ki18n("Nick Lopez") , ki18n("ALSA port"), "kimo_sabe@usa.net"); + aboutData.addCredit(ki18n("Nadeem Hasan") , ki18n("Mute and volume preview, other fixes"), "nhasan@kde.org"); ++ aboutData.addCredit(ki18n("Colin Guthrie") , ki18n("PulseAudio support"), "cguthrie@mandriva.org"); + + KCmdLineArgs::init( argc, argv, &aboutData ); + +diff --git a/kmix/mdwslider.cpp b/kmix/mdwslider.cpp +index c41b0c5..83eb7cc 100644 +--- a/kmix/mdwslider.cpp ++++ b/kmix/mdwslider.cpp +@@ -349,7 +349,7 @@ void MDWSlider::createWidgetsTopPart(QBoxLayout *layout, bool showMuteLED) + m_iconLabelSimple = 0L; + if ( showMuteLED ) { + //kDebug(67100) << ">>> MixDevice " << m_mixdevice->readableName() << " icon calculation:"; +- setIcon( m_mixdevice->type() ); ++ setIcon( m_mixdevice->iconName() ); + m_iconLayout->addWidget( m_iconLabelSimple ); + QString muteTip( m_mixdevice->readableName() ); + m_iconLabelSimple->setToolTip( muteTip ); +@@ -512,70 +512,13 @@ void MDWSlider::addSliders( QBoxLayout *volLayout, char type, bool addLabel) + } // for all channels of this device + } + +- +-QPixmap MDWSlider::icon( int icontype ) +-{ +- QPixmap miniDevPM; +- +- switch (icontype) { +- case MixDevice::AUDIO: +- miniDevPM = _iconName = "mixer-pcm"; break; +- case MixDevice::BASS: +- case MixDevice::SURROUND_LFE: // "LFE" SHOULD have an own icon +- miniDevPM = _iconName ="mixer-lfe"; break; +- case MixDevice::CD: +- miniDevPM = _iconName ="mixer-cd"; break; +- case MixDevice::EXTERNAL: +- miniDevPM = _iconName = "mixer-line"; break; +- case MixDevice::MICROPHONE: +- miniDevPM = _iconName ="mixer-microphone";break; +- case MixDevice::MIDI: +- miniDevPM = _iconName ="mixer-midi"; break; +- case MixDevice::RECMONITOR: +- miniDevPM = _iconName ="mixer-capture"; break; +- case MixDevice::TREBLE: +- miniDevPM = _iconName ="mixer-pcm-default"; break; +- case MixDevice::UNKNOWN: +- miniDevPM = _iconName ="mixer-front"; break; +- case MixDevice::VOLUME: +- miniDevPM = _iconName ="mixer-master"; break; +- case MixDevice::VIDEO: +- miniDevPM = _iconName ="mixer-video"; break; +- case MixDevice::SURROUND: +- case MixDevice::SURROUND_BACK: +- miniDevPM = _iconName = "mixer-surround"; break; +- case MixDevice::SURROUND_CENTERFRONT: +- case MixDevice::SURROUND_CENTERBACK: +- miniDevPM = _iconName ="mixer-surround-center"; break; +- case MixDevice::HEADPHONE: +- miniDevPM = _iconName = "mixer-headset"; break; +- case MixDevice::DIGITAL: +- miniDevPM = _iconName = "mixer-digital"; break; +- case MixDevice::AC97: +- miniDevPM = _iconName = "mixer-ac97"; break; +- case MixDevice::SPEAKER: +- miniDevPM = _iconName = "mixer-pc-speaker"; break; +- case MixDevice::MICROPHONE_BOOST: +- miniDevPM = _iconName = "mixer-microphone-boost"; break; +- case MixDevice::MICROPHONE_FRONT_BOOST: +- miniDevPM = _iconName = "mixer-microphone-front-boost"; break; +- case MixDevice::MICROPHONE_FRONT: +- miniDevPM = _iconName = "mixer-microphone-front"; break; +- default: +- miniDevPM = _iconName ="mixer-front"; break; +- } +- +- miniDevPM = loadIcon(_iconName); +- return miniDevPM; +-} +- + QPixmap MDWSlider::loadIcon( QString& filename ) + { + return KIconLoader::global()->loadIcon( filename, KIconLoader::Small, KIconLoader::SizeSmallMedium ); + } + + void +-MDWSlider::setIcon( int icontype ) ++MDWSlider::setIcon( QString filename ) + { + if( !m_iconLabelSimple ) + { +@@ -583,7 +526,7 @@ MDWSlider::setIcon( int icontype ) + installEventFilter( m_iconLabelSimple ); + } + +- QPixmap miniDevPM = icon( icontype ); ++ QPixmap miniDevPM = loadIcon( filename ); + if ( !miniDevPM.isNull() ) + { + if ( m_small ) +@@ -608,6 +551,10 @@ MDWSlider::setIcon( int icontype ) + layout()->activate(); + } + ++QString MDWSlider::iconName() ++{ ++ return m_mixdevice->iconName(); ++} + + void + MDWSlider::toggleStereoLinked() +diff --git a/kmix/mdwslider.h b/kmix/mdwslider.h +index 49088ff..6093a46 100644 +--- a/kmix/mdwslider.h ++++ b/kmix/mdwslider.h +@@ -71,7 +71,7 @@ public: + void setMutedColors( QColor high, QColor low, QColor back ); + + bool eventFilter( QObject* obj, QEvent* e ); +- const QString& iconName() const { return _iconName; } ++ QString iconName(); + // Layout + QSizePolicy sizePolicy() const; + int playbackExtentHint() const; +@@ -107,8 +107,7 @@ private slots: + + private: + KShortcut dummyShortcut; +- QPixmap icon( int icontype ); +- void setIcon( int icontype ); ++ void setIcon( QString iconname ); + QPixmap loadIcon( QString& filename ); + void createWidgets( bool showMuteLED, bool showCaptureLED ); + void createWidgetsTopPart(QBoxLayout *, bool showMuteLED); +diff --git a/kmix/mixdevice.cpp b/kmix/mixdevice.cpp +index 54459c9..7b6e8ad 100644 +--- a/kmix/mixdevice.cpp ++++ b/kmix/mixdevice.cpp +@@ -25,6 +25,57 @@ + #include "mixdevice.h" + #include "volume.h" + ++static const QString channelTypeToIconName( MixDevice::ChannelType type ) ++{ ++ switch (type) { ++ case MixDevice::AUDIO: ++ return "mixer-pcm"; ++ case MixDevice::BASS: ++ case MixDevice::SURROUND_LFE: // "LFE" SHOULD have an own icon ++ return "mixer-lfe"; ++ case MixDevice::CD: ++ return "mixer-cd"; ++ case MixDevice::EXTERNAL: ++ return "mixer-line"; ++ case MixDevice::MICROPHONE: ++ return "mixer-microphone"; ++ case MixDevice::MIDI: ++ return "mixer-midi"; ++ case MixDevice::RECMONITOR: ++ return "mixer-capture"; ++ case MixDevice::TREBLE: ++ return "mixer-pcm-default"; ++ case MixDevice::UNKNOWN: ++ return "mixer-front"; ++ case MixDevice::VOLUME: ++ return "mixer-master"; ++ case MixDevice::VIDEO: ++ return "mixer-video"; ++ case MixDevice::SURROUND: ++ case MixDevice::SURROUND_BACK: ++ return "mixer-surround"; ++ case MixDevice::SURROUND_CENTERFRONT: ++ case MixDevice::SURROUND_CENTERBACK: ++ return "mixer-surround-center"; ++ case MixDevice::HEADPHONE: ++ return "mixer-headset"; ++ case MixDevice::DIGITAL: ++ return "mixer-digital"; ++ case MixDevice::AC97: ++ return "mixer-ac97"; ++ case MixDevice::SPEAKER: ++ return "mixer-pc-speaker"; ++ case MixDevice::MICROPHONE_BOOST: ++ return "mixer-microphone-boost"; ++ case MixDevice::MICROPHONE_FRONT_BOOST: ++ return "mixer-microphone-front-boost"; ++ case MixDevice::MICROPHONE_FRONT: ++ return "mixer-microphone-front"; ++ } ++ return "mixer-front"; ++} ++ ++ + /** + * Constructs a MixDevice. A MixDevice represents one channel or control of + * the mixer hardware. A MixDevice has a type (e.g. PCM), a descriptive name +@@ -35,13 +86,29 @@ + * Hints: Meaning of "category" has changed. In future the MixDevice might contain two + * Volume objects, one for Output (Playback volume) and one for Input (Record volume). + */ +-MixDevice::MixDevice( Mixer* mixer, const QString& id, const QString& name, ChannelType type ) : +- _mixer(mixer), _type( type ), _id( id ) ++MixDevice::MixDevice( Mixer* mixer, const QString& id, const QString& name, ChannelType type ) ++{ ++ init(mixer, id, name, channelTypeToIconName(type), false); ++} ++ ++MixDevice::MixDevice( Mixer* mixer, const QString& id, const QString& name, const QString& iconName, bool doNotRestore ) + { ++ init(mixer, id, name, iconName, doNotRestore); ++} ++ ++void MixDevice::init( Mixer* mixer, const QString& id, const QString& name, const QString& iconName, bool doNotRestore ) ++{ ++ _mixer = mixer; ++ _id = id; + if( name.isEmpty() ) + _name = i18n("unknown"); + else + _name = name; ++ if ( iconName.isEmpty() ) ++ _iconName = "mixer-front"; ++ else ++ _iconName = iconName; ++ _doNotRestore = doNotRestore; + if ( _id.contains(' ') ) { + // The key is used in the config file. It MUST NOT contain spaces + kError(67100) << "MixDevice::setId(\"" << id << "\") . Invalid key - it might not contain spaces" << endl; +@@ -116,6 +183,7 @@ bool MixDevice::operator==(const MixDevice& other) const + return ( _id == other._id ); + } + ++ + /** + * This methhod is currently only called on "kmixctrl --restore" + * +@@ -125,13 +193,17 @@ bool MixDevice::operator==(const MixDevice& other) const + */ + void MixDevice::read( KConfig *config, const QString& grp ) + { +- QString devgrp; +- devgrp.sprintf( "%s.Dev%s", grp.toAscii().data(), _id.toAscii().data() ); +- KConfigGroup cg = config->group( devgrp ); +- //kDebug(67100) << "MixDevice::read() of group devgrp=" << devgrp; ++ if (_doNotRestore) { ++ kDebug(67100) << "MixDevice::read(): This MixDevice does not permit volume restoration (i.e. because it is handled lower down in the audio stack). Ignoring."; ++ } else { ++ QString devgrp; ++ devgrp.sprintf( "%s.Dev%s", grp.toAscii().data(), _id.toAscii().data() ); ++ KConfigGroup cg = config->group( devgrp ); ++ //kDebug(67100) << "MixDevice::read() of group devgrp=" << devgrp; + +- readPlaybackOrCapture(cg, "volumeL" , "volumeR" , false); +- readPlaybackOrCapture(cg, "volumeLCapture", "volumeRCapture", true ); ++ readPlaybackOrCapture(cg, "volumeL" , "volumeR" , false); ++ readPlaybackOrCapture(cg, "volumeLCapture", "volumeRCapture", true ); ++ } + } + + void MixDevice::readPlaybackOrCapture(const KConfigGroup& config, const char* nameLeftVolume, const char* nameRightVolume, bool capture) +@@ -180,13 +252,17 @@ void MixDevice::readPlaybackOrCapture(const KConfigGroup& config, const char* na + */ + void MixDevice::write( KConfig *config, const QString& grp ) + { +- QString devgrp; +- devgrp.sprintf( "%s.Dev%s", grp.toAscii().data(), _id.toAscii().data() ); +- KConfigGroup cg = config->group(devgrp); +- // kDebug(67100) << "MixDevice::write() of group devgrp=" << devgrp; ++ if (_doNotRestore) { ++ kDebug(67100) << "MixDevice::write(): This MixDevice does not permit volume saving (i.e. because it is handled lower down in the audio stack). Ignoring."; ++ } else { ++ QString devgrp; ++ devgrp.sprintf( "%s.Dev%s", grp.toAscii().data(), _id.toAscii().data() ); ++ KConfigGroup cg = config->group(devgrp); ++ // kDebug(67100) << "MixDevice::write() of group devgrp=" << devgrp; + +- writePlaybackOrCapture(cg, "volumeL" , "volumeR" , false); +- writePlaybackOrCapture(cg, "volumeLCapture", "volumeRCapture", true ); ++ writePlaybackOrCapture(cg, "volumeL" , "volumeR" , false); ++ writePlaybackOrCapture(cg, "volumeLCapture", "volumeRCapture", true ); ++ } + } + + void MixDevice::writePlaybackOrCapture(KConfigGroup& config, const char* nameLeftVolume, const char* nameRightVolume, bool capture) +diff --git a/kmix/mixdevice.h b/kmix/mixdevice.h +index 7b2c93b..c55f987 100644 +--- a/kmix/mixdevice.h ++++ b/kmix/mixdevice.h +@@ -99,9 +99,12 @@ public: + * @par name is the readable name. This one is presented to the user in the GUI + * @par type The control type. It is only used to find an appropriate icon + */ +- MixDevice( Mixer* mixer, const QString& id, const QString& name, ChannelType type = UNKNOWN ); ++ MixDevice( Mixer* mixer, const QString& id, const QString& name, ChannelType type ); ++ MixDevice( Mixer* mixer, const QString& id, const QString& name, const QString& iconName = "", bool doNotRestore = false ); + ~MixDevice(); + ++ const QString& iconName() const { return _iconName; } ++ + void addPlaybackVolume(Volume &playbackVol); + void addCaptureVolume (Volume &captureVol); + void addEnums (QList& ref_enumList); +@@ -145,8 +148,6 @@ public: + virtual void read( KConfig *config, const QString& grp ); + virtual void write( KConfig *config, const QString& grp ); + +- ChannelType type() { return _type; } +- + private: + Mixer *_mixer; + Volume _playbackVolume; +@@ -154,11 +155,13 @@ private: + int _enumCurrentId; + QList _enumValues; // A MixDevice, that is an ENUM, has these _enumValues + +- ChannelType _type; ++ bool _doNotRestore; ++ QString _iconName; + + QString _name; // Channel name + QString _id; // Primary key, used as part in config file keys + ++ void init( Mixer* mixer, const QString& id, const QString& name, const QString& iconName, bool doNotRestore ); + void readPlaybackOrCapture(const KConfigGroup& config, const char* nameLeftVolume, const char* nameRightVolume, bool capture); + void writePlaybackOrCapture(KConfigGroup& config, const char* nameLeftVolume, const char* nameRightVolume, bool capture); + +diff --git a/kmix/mixer.cpp b/kmix/mixer.cpp +index 82bfba4..caf056e 100644 +--- a/kmix/mixer.cpp ++++ b/kmix/mixer.cpp +@@ -150,6 +150,7 @@ bool Mixer::openIfValid() { + setLocalMasterMD(noMaster); // no master + } + connect( _mixerBackend, SIGNAL(controlChanged()), SLOT(controlChangedForwarder()) ); ++ connect( _mixerBackend, SIGNAL(controlsReconfigured(int)), SLOT(controlsReconfiguredForwarder(int)) ); + + m_dbusName = "/Mixer" + QString::number(_mixerBackend->m_devnum); + QDBusConnection::sessionBus().registerObject(m_dbusName, this); +@@ -163,6 +164,11 @@ void Mixer::controlChangedForwarder() + emit controlChanged(); + } + ++void Mixer::controlsReconfiguredForwarder(int mixerTabIndex) ++{ ++ emit controlsReconfigured(mixerTabIndex); ++} ++ + /** + * Closes the mixer. + * Also, stops the polling timer. +diff --git a/kmix/mixer.h b/kmix/mixer.h +index e0cdf4d..e193b16 100644 +--- a/kmix/mixer.h ++++ b/kmix/mixer.h +@@ -166,6 +166,7 @@ class Mixer : public QObject + signals: + void newBalance( Volume& ); + void controlChanged(void); ++ void controlsReconfigured(int mixerTabIndex); + + protected: + int m_balance; // from -100 (just left) to 100 (just right) +@@ -173,6 +174,7 @@ class Mixer : public QObject + + private slots: + void controlChangedForwarder(); ++ void controlsReconfiguredForwarder(int mixerTabIndex); + + public: + static QList& mixers(); +diff --git a/kmix/mixer_alsa9.cpp b/kmix/mixer_alsa9.cpp +index 5f12bdc..1799535 100644 +--- a/kmix/mixer_alsa9.cpp ++++ b/kmix/mixer_alsa9.cpp +@@ -420,10 +420,10 @@ Volume* Mixer_ALSA::addVolume(snd_mixer_elem_t *elem, bool capture) + } + + +- // Chek if this control has at least one volume control ++ // Check if this control has at least one volume control + bool hasVolume = (chn != Volume::MNONE); + +- // Chek if a appropriate switch is present (appropriate means, based o nthe "capture" parameer) ++ // Check if a appropriate switch is present (appropriate means, based o nthe "capture" parameer) + bool hasCommonSwitch = snd_mixer_selem_has_common_switch ( elem ); + + bool hasSwitch = hasCommonSwitch | +diff --git a/kmix/mixer_backend.h b/kmix/mixer_backend.h +index 26e9557..3a7b5d7 100644 +--- a/kmix/mixer_backend.h ++++ b/kmix/mixer_backend.h +@@ -131,6 +131,7 @@ protected: + + signals: + void controlChanged( void ); ++ void controlsReconfigured( int mixerTabIndex ); + + protected slots: + virtual void readSetFromHW(); +diff --git a/kmix/mixer_pulse.cpp b/kmix/mixer_pulse.cpp +index 694b9a9..c2e5ad9 100644 +--- a/kmix/mixer_pulse.cpp ++++ b/kmix/mixer_pulse.cpp +@@ -20,12 +20,707 @@ + */ + + #include ++#include + + #include "mixer_pulse.h" + #include "mixer.h" + ++#include ++#include ++ ++ ++#define KMIXPA_PLAYBACK 0 ++#define KMIXPA_CAPTURE 1 ++#define KMIXPA_APP_PLAYBACK 2 ++#define KMIXPA_APP_CAPTURE 3 ++#define KMIXPA_WIDGET_MAX KMIXPA_APP_CAPTURE ++ ++static unsigned int refcount = 0; + static pa_context *context = NULL; + static pa_glib_mainloop *mainloop = NULL; ++static QEventLoop *s_connectionEventloop = NULL; ++static enum { UNKNOWN, ACTIVE, INACTIVE } s_pulseActive = UNKNOWN; ++static int s_OutstandingRequests = 0; ++ ++QMap s_Mixers; ++ ++typedef QMap devmap; ++static devmap outputDevices; ++static devmap captureDevices; ++static QMap clients; ++static devmap outputStreams; ++static devmap captureStreams; ++static devmap outputRoles; ++ ++static void dec_outstanding() { ++ if (s_OutstandingRequests <= 0) ++ return; ++ ++ if (--s_OutstandingRequests <= 0) ++ { ++ s_pulseActive = ACTIVE; ++ if (s_connectionEventloop) { ++ s_connectionEventloop->exit(0); ++ s_connectionEventloop = NULL; ++ ++ // If we have no devices then we consider PA to be 'INACTIVE' ++ if (outputDevices.isEmpty() && captureDevices.isEmpty()) ++ s_pulseActive = INACTIVE; ++ else ++ s_pulseActive = ACTIVE; ++ } ++ } ++} ++ ++static void translateMasksAndMaps(devinfo& dev) ++{ ++ dev.chanMask = Volume::MNONE; ++ dev.chanIDs.clear(); ++ ++ if (dev.channel_map.channels != dev.volume.channels) { ++ kError() << "Hiddeous Channel mixup map says " << dev.channel_map.channels << ", volume says: " << dev.volume.channels; ++ return; ++ } ++ if (1 == dev.channel_map.channels && PA_CHANNEL_POSITION_MONO == dev.channel_map.map[0]) { ++ // We just use the left channel to represent this. ++ dev.chanMask = (Volume::ChannelMask)( dev.chanMask | Volume::MLEFT); ++ dev.chanIDs[0] = Volume::LEFT; ++ } else { ++ for (uint8_t i = 0; i < dev.channel_map.channels; ++i) { ++ switch (dev.channel_map.map[i]) { ++ case PA_CHANNEL_POSITION_MONO: ++ kWarning(67100) << "Channel Map contains a MONO element but has >1 channel - we can't handle this."; ++ return; ++ ++ case PA_CHANNEL_POSITION_FRONT_LEFT: ++ dev.chanMask = (Volume::ChannelMask)( dev.chanMask | Volume::MLEFT); ++ dev.chanIDs[i] = Volume::LEFT; ++ break; ++ case PA_CHANNEL_POSITION_FRONT_RIGHT: ++ dev.chanMask = (Volume::ChannelMask)( dev.chanMask | Volume::MRIGHT); ++ dev.chanIDs[i] = Volume::RIGHT; ++ break; ++ case PA_CHANNEL_POSITION_FRONT_CENTER: ++ dev.chanMask = (Volume::ChannelMask)( dev.chanMask | Volume::MCENTER); ++ dev.chanIDs[i] = Volume::CENTER; ++ break; ++ case PA_CHANNEL_POSITION_REAR_CENTER: ++ dev.chanMask = (Volume::ChannelMask)( dev.chanMask | Volume::MREARCENTER); ++ dev.chanIDs[i] = Volume::REARCENTER; ++ break; ++ case PA_CHANNEL_POSITION_REAR_LEFT: ++ dev.chanMask = (Volume::ChannelMask)( dev.chanMask | Volume::MSURROUNDLEFT); ++ dev.chanIDs[i] = Volume::SURROUNDLEFT; ++ break; ++ case PA_CHANNEL_POSITION_REAR_RIGHT: ++ dev.chanMask = (Volume::ChannelMask)( dev.chanMask | Volume::MSURROUNDRIGHT); ++ dev.chanIDs[i] = Volume::SURROUNDRIGHT; ++ break; ++ case PA_CHANNEL_POSITION_LFE: ++ dev.chanMask = (Volume::ChannelMask)( dev.chanMask | Volume::MWOOFER); ++ dev.chanIDs[i] = Volume::WOOFER; ++ break; ++ case PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER: ++ dev.chanMask = (Volume::ChannelMask)( dev.chanMask | Volume::MREARSIDELEFT); ++ dev.chanIDs[i] = Volume::REARSIDELEFT; ++ break; ++ case PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER: ++ dev.chanMask = (Volume::ChannelMask)( dev.chanMask | Volume::MREARSIDERIGHT); ++ dev.chanIDs[i] = Volume::REARSIDERIGHT; ++ break; ++ default: ++ kWarning(67100) << "Channel Map contains a pa_channel_position we cannot handle " << dev.channel_map.map[i]; ++ break; ++ } ++ } ++ } ++} ++ ++static QString getIconNameFromProplist(pa_proplist *l) { ++ const char *t; ++ ++ if ((t = pa_proplist_gets(l, PA_PROP_MEDIA_ICON_NAME))) ++ return t; ++ ++ if ((t = pa_proplist_gets(l, PA_PROP_WINDOW_ICON_NAME))) ++ return t; ++ ++ if ((t = pa_proplist_gets(l, PA_PROP_APPLICATION_ICON_NAME))) ++ return t; ++ ++ if ((t = pa_proplist_gets(l, PA_PROP_MEDIA_ROLE))) { ++ ++ if (strcmp(t, "video") == 0 || strcmp(t, "phone") == 0) ++ return t; ++ ++ if (strcmp(t, "music") == 0) ++ return "audio"; ++ ++ if (strcmp(t, "game") == 0) ++ return "applications-games"; ++ ++ if (strcmp(t, "event") == 0) ++ return "dialog-information"; ++ } ++ ++ return ""; ++} ++ ++static void sink_cb(pa_context *c, const pa_sink_info *i, int eol, void *) { ++ ++ Q_ASSERT(c == context); ++ ++ if (eol < 0) { ++ if (pa_context_errno(c) == PA_ERR_NOENTITY) ++ return; ++ ++ kWarning(67100) << "Sink callback failure"; ++ return; ++ } ++ ++ if (eol > 0) { ++ dec_outstanding(); ++ if (s_Mixers.contains(KMIXPA_PLAYBACK)) ++ s_Mixers[KMIXPA_PLAYBACK]->triggerUpdate(); ++ return; ++ } ++ ++ devinfo s; ++ s.index = s.device_index = i->index; ++ s.restore.name = i->name; ++ s.restore.device = ""; ++ s.name = QString(i->name).replace(' ', '_'); ++ s.description = i->description; ++ s.icon_name = pa_proplist_gets(i->proplist, PA_PROP_DEVICE_ICON_NAME); ++ s.volume = i->volume; ++ s.channel_map = i->channel_map; ++ s.mute = !!i->mute; ++ ++ translateMasksAndMaps(s); ++ ++ bool is_new = !outputDevices.contains(s.index); ++ outputDevices[s.index] = s; ++ kDebug(67100) << "Got some info about sink: " << s.description; ++ ++ if (is_new && s_Mixers.contains(KMIXPA_PLAYBACK)) ++ s_Mixers[KMIXPA_PLAYBACK]->addWidget(s.index); ++} ++ ++static void source_cb(pa_context *c, const pa_source_info *i, int eol, void *) { ++ ++ Q_ASSERT(c == context); ++ ++ if (eol < 0) { ++ if (pa_context_errno(c) == PA_ERR_NOENTITY) ++ return; ++ ++ kWarning(67100) << "Source callback failure"; ++ return; ++ } ++ ++ if (eol > 0) { ++ dec_outstanding(); ++ if (s_Mixers.contains(KMIXPA_CAPTURE)) ++ s_Mixers[KMIXPA_CAPTURE]->triggerUpdate(); ++ return; ++ } ++ ++ // Do something.... ++ if (PA_INVALID_INDEX != i->monitor_of_sink) ++ { ++ kDebug(67100) << "Ignoring Monitor Source: " << i->description; ++ return; ++ } ++ ++ devinfo s; ++ s.index = s.device_index = i->index; ++ s.restore.name = i->name; ++ s.restore.device = ""; ++ s.name = QString(i->name).replace(' ', '_'); ++ s.description = i->description; ++ s.icon_name = pa_proplist_gets(i->proplist, PA_PROP_DEVICE_ICON_NAME); ++ s.volume = i->volume; ++ s.channel_map = i->channel_map; ++ s.mute = !!i->mute; ++ ++ translateMasksAndMaps(s); ++ ++ bool is_new = !captureDevices.contains(s.index); ++ captureDevices[s.index] = s; ++ kDebug(67100) << "Got some info about source: " << s.description; ++ ++ if (is_new && s_Mixers.contains(KMIXPA_CAPTURE)) ++ s_Mixers[KMIXPA_CAPTURE]->addWidget(s.index); ++} ++ ++static void client_cb(pa_context *c, const pa_client_info *i, int eol, void *) { ++ ++ Q_ASSERT(c == context); ++ ++ if (eol < 0) { ++ if (pa_context_errno(c) == PA_ERR_NOENTITY) ++ return; ++ ++ kWarning(67100) << "Client callback failure"; ++ return; ++ } ++ ++ if (eol > 0) { ++ dec_outstanding(); ++ return; ++ } ++ ++ clients[i->index] = i->name; ++ kDebug(67100) << "Got some info about client: " << i->name; ++} ++ ++static void sink_input_cb(pa_context *c, const pa_sink_input_info *i, int eol, void *) { ++ ++ Q_ASSERT(c == context); ++ ++ if (eol < 0) { ++ if (pa_context_errno(c) == PA_ERR_NOENTITY) ++ return; ++ ++ kWarning(67100) << "Sink Input callback failure"; ++ return; ++ } ++ ++ if (eol > 0) { ++ dec_outstanding(); ++ if (s_Mixers.contains(KMIXPA_APP_PLAYBACK)) ++ s_Mixers[KMIXPA_APP_PLAYBACK]->triggerUpdate(); ++ return; ++ } ++ ++ const char *t; ++ if ((t = pa_proplist_gets(i->proplist, "module-stream-restore.id"))) { ++ if (strcmp(t, "sink-input-by-media-role:event") == 0) { ++ kWarning(67100) << "Ignoring sink-input due to it being designated as an event and thus handled by the Event slider"; ++ return; ++ } ++ } ++ ++ QString prefix = QString("%1: ").arg(i18n("Unknown Application")); ++ if (clients.contains(i->client)) ++ prefix = QString("%1: ").arg(clients[i->client]); ++ ++ devinfo s; ++ s.index = i->index; ++ s.device_index = i->sink; ++ s.restore.name = i->name; ++ s.restore.device = ""; ++ s.description = prefix + i->name; ++ s.name = QString("stream:") + QString(s.description).replace(' ', '_'); ++ s.icon_name = getIconNameFromProplist(i->proplist); ++ s.volume = i->volume; ++ s.channel_map = i->channel_map; ++ s.mute = !!i->mute; ++ ++ translateMasksAndMaps(s); ++ ++ bool is_new = !outputStreams.contains(s.index); ++ outputStreams[s.index] = s; ++ kDebug(67100) << "Got some info about sink input (playback stream): " << s.description; ++ ++ if (is_new && s_Mixers.contains(KMIXPA_APP_PLAYBACK)) ++ s_Mixers[KMIXPA_APP_PLAYBACK]->addWidget(s.index); ++} ++ ++static void source_output_cb(pa_context *c, const pa_source_output_info *i, int eol, void *) { ++ ++ Q_ASSERT(c == context); ++ ++ if (eol < 0) { ++ if (pa_context_errno(c) == PA_ERR_NOENTITY) ++ return; ++ ++ kWarning(67100) << "Source Output callback failure"; ++ return; ++ } ++ ++ if (eol > 0) { ++ dec_outstanding(); ++ if (s_Mixers.contains(KMIXPA_APP_CAPTURE)) ++ s_Mixers[KMIXPA_APP_CAPTURE]->triggerUpdate(); ++ return; ++ } ++ ++ /* NB Until Source Outputs support volumes, we just use the volume of the source itself */ ++ if (!captureDevices.contains(i->source)) { ++ kWarning(67100) << "Source Output refers to a Source we don't have any info for :s"; ++ return; ++ } ++ ++ QString prefix = QString("%1: ").arg(i18n("Unknown Application")); ++ if (clients.contains(i->client)) ++ prefix = QString("%1: ").arg(clients[i->client]); ++ ++ devinfo s; ++ s.index = i->index; ++ s.device_index = i->source; ++ s.restore.name = i->name; ++ s.restore.device = ""; ++ s.description = prefix + i->name; ++ s.name = QString("stream:") + QString(s.description).replace(' ', '_'); ++ s.icon_name = getIconNameFromProplist(i->proplist); ++ //s.volume = i->volume; ++ s.volume = captureDevices[i->source].volume; ++ s.channel_map = i->channel_map; ++ //s.mute = !!i->mute; ++ s.mute = captureDevices[i->source].mute; ++ ++ translateMasksAndMaps(s); ++ ++ bool is_new = !captureStreams.contains(s.index); ++ captureStreams[s.index] = s; ++ kDebug(67100) << "Got some info about source output (capture stream): " << s.description; ++ ++ if (is_new && s_Mixers.contains(KMIXPA_APP_CAPTURE)) ++ s_Mixers[KMIXPA_APP_CAPTURE]->addWidget(s.index); ++} ++ ++ ++static devinfo create_role_devinfo(const char* name, const char* device, const pa_volume_t vol, int mute) { ++ ++ // Fake a Mono Device/Volume ++ pa_cvolume volume; ++ volume.channels = 1; ++ volume.values[0] = vol; ++ pa_channel_map channel_map; ++ channel_map.channels = 1; ++ channel_map.map[0] = PA_CHANNEL_POSITION_MONO; ++ ++ devinfo s; ++ s.index = s.device_index = PA_INVALID_INDEX; ++ s.restore.name = name; ++ s.restore.device = device; ++ s.description = i18n("Event Sounds"); ++ s.name = QString("restore:") + name; ++ s.icon_name = "dialog-information"; ++ s.volume = volume; ++ s.channel_map = channel_map; ++ s.mute = !!mute; ++ ++ translateMasksAndMaps(s); ++ return s; ++} ++ ++ ++void ext_stream_restore_read_cb(pa_context *c, const pa_ext_stream_restore_info *i, int eol, void *) { ++ ++ Q_ASSERT(c == context); ++ ++ if (eol < 0) { ++ dec_outstanding(); ++ kWarning(67100) << "Failed to initialize stream_restore extension: " << pa_strerror(pa_context_errno(context)); ++ return; ++ } ++ ++ if (eol > 0) { ++ dec_outstanding(); ++ // Special case: ensure that our media events exists. ++ // On first login by a new users, this wont be in our database so we should create it. ++ if (!outputRoles.contains(PA_INVALID_INDEX)) { ++ devinfo s = create_role_devinfo("sink-input-by-media-role:event", NULL, PA_VOLUME_NORM, 0); ++ outputRoles[s.index] = s; ++ kDebug(67100) << "Initialising restore rule for new user: " << s.description; ++ } ++ ++ if (s_Mixers.contains(KMIXPA_APP_PLAYBACK)) ++ s_Mixers[KMIXPA_APP_PLAYBACK]->triggerUpdate(); ++ return; ++ } ++ ++ // We only want to know about Sound Events for now... ++ if (strcmp(i->name, "sink-input-by-media-role:event") != 0) ++ return; ++ ++ devinfo s = create_role_devinfo(i->name, i->device, pa_cvolume_max(&i->volume), i->mute); ++ outputRoles[s.index] = s; ++ kDebug(67100) << "Got some info about restore rule: " << s.description; ++} ++ ++static void ext_stream_restore_subscribe_cb(pa_context *c, void *) { ++ ++ Q_ASSERT(c == context); ++ ++ pa_operation *o; ++ if (!(o = pa_ext_stream_restore_read(c, ext_stream_restore_read_cb, NULL))) { ++ kWarning(67100) << "pa_ext_stream_restore_read() failed"; ++ return; ++ } ++ ++ pa_operation_unref(o); ++} ++ ++ ++static void subscribe_cb(pa_context *c, pa_subscription_event_type_t t, uint32_t index, void *) { ++ ++ Q_ASSERT(c == context); ++ ++ switch (t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) { ++ case PA_SUBSCRIPTION_EVENT_SINK: ++ if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) { ++ if (s_Mixers.contains(KMIXPA_PLAYBACK)) ++ s_Mixers[KMIXPA_PLAYBACK]->removeWidget(index); ++ } else { ++ pa_operation *o; ++ if (!(o = pa_context_get_sink_info_by_index(c, index, sink_cb, NULL))) { ++ kWarning(67100) << "pa_context_get_sink_info_by_index() failed"; ++ return; ++ } ++ pa_operation_unref(o); ++ } ++ break; ++ ++ case PA_SUBSCRIPTION_EVENT_SOURCE: ++ if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) { ++ if (s_Mixers.contains(KMIXPA_CAPTURE)) ++ s_Mixers[KMIXPA_CAPTURE]->removeWidget(index); ++ } else { ++ pa_operation *o; ++ if (!(o = pa_context_get_source_info_by_index(c, index, source_cb, NULL))) { ++ kWarning(67100) << "pa_context_get_source_info_by_index() failed"; ++ return; ++ } ++ pa_operation_unref(o); ++ } ++ break; ++ ++ case PA_SUBSCRIPTION_EVENT_SINK_INPUT: ++ if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) { ++ if (s_Mixers.contains(KMIXPA_APP_PLAYBACK)) ++ s_Mixers[KMIXPA_APP_PLAYBACK]->removeWidget(index); ++ } else { ++ pa_operation *o; ++ if (!(o = pa_context_get_sink_input_info(c, index, sink_input_cb, NULL))) { ++ kWarning(67100) << "pa_context_get_sink_input_info() failed"; ++ return; ++ } ++ pa_operation_unref(o); ++ } ++ break; ++ ++ case PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT: ++ if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) { ++ if (s_Mixers.contains(KMIXPA_APP_CAPTURE)) ++ s_Mixers[KMIXPA_APP_CAPTURE]->removeWidget(index); ++ } else { ++ pa_operation *o; ++ if (!(o = pa_context_get_source_output_info(c, index, source_output_cb, NULL))) { ++ kWarning(67100) << "pa_context_get_sink_input_info() failed"; ++ return; ++ } ++ pa_operation_unref(o); ++ } ++ break; ++ ++ case PA_SUBSCRIPTION_EVENT_CLIENT: ++ if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) { ++ clients.remove(index); ++ } else { ++ pa_operation *o; ++ if (!(o = pa_context_get_client_info(c, index, client_cb, NULL))) { ++ kWarning(67100) << "pa_context_get_client_info() failed"; ++ return; ++ } ++ pa_operation_unref(o); ++ } ++ break; ++ ++ } ++} ++ ++ ++static void context_state_callback(pa_context *c, void *) ++{ ++ Q_ASSERT(c == context); ++ ++ switch (pa_context_get_state(c)) { ++ case PA_CONTEXT_UNCONNECTED: ++ case PA_CONTEXT_CONNECTING: ++ case PA_CONTEXT_AUTHORIZING: ++ case PA_CONTEXT_SETTING_NAME: ++ break; ++ ++ case PA_CONTEXT_READY: ++ // Attempt to load things up ++ pa_operation *o; ++ ++ pa_context_set_subscribe_callback(c, subscribe_cb, NULL); ++ ++ if (!(o = pa_context_subscribe(c, (pa_subscription_mask_t) ++ (PA_SUBSCRIPTION_MASK_SINK| ++ PA_SUBSCRIPTION_MASK_SOURCE| ++ PA_SUBSCRIPTION_MASK_CLIENT| ++ PA_SUBSCRIPTION_MASK_SINK_INPUT| ++ PA_SUBSCRIPTION_MASK_SOURCE_OUTPUT), NULL, NULL))) { ++ kWarning(67100) << "pa_context_subscribe() failed"; ++ return; ++ } ++ pa_operation_unref(o); ++ ++ if (!(o = pa_context_get_sink_info_list(c, sink_cb, NULL))) { ++ kWarning(67100) << "pa_context_get_sink_info_list() failed"; ++ return; ++ } ++ pa_operation_unref(o); ++ s_OutstandingRequests++; ++ ++ if (!(o = pa_context_get_source_info_list(c, source_cb, NULL))) { ++ kWarning(67100) << "pa_context_get_source_info_list() failed"; ++ return; ++ } ++ pa_operation_unref(o); ++ s_OutstandingRequests++; ++ ++ ++ if (!(o = pa_context_get_client_info_list(c, client_cb, NULL))) { ++ kWarning(67100) << "pa_context_client_info_list() failed"; ++ return; ++ } ++ pa_operation_unref(o); ++ s_OutstandingRequests++; ++ ++ if (!(o = pa_context_get_sink_input_info_list(c, sink_input_cb, NULL))) { ++ kWarning(67100) << "pa_context_get_sink_input_info_list() failed"; ++ return; ++ } ++ pa_operation_unref(o); ++ s_OutstandingRequests++; ++ ++ if (!(o = pa_context_get_source_output_info_list(c, source_output_cb, NULL))) { ++ kWarning(67100) << "pa_context_get_source_output_info_list() failed"; ++ return; ++ } ++ pa_operation_unref(o); ++ s_OutstandingRequests++; ++ ++ /* These calls are not always supported */ ++ if ((o = pa_ext_stream_restore_read(c, ext_stream_restore_read_cb, NULL))) { ++ pa_operation_unref(o); ++ s_OutstandingRequests++; ++ ++ pa_ext_stream_restore_set_subscribe_cb(c, ext_stream_restore_subscribe_cb, NULL); ++ ++ if ((o = pa_ext_stream_restore_subscribe(c, 1, NULL, NULL))) ++ pa_operation_unref(o); ++ } else ++ kWarning(67100) << "Failed to initialize stream_restore extension: " << pa_strerror(pa_context_errno(context)); ++ ++ break; ++ ++ case PA_CONTEXT_FAILED: ++ s_pulseActive = INACTIVE; ++ if (s_connectionEventloop) { ++ s_connectionEventloop->exit(0); ++ s_connectionEventloop = NULL; ++ } ++ break; ++ ++ case PA_CONTEXT_TERMINATED: ++ default: ++ s_pulseActive = INACTIVE; ++ /// @todo Deal with reconnection... ++ break; ++ } ++} ++ ++static void setVolumeFromPulse(Volume& volume, const devinfo& dev) ++{ ++ chanIDMap::const_iterator iter; ++ for (iter = dev.chanIDs.begin(); iter != dev.chanIDs.end(); ++iter) ++ { ++ //kDebug(67100) << "Setting volume for channel " << iter.value() << " to " << (long)dev.volume.values[iter.key()] << " (" << ((100*(long)dev.volume.values[iter.key()]) / PA_VOLUME_NORM) << "%)"; ++ volume.setVolume(iter.value(), (long)dev.volume.values[iter.key()]); ++ } ++} ++ ++static pa_cvolume genVolumeForPulse(const devinfo& dev, Volume& volume) ++{ ++ pa_cvolume cvol = dev.volume; ++ ++ chanIDMap::const_iterator iter; ++ for (iter = dev.chanIDs.begin(); iter != dev.chanIDs.end(); ++iter) ++ { ++ cvol.values[iter.key()] = (uint32_t)volume.getVolume(iter.value()); ++ //kDebug(67100) << "Setting volume for channel " << iter.value() << " to " << cvol.values[iter.key()] << " (" << ((100*cvol.values[iter.key()]) / PA_VOLUME_NORM) << "%)"; ++ } ++ return cvol; ++} ++ ++static devmap* get_widget_map(int type) ++{ ++ Q_ASSERT(type >= 0 && type <= KMIXPA_WIDGET_MAX); ++ ++ if (KMIXPA_PLAYBACK == type) ++ return &outputDevices; ++ else if (KMIXPA_CAPTURE == type) ++ return &captureDevices; ++ else if (KMIXPA_APP_PLAYBACK == type) ++ return &outputStreams; ++ else if (KMIXPA_APP_CAPTURE == type) ++ return &captureStreams; ++ ++ Q_ASSERT(0); ++ return NULL; ++} ++ ++void Mixer_PULSE::addWidget(int index) ++{ ++ devmap* map = get_widget_map(m_devnum); ++ bool capture = (KMIXPA_CAPTURE == m_devnum || KMIXPA_APP_CAPTURE == m_devnum); ++ ++ if (!map->contains(index)) { ++ kWarning(67100) << "New " << m_devnum << " widget notified for index " << index << " but I cannot find it in my list :s"; ++ return; ++ } ++ addDevice((*map)[index], capture); ++ emit controlsReconfigured(m_devnum); ++} ++ ++void Mixer_PULSE::removeWidget(int index) ++{ ++ devmap* map = get_widget_map(m_devnum); ++ ++ if (!map->contains(index)) { ++ //kWarning(67100) << "Removing " << m_devnum << " widget notified for index " << index << " but I cannot find it in my list :s"; ++ // Sometimes we ignore things (e.g. event sounds) so don't be too noisy here. ++ return; ++ } ++ ++ QString id = (*map)[index].name; ++ map->remove(index); ++ ++ // We need to find the MixDevice that goes with this widget and remove it. ++ MixSet::iterator iter; ++ for (iter = m_mixDevices.begin(); iter != m_mixDevices.end(); ++iter) ++ { ++ if ((*iter)->id() == id) ++ { ++ delete *iter; ++ m_mixDevices.erase(iter); ++ emit controlsReconfigured(m_devnum); ++ return; ++ } ++ } ++} ++ ++void Mixer_PULSE::addDevice(devinfo& dev, bool capture) ++{ ++ if (dev.chanMask != Volume::MNONE) { ++ Volume v(dev.chanMask, PA_VOLUME_NORM, PA_VOLUME_MUTED, !capture/*mute switch*/, capture); ++ setVolumeFromPulse(v, dev); ++ MixDevice* md = new MixDevice( _mixer, dev.name, dev.description, dev.icon_name, true); ++ if (capture) ++ md->addCaptureVolume(v); ++ else ++ md->addPlaybackVolume(v); ++ md->setMuted(dev.mute); ++ m_mixDevices.append(md); ++ } ++} + + Mixer_Backend* PULSE_getMixer( Mixer *mixer, int devnum ) + { +@@ -39,203 +734,286 @@ Mixer_PULSE::Mixer_PULSE(Mixer *mixer, int devnum) : Mixer_Backend(mixer, devnum + { + if ( devnum == -1 ) + m_devnum = 0; ++ ++ QString pulseenv = qgetenv("KMIX_PULSEAUDIO_DISABLE"); ++ if (pulseenv.toInt()) ++ s_pulseActive = INACTIVE; ++ ++ ++refcount; ++ if (INACTIVE != s_pulseActive && 1 == refcount) ++ { ++ mainloop = pa_glib_mainloop_new(g_main_context_default()); ++ g_assert(mainloop); ++ ++ pa_mainloop_api *api = pa_glib_mainloop_get_api(mainloop); ++ g_assert(api); ++ ++ context = pa_context_new(api, "KMix KDE 4"); ++ g_assert(context); ++ ++ // We create a simple event loop to allow the glib loop ++ // to iterate until we've connected or not to the server. ++ s_connectionEventloop = new QEventLoop; ++ ++ kDebug(67100) << "Attempting connection to PulseAudio sound daemon"; ++ // (cg) Convert to PA_CONTEXT_NOFLAGS when PulseAudio 0.9.19 is required ++ if (pa_context_connect(context, NULL, static_cast(0), 0) >= 0) { ++ pa_context_set_state_callback(context, &context_state_callback, s_connectionEventloop); ++ // Now we block until we connect or otherwise... ++ s_connectionEventloop->exec(); ++ } ++ kDebug(67100) << "PulseAudio status: " << (s_pulseActive==UNKNOWN ? "Unknown (bug)" : (s_pulseActive==ACTIVE ? "Active" : "Inactive")); ++ } ++ ++ s_Mixers[m_devnum] = this; + } + + Mixer_PULSE::~Mixer_PULSE() + { +- close(); ++ s_Mixers.remove(m_devnum); ++ ++ if (refcount > 0) ++ { ++ --refcount; ++ if (0 == refcount) ++ { ++ if (context) { ++ pa_context_unref(context); ++ context = NULL; ++ } ++ ++ if (mainloop) { ++ pa_glib_mainloop_free(mainloop); ++ mainloop = NULL; ++ } ++ } ++ } + } + + int Mixer_PULSE::open() + { +- kDebug(67100) << "Trying Pulse sink"; +- mainloop = pa_glib_mainloop_new(g_main_context_default()); +- g_assert(mainloop); +- pa_mainloop_api *api = pa_glib_mainloop_get_api(mainloop); +- g_assert(api); +- +- context = pa_context_new(api, "KMix KDE 4"); +- g_assert(context); +- //return Mixer::ERR_OPEN; +- +-/* +- // +- // Mixer is open. Now define all of the mix devices. +- // +- +- for ( int idx = 0; idx < numDevs; idx++ ) +- { +- Volume vol( 2, AUDIO_MAX_GAIN ); +- QString id; +- id.setNum(idx); +- MixDevice* md = new MixDevice( _mixer, id, +- QString(MixerDevNames[idx]), MixerChannelTypes[idx]); +- md->addPlaybackVolume(vol); +- md->setRecSource( isRecsrcHW( idx ) ); +- m_mixDevices.append( md ); +- } +-*/ +- +- m_mixerName = "PULSE Audio Mixer"; +- +- m_isOpen = true; ++ //kDebug(67100) << "Trying Pulse sink"; ++ ++ if (ACTIVE == s_pulseActive && m_devnum <= KMIXPA_APP_CAPTURE) ++ { ++ devmap::iterator iter; ++ if (KMIXPA_PLAYBACK == m_devnum) ++ { ++ m_mixerName = i18n("Playback Devices"); ++ for (iter = outputDevices.begin(); iter != outputDevices.end(); ++iter) ++ addDevice(*iter, false); ++ } ++ else if (KMIXPA_CAPTURE == m_devnum) ++ { ++ m_mixerName = i18n("Capture Devices"); ++ for (iter = captureDevices.begin(); iter != captureDevices.end(); ++iter) ++ addDevice(*iter, true); ++ } ++ else if (KMIXPA_APP_PLAYBACK == m_devnum) ++ { ++ m_mixerName = i18n("Playback Streams"); ++ for (iter = outputRoles.begin(); iter != outputRoles.end(); ++iter) ++ addDevice(*iter, false); ++ for (iter = outputStreams.begin(); iter != outputStreams.end(); ++iter) ++ addDevice(*iter, false); ++ } ++ else if (KMIXPA_APP_CAPTURE == m_devnum) ++ { ++ m_mixerName = i18n("Capture Streams"); ++ for (iter = captureStreams.begin(); iter != captureStreams.end(); ++iter) ++ addDevice(*iter, true); ++ } + ++ kDebug(67100) << "Using PulseAudio for mixer: " << m_mixerName; ++ m_isOpen = true; ++ } ++ + return 0; + } + + int Mixer_PULSE::close() + { +- if (context) +- { +- pa_context_unref(context); +- context = NULL; +- } +- if (mainloop) +- { +- pa_glib_mainloop_free(mainloop); +- mainloop = NULL; +- } + return 1; + } + + int Mixer_PULSE::readVolumeFromHW( const QString& id, MixDevice *md ) + { +-/* audio_info_t audioinfo; +- uint_t devMask = MixerSunPortMasks[devnum]; +- +- Volume& volume = md->playbackVolume(); +- int devnum = id2num(id); +- // +- // Read the current audio information from the driver +- // +- if ( ioctl( fd, AUDIO_GETINFO, &audioinfo ) < 0 ) +- { +- return( Mixer::ERR_READ ); +- } +- else +- { +- // +- // Extract the appropriate fields based on the requested device +- // +- switch ( devnum ) +- { +- case MIXERDEV_MASTER_VOLUME : +- volume.setSwitchActivated( audioinfo.output_muted ); +- GainBalanceToVolume( audioinfo.play.gain, +- audioinfo.play.balance, +- volume ); +- break; +- +- case MIXERDEV_RECORD_MONITOR : +- md->setMuted(false); +- volume.setAllVolumes( audioinfo.monitor_gain ); +- break; ++ devmap *map = NULL; ++ Volume *vol = NULL; + +- case MIXERDEV_INTERNAL_SPEAKER : +- case MIXERDEV_HEADPHONE : +- case MIXERDEV_LINE_OUT : +- md->setMuted( (audioinfo.play.port & devMask) ? false : true ); +- GainBalanceToVolume( audioinfo.play.gain, +- audioinfo.play.balance, +- volume ); +- break; ++ if (KMIXPA_PLAYBACK == m_devnum) { ++ map = &outputDevices; ++ vol = &md->playbackVolume(); ++ } else if (KMIXPA_CAPTURE == m_devnum) { ++ map = &captureDevices; ++ vol = &md->captureVolume(); ++ } else if (KMIXPA_APP_PLAYBACK == m_devnum) { ++ if (id.startsWith("stream:")) ++ map = &outputStreams; ++ else if (id.startsWith("restore:")) ++ map = &outputRoles; ++ vol = &md->playbackVolume(); ++ } else if (KMIXPA_APP_CAPTURE == m_devnum) { ++ map = &captureStreams; ++ vol = &md->captureVolume(); ++ } + +- case MIXERDEV_MICROPHONE : +- case MIXERDEV_LINE_IN : +- case MIXERDEV_CD : +- md->setMuted( (audioinfo.record.port & devMask) ? false : true ); +- GainBalanceToVolume( audioinfo.record.gain, +- audioinfo.record.balance, +- volume ); ++ Q_ASSERT(map); ++ Q_ASSERT(vol); ++ ++ devmap::iterator iter; ++ for (iter = map->begin(); iter != map->end(); ++iter) ++ { ++ if (iter->name == id) ++ { ++ setVolumeFromPulse(*vol, *iter); ++ md->setMuted(iter->mute); + break; ++ } ++ } + +- default : +- return Mixer::ERR_READ; +- } +- return 0; +- }*/ +- return 0; ++ return 0; + } + + int Mixer_PULSE::writeVolumeToHW( const QString& id, MixDevice *md ) + { +-/* uint_t gain; +- uchar_t balance; +- uchar_t mute; +- +- Volume& volume = md->playbackVolume(); +- int devnum = id2num(id); +- // +- // Convert the Volume(left vol, right vol) to the Gain/Balance Sun uses +- // +- VolumeToGainBalance( volume, gain, balance ); +- mute = md->isMuted() ? 1 : 0; +- +- // +- // Read the current audio settings from the hardware +- // +- audio_info_t audioinfo; +- if ( ioctl( fd, AUDIO_GETINFO, &audioinfo ) < 0 ) +- { +- return( Mixer::ERR_READ ); +- } ++ devmap::iterator iter; ++ if (KMIXPA_PLAYBACK == m_devnum) ++ { ++ for (iter = outputDevices.begin(); iter != outputDevices.end(); ++iter) ++ { ++ if (iter->name == id) ++ { ++ pa_operation *o; + +- // +- // Now, based on the devnum that we are writing to, update the appropriate +- // volume field and twiddle the appropriate bitmask to enable/mute the +- // device as necessary. +- // +- switch ( devnum ) +- { +- case MIXERDEV_MASTER_VOLUME : +- audioinfo.play.gain = gain; +- audioinfo.play.balance = balance; +- audioinfo.output_muted = mute; +- break; +- +- case MIXERDEV_RECORD_MONITOR : +- audioinfo.monitor_gain = gain; +- // no mute or balance for record monitor +- break; +- +- case MIXERDEV_INTERNAL_SPEAKER : +- case MIXERDEV_HEADPHONE : +- case MIXERDEV_LINE_OUT : +- audioinfo.play.gain = gain; +- audioinfo.play.balance = balance; +- if ( mute ) +- audioinfo.play.port &= ~MixerSunPortMasks[devnum]; +- else +- audioinfo.play.port |= MixerSunPortMasks[devnum]; +- break; +- +- case MIXERDEV_MICROPHONE : +- case MIXERDEV_LINE_IN : +- case MIXERDEV_CD : +- audioinfo.record.gain = gain; +- audioinfo.record.balance = balance; +- if ( mute ) +- audioinfo.record.port &= ~MixerSunPortMasks[devnum]; +- else +- audioinfo.record.port |= MixerSunPortMasks[devnum]; +- break; +- +- default : +- return Mixer::ERR_READ; +- } ++ pa_cvolume volume = genVolumeForPulse(*iter, md->playbackVolume()); ++ if (!(o = pa_context_set_sink_volume_by_index(context, iter->index, &volume, NULL, NULL))) { ++ kWarning(67100) << "pa_context_set_sink_volume_by_index() failed"; ++ return Mixer::ERR_READ; ++ } ++ pa_operation_unref(o); + +- // +- // Now that we've updated the audioinfo struct, write it back to the hardware +- // +- if ( ioctl( fd, AUDIO_SETINFO, &audioinfo ) < 0 ) +- { +- return( Mixer::ERR_WRITE ); +- } +- else +- { +- return 0; +- }*/ +- return 0; ++ if (!(o = pa_context_set_sink_mute_by_index(context, iter->index, (md->isMuted() ? 1 : 0), NULL, NULL))) { ++ kWarning(67100) << "pa_context_set_sink_mute_by_index() failed"; ++ return Mixer::ERR_READ; ++ } ++ pa_operation_unref(o); ++ ++ return 0; ++ } ++ } ++ } ++ else if (KMIXPA_CAPTURE == m_devnum) ++ { ++ for (iter = captureDevices.begin(); iter != captureDevices.end(); ++iter) ++ { ++ if (iter->name == id) ++ { ++ pa_operation *o; ++ ++ pa_cvolume volume = genVolumeForPulse(*iter, md->captureVolume()); ++ if (!(o = pa_context_set_source_volume_by_index(context, iter->index, &volume, NULL, NULL))) { ++ kWarning(67100) << "pa_context_set_source_volume_by_index() failed"; ++ return Mixer::ERR_READ; ++ } ++ pa_operation_unref(o); ++ ++ if (!(o = pa_context_set_source_mute_by_index(context, iter->index, (md->isMuted() ? 1 : 0), NULL, NULL))) { ++ kWarning(67100) << "pa_context_set_source_mute_by_index() failed"; ++ return Mixer::ERR_READ; ++ } ++ pa_operation_unref(o); ++ ++ return 0; ++ } ++ } ++ } ++ else if (KMIXPA_APP_PLAYBACK == m_devnum) ++ { ++ if (id.startsWith("stream:")) ++ { ++ for (iter = outputStreams.begin(); iter != outputStreams.end(); ++iter) ++ { ++ if (iter->name == id) ++ { ++ pa_operation *o; ++ ++ pa_cvolume volume = genVolumeForPulse(*iter, md->playbackVolume()); ++ if (!(o = pa_context_set_sink_input_volume(context, iter->index, &volume, NULL, NULL))) { ++ kWarning(67100) << "pa_context_set_sink_input_volume() failed"; ++ return Mixer::ERR_READ; ++ } ++ pa_operation_unref(o); ++ ++ if (!(o = pa_context_set_sink_input_mute(context, iter->index, (md->isMuted() ? 1 : 0), NULL, NULL))) { ++ kWarning(67100) << "pa_context_set_sink_input_mute() failed"; ++ return Mixer::ERR_READ; ++ } ++ pa_operation_unref(o); ++ ++ return 0; ++ } ++ } ++ } ++ else if (id.startsWith("restore:")) ++ { ++ for (iter = outputRoles.begin(); iter != outputRoles.end(); ++iter) ++ { ++ if (iter->name == id) ++ { ++ pa_ext_stream_restore_info info; ++ info.name = iter->restore.name.toLatin1().constData(); ++ info.channel_map = iter->channel_map; ++ info.volume = genVolumeForPulse(*iter, md->playbackVolume()); ++ info.device = iter->restore.device.isEmpty() ? NULL : iter->restore.device.toLatin1().constData(); ++ info.mute = (md->isMuted() ? 1 : 0); ++ ++ pa_operation* o; ++ if (!(o = pa_ext_stream_restore_write(context, PA_UPDATE_REPLACE, &info, 1, TRUE, NULL, NULL))) { ++ kWarning(67100) << "pa_ext_stream_restore_write() failed" << info.channel_map.channels << info.volume.channels << info.name; ++ return Mixer::ERR_READ; ++ } ++ pa_operation_unref(o); ++ ++ return 0; ++ } ++ } ++ } ++ } ++ else if (KMIXPA_APP_CAPTURE == m_devnum) ++ { ++ for (iter = captureStreams.begin(); iter != captureStreams.end(); ++iter) ++ { ++ if (iter->name == id) ++ { ++ pa_operation *o; ++ ++ // NB Note that this is different from APP_PLAYBACK in that we set the volume on the source itself. ++ pa_cvolume volume = genVolumeForPulse(*iter, md->captureVolume()); ++ if (!(o = pa_context_set_source_volume_by_index(context, iter->device_index, &volume, NULL, NULL))) { ++ kWarning(67100) << "pa_context_set_source_volume_by_index() failed"; ++ return Mixer::ERR_READ; ++ } ++ pa_operation_unref(o); ++ ++ if (!(o = pa_context_set_source_mute_by_index(context, iter->device_index, (md->isMuted() ? 1 : 0), NULL, NULL))) { ++ kWarning(67100) << "pa_context_set_source_mute_by_index() failed"; ++ return Mixer::ERR_READ; ++ } ++ pa_operation_unref(o); ++ ++ return 0; ++ } ++ } ++ } ++ ++ return 0; ++} ++ ++void Mixer_PULSE::triggerUpdate() ++{ ++ readSetFromHWforceUpdate(); ++ readSetFromHW(); + } + + void Mixer_PULSE::setRecsrcHW( const QString& /*id*/, bool /* on */ ) +@@ -243,19 +1021,8 @@ void Mixer_PULSE::setRecsrcHW( const QString& /*id*/, bool /* on */ ) + return; + } + +-bool Mixer_PULSE::isRecsrcHW( const QString& id ) ++bool Mixer_PULSE::isRecsrcHW( const QString& /*id*/ ) + { +-/* int devnum = id2num(id); +- switch ( devnum ) +- { +- case MIXERDEV_MICROPHONE : +- case MIXERDEV_LINE_IN : +- case MIXERDEV_CD : +- return true; +- +- default : +- return false; +- }*/ + return false; + } + +diff --git a/kmix/mixer_pulse.h b/kmix/mixer_pulse.h +index 6dcd68b..e6608ec 100644 +--- a/kmix/mixer_pulse.h ++++ b/kmix/mixer_pulse.h +@@ -24,30 +24,56 @@ + + #include + ++#include "mixer_backend.h" + #include +-#include +-#include + +-#include "mixer_backend.h" ++typedef QMap chanIDMap; ++typedef struct { ++ int index; ++ int device_index; ++ QString name; ++ QString description; ++ QString icon_name; ++ pa_cvolume volume; ++ pa_channel_map channel_map; ++ bool mute; ++ ++ struct { ++ QString name; ++ QString device; ++ } restore; ++ ++ Volume::ChannelMask chanMask; ++ chanIDMap chanIDs; ++} devinfo; + + class Mixer_PULSE : public Mixer_Backend + { +-public: +- Mixer_PULSE(Mixer *mixer, int devnum); +- virtual ~Mixer_PULSE(); ++ public: ++ Mixer_PULSE(Mixer *mixer, int devnum); ++ virtual ~Mixer_PULSE(); ++ ++ virtual int readVolumeFromHW( const QString& id, MixDevice *md ); ++ virtual int writeVolumeToHW ( const QString& id, MixDevice *md ); ++ void setRecsrcHW ( const QString& id, bool on ); ++ bool isRecsrcHW ( const QString& id ); ++ ++ virtual QString getDriverName(); ++ virtual bool needsPolling() { return false; } ++ ++ void triggerUpdate(); ++ void addWidget(int index); ++ void removeWidget(int index); + +- virtual int readVolumeFromHW( const QString& id, MixDevice *md ); +- virtual int writeVolumeToHW ( const QString& id, MixDevice *md ); +- void setRecsrcHW ( const QString& id, bool on ); +- bool isRecsrcHW ( const QString& id ); ++ protected: ++ virtual int open(); ++ virtual int close(); + +- virtual QString getDriverName(); ++ int fd; + +-protected: +- virtual int open(); +- virtual int close(); ++ private: ++ void addDevice(devinfo& dev, bool capture); + +- int fd; + }; + + #endif +diff --git a/kmix/mixertoolbox.cpp b/kmix/mixertoolbox.cpp +index f424916..ed64cd8 100644 +--- a/kmix/mixertoolbox.cpp ++++ b/kmix/mixertoolbox.cpp +@@ -112,7 +112,8 @@ void MixerToolBox::initMixer(bool multiDriverMode, QString& ref_hwInfoString) + for( int drv=0; drv sane exit from outer loop + break; +@@ -136,31 +137,33 @@ void MixerToolBox::initMixer(bool multiDriverMode, QString& ref_hwInfoString) + autodetectionFinished = true; // highest device number of driver and a Mixer => finished + } + +- // append driverName (used drivers) +- if ( mixerAccepted && !drvInfoAppended ) ++ if ( mixerAccepted ) + { +- drvInfoAppended = true; +- QString driverName = Mixer::driverName(drv); +- if ( Mixer::mixers().count() > 1) { +- driverInfoUsed += " + "; +- } +- driverInfoUsed += driverName; +- } +- +- // Check whether there are mixers in different drivers, so that the user can be warned +- if (mixerAccepted && !multipleDriversActive) +- { +- if ( driverWithMixer == -1 ) ++ kDebug(67100) << "Success! Found a mixer with the : " << driverName << " driver"; ++ // append driverName (used drivers) ++ if ( !drvInfoAppended ) + { +- // Aha, this is the very first detected device +- driverWithMixer = drv; ++ drvInfoAppended = true; ++ if ( Mixer::mixers().count() > 1) ++ driverInfoUsed += " + "; ++ driverInfoUsed += driverName; + } +- else if ( driverWithMixer != drv ) ++ ++ // Check whether there are mixers in different drivers, so that the user can be warned ++ if ( !multipleDriversActive ) + { +- // Got him: There are mixers in different drivers +- multipleDriversActive = true; +- } +- } // !multipleDriversActive ++ if ( driverWithMixer == -1 ) ++ { ++ // Aha, this is the very first detected device ++ driverWithMixer = drv; ++ } ++ else if ( driverWithMixer != drv ) ++ { ++ // Got him: There are mixers in different drivers ++ multipleDriversActive = true; ++ } ++ } // !multipleDriversActive ++ } // mixerAccepted + + } // loop over sound card devices of current driver + +diff --git a/kmix/viewbase.cpp b/kmix/viewbase.cpp +index b53ecd4..4ab41da 100644 +--- a/kmix/viewbase.cpp ++++ b/kmix/viewbase.cpp +@@ -76,6 +76,7 @@ ViewBase::ViewBase(QWidget* parent, const char* id, Mixer* mixer, Qt::WFlags f, + action->setText(i18n("&Channels")); + connect(action, SIGNAL(triggered(bool) ), SLOT(configureView())); + connect ( _mixer, SIGNAL(controlChanged()), this, SLOT(refreshVolumeLevels()) ); ++ connect ( _mixer, SIGNAL(controlsReconfigured(int)), this, SLOT(controlsReconfigured(int)) ); + } + + ViewBase::~ViewBase() { +@@ -203,6 +204,11 @@ void ViewBase::showContextMenu() + _popMenu->popup( pos ); + } + ++void ViewBase::controlsReconfigured(int mixerTabIndex) ++{ ++ Q_UNUSED(mixerTabIndex); ++ emit rebuildGUI(); ++} + + void ViewBase::refreshVolumeLevels() + { +diff --git a/kmix/viewbase.h b/kmix/viewbase.h +index 0470f18..35cccfc 100644 +--- a/kmix/viewbase.h ++++ b/kmix/viewbase.h +@@ -130,6 +130,7 @@ protected: + GUIProfile* _guiprof; + KActionCollection *_localActionColletion; + public slots: ++ virtual void controlsReconfigured(int mixerTabIndex); + virtual void refreshVolumeLevels(); + virtual void configureView(); + void toggleMenuBarSlot();