diff --git a/src/kcm/package/contents/ui/Advanced.qml b/src/kcm/package/contents/ui/Advanced.qml --- a/src/kcm/package/contents/ui/Advanced.qml +++ b/src/kcm/package/contents/ui/Advanced.qml @@ -23,6 +23,8 @@ import QtQuick.Controls 1.3 import org.kde.plasma.private.volume 0.1 +import org.kde.kcoreaddons 1.0 as KCoreAddons + ScrollView { id: scrollView @@ -90,5 +92,131 @@ text: i18n("Requires 'module-gconf' PulseAudio module") visible: moduleManager.loadedModules.indexOf("module-gconf") == -1 } + + Header { + Layout.fillWidth: true + text: i18n("Speaker Placement and Testing") + } + + ListView { + id: speakersview + Layout.fillWidth: true + Layout.preferredHeight: contentHeight + Layout.margins: units.gridUnit / 2 + interactive: false + spacing: units.smallSpacing * 2 + model: SinkModel { + id: sinkmodel + } + + delegate: + + Grid { + columns: 3 + spacing: 5 + width: parent.width + id: grid + + Item { + width: grid.width/3 + height: 50 + + Button{ + text: "Front Left" + anchors.centerIn: parent + visible: Channels.indexOf("Front Left") > -1 + onClicked: PulseObject.testChannel("Front Left") + } + } + Item{ + + width: grid.width/3 + height: 50 + + Button{ + text: "Front Center" + anchors.centerIn: parent + visible: Channels.indexOf("Front Center") > -1 + onClicked: PulseObject.testChannel("Front Center") + + } + } + Item{ + width: grid.width/3 + height: 50 + + Button{ + text: "Front Right" + anchors.centerIn: parent + onClicked: PulseObject.testChannel("Front Right") + } + } + Item{ + width: grid.width/3 + height: 50 + + Button{ + text: "Side Left" + anchors.centerIn: parent + visible: Channels.indexOf("Side Left") > -1 + onClicked: 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: "Side Right" + anchors.centerIn: parent + visible: Channels.indexOf("Side Right") > -1 + onClicked: PulseObject.testChannel("Side Right") + } + } + Item{ + width: grid.width/3 + height: 50 + Button{ + text: "Rear Left" + anchors.centerIn: parent + visible: Channels.indexOf("Rear Left") > -1 + onClicked: PulseObject.testChannel("Rear Left") + } + } + Item{ + width: grid.width/3 + height: 50 + Button{ + text: "Subwoofer" + anchors.centerIn: parent + visible: Channels.indexOf("Subwoofer") > -1 + onClicked: PulseObject.testChannel("LFE") + } + } + Item{ + width: grid.width/3 + height: 50 + Button{ + text: "Rear Right" + anchors.centerIn: parent + visible: Channels.indexOf("Rear Right") > -1 + onClicked: PulseObject.testChannel("Rear Right") + } + } + } + } } } diff --git a/src/sink.h b/src/sink.h --- a/src/sink.h +++ b/src/sink.h @@ -22,6 +22,8 @@ #define SINK_H #include "device.h" +#include +#include namespace QPulseAudio { @@ -31,6 +33,7 @@ Q_OBJECT public: Sink(QObject *parent); + virtual ~Sink(); void update(const pa_sink_info *info); void setVolume(qint64 volume) Q_DECL_OVERRIDE; @@ -40,6 +43,17 @@ bool isDefault() const Q_DECL_OVERRIDE; void setDefault(bool enable) Q_DECL_OVERRIDE; + +public 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); + + ca_context *m_canberra; + }; } // QPulseAudio diff --git a/src/sink.cpp b/src/sink.cpp --- a/src/sink.cpp +++ b/src/sink.cpp @@ -22,16 +22,26 @@ #include "context.h" #include "server.h" +#include namespace QPulseAudio { Sink::Sink(QObject *parent) : Device(parent) { connect(context()->server(), &Server::defaultSinkChanged, this, &Sink::defaultChanged); + + ca_context_create(&m_canberra); } +Sink::~Sink() +{ + if (m_canberra) { + ca_context_destroy(m_canberra); + } + +} void Sink::update(const pa_sink_info *info) { updateDevice(info); @@ -74,4 +84,123 @@ } } +pa_channel_position_t Sink::channelNameToPosition(const QString& name) +{ + if (name == QStringLiteral("Front Left")) + return PA_CHANNEL_POSITION_FRONT_LEFT; + + if (name == QStringLiteral("Front Center")) + return PA_CHANNEL_POSITION_FRONT_CENTER; + + if (name == QStringLiteral("Front Right")) + return PA_CHANNEL_POSITION_FRONT_RIGHT; + + if (name == QStringLiteral("Side Left")) + return PA_CHANNEL_POSITION_SIDE_LEFT; + + if (name == QStringLiteral("Side Right")) + return PA_CHANNEL_POSITION_SIDE_RIGHT; + + if (name == QStringLiteral("Rear Left")) + return PA_CHANNEL_POSITION_REAR_LEFT; + + if (name == QStringLiteral("Rear Right")) + return PA_CHANNEL_POSITION_REAR_RIGHT; + + if (name == QStringLiteral("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) +{ + if (!m_canberra) + return; + + char dev[64]; + snprintf(dev, sizeof(dev), "%lu", (unsigned long) m_index); + ca_context_change_device(m_canberra, dev); + + QString sound_name = QStringLiteral("audio-channel-") + positionAsString(channelNameToPosition(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_ENABLE, "1"); + + ca_proplist_sets(proplist, CA_PROP_EVENT_ID, sound_name.toLatin1().data()); + if (ca_context_play_full(m_canberra, 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(m_canberra, 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(m_canberra, 0, proplist, nullptr, NULL); + } + } + + ca_context_change_device(m_canberra, 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