2 * KMix -- KDE's full featured mini mixer
4 * Copyright 1996-2014 The KMix authors. Maintainer: Christian Esken <esken@kde.org>
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.
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.
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.
21 #include "apps/kmix.h"
23 // include files for QT
26 #include <QDesktopWidget>
27 #include <QPushButton>
28 #include <qradiobutton.h>
34 // include files for KDE
35 #include <KConfigSkeleton>
36 #include <kcombobox.h>
37 #include <kiconloader.h>
38 #include <kmessagebox.h>
42 #include <kapplication.h>
43 #include <kstandardaction.h>
45 #include <khelpmenu.h>
47 #include <kxmlguifactory.h>
49 #include <kactioncollection.h>
50 #include <ktoggleaction.h>
51 #include <ksystemeventfilter.h>
55 #include "gui/guiprofile.h"
56 #include "core/ControlManager.h"
57 #include "core/GlobalConfig.h"
58 #include "core/MasterControl.h"
59 #include "core/MediaController.h"
60 #include "core/mixertoolbox.h"
61 #include "core/kmixdevicemanager.h"
62 #include "gui/kmixerwidget.h"
63 #include "gui/kmixprefdlg.h"
64 #include "gui/kmixdockwidget.h"
65 #include "gui/kmixtoolbox.h"
66 #include "core/version.h"
67 #include "gui/viewdockareapopup.h"
68 #include "gui/dialogaddview.h"
69 #include "gui/dialogselectmaster.h"
70 #include "dbus/dbusmixsetwrapper.h"
71 #include "gui/osdwidget.h"
74 #include <X11/XF86keysym.h>
78 * Constructs a mixer window (KMix main window)
81 static KeyCode s_xf86lowervolume = 0;
82 static KeyCode s_xf86raisevolume = 0;
84 KMixWindow::KMixWindow(bool invisible) :
85 KXmlGuiWindow(0, Qt::WindowContextHelpButtonHint),
86 m_multiDriverMode(false), // -<- I never-ever want the multi-drivermode to be activated by accident
87 m_dockWidget(), m_dsm(0), m_dontSetDefaultCardOnStart(false)
89 setObjectName(QLatin1String("KMixWindow"));
90 // disable delete-on-close because KMix might just sit in the background waiting for cards to be plugged in
91 setAttribute(Qt::WA_DeleteOnClose, false);
93 initActions(); // init actions first, so we can use them in the loadConfig() already
94 loadConfig(); // Load config before initMixer(), e.g. due to "MultiDriver" keyword
95 initActionsLate(); // init actions that require a loaded config
96 KGlobal::locale()->insertCatalog(QLatin1String("kmix-controls"));
99 DBusMixSetWrapper::initialize(this, "/Mixers");
100 MixerToolBox::instance()->initMixer(m_multiDriverMode, m_backendFilter,
102 KMixDeviceManager *theKMixDeviceManager = KMixDeviceManager::instance();
103 initActionsAfterInitMixer(); // init actions that require initialized mixer backend(s).
106 if (m_wsMixers->count() < 1)
108 // Something is wrong. Perhaps a hardware or driver or backend change. Let KMix search harder
109 recreateGUI(false, QString(), true);
112 if (!kapp->isSessionRestored() ) // done by the session manager otherwise
115 theKMixDeviceManager->initHotplug();
116 connect(theKMixDeviceManager, SIGNAL(plugged(const char*,QString,QString&)),
117 SLOT (plugged(const char*,QString,QString&)));
118 connect(theKMixDeviceManager, SIGNAL(unplugged(QString)),
119 SLOT (unplugged(QString)));
120 if (m_startVisible && !invisible)
121 show(); // Started visible
123 connect(kapp, SIGNAL(aboutToQuit()), SLOT(saveConfig()) );
125 ControlManager::instance().addListener(
126 QString(), // All mixers (as the Global master Mixer might change)
127 (ControlChangeType::Type)(ControlChangeType::ControlList | ControlChangeType::MasterChanged),
129 QString("KMixWindow")
132 // Send an initial volume refresh (otherwise all volumes are 0 until the next change)
133 ControlManager::instance().announce(QString(), ControlChangeType::Volume, QString("Startup"));
136 KMixWindow::~KMixWindow()
138 if (m_autouseMultimediaKeys) {
142 AnyModifier, QX11Info::appRootWindow()
148 AnyModifier, QX11Info::appRootWindow()
152 ControlManager::instance().removeListener(this);
157 // -1- Cleanup Memory: clearMixerWidgets
158 while (m_wsMixers->count() != 0)
160 QWidget *mw = m_wsMixers->widget(0);
161 m_wsMixers->removeTab(0);
166 MixerToolBox::instance()->deinitMixer();
168 // -3- Action collection (just to please Valgrind)
169 actionCollection()->clear();
171 // GUIProfile cache should be cleared very very late, as GUIProfile instances are used in the Views, which
172 // means main window and potentially also in the tray popup (at least we might do so in the future).
173 // This place here could be to early, if we would start to GUIProfile outside KMixWIndow, e.g. in the tray popup.
174 // Until we do so, this is the best place to call clearCache(). Later, e.g. in main() would likely be problematic.
176 GUIProfile::clearCache();
183 void KMixWindow::controlsChange(int changeType)
185 ControlChangeType::Type type = ControlChangeType::fromInt(changeType);
188 case ControlChangeType::ControlList:
189 case ControlChangeType::MasterChanged:
194 ControlManager::warnUnexpectedChangeType(type, this);
201 KMixWindow::initActions()
204 KStandardAction::quit(this, SLOT(quit()), actionCollection());
207 _actionShowMenubar = KStandardAction::showMenubar(this, SLOT(toggleMenuBar()),
209 //actionCollection()->addAction( a->objectName(), a );
210 KStandardAction::preferences(this, SLOT(showSettings()), actionCollection());
211 KStandardAction::keyBindings(guiFactory(), SLOT(configureShortcuts()),
213 KAction* action = actionCollection()->addAction("launch_kdesoundsetup");
214 action->setText(i18n("Audio Setup"));
215 connect(action, SIGNAL(triggered(bool)), SLOT(slotKdeAudioSetupExec()));
217 action = actionCollection()->addAction("hwinfo");
218 action->setText(i18n("Hardware &Information"));
219 connect(action, SIGNAL(triggered(bool)), SLOT(slotHWInfo()));
220 action = actionCollection()->addAction("hide_kmixwindow");
221 action->setText(i18n("Hide Mixer Window"));
222 connect(action, SIGNAL(triggered(bool)), SLOT(hideOrClose()));
223 action->setShortcut(QKeySequence(Qt::Key_Escape));
224 action = actionCollection()->addAction("toggle_channels_currentview");
225 action->setText(i18n("Configure &Channels..."));
226 connect(action, SIGNAL(triggered(bool)), SLOT(slotConfigureCurrentView()));
227 action = actionCollection()->addAction("select_master");
228 action->setText(i18n("Select Master Channel..."));
229 connect(action, SIGNAL(triggered(bool)), SLOT(slotSelectMaster()));
231 action = actionCollection()->addAction("save_1");
232 action->setShortcut(KShortcut(Qt::CTRL + Qt::SHIFT + Qt::Key_1));
233 action->setText(i18n("Save volume profile 1"));
234 connect(action, SIGNAL(triggered(bool)), SLOT(saveVolumes1()));
236 action = actionCollection()->addAction("save_2");
237 action->setShortcut(KShortcut(Qt::CTRL + Qt::SHIFT + Qt::Key_2));
238 action->setText(i18n("Save volume profile 2"));
239 connect(action, SIGNAL(triggered(bool)), SLOT(saveVolumes2()));
241 action = actionCollection()->addAction("save_3");
242 action->setShortcut(KShortcut(Qt::CTRL + Qt::SHIFT + Qt::Key_3));
243 action->setText(i18n("Save volume profile 3"));
244 connect(action, SIGNAL(triggered(bool)), SLOT(saveVolumes3()));
246 action = actionCollection()->addAction("save_4");
247 action->setShortcut(KShortcut(Qt::CTRL + Qt::SHIFT + Qt::Key_4));
248 action->setText(i18n("Save volume profile 4"));
249 connect(action, SIGNAL(triggered(bool)), SLOT(saveVolumes4()));
251 action = actionCollection()->addAction("load_1");
252 action->setShortcut(KShortcut(Qt::CTRL + Qt::Key_1));
253 action->setText(i18n("Load volume profile 1"));
254 connect(action, SIGNAL(triggered(bool)), SLOT(loadVolumes1()));
256 action = actionCollection()->addAction("load_2");
257 action->setShortcut(KShortcut(Qt::CTRL + Qt::Key_2));
258 action->setText(i18n("Load volume profile 2"));
259 connect(action, SIGNAL(triggered(bool)), SLOT(loadVolumes2()));
261 action = actionCollection()->addAction("load_3");
262 action->setShortcut(KShortcut(Qt::CTRL + Qt::Key_3));
263 action->setText(i18n("Load volume profile 3"));
264 connect(action, SIGNAL(triggered(bool)), SLOT(loadVolumes3()));
266 action = actionCollection()->addAction("load_4");
267 action->setShortcut(KShortcut(Qt::CTRL + Qt::Key_4));
268 action->setText(i18n("Load volume profile 4"));
269 connect(action, SIGNAL(triggered(bool)), SLOT(loadVolumes4()));
271 osdWidget = new OSDWidget();
273 createGUI(QLatin1String("kmixui.rc"));
277 KMixWindow::initActionsLate()
279 if (m_autouseMultimediaKeys)
281 KAction* globalAction = actionCollection()->addAction("increase_volume");
282 globalAction->setText(i18n("Increase Volume"));
283 connect(globalAction, SIGNAL(triggered(bool)), SLOT(slotIncreaseVolume()));
285 globalAction = actionCollection()->addAction("decrease_volume");
286 globalAction->setText(i18n("Decrease Volume"));
287 connect(globalAction, SIGNAL(triggered(bool)), SLOT(slotDecreaseVolume()));
289 globalAction = actionCollection()->addAction("mute");
290 globalAction->setText(i18n("Mute"));
291 connect(globalAction, SIGNAL(triggered(bool)), SLOT(slotMute()));
293 s_xf86lowervolume = XKeysymToKeycode(QX11Info::display(), XF86XK_AudioLowerVolume);
294 s_xf86raisevolume = XKeysymToKeycode(QX11Info::display(), XF86XK_AudioRaiseVolume);
299 AnyModifier, QX11Info::appRootWindow(), False, GrabModeAsync, GrabModeAsync
305 AnyModifier, QX11Info::appRootWindow(), False, GrabModeAsync, GrabModeAsync
308 KSystemEventFilter::installEventFilter(this);
312 bool KMixWindow::x11Event(XEvent *xevent)
314 if (xevent->type == KeyPress) {
315 // qDebug() << Q_FUNC_INFO << xevent << xevent->xkey.keycode;
316 // qDebug() << Q_FUNC_INFO << XF86XK_AudioLowerVolume << s_xf86lowervolume;
317 // qDebug() << Q_FUNC_INFO << XF86XK_AudioRaiseVolume << s_xf86raisevolume;
318 if (xevent->xkey.keycode == s_xf86lowervolume) {
319 increaseOrDecreaseVolume(false);
321 } else if (xevent->xkey.keycode == s_xf86raisevolume) {
322 increaseOrDecreaseVolume(true);
330 KMixWindow::initActionsAfterInitMixer()
332 QPixmap cornerNewPM = KIconLoader::global()->loadIcon("tab-new", KIconLoader::Toolbar, KIconLoader::SizeSmall);
333 QPushButton* _cornerLabelNew = new QPushButton();
334 _cornerLabelNew->setIcon(cornerNewPM);
335 //cornerLabelNew->setSizePolicy(QSizePolicy());
336 m_wsMixers->setCornerWidget(_cornerLabelNew, Qt::TopLeftCorner);
337 connect(_cornerLabelNew, SIGNAL(clicked()), SLOT (newView()));
341 KMixWindow::initPrefDlg()
343 KMixPrefDlg* prefDlg = KMixPrefDlg::createInstance(this, GlobalConfig::instance());
344 connect(prefDlg, SIGNAL(kmixConfigHasChanged()), SLOT(applyPrefs()));
348 KMixWindow::initWidgets()
350 m_wsMixers = new KTabWidget();
351 m_wsMixers->setDocumentMode(true);
352 setCentralWidget(m_wsMixers);
353 m_wsMixers->setTabsClosable(false);
354 connect(m_wsMixers, SIGNAL(tabCloseRequested(int)),
355 SLOT(saveAndCloseView(int)));
357 QPixmap cornerNewPM = KIconLoader::global()->loadIcon("tab-new",
358 KIconLoader::Toolbar, KIconLoader::SizeSmall);
360 connect(m_wsMixers, SIGNAL(currentChanged(int)), SLOT(newMixerShown(int)));
362 // show menubar if the actions says so (or if the action does not exist)
363 menuBar()->setVisible(
364 (_actionShowMenubar == 0) || _actionShowMenubar->isChecked());
368 KMixWindow::setInitialSize()
370 KConfigGroup config(KGlobal::config(), "Global");
372 // HACK: QTabWidget will bound its sizeHint to 200x200 unless scrollbuttons
373 // are disabled, so we disable them, get a decent sizehint and enable them
375 m_wsMixers->setUsesScrollButtons(false);
376 QSize defSize = sizeHint();
377 m_wsMixers->setUsesScrollButtons(true);
378 QSize size = config.readEntry("Size", defSize);
382 QPoint defPos = pos();
383 QPoint pos = config.readEntry("Position", defPos);
388 void KMixWindow::removeDock()
392 m_dockWidget->deleteLater();
398 * Creates or deletes the KMixDockWidget, depending on whether there is a Mixer instance available.
400 * @returns true, if the docking succeeded. Failure usually means that there
401 * was no suitable mixer control selected.
403 bool KMixWindow::updateDocking()
405 GlobalConfigData& gcd = GlobalConfig::instance().data;
407 if ( !gcd.showDockWidget || Mixer::mixers().isEmpty())
414 m_dockWidget = new KMixDockWidget(this);
419 KMixWindow::saveConfig()
424 #warning We must Sync here, or we will lose configuration data. The reson for that is unknown.
426 // TODO cesken The reason for not writing might be that we have multiple cascaded KConfig objects. I must migrate to KSharedConfig !!!
427 KGlobal::config()->sync();
429 << "Saved config ... sync finished";
432 void KMixWindow::saveBaseConfig()
434 GlobalConfig::instance().writeConfig();
436 KConfigGroup config(KGlobal::config(), "Global");
438 config.writeEntry("Size", size());
439 config.writeEntry("Position", pos());
440 // Cannot use isVisible() here, as in the "aboutToQuit()" case this widget is already hidden.
441 // (Please note that the problem was only there when quitting via Systray - esken).
442 // Using it again, as internal behaviour has changed with KDE4
443 config.writeEntry("Visible", isVisible());
444 config.writeEntry("Menubar", _actionShowMenubar->isChecked());
445 config.writeEntry("Soundmenu.Mixers", GlobalConfig::instance().getMixersForSoundmenu().toList());
447 config.writeEntry("DefaultCardOnStart", m_defaultCardOnStart);
448 config.writeEntry("ConfigVersion", KMIX_CONFIG_VERSION);
449 config.writeEntry("AutoUseMultimediaKeys", m_autouseMultimediaKeys);
451 MasterControl& master = Mixer::getGlobalMasterPreferred();
452 if (master.isValid())
454 config.writeEntry("MasterMixer", master.getCard());
455 config.writeEntry("MasterMixerDevice", master.getControl());
457 QString mixerIgnoreExpression = MixerToolBox::instance()->mixerIgnoreExpression();
458 config.writeEntry("MixerIgnoreExpression", mixerIgnoreExpression);
461 << "Base configuration saved";
464 void KMixWindow::saveViewConfig()
466 QMap<QString, QStringList> mixerViews;
468 // The following loop is necessary for the case that the user has hidden all views for a Mixer instance.
469 // Otherwise we would not save the Meta information (step -2- below for that mixer.
470 // We also do not save dynamic mixers (e.g. PulseAudio)
471 foreach ( Mixer* mixer, Mixer::mixers() )
473 if ( !mixer->isDynamic() )
475 mixerViews[mixer->id()]; // just insert a map entry
479 // -1- Save the views themselves
480 for (int i = 0; i < m_wsMixers->count(); ++i)
482 QWidget *w = m_wsMixers->widget(i);
483 if (w->inherits("KMixerWidget"))
485 KMixerWidget* mw = (KMixerWidget*) w;
486 // Here also Views are saved. even for Mixers that are closed. This is necessary when unplugging cards.
487 // Otherwise the user will be confused afer re-plugging the card (as the config was not saved).
488 mw->saveConfig(KGlobal::config().data());
489 // add the view to the corresponding mixer list, so we can save a views-per-mixer list below
490 if (!mw->mixer()->isDynamic())
492 QStringList& qsl = mixerViews[mw->mixer()->id()];
493 qsl.append(mw->getGuiprof()->getId());
498 // -2- Save Meta-Information (which views, and in which order). views-per-mixer list
499 KConfigGroup pconfig(KGlobal::config(), "Profiles");
500 QMap<QString, QStringList>::const_iterator itEnd = mixerViews.constEnd();
501 for (QMap<QString, QStringList>::const_iterator it = mixerViews.constBegin(); it != itEnd; ++it)
503 const QString& mixerProfileKey = it.key(); // this is actually some mixer->id()
504 const QStringList& qslProfiles = it.value();
505 pconfig.writeEntry(mixerProfileKey, qslProfiles);
507 << "Save Profile List for " << mixerProfileKey << ", number of views is " << qslProfiles.count();
511 << "View configuration saved";
515 * Stores the volumes of all mixers Can be restored via loadVolumes() or
516 * the kmixctrl application.
519 KMixWindow::saveVolumes()
521 saveVolumes(QString());
525 KMixWindow::saveVolumes(QString postfix)
527 const QString& kmixctrlRcFilename = getKmixctrlRcFilename(postfix);
528 KConfig *cfg = new KConfig(kmixctrlRcFilename);
529 for (int i = 0; i < Mixer::mixers().count(); ++i)
531 Mixer *mixer = (Mixer::mixers())[i];
533 { // protect from unplugged devices (better do *not* save them)
534 mixer->volumeSave(cfg);
540 << "Volume configuration saved";
544 KMixWindow::getKmixctrlRcFilename(QString postfix)
546 QString kmixctrlRcFilename("kmixctrlrc");
547 if (!postfix.isEmpty())
549 kmixctrlRcFilename.append(".").append(postfix);
551 return kmixctrlRcFilename;
555 KMixWindow::loadConfig()
559 //loadViewConfig(); // mw->loadConfig() explicitly called always after creating mw.
560 //loadVolumes(); // not in use
562 // create an initial snapshot, so we have a reference of the state before changes through the preferences dialog
563 configDataSnapshot = GlobalConfig::instance().data;
567 KMixWindow::loadBaseConfig()
569 KConfigGroup config(KGlobal::config(), "Global");
571 QList<QString> preferredMixersInSoundMenu;
572 preferredMixersInSoundMenu = config.readEntry("Soundmenu.Mixers", preferredMixersInSoundMenu);
573 GlobalConfig::instance().setMixersForSoundmenu(preferredMixersInSoundMenu.toSet());
575 m_startVisible = config.readEntry("Visible", false);
576 m_multiDriverMode = config.readEntry("MultiDriver", false);
577 m_defaultCardOnStart = config.readEntry("DefaultCardOnStart", "");
578 m_autouseMultimediaKeys = config.readEntry("AutoUseMultimediaKeys", true);
579 QString mixerMasterCard = config.readEntry("MasterMixer", "");
580 QString masterDev = config.readEntry("MasterMixerDevice", "");
581 Mixer::setGlobalMaster(mixerMasterCard, masterDev, true);
582 QString mixerIgnoreExpression = config.readEntry("MixerIgnoreExpression",
584 MixerToolBox::instance()->setMixerIgnoreExpression(mixerIgnoreExpression);
586 // --- Advanced options, without GUI: START -------------------------------------
587 QString volumePercentageStepString = config.readEntry("VolumePercentageStep");
588 if (!volumePercentageStepString.isNull())
590 float volumePercentageStep = volumePercentageStepString.toFloat();
591 if (volumePercentageStep > 0 && volumePercentageStep <= 100)
592 Volume::VOLUME_STEP_DIVISOR = (100 / volumePercentageStep);
595 // --- Advanced options, without GUI: END -------------------------------------
597 m_backendFilter = config.readEntry<>("Backends", QList<QString>());
598 kDebug() << "Backends: " << m_backendFilter;
600 // show/hide menu bar
601 bool showMenubar = config.readEntry("Menubar", true);
603 if (_actionShowMenubar)
604 _actionShowMenubar->setChecked(showMenubar);
608 * Loads the volumes of all mixers from kmixctrlrc.
610 * Restores the default voumes as stored via saveVolumes() or the
611 * execution of "kmixctrl --save"
615 KMixWindow::loadVolumes()
617 loadVolumes(QString());
621 KMixWindow::loadVolumes(QString postfix)
624 << "About to load config (Volume)";
625 const QString& kmixctrlRcFilename = getKmixctrlRcFilename(postfix);
627 KConfig *cfg = new KConfig(kmixctrlRcFilename);
628 for (int i = 0; i < Mixer::mixers().count(); ++i)
630 Mixer *mixer = (Mixer::mixers())[i];
631 mixer->volumeLoad(cfg);
638 KMixWindow::recreateGUIwithSavingView()
645 KMixWindow::recreateGUI(bool saveConfig)
647 recreateGUI(saveConfig, QString(), false);
651 * Create or recreate the Mixer GUI elements
653 * @param saveConfig Whether to save all View configurations before recreating
654 * @param forceNewTab To enforce opening a new tab, even when the profileList in the kmixrc is empty.
655 * It should only be set to "true" in case of a Hotplug (because then the user definitely expects a new Tab to show).
658 KMixWindow::recreateGUI(bool saveConfig, const QString& mixerId,
661 // -1- Remember which of the tabs is currently selected for restoration for re-insertion
662 int oldTabPosition = m_wsMixers->currentIndex();
665 saveViewConfig(); // save the state before recreating
667 // -2- RECREATE THE ALREADY EXISTING TABS **********************************
668 QMap<Mixer*, bool> mixerHasProfile;
670 // -2a- Build a list of all active profiles in the main window (that means: from all tabs)
671 QList<GUIProfile*> activeGuiProfiles;
672 for (int i = 0; i < m_wsMixers->count(); ++i)
674 KMixerWidget* kmw = qobject_cast<KMixerWidget*>(m_wsMixers->widget(i));
677 activeGuiProfiles.append(kmw->getGuiprof());
681 // TODO The following loop is a bit buggy, as it iterates over all cached Profiles. But that is wrong for Tabs that got closed.
682 // I need to loop over something else, e.g. a profile list built from the currently open Tabs.
683 // Or (if it that is easier) I might discard the Profile from the cache on "close-tab" (but that must also include unplug actions).
684 foreach( GUIProfile* guiprof, activeGuiProfiles){
685 KMixerWidget* kmw = findKMWforTab(guiprof->getId());
686 Mixer *mixer = Mixer::findMixer( guiprof->getMixerId() );
689 kError() << "MixerToolBox::find() hasn't found the Mixer for the profile " << guiprof->getId();
692 mixerHasProfile[mixer] = true;
695 // does not yet exist => create
696 addMixerWidget(mixer->id(), guiprof->getId(), -1);
700 // did exist => remove and insert new guiprof at old position
701 int indexOfTab = m_wsMixers->indexOf(kmw);
702 if ( indexOfTab != -1 ) m_wsMixers->removeTab(indexOfTab);
704 addMixerWidget(mixer->id(), guiprof->getId(), indexOfTab);
706 } // Loop over all GUIProfile's
708 // -3- ADD TABS FOR Mixer instances that have no tab yet **********************************
709 KConfigGroup pconfig(KGlobal::config(), "Profiles");
710 foreach ( Mixer *mixer, Mixer::mixers()){
711 if ( mixerHasProfile.contains(mixer))
713 continue; // OK, this mixer already has a profile => skip it
715 // No TAB YET => This should mean KMix is just started, or the user has just plugged in a card
716 bool profileListHasKey = false;
717 QStringList profileList;
718 bool aProfileWasAddedSucesufully = false;
720 if ( !mixer->isDynamic() )
722 // We do not support save profiles for dynamic mixers (i.e. PulseAudio)
724 profileListHasKey = pconfig.hasKey( mixer->id() );// <<< SHOULD be before the following line
725 profileList = pconfig.readEntry( mixer->id(), QStringList() );
727 foreach ( QString profileId, profileList)
729 // This handles the profileList form the kmixrc
730 kDebug() << "Now searching for profile: " << profileId;
731 GUIProfile* guiprof = GUIProfile::find(mixer, profileId, true, false);// ### Card specific profile ###
734 addMixerWidget(mixer->id(), guiprof->getId(), -1);
735 aProfileWasAddedSucesufully = true;
739 kError() << "Cannot load profile " << profileId << " . It was removed by the user, or the KMix config file is defective.";
744 // The we_need_a_fallback case is a bit tricky. Please ask the author (cesken) before even considering to change the code.
745 bool we_need_a_fallback = !aProfileWasAddedSucesufully;// we *possibly* want a fallback, if we couldn't add one
746 bool thisMixerShouldBeForced = forceNewTab && ( mixerId.isEmpty() || (mixer->id() == mixerId) );
747 we_need_a_fallback = we_need_a_fallback && ( thisMixerShouldBeForced || !profileListHasKey );// Additional requirement: "forced-tab-for-this-mixer" OR "no key stored in kmixrc yet"
748 if ( we_need_a_fallback )
750 // The profileList was empty or nothing could be loaded
751 // (Hint: This means the user cannot hide a device completely
753 // Lets try a bunch of fallback strategies:
754 GUIProfile* guiprof = 0;
755 if ( !mixer->isDynamic() )
757 // We know that GUIProfile::find() will return 0 if the mixer is dynamic, so don't bother checking.
758 kDebug() << "Attempting to find a card-specific GUI Profile for the mixer " << mixer->id();
759 guiprof = GUIProfile::find(mixer, QString("default"), false, false);// ### Card specific profile ###
762 kDebug() << "Not found. Attempting to find a generic GUI Profile for the mixer " << mixer->id();
763 guiprof = GUIProfile::find(mixer, QString("default"), false, true); // ### Card unspecific profile ###
768 kDebug() << "Using fallback GUI Profile for the mixer " << mixer->id();
769 // This means there is neither card specific nor card unspecific profile
770 // This is the case for some backends (as they don't ship profiles).
771 guiprof = GUIProfile::fallbackProfile(mixer);
776 guiprof->setDirty(); // All fallback => dirty
777 addMixerWidget(mixer->id(), guiprof->getId(), -1);
781 kError() << "Cannot use ANY profile (including Fallback) for mixer " << mixer->id() << " . This is impossible, and thus this mixer can NOT be used.";
786 mixerHasProfile.clear();
788 // -4- FINALIZE **********************************
789 if (m_wsMixers->count() > 0)
791 if (oldTabPosition >= 0)
793 m_wsMixers->setCurrentIndex(oldTabPosition);
795 bool dockingSucceded = updateDocking();
796 if (!dockingSucceded && !Mixer::mixers().empty())
798 show(); // avoid invisible and unaccessible main window
803 // No soundcard found. Do not complain, but sit in the background, and wait for newly plugged soundcards.
804 updateDocking(); // -<- removes the DockIcon
811 KMixWindow::findKMWforTab(const QString& kmwId)
813 for (int i = 0; i < m_wsMixers->count(); ++i)
815 KMixerWidget* kmw = (KMixerWidget*) m_wsMixers->widget(i);
816 if (kmw->getGuiprof()->getId() == kmwId)
825 KMixWindow::newView()
827 if (Mixer::mixers().empty())
829 kError() << "Trying to create a View, but no Mixer exists";
830 return; // should never happen
833 Mixer *mixer = Mixer::mixers()[0];
834 QPointer<DialogAddView> dav = new DialogAddView(this, mixer);
835 int ret = dav->exec();
837 if (QDialog::Accepted == ret)
839 QString profileName = dav->getresultViewName();
840 QString mixerId = dav->getresultMixerId();
841 mixer = Mixer::findMixer(mixerId);
843 << ">>> mixer = " << mixerId << " -> " << mixer;
845 GUIProfile* guiprof = GUIProfile::find(mixer, profileName, false, false);
848 guiprof = GUIProfile::find(mixer, profileName, false, true);
853 static const QString msg(
854 i18n("Cannot add view - GUIProfile is invalid."));
859 bool ret = addMixerWidget(mixer->id(), guiprof->getId(), -1);
862 errorPopup(i18n("View already exists. Cannot add View."));
869 //kDebug() << "Exit";
873 * Save the view and close it
875 * @arg idx The index in the TabWidget
878 KMixWindow::saveAndCloseView(int idx)
882 QWidget *w = m_wsMixers->widget(idx);
883 KMixerWidget* kmw = ::qobject_cast<KMixerWidget*>(w);
886 kmw->saveConfig(KGlobal::config().data()); // -<- This alone is not enough, as I need to save the META information as well. Thus use saveViewConfig() below
887 m_wsMixers->removeTab(idx);
888 updateTabsClosable();
897 KMixWindow::plugged(const char* driverName, const QString& udi, QString& dev)
900 << "Plugged: dev=" << dev << "(" << driverName << ") udi=" << udi << "\n";
901 QString driverNameString;
902 driverNameString = driverName;
903 int devNum = dev.toInt();
904 Mixer *mixer = new Mixer(driverNameString, devNum);
908 << "Plugged: dev=" << dev << "\n";
909 MixerToolBox::instance()->possiblyAddMixer(mixer);
910 recreateGUI(true, mixer->id(), true);
915 KMixWindow::unplugged(const QString& udi)
918 << "Unplugged: udi=" << udi << "\n";
919 for (int i = 0; i < Mixer::mixers().count(); ++i)
921 Mixer *mixer = (Mixer::mixers())[i];
922 // kDebug(67100) << "Try Match with:" << mixer->udi() << "\n";
923 if (mixer->udi() == udi)
925 kDebug() << "Unplugged Match: Removing udi=" << udi << "\n";
926 //KMixToolBox::notification("MasterFallback", "aaa");
927 bool globalMasterMixerDestroyed = (mixer == Mixer::getGlobalMasterMixer());
928 // Part 1) Remove Tab
929 for (int i = 0; i < m_wsMixers->count(); ++i)
931 QWidget *w = m_wsMixers->widget(i);
932 KMixerWidget* kmw = ::qobject_cast<KMixerWidget*>(w);
933 if (kmw && kmw->mixer() == mixer)
936 i = -1; // Restart loop from scratch (indices are most likely invalidated at removeTab() )
939 MixerToolBox::instance()->removeMixer(mixer);
940 // Check whether the Global Master disappeared, and select a new one if necessary
941 std::shared_ptr<MixDevice> md = Mixer::getGlobalMasterMD();
942 if (globalMasterMixerDestroyed || md.get() == 0)
944 // We don't know what the global master should be now.
945 // So lets play stupid, and just select the recommended master of the first device
946 if (Mixer::mixers().count() > 0)
948 std::shared_ptr<MixDevice> master =
949 ((Mixer::mixers())[0])->getLocalMasterMD();
950 if (master.get() != 0)
952 QString localMaster = master->id();
953 Mixer::setGlobalMaster(((Mixer::mixers())[0])->id(), localMaster, false);
958 "The soundcard containing the master device was unplugged. Changing to control %1 on card %2.",
959 master->readableName(),
960 ((Mixer::mixers())[0])->readableName());
961 KMixToolBox::notification("MasterFallback", text);
965 if (Mixer::mixers().count() == 0)
968 text = i18n("The last soundcard was unplugged.");
969 KMixToolBox::notification("MasterFallback", text);
979 * Create a widget with an error message
980 * This widget shows an error message like "no mixers detected.
981 void KMixWindow::setErrorMixerWidget()
983 QString s = i18n("Please plug in your soundcard.No soundcard found. Probably you have not set it up or are missing soundcard drivers. Please check your operating system manual for installing your soundcard."); // !! better text
984 m_errorLabel = new QLabel( s,this );
985 m_errorLabel->setAlignment( Qt::AlignCenter );
986 m_errorLabel->setWordWrap(true);
987 m_errorLabel->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
988 m_wsMixers->addTab( m_errorLabel, i18n("No soundcard found") );
996 KMixWindow::profileExists(QString guiProfileId)
998 for (int i = 0; i < m_wsMixers->count(); ++i)
1000 KMixerWidget* kmw = qobject_cast<KMixerWidget*>(m_wsMixers->widget(i));
1001 if (kmw && kmw->getGuiprof()->getId() == guiProfileId)
1008 KMixWindow::addMixerWidget(const QString& mixer_ID, QString guiprofId, int insertPosition)
1010 kDebug() << "Add " << guiprofId;
1011 GUIProfile* guiprof = GUIProfile::find(guiprofId);
1012 if (guiprof != 0 && profileExists(guiprof->getId())) // TODO Bad place. Should be checked in the add-tab-dialog
1013 return false; // already present => don't add again
1014 Mixer *mixer = Mixer::findMixer(mixer_ID);
1016 return false; // no such Mixer
1018 // kDebug(67100) << "KMixWindow::addMixerWidget() " << mixer_ID << " is being added";
1019 ViewBase::ViewFlags vflags = ViewBase::HasMenuBar;
1020 if ((_actionShowMenubar == 0) || _actionShowMenubar->isChecked())
1021 vflags |= ViewBase::MenuBarVisible;
1022 if (GlobalConfig::instance().data.getToplevelOrientation() == Qt::Vertical)
1023 vflags |= ViewBase::Horizontal;
1025 vflags |= ViewBase::Vertical;
1027 KMixerWidget *kmw = new KMixerWidget(mixer, this, vflags, guiprofId,
1028 actionCollection());
1029 /* A newly added mixer will automatically added at the top
1030 * and thus the window title is also set appropriately */
1033 * Skip the name from the profile for now. I would at least have to do the '&' quoting for the tab label. But I am
1034 * also not 100% sure whether the current name from the profile is any good - it does (likely) not even contain the
1035 * card ID. This means you cannot distinguish between cards with an identical name.
1037 // QString tabLabel = guiprof->getName();
1038 // if (tabLabel.isEmpty())
1039 // QString tabLabel = kmw->mixer()->readableName(true);
1041 QString tabLabel = kmw->mixer()->readableName(true);
1043 m_dontSetDefaultCardOnStart = true; // inhibit implicit setting of m_defaultCardOnStart
1045 if (insertPosition == -1)
1046 m_wsMixers->addTab(kmw, tabLabel);
1048 m_wsMixers->insertTab(insertPosition, kmw, tabLabel);
1050 if (kmw->getGuiprof()->getId() == m_defaultCardOnStart)
1052 m_wsMixers->setCurrentWidget(kmw);
1055 updateTabsClosable();
1056 m_dontSetDefaultCardOnStart = false;
1058 kmw->loadConfig(KGlobal::config().data());
1059 // Now force to read for new tabs, especially after hotplug. Note: Doing it here is bad design and possibly
1060 // obsolete, as the backend should take care of upating itself.
1061 kmw->mixer()->readSetFromHWforceUpdate();
1065 void KMixWindow::updateTabsClosable()
1067 // Ddo not allow to close the last view
1068 m_wsMixers->setTabsClosable(m_wsMixers->count() > 1);
1071 bool KMixWindow::queryClose()
1073 GlobalConfigData& gcd = GlobalConfig::instance().data;
1074 if (gcd.showDockWidget && !kapp->sessionSaving() )
1076 // Hide (don't close and destroy), if docking is enabled. Except when session saving (shutdown) is in process.
1082 // Accept the close, if:
1083 // The user has disabled docking
1084 // or SessionSaving() is running
1085 // kDebug(67100) << "close";
1090 void KMixWindow::hideOrClose()
1092 GlobalConfigData& gcd = GlobalConfig::instance().data;
1093 if (gcd.showDockWidget && m_dockWidget != 0)
1095 // we can hide if there is a dock widget
1100 // if there is no dock widget, we will quit
1105 // internal helper to prevent code duplication in slotIncreaseVolume and slotDecreaseVolume
1107 KMixWindow::increaseOrDecreaseVolume(bool increase)
1109 Mixer* mixer = Mixer::getGlobalMasterMixer(); // only needed for the awkward construct below
1111 return; // e.g. when no soundcard is available
1112 std::shared_ptr<MixDevice> md = Mixer::getGlobalMasterMD();
1114 return; // shouldn't happen, but lets play safe
1116 Volume::VolumeTypeFlag volumeType = md->playbackVolume().hasVolume() ? Volume::Playback : Volume::Capture;
1117 md->increaseOrDecreaseVolume(!increase, volumeType);
1118 md->mixer()->commitVolumeChange(md);
1120 showVolumeDisplay();
1124 KMixWindow::slotIncreaseVolume()
1126 increaseOrDecreaseVolume(true);
1130 KMixWindow::slotDecreaseVolume()
1132 increaseOrDecreaseVolume(false);
1136 KMixWindow::showVolumeDisplay()
1138 Mixer* mixer = Mixer::getGlobalMasterMixer();
1140 return; // e.g. when no soundcard is available
1141 std::shared_ptr<MixDevice> md = Mixer::getGlobalMasterMD();
1143 return; // shouldn't happen, but lets play safe
1146 // Setting not required any more, as the OSD updates the volume level itself
1147 // Volume& vol = md->playbackVolume();
1148 // osdWidget->setCurrentVolume(vol.getAvgVolumePercent(Volume::MALL),
1150 if (GlobalConfig::instance().data.showOSD)
1153 osdWidget->activateOSD(); //Enable the hide timer
1156 QRect rect = KApplication::kApplication()->desktop()->screenGeometry(
1158 QSize size = osdWidget->sizeHint();
1159 int posX = rect.x() + (rect.width() - size.width()) / 2;
1160 int posY = rect.y() + 4 * rect.height() / 5;
1161 osdWidget->setGeometry(posX, posY, size.width(), size.height());
1165 * Mutes the global master. (SLOT)
1167 void KMixWindow::slotMute()
1169 Mixer* mixer = Mixer::getGlobalMasterMixer();
1171 return; // e.g. when no soundcard is available
1172 std::shared_ptr<MixDevice> md = Mixer::getGlobalMasterMD();
1174 return; // shouldn't happen, but lets play safe
1176 mixer->commitVolumeChange(md);
1177 showVolumeDisplay();
1183 // kDebug(67100) << "quit";
1188 * Shows the configuration dialog, with the "general" tab opened.
1190 void KMixWindow::showSettings()
1192 KMixPrefDlg::getInstance()->switchToPage(KMixPrefDlg::PrefGeneral);
1193 KMixPrefDlg::getInstance()->show();
1197 KMixWindow::showHelp()
1199 actionCollection()->action("help_contents")->trigger();
1203 KMixWindow::showAbout()
1205 actionCollection()->action("help_about_app")->trigger();
1209 * Apply the Preferences from the preferences dialog. Depending on what has been changed,
1210 * the corresponding announcemnts are made.
1212 void KMixWindow::applyPrefs()
1214 // -1- Determine what has changed ------------------------------------------------------------------
1215 GlobalConfigData& config = GlobalConfig::instance().data;
1216 GlobalConfigData& configBefore = configDataSnapshot;
1218 bool labelsHasChanged = config.showLabels ^ configBefore.showLabels;
1219 bool ticksHasChanged = config.showTicks ^ configBefore.showTicks;
1221 bool dockwidgetHasChanged = config.showDockWidget ^ configBefore.showDockWidget;
1223 bool toplevelOrientationHasChanged = config.getToplevelOrientation() != configBefore.getToplevelOrientation();
1224 bool traypopupOrientationHasChanged = config.getTraypopupOrientation() != configBefore.getTraypopupOrientation();
1225 kDebug() << "toplevelOrientationHasChanged=" << toplevelOrientationHasChanged <<
1226 ", config=" << config.getToplevelOrientation() << ", configBefore=" << configBefore.getToplevelOrientation();
1227 kDebug() << "trayOrientationHasChanged=" << traypopupOrientationHasChanged <<
1228 ", config=" << config.getTraypopupOrientation() << ", configBefore=" << configBefore.getTraypopupOrientation();
1230 // -2- Determine what effect the changes have ------------------------------------------------------------------
1232 if (dockwidgetHasChanged || toplevelOrientationHasChanged
1233 || traypopupOrientationHasChanged)
1235 // These might need a complete relayout => announce a ControlList change to rebuild everything
1236 ControlManager::instance().announce(QString(), ControlChangeType::ControlList, QString("Preferences Dialog"));
1238 else if (labelsHasChanged || ticksHasChanged)
1240 ControlManager::instance().announce(QString(), ControlChangeType::GUI, QString("Preferences Dialog"));
1242 // showOSD does not require any information. It reads on-the-fly from GlobalConfig.
1245 // -3- Apply all changes ------------------------------------------------------------------
1247 // this->repaint(); // make KMix look fast (saveConfig() often uses several seconds)
1248 kapp->processEvents();
1250 configDataSnapshot = GlobalConfig::instance().data; // create a new snapshot as all current changes are applied now
1252 // Remove saveConfig() IF aa changes have been migrated to GlobalConfig.
1253 // Currently there is still stuff like "show menu bar".
1258 KMixWindow::toggleMenuBar()
1260 menuBar()->setVisible(_actionShowMenubar->isChecked());
1264 KMixWindow::slotHWInfo()
1266 KMessageBox::information(0, m_hwInfoString,
1267 i18n("Mixer Hardware Information"));
1271 KMixWindow::slotKdeAudioSetupExec()
1273 static const QString kcmshell4 = QString::fromLatin1("kcmshell4");
1274 static const QStringList kcmshell4args = QStringList() << QString::fromLatin1("kcmplayer");
1275 if (!QProcess::startDetached(kcmshell4, kcmshell4args)) {
1276 errorPopup(i18n("The kcmshell4 application is either not installed or not working."));
1281 KMixWindow::errorPopup(const QString& msg)
1283 QPointer<KDialog> dialog = new KDialog(this);
1284 dialog->setButtons(KDialog::Ok);
1285 dialog->setCaption(i18n("Error"));
1286 QLabel* qlbl = new QLabel(msg);
1287 dialog->setMainWidget(qlbl);
1294 KMixWindow::slotConfigureCurrentView()
1296 KMixerWidget* mw = (KMixerWidget*) m_wsMixers->currentWidget();
1299 view = mw->currentView();
1301 view->configureView();
1304 void KMixWindow::slotSelectMasterClose(QObject*)
1309 void KMixWindow::slotSelectMaster()
1311 Mixer *mixer = Mixer::getGlobalMasterMixer();
1315 m_dsm = new DialogSelectMaster(Mixer::getGlobalMasterMixer(), this);
1316 connect(m_dsm, SIGNAL(destroyed(QObject*)), this, SLOT(slotSelectMasterClose(QObject*)));
1317 m_dsm->setAttribute(Qt::WA_DeleteOnClose, true);
1321 m_dsm->activateWindow();
1325 KMessageBox::error(0, i18n("No sound card is installed or currently plugged in."));
1330 KMixWindow::newMixerShown(int /*tabIndex*/)
1332 KMixerWidget* kmw = (KMixerWidget*) m_wsMixers->currentWidget();
1335 // I am using the app name as a PREFIX, as KMix is a single window app, and it is
1336 // more helpful to the user to see "KDE Mixer" in a window list than a possibly cryptic
1337 // soundcard name like "HDA ATI SB"
1338 setWindowTitle(i18n("KDE Mixer") + " - " + kmw->mixer()->readableName());
1339 if (!m_dontSetDefaultCardOnStart)
1340 m_defaultCardOnStart = kmw->getGuiprof()->getId();
1341 // As switching the tab does NOT mean switching the master card, we do not need to update dock icon here.
1342 // It would lead to unnecesary flickering of the (complete) dock area.
1344 // We only show the "Configure Channels..." menu item if the mixer is not dynamic
1345 ViewBase* view = kmw->currentView();
1346 QAction* action = actionCollection()->action(
1347 "toggle_channels_currentview");
1349 action->setVisible(!view->isDynamic());
1353 #include "moc_kmix.cpp"