diff --git a/src/context.h b/src/context.h index 00e7d26..bc59c47 100644 --- a/src/context.h +++ b/src/context.h @@ -1,186 +1,206 @@ /* 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 CONTEXT_H #define CONTEXT_H #include #include #include #include #include #include #include #include "maps.h" #include "operation.h" namespace QPulseAudio { class Server; class Context : public QObject { Q_OBJECT public: explicit Context(QObject *parent = nullptr); ~Context() override; static Context *instance(); static const qint64 NormalVolume; static const qint64 MinimalVolume; static const qint64 MaximalVolume; void ref(); void unref(); bool isValid() { return m_context && m_mainloop; } const SinkMap &sinks() const { return m_sinks; } const SinkInputMap &sinkInputs() const { return m_sinkInputs; } const SourceMap &sources() const { return m_sources; } const SourceOutputMap &sourceOutputs() const { return m_sourceOutputs; } const ClientMap &clients() const { return m_clients; } const CardMap &cards() const { return m_cards; } const ModuleMap &modules() const { return m_modules; } const StreamRestoreMap &streamRestores() const { return m_streamRestores; } Server *server() const { return m_server; } QString newDefaultSink() const { return m_newDefaultSink; } QString newDefaultSource() const { return m_newDefaultSource; } void subscribeCallback(pa_context *context, pa_subscription_event_type_t type, uint32_t index); void contextStateCallback(pa_context *context); void sinkCallback(const pa_sink_info *info); void sinkInputCallback(const pa_sink_input_info *info); void sourceCallback(const pa_source_info *info); void sourceOutputCallback(const pa_source_output_info *info); void clientCallback(const pa_client_info *info); void cardCallback(const pa_card_info *info); void moduleCallback(const pa_module_info *info); void streamRestoreCallback(const pa_ext_stream_restore_info *info); void serverCallback(const pa_server_info *info); void setCardProfile(quint32 index, const QString &profile); void setDefaultSink(const QString &name); void setDefaultSource(const QString &name); void streamRestoreWrite(const pa_ext_stream_restore_info *info); template void setGenericVolume(quint32 index, int channel, qint64 newVolume, pa_cvolume cVolume, PAFunction pa_set_volume) { if (!m_context) { return; } newVolume = qBound(0, newVolume, PA_VOLUME_MAX); pa_cvolume newCVolume = cVolume; if (channel == -1) { // -1 all channels const qint64 diff = newVolume - pa_cvolume_max(&cVolume); for (int i = 0; i < newCVolume.channels; ++i) { newCVolume.values[i] = qBound(0, newCVolume.values[i] + diff, PA_VOLUME_MAX); } } else { Q_ASSERT(newCVolume.channels > channel); newCVolume.values[channel] = newVolume; } if (!PAOperation(pa_set_volume(m_context, index, &newCVolume, nullptr, nullptr))) { qCWarning(PLASMAPA) << "pa_set_volume failed"; return; } } + template + void setGenericVolumes(quint32 index, QVector channelVolumes, + pa_cvolume cVolume, PAFunction pa_set_volume) + { + if (!m_context) { + return; + } + Q_ASSERT(channelVolumes.count() == cVolume.channels); + + pa_cvolume newCVolume = cVolume; + for (int i = 0; i < channelVolumes.count(); ++i) { + newCVolume.values[i] = qBound(0, channelVolumes.at(i), PA_VOLUME_MAX); + } + + if (!PAOperation(pa_set_volume(m_context, index, &newCVolume, nullptr, nullptr))) { + qCWarning(PLASMAPA) << "pa_set_volume failed"; + return; + } + } + template void setGenericMute(quint32 index, bool mute, PAFunction pa_set_mute) { if (!m_context) { return; } if (!PAOperation(pa_set_mute(m_context, index, mute, nullptr, nullptr))) { qCWarning(PLASMAPA) << "pa_set_mute failed"; return; } } template void setGenericPort(quint32 index, const QString &portName, PAFunction pa_set_port) { if (!m_context) { return; } if (!PAOperation(pa_set_port(m_context, index, portName.toUtf8().constData(), nullptr, nullptr))) { qCWarning(PLASMAPA) << "pa_set_port failed"; return; } } template void setGenericDeviceForStream(quint32 streamIndex, quint32 deviceIndex, PAFunction pa_move_stream_to_device) { if (!m_context) { return; } if (!PAOperation(pa_move_stream_to_device(m_context, streamIndex, deviceIndex, nullptr, nullptr))) { qCWarning(PLASMAPA) << "pa_move_stream_to_device failed"; return; } } private: void connectToDaemon(); void reset(); // Don't forget to add things to reset(). SinkMap m_sinks; SinkInputMap m_sinkInputs; SourceMap m_sources; SourceOutputMap m_sourceOutputs; ClientMap m_clients; CardMap m_cards; ModuleMap m_modules; StreamRestoreMap m_streamRestores; Server *m_server; pa_context *m_context; pa_glib_mainloop *m_mainloop; QString m_newDefaultSink; QString m_newDefaultSource; int m_references; static Context* s_context; }; } // QPulseAudio #endif // CONTEXT_H diff --git a/src/kcm/package/contents/ui/DeviceListItem.qml b/src/kcm/package/contents/ui/DeviceListItem.qml index 9b25988..660f96e 100644 --- a/src/kcm/package/contents/ui/DeviceListItem.qml +++ b/src/kcm/package/contents/ui/DeviceListItem.qml @@ -1,121 +1,204 @@ /* Copyright 2014-2015 Harald Sitter Copyright 2019 Sefa Eyeoglu 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.Controls 2.0 import QtQuick.Layouts 1.0 import org.kde.kirigami 2.5 as Kirigami import org.kde.plasma.private.volume 0.1 import "../code/icon.js" as Icon ColumnLayout { id: delegate spacing: Kirigami.Units.smallSpacing * 2 width: parent.width property bool isPlayback: type.substring(0, 4) == "sink" readonly property var currentPort: Ports[ActivePortIndex] RowLayout { spacing: Kirigami.Units.smallSpacing Layout.minimumHeight: portbox.implicitHeight RadioButton { id: defaultButton // Maximum width of the button need to match the text. Empty area must not change the default device. Layout.maximumWidth: delegate.width - Layout.leftMargin - Layout.rightMargin - (portbox.visible ? Kirigami.Units.gridUnit + portLabel.implicitWidth + Kirigami.Units.smallSpacing + portbox.implicitWidth : 0) // Margins and spacing are set to center RadioButton with muteButton, and text with VolumeSlider. Layout.leftMargin: LayoutMirroring.enabled ? 0 : Math.round((muteButton.width - defaultButton.indicator.width) / 2) Layout.rightMargin: LayoutMirroring.enabled ? Math.round((muteButton.width - defaultButton.indicator.width) / 2) : 0 spacing: Kirigami.Units.smallSpacing + Math.round((muteButton.width - defaultButton.indicator.width) / 2) checked: Default visible: delegate.ListView.view.count > 1 onClicked: Default = true text: !currentPort ? Description : i18ndc("kcm_pulseaudio", "label of device items", "%1 (%2)", currentPort.description, Description) } Label { id: soloLabel Layout.maximumWidth: delegate.width - (portbox.visible ? Kirigami.Units.gridUnit + portLabel.implicitWidth + Kirigami.Units.smallSpacing + portbox.implicitWidth : 0) text: defaultButton.text visible: delegate.ListView.view.count <= 1 elide: Text.ElideRight } Item { Layout.fillWidth: true - visible: portbox.visible } Label { id: portLabel visible: portbox.visible text: i18nd("kcm_pulseaudio", "Port:") } ComboBox { id: portbox readonly property var ports: Ports visible: portbox.count > 1 && delegate.width - Kirigami.Units.gridUnit * 8 > implicitWidth onModelChanged: currentIndex = ActivePortIndex currentIndex: ActivePortIndex onActivated: ActivePortIndex = index onPortsChanged: { var items = []; for (var i = 0; i < ports.length; ++i) { var port = ports[i]; var text = port.description; if (port.availability == Port.Unavailable) { if (port.name == "analog-output-speaker" || port.name == "analog-input-microphone-internal") { text += i18ndc("kcm_pulseaudio", "Port is unavailable", " (unavailable)"); } else { text += i18ndc("kcm_pulseaudio", "Port is unplugged", " (unplugged)"); } } items.push(text); } model = items; } } + + Button { + id: unlockChannelsButton + Accessible.name: i18n("Adjust channels individually") + icon.name: "object-unlocked" + checkable: true + + onClicked: { + if (checked) { + return; + } + + // When unifying channels again, set all of them to the same value + // as to not keep an odd difference when adjusting them in unison + let volumes = ChannelVolumes; + for (let i = 0, count = RawChannels.length; i < count; ++i) { + volumes[i] = Volume; + } + // NOTE "ChannelVolumes = volumes" does not work as the + // AbstractModel does not have the magic JS Array to Qt List + // conversion stuff and only sees our Array as a QJSValue + PulseObject.channelVolumes = volumes; + } + + // Default to individual mode when a channel has a different volume + Component.onCompleted: { + checked = ChannelVolumes.some((volume) => { + return volume !== ChannelVolumes[0]; + }); + } + + ToolTip { + text: unlockChannelsButton.Accessible.name + } + } } RowLayout { spacing: Kirigami.Units.smallSpacing MuteButton { id: muteButton Layout.alignment: Qt.AlignTop Layout.topMargin: -Math.round((height - volumeSlider.height) / 2) muted: Muted onCheckedChanged: Muted = checked toolTipText: !currentPort ? Description : currentPort.description } - VolumeSlider { - id: volumeSlider - Layout.alignment: Qt.AlignTop + GridLayout { + columns: 2 + + VolumeSlider { + id: volumeSlider + Layout.columnSpan: 2 + Layout.column: 0 + Layout.row: 1 + Layout.alignment: Qt.AlignTop + visible: !unlockChannelsButton.checked + + value: Volume + onMoved: { + Volume = value; + Muted = (value === 0); + } + } + + Repeater { + model: unlockChannelsButton.checked ? Channels : null + + Label { + Layout.alignment: Qt.AlignTop | Qt.AlignRight + Layout.column: 0 + Layout.row: index + 1 + text: i18nc("Placeholder is channel name", "%1:", modelData) + } + } + + Repeater { + id: channelSliderRepeater + model: unlockChannelsButton.checked ? RawChannels : null + + VolumeSlider { + Layout.column: 1 + Layout.row: index + 1 + Layout.fillWidth: true + hundredPercentLabelVisible: index === channelSliderRepeater.count -1 + + value: ChannelVolumes[index] + onMoved: { + PulseObject.setChannelVolume(index, value); + + // volumes are updated async, so we'll just assume it worked here + let newChannelVolumes = ChannelVolumes; + newChannelVolumes[index] = value; + Muted = newChannelVolumes.every((volume) => { + return volume === 0; + }); + } + } + } } } } diff --git a/src/kcm/package/contents/ui/StreamListItem.qml b/src/kcm/package/contents/ui/StreamListItem.qml index 03bdc0b..d3d9694 100644 --- a/src/kcm/package/contents/ui/StreamListItem.qml +++ b/src/kcm/package/contents/ui/StreamListItem.qml @@ -1,106 +1,112 @@ /* Copyright 2014-2015 Harald Sitter Copyright 2019 Sefa Eyeoglu 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.Controls 2.0 import QtQuick.Layouts 1.0 import org.kde.kirigami 2.5 as Kirigami import org.kde.plasma.private.volume 0.1 ColumnLayout { id: delegate property alias deviceModel: deviceComboBox.model readonly property bool isEventStream: Name == "sink-input-by-media-role:event" property bool isPlayback: type.substring(0, 4) == "sink" width: parent.width RowLayout { Layout.fillWidth: true spacing: units.smallSpacing * 2 Kirigami.Icon { Layout.alignment: Qt.AlignHCenter Layout.preferredHeight: delegateColumn.height * 0.75 Layout.preferredWidth: Layout.preferredHeight source: IconName || "unknown" } ColumnLayout { id: delegateColumn Layout.fillWidth: true RowLayout { Label { id: inputText Layout.fillWidth: true text: { if (isEventStream) { return i18nd("kcm_pulseaudio", "Notification Sounds"); } else if (Client) { return i18ndc("kcm_pulseaudio", "label of stream items", "%1: %2", Client.name, Name); } else { return Name; } } elide: Text.ElideRight } DeviceComboBox { id: deviceComboBox Layout.leftMargin: units.smallSpacing Layout.rightMargin: units.smallSpacing Layout.preferredWidth: delegate.width / 3 visible: !isEventStream && count > 1 } } RowLayout { MuteButton { Layout.alignment: Qt.AlignTop Layout.topMargin: -Math.round((height - volumeSlider.height) / 2) muted: Muted onCheckedChanged: Muted = checked toolTipText: inputText.text } VolumeSlider { id: volumeSlider Layout.alignment: Qt.AlignTop + + value: Volume + onMoved: { + Volume = value; + Muted = (value === 0); + } } } } } ListItemSeperator { view: delegate.ListView.view Component.onCompleted: { if (isEventStream) { visible = Qt.binding(function() { return sinkInputView.count > 0; }); } } } } diff --git a/src/kcm/package/contents/ui/VolumeSlider.qml b/src/kcm/package/contents/ui/VolumeSlider.qml index 4439de1..c0530f3 100644 --- a/src/kcm/package/contents/ui/VolumeSlider.qml +++ b/src/kcm/package/contents/ui/VolumeSlider.qml @@ -1,115 +1,81 @@ /* Copyright 2014-2015 Harald Sitter Copyright 2019 Sefa Eyeoglu 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.4 import QtQuick.Layouts 1.0 import QtQuick.Controls 2.5 as QQC2 import org.kde.plasma.private.volume 0.1 RowLayout { - Layout.bottomMargin: hundredPercentLabel.height + id: sliderRow + + Layout.bottomMargin: hundredPercentLabelVisible ? hundredPercentLabel.height : 0 + + signal moved() + + property alias value: slider.value + + property bool hundredPercentLabelVisible: true QQC2.Slider { id: slider - // Helper properties to allow async slider updates. - // While we are sliding we must not react to value updates - // as otherwise we can easily end up in a loop where value - // changes trigger volume changes trigger value changes. - property int volume: Volume - property bool ignoreValueChange: true - Layout.fillWidth: true + + value: Volume from: PulseAudio.MinimalVolume to: PulseAudio.MaximalVolume // TODO: implement a way to hide tickmarks // stepSize: to / (PulseAudio.MaximalVolume / PulseAudio.NormalVolume * 100.0) visible: HasVolume enabled: VolumeWritable opacity: Muted ? 0.5 : 1 - - Component.onCompleted: { - ignoreValueChange = false; - } - - onVolumeChanged: { - var oldIgnoreValueChange = ignoreValueChange; - ignoreValueChange = true; - value = Volume; - ignoreValueChange = oldIgnoreValueChange; - } - - onValueChanged: { - if (!ignoreValueChange) { - Volume = value; - Muted = value == 0; - - if (!pressed) { - updateTimer.restart(); - } - } - } - - onPressedChanged: { - if (!pressed) { - // Make sure to sync the volume once the button was - // released. - // Otherwise it might be that the slider is at v10 - // whereas PA rejected the volume change and is - // still at v15 (e.g.). - updateTimer.restart(); - } - } + onMoved: sliderRow.moved() QQC2.Label { id: hundredPercentLabel readonly property real hundredPos: (slider.width / slider.to) * PulseAudio.NormalVolume z: slider.z - 1 x: (Qt.application.layoutDirection == Qt.RightToLeft ? slider.width - hundredPos : hundredPos) - width / 2 y: slider.height + visible: sliderRow.hundredPercentLabelVisible opacity: 0.5 font.pixelSize: slider.height / 2.2 text: i18nd("kcm_pulseaudio", "100%") } - - Timer { - id: updateTimer - interval: 200 - onTriggered: slider.value = Volume - } } QQC2.Label { id: percentText readonly property real value: PulseObject.volume > slider.maximumValue ? PulseObject.volume : slider.value Layout.alignment: Qt.AlignHCenter Layout.minimumWidth: percentMetrics.advanceWidth horizontalAlignment: Qt.AlignRight text: i18ndc("kcm_pulseaudio", "volume percentage", "%1%", Math.round(value / PulseAudio.NormalVolume * 100.0)) } TextMetrics { id: percentMetrics font: percentText.font text: i18ndc("kcm_pulseaudio", "only used for sizing, should be widest possible string", "100%") } } diff --git a/src/sink.cpp b/src/sink.cpp index 1cd0bd8..3e617da 100644 --- a/src/sink.cpp +++ b/src/sink.cpp @@ -1,128 +1,133 @@ /* 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 "sinkinput.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); } +void Sink::setChannelVolumes(const QVector &channelVolumes) +{ + context()->setGenericVolumes(index(), channelVolumes, 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); } } 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-") + 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, 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); } void Sink::switchStreams() { auto data = context()->sinkInputs().data(); std::for_each(data.begin(), data.end(), [this](SinkInput* paObj) { paObj->setDeviceIndex(m_index); }); } } // QPulseAudio diff --git a/src/sink.h b/src/sink.h index da08e42..0b85411 100644 --- a/src/sink.h +++ b/src/sink.h @@ -1,55 +1,56 @@ /* 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; + void setChannelVolumes(const QVector &channelVolumes) override; bool isDefault() const override; void setDefault(bool enable) override; void switchStreams() override; public Q_SLOTS: void testChannel(const QString &name); }; } // QPulseAudio #endif // SINK_H diff --git a/src/sinkinput.cpp b/src/sinkinput.cpp index 147e60b..6e80ebd 100644 --- a/src/sinkinput.cpp +++ b/src/sinkinput.cpp @@ -1,62 +1,67 @@ /* 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 "sinkinput.h" #include "context.h" namespace QPulseAudio { SinkInput::SinkInput(QObject *parent) : Stream(parent) { } void SinkInput::update(const pa_sink_input_info *info) { updateStream(info); if (m_deviceIndex != info->sink) { m_deviceIndex = info->sink; Q_EMIT deviceIndexChanged(); } } void SinkInput::setDeviceIndex(quint32 deviceIndex) { context()->setGenericDeviceForStream(index(), deviceIndex, &pa_context_move_sink_input_by_index); } void SinkInput::setVolume(qint64 volume) { context()->setGenericVolume(index(), -1, volume, cvolume(), &pa_context_set_sink_input_volume); } void SinkInput::setMuted(bool muted) { context()->setGenericMute(index(), muted, &pa_context_set_sink_input_mute); } void SinkInput::setChannelVolume(int channel, qint64 volume) { context()->setGenericVolume(index(), channel, volume, cvolume(), &pa_context_set_sink_input_volume); } +void SinkInput::setChannelVolumes(const QVector &channelVolumes) +{ + context()->setGenericVolumes(index(), channelVolumes, cvolume(), &pa_context_set_sink_input_volume); +} + } // QPulseAudio diff --git a/src/sinkinput.h b/src/sinkinput.h index 9db5572..11f9183 100644 --- a/src/sinkinput.h +++ b/src/sinkinput.h @@ -1,47 +1,48 @@ /* 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 SINKINPUT_H #define SINKINPUT_H #include "stream.h" namespace QPulseAudio { class SinkInput : public Stream { Q_OBJECT public: explicit SinkInput(QObject *parent); void update(const pa_sink_input_info *info); void setSinkIndex(quint32 sinkIndex); void setVolume(qint64 volume) override; void setMuted(bool muted) override; void setChannelVolume(int channel, qint64 volume) override; + void setChannelVolumes(const QVector &channelVolumes) override; void setDeviceIndex(quint32 deviceIndex) override; }; } // QPulseAudio #endif // SINKINPUT_H diff --git a/src/source.cpp b/src/source.cpp index b63718f..b98b763 100644 --- a/src/source.cpp +++ b/src/source.cpp @@ -1,86 +1,91 @@ /* 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 "source.h" #include "context.h" #include "server.h" #include "sourceoutput.h" namespace QPulseAudio { Source::Source(QObject *parent) : Device(parent) { connect(context()->server(), &Server::defaultSourceChanged, this, &Source::defaultChanged); } void Source::update(const pa_source_info *info) { updateDevice(info); } void Source::setVolume(qint64 volume) { context()->setGenericVolume(index(), -1, volume, cvolume(), &pa_context_set_source_volume_by_index); } void Source::setMuted(bool muted) { context()->setGenericMute(index(), muted, &pa_context_set_source_mute_by_index); } void Source::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_source_port_by_index); } void Source::setChannelVolume(int channel, qint64 volume) { context()->setGenericVolume(index(), channel, volume, cvolume(), &pa_context_set_source_volume_by_index); } +void Source::setChannelVolumes(const QVector &volumes) +{ + context()->setGenericVolumes(index(), volumes, cvolume(), &pa_context_set_source_volume_by_index); +} + bool Source::isDefault() const { return context()->server()->defaultSource() == this; } void Source::setDefault(bool enable) { if (!isDefault() && enable) { context()->server()->setDefaultSource(this); } } void Source::switchStreams() { auto data = context()->sourceOutputs().data(); std::for_each(data.begin(), data.end(), [this](SourceOutput* paObj) { paObj->setDeviceIndex(m_index); }); } } // QPulseAudio diff --git a/src/source.h b/src/source.h index 7be1c9f..e3f281c 100644 --- a/src/source.h +++ b/src/source.h @@ -1,49 +1,50 @@ /* 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 SOURCE_H #define SOURCE_H #include "device.h" namespace QPulseAudio { class Source : public Device { Q_OBJECT public: explicit Source(QObject *parent); void update(const pa_source_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; + void setChannelVolumes(const QVector &volumes) override; bool isDefault() const override; void setDefault(bool enable) override; void switchStreams() override; }; } // QPulseAudio #endif // SOURCE_H diff --git a/src/sourceoutput.cpp b/src/sourceoutput.cpp index ad0011f..570bffb 100644 --- a/src/sourceoutput.cpp +++ b/src/sourceoutput.cpp @@ -1,62 +1,67 @@ /* 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 "sourceoutput.h" #include "context.h" namespace QPulseAudio { SourceOutput::SourceOutput(QObject *parent) : Stream(parent) { } void SourceOutput::update(const pa_source_output_info *info) { updateStream(info); if (m_deviceIndex != info->source) { m_deviceIndex = info->source; Q_EMIT deviceIndexChanged(); } } void SourceOutput::setDeviceIndex(quint32 deviceIndex) { context()->setGenericDeviceForStream(index(), deviceIndex, &pa_context_move_source_output_by_index); } void SourceOutput::setVolume(qint64 volume) { context()->setGenericVolume(index(), -1, volume, cvolume(), &pa_context_set_source_output_volume); } void SourceOutput::setMuted(bool muted) { context()->setGenericMute(index(), muted, &pa_context_set_source_output_mute); } void SourceOutput::setChannelVolume(int channel, qint64 volume) { context()->setGenericVolume(index(), channel, volume, cvolume(), &pa_context_set_source_output_volume); } +void SourceOutput::setChannelVolumes(const QVector &channelVolumes) +{ + context()->setGenericVolumes(index(), channelVolumes, cvolume(), &pa_context_set_source_output_volume); +} + } // QPulseAudio diff --git a/src/sourceoutput.h b/src/sourceoutput.h index 94d55bc..9156bf9 100644 --- a/src/sourceoutput.h +++ b/src/sourceoutput.h @@ -1,45 +1,46 @@ /* 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 SOURCEOUTPUT_H #define SOURCEOUTPUT_H #include "stream.h" namespace QPulseAudio { class SourceOutput : public Stream { Q_OBJECT public: explicit SourceOutput(QObject *parent); void update(const pa_source_output_info *info); void setVolume(qint64 volume) override; void setMuted(bool muted) override; void setChannelVolume(int channel, qint64 volume) override; + void setChannelVolumes(const QVector &channelVolumes) override; void setDeviceIndex(quint32 deviceIndex) override; }; } // QPulseAudio #endif // SOURCEOUTPUT_H diff --git a/src/volumeobject.cpp b/src/volumeobject.cpp index c4055b3..9eb565b 100644 --- a/src/volumeobject.cpp +++ b/src/volumeobject.cpp @@ -1,84 +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 +QVector VolumeObject::channelVolumes() const { - QList ret; + QVector 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 8cc9875..bc26f79 100644 --- a/src/volumeobject.h +++ b/src/volumeobject.h @@ -1,115 +1,117 @@ /* 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) + Q_PROPERTY(QVector channelVolumes READ channelVolumes WRITE setChannelVolumes 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; + + QVector channelVolumes() const; + virtual void setChannelVolumes(const QVector &channelVolumes) = 0; 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