diff --git a/apps/kmix.cpp b/apps/kmix.cpp index fb5f07ad..b97852cc 100644 --- a/apps/kmix.cpp +++ b/apps/kmix.cpp @@ -1,1310 +1,1310 @@ /* * KMix -- KDE's full featured mini mixer * * Copyright 1996-2014 The KMix authors. Maintainer: Christian Esken * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this program; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "apps/kmix.h" -// include files for QT +// include files for Qt #include #include #include #include #include #include #include #include #include #include #include // include files for KDE #include #include #include #include #include #include #include #include #include // KMix #include "gui/guiprofile.h" #include "core/ControlManager.h" #include "core/GlobalConfig.h" #include "core/MasterControl.h" #include "core/MediaController.h" #include "core/mixertoolbox.h" #include "core/kmixdevicemanager.h" #include "gui/kmixerwidget.h" #include "gui/kmixprefdlg.h" #include "gui/kmixdockwidget.h" #include "gui/kmixtoolbox.h" #include "core/version.h" #include "gui/viewdockareapopup.h" #include "gui/dialogaddview.h" #include "gui/dialogselectmaster.h" #include "dbus/dbusmixsetwrapper.h" #include "kmix_debug.h" #include #include #include /* KMixWindow * Constructs a mixer window (KMix main window) */ KMixWindow::KMixWindow(bool invisible, bool reset) : KXmlGuiWindow(0, Qt::WindowFlags( KDE_DEFAULT_WINDOWFLAGS | Qt::WindowContextHelpButtonHint)), m_multiDriverMode(false), // -<- I never-ever want the multi-drivermode to be activated by accident m_autouseMultimediaKeys(true), m_dockWidget(), m_dsm(0), m_dontSetDefaultCardOnStart(false) { setObjectName(QStringLiteral("KMixWindow")); // disable delete-on-close because KMix might just sit in the background waiting for cards to be plugged in setAttribute(Qt::WA_DeleteOnClose, false); initActions(); // init actions first, so we can use them in the loadConfig() already loadAndInitConfig(reset); // Load config before initMixer(), e.g. due to "MultiDriver" keyword initActionsLate(); // init actions that require a loaded config // TODO: Port to KF5 //KGlobal::locale()->insertCatalog(QLatin1String("kmix-controls")); initWidgets(); initPrefDlg(); DBusMixSetWrapper::initialize(this, QStringLiteral("/Mixers")); MixerToolBox::instance()->initMixer(m_multiDriverMode, m_backendFilter, m_hwInfoString, true); KMixDeviceManager *theKMixDeviceManager = KMixDeviceManager::instance(); initActionsAfterInitMixer(); // init actions that require initialized mixer backend(s). recreateGUI(false, reset); if (m_wsMixers->count() < 1) { // Something is wrong. Perhaps a hardware or driver or backend change. Let KMix search harder recreateGUI(false, QString(), true, reset); } if (!qApp->isSessionRestored() ) // done by the session manager otherwise setInitialSize(); fixConfigAfterRead(); theKMixDeviceManager->initHotplug(); connect(theKMixDeviceManager, SIGNAL(plugged(const char*,QString,QString&)), SLOT(plugged(const char*,QString,QString&))); connect(theKMixDeviceManager, SIGNAL(unplugged(QString)), SLOT(unplugged(QString))); if (m_startVisible && !invisible) show(); // Started visible connect(qApp, SIGNAL(aboutToQuit()), SLOT(saveConfig()) ); ControlManager::instance().addListener( QString(), // All mixers (as the Global master Mixer might change) (ControlChangeType::Type) (ControlChangeType::ControlList | ControlChangeType::MasterChanged), this, "KMixWindow"); // Send an initial volume refresh (otherwise all volumes are 0 until the next change) ControlManager::instance().announce(QString(), ControlChangeType::Volume, "Startup"); } KMixWindow::~KMixWindow() { ControlManager::instance().removeListener(this); delete m_dsm; // -1- Cleanup Memory: clearMixerWidgets while (m_wsMixers->count() != 0) { QWidget *mw = m_wsMixers->widget(0); m_wsMixers->removeTab(0); delete mw; } // -2- Mixer HW MixerToolBox::instance()->deinitMixer(); // -3- Action collection (just to please Valgrind) actionCollection()->clear(); // GUIProfile cache should be cleared very very late, as GUIProfile instances are used in the Views, which // means main window and potentially also in the tray popup (at least we might do so in the future). // This place here could be to early, if we would start to GUIProfile outside KMixWIndow, e.g. in the tray popup. // Until we do so, this is the best place to call clearCache(). Later, e.g. in main() would likely be problematic. GUIProfile::clearCache(); } void KMixWindow::controlsChange(int changeType) { ControlChangeType::Type type = ControlChangeType::fromInt(changeType); switch (type) { case ControlChangeType::ControlList: case ControlChangeType::MasterChanged: updateDocking(); break; default: ControlManager::warnUnexpectedChangeType(type, this); break; } } void KMixWindow::initActions() { // file menu KStandardAction::quit(this, SLOT(quit()), actionCollection()); // settings menu _actionShowMenubar = KStandardAction::showMenubar(this, SLOT(toggleMenuBar()), actionCollection()); //actionCollection()->addAction(QStringLiteral( a->objectName()), a ); KStandardAction::preferences(this, SLOT(showSettings()), actionCollection()); KStandardAction::keyBindings(guiFactory(), SLOT(configureShortcuts()), actionCollection()); QAction* action = actionCollection()->addAction(QStringLiteral("launch_kdesoundsetup")); action->setText(i18n("Audio Setup")); connect(action, SIGNAL(triggered(bool)), SLOT(slotKdeAudioSetupExec())); action = actionCollection()->addAction(QStringLiteral("hwinfo")); action->setText(i18n("Hardware &Information")); connect(action, SIGNAL(triggered(bool)), SLOT(slotHWInfo())); action = actionCollection()->addAction(QStringLiteral("hide_kmixwindow")); action->setText(i18n("Hide Mixer Window")); connect(action, SIGNAL(triggered(bool)), SLOT(hideOrClose())); actionCollection()->setDefaultShortcut(action, Qt::Key_Escape); action = actionCollection()->addAction(QStringLiteral("toggle_channels_currentview")); action->setText(i18n("Configure &Channels...")); connect(action, SIGNAL(triggered(bool)), SLOT(slotConfigureCurrentView())); action = actionCollection()->addAction(QStringLiteral("select_master")); action->setText(i18n("Select Master Channel...")); connect(action, SIGNAL(triggered(bool)), SLOT(slotSelectMaster())); action = actionCollection()->addAction(QStringLiteral("save_1")); actionCollection()->setDefaultShortcut(action, Qt::CTRL + Qt::SHIFT + Qt::Key_1); action->setText(i18n("Save volume profile 1")); connect(action, SIGNAL(triggered(bool)), SLOT(saveVolumes1())); action = actionCollection()->addAction(QStringLiteral("save_2")); actionCollection()->setDefaultShortcut(action, Qt::CTRL + Qt::SHIFT + Qt::Key_2); action->setText(i18n("Save volume profile 2")); connect(action, SIGNAL(triggered(bool)), SLOT(saveVolumes2())); action = actionCollection()->addAction(QStringLiteral("save_3")); actionCollection()->setDefaultShortcut(action, Qt::CTRL + Qt::SHIFT + Qt::Key_3); action->setText(i18n("Save volume profile 3")); connect(action, SIGNAL(triggered(bool)), SLOT(saveVolumes3())); action = actionCollection()->addAction(QStringLiteral("save_4")); actionCollection()->setDefaultShortcut(action, Qt::CTRL + Qt::SHIFT + Qt::Key_4); action->setText(i18n("Save volume profile 4")); connect(action, SIGNAL(triggered(bool)), SLOT(saveVolumes4())); action = actionCollection()->addAction(QStringLiteral("load_1")); actionCollection()->setDefaultShortcut(action, Qt::CTRL + Qt::Key_1); action->setText(i18n("Load volume profile 1")); connect(action, SIGNAL(triggered(bool)), SLOT(loadVolumes1())); action = actionCollection()->addAction(QStringLiteral("load_2")); actionCollection()->setDefaultShortcut(action, Qt::CTRL + Qt::Key_2); action->setText(i18n("Load volume profile 2")); connect(action, SIGNAL(triggered(bool)), SLOT(loadVolumes2())); action = actionCollection()->addAction(QStringLiteral("load_3")); actionCollection()->setDefaultShortcut(action, Qt::CTRL + Qt::Key_3); action->setText(i18n("Load volume profile 3")); connect(action, SIGNAL(triggered(bool)), SLOT(loadVolumes3())); action = actionCollection()->addAction(QStringLiteral("load_4")); actionCollection()->setDefaultShortcut(action, Qt::CTRL + Qt::Key_4); action->setText(i18n("Load volume profile 4")); connect(action, SIGNAL(triggered(bool)), SLOT(loadVolumes4())); createGUI(QLatin1String("kmixui.rc")); } void KMixWindow::initActionsLate() { if (m_autouseMultimediaKeys) { QAction* globalAction = actionCollection()->addAction(QStringLiteral("increase_volume")); globalAction->setText(i18n("Increase Volume")); KGlobalAccel::setGlobalShortcut(globalAction, Qt::Key_VolumeUp); connect(globalAction, SIGNAL(triggered(bool)), SLOT(slotIncreaseVolume())); globalAction = actionCollection()->addAction(QStringLiteral("decrease_volume")); globalAction->setText(i18n("Decrease Volume")); KGlobalAccel::setGlobalShortcut(globalAction, Qt::Key_VolumeDown); connect(globalAction, SIGNAL(triggered(bool)), SLOT(slotDecreaseVolume())); globalAction = actionCollection()->addAction(QStringLiteral("mute")); globalAction->setText(i18n("Mute")); KGlobalAccel::setGlobalShortcut(globalAction, Qt::Key_VolumeMute); connect(globalAction, SIGNAL(triggered(bool)), SLOT(slotMute())); } } void KMixWindow::initActionsAfterInitMixer() { // Only show the new tab widget if Pulseaudio is not used. Hint: The Pulseaudio backend always // runs with 4 fixed Tabs. if (!Mixer::pulseaudioPresent()) { QPixmap cornerNewPM = KIconLoader::global()->loadIcon("tab-new", KIconLoader::Toolbar, IconSize(KIconLoader::Toolbar)); QPushButton* _cornerLabelNew = new QPushButton(); _cornerLabelNew->setIcon(cornerNewPM); _cornerLabelNew->setToolTip(i18n("Add new view")); //cornerLabelNew->setSizePolicy(QSizePolicy()); m_wsMixers->setCornerWidget(_cornerLabelNew, Qt::TopLeftCorner); connect(_cornerLabelNew, SIGNAL(clicked()), SLOT(newView())); } } void KMixWindow::initPrefDlg() { KMixPrefDlg* prefDlg = KMixPrefDlg::createInstance(this, GlobalConfig::instance()); connect(prefDlg, SIGNAL(kmixConfigHasChanged()), SLOT(applyPrefs())); } void KMixWindow::initWidgets() { m_wsMixers = new QTabWidget(); m_wsMixers->setDocumentMode(true); setCentralWidget(m_wsMixers); m_wsMixers->setTabsClosable(false); connect(m_wsMixers, SIGNAL(tabCloseRequested(int)), SLOT(saveAndCloseView(int))); QPixmap cornerNewPM = KIconLoader::global()->loadIcon("tab-new", KIconLoader::Toolbar, IconSize(KIconLoader::Small)); connect(m_wsMixers, SIGNAL(currentChanged(int)), SLOT(newMixerShown(int))); // show menubar if the actions says so (or if the action does not exist) menuBar()->setVisible((_actionShowMenubar == 0) || _actionShowMenubar->isChecked()); } void KMixWindow::setInitialSize() { KConfigGroup config(KSharedConfig::openConfig(), "Global"); // HACK: QTabWidget will bound its sizeHint to 200x200 unless scrollbuttons // are disabled, so we disable them, get a decent sizehint and enable them // back m_wsMixers->setUsesScrollButtons(false); QSize defSize = sizeHint(); m_wsMixers->setUsesScrollButtons(true); QSize size = config.readEntry("Size", defSize); if (!size.isEmpty()) resize(size); QPoint defPos = pos(); QPoint pos = config.readEntry("Position", defPos); move(pos); } void KMixWindow::removeDock() { if (m_dockWidget) { m_dockWidget->deleteLater(); m_dockWidget = 0; } } /** * Creates or deletes the KMixDockWidget, depending on whether there is a Mixer instance available. * * @returns true, if the docking succeeded. Failure usually means that there * was no suitable mixer control selected. */ bool KMixWindow::updateDocking() { GlobalConfigData& gcd = GlobalConfig::instance().data; if (!gcd.showDockWidget || Mixer::mixers().isEmpty()) { removeDock(); return false; } if (!m_dockWidget) { m_dockWidget = new KMixDockWidget(this); } return true; } void KMixWindow::saveConfig() { saveBaseConfig(); saveViewConfig(); saveVolumes(); #ifdef __GNUC_ #warn We must Sync here, or we will lose configuration data. The reson for that is unknown. #endif // TODO cesken The reason for not writing might be that we have multiple cascaded KConfig objects. I must migrate to KSharedConfig !!! KSharedConfig::openConfig()->sync(); qCDebug(KMIX_LOG) << "Saved config ... sync finished"; } void KMixWindow::saveBaseConfig() { GlobalConfig::instance().save(); KConfigGroup config(KSharedConfig::openConfig(), "Global"); config.writeEntry("Size", size()); config.writeEntry("Position", pos()); // Cannot use isVisible() here, as in the "aboutToQuit()" case this widget is already hidden. // (Please note that the problem was only there when quitting via Systray - esken). // Using it again, as internal behaviour has changed with KDE4 config.writeEntry("Visible", isVisible()); config.writeEntry("Menubar", _actionShowMenubar->isChecked()); config.writeEntry("Soundmenu.Mixers", GlobalConfig::instance().getMixersForSoundmenu().toList()); config.writeEntry("DefaultCardOnStart", m_defaultCardOnStart); config.writeEntry("ConfigVersion", KMIX_CONFIG_VERSION); config.writeEntry("AutoUseMultimediaKeys", m_autouseMultimediaKeys); MasterControl& master = Mixer::getGlobalMasterPreferred(false); config.writeEntry("MasterMixer", master.getCard()); config.writeEntry("MasterMixerDevice", master.getControl()); QString mixerIgnoreExpression = MixerToolBox::instance()->mixerIgnoreExpression(); config.writeEntry("MixerIgnoreExpression", mixerIgnoreExpression); qCDebug(KMIX_LOG) << "Base configuration saved"; } void KMixWindow::saveViewConfig() { QMap mixerViews; // The following loop is necessary for the case that the user has hidden all views for a Mixer instance. // Otherwise we would not save the Meta information (step -2- below for that mixer. // We also do not save dynamic mixers (e.g. PulseAudio) foreach ( Mixer* mixer, Mixer::mixers() ) { mixerViews[mixer->id()]; // just insert a map entry } // -1- Save the views themselves for (int i = 0; i < m_wsMixers->count(); ++i) { QWidget *w = m_wsMixers->widget(i); if (w->inherits("KMixerWidget")) { KMixerWidget* mw = (KMixerWidget*) w; // Here also Views are saved. even for Mixers that are closed. This is necessary when unplugging cards. // Otherwise the user will be confused afer re-plugging the card (as the config was not saved). mw->saveConfig(KSharedConfig::openConfig().data()); // add the view to the corresponding mixer list, so we can save a views-per-mixer list below // if (!mw->mixer()->isDynamic()) // { QStringList& qsl = mixerViews[mw->mixer()->id()]; qsl.append(mw->getGuiprof()->getId()); // } } } // -2- Save Meta-Information (which views, and in which order). views-per-mixer list KConfigGroup pconfig(KSharedConfig::openConfig(), "Profiles"); QMap::const_iterator itEnd = mixerViews.constEnd(); for (QMap::const_iterator it = mixerViews.constBegin(); it != itEnd; ++it) { const QString& mixerProfileKey = it.key(); // this is actually some mixer->id() const QStringList& qslProfiles = it.value(); pconfig.writeEntry(mixerProfileKey, qslProfiles); qCDebug(KMIX_LOG) << "Save Profile List for " << mixerProfileKey << ", number of views is " << qslProfiles.count(); } qCDebug(KMIX_LOG) << "View configuration saved"; } /** * Stores the volumes of all mixers Can be restored via loadVolumes() or * the kmixctrl application. */ void KMixWindow::saveVolumes() { saveVolumes(QString()); } void KMixWindow::saveVolumes(QString postfix) { const QString& kmixctrlRcFilename = getKmixctrlRcFilename(postfix); KConfig *cfg = new KConfig(kmixctrlRcFilename); for (int i = 0; i < Mixer::mixers().count(); ++i) { Mixer *mixer = (Mixer::mixers())[i]; if (mixer->isOpen()) { // protect from unplugged devices (better do *not* save them) mixer->volumeSave(cfg); } } cfg->sync(); delete cfg; qCDebug(KMIX_LOG) << "Volume configuration saved"; } QString KMixWindow::getKmixctrlRcFilename(QString postfix) { QString kmixctrlRcFilename("kmixctrlrc"); if (!postfix.isEmpty()) { kmixctrlRcFilename.append(".").append(postfix); } return kmixctrlRcFilename; } void KMixWindow::loadAndInitConfig(bool reset) { if (!reset) { loadBaseConfig(); } //loadViewConfig(); // mw->loadConfig() explicitly called always after creating mw. //loadVolumes(); // not in use // create an initial snapshot, so we have a reference of the state before changes through the preferences dialog configDataSnapshot = GlobalConfig::instance().data; } void KMixWindow::loadBaseConfig() { KConfigGroup config(KSharedConfig::openConfig(), "Global"); GlobalConfigData& gcd = GlobalConfig::instance().data; QList preferredMixersInSoundMenu; preferredMixersInSoundMenu = config.readEntry("Soundmenu.Mixers", preferredMixersInSoundMenu); GlobalConfig::instance().setMixersForSoundmenu(preferredMixersInSoundMenu.toSet()); m_startVisible = config.readEntry("Visible", false); m_multiDriverMode = config.readEntry("MultiDriver", false); m_defaultCardOnStart = config.readEntry("DefaultCardOnStart", ""); m_configVersion = config.readEntry("ConfigVersion", 0); // WARNING Don't overwrite m_configVersion with the "correct" value, before having it // evaluated. Better only write that in saveBaseConfig() m_autouseMultimediaKeys = config.readEntry("AutoUseMultimediaKeys", true); QString mixerMasterCard = config.readEntry("MasterMixer", ""); QString masterDev = config.readEntry("MasterMixerDevice", ""); Mixer::setGlobalMaster(mixerMasterCard, masterDev, true); QString mixerIgnoreExpression = config.readEntry("MixerIgnoreExpression", "Modem"); MixerToolBox::instance()->setMixerIgnoreExpression(mixerIgnoreExpression); // --- Advanced options, without GUI: START ------------------------------------- QString volumePercentageStepString = config.readEntry("VolumePercentageStep"); if (!volumePercentageStepString.isNull()) { float volumePercentageStep = volumePercentageStepString.toFloat(); if (volumePercentageStep > 0 && volumePercentageStep <= 100) Volume::VOLUME_STEP_DIVISOR = (100 / volumePercentageStep); } // --- Advanced options, without GUI: END ------------------------------------- // The following log is very helpful in bug reports. Please keep it. m_backendFilter = config.readEntry<>("Backends", QList()); qCDebug(KMIX_LOG) << "Backends: " << m_backendFilter; // show/hide menu bar bool showMenubar = config.readEntry("Menubar", true); if (_actionShowMenubar) _actionShowMenubar->setChecked(showMenubar); } /** * Loads the volumes of all mixers from kmixctrlrc. * In other words: * Restores the default voumes as stored via saveVolumes() or the * execution of "kmixctrl --save" */ void KMixWindow::loadVolumes() { loadVolumes(QString()); } void KMixWindow::loadVolumes(QString postfix) { qCDebug(KMIX_LOG) << "About to load config (Volume)"; const QString& kmixctrlRcFilename = getKmixctrlRcFilename(postfix); KConfig *cfg = new KConfig(kmixctrlRcFilename); for (int i = 0; i < Mixer::mixers().count(); ++i) { Mixer *mixer = (Mixer::mixers())[i]; mixer->volumeLoad(cfg); } delete cfg; } void KMixWindow::recreateGUIwithSavingView() { recreateGUI(true, false); } void KMixWindow::recreateGUI(bool saveConfig, bool reset) { recreateGUI(saveConfig, QString(), false, reset); } /** * Create or recreate the Mixer GUI elements * * @param saveConfig Whether to save all View configurations before recreating * @param forceNewTab To enforce opening a new tab, even when the profileList in the kmixrc is empty. * It should only be set to "true" in case of a Hotplug (because then the user definitely expects a new Tab to show). */ void KMixWindow::recreateGUI(bool saveConfig, const QString& mixerId, bool forceNewTab, bool reset) { // -1- Remember which of the tabs is currently selected for restoration for re-insertion int oldTabPosition = m_wsMixers->currentIndex(); if (!reset && saveConfig) saveViewConfig(); // save the state before recreating // -2- RECREATE THE ALREADY EXISTING TABS ********************************** QMap mixerHasProfile; // -2a- Build a list of all active profiles in the main window (that means: from all tabs) QList activeGuiProfiles; for (int i = 0; i < m_wsMixers->count(); ++i) { KMixerWidget* kmw = dynamic_cast(m_wsMixers->widget(i)); if (kmw) { activeGuiProfiles.append(kmw->getGuiprof()); } } foreach ( GUIProfile* guiprof, activeGuiProfiles) { Mixer *mixer = Mixer::findMixer( guiprof->getMixerId() ); if ( mixer == 0 ) { qCCritical(KMIX_LOG) << "MixerToolBox::find() hasn't found the Mixer for the profile " << guiprof->getId(); continue; } mixerHasProfile[mixer] = true; KMixerWidget* kmw = findKMWforTab(guiprof->getId()); if ( kmw == 0 ) { // does not yet exist => create addMixerWidget(mixer->id(), guiprof->getId(), -1); } else { // did exist => remove and insert new guiprof at old position int indexOfTab = m_wsMixers->indexOf(kmw); if ( indexOfTab != -1 ) m_wsMixers->removeTab(indexOfTab); delete kmw; addMixerWidget(mixer->id(), guiprof->getId(), indexOfTab); } } // Loop over all GUIProfile's // -3- ADD TABS FOR Mixer instances that have no tab yet ********************************** KConfigGroup pconfig(KSharedConfig::openConfig(), "Profiles"); foreach ( Mixer *mixer, Mixer::mixers()) { if ( mixerHasProfile.contains(mixer)) { continue; // OK, this mixer already has a profile => skip it } // ========================================================================================= // No TAB YET => This should mean KMix is just started, or the user has just plugged in a card { GUIProfile* guiprof = 0; if (reset) { guiprof = GUIProfile::find(mixer, QString("default"), false, true); // ### Card unspecific profile ### } if ( guiprof != 0 ) { guiprof->setDirty(); // All fallback => dirty addMixerWidget(mixer->id(), guiprof->getId(), -1); continue; } } // ========================================================================================= // The trivial cases have not added anything => Look at [Profiles] in config file QStringList profileList = pconfig.readEntry( mixer->id(), QStringList() ); bool allProfilesRemovedByUser = pconfig.hasKey(mixer->id()) && profileList.isEmpty(); if (allProfilesRemovedByUser) { continue; // User has explicitly hidden the views => do no further checks } { bool atLeastOneProfileWasAdded = false; foreach ( QString profileId, profileList) { // This handles the profileList form the kmixrc qCDebug(KMIX_LOG) << "Now searching for profile: " << profileId; GUIProfile* guiprof = GUIProfile::find(mixer, profileId, true, false);// ### Card specific profile ### if ( guiprof != 0 ) { addMixerWidget(mixer->id(), guiprof->getId(), -1); atLeastOneProfileWasAdded = true; } else { qCCritical(KMIX_LOG) << "Cannot load profile " << profileId << " . It was removed by the user, or the KMix config file is defective."; } } if (atLeastOneProfileWasAdded) { // Continue continue; } } // ========================================================================================= // Neither trivial cases have added something, nor the anything => Look at [Profiles] in config file // The we_need_a_fallback case is a bit tricky. Please ask the author (cesken) before even considering to change the code. bool mixerIdMatch = mixerId.isEmpty() || (mixer->id() == mixerId); bool thisMixerShouldBeForced = forceNewTab && mixerIdMatch; bool we_need_a_fallback = mixerIdMatch && thisMixerShouldBeForced; if ( we_need_a_fallback ) { // The profileList was empty or nothing could be loaded // (Hint: This means the user cannot hide a device completely // Lets try a bunch of fallback strategies: qCDebug(KMIX_LOG) << "Attempting to find a card-specific GUI Profile for the mixer " << mixer->id(); GUIProfile* guiprof = GUIProfile::find(mixer, QString("default"), false, false);// ### Card specific profile ### if ( guiprof == 0 ) { qCDebug(KMIX_LOG) << "Not found. Attempting to find a generic GUI Profile for the mixer " << mixer->id(); guiprof = GUIProfile::find(mixer, QString("default"), false, true); // ### Card unspecific profile ### } if ( guiprof == 0) { qCDebug(KMIX_LOG) << "Using fallback GUI Profile for the mixer " << mixer->id(); // This means there is neither card specific nor card unspecific profile // This is the case for some backends (as they don't ship profiles). guiprof = GUIProfile::fallbackProfile(mixer); } if ( guiprof != 0 ) { guiprof->setDirty(); // All fallback => dirty addMixerWidget(mixer->id(), guiprof->getId(), -1); } else { qCCritical(KMIX_LOG) << "Cannot use ANY profile (including Fallback) for mixer " << mixer->id() << " . This is impossible, and thus this mixer can NOT be used."; } } } mixerHasProfile.clear(); // -4- FINALIZE ********************************** if (m_wsMixers->count() > 0) { if (oldTabPosition >= 0) { m_wsMixers->setCurrentIndex(oldTabPosition); } bool dockingSucceded = updateDocking(); if (!dockingSucceded && !Mixer::mixers().empty()) { show(); // avoid invisible and unaccessible main window } } else { // No soundcard found. Do not complain, but sit in the background, and wait for newly plugged soundcards. updateDocking(); // -<- removes the DockIcon hide(); } } KMixerWidget* KMixWindow::findKMWforTab(const QString& kmwId) { for (int i = 0; i < m_wsMixers->count(); ++i) { KMixerWidget* kmw = (KMixerWidget*) m_wsMixers->widget(i); if (kmw->getGuiprof()->getId() == kmwId) { return kmw; } } return 0; } void KMixWindow::newView() { if (Mixer::mixers().empty()) { qCCritical(KMIX_LOG) << "Trying to create a View, but no Mixer exists"; return; // should never happen } Mixer *mixer = Mixer::mixers()[0]; QPointer dav = new DialogAddView(this, mixer); int ret = dav->exec(); if (QDialog::Accepted == ret) { QString profileName = dav->getresultViewName(); QString mixerId = dav->getresultMixerId(); mixer = Mixer::findMixer(mixerId); qCDebug(KMIX_LOG) << ">>> mixer = " << mixerId << " -> " << mixer; GUIProfile* guiprof = GUIProfile::find(mixer, profileName, false, false); if (guiprof == nullptr) { guiprof = GUIProfile::find(mixer, profileName, false, true); } if (guiprof == nullptr) { KMessageBox::sorry(this, i18n("Cannot add view - GUIProfile is invalid."), i18n("Error")); } else { bool ret = addMixerWidget(mixer->id(), guiprof->getId(), -1); if (!ret) { KMessageBox::sorry(this, i18n("Cannot add view - View already exists."), i18n("Error")); } } delete dav; } //qCDebug(KMIX_LOG) << "Exit"; } /** * Save the view and close it * * @arg idx The index in the TabWidget */ void KMixWindow::saveAndCloseView(int idx) { qCDebug(KMIX_LOG) << "Enter"; QWidget *w = m_wsMixers->widget(idx); KMixerWidget* kmw = ::qobject_cast(w); if (kmw) { kmw->saveConfig(KSharedConfig::openConfig().data()); // -<- This alone is not enough, as I need to save the META information as well. Thus use saveViewConfig() below m_wsMixers->removeTab(idx); updateTabsClosable(); saveViewConfig(); delete kmw; } qCDebug(KMIX_LOG) << "Exit"; } void KMixWindow::fixConfigAfterRead() { KConfigGroup grp(KSharedConfig::openConfig(), "Global"); unsigned int configVersion = grp.readEntry("ConfigVersion", 0); if (configVersion < 3) { // Fix the "double Base" bug, by deleting all groups starting with "View.Base.Base.". // The group has been copied over by KMixToolBox::loadView() for all soundcards, so // we should be fine now QStringList cfgGroups = KSharedConfig::openConfig()->groupList(); QStringListIterator it(cfgGroups); while (it.hasNext()) { QString groupName = it.next(); if (groupName.indexOf("View.Base.Base") == 0) { qCDebug(KMIX_LOG) << "Fixing group " << groupName; KConfigGroup buggyDevgrpCG(KSharedConfig::openConfig(), groupName); buggyDevgrpCG.deleteGroup(); } // remove buggy group } // for all groups } // if config version < 3 } void KMixWindow::plugged(const char* driverName, const QString& udi, QString& dev) { qCDebug(KMIX_LOG) << "Plugged: dev=" << dev << "(" << driverName << ") udi=" << udi << "\n"; QString driverNameString; driverNameString = driverName; int devNum = dev.toInt(); Mixer *mixer = new Mixer(driverNameString, devNum); if (mixer != 0) { qCDebug(KMIX_LOG) << "Plugged: dev=" << dev << "\n"; if (MixerToolBox::instance()->possiblyAddMixer(mixer)) recreateGUI(true, mixer->id(), true, false); } } void KMixWindow::unplugged(const QString& udi) { qCDebug(KMIX_LOG) << "Unplugged: udi=" << udi << "\n"; for (int i = 0; i < Mixer::mixers().count(); ++i) { Mixer *mixer = (Mixer::mixers())[i]; // qCDebug(KMIX_LOG) << "Try Match with:" << mixer->udi() << "\n"; if (mixer->udi() == udi) { qCDebug(KMIX_LOG) << "Unplugged Match: Removing udi=" << udi << "\n"; //KMixToolBox::notification("MasterFallback", "aaa"); bool globalMasterMixerDestroyed = (mixer == Mixer::getGlobalMasterMixer()); // Part 1) Remove Tab for (int i = 0; i < m_wsMixers->count(); ++i) { QWidget *w = m_wsMixers->widget(i); KMixerWidget* kmw = ::qobject_cast(w); if (kmw && kmw->mixer() == mixer) { saveAndCloseView(i); i = -1; // Restart loop from scratch (indices are most likely invalidated at removeTab() ) } } MixerToolBox::instance()->removeMixer(mixer); // Check whether the Global Master disappeared, and select a new one if necessary shared_ptr md = Mixer::getGlobalMasterMD(); if (globalMasterMixerDestroyed || md.get() == 0) { // We don't know what the global master should be now. // So lets play stupid, and just select the recommended master of the first device if (Mixer::mixers().count() > 0) { shared_ptr master = ((Mixer::mixers())[0])->getLocalMasterMD(); if (master.get() != 0) { QString localMaster = master->id(); Mixer::setGlobalMaster(((Mixer::mixers())[0])->id(), localMaster, false); QString text; text = i18n( "The soundcard containing the master device was unplugged. Changing to control %1 on card %2.", master->readableName(), ((Mixer::mixers())[0])->readableName()); KMixToolBox::notification("MasterFallback", text); } } } if (Mixer::mixers().count() == 0) { QString text; text = i18n("The last soundcard was unplugged."); KMixToolBox::notification("MasterFallback", text); } recreateGUI(true, false); break; } } } /** * Create a widget with an error message * This widget shows an error message like "no mixers detected. void KMixWindow::setErrorMixerWidget() { 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 m_errorLabel = new QLabel( s,this ); m_errorLabel->setAlignment( Qt::AlignCenter ); m_errorLabel->setWordWrap(true); m_errorLabel->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); m_wsMixers->addTab( m_errorLabel, i18n("No soundcard found") ); } */ /** * */ bool KMixWindow::profileExists(QString guiProfileId) { for (int i = 0; i < m_wsMixers->count(); ++i) { KMixerWidget* kmw = dynamic_cast(m_wsMixers->widget(i)); if (kmw && kmw->getGuiprof()->getId() == guiProfileId) return true; } return false; } bool KMixWindow::addMixerWidget(const QString& mixer_ID, QString guiprofId, int insertPosition) { qCDebug(KMIX_LOG) << "Add " << guiprofId; GUIProfile* guiprof = GUIProfile::find(guiprofId); if (guiprof != 0 && profileExists(guiprof->getId())) // TODO Bad place. Should be checked in the add-tab-dialog return false; // already present => don't add again Mixer *mixer = Mixer::findMixer(mixer_ID); if (mixer == 0) return false; // no such Mixer // qCDebug(KMIX_LOG) << "KMixWindow::addMixerWidget() " << mixer_ID << " is being added"; ViewBase::ViewFlags vflags = ViewBase::HasMenuBar; if ((_actionShowMenubar == 0) || _actionShowMenubar->isChecked()) vflags |= ViewBase::MenuBarVisible; if (GlobalConfig::instance().data.getToplevelOrientation() == Qt::Vertical) vflags |= ViewBase::Horizontal; else vflags |= ViewBase::Vertical; KMixerWidget *kmw = new KMixerWidget(mixer, this, vflags, guiprofId, actionCollection()); /* A newly added mixer will automatically added at the top * and thus the window title is also set appropriately */ /* * Skip the name from the profile for now. I would at least have to do the '&' quoting for the tab label. But I am * also not 100% sure whether the current name from the profile is any good - it does (likely) not even contain the * card ID. This means you cannot distinguish between cards with an identical name. */ // QString tabLabel = guiprof->getName(); // if (tabLabel.isEmpty()) // QString tabLabel = kmw->mixer()->readableName(true); QString tabLabel = kmw->mixer()->readableName(true); m_dontSetDefaultCardOnStart = true; // inhibit implicit setting of m_defaultCardOnStart if (insertPosition == -1) m_wsMixers->addTab(kmw, tabLabel); else m_wsMixers->insertTab(insertPosition, kmw, tabLabel); if (kmw->getGuiprof()->getId() == m_defaultCardOnStart) { m_wsMixers->setCurrentWidget(kmw); } updateTabsClosable(); m_dontSetDefaultCardOnStart = false; kmw->loadConfig(KSharedConfig::openConfig().data()); // Now force to read for new tabs, especially after hotplug. Note: Doing it here is bad design and possibly // obsolete, as the backend should take care of upating itself. kmw->mixer()->readSetFromHWforceUpdate(); return true; } void KMixWindow::updateTabsClosable() { // Pulseaudio runs with 4 fixed tabs - don't allow to close them. // Also do not allow to close the last view m_wsMixers->setTabsClosable(!Mixer::pulseaudioPresent() && m_wsMixers->count() > 1); } bool KMixWindow::queryClose() { GlobalConfigData& gcd = GlobalConfig::instance().data; if (gcd.showDockWidget && !qApp->isSavingSession() ) { // Hide (don't close and destroy), if docking is enabled. Except when session saving (shutdown) is in process. hide(); return false; } else { // Accept the close, if: // The user has disabled docking // or SessionSaving() is running // qCDebug(KMIX_LOG) << "close"; return true; } } void KMixWindow::hideOrClose() { GlobalConfigData& gcd = GlobalConfig::instance().data; if (gcd.showDockWidget && m_dockWidget != 0) { // we can hide if there is a dock widget hide(); } else { // if there is no dock widget, we will quit quit(); } } // internal helper to prevent code duplication in slotIncreaseVolume and slotDecreaseVolume void KMixWindow::increaseOrDecreaseVolume(bool increase) { Mixer* mixer = Mixer::getGlobalMasterMixer(); // only needed for the awkward construct below if (mixer == 0) return; // e.g. when no soundcard is available shared_ptr md = Mixer::getGlobalMasterMD(); if (md.get() == 0) return; // shouldn't happen, but lets play safe Volume::VolumeTypeFlag volumeType = md->playbackVolume().hasVolume() ? Volume::Playback : Volume::Capture; md->increaseOrDecreaseVolume(!increase, volumeType); md->mixer()->commitVolumeChange(md); showVolumeDisplay(); } void KMixWindow::slotIncreaseVolume() { increaseOrDecreaseVolume(true); } void KMixWindow::slotDecreaseVolume() { increaseOrDecreaseVolume(false); } void KMixWindow::showVolumeDisplay() { Mixer* mixer = Mixer::getGlobalMasterMixer(); if (mixer == 0) return; // e.g. when no soundcard is available shared_ptr md = Mixer::getGlobalMasterMD(); if (md.get() == 0) return; // shouldn't happen, but lets play safe if (GlobalConfig::instance().data.showOSD) { QDBusMessage msg = QDBusMessage::createMethodCall( "org.kde.plasmashell", "/org/kde/osdService", "org.kde.osdService", "volumeChanged" ); int currentVolume = 0; if (!md->isMuted()) { currentVolume = md->playbackVolume().getAvgVolumePercent(Volume::MALL); } msg.setArguments(QList() << currentVolume); QDBusConnection::sessionBus().asyncCall(msg); } } /** * Mutes the global master. (SLOT) */ void KMixWindow::slotMute() { Mixer* mixer = Mixer::getGlobalMasterMixer(); if (mixer == 0) return; // e.g. when no soundcard is available shared_ptr md = Mixer::getGlobalMasterMD(); if (md.get() == 0) return; // shouldn't happen, but lets play safe md->toggleMute(); mixer->commitVolumeChange(md); showVolumeDisplay(); } void KMixWindow::quit() { // qCDebug(KMIX_LOG) << "quit"; qApp->quit(); } /** * Shows the configuration dialog, with the "general" tab opened. */ void KMixWindow::showSettings() { KMixPrefDlg::getInstance()->switchToPage(KMixPrefDlg::PrefGeneral); KMixPrefDlg::getInstance()->show(); } void KMixWindow::showHelp() { actionCollection()->action("help_contents")->trigger(); } void KMixWindow::showAbout() { actionCollection()->action("help_about_app")->trigger(); } /** * Apply the Preferences from the preferences dialog. Depending on what has been changed, * the corresponding announcemnts are made. */ void KMixWindow::applyPrefs() { // -1- Determine what has changed ------------------------------------------------------------------ GlobalConfigData& config = GlobalConfig::instance().data; GlobalConfigData& configBefore = configDataSnapshot; bool labelsHasChanged = config.showLabels ^ configBefore.showLabels; bool ticksHasChanged = config.showTicks ^ configBefore.showTicks; bool dockwidgetHasChanged = config.showDockWidget ^ configBefore.showDockWidget; bool toplevelOrientationHasChanged = config.getToplevelOrientation() != configBefore.getToplevelOrientation(); bool traypopupOrientationHasChanged = config.getTraypopupOrientation() != configBefore.getTraypopupOrientation(); qCDebug(KMIX_LOG) << "toplevelOrientationHasChanged=" << toplevelOrientationHasChanged << ", config=" << config.getToplevelOrientation() << ", configBefore=" << configBefore.getToplevelOrientation(); qCDebug(KMIX_LOG) << "trayOrientationHasChanged=" << traypopupOrientationHasChanged << ", config=" << config.getTraypopupOrientation() << ", configBefore=" << configBefore.getTraypopupOrientation(); // -2- Determine what effect the changes have ------------------------------------------------------------------ if (dockwidgetHasChanged || toplevelOrientationHasChanged || traypopupOrientationHasChanged) { // These might need a complete relayout => announce a ControlList change to rebuild everything ControlManager::instance().announce(QString(), ControlChangeType::ControlList, QString("Preferences Dialog")); } else if (labelsHasChanged || ticksHasChanged) { ControlManager::instance().announce(QString(), ControlChangeType::GUI, QString("Preferences Dialog")); } // showOSD does not require any information. It reads on-the-fly from GlobalConfig. // -3- Apply all changes ------------------------------------------------------------------ // this->repaint(); // make KMix look fast (saveConfig() often uses several seconds) qApp->processEvents(); configDataSnapshot = GlobalConfig::instance().data; // create a new snapshot as all current changes are applied now // Remove saveConfig() IF aa changes have been migrated to GlobalConfig. // Currently there is still stuff like "show menu bar". saveConfig(); } void KMixWindow::toggleMenuBar() { menuBar()->setVisible(_actionShowMenubar->isChecked()); } void KMixWindow::slotHWInfo() { KMessageBox::information(0, m_hwInfoString, i18n("Mixer Hardware Information")); } void KMixWindow::slotKdeAudioSetupExec() { forkExec(QStringList() << "kcmshell5" << "kcm_phonon"); } void KMixWindow::forkExec(const QStringList& args) { int pid = KProcess::startDetached(args); if (pid == 0) { KMessageBox::error(this, i18n("The helper application is either not installed or not working.\n\n%1", args.join(QLatin1String(" ")))); } } void KMixWindow::slotConfigureCurrentView() { KMixerWidget* mw = (KMixerWidget*) m_wsMixers->currentWidget(); ViewBase* view = 0; if (mw) view = mw->currentView(); if (view) view->configureView(); } void KMixWindow::slotSelectMasterClose(QObject*) { m_dsm = 0; } void KMixWindow::slotSelectMaster() { Mixer *mixer = Mixer::getGlobalMasterMixer(); if (mixer != 0) { if (!m_dsm) { m_dsm = new DialogSelectMaster(Mixer::getGlobalMasterMixer(), this); connect(m_dsm, SIGNAL(destroyed(QObject*)), this, SLOT(slotSelectMasterClose(QObject*))); m_dsm->setAttribute(Qt::WA_DeleteOnClose, true); m_dsm->show(); } m_dsm->raise(); m_dsm->activateWindow(); } else { KMessageBox::error(0, i18n("No sound card is installed or currently plugged in.")); } } void KMixWindow::newMixerShown(int /*tabIndex*/) { KMixerWidget* kmw = (KMixerWidget*) m_wsMixers->currentWidget(); if (kmw) { // I am using the app name as a PREFIX, as KMix is a single window app, and it is // more helpful to the user to see "KDE Mixer" in a window list than a possibly cryptic // soundcard name like "HDA ATI SB". // Reformatted for KF5 so as to not say "KDE" // and so that there are not two different dashes. setWindowTitle(i18n("Mixer (%1)", kmw->mixer()->readableName())); if (!m_dontSetDefaultCardOnStart) m_defaultCardOnStart = kmw->getGuiprof()->getId(); // As switching the tab does NOT mean switching the master card, we do not need to update dock icon here. // It would lead to unnecesary flickering of the (complete) dock area. // We only show the "Configure Channels..." menu item if the mixer is not dynamic ViewBase* view = kmw->currentView(); QAction* action = actionCollection()->action("toggle_channels_currentview"); if (view && action) action->setVisible(!view->isDynamic()); } } diff --git a/backends/mixer_alsa.h b/backends/mixer_alsa.h index 671ea739..4faebf69 100644 --- a/backends/mixer_alsa.h +++ b/backends/mixer_alsa.h @@ -1,90 +1,90 @@ //-*-C++-*- /* * KMix -- KDE's full featured mini mixer * * Copyright Christian Esken * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this program; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef MIXER_ALSA_H #define MIXER_ALSA_H -// QT includes +// Qt includes #include #include -// Forward QT includes +// Forward Qt includes class QString; class QSocketNotifier; #include "mixer_backend.h" extern "C" { #include } class Mixer_ALSA : public Mixer_Backend { public: explicit Mixer_ALSA(Mixer *mixer, int device = -1 ); ~Mixer_ALSA(); int readVolumeFromHW( const QString& id, shared_ptr md ) Q_DECL_OVERRIDE; int writeVolumeToHW ( const QString& id, shared_ptr md ) Q_DECL_OVERRIDE; void setEnumIdHW( const QString& id, unsigned int) Q_DECL_OVERRIDE; unsigned int enumIdHW(const QString& id) Q_DECL_OVERRIDE; bool hasChangedControls() Q_DECL_OVERRIDE; bool needsPolling() Q_DECL_OVERRIDE { return false; } QString getDriverName() Q_DECL_OVERRIDE; protected: int open() Q_DECL_OVERRIDE; int close() Q_DECL_OVERRIDE; int id2num(const QString& id); private: int openAlsaDevice(const QString& devName); void addEnumerated(snd_mixer_elem_t *elem, QList&); Volume* addVolume(snd_mixer_elem_t *elem, bool capture); int setupAlsaPolling(); void deinitAlsaPolling(); virtual bool isRecsrcHW( const QString& id ); int identify( snd_mixer_selem_id_t *sid ); snd_mixer_elem_t* getMixerElem(int devnum); QString errorText(int mixer_error) Q_DECL_OVERRIDE; typedef QListAlsaMixerSidList; AlsaMixerSidList mixer_sid_list; typedef QList AlsaMixerElemList; AlsaMixerElemList mixer_elem_list; typedef QHash Id2numHash; Id2numHash m_id2numHash; bool _initialUpdate; snd_mixer_t* _handle; snd_ctl_t* ctl_handle; QString devName; struct pollfd *m_fds; QList m_sns; //int m_count; static bool warnOnce; }; #endif diff --git a/backends/mixer_oss.cpp b/backends/mixer_oss.cpp index ccc9f5f7..754b28ad 100644 --- a/backends/mixer_oss.cpp +++ b/backends/mixer_oss.cpp @@ -1,482 +1,482 @@ /* * KMix -- KDE's full featured mini mixer * * Copyright (C) 1996-2000 Christian Esken * esken@kde.org * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this program; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "mixer_oss.h" #include "core/mixer.h" #include "core/kmixdevicemanager.h" #include #include #include #include #include #include // Since we're guaranteed an OSS setup here, let's make life easier #if !defined(__NetBSD__) && !defined(__OpenBSD__) #include #else #include #endif #include /* I am using a fixed MAX_MIXDEVS #define here. People might argue, that I should rather use the SOUND_MIXER_NRDEVICES #define used by OSS. But using this #define is not good, because it is evaluated during compile time. Compiling on one platform and running on another with another version of OSS with a different value of SOUND_MIXER_NRDEVICES is very bad. Because of this, usage of SOUND_MIXER_NRDEVICES should be discouraged. The #define below is only there for internal reasons. In other words: Don't play around with this value */ #define MAX_MIXDEVS 32 const char* MixerDevNames[32]={ I18N_NOOP("Volume"), I18N_NOOP("Bass"), I18N_NOOP("Treble"), I18N_NOOP("Synth"), I18N_NOOP("Pcm"), I18N_NOOP("Speaker"), I18N_NOOP("Line"), I18N_NOOP("Microphone"), I18N_NOOP("CD"), I18N_NOOP("Mix"), I18N_NOOP("Pcm2"), I18N_NOOP("RecMon"), I18N_NOOP("IGain"), I18N_NOOP("OGain"), I18N_NOOP("Line1"), I18N_NOOP("Line2"), I18N_NOOP("Line3"), I18N_NOOP("Digital1"), I18N_NOOP("Digital2"), I18N_NOOP("Digital3"), I18N_NOOP("PhoneIn"), I18N_NOOP("PhoneOut"), I18N_NOOP("Video"), I18N_NOOP("Radio"), I18N_NOOP("Monitor"), I18N_NOOP("3D-depth"), I18N_NOOP("3D-center"), I18N_NOOP("unknown"), I18N_NOOP("unknown"), I18N_NOOP("unknown"), I18N_NOOP("unknown") , I18N_NOOP("unused") }; const MixDevice::ChannelType MixerChannelTypes[32] = { MixDevice::VOLUME, MixDevice::BASS, MixDevice::TREBLE, MixDevice::MIDI, MixDevice::AUDIO, MixDevice::SPEAKER, MixDevice::EXTERNAL, MixDevice::MICROPHONE, MixDevice::CD, MixDevice::VOLUME, MixDevice::AUDIO, MixDevice::RECMONITOR, MixDevice::VOLUME, MixDevice::RECMONITOR, MixDevice::EXTERNAL, MixDevice::EXTERNAL, MixDevice::EXTERNAL, MixDevice::DIGITAL, MixDevice::DIGITAL, MixDevice::DIGITAL, MixDevice::EXTERNAL, MixDevice::EXTERNAL, MixDevice::VIDEO, MixDevice::EXTERNAL, MixDevice::EXTERNAL, MixDevice::VOLUME, MixDevice::VOLUME, MixDevice::UNKNOWN, MixDevice::UNKNOWN, MixDevice::UNKNOWN, MixDevice::UNKNOWN, MixDevice::UNKNOWN }; Mixer_Backend* OSS_getMixer( Mixer* mixer, int device ) { Mixer_Backend *l_mixer; l_mixer = new Mixer_OSS( mixer, device ); return l_mixer; } Mixer_OSS::Mixer_OSS(Mixer* mixer, int device) : Mixer_Backend(mixer, device) { if (device == -1) { m_devnum = 0; } m_fd = -1; // point to an invalid FD } Mixer_OSS::~Mixer_OSS() { close(); } int Mixer_OSS::open() { QString finalDeviceName; finalDeviceName = deviceName( m_devnum ); qCDebug(KMIX_LOG) << "OSS open() " << finalDeviceName; if ((m_fd= ::open( finalDeviceName.toLatin1().data(), O_RDWR)) < 0) { if ( errno == EACCES ) return Mixer::ERR_PERM; else { finalDeviceName = deviceNameDevfs( m_devnum ); if ((m_fd= ::open( finalDeviceName.toLatin1().data(), O_RDWR)) < 0) { if ( errno == EACCES ) return Mixer::ERR_PERM; else return Mixer::ERR_OPEN; } } } _udi = KMixDeviceManager::instance()->getUDI_OSS(finalDeviceName); if ( _udi.isEmpty() ) { QString msg("No UDI found for '"); msg += finalDeviceName; msg += "'. Hotplugging not possible"; qCDebug(KMIX_LOG) << msg; } int devmask, recmask, i_recsrc, stereodevs; // Mixer is open. Now define properties if (ioctl(m_fd, SOUND_MIXER_READ_DEVMASK, &devmask) == -1) return Mixer::ERR_READ; if (ioctl(m_fd, SOUND_MIXER_READ_RECMASK, &recmask) == -1) return Mixer::ERR_READ; if (ioctl(m_fd, SOUND_MIXER_READ_RECSRC, &i_recsrc) == -1) return Mixer::ERR_READ; if (ioctl(m_fd, SOUND_MIXER_READ_STEREODEVS, &stereodevs) == -1) return Mixer::ERR_READ; int idx = 0; while( devmask && idx < MAX_MIXDEVS ) { if( devmask & ( 1 << idx ) ) // device active? { - Volume playbackVol( 100, 1, true, false ); + Volume playbackVol( 100, 0, true, false ); playbackVol.addVolumeChannel(VolumeChannel(Volume::LEFT)); if ( stereodevs & ( 1 << idx ) ) playbackVol.addVolumeChannel(VolumeChannel(Volume::RIGHT)); QString id; id.setNum(idx); MixDevice* md = new MixDevice( _mixer, id, i18n(MixerDevNames[idx]), MixerChannelTypes[idx]); md->addPlaybackVolume(playbackVol); // Tutorial: Howto add a simple capture switch if ( recmask & ( 1 << idx ) ) { // can be captured => add capture volume, with no capture volume - Volume captureVol( 100, 1, true, true ); + Volume captureVol( 100, 0, true, true ); md->addCaptureVolume(captureVol); } m_mixDevices.append( md->addToPool() ); } idx++; } #if defined(SOUND_MIXER_INFO) struct mixer_info l_mix_info; if (ioctl(m_fd, SOUND_MIXER_INFO, &l_mix_info) != -1) { registerCard(l_mix_info.name); } else #endif { registerCard("OSS Audio Mixer"); } m_isOpen = true; return 0; } int Mixer_OSS::close() { _pollingTimer->stop(); m_isOpen = false; int l_i_ret = ::close(m_fd); closeCommon(); return l_i_ret; } QString Mixer_OSS::deviceName(int devnum) { switch (devnum) { case 0: return QString("/dev/mixer"); break; default: QString devname("/dev/mixer%1"); return devname.arg(devnum); } } QString Mixer_OSS::deviceNameDevfs(int devnum) { switch (devnum) { case 0: return QString("/dev/sound/mixer"); break; default: QString devname("/dev/sound/mixer"); devname += ('0'+devnum); return devname; } } QString Mixer_OSS::errorText(int mixer_error) { QString l_s_errmsg; switch (mixer_error) { case Mixer::ERR_PERM: l_s_errmsg = i18n("kmix: You do not have permission to access the mixer device.\n" \ "Login as root and do a 'chmod a+rw /dev/mixer*' to allow the access."); break; case Mixer::ERR_OPEN: l_s_errmsg = i18n("kmix: Mixer cannot be found.\n" \ "Please check that the soundcard is installed and the\n" \ "soundcard driver is loaded.\n" \ "On Linux you might need to use 'insmod' to load the driver.\n" \ "Use 'soundon' when using commercial OSS."); break; default: l_s_errmsg = Mixer_Backend::errorText(mixer_error); break; } return l_s_errmsg; } void print_recsrc(int recsrc) { int i; QString msg; for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) { if ((1 << i) & recsrc) msg += '+'; else msg += '.'; } qCDebug(KMIX_LOG) << msg; } int Mixer_OSS::setRecsrcToOSS( const QString& id, bool on ) { int i_recsrc; //, oldrecsrc; int devnum = id2num(id); if (ioctl(m_fd, SOUND_MIXER_READ_RECSRC, &i_recsrc) == -1) { errormsg(Mixer::ERR_READ); return Mixer::ERR_READ; } // oldrecsrc = i_recsrc = on ? // (i_recsrc | (1 << devnum )) : // (i_recsrc & ~(1 << devnum )); // Change status of record source(s) if (ioctl(m_fd, SOUND_MIXER_WRITE_RECSRC, &i_recsrc) == -1) { errormsg (Mixer::ERR_WRITE); // don't return here. It is much better to re-read the capture switch states. } /* The following if {} patch was submitted by Tim McCormick . */ /* Comment (cesken): This patch fixes an issue with mutual exclusive recording sources. Actually the kernel soundcard driver *could* "do the right thing" by examining the change (old-recsrc XOR new-recsrc), and knowing which sources are mutual exclusive. The OSS v3 API docs indicate that the behaviour is undefined for this case, and it is not clearly documented how and whether SOUND_MIXER_CAP_EXCL_INPUT is evaluated in the OSS driver. Evaluating that in the application (KMix) could help, but the patch will work independent on whether SOUND_MIXER_CAP_EXCL_INPUT is set or not. In any case this patch is a superb workaround for a shortcoming of the OSS v3 API. */ // If the record source is supposed to be on, but wasn't set, explicitly // set the record source. Not all cards support multiple record sources. // As a result, we also need to do the read & write again. if (((i_recsrc & ( 1< Try to enable it *exclusively* // oldrecsrc = i_recsrc = 1 << devnum; if (ioctl(m_fd, SOUND_MIXER_WRITE_RECSRC, &i_recsrc) == -1) errormsg (Mixer::ERR_WRITE); if (ioctl(m_fd, SOUND_MIXER_READ_RECSRC, &i_recsrc) == -1) errormsg(Mixer::ERR_READ); } // Re-read status of record source(s). Just in case the hardware/driver has // some limitaton (like exclusive switches) int recsrcMask; if (ioctl(m_fd, SOUND_MIXER_READ_RECSRC, &recsrcMask) == -1) errormsg(Mixer::ERR_READ); else { for(int i=0; i< m_mixDevices.count() ; i++ ) { shared_ptr md = m_mixDevices[i]; bool isRecsrc = ( (recsrcMask & ( 1<setRecSource(isRecsrc); } // for all controls } // reading newrecsrcmask is OK return Mixer::OK; } int Mixer_OSS::id2num(const QString& id) { return id.toInt(); } /** * Prints out a translated error text for the given error number on stderr */ void Mixer_OSS::errormsg(int mixer_error) { QString l_s_errText; l_s_errText = errorText(mixer_error); qCCritical(KMIX_LOG) << l_s_errText << "\n"; } int Mixer_OSS::readVolumeFromHW( const QString& id, shared_ptr md ) { int ret = 0; // --- VOLUME --- Volume& vol = md->playbackVolume(); int devnum = id2num(id); bool controlChanged = false; if ( vol.hasVolume() ) { int volume; if (ioctl(m_fd, MIXER_READ( devnum ), &volume) == -1) { /* Oops, can't read mixer */ errormsg(Mixer::ERR_READ); ret = Mixer::ERR_READ; } else { int volLeft = (volume & 0x7f); int volRight = ((volume>>8) & 0x7f); // // if ( md->id() == "0" ) // qCDebug(KMIX_LOG) << md->id() << ": " << "volLeft=" << volLeft << ", volRight" << volRight; bool isMuted = volLeft==0 && ( vol.count() < 2 || volRight==0 ); // muted is "left and right muted" or "left muted when mono" md->setMuted( isMuted ); if ( ! isMuted ) { // Muted is represented in OSS by value 0. We don't want to write the value 0 as a volume, // but instead we only mark it muted (see setMuted() above). foreach (VolumeChannel vc, vol.getVolumes() ) { long volOld = 0; long volNew = 0; switch(vc.chid) { case Volume::LEFT: volOld = vol.getVolume(Volume::LEFT); volNew = volLeft; vol.setVolume( Volume::LEFT, volNew ); break; case Volume::RIGHT: volOld = vol.getVolume(Volume::RIGHT); volNew = volRight; vol.setVolume( Volume::RIGHT, volNew ); break; default: // not supported by OSSv3 break; } if ( volOld != volNew ) { controlChanged = true; //if ( md->id() == "0" ) qCDebug(KMIX_LOG) << "changed"; } } // foreach } // muted } } // --- RECORD SWITCH --- //Volume& captureVol = md->captureVolume(); int recsrcMask; if (ioctl(m_fd, SOUND_MIXER_READ_RECSRC, &recsrcMask) == -1) ret = Mixer::ERR_READ; else { bool isRecsrcOld = md->isRecSource(); // test if device bit is set in record bit mask bool isRecsrc = ( (recsrcMask & ( 1<setRecSource(isRecsrc); if ( isRecsrcOld != isRecsrc ) controlChanged = true; } if ( ret== 0) { if ( controlChanged ) { //qCDebug(KMIX_LOG) << "FINE! " << ret; return Mixer::OK; } else { return Mixer::OK_UNCHANGED; } } else { //qCDebug(KMIX_LOG) << "SHIT! " << ret; return ret; } } int Mixer_OSS::writeVolumeToHW( const QString& id, shared_ptr md) { int volume; int devnum = id2num(id); Volume& vol = md->playbackVolume(); if( md->isMuted() ) volume = 0; else { if ( vol.getVolumes().count() > 1 ) volume = (vol.getVolume(Volume::LEFT) + (vol.getVolume(Volume::RIGHT)<<8)); else volume = vol.getVolume(Volume::LEFT); } if (ioctl(m_fd, MIXER_WRITE( devnum ), &volume) == -1) return Mixer::ERR_WRITE; setRecsrcToOSS( id, md->isRecSource() ); return 0; } QString OSS_getDriverName() { return "OSS"; } QString Mixer_OSS::getDriverName() { return "OSS"; } diff --git a/gui/dialogviewconfiguration.h b/gui/dialogviewconfiguration.h index 55fa9b6a..7c39a856 100644 --- a/gui/dialogviewconfiguration.h +++ b/gui/dialogviewconfiguration.h @@ -1,137 +1,137 @@ //-*-C++-*- /* * KMix -- KDE's full featured mini mixer * * Copyright Christian Esken * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this program; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef DIALOGVIEWCONFIGURATION_H #define DIALOGVIEWCONFIGURATION_H -// QT +// Qt #include class QLabel; #include class QHBoxLayout; #include #include class QScrollArea; class QVBoxLayout; -// QT DND +// Qt DND #include #include // KMix #include "dialogbase.h" #include "gui/guiprofile.h" #include "viewbase.h" class DialogViewConfigurationItem : public QListWidgetItem { friend class QDataStream; public: DialogViewConfigurationItem( QListWidget *parent); DialogViewConfigurationItem( QListWidget *parent, QString id, bool shown, QString name, int splitted, const QString& iconName ); void refreshItem(); public: QString _id; bool _shown; QString _name; int _splitted; QString _iconName; }; class DialogViewConfigurationWidget : public QListWidget { Q_OBJECT public: DialogViewConfigurationWidget(QWidget *parent=0); void setActiveList(bool isActiveList) { m_activeList = isActiveList; } bool isActiveList() const { return m_activeList; }; Q_SIGNALS: void dropped(DialogViewConfigurationWidget* list, int index, DialogViewConfigurationItem* item, bool sourceIsActiveList); protected: QMimeData* mimeData(const QList items) const Q_DECL_OVERRIDE; bool dropMimeData(int index, const QMimeData * mimeData, Qt::DropAction action) Q_DECL_OVERRIDE; Qt::DropActions supportedDropActions() const Q_DECL_OVERRIDE { //qCDebug(KMIX_LOG) << "supportedDropActions!"; return Qt::MoveAction; } QStringList mimeTypes() const Q_DECL_OVERRIDE { //qCDebug(KMIX_LOG) << "mimeTypes!"; return QStringList() << "application/x-kde-action-list"; } // Skip internal dnd handling in QListWidget ---- how is one supposed to figure this out // without reading the QListWidget code !? void dropEvent(QDropEvent* ev) Q_DECL_OVERRIDE { QAbstractItemView::dropEvent(ev); } private: bool m_activeList; }; class DialogViewConfiguration : public DialogBase { Q_OBJECT public: DialogViewConfiguration(QWidget* parent, ViewBase& view); ~DialogViewConfiguration(); public slots: void apply(); private slots: void slotDropped(DialogViewConfigurationWidget* list, int index, DialogViewConfigurationItem* item, bool sourceIsActiveList ); void moveSelectionToActiveList(); void moveSelectionToInactiveList(); void selectionChangedActive(); void selectionChangedInactive(); private: //void dragEnterEvent(QDragEnterEvent *event); void prepareControls(QAbstractItemModel* model, bool isActiveView, GUIProfile::ControlSet& oldCtlSet, GUIProfile::ControlSet& newCtlSet); void createPage(); void addSpacer(int row, int col); void moveSelection(DialogViewConfigurationWidget* from, DialogViewConfigurationWidget* to); ViewBase& _view; QGridLayout *_glayout; QPushButton* moveLeftButton; QPushButton* moveRightButton; DialogViewConfigurationWidget *_qlw; DialogViewConfigurationWidget *_qlwInactive; }; #endif diff --git a/gui/kmixerwidget.h b/gui/kmixerwidget.h index b8c77d89..2e5ea2bf 100644 --- a/gui/kmixerwidget.h +++ b/gui/kmixerwidget.h @@ -1,86 +1,86 @@ /* * KMix -- KDE's full featured mini mixer * * * Copyright (C) 2000 Stefan Schimanski <1Stein@gmx.de> * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this program; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KMIXERWIDGET_H #define KMIXERWIDGET_H #include #include class QString; #include "core/ControlManager.h" #include "core/mixer.h" #include "gui/mixdevicewidget.h" -// QT +// Qt #include // KDE class KActionCollection; class KConfig; // KMix class GUIProfile; class ProfTab; class Mixer; #include "viewbase.h" // KMix experimental class KMixerWidget : public QWidget { Q_OBJECT public: explicit KMixerWidget( Mixer *mixer, QWidget *parent, ViewBase::ViewFlags vflags, QString guiprofId, KActionCollection* coll = 0 ); ~KMixerWidget(); Mixer *mixer() { return _mixer; } ViewBase* currentView(); GUIProfile* getGuiprof() { return GUIProfile::find(_guiprofId); }; signals: void toggleMenuBar(); public slots: void setIcons( bool on ); void toggleMenuBarSlot(); void saveConfig( KConfig *config ); void loadConfig( KConfig *config ); private: Mixer *_mixer; QVBoxLayout *m_topLayout; // contains TabWidget QString _guiprofId; ProfTab* _tab; std::vector _views; KActionCollection* _actionCollection; // -<- applciations wide action collection void createLayout(ViewBase::ViewFlags vflags); bool possiblyAddView(ViewBase* vbase); }; #endif diff --git a/gui/viewbase.cpp b/gui/viewbase.cpp index 65d04583..69fe8a69 100644 --- a/gui/viewbase.cpp +++ b/gui/viewbase.cpp @@ -1,499 +1,499 @@ /* * KMix -- KDE's full featured mini mixer * * * Copyright (C) 1996-2004 Christian Esken * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this program; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "viewbase.h" -// QT +// Qt #include #include #include // KDE #include #include #include #include #include #include // KMix #include "dialogviewconfiguration.h" #include "gui/guiprofile.h" #include "gui/kmixtoolbox.h" #include "gui/mixdevicewidget.h" #include "gui/mdwslider.h" #include "core/ControlManager.h" #include "core/GlobalConfig.h" #include "core/mixer.h" #include "core/mixertoolbox.h" /** * Creates an empty View. To populate it with MixDevice instances, you must implement * _setMixSet() in your derived class. */ ViewBase::ViewBase(QWidget* parent, QString id, Qt::WindowFlags f, ViewBase::ViewFlags vflags, QString guiProfileId, KActionCollection *actionColletion) : QWidget(parent, f), _popMenu(NULL), _actions(actionColletion), _vflags(vflags), _guiProfileId(guiProfileId) , guiLevel(GuiVisibility::GuiSIMPLE) { setObjectName(id); // When loding the View from the XML profile, guiLevel can get overridden m_viewId = id; configureIcon = QIcon::fromTheme( QLatin1String( "configure" )); if ( _actions == 0 ) { // We create our own action collection, if the actionColletion was 0. // This is currently done for the ViewDockAreaPopup, but only because it has not been converted to use the app-wide // actionCollection(). This is a @todo. _actions = new KActionCollection( this ); } _localActionColletion = new KActionCollection( this ); // Plug in the "showMenubar" action, if the caller wants it. Typically this is only necessary for views in the KMix main window. if ( vflags & ViewBase::HasMenuBar ) { KToggleAction *m = static_cast( _actions->action( name(KStandardAction::ShowMenubar) ) ) ; if ( m != 0 ) { bool visible = ( vflags & ViewBase::MenuBarVisible ); m->setChecked(visible); } } } ViewBase::~ViewBase() { // Hint: The GUI profile will not be removed, as it is pooled and might be applied to a new View. } void ViewBase::addMixer(Mixer *mixer) { _mixers.append(mixer); } //void ViewBase::configurationUpdate() { //} QPushButton* ViewBase::createConfigureViewButton() { QPushButton* configureViewButton = new QPushButton(configureIcon, "", this); configureViewButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); configureViewButton->setToolTip(i18n( "Configure this view" )); connect(configureViewButton, SIGNAL(clicked(bool)), SLOT(configureView())); return configureViewButton; } void ViewBase::updateGuiOptions() { setTicks(GlobalConfig::instance().data.showTicks); setLabels(GlobalConfig::instance().data.showLabels); updateMediaPlaybackIcons(); } QString ViewBase::id() const { return m_viewId; } bool ViewBase::isValid() const { return ( !_mixSet.isEmpty() || isDynamic() ); } void ViewBase::setIcons (bool on) { KMixToolBox::setIcons (_mdws, on ); } void ViewBase::setLabels(bool on) { KMixToolBox::setLabels(_mdws, on ); } void ViewBase::setTicks (bool on) { KMixToolBox::setTicks (_mdws, on ); } /** * Updates all playback icons to their (new) state, e.g. Paused, or Playing */ void ViewBase::updateMediaPlaybackIcons() { for (int i = 0; i < _mdws.count(); ++i) { // Currently media controls are always attached to sliders => use MDWSlider MDWSlider* mdw = qobject_cast(_mdws[i]); if (mdw != 0) { mdw->updateMediaButton(); } } } /** * Create all widgets. * This is a loop over all supported devices of the corresponding view. * On each device add() is called - the derived class must implement add() for creating and placing * the real MixDeviceWidget. * The added MixDeviceWidget is appended to the _mdws list. */ void ViewBase::createDeviceWidgets() { _setMixSet(); foreach ( shared_ptr md, _mixSet ) { QWidget* mdw = add(md); // a) Let the View implementation do its work _mdws.append(mdw); // b) Add it to the local list connect(mdw, SIGNAL(guiVisibilityChange(MixDeviceWidget*, bool)), SLOT(guiVisibilitySlot(MixDeviceWidget*, bool))); } if ( !isDynamic() ) { QAction *action = _localActionColletion->addAction("toggle_channels"); action->setText(i18n("&Channels")); connect(action, SIGNAL(triggered(bool)), SLOT(configureView())); } // allow view to "polish" itself constructionFinished(); } /** * Called when a specific control is to be shown or hidden. At the moment it is only called via * the "hide" action in the MDW context menu. * * @param mdw * @param enable */ void ViewBase::guiVisibilitySlot(MixDeviceWidget* mdw, bool enable) { MixDevice* md = mdw->mixDevice().get(); qCDebug(KMIX_LOG) << "Change " << md->id() << " to visible=" << enable; ProfControl* pctl = findMdw(md->id()); if (pctl == 0) { qCWarning(KMIX_LOG) << "MixDevice not found, and cannot be hidden, id=" << md->id(); return; // Ignore } pctl->setVisible(enable); ControlManager::instance().announce(md->mixer()->id(), ControlChangeType::ControlList, QString("ViewBase::guiVisibilitySlot")); } // ---------- Popup stuff START --------------------- void ViewBase::mousePressEvent( QMouseEvent *e ) { if ( e->button() == Qt::RightButton ) showContextMenu(); } /** * Return a popup menu. This contains basic entries. * More can be added by the caller. */ QMenu* ViewBase::getPopup() { popupReset(); return _popMenu; } void ViewBase::popupReset() { QAction *act; delete _popMenu; _popMenu = new QMenu( this ); _popMenu->addSection( QIcon::fromTheme( QLatin1String( "kmix" ) ), i18n("Device Settings" )); act = _localActionColletion->action( "toggle_channels" ); if ( act ) _popMenu->addAction(act); act = _actions->action( "options_show_menubar" ); if ( act ) _popMenu->addAction(act); } /** This will only get executed, when the user has removed all items from the view. Don't remove this method, because then the user cannot get a menu for getting his channels back */ void ViewBase::showContextMenu() { //qCDebug(KMIX_LOG) << "ViewBase::showContextMenu()"; popupReset(); QPoint pos = QCursor::pos(); _popMenu->popup( pos ); } void ViewBase::refreshVolumeLevels() { // is virtual } /** * Check all Mixer instances of this view. * If at least on is dynamic, return true. * Please note that usually there is only one Mixer instance per View. * The only exception as of today (June 2011) is the Tray Popup, which * can contain controls from e.g. ALSA and MPRIS2 backends. */ bool ViewBase::isDynamic() const { foreach (Mixer* mixer , _mixers ) { if ( mixer->isDynamic() ) return true; } return false; } bool ViewBase::pulseaudioPresent() const { // We do not use Mixer::pulseaudioPresent(), as we are only interested in Mixer instances contained in this View. foreach (Mixer* mixer , _mixers ) { if ( mixer->getDriverName() == "PulseAudio" ) return true; } return false; } void ViewBase::resetMdws() { // We need to delete the current MixDeviceWidgets so we can redraw them while (!_mdws.isEmpty()) delete _mdws.takeFirst(); // _mixSet contains shared_ptr instances, so clear() should be enough to prevent mem leak _mixSet.clear(); // Clean up our _mixSet so we can reapply our GUIProfile } int ViewBase::visibleControls() { int visibleCount = 0; foreach (QWidget* qw, _mdws) { if (qw->isVisible()) ++ visibleCount; } return visibleCount; } /** * Open the View configuration dialog. The user can select which channels he wants * to see and which not. */ void ViewBase::configureView() { Q_ASSERT( !isDynamic() ); Q_ASSERT( !pulseaudioPresent() ); DialogViewConfiguration* dvc = new DialogViewConfiguration(0, *this); dvc->show(); } void ViewBase::toggleMenuBarSlot() { //qCDebug(KMIX_LOG) << "ViewBase::toggleMenuBarSlot() start\n"; emit toggleMenuBar(); //qCDebug(KMIX_LOG) << "ViewBase::toggleMenuBarSlot() done\n"; } /** * Loads the configuration of this view. *

* Future directions: The view should probably know its config in advance, so we can use it in #load() and #save() * * * @param config The view for this config */ void ViewBase::load(KConfig *config) { ViewBase *view = this; QString grp = "View."; grp += view->id(); //KConfigGroup cg = config->group( grp ); qCDebug(KMIX_LOG) << "KMixToolBox::loadView() grp=" << grp.toLatin1(); static GuiVisibility guiVisibilities[3] = { GuiVisibility::GuiSIMPLE, GuiVisibility::GuiEXTENDED, GuiVisibility::GuiFULL }; bool guiLevelSet = false; for (int i=0; i<3; ++i) { GuiVisibility& guiCompl = guiVisibilities[i]; bool atLeastOneControlIsShown = false; foreach(QWidget *qmdw, view->_mdws) { if (qmdw->inherits("MixDeviceWidget")) { MixDeviceWidget* mdw = (MixDeviceWidget*) qmdw; shared_ptr md = mdw->mixDevice(); QString devgrp = md->configGroupName(grp); KConfigGroup devcg = config->group(devgrp); if (mdw->inherits("MDWSlider")) { // only sliders have the ability to split apart in mutliple channels bool splitChannels = devcg.readEntry("Split", !mdw->isStereoLinked()); mdw->setStereoLinked(!splitChannels); } // Future directions: "Visibility" is very dirty: It is read from either config file or // GUIProfile. Thus we have a lot of doubled mdw visibility code all throughout KMix. bool mdwEnabled = false; // Consult GuiProfile for visibility mdwEnabled = findMdw(mdw->mixDevice()->id(), guiCompl) != 0; // Match GUI complexity // qCWarning(KMIX_LOG) << "---------- FIRST RUN: md=" << md->id() << ", guiVisibility=" << guiCompl.getId() << ", enabled=" << mdwEnabled; if (mdwEnabled) { atLeastOneControlIsShown = true; } mdw->setVisible(mdwEnabled); } // inherits MixDeviceWidget } // for all MDW's if (atLeastOneControlIsShown) { guiLevelSet = true; setGuiLevel(guiCompl); break; // If there were controls in this complexity level, don't try more } } // for try = 0 ... 1 if (!guiLevelSet) setGuiLevel(guiVisibilities[2]); } void ViewBase::setGuiLevel(GuiVisibility& guiLevel) { this->guiLevel = guiLevel; } /** * Checks whether the given mixDevice shall be shown according to the requested * GuiVisibility. All ProfControl objects are inspected. The first found is returned. * * @param mdwId The control ID * @param requestedGuiComplexityName The GUI name * @return The corresponding ProfControl* * */ ProfControl* ViewBase::findMdw(const QString& mdwId, GuiVisibility visibility) { foreach ( ProfControl* pControl, guiProfile()->getControls() ) { QRegExp idRegExp(pControl->id); if ( mdwId.contains(idRegExp) ) { if (pControl->getVisibility().satisfiesVisibility(visibility)) { // qCDebug(KMIX_LOG) << " MATCH " << (*pControl).id << " for " << mdwId << " with visibility " << pControl->getVisibility().getId() << " to " << visibility.getId(); return pControl; } else { // qCDebug(KMIX_LOG) << "NOMATCH " << (*pControl).id << " for " << mdwId << " with visibility " << pControl->getVisibility().getId() << " to " << visibility.getId(); } } } // iterate over all ProfControl entries return 0; // not found } /** * Returns the ProfControl* to the given id. The first found is returned. * GuiVisibilityis not taken into account. . All ProfControl objects are inspected. * * @param id The control ID * @return The corresponding ProfControl*, or 0 if no match was found */ ProfControl* ViewBase::findMdw(const QString& id) { foreach ( ProfControl* pControl, guiProfile()->getControls() ) { QRegExp idRegExp(pControl->id); //qCDebug(KMIX_LOG) << "KMixToolBox::loadView() try match " << (*pControl).id << " for " << mdw->mixDevice()->id(); if ( id.contains(idRegExp) ) { return pControl; } } // iterate over all ProfControl entries return 0;// not found } /* * Saves the View configuration */ void ViewBase::save(KConfig *config) { ViewBase *view = this; QString grp = "View."; grp += view->id(); // Certain bits are not saved for dynamic mixers (e.g. PulseAudio) bool dynamic = isDynamic(); // TODO 11 Dynamic view configuration for (int i = 0; i < view->_mdws.count(); ++i) { QWidget *qmdw = view->_mdws[i]; if (qmdw->inherits("MixDeviceWidget")) { MixDeviceWidget* mdw = (MixDeviceWidget*) qmdw; shared_ptr md = mdw->mixDevice(); //qCDebug(KMIX_LOG) << " grp=" << grp.toLatin1(); //qCDebug(KMIX_LOG) << " mixer=" << view->id().toLatin1(); //qCDebug(KMIX_LOG) << " mdwPK=" << mdw->mixDevice()->id().toLatin1(); QString devgrp = QString("%1.%2.%3").arg(grp).arg(md->mixer()->id()).arg(md->id()); KConfigGroup devcg = config->group(devgrp); if (mdw->inherits("MDWSlider")) { // only sliders have the ability to split apart in mutliple channels devcg.writeEntry("Split", !mdw->isStereoLinked()); } /* if (!dynamic) { devcg.writeEntry("Show", mdw->isVisibleTo(view)); // qCDebug(KMIX_LOG) << "Save devgrp" << devgrp << "show=" << mdw->isVisibleTo(view); } */ } // inherits MixDeviceWidget } // for all MDW's if (!dynamic) { // We do not save GUIProfiles (as they cannot be customized) for dynamic mixers (e.g. PulseAudio) if (guiProfile()->isDirty()) { qCDebug(KMIX_LOG) << "Writing dirty profile. grp=" << grp; guiProfile()->writeProfile(); } } } // ---------- Popup stuff END --------------------- diff --git a/gui/viewbase.h b/gui/viewbase.h index 7562ce1a..7e098139 100644 --- a/gui/viewbase.h +++ b/gui/viewbase.h @@ -1,171 +1,171 @@ //-*-C++-*- /* * KMix -- KDE's full featured mini mixer * * Copyright Christian Esken * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this program; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef ViewBase_h #define ViewBase_h -// QT +// Qt #include #include #include #include // KDE #include class QMenu; class Mixer; class MixDevice; // KMix #include "core/mixdevice.h" #include "core/mixset.h" #include "gui/guiprofile.h" #include "gui/mixdevicewidget.h" /** * The ViewBase is a virtual base class, to be used for subclassing the real Mixer Views. */ class ViewBase : public QWidget { Q_OBJECT friend class KMixToolBox; // the toolbox is everybodys friend :-) public: typedef uint ViewFlags; enum ViewFlagsEnum { // Regular flags HasMenuBar = 0x0001, MenuBarVisible = 0x0002, Horizontal = 0x0004, Vertical = 0x0008 }; ViewBase(QWidget* parent, QString id, Qt::WindowFlags f, ViewFlags vflags, QString guiProfileId, KActionCollection* actionCollection = 0); virtual ~ViewBase(); void addMixer(Mixer *mixer); QString id() const; // This method is called by ViewBase at the end of createDeviceWidgets(). The default // implementation does nothing. Subclasses can override this method for doing final // touches. This is very much like polish(), but called at an exactly well-known time. // Also I do not want Views to interfere with polish() virtual void constructionFinished() = 0; /** * Creates a suitable representation for the given MixDevice. */ virtual QWidget* add(shared_ptr) = 0; // This method is called after a configuration update (show/hide controls, split/unsplit). virtual void configurationUpdate() = 0; void load(KConfig *config); void save(KConfig *config); /** * Creates the widgets for all supported devices. The default implementation loops * over the supported MixDevice's and calls add() for each of it. */ virtual void createDeviceWidgets(); int visibleControls(); bool isDynamic() const; bool pulseaudioPresent() const; /** * Popup stuff */ virtual QMenu* getPopup(); virtual void popupReset(); virtual void showContextMenu(); virtual bool isValid() const; void setIcons(bool on); void setLabels(bool on); void setTicks(bool on); GUIProfile* guiProfile() { return GUIProfile::find(_guiProfileId); }; // GUIComplexity getGuiComplexity() { return guiComplexity; }; ProfControl* findMdw(const QString& id); ProfControl* findMdw(const QString& mdwId, GuiVisibility visibility); KActionCollection* actionCollection() { return _actions; }; QList& getMixers() { return _mixers; }; /** * Contains the widgets for the _mixSet. There is a 1:1 relationship, which means: * _mdws[i] is the Widget for the MixDevice _mixSet[i] - please see ViewBase::createDeviceWidgets(). * Hint: !! The new ViewSurround class shows that a 1:1 relationship does not work in a general scenario. * I actually DID expect this. The solution is unclear yet, probably there will be a virtual mapper method. */ QList _mdws; protected: MixSet _mixSet; QList _mixers; QMenu *_popMenu; KActionCollection* _actions; // -<- application wide action collection ViewFlags _vflags; const QString _guiProfileId; KActionCollection *_localActionColletion; QIcon configureIcon; virtual void _setMixSet() = 0; void resetMdws(); void updateGuiOptions(); QPushButton* createConfigureViewButton(); void setGuiLevel(GuiVisibility& guiLevel); GuiVisibility guiLevel; public slots: virtual void refreshVolumeLevels(); // TODO remove virtual void configureView(); void toggleMenuBarSlot(); protected slots: void mousePressEvent( QMouseEvent *e ) Q_DECL_OVERRIDE; signals: void toggleMenuBar(); private: QString m_viewId; void updateMediaPlaybackIcons(); private slots: void guiVisibilitySlot(MixDeviceWidget* source, bool enable); }; #endif