kde-extraapps/kmix/core/mixdevice.cpp
Ivailo Monev 45dcabf7f8 generic: prepare for Katie changes
Signed-off-by: Ivailo Monev <xakepa10@gmail.com>
2022-11-13 15:37:30 +02:00

471 lines
14 KiB
C++

/*
* KMix -- KDE's full featured mini mixer
*
*
* Copyright (C) 1996-2004 Christian Esken <esken@kde.org>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this program; if not, write to the Free
* Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "core/mixdevice.h"
#include <qregexp.h>
#include <kdebug.h>
#include <klocale.h>
#include "core/ControlPool.h"
#include "core/mixer.h"
#include "dbus/dbuscontrolwrapper.h"
#include "gui/guiprofile.h"
#include "core/volume.h"
static const QString channelTypeToIconName( MixDevice::ChannelType type )
{
switch (type) {
case MixDevice::AUDIO:
return "mixer-pcm";
case MixDevice::BASS:
case MixDevice::SURROUND_LFE: // "LFE" SHOULD have an own icon
return "mixer-lfe";
case MixDevice::CD:
return "mixer-cd";
case MixDevice::EXTERNAL:
return "mixer-line";
case MixDevice::MICROPHONE:
return "mixer-microphone";
case MixDevice::MIDI:
return "mixer-midi";
case MixDevice::RECMONITOR:
return "mixer-capture";
case MixDevice::TREBLE:
return "mixer-pcm-default";
case MixDevice::UNKNOWN:
return "mixer-front";
case MixDevice::VOLUME:
return "mixer-master";
case MixDevice::VIDEO:
return "mixer-video";
case MixDevice::SURROUND:
case MixDevice::SURROUND_BACK:
return "mixer-surround";
case MixDevice::SURROUND_CENTERFRONT:
case MixDevice::SURROUND_CENTERBACK:
return "mixer-surround-center";
case MixDevice::HEADPHONE:
return "mixer-headset";
case MixDevice::DIGITAL:
return "mixer-digital";
case MixDevice::AC97:
return "mixer-ac97";
case MixDevice::SPEAKER:
return "mixer-pc-speaker";
case MixDevice::MICROPHONE_BOOST:
return "mixer-microphone-boost";
case MixDevice::MICROPHONE_FRONT_BOOST:
return "mixer-microphone-front-boost";
case MixDevice::MICROPHONE_FRONT:
return "mixer-microphone-front";
case MixDevice::KMIX_COMPOSITE:
return "mixer-line";
case MixDevice::APPLICATION_AMAROK:
return "amarok";
case MixDevice::APPLICATION_BANSHEE:
return "media-player-banshee";
case MixDevice::APPLICATION_XMM2:
return "xmms";
case MixDevice::APPLICATION_TOMAHAWK:
return "tomahawk";
case MixDevice::APPLICATION_CLEMENTINE:
return "application-x-clementine";
case MixDevice::APPLICATION_VLC:
return "vlc";
case MixDevice::APPLICATION_STREAM:
return "mixer-pcm";
}
return "mixer-front";
}
/**
* Constructs a MixDevice. A MixDevice represents one channel or control of
* the mixer hardware. A MixDevice has a type (e.g. PCM), a descriptive name
* (for example "Master" or "Headphone" or "IEC 958 Output"),
* can have a volume level (2 when stereo), can be recordable and muted.
* The ChannelType tells which kind of control the MixDevice is.
*/
MixDevice::MixDevice( Mixer* mixer, const QString& id, const QString& name, ChannelType type )
{
init(mixer, id, name, channelTypeToIconName(type), (MixSet*)0);
}
MixDevice::MixDevice( Mixer* mixer, const QString& id, const QString& name, const QString& iconName, MixSet* moveDestinationMixSet )
{
init(mixer, id, name, iconName, moveDestinationMixSet);
}
void MixDevice::init( Mixer* mixer, const QString& id, const QString& name, const QString& iconName, MixSet* moveDestinationMixSet )
{
_artificial = false;
_applicationStream = false;
_dbusControlWrapper = 0; // will be set in addToPool()
_mixer = mixer;
_id = id;
_enumCurrentId = 0;
mediaController = new MediaController(_id);
if( name.isEmpty() )
_name = i18n("unknown");
else
_name = name;
if ( iconName.isEmpty() )
_iconName = "mixer-front";
else
_iconName = iconName;
_moveDestinationMixSet = moveDestinationMixSet;
if ( _id.contains(' ') ) {
// The key is used in the config file. IdbusControlWrappert MUST NOT contain spaces
kError(67100) << "MixDevice::setId(\"" << id << "\") . Invalid key - it must not contain spaces";
_id.replace(' ', '_');
}
// kDebug(67100) << "MixDevice::init() _id=" << _id;
}
/*
* When a MixDevice shall be finally discarded, you must use this method to free its resources.
* You must not use this MixDevice after calling close().
* <br>
* The necessity stems from a memory leak due to object cycle (MixDevice<->DBusControlWrapper), so the reference
* counting std::shared_ptr has no chance to clean up. See Bug 309464 for background information.
*/
void MixDevice::close()
{
delete _dbusControlWrapper;
_dbusControlWrapper = 0;
}
MediaController* MixDevice::getMediaController()
{
return mediaController;
}
std::shared_ptr<MixDevice> MixDevice::addToPool()
{
// kDebug() << "id=" << _mixer->id() << ":" << _id;
std::shared_ptr<MixDevice> thisSharedPtr(this);
// std::shared_ptr<MixDevice> thisSharedPtr = ControlPool::instance()->add(fullyQualifiedId, this);
_dbusControlWrapper = new DBusControlWrapper( thisSharedPtr, dbusPath() );
return thisSharedPtr;
}
/**
* Changes the internal state of this MixDevice.
* It does not commit the change to the hardware.
*
* You might want to call something like m_mixdevice->mixer()->commitVolumeChange(m_mixdevice); after calling this method.
*/
void MixDevice::increaseOrDecreaseVolume(bool decrease, Volume::VolumeTypeFlag volumeType)
{
bool debugme = false;
// bool debugme = id() == "PCM:0" ;
if (volumeType & Volume::Playback)
{
Volume& volP = playbackVolume();
long inc = volP.volumeStep(decrease);
if (debugme)
kDebug() << ( decrease ? "decrease by " : "increase by " ) << inc ;
if (!decrease && isMuted())
{
// increasing from muted state: unmute and start with a low volume level
if (debugme)
kDebug() << "set all to " << inc << "muted old=" << isMuted();
setMuted(false);
volP.setAllVolumes(inc);
}
else
{
volP.changeAllVolumes(inc);
if (debugme)
kDebug() << (decrease ? "decrease by " : "increase by ") << inc;
}
}
if (volumeType & Volume::Capture)
{
if (debugme)
kDebug() << "VolumeType=" << volumeType << " c";
Volume& volC = captureVolume();
long inc = volC.volumeStep(decrease);
volC.changeAllVolumes(inc);
}
}
/**
* Returns the name of the config group
* @param Prefix of the group, e.g. "View_ALSA_USB_01"
* @returns The config group name in the format "prefix.mixerId,controlId"
*/
QString MixDevice::configGroupName(QString prefix)
{
QString devgrp = QString("%1.%2.%3").arg(prefix).arg(mixer()->id()).arg(id());
return devgrp;
}
QString MixDevice::getFullyQualifiedId()
{
QString fqId = QString("%1@%2").arg(_id).arg(_mixer->id());
return fqId;
}
void MixDevice::addPlaybackVolume(Volume &playbackVol)
{
// Hint: "_playbackVolume" gets COPIED from "playbackVol", because the copy-constructor actually copies the volume levels.
_playbackVolume = playbackVol;
_playbackVolume.setSwitchType(Volume::PlaybackSwitch);
}
void MixDevice::addCaptureVolume (Volume &captureVol)
{
_captureVolume = captureVol;
_captureVolume.setSwitchType(Volume::CaptureSwitch);
}
void MixDevice::addEnums(QList<QString*>& ref_enumList)
{
if ( ref_enumList.count() > 0 ) {
int maxEnumId = ref_enumList.count();
for (int i=0; i<maxEnumId; i++ ) {
// we have an enum. Lets set the names of the enum items in the MixDevice
// the enum names are assumed to be static!
_enumValues.append( *(ref_enumList.at(i)) );
}
}
_enumCurrentId = 0; // default is first entry (used if we don't get a value via backend or volume restore)
}
MixDevice::~MixDevice()
{
_enumValues.clear(); // The QString's inside will be auto-deleted, as they get unref'ed
delete _dbusControlWrapper;
}
Volume& MixDevice::playbackVolume()
{
return _playbackVolume;
}
Volume& MixDevice::captureVolume()
{
return _captureVolume;
}
void MixDevice::setEnumId(int enumId)
{
if ( enumId < _enumValues.count() ) {
_enumCurrentId = enumId;
}
}
unsigned int MixDevice::enumId()
{
return _enumCurrentId;
}
QList<QString>& MixDevice::enumValues() {
return _enumValues;
}
const QString& MixDevice::id() const {
return _id;
}
const QString MixDevice::dbusPath() {
QString controlPath = _id;
controlPath.replace(QRegExp("[^a-zA-Z0-9_]"), "_");
controlPath.replace(QLatin1String("//"), QLatin1String("/"));
if ( controlPath.endsWith( '/' ) )
{
controlPath.chop(1);
}
return _mixer->dbusPath() + '/' + controlPath;
}
bool MixDevice::isMuted() { return ! _playbackVolume.isSwitchActivated(); }
/**
* Returns whether this MixDevice is virtually muted. Only MixDevice objects w/o a physical switch can be muted virtually.
*/
bool MixDevice::isVirtuallyMuted()
{
return !hasPhysicalMuteSwitch() && isMuted();
}
void MixDevice::setMuted(bool mute) { _playbackVolume.setSwitch(!mute); }
void MixDevice::toggleMute() { setMuted( _playbackVolume.isSwitchActivated()); }
bool MixDevice::hasMuteSwitch() { return playbackVolume().hasVolume() || playbackVolume().hasSwitch(); }
bool MixDevice::hasPhysicalMuteSwitch() { return playbackVolume().hasSwitch(); }
bool MixDevice::isRecSource() { return ( _captureVolume.hasSwitch() && _captureVolume.isSwitchActivated() ); }
bool MixDevice::isNotRecSource() { return ( _captureVolume.hasSwitch() && !_captureVolume.isSwitchActivated() ); }
void MixDevice::setRecSource(bool value) { _captureVolume.setSwitch( value ); }
bool MixDevice::isEnum() { return ( ! _enumValues.empty() ); }
int MixDevice::mediaPlay() { return mixer()->mediaPlay(_id); }
int MixDevice::mediaPrev() { return mixer()->mediaPrev(_id); }
int MixDevice::mediaNext() { return mixer()->mediaNext(_id); }
bool MixDevice::operator==(const MixDevice& other) const
{
return ( _id == other._id );
}
void MixDevice::setControlProfile(ProfControl* control)
{
_profControl = control;
}
ProfControl* MixDevice::controlProfile() {
return _profControl;
}
/**
* This method is currently only called on "kmixctrl --restore"
*
* Normally we have a working _volume object already, which is very important,
* because we need to read the minimum and maximum volume levels.
* (Another solution would be to "equip" volFromConfig with maxInt and minInt values).
*/
bool MixDevice::read( KConfig *config, const QString& grp )
{
if ( _mixer->isDynamic() || isArtificial() ) {
kDebug(67100) << "MixDevice::read(): This MixDevice does not permit volume restoration (i.e. because it is handled lower down in the audio stack). Ignoring.";
return false;
}
QString devgrp = QString("%1.Dev%2").arg(grp).arg(_id);
KConfigGroup cg = config->group( devgrp );
//kDebug(67100) << "MixDevice::read() of group devgrp=" << devgrp;
readPlaybackOrCapture(cg, false);
readPlaybackOrCapture(cg, true );
bool mute = cg.readEntry("is_muted", false);
setMuted( mute );
bool recsrc = cg.readEntry("is_recsrc", false);
setRecSource( recsrc );
int enumId = cg.readEntry("enum_id", -1);
if ( enumId != -1 ) {
setEnumId( enumId );
}
return true;
}
void MixDevice::readPlaybackOrCapture(const KConfigGroup& config, bool capture)
{
Volume& volume = capture ? captureVolume() : playbackVolume();
for ( Volume::ChannelID chid=Volume::CHIDMIN; chid<= Volume::CHIDMAX; )
{
QString volstr = getVolString(chid,capture);
if ( config.hasKey(volstr) ) {
volume.setVolume(chid, config.readEntry(volstr, 0));
} // if saved channel exists
chid = (Volume::ChannelID)( 1 + (int)chid); // ugly
} // for all channels
}
/**
* called on "kmixctrl --save" and from the GUI's (currently only on exit)
*/
bool MixDevice::write( KConfig *config, const QString& grp )
{
if (_mixer->isDynamic() || isArtificial()) {
kDebug(67100) << "MixDevice::write(): This MixDevice does not permit volume saving (i.e. because it is handled lower down in the audio stack). Ignoring.";
return false;
}
QString devgrp = QString("%1.Dev%2").arg(grp).arg(_id);
KConfigGroup cg = config->group(devgrp);
// kDebug(67100) << "MixDevice::write() of group devgrp=" << devgrp;
writePlaybackOrCapture(cg, false);
writePlaybackOrCapture(cg, true );
cg.writeEntry("is_muted" , isMuted() );
cg.writeEntry("is_recsrc", isRecSource() );
cg.writeEntry("name", _name);
if ( isEnum() ) {
cg.writeEntry("enum_id", enumId() );
}
return true;
}
void MixDevice::writePlaybackOrCapture(KConfigGroup& config, bool capture)
{
Volume& volume = capture ? captureVolume() : playbackVolume();
foreach (VolumeChannel vc, volume.getVolumes() )
{
config.writeEntry(getVolString(vc.chid,capture) , (int)vc.volume);
} // for all channels
}
QString MixDevice::getVolString(Volume::ChannelID chid, bool capture)
{
QString volstr (Volume::ChannelNameForPersistence[chid]);
if ( capture ) volstr += "Capture";
return volstr;
}
/**
* Returns the playback volume level in percent. If the volume is muted, 0 is returned.
* If the given MixDevice contains no playback volume, the capture volume isd used
* instead, and 0 is returned if capturing is disabled for the given MixDevice.
*
* @returns The volume level in percent
*/
int MixDevice::getUserfriendlyVolumeLevel()
{
MixDevice* md = this;
bool usePlayback = md->playbackVolume().hasVolume();
Volume& vol = usePlayback ? md->playbackVolume() : md->captureVolume();
bool isActive = usePlayback ? !md->isMuted() : md->isRecSource();
int val = isActive ? vol.getAvgVolumePercent(Volume::MALL) : 0;
return val;
}
#include "moc_mixdevice.cpp"