Allow moving audio streams between devices

This commit is contained in:
Victor Tran 2021-03-05 02:49:32 +11:00
parent bd60242dec
commit 169270e195
No known key found for this signature in database
GPG key ID: 1F0729FE016CDC3E
15 changed files with 282 additions and 5 deletions

View file

@ -37,6 +37,7 @@ Common::DevicePort Common::portForSink(PulseAudioQt::Sink* sink) {
}
}
if (port != nullptr) {
QString newPort;
if (port->name().contains("headphones", Qt::CaseInsensitive)) {

View file

@ -54,6 +54,8 @@ void MicChunk::updateSourceOutputs() {
// }
}
micClients.removeDuplicates();
if (micClients.isEmpty()) {
if (StateManager::barManager()->isChunkRegistered(this)) StateManager::barManager()->removeChunk(this);
} else {

View file

@ -20,6 +20,7 @@
#include "quickwidgetsink.h"
#include "ui_quickwidgetsink.h"
#include <QMenu>
#include <the-libs_global.h>
#include <Context>
#include <Server>
@ -27,6 +28,8 @@
struct QuickWidgetSinkPrivate {
PulseAudioQt::Sink* sink;
bool changingVolume = false;
QMenu* menu;
};
QuickWidgetSink::QuickWidgetSink(PulseAudioQt::Sink* sink, QWidget* parent) :
@ -36,12 +39,13 @@ QuickWidgetSink::QuickWidgetSink(PulseAudioQt::Sink* sink, QWidget* parent) :
d = new QuickWidgetSinkPrivate();
d->sink = sink;
ui->nameLabel->setText(this->fontMetrics().elidedText(sink->description(), Qt::ElideRight, SC_DPI(200)));
connect(PulseAudioQt::Context::instance()->server(), &PulseAudioQt::Server::defaultSinkChanged, this, &QuickWidgetSink::updateVisibility);
connect(PulseAudioQt::Context::instance()->server(), &PulseAudioQt::Server::defaultSinkChanged, this, &QuickWidgetSink::updateDefault);
updateDefault();
connect(sink, &PulseAudioQt::Sink::volumeChanged, this, &QuickWidgetSink::updateVolume);
connect(sink, &PulseAudioQt::Sink::propertiesChanged, this, &QuickWidgetSink::updateName);
updateVolume();
updateName();
connect(PulseAudioQt::Context::instance(), &PulseAudioQt::Context::sinkInputAdded, this, &QuickWidgetSink::sinkInputAdded);
connect(PulseAudioQt::Context::instance(), &PulseAudioQt::Context::sinkInputRemoved, this, &QuickWidgetSink::updateVisibility);
@ -50,6 +54,11 @@ QuickWidgetSink::QuickWidgetSink(PulseAudioQt::Sink* sink, QWidget* parent) :
this->setFixedWidth(SC_DPI(600));
ui->nameLabel->setFixedWidth(SC_DPI(200));
d->menu = new QMenu();
d->menu->addAction(ui->actionMake_Default);
d->menu->addAction(ui->actionMove_All_Applications);
ui->menuButton->setMenu(d->menu);
}
QuickWidgetSink::~QuickWidgetSink() {
@ -64,6 +73,11 @@ void QuickWidgetSink::updateVolume() {
ui->volumeSlider->setValue(volume);
}
void QuickWidgetSink::updateDefault() {
ui->actionMake_Default->setVisible(PulseAudioQt::Context::instance()->server()->defaultSink() != d->sink);
updateVisibility();
}
void QuickWidgetSink::on_volumeSlider_sliderPressed() {
d->changingVolume = true;
}
@ -93,8 +107,23 @@ void QuickWidgetSink::updateVisibility() {
this->setVisible(false);
}
void QuickWidgetSink::updateName() {
QString name = d->sink->properties().value("device.product.name").toString();
ui->nameLabel->setText(this->fontMetrics().elidedText(name, Qt::ElideRight, SC_DPI(200)));
}
void QuickWidgetSink::on_volumeSlider_valueChanged(int value) {
qint64 factor = PulseAudioQt::normalVolume() / 100;
qint64 newVolume = value * factor;
d->sink->setVolume(newVolume);
}
void QuickWidgetSink::on_actionMake_Default_triggered() {
PulseAudioQt::Context::instance()->server()->setDefaultSink(d->sink);
}
void QuickWidgetSink::on_actionMove_All_Applications_triggered() {
for (PulseAudioQt::SinkInput* sinkInput : PulseAudioQt::Context::instance()->sinkInputs()) {
sinkInput->setDeviceIndex(d->sink->index());
}
}

View file

@ -43,14 +43,19 @@ class QuickWidgetSink : public QWidget {
void on_volumeSlider_valueChanged(int value);
void on_actionMake_Default_triggered();
void on_actionMove_All_Applications_triggered();
private:
Ui::QuickWidgetSink* ui;
QuickWidgetSinkPrivate* d;
void sinkInputAdded(PulseAudioQt::SinkInput* sinkInput);
void updateVisibility();
void updateName();
void updateVolume();
void updateDefault();
};
#endif // QUICKWIDGETSINK_H

View file

@ -40,7 +40,42 @@
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="menuButton">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string notr="true"/>
</property>
<property name="icon">
<iconset theme="application-menu"/>
</property>
<property name="popupMode">
<enum>QToolButton::InstantPopup</enum>
</property>
</widget>
</item>
</layout>
<action name="actionMake_Default">
<property name="icon">
<iconset theme="default"/>
</property>
<property name="text">
<string>Make Default</string>
</property>
</action>
<action name="actionMove_All_Applications">
<property name="icon">
<iconset theme="edit-select-all"/>
</property>
<property name="text">
<string>Use for All Applications</string>
</property>
</action>
</widget>
<resources/>
<connections/>

View file

@ -22,6 +22,7 @@
#include <Context>
#include <the-libs_global.h>
#include <QMenu>
struct QuickWidgetSinkInputPrivate {
bool changingVolume = false;
@ -29,6 +30,11 @@ struct QuickWidgetSinkInputPrivate {
QString pid;
static QMultiMap<QString, QuickWidgetSinkInput*> sinkInputsByPid;
QMenu* menu;
QMenu* devicesMenu;
QMap<PulseAudioQt::Sink*, QAction*> sinkActions;
QActionGroup* devicesGroup;
};
QMultiMap<QString, QuickWidgetSinkInput*> QuickWidgetSinkInputPrivate::sinkInputsByPid = QMultiMap<QString, QuickWidgetSinkInput*>();
@ -48,6 +54,21 @@ QuickWidgetSinkInput::QuickWidgetSinkInput(PulseAudioQt::SinkInput* sinkInput, Q
updateVolume();
updateProperties();
d->devicesGroup = new QActionGroup(this);
d->devicesGroup->setExclusive(true);
d->devicesMenu = new QMenu();
d->devicesMenu->setTitle(tr("Play on"));
d->devicesMenu->setIcon(QIcon::fromTheme("audio-headphones"));
d->menu = new QMenu();
d->menu->addMenu(d->devicesMenu);
ui->menuButton->setMenu(d->menu);
connect(PulseAudioQt::Context::instance(), &PulseAudioQt::Context::sinkAdded, this, &QuickWidgetSinkInput::sinkAdded);
connect(PulseAudioQt::Context::instance(), &PulseAudioQt::Context::sinkRemoved, this, &QuickWidgetSinkInput::sinkRemoved);
for (PulseAudioQt::Sink* sink : PulseAudioQt::Context::instance()->sinks()) sinkAdded(sink);
this->setFixedWidth(SC_DPI(600));
ui->nameLabel->setFixedWidth(SC_DPI(200));
}
@ -102,3 +123,35 @@ void QuickWidgetSinkInput::on_volumeSlider_valueChanged(int value) {
for (QuickWidgetSinkInput* sinkInputWidget : d->sinkInputsByPid.values(d->pid)) sinkInputWidget->d->sinkInput->setVolume(newVolume);
}
void QuickWidgetSinkInput::sinkAdded(PulseAudioQt::Sink* sink) {
QAction* action = new QAction(this);
action->setCheckable(true);
connect(sink, &PulseAudioQt::Sink::propertiesChanged, this, [ = ] {
action->setText(sink->properties().value("device.product.name").toString());
});
action->setText(sink->properties().value("device.product.name").toString());
connect(d->sinkInput, &PulseAudioQt::SinkInput::deviceIndexChanged, this, [ = ] {
action->setChecked(sink->index() == d->sinkInput->deviceIndex());
});
action->setChecked(sink->index() == d->sinkInput->deviceIndex());
connect(action, &QAction::toggled, this, [ = ](bool checked) {
if (checked) {
for (QuickWidgetSinkInput* sinkInputWidget : d->sinkInputsByPid.values(d->pid)) sinkInputWidget->d->sinkInput->setDeviceIndex(sink->index());
}
});
d->devicesMenu->addAction(action);
d->devicesGroup->addAction(action);
d->sinkActions.insert(sink, action);
}
void QuickWidgetSinkInput::sinkRemoved(PulseAudioQt::Sink* sink) {
QAction* action = d->sinkActions.take(sink);
d->devicesMenu->removeAction(action);
d->devicesGroup->removeAction(action);
action->deleteLater();
}

View file

@ -21,6 +21,7 @@
#define QUICKWIDGETSINKINPUT_H
#include <QWidget>
#include <Sink>
#include <SinkInput>
namespace Ui {
@ -46,6 +47,9 @@ class QuickWidgetSinkInput : public QWidget {
Ui::QuickWidgetSinkInput* ui;
QuickWidgetSinkInputPrivate* d;
void sinkAdded(PulseAudioQt::Sink* sink);
void sinkRemoved(PulseAudioQt::Sink* sink);
void updateVolume();
void updateClient();
void updateProperties();

View file

@ -43,6 +43,25 @@
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="menuButton">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string notr="true">...</string>
</property>
<property name="icon">
<iconset theme="application-menu"/>
</property>
<property name="popupMode">
<enum>QToolButton::InstantPopup</enum>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>

View file

@ -53,4 +53,22 @@
</translation>
</message>
</context>
<context>
<name>QuickWidgetSink</name>
<message>
<source>Make Default</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Use for All Applications</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>QuickWidgetSinkInput</name>
<message>
<source>Play on</source>
<translation type="unfinished"></translation>
</message>
</context>
</TS>

View file

@ -55,11 +55,32 @@
<context>
<name>MicChunk</name>
<message numerus="yes">
<location filename="../micchunk.cpp" line="64"/>
<location filename="../micchunk.cpp" line="66"/>
<source>%n applications</source>
<translation type="unfinished">
<numerusform></numerusform>
</translation>
</message>
</context>
<context>
<name>QuickWidgetSink</name>
<message>
<location filename="../quickwidgetsink.ui" line="68"/>
<source>Make Default</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../quickwidgetsink.ui" line="76"/>
<source>Use for All Applications</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>QuickWidgetSinkInput</name>
<message>
<location filename="../quickwidgetsinkinput.cpp" line="61"/>
<source>Play on</source>
<translation type="unfinished"></translation>
</message>
</context>
</TS>

View file

@ -54,4 +54,22 @@
</translation>
</message>
</context>
<context>
<name>QuickWidgetSink</name>
<message>
<source>Make Default</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Use for All Applications</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>QuickWidgetSinkInput</name>
<message>
<source>Play on</source>
<translation type="unfinished"></translation>
</message>
</context>
</TS>

View file

@ -52,4 +52,22 @@
</translation>
</message>
</context>
<context>
<name>QuickWidgetSink</name>
<message>
<source>Make Default</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Use for All Applications</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>QuickWidgetSinkInput</name>
<message>
<source>Play on</source>
<translation type="unfinished"></translation>
</message>
</context>
</TS>

View file

@ -52,4 +52,22 @@
</translation>
</message>
</context>
<context>
<name>QuickWidgetSink</name>
<message>
<source>Make Default</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Use for All Applications</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>QuickWidgetSinkInput</name>
<message>
<source>Play on</source>
<translation type="unfinished"></translation>
</message>
</context>
</TS>

View file

@ -52,4 +52,22 @@
</translation>
</message>
</context>
<context>
<name>QuickWidgetSink</name>
<message>
<source>Make Default</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Use for All Applications</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>QuickWidgetSinkInput</name>
<message>
<source>Play on</source>
<translation type="unfinished"></translation>
</message>
</context>
</TS>

View file

@ -52,4 +52,22 @@
</translation>
</message>
</context>
<context>
<name>QuickWidgetSink</name>
<message>
<source>Make Default</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Use for All Applications</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>QuickWidgetSinkInput</name>
<message>
<source>Play on</source>
<translation type="unfinished"></translation>
</message>
</context>
</TS>