1 /*
2 * KMix -- KDE's full featured mini mixer
3 *
4 * Copyright 2011 Igor Poboiko <igor.poboiko@gmail.com>
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 "mixerengine.h"
22 #include "mixset_interface.h"
23 #include "mixer_interface.h"
24 #include "control_interface.h"
25 #include "mixerservice.h"
26
27 #include <QIcon>
28 #include <QTimer>
29
30
31 const QString MixerEngine::KMIX_DBUS_SERVICE = "org.kde.kmix";
32 const QString MixerEngine::KMIX_DBUS_PATH = "/Mixers";
33
MixerEngine(QObject * parent,const QVariantList & args)34 MixerEngine::MixerEngine(QObject *parent, const QVariantList &args)
35 : Plasma::DataEngine(parent, args)
36 , m_kmix(0)
37 {
38 Q_UNUSED(args)
39
40 interface = QDBusConnection::sessionBus().interface();
41 watcher = new QDBusServiceWatcher( this );
42 watcher->addWatchedService( KMIX_DBUS_SERVICE );
43 watcher->setConnection( QDBusConnection::sessionBus() );
44 watcher->setWatchMode( QDBusServiceWatcher::WatchForRegistration | QDBusServiceWatcher::WatchForUnregistration );
45 connect( watcher, SIGNAL(serviceRegistered(QString)),
46 this, SLOT(slotServiceRegistered(QString)) );
47 connect( watcher, SIGNAL(serviceUnregistered(QString)),
48 this, SLOT(slotServiceUnregistered(QString)) );
49 init();
50 }
51
~MixerEngine()52 MixerEngine::~MixerEngine()
53 {
54 // Cleanup
55 delete watcher;
56 // it is bad idea to call removeSource() here
57 clearInternalData(false);
58 delete m_kmix;
59 }
60
sources() const61 QStringList MixerEngine::sources() const
62 {
63 QStringList sources;
64 sources << "Mixers";
65 return sources;
66 }
67
init()68 void MixerEngine::init()
69 {
70 getInternalData();
71 }
72
createMixerInfo(const QString & dbusPath)73 MixerInfo* MixerEngine::createMixerInfo( const QString& dbusPath )
74 {
75 MixerInfo* curmi = new MixerInfo;
76 curmi->iface = new OrgKdeKMixMixerInterface( KMIX_DBUS_SERVICE, dbusPath,
77 QDBusConnection::sessionBus(), this );
78 curmi->id = curmi->iface->id();
79 curmi->dbusPath = dbusPath;
80 curmi->updateRequired = false;
81 curmi->unused = false;
82 curmi->connected = false;
83 QDBusConnection::sessionBus().connect( KMIX_DBUS_SERVICE, dbusPath,
84 "org.kde.KMix.Mixer", "changed",
85 this, SLOT(slotControlsReconfigured()) );
86 m_mixers.insert( dbusPath, curmi );
87 return curmi;
88 }
89
createControlInfo(const QString & mixerId,const QString & dbusPath)90 ControlInfo* MixerEngine::createControlInfo( const QString& mixerId, const QString& dbusPath )
91 {
92 ControlInfo* curci = new ControlInfo;
93 curci->iface = new OrgKdeKMixControlInterface( KMIX_DBUS_SERVICE, dbusPath,
94 QDBusConnection::sessionBus(), this );
95 curci->mixerId = mixerId;
96 curci->id = curci->iface->id();
97 curci->dbusPath = dbusPath;
98 curci->updateRequired = false;
99 curci->unused = false;
100 m_controls.insert(mixerId, curci);
101 return curci;
102 }
103
getInternalData()104 void MixerEngine::getInternalData()
105 {
106 clearInternalData(true);
107 if ( !interface->isServiceRegistered( KMIX_DBUS_SERVICE ) )
108 return;
109 if ( !m_kmix )
110 {
111 m_kmix = new OrgKdeKMixMixSetInterface( KMIX_DBUS_SERVICE, KMIX_DBUS_PATH,
112 QDBusConnection::sessionBus(), this );
113 QDBusConnection::sessionBus().connect( KMIX_DBUS_SERVICE, KMIX_DBUS_PATH,
114 "org.kde.KMix.MixSet", "mixersChanged",
115 this, SLOT(slotMixersChanged()) );
116 QDBusConnection::sessionBus().connect( KMIX_DBUS_SERVICE, KMIX_DBUS_PATH,
117 "org.kde.KMix.MixSet", "masterChanged",
118 this, SLOT(slotMasterChanged()) );
119 }
120
121 for (const QString &path : m_kmix->mixers())
122 {
123 const MixerInfo *curmi = createMixerInfo( path );
124 for (const QString &controlPath : curmi->iface->controls())
125 {
126 createControlInfo( curmi->id, controlPath );
127 }
128 }
129 // Update "Mixers" source
130 getMixersData();
131 }
132
clearInternalData(bool removeSources)133 void MixerEngine::clearInternalData(bool removeSources)
134 {
135 for (MixerInfo *mi : qAsConst(m_mixers))
136 {
137 if ( removeSources )
138 removeSource( mi->id );
139 delete mi->iface;
140 delete mi;
141 }
142 m_mixers.clear();
143
144 for (ControlInfo *ci : qAsConst(m_controls))
145 {
146 if ( removeSources )
147 removeSource( ci->mixerId + '/' + ci->id );
148 delete ci->iface;
149 delete ci;
150 }
151 m_controls.clear();
152 }
153
sourceRequestEvent(const QString & name)154 bool MixerEngine::sourceRequestEvent( const QString &name )
155 {
156 if ( name == "Mixers" )
157 return getMixersData();
158 else if ( name.indexOf("/") == -1 )
159 // This request is for mixer
160 return getMixerData( name );
161 else
162 // This request is for control
163 return getControlData( name );
164 }
165
updateSourceEvent(const QString & name)166 bool MixerEngine::updateSourceEvent( const QString &name )
167 {
168 return sourceRequestEvent( name );
169 }
170
getMixersData()171 bool MixerEngine::getMixersData()
172 {
173 QStringList mixerIds;
174 if ( interface->isServiceRegistered( KMIX_DBUS_SERVICE ) && m_kmix )
175 {
176 for (const MixerInfo *mi : qAsConst(m_mixers)) mixerIds.append(mi->id);
177 /* FIXME: this is used to know whether kmix isn't running or
178 * it can't find any audio device; also it works as a strange
179 * workaround: without it there is no dataUpdated() call sometimes
180 * when it is updated here */
181 setData( "Mixers", "Running", true );
182 setData( "Mixers", "Mixers", mixerIds );
183 setData( "Mixers", "Current Master Mixer", m_kmix->currentMasterMixer() );
184 setData( "Mixers", "Current Master Control", m_kmix->currentMasterControl() );
185 }
186 else
187 {
188 setData( "Mixers", "Running", false );
189 removeData( "Mixers", "Mixers" );
190 removeData( "Mixers", "Current Master Mixer" );
191 removeData( "Mixers", "Current Master Control" );
192 }
193 return true;
194 }
195
getMixerData(const QString & source)196 bool MixerEngine::getMixerData( const QString& source )
197 {
198 // Trying to find this mixer
199 MixerInfo *curmi = 0;
200 for (MixerInfo *mi : qAsConst(m_mixers))
201 {
202 if ( mi->id == source )
203 {
204 curmi = mi;
205 break;
206 }
207 }
208
209 if ( !curmi || !curmi->iface->connection().isConnected() )
210 return false;
211 // Setting data
212 curmi->updateRequired = true;
213 QStringList controlIds;
214 QStringList controlReadableNames;
215 QStringList controlIcons;
216 for (const ControlInfo *ci : m_controls.values(curmi->id))
217 {
218 if ( ci->iface->connection().isConnected() )
219 {
220 controlIds.append( ci->id );
221 controlReadableNames.append( ci->iface->readableName() );
222 controlIcons.append( ci->iface->iconName() );
223 }
224 }
225
226 setData( source, "Opened", curmi->iface->opened() );
227 setData( source, "Readable Name", curmi->iface->readableName() );
228 setData( source, "Balance", curmi->iface->balance() );
229 setData( source, "Controls", controlIds );
230 setData( source, "Controls Readable Names", controlReadableNames );
231 setData( source, "Controls Icons Names", controlIcons );
232 return true;
233 }
234
getControlData(const QString & source)235 bool MixerEngine::getControlData( const QString &source )
236 {
237 QString mixerId = source.section( '/', 0, 0 );
238 QString controlId = source.section( '/', 1 );
239 // Trying to find mixer for this control
240 // and monitor for its changes
241 for (MixerInfo *mi : qAsConst(m_mixers))
242 {
243 if ( mi->id == mixerId )
244 {
245 if ( !mi->connected )
246 {
247 QDBusConnection::sessionBus().connect( KMIX_DBUS_SERVICE, mi->dbusPath,
248 "org.kde.KMix.Mixer", "controlChanged",
249 this, SLOT(slotControlChanged()) );
250 mi->connected = true;
251 }
252 break;
253 }
254 }
255
256 // Trying to find this control
257 ControlInfo *curci = 0;
258 for (ControlInfo *ci : m_controls.values(mixerId))
259 {
260 if ( ci->id == controlId ) {
261 curci = ci;
262 break;
263 }
264 }
265
266 if ( !curci || !curci->iface->connection().isConnected() )
267 return false;
268 // Setting data
269 curci->updateRequired = true;
270 setControlData( curci );
271 return true;
272 }
273
setControlData(ControlInfo * ci)274 void MixerEngine::setControlData(ControlInfo* ci)
275 {
276 QString source = ci->mixerId + '/' + ci->id;
277 setData( source, "Volume", ci->iface->volume() );
278 setData( source, "Mute", ci->iface->mute() );
279 setData( source, "Can Be Muted", ci->iface->canMute() );
280 setData( source, "Readable Name", ci->iface->readableName() );
281 setData( source, "Icon", QIcon::fromTheme(ci->iface->iconName()) );
282 setData( source, "Record Source", ci->iface->recordSource() );
283 setData( source, "Has Capture Switch", ci->iface->hasCaptureSwitch() );
284 }
285
286
slotServiceRegistered(const QString & serviceName)287 void MixerEngine::slotServiceRegistered( const QString &serviceName)
288 {
289 // Let's give KMix some time to load
290 if ( serviceName == KMIX_DBUS_SERVICE )
291 QTimer::singleShot( 1000, this, SLOT(getInternalData()) );
292 }
293
slotServiceUnregistered(const QString & serviceName)294 void MixerEngine::slotServiceUnregistered( const QString &serviceName)
295 {
296 if ( serviceName == KMIX_DBUS_SERVICE )
297 clearInternalData(true);
298 // Updating 'Mixers' source
299 getMixersData();
300 }
301
slotControlChanged()302 void MixerEngine::slotControlChanged()
303 {
304 // Trying to find mixer from which signal was emitted
305 const MixerInfo *curmi = m_mixers.value( message().path(), 0 );
306 if (curmi==nullptr) return;
307 // Updating all controls that might change
308 for (ControlInfo *ci : m_controls.values(curmi->id))
309 {
310 if (ci->updateRequired) setControlData(ci);
311 }
312 }
313
slotControlsReconfigured()314 void MixerEngine::slotControlsReconfigured()
315 {
316 // Trying to find mixer from which signal was emitted
317 const MixerInfo *curmi = m_mixers.value( message().path(), 0 );
318 if (curmi==nullptr) return;
319
320 // Updating
321 QList<ControlInfo*> controlsForMixer = m_controls.values( curmi->id );
322 QStringList controlIds;
323 QStringList controlReadableNames;
324 QStringList controlIconNames;
325 for (ControlInfo *ci : qAsConst(controlsForMixer)) ci->unused = true;
326 for (const QString &controlPath : curmi->iface->controls())
327 {
328 ControlInfo* curci = 0;
329 for (ControlInfo *ci : qAsConst(controlsForMixer))
330 {
331 if ( ci->dbusPath == controlPath )
332 {
333 curci = ci;
334 break;
335 }
336 }
337
338 // If control not found then we should add a new
339 if ( !curci )
340 curci = createControlInfo( curmi->id, controlPath );
341 curci->unused = false;
342 controlIds.append( curci->id );
343 controlReadableNames.append( curci->iface->readableName() );
344 controlIconNames.append( curci->iface->iconName() );
345 }
346
347 // If control is unused then we should remove it
348 for (ControlInfo *ci : qAsConst(controlsForMixer))
349 {
350 if ( ci->unused )
351 {
352 m_controls.remove( curmi->id, ci );
353 delete ci->iface;
354 delete ci;
355 }
356 }
357 if ( curmi->updateRequired )
358 {
359 setData( curmi->id, "Controls", controlIds );
360 setData( curmi->id, "Controls Readable Names", controlReadableNames );
361 setData( curmi->id, "Controls Icons Names", controlIconNames );
362 }
363 }
364
updateInternalMixersData()365 void MixerEngine::updateInternalMixersData()
366 {
367 // Some mixer added or removed
368 for (MixerInfo *mi : qAsConst(m_mixers)) mi->unused = true;
369 for (const QString& mixerPath : m_kmix->mixers())
370 {
371 MixerInfo* curmi = m_mixers.value( mixerPath, 0 );
372 // if mixer was added, we need to add one to m_mixers
373 // and add all controls for this mixer to m_controls
374 if ( !curmi )
375 {
376 curmi = createMixerInfo( mixerPath );
377 for (const QString &controlPath : curmi->iface->controls())
378 createControlInfo( curmi->id, controlPath );
379 }
380 curmi->unused = false;
381 }
382
383 // and if it was removed, we should remove it
384 // and remove all controls
385 for (MixerInfo *mi : qAsConst(m_mixers))
386 {
387 if ( mi->unused )
388 {
389 for (ControlInfo *ci : m_controls.values(mi->id))
390 {
391 m_controls.remove( mi->id, ci );
392 removeSource( ci->mixerId + '/' + ci->id );
393 delete ci->iface;
394 delete ci;
395 }
396 m_mixers.remove( mi->dbusPath );
397 removeSource( mi->id );
398 delete mi->iface;
399 delete mi;
400 }
401 }
402 }
403
slotMixersChanged()404 void MixerEngine::slotMixersChanged()
405 {
406 // Let's give KMix some time to register this mixer on bus and so on
407 QTimer::singleShot( 1000, this, SLOT(updateInternalMixersData()) );
408 }
409
slotMasterChanged()410 void MixerEngine::slotMasterChanged()
411 {
412 setData( "Mixers", "Current Master Mixer", m_kmix->currentMasterMixer() );
413 setData( "Mixers", "Current Master Control", m_kmix->currentMasterControl() );
414 }
415
serviceForSource(const QString & source)416 Plasma::Service* MixerEngine::serviceForSource(const QString& source)
417 {
418 QString mixerId = source.section( '/', 0, 0 );
419 QString controlId = source.section( '/', 1 );
420 // Trying to find this control
421 ControlInfo *curci = 0;
422 for (ControlInfo *ci : m_controls.values(mixerId))
423 {
424 if ( ci->id == controlId ) {
425 curci = ci;
426 break;
427 }
428 }
429
430 if (curci==nullptr) return Plasma::DataEngine::serviceForSource(source);
431 return (new MixerService(this, curci->iface));
432 }
433
434 K_EXPORT_PLASMA_DATAENGINE_WITH_JSON(mixer, MixerEngine, "plasma-dataengine-mixer.json")
435
436 #include "mixerengine.moc"
437