diff --git a/core/mixer.cpp b/core/mixer.cpp index f118b80d..45777e56 100644 --- a/core/mixer.cpp +++ b/core/mixer.cpp @@ -1,719 +1,720 @@ /* * 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() { return _mixerBackend->m_mixDevices; } /** * Returns the driver name, that handles this Mixer. */ QString Mixer::getDriverName() { 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) +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() { 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; } */ -QString& Mixer::id() +const QString &Mixer::id() const { return _id; } -QString& Mixer::udi(){ +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() +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) +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; } diff --git a/core/mixer.h b/core/mixer.h index 3237d170..91481495 100644 --- a/core/mixer.h +++ b/core/mixer.h @@ -1,225 +1,225 @@ //-*-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(); - shared_ptr find(const QString& devPK); + 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(); /// 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); + 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 ":::" */ - QString& id(); + 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 - QString& udi(); + 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(); + shared_ptr getLocalMasterMD() const; void setLocalMasterMD(QString&); /// get the actual MixSet MixSet& getMixSet(); /// 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 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/dialogaddview.cpp b/gui/dialogaddview.cpp index e347043b..0fbab838 100644 --- a/gui/dialogaddview.cpp +++ b/gui/dialogaddview.cpp @@ -1,225 +1,227 @@ /* * 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/dialogaddview.h" #include #include #include #include #include #include #include "core/mixdevice.h" #include "core/mixer.h" static QStringList viewNames; static QStringList viewIds; DialogAddView::DialogAddView(QWidget *parent, Mixer *mixer) : DialogBase( parent ) { // TODO 000 Adding View for MPRIS2 is broken. We need at least a dummy XML GUI Profile. Also the // fixed list below is plain wrong. Actually we should get the Profile list from either the XML files or // from the backend. The latter is probably easier for now. if ( viewNames.isEmpty() ) { // initialize static list. Later this list could be generated from the actually installed profiles viewNames.append(i18n("All controls")); viewNames.append(i18n("Only playback controls")); viewNames.append(i18n("Only capture controls")); viewIds.append("default"); viewIds.append("playback"); viewIds.append("capture"); } setWindowTitle( i18n( "Add View" ) ); if ( Mixer::mixers().count() > 0 ) setButtons( QDialogButtonBox::Ok|QDialogButtonBox::Cancel ); else { setButtons( QDialogButtonBox::Cancel ); } m_listForChannelSelector = nullptr; createWidgets(mixer); // Open with Mixer Hardware #0 } /** * Create basic widgets of the Dialog. */ void DialogAddView::createWidgets(Mixer *ptr_mixer) { QWidget *mainFrame = new QWidget(this); setMainWidget(mainFrame); QVBoxLayout *layout = new QVBoxLayout(mainFrame); if ( Mixer::mixers().count() > 1 ) { // More than one Mixer => show Combo-Box to select Mixer // Mixer widget line QHBoxLayout* mixerNameLayout = new QHBoxLayout(); layout->addLayout(mixerNameLayout); mixerNameLayout->setSpacing(DialogBase::horizontalSpacing()); QLabel *qlbl = new QLabel( i18n("Select mixer:"), mainFrame ); mixerNameLayout->addWidget(qlbl); qlbl->setFixedHeight(qlbl->sizeHint().height()); m_cMixer = new QComboBox(mainFrame); m_cMixer->setObjectName( QLatin1String( "mixerCombo" ) ); m_cMixer->setFixedHeight(m_cMixer->sizeHint().height()); connect( m_cMixer, SIGNAL(activated(int)), this, SLOT(createPageByID(int)) ); for( int i =0; iaddItem( mixer->readableName() ); + const Mixer *mixer = (Mixer::mixers())[i]; + const shared_ptr md = mixer->getLocalMasterMD(); + const QString iconName = (md!=nullptr) ? md->iconName() : "media-playback-start"; + m_cMixer->addItem(QIcon::fromTheme(iconName), mixer->readableName() ); } // end for all_Mixers // Make the current Mixer the current item in the ComboBox int findIndex = m_cMixer->findText( ptr_mixer->readableName() ); if ( findIndex != -1 ) m_cMixer->setCurrentIndex( findIndex ); m_cMixer->setToolTip( i18n("Current mixer" ) ); mixerNameLayout->addWidget(m_cMixer); } // end if (more_than_1_Mixer) if ( Mixer::mixers().count() > 0 ) { QLabel *qlbl = new QLabel( i18n("Select the design for the new view:"), mainFrame ); layout->addWidget(qlbl); createPage(Mixer::mixers()[0]); connect( this, SIGNAL(accepted()) , this, SLOT(apply()) ); } else { QLabel *qlbl = new QLabel( i18n("No sound card is installed or currently plugged in."), mainFrame ); layout->addWidget(qlbl); } } /** * Create RadioButton's for the Mixer with number 'mixerId'. * @par mixerId The Mixer, for which the RadioButton's should be created. */ void DialogAddView::createPageByID(int mixerId) { //qCDebug(KMIX_LOG) << "DialogAddView::createPage()"; QString selectedMixerName = m_cMixer->itemText(mixerId); for( int i =0; ireadableName() == selectedMixerName ) { createPage(mixer); break; } } // for } /** * Create RadioButton's for the Mixer with number 'mixerId'. * @par mixerId The Mixer, for which the RadioButton's should be created. */ void DialogAddView::createPage(Mixer *mixer) { /** --- Reset page ----------------------------------------------- * In case the user selected a new Mixer via m_cMixer, we need * to remove the stuff created on the last call. */ delete m_listForChannelSelector; setButtonEnabled(QDialogButtonBox::Ok, false); /** Reset page end -------------------------------------------------- */ QWidget *mainFrame = mainWidget(); QVBoxLayout *layout = qobject_cast(mainFrame->layout()); Q_ASSERT(layout!=nullptr); m_listForChannelSelector = new QListWidget(mainFrame); m_listForChannelSelector->setUniformItemSizes(true); m_listForChannelSelector->setSelectionMode(QAbstractItemView::SingleSelection); connect(m_listForChannelSelector, SIGNAL(itemSelectionChanged()), this, SLOT(profileSelectionChanged())); layout->addWidget(m_listForChannelSelector); for (int i = 0; iisDynamic()) { // TODO: The mixer's backend MUST be inspected to find out the supported profiles. // Hardcoding it here is only a quick workaround. continue; } // Create an item for each view type QString name = viewNames.at(i); QListWidgetItem *item = new QListWidgetItem(m_listForChannelSelector); item->setText(name); item->setData(Qt::UserRole, viewIds.at(i)); // mixer ID as data } // If there is only one option available to select, then preselect it. if (m_listForChannelSelector->count()==1) m_listForChannelSelector->setCurrentRow(0); } void DialogAddView::profileSelectionChanged() { QList items = m_listForChannelSelector->selectedItems(); setButtonEnabled(QDialogButtonBox::Ok, !items.isEmpty()); } void DialogAddView::apply() { Mixer *mixer = nullptr; if ( Mixer::mixers().count() == 1 ) { // only one mixer => no combo box => take first entry mixer = (Mixer::mixers())[0]; } else if ( Mixer::mixers().count() > 1 ) { // find mixer that is currently active in the ComboBox QString selectedMixerName = m_cMixer->itemText(m_cMixer->currentIndex()); for( int i =0; ireadableName() == selectedMixerName ) { mixer = (Mixer::mixers())[i]; break; } } // for } Q_ASSERT(mixer!=nullptr); QList items = m_listForChannelSelector->selectedItems(); if (items.isEmpty()) return; // nothing selected QListWidgetItem *item = items.first(); QString viewName = item->data(Qt::UserRole).toString(); qCDebug(KMIX_LOG) << "We should now create a new view " << viewName << " for mixer " << mixer->id(); resultMixerId = mixer->id(); resultViewName = viewName; } diff --git a/gui/dialogselectmaster.cpp b/gui/dialogselectmaster.cpp index 57f7877c..5c7db5ca 100644 --- a/gui/dialogselectmaster.cpp +++ b/gui/dialogselectmaster.cpp @@ -1,229 +1,232 @@ /* * 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/dialogselectmaster.h" #include #include #include #include #include #include #include "core/ControlManager.h" #include "core/mixdevice.h" #include "core/mixer.h" DialogSelectMaster::DialogSelectMaster( Mixer *mixer, QWidget *parent ) : DialogBase( parent ) { setWindowTitle(i18n("Select Master Channel")); if ( Mixer::mixers().count() > 0 ) setButtons(QDialogButtonBox::Ok|QDialogButtonBox::Cancel); else { setButtons(QDialogButtonBox::Cancel); } m_channelSelector = 0; createWidgets(mixer); // Open with Mixer Hardware #0 } /** * Create basic widgets of the Dialog. */ void DialogSelectMaster::createWidgets(Mixer *ptr_mixer) { QWidget *mainFrame = new QWidget(this); setMainWidget(mainFrame); QVBoxLayout *layout = new QVBoxLayout(mainFrame); if ( Mixer::mixers().count() > 1 ) { // More than one Mixer => show Combo-Box to select Mixer // Mixer widget line QHBoxLayout* mixerNameLayout = new QHBoxLayout(); layout->addLayout( mixerNameLayout ); mixerNameLayout->setMargin(0); mixerNameLayout->setSpacing(DialogBase::horizontalSpacing()); QLabel *qlbl = new QLabel( i18n("Current mixer:"), mainFrame ); mixerNameLayout->addWidget(qlbl); qlbl->setFixedHeight(qlbl->sizeHint().height()); m_cMixer = new QComboBox(mainFrame); m_cMixer->setObjectName( QLatin1String( "mixerCombo" ) ); m_cMixer->setFixedHeight(m_cMixer->sizeHint().height()); connect( m_cMixer, SIGNAL(activated(int)), this, SLOT(createPageByID(int)) ); for( int i =0; iaddItem( mixer->readableName(), mixer->id() ); + const Mixer *mixer = (Mixer::mixers())[i]; + const shared_ptr md = mixer->getLocalMasterMD(); + const QString iconName = (md!=nullptr) ? md->iconName() : "media-playback-start"; + m_cMixer->addItem(QIcon::fromTheme(iconName), mixer->readableName(), mixer->id() ); } // end for all_Mixers // Make the current Mixer the current item in the ComboBox int findIndex = m_cMixer->findData( ptr_mixer->id() ); if ( findIndex != -1 ) m_cMixer->setCurrentIndex( findIndex ); - m_cMixer->setToolTip( i18n("Current mixer" ) ); mixerNameLayout->addWidget(m_cMixer, 1); layout->addSpacing(DialogBase::verticalSpacing()); } // end if (more_than_1_Mixer) if ( Mixer::mixers().count() > 0 ) { QLabel *qlbl = new QLabel( i18n("Select the channel representing the master volume:"), mainFrame ); layout->addWidget(qlbl); createPage(ptr_mixer); connect(this, SIGNAL(accepted()), this, SLOT(apply())); } else { QLabel *qlbl = new QLabel( i18n("No sound card is installed or currently plugged in."), mainFrame ); layout->addWidget(qlbl); } } /** * Create RadioButton's for the Mixer with number 'mixerId'. * @par mixerId The Mixer, for which the RadioButton's should be created. */ void DialogSelectMaster::createPageByID(int mixerId) { QString mixer_id = m_cMixer->itemData(mixerId).toString(); Mixer * mixer = Mixer::findMixer(mixer_id); if ( mixer != NULL ) createPage(mixer); } /** * Create RadioButton's for the Mixer with number 'mixerId'. * @par mixerId The Mixer, for which the RadioButton's should be created. */ void DialogSelectMaster::createPage(Mixer* mixer) { /** --- Reset page ----------------------------------------------- * In case the user selected a new Mixer via m_cMixer, we need * to remove the stuff created on the last call. */ // delete the list widget. // This should automatically remove all contained items. delete m_channelSelector; /** Reset page end -------------------------------------------------- */ QWidget *mainFrame = mainWidget(); QVBoxLayout *layout = qobject_cast(mainFrame->layout()); Q_ASSERT(layout!=nullptr); m_channelSelector = new QListWidget(mainFrame); #ifndef QT_NO_ACCESSIBILITY m_channelSelector->setAccessibleName( i18n("Select Master Channel") ); #endif m_channelSelector->setSelectionMode(QAbstractItemView::SingleSelection); m_channelSelector->setDragEnabled(false); m_channelSelector->setAlternatingRowColors(true); layout->addWidget(m_channelSelector); // shared_ptr master = mixer->getLocalMasterMD(); // QString masterKey = ( master.get() != 0 ) ? master->id() : "----noMaster---"; // Use non-matching name as default const MixSet& mixset = mixer->getMixSet(); MixSet& mset = const_cast(mixset); MasterControl mc = mixer->getGlobalMasterPreferred(false); QString masterKey = mc.getControl(); if (!masterKey.isEmpty() && !mset.get(masterKey)) { shared_ptr master = mixer->getLocalMasterMD(); if (master.get() != 0) masterKey = master->id(); } int msetCount = 0; for (int i = 0; i < mset.count(); ++i) { shared_ptr md = mset[i]; if ( md->playbackVolume().hasVolume() ) ++msetCount; } if (msetCount > 0 && !mixer->isDynamic()) { QString mdName = i18n("Automatic (%1 recommendation)", mixer->getDriverName()); QPixmap icon = KIconLoader::global()->loadIcon("audio-volume-high", KIconLoader::Small, IconSize(KIconLoader::Small)); QListWidgetItem *item = new QListWidgetItem(icon, mdName, m_channelSelector); item->setData(Qt::UserRole, QString()); // ID here: see apply(), empty String => Automatic if (masterKey.isEmpty()) m_channelSelector->setCurrentItem(item); } // Populate ListView with the MixDevice's having a playbakc volume (excludes pure capture controls and pure enum's) for (int i = 0; i < mset.count(); ++i) { shared_ptr md = mset[i]; if ( md->playbackVolume().hasVolume() ) { QString mdName = md->readableName(); QPixmap icon = KIconLoader::global()->loadIcon(md->iconName(), KIconLoader::Small, IconSize(KIconLoader::Small)); QListWidgetItem *item = new QListWidgetItem(icon, mdName, m_channelSelector); item->setData(Qt::UserRole, md->id()); // ID here: see apply() if ( md->id() == masterKey ) { // select the current master m_channelSelector->setCurrentItem(item); } } } + + setButtonEnabled(QDialogButtonBox::Ok, m_channelSelector->count()>0); } void DialogSelectMaster::apply() { Mixer *mixer = 0; if ( Mixer::mixers().count() == 1 ) { // only one mxier => no combo box => take first entry mixer = (Mixer::mixers())[0]; } else if ( Mixer::mixers().count() > 1 ) { // find mixer that is currently active in the ComboBox int idx = m_cMixer->currentIndex(); QString mixer_id = m_cMixer->itemData(idx).toString(); mixer = Mixer::findMixer(mixer_id); } if ( mixer == 0 ) return; // User must have unplugged everything QList items = m_channelSelector->selectedItems(); if (items.count()==1) { QListWidgetItem *item = items.first(); QString control_id = item->data(Qt::UserRole).toString(); mixer->setLocalMasterMD( control_id ); Mixer::setGlobalMaster(mixer->id(), control_id, true); ControlManager::instance().announce(mixer->id(), ControlManager::MasterChanged, QString("Select Master Dialog")); } }