diff options
-rw-r--r-- | src/CMakeLists.txt | 3 | ||||
-rw-r--r-- | src/icons/checkmark.svg | 20 | ||||
-rw-r--r-- | src/main.cpp | 25 | ||||
-rw-r--r-- | src/mainwindow.cpp | 12 | ||||
-rw-r--r-- | src/mainwindow.ui | 86 | ||||
-rw-r--r-- | src/setdefault.cpp | 151 | ||||
-rw-r--r-- | src/setdefault.h | 8 | ||||
-rw-r--r-- | src/slxoutput.cpp | 4 |
8 files changed, 276 insertions, 33 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 709a77e..78b49b4 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -12,12 +12,14 @@ find_package(Qt5Core ${QT_MINIMUM_VERSION} REQUIRED) set(pavucontrol-qt_HDRS main.h mainwindow.h + setdefault.h slxoutput.h ) set(pavucontrol-qt_SRCS main.cpp mainwindow.cpp + setdefault.cpp slxoutput.cpp ) @@ -31,6 +33,7 @@ qt5_add_resources(pavucontrol-qt_RCS resources.qrc) add_executable(pavucontrol-qt ${pavucontrol-qt_SRCS} + ${pavucontrol-qt_HDRS} ${pavucontrol-qt_RCS} ${pavucontrol-qt_UI_HEADERS} ) diff --git a/src/icons/checkmark.svg b/src/icons/checkmark.svg index 214db50..fad3bec 100644 --- a/src/icons/checkmark.svg +++ b/src/icons/checkmark.svg @@ -1,21 +1,19 @@ <?xml version="1.0" encoding="UTF-8" standalone="no"?> -<!-- Created with Inkscape (http://www.inkscape.org/) --> - <svg - width="200mm" - height="200mm" - viewBox="0 0 200 200" + viewBox="0 0 24 24" version="1.1" id="svg5" + width="24" + height="24" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg"> - <defs - id="defs2" /> <g - id="layer1"> + id="surface1" + transform="matrix(0.83191502,0,0,0.83175649,-1.3106403,-1.587701)" + style="fill:#000000;fill-opacity:1;stroke:#ffffff;stroke-width:0.999583;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"> <path - style="fill:none;fill-rule:evenodd;stroke:#800000;stroke-width:13;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - d="M 11.632616,103.25917 C 62.406956,156.18139 50.284158,255.87375 124.78438,132.14843 199.28477,8.4228577 183.63194,8.1400739 183.63194,8.1400739" - id="path42" /> + d="M 28.28125,6.28125 11,23.5625 3.71875,16.28125 l -1.4375,1.4375 8,8 0.71875,0.6875 0.71875,-0.6875 18,-18 z" + id="path2" + style="fill:#000000;fill-opacity:1;stroke:#ffffff;stroke-width:0.999583;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> </g> </svg> diff --git a/src/main.cpp b/src/main.cpp index cc149a4..b1736cc 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,6 +1,7 @@ #include "main.h" #include "mainwindow.h" #include "slxoutput.h" +#include "setdefault.h" #include <cstdio> #include <cstring> @@ -19,6 +20,7 @@ #include <QList> #include <QSet> #include <QTimer> +#include <QCommandLineParser> // libkf5pulseaudioqt-dev // Public @@ -76,6 +78,7 @@ static void addDevicePortToWindow(PulseAudioQt::Card *card, PulseAudioQt::Device volume = 65535; device->setVolume(volume); } + device->setMuted(false); } //knownCardPortCombos.insert(card->name() + ":" + port->name()); _mainWindow->getDevice(card, device, port, isOutput)->updateDeviceAndPort(defaultSinkAndPort, device->isMuted(), @@ -430,10 +433,26 @@ void enableSource(const QString &source, const QString &port) int main(int argc, char **argv) { QApplication a(argc, argv); - printf("Muh\n"); auto *i = PulseAudioQt::Context::instance(); - printf("Pa is %p = %d\n", i, (int)i->isValid()); - printf("Cards: %d\n", (int)i->cards().size()); + + a.setOrganizationName(QStringLiteral("slxmix")); + a.setApplicationVersion(QLatin1String("1.0.0.0.0")); + QCommandLineParser parser; + parser.setApplicationDescription(QObject::tr("SLXmix Volume Control")); + parser.addHelpOption(); + QCommandLineOption selectOption(QStringList() << QStringLiteral("output") << QStringLiteral("o"), + QObject::tr("Select a specific output configuration and quit. Will select the Profile/Sink/Port combo that best matches the list of given keywords, e.g. 'HDMI 1 5.1'"), + QStringLiteral("keywords")); + parser.addOption(selectOption); + parser.process(a); + // Select default output and exit + if (parser.isSet(selectOption)) { + QString what = parser.value(selectOption); + QTimer::singleShot(100, [what]() { + setDefaultOutput(what); + }); + return a.exec(); + } // There's no signal, or no way to check if we're connected to PA, so // just wait a bit and hope for the best. diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 0fc6341..83b999e 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -10,8 +10,12 @@ MainWindow::MainWindow() : QDialog() { setupUi(this); setWindowFlags(Qt::WindowStaysOnTopHint | windowFlags()); - outputTab->setLayout(new QVBoxLayout(outputTab)); - inputTab->setLayout(new QVBoxLayout(inputTab)); + // Make heading 20% larger than system default + QFont f = inputLabel->font(); + f.setPointSize(f.pointSize() * 6 / 5); + inputLabel->setFont(f); + outputLabel->setFont(f); + cardLabel->setFont(f); } MainWindow::~MainWindow() @@ -86,9 +90,9 @@ void MainWindow::insertItemWidget(SlxOutput* w, bool isDevice) if (w->type() == SlxOutput::CardProfileItem) { container = cards; } else if (w->type() == SlxOutput::SinkPortItem) { - container = qobject_cast<QBoxLayout*>(outputTab->layout()); + container = outputListLayout; } else { - container = qobject_cast<QBoxLayout*>(inputTab->layout()); + container = inputListLayout; } for (idx = 0; idx < container->count(); ++idx) { auto *l = container->itemAt(idx); diff --git a/src/mainwindow.ui b/src/mainwindow.ui index af88c18..03e1aae 100644 --- a/src/mainwindow.ui +++ b/src/mainwindow.ui @@ -15,20 +15,80 @@ </property> <layout class="QVBoxLayout" name="verticalLayout"> <item> - <widget class="QTabWidget" name="inOutTab"> - <property name="currentIndex"> - <number>0</number> + <layout class="QHBoxLayout" name="horizontalLayout"> + <item> + <layout class="QVBoxLayout" name="verticalLayout_3"> + <item> + <widget class="QLabel" name="outputLabel"> + <property name="font"> + <font> + <weight>75</weight> + <bold>true</bold> + </font> + </property> + <property name="text"> + <string>Output</string> + </property> + </widget> + </item> + <item> + <layout class="QVBoxLayout" name="outputListLayout"/> + </item> + <item> + <spacer name="verticalSpacer_3"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>40</height> + </size> + </property> + </spacer> + </item> + </layout> + </item> + <item> + <layout class="QVBoxLayout" name="verticalLayout_2"> + <item> + <widget class="QLabel" name="inputLabel"> + <property name="font"> + <font> + <weight>75</weight> + <bold>true</bold> + </font> + </property> + <property name="text"> + <string>Input</string> + </property> + </widget> + </item> + <item> + <layout class="QVBoxLayout" name="inputListLayout"/> + </item> + <item> + <spacer name="verticalSpacer_2"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>40</height> + </size> + </property> + </spacer> + </item> + </layout> + </item> + </layout> + </item> + <item> + <widget class="QLabel" name="cardLabel"> + <property name="text"> + <string>Card/Profile</string> </property> - <widget class="QWidget" name="outputTab"> - <attribute name="title"> - <string>Output</string> - </attribute> - </widget> - <widget class="QWidget" name="inputTab"> - <attribute name="title"> - <string>Input</string> - </attribute> - </widget> </widget> </item> <item> diff --git a/src/setdefault.cpp b/src/setdefault.cpp new file mode 100644 index 0000000..21b368e --- /dev/null +++ b/src/setdefault.cpp @@ -0,0 +1,151 @@ +#include "setdefault.h" + +#include <PulseAudioQt/Context> +#include <PulseAudioQt/Server> +#include <PulseAudioQt/Profile> +#include <PulseAudioQt/PulseObject> +#include <PulseAudioQt/SinkInput> +#include <PulseAudioQt/Sink> +#include <PulseAudioQt/Card> +#include <PulseAudioQt/Port> + +#include <QRegularExpression> +#include <QDebug> +#include <QCoreApplication> +#include <QTimer> + +static const QRegularExpression _splitter("[() \t-]"); + +static int countMatches(const QStringList &find, const QString &haystack); + +static void setDefaultSink(const QString &bestCardId, const QStringList &words); + +void setDefaultOutput(const QString &name) +{ + auto *ctx = PulseAudioQt::Context::instance(); + const QStringList words = name.toLower().split(_splitter, QString::SkipEmptyParts); + int bestNum = -1000; + int bestProfileIdx = -1; + PulseAudioQt::Card *bestCard = nullptr; + for (auto *card : ctx->cards()) { + int i = -1; + for (auto *profile : card->profiles()) { + ++i; + if (profile->sinks() == 0) + continue; + int matches = countMatches(words, profile->description()); + // Prefer Duplex + if (profile->description().contains(QLatin1String("Duplex"))) { + matches += 2; + } + // Penaltize unavailable profiles + if (profile->availability() != PulseAudioQt::Profile::Available) { + matches -= 11; + } + qDebug() << matches << "Profile" << profile->description(); + if (matches > bestNum) { + bestNum = matches; + bestCard = card; + bestProfileIdx = i; + } + } + } + if (bestCard == nullptr) { + qDebug() << "No match found"; + qApp->quit(); + return; + } + QString bestCardId = bestCard->name(); + if (bestCard->activeProfileIndex() == bestProfileIdx) { + setDefaultSink(bestCardId, words); + return; + } + QObject::connect(bestCard, &PulseAudioQt::Card::activeProfileIndexChanged, [bestCardId, words]() { + QTimer::singleShot(10, [bestCardId, words]() { + setDefaultSink(bestCardId, words); + }); + }); + qDebug() << "Switching card's profile..."; + bestCard->setActiveProfileIndex(bestProfileIdx); + // Wait for switch - add safety timeout + QTimer::singleShot(1000, []() { + qDebug() << "WARNING - timeout reached, exiting..."; + qApp->exit(1); + }); +} + +static void setDefaultSink(const QString &bestCardId, const QStringList &words) +{ + auto *ctx = PulseAudioQt::Context::instance(); + int idx = -1; + int bestCardIdx = -1; + for (auto *card : ctx->cards()) { + idx++; + if (card->name() == bestCardId) { + bestCardIdx = idx; + break; + } + } + int bestNum = -1000; + int bestPortIdx = -1; + PulseAudioQt::Sink *bestSink = nullptr; + for (auto *sink : ctx->sinks()) { + if (bestCardIdx != -1 && sink->cardIndex() != bestCardIdx) + continue; // Not sink of card + int sinkMatchVal = countMatches(words, sink->description()); + int pi = -1; + for (auto *port : sink->ports()) { + pi++; + int portMatchVal = sinkMatchVal + countMatches(words, port->description()); + qDebug() << portMatchVal << "SinkPort" << sink->description() << port->description(); + if (portMatchVal > bestNum) { + bestNum = portMatchVal; + bestSink = sink; + bestPortIdx = pi; + } + + } + } + if (bestSink == nullptr) { + qDebug() << "No match found"; + qApp->exit(1); + } + if (bestPortIdx != -1 && bestSink->activePortIndex() != bestPortIdx) { + qDebug() << "Selecting port"; + bestSink->setActivePortIndex(bestPortIdx); + } + QTimer::singleShot(10, [bestSink]() { + qDebug() << "Unmuting sink and selecting as default:" << bestSink->description(); + bestSink->setDefault(true); + bestSink->setMuted(false); + bestSink->setVolume(65535); + }); + QTimer::singleShot(20, []() { + qDebug() << "Done"; + qApp->quit(); + }); +} + +static int countMatches(const QStringList &find, const QString &haystack) +{ + int ret = 0; + QStringList hayList = haystack.toLower().split(_splitter, QString::SkipEmptyParts); + qDebug() << "Comparing" << hayList; + // Look for all the words we want to find + for (const QString &word : find) { + if (hayList.contains(word)) { + ret += 10; + } + } + if (ret == 0) { + // If not a single word matched, bail out + return -1000; + } + // Penaltize additonal words in haystack that we don't want to find + for (const QString &word : hayList) { + if (!find.contains(word)) { + ret -= 1; + } + } + return ret; +} diff --git a/src/setdefault.h b/src/setdefault.h new file mode 100644 index 0000000..123ad20 --- /dev/null +++ b/src/setdefault.h @@ -0,0 +1,8 @@ +#ifndef _SET_DEFAULT_H_ +#define _SET_DEFAULT_H_ + +#include <QString> + +void setDefaultOutput(const QString &name); + +#endif diff --git a/src/slxoutput.cpp b/src/slxoutput.cpp index b1da26f..637ffc3 100644 --- a/src/slxoutput.cpp +++ b/src/slxoutput.cpp @@ -15,9 +15,9 @@ SlxOutput::SlxOutput(ItemType type, const QString &id, QWidget(nullptr) { setupUi(this); - // Make heading 25% larger than system default + // Make heading 20% larger than system default QFont f = headingLabel->font(); - f.setPointSize(f.pointSize() * 5 / 4); + f.setPointSize(f.pointSize() * 6 / 5); f.setBold(_type == SinkPortItem || _type == SourcePortItem); headingLabel->setFont(f); // |