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