diff --git a/src/kcm/package/contents/ui/Advanced.qml b/src/kcm/package/contents/ui/Advanced.qml index e208006..c737ff4 100644 --- a/src/kcm/package/contents/ui/Advanced.qml +++ b/src/kcm/package/contents/ui/Advanced.qml @@ -1,245 +1,245 @@ /* 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 import org.kde.plasma.private.volume 0.1 import org.kde.kcoreaddons 1.0 as KCoreAddons ScrollView { id: scrollView ColumnLayout { Component.onCompleted: { // Normal binding causes binding loops width = Qt.binding(function() { return scrollView.viewport.width; }); } Header { Layout.fillWidth: true enabled: view.count > 0 text: i18n("Device Profiles") disabledText: i18nc("@label", "No Device Profiles Available") } ListView { id: view Layout.fillWidth: true Layout.preferredHeight: contentHeight Layout.margins: units.gridUnit / 2 interactive: false spacing: units.smallSpacing * 2 model: CardModel {} delegate: CardListItem {} } Header { Layout.fillWidth: true text: i18n("Advanced Output Configuration") visible: moduleManager.settingsSupported } ModuleManager { id: moduleManager } CheckBox { Layout.fillWidth: true Layout.topMargin: units.smallSpacing Layout.leftMargin: units.gridUnit / 2 Layout.rightMargin: units.gridUnit / 2 text: i18n("Add virtual output device for simultaneous output on all local sound cards") checked: moduleManager.combineSinks onCheckedChanged: moduleManager.combineSinks = checked; enabled: moduleManager.loadedModules.indexOf("module-gconf") != -1 visible: moduleManager.settingsSupported } CheckBox { Layout.fillWidth: true Layout.leftMargin: units.gridUnit / 2 Layout.rightMargin: units.gridUnit / 2 text: i18n("Automatically switch all running streams when a new output becomes available") checked: moduleManager.switchOnConnect onCheckedChanged: moduleManager.switchOnConnect = checked; enabled: moduleManager.loadedModules.indexOf("module-gconf") != -1 visible: moduleManager.settingsSupported } Label { Layout.alignment: Qt.AlignHCenter enabled: false font.italic: true text: i18n("Requires 'module-gconf' PulseAudio module") visible: moduleManager.settingsSupported && moduleManager.loadedModules.indexOf("module-gconf") == -1 } Header { Layout.fillWidth: true text: i18n("Speaker Placement and Testing") } RowLayout { Layout.margins: units.gridUnit / 2 visible: sinks.count > 1 Label { text: i18nc("@label", "Output:") font.bold: true } ComboBox { id: sinks property var pulseObject: null Layout.fillWidth: true textRole: "Description" model: SinkModel { onRowsInserted: sinks.updatePulseObject() onRowsRemoved: sinks.updatePulseObject() onDataChanged: sinks.updatePulseObject() } onCurrentIndexChanged: updatePulseObject() onCurrentTextChanged: updatePulseObject() Component.onCompleted: updatePulseObject() function updatePulseObject() { Qt.callLater(function() { pulseObject = model.data(model.index(sinks.currentIndex, 0), model.role("PulseObject")); }); } } } Grid { id: grid columns: 3 spacing: 5 Layout.fillWidth: true Item { width: grid.width/3 height: 50 Button{ text: i18n("Front Left") anchors.centerIn: parent - visible: sinks.pulseObject ? sinks.pulseObject.channels.indexOf("Front Left") > -1 : false - onClicked: sinks.pulseObject.testChannel("Front Left") + visible: sinks.pulseObject ? sinks.pulseObject.rawChannels.indexOf("front-left") > -1 : false + onClicked: sinks.pulseObject.testChannel("front-left") } } Item { width: grid.width/3 height: 50 Button{ text: i18n("Front Center") anchors.centerIn: parent - visible: sinks.pulseObject ? sinks.pulseObject.channels.indexOf("Front Center") > -1 : false - onClicked: sinks.pulseObject.testChannel("Front Center") + visible: sinks.pulseObject ? sinks.pulseObject.rawChannels.indexOf("front-center") > -1 : false + onClicked: sinks.pulseObject.testChannel("front-center") } } Item { width: grid.width/3 height: 50 Button{ text: i18n("Front Right") anchors.centerIn: parent - visible: sinks.pulseObject ? sinks.pulseObject.channels.indexOf("Front Right") > -1 : false - onClicked: sinks.pulseObject.testChannel("Front Right") + visible: sinks.pulseObject ? sinks.pulseObject.rawChannels.indexOf("front-right") > -1 : false + onClicked: sinks.pulseObject.testChannel("front-right") } } Item { width: grid.width/3 height: 50 Button{ text: i18n("Side Left") anchors.centerIn: parent - visible: sinks.pulseObject ? sinks.pulseObject.channels.indexOf("Side Left") > -1 : false - onClicked: sinks.pulseObject.testChannel("Side Left") + visible: sinks.pulseObject ? sinks.pulseObject.rawChannels.indexOf("side-left") > -1 : false + onClicked: sinks.pulseObject.testChannel("side-left") } } Item { width: grid.width/3 height: 50 KCoreAddons.KUser { id: kuser } Image { source: kuser.faceIconUrl anchors.centerIn: parent width: 50 height: 50 } } Item { width: grid.width/3 height: 50 Button{ text: i18n("Side Right") anchors.centerIn: parent - visible: sinks.pulseObject ? sinks.pulseObject.channels.indexOf("Side Right") > -1 : false - onClicked: sinks.pulseObject.testChannel("Side Right") + visible: sinks.pulseObject ? sinks.pulseObject.rawChannels.indexOf("side-right") > -1 : false + onClicked: sinks.pulseObject.testChannel("side-right") } } Item { width: grid.width/3 height: 50 Button{ text: i18n("Rear Left") anchors.centerIn: parent - visible: sinks.pulseObject ? sinks.pulseObject.channels.indexOf("Rear Left") > -1 : false - onClicked: sinks.pulseObject.testChannel("Rear Left") + visible: sinks.pulseObject ? sinks.pulseObject.rawChannels.indexOf("rear-left") > -1 : false + onClicked: sinks.pulseObject.testChannel("rear-left") } } Item { width: grid.width/3 height: 50 Button{ text: i18n("Subwoofer") anchors.centerIn: parent - visible: sinks.pulseObject ? sinks.pulseObject.channels.indexOf("Subwoofer") > -1 : false - onClicked: sinks.pulseObject.testChannel("Subwoofer") + visible: sinks.pulseObject ? sinks.pulseObject.rawChannels.indexOf("lfe") > -1 : false + onClicked: sinks.pulseObject.testChannel("subwoofer") } } Item { width: grid.width/3 height: 50 Button{ text: i18n("Rear Right") anchors.centerIn: parent - visible: sinks.pulseObject ? sinks.pulseObject.channels.indexOf("Rear Right") > -1 : false - onClicked: sinks.pulseObject.testChannel("Rear Right") + visible: sinks.pulseObject ? sinks.pulseObject.rawChannels.indexOf("rear-right") > -1 : false + onClicked: sinks.pulseObject.testChannel("rear-right") } } } } } diff --git a/src/sink.cpp b/src/sink.cpp index b183c3a..78eaafd 100644 --- a/src/sink.cpp +++ b/src/sink.cpp @@ -1,198 +1,119 @@ /* 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 "sink.h" #include "context.h" #include "server.h" #include "canberracontext.h" #include namespace QPulseAudio { Sink::Sink(QObject *parent) : Device(parent) { connect(context()->server(), &Server::defaultSinkChanged, this, &Sink::defaultChanged); CanberraContext::instance()->ref(); } Sink::~Sink() { CanberraContext::instance()->unref(); } void Sink::update(const pa_sink_info *info) { updateDevice(info); } void Sink::setVolume(qint64 volume) { context()->setGenericVolume(index(), -1, volume, cvolume(), &pa_context_set_sink_volume_by_index); } void Sink::setMuted(bool muted) { context()->setGenericMute(m_index, muted, &pa_context_set_sink_mute_by_index); } void Sink::setActivePortIndex(quint32 port_index) { Port *port = qobject_cast(ports().at(port_index)); if (!port) { qCWarning(PLASMAPA) << "invalid port set request" << port_index; return; } context()->setGenericPort(index(), port->name(), &pa_context_set_sink_port_by_index); } void Sink::setChannelVolume(int channel, qint64 volume) { context()->setGenericVolume(index(), channel, volume, cvolume(), &pa_context_set_sink_volume_by_index); } bool Sink::isDefault() const { return context()->server()->defaultSink() == this; } void Sink::setDefault(bool enable) { if (!isDefault() && enable) { context()->server()->setDefaultSink(this); } } -pa_channel_position_t Sink::channelNameToPosition(const QString &name) -{ - if (name == QLatin1String("Front Left")) { - return PA_CHANNEL_POSITION_FRONT_LEFT; - } else if (name == QLatin1String("Front Center")) { - return PA_CHANNEL_POSITION_FRONT_CENTER; - } else if (name == QLatin1String("Front Right")) { - return PA_CHANNEL_POSITION_FRONT_RIGHT; - } else if (name == QLatin1String("Side Left")) { - return PA_CHANNEL_POSITION_SIDE_LEFT; - } else if (name == QLatin1String("Side Right")) { - return PA_CHANNEL_POSITION_SIDE_RIGHT; - } else if (name == QLatin1String("Rear Left")) { - return PA_CHANNEL_POSITION_REAR_LEFT; - } else if (name == QLatin1String("Rear Right")) { - return PA_CHANNEL_POSITION_REAR_RIGHT; - } else if (name == QLatin1String("Subwoofer")) { - return PA_CHANNEL_POSITION_SUBWOOFER; - } - - return PA_CHANNEL_POSITION_MONO; -} - -QString Sink::positionToChannelName(pa_channel_position_t position) -{ - switch (position) { - case PA_CHANNEL_POSITION_FRONT_LEFT: - return QStringLiteral("Front Left"); - case PA_CHANNEL_POSITION_FRONT_RIGHT: - return QStringLiteral("Front Right"); - case PA_CHANNEL_POSITION_FRONT_CENTER: - return QStringLiteral("Front Center"); - case PA_CHANNEL_POSITION_SIDE_LEFT: - return QStringLiteral("Side Left"); - case PA_CHANNEL_POSITION_SIDE_RIGHT: - return QStringLiteral("Side Right"); - case PA_CHANNEL_POSITION_REAR_LEFT: - return QStringLiteral("Rear Left"); - case PA_CHANNEL_POSITION_REAR_RIGHT: - return QStringLiteral("Rear Right"); - case PA_CHANNEL_POSITION_SUBWOOFER: - return QStringLiteral("Subwoofer"); - default: - return QStringLiteral("Mono"); - } -} - void Sink::testChannel(const QString &name) { auto context = CanberraContext::instance()->canberra(); if (!context) return; char dev[64]; snprintf(dev, sizeof(dev), "%lu", (unsigned long) m_index); ca_context_change_device(context, dev); - QString sound_name = QStringLiteral("audio-channel-") + positionAsString(channelNameToPosition(name)); + QString sound_name = QStringLiteral("audio-channel-") + name; ca_proplist *proplist; ca_proplist_create(&proplist); ca_proplist_sets(proplist, CA_PROP_MEDIA_ROLE, "test"); ca_proplist_sets(proplist, CA_PROP_MEDIA_NAME, name.toLatin1().constData()); - ca_proplist_sets(proplist, CA_PROP_CANBERRA_FORCE_CHANNEL, positionAsString(channelNameToPosition(name)).toLatin1().data()); + ca_proplist_sets(proplist, CA_PROP_CANBERRA_FORCE_CHANNEL, name.toLatin1().data()); ca_proplist_sets(proplist, CA_PROP_CANBERRA_ENABLE, "1"); ca_proplist_sets(proplist, CA_PROP_EVENT_ID, sound_name.toLatin1().data()); if (ca_context_play_full(context, 0, proplist, nullptr, NULL) < 0) { // Try a different sound name. ca_proplist_sets(proplist, CA_PROP_EVENT_ID, "audio-test-signal"); if (ca_context_play_full(context, 0, proplist, nullptr, NULL) < 0) { // Finaly try this... if this doesn't work, then stuff it. ca_proplist_sets(proplist, CA_PROP_EVENT_ID, "bell-window-system"); ca_context_play_full(context, 0, proplist, nullptr, NULL); } } ca_context_change_device(context, nullptr); ca_proplist_destroy(proplist); } -QString Sink::positionAsString(pa_channel_position_t pos) -{ - switch (pos) { - case PA_CHANNEL_POSITION_FRONT_LEFT: - return QStringLiteral("front-left"); - case PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER: - return QStringLiteral("front-left-of-center"); - case PA_CHANNEL_POSITION_FRONT_CENTER: - return QStringLiteral("front-center"); - case PA_CHANNEL_POSITION_MONO: - return QStringLiteral("mono"); - case PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER: - return QStringLiteral("front-right-of-center"); - case PA_CHANNEL_POSITION_FRONT_RIGHT: - return QStringLiteral("front-right"); - case PA_CHANNEL_POSITION_SIDE_LEFT: - return QStringLiteral("side-left"); - case PA_CHANNEL_POSITION_SIDE_RIGHT: - return QStringLiteral("side-right"); - case PA_CHANNEL_POSITION_REAR_LEFT: - return QStringLiteral("rear-left"); - case PA_CHANNEL_POSITION_REAR_CENTER: - return QStringLiteral("rear-center"); - case PA_CHANNEL_POSITION_REAR_RIGHT: - return QStringLiteral("rear-right"); - case PA_CHANNEL_POSITION_SUBWOOFER: - return QStringLiteral("subwoofer"); - default: - break; - } - return QStringLiteral("invalid"); -} } // QPulseAudio diff --git a/src/sink.h b/src/sink.h index 115d702..9cb5114 100644 --- a/src/sink.h +++ b/src/sink.h @@ -1,59 +1,53 @@ /* 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 . */ #ifndef SINK_H #define SINK_H #include "device.h" #include #include namespace QPulseAudio { class Sink : public Device { Q_OBJECT public: explicit Sink(QObject *parent); virtual ~Sink(); void update(const pa_sink_info *info); void setVolume(qint64 volume) override; void setMuted(bool muted) override; void setActivePortIndex(quint32 port_index) override; void setChannelVolume(int channel, qint64 volume) override; bool isDefault() const override; void setDefault(bool enable) override; public Q_SLOTS: void testChannel(const QString &name); - -private: - pa_channel_position_t channelNameToPosition(const QString &name); - QString positionToChannelName(pa_channel_position_t position); - QString positionAsString(pa_channel_position_t pos); - }; } // QPulseAudio #endif // SINK_H diff --git a/src/volumeobject.cpp b/src/volumeobject.cpp index 33cc100..c4055b3 100644 --- a/src/volumeobject.cpp +++ b/src/volumeobject.cpp @@ -1,79 +1,84 @@ /* 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 "volumeobject.h" namespace QPulseAudio { VolumeObject::VolumeObject(QObject *parent) : PulseObject(parent) , m_muted(true) , m_hasVolume(true) , m_volumeWritable(true) { pa_cvolume_init(&m_volume); } VolumeObject::~VolumeObject() { } qint64 VolumeObject::volume() const { return pa_cvolume_max(&m_volume); } bool VolumeObject::isMuted() const { return m_muted; } pa_cvolume VolumeObject::cvolume() const { return m_volume; } bool VolumeObject::hasVolume() const { return m_hasVolume; } bool VolumeObject::isVolumeWritable() const { return m_volumeWritable; } QStringList VolumeObject::channels() const { return m_channels; } +QStringList VolumeObject::rawChannels() const +{ + return m_rawChannels; +} + QList VolumeObject::channelVolumes() const { QList ret; ret.reserve(m_volume.channels); for (int i = 0; i < m_volume.channels; ++i) { ret << m_volume.values[i]; } return ret; } } // QPulseAudio diff --git a/src/volumeobject.h b/src/volumeobject.h index 4208610..8cc9875 100644 --- a/src/volumeobject.h +++ b/src/volumeobject.h @@ -1,101 +1,115 @@ /* 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 . */ #ifndef VOLUMEOBJECT_H #define VOLUMEOBJECT_H #include #include "pulseobject.h" namespace QPulseAudio { class VolumeObject : public PulseObject { Q_OBJECT Q_PROPERTY(qint64 volume READ volume WRITE setVolume NOTIFY volumeChanged) Q_PROPERTY(bool muted READ isMuted WRITE setMuted NOTIFY mutedChanged) Q_PROPERTY(bool hasVolume READ hasVolume NOTIFY hasVolumeChanged) Q_PROPERTY(bool volumeWritable READ isVolumeWritable NOTIFY isVolumeWritableChanged) Q_PROPERTY(QStringList channels READ channels NOTIFY channelsChanged) + Q_PROPERTY(QStringList rawChannels READ rawChannels NOTIFY rawChannelsChanged) Q_PROPERTY(QList channelVolumes READ channelVolumes NOTIFY channelVolumesChanged) public: explicit VolumeObject(QObject *parent); ~VolumeObject() override; template void updateVolumeObject(PAInfo *info) { updatePulseObject(info); if (m_muted != info->mute) { m_muted = info->mute; Q_EMIT mutedChanged(); } if (!pa_cvolume_equal(&m_volume, &info->volume)) { m_volume = info->volume; Q_EMIT volumeChanged(); Q_EMIT channelVolumesChanged(); } QStringList infoChannels; infoChannels.reserve(info->channel_map.channels); for (int i = 0; i < info->channel_map.channels; ++i) { infoChannels << QString::fromUtf8(pa_channel_position_to_pretty_string(info->channel_map.map[i])); } if (m_channels != infoChannels) { m_channels = infoChannels; Q_EMIT channelsChanged(); } + + QStringList infoRawChannels; + infoRawChannels.reserve(info->channel_map.channels); + for (int i = 0; i < info->channel_map.channels; ++i) { + infoRawChannels << QString::fromUtf8(pa_channel_position_to_string(info->channel_map.map[i])); + } + if (m_rawChannels != infoRawChannels) { + m_rawChannels = infoRawChannels; + Q_EMIT rawChannelsChanged(); + } } qint64 volume() const; virtual void setVolume(qint64 volume) = 0; bool isMuted() const; virtual void setMuted(bool muted) = 0; bool hasVolume() const; bool isVolumeWritable() const; QStringList channels() const; + QStringList rawChannels() const; QList channelVolumes() const; Q_INVOKABLE virtual void setChannelVolume(int channel, qint64 volume) = 0; Q_SIGNALS: void volumeChanged(); void mutedChanged(); void hasVolumeChanged(); void isVolumeWritableChanged(); void channelsChanged(); + void rawChannelsChanged(); void channelVolumesChanged(); protected: pa_cvolume cvolume() const; pa_cvolume m_volume; bool m_muted; bool m_hasVolume; bool m_volumeWritable; QStringList m_channels; + QStringList m_rawChannels; }; } // QPulseAudio #endif // VOLUMEOBJECT_H