diff --git a/kmix/CMakeLists.txt b/kmix/CMakeLists.txt index 4880470..6578b3a 100644 --- a/kmix/CMakeLists.txt +++ b/kmix/CMakeLists.txt @@ -18,6 +18,7 @@ set(kmix_KDEINIT_SRCS ${kmix_adaptor_SRCS} viewdockareapopup.cpp viewsliders.cpp mixdevicewidget.cpp + mdwmoveaction.cpp mdwslider.cpp mdwenum.cpp kmixerwidget.cpp 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/dialogviewconfiguration.cpp b/kmix/dialogviewconfiguration.cpp index dddf4ec..3ff40cc 100644 --- a/kmix/dialogviewconfiguration.cpp +++ b/kmix/dialogviewconfiguration.cpp @@ -264,10 +264,10 @@ void DialogViewConfiguration::createPage() //qDebug() << "add DialogViewConfigurationItem: " << mdName << " visible=" << mdw->isVisible() << "splitted=" << splitted; if ( mdw->isVisible() ) { - new DialogViewConfigurationItem(_qlw, md->id(), mdw->isVisible(), mdName, splitted, mdw->iconName()); + new DialogViewConfigurationItem(_qlw, md->id(), mdw->isVisible(), mdName, splitted, mdw->mixDevice()->iconName()); } else { - new DialogViewConfigurationItem(_qlwInactive, md->id(), mdw->isVisible(), mdName, splitted, mdw->iconName()); + new DialogViewConfigurationItem(_qlwInactive, md->id(), mdw->isVisible(), mdName, splitted, mdw->mixDevice()->iconName()); } /* @@ -326,19 +326,22 @@ void DialogViewConfiguration::apply() prepareControls(model, false, oldControlset, newControlset); // --- Step 2: Copy controls + QString tabName = "Base"; oldControlset.clear(); std::vector<ProfControl*>::const_iterator itEnd = newControlset.end(); for ( std::vector<ProfControl*>::const_iterator it = newControlset.begin(); it != itEnd; ++it) { ProfControl* control = *it; control->id = "^" + control->id + "$"; // Create a regexp from the control name + if ( ! control->tab.isEmpty() ) + tabName = control->tab; kDebug() << "Add control " << control->id; oldControlset.push_back(control); } ProfControl* fallbackMatchAllControl = new ProfControl; fallbackMatchAllControl->id = "^.*$"; fallbackMatchAllControl->subcontrols = ".*"; - fallbackMatchAllControl->tab = "Base"; + fallbackMatchAllControl->tab = tabName; fallbackMatchAllControl->show = "extended"; oldControlset.push_back(fallbackMatchAllControl); 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 cee9f77..e802f43 100644 --- a/kmix/kmix.cpp +++ b/kmix/kmix.cpp @@ -49,6 +49,7 @@ #include <ktoggleaction.h> // KMix +#include "guiprofile.h" #include "mixertoolbox.h" #include "kmix.h" #include "kmixdevicemanager.h" @@ -402,11 +403,30 @@ void KMixWindow::recreateGUIwithoutSavingView() */ void KMixWindow::recreateGUI(bool saveConfig) { + // Find out which of the tabs is currently selected for restoration + int current_tab = -1; + if (m_wsMixers) + current_tab = m_wsMixers->currentIndex(); + + // NOTE (coling) This is a bug but I don't have time to find the source. + // When returning from "Configure Mixers..." we MUST save, but the + // flag comes through as false, presumably due to the rebuildGUI() signal + // being tied to the recreateGUIwithoutSavingView() slot. + // This should really be fixed :s + Q_UNUSED(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; i<Mixer::mixers().count(); ++i) + MixerToolBox::instance()->selectProfile((Mixer::mixers())[i])->increaseRefcount(); clearMixerWidgets(); + if ( Mixer::mixers().count() > 0 ) { for (int i=0; i<Mixer::mixers().count(); ++i) { Mixer *mixer = (Mixer::mixers())[i]; + // We've increased the refcount before clearing, so remember and decrease it again. + MixerToolBox::instance()->selectProfile(mixer)->decreaseRefcount(); addMixerWidget(mixer->id()); } bool dockingSucceded = updateDocking(); @@ -418,6 +438,39 @@ void KMixWindow::recreateGUI(bool saveConfig) updateDocking(); // -<- removes the DockIcon hide(); } + + if (current_tab >= 0) { + m_wsMixers->setCurrentIndex(current_tab); + } +} + + +/** +* Create or recreate the Mixer GUI elements +*/ +void KMixWindow::redrawMixer( const QString& mixer_ID ) +{ + for ( int i=0; i<m_wsMixers->count() ; ++i ) + { + QWidget *w = m_wsMixers->widget(i); + if ( w->inherits("KMixerWidget") ) + { + KMixerWidget* kmw = (KMixerWidget*)w; + if ( kmw->mixer()->id() == mixer_ID ) + { + kDebug(67100) << "KMixWindow::redrawMixer() " << mixer_ID << " is being redrawn"; + kmw->loadConfig( KGlobal::config().data() ); + + // Is the below needed? It is done on startup so copied it here... + kmw->setTicks( m_showTicks ); + kmw->setLabels( m_showLabels ); + + return; + } + } + } + + kWarning(67100) << "KMixWindow::redrawMixer() Requested to redraw " << mixer_ID << " but I cannot find it :s"; } diff --git a/kmix/kmix.h b/kmix/kmix.h index fdadb7d..0931b74 100644 --- a/kmix/kmix.h +++ b/kmix/kmix.h @@ -88,6 +88,7 @@ KMixWindow : public KXmlGuiWindow virtual void applyPrefs( KMixPrefDlg *prefDlg ); void recreateGUI(bool saveView); void recreateGUIwithoutSavingView(); + void redrawMixer( const QString& mixer_ID ); //void stopVisibilityUpdates(); diff --git a/kmix/kmixerwidget.cpp b/kmix/kmixerwidget.cpp index b7fdb2a..def1bae 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; @@ -182,6 +183,7 @@ bool KMixerWidget::possiblyAddView(ViewBase* vbase) connect( vbase, SIGNAL(toggleMenuBar()), parentWidget(), SLOT(toggleMenuBar()) ); // *this will be deleted on rebuildGUI(), so lets queue the signal connect( vbase, SIGNAL(rebuildGUI()) , parentWidget(), SLOT(recreateGUIwithoutSavingView()), Qt::QueuedConnection ); + connect( vbase, SIGNAL(redrawMixer(const QString&)), parentWidget(), SLOT(redrawMixer(const QString&)), Qt::QueuedConnection ); return true; } } diff --git a/kmix/kmixerwidget.h b/kmix/kmixerwidget.h index ad51965..6d71fa1 100644 --- a/kmix/kmixerwidget.h +++ b/kmix/kmixerwidget.h @@ -62,6 +62,7 @@ class KMixerWidget : public QWidget signals: void toggleMenuBar(); void rebuildGUI(); + void redrawMixer( const QString& mixer_ID ); public slots: void setTicks( bool on ); 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 <kdialog.h> 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/mdwmoveaction.cpp b/kmix/mdwmoveaction.cpp new file mode 100644 index 0000000..8aaa3b5 --- /dev/null +++ b/kmix/mdwmoveaction.cpp @@ -0,0 +1,48 @@ +/* + * KMix -- KDE's full featured mini mixer + * + * + * Copyright (C) 1996-2004 Christian Esken <esken@kde.org> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + + +//KMix +#include "mdwmoveaction.h" +#include "mixdevice.h" + +// Qt +#include <QString> + +MDWMoveAction::MDWMoveAction(MixDevice* md, QObject *parent) + : KAction(parent), m_mixDevice(md) +{ + Q_ASSERT(md); + + setText(m_mixDevice->readableName()); + setIcon(KIcon(m_mixDevice->iconName())); + connect(this, SIGNAL(triggered(bool) ), SLOT(triggered(bool))); +} + +MDWMoveAction::~MDWMoveAction() +{ +} + +void MDWMoveAction::triggered(bool checked) +{ + Q_UNUSED(checked); + emit moveRequest(m_mixDevice->id()); +} diff --git a/kmix/mdwmoveaction.h b/kmix/mdwmoveaction.h new file mode 100644 index 0000000..8e1b428 --- /dev/null +++ b/kmix/mdwmoveaction.h @@ -0,0 +1,46 @@ +//-*-C++-*- +/* + * KMix -- KDE's full featured mini mixer + * + * Copyright Christian Esken <esken@kde.org> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +#ifndef MDWMoveAction_h +#define MDWMoveAction_h + +#include <KAction> + +class MixDevice; + +class MDWMoveAction : public KAction +{ + Q_OBJECT + + public: + MDWMoveAction(MixDevice* md, QObject *parent); + ~MDWMoveAction(); + + signals: + void moveRequest(QString id); + + protected slots: + void triggered(bool checked); + + private: + MixDevice *m_mixDevice; +}; + +#endif diff --git a/kmix/mdwslider.cpp b/kmix/mdwslider.cpp index c41b0c5..a0d833b 100644 --- a/kmix/mdwslider.cpp +++ b/kmix/mdwslider.cpp @@ -48,6 +48,7 @@ #include "kledbutton.h" #include "ksmallslider.h" #include "verticaltext.h" +#include "mdwmoveaction.h" static const int MIN_SLIDER_SIZE = 50; @@ -68,32 +69,39 @@ MDWSlider::MDWSlider(MixDevice* md, MixDeviceWidget(md,small,orientation,parent,mw), m_linked(true), m_defaultLabelSpacer(0), m_iconLabelSimple(0), m_qcb(0), m_muteText(0), m_playbackSpacer(0), _layout(0), m_extraCaptureLabel( 0 ), m_label( 0 ), - m_captureLED( 0 ), m_captureText(0), m_captureSpacer(0) + m_captureLED( 0 ), m_captureText(0), m_captureSpacer(0), m_moveMenu(0) { + _mdwMoveActions = new KActionCollection( this ); + // create actions (on _mdwActions, see MixDeviceWidget) - KToggleAction *action = _mdwActions->add<KToggleAction>( "stereo" ); - action->setText( i18n("&Split Channels") ); - connect(action, SIGNAL(triggered(bool) ), SLOT(toggleStereoLinked())); - action = _mdwActions->add<KToggleAction>( "hide" ); + KToggleAction *taction = _mdwActions->add<KToggleAction>( "stereo" ); + taction->setText( i18n("&Split Channels") ); + connect(taction, SIGNAL(triggered(bool) ), SLOT(toggleStereoLinked())); + KAction *action = _mdwActions->add<KAction>( "hide" ); action->setText( i18n("&Hide") ); connect(action, SIGNAL(triggered(bool) ), SLOT(setDisabled())); if( m_mixdevice->playbackVolume().hasSwitch() ) { - KToggleAction *a = _mdwActions->add<KToggleAction>( "mute" ); - a->setText( i18n("&Muted") ); - connect( a, SIGNAL(toggled(bool)), SLOT(toggleMuted()) ); + taction = _mdwActions->add<KToggleAction>( "mute" ); + taction->setText( i18n("&Muted") ); + connect(taction, SIGNAL(toggled(bool)), SLOT(toggleMuted())); } if( m_mixdevice->captureVolume().hasSwitch() ) { - KToggleAction *a = _mdwActions->add<KToggleAction>( "recsrc" ); - a->setText( i18n("Set &Record Source") ); - connect( a, SIGNAL(toggled(bool)), SLOT( toggleRecsrc()) ); + taction = _mdwActions->add<KToggleAction>( "recsrc" ); + taction->setText( i18n("Set &Record Source") ); + connect(taction, SIGNAL(toggled(bool)), SLOT( toggleRecsrc())); + } + + if( m_mixdevice->isMovable() ) { + m_moveMenu = new KMenu( i18n("Mo&ve"), this); + connect(m_moveMenu, SIGNAL(aboutToShow()), SLOT( showMoveMenu())); } - KAction *c = _mdwActions->addAction( "keys" ); - c->setText( i18n("C&onfigure Shortcuts...") ); - connect(c, SIGNAL(triggered(bool) ), SLOT(defineKeys())); + action = _mdwActions->addAction( "keys" ); + action->setText( i18n("C&onfigure Shortcuts...") ); + connect(action, SIGNAL(triggered(bool) ), SLOT(defineKeys())); // create widgets createWidgets( showMuteLED, showCaptureLED ); @@ -349,7 +357,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 +520,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 +534,7 @@ MDWSlider::setIcon( int icontype ) installEventFilter( m_iconLabelSimple ); } - QPixmap miniDevPM = icon( icontype ); + QPixmap miniDevPM = loadIcon( filename ); if ( !miniDevPM.isNull() ) { if ( m_small ) @@ -608,6 +559,10 @@ MDWSlider::setIcon( int icontype ) layout()->activate(); } +QString MDWSlider::iconName() +{ + return m_mixdevice->iconName(); +} void MDWSlider::toggleStereoLinked() @@ -924,6 +879,16 @@ void MDWSlider::decreaseVolume() } +void MDWSlider::moveStreamAutomatic() +{ + m_mixdevice->mixer()->moveStream(m_mixdevice->id(), ""); +} + +void MDWSlider::moveStream(QString destId) +{ + m_mixdevice->mixer()->moveStream(m_mixdevice->id(), destId); +} + /** This is called whenever there are volume updates pending from the hardware for this MDW. At the moment it is called regulary via a QTimer (implicitely). @@ -934,6 +899,14 @@ void MDWSlider::update() updateInternal(m_mixdevice->playbackVolume(), m_slidersPlayback, _slidersChidsPlayback); if (m_slidersCapture.count() != 0 || m_mixdevice->captureVolume().hasSwitch()) updateInternal(m_mixdevice->captureVolume(), m_slidersCapture , _slidersChidsCapture ); + if (m_label) { + QLabel *l; + VerticalText *v; + if ((l = dynamic_cast<QLabel*>(m_label))) + l->setText(m_mixdevice->readableName()); + else if ((v = dynamic_cast<VerticalText*>(m_label))) + v->setText(m_mixdevice->readableName()); + } } void MDWSlider::updateInternal(Volume& vol, QList<QWidget *>& ref_sliders, QList<Volume::ChannelID>& ref_slidersChids) @@ -1030,6 +1003,14 @@ void MDWSlider::showContextMenu() if ( a ) menu->addAction( a ); + if (m_moveMenu) { + MixSet *ms = m_mixdevice->getMoveDestinationMixSet(); + Q_ASSERT(ms); + + m_moveMenu->setEnabled((ms->count() > 1)); + menu->addMenu( m_moveMenu ); + } + QAction *b = _mdwActions->action( "keys" ); if ( b ) { // QAction sep( _mdwPopupActions ); @@ -1043,6 +1024,35 @@ void MDWSlider::showContextMenu() } +void MDWSlider::showMoveMenu() +{ + MixSet *ms = m_mixdevice->getMoveDestinationMixSet(); + Q_ASSERT(ms); + + _mdwMoveActions->clear(); + m_moveMenu->clear(); + + // Default + KAction *a = new KAction(_mdwMoveActions); + a->setText( i18n("Automatic According to Category") ); + _mdwMoveActions->addAction( QString("moveautomatic"), a); + connect(a, SIGNAL(triggered(bool)), SLOT(moveStreamAutomatic())); + m_moveMenu->addAction( a ); + + a = new KAction(_mdwMoveActions); + a->setSeparator(true); + _mdwMoveActions->addAction( QString("-"), a); + + m_moveMenu->addAction( a ); + for (int i = 0; i < ms->count(); ++i) { + MixDevice* md = (*ms)[i]; + a = new MDWMoveAction(md, _mdwMoveActions); + _mdwMoveActions->addAction( QString("moveto") + md->id(), a); + connect(a, SIGNAL(moveRequest(QString)), SLOT(moveStream(QString))); + m_moveMenu->addAction( a ); + } +} + /** * An event filter for the various QWidgets. We watch for Mouse press Events, so * that we can popup the context menu. diff --git a/kmix/mdwslider.h b/kmix/mdwslider.h index 49088ff..8ea5973 100644 --- a/kmix/mdwslider.h +++ b/kmix/mdwslider.h @@ -37,6 +37,7 @@ class QLabel; class KLed; class KLedButton; class KAction; +class KMenu; #include <kshortcut.h> class MixDevice; @@ -57,7 +58,7 @@ public: bool showMuteLED, bool showRecordLED, bool small, Qt::Orientation, QWidget* parent = 0, ViewBase* mw = 0); - ~MDWSlider() {} + ~MDWSlider() { } void addActionToPopup( KAction *action ); @@ -71,7 +72,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; @@ -88,6 +89,7 @@ public slots: void setDisabled(); void setDisabled( bool value ); void update(); + void showMoveMenu(); virtual void showContextMenu(); @@ -105,10 +107,12 @@ private slots: void increaseVolume(); void decreaseVolume(); + void moveStreamAutomatic(); + void moveStream( QString destId ); + 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); @@ -144,6 +148,8 @@ private: QLabel* m_captureText; QWidget *m_captureSpacer; // static KShortcut dummyShortcut; + KActionCollection* _mdwMoveActions; + KMenu *m_moveMenu; QList<QWidget *> m_slidersPlayback; QList<QWidget *> m_slidersCapture; diff --git a/kmix/mixdevice.cpp b/kmix/mixdevice.cpp index 54459c9..8c91198 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,30 @@ * 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, 0); +} + +MixDevice::MixDevice( Mixer* mixer, const QString& id, const QString& name, const QString& iconName, bool doNotRestore, MixSet* moveDestinationMixSet ) { + init(mixer, id, name, iconName, doNotRestore, moveDestinationMixSet); +} + +void MixDevice::init( Mixer* mixer, const QString& id, const QString& name, const QString& iconName, bool doNotRestore, MixSet* moveDestinationMixSet ) +{ + _mixer = mixer; + _id = id; if( name.isEmpty() ) _name = i18n("unknown"); else _name = name; + if ( iconName.isEmpty() ) + _iconName = "mixer-front"; + else + _iconName = iconName; + _doNotRestore = doNotRestore; + _moveDestinationMixSet = moveDestinationMixSet; 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 +184,7 @@ bool MixDevice::operator==(const MixDevice& other) const return ( _id == other._id ); } + /** * This methhod is currently only called on "kmixctrl --restore" * @@ -125,13 +194,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 +253,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..f54e03a 100644 --- a/kmix/mixdevice.h +++ b/kmix/mixdevice.h @@ -23,6 +23,7 @@ //KMix class Mixer; +class MixSet; #include "volume.h" // KDE @@ -99,9 +100,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, MixSet* moveDestinationMixSet = 0 ); ~MixDevice(); + const QString& iconName() const { return _iconName; } + void addPlaybackVolume(Volume &playbackVol); void addCaptureVolume (Volume &captureVol); void addEnums (QList<QString*>& ref_enumList); @@ -134,6 +138,8 @@ public: bool isEnum() { return ( ! _enumValues.empty() ); } + bool isMovable() { return (0 != _moveDestinationMixSet); } + MixSet *getMoveDestinationMixSet() { return _moveDestinationMixSet; } Volume& playbackVolume(); Volume& captureVolume(); @@ -145,8 +151,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 +158,14 @@ private: int _enumCurrentId; QList<QString> _enumValues; // A MixDevice, that is an ENUM, has these _enumValues - ChannelType _type; + bool _doNotRestore; + MixSet *_moveDestinationMixSet; + 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, MixSet* moveDestinationMixSet ); 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/mixdevicewidget.cpp b/kmix/mixdevicewidget.cpp index 00feefd..56a024a 100644 --- a/kmix/mixdevicewidget.cpp +++ b/kmix/mixdevicewidget.cpp @@ -110,7 +110,6 @@ void MixDeviceWidget::setColors( QColor , QColor , QColor ) { /* is virtual */ } void MixDeviceWidget::setIcons( bool ) { /* is virtual */ } void MixDeviceWidget::setLabeled( bool ) { /* is virtual */ } void MixDeviceWidget::setMutedColors( QColor , QColor , QColor ) { /* is virtual */ } -const QString& MixDeviceWidget::iconName() const { return _iconName; /* is virtual */} diff --git a/kmix/mixdevicewidget.h b/kmix/mixdevicewidget.h index a056c54..518ec8d 100644 --- a/kmix/mixdevicewidget.h +++ b/kmix/mixdevicewidget.h @@ -63,7 +63,6 @@ public: virtual void setStereoLinked( bool ) {} virtual void setLabeled( bool ); virtual void setTicks( bool ) {} - const QString& iconName() const; public slots: @@ -86,7 +85,6 @@ protected: Qt::Orientation _orientation; bool m_small; KShortcutsDialog* m_shortcutsDialog; - QString _iconName; private: void mousePressEvent( QMouseEvent *e ); diff --git a/kmix/mixer.cpp b/kmix/mixer.cpp index 82bfba4..7a1a9ce 100644 --- a/kmix/mixer.cpp +++ b/kmix/mixer.cpp @@ -62,7 +62,7 @@ QList<Mixer *>& Mixer::mixers() } Mixer::Mixer( QString& ref_driverName, int device ) - : m_balance(0), _mixerBackend(0L) + : m_balance(0), _mixerBackend(0L), m_dynamic(false) { (void)new KMixAdaptor(this); @@ -145,11 +145,13 @@ bool Mixer::openIfValid() { kDebug() << "Mixer::open() detected master: " << recommendedMaster->id(); } else { - kError(67100) << "Mixer::open() no master detected." << endl; + if ( !m_dynamic ) + kError(67100) << "Mixer::open() no master detected." << endl; QString noMaster = "---no-master-detected---"; setLocalMasterMD(noMaster); // no master } connect( _mixerBackend, SIGNAL(controlChanged()), SLOT(controlChangedForwarder()) ); + connect( _mixerBackend, SIGNAL(controlsReconfigured(const QString&)), SLOT(controlsReconfiguredForwarder(const QString&)) ); m_dbusName = "/Mixer" + QString::number(_mixerBackend->m_devnum); QDBusConnection::sessionBus().registerObject(m_dbusName, this); @@ -163,6 +165,11 @@ void Mixer::controlChangedForwarder() emit controlChanged(); } +void Mixer::controlsReconfiguredForwarder( const QString& mixer_ID ) +{ + emit controlsReconfigured(mixer_ID); +} + /** * Closes the mixer. * Also, stops the polling timer. @@ -677,4 +684,20 @@ bool Mixer::isAvailableDevice( const QString& mixdeviceID ) return getMixdeviceById( mixdeviceID ); } +void Mixer::setDynamic ( bool dynamic ) +{ + m_dynamic = dynamic; +} + +bool Mixer::dynamic() +{ + return m_dynamic; +} + +bool Mixer::moveStream( const QString id, const QString& destId ) +{ + // We should really check that id is within our md's.... + return _mixerBackend->moveStream( id, destId ); +} + #include "mixer.moc" diff --git a/kmix/mixer.h b/kmix/mixer.h index e0cdf4d..221e284 100644 --- a/kmix/mixer.h +++ b/kmix/mixer.h @@ -155,6 +155,12 @@ class Mixer : public QObject virtual bool isAvailableDevice( const QString& mixdeviceID ); + /// Says if we are dynamic (e.g. widgets can come and go) + virtual void setDynamic( bool dynamic = true ); + virtual bool dynamic(); + + virtual bool moveStream( const QString id, const QString& destId ); + void commitVolumeChange( MixDevice* md ); public slots: @@ -166,6 +172,7 @@ class Mixer : public QObject signals: void newBalance( Volume& ); void controlChanged(void); + void controlsReconfigured( const QString& mixer_ID ); protected: int m_balance; // from -100 (just left) to 100 (just right) @@ -173,6 +180,7 @@ class Mixer : public QObject private slots: void controlChangedForwarder(); + void controlsReconfiguredForwarder( const QString& mixer_ID ); public: static QList<Mixer *>& mixers(); @@ -186,6 +194,7 @@ class Mixer : public QObject static QString _globalMasterCardDevice; QString m_dbusName; + bool m_dynamic; }; #endif 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.cpp b/kmix/mixer_backend.cpp index 22d3792..00e2999 100644 --- a/kmix/mixer_backend.cpp +++ b/kmix/mixer_backend.cpp @@ -48,7 +48,7 @@ Mixer_Backend::~Mixer_Backend() bool Mixer_Backend::openIfValid() { bool valid = false; int ret = open(); - if ( ret == 0 && m_mixDevices.count() > 0) { + if ( ret == 0 && (m_mixDevices.count() > 0 || _mixer->dynamic())) { valid = true; // A better ID is now calculated in mixertoolbox.cpp, and set via setID(), // but we want a somehow usable fallback just in case. @@ -139,8 +139,10 @@ MixDevice* Mixer_Backend::recommendedMaster() { return m_mixDevices.at(0); // Backend has NOT set a recommended master. Evil backend => lets help out. } //first device (if exists) else { - // This should never ever happen, as KMix doe NOT accept soundcards without controls - kError(67100) << "Mixer_Backend::recommendedMaster(): returning invalid master. This is a bug in KMix. Please file a bug report stating how you produced this." << endl; + if ( !_mixer->dynamic()) { + // This should never ever happen, as KMix doe NOT accept soundcards without controls + kError(67100) << "Mixer_Backend::recommendedMaster(): returning invalid master. This is a bug in KMix. Please file a bug report stating how you produced this." << endl; + } return (MixDevice*)0; } } @@ -165,6 +167,15 @@ unsigned int Mixer_Backend::enumIdHW(const QString& ) { return 0; } +/** + * Move the stream to a new destination + */ +bool Mixer_Backend::moveStream( const QString& id, const QString& destId ) { + Q_UNUSED(id); + Q_UNUSED(destId); + return false; +} + void Mixer_Backend::errormsg(int mixer_error) { QString l_s_errText; diff --git a/kmix/mixer_backend.h b/kmix/mixer_backend.h index 26e9557..bc147b2 100644 --- a/kmix/mixer_backend.h +++ b/kmix/mixer_backend.h @@ -81,6 +81,8 @@ protected: virtual void setRecsrcHW( const QString& id, bool on) = 0; //virtual bool isRecsrcHW( const QString& id ) = 0; + virtual bool moveStream( const QString& id, const QString& destId ); + /// Overwrite in the backend if the backend can see changes without polling virtual bool needsPolling() { return true; } @@ -131,6 +133,10 @@ protected: signals: void controlChanged( void ); + void controlsReconfigured( const QString& mixer_ID ); + +public slots: + virtual void reinit() {}; protected slots: virtual void readSetFromHW(); diff --git a/kmix/mixer_pulse.cpp b/kmix/mixer_pulse.cpp index 694b9a9..514a407 100644 --- a/kmix/mixer_pulse.cpp +++ b/kmix/mixer_pulse.cpp @@ -20,12 +20,779 @@ */ #include <cstdlib> +#include <QtCore/QAbstractEventDispatcher> +#include <QTimer> #include "mixer_pulse.h" #include "mixer.h" -static pa_context *context = NULL; -static pa_glib_mainloop *mainloop = NULL; +#include <pulse/glib-mainloop.h> +#include <pulse/ext-stream-restore.h> + + +#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_glib_mainloop *s_mainloop = NULL; +static pa_context *s_context = NULL; +static enum { UNKNOWN, ACTIVE, INACTIVE } s_pulseActive = UNKNOWN; +static int s_outstandingRequests = 0; + +QMap<int,Mixer_PULSE*> s_mixers; + +typedef QMap<int,devinfo> devmap; +static devmap outputDevices; +static devmap captureDevices; +static QMap<int,QString> clients; +static devmap outputStreams; +static devmap captureStreams; +static devmap outputRoles; + +typedef struct { + pa_channel_map channel_map; + pa_cvolume volume; + bool mute; + QString device; +} restoreRule; +static QMap<QString,restoreRule> s_RestoreRules; + +static void dec_outstanding(pa_context *c) { + if (s_outstandingRequests <= 0) + return; + + if (--s_outstandingRequests == 0) + { + s_pulseActive = ACTIVE; + + // If this is our probe phase, exit our context immediately + if (s_context != c) { + pa_context_disconnect(c); + } else + kDebug(67100) << "Reconnected to PulseAudio"; + } +} + +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 QString::fromUtf8(t); + + if ((t = pa_proplist_gets(l, PA_PROP_WINDOW_ICON_NAME))) + return QString::fromUtf8(t); + + if ((t = pa_proplist_gets(l, PA_PROP_APPLICATION_ICON_NAME))) + return QString::fromUtf8(t); + + if ((t = pa_proplist_gets(l, PA_PROP_MEDIA_ROLE))) { + + if (strcmp(t, "video") == 0 || strcmp(t, "phone") == 0) + return QString::fromUtf8(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 *) { + + if (eol < 0) { + if (pa_context_errno(c) == PA_ERR_NOENTITY) + return; + + kWarning(67100) << "Sink callback failure"; + return; + } + + if (eol > 0) { + dec_outstanding(c); + if (s_mixers.contains(KMIXPA_PLAYBACK)) + s_mixers[KMIXPA_PLAYBACK]->triggerUpdate(); + return; + } + + devinfo s; + s.index = s.device_index = i->index; + s.name = QString(i->name).replace(' ', '_'); + s.description = QString::fromUtf8(i->description); + s.icon_name = QString::fromUtf8(pa_proplist_gets(i->proplist, PA_PROP_DEVICE_ICON_NAME)); + s.volume = i->volume; + s.channel_map = i->channel_map; + s.mute = !!i->mute; + s.stream_restore_rule = ""; + + translateMasksAndMaps(s); + + bool is_new = !outputDevices.contains(s.index); + outputDevices[s.index] = s; + kDebug(67100) << "Got some info about sink: " << s.description; + + if (s_mixers.contains(KMIXPA_PLAYBACK)) { + if (is_new) + s_mixers[KMIXPA_PLAYBACK]->addWidget(s.index); + else { + int mid = s_mixers[KMIXPA_PLAYBACK]->id2num(s.name); + if (mid >= 0) { + MixSet *ms = s_mixers[KMIXPA_PLAYBACK]->getMixSet(); + (*ms)[mid]->setReadableName(s.description); + } + } + } +} + +static void source_cb(pa_context *c, const pa_source_info *i, int eol, void *) { + + if (eol < 0) { + if (pa_context_errno(c) == PA_ERR_NOENTITY) + return; + + kWarning(67100) << "Source callback failure"; + return; + } + + if (eol > 0) { + dec_outstanding(c); + 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.name = QString(i->name).replace(' ', '_'); + s.description = QString::fromUtf8(i->description); + s.icon_name = QString::fromUtf8(pa_proplist_gets(i->proplist, PA_PROP_DEVICE_ICON_NAME)); + s.volume = i->volume; + s.channel_map = i->channel_map; + s.mute = !!i->mute; + s.stream_restore_rule = ""; + + translateMasksAndMaps(s); + + bool is_new = !captureDevices.contains(s.index); + captureDevices[s.index] = s; + kDebug(67100) << "Got some info about source: " << s.description; + + if (s_mixers.contains(KMIXPA_CAPTURE)) { + if (is_new) + s_mixers[KMIXPA_CAPTURE]->addWidget(s.index); + else { + int mid = s_mixers[KMIXPA_CAPTURE]->id2num(s.name); + if (mid >= 0) { + MixSet *ms = s_mixers[KMIXPA_CAPTURE]->getMixSet(); + (*ms)[mid]->setReadableName(s.description); + } + } + } +} + +static void client_cb(pa_context *c, const pa_client_info *i, int eol, void *) { + + if (eol < 0) { + if (pa_context_errno(c) == PA_ERR_NOENTITY) + return; + + kWarning(67100) << "Client callback failure"; + return; + } + + if (eol > 0) { + dec_outstanding(c); + return; + } + + clients[i->index] = QString::fromUtf8(i->name); + kDebug(67100) << "Got some info about client: " << clients[i->index]; +} + +static void sink_input_cb(pa_context *c, const pa_sink_input_info *i, int eol, void *) { + + if (eol < 0) { + if (pa_context_errno(c) == PA_ERR_NOENTITY) + return; + + kWarning(67100) << "Sink Input callback failure"; + return; + } + + if (eol > 0) { + dec_outstanding(c); + 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.description = prefix + QString::fromUtf8(i->name); + s.name = QString("stream:") + i->index; + s.icon_name = getIconNameFromProplist(i->proplist); + s.volume = i->volume; + s.channel_map = i->channel_map; + s.mute = !!i->mute; + s.stream_restore_rule = t; + + 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 (s_mixers.contains(KMIXPA_APP_PLAYBACK)) { + if (is_new) + s_mixers[KMIXPA_APP_PLAYBACK]->addWidget(s.index); + else { + int mid = s_mixers[KMIXPA_APP_PLAYBACK]->id2num(s.name); + if (mid >= 0) { + MixSet *ms = s_mixers[KMIXPA_APP_PLAYBACK]->getMixSet(); + (*ms)[mid]->setReadableName(s.description); + } + } + } +} + +static void source_output_cb(pa_context *c, const pa_source_output_info *i, int eol, void *) { + + if (eol < 0) { + if (pa_context_errno(c) == PA_ERR_NOENTITY) + return; + + kWarning(67100) << "Source Output callback failure"; + return; + } + + if (eol > 0) { + dec_outstanding(c); + 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.description = prefix + QString::fromUtf8(i->name); + s.name = QString("stream:") + i->index; + 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; + s.stream_restore_rule = pa_proplist_gets(i->proplist, "module-stream-restore.id"); + + 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 (s_mixers.contains(KMIXPA_APP_CAPTURE)) { + if (is_new) + s_mixers[KMIXPA_APP_CAPTURE]->addWidget(s.index); + else { + int mid = s_mixers[KMIXPA_APP_CAPTURE]->id2num(s.name); + if (mid >= 0) { + MixSet *ms = s_mixers[KMIXPA_APP_CAPTURE]->getMixSet(); + (*ms)[mid]->setReadableName(s.description); + } + } + } +} + + +static devinfo create_role_devinfo(const char* name) { + + Q_ASSERT(s_RestoreRules.contains(name)); + + devinfo s; + s.index = s.device_index = PA_INVALID_INDEX; + s.description = i18n("Event Sounds"); + s.name = QString("restore:") + name; + s.icon_name = "dialog-information"; + s.channel_map = s_RestoreRules[name].channel_map; + s.volume = s_RestoreRules[name].volume; + s.mute = s_RestoreRules[name].mute; + s.stream_restore_rule = name; + + translateMasksAndMaps(s); + return s; +} + + +void ext_stream_restore_read_cb(pa_context *c, const pa_ext_stream_restore_info *i, int eol, void *) { + + if (eol < 0) { + dec_outstanding(c); + kWarning(67100) << "Failed to initialize stream_restore extension: " << pa_strerror(pa_context_errno(s_context)); + return; + } + + if (eol > 0) { + dec_outstanding(c); + // 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)) { + // Create a fake rule + restoreRule rule; + rule.channel_map.channels = 1; + rule.channel_map.map[0] = PA_CHANNEL_POSITION_MONO; + rule.volume.channels = 1; + rule.volume.values[0] = PA_VOLUME_NORM; + rule.mute = false; + rule.device = ""; + s_RestoreRules["sink-input-by-media-role:event"] = rule; + + devinfo s = create_role_devinfo("sink-input-by-media-role:event"); + 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]->addWidget(s.index); + } + + if (s_mixers.contains(KMIXPA_APP_PLAYBACK)) + s_mixers[KMIXPA_APP_PLAYBACK]->triggerUpdate(); + return; + } + + kDebug(67100) << "Got some info about restore rule: " << i->name << i->device; + restoreRule rule; + rule.channel_map = i->channel_map; + rule.volume = i->volume; + rule.mute = !!i->mute; + rule.device = i->device; + s_RestoreRules[i->name] = rule; + + // We only want to know about Sound Events for now... + if (strcmp(i->name, "sink-input-by-media-role:event") == 0) { + devinfo s = create_role_devinfo(i->name); + bool is_new = !outputRoles.contains(s.index); + outputRoles[s.index] = s; + + if (is_new && s_mixers.contains(KMIXPA_APP_PLAYBACK)) + s_mixers[KMIXPA_APP_PLAYBACK]->addWidget(s.index); + } +} + +static void ext_stream_restore_subscribe_cb(pa_context *c, void *) { + + Q_ASSERT(c == s_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 == s_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 *) +{ + pa_context_state_t state = pa_context_get_state(c); + if (state == PA_CONTEXT_READY) { + // Attempt to load things up + pa_operation *o; + + // 1. Register for the stream changes (except during probe) + if (s_context == c) { + 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(s_context)); + } + } else if (!PA_CONTEXT_IS_GOOD(state)) { + // If this is our probe phase, exit our context immediately + if (s_context != c) { + pa_context_disconnect(c); + } else { + // If we're not probing, it means we've been disconnected from our + // glib context + pa_context_unref(s_context); + s_context = NULL; + + // Remove all GUI elements + QMap<int,Mixer_PULSE*>::iterator it; + for (it = s_mixers.begin(); it != s_mixers.end(); ++it) { + (*it)->removeAllWidgets(); + } + // This one is not handled above. + clients.clear(); + + if (s_mixers.contains(KMIXPA_PLAYBACK)) { + kWarning(67100) << "Connection to PulseAudio daemon closed. Attempting reconnection."; + s_pulseActive = UNKNOWN; + QTimer::singleShot(50, s_mixers[KMIXPA_PLAYBACK], SLOT(reinit())); + } + } + } +} + +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, QString id = "") +{ + 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) { + if (id.startsWith("restore:")) + return &outputRoles; + return &outputStreams; + } else if (KMIXPA_APP_CAPTURE == type) + return &captureStreams; + + Q_ASSERT(0); + return NULL; +} +static devmap* get_widget_map(int type, int index) +{ + if (PA_INVALID_INDEX == (uint32_t)index) + return get_widget_map(type, "restore:"); + return get_widget_map(type); +} + +void Mixer_PULSE::addWidget(int index) +{ + devmap* map = get_widget_map(m_devnum, index); + + 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]); + emit controlsReconfigured(_mixer->id()); +} + +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(_mixer->id()); + return; + } + } +} + +void Mixer_PULSE::removeAllWidgets() +{ + devmap* map = get_widget_map(m_devnum); + map->clear(); + + // Special case + if (KMIXPA_APP_PLAYBACK == m_devnum) + outputRoles.clear(); + + MixSet::iterator iter; + for (iter = m_mixDevices.begin(); iter != m_mixDevices.end(); ++iter) + { + delete *iter; + m_mixDevices.erase(iter); + } + emit controlsReconfigured(_mixer->id()); +} + +void Mixer_PULSE::addDevice(devinfo& dev) +{ + if (dev.chanMask != Volume::MNONE) { + MixSet *ms = 0; + if (m_devnum == KMIXPA_APP_PLAYBACK && s_mixers.contains(KMIXPA_PLAYBACK)) + ms = s_mixers[KMIXPA_PLAYBACK]->getMixSet(); + else if (m_devnum == KMIXPA_APP_CAPTURE && s_mixers.contains(KMIXPA_CAPTURE)) + ms = s_mixers[KMIXPA_CAPTURE]->getMixSet(); + + Volume v(dev.chanMask, PA_VOLUME_NORM, PA_VOLUME_MUTED, true, false); + setVolumeFromPulse(v, dev); + MixDevice* md = new MixDevice( _mixer, dev.name, dev.description, dev.icon_name, true, ms); + md->addPlaybackVolume(v); + md->setMuted(dev.mute); + m_mixDevices.append(md); + } +} Mixer_Backend* PULSE_getMixer( Mixer *mixer, int devnum ) { @@ -34,208 +801,423 @@ Mixer_Backend* PULSE_getMixer( Mixer *mixer, int devnum ) return l_mixer; } +bool Mixer_PULSE::connectToDaemon() +{ + Q_ASSERT(NULL == s_context); + + kDebug(67100) << "Attempting connection to PulseAudio sound daemon"; + pa_mainloop_api *api = pa_glib_mainloop_get_api(s_mainloop); + Q_ASSERT(api); + + s_context = pa_context_new(api, "KMix KDE 4"); + Q_ASSERT(s_context); + + if (pa_context_connect(s_context, NULL, PA_CONTEXT_NOFAIL, 0) < 0) { + pa_context_unref(s_context); + s_context = NULL; + return false; + } + pa_context_set_state_callback(s_context, &context_state_callback, NULL); + return true; +} + Mixer_PULSE::Mixer_PULSE(Mixer *mixer, int devnum) : Mixer_Backend(mixer, devnum) { - if ( devnum == -1 ) - m_devnum = 0; + if ( devnum == -1 ) + m_devnum = 0; + + QString pulseenv = qgetenv("KMIX_PULSEAUDIO_DISABLE"); + if (pulseenv.toInt()) + s_pulseActive = INACTIVE; + + // We require a glib event loop + if (QLatin1String(QAbstractEventDispatcher::instance()->metaObject()->className()) + != "QGuiEventDispatcherGlib") { + kDebug(67100) << "Disabling PulseAudio integration for lack of GLib event loop."; + s_pulseActive = INACTIVE; + } + + + ++refcount; + if (INACTIVE != s_pulseActive && 1 == refcount) + { + // First of all conenct to PA via simple/blocking means and if that succeeds, + // use a fully async integrated mainloop method to connect and get proper support. + pa_mainloop *p_test_mainloop; + if (!(p_test_mainloop = pa_mainloop_new())) { + kDebug(67100) << "PulseAudio support disabled: Unable to create mainloop"; + s_pulseActive = INACTIVE; + goto endconstruct; + } + + pa_context *p_test_context; + if (!(p_test_context = pa_context_new(pa_mainloop_get_api(p_test_mainloop), "kmix-probe"))) { + kDebug(67100) << "PulseAudio support disabled: Unable to create context"; + pa_mainloop_free(p_test_mainloop); + s_pulseActive = INACTIVE; + goto endconstruct; + } + + kDebug(67100) << "Probing for PulseAudio..."; + // (cg) Convert to PA_CONTEXT_NOFLAGS when PulseAudio 0.9.19 is required + if (pa_context_connect(p_test_context, NULL, static_cast<pa_context_flags_t>(0), NULL) < 0) { + kDebug(67100) << QString("PulseAudio support disabled: %1").arg(pa_strerror(pa_context_errno(p_test_context))); + pa_context_disconnect(p_test_context); + pa_context_unref(p_test_context); + pa_mainloop_free(p_test_mainloop); + s_pulseActive = INACTIVE; + goto endconstruct; + } + + // Assume we are inactive, it will be set to active if appropriate + s_pulseActive = INACTIVE; + pa_context_set_state_callback(p_test_context, &context_state_callback, NULL); + for (;;) { + pa_mainloop_iterate(p_test_mainloop, 1, NULL); + + if (!PA_CONTEXT_IS_GOOD(pa_context_get_state(p_test_context))) { + kDebug(67100) << "PulseAudio probe complete."; + break; + } + } + pa_context_disconnect(p_test_context); + pa_context_unref(p_test_context); + pa_mainloop_free(p_test_mainloop); + + + if (INACTIVE != s_pulseActive) + { + // Reconnect via integrated mainloop + s_mainloop = pa_glib_mainloop_new(NULL); + Q_ASSERT(s_mainloop); + + connectToDaemon(); + } + + kDebug(67100) << "PulseAudio status: " << (s_pulseActive==UNKNOWN ? "Unknown (bug)" : (s_pulseActive==ACTIVE ? "Active" : "Inactive")); + } + +endconstruct: + s_mixers[m_devnum] = this; } Mixer_PULSE::~Mixer_PULSE() { - close(); + s_mixers.remove(m_devnum); + + if (refcount > 0) + { + --refcount; + if (0 == refcount) + { + if (s_context) { + pa_context_unref(s_context); + s_context = NULL; + } + + if (s_mainloop) { + pa_glib_mainloop_free(s_mainloop); + s_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 ); - } -*/ + //kDebug(67100) << "Trying Pulse sink"; - m_mixerName = "PULSE Audio Mixer"; + if (ACTIVE == s_pulseActive && m_devnum <= KMIXPA_APP_CAPTURE) + { + // Make sure the GUI layers know we are dynamic so as to always paint us + _mixer->setDynamic(); - m_isOpen = true; + devmap::iterator iter; + if (KMIXPA_PLAYBACK == m_devnum) + { + m_mixerName = i18n("Playback Devices"); + for (iter = outputDevices.begin(); iter != outputDevices.end(); ++iter) + addDevice(*iter); + } + else if (KMIXPA_CAPTURE == m_devnum) + { + m_mixerName = i18n("Capture Devices"); + for (iter = captureDevices.begin(); iter != captureDevices.end(); ++iter) + addDevice(*iter); + } + else if (KMIXPA_APP_PLAYBACK == m_devnum) + { + m_mixerName = i18n("Playback Streams"); + for (iter = outputRoles.begin(); iter != outputRoles.end(); ++iter) + addDevice(*iter); + for (iter = outputStreams.begin(); iter != outputStreams.end(); ++iter) + addDevice(*iter); + } + else if (KMIXPA_APP_CAPTURE == m_devnum) + { + m_mixerName = i18n("Capture Streams"); + for (iter = captureStreams.begin(); iter != captureStreams.end(); ++iter) + addDevice(*iter); + } + 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::id2num(const QString& id) { + //kDebug(67100) << "id2num() id=" << id; + int num = -1; + // todo: Store this in a hash or similar + int i; + for (i = 0; i < m_mixDevices.size(); ++i) { + if (m_mixDevices[i]->id() == id) { + num = i; + break; + } + } + //kDebug(67100) << "id2num() num=" << num; + return num; +} + 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; + devmap *map = get_widget_map(m_devnum, id); - case MIXERDEV_RECORD_MONITOR : - md->setMuted(false); - volume.setAllVolumes( audioinfo.monitor_gain ); + devmap::iterator iter; + for (iter = map->begin(); iter != map->end(); ++iter) + { + if (iter->name == id) + { + setVolumeFromPulse(md->playbackVolume(), *iter); + md->setMuted(iter->mute); 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; + return 0; +} + +int Mixer_PULSE::writeVolumeToHW( const QString& id, MixDevice *md ) +{ + 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(s_context, iter->index, &volume, NULL, NULL))) { + kWarning(67100) << "pa_context_set_sink_volume_by_index() failed"; + return Mixer::ERR_READ; + } + pa_operation_unref(o); - 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 ); + if (!(o = pa_context_set_sink_mute_by_index(s_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->playbackVolume()); + if (!(o = pa_context_set_source_volume_by_index(s_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(s_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(s_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(s_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) + { + restoreRule &rule = s_RestoreRules[iter->stream_restore_rule]; + pa_ext_stream_restore_info info; + info.name = iter->stream_restore_rule.toAscii().constData(); + info.channel_map = rule.channel_map; + info.volume = genVolumeForPulse(*iter, md->playbackVolume()); + info.device = rule.device.isEmpty() ? NULL : rule.device.toAscii().constData(); + info.mute = (md->isMuted() ? 1 : 0); + + pa_operation* o; + if (!(o = pa_ext_stream_restore_write(s_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->playbackVolume()); + if (!(o = pa_context_set_source_volume_by_index(s_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(s_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; +} + +/** +* Move the stream to a new destination +*/ +bool Mixer_PULSE::moveStream( const QString& id, const QString& destId ) { + Q_ASSERT(KMIXPA_APP_PLAYBACK == m_devnum || KMIXPA_APP_CAPTURE == m_devnum); + + kDebug(67100) << "Mixer_PULSE::moveStream(): Move Stream Requested - Stream: " << id << ", Destination: " << destId; + + // Lookup the stream index. + uint32_t stream_index = PA_INVALID_INDEX; + const char* stream_restore_rule = NULL; + devmap::iterator iter; + devmap *map = get_widget_map(m_devnum); + for (iter = map->begin(); iter != map->end(); ++iter) { + if (iter->name == id) { + stream_index = iter->index; + stream_restore_rule = iter->stream_restore_rule.isEmpty() ? NULL : iter->stream_restore_rule.toAscii().constData(); break; + } + } - default : - return Mixer::ERR_READ; - } - return 0; - }*/ - return 0; + if (PA_INVALID_INDEX == stream_index) { + kError(67100) << "Mixer_PULSE::moveStream(): Cannot find stream index"; + return false; + } + + if (destId.isEmpty()) { + // We want to remove any specific device in the stream restore rule. + if (!stream_restore_rule || !s_RestoreRules.contains(stream_restore_rule)) { + kWarning(67100) << "Mixer_PULSE::moveStream(): Trying to set Automatic on a stream with no rule"; + } else { + restoreRule &rule = s_RestoreRules[stream_restore_rule]; + pa_ext_stream_restore_info info; + info.name = stream_restore_rule; + info.channel_map = rule.channel_map; + info.volume = rule.volume; + info.device = NULL; + info.mute = rule.mute ? 1 : 0; + + pa_operation* o; + if (!(o = pa_ext_stream_restore_write(s_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); + } + } else { + pa_operation* o; + if (KMIXPA_APP_PLAYBACK == m_devnum) { + if (!(o = pa_context_move_sink_input_by_name(s_context, stream_index, destId.toAscii().constData(), NULL, NULL))) { + kWarning(67100) << "pa_context_move_sink_input_by_name() failed"; + return false; + } + } else { + if (!(o = pa_context_move_source_output_by_name(s_context, stream_index, destId.toAscii().constData(), NULL, NULL))) { + kWarning(67100) << "pa_context_move_source_output_by_name() failed"; + return false; + } + } + pa_operation_unref(o); + } + + return true; } -int Mixer_PULSE::writeVolumeToHW( const QString& id, MixDevice *md ) +void Mixer_PULSE::reinit() +{ + // We only support reinit on our primary mixer. + Q_ASSERT(KMIXPA_PLAYBACK == m_devnum); + connectToDaemon(); +} + +void Mixer_PULSE::triggerUpdate() { -/* 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 ); - } - - // - // 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; - } - - // - // 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; + readSetFromHWforceUpdate(); + readSetFromHW(); } void Mixer_PULSE::setRecsrcHW( const QString& /*id*/, bool /* on */ ) @@ -243,19 +1225,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..6c43b0c 100644 --- a/kmix/mixer_pulse.h +++ b/kmix/mixer_pulse.h @@ -24,30 +24,61 @@ #include <QString> +#include "mixer_backend.h" #include <pulse/pulseaudio.h> -#include <pulse/glib-mainloop.h> -#include <pulse/ext-stream-restore.h> -#include "mixer_backend.h" +typedef QMap<uint8_t,Volume::ChannelID> 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; + QString stream_restore_rule; + + 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 bool moveStream( const QString& id, const QString& destId ); + + virtual QString getDriverName(); + virtual bool needsPolling() { return false; } + + void triggerUpdate(); + void addWidget(int index); + void removeWidget(int index); + void removeAllWidgets(); + MixSet *getMixSet() { return &m_mixDevices; } + int id2num(const QString& id); + + protected: + virtual int open(); + virtual int close(); - 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 ); + int fd; - virtual QString getDriverName(); + private: + void addDevice(devinfo& dev); + bool connectToDaemon(); -protected: - virtual int open(); - virtual int close(); + public slots: + void reinit(); - int fd; }; -#endif +#endif diff --git a/kmix/verticaltext.cpp b/kmix/verticaltext.cpp index 3cdfc88..62cd32a 100644 --- a/kmix/verticaltext.cpp +++ b/kmix/verticaltext.cpp @@ -33,6 +33,13 @@ VerticalText::VerticalText(QWidget * parent, const QString& text, Qt::WFlags f) VerticalText::~VerticalText() { } +void VerticalText::setText(QString text) { + if (m_labelText != text) { + m_labelText = text; + updateGeometry(); + } +} + void VerticalText::paintEvent ( QPaintEvent * /*event*/ ) { QPainter paint(this); diff --git a/kmix/verticaltext.h b/kmix/verticaltext.h index 16489ce..91486f9 100644 --- a/kmix/verticaltext.h +++ b/kmix/verticaltext.h @@ -30,6 +30,7 @@ class VerticalText : public QWidget VerticalText(QWidget * parent, const QString&, Qt::WFlags f = 0); ~VerticalText(); + void setText(QString text); QSize sizeHint() const; QSizePolicy sizePolicy () const; QSize minimumSizeHint() const; diff --git a/kmix/viewbase.cpp b/kmix/viewbase.cpp index b53ecd4..05742d2 100644 --- a/kmix/viewbase.cpp +++ b/kmix/viewbase.cpp @@ -39,6 +39,7 @@ #include "kmixtoolbox.h" #include "mixdevicewidget.h" #include "mixer.h" +#include "mixertoolbox.h" ViewBase::ViewBase(QWidget* parent, const char* id, Mixer* mixer, Qt::WFlags f, ViewBase::ViewFlags vflags, GUIProfile *guiprof, KActionCollection *actionColletion) @@ -76,6 +77,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(const QString&)), this, SLOT(controlsReconfigured(const QString&)) ); } ViewBase::~ViewBase() { @@ -101,7 +103,7 @@ QString ViewBase::id() const { bool ViewBase::isValid() const { - return (_mixSet->count() > 0 ); + return ( _mixSet->count() > 0 || _mixer->dynamic() ); } void ViewBase::setIcons (bool on) { KMixToolBox::setIcons (_mdws, on ); } @@ -122,8 +124,8 @@ void ViewBase::createDeviceWidgets() { MixDevice *mixDevice; mixDevice = (*_mixSet)[i]; - QWidget* mdw = add(mixDevice); - _mdws.append(mdw); + QWidget* mdw = add(mixDevice); + _mdws.append(mdw); } // allow view to "polish" itself constructionFinished(); @@ -203,16 +205,78 @@ void ViewBase::showContextMenu() _popMenu->popup( pos ); } +void ViewBase::controlsReconfigured( const QString& mixer_ID ) +{ + if ( _mixer->id() == mixer_ID ) + { + kDebug(67100) << "ViewBase::controlsReconfigured() " << mixer_ID << " is being redrawn (mixset contains: " << _mixSet->count() << ")"; + setMixSet(); + kDebug(67100) << "ViewBase::controlsReconfigured() " << mixer_ID << ": Recreating widgets (mixset contains: " << _mixSet->count() << ")"; + createDeviceWidgets(); + + // We've done the low level stuff our selves but let elements + // above know what has happened so they can reload config etc. + emit redrawMixer(mixer_ID); + } +} void ViewBase::refreshVolumeLevels() { // is virtual } -Mixer* ViewBase::getMixer() { +Mixer* ViewBase::getMixer() +{ return _mixer; } +void ViewBase::setMixSet() +{ + if ( _mixer->dynamic()) { + + // Check the guiprofile... if it is not the fallback GUIProfile, then + // make sure that we add a specific entry for any devices not present. + if ( 0 != _guiprof && MixerToolBox::instance()->fallbackProfile(_mixer) != _guiprof ) { + kDebug(67100) << "Dynamic mixer " << _mixer->id() << " is NOT using Fallback GUIProfile. Checking to see if new controls are present"; + + QList<QString> new_mix_devices; + MixSet ms = _mixer->getMixSet(); + for (int i=0; i < ms.count(); ++i) + new_mix_devices.append("^" + ms[i]->id() + "$"); + std::vector<ProfControl*>::const_iterator itEnd = _guiprof->_controls.end(); + for ( std::vector<ProfControl*>::const_iterator it = _guiprof->_controls.begin(); it != itEnd; ++it) + new_mix_devices.removeAll((*it)->id); + + if ( new_mix_devices.count() > 0 ) { + kDebug(67100) << "Found " << new_mix_devices.count() << " new controls. Adding to GUIProfile"; + while ( new_mix_devices.count() > 0 ) { + ProfControl* ctl = new ProfControl(); + ctl->id = new_mix_devices.takeAt(0); + ctl->subcontrols = ".*"; + ctl->tab = _guiprof->_tabs[0]->name; // Use the first tab... not ideal but should work most of the time; + ctl->show = "simple"; + _guiprof->_controls.push_back(ctl); + } + QString profileName; + profileName = _mixer->id() + "." + id(); + _guiprof->writeProfile(profileName); + } + } + + // We need to delete the current MixDeviceWidgets so we can redraw them + while (!_mdws.isEmpty()) { + QWidget* mdw = _mdws.last(); + _mdws.pop_back(); + delete mdw; + } + + // Clean up our _mixSet so we can reapply our GUIProfile + _mixSet->clear(); + } + _setMixSet(); +} + + /** * Open the View configuration dialog. The user can select which channels he wants * to see and which not. diff --git a/kmix/viewbase.h b/kmix/viewbase.h index 0470f18..e3a6f9d 100644 --- a/kmix/viewbase.h +++ b/kmix/viewbase.h @@ -83,7 +83,7 @@ public: */ virtual void createDeviceWidgets(); - virtual void setMixSet() = 0; + void setMixSet(); Mixer* getMixer(); @@ -118,6 +118,7 @@ public: signals: void rebuildGUI(); + void redrawMixer( const QString& mixer_ID ); protected: @@ -129,7 +130,11 @@ protected: ViewFlags _vflags; GUIProfile* _guiprof; KActionCollection *_localActionColletion; + + virtual void _setMixSet() = 0; + public slots: + virtual void controlsReconfigured( const QString& mixer_ID ); virtual void refreshVolumeLevels(); virtual void configureView(); void toggleMenuBarSlot(); diff --git a/kmix/viewdockareapopup.cpp b/kmix/viewdockareapopup.cpp index 57ac597..f050800 100644 --- a/kmix/viewdockareapopup.cpp +++ b/kmix/viewdockareapopup.cpp @@ -46,7 +46,7 @@ // Users will not be able to close the Popup without opening the KMix main window then. // See Bug #93443, #96332 and #96404 for further details. -- esken ViewDockAreaPopup::ViewDockAreaPopup(QWidget* parent, const char* name, Mixer* mixer, ViewBase::ViewFlags vflags, GUIProfile *guiprof, KMixWindow *dockW ) - : ViewBase(parent, name, mixer, /*Qt::FramelessWindowHint | Qt::MSWindowsFixedSizeDialogHint*/0, vflags, guiprof), _mdw(0), _dock(dockW) + : ViewBase(parent, name, mixer, /*Qt::FramelessWindowHint | Qt::MSWindowsFixedSizeDialogHint*/0, vflags, guiprof), _dock(dockW) { _layoutMDW = new QGridLayout( this ); _layoutMDW->setSpacing( KDialog::spacingHint() ); @@ -63,14 +63,23 @@ ViewDockAreaPopup::~ViewDockAreaPopup() { void ViewDockAreaPopup::wheelEvent ( QWheelEvent * e ) { // Pass wheel event from "border widget" to child - if ( _mdw != 0 ) { - QApplication::sendEvent( _mdw, e); - } + QWidget* mdw = 0; + if ( !_mdws.isEmpty() ) + mdw = _mdws.first(); + + if ( mdw != 0 ) + QApplication::sendEvent( mdw, e); } MixDevice* ViewDockAreaPopup::dockDevice() { - return _mdw->mixDevice(); + MixDeviceWidget* mdw = 0; + if ( !_mdws.isEmpty() ) + mdw = (MixDeviceWidget*)_mdws.first(); + + if ( mdw != 0 ) + return mdw->mixDevice(); + return (MixDevice*)(0); } @@ -81,9 +90,18 @@ void ViewDockAreaPopup::showContextMenu() } -void ViewDockAreaPopup::setMixSet() +void ViewDockAreaPopup::_setMixSet() { // kDebug(67100) << "ViewDockAreaPopup::setMixSet()\n"; + + if ( _mixer->dynamic() ) { + // Our _layoutMDW now should only contain spacer widgets from the QSpacerItems's in add() below. + // We need to trash those too otherwise all sliders gradually migrate away from the edge :p + QLayoutItem *li; + while ( ( li = _layoutMDW->takeAt(0) ) ) + delete li; + } + MixDevice *dockMD = Mixer::getGlobalMasterMD(); if ( dockMD == 0 ) { // If we have no dock device yet, we will take the first available mixer device @@ -98,7 +116,7 @@ void ViewDockAreaPopup::setMixSet() QWidget* ViewDockAreaPopup::add(MixDevice *md) { - _mdw = new MDWSlider( + MixDeviceWidget *mdw = new MDWSlider( md, // only 1 device. This is actually _dockDevice true, // Show Mute LED false, // Show Record LED @@ -109,7 +127,7 @@ QWidget* ViewDockAreaPopup::add(MixDevice *md) ); _layoutMDW->addItem( new QSpacerItem( 5, 20 ), 0, 2 ); _layoutMDW->addItem( new QSpacerItem( 5, 20 ), 0, 0 ); - _layoutMDW->addWidget( _mdw, 0, 1 ); + _layoutMDW->addWidget( mdw, 0, 1 ); // Add button to show main panel _showPanelBox = new QPushButton( i18n("Mixer"), this ); @@ -117,21 +135,28 @@ QWidget* ViewDockAreaPopup::add(MixDevice *md) connect ( _showPanelBox, SIGNAL( clicked() ), SLOT( showPanelSlot() ) ); _layoutMDW->addWidget( _showPanelBox, 1, 0, 1, 3 ); - return _mdw; + return mdw; } void ViewDockAreaPopup::constructionFinished() { // kDebug(67100) << "ViewDockAreaPopup::constructionFinished()\n"; - if (_mdw != 0) { - _mdw->move(0,0); - _mdw->show(); + QWidget* mdw = 0; + if ( !_mdws.isEmpty() ) + mdw = _mdws.first(); + + if ( mdw != 0 ) { + mdw->move(0,0); + mdw->show(); } } void ViewDockAreaPopup::refreshVolumeLevels() { // kDebug(67100) << "ViewDockAreaPopup::refreshVolumeLevels()\n"; - QWidget* mdw = _mdws.first(); + QWidget* mdw = 0; + if ( !_mdws.isEmpty() ) + mdw = _mdws.first(); + if ( mdw == 0 ) { kError(67100) << "ViewDockAreaPopup::refreshVolumeLevels(): mdw == 0\n"; // sanity check (normally the lists are set up correctly) diff --git a/kmix/viewdockareapopup.h b/kmix/viewdockareapopup.h index f289dd6..68f1b22 100644 --- a/kmix/viewdockareapopup.h +++ b/kmix/viewdockareapopup.h @@ -43,7 +43,6 @@ public: ~ViewDockAreaPopup(); MixDevice* dockDevice(); - virtual void setMixSet(); virtual QWidget* add(MixDevice *mdw); virtual void constructionFinished(); virtual void refreshVolumeLevels(); @@ -52,12 +51,12 @@ public: //QSize sizeHint() const; protected: - MixDeviceWidget *_mdw; KMixWindow *_dock; //MixDevice *_dockDevice; QPushButton *_showPanelBox; void wheelEvent ( QWheelEvent * e ); + virtual void _setMixSet(); private: QGridLayout* _layoutMDW; diff --git a/kmix/viewsliders.cpp b/kmix/viewsliders.cpp index fa2246c..a43ceef 100644 --- a/kmix/viewsliders.cpp +++ b/kmix/viewsliders.cpp @@ -119,10 +119,20 @@ QWidget* ViewSliders::add(MixDevice *md) } -void ViewSliders::setMixSet() +void ViewSliders::_setMixSet() { - const MixSet& mixset = _mixer->getMixSet(); - + const MixSet& mixset = _mixer->getMixSet(); + + if ( _mixer->dynamic() ) { + // We will be recreating our sliders, so make sure we trash all the separators too. + qDeleteAll(_separators); + _separators.clear(); + // Our _layoutSliders now should only contain spacer widgets from the addSpacing() calls in add() above. + // We need to trash those too otherwise all sliders gradually migrate away from the edge :p + QLayoutItem *li; + while ( ( li = _layoutSliders->takeAt(0) ) ) + delete li; + } // This method iterates the controls from the Profile // Each control is checked, whether it is also contained in the mixset, and @@ -135,7 +145,7 @@ void ViewSliders::setMixSet() if ( control->tab == id() ) { // The TabName of the control matches this View name (!! attention: Better use some ID, due to i18n() ) bool isUsed = false; - + QRegExp idRegexp(control->id); //kDebug(67100) << "ViewSliders::setMixSet(): Check GUIProfile id==" << control->id << "\n"; // The following for-loop could be simplified by using a std::find_if diff --git a/kmix/viewsliders.h b/kmix/viewsliders.h index 1695595..9f7ce32 100644 --- a/kmix/viewsliders.h +++ b/kmix/viewsliders.h @@ -36,7 +36,6 @@ public: ViewSliders(QWidget* parent, const char* name, Mixer* mixer, ViewBase::ViewFlags vflags, GUIProfile *guiprof, KActionCollection *actColl); ~ViewSliders(); - virtual void setMixSet(); virtual QWidget* add(MixDevice *mdw); virtual void constructionFinished(); virtual void configurationUpdate(); @@ -44,6 +43,9 @@ public: public slots: virtual void refreshVolumeLevels(); +protected: + virtual void _setMixSet(); + private: QBoxLayout* _layoutMDW; QLayout* _layoutSliders;