diff --git a/core/mixer.cpp b/core/mixer.cpp index 45777e56..38698c08 100644 --- a/core/mixer.cpp +++ b/core/mixer.cpp @@ -1,720 +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 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() +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; } diff --git a/core/mixer.h b/core/mixer.h index 91481495..ef5cbe0a 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(); + 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(); + 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(); /// 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/guiprofile.cpp b/gui/guiprofile.cpp index 22d356f6..7e12c0e8 100644 --- a/gui/guiprofile.cpp +++ b/gui/guiprofile.cpp @@ -1,878 +1,876 @@ /* * KMix -- KDE's full featured mini mixer * * Copyright 2006-2007 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/guiprofile.h" // Qt -#include -#include -#include +#include +#include +#include #include -// System -#include -#include - // KMix #include "core/mixer.h" + QMap s_profiles; static QString visibilityToString(GuiVisibility vis) { switch (vis) { case GuiVisibility::Simple: return ("simple"); case GuiVisibility::Extended: return ("extended"); // For backwards compatibility, 'Full' has the ID "all" and not "full" case GuiVisibility::Full: return ("all"); case GuiVisibility::Custom: return ("custom"); case GuiVisibility::Never: return ("never"); case GuiVisibility::Default: return ("default"); default: return ("unknown"); } } static GuiVisibility visibilityFromString(const QString &str) { if (str=="simple") return (GuiVisibility::Simple); else if (str=="extended") return (GuiVisibility::Extended); else if (str=="all") return (GuiVisibility::Full); else if (str=="custom") return (GuiVisibility::Custom); else if (str=="never") return (GuiVisibility::Never); qCWarning(KMIX_LOG) << "Unknown string value" << str; return (GuiVisibility::Full); } /** * Product comparator for sorting: * We want the comparator to sort ascending by Vendor. "Inside" the Vendors, we sort by Product Name. */ bool ProductComparator::operator()(const ProfProduct* p1, const ProfProduct* p2) const { if ( p1->vendor < p2->vendor ) { return ( true ); } else if ( p1->vendor > p2->vendor ) { return ( false ); } else if ( p1->productName < p2->productName ) { return ( true ); } else if ( p1->productName > p2->productName ) { return ( false ); } else { /** * We reach this point, if vendor and product name is identical. * Actually we don't care about the order then, so we decide that "p1" comes first. * * (Hint: As this is a set comparator, the return value HERE doesn't matter that * much. But if we would decide later to change this Comparator to be a Map Comparator, * we must NOT return a "0" for identity - this would lead to non-insertion on insert()) */ return true; } } GUIProfile::GUIProfile() { _dirty = false; _driverVersionMin = 0; _driverVersionMax = 0; _generation = 1; } GUIProfile::~GUIProfile() { qCWarning(KMIX_LOG) << "Thou shalt not delete any GUI profile. This message is only OK, when quitting KMix"; qDeleteAll(_controls); qDeleteAll(_products); } /** * Clears the GUIProfile cache. You must only call this * before termination of the application, as GUIProfile instances are used in other classes, especially the views. * There is no need to call this in non-GUI applications like kmixd and kmixctrl. */ void GUIProfile::clearCache() { qDeleteAll(s_profiles); s_profiles.clear(); } /** * Build a profile name. Suitable to use as primary key and to build filenames. * @arg mixer The mixer * @arg profileName The profile name (e.g. "capture", "playback", "my-cool-profile", or "any" * @return The profile name */ -static QString buildProfileName(Mixer *mixer, const QString &profileName, bool ignoreCard) +static QString buildProfileName(const Mixer *mixer, const QString &profileName, bool ignoreCard) { QString fname; fname += mixer->getDriverName(); if (!ignoreCard) { fname += ".%1.%2"; fname = fname.arg(mixer->getBaseName()).arg(mixer->getCardInstance()); } fname += '.' + profileName; fname.replace(' ','_'); return fname; } /** * Generate a readable profile name (for presenting to the user). * Hint: Currently used as Tab label. */ -static QString buildReadableProfileName(Mixer *mixer, const QString &profileName) +static QString buildReadableProfileName(const Mixer *mixer, const QString &profileName) { QString fname; fname += mixer->getBaseName(); if ( mixer->getCardInstance() > 1 ) { fname += " %1"; fname = fname.arg(mixer->getCardInstance()); } if ( profileName != "default" ) { fname += ' ' + profileName; } qCDebug(KMIX_LOG) << fname; return fname; } /** * Returns the GUIProfile for the given ID (= "fullyQualifiedName"). * If not found 0 is returned. There is no try to load it. * * @returns The loaded GUIProfile for the given ID */ GUIProfile *GUIProfile::find(const QString &id) { // Return found value or default-constructed one (nullptr). // Does not insert into map. Now thread-safe. return (s_profiles.value(id)); } static QString createNormalizedFilename(const QString &profileId) { QString profileIdNormalized(profileId); profileIdNormalized.replace(':', '.'); QString fileName("profiles/"); fileName = fileName + profileIdNormalized + ".xml"; return fileName; } /** * Loads a GUI Profile from disk (XML profile file). * It tries to load the Soundcard specific file first (a). * If it doesn't exist, it will load the default profile corresponding to the soundcard driver (b). */ -static GUIProfile *loadProfileFromXMLfiles(Mixer *mixer, const QString &profileName) +static GUIProfile *loadProfileFromXMLfiles(const Mixer *mixer, const QString &profileName) { - GUIProfile* guiprof = 0; + GUIProfile* guiprof = nullptr; QString fileName = createNormalizedFilename(profileName); QString fileNameFQ = QStandardPaths::locate(QStandardPaths::DataLocation, fileName ); if ( ! fileNameFQ.isEmpty() ) { guiprof = new GUIProfile(); if ( guiprof->readProfile(fileNameFQ) && ( guiprof->match(mixer) > 0) ) { // loaded } else { delete guiprof; // not good (e.g. Parsing error => drop this profile silently) - guiprof = 0; + guiprof = nullptr; } } else { qCDebug(KMIX_LOG) << "Ignore file " <getId()] = guiprof; qCDebug(KMIX_LOG) << "I have added" << guiprof->getId() << "; Number of profiles is now " << s_profiles.size() ; } /** * Finds the correct profile for the given mixer. * If already loaded from disk, returns the cached version. * Otherwise load profile from disk: Priority: Card specific profile, Card unspecific profile * * @arg mixer The mixer * @arg profileName The profile name (e.g. "ALSA.X-Fi.default", or "OSS.intel-cha51.playback") * A special case is "", which means that a card specific name should be generated. * @arg profileNameIsFullyQualified If true, an exact match will be searched. Otherwise it is a simple name like "playback" or "capture" * @arg ignoreCardName If profileName not fully qualified, this is used in building the requestedProfileName * @return GUIProfile* The loaded GUIProfile, or 0 if no profile matched. Hint: if you use allowFallback==true, this should never return 0. */ -GUIProfile *GUIProfile::find(Mixer *mixer, const QString &profileName, bool profileNameIsFullyQualified, bool ignoreCardName) +GUIProfile *GUIProfile::find(const Mixer *mixer, const QString &profileName, bool profileNameIsFullyQualified, bool ignoreCardName) { GUIProfile *guiprof = nullptr; if (mixer==nullptr || profileName.isEmpty()) return (nullptr); // if ( mixer->isDynamic() ) { // qCDebug(KMIX_LOG) << "GUIProfile::find() Not loading GUIProfile for Dynamic Mixer (e.g. PulseAudio)"; // return 0; // } QString requestedProfileName; QString fullQualifiedProfileName; if ( profileNameIsFullyQualified ) { requestedProfileName = profileName; fullQualifiedProfileName = profileName; } else { requestedProfileName = buildProfileName(mixer, profileName, ignoreCardName); fullQualifiedProfileName = buildProfileName(mixer, profileName, false); } if ( s_profiles.contains(fullQualifiedProfileName) ) { guiprof = s_profiles.value(fullQualifiedProfileName); // Cached } else { guiprof = loadProfileFromXMLfiles(mixer, requestedProfileName); // Load from XML ###Card specific profile### if ( guiprof!=nullptr) { guiprof->_mixerId = mixer->id(); guiprof->setId(fullQualifiedProfileName); // this one contains some soundcard id (basename + instance) if ( guiprof->getName().isEmpty() ) { // If the profile didn't contain a name then lets define one guiprof->setName(buildReadableProfileName(mixer,profileName)); // The caller can rename this if he likes guiprof->setDirty(); } if ( requestedProfileName != fullQualifiedProfileName) { // This is very important! // When the final profileName (fullQualifiedProfileName) is different from // what we have loaded (requestedProfileName, e.g. "default"), we MUST // set the profile dirty, so it gets saved. Otherwise we would write the // fullQualifiedProfileName in the kmixrc, and will not find it on the next // start of KMix. guiprof->setDirty(); } addProfile(guiprof); } } return (guiprof); } /** * Returns a fallback GUIProfile. You can call this if the backends ships no profile files. * The returned GUIProfile is also added to the static Map of all GUIProfile instances. */ -GUIProfile* GUIProfile::fallbackProfile(Mixer *mixer) +GUIProfile* GUIProfile::fallbackProfile(const Mixer *mixer) { // -1- Get name QString fullQualifiedProfileName = buildProfileName(mixer, QString("default"), false); GUIProfile *fallback = new GUIProfile(); // -2- Fill details ProfProduct* prd = new ProfProduct(); prd->vendor = mixer->getDriverName(); prd->productName = mixer->readableName(); prd->productRelease = "1.0"; fallback->_products.insert(prd); static QString matchAll(".*"); static QString matchAllSctl(".*"); ProfControl* ctl = new ProfControl(matchAll, matchAllSctl); //ctl->regexp = matchAll; // make sure id matches the regexp ctl->setMandatory(true); fallback->_controls.push_back(ctl); fallback->_soundcardDriver = mixer->getDriverName(); fallback->_soundcardName = mixer->readableName(); fallback->finalizeProfile(); fallback->_mixerId = mixer->id(); fallback->setId(fullQualifiedProfileName); // this one contains some soundcard id (basename + instance) fallback->setName(buildReadableProfileName(mixer, QString("default"))); // The caller can rename this if he likes fallback->setDirty(); /* -3- Add the profile to the static list * Hint: This looks like a memory leak, as we never remove profiles from memory while KMix runs. * Especially with application streams it looks suspicious. But please be aware that this method is only * called for soundcard hotplugs, and not on stream hotplugs. At least it is supposed to be like that. * * Please also see the docs at addProfile(), they also address the possible memory leakage. */ addProfile(fallback); return fallback; } /** * Fill the profile with the data from the given XML profile file. * @par ref_fileName: Full qualified filename (with path). * @return bool True, if the profile was successfully created. False if not (e.g. parsing error). */ -bool GUIProfile::readProfile(const QString& ref_fileName) +bool GUIProfile::readProfile(const QString &ref_fileName) { - QXmlSimpleReader *xmlReader = new QXmlSimpleReader(); - qCDebug(KMIX_LOG) << "Read profile:" << ref_fileName ; - QFile xmlFile( ref_fileName ); - QXmlInputSource source( &xmlFile ); - GUIProfileParser* gpp = new GUIProfileParser(this); - xmlReader->setContentHandler(gpp); - bool ok = xmlReader->parse( source ); + QXmlSimpleReader xmlReader; + qCDebug(KMIX_LOG) << "Read profile" << ref_fileName; + QFile xmlFile(ref_fileName); + QXmlInputSource source(&xmlFile); + GUIProfileParser gpp(this); + xmlReader.setContentHandler(&gpp); + bool ok = xmlReader.parse(source); //std::cout << "Raw Profile: " << *this; if ( ok ) { ok = finalizeProfile(); } // Read OK else { // !! this error message about faulty profiles should probably be surrounded with i18n() - qCCritical(KMIX_LOG) << "ERROR: The profile '" << ref_fileName<< "' contains errors, and is not used."; + qCCritical(KMIX_LOG) << "ERROR: The profile" << ref_fileName<< "contains errors, and cannot be used"; } - // TODO: can these 2 be stack variables? - delete gpp; - delete xmlReader; - return ok; } bool GUIProfile::writeProfile() { - bool ret = false; QString profileId = getId(); QString fileName = createNormalizedFilename(profileId); QString fileNameFQ = QStandardPaths::writableLocation(QStandardPaths::DataLocation) + '/' + fileName; - qCDebug(KMIX_LOG) << "Write profile:" << fileNameFQ ; - QFile f(fileNameFQ); - if ( f.open(QIODevice::WriteOnly | QFile::Truncate) ) - { - QTextStream out(&f); - out << *this; - f.close(); - ret = true; + qCDebug(KMIX_LOG) << "Write profile" << fileNameFQ; + QSaveFile f(fileNameFQ); + if (!f.open(QIODevice::WriteOnly|QFile::Truncate)) + { + qCWarning(KMIX_LOG) << "Cannot save profile to" << fileNameFQ; + return (false); } - if ( ret ) { - _dirty = false; + QXmlStreamWriter writer(&f); + writer.setAutoFormatting(true); + + // + writer.writeStartDocument(); + + // + writer.writeEndElement(); + + foreach (const ProfProduct *prd, qAsConst(_products)) + { + // vendor + writer.writeAttribute("vendor", prd->vendor); + // name= prd->productName + writer.writeAttribute("name", prd->productName); + // release= prd->productRelease + if (!prd->productRelease.isNull()) writer.writeAttribute("release", prd->productRelease); + // comment= prd->comment + if (!prd->comment.isNull()) writer.writeAttribute("comment", prd->comment); + // /> + writer.writeEndElement(); + } // for all products + + foreach (const ProfControl *profControl, qAsConst(getControls())) + { + // id() + writer.writeAttribute("id", profControl->id()); + // name= profControl->name() + const QString name = profControl->name(); + if (!name.isNull() && name!=profControl->id()) writer.writeAttribute("name", name); + // subcontrols= profControl->renderSubcontrols() + writer.writeAttribute("subcontrols", profControl->renderSubcontrols()); + // show= visibilityToString(profControl->getVisibility()) + writer.writeAttribute("show", visibilityToString(profControl->getVisibility())); + // mandatory= "true" + if (profControl->isMandatory()) writer.writeAttribute("mandatory", "true"); + // split= "true" + if (profControl->isSplit()) writer.writeAttribute("split", "true"); + // /> + writer.writeEndElement(); + } // for all controls + + // + writer.writeEndElement(); + + writer.writeEndDocument(); + if (writer.hasError()) + { + qCWarning(KMIX_LOG) << "XML writing failed to" << fileNameFQ; + f.cancelWriting(); + return (false); } - return ret; + + f.commit(); + _dirty = false; + return (true); } + /** This is now empty. It can be removed */ bool GUIProfile::finalizeProfile() const { return (true); } // ------------------------------------------------------------------------------------- void GUIProfile::setControls(ControlSet& newControlSet) { qDeleteAll(_controls); _controls = newControlSet; } // ------------------------------------------------------------------------------------- /** * Returns how good the given Mixer matches this GUIProfile. * A value between 0 (not matching at all) and MAXLONG (perfect match) is returned. * * Here is the current algorithm: * * If the driver doesn't match, 0 is returned. (OK) * If the card-name ... (OK) * is "*", this is worth 1 point * doesn't match, 0 is returned. * matches, this is worth 500 points. * * If the "card type" ... * is empty, this is worth 0 points. !!! not implemented yet * doesn't match, 0 is returned. !!! not implemented yet * matches , this is worth 500 points. !!! not implemented yet * * If the "driver version" doesn't match, 0 is returned. !!! not implemented yet * If the "driver version" matches, this is worth ... * 4000 unlimited <=> "*:*" * 6000 toLower-bound-limited <=> "toLower-bound:*" * 6000 upper-bound-limited <=> "*:upper-bound" * 8000 upper- and toLower-bound limited <=> "toLower-bound:upper-bound" * or 10000 points (upper-bound=toLower-bound=bound <=> "bound:bound" * * The Profile-Generation is added to the already achieved points. (done) * The maximum gain is 900 points. * Thus you can create up to 900 generations (0-899) without "overriding" * the points gained from the "driver version" or "card-type". * * For example: card-name="*" (1), card-type matches (1000), * driver version "*:*" (4000), Profile-Generation 4 (4). * Sum: 1 + 1000 + 4000 + 4 = 5004 * * @todo Implement "card type" match value * @todo Implement "version" match value (must be in backends as well) */ -unsigned long GUIProfile::match(Mixer* mixer) const +unsigned long GUIProfile::match(const Mixer *mixer) const { unsigned long matchValue = 0; if ( _soundcardDriver != mixer->getDriverName() ) { return 0; } if ( _soundcardName == "*" ) { matchValue += 1; } else if ( _soundcardName != mixer->getBaseName() ) { return 0; // card name does not match } else { matchValue += 500; // card name matches } // !!! we don't check current for the driver version. // So we assign simply 4000 points for now. matchValue += 4000; if ( _generation < 900 ) { matchValue += _generation; } else { matchValue += 900; } return matchValue; } -static QByteArray xmlify(const QString &str) -{ - QByteArray raw = str.toUtf8(); -// qCDebug(KMIX_LOG) << "Before: " << raw; - raw.replace('&', "&"); - raw.replace('<', "<"); - raw.replace('>', ">"); - raw.replace('\'', "'"); - raw.replace('\"', """); -// qCDebug(KMIX_LOG) << "After : " << raw; - return (raw); -} - - -// TODO: use QXmlStreamWriter -QTextStream& operator<<(QTextStream &os, const GUIProfile& guiprof) -{ -// qCDebug(KMIX_LOG) << "ENTER QTextStream& operator<<"; - os << ""; - os << endl << endl; - - os << "" << endl << endl ; - - os << "" << endl; - - for ( GUIProfile::ProductSet::const_iterator it = guiprof._products.begin(); it != guiprof._products.end(); ++it) - { - ProfProduct* prd = *it; - os << "vendor) << "\" name=\"" << xmlify(prd->productName) << "\""; - if ( ! prd->productRelease.isNull() ) { - os << " release=\"" << xmlify(prd->productRelease) << "\""; - } - if ( ! prd->comment.isNull() ) { - os << " comment=\"" << xmlify(prd->comment) << "\""; - } - os << " />" << endl; - } // for all products - os << endl; - - foreach ( ProfControl* profControl, guiprof.getControls() ) - { - os << "id()) << "\"" ; - if ( !profControl->name().isNull() && profControl->name()!=profControl->id()) { - os << " name=\"" << xmlify(profControl->name()) << "\"" ; - } - os << " subcontrols=\"" << xmlify( profControl->renderSubcontrols()) << "\"" ; - os << " show=\"" << xmlify(visibilityToString(profControl->getVisibility())) << "\"" ; - if ( profControl->isMandatory() ) { - os << " mandatory=\"true\""; - } - if ( profControl->isSplit() ) { - os << " split=\"true\""; - } - os << " />" << endl; - } // for all controls - os << endl; - - os << "" << endl; -// qCDebug(KMIX_LOG) << "EXIT QTextStream& operator<<"; - return os; -} - - ProfControl::ProfControl(const QString &id, const QString &subcontrols) : _id(id), _visibility(GuiVisibility::Simple), _mandatory(false), _split(false) { setSubcontrols(subcontrols); } ProfControl::ProfControl(const ProfControl &profControl) : _mandatory(false), _split(false) { _id = profControl._id; _name = profControl._name; _visibility = profControl._visibility; _useSubcontrolPlayback = profControl._useSubcontrolPlayback; _useSubcontrolCapture = profControl._useSubcontrolCapture; _useSubcontrolPlaybackSwitch = profControl._useSubcontrolPlaybackSwitch; _useSubcontrolCaptureSwitch = profControl._useSubcontrolCaptureSwitch; _useSubcontrolEnum = profControl._useSubcontrolEnum; _subcontrols = profControl._subcontrols; _backgroundColor = profControl._backgroundColor; _switchtype = profControl._switchtype; _mandatory = profControl._mandatory; _split = profControl._split; } bool ProfControl::satisfiesVisibility(GuiVisibility vis) const { GuiVisibility me = getVisibility(); if (me==GuiVisibility::Never || vis==GuiVisibility::Never) return (false); if (me==GuiVisibility::Custom || vis==GuiVisibility::Custom) return (false); if (vis==GuiVisibility::Default) return (true); return (static_cast(me)<=static_cast(vis)); } /** * An overridden method that either sets * GuiVisibility::Simple or GuiVisibility::Never. */ void ProfControl::setVisible(bool visible) { setVisibility(visible ? GuiVisibility::Simple : GuiVisibility::Never); } void ProfControl::setVisibility(GuiVisibility vis) { _visibility = vis; } void ProfControl::setVisibility(const QString &visString) { setVisibility(visibilityFromString(visString)); } void ProfControl::setSubcontrols(QString sctls) { _subcontrols = sctls; _useSubcontrolPlayback = false; _useSubcontrolCapture = false; _useSubcontrolPlaybackSwitch = false; _useSubcontrolCaptureSwitch = false; _useSubcontrolEnum = false; QStringList qsl = sctls.split( ',', QString::SkipEmptyParts, Qt::CaseInsensitive); QStringListIterator qslIt(qsl); while (qslIt.hasNext()) { QString sctl = qslIt.next(); //qCDebug(KMIX_LOG) << "setSubcontrols found: " << sctl.toLocal8Bit().constData(); if ( sctl == "pvolume" ) _useSubcontrolPlayback = true; else if ( sctl == "cvolume" ) _useSubcontrolCapture = true; else if ( sctl == "pswitch" ) _useSubcontrolPlaybackSwitch = true; else if ( sctl == "cswitch" ) _useSubcontrolCaptureSwitch = true; else if ( sctl == "enum" ) _useSubcontrolEnum = true; else if ( sctl == "*" || sctl == ".*") { _useSubcontrolCapture = true; _useSubcontrolCaptureSwitch = true; _useSubcontrolPlayback = true; _useSubcontrolPlaybackSwitch = true; _useSubcontrolEnum = true; } else qCWarning(KMIX_LOG) << "Ignoring unknown subcontrol type '" << sctl << "' in profile"; } } -QString ProfControl::renderSubcontrols() +QString ProfControl::renderSubcontrols() const { QString sctlString; if ( _useSubcontrolPlayback && _useSubcontrolPlaybackSwitch && _useSubcontrolCapture && _useSubcontrolCaptureSwitch && _useSubcontrolEnum ) { return QString("*"); } else { if ( _useSubcontrolPlayback ) { sctlString += "pvolume,"; } if ( _useSubcontrolCapture ) { sctlString += "cvolume,"; } if ( _useSubcontrolPlaybackSwitch ) { sctlString += "pswitch,"; } if ( _useSubcontrolCaptureSwitch ) { sctlString += "cswitch,"; } if ( _useSubcontrolEnum ) { sctlString += "enum,"; } if ( sctlString.length() > 0 ) { sctlString.chop(1); } return sctlString; } } // ### PARSER START ################################################ GUIProfileParser::GUIProfileParser(GUIProfile* ref_gp) : _guiProfile(ref_gp) { _scope = GUIProfileParser::NONE; // no scope yet } bool GUIProfileParser::startDocument() { _scope = GUIProfileParser::NONE; // no scope yet return true; } bool GUIProfileParser::startElement( const QString& , const QString& , const QString& qName, const QXmlAttributes& attributes ) { switch ( _scope ) { case GUIProfileParser::NONE: /** we are reading the "top level" ***************************/ if ( qName.toLower() == "soundcard" ) { _scope = GUIProfileParser::SOUNDCARD; addSoundcard(attributes); } else { // skip unknown top-level nodes - std::cerr << "Ignoring unsupported element '" << qName.toUtf8().constData() << "'" << std::endl; + qCWarning(KMIX_LOG) << "Ignoring unsupported element" << qName; } // we are accepting only break; case GUIProfileParser::SOUNDCARD: if ( qName.toLower() == "product" ) { // Defines product names under which the chipset/hardware is sold addProduct(attributes); } else if ( qName.toLower() == "control" ) { addControl(attributes); } else if ( qName.toLower() == "profile" ) { addProfileInfo(attributes); } else { - std::cerr << "Ignoring unsupported element '" << qName.toUtf8().constData() << "'" << std::endl; + qCWarning(KMIX_LOG) << "Ignoring unsupported element" << qName; } // we are accepting , and break; } // switch() return true; } bool GUIProfileParser::endElement( const QString&, const QString&, const QString& qName ) { if ( qName == "soundcard" ) { _scope = GUIProfileParser::NONE; // should work out OK, as we don't nest soundcard entries } return true; } void GUIProfileParser::addSoundcard(const QXmlAttributes& attributes) { /* std::cout << "Soundcard: "; printAttributes(attributes); */ QString driver = attributes.value("driver"); QString version = attributes.value("version"); QString name = attributes.value("name"); QString type = attributes.value("type"); QString generation = attributes.value("generation"); if ( !driver.isNull() && !name.isNull() ) { _guiProfile->_soundcardDriver = driver; _guiProfile->_soundcardName = name; if ( type.isNull() ) { _guiProfile->_soundcardType = ""; } else { _guiProfile->_soundcardType = type; } if ( version.isNull() ) { _guiProfile->_driverVersionMin = 0; _guiProfile->_driverVersionMax = 0; } else { std::pair versionMinMax; splitPair(version, versionMinMax, ':'); _guiProfile->_driverVersionMin = versionMinMax.first.toULong(); _guiProfile->_driverVersionMax = versionMinMax.second.toULong(); } if ( type.isNull() ) { type = ""; }; if ( generation.isNull() ) { _guiProfile->_generation = 0; } else { // Hint: If the conversion fails, _generation will be assigned 0 (which is fine) _guiProfile->_generation = generation.toUInt(); } } } void GUIProfileParser::addProfileInfo(const QXmlAttributes& attributes) { QString name = attributes.value("name"); QString id = attributes.value("id"); _guiProfile->setId(id); _guiProfile->setName(name); } void GUIProfileParser::addProduct(const QXmlAttributes& attributes) { /* std::cout << "Product: "; printAttributes(attributes); */ QString vendor = attributes.value("vendor"); QString name = attributes.value("name"); QString release = attributes.value("release"); QString comment = attributes.value("comment"); if ( !vendor.isNull() && !name.isNull() ) { // Adding a product makes only sense if we have at least vendor and product name ProfProduct *prd = new ProfProduct(); prd->vendor = vendor; prd->productName = name; prd->productRelease = release; prd->comment = comment; _guiProfile->addProduct(prd); } } void GUIProfileParser::addControl(const QXmlAttributes& attributes) { /* std::cout << "Control: "; printAttributes(attributes); */ QString id = attributes.value("id"); QString subcontrols = attributes.value("subcontrols"); QString name = attributes.value("name"); QString show = attributes.value("show"); QString background = attributes.value("background"); QString switchtype = attributes.value("switchtype"); QString mandatory = attributes.value("mandatory"); QString split = attributes.value("split"); bool isMandatory = false; if ( !id.isNull() ) { // We need at least an "id". We can set defaults for the rest, if undefined. if ( subcontrols.isNull() || subcontrols.isEmpty() ) { subcontrols = '*'; // for compatibility reasons, we interpret an empty string as match-all (aka "*") } if ( name.isNull() ) { // ignore. isNull() will be checked by all users. } if ( ! mandatory.isNull() && mandatory == "true" ) { isMandatory = true; } if ( !background.isNull() ) { // ignore. isNull() will be checked by all users. } if ( !switchtype.isNull() ) { // ignore. isNull() will be checked by all users. } ProfControl *profControl = new ProfControl(id, subcontrols); profControl->setName(name); profControl->setVisibility(show.isNull() ? "all" : show); profControl->setBackgroundColor( background ); profControl->setSwitchtype(switchtype); profControl->setMandatory(isMandatory); if (split=="true") profControl->setSplit(true); _guiProfile->addControl(profControl); } // id != null } -void GUIProfileParser::printAttributes(const QXmlAttributes& attributes) { - if ( attributes.length() > 0 ) { - for ( int i = 0 ; i < attributes.length(); i++ ) { - std::cout << attributes.qName(i).toUtf8().constData() << ":"<< attributes.value(i).toUtf8().constData() << " , "; - } - std::cout << std::endl; - } +void GUIProfileParser::printAttributes(const QXmlAttributes& attributes) +{ + if (attributes.length() > 0 ) { + for ( int i = 0 ; i < attributes.length(); i++ ) { + qCDebug(KMIX_LOG) << i << attributes.qName(i) << "="<< attributes.value(i); + } + } } void GUIProfileParser::splitPair(const QString& pairString, std::pair& result, char delim) { int delimPos = pairString.indexOf(delim); if ( delimPos == -1 ) { // delimiter not found => use an empty String for "second" result.first = pairString; result.second = ""; } else { // delimiter found result.first = pairString.mid(0,delimPos); result.second = pairString.left(delimPos+1); } } diff --git a/gui/guiprofile.h b/gui/guiprofile.h index 92b935eb..206c041d 100644 --- a/gui/guiprofile.h +++ b/gui/guiprofile.h @@ -1,234 +1,230 @@ /* * KMix -- KDE's full featured mini mixer * * Copyright 2006-2007 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. */ #ifndef _GUIPROFILE_H_ #define _GUIPROFILE_H_ class Mixer; #include "kmix_debug.h" #include #include #include #include #include #include #include struct ProfProduct { QString vendor; QString productName; // In case the vendor ships different products under the same productName QString productRelease; QString comment; }; /** * GuiVisibility can be used in different contexts. One is, to define in the XML GUI Profile, which control to show, e.g. show * "MIC Boost" in EXTENDED mode. The other is for representing the GUI complexity (e.g. for letting the user select a preset like "SIMPLE". */ enum class GuiVisibility { Simple, Extended, Full, Custom, Never, Default }; class ProfControl { public: ProfControl(const QString &id, const QString &subcontrols); ProfControl(const ProfControl &ctl); // copy constructor ~ProfControl() = default; // ID as returned by the Mixer Backend, e.g. "Master:0" QString id() const { return (_id); } void setId(const QString &id) { _id = id; } // Visible name for the User (if null, 'id' will be used). // And in the future a default lookup table will be consulted. // Because the name is visible, some kind of i18n() should be used. QString name() const { return (_name); } void setName(const QString &name) { _name = name; } void setSubcontrols(QString sctls); bool useSubcontrolPlayback() const { return (_useSubcontrolPlayback); } bool useSubcontrolCapture() const { return (_useSubcontrolCapture); } bool useSubcontrolPlaybackSwitch() const { return (_useSubcontrolPlaybackSwitch); } bool useSubcontrolCaptureSwitch() const { return (_useSubcontrolCaptureSwitch); } bool useSubcontrolEnum() const { return (_useSubcontrolEnum); } - QString renderSubcontrols(); + QString renderSubcontrols() const; QString getBackgroundColor() const { return (_backgroundColor); } void setBackgroundColor(const QString &col) { _backgroundColor = col; } QString getSwitchtype() const { return (_switchtype); } void setSwitchtype(const QString &swtype) { _switchtype = swtype; } void setVisible(bool visible); void setVisibility(GuiVisibility vis); void setVisibility(const QString &visString); GuiVisibility getVisibility() const { return (_visibility); } bool isMandatory() const { return (_mandatory); } void setMandatory(bool mandatory) { _mandatory = mandatory; } void setSplit (bool split) { _split = split; } bool isSplit() const { return (_split); } /** * Returns whether this ProfControl's GuiVisibility satisfies the other GuiVisibility. * 'Never' can never be satisfied - if this or the other is 'Never', the result is false. * 'Custom' is always satisfied - if this or the other is 'Custom', the result is true. * 'Default' for the other is always satisfied. * The other 3 enum values are completely ordered as 'Simple' < 'Extended' < 'Full'. *

* For example, 'Simple' satisfies 'Full', as the simple GUI is part of the full GUI. */ bool satisfiesVisibility(GuiVisibility vis) const; private: // The following are the deserialized values of _subcontrols bool _useSubcontrolPlayback; bool _useSubcontrolCapture; bool _useSubcontrolPlaybackSwitch; bool _useSubcontrolCaptureSwitch; bool _useSubcontrolEnum; QString _id; QString _name; // For applying custom colors QString _backgroundColor; // For defining the switch type when it is not a standard palyback or capture switch QString _switchtype; // show or hide (contains the GUI type: simple, extended, all) GuiVisibility _visibility; bool _mandatory; // A mandatory control must be included in all GUIProfile copies bool _split; // true if this widget is to show two sliders // List of controls, e.g: "rec:1-2,recswitch" // THIS IS RAW DATA AS LOADED FROM THE PROFILE. DO NOT USE IT, except for debugging. QString _subcontrols; }; struct ProductComparator { bool operator()(const ProfProduct*, const ProfProduct*) const; }; class GUIProfile { public: typedef std::set ProductSet; typedef QList ControlSet; public: GUIProfile(); ~GUIProfile(); - bool readProfile(const QString& ref_fileNamestring); + bool readProfile(const QString &ref_fileNamestring); bool finalizeProfile() const; bool writeProfile(); bool isDirty() const { return (_dirty); } void setDirty() { _dirty = true; } void setId(const QString &id) { _id = id; } QString getId() const { return (_id); } QString getMixerId() const { return (_mixerId); } - unsigned long match(Mixer *mixer) const; - - friend QTextStream& operator<<(QTextStream &outStream, const GUIProfile& guiprof); + unsigned long match(const Mixer *mixer) const; static void clearCache(); static GUIProfile *find(const QString &id); - static GUIProfile *find(Mixer *mixer, const QString &profileName, bool profileNameIsFullyQualified, bool ignoreCardName); - static GUIProfile *fallbackProfile(Mixer *mixer); + static GUIProfile *find(const Mixer *mixer, const QString &profileName, bool profileNameIsFullyQualified, bool ignoreCardName); + static GUIProfile *fallbackProfile(const Mixer *mixer); // --- Getters and setters ---------------------------------------------------------------------- const ControlSet &getControls() const { return (_controls); } void setControls(ControlSet &newControlSet); void addControl(ProfControl *ctrl) { _controls.push_back(ctrl); } void addProduct(ProfProduct *prod) { _products.insert(prod); } QString getName() const { return (_name); } void setName(const QString &name) { _name = name; } // --- The values from the tag: No getters and setters for them (yet) ----------------------------- QString _soundcardDriver; // The driver version: 1000*1000*MAJOR + 1000*MINOR + PATCHLEVEL unsigned long _driverVersionMin; unsigned long _driverVersionMax; QString _soundcardName; QString _soundcardType; unsigned long _generation; private: ControlSet _controls; ProductSet _products; QString _id; QString _name; QString _mixerId; bool _dirty; }; -//std::ostream& operator<<(std::ostream& os, const GUIProfile& vol); -QTextStream& operator<<(QTextStream &outStream, const GUIProfile& guiprof); class GUIProfileParser : public QXmlDefaultHandler { public: explicit GUIProfileParser(GUIProfile* ref_gp); // Enumeration for the scope enum ProfileScope { NONE, SOUNDCARD }; bool startDocument() Q_DECL_OVERRIDE; bool startElement( const QString&, const QString&, const QString& , const QXmlAttributes& ) Q_DECL_OVERRIDE; bool endElement( const QString&, const QString&, const QString& ) Q_DECL_OVERRIDE; private: void addControl(const QXmlAttributes& attributes); void addProduct(const QXmlAttributes& attributes); void addSoundcard(const QXmlAttributes& attributes); void addProfileInfo(const QXmlAttributes& attributes); void printAttributes(const QXmlAttributes& attributes); void splitPair(const QString& pairString, std::pair& result, char delim); ProfileScope _scope; GUIProfile* _guiProfile; }; #endif //_GUIPROFILE_H_