1 /*
2  * KMix -- KDE's full featured mini mixer
3  *
4  * Copyright 1996-2014 The KMix authors. Maintainer: 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 "kmixwindow.h"
22 
23 // include files for Qt
24 #include <QApplication>
25 #include <QMenuBar>
26 #include <QTabWidget>
27 #include <QPointer>
28 #include <QHash>
29 #include <QTimer>
30 #include <QDBusInterface>
31 #include <QDBusPendingCall>
32 
33 // include files for KDE
34 #include <kxmlgui_version.h>
35 #include <kglobalaccel.h>
36 #include <kmessagebox.h>
37 #include <klocalizedstring.h>
38 #include <kstandardaction.h>
39 #include <kxmlguifactory.h>
40 #include <KProcess>
41 
42 // KMix
43 #include "kmix_debug.h"
44 #include "core/ControlManager.h"
45 #include "core/mixertoolbox.h"
46 #include "core/kmixdevicemanager.h"
47 #include "gui/kmixerwidget.h"
48 #include "gui/kmixprefdlg.h"
49 #include "gui/kmixdockwidget.h"
50 #include "gui/kmixtoolbox.h"
51 #include "gui/dialogaddview.h"
52 #include "gui/dialogselectmaster.h"
53 #include "dbus/dbusmixsetwrapper.h"
54 #include "settings.h"
55 
56 #ifdef HAVE_CANBERRA
57 #include "volumefeedback.h"
58 #endif
59 
60 
61 /* KMixWindow
62  * Constructs a mixer window (KMix main window)
63  */
64 
KMixWindow(bool invisible,bool reset)65 KMixWindow::KMixWindow(bool invisible, bool reset) :
66 		KXmlGuiWindow(nullptr, Qt::WindowFlags(KDE_DEFAULT_WINDOWFLAGS|Qt::WindowContextHelpButtonHint)),
67 		m_multiDriverMode(false), // -<- I never-ever want the multi-drivermode to be activated by accident
68 		m_autouseMultimediaKeys(true),
69 		m_dockWidget(), m_dsm(0), m_dontSetDefaultCardOnStart(false)
70 {
71 	setObjectName(QStringLiteral("KMixWindow"));
72 	// disable delete-on-close because KMix might just sit in the background waiting for cards to be plugged in
73 	setAttribute(Qt::WA_DeleteOnClose, false);
74 
75 	initActions(); // init actions first, so we can use them in the loadConfig() already
76 	loadAndInitConfig(reset); // Load config before initMixer(), e.g. due to "MultiDriver" keyword
77 	initActionsLate(); // init actions that require a loaded config
78 	// TODO: Port to KF5
79 	//KGlobal::locale()->insertCatalog(QLatin1String("kmix-controls"));
80 	initWidgets();
81 	initPrefDlg();
82 	DBusMixSetWrapper::initialize(this, QStringLiteral("/Mixers"));
83 	MixerToolBox::initMixer(m_multiDriverMode, m_backendFilter, true);
84 	KMixDeviceManager *theKMixDeviceManager = KMixDeviceManager::instance();
85 	initActionsAfterInitMixer(); // init actions that require initialized mixer backend(s).
86 
87 	recreateGUI(false, reset);
88 	if (m_wsMixers->count() < 1)
89 	{
90 		// Something is wrong. Perhaps a hardware or driver or backend change. Let KMix search harder
91 		recreateGUI(false, QString(), true, reset);
92 	}
93 
94 	if (!qApp->isSessionRestored() ) // done by the session manager otherwise
95 		setInitialSize();
96 
97 	fixConfigAfterRead();
98 	connect(theKMixDeviceManager, &KMixDeviceManager::plugged, this, &KMixWindow::plugged);
99 	connect(theKMixDeviceManager, &KMixDeviceManager::unplugged, this, &KMixWindow::unplugged);
100 	theKMixDeviceManager->initHotplug();
101 
102 	if (m_startVisible && !invisible) show();	// Started visible
103 
104 	connect(qApp, SIGNAL(aboutToQuit()), SLOT(saveConfig()) );
105 
106 	ControlManager::instance().addListener(
107 			QString(), // All mixers (as the Global master Mixer might change)
108 			ControlManager::ControlList|ControlManager::MasterChanged, this,
109 			"KMixWindow");
110 #ifdef HAVE_CANBERRA
111 	VolumeFeedback::instance()->init();		// set up for volume feedback
112 #endif
113 	// Send an initial volume refresh (otherwise all volumes are 0 until the next change)
114 	ControlManager::instance().announce(QString(), ControlManager::Volume, "Startup");
115 }
116 
~KMixWindow()117 KMixWindow::~KMixWindow()
118 {
119 	ControlManager::instance().removeListener(this);
120 
121 	delete m_dsm;
122 
123 	// -1- Cleanup Memory: clearMixerWidgets
124 	while (m_wsMixers->count() != 0)
125 	{
126 		QWidget *mw = m_wsMixers->widget(0);
127 		m_wsMixers->removeTab(0);
128 		delete mw;
129 	}
130 
131 	// -2- Mixer HW
132 	MixerToolBox::deinitMixer();
133 
134 	// -3- Action collection (just to please Valgrind)
135 	actionCollection()->clear();
136 
137 	// GUIProfile cache should be cleared very very late, as GUIProfile instances are used in the Views, which
138 	// means main window and potentially also in the tray popup (at least we might do so in the future).
139 	// This place here could be to early, if we would start to GUIProfile outside KMixWIndow, e.g. in the tray popup.
140 	// Until we do so, this is the best place to call clearCache(). Later, e.g. in main() would likely be problematic.
141 
142 	GUIProfile::clearCache();
143 
144 }
145 
controlsChange(ControlManager::ChangeType changeType)146 void KMixWindow::controlsChange(ControlManager::ChangeType changeType)
147 {
148 	switch (changeType)
149 	{
150 	case ControlManager::ControlList:
151 	case ControlManager::MasterChanged:
152 		updateDocking();
153 		break;
154 
155 	default:
156 		ControlManager::warnUnexpectedChangeType(changeType, this);
157 		break;
158 	}
159 
160 }
161 
162 
initActions()163 void KMixWindow::initActions()
164 {
165 	// file menu
166 	KStandardAction::quit(this, SLOT(quit()), actionCollection());
167 
168 	// settings menu
169 	_actionShowMenubar = KStandardAction::showMenubar(this, SLOT(toggleMenuBar()), actionCollection());
170 	KStandardAction::preferences(this, SLOT(showSettings()), actionCollection());
171 
172 #if KXMLGUI_VERSION >= QT_VERSION_CHECK(5, 84, 0)
173 	KStandardAction::keyBindings(guiFactory(), &KXMLGUIFactory::showConfigureShortcutsDialog, actionCollection());
174 #else
175 	KStandardAction::keyBindings(guiFactory(), SLOT(configureShortcuts()), actionCollection());
176 #endif
177 
178 	QAction* action = actionCollection()->addAction(QStringLiteral("launch_kdesoundsetup"));
179 	action->setText(i18n("Audio Setup..."));
180 	connect(action, SIGNAL(triggered(bool)), SLOT(slotKdeAudioSetupExec()));
181 
182 	action = actionCollection()->addAction(QStringLiteral("hide_kmixwindow"));
183 	action->setText(i18n("Hide Mixer Window"));
184 	connect(action, SIGNAL(triggered(bool)), SLOT(hideOrClose()));
185 	actionCollection()->setDefaultShortcut(action, Qt::Key_Escape);
186 
187 	action = actionCollection()->addAction(QStringLiteral("toggle_channels_currentview"));
188 	action->setText(i18n("Configure &Channels..."));
189 	connect(action, SIGNAL(triggered(bool)), SLOT(slotConfigureCurrentView()));
190 
191 	action = actionCollection()->addAction(QStringLiteral("select_master"));
192 	action->setText(i18n("Select Master Channel..."));
193 	connect(action, SIGNAL(triggered(bool)), SLOT(slotSelectMaster()));
194 
195 	action = actionCollection()->addAction(QStringLiteral("save_1"));
196 	actionCollection()->setDefaultShortcut(action, Qt::CTRL + Qt::SHIFT + Qt::Key_1);
197 	action->setText(i18n("Save volume profile 1"));
198 	connect(action, SIGNAL(triggered(bool)), SLOT(saveVolumes1()));
199 
200 	action = actionCollection()->addAction(QStringLiteral("save_2"));
201 	actionCollection()->setDefaultShortcut(action, Qt::CTRL + Qt::SHIFT + Qt::Key_2);
202 	action->setText(i18n("Save volume profile 2"));
203 	connect(action, SIGNAL(triggered(bool)), SLOT(saveVolumes2()));
204 
205 	action = actionCollection()->addAction(QStringLiteral("save_3"));
206 	actionCollection()->setDefaultShortcut(action, Qt::CTRL + Qt::SHIFT + Qt::Key_3);
207 	action->setText(i18n("Save volume profile 3"));
208 	connect(action, SIGNAL(triggered(bool)), SLOT(saveVolumes3()));
209 
210 	action = actionCollection()->addAction(QStringLiteral("save_4"));
211 	actionCollection()->setDefaultShortcut(action, Qt::CTRL + Qt::SHIFT + Qt::Key_4);
212 	action->setText(i18n("Save volume profile 4"));
213 	connect(action, SIGNAL(triggered(bool)), SLOT(saveVolumes4()));
214 
215 	action = actionCollection()->addAction(QStringLiteral("load_1"));
216 	actionCollection()->setDefaultShortcut(action, Qt::CTRL + Qt::Key_1);
217 	action->setText(i18n("Load volume profile 1"));
218 	connect(action, SIGNAL(triggered(bool)), SLOT(loadVolumes1()));
219 
220 	action = actionCollection()->addAction(QStringLiteral("load_2"));
221 	actionCollection()->setDefaultShortcut(action, Qt::CTRL + Qt::Key_2);
222 	action->setText(i18n("Load volume profile 2"));
223 	connect(action, SIGNAL(triggered(bool)), SLOT(loadVolumes2()));
224 
225 	action = actionCollection()->addAction(QStringLiteral("load_3"));
226 	actionCollection()->setDefaultShortcut(action, Qt::CTRL + Qt::Key_3);
227 	action->setText(i18n("Load volume profile 3"));
228 	connect(action, SIGNAL(triggered(bool)), SLOT(loadVolumes3()));
229 
230 	action = actionCollection()->addAction(QStringLiteral("load_4"));
231 	actionCollection()->setDefaultShortcut(action, Qt::CTRL + Qt::Key_4);
232 	action->setText(i18n("Load volume profile 4"));
233 	connect(action, SIGNAL(triggered(bool)), SLOT(loadVolumes4()));
234 
235 	createGUI(QLatin1String("kmixui.rc"));
236 }
237 
initActionsLate()238 void KMixWindow::initActionsLate()
239 {
240 	if (m_autouseMultimediaKeys)
241 	{
242 		QAction* globalAction = actionCollection()->addAction(QStringLiteral("increase_volume"));
243 		globalAction->setText(i18n("Increase Volume"));
244 
245 		KGlobalAccel::setGlobalShortcut(globalAction, Qt::Key_VolumeUp);
246 
247 		connect(globalAction, SIGNAL(triggered(bool)), SLOT(slotIncreaseVolume()));
248 
249 		globalAction = actionCollection()->addAction(QStringLiteral("decrease_volume"));
250 		globalAction->setText(i18n("Decrease Volume"));
251 		KGlobalAccel::setGlobalShortcut(globalAction, Qt::Key_VolumeDown);
252 		connect(globalAction, SIGNAL(triggered(bool)), SLOT(slotDecreaseVolume()));
253 
254 		globalAction = actionCollection()->addAction(QStringLiteral("mute"));
255 		globalAction->setText(i18n("Mute"));
256 		KGlobalAccel::setGlobalShortcut(globalAction, Qt::Key_VolumeMute);
257 		connect(globalAction, SIGNAL(triggered(bool)), SLOT(slotMute()));
258 	}
259 }
260 
initActionsAfterInitMixer()261 void KMixWindow::initActionsAfterInitMixer()
262 {
263 	// Only show the new tab widget if Pulseaudio is not used. Hint: The Pulseaudio backend always
264 	// runs with 4 fixed Tabs.
265 	if (!Mixer::pulseaudioPresent())
266 	{
267 		QPushButton* _cornerLabelNew = new QPushButton();
268 		_cornerLabelNew->setIcon(QIcon::fromTheme("tab-new"));
269 		_cornerLabelNew->setToolTip(i18n("Add new view"));
270 		m_wsMixers->setCornerWidget(_cornerLabelNew, Qt::TopLeftCorner);
271 		connect(_cornerLabelNew, SIGNAL(clicked()), SLOT(newView()));
272 	}
273 }
274 
275 
276 //  This needs to done on initialisation, so that the
277 //  signal can be connected.
initPrefDlg()278 void KMixWindow::initPrefDlg()
279 {
280 	KMixPrefDlg *prefDlg = KMixPrefDlg::instance(this);
281 	connect(prefDlg, &KMixPrefDlg::kmixConfigHasChanged, this, &KMixWindow::applyPrefs);
282 }
283 
284 
initWidgets()285 void KMixWindow::initWidgets()
286 {
287 	m_wsMixers = new QTabWidget();
288 	m_wsMixers->setDocumentMode(true);
289 	setCentralWidget(m_wsMixers);
290 	m_wsMixers->setTabsClosable(false);
291 	connect(m_wsMixers, SIGNAL(tabCloseRequested(int)), SLOT(saveAndCloseView(int)));
292 
293 	connect(m_wsMixers, SIGNAL(currentChanged(int)), SLOT(newMixerShown(int)));
294 
295 	// show menubar if the actions says so (or if the action does not exist)
296 	menuBar()->setVisible((_actionShowMenubar == 0) || _actionShowMenubar->isChecked());
297 }
298 
setInitialSize()299 void KMixWindow::setInitialSize()
300 {
301 	// HACK: QTabWidget will bound its sizeHint to 200x200 unless scrollbuttons
302 	// are disabled, so we disable them, get a decent sizehint and enable them
303 	// back
304 	m_wsMixers->setUsesScrollButtons(false);
305 	QSize defSize = sizeHint();
306 	m_wsMixers->setUsesScrollButtons(true);
307 	QSize size = Settings::size();
308 	if (size.isNull()) size = defSize;
309 	if (!size.isNull()) resize(size);
310 
311 	QPoint pos = Settings::position();
312 	if (!pos.isNull()) move(pos);
313 }
314 
removeDock()315 void KMixWindow::removeDock()
316 {
317 	if (m_dockWidget)
318 	{
319 		m_dockWidget->deleteLater();
320 		m_dockWidget = 0;
321 	}
322 }
323 
324 /**
325  * Creates or deletes the KMixDockWidget, depending on whether there is a Mixer instance available.
326  *
327  * @returns true, if the docking succeeded. Failure usually means that there
328  *    was no suitable mixer control selected.
329  */
updateDocking()330 bool KMixWindow::updateDocking()
331 {
332 	if (!Settings::showDockWidget() || Mixer::mixers().isEmpty())
333 	{
334 		removeDock();
335 		return false;
336 	}
337 	if (!m_dockWidget)
338 	{
339 		m_dockWidget = new KMixDockWidget(this);
340 	}
341 	return true;
342 }
saveConfig()343 void KMixWindow::saveConfig()
344 {
345 	saveBaseConfig();
346 	saveViewConfig();
347 	saveVolumes();
348 
349 	// TODO cesken The reason for not writing might be that we have multiple cascaded KConfig objects. I must migrate to KSharedConfig !!!
350 	KSharedConfig::openConfig()->sync();
351 	qCDebug(KMIX_LOG)
352 	<< "Saved config ... sync finished";
353 }
354 
saveBaseConfig()355 void KMixWindow::saveBaseConfig()
356 {
357 	Settings::setConfigVersion(KMIX_CONFIG_VERSION);
358 
359 	Settings::setSize(size());
360 	Settings::setPosition(pos());
361 	// Cannot use isVisible() here, as in the "aboutToQuit()" case this widget is already hidden.
362 	// (Please note that the problem was only there when quitting via Systray - esken).
363 	// Using it again, as internal behaviour has changed with KDE4
364 	Settings::setVisible(isVisible());
365 	Settings::setMenubar(_actionShowMenubar->isChecked());
366 
367 	// TODO: check whether the next line is needed
368 	//Settings::setMixersForSoundMenu(GlobalConfig::instance().getMixersForSoundmenu().values());
369 	Settings::setDefaultCardOnStart(m_defaultCardOnStart);
370 	Settings::setAutoUseMultimediaKeys(m_autouseMultimediaKeys);
371 
372 	const MasterControl &master = Mixer::getGlobalMasterPreferred(false);
373 	Settings::setMasterMixer(master.getCard());
374 	Settings::setMasterMixerDevice(master.getControl());
375 
376 	const QString mixerIgnoreExpression = MixerToolBox::mixerIgnoreExpression();
377 	Settings::setMixerIgnoreExpression(mixerIgnoreExpression);
378 
379 	Settings::self()->save();
380 	qCDebug(KMIX_LOG) << "Base configuration saved";
381 }
382 
saveViewConfig()383 void KMixWindow::saveViewConfig()
384 {
385 	QMap<QString, QStringList> mixerViews;
386 
387 	// The following loop is necessary for the case that the user has hidden all views for a Mixer instance.
388 	// Otherwise we would not save the Meta information (step -2- below for that mixer.
389 	// We also do not save dynamic mixers (e.g. PulseAudio)
390 	for (const Mixer *mixer : qAsConst(Mixer::mixers()))
391 	{
392 		mixerViews[mixer->id()];		// just insert a map entry
393 	}
394 
395 // -1- Save the views themselves
396 	for (int i = 0; i < m_wsMixers->count(); ++i)
397 	{
398 		QWidget *w = m_wsMixers->widget(i);
399 		KMixerWidget *mw = qobject_cast<KMixerWidget *>(w);
400 		if (mw!=nullptr)
401 		{
402 			// Here also Views are saved. even for Mixers that are closed. This is necessary when unplugging cards.
403 			// Otherwise the user will be confused afer re-plugging the card (as the config was not saved).
404 			mw->saveConfig(Settings::self()->config());
405 			// add the view to the corresponding mixer list, so we can save a views-per-mixer list below
406 //			if (!mw->mixer()->isDynamic())
407 //			{
408 				QStringList& qsl = mixerViews[mw->mixer()->id()];
409 				qsl.append(mw->getGuiprof()->getId());
410 //			}
411 		}
412 	}
413 
414 	// -2- Save Meta-Information (which views, and in which order). views-per-mixer list
415 	KConfigGroup pconfig(KSharedConfig::openConfig(), "Profiles");
416 	QMap<QString, QStringList>::const_iterator itEnd = mixerViews.constEnd();
417 	for (QMap<QString, QStringList>::const_iterator it = mixerViews.constBegin(); it != itEnd; ++it)
418 	{
419 		const QString& mixerProfileKey = it.key(); // this is actually some mixer->id()
420 		const QStringList& qslProfiles = it.value();
421 		pconfig.writeEntry(mixerProfileKey, qslProfiles);
422 		qCDebug(KMIX_LOG)
423 		<< "Save Profile List for " << mixerProfileKey << ", number of views is " << qslProfiles.count();
424 	}
425 
426 	qCDebug(KMIX_LOG)
427 	<< "View configuration saved";
428 }
429 
430 /**
431  * Stores the volumes of all mixers  Can be restored via loadVolumes() or
432  * the kmixctrl application.
433  */
saveVolumes()434 void KMixWindow::saveVolumes()
435 {
436 	saveVolumes(QString());
437 }
438 
saveVolumes(const QString & postfix)439 void KMixWindow::saveVolumes(const QString &postfix)
440 {
441 	const QString& kmixctrlRcFilename = getKmixctrlRcFilename(postfix);
442 	KConfig *cfg = new KConfig(kmixctrlRcFilename);
443 	for (int i = 0; i < Mixer::mixers().count(); ++i)
444 	{
445 		Mixer *mixer = (Mixer::mixers())[i];
446 		if (mixer->isOpen())
447 		{ // protect from unplugged devices (better do *not* save them)
448 			mixer->volumeSave(cfg);
449 		}
450 	}
451 	cfg->sync();
452 	delete cfg;
453 	qCDebug(KMIX_LOG)
454 	<< "Volume configuration saved";
455 }
456 
getKmixctrlRcFilename(const QString & postfix)457 QString KMixWindow::getKmixctrlRcFilename(const QString &postfix)
458 {
459 	QString kmixctrlRcFilename("kmixctrlrc");
460 	if (!postfix.isEmpty())
461 	{
462 		kmixctrlRcFilename.append(".").append(postfix);
463 	}
464 	return kmixctrlRcFilename;
465 }
466 
467 
loadAndInitConfig(bool reset)468 void KMixWindow::loadAndInitConfig(bool reset)
469 {
470 	if (!reset) loadBaseConfig();
471 }
472 
473 
loadBaseConfig()474 void KMixWindow::loadBaseConfig()
475 {
476 	m_startVisible = Settings::visible();
477 	m_multiDriverMode = Settings::multiDriver();
478 	m_defaultCardOnStart = Settings::defaultCardOnStart();
479 	m_configVersion = Settings::configVersion();
480 	// WARNING Don't overwrite m_configVersion with the "correct" value, before having it
481 	// evaluated. Better only write that in saveBaseConfig()
482 	m_autouseMultimediaKeys = Settings::autoUseMultimediaKeys();
483 	QString mixerMasterCard = Settings::masterMixer();
484 	QString masterDev = Settings::masterMixerDevice();
485 	Mixer::setGlobalMaster(mixerMasterCard, masterDev, true);
486 
487 	QString mixerIgnoreExpression = Settings::mixerIgnoreExpression();
488 	if (!mixerIgnoreExpression.isEmpty()) MixerToolBox::setMixerIgnoreExpression(mixerIgnoreExpression);
489 
490 	// The global volume step setting.
491 	const int volumePercentageStep = Settings::volumePercentageStep();
492 	if (volumePercentageStep>0) Volume::setVolumeStep(volumePercentageStep);
493 
494 	// The following log is very helpful in bug reports. Please keep it.
495 	m_backendFilter = Settings::backends();
496 	qCDebug(KMIX_LOG) << "Backends from settings" << m_backendFilter;
497 
498 	// show/hide menu bar
499 	bool showMenubar = Settings::menubar();
500 	if (_actionShowMenubar!=nullptr) _actionShowMenubar->setChecked(showMenubar);
501 }
502 
503 /**
504  * Loads the volumes of all mixers from kmixctrlrc.
505  * In other words:
506  * Restores the default volumes as stored via saveVolumes() or the
507  * execution of "kmixctrl --save"
508  */
509 
loadVolumes()510 void KMixWindow::loadVolumes()
511 {
512 	loadVolumes(QString());
513 }
514 
loadVolumes(QString postfix)515 void KMixWindow::loadVolumes(QString postfix)
516 {
517 	qCDebug(KMIX_LOG)
518 	<< "About to load config (Volume)";
519 	const QString& kmixctrlRcFilename = getKmixctrlRcFilename(postfix);
520 
521 	KConfig *cfg = new KConfig(kmixctrlRcFilename);
522 	for (int i = 0; i < Mixer::mixers().count(); ++i)
523 	{
524 		Mixer *mixer = (Mixer::mixers())[i];
525 		mixer->volumeLoad(cfg);
526 	}
527 	delete cfg;
528 }
529 
recreateGUIwithSavingView()530 void KMixWindow::recreateGUIwithSavingView()
531 {
532 	recreateGUI(true, false);
533 }
534 
recreateGUI(bool saveConfig,bool reset)535 void KMixWindow::recreateGUI(bool saveConfig, bool reset)
536 {
537 	recreateGUI(saveConfig, QString(), false, reset);
538 }
539 
540 /**
541  * Create or recreate the Mixer GUI elements
542  *
543  * @param saveConfig  Whether to save all View configurations before recreating
544  * @param forceNewTab To enforce opening a new tab, even when the profileList in the kmixrc is empty.
545  *                    It should only be set to "true" in case of a Hotplug (because then the user definitely expects a new Tab to show).
546  */
recreateGUI(bool saveConfig,const QString & mixerId,bool forceNewTab,bool reset)547 void KMixWindow::recreateGUI(bool saveConfig, const QString& mixerId, bool forceNewTab, bool reset)
548 {
549 	// -1- Remember which of the tabs is currently selected for restoration for re-insertion
550 	int oldTabPosition = m_wsMixers->currentIndex();
551 
552 	if (!reset && saveConfig)
553 		saveViewConfig();  // save the state before recreating
554 
555 	// -2- RECREATE THE ALREADY EXISTING TABS **********************************
556 	QHash<const Mixer *, bool> mixerHasProfile;
557 
558 // -2a- Build a list of all active profiles in the main window (that means: from all tabs)
559 	QList<GUIProfile*> activeGuiProfiles;
560 	for (int i = 0; i < m_wsMixers->count(); ++i)
561 	{
562 		KMixerWidget* kmw = dynamic_cast<KMixerWidget*>(m_wsMixers->widget(i));
563 		if (kmw)
564 		{
565 			activeGuiProfiles.append(kmw->getGuiprof());
566 		}
567 	}
568 
569 	for (const GUIProfile *guiprof : qAsConst(activeGuiProfiles))
570 	{
571 		const Mixer *mixer = Mixer::findMixer(guiprof->getMixerId());
572 		if (mixer==nullptr)
573 		{
574 			qCCritical(KMIX_LOG) << "MixerToolBox::find() hasn't found the Mixer for the profile " << guiprof->getId();
575 			continue;
576 		}
577 		mixerHasProfile[mixer] = true;
578 
579 		KMixerWidget* kmw = findKMWforTab(guiprof->getId());
580 		if ( kmw == 0 )
581 		{
582 			// does not yet exist => create
583 			addMixerWidget(mixer->id(), guiprof->getId(), -1);
584 		}
585 		else
586 		{
587 			// did exist => remove and insert new guiprof at old position
588 			int indexOfTab = m_wsMixers->indexOf(kmw);
589 			if ( indexOfTab != -1 ) m_wsMixers->removeTab(indexOfTab);
590 			delete kmw;
591 			addMixerWidget(mixer->id(), guiprof->getId(), indexOfTab);
592 		}
593 	} // Loop over all GUIProfile's
594 
595 
596 
597 	// -3- ADD TABS FOR Mixer instances that have no tab yet **********************************
598 	KConfigGroup pconfig(KSharedConfig::openConfig(), "Profiles");
599 	for (const Mixer *mixer : qAsConst(Mixer::mixers()))
600 	{
601 		if ( mixerHasProfile.contains(mixer))
602 		{
603 			continue;  // OK, this mixer already has a profile => skip it
604 		}
605 
606 
607 		// =========================================================================================
608 		// No TAB YET => This should mean KMix is just started, or the user has just plugged in a card
609 
610 		{
611 			GUIProfile* guiprof = 0;
612 			if (reset)
613 			{
614 				guiprof = GUIProfile::find(mixer, QString("default"), false, true); // ### Card unspecific profile ###
615 			}
616 
617 			if ( guiprof != 0 )
618 			{
619 				guiprof->setDirty();  // All fallback => dirty
620 				addMixerWidget(mixer->id(), guiprof->getId(), -1);
621 				continue;
622 			}
623 		}
624 
625 
626 		// =========================================================================================
627 		// The trivial cases have not added anything => Look at [Profiles] in config file
628 
629 		QStringList	profileList = pconfig.readEntry( mixer->id(), QStringList() );
630 		bool allProfilesRemovedByUser = pconfig.hasKey(mixer->id()) && profileList.isEmpty();
631 		if (allProfilesRemovedByUser)
632 		{
633 			continue; // User has explicitly hidden the views => do no further checks
634 		}
635 
636 		{
637 			bool atLeastOneProfileWasAdded = false;
638 
639 			for (const QString &profileId : qAsConst(profileList))
640 			{
641 				// This handles the profileList form the kmixrc
642 				qCDebug(KMIX_LOG) << "Searching for GUI profile" << profileId;
643 				GUIProfile* guiprof = GUIProfile::find(mixer, profileId, true, false);// ### Card specific profile ###
644 
645 				if (guiprof==nullptr)
646 				{
647 					qCWarning(KMIX_LOG) << "Cannot load profile" << profileId;
648 					if (profileId.startsWith(QLatin1String("MPRIS2.")))
649 					{
650 						const QString fallbackProfileId = "MPRIS2.default";
651 						qCDebug(KMIX_LOG) << "For MPRIS2 falling back to" << fallbackProfileId;
652 						guiprof = GUIProfile::find(mixer, fallbackProfileId, true, false);
653 					}
654 				}
655 
656 				if (guiprof!=nullptr)
657 				{
658 					addMixerWidget(mixer->id(), guiprof->getId(), -1);
659 					atLeastOneProfileWasAdded = true;
660 				}
661 			}
662 
663 			if (atLeastOneProfileWasAdded)
664 			{
665 				// Continue
666 				continue;
667 			}
668 		}
669 
670 		// =========================================================================================
671 		// Neither trivial cases have added something, nor the anything => Look at [Profiles] in config file
672 
673 		// The we_need_a_fallback case is a bit tricky. Please ask the author (cesken) before even considering to change the code.
674 		bool mixerIdMatch = mixerId.isEmpty() || (mixer->id() == mixerId);
675 		bool thisMixerShouldBeForced = forceNewTab && mixerIdMatch;
676 		bool we_need_a_fallback = mixerIdMatch && thisMixerShouldBeForced;
677 		if ( we_need_a_fallback )
678 		{
679 			// The profileList was empty or nothing could be loaded
680 			//     (Hint: This means the user cannot hide a device completely
681 
682 			// Lets try a bunch of fallback strategies:
683 			qCDebug(KMIX_LOG) << "Attempting to find a card-specific GUI Profile for the mixer " << mixer->id();
684 			GUIProfile* guiprof = GUIProfile::find(mixer, QString("default"), false, false);// ### Card specific profile ###
685 			if ( guiprof == 0 )
686 			{
687 				qCDebug(KMIX_LOG) << "Not found. Attempting to find a generic GUI Profile for the mixer " << mixer->id();
688 				guiprof = GUIProfile::find(mixer, QString("default"), false, true); // ### Card unspecific profile ###
689 			}
690 			if ( guiprof == 0)
691 			{
692 				qCDebug(KMIX_LOG) << "Using fallback GUI Profile for the mixer " << mixer->id();
693 				// This means there is neither card specific nor card unspecific profile
694 				// This is the case for some backends (as they don't ship profiles).
695 				guiprof = GUIProfile::fallbackProfile(mixer);
696 			}
697 
698 			if ( guiprof != 0 )
699 			{
700 				guiprof->setDirty();  // All fallback => dirty
701 				addMixerWidget(mixer->id(), guiprof->getId(), -1);
702 			}
703 			else
704 			{
705 				qCCritical(KMIX_LOG) << "Cannot use ANY profile (including Fallback) for mixer " << mixer->id() << " . This is impossible, and thus this mixer can NOT be used.";
706 			}
707 
708 		}
709 	}
710 	mixerHasProfile.clear();
711 
712 	// -4- FINALIZE **********************************
713 	if (m_wsMixers->count() > 0)
714 	{
715 		if (oldTabPosition >= 0)
716 		{
717 			m_wsMixers->setCurrentIndex(oldTabPosition);
718 		}
719 		bool dockingSucceded = updateDocking();
720 		if (!dockingSucceded && !Mixer::mixers().empty())
721 		{
722 			show(); // avoid invisible and inaccessible main window
723 		}
724 	}
725 	else
726 	{
727 		// No soundcard found. Do not complain, but sit in the background, and wait for newly plugged soundcards.
728 		updateDocking();  // -<- removes the DockIcon
729 		hide();
730 	}
731 
732 }
733 
734 KMixerWidget*
findKMWforTab(const QString & kmwId)735 KMixWindow::findKMWforTab(const QString& kmwId)
736 {
737 	for (int i = 0; i < m_wsMixers->count(); ++i)
738 	{
739 		KMixerWidget *kmw = qobject_cast<KMixerWidget *>(m_wsMixers->widget(i));
740 		if (kmw->getGuiprof()->getId() == kmwId)
741 		{
742 			return kmw;
743 		}
744 	}
745 	return 0;
746 }
747 
newView()748 void KMixWindow::newView()
749 {
750 	if (Mixer::mixers().empty())
751 	{
752 		qCCritical(KMIX_LOG) << "Trying to create a View, but no Mixer exists";
753 		return; // should never happen
754 	}
755 
756 	Mixer *mixer = Mixer::mixers()[0];
757 	QPointer<DialogAddView> dav = new DialogAddView(this, mixer);
758 	int ret = dav->exec();
759 
760 	// TODO: it is pointless using a smart pointer for the dialogue
761 	// (which is good practice) here and then not checking it!
762 	if (QDialog::Accepted == ret)
763 	{
764 		QString profileName = dav->getresultViewName();
765 		QString mixerId = dav->getresultMixerId();
766 		mixer = Mixer::findMixer(mixerId);
767 		qCDebug(KMIX_LOG)
768 		<< ">>> mixer = " << mixerId << " -> " << mixer;
769 
770 		GUIProfile* guiprof = GUIProfile::find(mixer, profileName, false, false);
771 		if (guiprof == nullptr)
772 		{
773 			guiprof = GUIProfile::find(mixer, profileName, false, true);
774 		}
775 
776 		if (guiprof == nullptr)
777 		{
778 			KMessageBox::sorry(this, i18n("Cannot add view - GUIProfile is invalid."), i18n("Error"));
779 		}
780 		else
781 		{
782 			bool ret = addMixerWidget(mixer->id(), guiprof->getId(), -1);
783 			if (!ret)
784 			{
785 				KMessageBox::sorry(this, i18n("Cannot add view - View already exists."), i18n("Error"));
786 			}
787 		}
788 
789 		delete dav;
790 	}
791 
792 	//qCDebug(KMIX_LOG) << "Exit";
793 }
794 
795 /**
796  * Save the view and close it
797  *
798  * @arg idx The index in the TabWidget
799  */
saveAndCloseView(int idx)800 void KMixWindow::saveAndCloseView(int idx)
801 {
802 	qCDebug(KMIX_LOG)
803 	<< "Enter";
804 	QWidget *w = m_wsMixers->widget(idx);
805 	KMixerWidget* kmw = ::qobject_cast<KMixerWidget*>(w);
806 	if (kmw)
807 	{
808 		kmw->saveConfig(Settings::self()->config()); // -<- This alone is not enough, as I need to save the META information as well. Thus use saveViewConfig() below
809 		m_wsMixers->removeTab(idx);
810 		updateTabsClosable();
811 		saveViewConfig();
812 		delete kmw;
813 	}
814 	qCDebug(KMIX_LOG)
815 	<< "Exit";
816 }
817 
fixConfigAfterRead()818 void KMixWindow::fixConfigAfterRead()
819 {
820 	unsigned int configVersion = Settings::configVersion();
821 	if (configVersion < 3)
822 	{
823 		// Fix the "double Base" bug, by deleting all groups starting with "View.Base.Base.".
824 		// The group has been copied over by KMixToolBox::loadView() for all soundcards, so
825 		// we should be fine now
826 		QStringList cfgGroups = KSharedConfig::openConfig()->groupList();
827 		QStringListIterator it(cfgGroups);
828 		while (it.hasNext())
829 		{
830 			QString groupName = it.next();
831 			if (groupName.indexOf("View.Base.Base") == 0)
832 			{
833 				qCDebug(KMIX_LOG) << "Fixing group " << groupName;
834 				KConfigGroup buggyDevgrpCG(KSharedConfig::openConfig(), groupName);
835 				buggyDevgrpCG.deleteGroup();
836 			} // remove buggy group
837 		} // for all groups
838 	} // if config version < 3
839 }
840 
841 
plugged(const char * driverName,const QString & udi,int dev)842 void KMixWindow::plugged(const char *driverName, const QString &udi, int dev)
843 {
844 	qCDebug(KMIX_LOG) << "dev" << dev << "driver" << driverName << "udi" << udi;
845 	Mixer *mixer = new Mixer(QString::fromLocal8Bit(driverName), dev);
846 	if (mixer!=nullptr)
847 	{
848 		if (MixerToolBox::possiblyAddMixer(mixer))
849 		{
850 			qCDebug(KMIX_LOG) << "adding mixer id" << mixer->id() << "name" << mixer->readableName();
851 			recreateGUI(true, mixer->id(), true, false);
852 		}
853 		else qCWarning(KMIX_LOG) << "Cannot add mixer to GUI";
854 	}
855 }
856 
857 
unplugged(const QString & udi)858 void KMixWindow::unplugged(const QString &udi)
859 {
860 	qCDebug(KMIX_LOG) << "udi" << udi;
861 	for (int i = 0; i < Mixer::mixers().count(); ++i)
862 	{
863 		Mixer *mixer = (Mixer::mixers())[i];
864 		// qCDebug(KMIX_LOG) << "Try Match with:" << mixer->udi();
865 		if (mixer->udi() == udi)
866 		{
867 			qCDebug(KMIX_LOG) << "Removing mixer";
868 			bool globalMasterMixerDestroyed = (mixer == Mixer::getGlobalMasterMixer());
869 
870 			// Part 1: Remove tab from GUI
871 			for (int i = 0; i < m_wsMixers->count(); ++i)
872 			{
873 				QWidget *w = m_wsMixers->widget(i);
874 				KMixerWidget* kmw = ::qobject_cast<KMixerWidget*>(w);
875 				if (kmw && kmw->mixer() == mixer)
876 				{
877 					saveAndCloseView(i);
878 					i = -1; // Restart loop from scratch (indices are most likely invalidated at removeTab() )
879 				}
880 			}
881 
882 			// Part 2: Remove mixer from known list
883 			MixerToolBox::removeMixer(mixer);
884 
885 			// Part 3: Check whether the Global Master disappeared,
886 			// and select a new one if necessary
887 			shared_ptr<MixDevice> md = Mixer::getGlobalMasterMD();
888 			if (globalMasterMixerDestroyed || md.get() == 0)
889 			{
890 				// We don't know what the global master should be now.
891 				// So lets play stupid, and just select the recommended master of the first device
892 				if (Mixer::mixers().count() > 0)
893 				{
894 					shared_ptr<MixDevice> master = ((Mixer::mixers())[0])->getLocalMasterMD();
895 					if (master.get() != 0)
896 					{
897 						QString localMaster = master->id();
898 						Mixer::setGlobalMaster(((Mixer::mixers())[0])->id(), localMaster, false);
899 
900 						QString text;
901 						text =
902 								i18n(
903 										"The soundcard containing the master device was unplugged. Changing to control %1 on card %2.",
904 										master->readableName(), ((Mixer::mixers())[0])->readableName());
905 						KMixToolBox::notification("MasterFallback", text);
906 					}
907 				}
908 			}
909 			if (Mixer::mixers().count() == 0)
910 			{
911 				QString text;
912 				text = i18n("The last soundcard was unplugged.");
913 				KMixToolBox::notification("MasterFallback", text);
914 			}
915 			recreateGUI(true, false);
916 			break;
917 		}
918 	}
919 
920 }
921 
922 
923 /**
924  *
925  */
profileExists(QString guiProfileId)926 bool KMixWindow::profileExists(QString guiProfileId)
927 {
928 	for (int i = 0; i < m_wsMixers->count(); ++i)
929 	{
930 		KMixerWidget* kmw = dynamic_cast<KMixerWidget*>(m_wsMixers->widget(i));
931 		if (kmw && kmw->getGuiprof()->getId() == guiProfileId)
932 			return true;
933 	}
934 	return false;
935 }
936 
addMixerWidget(const QString & mixer_ID,QString guiprofId,int insertPosition)937 bool KMixWindow::addMixerWidget(const QString& mixer_ID, QString guiprofId, int insertPosition)
938 {
939 	qCDebug(KMIX_LOG)
940 	<< "Add " << guiprofId;
941 	GUIProfile* guiprof = GUIProfile::find(guiprofId);
942 	if (guiprof != 0 && profileExists(guiprof->getId())) // TODO Bad place. Should be checked in the add-tab-dialog
943 		return false; // already present => don't add again
944 	Mixer *mixer = Mixer::findMixer(mixer_ID);
945 	if (mixer == 0)
946 		return false; // no such Mixer
947 
948 	//       qCDebug(KMIX_LOG) << "KMixWindow::addMixerWidget() " << mixer_ID << " is being added";
949 	ViewBase::ViewFlags vflags = ViewBase::HasMenuBar;
950 	if ((_actionShowMenubar == 0) || _actionShowMenubar->isChecked())
951 		vflags |= ViewBase::MenuBarVisible;
952 	KMixerWidget *kmw = new KMixerWidget(mixer, this, vflags, guiprofId, actionCollection());
953 	/* A newly added mixer will automatically added at the top
954 	 * and thus the window title is also set appropriately */
955 
956 	/*
957 	 * Skip the name from the profile for now. I would at least have to do the '&' quoting for the tab label. But I am
958 	 * also not 100% sure whether the current name from the profile is any good - it does (likely) not even contain the
959 	 * card ID. This means you cannot distinguish between cards with an identical name.
960 	 */
961 //  QString tabLabel = guiprof->getName();
962 //  if (tabLabel.isEmpty())
963 //	  QString tabLabel = kmw->mixer()->readableName(true);
964 	QString tabLabel = kmw->mixer()->readableName(true);
965 
966 	m_dontSetDefaultCardOnStart = true; // inhibit implicit setting of m_defaultCardOnStart
967 
968 	if (insertPosition == -1)
969 		m_wsMixers->addTab(kmw, tabLabel);
970 	else
971 		m_wsMixers->insertTab(insertPosition, kmw, tabLabel);
972 
973 	if (kmw->getGuiprof()->getId() == m_defaultCardOnStart)
974 	{
975 		m_wsMixers->setCurrentWidget(kmw);
976 	}
977 
978 	updateTabsClosable();
979 	m_dontSetDefaultCardOnStart = false;
980 
981 	kmw->loadConfig(Settings::self()->config());
982 	// Now force to read for new tabs, especially after hotplug. Note: Doing it here is bad design and possibly
983 	// obsolete, as the backend should take care of updating itself.
984 	kmw->mixer()->readSetFromHWforceUpdate();
985 	return true;
986 }
987 
updateTabsClosable()988 void KMixWindow::updateTabsClosable()
989 {
990 	// Pulseaudio runs with 4 fixed tabs - don't allow to close them.
991 	// Also do not allow to close the last view
992 	m_wsMixers->setTabsClosable(!Mixer::pulseaudioPresent() && m_wsMixers->count() > 1);
993 }
994 
queryClose()995 bool KMixWindow::queryClose()
996 {
997 	if (Settings::showDockWidget() && !qApp->isSavingSession())
998 	{
999 		// Hide (don't close and destroy), if docking is enabled. Except when session saving (shutdown) is in process.
1000 		hide();
1001 		return false;
1002 	}
1003 	else
1004 	{
1005 		// Accept the close, if:
1006 		//     The user has disabled docking
1007 		// or  SessionSaving() is running
1008 		//         qCDebug(KMIX_LOG) << "close";
1009 		return true;
1010 	}
1011 }
1012 
hideOrClose()1013 void KMixWindow::hideOrClose()
1014 {
1015 	if (Settings::showDockWidget() && m_dockWidget!=nullptr)
1016 	{
1017 		// we can hide if there is a dock widget
1018 		hide();
1019 	}
1020 	else
1021 	{
1022 		//  if there is no dock widget, we will quit
1023 		quit();
1024 	}
1025 }
1026 
1027 // internal helper to prevent code duplication in slotIncreaseVolume and slotDecreaseVolume
increaseOrDecreaseVolume(bool increase)1028 void KMixWindow::increaseOrDecreaseVolume(bool increase)
1029 {
1030 	Mixer* mixer = Mixer::getGlobalMasterMixer(); // only needed for the awkward construct below
1031 	if (mixer == 0)
1032 		return; // e.g. when no soundcard is available
1033 	shared_ptr<MixDevice> md = Mixer::getGlobalMasterMD();
1034 	if (md.get() == 0)
1035 		return; // shouldn't happen, but lets play safe
1036 
1037 	Volume::VolumeTypeFlag volumeType = md->playbackVolume().hasVolume() ? Volume::Playback : Volume::Capture;
1038 	md->increaseOrDecreaseVolume(!increase, volumeType);
1039 	md->mixer()->commitVolumeChange(md);
1040 
1041 	showVolumeDisplay();
1042 }
1043 
slotIncreaseVolume()1044 void KMixWindow::slotIncreaseVolume()
1045 {
1046 	increaseOrDecreaseVolume(true);
1047 }
1048 
slotDecreaseVolume()1049 void KMixWindow::slotDecreaseVolume()
1050 {
1051 	increaseOrDecreaseVolume(false);
1052 }
1053 
showVolumeDisplay()1054 void KMixWindow::showVolumeDisplay()
1055 {
1056 	Mixer* mixer = Mixer::getGlobalMasterMixer();
1057 	if (mixer == 0)
1058 		return; // e.g. when no soundcard is available
1059 	shared_ptr<MixDevice> md = Mixer::getGlobalMasterMD();
1060 	if (md.get() == 0)
1061 		return; // shouldn't happen, but lets play safe
1062 
1063 	if (Settings::showOSD())
1064 	{
1065         QDBusMessage msg = QDBusMessage::createMethodCall(
1066             "org.kde.plasmashell",
1067             "/org/kde/osdService",
1068             "org.kde.osdService",
1069             "volumeChanged"
1070         );
1071 
1072         int currentVolume = 0;
1073         if (!md->isMuted()) {
1074             currentVolume = md->playbackVolume().getAvgVolumePercent(Volume::MALL);
1075         }
1076 
1077         msg.setArguments(QList<QVariant>() << currentVolume);
1078 
1079         QDBusConnection::sessionBus().asyncCall(msg);
1080     }
1081 }
1082 
1083 /**
1084  * Mutes the global master. (SLOT)
1085  */
slotMute()1086 void KMixWindow::slotMute()
1087 {
1088 	Mixer* mixer = Mixer::getGlobalMasterMixer();
1089 	if (mixer == 0)
1090 		return; // e.g. when no soundcard is available
1091 	shared_ptr<MixDevice> md = Mixer::getGlobalMasterMD();
1092 	if (md.get() == 0)
1093 		return; // shouldn't happen, but lets play safe
1094 	md->toggleMute();
1095 	mixer->commitVolumeChange(md);
1096 	showVolumeDisplay();
1097 }
1098 
quit()1099 void KMixWindow::quit()
1100 {
1101 	//     qCDebug(KMIX_LOG) << "quit";
1102 	qApp->quit();
1103 }
1104 
1105 /**
1106  * Shows the configuration dialog, with the "General" tab opened.
1107  */
showSettings()1108 void KMixWindow::showSettings()
1109 {
1110 	KMixPrefDlg::instance()->showAtPage(KMixPrefDlg::PageGeneral);
1111 }
1112 
1113 
showHelp()1114 void KMixWindow::showHelp()
1115 {
1116 	actionCollection()->action("help_contents")->trigger();
1117 }
1118 
showAbout()1119 void KMixWindow::showAbout()
1120 {
1121 	actionCollection()->action("help_about_app")->trigger();
1122 }
1123 
1124 
1125 /**
1126  * Apply the Preferences from the preferences dialog. Depending on what has been changed,
1127  * the corresponding announcements are made.
1128  */
applyPrefs(KMixPrefDlg::PrefChanges changes)1129 void KMixWindow::applyPrefs(KMixPrefDlg::PrefChanges changes)
1130 {
1131 	qCDebug(KMIX_LOG) << "changes" << changes;
1132 
1133 	if (changes & KMixPrefDlg::ChangedControls)
1134 	{
1135 		// These might need a complete relayout => announce a ControlList change to rebuild everything
1136 		ControlManager::instance().announce(QString(), ControlManager::ControlList, QString("Preferences Dialog"));
1137 	}
1138 	else if (changes & KMixPrefDlg::ChangedMaster)
1139 	{
1140 		// This announce was originally made in KMixPrefDlg::updateSettings().
1141 		// It is treated as equivalent to ControlManager::ControlList by
1142 		// the system tray popup, hence the 'else' here.
1143 		ControlManager::instance().announce(QString(), ControlManager::MasterChanged, QString("Select Backends Dialog"));
1144 	}
1145 	if (changes & KMixPrefDlg::ChangedGui)
1146 	{
1147 		ControlManager::instance().announce(QString(), ControlManager::GUI, QString("Preferences Dialog"));
1148 	}
1149 
1150 	//this->repaint(); // make KMix look fast (saveConfig() often uses several seconds)
1151 	qApp->processEvents();
1152 
1153 	// Remove saveConfig() IF aa changes have been migrated to GlobalConfig.
1154 	// Currently there is still stuff like "show menu bar".
1155 	saveConfig();
1156 }
1157 
1158 
toggleMenuBar()1159 void KMixWindow::toggleMenuBar()
1160 {
1161 	menuBar()->setVisible(_actionShowMenubar->isChecked());
1162 }
1163 
slotKdeAudioSetupExec()1164 void KMixWindow::slotKdeAudioSetupExec()
1165 {
1166     forkExec(QStringList() << "kcmshell5" << "kcm_pulseaudio");
1167 }
1168 
forkExec(const QStringList & args)1169 void KMixWindow::forkExec(const QStringList& args)
1170 {
1171    int pid = KProcess::startDetached(args);
1172    if (pid == 0)
1173    {
1174        KMessageBox::error(this, i18n("The helper application is either not installed or not working.\n\n%1",
1175                          args.join(QLatin1String(" "))));
1176    }
1177 }
1178 
slotConfigureCurrentView()1179 void KMixWindow::slotConfigureCurrentView()
1180 {
1181 	KMixerWidget *mw = qobject_cast<KMixerWidget *>(m_wsMixers->currentWidget());
1182 	ViewBase* view = 0;
1183 	if (mw)
1184 		view = mw->currentView();
1185 	if (view)
1186 		view->configureView();
1187 }
1188 
slotSelectMasterClose(QObject *)1189 void KMixWindow::slotSelectMasterClose(QObject*)
1190 {
1191 	m_dsm = 0;
1192 }
1193 
slotSelectMaster()1194 void KMixWindow::slotSelectMaster()
1195 {
1196 	const Mixer *mixer = Mixer::getGlobalMasterMixer();
1197 	if (mixer!=nullptr)
1198 	{
1199 		if (!m_dsm)
1200 		{
1201 			m_dsm = new DialogSelectMaster(mixer, this);
1202 			connect(m_dsm, SIGNAL(destroyed(QObject*)), this, SLOT(slotSelectMasterClose(QObject*)));
1203 			m_dsm->setAttribute(Qt::WA_DeleteOnClose, true);
1204 			m_dsm->show();
1205 		}
1206 		m_dsm->raise();
1207 		m_dsm->activateWindow();
1208 	}
1209 	else
1210 	{
1211 		KMessageBox::error(nullptr, KMixToolBox::noDevicesWarningString());
1212 	}
1213 }
1214 
newMixerShown(int)1215 void KMixWindow::newMixerShown(int /*tabIndex*/)
1216 {
1217 	KMixerWidget *kmw = qobject_cast<KMixerWidget *>(m_wsMixers->currentWidget());
1218 	if (kmw!=nullptr)
1219 	{
1220 		// I am using the app name as a PREFIX, as KMix is a single window app, and it is
1221 		// more helpful to the user to see "KDE Mixer" in a window list than a possibly cryptic
1222 		// soundcard name like "HDA ATI SB".
1223 		// Reformatted for KF5 so as to not say "KDE"
1224 		// and so that there are not two different dashes.
1225 		setWindowTitle(i18n("Mixer (%1)", kmw->mixer()->readableName()));
1226 		if (!m_dontSetDefaultCardOnStart)
1227 			m_defaultCardOnStart = kmw->getGuiprof()->getId();
1228 		// As switching the tab does NOT mean switching the master card, we do not need to update dock icon here.
1229 		// It would lead to unnecesary flickering of the (complete) dock area.
1230 
1231 		// We only show the "Configure Channels..." menu item if the mixer is not dynamic
1232 		ViewBase* view = kmw->currentView();
1233 		QAction* action = actionCollection()->action("toggle_channels_currentview");
1234 		if (view && action)
1235 			action->setVisible(!view->isDynamic());
1236 	}
1237 }
1238