diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 335af3e..65f9b83 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,36 +1,39 @@ -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 client.cpp context.cpp device.cpp maps.cpp operation.cpp port.cpp profile.cpp pulseaudio.cpp pulseobject.cpp ref.cpp sink.cpp sinkinput.cpp + modulemanager.cpp + gconfitem.cpp source.cpp sourceoutput.cpp stream.cpp volumeobject.cpp debug.cpp server.cpp streamrestore.cpp ) add_library(QPulseAudioPrivate SHARED ${SRC_LIST}) target_link_libraries(QPulseAudioPrivate Qt5::Core Qt5::Gui ${PULSEAUDIO_LIBRARY} ${PULSEAUDIO_MAINLOOP_LIBRARY} ) install(TARGETS QPulseAudioPrivate ${INSTALL_TARGETS_DEFAULT_ARGS}) add_subdirectory(kcm) add_subdirectory(qml) diff --git a/src/gconfitem.cpp b/src/gconfitem.cpp new file mode 100644 index 0000000..bbb06ce --- /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/gconfitem.h b/src/gconfitem.h new file mode 100644 index 0000000..1685a6a --- /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/kcm/package/contents/ui/CardView.qml b/src/kcm/package/contents/ui/CardView.qml index 2f78fed..c93df3d 100644 --- a/src/kcm/package/contents/ui/CardView.qml +++ b/src/kcm/package/contents/ui/CardView.qml @@ -1,25 +1,44 @@ /* Copyright 2014-2015 Harald Sitter This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 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 14 of version 3 of the license. 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ 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.cpp b/src/modulemanager.cpp new file mode 100644 index 0000000..e4bf206 --- /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(QStringLiteral(PA_GCONF_PATH_MODULES"/combine"), this)) +{ + connect(m_combineSinksConfig, &GConfItem::subtreeChanged, this, &ModuleManager::combineSinksChanged); +} + +ModuleManager::~ModuleManager() +{ +}; + + +bool ModuleManager::combineSinks() const +{ + return m_combineSinksConfig->value(QStringLiteral("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/modulemanager.h b/src/modulemanager.h new file mode 100644 index 0000000..d66caaa --- /dev/null +++ b/src/modulemanager.h @@ -0,0 +1,56 @@ +/* + 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 = nullptr); + ~ModuleManager(); + bool combineSinks() const; + void setCombineSinks(bool combineSinks); + +Q_SIGNALS: + void combineSinksChanged(); + +private: + GConfItem *m_combineSinksConfig; +}; + +} // QPulseAudio + +#endif // STREAM_H diff --git a/src/qml/plugin.cpp b/src/qml/plugin.cpp index 72a08a1..be54db8 100644 --- a/src/qml/plugin.cpp +++ b/src/qml/plugin.cpp @@ -1,61 +1,63 @@ /* Copyright 2014-2015 Harald Sitter 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 "plugin.h" #include #include "pulseaudio.h" #include "client.h" #include "sink.h" #include "source.h" #include "context.h" +#include "modulemanager.h" #include "globalactioncollection.h" #include "volumeosd.h" static QJSValue pulseaudio_singleton(QQmlEngine *engine, QJSEngine *scriptEngine) { Q_UNUSED(engine) QJSValue object = scriptEngine->newObject(); object.setProperty(QStringLiteral("NormalVolume"), (double) QPulseAudio::Context::NormalVolume); object.setProperty(QStringLiteral("MinimalVolume"), (double) QPulseAudio::Context::MinimalVolume); object.setProperty(QStringLiteral("MaximalVolume"), (double) QPulseAudio::Context::MaximalVolume); return object; } void Plugin::registerTypes(const char* uri) { qmlRegisterType(uri, 0, 1, "CardModel"); 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"); qmlRegisterType(uri, 0, 1, "GlobalActionCollection"); qmlRegisterType(uri, 0, 1, "VolumeOSD"); qmlRegisterSingletonType(uri, 0, 1, "PulseAudio", pulseaudio_singleton); qmlRegisterType(); qmlRegisterType(); qmlRegisterType(); }