diff --git a/CMakeLists.txt b/CMakeLists.txt index c8fac0a8..54402a95 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,368 +1,368 @@ cmake_minimum_required(VERSION 2.8.12) project(kmix) add_definitions( -DTRANSLATION_DOMAIN=\"kmix\" ) SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") set (QT_MIN_VERSION "5.4.0") set (KF5_MIN_VERSION "5.14.0") find_package(ECM ${KF5_MIN_VERSION} REQUIRED NO_MODULE) set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH} ${ECM_KDE_MODULE_DIR}) include(KDEInstallDirs) include(KDECMakeSettings) include(KDECompilerSettings NO_POLICY_SCOPE) include(ECMPackageConfigHelpers) include(ECMInstallIcons) include(ECMQtDeclareLoggingCategory) find_package(Qt5 ${QT_MIN_VERSION} REQUIRED NO_MODULE COMPONENTS Core DBus Gui Widgets Xml ) find_package(KF5 ${KF5_MIN_VERSION} REQUIRED COMPONENTS Completion Config ConfigWidgets Crash DBusAddons DocTools GlobalAccel I18n IconThemes + # Do not remove KDELibs4Support from here yet, + # it provides CMake modules to find GLIB2 and PulseAudio. KDELibs4Support + Init Notifications Plasma + Solid WidgetsAddons WindowSystem XmlGui ) set(CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake/modules ${CMAKE_MODULE_PATH}) -# Do not yet REQUIRE Phonon. Hint: As long as we do not find_package(), ${KDE4_PHONON_LIBS} will be empty below, but that should not hurt. -#find_package(Phonon REQUIRED) - # PulseAudio is an optional dependency set(PA_VER "0.9.16") find_package(PulseAudio "${PA_VER}") find_package(GLIB2) # Canberra is an optional dependency find_package(Canberra) -find_package(Alsa) +find_package(ALSA) if(ALSA_FOUND) - alsa_configure_file(${CMAKE_BINARY_DIR}/config-alsa.h) + set(HAVE_LIBASOUND2 TRUE) add_definitions(-DHAVE_LIBASOUND2) endif(ALSA_FOUND) include_directories (${CMAKE_SOURCE_DIR} ${CMAKE_BINARY_DIR}) if(MSVC) include_directories( ${TAGLIB_INCLUDES} ) endif(MSVC) include(CheckCXXSourceCompiles) check_cxx_source_compiles(" #include int main() { std::shared_ptr p; return 0; } " HAVE_STD_SHARED_PTR) check_cxx_source_compiles(" #include int main() { std::tr1::shared_ptr p; return 0; } " HAVE_STD_TR1_SHARED_PTR) configure_file (config.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config.h ) include_directories( ${GLIB2_INCLUDE_DIR} /usr/lib/oss/include) add_subdirectory(doc) add_subdirectory( pics ) add_subdirectory( profiles ) #add_subdirectory( tests ) if (PULSEAUDIO_FOUND) add_definitions(-DHAVE_PULSE) include_directories(${PULSEAUDIO_INCLUDE_DIR}) endif (PULSEAUDIO_FOUND) if (CANBERRA_FOUND) add_definitions(-DHAVE_CANBERRA) include_directories(${CANBERRA_INCLUDE_DIRS}) endif (CANBERRA_FOUND) ecm_qt_declare_logging_category(kmix_debug_SRCS HEADER kmix_debug.h IDENTIFIER KMIX_LOG CATEGORY_NAME org.kde.kmix) #################################################################################################### ########### definitions: DBUSADAPTOR ############################################################### #################################################################################################### set(kmix_adaptor_SRCS dbus/dbusmixerwrapper.cpp dbus/dbusmixsetwrapper.cpp dbus/dbuscontrolwrapper.cpp) qt5_add_dbus_adaptor( kmix_adaptor_SRCS dbus/org.kde.kmix.control.xml dbus/dbuscontrolwrapper.h DBusControlWrapper ) qt5_add_dbus_adaptor( kmix_adaptor_SRCS dbus/org.kde.kmix.mixer.xml dbus/dbusmixerwrapper.h DBusMixerWrapper ) qt5_add_dbus_adaptor( kmix_adaptor_SRCS dbus/org.kde.kmix.mixset.xml dbus/dbusmixsetwrapper.h DBusMixSetWrapper ) #################################################################################################### ########### definitions: backends ################################################################## #################################################################################################### set(kmix_backend_SRCS backends/mixer_backend.cpp backends/mixer_mpris2.cpp ) if (HAVE_LIBASOUND2) set(kmix_backend_SRCS ${kmix_backend_SRCS} backends/mixer_alsa9.cpp ) endif (HAVE_LIBASOUND2) if (PULSEAUDIO_FOUND) set(kmix_backend_SRCS ${kmix_backend_SRCS} backends/mixer_pulse.cpp ) endif (PULSEAUDIO_FOUND) #################################################################################################### ########### next target : kmix ##################################################################### #################################################################################################### set(kmix_KDEINIT_SRCS ${kmix_adaptor_SRCS} ${kmix_backend_SRCS} apps/main.cpp apps/kmix.cpp apps/KMixApp.cpp gui/dialogbase.cpp gui/dialogstatesaver.cpp gui/kmixdockwidget.cpp gui/kmixprefdlg.cpp gui/viewbase.cpp gui/viewdockareapopup.cpp gui/viewsliders.cpp gui/mixdevicewidget.cpp gui/mdwmoveaction.cpp gui/mdwslider.cpp gui/mdwenum.cpp gui/kmixerwidget.cpp gui/ksmallslider.cpp gui/verticaltext.cpp gui/volumeslider.cpp gui/kmixtoolbox.cpp gui/dialogaddview.cpp gui/dialogviewconfiguration.cpp gui/dialogselectmaster.cpp gui/dialogchoosebackends.cpp gui/guiprofile.cpp ${kmix_OSD_SRCS} core/MediaController.cpp core/mixertoolbox.cpp core/kmixdevicemanager.cpp core/ControlManager.cpp # core/ControlPool.cpp core/GlobalConfig.cpp core/MasterControl.cpp core/mixer.cpp core/mixset.cpp core/mixdevice.cpp core/mixdevicecomposite.cpp core/volume.cpp ${kmix_debug_SRCS} ) kf5_add_kdeinit_executable(kmix ${kmix_KDEINIT_SRCS}) target_link_libraries(kdeinit_kmix KF5::I18n KF5::ConfigCore KF5::ConfigGui KF5::ConfigWidgets KF5::IconThemes KF5::DBusAddons KF5::GlobalAccel - KF5::KDELibs4Support + KF5::XmlGui + KF5::Notifications + KF5::WindowSystem Qt5::Core Qt5::Widgets Qt5::DBus Qt5::Xml ) if (HAVE_LIBASOUND2) - target_link_libraries(kdeinit_kmix ${ASOUND_LIBRARY}) + target_link_libraries(kdeinit_kmix ${ALSA_LIBRARIES}) endif (HAVE_LIBASOUND2) if (PULSEAUDIO_FOUND) target_link_libraries(kdeinit_kmix ${PULSEAUDIO_LIBRARY} ${PULSEAUDIO_MAINLOOP_LIBRARY} ${GLIB2_LIBRARIES}) endif (PULSEAUDIO_FOUND) if (CANBERRA_FOUND) target_link_libraries(kdeinit_kmix ${CANBERRA_LIBRARIES}) endif (CANBERRA_FOUND) install(TARGETS kdeinit_kmix DESTINATION ${KDE_INSTALL_LIBDIR} ) target_link_libraries( kmix kdeinit_kmix ) install(TARGETS kmix ${KDE_INSTALL_TARGETS_DEFAULT_ARGS}) #################################################################################################### ########### next target : kmixd #################################################################### #################################################################################################### set(kded_kmixd_SRCS ${kmix_adaptor_SRCS} ${kmix_backend_SRCS} apps/kmixd.cpp core/ControlManager.cpp # core/ControlPool.cpp core/GlobalConfig.cpp core/MasterControl.cpp core/MediaController.cpp core/mixer.cpp core/mixset.cpp core/mixdevice.cpp core/volume.cpp core/mixertoolbox.cpp core/kmixdevicemanager.cpp ${kmix_debug_SRCS} ) #qt4_add_dbus_adaptor(kded_kmixd_SRCS org.kde.KMixD.xml kmixd.h Mixer) add_library(kded_kmixd ${kded_kmixd_SRCS}) target_link_libraries(kded_kmixd KF5::I18n KF5::ConfigCore KF5::ConfigGui KF5::CoreAddons KF5::Solid KF5::DBusAddons + KF5::CoreAddons Qt5::Core Qt5::Widgets Qt5::DBus Qt5::Xml - - ${KDEx_KDEUI_LIBS} - ${KDEx_PHONON_LIBS} ) if (HAVE_LIBASOUND2) - target_link_libraries(kded_kmixd ${ASOUND_LIBRARY}) + target_link_libraries(kded_kmixd ${ALSA_LIBRARIES}) endif (HAVE_LIBASOUND2) if (PULSEAUDIO_FOUND) target_link_libraries(kded_kmixd ${PULSEAUDIO_LIBRARY} ${PULSEAUDIO_MAINLOOP_LIBRARY} ${GLIB2_LIBRARIES}) endif (PULSEAUDIO_FOUND) if (CANBERRA_FOUND) target_link_libraries(kded_kmixd ${CANBERRA_LIBRARIES}) endif (CANBERRA_FOUND) install(TARGETS kded_kmixd DESTINATION ${KDE_INSTALL_PLUGINDIR}) #target_link_libraries( kmixd kded_kmixd ) #install(TARGETS kmixd DESTINATION ${PLUGIN_INSTALL_DIR} ) install( FILES kmixd.desktop DESTINATION ${KDE_INSTALL_KSERVICES5DIR}/kded ) #################################################################################################### ########### next target : kmixctrl ################################################################# #################################################################################################### set(kmixctrl_KDEINIT_SRCS ${kmix_adaptor_SRCS} ${kmix_backend_SRCS} apps/kmixctrl.cpp core/ControlManager.cpp # core/ControlPool.cpp core/GlobalConfig.cpp core/MasterControl.cpp core/MediaController.cpp core/mixer.cpp core/mixset.cpp core/mixdevice.cpp core/volume.cpp core/mixertoolbox.cpp ${kmix_debug_SRCS} # There is no actual need for kmixdevicemanager.cpp (hotplug makes no sense in kmixctrl), but # the dependency comes in via mixertoolbox.cpp core/kmixdevicemanager.cpp ) # gui/guiprofile.cpp KF5_ADD_KDEINIT_EXECUTABLE(kmixctrl ${kmixctrl_KDEINIT_SRCS}) target_link_libraries(kdeinit_kmixctrl KF5::ConfigCore KF5::ConfigGui KF5::CoreAddons KF5::I18n Qt5::Core Qt5::DBus Qt5::Widgets -#Qt5::Xml ) if (HAVE_LIBASOUND2) - target_link_libraries(kdeinit_kmixctrl ${ASOUND_LIBRARY}) + target_link_libraries(kdeinit_kmixctrl ${ALSA_LIBRARIES}) endif (HAVE_LIBASOUND2) if (PULSEAUDIO_FOUND) target_link_libraries(kdeinit_kmixctrl ${PULSEAUDIO_LIBRARY} ${PULSEAUDIO_MAINLOOP_LIBRARY} ${GLIB2_LIBRARIES}) endif (PULSEAUDIO_FOUND) if (CANBERRA_FOUND) target_link_libraries(kdeinit_kmixctrl ${CANBERRA_LIBRARIES}) endif (CANBERRA_FOUND) ########### next target ############### add_subdirectory( plasma ) #################################################################################################### ########### INSTALL RULES ########################################################################## #################################################################################################### install( TARGETS kdeinit_kmixctrl DESTINATION ${KDE_INSTALL_LIBDIR} ) target_link_libraries( kmixctrl kdeinit_kmixctrl ) install( TARGETS kmixctrl ${KDE_INSTALL_TARGETS_DEFAULT_ARGS} ) install( PROGRAMS org.kde.kmix.desktop DESTINATION ${KDE_INSTALL_APPDIR} ) install( PROGRAMS apps/kmixremote DESTINATION ${KDE_INSTALL_BINDIR} ) install( FILES restore_kmix_volumes.desktop DESTINATION ${KDE_INSTALL_AUTOSTARTDIR} ) install( FILES kmix_autostart.desktop DESTINATION ${KDE_INSTALL_AUTOSTARTDIR} ) install( FILES kmixui.rc DESTINATION ${KDE_INSTALL_KXMLGUI5DIR}/kmix ) install( FILES kmixctrl_restore.desktop DESTINATION ${KDE_INSTALL_KSERVICES5DIR} ) install( FILES dbus/org.kde.kmix.control.xml DESTINATION ${KDE_INSTALL_DBUSINTERFACEDIR} ) install( FILES dbus/org.kde.kmix.mixer.xml DESTINATION ${KDE_INSTALL_DBUSINTERFACEDIR} ) install( FILES dbus/org.kde.kmix.mixset.xml DESTINATION ${KDE_INSTALL_DBUSINTERFACEDIR} ) feature_summary(WHAT ALL FATAL_ON_MISSING_REQUIRED_PACKAGES) diff --git a/apps/kmix.cpp b/apps/kmix.cpp index c8eb11b9..ff592ca8 100644 --- a/apps/kmix.cpp +++ b/apps/kmix.cpp @@ -1,1310 +1,1307 @@ /* * 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 #include #include #include #include #include #include #include #include #include +#include // include files for KDE -#include -#include #include #include #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->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" setWindowTitle(i18n("KDE Mixer") + " - " + 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/apps/kmixd.cpp b/apps/kmixd.cpp index 246bec61..89a12d24 100644 --- a/apps/kmixd.cpp +++ b/apps/kmixd.cpp @@ -1,222 +1,221 @@ /* * KMix -- KDE's full featured mini mixer * * Copyright 1996-2000 Christian Esken * Copyright 2000-2003 Christian Esken , Stefan Schimanski <1Stein@gmx.de> * Copyright 2002-2007 Christian Esken , Helio Chissini de Castro * * 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 "kmixd.h" -#include #include #include #include #include // KMix #include "core/GlobalConfig.h" #include "core/mixertoolbox.h" #include "core/kmixdevicemanager.h" #include "core/mixer.h" #include "core/version.h" K_PLUGIN_FACTORY(KMixDFactory, registerPlugin(); ) K_EXPORT_PLUGIN(KMixDFactory("kmixd")) /* static const char description[] = I18N_NOOP("KMixD - KDE's full featured mini mixer Service"); extern "C" KDE_EXPORT int kdemain(int argc, char *argv[]) { KAboutData aboutData( "kmixd", 0, ki18n("KMixD"), APP_VERSION, ki18n(description), KAboutData::License_GPL, ki18n("(c) 2010 Christian Esken")); KCmdLineArgs::init( argc, argv, &aboutData ); // As in the KUniqueApplication example only create a instance AFTER // calling KUniqueApplication::start() KUniqueApplication app; KMixD kmixd; app.exec(); } */ /* KMixD * Constructs a mixer window (KMix main window) */ KMixD::KMixD(QObject* parent, const QList&) : KDEDModule(parent), m_multiDriverMode (false) // -<- I never-ever want the multi-drivermode to be activated by accident { setObjectName( QStringLiteral("KMixD" )); GlobalConfig::init(); qCWarning(KMIX_LOG) << "kmixd: Triggering delayed initialization"; QTimer::singleShot( 3000, this, SLOT(delayedInitialization())); } /** * This is called by a singleShot timer. Reason is that KMixD seems to "collide" with other applications * on DBUS, likely creating deadlocks. See Bug 317926 for that. By artificially delaying initialization, * KMixD gets hopefully out of the way of the other applications, avoiding these deadlocks. A small delay * should also not harm too much, as apps using the Mixer DBUS calls on KMixD must be prepared that the * interface is not available rihgt when they start. */ void KMixD::delayedInitialization() { qCWarning(KMIX_LOG) << "kmixd: Delayed initialization running now"; //initActions(); // init actions first, so we can use them in the loadConfig() already loadConfig(); // Load config before initMixer(), e.g. due to "MultiDriver" keyword MixerToolBox::instance()->initMixer(m_multiDriverMode, m_backendFilter, m_hwInfoString, true); KMixDeviceManager *theKMixDeviceManager = KMixDeviceManager::instance(); theKMixDeviceManager->initHotplug(); connect(theKMixDeviceManager, SIGNAL(plugged(const char*,QString,QString&)), SLOT (plugged(const char*,QString,QString&)) ); connect(theKMixDeviceManager, SIGNAL(unplugged(QString)), SLOT (unplugged(QString)) ); qCWarning(KMIX_LOG) << "kmixd: Delayed initialization done"; } KMixD::~KMixD() { MixerToolBox::instance()->deinitMixer(); } void KMixD::saveConfig() { qCDebug(KMIX_LOG) << "About to save config"; saveBaseConfig(); // saveVolumes(); // -<- removed from kmixd, as it is possibly a bad idea if both kmix and kmixd write the same config "kmixctrlrc" #ifdef __GNUC_ #warn We must Sync here, or we will lose configuration data. The reson for that is unknown. #endif qCDebug(KMIX_LOG) << "Saved config ... now syncing explicitly"; KSharedConfig::openConfig()->sync(); qCDebug(KMIX_LOG) << "Saved config ... sync finished"; } void KMixD::saveBaseConfig() { qCDebug(KMIX_LOG) << "About to save config (Base)"; KConfigGroup config(KSharedConfig::openConfig(), "Global"); config.writeEntry( "ConfigVersion", KMIX_CONFIG_VERSION ); Mixer* mixerMasterCard = Mixer::getGlobalMasterMixer(); if ( mixerMasterCard != 0 ) { config.writeEntry( "MasterMixer", mixerMasterCard->id() ); } shared_ptr mdMaster = Mixer::getGlobalMasterMD(); if ( mdMaster ) { config.writeEntry( "MasterMixerDevice", mdMaster->id() ); } QString mixerIgnoreExpression = MixerToolBox::instance()->mixerIgnoreExpression(); config.writeEntry( "MixerIgnoreExpression", mixerIgnoreExpression ); qCDebug(KMIX_LOG) << "Config (Base) saving done"; } void KMixD::loadConfig() { loadBaseConfig(); } void KMixD::loadBaseConfig() { KConfigGroup config(KSharedConfig::openConfig(), "Global"); m_multiDriverMode = config.readEntry("MultiDriver", false); QString mixerMasterCard = config.readEntry( "MasterMixer", "" ); QString masterDev = config.readEntry( "MasterMixerDevice", "" ); Mixer::setGlobalMaster(mixerMasterCard, masterDev, true); QString mixerIgnoreExpression = config.readEntry( "MixerIgnoreExpression", "Modem" ); m_backendFilter = config.readEntry<>( "Backends", QList() ); MixerToolBox::instance()->setMixerIgnoreExpression(mixerIgnoreExpression); } void KMixD::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"; MixerToolBox::instance()->possiblyAddMixer(mixer); } } void KMixD::unplugged( const QString& udi) { // qCDebug(KMIX_LOG) << "Unplugged: udi=" <udi() << "\n"; if (mixer->udi() == udi ) { qCDebug(KMIX_LOG) << "Unplugged Match: Removing udi=" <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); } break; } } } #include "kmixd.moc" diff --git a/backends/kmix-backends.cpp b/backends/kmix-backends.cpp index 13970993..5479602c 100644 --- a/backends/kmix-backends.cpp +++ b/backends/kmix-backends.cpp @@ -1,120 +1,117 @@ /* * 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. */ /* This code is being #include'd from mixer.cpp */ #include -#ifdef HAVE_LIBASOUND2 -#include -#endif #include "mixer_backend.h" #include "core/mixer.h" #include #if defined(sun) || defined(__sun__) #define SUN_MIXER #endif #ifdef __linux__ #define OSS_MIXER #endif #if defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) || defined(__bsdi__) || defined(_UNIXWARE) #define OSS_MIXER #endif #if defined(hpux) #error "The HP/UX port is not maintained anymore, an no official part of KMix / KDE at this point of time! Please contact the current KMix maintainer if you would like to maintain the port." #endif // hpux // PORTING: add #ifdef PLATFORM , commands , #endif, add your new mixer below // Compiled by its own! //#include "backends/mixer_mpris2.cpp" #if defined(SUN_MIXER) #include "backends/mixer_sun.cpp" #endif // OSS 3 / 4 #if defined(OSS_MIXER) #include "backends/mixer_oss.cpp" #if !defined(__NetBSD__) && !defined(__OpenBSD__) #include #else #include #endif #if !defined(__FreeBSD__) && (SOUND_VERSION >= 0x040000) #define OSS4_MIXER #endif #endif #if defined(OSS4_MIXER) #include "backends/mixer_oss4.cpp" #endif // Possibly encapsualte by #ifdef HAVE_DBUS Mixer_Backend* MPRIS2_getMixer(Mixer *mixer, int device ); QString MPRIS2_getDriverName(); Mixer_Backend* ALSA_getMixer(Mixer *mixer, int device ); QString ALSA_getDriverName(); Mixer_Backend* PULSE_getMixer(Mixer *mixer, int device ); QString PULSE_getDriverName(); MixerFactory g_mixerFactories[] = { #if defined(SUN_MIXER) { SUN_getMixer, SUN_getDriverName }, #endif #if defined(HAVE_PULSE) { PULSE_getMixer, PULSE_getDriverName }, #endif #if defined(HAVE_LIBASOUND2) { ALSA_getMixer, ALSA_getDriverName }, #endif #if defined(OSS_MIXER) { OSS_getMixer, OSS_getDriverName }, #endif #if defined(OSS4_MIXER) { OSS4_getMixer, OSS4_getDriverName }, #endif // Make sure MPRIS2 is at the end. Implementation of SINGLE_PLUS_MPRIS2 in MixerToolBox is much easier. // And also we make sure, streams are always the last backend, which is important for the default KMix GUI layout. { MPRIS2_getMixer, MPRIS2_getDriverName }, { 0, 0 } }; diff --git a/backends/mixer_alsa9.cpp b/backends/mixer_alsa9.cpp index 94393caf..6c2a8bf5 100644 --- a/backends/mixer_alsa9.cpp +++ b/backends/mixer_alsa9.cpp @@ -1,1007 +1,1007 @@ /* * KMix -- KDE's full featured mini mixer * Alsa 0.9x and 1.0 - Based on original alsamixer code * from alsa-project ( www/alsa-project.org ) * * * Copyright (C) 2002 Helio Chissini de Castro * 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. */ // KMix #include "mixer_alsa.h" #include "core/kmixdevicemanager.h" #include "core/mixer.h" #include "core/volume.h" // KDE #include // Qt #include // STD Headers #include #include #include #include #include //#include "core/mixer.h" //class Mixer; // #define if you want MUCH debugging output //#define ALSA_SWITCH_DEBUG //#define KMIX_ALSA_VOLUME_DEBUG Mixer_Backend* ALSA_getMixer(Mixer *mixer, int device ) { Mixer_Backend *l_mixer; l_mixer = new Mixer_ALSA(mixer, device ); return l_mixer; } Mixer_ALSA::Mixer_ALSA( Mixer* mixer, int device ) : Mixer_Backend(mixer, device ) { m_fds = 0; _handle = 0; ctl_handle = 0; _initialUpdate = true; } Mixer_ALSA::~Mixer_ALSA() { close(); } int Mixer_ALSA::identify( snd_mixer_selem_id_t *sid ) { const char * cname = snd_mixer_selem_id_get_name( sid ); QByteArray name = QByteArray::fromRawData( cname, qstrlen(cname) ).toLower(); if (name.contains("master" )) return MixDevice::VOLUME; if (name.contains("master mono")) return MixDevice::VOLUME; if (name.contains("front" ) && !name.contains("mic" )) return MixDevice::VOLUME; if (name.contains("pc speaker" )) return MixDevice::SPEAKER; if (name.contains("capture" )) return MixDevice::RECMONITOR; if (name.contains("music" )) return MixDevice::MIDI; if (name.contains("synth" )) return MixDevice::MIDI; if (name.contains("fm" )) return MixDevice::MIDI; if (name.contains("headphone" )) return MixDevice::HEADPHONE; if (name.contains("bass" )) return MixDevice::BASS; if (name.contains("treble" )) return MixDevice::TREBLE; if (name.contains("cd" )) return MixDevice::CD; if (name.contains("video" )) return MixDevice::VIDEO; if (name.contains("pcm" )) return MixDevice::AUDIO; if (name.contains("wave" )) return MixDevice::AUDIO; if (name.contains("surround" )) return MixDevice::SURROUND_BACK; if (name.contains("center" )) return MixDevice::SURROUND_CENTERFRONT; if (name.contains("ac97" )) return MixDevice::AC97; if (name.contains("coaxial" )) return MixDevice::DIGITAL; if (name.contains("optical" )) return MixDevice::DIGITAL; if (name.contains("iec958" )) return MixDevice::DIGITAL; if (name.contains("digital" )) return MixDevice::DIGITAL; if (name.contains("mic boost" )) return MixDevice::MICROPHONE_BOOST; if (name.contains("mic front" )) return MixDevice::MICROPHONE_FRONT; if (name.contains("front mic" )) return MixDevice::MICROPHONE_FRONT; if (name.contains("mic" )) return MixDevice::MICROPHONE; if (name.contains("lfe" )) return MixDevice::SURROUND_LFE; if (name.contains("monitor" )) return MixDevice::RECMONITOR; if (name.contains("3d" )) return MixDevice::SURROUND; if (name.contains("side" )) return MixDevice::SURROUND_BACK; return MixDevice::EXTERNAL; } int Mixer_ALSA::open() { int masterChosenQuality = 0; int err; snd_mixer_elem_t *elem; snd_mixer_selem_id_t *sid; bool USE_ALSO_ALLOCA = true; // TODO remove alloca() when adding "delete sid" in the destructor or close() if (USE_ALSO_ALLOCA) { snd_mixer_selem_id_alloca( &sid ); } // Determine a card name if( m_devnum < -1 || m_devnum > 31 ) devName = "default"; else devName = QString( "hw:%1" ).arg( m_devnum ); // Open the card err = openAlsaDevice(devName); if ( err != 0 ) { return err; } _udi = KMixDeviceManager::instance()->getUDI_ALSA(m_devnum); if ( _udi.isEmpty() ) { QString msg("No UDI found for '"); msg += devName; msg += "'. Hotplugging not possible"; qCWarning(KMIX_LOG) << msg; } // Run a loop over all controls of the card unsigned int idx = 0; for ( elem = snd_mixer_first_elem( _handle ); elem; elem = snd_mixer_elem_next( elem ) ) { // If element is not active, just skip if ( ! snd_mixer_selem_is_active ( elem ) ) { continue; } /* --- Create basic control structures: snd_mixer_selem_id_t*, ID, ... --------- */ // snd_mixer_selem_id_t* // I believe we must malloc it ourself (just guessing due to missing ALSA documentation) snd_mixer_selem_id_malloc ( &sid ); // !! Return code should be checked. Resource must be freed when unplugging card snd_mixer_selem_get_id( elem, sid ); // Generate ID QString mdID("%1:%2"); mdID = mdID.arg(snd_mixer_selem_id_get_name ( sid ) ) .arg(snd_mixer_selem_id_get_index( sid ) ); mdID.replace(' ','_'); // Any key/ID we use, must not uses spaces (rule) MixDevice::ChannelType ct = (MixDevice::ChannelType)identify( sid ); /* ------------------------------------------------------------------------------- */ Volume* volPlay = 0; Volume* volCapture = 0; QList enumList; if ( snd_mixer_selem_is_enumerated(elem) ) { // --- Enumerated --- addEnumerated(elem, enumList); } else { volPlay = addVolume(elem, false); volCapture = addVolume(elem, true ); } QString readableName; readableName = snd_mixer_selem_id_get_name( sid ); int controlInstanceIndex = snd_mixer_selem_id_get_index( sid ); if ( controlInstanceIndex > 0 ) { // Add a number to the control name, like "PCM 2", when the index is > 0 QString idxString; idxString.setNum(1+controlInstanceIndex); readableName += ' '; readableName += idxString; } // There can be an Enum-Control with the same name as a regular control. So we append a ".[cp]enum" prefix to always create a unique ID QString finalMixdeviceID = mdID; if ( ! enumList.isEmpty() ) { if (snd_mixer_selem_is_enum_capture ( elem ) ) finalMixdeviceID = mdID + ".cenum"; // capture enum else finalMixdeviceID = mdID + ".penum"; // playback enum } m_id2numHash[finalMixdeviceID] = idx; //qCDebug(KMIX_LOG) << "m_id2numHash[mdID] mdID=" << mdID << " idx=" << idx; mixer_elem_list.append( elem ); mixer_sid_list.append( sid ); idx++; MixDevice* mdNew = new MixDevice(_mixer, finalMixdeviceID, readableName, ct ); if ( volPlay != 0 ) { mdNew->addPlaybackVolume(*volPlay); delete volPlay; } if ( volCapture != 0 ) { mdNew->addCaptureVolume (*volCapture); delete volCapture; } if ( !enumList.isEmpty() ) { mdNew->addEnums(enumList); qDeleteAll(enumList); // clear temporary list } shared_ptr md = mdNew->addToPool(); m_mixDevices.append( md ); // --- Recommended master ---------------------------------------- if ( md->playbackVolume().hasVolume() ) { if ( mdID == "Master:0" && masterChosenQuality < 100 ) { // qCDebug(KMIX_LOG) << "Setting m_recommendedMaster to " << mdID; m_recommendedMaster = md; masterChosenQuality = 100; } else if ( mdID == "PCM:0" && masterChosenQuality < 80) { // qCDebug(KMIX_LOG) << "Setting m_recommendedMaster to " << mdID; m_recommendedMaster = md; masterChosenQuality = 80; } else if ( mdID == "Front:0" && masterChosenQuality < 60) { // qCDebug(KMIX_LOG) << "Setting m_recommendedMaster to " << mdID; m_recommendedMaster = md; masterChosenQuality = 60; } else if ( mdID == "DAC:0" && masterChosenQuality < 50) { // qCDebug(KMIX_LOG) << "Setting m_recommendedMaster to " << mdID; m_recommendedMaster = md; masterChosenQuality = 50; } else if ( mdID == "Headphone:0" && masterChosenQuality < 40) { // qCDebug(KMIX_LOG) << "Setting m_recommendedMaster to " << mdID; m_recommendedMaster = md; masterChosenQuality = 40; } else if ( mdID == "Master Mono:0" && masterChosenQuality < 30) { // qCDebug(KMIX_LOG) << "Setting m_recommendedMaster to " << mdID; m_recommendedMaster = md; masterChosenQuality = 30; } } } // for all elems m_isOpen = true; // return with success setupAlsaPolling(); // For updates return 0; } // warnOnce will make sure we only print the first ALSA device not found bool Mixer_ALSA::warnOnce = true; /** * This opens a ALSA device for further interaction. * As this is "slightly" more complicated than calling ::open(), it is put in a separate method. */ int Mixer_ALSA::openAlsaDevice(const QString& devName) { int err; QString probeMessage; probeMessage += "Trying ALSA Device '" + devName + "': "; - if ( ( err = snd_ctl_open ( &ctl_handle, devName.toAscii().data(), 0 ) ) < 0 ) + if ( ( err = snd_ctl_open ( &ctl_handle, devName.toLatin1().data(), 0 ) ) < 0 ) { if (Mixer_ALSA::warnOnce) { Mixer_ALSA::warnOnce = false; qCDebug(KMIX_LOG) << probeMessage << "not found: snd_ctl_open err=" << snd_strerror(err); } return Mixer::ERR_OPEN; } // Mixer name snd_ctl_card_info_t *hw_info; snd_ctl_card_info_alloca(&hw_info); if ( ( err = snd_ctl_card_info ( ctl_handle, hw_info ) ) < 0 ) { if (Mixer_ALSA::warnOnce) { Mixer_ALSA::warnOnce = false; qCDebug(KMIX_LOG) << probeMessage << "not found: snd_ctl_card_info err=" << snd_strerror(err); } //_stateMessage = errorText( Mixer::ERR_READ ); snd_ctl_close( ctl_handle ); return Mixer::ERR_READ; } const char* mixer_card_name = snd_ctl_card_info_get_name( hw_info ); //QString mixer_card_name_QString = mixer_card_name; registerCard(mixer_card_name); snd_ctl_close( ctl_handle ); /* open mixer device */ if ( ( err = snd_mixer_open ( &_handle, 0 ) ) < 0 ) { if (Mixer_ALSA::warnOnce) { Mixer_ALSA::warnOnce = false; qCDebug(KMIX_LOG) << probeMessage << "not found: snd_mixer_open err=" << snd_strerror(err); } _handle = 0; return Mixer::ERR_OPEN; // if we cannot open the mixer, we have no devices } - if ( ( err = snd_mixer_attach ( _handle, devName.toAscii().data() ) ) < 0 ) + if ( ( err = snd_mixer_attach ( _handle, devName.toLatin1().data() ) ) < 0 ) { if (Mixer_ALSA::warnOnce) { Mixer_ALSA::warnOnce = false; qCDebug(KMIX_LOG) << probeMessage << "not found: snd_mixer_attach err=" << snd_strerror(err); } return Mixer::ERR_OPEN; } if ( ( err = snd_mixer_selem_register ( _handle, NULL, NULL ) ) < 0 ) { if (Mixer_ALSA::warnOnce) { Mixer_ALSA::warnOnce = false; qCDebug(KMIX_LOG) << probeMessage << "not found: snd_mixer_selem_register err=" << snd_strerror(err); } return Mixer::ERR_READ; } if ( ( err = snd_mixer_load ( _handle ) ) < 0 ) { if (Mixer_ALSA::warnOnce) { Mixer_ALSA::warnOnce = false; qCDebug(KMIX_LOG) << probeMessage << "not found: snd_mixer_load err=" << snd_strerror(err); } close(); return Mixer::ERR_READ; } Mixer_ALSA::warnOnce = true; qCDebug(KMIX_LOG) << probeMessage << "found"; return 0; } /** * Setup for select on stdin and the mixer fd. Every call * * @return A return value from Mixer::MixerError */ int Mixer_ALSA::setupAlsaPolling() { // --- Step 1: Retrieve FD's from ALSALIB int err; int countNew = 0; if ((countNew = snd_mixer_poll_descriptors_count(_handle)) < 0) { qCDebug(KMIX_LOG) << "Mixer_ALSA::poll() , snd_mixer_poll_descriptors_count() err=" << countNew << "\n"; return Mixer::ERR_OPEN; } /* * The following "if (true)" read in earlier versions: * if ( countNew != m_sns.size() ) * * This mimics alsamixer behaviour. But reality has proven that * it is not enough to check for size change. Situations came up where * the size was identical, but the descriptors changed. This especially * seems to happen shortly after the kernel loads a soundcard driver and alsalib * initializes it. Very hard to reproduce, so I do expect some kind of race condition * there. * So the final solution is to ALWAYS use the freshest fd's * delivered by the snd_mixer_poll_descriptors_count() call from above. */ if (true) { // As documentation purpose, please keep the "if (true)" and the comment above explaining it. while (!m_sns.isEmpty()) delete m_sns.takeFirst(); free(m_fds); m_fds = (struct pollfd*)calloc(countNew, sizeof(struct pollfd)); if (m_fds == NULL) { qCDebug(KMIX_LOG) << "Mixer_ALSA::poll() , calloc() = null" << "\n"; return Mixer::ERR_OPEN; } if ((err = snd_mixer_poll_descriptors(_handle, m_fds, countNew)) < 0) { qCDebug(KMIX_LOG) << "Mixer_ALSA::poll() , snd_mixer_poll_descriptors_count() err=" << err << "\n"; return Mixer::ERR_OPEN; } if (err != countNew) { qCDebug(KMIX_LOG) << "Mixer_ALSA::poll() , snd_mixer_poll_descriptors_count() err=" << err << " m_count=" << countNew << "\n"; return Mixer::ERR_OPEN; } // --- Step 2: Create QSocketNotifier's for the FD's for ( int i = 0; i < countNew; ++i ) { QSocketNotifier* qsn = new QSocketNotifier(m_fds[i].fd, QSocketNotifier::Read); m_sns.append(qsn); connect(qsn, SIGNAL(activated(int)), SLOT(readSetFromHW()), Qt::QueuedConnection); } } return 0; } void Mixer_ALSA::addEnumerated(snd_mixer_elem_t *elem, QList& enumList) { // --- get Enum names START --- int numEnumitems = snd_mixer_selem_get_enum_items(elem); if ( numEnumitems > 0 ) { // OK. no error for (int iEnum = 0; iEnum ignore this entry } } Volume* Mixer_ALSA::addVolume(snd_mixer_elem_t *elem, bool capture) { Volume* vol = 0; long maxVolume = 0, minVolume = 0; // Add volumes if ( !capture && snd_mixer_selem_has_playback_volume(elem) ) { snd_mixer_selem_get_playback_volume_range( elem, &minVolume, &maxVolume ); } else if ( capture && snd_mixer_selem_has_capture_volume(elem) ) { snd_mixer_selem_get_capture_volume_range( elem, &minVolume, &maxVolume ); } // Check if this control has at least one volume control bool hasVolume = snd_mixer_selem_has_playback_volume(elem) || snd_mixer_selem_has_capture_volume(elem); // Check if a appropriate switch is present (appropriate means, based o nthe "capture" parameter) bool hasCommonSwitch = snd_mixer_selem_has_common_switch ( elem ); bool hasSwitch = hasCommonSwitch | capture ? snd_mixer_selem_has_capture_switch ( elem ) : snd_mixer_selem_has_playback_switch ( elem ); if ( hasVolume || hasSwitch ) { //qCDebug(KMIX_LOG) << "Add somthing with chn=" << chn << ", capture=" << capture; vol = new Volume( maxVolume, minVolume, hasSwitch, capture); // Add volumes if ( !capture && snd_mixer_selem_has_playback_volume(elem) ) { if ( snd_mixer_selem_has_playback_channel(elem,SND_MIXER_SCHN_FRONT_LEFT )) vol->addVolumeChannel(VolumeChannel(Volume::LEFT)); if ( snd_mixer_selem_has_playback_channel(elem,SND_MIXER_SCHN_FRONT_RIGHT )) vol->addVolumeChannel(VolumeChannel(Volume::RIGHT)); if ( snd_mixer_selem_has_playback_channel(elem,SND_MIXER_SCHN_FRONT_CENTER)) vol->addVolumeChannel(VolumeChannel(Volume::CENTER)); if ( snd_mixer_selem_has_playback_channel(elem,SND_MIXER_SCHN_REAR_LEFT )) vol->addVolumeChannel(VolumeChannel(Volume::SURROUNDLEFT)); if ( snd_mixer_selem_has_playback_channel(elem,SND_MIXER_SCHN_REAR_RIGHT )) vol->addVolumeChannel(VolumeChannel(Volume::SURROUNDRIGHT)); if ( snd_mixer_selem_has_playback_channel(elem,SND_MIXER_SCHN_REAR_CENTER )) vol->addVolumeChannel(VolumeChannel(Volume::REARCENTER)); if ( snd_mixer_selem_has_playback_channel(elem,SND_MIXER_SCHN_WOOFER )) vol->addVolumeChannel(VolumeChannel(Volume::WOOFER)); if ( snd_mixer_selem_has_playback_channel(elem,SND_MIXER_SCHN_SIDE_LEFT )) vol->addVolumeChannel(VolumeChannel(Volume::REARSIDELEFT)); if ( snd_mixer_selem_has_playback_channel(elem,SND_MIXER_SCHN_SIDE_RIGHT )) vol->addVolumeChannel(VolumeChannel(Volume::REARSIDERIGHT)); } else if ( capture && snd_mixer_selem_has_capture_volume(elem) ) { if ( snd_mixer_selem_has_capture_channel(elem,SND_MIXER_SCHN_FRONT_LEFT )) vol->addVolumeChannel(VolumeChannel(Volume::LEFT)); if ( snd_mixer_selem_has_capture_channel(elem,SND_MIXER_SCHN_FRONT_RIGHT )) vol->addVolumeChannel(VolumeChannel(Volume::RIGHT)); if ( snd_mixer_selem_has_capture_channel(elem,SND_MIXER_SCHN_FRONT_CENTER)) vol->addVolumeChannel(VolumeChannel(Volume::CENTER)); if ( snd_mixer_selem_has_capture_channel(elem,SND_MIXER_SCHN_REAR_LEFT )) vol->addVolumeChannel(VolumeChannel(Volume::SURROUNDLEFT)); if ( snd_mixer_selem_has_capture_channel(elem,SND_MIXER_SCHN_REAR_RIGHT )) vol->addVolumeChannel(VolumeChannel(Volume::SURROUNDRIGHT)); if ( snd_mixer_selem_has_capture_channel(elem,SND_MIXER_SCHN_REAR_CENTER )) vol->addVolumeChannel(VolumeChannel(Volume::REARCENTER)); if ( snd_mixer_selem_has_capture_channel(elem,SND_MIXER_SCHN_WOOFER )) vol->addVolumeChannel(VolumeChannel(Volume::WOOFER)); if ( snd_mixer_selem_has_capture_channel(elem,SND_MIXER_SCHN_SIDE_LEFT )) vol->addVolumeChannel(VolumeChannel(Volume::REARSIDELEFT)); if ( snd_mixer_selem_has_capture_channel(elem,SND_MIXER_SCHN_SIDE_RIGHT )) vol->addVolumeChannel(VolumeChannel(Volume::REARSIDERIGHT)); } } return vol; } void Mixer_ALSA::deinitAlsaPolling() { if ( m_fds ) free( m_fds ); m_fds = 0; while (!m_sns.isEmpty()) delete m_sns.takeFirst(); } int Mixer_ALSA::close() { // qCDebug(KMIX_LOG) << "close " << this; int ret=0; m_isOpen = false; if ( ctl_handle != 0) { //snd_ctl_close( ctl_handle ); ctl_handle = 0; } if ( _handle != 0 ) { //qCDebug(KMIX_LOG) << "IN Mixer_ALSA::close()"; snd_mixer_free ( _handle ); - if ( ( ret = snd_mixer_detach ( _handle, devName.toAscii().data() ) ) < 0 ) + if ( ( ret = snd_mixer_detach ( _handle, devName.toLatin1().data() ) ) < 0 ) { qCDebug(KMIX_LOG) << "snd_mixer_detach err=" << snd_strerror(ret); } int ret2 = 0; if ( ( ret2 = snd_mixer_close ( _handle ) ) < 0 ) { qCDebug(KMIX_LOG) << "snd_mixer_close err=" << snd_strerror(ret2); if ( ret == 0 ) ret = ret2; // no error before => use current error code } _handle = 0; //qCDebug(KMIX_LOG) << "OUT Mixer_ALSA::close()"; } mixer_elem_list.clear(); mixer_sid_list.clear(); m_id2numHash.clear(); deinitAlsaPolling(); closeCommon(); return ret; } /** * Resolve index to a control (snd_mixer_elem_t*) * @par idx Index to query. For any invalid index (including -1) returns a 0 control. */ snd_mixer_elem_t* Mixer_ALSA::getMixerElem(int idx) { snd_mixer_elem_t* elem = 0; if ( ! m_isOpen ) return elem; // unplugging guard if ( idx == -1 ) { return elem; } if ( int( mixer_sid_list.count() ) > idx ) { snd_mixer_selem_id_t * sid = mixer_sid_list[ idx ]; // The next line (hopefully) only finds selem's, not elem's. elem = snd_mixer_find_selem(_handle, sid); if ( elem == 0 ) { // !! Check, whether the warning should be omitted. Probably // Route controls are non-simple elements. qCDebug(KMIX_LOG) << "Error finding mixer element " << idx; } } return elem; /* I would have liked to use the following trivial implementation instead of the code above. But it will also return elem's. which are not selem's. As there is no way to check an elem's type (e.g. elem->type == SND_MIXER_ELEM_SIMPLE), callers of getMixerElem() cannot check the type. :-( snd_mixer_elem_t* elem = mixer_elem_list[ devnum ]; return elem; */ } int Mixer_ALSA::id2num(const QString& id) { //qCDebug(KMIX_LOG) << "id2num() id=" << id; int num = -1; if ( m_id2numHash.contains(id) ) { num = m_id2numHash[id]; } //qCDebug(KMIX_LOG) << "id2num() num=" << num; return num; } bool Mixer_ALSA::hasChangedControls() { if ( !m_fds || !m_isOpen ) return false; setupAlsaPolling(); // Poll on fds with 10ms timeout // Hint: alsamixer has an infinite timeout, but we cannot do this because we would block // the X11 event handling (Qt event loop) with this. int finished = poll(m_fds, m_sns.size(), 10); // TODO Could we pass 0 as timeout here? It makes no real sense to wait! if (finished > 0) { unsigned short revents; if (snd_mixer_poll_descriptors_revents(_handle, m_fds, m_sns.size(), &revents) >= 0) { if (revents & POLLNVAL) { /* Bug 127294 shows, that we receive POLLNVAL when the user unplugs an USB soundcard. Lets close the card. */ qCDebug(KMIX_LOG) << "Mixer_ALSA::poll() , Error: poll() returns POLLNVAL\n"; close(); // Card was unplugged (unplug, driver unloaded) } else if (revents & POLLERR) { qCDebug(KMIX_LOG) << "Mixer_ALSA::poll() , Error: poll() returns POLLERR\n"; } else if (revents & POLLIN) { //qCDebug(KMIX_LOG) << "Mixer_ALSA::prepareUpdate() 7\n"; int eventCount = snd_mixer_handle_events(_handle); if (eventCount >= 0) { /* * Treating everything that is not an error as a change, even if eventCount == 0. * For example, when unplugging the headphones from my ThinkPad Laptop ALSA reports "POLLIN" with eventCount == 0. * On the other hand, this means I can not likely detect changes */ // qCDebug(KMIX_LOG) << "Mixer_ALSA::poll() delivered changes. eventCount=" << eventCount; return true; } else { qCWarning(KMIX_LOG) << "Mixer_ALSA::poll() , Error: poll() returns POLLIN with errno=" + eventCount; } } } } return false; } bool Mixer_ALSA::isRecsrcHW( const QString& id ) { int devnum = id2num(id); bool isCurrentlyRecSrc = false; snd_mixer_elem_t *elem = getMixerElem( devnum ); if ( !elem ) { return false; } if ( snd_mixer_selem_has_capture_switch( elem ) ) { // Has a on-off switch // Yes, this element can be record source. But the user can switch it off, so lets see if it is switched on. int swLeft; int ret = snd_mixer_selem_get_capture_switch( elem, SND_MIXER_SCHN_FRONT_LEFT, &swLeft ); if ( ret != 0 ) qCDebug(KMIX_LOG) << "snd_mixer_selem_get_capture_switch() failed 1\n"; if (snd_mixer_selem_has_capture_switch_joined( elem ) ) { isCurrentlyRecSrc = (swLeft != 0); #ifdef ALSA_SWITCH_DEBUG qCDebug(KMIX_LOG) << "has_switch joined: #" << devnum << " >>> " << swLeft << " : " << isCurrentlyRecSrc; #endif } else { int swRight; snd_mixer_selem_get_capture_switch( elem, SND_MIXER_SCHN_FRONT_RIGHT, &swRight ); isCurrentlyRecSrc = ( (swLeft != 0) || (swRight != 0) ); #ifdef ALSA_SWITCH_DEBUG qCDebug(KMIX_LOG) << "has_switch non-joined, state " << isCurrentlyRecSrc; #endif } } else { // Has no on-off switch if ( snd_mixer_selem_has_capture_volume( elem ) ) { // Has a volume, but has no OnOffSwitch => We assume that this is a fixed record source (always on). (esken) isCurrentlyRecSrc = true; #ifdef ALSA_SWITCH_DEBUG qCDebug(KMIX_LOG) << "has_no_switch, state " << isCurrentlyRecSrc; #endif } } return isCurrentlyRecSrc; } /** * Sets the ID of the currently selected Enum entry. * Warning: ALSA supports to have different enums selected on each channel * of the SAME snd_mixer_elem_t. KMix does NOT support that and * always sets both channels (0 and 1). */ void Mixer_ALSA::setEnumIdHW(const QString& id, unsigned int idx) { //qCDebug(KMIX_LOG) << "Mixer_ALSA::setEnumIdHW() id=" << id << " , idx=" << idx << ") 1\n"; int devnum = id2num(id); snd_mixer_elem_t *elem = getMixerElem( devnum ); for (int i = 0; i <= SND_MIXER_SCHN_LAST; ++i) { int ret = snd_mixer_selem_set_enum_item(elem, (snd_mixer_selem_channel_id_t)i,idx); if (ret < 0 && i == 0) { // Log errors only for one channel. This should be enough, and another reason is that I also do not check which channels are supported at all. qCCritical(KMIX_LOG) << "Mixer_ALSA::setEnumIdHW(" << devnum << "), errno=" << ret << "\n"; } } return; } /** * Return the ID of the currently selected Enum entry. * Warning: ALSA supports to have different enums selected on each channel * of the SAME snd_mixer_elem_t. KMix does NOT support that and * always shows the value of the first channel. */ unsigned int Mixer_ALSA::enumIdHW(const QString& id) { int devnum = id2num(id); snd_mixer_elem_t *elem = getMixerElem( devnum ); unsigned int idx = 0; if ( elem != 0 && snd_mixer_selem_is_enumerated(elem) ) { int ret = snd_mixer_selem_get_enum_item(elem,SND_MIXER_SCHN_FRONT_LEFT,&idx); if (ret < 0) { idx = 0; qCCritical(KMIX_LOG) << "Mixer_ALSA::enumIdHW(" << devnum << "), errno=" << ret << "\n"; } } return idx; } int Mixer_ALSA::readVolumeFromHW( const QString& id, shared_ptr md ) { Volume& volumePlayback = md->playbackVolume(); Volume& volumeCapture = md->captureVolume(); int devnum = id2num(id); int elem_sw; long vol; snd_mixer_elem_t *elem = getMixerElem( devnum ); if ( !elem ) { return Mixer::OK_UNCHANGED; } vol = Volume::MNONE; // --- playback volume if ( snd_mixer_selem_has_playback_volume( elem ) ) { if ( md->isVirtuallyMuted() ) { // Special code path for controls w/o physical mute switch. Doing it in all backends is not perfect, // but it saves a lot of code and removes a lot of complexity in the Volume and MixDevice classes. // Don't feed back the actual 0 volume back from the device to KMix. Just do nothing! } else { foreach (VolumeChannel vc, volumePlayback.getVolumes() ) { int ret = 0; switch(vc.chid) { case Volume::LEFT : ret = snd_mixer_selem_get_playback_volume( elem, SND_MIXER_SCHN_FRONT_LEFT , &vol); break; case Volume::RIGHT : ret = snd_mixer_selem_get_playback_volume( elem, SND_MIXER_SCHN_FRONT_RIGHT , &vol); break; case Volume::CENTER : ret = snd_mixer_selem_get_playback_volume( elem, SND_MIXER_SCHN_FRONT_CENTER, &vol); break; case Volume::SURROUNDLEFT : ret = snd_mixer_selem_get_playback_volume( elem, SND_MIXER_SCHN_REAR_LEFT , &vol); break; case Volume::SURROUNDRIGHT: ret = snd_mixer_selem_get_playback_volume( elem, SND_MIXER_SCHN_REAR_RIGHT , &vol); break; case Volume::REARCENTER : ret = snd_mixer_selem_get_playback_volume( elem, SND_MIXER_SCHN_REAR_CENTER , &vol); break; case Volume::WOOFER : ret = snd_mixer_selem_get_playback_volume( elem, SND_MIXER_SCHN_WOOFER , &vol); break; case Volume::REARSIDELEFT : ret = snd_mixer_selem_get_playback_volume( elem, SND_MIXER_SCHN_SIDE_LEFT , &vol); break; case Volume::REARSIDERIGHT: ret = snd_mixer_selem_get_playback_volume( elem, SND_MIXER_SCHN_SIDE_RIGHT , &vol); break; default: qCDebug(KMIX_LOG) << "FATAL: Unknown channel type for playback << " << vc.chid << " ... please report this"; break; } if ( ret != 0 ) qCDebug(KMIX_LOG) << "readVolumeFromHW(" << devnum << ") [get_playback_volume] failed, errno=" << ret; else volumePlayback.setVolume( vc.chid, vol); //if (id== "Master:0" || id== "PCM:0" ) { qCDebug(KMIX_LOG) << "volumePlayback control=" << id << ", chid=" << i << ", vol=" << vol; } } } } // has playback volume // --- playback switch // TODO: What about has_common_switch() if ( snd_mixer_selem_has_playback_switch( elem ) ) { snd_mixer_selem_get_playback_switch( elem, SND_MIXER_SCHN_FRONT_LEFT, &elem_sw ); md->setMuted( elem_sw == 0 ); } vol = Volume::MNONE; // --- capture volume if ( snd_mixer_selem_has_capture_volume ( elem ) ) { foreach (VolumeChannel vc, volumeCapture.getVolumes() ) { int ret = 0; switch(vc.chid) { case Volume::LEFT : ret = snd_mixer_selem_get_capture_volume( elem, SND_MIXER_SCHN_FRONT_LEFT , &vol); break; case Volume::RIGHT : ret = snd_mixer_selem_get_capture_volume( elem, SND_MIXER_SCHN_FRONT_RIGHT , &vol); break; case Volume::CENTER : ret = snd_mixer_selem_get_capture_volume( elem, SND_MIXER_SCHN_FRONT_CENTER, &vol); break; case Volume::SURROUNDLEFT : ret = snd_mixer_selem_get_capture_volume( elem, SND_MIXER_SCHN_REAR_LEFT , &vol); break; case Volume::SURROUNDRIGHT: ret = snd_mixer_selem_get_capture_volume( elem, SND_MIXER_SCHN_REAR_RIGHT , &vol); break; case Volume::REARCENTER : ret = snd_mixer_selem_get_capture_volume( elem, SND_MIXER_SCHN_REAR_CENTER , &vol); break; case Volume::WOOFER : ret = snd_mixer_selem_get_capture_volume( elem, SND_MIXER_SCHN_WOOFER , &vol); break; case Volume::REARSIDELEFT : ret = snd_mixer_selem_get_capture_volume( elem, SND_MIXER_SCHN_SIDE_LEFT , &vol); break; case Volume::REARSIDERIGHT: ret = snd_mixer_selem_get_capture_volume( elem, SND_MIXER_SCHN_SIDE_RIGHT , &vol); break; default: qCDebug(KMIX_LOG) << "FATAL: Unknown channel type for capture << " << vc.chid << " ... please report this"; break; } if ( ret != 0 ) qCDebug(KMIX_LOG) << "readVolumeFromHW(" << devnum << ") [get_capture_volume] failed, errno=" << ret; else volumeCapture.setVolume( vc.chid, vol); } } // has capture volume // --- capture switch // TODO: What about has_common_switch() if ( snd_mixer_selem_has_capture_switch( elem ) ) { snd_mixer_selem_get_capture_switch( elem, SND_MIXER_SCHN_FRONT_LEFT, &elem_sw ); md->setRecSource( elem_sw == 1 ); // Refresh the capture switch information of *all* controls of this card. // Doing it for all is necessary, because enabling one record source often // automatically disables another record source (due to the hardware design) foreach ( shared_ptr md, m_mixDevices ) { bool isRecsrc = isRecsrcHW( md->id() ); // qCDebug(KMIX_LOG) << "Mixer::setRecordSource(): isRecsrcHW(" << md->id() << ") =" << isRecsrc; md->setRecSource( isRecsrc ); } } // The state Mixer::OK_UNCHANGED is not implemented. It is not strictly required for // non-pollling backends. return Mixer::OK; } int Mixer_ALSA::writeVolumeToHW( const QString& id, shared_ptr md ) { Volume& volumePlayback = md->playbackVolume(); Volume& volumeCapture = md->captureVolume(); int devnum = id2num(id); snd_mixer_elem_t *elem = getMixerElem( devnum ); if ( !elem ) { return 0; } // --- playback switch bool hasPlaybackSwitch = snd_mixer_selem_has_playback_switch( elem ) || snd_mixer_selem_has_common_switch ( elem ); if (hasPlaybackSwitch) { int sw = 0; if (!md->isMuted()) sw = !sw; // invert all bits snd_mixer_selem_set_playback_switch_all(elem, sw); } // --- playback volume if ( snd_mixer_selem_has_playback_volume( elem ) ) { // qCDebug(KMIX_LOG) << "phys=" << md->hasPhysicalMuteSwitch() << ", muted=" << md->isMuted(); if ( md->isVirtuallyMuted() ) { // Special code path for controls w/o physical mute switch. Doing it in all backends is not perfect, // but it saves a lot of code and removes a lot of complexity in the Volume and MixDevice classes. int ret = snd_mixer_selem_set_playback_volume_all( elem, (long)0); if ( ret != 0 ) qCDebug(KMIX_LOG) << "writeVolumeToHW(" << devnum << ") [set_playback_volume] failed, errno=" << ret; } else { foreach (VolumeChannel vc, volumePlayback.getVolumes() ) { int ret = 0; switch(vc.chid) { case Volume::LEFT : ret = snd_mixer_selem_set_playback_volume( elem, SND_MIXER_SCHN_FRONT_LEFT , vc.volume); break; case Volume::RIGHT : ret = snd_mixer_selem_set_playback_volume( elem, SND_MIXER_SCHN_FRONT_RIGHT , vc.volume); break; case Volume::CENTER : ret = snd_mixer_selem_set_playback_volume( elem, SND_MIXER_SCHN_FRONT_CENTER, vc.volume); break; case Volume::SURROUNDLEFT : ret = snd_mixer_selem_set_playback_volume( elem, SND_MIXER_SCHN_REAR_LEFT , vc.volume); break; case Volume::SURROUNDRIGHT: ret = snd_mixer_selem_set_playback_volume( elem, SND_MIXER_SCHN_REAR_RIGHT , vc.volume); break; case Volume::REARCENTER : ret = snd_mixer_selem_set_playback_volume( elem, SND_MIXER_SCHN_REAR_CENTER , vc.volume); break; case Volume::WOOFER : ret = snd_mixer_selem_set_playback_volume( elem, SND_MIXER_SCHN_WOOFER , vc.volume); break; case Volume::REARSIDELEFT : ret = snd_mixer_selem_set_playback_volume( elem, SND_MIXER_SCHN_SIDE_LEFT , vc.volume); break; case Volume::REARSIDERIGHT: ret = snd_mixer_selem_set_playback_volume( elem, SND_MIXER_SCHN_SIDE_RIGHT , vc.volume); break; default: qCDebug(KMIX_LOG) << "FATAL: Unknown channel type for playback << " << vc.chid << " ... please report this"; break; } if ( ret != 0 ) qCDebug(KMIX_LOG) << "writeVolumeToHW(" << devnum << ") [set_playback_volume] failed, errno=" << ret; //if (id== "Master:0" || id== "PCM:0" ) { qCDebug(KMIX_LOG) << "volumePlayback control=" << id << ", chid=" << vc.chid << ", vol=" << vc.volume; } } } } // has playback volume // --- capture volume if ( snd_mixer_selem_has_capture_volume ( elem ) ) { foreach (VolumeChannel vc, volumeCapture.getVolumes() ) { int ret = 0; switch(vc.chid) { case Volume::LEFT : ret = snd_mixer_selem_set_capture_volume( elem, SND_MIXER_SCHN_FRONT_LEFT , vc.volume); break; case Volume::RIGHT : ret = snd_mixer_selem_set_capture_volume( elem, SND_MIXER_SCHN_FRONT_RIGHT , vc.volume); break; case Volume::CENTER : ret = snd_mixer_selem_set_capture_volume( elem, SND_MIXER_SCHN_FRONT_CENTER, vc.volume); break; case Volume::SURROUNDLEFT : ret = snd_mixer_selem_set_capture_volume( elem, SND_MIXER_SCHN_REAR_LEFT , vc.volume); break; case Volume::SURROUNDRIGHT: ret = snd_mixer_selem_set_capture_volume( elem, SND_MIXER_SCHN_REAR_RIGHT , vc.volume); break; case Volume::REARCENTER : ret = snd_mixer_selem_set_capture_volume( elem, SND_MIXER_SCHN_REAR_CENTER , vc.volume); break; case Volume::WOOFER : ret = snd_mixer_selem_set_capture_volume( elem, SND_MIXER_SCHN_WOOFER , vc.volume); break; case Volume::REARSIDELEFT : ret = snd_mixer_selem_set_capture_volume( elem, SND_MIXER_SCHN_SIDE_LEFT , vc.volume); break; case Volume::REARSIDERIGHT: ret = snd_mixer_selem_set_capture_volume( elem, SND_MIXER_SCHN_SIDE_RIGHT , vc.volume); break; default: qCDebug(KMIX_LOG) << "FATAL: Unknown channel type for capture << " << vc.chid << " ... please report this"; break; } if ( ret != 0 ) qCDebug(KMIX_LOG) << "writeVolumeToHW(" << devnum << ") [set_capture_volume] failed, errno=" << ret; //if (id== "Master:0" || id== "PCM:0" ) { qCDebug(KMIX_LOG) << "volumecapture control=" << id << ", chid=" << i << ", vol=" << vc.volume; } } } // has capture volume // --- capture switch if ( snd_mixer_selem_has_capture_switch( elem ) ) { // Hint: snd_mixer_selem_has_common_switch() is already covered in the playback . // switch. This is probably enough. It would be helpful, if the ALSA project would // write documentation. Until then, I need to continue guessing semantics. int sw = 0; if ( md->isRecSource()) sw = !sw; // invert all bits snd_mixer_selem_set_capture_switch_all( elem, sw ); } return 0; } QString Mixer_ALSA::errorText( int mixer_error ) { QString l_s_errmsg; switch ( mixer_error ) { case Mixer::ERR_PERM: l_s_errmsg = i18n("You do not have permission to access the alsa mixer device.\n" \ "Please verify if all alsa devices are properly created."); break; case Mixer::ERR_OPEN: l_s_errmsg = i18n("Alsa mixer cannot be found.\n" \ "Please check that the soundcard is installed and the\n" \ "soundcard driver is loaded.\n" ); break; default: l_s_errmsg = Mixer_Backend::errorText( mixer_error ); } return l_s_errmsg; } QString ALSA_getDriverName() { return QStringLiteral("ALSA"); } QString Mixer_ALSA::getDriverName() { return QStringLiteral("ALSA"); } diff --git a/backends/mixer_oss.cpp b/backends/mixer_oss.cpp index f9a84b11..ccc9f5f7 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.toAscii().data(), O_RDWR)) < 0) + 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.toAscii().data(), O_RDWR)) < 0) + 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 ); 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 ); 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/backends/mixer_sun.cpp b/backends/mixer_sun.cpp index 85e1d0e5..1b7bfd36 100644 --- a/backends/mixer_sun.cpp +++ b/backends/mixer_sun.cpp @@ -1,495 +1,495 @@ /* * KMix -- KDE's full featured mini mixer * * * Copyright (C) 1996-2000 Christian Esken * 2000 Brian Hanson * * 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_sun.h" #include #include #include #include #include #include #include #include #include "core/mixer.h" #include #include //====================================================================== // CONSTANT/ENUM DEFINITIONS //====================================================================== // // Mixer Device Numbers // // Note: We can't just use the Sun port #defines because : // 1) Some logical devices don't correspond to ports (master&recmon) // 2) The play and record port definitions reuse the same values // enum MixerDevs { MIXERDEV_MASTER_VOLUME, MIXERDEV_INTERNAL_SPEAKER, MIXERDEV_HEADPHONE, MIXERDEV_LINE_OUT, MIXERDEV_RECORD_MONITOR, MIXERDEV_MICROPHONE, MIXERDEV_LINE_IN, MIXERDEV_CD, // Insert new devices before this marker MIXERDEV_END_MARKER }; const int numDevs = MIXERDEV_END_MARKER; // // Device name strings // const char* MixerDevNames[] = { I18N_NOOP("Master Volume"), I18N_NOOP("Internal Speaker"), I18N_NOOP("Headphone"), I18N_NOOP("Line Out"), I18N_NOOP("Record Monitor"), I18N_NOOP("Microphone"), I18N_NOOP("Line In"), I18N_NOOP("CD") }; // // Channel types (this specifies which icon to display) // const MixDevice::ChannelType MixerChannelTypes[] = { MixDevice::VOLUME, // MASTER_VOLUME MixDevice::AUDIO, // INTERNAL_SPEAKER MixDevice::EXTERNAL, // HEADPHONE (we really need an icon for this) MixDevice::EXTERNAL, // LINE_OUT MixDevice::RECMONITOR, // RECORD_MONITOR MixDevice::MICROPHONE, // MICROPHONE MixDevice::EXTERNAL, // LINE_IN MixDevice::CD // CD }; // // Mapping from device numbers to Sun port mask values // const uint_t MixerSunPortMasks[] = { 0, // MASTER_VOLUME - no associated port AUDIO_SPEAKER, AUDIO_HEADPHONE, AUDIO_LINE_OUT, 0, // RECORD_MONITOR - no associated port AUDIO_MICROPHONE, AUDIO_LINE_IN, AUDIO_CD }; //====================================================================== // FUNCTION/METHOD DEFINITIONS //====================================================================== //====================================================================== // FUNCTION : SUN_getMixer // DESCRIPTION : Creates and returns a new mixer object. //====================================================================== Mixer_Backend* SUN_getMixer( Mixer *mixer, int devnum ) { Mixer_Backend *l_mixer; l_mixer = new Mixer_SUN( mixer, devnum ); return l_mixer; } //====================================================================== // FUNCTION : Mixer::Mixer // DESCRIPTION : Class constructor. //====================================================================== Mixer_SUN::Mixer_SUN(Mixer *mixer, int devnum) : Mixer_Backend(mixer, devnum) { if ( devnum == -1 ) m_devnum = 0; } //====================================================================== // FUNCTION : Mixer::Mixer // DESCRIPTION : Class destructor. //====================================================================== Mixer_SUN::~Mixer_SUN() { close(); } //====================================================================== // FUNCTION : Mixer::open // DESCRIPTION : Initialize the mixer and open the hardware driver. //====================================================================== int Mixer_SUN::open() { // // We don't support multiple devices // if ( m_devnum !=0 ) return Mixer::ERR_OPEN; // // Open the mixer hardware driver // QString audiodev(getenv("AUDIODEV")); if(audiodev.isNull()) audiodev = "/dev/audio"; audiodev += "ctl"; _udi = audiodev; // use device name as UDI. Doesn't matter as we only use it for hotplugging/unplugging. - if ( ( fd = ::open( audiodev.toAscii().data(), O_RDWR ) ) < 0 ) + if ( ( fd = ::open( audiodev.toLatin1().data(), O_RDWR ) ) < 0 ) { if ( errno == EACCES ) return Mixer::ERR_PERM; else return Mixer::ERR_OPEN; } else { // // Mixer is open. Now define all of the mix devices. // int devmask, recmask, i_recsrc, stereodevs; // Mixer is open. Now define properties if (ioctl(fd, SOUND_MIXER_READ_DEVMASK, &devmask) == -1) return Mixer::ERR_READ; if (ioctl(fd, SOUND_MIXER_READ_RECMASK, &recmask) == -1) return Mixer::ERR_READ; if (ioctl(fd, SOUND_MIXER_READ_RECSRC, &i_recsrc) == -1) return Mixer::ERR_READ; if (ioctl(fd, SOUND_MIXER_READ_STEREODEVS, &stereodevs) == -1) return Mixer::ERR_READ; for ( int idx = 0; idx < numDevs; idx++ ) { Volume::ChannelMask chnmask = Volume::MLEFT; if ( stereodevs & ( 1 << idx ) ) chnmask = (Volume::ChannelMask)(chnmask|Volume::MRIGHT); Volume playbackVol( 100, 1, true, false ); QString id; id.setNum(idx); MixDevice* md = new MixDevice( _mixer, id, QString(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 chnmask = Volume::MNONE; Volume captureVol( 100, 1, true, true ); md->addCaptureVolume(captureVol); } m_mixDevices.append( md->addToPool() ); } registerCard("SUN Audio Mixer"); m_isOpen = true; return 0; } } //====================================================================== // FUNCTION : Mixer::close // DESCRIPTION : Close the hardware driver. //====================================================================== int Mixer_SUN::close() { _pollingTimer->stop(); m_isOpen = false; int l_i_ret = ::close( fd ); closeCommon(); return l_i_ret; } //====================================================================== // FUNCTION : Mixer::errorText // DESCRIPTION : Convert an error code enum to a text string. //====================================================================== QString Mixer_SUN::errorText( int mixer_error ) { QString errmsg; switch (mixer_error) { case Mixer::ERR_PERM: errmsg = i18n( "kmix: You do not have permission to access the mixer device.\n" "Ask your system administrator to fix /dev/audioctl to allow access." ); break; default: errmsg = Mixer_Backend::errorText( mixer_error ); } return errmsg; } //====================================================================== // FUNCTION : Mixer::readVolumeFromHW // DESCRIPTION : Read the audio information from the driver. //====================================================================== int Mixer_SUN::readVolumeFromHW( const QString& id, shared_ptr md ) { audio_info_t audioinfo; int devnum = id2num(id); uint_t devMask = MixerSunPortMasks[devnum]; Volume& volume = md->playbackVolume(); // // Read the current audio information from the driver // if ( ioctl( fd, AUDIO_GETINFO, &audioinfo ) < 0 ) { return( Mixer::ERR_READ ); } else { // // Extract the appropriate fields based on the requested device // switch ( devnum ) { case MIXERDEV_MASTER_VOLUME : //volume.setSwitchActivated( audioinfo.output_muted ); GainBalanceToVolume( audioinfo.play.gain, audioinfo.play.balance, volume ); break; case MIXERDEV_RECORD_MONITOR : md->setMuted(false); volume.setAllVolumes( audioinfo.monitor_gain ); break; case MIXERDEV_INTERNAL_SPEAKER : case MIXERDEV_HEADPHONE : case MIXERDEV_LINE_OUT : md->setMuted( (audioinfo.play.port & devMask) ? false : true ); GainBalanceToVolume( audioinfo.play.gain, audioinfo.play.balance, volume ); break; case MIXERDEV_MICROPHONE : case MIXERDEV_LINE_IN : case MIXERDEV_CD : md->setMuted( (audioinfo.record.port & devMask) ? false : true ); GainBalanceToVolume( audioinfo.record.gain, audioinfo.record.balance, volume ); break; default : return Mixer::ERR_READ; } return 0; } } //====================================================================== // FUNCTION : Mixer::writeVolumeToHW // DESCRIPTION : Write the specified audio settings to the hardware. //====================================================================== int Mixer_SUN::writeVolumeToHW( const QString& id, shared_ptr md ) { uint_t gain; uchar_t balance; uchar_t mute; Volume& volume = md->playbackVolume(); int devnum = id2num(id); // // Convert the Volume(left vol, right vol) to the Gain/Balance Sun uses // VolumeToGainBalance( volume, gain, balance ); mute = md->isMuted() ? 1 : 0; // // Read the current audio settings from the hardware // audio_info_t audioinfo; if ( ioctl( fd, AUDIO_GETINFO, &audioinfo ) < 0 ) { return( Mixer::ERR_READ ); } // // Now, based on the devnum that we are writing to, update the appropriate // volume field and twiddle the appropriate bitmask to enable/mute the // device as necessary. // switch ( devnum ) { case MIXERDEV_MASTER_VOLUME : audioinfo.play.gain = gain; audioinfo.play.balance = balance; audioinfo.output_muted = mute; break; case MIXERDEV_RECORD_MONITOR : audioinfo.monitor_gain = gain; // no mute or balance for record monitor break; case MIXERDEV_INTERNAL_SPEAKER : case MIXERDEV_HEADPHONE : case MIXERDEV_LINE_OUT : audioinfo.play.gain = gain; audioinfo.play.balance = balance; if ( mute ) audioinfo.play.port &= ~MixerSunPortMasks[devnum]; else audioinfo.play.port |= MixerSunPortMasks[devnum]; break; case MIXERDEV_MICROPHONE : case MIXERDEV_LINE_IN : case MIXERDEV_CD : audioinfo.record.gain = gain; audioinfo.record.balance = balance; if ( mute ) audioinfo.record.port &= ~MixerSunPortMasks[devnum]; else audioinfo.record.port |= MixerSunPortMasks[devnum]; break; default : return Mixer::ERR_READ; } // // Now that we've updated the audioinfo struct, write it back to the hardware // if ( ioctl( fd, AUDIO_SETINFO, &audioinfo ) < 0 ) { return( Mixer::ERR_WRITE ); } else { return 0; } } //====================================================================== // FUNCTION : Mixer::isRecsrcHW // DESCRIPTION : Returns true if the specified device is a record source. //====================================================================== // isRecsrcHW() is not supported any longer. You must set the state in the MixDevice in readVolumeFromHW() or writeVolumeFromHW() appropriately //bool Mixer_SUN::isRecsrcHW( isRecsrcHW(const QString& id ) //{ // int devnum = id2num(id); // switch ( devnum ) // { // case MIXERDEV_MICROPHONE : // case MIXERDEV_LINE_IN : // case MIXERDEV_CD : // return true; // // default : // return false; // } //} //====================================================================== // FUNCTION : Mixer::VolumeToGainBalance // DESCRIPTION : Converts a Volume(left vol + right vol) into the // Gain/Balance values used by Sun. //====================================================================== void Mixer_SUN::VolumeToGainBalance( Volume& volume, uint_t& gain, uchar_t& balance ) { if ( ( volume.count() == 1 ) || ( volume.getVolume(Volume::LEFT) == volume.getVolume(Volume::RIGHT) ) ) { gain = volume.getVolume(Volume::LEFT); balance = AUDIO_MID_BALANCE; } else { if ( volume.getVolume(Volume::LEFT) > volume.getVolume(Volume::RIGHT) ) { gain = volume.getVolume(Volume::LEFT); balance = AUDIO_LEFT_BALANCE + ( AUDIO_MID_BALANCE - AUDIO_LEFT_BALANCE ) * volume.getVolume(Volume::RIGHT) / volume.getVolume(Volume::LEFT); } else { gain = volume.getVolume(Volume::RIGHT); balance = AUDIO_RIGHT_BALANCE - ( AUDIO_RIGHT_BALANCE - AUDIO_MID_BALANCE ) * volume.getVolume(Volume::LEFT) / volume.getVolume(Volume::RIGHT); } } } //====================================================================== // FUNCTION : Mixer::GainBalanceToVolume // DESCRIPTION : Converts Gain/Balance returned by Sun driver to the // Volume(left vol + right vol) format used by kmix. //====================================================================== void Mixer_SUN::GainBalanceToVolume( uint_t& gain, uchar_t& balance, Volume& volume ) { if ( volume.count() == 1 ) { volume.setVolume( Volume::LEFT, gain ); } else { if ( balance <= AUDIO_MID_BALANCE ) { volume.setVolume( Volume::LEFT, gain ); volume.setVolume( Volume::RIGHT, gain * ( balance - AUDIO_LEFT_BALANCE ) / ( AUDIO_MID_BALANCE - AUDIO_LEFT_BALANCE ) ); } else { volume.setVolume( Volume::RIGHT, gain ); volume.setVolume( Volume::LEFT, gain * ( AUDIO_RIGHT_BALANCE - balance ) / ( AUDIO_RIGHT_BALANCE - AUDIO_MID_BALANCE ) ); } } } int Mixer_SUN::id2num(const QString& id) { return id.toInt(); } QString SUN_getDriverName() { return "SUNAudio"; } QString Mixer_SUN::getDriverName() { return "SUNAudio"; } diff --git a/gui/dialogaddview.cpp b/gui/dialogaddview.cpp index 0e9d7c70..330207ef 100644 --- a/gui/dialogaddview.cpp +++ b/gui/dialogaddview.cpp @@ -1,222 +1,222 @@ /* * 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 "gui/dialogaddview.h" #include #include #include #include +#include -#include #include #include "core/mixdevice.h" #include "core/mixer.h" static QStringList viewNames; static QStringList viewIds; DialogAddView::DialogAddView(QWidget *parent, Mixer *mixer) : DialogBase( parent ) { // TODO 000 Adding View for MPRIS2 is broken. We need at least a dummy XML GUI Profile. Also the // fixed list below is plain wrong. Actually we should get the Profile list from either the XML files or // from the backend. The latter is probably easier for now. if ( viewNames.isEmpty() ) { // initialize static list. Later this list could be generated from the actually installed profiles viewNames.append(i18n("All controls")); viewNames.append(i18n("Only playback controls")); viewNames.append(i18n("Only capture controls")); viewIds.append("default"); viewIds.append("playback"); viewIds.append("capture"); } setWindowTitle( i18n( "Add View" ) ); if ( Mixer::mixers().count() > 0 ) setButtons( QDialogButtonBox::Ok|QDialogButtonBox::Cancel ); else { setButtons( QDialogButtonBox::Cancel ); } m_listForChannelSelector = nullptr; createWidgets(mixer); // Open with Mixer Hardware #0 } /** * Create basic widgets of the Dialog. */ void DialogAddView::createWidgets(Mixer *ptr_mixer) { QWidget *mainFrame = new QWidget(this); setMainWidget(mainFrame); QVBoxLayout *layout = new QVBoxLayout(mainFrame); if ( Mixer::mixers().count() > 1 ) { // More than one Mixer => show Combo-Box to select Mixer // Mixer widget line QHBoxLayout* mixerNameLayout = new QHBoxLayout(); layout->addLayout(mixerNameLayout); mixerNameLayout->setSpacing(DialogBase::horizontalSpacing()); QLabel *qlbl = new QLabel( i18n("Select mixer:"), mainFrame ); mixerNameLayout->addWidget(qlbl); qlbl->setFixedHeight(qlbl->sizeHint().height()); - m_cMixer = new KComboBox( false, mainFrame); + m_cMixer = new QComboBox(mainFrame); m_cMixer->setObjectName( QLatin1String( "mixerCombo" ) ); m_cMixer->setFixedHeight(m_cMixer->sizeHint().height()); connect( m_cMixer, SIGNAL(activated(int)), this, SLOT(createPageByID(int)) ); for( int i =0; iaddItem( mixer->readableName() ); } // end for all_Mixers // Make the current Mixer the current item in the ComboBox int findIndex = m_cMixer->findText( ptr_mixer->readableName() ); if ( findIndex != -1 ) m_cMixer->setCurrentIndex( findIndex ); m_cMixer->setToolTip( i18n("Current mixer" ) ); mixerNameLayout->addWidget(m_cMixer); } // end if (more_than_1_Mixer) if ( Mixer::mixers().count() > 0 ) { QLabel *qlbl = new QLabel( i18n("Select the design for the new view:"), mainFrame ); layout->addWidget(qlbl); createPage(Mixer::mixers()[0]); connect( this, SIGNAL(accepted()) , this, SLOT(apply()) ); } else { QLabel *qlbl = new QLabel( i18n("No sound card is installed or currently plugged in."), mainFrame ); layout->addWidget(qlbl); } } /** * Create RadioButton's for the Mixer with number 'mixerId'. * @par mixerId The Mixer, for which the RadioButton's should be created. */ void DialogAddView::createPageByID(int mixerId) { //qCDebug(KMIX_LOG) << "DialogAddView::createPage()"; QString selectedMixerName = m_cMixer->itemText(mixerId); for( int i =0; ireadableName() == selectedMixerName ) { createPage(mixer); break; } } // for } /** * Create RadioButton's for the Mixer with number 'mixerId'. * @par mixerId The Mixer, for which the RadioButton's should be created. */ void DialogAddView::createPage(Mixer *mixer) { /** --- Reset page ----------------------------------------------- * In case the user selected a new Mixer via m_cMixer, we need * to remove the stuff created on the last call. */ delete m_listForChannelSelector; setButtonEnabled(QDialogButtonBox::Ok, false); /** Reset page end -------------------------------------------------- */ QWidget *mainFrame = mainWidget(); QVBoxLayout *layout = qobject_cast(mainFrame->layout()); Q_ASSERT(layout!=nullptr); m_listForChannelSelector = new QListWidget(mainFrame); m_listForChannelSelector->setUniformItemSizes(true); m_listForChannelSelector->setSelectionMode(QAbstractItemView::SingleSelection); connect(m_listForChannelSelector, SIGNAL(itemSelectionChanged()), this, SLOT(profileSelectionChanged())); layout->addWidget(m_listForChannelSelector); for( int i=0; iisDynamic()) { // TODO: The mixer's backend MUST be inspected to find out the supported profiles. // Hardcoding it here is only a quick workaround. continue; } // Create an item for each view type QString name = viewNames.at(i); QListWidgetItem *item = new QListWidgetItem(m_listForChannelSelector); item->setText(name); item->setData(Qt::UserRole, viewIds.at(i)); // mixer ID as data } } void DialogAddView::profileSelectionChanged() { QList items = m_listForChannelSelector->selectedItems(); setButtonEnabled(QDialogButtonBox::Ok, !items.isEmpty()); } void DialogAddView::apply() { Mixer *mixer = nullptr; if ( Mixer::mixers().count() == 1 ) { // only one mixer => no combo box => take first entry mixer = (Mixer::mixers())[0]; } else if ( Mixer::mixers().count() > 1 ) { // find mixer that is currently active in the ComboBox QString selectedMixerName = m_cMixer->itemText(m_cMixer->currentIndex()); for( int i =0; ireadableName() == selectedMixerName ) { mixer = (Mixer::mixers())[i]; break; } } // for } Q_ASSERT(mixer!=nullptr); QList items = m_listForChannelSelector->selectedItems(); if (items.isEmpty()) return; // nothing selected QListWidgetItem *item = items.first(); QString viewName = item->data(Qt::UserRole).toString(); qCDebug(KMIX_LOG) << "We should now create a new view " << viewName << " for mixer " << mixer->id(); resultMixerId = mixer->id(); resultViewName = viewName; } diff --git a/gui/dialogaddview.h b/gui/dialogaddview.h index 1608ec6d..d31686ed 100644 --- a/gui/dialogaddview.h +++ b/gui/dialogaddview.h @@ -1,60 +1,60 @@ //-*-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 DIALOGADDVIEW_H #define DIALOGADDVIEW_H #include #include "dialogbase.h" -class KComboBox; +class QComboBox; class QListWidget; class Mixer; class DialogAddView : public DialogBase { Q_OBJECT public: DialogAddView(QWidget *parent, Mixer *mixer); virtual ~DialogAddView() = default; QString getresultViewName() { return resultViewName; } QString getresultMixerId() { return resultMixerId; } public slots: void apply(); private: void createWidgets(Mixer*); void createPage(Mixer *mixer); - KComboBox* m_cMixer; + QComboBox* m_cMixer; QListWidget *m_listForChannelSelector; QString resultViewName; QString resultMixerId; private slots: void createPageByID(int mixerId); void profileSelectionChanged(); }; #endif diff --git a/gui/dialogchoosebackends.cpp b/gui/dialogchoosebackends.cpp index 5699cbb3..2346d7d3 100644 --- a/gui/dialogchoosebackends.cpp +++ b/gui/dialogchoosebackends.cpp @@ -1,163 +1,162 @@ /* * 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 "gui/dialogchoosebackends.h" #include #include #include #include #include #include -#include #include #include "core/ControlManager.h" #include "core/GlobalConfig.h" #include "core/mixdevice.h" #include "core/mixer.h" /** * Creates a dialog to choose mixers from. All currently known mixers will be shown, and the given mixerID's * will be preselected. * * @param mixerIds A set of preselected mixer ID's * @param noButtons is a migration option. When DialogChooseBackends has been integrated as a Tab, it will be removed. */ DialogChooseBackends::DialogChooseBackends(QWidget* parent, const QSet& mixerIds) : QWidget(parent), modified(false) { // setCaption( i18n( "Select Mixers" ) ); // setButtons( None ); _layout = 0; m_vboxForScrollView = 0; m_scrollableChannelSelector = 0; createWidgets(mixerIds); } DialogChooseBackends::~DialogChooseBackends() { delete _layout; delete m_vboxForScrollView; } /** * Create basic widgets of the Dialog. */ void DialogChooseBackends::createWidgets(const QSet& mixerIds) { m_mainFrame = this; // m_mainFrame = new QFrame( this ); // setMainWidget( m_mainFrame ); _layout = new QVBoxLayout(m_mainFrame); _layout->setMargin(0); if ( !Mixer::mixers().isEmpty() ) { QLabel *qlbl = new QLabel( i18n("Select the Mixers to display in the sound menu"), m_mainFrame ); _layout->addWidget(qlbl); createPage(mixerIds); } else { QLabel *qlbl = new QLabel( i18n("No sound card is installed or currently plugged in."), m_mainFrame ); _layout->addWidget(qlbl); } } /** * Create RadioButton's for the Mixer with number 'mixerId'. * @par mixerId The Mixer, for which the RadioButton's should be created. */ void DialogChooseBackends::createPage(const QSet& mixerIds) { m_scrollableChannelSelector = new QScrollArea(m_mainFrame); #ifndef QT_NO_ACCESSIBILITY m_scrollableChannelSelector->setAccessibleName(i18n("Select Mixers")); #endif _layout->addWidget(m_scrollableChannelSelector); m_vboxForScrollView = new QWidget(); QVBoxLayout *vbl = new QVBoxLayout(m_vboxForScrollView); vbl->setSpacing(0); bool hasMixerFilter = !mixerIds.isEmpty(); qCDebug(KMIX_LOG) << "MixerIds=" << mixerIds; foreach ( Mixer* mixer, Mixer::mixers()) { QCheckBox* qrb = new QCheckBox(mixer->readableName(true), m_vboxForScrollView); qrb->setObjectName(mixer->id());// The object name is used as ID here: see getChosenBackends() connect(qrb, SIGNAL(stateChanged(int)), SLOT(backendsModifiedSlot())); vbl->addWidget(qrb); checkboxes.append(qrb); bool mixerShouldBeShown = !hasMixerFilter || mixerIds.contains(mixer->id()); qrb->setChecked(mixerShouldBeShown); } m_scrollableChannelSelector->setWidget(m_vboxForScrollView); m_vboxForScrollView->show(); // show() is necessary starting with the second call to createPage() } QSet DialogChooseBackends::getChosenBackends() { QSet newMixerList; foreach ( QCheckBox* qcb, checkboxes) { if (qcb->isChecked()) { newMixerList.insert(qcb->objectName()); qCDebug(KMIX_LOG) << "apply found " << qcb->objectName(); } } qCDebug(KMIX_LOG) << "New list is " << newMixerList; return newMixerList; } /** * Returns whether there were any modifications (activation/deactivation) and resets the flag. * @return */ bool DialogChooseBackends::getAndResetModifyFlag() { bool modifiedOld = modified; modified = false; return modifiedOld; } bool DialogChooseBackends::getModifyFlag() { return modified; } void DialogChooseBackends::backendsModifiedSlot() { modified = true; emit backendsModified(); } diff --git a/gui/dialogselectmaster.cpp b/gui/dialogselectmaster.cpp index a1091eed..606efe14 100644 --- a/gui/dialogselectmaster.cpp +++ b/gui/dialogselectmaster.cpp @@ -1,229 +1,229 @@ /* * 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 "gui/dialogselectmaster.h" #include #include #include +#include -#include #include #include #include "core/ControlManager.h" #include "core/mixdevice.h" #include "core/mixer.h" DialogSelectMaster::DialogSelectMaster( Mixer *mixer, QWidget *parent ) : DialogBase( parent ) { setWindowTitle(i18n("Select Master Channel")); if ( Mixer::mixers().count() > 0 ) setButtons(QDialogButtonBox::Ok|QDialogButtonBox::Cancel); else { setButtons(QDialogButtonBox::Cancel); } m_channelSelector = 0; createWidgets(mixer); // Open with Mixer Hardware #0 } /** * Create basic widgets of the Dialog. */ void DialogSelectMaster::createWidgets(Mixer *ptr_mixer) { QWidget *mainFrame = new QWidget(this); setMainWidget(mainFrame); QVBoxLayout *layout = new QVBoxLayout(mainFrame); if ( Mixer::mixers().count() > 1 ) { // More than one Mixer => show Combo-Box to select Mixer // Mixer widget line QHBoxLayout* mixerNameLayout = new QHBoxLayout(); layout->addLayout( mixerNameLayout ); mixerNameLayout->setMargin(0); mixerNameLayout->setSpacing(DialogBase::horizontalSpacing()); QLabel *qlbl = new QLabel( i18n("Current mixer:"), mainFrame ); mixerNameLayout->addWidget(qlbl); qlbl->setFixedHeight(qlbl->sizeHint().height()); - m_cMixer = new KComboBox( false, mainFrame); + m_cMixer = new QComboBox(mainFrame); m_cMixer->setObjectName( QLatin1String( "mixerCombo" ) ); m_cMixer->setFixedHeight(m_cMixer->sizeHint().height()); connect( m_cMixer, SIGNAL(activated(int)), this, SLOT(createPageByID(int)) ); for( int i =0; iaddItem( mixer->readableName(), mixer->id() ); } // end for all_Mixers // Make the current Mixer the current item in the ComboBox int findIndex = m_cMixer->findData( ptr_mixer->id() ); if ( findIndex != -1 ) m_cMixer->setCurrentIndex( findIndex ); m_cMixer->setToolTip( i18n("Current mixer" ) ); mixerNameLayout->addWidget(m_cMixer, 1); layout->addSpacing(DialogBase::verticalSpacing()); } // end if (more_than_1_Mixer) if ( Mixer::mixers().count() > 0 ) { QLabel *qlbl = new QLabel( i18n("Select the channel representing the master volume:"), mainFrame ); layout->addWidget(qlbl); createPage(ptr_mixer); connect(this, SIGNAL(accepted()), this, SLOT(apply())); } else { QLabel *qlbl = new QLabel( i18n("No sound card is installed or currently plugged in."), mainFrame ); layout->addWidget(qlbl); } } /** * Create RadioButton's for the Mixer with number 'mixerId'. * @par mixerId The Mixer, for which the RadioButton's should be created. */ void DialogSelectMaster::createPageByID(int mixerId) { QString mixer_id = m_cMixer->itemData(mixerId).toString(); Mixer * mixer = Mixer::findMixer(mixer_id); if ( mixer != NULL ) createPage(mixer); } /** * Create RadioButton's for the Mixer with number 'mixerId'. * @par mixerId The Mixer, for which the RadioButton's should be created. */ void DialogSelectMaster::createPage(Mixer* mixer) { /** --- Reset page ----------------------------------------------- * In case the user selected a new Mixer via m_cMixer, we need * to remove the stuff created on the last call. */ // delete the list widget. // This should automatically remove all contained items. delete m_channelSelector; /** Reset page end -------------------------------------------------- */ QWidget *mainFrame = mainWidget(); QVBoxLayout *layout = qobject_cast(mainFrame->layout()); Q_ASSERT(layout!=nullptr); m_channelSelector = new QListWidget(mainFrame); #ifndef QT_NO_ACCESSIBILITY m_channelSelector->setAccessibleName( i18n("Select Master Channel") ); #endif m_channelSelector->setSelectionMode(QAbstractItemView::SingleSelection); m_channelSelector->setDragEnabled(false); m_channelSelector->setAlternatingRowColors(true); layout->addWidget(m_channelSelector); // shared_ptr master = mixer->getLocalMasterMD(); // QString masterKey = ( master.get() != 0 ) ? master->id() : "----noMaster---"; // Use non-matching name as default const MixSet& mixset = mixer->getMixSet(); MixSet& mset = const_cast(mixset); MasterControl mc = mixer->getGlobalMasterPreferred(false); QString masterKey = mc.getControl(); if (!masterKey.isEmpty() && !mset.get(masterKey)) { shared_ptr master = mixer->getLocalMasterMD(); if (master.get() != 0) masterKey = master->id(); } int msetCount = 0; for (int i = 0; i < mset.count(); ++i) { shared_ptr md = mset[i]; if ( md->playbackVolume().hasVolume() ) ++msetCount; } if (msetCount > 0 && !mixer->isDynamic()) { QString mdName = i18n("Automatic (%1 recommendation)", mixer->getDriverName()); QPixmap icon = KIconLoader::global()->loadIcon("audio-volume-high", KIconLoader::Small, IconSize(KIconLoader::Small)); QListWidgetItem *item = new QListWidgetItem(icon, mdName, m_channelSelector); item->setData(Qt::UserRole, QString()); // ID here: see apply(), empty String => Automatic if (masterKey.isEmpty()) m_channelSelector->setCurrentItem(item); } // Populate ListView with the MixDevice's having a playbakc volume (excludes pure capture controls and pure enum's) for (int i = 0; i < mset.count(); ++i) { shared_ptr md = mset[i]; if ( md->playbackVolume().hasVolume() ) { QString mdName = md->readableName(); QPixmap icon = KIconLoader::global()->loadIcon(md->iconName(), KIconLoader::Small, IconSize(KIconLoader::Small)); QListWidgetItem *item = new QListWidgetItem(icon, mdName, m_channelSelector); item->setData(Qt::UserRole, md->id()); // ID here: see apply() if ( md->id() == masterKey ) { // select the current master m_channelSelector->setCurrentItem(item); } } } } void DialogSelectMaster::apply() { Mixer *mixer = 0; if ( Mixer::mixers().count() == 1 ) { // only one mxier => no combo box => take first entry mixer = (Mixer::mixers())[0]; } else if ( Mixer::mixers().count() > 1 ) { // find mixer that is currently active in the ComboBox int idx = m_cMixer->currentIndex(); QString mixer_id = m_cMixer->itemData(idx).toString(); mixer = Mixer::findMixer(mixer_id); } if ( mixer == 0 ) return; // User must have unplugged everything QList items = m_channelSelector->selectedItems(); if (items.count()==1) { QListWidgetItem *item = items.first(); QString control_id = item->data(Qt::UserRole).toString(); mixer->setLocalMasterMD( control_id ); Mixer::setGlobalMaster(mixer->id(), control_id, true); ControlManager::instance().announce(mixer->id(), ControlChangeType::MasterChanged, QString("Select Master Dialog")); } } diff --git a/gui/dialogselectmaster.h b/gui/dialogselectmaster.h index 66f7ae3c..9486d36e 100644 --- a/gui/dialogselectmaster.h +++ b/gui/dialogselectmaster.h @@ -1,53 +1,53 @@ //-*-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 DIALOGSELECTMASTER_H #define DIALOGSELECTMASTER_H -class KComboBox; +class QComboBox; class QVBoxLayout; class QListWidget; #include "dialogbase.h" class Mixer; class DialogSelectMaster : public DialogBase { Q_OBJECT public: DialogSelectMaster(Mixer *mixer = 0, QWidget *parent = 0); virtual ~DialogSelectMaster() = default; public slots: void apply(); private: void createWidgets(Mixer*); void createPage(Mixer*); - KComboBox* m_cMixer; + QComboBox* m_cMixer; QListWidget *m_channelSelector; private slots: void createPageByID(int mixerId); }; #endif diff --git a/gui/kmixdockwidget.cpp b/gui/kmixdockwidget.cpp index 334befbd..e06bf890 100644 --- a/gui/kmixdockwidget.cpp +++ b/gui/kmixdockwidget.cpp @@ -1,435 +1,436 @@ /* * KMix -- KDE's full featured mini mixer * * * Copyright (C) 2000 Stefan Schimanski <1Stein@gmx.de> * Copyright (C) 2001 Preston Brown * Copyright (C) 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 "gui/kmixdockwidget.h" #include #include #include #include #include #include #include #include #include #include #include #include "apps/kmix.h" #include "core/ControlManager.h" #include "core/mixer.h" #include "core/mixertoolbox.h" #include "gui/dialogselectmaster.h" #include "gui/mixdevicewidget.h" #include "gui/viewdockareapopup.h" //#define FEATURE_UNITY_POPUP true KMixDockWidget::KMixDockWidget(KMixWindow* parent) : KStatusNotifierItem(parent) , _oldToolTipValue(-1) , _oldPixmapType('-') , _kmixMainWindow(parent) , _delta(0) { setToolTipIconByName("kmix"); setTitle(i18n( "Volume Control")); setCategory(Hardware); setStatus(Active); // TODO Unity / Gnome only support one type of activation (left-click == right-click) // So we should show here the ViewDockAreaPopup instead of the menu: //bool onlyOneMouseButtonAction = onlyHaveOneMouseButtonAction(); createMenuActions(); connect(this, SIGNAL(scrollRequested(int,Qt::Orientation)), this, SLOT(trayWheelEvent(int,Qt::Orientation))); connect(this, SIGNAL(secondaryActivateRequested(QPoint)), this, SLOT(dockMute())); // For bizarre reasons, we wrap the ViewDockAreaPopup in a QMenu. Must relate to how KStatusNotifierItem works. _dockAreaPopupMenuWrapper = new QMenu(parent); _volWA = new QWidgetAction(_dockAreaPopupMenuWrapper); _dockView = new ViewDockAreaPopup(_dockAreaPopupMenuWrapper, "dockArea", 0, QString("no-guiprofile-yet-in-dock"), parent); _volWA->setDefaultWidget(_dockView); _dockAreaPopupMenuWrapper->addAction(_volWA); connect(contextMenu(), SIGNAL(aboutToShow()), this, SLOT(contextMenuAboutToShow())); ControlManager::instance().addListener( QString(), // All mixers (as the Global master Mixer might change) (ControlChangeType::Type) (ControlChangeType::Volume | ControlChangeType::MasterChanged), this, QString("KMixDockWidget")); // Refresh in all cases. When there is no Golbal Master we still need // to initialize correctly (e.g. for showin 0% or hiding it) refreshVolumeLevels(); } KMixDockWidget::~KMixDockWidget() { ControlManager::instance().removeListener(this); // Note: deleting _volWA also deletes its associated ViewDockAreaPopup (_referenceWidget) and prevents the // action to be left with a dangling pointer. // cesken: I adapted the patch from https://bugs.kde.org/show_bug.cgi?id=220621#c27 to branch /branches/work/kmix delete _volWA; } void KMixDockWidget::controlsChange(int changeType) { ControlChangeType::Type type = ControlChangeType::fromInt(changeType); switch (type ) { case ControlChangeType::MasterChanged: // Notify the main window, as it might need to update the visibiliy of the dock icon. // _kmixMainWindow->updateDocking(); // _kmixMainWindow->saveConfig(); refreshVolumeLevels(); { QAction *selectMasterAction = findAction("select_master"); if(selectMasterAction) { // Review #120432 : Guard findAction("select_master"), as it is sometimes 0 on the KF5 build // This is probably not a final solution, but better than a crash. selectMasterAction->setEnabled(Mixer::getGlobalMasterMixer() != 0); } else { qCWarning(KMIX_LOG) << "select_master action not found. Cannot enable it in the Systray."; } } break; case ControlChangeType::Volume: refreshVolumeLevels(); break; default: ControlManager::warnUnexpectedChangeType(type, this); } } QAction* KMixDockWidget::findAction(const char* actionName) { QList actions = actionCollection(); int size = actions.size(); for (int i=0; idata().toString() == QString::fromUtf8(actionName)) return action; } qCWarning(KMIX_LOG) << "ACTION" << actionName << "NOT FOUND!"; return Q_NULLPTR; } /** * Updates all visual parts of the volume, namely tooltip and pixmap */ void KMixDockWidget::refreshVolumeLevels() { setVolumeTip(); updatePixmap(); } /** * Creates the right-click menu */ void KMixDockWidget::createMenuActions() { QMenu *menu = contextMenu(); if (!menu) return; // We do not use a menu shared_ptr md = Mixer::getGlobalMasterMD(); if ( md.get() != 0 && md->hasMuteSwitch() ) { // Put "Mute" selector in context menu KToggleAction *action = new KToggleAction(i18n("M&ute"), this); action->setData("dock_mute"); addAction("dock_mute", action); updateDockMuteAction(action); connect(action, SIGNAL(triggered(bool)), SLOT(dockMute())); menu->addAction( action ); } // Put "Select Master Channel" dialog in context menu QAction *action = new QAction(i18n("Select Master Channel..."), this); action->setData("select_master"); addAction("select_master", action); action->setEnabled(Mixer::getGlobalMasterMixer() != 0); connect(action, SIGNAL(triggered(bool)), _kmixMainWindow, SLOT(slotSelectMaster())); menu->addAction( action ); //Context menu entry to access phonon settings menu->addAction(_kmixMainWindow->actionCollection()->action("launch_kdesoundsetup")); } void KMixDockWidget::setVolumeTip() { shared_ptr md = Mixer::getGlobalMasterMD(); QString tip; QString subTip; int virtualToolTipValue = 0; if ( md.get() == 0 ) { tip = i18n("Mixer cannot be found"); // !! text could be reworked virtualToolTipValue = -2; } else { // Playback volume will be used for the DockIcon if available. // This heuristic is "good enough" for the DockIcon for now. int val = md->getUserfriendlyVolumeLevel(); tip += "" + i18n( "Volume at %1%", val ) + ""; if ( md->isMuted() ) tip += i18n( " (Muted)" ); subTip = QString( "%1
%2" ) - .arg( Qt::escape(md->mixer()->readableName()) ).arg( Qt::escape(md->readableName()) ); + .arg(md->mixer()->readableName().toHtmlEscaped()) + .arg(md->readableName().toHtmlEscaped()); // create a new "virtual" value. With that we see "volume changes" as well as "muted changes" virtualToolTipValue = val; if ( md->isMuted() ) virtualToolTipValue += 10000; } // The actual updating is only done when the "toolTipValue" was changed (to avoid flicker) if ( virtualToolTipValue != _oldToolTipValue ) { // changed (or completely new tooltip) setToolTipTitle(tip); setToolTipSubTitle(subTip); } _oldToolTipValue = virtualToolTipValue; } void KMixDockWidget::updatePixmap() { shared_ptr md = Mixer::getGlobalMasterMD(); char newPixmapType; if ( !md ) { // no such control => error newPixmapType = 'e'; } else { int percentage = md->getUserfriendlyVolumeLevel(); if ( percentage <= 0 ) newPixmapType = '0'; // Hint: also muted, and also negative-values else if ( percentage < 25 ) newPixmapType = '1'; else if ( percentage < 75 ) newPixmapType = '2'; else newPixmapType = '3'; } if ( newPixmapType != _oldPixmapType ) { // Pixmap must be changed => do so switch ( newPixmapType ) { case 'e': setIconByName( "kmixdocked_error" ); break; case 'm': case '0': setIconByName( "audio-volume-muted" ); break; case '1': setIconByName( "audio-volume-low" ); break; case '2': setIconByName( "audio-volume-medium" ); break; case '3': setIconByName( "audio-volume-high" ); break; } } _oldPixmapType = newPixmapType; } /** * Called whenever the icon gets "activated". Usually when its clicked. * @overload * @param pos */ void KMixDockWidget::activate(const QPoint &pos) { QWidget* dockAreaPopup = _dockAreaPopupMenuWrapper; // TODO Refactor to use _dockAreaPopupMenuWrapper directly if (dockAreaPopup->isVisible()) { dockAreaPopup->hide(); return; } _dockAreaPopupMenuWrapper->removeAction(_volWA); delete _volWA; _volWA = new QWidgetAction(_dockAreaPopupMenuWrapper); _dockView = new ViewDockAreaPopup(_dockAreaPopupMenuWrapper, "dockArea", 0, QString("no-guiprofile-yet-in-dock"), _kmixMainWindow); _volWA->setDefaultWidget(_dockView); _dockAreaPopupMenuWrapper->addAction(_volWA); //_dockView->show(); // TODO cesken check: this should be automatic // Showing, to hopefully get the geometry manager started. We need width and height below. Also // vdesktop->availableGeometry(dockAreaPopup) needs to know on which screen the widget will be shown. // dockAreaPopup->show(); _dockView->adjustSize(); dockAreaPopup->adjustSize(); int x = pos.x() - dockAreaPopup->width() / 2; if (x < 0) x = pos.x(); int y = pos.y() - dockAreaPopup->height() / 2; if (y < 0) y = pos.y(); // Now handle Multihead displays. And also make sure that the dialog is not // moved out-of-the screen on the right (see Bug 101742). const QDesktopWidget* vdesktop = QApplication::desktop(); int screenNumber = vdesktop->screenNumber(pos); const QRect& vScreenSize = vdesktop->availableGeometry(screenNumber); if ((x + dockAreaPopup->width()) > (vScreenSize.width() + vScreenSize.x())) { // move horizontally, so that it is completely visible x = vScreenSize.width() + vScreenSize.x() - dockAreaPopup->width() - 1; qCDebug(KMIX_LOG) << "Multihead: (case 1) moving to" << x << "," << y; } else if (x < vScreenSize.x()) { // horizontally out-of bound x = vScreenSize.x(); qCDebug(KMIX_LOG) << "Multihead: (case 2) moving to" << x << "," << y; } if ((y + dockAreaPopup->height()) > (vScreenSize.height() + vScreenSize.y())) { // move horizontally, so that it is completely visible y = vScreenSize.height() + vScreenSize.y() - dockAreaPopup->height() - 1; qCDebug(KMIX_LOG) << "Multihead: (case 3) moving to" << x << "," << y; } else if (y < vScreenSize.y()) { // horizontally out-of bound y = vScreenSize.y(); qCDebug(KMIX_LOG) << "Multihead: (case 4) moving to" << x << "," << y; } KWindowSystem::setType(dockAreaPopup->winId(), NET::Dock); KWindowSystem::setState(dockAreaPopup->winId(), NET::StaysOnTop | NET::SkipTaskbar | NET::SkipPager); dockAreaPopup->show(); dockAreaPopup->move(x, y); } void KMixDockWidget::trayWheelEvent(int delta,Qt::Orientation wheelOrientation) { shared_ptr md = Mixer::getGlobalMasterMD(); if ( md.get() == 0 ) return; Volume &vol = ( md->playbackVolume().hasVolume() ) ? md->playbackVolume() : md->captureVolume(); // qCDebug(KMIX_LOG) << "I am seeing a wheel event with delta=" << delta << " and orientation=" << wheelOrientation; if (wheelOrientation == Qt::Horizontal) // Reverse horizontal scroll: bko228780 { delta = -delta; } // bko313579, bko341536, Review #121725 - Use delta and round it by 120. _delta += delta; bool decrease = delta < 0; unsigned long inc = 0; while (_delta >= 120) { _delta -= 120; inc++; } while (_delta <= -120) { _delta += 120; inc++; } if (inc == 0) { return; } long cv = vol.volumeStep(decrease) * inc; bool isInactive = vol.isCapture() ? !md->isRecSource() : md->isMuted(); // qCDebug(KMIX_LOG) << "Operating on capture=" << vol.isCapture() << ", isInactive=" << isInactive; if ( cv > 0 && isInactive) { // increasing from muted state: unmute and start with a low volume level if ( vol.isCapture()) md->setRecSource(true); else md->setMuted(false); vol.setAllVolumes(cv); } else vol.changeAllVolumes(cv); md->mixer()->commitVolumeChange(md); refreshVolumeLevels(); } void KMixDockWidget::dockMute() { shared_ptr md = Mixer::getGlobalMasterMD(); if ( md ) { md->toggleMute(); md->mixer()->commitVolumeChange( md ); refreshVolumeLevels(); } } /** * Returns whether the running Desktop only supports one Mouse Button * Hint: Unity / Gnome only support one type of activation (left-click == right-click). */ bool KMixDockWidget::onlyHaveOneMouseButtonAction() { QDBusConnection connection = QDBusConnection::sessionBus(); bool unityIsRunning = (connection.interface()->isServiceRegistered("com.canonical.Unity.Panel.Service")); // Possibly implement other detectors, like for Gnome 3 or Gnome 2 return unityIsRunning; } void KMixDockWidget::contextMenuAboutToShow() { // Enable/Disable "Muted" menu item KToggleAction *dockMuteAction = static_cast(findAction("dock_mute")); qCDebug(KMIX_LOG) << "DOCK MUTE" << dockMuteAction; if (dockMuteAction) updateDockMuteAction(dockMuteAction); } void KMixDockWidget::updateDockMuteAction ( KToggleAction* dockMuteAction ) { shared_ptr md = Mixer::getGlobalMasterMD(); if ( md && dockMuteAction != 0 ) { Volume& vol = md->playbackVolume().hasVolume() ? md->playbackVolume() : md->captureVolume(); bool isInactive = vol.isCapture() ? !md->isRecSource() : md->isMuted(); bool hasSwitch = vol.isCapture() ? vol.hasSwitch() : md->hasMuteSwitch(); dockMuteAction->setEnabled( hasSwitch ); dockMuteAction->setChecked( isInactive ); } } diff --git a/gui/kmixtoolbox.cpp b/gui/kmixtoolbox.cpp index 1ff5684b..e1178fa9 100644 --- a/gui/kmixtoolbox.cpp +++ b/gui/kmixtoolbox.cpp @@ -1,92 +1,91 @@ /* * KMix -- KDE's full featured mini mixer * * * Copyright (C) 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 "gui/kmixtoolbox.h" #include #include #include -#include #include #include #include #include "gui/guiprofile.h" #include "mdwslider.h" #include "gui/mixdevicewidget.h" #include "core/mixdevice.h" #include "core/mixer.h" #include "viewbase.h" // TODO KMixToolbox is rather superfluous today, as there is no "KMix Applet" any more, and it was probably always bad style. // I only have to think what to do with KMixToolBox::notification() /*********************************************************************************** KMixToolbox contains several GUI relevant methods that are shared between the KMix Main Program, and the KMix Applet. kmixctrl - as not non-GUI application - does NOT link to KMixToolBox. This means: Shared GUI stuff goes into the KMixToolBox class , non-GUI stuff goes into the MixerToolBox class. ***********************************************************************************/ void KMixToolBox::setIcons(QList &mdws, bool on ) { for (int i=0; i < mdws.count(); ++i ){ QWidget *mdw = mdws[i]; if ( mdw->inherits("MixDeviceWidget") ) { // -<- play safe here static_cast(mdw)->setIcons( on ); } } } void KMixToolBox::setLabels(QList &mdws, bool on ) { for (int i=0; i < mdws.count(); ++i ){ QWidget *mdw = mdws[i]; if ( mdw->inherits("MixDeviceWidget") ) { // -<- play safe here static_cast(mdw)->setLabeled( on ); } } } void KMixToolBox::setTicks(QList &mdws, bool on ) { for (int i=0; i < mdws.count(); ++i ){ QWidget *mdw = mdws[i]; if ( mdw->inherits("MixDeviceWidget") ) { // -<- play safe here static_cast(mdw)->setTicks( on ); } } } void KMixToolBox::notification(const char *notificationName, const QString &text, const QStringList &actions, QObject *receiver, const char *actionSlot) { KNotification *notification = new KNotification(notificationName); //notification->setComponentData(componentData()); notification->setText(text); //notification->setPixmap(...); notification->addContext(QLatin1String("Application"), QCoreApplication::applicationName()); if (!actions.isEmpty() && receiver && actionSlot) { notification->setActions(actions); QObject::connect(notification, SIGNAL(activated(uint)), receiver, actionSlot); } notification->sendEvent(); } diff --git a/gui/mdwenum.cpp b/gui/mdwenum.cpp index 222f7e96..59be404c 100644 --- a/gui/mdwenum.cpp +++ b/gui/mdwenum.cpp @@ -1,196 +1,196 @@ /* * KMix -- KDE's full featured mini mixer * * * Copyright (C) 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. */ // KMix #include "mdwenum.h" #include "core/mixer.h" #include "viewbase.h" // KDE #include #include -#include #include #include #include // Qt #include #include #include #include #include #include +#include /** * Class that represents an Enum element (a select one-from-many selector) * The orientation (horizontal, vertical) is ignored */ MDWEnum::MDWEnum( shared_ptr md, Qt::Orientation orientation, QWidget* parent, ViewBase* view, ProfControl* par_pctl) : MixDeviceWidget(md, false, orientation, parent, view, par_pctl), _label(0), _enumCombo(0), _layout(0) { // create actions (on _mdwActions, see MixDeviceWidget) // KStandardAction::showMenubar() is in MixDeviceWidget now KToggleAction *action = _mdwActions->add( "hide" ); action->setText( i18n("&Hide") ); connect(action, SIGNAL(triggered(bool)), SLOT(setDisabled(bool))); QAction *c = _mdwActions->addAction( "keys" ); c->setText( i18n("C&onfigure Shortcuts...") ); connect(c, SIGNAL(triggered(bool)), SLOT(defineKeys())); // create widgets createWidgets(); /* remove this for production version QAction *a = _mdwActions->addAction( "Next Value" ); c->setText( i18n( "Next Value" ) ); connect(a, SIGNAL(triggered(bool)), SLOT(nextEnumId())); */ installEventFilter( this ); // filter for popup } MDWEnum::~MDWEnum() { } void MDWEnum::createWidgets() { if ( _orientation == Qt::Vertical ) { _layout = new QVBoxLayout( this ); _layout->setAlignment(Qt::AlignLeft); } else { _layout = new QHBoxLayout( this ); _layout->setAlignment(Qt::AlignLeft|Qt::AlignVCenter); } _label = new QLabel( m_mixdevice->readableName(), this); _layout->addWidget(_label); - _enumCombo = new KComboBox( false, this); + _enumCombo = new QComboBox(this); _enumCombo->installEventFilter(this); // ------------ fill ComboBox start ------------ int maxEnumId= m_mixdevice->enumValues().count(); for (int i=0; iaddItem( m_mixdevice->enumValues().at(i)); } // ------------ fill ComboBox end -------------- _layout->addWidget(_enumCombo); connect( _enumCombo, SIGNAL(activated(int)), this, SLOT(setEnumId(int)) ); _enumCombo->setToolTip( m_mixdevice->readableName() ); _layout->addStretch(1); } void MDWEnum::update() { if ( m_mixdevice->isEnum() ) { //qCDebug(KMIX_LOG) << "MDWEnum::update() enumID=" << m_mixdevice->enumId(); _enumCombo->setCurrentIndex( m_mixdevice->enumId() ); } else { qCCritical(KMIX_LOG) << "MDWEnum::update() enumID=" << m_mixdevice->enumId() << " is no Enum ... skipped"; } } void MDWEnum::showContextMenu(const QPoint& pos ) { if( m_view == 0 ) return; QMenu *menu = m_view->getPopup(); menu->popup( pos ); } QSizePolicy MDWEnum::sizePolicy() const { return QSizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::Fixed ); } /** This slot is called, when a user has clicked the mute button. Also it is called by any other associated KAction like the context menu. */ void MDWEnum::nextEnumId() { if( m_mixdevice->isEnum() ) { int curEnum = enumId(); if ( curEnum < m_mixdevice->enumValues().count() ) { // next enum value setEnumId(curEnum+1); } else { // wrap around setEnumId(0); } } // isEnum } void MDWEnum::setEnumId(int value) { if ( m_mixdevice->isEnum() ) { m_mixdevice->setEnumId( value ); m_mixdevice->mixer()->commitVolumeChange( m_mixdevice ); } } int MDWEnum::enumId() { if ( m_mixdevice->isEnum() ) { return m_mixdevice->enumId(); } else { return 0; } } void MDWEnum::setDisabled( bool hide ) { emit guiVisibilityChange(this, !hide); } /** * An event filter for the various QWidgets. We watch for Mouse press Events, so * that we can popup the context menu. */ bool MDWEnum::eventFilter( QObject* obj, QEvent* e ) { if (e->type() == QEvent::MouseButtonPress) { QMouseEvent *qme = static_cast(e); if (qme->button() == Qt::RightButton) { showContextMenu(); return true; } } else if (e->type() == QEvent::ContextMenu) { QPoint pos = reinterpret_cast(obj)->mapToGlobal(QPoint(0, 0)); showContextMenu(pos); return true; } return QWidget::eventFilter(obj,e); } diff --git a/gui/mdwenum.h b/gui/mdwenum.h index a97ae0a0..6d523abc 100644 --- a/gui/mdwenum.h +++ b/gui/mdwenum.h @@ -1,76 +1,74 @@ //-*-C++-*- /* * KMix -- KDE's full featured mini mixer * * * Copyright (C) 2004 Chrisitan 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 MDWENUM_H #define MDWENUM_H #include #include "core/volume.h" // KMix class MixDevice; class ViewBase; -// KDE -class KComboBox; - // Qt class QBoxLayout; +class QComboBox; class QLabel; #include "gui/mixdevicewidget.h" class MDWEnum : public MixDeviceWidget { Q_OBJECT public: MDWEnum( shared_ptr md, Qt::Orientation orientation, QWidget* parent, ViewBase* view, ProfControl* pctl); ~MDWEnum(); void addActionToPopup( QAction *action ); QSizePolicy sizePolicy() const; bool eventFilter( QObject* obj, QEvent* e ) Q_DECL_OVERRIDE; public slots: // GUI hide and show void setDisabled(bool) Q_DECL_OVERRIDE; // Enum handling: next and selecting void nextEnumId(); int enumId(); void setEnumId(int value); void update() Q_DECL_OVERRIDE; void showContextMenu(const QPoint& pos = QCursor::pos()) Q_DECL_OVERRIDE; private: void createWidgets(); QLabel *_label; - KComboBox *_enumCombo; + QComboBox *_enumCombo; QBoxLayout *_layout; }; #endif diff --git a/gui/mdwslider.h b/gui/mdwslider.h index 144b3984..d11211e0 100644 --- a/gui/mdwslider.h +++ b/gui/mdwslider.h @@ -1,180 +1,180 @@ //-*-C++-*- /* * KMix -- KDE's full featured mini mixer * * * Copyright Chrisitan 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 MDWSLIDER_H #define MDWSLIDER_H #include "volumeslider.h" #include #include #include #include #include class QBoxLayout; class QToolButton; class QLabel; - class QMenu; + #include class MixDevice; class VerticalText; class ViewBase; #include "gui/mixdevicewidget.h" #include "core/volume.h" class MDWSlider : public MixDeviceWidget { Q_OBJECT public: MDWSlider( shared_ptr md, bool includePlayback, bool includeCapture, bool includeMixerName, bool small, Qt::Orientation, QWidget* parent, ViewBase* view, ProfControl *pctl); virtual ~MDWSlider(); enum LabelType { LT_ALL, LT_FIRST_CAPTURE, LT_NONE }; void addActionToPopup( QAction *action ); void createActions(); void createShortcutActions(); // GUI bool isStereoLinked() const Q_DECL_OVERRIDE { return m_linked; } void setStereoLinked( bool value ) Q_DECL_OVERRIDE; void setLabeled( bool value ) Q_DECL_OVERRIDE; void setTicks( bool ticks ) Q_DECL_OVERRIDE; void setIcons( bool value ) Q_DECL_OVERRIDE; // void setIcon( QString filename, QLabel** label ); void setIcon( QString filename, QWidget* label ); QToolButton* addMediaButton(QString iconName, QLayout* layout, QWidget *parent); void updateMediaButton(); void setColors( QColor high, QColor low, QColor back ) Q_DECL_OVERRIDE; void setMutedColors( QColor high, QColor low, QColor back ) Q_DECL_OVERRIDE; bool eventFilter( QObject* obj, QEvent* e ) Q_DECL_OVERRIDE; QString iconName(); // Layout QSizePolicy sizePolicy() const; QSize sizeHint() const Q_DECL_OVERRIDE; int labelExtentHint() const; void setLabelExtent(int extent); bool hasMuteButton() const; void setMuteButtonSpace(bool); void setCaptureLEDSpace(bool); bool hasCaptureLED() const; static VolumeSliderExtraData DummVolumeSliderExtraData; static bool debugMe; public slots: void toggleRecsrc(); void toggleMuted(); void toggleStereoLinked(); void setDisabled( bool value ) Q_DECL_OVERRIDE; void update() Q_DECL_OVERRIDE; void showMoveMenu(); void showContextMenu( const QPoint &pos = QCursor::pos() ) Q_DECL_OVERRIDE; void increaseOrDecreaseVolume(bool arg1, Volume::VolumeTypeFlag volumeType); VolumeSliderExtraData& extraData(QAbstractSlider *slider); void addMediaControls(QBoxLayout* arg1); private slots: void setRecsrc( bool value ); void setMuted(bool value); void volumeChange( int ); void sliderPressed(); void sliderReleased(); void increaseVolume(); void decreaseVolume(); void moveStreamAutomatic(); void moveStream( QString destId ); void mediaPlay(bool); void mediaNext(bool); void mediaPrev(bool); private: QPixmap loadIcon( QString filename, KIconLoader::Group group ); void createWidgets( bool showMuteLED, bool showCaptureLED, bool includeMixer ); void addSliders( QBoxLayout *volLayout, char type, Volume& vol, QList& ref_sliders, QString tooltipText ); //void addDefaultLabel(QBoxLayout *layout, Qt::Orientation orientation); // Methods that are called two times from a wrapper. Once for playabck, once for capture void setStereoLinkedInternal( QList< QAbstractSlider* >& ref_sliders, bool showSubcontrolLabels); void setTicksInternal( QList< QAbstractSlider* >& ref_sliders, bool ticks ); void volumeChangeInternal(Volume& vol, QList< QAbstractSlider* >& ref_sliders ); void updateInternal(Volume& vol, QList< QAbstractSlider* >& ref_sliders, bool muted); #ifndef QT_NO_ACCESSIBILITY void updateAccesability(); #endif QWidget* createLabel(QWidget* parent, QString& label, QBoxLayout *layout, bool); QString calculatePlaybackIcon(MediaController::PlayState playState); void guiAddSlidersAndMediacontrols(bool playSliders, bool capSliders, bool mediaControls, QBoxLayout* layout, const QString& tooltipText, const QString& captureTooltipText); void guiAddCaptureCheckbox(bool wantsCaptureLED, const Qt::Alignment& alignmentForCapture, QBoxLayout* layoutForCapture, const QString& captureTooltipText); void guiAddMuteButton(bool wantsMuteButton, Qt::Alignment alignment, QBoxLayout* layoutForMuteButton, const QString& muteTooltipText); void guiAddControlIcon(Qt::Alignment alignment, QBoxLayout* layout, const QString& tooltipText); void addGlobalShortcut(QAction* action, const QString& label, bool dynamicControl); bool m_linked; QWidget *muteButtonSpacer; QWidget *captureSpacer; QWidget *labelSpacer; // GUI: Top portion ( Icon + Mute) QLabel *m_iconLabelSimple; QToolButton* m_qcb; QLabel* m_muteText; QLabel *m_label; // is either QLabel or VerticalText QToolButton *mediaButton; QCheckBox* m_captureCheckbox; QLabel* m_captureText; int labelSpacing; bool muteButtonSpacing; bool captureLEDSpacing; KActionCollection* _mdwMoveActions; QMenu *m_moveMenu; QList m_slidersPlayback; QList m_slidersCapture; bool m_sliderInWork; int m_waitForSoundSetComplete; QList volumeValues; }; #endif diff --git a/gui/verticaltext.cpp b/gui/verticaltext.cpp index 048444c3..cb07df22 100644 --- a/gui/verticaltext.cpp +++ b/gui/verticaltext.cpp @@ -1,75 +1,75 @@ /* * KMix -- KDE's full featured mini mixer * * * Copyright (C) 2003-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 "verticaltext.h" #include -VerticalText::VerticalText(QWidget * parent, const QString& text, Qt::WFlags f) : QWidget(parent,f) +VerticalText::VerticalText(QWidget * parent, const QString& text, Qt::WindowFlags f) : QWidget(parent,f) { m_labelText = text; } VerticalText::~VerticalText() { } void VerticalText::setText(const QString& text) { if (m_labelText != text) { m_labelText = text; updateGeometry(); } } void VerticalText::paintEvent ( QPaintEvent * /*event*/ ) { QPainter paint(this); paint.rotate(270); // paint.translate(0,-4); // Silly "solution" to make underlengths work // Fix for bug 72520 //- paint.drawText(-height()+2,width(),name()); //+ paint.drawText( -height()+2, width(), QString::fromUtf8(name()) ); int posX = -height(); int posY = width(); paint.drawText( posX, posY, m_labelText ); } QSize VerticalText::sizeHint() const { const QFontMetrics& fontMetr = fontMetrics(); QSize textSize(fontMetr.width(m_labelText), fontMetr.height()); textSize.transpose(); return textSize; } QSize VerticalText::minimumSizeHint() const { const QFontMetrics& fontMetr = fontMetrics(); QSize textSize(fontMetr.width("MMMM"), fontMetr.height()); textSize.transpose(); return textSize; } QSizePolicy VerticalText::sizePolicy () const { return QSizePolicy(QSizePolicy::Fixed, QSizePolicy::MinimumExpanding); } diff --git a/gui/verticaltext.h b/gui/verticaltext.h index a9fd89ee..4e600669 100644 --- a/gui/verticaltext.h +++ b/gui/verticaltext.h @@ -1,44 +1,44 @@ //-*-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 VerticalText_h #define VerticalText_h #include #include class VerticalText : public QWidget { public: - VerticalText(QWidget * parent, const QString&, Qt::WFlags f = 0); + VerticalText(QWidget * parent, const QString&, Qt::WindowFlags f = 0); ~VerticalText(); void setText(const QString& text); QSize sizeHint() const Q_DECL_OVERRIDE; QSizePolicy sizePolicy () const; QSize minimumSizeHint() const Q_DECL_OVERRIDE; protected: void paintEvent ( QPaintEvent * event ) Q_DECL_OVERRIDE; private: QString m_labelText; }; #endif diff --git a/gui/viewbase.cpp b/gui/viewbase.cpp index 2cd18440..b341b493 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 #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::WFlags f, ViewBase::ViewFlags vflags, QString guiProfileId, KActionCollection *actionColletion) +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 Channels" )); 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.toAscii(); + << "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.toAscii(); - //qCDebug(KMIX_LOG) << " mixer=" << view->id().toAscii(); - //qCDebug(KMIX_LOG) << " mdwPK=" << mdw->mixDevice()->id().toAscii(); + //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 85d30a8e..7562ce1a 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 #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::WFlags, ViewFlags vflags, QString guiProfileId, KActionCollection* actionCollection = 0); + 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