diff --git a/src/context.cpp b/src/context.cpp --- a/src/context.cpp +++ b/src/context.cpp @@ -486,22 +486,7 @@ void Context::streamRestoreCallback(const pa_ext_stream_restore_info *info) { - if (qstrcmp(info->name, "sink-input-by-media-role:event") != 0) { - return; - } - - const int eventRoleIndex = 1; - StreamRestore *obj = qobject_cast(m_streamRestores.data().value(eventRoleIndex)); - - if (!obj) { - QVariantMap props; - props.insert(QStringLiteral("application.icon_name"), - QStringLiteral("preferences-desktop-notification")); - obj = new StreamRestore(eventRoleIndex, props, this); - m_streamRestores.insert(obj); - } - - obj->update(info); + m_streamRestores.updateEntryByName(info, this); } void Context::serverCallback(const pa_server_info *info) diff --git a/src/kcm/package/contents/ui/Applications.qml b/src/kcm/package/contents/ui/Applications.qml --- a/src/kcm/package/contents/ui/Applications.qml +++ b/src/kcm/package/contents/ui/Applications.qml @@ -57,6 +57,7 @@ } delegate: StreamListItem { deviceModel: sinkModel + seperatorVisible: sinkInputView.count > 0 } } diff --git a/src/kcm/package/contents/ui/StreamListItem.qml b/src/kcm/package/contents/ui/StreamListItem.qml --- a/src/kcm/package/contents/ui/StreamListItem.qml +++ b/src/kcm/package/contents/ui/StreamListItem.qml @@ -57,7 +57,7 @@ text: { if (isEventStream) { return i18n("Notification Sounds"); - } else if (Client) { + } else if (typeof Client !== "undefined" && Client) { return i18nc("label of stream items", "%1: %2", Client.name, Name); } else { return Name; @@ -84,15 +84,9 @@ } } + property alias seperatorVisible: seperator.visible ListItemSeperator { + id: seperator view: delegate.ListView.view - - Component.onCompleted: { - if (isEventStream) { - visible = Qt.binding(function() { - return sinkInputView.count > 0; - }); - } - } } } diff --git a/src/kcm/package/contents/ui/main.qml b/src/kcm/package/contents/ui/main.qml --- a/src/kcm/package/contents/ui/main.qml +++ b/src/kcm/package/contents/ui/main.qml @@ -69,6 +69,10 @@ Applications {} } Tab { + title: i18nc("@title:tab", "Stream Restore") + StreamRestoreTab {} + } + Tab { title: i18nc("@title:tab", "Advanced") Advanced {} } diff --git a/src/maps.h b/src/maps.h --- a/src/maps.h +++ b/src/maps.h @@ -168,7 +168,113 @@ typedef MapBase ClientMap; typedef MapBase CardMap; typedef MapBase ModuleMap; -typedef MapBase StreamRestoreMap; + + + +/** + * Maps a specific name to a specific object pointer. + * This is used to give the unique arbitrary PulseAudio name of a PulseObject a + * serialized list index. Namely it enables us to translate a discrete list + * index to a pulse name to an object, and any permutation thereof. + */ +template +class StringMapBase : public MapBaseQObject +{ +public: + virtual ~StringMapBase() {} + + const QMap &data() const { return m_data; } + + int count() const Q_DECL_OVERRIDE + { + return m_data.count(); + } + + int indexOfObject(QObject *object) const Q_DECL_OVERRIDE + { + int index = 0; + QMapIterator it(m_data); + while (it.hasNext()) { + it.next(); + if (it.value() == object) { + return index; + } + index++; + } + return -1; + } + + QObject *objectAt(int index) const Q_DECL_OVERRIDE + { + return (m_data.constBegin() + index).value(); + } + + void reset() + { + while (!m_data.isEmpty()) { + removeEntry(m_data.lastKey()); + } + m_pendingRemovals.clear(); + } + + void insert(Type *object) + { + Q_ASSERT(!m_data.contains(object->name())); + + m_data.insert(object->name(), object); + + const int modelIndex = m_data.keys().indexOf(object->name()); + Q_ASSERT(modelIndex >= 0); + emit added(modelIndex); + } + + // Context is passed in as parent because context needs to include the maps + // so we'd cause a circular dep if we were to try to use the instance here. + // Plus that's weird separation anyway. + void updateEntryByName(const PAInfo *info, QObject *parent) + { + Q_ASSERT(info); + + QString infoName = QString::fromUtf8(info->name); + + if (m_pendingRemovals.remove(infoName)) { + // Was already removed again. + return; + } + + const bool isNew = !m_data.contains(infoName); + + auto *obj = m_data.value(infoName, nullptr); + if (!obj) { + obj = new Type(parent); + } + obj->update(info); + m_data.insert(infoName, obj); + + if (isNew) { + const int modelIndex = m_data.keys().indexOf(infoName); + Q_ASSERT(modelIndex >= 0); + emit added(modelIndex); + } + } + + void removeEntry(QString name) + { + if (!m_data.contains(name)) { + m_pendingRemovals.insert(name); + } else { + const int modelIndex = m_data.keys().indexOf(name); + delete m_data.take(name); + emit removed(modelIndex); + } + } + +protected: + QMap m_data; + QSet m_pendingRemovals; +}; + +typedef StringMapBase StreamRestoreMap; } // QPulseAudio diff --git a/src/streamrestore.h b/src/streamrestore.h --- a/src/streamrestore.h +++ b/src/streamrestore.h @@ -41,7 +41,7 @@ Q_PROPERTY(QList channelVolumes READ channelVolumes NOTIFY channelVolumesChanged) Q_PROPERTY(quint32 deviceIndex READ deviceIndex WRITE setDeviceIndex NOTIFY deviceIndexChanged) public: - StreamRestore(quint32 index, const QVariantMap &properties, QObject *parent); + StreamRestore(QObject *parent); void update(const pa_ext_stream_restore_info *info); diff --git a/src/streamrestore.cpp b/src/streamrestore.cpp --- a/src/streamrestore.cpp +++ b/src/streamrestore.cpp @@ -25,15 +25,14 @@ namespace QPulseAudio { -StreamRestore::StreamRestore(quint32 index, const QVariantMap &properties, QObject *parent) +StreamRestore::StreamRestore(QObject *parent) : PulseObject(parent) , m_muted(false) { memset(&m_volume, 0, sizeof(m_volume)); memset(&m_channelMap, 0, sizeof(m_channelMap)); - m_index = index; - m_properties = properties; + m_properties.insert(QStringLiteral("application.icon_name"), QStringLiteral("preferences-desktop-notification")); } void StreamRestore::update(const pa_ext_stream_restore_info *info)