diff --git a/core/mixer.cpp b/core/mixer.cpp index bcdd3c8d..f56f6fa1 100644 --- a/core/mixer.cpp +++ b/core/mixer.cpp @@ -1,726 +1,721 @@ /* * KMix -- KDE's full featured mini mixer * * * Copyright (C) 1996-2004 Christian Esken - esken@kde.org * 2002 Helio Chissini de Castro - helio@conectiva.com.br * * 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. */ #include "core/mixer.h" #include #include #include "backends/mixer_backend.h" #include "backends/kmix-backends.cpp" #include "core/ControlManager.h" #include "core/GlobalConfig.h" #include "core/volume.h" /** * Some general design hints. Hierarchy is Mixer->MixDevice->Volume */ QList Mixer::s_mixers; MasterControl Mixer::_globalMasterCurrent; MasterControl Mixer::_globalMasterPreferred; bool Mixer::m_beepOnVolumeChange = false; int Mixer::numDrivers() { MixerFactory *factory = g_mixerFactories; int num = 0; while( factory->getMixer!=0 ) { num++; factory++; } return num; } /* * Returns a reference of the current mixer list. */ QList& Mixer::mixers() { return s_mixers; } /** * Returns whether there is at least one dynamic mixer active. * @returns true, if at least one dynamic mixer is active */ bool Mixer::dynamicBackendsPresent() { foreach ( Mixer* mixer, Mixer::mixers() ) { if ( mixer->isDynamic() ) return true; } return false; } bool Mixer::pulseaudioPresent() { foreach ( Mixer* mixer, Mixer::mixers() ) { if ( mixer->getDriverName() == "PulseAudio" ) return true; } return false; } Mixer::Mixer(const QString &ref_driverName, int device) : m_balance(0), _mixerBackend(nullptr), m_dynamic(false) { _mixerBackend = 0; int driverCount = numDrivers(); for (int driver=0; driver retrieve Mixer factory for that driver getMixerFunc *f = g_mixerFactories[driver].getMixer; if( f!=0 ) { _mixerBackend = f( this, device ); readSetFromHWforceUpdate(); // enforce an initial update on first readSetFromHW() } break; } } } Mixer::~Mixer() { // Close the mixer. This might also free memory, depending on the called backend method close(); delete _mixerBackend; } /* * Find a Mixer. If there is no mixer with the given id, 0 is returned */ Mixer* Mixer::findMixer( const QString& mixer_id) { Mixer *mixer = 0; int mixerCount = Mixer::mixers().count(); for ( int i=0; iid() == mixer_id ) { mixer = (Mixer::mixers())[i]; break; } } return mixer; } /** * Set the final ID of this Mixer. *
Warning: This method is VERY fragile, because it is requires information that we have very late, * especially the _cardInstance. We only know the _cardInstance, when we know the ID of the _mixerBackend->getId(). * OTOH, the Mixer backend needs the _cardInstance during construction of its MixDevice instances. * * This means, we need the _cardInstance during construction of the Mixer, but we only know it after its constructed. * Actually its a design error. The _cardInstance MUST be set and managed by the backend. * * The current solution works but is very hacky - cardInstance is a parameter of openIfValid(). * */ void Mixer::recreateId() { /* As we use "::" and ":" as separators, the parts %1,%2 and %3 may not * contain it. * %1, the driver name is from the KMix backends, it does not contain colons. * %2, the mixer name, is typically coming from an OS driver. It could contain colons. * %3, the mixer number, is a number: it does not contain colons. */ QString mixerName = _mixerBackend->getId(); mixerName.replace(':','_'); QString primaryKeyOfMixer = QString("%1::%2:%3") .arg(getDriverName(), mixerName) .arg(getCardInstance()); // The following 3 replaces are for not messing up the config file primaryKeyOfMixer.replace(']','_'); primaryKeyOfMixer.replace('[','_'); // not strictly necessary, but lets play safe primaryKeyOfMixer.replace(' ','_'); primaryKeyOfMixer.replace('=','_'); _id = primaryKeyOfMixer; // qCDebug(KMIX_LOG) << "Early _id=" << _id; } const QString Mixer::dbusPath() { // _id needs to be fixed from the very beginning, as the MixDevice construction uses MixDevice::dbusPath(). // So once the first MixDevice is created, this must return the correct value if (_id.isEmpty()) { if (! _mixerBackend->_cardRegistered) { // Bug 308014: By checking _cardRegistered, we can be sure that everything is fine, including the fact that // the cardId (aka "card instance") is set. If _cardRegistered would be false, we will create potentially // wrong/duplicated DBUS Paths here. qCWarning(KMIX_LOG) << "Mixer id was empty when creating DBUS path. Emergency code created the id=" <<_id; } // Bug 308014: Actually this a shortcut (you could also call it a hack). It would likely better if registerCard() // would create the Id, but it requires cooperation from ALL backends. Also Mixer->getId() would need to // proxy that to the backend. // So for now we lazily create the MixerId here, while creating the first MixDevice for that card. recreateId(); } // mixerName may contain arbitrary characters, so replace all that are not allowed to be be part of a DBUS path QString cardPath = _id; cardPath.replace(QRegExp("[^a-zA-Z0-9_]"), "_"); cardPath.replace(QLatin1String("//"), QLatin1String("/")); return QString("/Mixers/" + cardPath); } void Mixer::volumeSave( KConfig *config ) { // qCDebug(KMIX_LOG) << "Mixer::volumeSave()"; _mixerBackend->readSetFromHW(); QString grp("Mixer"); grp.append(id()); _mixerBackend->m_mixDevices.write( config, grp ); // This might not be the standard application config object // => Better be safe and call sync(). config->sync(); } void Mixer::volumeLoad( KConfig *config ) { QString grp("Mixer"); grp.append(id()); if ( ! config->hasGroup(grp) ) { // no such group. Volumes (of this mixer) were never saved beforehand. // Thus don't restore anything (also see Bug #69320 for understanding the real reason) return; // make sure to bail out immediately } // else restore the volumes if ( ! _mixerBackend->m_mixDevices.read( config, grp ) ) { // Some mixer backends don't support reading the volume into config // files, so bail out early if that's the case. return; } // set new settings for(int i=0; i<_mixerBackend->m_mixDevices.count() ; i++ ) { shared_ptr md = _mixerBackend->m_mixDevices[i]; if ( md.get() == 0 ) continue; _mixerBackend->writeVolumeToHW( md->id(), md ); if ( md->isEnum() ) _mixerBackend->setEnumIdHW( md->id(), md->enumId() ); } } /** * Opens the mixer. * Also, starts the polling timer, for polling the Volumes from the Mixer. * * @return true, if Mixer could be opened. */ bool Mixer::openIfValid() { if (_mixerBackend==nullptr) { // If we did not instantiate a suitable backend, then the mixer is invalid. qCWarning(KMIX_LOG) << "no mixer backend"; return false; } bool ok = _mixerBackend->openIfValid(); if (!ok) return (false); recreateId(); shared_ptr recommendedMaster = _mixerBackend->recommendedMaster(); if (recommendedMaster.get()!=nullptr) { QString recommendedMasterStr = recommendedMaster->id(); setLocalMasterMD( recommendedMasterStr ); qCDebug(KMIX_LOG) << "Detected master" << recommendedMaster->id(); } else { if (!m_dynamic) qCCritical(KMIX_LOG) << "No master detected and not dynamic"; else qCDebug(KMIX_LOG) << "No master detected but dynamic"; QString noMaster = "---no-master-detected---"; setLocalMasterMD(noMaster); // no master } new DBusMixerWrapper(this, dbusPath()); return (true); } /** * Closes the mixer. */ void Mixer::close() { if ( _mixerBackend != 0) _mixerBackend->closeCommon(); } /* ------- WRAPPER METHODS. START ------------------------------ */ unsigned int Mixer::size() const { return _mixerBackend->m_mixDevices.count(); } -shared_ptr Mixer::operator[](int num) -{ - shared_ptr md = _mixerBackend->m_mixDevices.at( num ); - return md; -} -MixSet& Mixer::getMixSet() +MixSet &Mixer::getMixSet() const { - return _mixerBackend->m_mixDevices; + return (_mixerBackend->m_mixDevices); } /** * Returns the driver name, that handles this Mixer. */ QString Mixer::getDriverName() const { QString driverName = _mixerBackend->getDriverName(); // qCDebug(KMIX_LOG) << "Mixer::getDriverName() = " << driverName << "\n"; return driverName; } bool Mixer::isOpen() const { if ( _mixerBackend == 0 ) return false; else return _mixerBackend->isOpen(); } void Mixer::readSetFromHWforceUpdate() const { _mixerBackend->readSetFromHWforceUpdate(); } /// Returns translated WhatsThis messages for a control.Translates from QString Mixer::translateKernelToWhatsthis(const QString &kernelName) { return _mixerBackend->translateKernelToWhatsthis(kernelName); } /* ------- WRAPPER METHODS. END -------------------------------- */ int Mixer::balance() const { return m_balance; } void Mixer::setBalance(int balance) { if( balance == m_balance ) { // balance unchanged => return return; } m_balance = balance; shared_ptr master = getLocalMasterMD(); if ( master.get() == 0 ) { // no master device available => return return; } Volume& volP = master->playbackVolume(); setBalanceInternal(volP); Volume& volC = master->captureVolume(); setBalanceInternal(volC); _mixerBackend->writeVolumeToHW( master->id(), master ); emit newBalance( volP ); } void Mixer::setBalanceInternal(Volume& vol) { //_mixerBackend->readVolumeFromHW( master->id(), master ); int left = vol.getVolume(Volume::LEFT); int right = vol.getVolume( Volume::RIGHT ); int refvol = left > right ? left : right; if( m_balance < 0 ) // balance left { vol.setVolume( Volume::LEFT, refvol); vol.setVolume( Volume::RIGHT, (m_balance * refvol) / 100 + refvol ); } else { vol.setVolume( Volume::LEFT, -(m_balance * refvol) / 100 + refvol ); vol.setVolume( Volume::RIGHT, refvol); } } /** * Returns a name suitable for a human user to read (on a label, ...) */ QString Mixer::readableName(bool ampersandQuoted) const { QString finalName = _mixerBackend->getName(); if (ampersandQuoted) finalName.replace('&', "&&"); if ( getCardInstance() > 1) finalName = finalName.append(" %1").arg(getCardInstance()); // qCDebug(KMIX_LOG) << "name=" << _mixerBackend->getName() << "instance=" << getCardInstance() << ", finalName" << finalName; return finalName; } QString Mixer::getBaseName() const { return _mixerBackend->getName(); } /** * Queries the Driver Factory for a driver. * @par driver Index number. 0 <= driver < numDrivers() */ QString Mixer::driverName( int driver ) { getDriverNameFunc *f = g_mixerFactories[driver].getDriverName; if( f!=0 ) return f(); else return "unknown"; } /* obsoleted by setInstance() void Mixer::setID(QString& ref_id) { _id = ref_id; } */ const QString &Mixer::id() const { return _id; } const QString &Mixer::udi() const { return _mixerBackend->udi(); } /** * Set the global master, which is shown in the dock area and which is accessible via the * DBUS masterVolume() method. * * The parameters are taken over as-is, this means without checking for validity. * This allows the User to define a master card that is not always available * (e.g. it is an USB hotplugging device). Also you can set the master at any time you * like, e.g. after reading the KMix configuration file and before actually constructing * the Mixer instances (hint: this method is static!). * * @param ref_card The card id * @param ref_control The control id. The corresponding control must be present in the card. * @param preferred Whether this is the preferred master (auto-selected on coldplug and hotplug). */ void Mixer::setGlobalMaster(QString ref_card, QString ref_control, bool preferred) { qCDebug(KMIX_LOG) << "ref_card=" << ref_card << ", ref_control=" << ref_control << ", preferred=" << preferred; _globalMasterCurrent.set(ref_card, ref_control); if ( preferred ) _globalMasterPreferred.set(ref_card, ref_control); qCDebug(KMIX_LOG) << "Mixer::setGlobalMaster() card=" <id(); return mixer; } /** * Return the preferred global master. * If there is no preferred global master, returns the current master instead. */ MasterControl& Mixer::getGlobalMasterPreferred(bool fallbackAllowed) { static MasterControl result; if ( !fallbackAllowed || _globalMasterPreferred.isValid() ) { // qCDebug(KMIX_LOG) << "Returning preferred master"; return _globalMasterPreferred; } Mixer* mm = Mixer::getGlobalMasterMixerNoFalback(); if (mm) { result.set(_globalMasterPreferred.getCard(), mm->getRecommendedDeviceId()); if (!result.getControl().isEmpty()) // qCDebug(KMIX_LOG) << "Returning extended preferred master"; return result; } qCDebug(KMIX_LOG) << "Returning current master"; return _globalMasterCurrent; } shared_ptr Mixer::getGlobalMasterMD() { return getGlobalMasterMD(true); } shared_ptr Mixer::getGlobalMasterMD(bool fallbackAllowed) { shared_ptr mdRet; shared_ptr firstDevice; Mixer *mixer = fallbackAllowed ? Mixer::getGlobalMasterMixer() : Mixer::getGlobalMasterMixerNoFalback(); if ( mixer == 0 ) return mdRet; if (_globalMasterCurrent.getControl().isEmpty()) { // Default (recommended) control return mixer->_mixerBackend->recommendedMaster(); } foreach (shared_ptr md, mixer->_mixerBackend->m_mixDevices ) { if ( md.get() == 0 ) continue; // invalid firstDevice=md; if ( md->id() == _globalMasterCurrent.getControl() ) { mdRet = md; break; // found } } if ( mdRet.get() == 0 ) { //For some sound cards when using pulseaudio the mixer id is not proper hence returning the first device as master channel device //This solves the bug id:290177 and problems stated in review #105422 qCDebug(KMIX_LOG) << "Mixer::masterCardDevice() returns 0 (no globalMaster), returning the first device"; mdRet=firstDevice; } return mdRet; } QString Mixer::getRecommendedDeviceId() { if ( _mixerBackend != 0 ) { shared_ptr recommendedMaster = _mixerBackend->recommendedMaster(); if ( recommendedMaster.get() != 0 ) return recommendedMaster->id(); } return QString(); } shared_ptr Mixer::getLocalMasterMD() const { if (_mixerBackend && _masterDevicePK.isEmpty()) return _mixerBackend->recommendedMaster(); return find( _masterDevicePK ); } void Mixer::setLocalMasterMD(QString &devPK) { _masterDevicePK = devPK; } shared_ptr Mixer::find(const QString& mixdeviceID) const { shared_ptr mdRet; foreach (shared_ptr md, _mixerBackend->m_mixDevices ) { if ( md.get() == 0 ) continue; // invalid if ( md->id() == mixdeviceID ) { mdRet = md; break; // found } } return mdRet; } shared_ptr Mixer::getMixdeviceById( const QString& mixdeviceID ) { qCDebug(KMIX_LOG) << "id=" << mixdeviceID << "md=" << _mixerBackend->m_mixDevices.get(mixdeviceID).get()->id(); return _mixerBackend->m_mixDevices.get(mixdeviceID); // shared_ptr md; // int num = _mixerBackend->id2num(mixdeviceID); // if ( num!=-1 && num < (int)size() ) // { // md = (*this)[num]; // } // return md; } /** Call this if you have a *reference* to a Volume object and have modified that locally. Pass the MixDevice associated to that Volume to this method for writing back the changed value to the mixer. Hint: Why do we do it this way? - It is fast (no copying of Volume objects required) - It is easy to understand ( read - modify - commit ) */ void Mixer::commitVolumeChange(shared_ptr md) { _mixerBackend->writeVolumeToHW(md->id(), md); if (md->isEnum()) { _mixerBackend->setEnumIdHW(md->id(), md->enumId()); } if (md->captureVolume().hasSwitch()) { // Make sure to re-read the hardware, because setting capture might have failed. // This is due to exclusive capture groups. // If we wouldn't do this, KMix might show a Capture Switch disabled, but // in reality the capture switch is still on. // // We also cannot rely on a notification from the driver (SocketNotifier), because // nothing has changed, and so there s nothing to notify. _mixerBackend->readSetFromHWforceUpdate(); if (GlobalConfig::instance().data.debugControlManager) qCDebug(KMIX_LOG) << "committing a control with capture volume, that might announce: " << md->id(); _mixerBackend->readSetFromHW(); } if (GlobalConfig::instance().data.debugControlManager) qCDebug(KMIX_LOG) << "committing announces the change of: " << md->id(); // We announce the change we did, so all other parts of KMix can pick up the change ControlManager::instance().announce(md->mixer()->id(), ControlManager::Volume, QString("Mixer.commitVolumeChange()")); } // @dbus, used also in kmix app void Mixer::increaseVolume( const QString& mixdeviceID ) { increaseOrDecreaseVolume(mixdeviceID, false); } // @dbus void Mixer::decreaseVolume( const QString& mixdeviceID ) { increaseOrDecreaseVolume(mixdeviceID, true); } /** * Increase or decrease all playback and capture channels of the given control. * This method is very similar to MDWSlider::increaseOrDecreaseVolume(), but it will * NOT auto-unmute. * * @param mixdeviceID The control name * @param decrease true for decrease. false for increase */ void Mixer::increaseOrDecreaseVolume( const QString& mixdeviceID, bool decrease ) { shared_ptr md= getMixdeviceById( mixdeviceID ); if (md.get() != 0) { Volume& volP=md->playbackVolume(); if ( volP.hasVolume() ) { volP.changeAllVolumes(volP.volumeStep(decrease)); } Volume& volC=md->captureVolume(); if ( volC.hasVolume() ) { volC.changeAllVolumes(volC.volumeStep(decrease)); } _mixerBackend->writeVolumeToHW(mixdeviceID, md); } ControlManager::instance().announce(md->mixer()->id(), ControlManager::Volume, QString("Mixer.increaseOrDecreaseVolume()")); /************************************************************ It is important, not to implement this method like this: int vol=volume(mixdeviceID); setVolume(mixdeviceID, vol-5); It creates too big rounding errors. If you don't believe me, then do a decreaseVolume() and increaseVolume() with "vol.maxVolume() == 31". ***********************************************************/ } void Mixer::setDynamic ( bool dynamic ) { m_dynamic = dynamic; } bool Mixer::isDynamic() { return m_dynamic; } bool Mixer::moveStream(const QString &id, const QString &destId) { // We should really check that id is within our md's.... bool ret = _mixerBackend->moveStream(id, destId); ControlManager::instance().announce(QString(), ControlManager::ControlList, QString("Mixer.moveStream()")); return (ret); } QString Mixer::currentStreamDevice(const QString &id) const { return (_mixerBackend->currentStreamDevice(id)); } diff --git a/core/mixer.h b/core/mixer.h index e3ae794e..99d26d58 100644 --- a/core/mixer.h +++ b/core/mixer.h @@ -1,225 +1,221 @@ //-*-C++-*- /* * KMix -- KDE's full featured mini mixer * * * Copyright (C) 2000 Stefan Schimanski <1Stein@gmx.de> * 1996-2000 Christian Esken * Sven Fischer * 2002 - Helio Chissini de Castro * * 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 RANDOMPREFIX_MIXER_H #define RANDOMPREFIX_MIXER_H #include #include #include #include "core/volume.h" #include "backends/mixer_backend.h" #include "core/MasterControl.h" #include "mixset.h" #include "core/GlobalConfig.h" #include "core/mixdevice.h" #include "dbus/dbusmixerwrapper.h" #include "kmixcore_export.h" class Volume; class KConfig; class KMIXCORE_EXPORT Mixer : public QObject { Q_OBJECT public: /** * Status for Mixer operations. * * OK_UNCHANGED is a special variant of OK. It must be implemented by * backends that use needsPolling() == true. See Mixer_OSS.cpp for an * example. Rationale is that we need a proper change check: Otherwise * the DBUS Session Bus is massively spammed. Also quite likely the Mixer * GUI might get updated all the time. * */ enum MixerError { OK=0, ERR_PERM=1, ERR_WRITE, ERR_READ, ERR_OPEN, OK_UNCHANGED }; Mixer(const QString &ref_driverName, int device); virtual ~Mixer(); static int numDrivers(); QString getDriverName() const; shared_ptr find(const QString& devPK) const; static Mixer* findMixer( const QString& mixer_id); void volumeSave( KConfig *config ); void volumeLoad( KConfig *config ); /// Tells the number of the mixing devices unsigned int size() const; - /// Returns a pointer to the mix device with the given number - // TODO remove this method. Only used by ViewDockAreaPopup: dockMD = (*mixer)[0]; - shared_ptr operator[](int val_i_num); - /// Returns a pointer to the mix device whose type matches the value /// given by the parameter and the array MixerDevNames given in /// mixer_oss.cpp (0 is Volume, 4 is PCM, etc.) shared_ptr getMixdeviceById( const QString& deviceID ); /// Open/grab the mixer for further interaction bool openIfValid(); /// Returns whether the card is open/operational bool isOpen() const; /// Close/release the mixer virtual void close(); /// Reads balance int balance() const; /// Returns a detailed state message after errors. Only for diagnostic purposes, no i18n. QString& stateMessage() const; /** * Returns the name of the card/chip/hardware, as given by the driver. The name is NOT instance specific, * so if you install two identical soundcards, two of them will deliver the same mixerName(). * Use this method if you need an instance-UNspecific name, e.g. for finding an appropriate * mixer layout for this card, or as a prefix for constructing instance specific ID's like in id(). */ virtual QString getBaseName() const; /// Wrapper to Mixer_Backend QString translateKernelToWhatsthis(const QString &kernelName); /** * Get a name suitable for a human user to read, possibly with quoted ampersand. * The latter is required by some GUI elements like QRadioButton or when used as a * tab label, as '&' introduces an accelerator there. * * @param ampersandQuoted @c true if '&' characters are to be quoted * @return the readable device name */ QString readableName(bool ampersandQuoted = false) const; // Returns the name of the driver, e.g. "OSS" or "ALSA0.9" static QString driverName(int num); static bool getBeepOnVolumeChange() { GlobalConfigData& gcd = GlobalConfig::instance().data; return gcd.beepOnVolumeChange; } /** * Returns an unique ID of the Mixer. It currently looks like ":::" */ const QString &id() const; int getCardInstance() const { return _mixerBackend->getCardInstance(); } /// Returns an Universal Device Identification of the Mixer. This is an ID that relates to the underlying operating system. // For OSS and ALSA this is taken from Solid (actually HAL). For Solaris this is just the device name. // Examples: // ALSA: /org/freedesktop/Hal/devices/usb_device_d8c_1_noserial_if0_sound_card_0_2_alsa_control__1 // OSS: /org/freedesktop/Hal/devices/usb_device_d8c_1_noserial_if0_sound_card_0_2_oss_mixer__1 // Solaris: /dev/audio const QString &udi() const; // Returns a DBus path for this mixer // Used also by MixDevice to bind to this path const QString dbusPath(); static QList & mixers(); /****************************************** The KMix GLOBAL master card. Please note that KMix and KMixPanelApplet can have a different MasterCard's at the moment (but actually KMixPanelApplet does not read/save this yet). At the moment it is only used for selecting the Mixer to use in KMix's DockIcon. ******************************************/ static void setGlobalMaster(QString ref_card, QString ref_control, bool preferred); static shared_ptr getGlobalMasterMD(); static shared_ptr getGlobalMasterMD(bool fallbackAllowed); static Mixer* getGlobalMasterMixer(); static Mixer* getGlobalMasterMixerNoFalback(); static MasterControl& getGlobalMasterPreferred(bool fallbackAllowed = true); QString getRecommendedDeviceId(); /****************************************** The recommended master of this Mixer. ******************************************/ shared_ptr getLocalMasterMD() const; void setLocalMasterMD(QString&); /// get the actual MixSet - MixSet& getMixSet(); + MixSet &getMixSet() const; /// DBUS oriented methods virtual void increaseVolume( const QString& mixdeviceID ); virtual void decreaseVolume( const QString& mixdeviceID ); /// Says if we are dynamic (e.g. widgets can come and go) virtual void setDynamic( bool dynamic = true ); virtual bool isDynamic(); static bool dynamicBackendsPresent(); static bool pulseaudioPresent(); virtual bool moveStream(const QString &id, const QString &destId); virtual QString currentStreamDevice(const QString &id) const; virtual int mediaPlay(QString id) { return _mixerBackend->mediaPlay(id); }; virtual int mediaPrev(QString id) { return _mixerBackend->mediaPrev(id); }; virtual int mediaNext(QString id) { return _mixerBackend->mediaNext(id); }; void commitVolumeChange( shared_ptr md ); public slots: void readSetFromHWforceUpdate() const; virtual void setBalance(int balance); // sets the m_balance (see there) signals: void newBalance(Volume& ); void controlChanged(void); // TODO remove? protected: int m_balance; // from -100 (just left) to 100 (just right) static QList s_mixers; private: void setBalanceInternal(Volume& vol); void recreateId(); void increaseOrDecreaseVolume( const QString& mixdeviceID, bool decrease ); Mixer_Backend *_mixerBackend; QString _id; QString _masterDevicePK; static MasterControl _globalMasterCurrent; static MasterControl _globalMasterPreferred; bool m_dynamic; static bool m_beepOnVolumeChange; }; #endif diff --git a/gui/dialogchoosebackends.cpp b/gui/dialogchoosebackends.cpp index ff90889e..2d50ab1d 100644 --- a/gui/dialogchoosebackends.cpp +++ b/gui/dialogchoosebackends.cpp @@ -1,154 +1,155 @@ /* * KMix -- KDE's full featured mini mixer * * * Copyright (C) 1996-2004 Christian Esken * * 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. */ #include "gui/dialogchoosebackends.h" #include #include #include #include #include #include "core/ControlManager.h" #include "core/GlobalConfig.h" #include "core/mixdevice.h" #include "core/mixer.h" /** * Creates a dialog to choose mixers from. All currently known mixers will be shown, and the given mixerID's * will be preselected. * * @param mixerIds A set of preselected mixer ID's * @param noButtons is a migration option. When DialogChooseBackends has been integrated as a Tab, it will be removed. */ DialogChooseBackends::DialogChooseBackends(QWidget* parent, const QSet& mixerIds) : QWidget(parent), modified(false) { // setCaption( i18n( "Select Mixers" ) ); // setButtons( None ); _layout = 0; createWidgets(mixerIds); } /** * Create basic widgets of the Dialog. */ void DialogChooseBackends::createWidgets(const QSet& mixerIds) { _layout = new QVBoxLayout(this); _layout->setMargin(0); if ( !Mixer::mixers().isEmpty() ) { m_listLabel = new QLabel( i18n("Mixers to show in the popup volume control:"), this); _layout->addWidget(m_listLabel); createPage(mixerIds); } else { + // TODO: use KMessageWidget m_listLabel = new QLabel( i18n("No sound card is installed or currently plugged in."), this); _layout->addWidget(m_listLabel); } } /** * Create RadioButton's for the Mixer with number 'mixerId'. * @par mixerId The Mixer, for which the RadioButton's should be created. */ void DialogChooseBackends::createPage(const QSet& mixerIds) { m_mixerList = new QListWidget(this); m_mixerList->setUniformItemSizes(true); m_mixerList->setAlternatingRowColors(true); m_mixerList->setSelectionMode(QAbstractItemView::NoSelection); m_mixerList->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); #ifndef QT_NO_ACCESSIBILITY m_mixerList->setAccessibleName(i18n("Select Mixers")); #endif m_listLabel->setBuddy(m_mixerList); _layout->addWidget(m_mixerList); bool hasMixerFilter = !mixerIds.isEmpty(); qCDebug(KMIX_LOG) << "MixerIds=" << mixerIds; foreach ( Mixer* mixer, Mixer::mixers()) { QListWidgetItem *item = new QListWidgetItem(m_mixerList); item->setText(mixer->readableName(true)); item->setSizeHint(QSize(1, 16)); item->setFlags(Qt::ItemIsEnabled|Qt::ItemIsUserCheckable|Qt::ItemNeverHasChildren); const bool mixerShouldBeShown = !hasMixerFilter || mixerIds.contains(mixer->id()); item->setCheckState(mixerShouldBeShown ? Qt::Checked : Qt::Unchecked); item->setData(Qt::UserRole, mixer->id()); } connect(m_mixerList, SIGNAL(itemChanged(QListWidgetItem*)), SLOT(backendsModifiedSlot())); connect(m_mixerList, SIGNAL(itemActivated(QListWidgetItem*)), SLOT(itemActivatedSlot(QListWidgetItem*))); } QSet DialogChooseBackends::getChosenBackends() { QSet newMixerList; for (int row = 0; rowcount(); ++row) { QListWidgetItem *item = m_mixerList->item(row); if (item->checkState()==Qt::Checked) { const QString mixer = item->data(Qt::UserRole).toString(); newMixerList.insert(mixer); qCDebug(KMIX_LOG) << "apply found " << mixer; } } qCDebug(KMIX_LOG) << "New list is " << newMixerList; return newMixerList; } /** * Returns whether there were any modifications (activation/deactivation) and resets the flag. * @return */ bool DialogChooseBackends::getAndResetModifyFlag() { bool modifiedOld = modified; modified = false; return modifiedOld; } bool DialogChooseBackends::getModifyFlag() { return modified; } void DialogChooseBackends::backendsModifiedSlot() { modified = true; emit backendsModified(); } void DialogChooseBackends::itemActivatedSlot(QListWidgetItem *item) { item->setCheckState(item->checkState()==Qt::Checked ? Qt::Unchecked : Qt::Checked); } diff --git a/gui/viewdockareapopup.cpp b/gui/viewdockareapopup.cpp index cfcc9a60..2d5d72e2 100644 --- a/gui/viewdockareapopup.cpp +++ b/gui/viewdockareapopup.cpp @@ -1,435 +1,449 @@ /* * KMix -- KDE's full featured mini mixer * * * Copyright (C) 1996-2004 Christian Esken * * 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. */ #include "gui/viewdockareapopup.h" // Qt #include #include #include #include #include #include #include // KDE #include #include // KMix #include "apps/kmix.h" #include "core/mixer.h" #include "core/ControlManager.h" #include "gui/dialogchoosebackends.h" #include "gui/guiprofile.h" #include "gui/kmixprefdlg.h" #include "gui/mdwslider.h" #include "dialogbase.h" // Restore volume button feature is incomplete => disabling for KDE 4.10 #undef RESTORE_VOLUME_BUTTON ViewDockAreaPopup::ViewDockAreaPopup(QWidget* parent, QString id, ViewBase::ViewFlags vflags, QString guiProfileId, KMixWindow *dockW) : ViewBase(parent, id, 0, vflags, guiProfileId), _kmixMainWindow(dockW) { resetRefs(); setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); /* * Bug 206724: * Following are excerpts of trying to receive key events while this popup is open. * The best I could do with lots of hacks is to get the keyboard events after a mouse-click in the popup. * But such a solution is neither intuitive nor helpful - if one clicks, then usage of keyboard makes not much sense any longer. * I finally gave up on fixing Bug 206724. */ /* releaseKeyboard(); setFocusPolicy(Qt::StrongFocus); setFocus(Qt::TabFocusReason); releaseKeyboard(); mainWindowButton->setFocusPolicy(Qt::StrongFocus); Also implemented the following "event handlers", but they do not show any signs of key presses: keyPressEvent() x11Event() eventFilter() */ foreach ( Mixer* mixer, Mixer::mixers() ) { // Adding all mixers, as we potentially want to show all master controls addMixer(mixer); // The list will be redone in initLayout() with the actual Mixer instances to use } restoreVolumeIcon = QIcon::fromTheme(QLatin1String("quickopen-file")); createDeviceWidgets(); // Register listeners for all mixers ControlManager::instance().addListener( QString(), // all mixers ControlManager::GUI|ControlManager::ControlList|ControlManager::Volume|ControlManager::MasterChanged, this, QString("ViewDockAreaPopup")); } ViewDockAreaPopup::~ViewDockAreaPopup() { ControlManager::instance().removeListener(this); delete _layoutMDW; // Hint: optionsLayout and "everything else" is deleted when "delete _layoutMDW" cascades down } void ViewDockAreaPopup::controlsChange(ControlManager::ChangeType changeType) { switch (changeType) { case ControlManager::ControlList: case ControlManager::MasterChanged: createDeviceWidgets(); break; case ControlManager::GUI: updateGuiOptions(); break; case ControlManager::Volume: refreshVolumeLevels(); break; default: ControlManager::warnUnexpectedChangeType(changeType, this); break; } } void ViewDockAreaPopup::configurationUpdate() { // TODO Do we still need configurationUpdate(). It was never implemented for ViewDockAreaPopup } // TODO Currently no right-click, as we have problems to get the ViewDockAreaPopup resized void ViewDockAreaPopup::showContextMenu() { // no right-button-menu on "dock area popup" return; } void ViewDockAreaPopup::resetRefs() { seperatorBetweenMastersAndStreams = 0; separatorBetweenMastersAndStreamsInserted = false; separatorBetweenMastersAndStreamsRequired = false; configureViewButton = 0; restoreVolumeButton1 = 0; restoreVolumeButton2 = 0; restoreVolumeButton3 = 0; restoreVolumeButton4 = 0; mainWindowButton = 0; optionsLayout = 0; _layoutMDW = 0; } void ViewDockAreaPopup::initLayout() { resetMdws(); if (optionsLayout != 0) { QLayoutItem *li2; while ((li2 = optionsLayout->takeAt(0))) delete li2; } // Hint : optionsLayout itself is deleted when "delete _layoutMDW" cascades down if (_layoutMDW != 0) { QLayoutItem *li; while ((li = _layoutMDW->takeAt(0))) delete li; } /* * Strangely enough, I cannot delete optionsLayout in a loop. I get a strange stacktrace: * Application: KMix (kmix), signal: Segmentation fault [...] #6 0x00007f9c9a282900 in QString::shared_null () from /usr/lib/x86_64-linux-gnu/libQtCore.so.4 #7 0x00007f9c9d4286b0 in ViewDockAreaPopup::initLayout (this=0x1272b60) at /home/chris/workspace/kmix-git-trunk/gui/viewdockareapopup.cpp:164 #8 0x00007f9c9d425700 in ViewBase::createDeviceWidgets (this=0x1272b60) at /home/chris/workspace/kmix-git-trunk/gui/viewbase.cpp:137 #9 0x00007f9c9d42845b in ViewDockAreaPopup::controlsChange (this=0x1272b60, changeType=2) at /home/chris/workspace/kmix-git-trunk/gui/viewdockareapopup.cpp:91 */ // if ( optionsLayout != 0 ) // { // QLayoutItem *li2; // while ( ( li2 = optionsLayout->takeAt(0) ) ) // strangely enough, it crashes here // delete li2; // } // --- Due to the strange crash, delete everything manually : START --------------- // I am a bit confused why this doesn't crash. I moved the "optionsLayout->takeAt(0) delete" loop at the beginning, // so the objects should already be deleted. ... delete configureViewButton; delete restoreVolumeButton1; delete restoreVolumeButton2; delete restoreVolumeButton3; delete restoreVolumeButton4; delete mainWindowButton; delete seperatorBetweenMastersAndStreams; // --- Due to the strange crash, delete everything manually : END --------------- resetRefs(); setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); /* * BKO 299754: Looks like I need to explicitly delete layout(). I have no idea why * "delete _layoutMDW" is not enough, as that is supposed to be the layout * of this ViewDockAreaPopup * (Hint: it might have been 0 already. Nowadays it is definitely, see #resetRefs()) */ delete layout(); // BKO 299754 _layoutMDW = new QGridLayout(this); _layoutMDW->setSpacing(DialogBase::verticalSpacing()); // Review #121166: Add some space over device icons, otherwise they may hit window border _layoutMDW->setContentsMargins(0,5,0,0); //_layoutMDW->setSizeConstraint(QLayout::SetMinimumSize); _layoutMDW->setSizeConstraint(QLayout::SetMaximumSize); _layoutMDW->setObjectName(QLatin1String("KmixPopupLayout")); - setLayout(_layoutMDW); + setLayout(_layoutMDW); // Adding all mixers, as we potentially want to show all master controls. Due to hotplugging // we have to redo the list on each initLayout() (instead of setting it once in the Constructor) _mixers.clear(); QSet preferredMixersForSoundmenu = GlobalConfig::instance().getMixersForSoundmenu(); -// qCDebug(KMIX_LOG) << "Launch with " << preferredMixersForSoundmenu; + //qCDebug(KMIX_LOG) << "Launch with " << preferredMixersForSoundmenu; foreach ( Mixer* mixer, Mixer::mixers() ) { bool useMixer = preferredMixersForSoundmenu.isEmpty() || preferredMixersForSoundmenu.contains(mixer->id()); - if (useMixer) - addMixer(mixer); + if (useMixer) addMixer(mixer); } // The following loop is for the case when everything gets filtered out. We "reset" to show everything then. // Hint: Filtering everything out can only be an "accident", e.g. when restarting KMix with changed hardware or // backends. if ( _mixers.isEmpty() ) { foreach ( Mixer* mixer, Mixer::mixers() ) { addMixer(mixer); } } - // A loop that adds the Master control of each card - foreach ( Mixer* mixer, _mixers ) + + // TODO: check whether this is working as intended for PulseAudio. + // + // Logic might say that enabling "Playback Devices" should show all of the + // playback devices (cards) in the popup, whereas at the moment it only + // shows the configured master playback device. This is the same behaviour + // for the non-PulseAudio case where only the master control for each card + // is shown, although each card can be configured to individually + // appear in the popup. With PulseAudio "Playback Devices" is considered + // to be a single card and only the master channel from it is shown. + // + // To do this, there needs to be a loop over all 'MixDevice's of the 'Mixer' + // instead of just taking the getLocalMaster() device of it. + // + // Maybe need a configuration option? + + foreach (const Mixer *mixer, _mixers) { -// qCDebug(KMIX_LOG) << "ADD? mixerId=" << mixer->id(); - shared_ptrdockMD = mixer->getLocalMasterMD(); - if ( !dockMD && mixer->size() > 0 ) + //qCDebug(KMIX_LOG) << "ADD? mixerId=" << mixer->id(); + // Get the configured master control for the mixer. + shared_ptr dockMD = mixer->getLocalMasterMD(); + if (dockMD==nullptr && mixer->size()>0) { - // If we have no dock device yet, we will take the first available mixer device. - dockMD = (*mixer)[0]; + // If the mixer has no local master device defined, + // then take its first available device. + dockMD = mixer->getMixSet().first(); } - if ( dockMD ) + + if (dockMD!=nullptr) // have a master device to dock { -// qCDebug(KMIX_LOG) << "ADD? mixerId=" << mixer->id() << ", md=" << dockMD->id(); - if ( !dockMD->isApplicationStream() && dockMD->playbackVolume().hasVolume()) + // Do not add application streams here, they are handled below. + if (dockMD->isApplicationStream()) continue; + + //qCDebug(KMIX_LOG) << "ADD? mixerId=" << mixer->id() << ", md=" << dockMD->id(); + if (dockMD->playbackVolume().hasVolume() || dockMD->captureVolume().hasVolume()) { -// qCDebug(KMIX_LOG) << "ADD? mixerId=" << mixer->id() << ", md=" << dockMD->id() << ": YES"; - // don't add application streams here. They are handled below, so - // we make sure to not add them twice + //qCDebug(KMIX_LOG) << "ADD? mixerId=" << mixer->id() << ", md=" << dockMD->id() << ": YES"; _mixSet.append(dockMD); } } } // loop over all cards - // Add all application streams - foreach ( Mixer* mixer2 , _mixers ) + // Finally add all application streams + foreach (const Mixer *mixer, _mixers) { - foreach ( shared_ptr md, mixer2->getMixSet() ) + foreach (shared_ptr md, mixer->getMixSet()) { - if (md->isApplicationStream()) - { - _mixSet.append(md); - } + if (md->isApplicationStream()) _mixSet.append(md); } } - } QWidget* ViewDockAreaPopup::add(shared_ptr md) { const bool vertical = (orientation()==Qt::Vertical); /* QString dummyMatchAll("*"); QString matchAllPlaybackAndTheCswitch("pvolume,cvolume,pswitch,cswitch"); // Leak | relevant | pctl Each time a stream is added, a new ProfControl gets created. // It cannot be deleted in ~MixDeviceWidget, as ProfControl* ownership is not consistent. // here a new pctl is created (could be deleted), but in ViewSliders the ProcControl is taken from the // MixDevice, which in turn uses it from the GUIProfile. // Summarizing: ProfControl* is either owned by the GUIProfile or created new (ownership unclear). // Hint: dummyMatchAll and matchAllPlaybackAndTheCswitch leak together with pctl ProfControl *pctl = new ProfControl( dummyMatchAll, matchAllPlaybackAndTheCswitch); */ if ( !md->isApplicationStream() ) { separatorBetweenMastersAndStreamsRequired = true; } if ( !separatorBetweenMastersAndStreamsInserted && separatorBetweenMastersAndStreamsRequired && md->isApplicationStream() ) { // First application stream => add separator separatorBetweenMastersAndStreamsInserted = true; int sliderColumn = vertical ? _layoutMDW->columnCount() : _layoutMDW->rowCount(); int row = vertical ? 0 : sliderColumn; int col = vertical ? sliderColumn : 0; seperatorBetweenMastersAndStreams = new QFrame(this); if (vertical) seperatorBetweenMastersAndStreams->setFrameStyle(QFrame::VLine); else seperatorBetweenMastersAndStreams->setFrameStyle(QFrame::HLine); _layoutMDW->addWidget( seperatorBetweenMastersAndStreams, row, col ); //_layoutMDW->addItem( new QSpacerItem( 5, 5 ), row, col ); } static ProfControl *MatchAllForSoundMenu = nullptr; if (MatchAllForSoundMenu==nullptr) { // Lazy init of static member on first use MatchAllForSoundMenu = new ProfControl("*", "pvolume,cvolume,pswitch,cswitch"); } MixDeviceWidget *mdw = new MDWSlider(md, MixDeviceWidget::ShowMute|MixDeviceWidget::ShowCapture|MixDeviceWidget::ShowMixerName, this, MatchAllForSoundMenu); mdw->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); int sliderColumn = vertical ? _layoutMDW->columnCount() : _layoutMDW->rowCount(); //if (sliderColumn == 1 ) sliderColumn =0; int row = vertical ? 0 : sliderColumn; int col = vertical ? sliderColumn : 0; _layoutMDW->addWidget( mdw, row, col ); //qCDebug(KMIX_LOG) << "ADDED " << md->id() << " at column " << sliderColumn; return mdw; } void ViewDockAreaPopup::constructionFinished() { // qCDebug(KMIX_LOG) << "ViewDockAreaPopup::constructionFinished()\n"; mainWindowButton = new QPushButton(QIcon::fromTheme("show-mixer"), "" , this); mainWindowButton->setObjectName(QLatin1String("MixerPanel")); mainWindowButton->setToolTip(i18n("Show the full mixer window")); connect(mainWindowButton, SIGNAL(clicked()), SLOT(showPanelSlot())); configureViewButton = createConfigureViewButton(); optionsLayout = new QHBoxLayout(); optionsLayout->addWidget(mainWindowButton); optionsLayout->addStretch(1); optionsLayout->addWidget(configureViewButton); #ifdef RESTORE_VOLUME_BUTTON restoreVolumeButton1 = createRestoreVolumeButton(1); optionsLayout->addWidget( restoreVolumeButton1 ); // TODO enable only if user has saved a volume profile // optionsLayout->addWidget( createRestoreVolumeButton(2) ); // optionsLayout->addWidget( createRestoreVolumeButton(3) ); // optionsLayout->addWidget( createRestoreVolumeButton(4) ); #endif int sliderRow = _layoutMDW->rowCount(); _layoutMDW->addLayout(optionsLayout, sliderRow, 0, 1, _layoutMDW->columnCount()); updateGuiOptions(); _layoutMDW->update(); _layoutMDW->activate(); //bool fnc = focusNextChild(); //qCWarning(KMIX_LOG) << "fnc=" <setToolTip(i18n("Load volume profile %1", storageSlot)); profileButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); return profileButton; } void ViewDockAreaPopup::refreshVolumeLevels() { const int num = mixDeviceCount(); for (int i = 0; i(mixDeviceAt(i)); if (mdw!=nullptr) mdw->update(); } } void ViewDockAreaPopup::configureView() { // Q_ASSERT( !pulseaudioPresent() ); // QSet currentlyActiveMixersInDockArea; // foreach ( Mixer* mixer, _mixers ) // { // currentlyActiveMixersInDockArea.insert(mixer->id()); // } KMixPrefDlg* prefDlg = KMixPrefDlg::getInstance(); //prefDlg->setActiveMixersInDock(currentlyActiveMixersInDockArea); prefDlg->switchToPage(KMixPrefDlg::PrefSoundMenu); } /** * This gets activated whne a user clicks the "Mixer" PushButton in this popup. */ void ViewDockAreaPopup::showPanelSlot() { _kmixMainWindow->setVisible(true); KWindowSystem::setOnDesktop(_kmixMainWindow->winId(), KWindowSystem::currentDesktop()); KWindowSystem::activateWindow(_kmixMainWindow->winId()); // This is only needed when the window is already visible. static_cast(parent())->hide(); } Qt::Orientation ViewDockAreaPopup::orientationSetting() const { return (GlobalConfig::instance().data.getTraypopupOrientation()); }