1 /*
2  * KMix -- KDE's full featured mini mixer
3  *
4  * Copyright 2006-2007 Christian Esken <esken@kde.org>
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Library General Public
8  * License as published by the Free Software Foundation; either
9  * version 2 of the License, or (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Library General Public License for more details.
15  *
16  * You should have received a copy of the GNU Library General Public
17  * License along with this program; if not, write to the Free
18  * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
19  */
20 
21 #include "mixer_backend.h"
22 
23 #include <klocalizedstring.h>
24 
25 // for the "ERR_" declarations, #include mixer.h
26 #include "core/mixer.h"
27 #include "core/ControlManager.h"
28 
29 #include <QTimer>
30 
31 #define POLL_RATE_SLOW 1500
32 #define POLL_RATE_FAST 50
33 
34 
35 #include "mixer_backend_i18n.cpp"
36 
Mixer_Backend(Mixer * mixer,int device)37 Mixer_Backend::Mixer_Backend(Mixer *mixer, int device) :
38 m_devnum (device) , m_isOpen(false), m_recommendedMaster(), _mixer(mixer), _pollingTimer(0), _cardInstance(1), _cardRegistered(false)
39 
40 {
41 	// In all cases create a QTimer. We will use it once as a singleShot(), even if something smart
42 	// like ::select() is possible (as in ALSA). And force to do an update.
43 	_readSetFromHWforceUpdate = true;
44 	_pollingTimer = new QTimer(); // will be started on open() and stopped on close()
45 	connect( _pollingTimer, SIGNAL(timeout()), this, SLOT(readSetFromHW()), Qt::QueuedConnection);
46 
47 }
48 
closeCommon()49 void Mixer_Backend::closeCommon()
50 {
51 	freeMixDevices();
52 }
53 
close()54 int Mixer_Backend::close()
55 {
56 	qCDebug(KMIX_LOG) << "Implicit close on " << this << ". Please instead call closeCommon() and close() explicitly (in concrete Backend destructor)";
57 	// ^^^ Background. before the destructor runs, the C++ runtime changes the virtual pointers to point back
58 	//     to the common base class. So what actually runs is not run Mixer_ALSA::close(), but this method.
59 	//
60 	//     See https://stackoverflow.com/questions/99552/where-do-pure-virtual-function-call-crashes-come-from?lq=1
61 	//
62 	//     Comment: IMO this is totally stupid and insane behavior of C++, because you cannot simply cannot call
63 	//              the overwritten (cleanup) methods in the destructor.
64 	return 0;
65 }
66 
~Mixer_Backend()67 Mixer_Backend::~Mixer_Backend()
68 {
69 	unregisterCard(this->getName());
70 	if (!m_mixDevices.isEmpty())
71 	{
72 		qCDebug(KMIX_LOG) << "Implicit close on " << this << ". Please instead call closeCommon() and close() explicitly (in concrete Backend destructor)";
73 	}
74 	delete _pollingTimer;
75 }
76 
freeMixDevices()77 void Mixer_Backend::freeMixDevices()
78 {
79 	for (shared_ptr<MixDevice> md : qAsConst(m_mixDevices)) md->close();
80 	m_mixDevices.clear();
81 }
82 
83 
openIfValid()84 bool Mixer_Backend::openIfValid()
85 {
86 	const int ret = open();
87 	if (ret!=0)
88 	{
89 		//qCWarning(KMIX_LOG) << "open" << getName() << "failed" << ret;
90 		return false;				// could not open
91 	}
92 
93 	qCDebug(KMIX_LOG) << "opened" << getName() <<  "count" << m_mixDevices.count()
94 			  << "dynamic?" << _mixer->isDynamic() << "needsPolling?" << needsPolling();
95 	if (m_mixDevices.count() > 0 || _mixer->isDynamic())
96 	{
97 		if (needsPolling())
98 		{
99 			_pollingTimer->start(POLL_RATE_FAST);
100 		}
101 		else
102 		{
103 			// The initial state must be read manually
104 			QTimer::singleShot( POLL_RATE_FAST, this, SLOT(readSetFromHW()));
105 		}
106 		return true;				// could be opened
107 	}
108 	else
109 	{
110 		qCWarning(KMIX_LOG) << "no mix devices and not dynamic";
111 		return false;				// could not open
112 	}
113 }
114 
115 
isOpen()116 bool Mixer_Backend::isOpen() {
117 	return m_isOpen;
118 }
119 
120 /**
121  * Queries the backend driver whether there are new changes in any of the controls.
122  * If you cannot find out for a backend, return "true" - this is also the default implementation.
123  * @return true, if there are changes. Otherwise false is returned.
124  */
hasChangedControls()125 bool Mixer_Backend::hasChangedControls()
126 {
127 	return true;
128 }
129 
130 /**
131  * The name of the Mixer this backend represents.
132  * Often it is just a name/id for the kernel. so name and id are usually identical. Virtual/abstracting backends are
133  * different, as they represent some distinct function like "Application streams" or "Capture Devices". Also backends
134  * that do not have names might can to set ID and name different like i18n("SUN Audio") and "SUNAudio".
135  */
getName() const136 QString Mixer_Backend::getName() const
137 {
138 	return m_mixerName;
139 }
140 
141 /**
142  * The id of the Mixer this backend represents. The default implementation simply returns the name.
143  * Often it is just a name/id for the kernel. so name and id are usually identical. See also
144  * Mixer_Backend::getName().
145  * You must override this method if you want to set ID different from name.
146  */
getId() const147 QString Mixer_Backend::getId() const
148 {
149 	return m_mixerName; // Backwards compatibility. PulseAudio overrides it.
150 }
151 
152 /**
153  * After calling this, readSetFromHW() will do a complete update. This will
154  * trigger emitting the appropriate signals like controlChanged().
155  *
156  * This method is useful, if you need to get a "refresh signal" - used at:
157  * 1) Start of KMix - so that we can be sure an initial signal is emitted
158  * 2) When reconstructing any MixerWidget (e.g. DockIcon after applying preferences)
159  */
readSetFromHWforceUpdate() const160 void Mixer_Backend::readSetFromHWforceUpdate() const
161 {
162 	_readSetFromHWforceUpdate = true;
163 }
164 
165 
166 /**
167  * You can call this to retrieve the freshest information from the mixer HW.
168  * This method is also called regularly by the mixer timer.
169  */
readSetFromHW()170 void Mixer_Backend::readSetFromHW()
171 {
172 	bool updated = hasChangedControls();
173 	if ( (! updated) && (! _readSetFromHWforceUpdate) ) {
174 		// Some drivers (ALSA) are smart. We don't need to run the following
175 		// time-consuming update loop if there was no change
176 		qCDebug(KMIX_LOG) << "smart-update-tick";
177 		return;
178 	}
179 
180 	_readSetFromHWforceUpdate = false;
181 
182 	int ret = Mixer::OK_UNCHANGED;
183 
184 	for (shared_ptr<MixDevice> md : qAsConst(m_mixDevices))
185 	{
186 	  //bool debugMe = (md->id() == "PCM:0" );
187 	  bool debugMe = false;
188 	  if (debugMe) qCDebug(KMIX_LOG) << "Old PCM:0 playback state" << md->isMuted()
189 	    << ", vol=" << md->playbackVolume().getAvgVolumePercent(Volume::MALL);
190 
191 		int retLoop = readVolumeFromHW( md->id(), md );
192 	  if (debugMe) qCDebug(KMIX_LOG) << "New PCM:0 playback state" << md->isMuted()
193 	    << ", vol=" << md->playbackVolume().getAvgVolumePercent(Volume::MALL);
194 		if (md->isEnum() )
195 		{
196 			/*
197 			 * This could be reworked:
198 			 * Plan: Read everything (including enum's) in readVolumeFromHW().
199 			 * readVolumeFromHW() should then be renamed to readHW().
200 			 */
201 			md->setEnumId( enumIdHW(md->id()) );
202 		}
203 
204 		// Transition the outer return value with the value from this loop iteration
205 		if ( retLoop == Mixer::OK && ret == Mixer::OK_UNCHANGED )
206 		{
207 			// Unchanged => OK (Changed)
208 			ret = Mixer::OK;
209 		}
210 		else if ( retLoop != Mixer::OK && retLoop != Mixer::OK_UNCHANGED )
211 		{
212 			// If current ret from loop in not OK, then transition to that: ret (Something) => retLoop (Error)
213 			ret = retLoop;
214 		}
215 	}
216 
217 	if ( ret == Mixer::OK )
218 	{
219 		// We explicitly exclude Mixer::OK_UNCHANGED and Mixer::ERROR_READ
220 		if ( needsPolling() )
221 		{
222 			// Upgrade polling frequency temporarily to be more smoooooth
223 			_pollingTimer->setInterval(POLL_RATE_FAST);
224 			QTime fastPollingEndsAt = QTime::currentTime ();
225 			fastPollingEndsAt = fastPollingEndsAt.addSecs(5);
226 			_fastPollingEndsAt = fastPollingEndsAt;
227 			//_fastPollingEndsAt = fastPollingEndsAt;
228 			qCDebug(KMIX_LOG) << "Start fast polling from " << QTime::currentTime() <<"until " << _fastPollingEndsAt;
229 		}
230 
231 		ControlManager::instance().announce(_mixer->id(), ControlManager::Volume, QString("Mixer.fromHW"));
232 	}
233 
234 	else
235 	{
236 		// This code path is entered on Mixer::OK_UNCHANGED and ERROR
237 		bool fastPollingEndsNow = (!_fastPollingEndsAt.isNull()) && _fastPollingEndsAt < QTime::currentTime ();
238 		if ( fastPollingEndsNow )
239 		{
240 			qCDebug(KMIX_LOG) << "End fast polling";
241 			_fastPollingEndsAt = QTime(); // NULL time
242 			_pollingTimer->setInterval(POLL_RATE_SLOW);
243 		}
244 	}
245 }
246 
247 /**
248  * Return the MixDevice, that would qualify best as MasterDevice. The default is to return the
249  * first device in the device list. Backends can override this (i.e. the ALSA Backend does so).
250  * The users preference is NOT returned by this method - see the Mixer class for that.
251  */
recommendedMaster()252 shared_ptr<MixDevice> Mixer_Backend::recommendedMaster()
253 {
254 	if ( m_recommendedMaster )
255 	{
256 		// Backend has set a recommended master. Thats fine. Using it.
257 		return m_recommendedMaster;
258 	}
259 	else if ( ! m_mixDevices.isEmpty() )
260 	{
261 		// Backend has NOT set a recommended master. Evil backend
262 		// => lets help out, using the first device (if exists)
263 		return m_mixDevices.at(0);
264 	}
265 	else
266 	{
267 		if ( !_mixer->isDynamic())
268 			// This should never ever happen, as KMix does NOT accept soundcards without controls
269 			qCCritical(KMIX_LOG) << "Mixer_Backend::recommendedMaster(): returning invalid master. This is a bug in KMix. Please file a bug report stating how you produced this.";
270 	}
271 
272 	// If we reach this code path, then obviously m_recommendedMaster == 0 (see above)
273 	return m_recommendedMaster;
274 
275 }
276 
277 /**
278  * Sets the ID of the currently selected Enum entry.
279  * This is a dummy implementation - if the Mixer backend
280  * wants to support it, it must implement the driver specific
281  * code in its subclass (see Mixer_ALSA.cpp for an example).
282  */
setEnumIdHW(const QString &,unsigned int)283 void Mixer_Backend::setEnumIdHW(const QString& , unsigned int) {
284 	return;
285 }
286 
287 /**
288  * Return the ID of the currently selected Enum entry.
289  * This is a dummy implementation - if the Mixer backend
290  * wants to support it, it must implement the driver specific
291  * code in its subclass (see Mixer_ALSA.cpp for an example).
292  */
enumIdHW(const QString &)293 unsigned int Mixer_Backend::enumIdHW(const QString& ) {
294 	return 0;
295 }
296 
297 
298 /**
299  * Move the stream to a new destination
300  */
moveStream(const QString & id,const QString & destId)301 bool Mixer_Backend::moveStream(const QString &id, const QString &destId)
302 {
303 	qCDebug(KMIX_LOG) << "called for unsupported" << id;
304 	Q_UNUSED(destId);
305 	return (false);
306 }
307 
308 /**
309  * Get the current destination device of a stream
310  */
currentStreamDevice(const QString & id) const311 QString Mixer_Backend::currentStreamDevice(const QString &id) const
312 {
313 	qCDebug(KMIX_LOG) << "called for unsupported" << id;
314 	return (QString());
315 }
316 
317 
errorText(int mixer_error)318 QString Mixer_Backend::errorText(int mixer_error)
319 {
320 	QString l_s_errmsg;
321 	switch (mixer_error)
322 	{
323 	case Mixer::ERR_PERM:
324 		l_s_errmsg = i18n("kmix:You do not have permission to access the mixer device.\n" \
325 				"Please check your operating systems manual to allow the access.");
326 		break;
327 	case Mixer::ERR_WRITE:
328 		l_s_errmsg = i18n("kmix: Could not write to mixer.");
329 		break;
330 	case Mixer::ERR_READ:
331 		l_s_errmsg = i18n("kmix: Could not read from mixer.");
332 		break;
333 	case Mixer::ERR_OPEN:
334 		l_s_errmsg = i18n("kmix: Mixer cannot be found.\n" \
335 				"Please check that the soundcard is installed and that\n" \
336 				"the soundcard driver is loaded.\n");
337 		break;
338 	default:
339 		l_s_errmsg = i18n("kmix: Unknown error. Please report how you produced this error.");
340 		break;
341 	}
342 	return l_s_errmsg;
343 }
344 
345 
346 
347 
348