diff --git a/CMakeLists.txt b/CMakeLists.txt --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -18,6 +18,10 @@ include(KDECompilerSettings NO_POLICY_SCOPE) include(ECMOptionalAddSubdirectory) +include(FindPkgConfig) +pkg_check_modules(GCONF REQUIRED gconf-2.0) +pkg_check_modules(GOBJECT REQUIRED gobject-2.0) + find_package(Qt5 ${QT_MIN_VERSION} REQUIRED COMPONENTS Core Gui diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,4 +1,5 @@ -include_directories(${PULSEAUDIO_INCLUDE_DIR} ${GLIB2_INCLUDE_DIR}) +include_directories(${PULSEAUDIO_INCLUDE_DIR} ${GLIB2_INCLUDE_DIR} ${GCONF_INCLUDE_DIRS} + ${GOBJECT_INCLUDE_DIRS}) set(SRC_LIST card.cpp @@ -14,6 +15,8 @@ ref.cpp sink.cpp sinkinput.cpp + modulemanager.cpp + gconfitem.cpp source.cpp sourceoutput.cpp stream.cpp @@ -29,6 +32,8 @@ Qt5::Gui ${PULSEAUDIO_LIBRARY} ${PULSEAUDIO_MAINLOOP_LIBRARY} + ${GCONF_LDFLAGS} + ${GOBJECT_LDFLAGS} ) install(TARGETS QPulseAudioPrivate ${INSTALL_TARGETS_DEFAULT_ARGS}) diff --git a/src/gconfitem.h b/src/gconfitem.h new file mode 100644 --- /dev/null +++ b/src/gconfitem.h @@ -0,0 +1,134 @@ +/* + * Copyright (C) 2009 Nokia Corporation. + * Copyright (C) 2016 David Edmundson + * + * Contact: Marius Vollmer + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * version 2.1 as published by the Free Software Foundation. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#ifndef GCONFITEM_H +#define GCONFITEM_H + +#include +#include +#include + +/*! + + \brief GConfItem is a simple C++ wrapper for GConf. + + Creating a GConfItem instance gives you access to a single GConf + key. You can get and set its value, and connect to its + valueChanged() signal to be notified about changes. + + The value of a GConf key is returned to you as a QVariant, and you + pass in a QVariant when setting the value. GConfItem converts + between a QVariant and GConf values as needed, and according to the + following rules: + + - A QVariant of type QVariant::Invalid denotes an unset GConf key. + + - QVariant::Int, QVariant::Double, QVariant::Bool are converted to + and from the obvious equivalents. + + - QVariant::String is converted to/from a GConf string and always + uses the UTF-8 encoding. No other encoding is supported. + + - QVariant::StringList is converted to a list of UTF-8 strings. + + - QVariant::List (which denotes a QList) is converted + to/from a GConf list. All elements of such a list must have the + same type, and that type must be one of QVariant::Int, + QVariant::Double, QVariant::Bool, or QVariant::String. (A list of + strings is returned as a QVariant::StringList, however, when you + get it back.) + + - Any other QVariant or GConf value is essentially ignored. + + - This is fored by Dave from libqtgconf to really reduce the amount of QObjects needed + to manipulate various items in a tree. + + + \warning GConfItem is as thread-safe as GConf. + +*/ + + +class GConfItem : public QObject +{ + Q_OBJECT + + public: + /*! Initializes a GConfItem to access the GConf key denoted by + \a key. Key names should follow the normal GConf conventions + like "/myapp/settings/first". + + \param key The name of the key. + \param parent Parent object + */ + explicit GConfItem(const QString &keyRoot, QObject *parent = 0); + + /*! Finalizes a GConfItem. + */ + virtual ~GConfItem(); + + /*! Returns the root of this item, as given to the constructor. + */ + QString root() const; + + /*! Returns the current value of this item, as a QVariant. + * subkey is realtive to the provided root. + */ + QVariant value(const QString &subKey) const; + + /*! Returns the current value of this item, as a QVariant. If + * there is no value for this item, return \a def instead. + + */ + void set(const QString &subKey, const QVariant &val); + + /*! Return a list of the directories below this item. The + returned strings are absolute key names like + "/myapp/settings". + + A directory is a key that has children. The same key might + also have a value, but that is confusing and best avoided. + */ + QList listDirs() const; + + /*! Return a list of entries below this item. The returned + strings are absolute key names like "/myapp/settings/first". + + A entry is a key that has a value. The same key might also + have children, but that is confusing and is best avoided. + */ + QList listEntries() const; + + Q_SIGNALS: + /*! Emitted when some value in subtree of this item changes + */ + + void subtreeChanged(const QString& key, const QVariant& value); + + private: + friend struct GConfItemPrivate; + struct GConfItemPrivate *priv; + + void update_value(bool emit_signal, const QString& key, const QVariant& value); +}; + +#endif // GCONFITEM_H diff --git a/src/gconfitem.cpp b/src/gconfitem.cpp new file mode 100644 --- /dev/null +++ b/src/gconfitem.cpp @@ -0,0 +1,335 @@ +/* + * Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). + * All rights reserved. + * Copyright (C) 2016 David Edmundson + * + * Contact: Marius Vollmer + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * version 2.1 as published by the Free Software Foundation. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#include +#include +#include +#include +#include + +#include "gconfitem.h" + +#include +#include +#include + +struct GConfItemPrivate +{ + QString root; + QVariant value; + guint notify_id; + + static void notify_trampoline(GConfClient*, guint, GConfEntry *, gpointer); +}; + +#define withClient(c) for(GConfClient *c = (gconf_client_get_default()); c; g_object_unref(c), c=NULL) + +static QByteArray convertKey(QString key) +{ + if (key.startsWith('/')) { + return key.toUtf8(); + } else { + qWarning() << "Using dot-separated key names with GConfItem is deprecated."; + qWarning() << "Please use" << '/' + key.replace('.', '/') << "instead of" << key; + return '/' + key.replace('.', '/').toUtf8(); + } +} + +static QString convertKey(const char *key) +{ + return QString::fromUtf8(key); +} + +static QVariant convertValue(GConfValue *src) +{ + if (!src) { + return QVariant(); + } else { + switch (src->type) { + case GCONF_VALUE_INVALID: + return QVariant(QVariant::Invalid); + case GCONF_VALUE_BOOL: + return QVariant((bool)gconf_value_get_bool(src)); + case GCONF_VALUE_INT: + return QVariant(gconf_value_get_int(src)); + case GCONF_VALUE_FLOAT: + return QVariant(gconf_value_get_float(src)); + case GCONF_VALUE_STRING: + return QVariant(QString::fromUtf8(gconf_value_get_string(src))); + case GCONF_VALUE_LIST: + switch (gconf_value_get_list_type(src)) { + case GCONF_VALUE_STRING: + { + QStringList result; + for (GSList *elts = gconf_value_get_list(src); elts; elts = elts->next) + result.append(QString::fromUtf8(gconf_value_get_string((GConfValue *)elts->data))); + return QVariant(result); + } + default: + { + QList result; + for (GSList *elts = gconf_value_get_list(src); elts; elts = elts->next) + result.append(convertValue((GConfValue *)elts->data)); + return QVariant(result); + } + } + case GCONF_VALUE_SCHEMA: + default: + return QVariant(); + } + } +} + +static GConfValue *convertString(const QString &str) +{ + GConfValue *v = gconf_value_new (GCONF_VALUE_STRING); + gconf_value_set_string (v, str.toUtf8().data()); + return v; +} + +static GConfValueType primitiveType (const QVariant &elt) +{ + switch(elt.type()) { + case QVariant::String: + return GCONF_VALUE_STRING; + case QVariant::Int: + return GCONF_VALUE_INT; + case QVariant::Double: + return GCONF_VALUE_FLOAT; + case QVariant::Bool: + return GCONF_VALUE_BOOL; + default: + return GCONF_VALUE_INVALID; + } +} + +static GConfValueType uniformType(const QList &list) +{ + GConfValueType result = GCONF_VALUE_INVALID; + + Q_FOREACH (const QVariant &elt, list) { + GConfValueType elt_type = primitiveType (elt); + + if (elt_type == GCONF_VALUE_INVALID) + return GCONF_VALUE_INVALID; + + if (result == GCONF_VALUE_INVALID) + result = elt_type; + else if (result != elt_type) + return GCONF_VALUE_INVALID; + } + + if (result == GCONF_VALUE_INVALID) + return GCONF_VALUE_STRING; // empty list. + else + return result; +} + +static int convertValue(const QVariant &src, GConfValue **valp) +{ + GConfValue *v; + + switch(src.type()) { + case QVariant::Invalid: + v = NULL; + break; + case QVariant::Bool: + v = gconf_value_new (GCONF_VALUE_BOOL); + gconf_value_set_bool (v, src.toBool()); + break; + case QVariant::Int: + v = gconf_value_new (GCONF_VALUE_INT); + gconf_value_set_int (v, src.toInt()); + break; + case QVariant::Double: + v = gconf_value_new (GCONF_VALUE_FLOAT); + gconf_value_set_float (v, src.toDouble()); + break; + case QVariant::String: + v = convertString(src.toString()); + break; + case QVariant::StringList: + { + GSList *elts = NULL; + v = gconf_value_new(GCONF_VALUE_LIST); + gconf_value_set_list_type(v, GCONF_VALUE_STRING); + Q_FOREACH (const QString &str, src.toStringList()) + elts = g_slist_prepend(elts, convertString(str)); + gconf_value_set_list_nocopy(v, g_slist_reverse(elts)); + break; + } + case QVariant::List: + { + GConfValueType elt_type = uniformType(src.toList()); + if (elt_type == GCONF_VALUE_INVALID) + v = NULL; + else + { + GSList *elts = NULL; + v = gconf_value_new(GCONF_VALUE_LIST); + gconf_value_set_list_type(v, elt_type); + Q_FOREACH (const QVariant &elt, src.toList()) + { + GConfValue *val = NULL; + convertValue(elt, &val); // guaranteed to succeed. + elts = g_slist_prepend(elts, val); + } + gconf_value_set_list_nocopy(v, g_slist_reverse(elts)); + } + break; + } + default: + return 0; + } + + *valp = v; + return 1; +} + +void GConfItemPrivate::notify_trampoline (GConfClient*, + guint, + GConfEntry *entry, + gpointer data) +{ + GConfItem *item = (GConfItem *)data; + + item->update_value (true, entry->key, convertValue(entry->value)); +} + +void GConfItem::update_value (bool emit_signal, const QString& key, const QVariant& value) +{ + QVariant new_value; + + if (emit_signal) { + subtreeChanged(key, value); + } +} + +QString GConfItem::root() const +{ + return priv->root; +} + +QVariant GConfItem::value(const QString &subKey) const +{ + QVariant new_value; + withClient(client) { + GError *error = NULL; + QByteArray k = convertKey(priv->root + '/' + subKey); + GConfValue *v = gconf_client_get(client, k.data(), &error); + + if (error) { + qWarning() << error->message; + g_error_free (error); + new_value = QVariant(); + } else { + new_value = convertValue(v); + if (v) + gconf_value_free(v); + } + } + return new_value; +} + +void GConfItem::set(const QString &subKey, const QVariant &val) +{ + withClient(client) { + QByteArray k = convertKey(priv->root + '/' + subKey); + GConfValue *v; + if (convertValue(val, &v)) { + GError *error = NULL; + + if (v) { + gconf_client_set(client, k.data(), v, &error); + gconf_value_free(v); + } else { + gconf_client_unset(client, k.data(), &error); + } + + if (error) { + qWarning() << error->message; + g_error_free(error); + } + } else { + qWarning() << "Can't store a" << val.typeName(); + } + } +} + +QList GConfItem::listDirs() const +{ + QList children; + + withClient(client) { + QByteArray k = convertKey(priv->root); + GSList *dirs = gconf_client_all_dirs(client, k.data(), NULL); + for (GSList *d = dirs; d; d = d->next) { + children.append(convertKey((char *)d->data)); + g_free (d->data); + } + g_slist_free (dirs); + } + + return children; +} + +QList GConfItem::listEntries() const +{ + QList children; + + withClient(client) { + QByteArray k = convertKey(priv->root); + GSList *entries = gconf_client_all_entries(client, k.data(), NULL); + for (GSList *e = entries; e; e = e->next) { + children.append(convertKey(((GConfEntry *)e->data)->key)); + gconf_entry_free ((GConfEntry *)e->data); + } + g_slist_free (entries); + } + + return children; +} + +GConfItem::GConfItem(const QString &key, QObject *parent) + : QObject (parent), + priv(new GConfItemPrivate) +{ + priv->root = key; + withClient(client) { + QByteArray k = convertKey(priv->root); + gconf_client_add_dir (client, k.data(), GCONF_CLIENT_PRELOAD_ONELEVEL, NULL); + priv->notify_id = gconf_client_notify_add (client, k.data(), + GConfItemPrivate::notify_trampoline, this, + NULL, NULL); + } +} + +GConfItem::~GConfItem() +{ + withClient(client) { + QByteArray k = convertKey(priv->root); + gconf_client_notify_remove (client, priv->notify_id); + gconf_client_remove_dir (client, k.data(), NULL); + } + delete priv; +} diff --git a/src/kcm/package/contents/ui/CardView.qml b/src/kcm/package/contents/ui/CardView.qml --- a/src/kcm/package/contents/ui/CardView.qml +++ b/src/kcm/package/contents/ui/CardView.qml @@ -19,7 +19,26 @@ */ import QtQuick 2.0 +import QtQuick.Layouts 1.1 +import QtQuick.Controls 1.3 -PulseView { - delegate: CardListItem {} +import org.kde.plasma.private.volume 0.1 + +ColumnLayout { + property alias model: view.model + property alias emptyText: view.emptyText + PulseView { + id: view + Layout.fillHeight: true + Layout.fillWidth: true + delegate: CardListItem {} + } + CheckBox { + ModuleManager { + id: moduleManager + } + text: i18n("Add virtual output device for simultaneous output on all local sound cards") + checked: moduleManager.combineSinks + onCheckedChanged: moduleManager.combineSinks = checked; + } } diff --git a/src/modulemanager.h b/src/modulemanager.h new file mode 100644 --- /dev/null +++ b/src/modulemanager.h @@ -0,0 +1,57 @@ +/* + Copyright 2016 David Edmundson + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) version 3, or any + later version accepted by the membership of KDE e.V. (or its + successor approved by the membership of KDE e.V.), which shall + act as a proxy defined in Section 6 of version 3 of the license. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library. If not, see . +*/ + +#ifndef MODULEMANAGER_H +#define MODULEMANAGER_H + +#include + +#include + +#include "context.h" +// Properties need fully qualified classes even with pointers. +#include "client.h" + +class GConfItem; + +namespace QPulseAudio +{ + +class Q_DECL_EXPORT ModuleManager : public QObject +{ + Q_OBJECT + Q_PROPERTY(bool combineSinks READ combineSinks WRITE setCombineSinks NOTIFY combineSinksChanged) +public: + ModuleManager(QObject *parent=0); + ~ModuleManager(); + bool combineSinks() const; + void setCombineSinks(bool combineSinks); + +Q_SIGNALS: + void combineSinksChanged(); + +protected: +private: + GConfItem *m_combineSinksConfig; +}; + +} // QPulseAudio + +#endif // STREAM_H diff --git a/src/modulemanager.cpp b/src/modulemanager.cpp new file mode 100644 --- /dev/null +++ b/src/modulemanager.cpp @@ -0,0 +1,65 @@ +/* + Copyright 2016 David Edmundson + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) version 3, or any + later version accepted by the membership of KDE e.V. (or its + successor approved by the membership of KDE e.V.), which shall + act as a proxy defined in Section 6 of version 3 of the license. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library. If not, see . +*/ + + +#include "modulemanager.h" + +#define PA_GCONF_ROOT "/system/pulseaudio" +#define PA_GCONF_PATH_MODULES PA_GCONF_ROOT"/modules" + +#include "gconfitem.h" + +namespace QPulseAudio +{ +ModuleManager::ModuleManager(QObject *parent) : + QObject(parent), + m_combineSinksConfig(new GConfItem(PA_GCONF_PATH_MODULES"/combine", this)) +{ + connect(m_combineSinksConfig, &GConfItem::subtreeChanged , this, &ModuleManager::combineSinksChanged); +} + +ModuleManager::~ModuleManager() +{ +}; + + +bool ModuleManager::combineSinks() const +{ + return m_combineSinksConfig->value("enabled").toBool(); +} + +void ModuleManager::setCombineSinks(bool combineSinks) +{ + GConfItem combine(); + + m_combineSinksConfig->set(QStringLiteral("locked"), true); + + if (combineSinks) { + m_combineSinksConfig->set(QStringLiteral("name0"), QStringLiteral("module-combine")); + m_combineSinksConfig->set(QStringLiteral("args0"), QVariant()); + + m_combineSinksConfig->set(QStringLiteral("enabled"), true); + } else { + m_combineSinksConfig->set(QStringLiteral("enabled"), false); + } + m_combineSinksConfig->set(QStringLiteral("locked"), false); +} + +} diff --git a/src/qml/plugin.cpp b/src/qml/plugin.cpp --- a/src/qml/plugin.cpp +++ b/src/qml/plugin.cpp @@ -27,6 +27,7 @@ #include "sink.h" #include "source.h" #include "context.h" +#include "modulemanager.h" #include "globalactioncollection.h" #include "volumeosd.h" @@ -48,6 +49,7 @@ qmlRegisterType(uri, 0, 1, "SinkModel"); qmlRegisterType(uri, 0, 1, "SinkInputModel"); qmlRegisterType(uri, 0, 1, "SourceModel"); + qmlRegisterType(uri, 0, 1, "ModuleManager"); qmlRegisterType(uri, 0, 1, "SourceOutputModel"); qmlRegisterType(uri, 0, 1, "StreamRestoreModel"); qmlRegisterType(uri, 0, 1, "GlobalAction");