File indexing completed on 2024-12-01 05:05:12

0001 /*
0002     SPDX-FileCopyrightText: 2016 David Rosca <nowrep@gmail.com>
0003 
0004     SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
0005 */
0006 
0007 #include "streamrestore.h"
0008 #include "context.h"
0009 #include "debug.h"
0010 
0011 namespace QPulseAudio
0012 {
0013 StreamRestore::StreamRestore(quint32 index, const QVariantMap &properties, QObject *parent)
0014     : PulseObject(parent)
0015 {
0016     memset(&m_volume, 0, sizeof(m_volume));
0017     memset(&m_channelMap, 0, sizeof(m_channelMap));
0018 
0019     m_index = index;
0020     m_properties = properties;
0021 }
0022 
0023 void StreamRestore::update(const pa_ext_stream_restore_info *info)
0024 {
0025     m_cache.valid = false;
0026     const QString infoName = QString::fromUtf8(info->name);
0027     if (m_name != infoName) {
0028         m_name = infoName;
0029         Q_EMIT nameChanged();
0030     }
0031     const QString infoDevice = QString::fromUtf8(info->device);
0032     if (m_device != infoDevice) {
0033         m_device = infoDevice;
0034         Q_EMIT deviceChanged();
0035     }
0036     if (m_muted != info->mute) {
0037         m_muted = info->mute;
0038         Q_EMIT mutedChanged();
0039     }
0040     if (!pa_cvolume_equal(&m_volume, &info->volume)) {
0041         m_volume = info->volume;
0042         Q_EMIT volumeChanged();
0043         Q_EMIT channelVolumesChanged();
0044     }
0045     if (!pa_channel_map_equal(&m_channelMap, &info->channel_map)) {
0046         m_channels.clear();
0047         m_channels.reserve(info->channel_map.channels);
0048         for (int i = 0; i < info->channel_map.channels; ++i) {
0049             m_channels << QString::fromUtf8(pa_channel_position_to_pretty_string(info->channel_map.map[i]));
0050         }
0051         m_channelMap = info->channel_map;
0052         Q_EMIT channelsChanged();
0053     }
0054 }
0055 
0056 QString StreamRestore::name() const
0057 {
0058     return m_name;
0059 }
0060 
0061 QString StreamRestore::device() const
0062 {
0063     return m_device;
0064 }
0065 
0066 void StreamRestore::setDevice(const QString &device)
0067 {
0068     if (m_cache.valid) {
0069         if (m_cache.device != device) {
0070             writeChanges(m_cache.volume, m_cache.muted, device);
0071         }
0072     } else {
0073         if (m_device != device) {
0074             writeChanges(m_volume, m_muted, device);
0075         }
0076     }
0077 }
0078 
0079 qint64 StreamRestore::volume() const
0080 {
0081     // FIXME: workaround for pipewire not supporting stream restore subscription
0082     // revert when fix is released
0083     // https://gitlab.freedesktop.org/pipewire/pipewire/-/issues/3805
0084     if (m_cache.valid) {
0085         return m_cache.volume.values[0];
0086     } else {
0087         return m_volume.values[0];
0088     }
0089 }
0090 
0091 void StreamRestore::setVolume(qint64 volume)
0092 {
0093     pa_cvolume vol = m_cache.valid ? m_cache.volume : m_volume;
0094 
0095     // If no channel exists force one. We need one to be able to control the volume
0096     // See https://bugs.kde.org/show_bug.cgi?id=407397
0097     if (vol.channels == 0) {
0098         vol.channels = 1;
0099     }
0100 
0101     for (int i = 0; i < vol.channels; ++i) {
0102         vol.values[i] = volume;
0103     }
0104 
0105     if (m_cache.valid) {
0106         writeChanges(vol, m_cache.muted, m_cache.device);
0107     } else {
0108         writeChanges(vol, m_muted, m_device);
0109     }
0110 }
0111 
0112 bool StreamRestore::isMuted() const
0113 {
0114     // FIXME: workaround for pipewire not supporting stream restore subscription
0115     // revert when fix is released
0116     // https://gitlab.freedesktop.org/pipewire/pipewire/-/issues/3805
0117     if (m_cache.valid) {
0118         return m_cache.muted;
0119     } else {
0120         return m_muted;
0121     }
0122 }
0123 
0124 void StreamRestore::setMuted(bool muted)
0125 {
0126     if (m_cache.valid) {
0127         if (m_cache.muted != muted) {
0128             writeChanges(m_cache.volume, muted, m_cache.device);
0129         }
0130     } else {
0131         if (m_muted != muted) {
0132             writeChanges(m_volume, muted, m_device);
0133         }
0134     }
0135 }
0136 
0137 bool StreamRestore::hasVolume() const
0138 {
0139     return true;
0140 }
0141 
0142 bool StreamRestore::isVolumeWritable() const
0143 {
0144     return true;
0145 }
0146 
0147 QStringList StreamRestore::channels() const
0148 {
0149     return m_channels;
0150 }
0151 
0152 QList<qreal> StreamRestore::channelVolumes() const
0153 {
0154     QList<qreal> ret;
0155     ret.reserve(m_volume.channels);
0156     for (int i = 0; i < m_volume.channels; ++i) {
0157         ret << m_volume.values[i];
0158     }
0159     return ret;
0160 }
0161 
0162 void StreamRestore::setChannelVolume(int channel, qint64 volume)
0163 {
0164     Q_ASSERT(channel >= 0 && channel < m_volume.channels);
0165     pa_cvolume vol = m_cache.valid ? m_cache.volume : m_volume;
0166     vol.values[channel] = volume;
0167 
0168     if (m_cache.valid) {
0169         writeChanges(vol, m_cache.muted, m_cache.device);
0170     } else {
0171         writeChanges(vol, m_muted, m_device);
0172     }
0173 }
0174 
0175 quint32 StreamRestore::deviceIndex() const
0176 {
0177     return PA_INVALID_INDEX;
0178 }
0179 
0180 void StreamRestore::setDeviceIndex(quint32 deviceIndex)
0181 {
0182     Q_UNUSED(deviceIndex);
0183     qCWarning(PLASMAPA) << "Not implemented";
0184 }
0185 
0186 void StreamRestore::writeChanges(const pa_cvolume &volume, bool muted, const QString &device)
0187 {
0188     const QByteArray nameData = m_name.toUtf8();
0189     const QByteArray deviceData = device.toUtf8();
0190 
0191     pa_ext_stream_restore_info info;
0192     info.name = nameData.constData();
0193     info.channel_map = m_channelMap;
0194     info.volume = volume;
0195     info.device = deviceData.isEmpty() ? nullptr : deviceData.constData();
0196     info.mute = muted;
0197 
0198     // If no channel exists force one. We need one to be able to control the volume
0199     // See https://bugs.kde.org/show_bug.cgi?id=407397
0200     if (info.channel_map.channels == 0) {
0201         info.channel_map.channels = 1;
0202         info.channel_map.map[0] = PA_CHANNEL_POSITION_MONO;
0203     }
0204 
0205     m_cache.valid = true;
0206     m_cache.volume = volume;
0207     m_cache.muted = muted;
0208     m_cache.device = device;
0209 
0210     context()->streamRestoreWrite(&info);
0211 }
0212 
0213 } // QPulseAudio