Index: kmix/apps/kmix.cpp =================================================================== --- kmix/apps/kmix.cpp (revision 1226955) +++ kmix/apps/kmix.cpp (revision 1226956) @@ -78,8 +78,6 @@ m_dockWidget(), m_dontSetDefaultCardOnStart (false) { - _cornerLabelNew = 0; - setObjectName( QLatin1String("KMixWindow" )); // disable delete-on-close because KMix might just sit in the background waiting for cards to be plugged in setAttribute(Qt::WA_DeleteOnClose, false); @@ -184,16 +182,29 @@ void KMixWindow::initActionsAfterInitMixer() { + bool isPulseAudio = false; // Add "launch_pavucontrol" to menu, if Pulseaudio backend is in use foreach( Mixer* mixer, Mixer::mixers() ) { - if ( mixer->getDriverName() == "PulseAudio") { + if ( mixer->getDriverName() == "PulseAudio") + { + isPulseAudio = true; KAction* action = actionCollection()->addAction( "launch_pavucontrol" ); action->setText( i18n( "Audio setup (&Pulseaudio)" ) ); connect(action, SIGNAL(triggered(bool) ), SLOT( slotPavucontrolExec() )); break; } } + + if (! isPulseAudio ) + { + QPixmap cornerNewPM = KIconLoader::global()->loadIcon( "tab-new", KIconLoader::Toolbar, KIconLoader::SizeSmall ); + QPushButton* _cornerLabelNew = new QPushButton(); + _cornerLabelNew->setIcon(cornerNewPM); + //cornerLabelNew->setSizePolicy(QSizePolicy()); + m_wsMixers->setCornerWidget(_cornerLabelNew, Qt::TopLeftCorner); + connect ( _cornerLabelNew, SIGNAL( clicked() ), SLOT (newView() ) ); + } } void KMixWindow::initPrefDlg() @@ -210,15 +221,10 @@ m_wsMixers = new KTabWidget(); m_wsMixers->setDocumentMode(true); setCentralWidget(m_wsMixers); - m_wsMixers->setTabsClosable(true); + m_wsMixers->setTabsClosable(false); connect (m_wsMixers, SIGNAL(tabCloseRequested(int)), SLOT(saveAndCloseView(int)) ); QPixmap cornerNewPM = KIconLoader::global()->loadIcon( "tab-new", KIconLoader::Toolbar, KIconLoader::SizeSmall ); - _cornerLabelNew = new QPushButton(); - _cornerLabelNew->setIcon(cornerNewPM); - //cornerLabelNew->setSizePolicy(QSizePolicy()); - m_wsMixers->setCornerWidget(_cornerLabelNew, Qt::TopLeftCorner); - connect ( _cornerLabelNew, SIGNAL( clicked() ), SLOT (newView() ) ); connect( m_wsMixers, SIGNAL( currentChanged ( int ) ), SLOT( newMixerShown(int)) ); @@ -516,6 +522,7 @@ addMixerWidget(mixer->id(), guiprof, -1); } else { + // did exist => remove and insert new guiprof at old position int indexOfTab = m_wsMixers->indexOf(kmw); if ( indexOfTab != -1 ) m_wsMixers->removeTab(indexOfTab); delete kmw; @@ -676,9 +683,9 @@ m_wsMixers->removeTab(idx); delete kmw; - if ( m_wsMixers->count() < 2 ) { - m_wsMixers->setTabsClosable(false); - } + bool isPulseAudio = kmw->mixer()->getDriverName() == "PulseAudio"; + m_wsMixers->setTabsClosable(!isPulseAudio && m_wsMixers->count() > 1); + saveViewConfig(); } kDebug() << "Exit"; @@ -874,9 +881,9 @@ if ( kmw->getGuiprof()->getId() == m_defaultCardOnStart ) { m_wsMixers->setCurrentWidget(kmw); } - if ( m_wsMixers->count() > 1 ) { - m_wsMixers->setTabsClosable(true); - } + + bool isPulseAudio = mixer->getDriverName() == "PulseAudio"; + m_wsMixers->setTabsClosable(!isPulseAudio && m_wsMixers->count() > 1); m_dontSetDefaultCardOnStart = false; Index: kmix/apps/kmix.h =================================================================== --- kmix/apps/kmix.h (revision 1226955) +++ kmix/apps/kmix.h (revision 1226956) @@ -116,7 +116,6 @@ Qt::Orientation m_toplevelOrientation; KTabWidget *m_wsMixers; - QPushButton* _cornerLabelNew; KMixPrefDlg *m_prefDlg; KMixDockWidget *m_dockWidget; Index: kmix/gui/viewbase.cpp =================================================================== --- kmix/gui/viewbase.cpp (revision 1226956) +++ kmix/gui/viewbase.cpp (revision 1226957) @@ -226,34 +226,35 @@ // 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 && GUIProfile::fallbackProfile(_mixer) != _guiprof ) { + if ( 0 != _guiprof && GUIProfile::fallbackProfile(_mixer) != _guiprof ) // TODO colin/cesken IMO calling GUIProfile::fallbackProfile(_mixer) is wrong, as it ALWAYS creates a new Object. fallbackProfile() would need to cache the created fallback profiles so this should make any sense. + { kDebug(67100) << "Dynamic mixer " << _mixer->id() << " is NOT using Fallback GUIProfile. Checking to see if new controls are present"; QList new_mix_devices; MixSet ms = _mixer->getMixSet(); for (int i=0; i < ms.count(); ++i) + { new_mix_devices.append("^" + ms[i]->id() + "$"); + kDebug(67100) << "new_mix_devices.append => " << ms[i]->id(); + } + GUIProfile::ControlSet& ctlSet = _guiprof->getControls(); + // std::vector::const_iterator itEnd = _guiprof->_controls.end(); // for ( std::vector::const_iterator it = _guiprof->_controls.begin(); it != itEnd; ++it) // new_mix_devices.removeAll((*it)->id); // TODO Please check this change, Colin - foreach ( ProfControl* pctl, _guiprof->getControls() ) { + foreach ( ProfControl* pctl, ctlSet ) { new_mix_devices.removeAll(pctl->id); } if ( new_mix_devices.count() > 0 ) { kDebug(67100) << "Found " << new_mix_devices.count() << " new controls. Adding to GUIProfile"; + QString sctlMatchAll("*"); while ( new_mix_devices.count() > 0 ) { - QString sctlMatchAll("*"); QString new_mix_devices0 = new_mix_devices.takeAt(0); - ProfControl* ctl = new ProfControl(new_mix_devices0, sctlMatchAll); -// ctl->id = new_mix_devices.takeAt(0); -// ctl->setSubcontrols(QString("*")); -// ctl->tab = (_guiprof->tabs())[0]->name(); // Use the first tab... not ideal but should work most of the time; -// ctl->show = "simple"; - _guiprof->getControls().push_back(ctl); + ctlSet.push_back(new ProfControl(new_mix_devices0, sctlMatchAll)); } _guiprof->setDirty(); } Index: kmix/gui/viewbase.cpp =================================================================== --- kmix/gui/viewbase.cpp (revision 1226957) +++ kmix/gui/viewbase.cpp (revision 1226958) @@ -33,6 +33,7 @@ #include #include #include +#include // KMix #include "dialogviewconfiguration.h" #include "gui/guiprofile.h" @@ -43,7 +44,7 @@ ViewBase::ViewBase(QWidget* parent, const char* id, Mixer* mixer, Qt::WFlags f, ViewBase::ViewFlags vflags, GUIProfile *guiprof, KActionCollection *actionColletion) - : QWidget(parent, f), _actions(actionColletion), _vflags(vflags), _guiprof(guiprof) + : QWidget(parent, f), _popMenu(NULL), _actions(actionColletion), _vflags(vflags), _guiprof(guiprof) { setObjectName(id); m_viewId = id; @@ -73,9 +74,11 @@ } } } - QAction *action = _localActionColletion->addAction("toggle_channels"); - action->setText(i18n("&Channels")); - connect(action, SIGNAL(triggered(bool) ), SLOT(configureView())); + if ( !_mixer->isDynamic() ) { + QAction *action = _localActionColletion->addAction("toggle_channels"); + 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&)) ); } @@ -95,7 +98,7 @@ bool ViewBase::isValid() const { - return ( _mixSet->count() > 0 || _mixer->dynamic() ); + return ( _mixSet->count() > 0 || _mixer->isDynamic() ); } void ViewBase::setIcons (bool on) { KMixToolBox::setIcons (_mdws, on ); } @@ -170,6 +173,8 @@ { QAction *a; + if ( _popMenu ) + delete _popMenu; _popMenu = new KMenu( this ); _popMenu->addTitle( KIcon( QLatin1String( "kmix" ) ), i18n("Device Settings" )); @@ -222,44 +227,8 @@ void ViewBase::setMixSet() { - if ( _mixer->dynamic()) { + if ( _mixer->isDynamic() ) { - // 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 && GUIProfile::fallbackProfile(_mixer) != _guiprof ) // TODO colin/cesken IMO calling GUIProfile::fallbackProfile(_mixer) is wrong, as it ALWAYS creates a new Object. fallbackProfile() would need to cache the created fallback profiles so this should make any sense. - { - kDebug(67100) << "Dynamic mixer " << _mixer->id() << " is NOT using Fallback GUIProfile. Checking to see if new controls are present"; - - QList new_mix_devices; - MixSet ms = _mixer->getMixSet(); - for (int i=0; i < ms.count(); ++i) - { - new_mix_devices.append("^" + ms[i]->id() + "$"); - kDebug(67100) << "new_mix_devices.append => " << ms[i]->id(); - } - - GUIProfile::ControlSet& ctlSet = _guiprof->getControls(); - -// std::vector::const_iterator itEnd = _guiprof->_controls.end(); -// for ( std::vector::const_iterator it = _guiprof->_controls.begin(); it != itEnd; ++it) -// new_mix_devices.removeAll((*it)->id); - // TODO Please check this change, Colin - foreach ( ProfControl* pctl, ctlSet ) { - new_mix_devices.removeAll(pctl->id); - } - - - if ( new_mix_devices.count() > 0 ) { - kDebug(67100) << "Found " << new_mix_devices.count() << " new controls. Adding to GUIProfile"; - QString sctlMatchAll("*"); - while ( new_mix_devices.count() > 0 ) { - QString new_mix_devices0 = new_mix_devices.takeAt(0); - ctlSet.push_back(new ProfControl(new_mix_devices0, sctlMatchAll)); - } - _guiprof->setDirty(); - } - } - // We need to delete the current MixDeviceWidgets so we can redraw them while (!_mdws.isEmpty()) { QWidget* mdw = _mdws.last(); @@ -280,6 +249,8 @@ */ void ViewBase::configureView() { + Q_ASSERT( !_mixer->isDynamic() ); + DialogViewConfiguration* dvc = new DialogViewConfiguration(0, *this); dvc->show(); // !! The dialog is modal. Does it delete itself? @@ -302,6 +273,10 @@ kDebug(67100) << "KMixToolBox::loadView() grp=" << grp.toAscii(); static char guiComplexity[3][20] = { "simple", "extended", "all" }; + + // Certain bits are not saved for dynamic mixers (e.g. PulseAudio) + bool dynamic = _mixer->isDynamic(); + for ( int tries = 0; tries < 3; tries++ ) { bool atLeastOneControlIsShown = false; @@ -315,12 +290,12 @@ Workaround: If found, write back correct group name. */ MixDeviceWidget* mdw = (MixDeviceWidget*)qmdw; - QString devgrp; - devgrp.sprintf( "%s.%s.%s", grp.toAscii().data(), mdw->mixDevice()->mixer()->id().toAscii().data(), mdw->mixDevice()->id().toAscii().data() ); + MixDevice* md = mdw->mixDevice(); + + QString devgrp = QString("%1.%2.%3").arg(grp).arg(md->mixer()->id()).arg(md->id()); KConfigGroup devcg = config->group( devgrp ); - QString buggyDevgrp; - buggyDevgrp.sprintf( "%s.%s.%s", grp.toAscii().data(), view->id().toAscii().data(), mdw->mixDevice()->id().toAscii().data() ); + QString buggyDevgrp = QString("%1.%2.%3").arg(grp).arg(view->id()).arg(md->id()); KConfigGroup buggyDevgrpCG = config->group( buggyDevgrp ); if ( buggyDevgrpCG.exists() ) { buggyDevgrpCG.copyTo(&devcg); @@ -335,7 +310,7 @@ } bool mdwEnabled = false; - if ( devcg.hasKey("Show") ) + if ( !dynamic && devcg.hasKey("Show") ) { mdwEnabled = ( true == devcg.readEntry("Show", true) ); //kDebug() << "Load devgrp" << devgrp << "show=" << mdwEnabled; @@ -382,20 +357,23 @@ QString grp = "View."; grp += view->id(); // KConfigGroup cg = config->group( grp ); - kDebug(67100) << "KMixToolBox::saveView() grp=" << grp.toAscii(); + kDebug(67100) << "KMixToolBox::saveView() grp=" << grp; + // Certain bits are not saved for dynamic mixers (e.g. PulseAudio) + bool dynamic = _mixer->isDynamic(); + for (int i=0; i < view->_mdws.count(); ++i ){ QWidget *qmdw = view->_mdws[i]; if ( qmdw->inherits("MixDeviceWidget") ) { MixDeviceWidget* mdw = (MixDeviceWidget*)qmdw; + MixDevice* md = mdw->mixDevice(); //kDebug(67100) << " grp=" << grp.toAscii(); //kDebug(67100) << " mixer=" << view->id().toAscii(); //kDebug(67100) << " mdwPK=" << mdw->mixDevice()->id().toAscii(); - QString devgrp; - devgrp.sprintf( "%s.%s.%s", grp.toAscii().data(), mdw->mixDevice()->mixer()->id().toAscii().data(), mdw->mixDevice()->id().toAscii().data() ); + QString devgrp = QString("%1.%2.%3").arg(grp).arg(md->mixer()->id()).arg(md->id()); KConfigGroup devcg = config->group( devgrp ); if ( mdw->inherits("MDWSlider") ) @@ -403,15 +381,19 @@ // only sliders have the ability to split apart in mutliple channels devcg.writeEntry( "Split", ! mdw->isStereoLinked() ); } - devcg.writeEntry( "Show" , mdw->isVisibleTo(view) ); -kDebug() << "Save devgrp" << devgrp << "show=" << mdw->isVisibleTo(view); + if ( !dynamic ) { + devcg.writeEntry( "Show" , mdw->isVisibleTo(view) ); + kDebug() << "Save devgrp" << devgrp << "show=" << mdw->isVisibleTo(view); + } } // inherits MixDeviceWidget } // for all MDW's - kDebug(67100) << "GUIProfile is dirty: " << guiProfile()->isDirty(); - if ( guiProfile()->isDirty() ) { - guiProfile()->writeProfile(); + if ( !dynamic ) { + // We do not save GUIProfiles (as they cannot be customised) for dynamic mixers (e.g. PulseAudio) + kDebug(67100) << "GUIProfile is dirty: " << guiProfile()->isDirty(); + if ( guiProfile()->isDirty() ) + guiProfile()->writeProfile(); } } Index: kmix/gui/mdwslider.cpp =================================================================== --- kmix/gui/mdwslider.cpp (revision 1226957) +++ kmix/gui/mdwslider.cpp (revision 1226958) @@ -86,10 +86,14 @@ KToggleAction *taction = _mdwActions->add( "stereo" ); taction->setText( i18n("&Split Channels") ); connect( taction, SIGNAL( triggered(bool) ), SLOT( toggleStereoLinked() ) ); - KAction *action = _mdwActions->add( "hide" ); - action->setText( i18n("&Hide") ); - connect( action, SIGNAL( triggered(bool) ), SLOT( setDisabled() ) ); + KAction *action; + if ( ! m_mixdevice->mixer()->isDynamic() ) { + action = _mdwActions->add( "hide" ); + action->setText( i18n("&Hide") ); + connect( action, SIGNAL( triggered(bool) ), SLOT( setDisabled() ) ); + } + if( m_mixdevice->playbackVolume().hasSwitch() ) { taction = _mdwActions->add( "mute" ); taction->setText( i18n("&Muted") ); @@ -129,8 +133,8 @@ #ifdef __GNUC__ #warning GLOBAL SHORTCUTS ARE NOW ASSIGNED TO ALL CONTROLS, as enableGlobalShortcut(), has not been committed #endif - if ( ! mixDevice()->isEthereal() ) { - // virtual / ethereal controls won't get shortcuts + if ( ! mixDevice()->mixer()->isDynamic() ) { + // virtual / dynamic controls won't get shortcuts b->setGlobalShortcut(dummyShortcut); // -<- enableGlobalShortcut() is not there => use workaround // b->enableGlobalShortcut(); connect( b, SIGNAL( triggered(bool) ), SLOT( increaseVolume() ) ); @@ -143,8 +147,8 @@ #ifdef __GNUC__ #warning GLOBAL SHORTCUTS ARE NOW ASSIGNED TO ALL CONTROLS, as enableGlobalShortcut(), has not been committed #endif - if ( ! mixDevice()->isEthereal() ) { - // virtual / ethereal controls won't get shortcuts + if ( ! mixDevice()->mixer()->isDynamic() ) { + // virtual / dynamic controls won't get shortcuts b->setGlobalShortcut(dummyShortcut); // -<- enableGlobalShortcut() is not there => use workaround // b->enableGlobalShortcut(); connect( b, SIGNAL( triggered(bool) ), SLOT( decreaseVolume() ) ); @@ -157,8 +161,8 @@ #ifdef __GNUC__ #warning GLOBAL SHORTCUTS ARE NOW ASSIGNED TO ALL CONTROLS, as enableGlobalShortcut(), has not been committed #endif - if ( ! mixDevice()->isEthereal() ) { - // virtual / ethereal controls won't get shortcuts + if ( ! mixDevice()->mixer()->isDynamic() ) { + // virtual / dynamic controls won't get shortcuts b->setGlobalShortcut(dummyShortcut); // -<- enableGlobalShortcut() is not there => use workaround // b->enableGlobalShortcut(); connect( b, SIGNAL( triggered(bool) ), SLOT( toggleMuted() ) ); Index: kmix/gui/viewdockareapopup.h =================================================================== --- kmix/gui/viewdockareapopup.h (revision 1226957) +++ kmix/gui/viewdockareapopup.h (revision 1226958) @@ -24,7 +24,6 @@ #include "viewbase.h" class QGridLayout; -class QPushButton; class QWidget; class Mixer; @@ -48,8 +47,6 @@ protected: KMixWindow *_dock; - //MixDevice *_dockDevice; - QPushButton *_showPanelBox; void wheelEvent ( QWheelEvent * e ); virtual void _setMixSet(); Index: kmix/gui/viewsliders.cpp =================================================================== --- kmix/gui/viewsliders.cpp (revision 1226957) +++ kmix/gui/viewsliders.cpp (revision 1226958) @@ -148,7 +148,7 @@ { const MixSet& mixset = _mixer->getMixSet(); - if ( _mixer->dynamic() ) { + if ( _mixer->isDynamic() ) { // We will be recreating our sliders, so make sure we trash all the separators too. qDeleteAll(_separators); _separators.clear(); Index: kmix/gui/guiprofile.cpp =================================================================== --- kmix/gui/guiprofile.cpp (revision 1226957) +++ kmix/gui/guiprofile.cpp (revision 1226958) @@ -158,8 +158,12 @@ { GUIProfile* guiprof = 0; - if ( mixer == 0 || profileName.isEmpty() ) { + if ( mixer == 0 || profileName.isEmpty() ) return 0; + + if ( mixer->isDynamic() ) { + kDebug(67100) << "GUIProfile::find() Not loading GUIProfile for Dynamic Mixer (e.g. PulseAudio)"; + return 0; } QString requestedProfileName; Index: kmix/gui/viewbase.h =================================================================== --- kmix/gui/viewbase.h (revision 1226957) +++ kmix/gui/viewbase.h (revision 1226958) @@ -133,7 +133,7 @@ ViewFlags _vflags; GUIProfile* _guiprof; - KActionCollection *_localActionColletion; + KActionCollection *_localActionColletion; virtual void _setMixSet() = 0; Index: kmix/gui/viewdockareapopup.cpp =================================================================== --- kmix/gui/viewdockareapopup.cpp (revision 1226957) +++ kmix/gui/viewdockareapopup.cpp (revision 1226958) @@ -88,8 +88,8 @@ { // kDebug(67100) << "ViewDockAreaPopup::setMixSet()\n"; - if ( _mixer->dynamic() ) { - // Our _layoutMDW now should only contain spacer widgets from the QSpacerItems's in add() below. + if ( _mixer->isDynamic() ) { + // Our _layoutMDW now should only contain spacer widgets from the QSpacerItem'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) ) ) @@ -114,9 +114,9 @@ QString matchAllPlaybackAndTheCswitch("pvolume,pswitch,cswitch"); ProfControl *pctl = new ProfControl( dummyMatchAll, matchAllPlaybackAndTheCswitch); MixDeviceWidget *mdw = new MDWSlider( - md, // only 1 device. This is actually _dockDevice + md, // only 1 device. true, // Show Mute LED - false, // Show Record LED + false, // Show Record LED false, // Small Qt::Vertical, // Direction: only 1 device, so doesn't matter this, // parent @@ -128,10 +128,10 @@ _layoutMDW->addWidget( mdw, 0, 1 ); // Add button to show main panel - _showPanelBox = new QPushButton( i18n("Mixer"), this ); - _showPanelBox->setObjectName( QLatin1String("MixerPanel" )); - connect ( _showPanelBox, SIGNAL( clicked() ), SLOT( showPanelSlot() ) ); - _layoutMDW->addWidget( _showPanelBox, 1, 0, 1, 3 ); + QPushButton *pb = new QPushButton( i18n("Mixer"), this ); + pb->setObjectName( QLatin1String("MixerPanel" )); + connect ( pb, SIGNAL( clicked() ), SLOT( showPanelSlot() ) ); + _layoutMDW->addWidget( pb, 1, 0, 1, 3 ); return mdw; } Index: kmix/core/mixdevice.h =================================================================== --- kmix/core/mixdevice.h (revision 1226957) +++ kmix/core/mixdevice.h (revision 1226958) @@ -157,15 +157,6 @@ _artificial = artificial; } - bool isEthereal() const - { - return _ethereal; - } - void setEthereal(bool _ethereal) - { - this->_ethereal = _ethereal; - } - void setControlProfile(ProfControl* control); ProfControl* controlProfile(); @@ -191,14 +182,12 @@ int _enumCurrentId; QList _enumValues; // A MixDevice, that is an ENUM, has these _enumValues - //bool _doNotRestore; // A virtual control. It will not be saved/restored and/or doesn't get shortcuts - // Actually we discriminate those "virtual" controls in artificial controls and ethereal controls: + // Actually we discriminate those "virtual" controls in artificial controls and dynamic controls: // Type Shortcut Restore // Artificial: yes no Virtual::GlobalMaster or Virtual::CaptureGroup_3 (controls that are constructed artificially from other controls) - // Ethereal : no no Controls that come and go, like Pulse Stream controls + // Dynamic : no no Controls that come and go, like Pulse Stream controls bool _artificial; - bool _ethereal; MixSet *_moveDestinationMixSet; QString _iconName; Index: kmix/core/mixer.cpp =================================================================== --- kmix/core/mixer.cpp (revision 1226957) +++ kmix/core/mixer.cpp (revision 1226958) @@ -748,7 +748,7 @@ m_dynamic = dynamic; } -bool Mixer::dynamic() +bool Mixer::isDynamic() { return m_dynamic; } Index: kmix/core/mixer.h =================================================================== --- kmix/core/mixer.h (revision 1226957) +++ kmix/core/mixer.h (revision 1226958) @@ -164,7 +164,7 @@ /// Says if we are dynamic (e.g. widgets can come and go) virtual void setDynamic( bool dynamic = true ); - virtual bool dynamic(); + virtual bool isDynamic(); virtual bool moveStream( const QString id, const QString& destId ); Index: kmix/core/mixdevice.cpp =================================================================== --- kmix/core/mixdevice.cpp (revision 1226957) +++ kmix/core/mixdevice.cpp (revision 1226958) @@ -23,6 +23,7 @@ #include #include "core/mixdevice.h" +#include "core/mixer.h" #include "gui/guiprofile.h" #include "core/volume.h" @@ -96,14 +97,12 @@ MixDevice::MixDevice( Mixer* mixer, const QString& id, const QString& name, const QString& iconName, MixSet* moveDestinationMixSet ) { - // doNotRestore is superseded by the more generic concepts isEthereal(), isArtificial() init(mixer, id, name, iconName, moveDestinationMixSet); } void MixDevice::init( Mixer* mixer, const QString& id, const QString& name, const QString& iconName, MixSet* moveDestinationMixSet ) { _artificial = false; - _ethereal = false; _mixer = mixer; _id = id; if( name.isEmpty() ) @@ -117,9 +116,10 @@ _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; + kError(67100) << "MixDevice::setId(\"" << id << "\") . Invalid key - it must not contain spaces" << endl; _id.replace(' ', '_'); } + kDebug(67100) << "MixDevice::init() _id=" << _id; } void MixDevice::addPlaybackVolume(Volume &playbackVol) @@ -216,11 +216,10 @@ */ void MixDevice::read( KConfig *config, const QString& grp ) { - if ( isEthereal() || isArtificial() ) { + if ( _mixer->isDynamic() || isArtificial() ) { 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() ); + QString devgrp = QString("%1.Dev%2").arg(grp).arg(_id); KConfigGroup cg = config->group( devgrp ); //kDebug(67100) << "MixDevice::read() of group devgrp=" << devgrp; @@ -264,11 +263,10 @@ */ void MixDevice::write( KConfig *config, const QString& grp ) { - if (isEthereal() || isArtificial()) { + if (_mixer->isDynamic() || isArtificial()) { 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() ); + QString devgrp = QString("%1.Dev%2").arg(grp).arg(_id); KConfigGroup cg = config->group(devgrp); // kDebug(67100) << "MixDevice::write() of group devgrp=" << devgrp; Index: kmix/apps/kmix.cpp =================================================================== --- kmix/apps/kmix.cpp (revision 1226957) +++ kmix/apps/kmix.cpp (revision 1226958) @@ -182,21 +182,20 @@ void KMixWindow::initActionsAfterInitMixer() { - bool isPulseAudio = false; - // Add "launch_pavucontrol" to menu, if Pulseaudio backend is in use + // Only show the new tab widget if some of the mixers are not Dynamic. + // The GUI that then pops up could then make a new mixer from a dynamic one, + // if mixed dynamic and non-dynamic mixers were allowed, but this is generally not the case. + bool allDynamic = true; foreach( Mixer* mixer, Mixer::mixers() ) { - if ( mixer->getDriverName() == "PulseAudio") + if ( !mixer->isDynamic() ) { - isPulseAudio = true; - KAction* action = actionCollection()->addAction( "launch_pavucontrol" ); - action->setText( i18n( "Audio setup (&Pulseaudio)" ) ); - connect(action, SIGNAL(triggered(bool) ), SLOT( slotPavucontrolExec() )); + allDynamic = false; break; } } - if (! isPulseAudio ) + if (! allDynamic ) { QPixmap cornerNewPM = KIconLoader::global()->loadIcon( "tab-new", KIconLoader::Toolbar, KIconLoader::SizeSmall ); QPushButton* _cornerLabelNew = new QPushButton(); @@ -339,8 +338,11 @@ // The following loop is necessary for the case that the user has hidden all views for a Mixer instance. // Otherwise we would not save the Meta information (step -2- below for that mixer. - foreach ( Mixer* mixer, Mixer::mixers() ) - mixerViews[mixer->id()]; // just insert a map entry + // We also do not save dynamic mixers (e.g. PulseAudio) + foreach ( Mixer* mixer, Mixer::mixers() ) { + if ( !mixer->isDynamic() ) + mixerViews[mixer->id()]; // just insert a map entry + } // -1- Save the views themselves for ( int i=0; icount() ; ++i ) { @@ -351,8 +353,10 @@ // Otherwise the user will be confused afer re-plugging the card (as the config was not saved). mw->saveConfig( KGlobal::config().data() ); // add the view to the corresponding mixer list, so we can save a views-per-mixer list below - QStringList& qsl = mixerViews[mw->mixer()->id()]; - qsl.append(mw->getGuiprof()->getId()); + if ( !mw->mixer()->isDynamic() ) { + QStringList& qsl = mixerViews[mw->mixer()->id()]; + qsl.append(mw->getGuiprof()->getId()); + } } } @@ -539,22 +543,29 @@ continue; // OK, this mixer already has a profile => skip it } // No TAB YET => This should mean KMix is just started, or the user has just plugged in a card - bool profileListHasKey = pconfig.hasKey( mixer->id() ); // <<< SHOULD be before the following line - QStringList profileList = pconfig.readEntry( mixer->id(), QStringList() ); + bool profileListHasKey = false; + QStringList profileList; + bool aProfileWasAddedSucesufully = false; - bool aProfileWasAddedSucesufully = false; - foreach ( QString profileId, profileList) - { - // This handles the profileList form the kmixrc - kDebug() << "Now searching for profile: " << profileId ; - GUIProfile* guiprof = GUIProfile::find(mixer, profileId, true, false); // ### Card specific profile ### - if ( guiprof != 0 ) { - addMixerWidget(mixer->id(), guiprof, -1); - aProfileWasAddedSucesufully = true; + if ( !mixer->isDynamic() ) { + // We do not support save profiles for dynamic mixers (i.e. PulseAudio) + + profileListHasKey = pconfig.hasKey( mixer->id() ); // <<< SHOULD be before the following line + profileList = pconfig.readEntry( mixer->id(), QStringList() ); + + foreach ( QString profileId, profileList) + { + // This handles the profileList form the kmixrc + kDebug() << "Now searching for profile: " << profileId ; + GUIProfile* guiprof = GUIProfile::find(mixer, profileId, true, false); // ### Card specific profile ### + if ( guiprof != 0 ) { + addMixerWidget(mixer->id(), guiprof, -1); + aProfileWasAddedSucesufully = true; + } + else { + kError() << "Cannot load profile " << profileId << " . It was removed by the user, or the KMix config file is defective."; + } } - else { - kError() << "Cannot load profile " << profileId << " . It was removed by the user, or the KMix config file is defective."; - } } // The we_need_a_fallback case is a bit tricky. Please ask the author (cesken) before even considering to change the code. @@ -568,11 +579,17 @@ // Lets try a bunch of fallback strategies: GUIProfile* guiprof = 0; + if ( !mixer->isDynamic() ) { + // We know that GUIProfile::find() will return 0 if the mixer is dynamic, so don't bother checking. + kDebug() << "Attempting to find a card-specific GUI Profile for the mixer " << mixer->id(); guiprof = GUIProfile::find(mixer, QString("default"), false, false); // ### Card specific profile ### - if ( guiprof == 0 ) { - guiprof = GUIProfile::find(mixer, QString("default"), false, true); // ### Card unspecific profile ### + if ( guiprof == 0 ) { + kDebug() << "Not found. Attempting to find a generic GUI Profile for the mixer " << mixer->id(); + guiprof = GUIProfile::find(mixer, QString("default"), false, true); // ### Card unspecific profile ### + } } if ( guiprof == 0) { + kDebug() << "Using fallback GUI Profile for the mixer " << mixer->id(); // This means there is neither card specific nor card unspecific profile // This is the case for some backends (as they don't ship profiles). guiprof = GUIProfile::fallbackProfile(mixer); @@ -683,8 +700,7 @@ m_wsMixers->removeTab(idx); delete kmw; - bool isPulseAudio = kmw->mixer()->getDriverName() == "PulseAudio"; - m_wsMixers->setTabsClosable(!isPulseAudio && m_wsMixers->count() > 1); + m_wsMixers->setTabsClosable(!kmw->mixer()->isDynamic() && m_wsMixers->count() > 1); saveViewConfig(); } @@ -882,8 +898,7 @@ m_wsMixers->setCurrentWidget(kmw); } - bool isPulseAudio = mixer->getDriverName() == "PulseAudio"; - m_wsMixers->setTabsClosable(!isPulseAudio && m_wsMixers->count() > 1); + m_wsMixers->setTabsClosable(!mixer->isDynamic() && m_wsMixers->count() > 1); m_dontSetDefaultCardOnStart = false; @@ -1071,12 +1086,6 @@ KMessageBox::information( 0, m_hwInfoString, i18n("Mixer Hardware Information") ); } -void KMixWindow::slotPavucontrolExec() -{ - QStringList args("pavucontrol"); - forkExec(args); -} - void KMixWindow::slotKdeAudioSetupExec() { QStringList args; @@ -1133,6 +1142,12 @@ m_defaultCardOnStart = kmw->getGuiprof()->getId(); // As switching the tab does NOT mean switching the master card, we do not need to update dock icon here. // It would lead to unnecesary flickering of the (complete) dock area. + + // We only show the "Configure Channels..." menu item if the mixer is not dynamic + ViewBase* view = kmw->currentView(); + QAction* action = actionCollection()->action( "toggle_channels_currentview" ); + if (view && action) + action->setVisible( !view->getMixer()->isDynamic() ); } } Index: kmix/apps/kmix.h =================================================================== --- kmix/apps/kmix.h (revision 1226957) +++ kmix/apps/kmix.h (revision 1226958) @@ -136,7 +136,6 @@ private slots: void saveConfig(); void slotHWInfo(); - void slotPavucontrolExec(); void slotKdeAudioSetupExec(); void slotConfigureCurrentView(); void slotSelectMaster(); Index: kmix/kmixui.rc =================================================================== --- kmix/kmixui.rc (revision 1226957) +++ kmix/kmixui.rc (revision 1226958) @@ -17,7 +17,6 @@ - &Help Index: kmix/backends/mixer_backend.cpp =================================================================== --- kmix/backends/mixer_backend.cpp (revision 1226957) +++ kmix/backends/mixer_backend.cpp (revision 1226958) @@ -48,7 +48,7 @@ bool Mixer_Backend::openIfValid() { bool valid = false; int ret = open(); - if ( ret == 0 && (m_mixDevices.count() > 0 || _mixer->dynamic())) { + if ( ret == 0 && (m_mixDevices.count() > 0 || _mixer->isDynamic())) { 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,7 +139,7 @@ return m_mixDevices.at(0); // Backend has NOT set a recommended master. Evil backend => lets help out. } //first device (if exists) else { - if ( !_mixer->dynamic()) { + if ( !_mixer->isDynamic()) { // 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; } Index: kmix/backends/mixer_pulse.cpp =================================================================== --- kmix/backends/mixer_pulse.cpp (revision 1226957) +++ kmix/backends/mixer_pulse.cpp (revision 1226958) @@ -36,6 +36,8 @@ #define KMIXPA_APP_CAPTURE 3 #define KMIXPA_WIDGET_MAX KMIXPA_APP_CAPTURE +#define KMIXPA_EVENT_KEY "sink-input-by-media-role:event" + static unsigned int refcount = 0; static pa_glib_mainloop *s_mainloop = NULL; static pa_context *s_context = NULL; @@ -189,7 +191,7 @@ devinfo s; s.index = s.device_index = i->index; - s.name = QString(i->name).replace(' ', '_'); + s.name = QString::fromUtf8(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; @@ -242,7 +244,7 @@ devinfo s; s.index = s.device_index = i->index; - s.name = QString(i->name).replace(' ', '_'); + s.name = QString::fromUtf8(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; @@ -307,26 +309,28 @@ const char *t; if ((t = pa_proplist_gets(i->proplist, "module-stream-restore.id"))) { - if (strcmp(t, "sink-input-by-media-role:event") == 0) { + if (strcmp(t, KMIXPA_EVENT_KEY) == 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")); + QString appname = i18n("Unknown Application"); if (clients.contains(i->client)) - prefix = QString("%1: ").arg(clients[i->client]); + appname = clients[i->client]; + QString prefix = QString("%1: ").arg(appname); + 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.name = QString("stream:") + QString::number(i->index); //appname.replace(' ', '_').toLower(); 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; + s.stream_restore_rule = QString::fromUtf8(t); translateMasksAndMaps(s); @@ -370,22 +374,24 @@ return; } - QString prefix = QString("%1: ").arg(i18n("Unknown Application")); + QString appname = i18n("Unknown Application"); if (clients.contains(i->client)) - prefix = QString("%1: ").arg(clients[i->client]); + appname = clients[i->client]; + QString prefix = QString("%1: ").arg(appname); + 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.name = QString("stream:") + QString::number(i->index); //appname.replace(' ', '_').toLower(); 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"); + s.stream_restore_rule = QString::fromUtf8(pa_proplist_gets(i->proplist, "module-stream-restore.id")); translateMasksAndMaps(s); @@ -407,7 +413,7 @@ } -static devinfo create_role_devinfo(const char* name) { +static devinfo create_role_devinfo(QString name) { Q_ASSERT(s_RestoreRules.contains(name)); @@ -436,9 +442,10 @@ 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)) { + if (!s_RestoreRules.contains(KMIXPA_EVENT_KEY)) { // Create a fake rule restoreRule rule; rule.channel_map.channels = 1; @@ -447,37 +454,56 @@ rule.volume.values[0] = PA_VOLUME_NORM; rule.mute = false; rule.device = ""; - s_RestoreRules["sink-input-by-media-role:event"] = rule; + s_RestoreRules[KMIXPA_EVENT_KEY] = rule; + kDebug(67100) << "Initialising restore rule for new user: " << i18n("Event Sounds"); + } - 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)) { + // If we have rules, it will be created below... but if no rules + // then we add it here. + if (!outputRoles.contains(PA_INVALID_INDEX)) { + devinfo s = create_role_devinfo(KMIXPA_EVENT_KEY); + outputRoles[s.index] = s; - if (s_mixers.contains(KMIXPA_APP_PLAYBACK)) s_mixers[KMIXPA_APP_PLAYBACK]->addWidget(s.index); + } + + s_mixers[KMIXPA_APP_PLAYBACK]->triggerUpdate(); } - 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; + + QString name = QString::fromUtf8(i->name); + kDebug(67100) << QString("Got some info about restore rule: '%1' (Device: %2)").arg(name).arg(i->device ? i->device : "None"); 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 (rule.channel_map.channels < 1 && name == KMIXPA_EVENT_KEY) { + // Stream restore rules may not have valid volumes/channel maps (as these are optional) + // but we need a valid volume+channelmap for our events sounds so fix it up. + 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; + } - if (is_new && s_mixers.contains(KMIXPA_APP_PLAYBACK)) - s_mixers[KMIXPA_APP_PLAYBACK]->addWidget(s.index); + s_RestoreRules[name] = rule; + + if (s_mixers.contains(KMIXPA_APP_PLAYBACK)) { + // We only want to know about Sound Events for now... + if (name == KMIXPA_EVENT_KEY) { + devinfo s = create_role_devinfo(name); + bool is_new = !outputRoles.contains(s.index); + outputRoles[s.index] = s; + + if (is_new) + s_mixers[KMIXPA_APP_PLAYBACK]->addWidget(s.index); + } } } @@ -788,7 +814,6 @@ 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, ms); - md->setEthereal(true); md->addPlaybackVolume(v); md->setMuted(dev.mute); m_mixDevices.append(md); @@ -1095,10 +1120,10 @@ { restoreRule &rule = s_RestoreRules[iter->stream_restore_rule]; pa_ext_stream_restore_info info; - info.name = iter->stream_restore_rule.toAscii().constData(); + info.name = iter->stream_restore_rule.toUtf8().constData(); info.channel_map = rule.channel_map; info.volume = genVolumeForPulse(*iter, md->playbackVolume()); - info.device = rule.device.isEmpty() ? NULL : rule.device.toAscii().constData(); + info.device = rule.device.isEmpty() ? NULL : rule.device.toUtf8().constData(); info.mute = (md->isMuted() ? 1 : 0); pa_operation* o; @@ -1153,13 +1178,13 @@ // Lookup the stream index. uint32_t stream_index = PA_INVALID_INDEX; - const char* stream_restore_rule = NULL; + QString stream_restore_rule = ""; 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(); + stream_restore_rule = iter->stream_restore_rule; break; } } @@ -1171,12 +1196,12 @@ 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)) { + if (stream_restore_rule.isEmpty() || !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.name = stream_restore_rule.toUtf8().constData(); info.channel_map = rule.channel_map; info.volume = rule.volume; info.device = NULL; @@ -1192,12 +1217,12 @@ } 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))) { + if (!(o = pa_context_move_sink_input_by_name(s_context, stream_index, destId.toUtf8().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))) { + if (!(o = pa_context_move_source_output_by_name(s_context, stream_index, destId.toUtf8().constData(), NULL, NULL))) { kWarning(67100) << "pa_context_move_source_output_by_name() failed"; return false; }