summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSimon Rettberg2021-11-22 15:56:24 +0100
committerSimon Rettberg2021-11-22 15:56:24 +0100
commitbd13c3716248570f14982b8e03a4adf4926e0c24 (patch)
tree0ef0dc9bad8de36f53c6d1cc9d5657c6576f1576
parentTry to group the output configurations in the last tab (diff)
downloadpavucontrol-slx-bd13c3716248570f14982b8e03a4adf4926e0c24.tar.gz
pavucontrol-slx-bd13c3716248570f14982b8e03a4adf4926e0c24.tar.xz
pavucontrol-slx-bd13c3716248570f14982b8e03a4adf4926e0c24.zip
Add -o option to select output profilev29r1
-rw-r--r--src/CMakeLists.txt2
-rw-r--r--src/cardwidget.cc3
-rw-r--r--src/cardwidget.h26
-rw-r--r--src/helper.cc75
-rw-r--r--src/helper.h61
-rw-r--r--src/mainwindow.cc72
-rw-r--r--src/pavucontrol.cc172
7 files changed, 285 insertions, 126 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 4c95f63..7e4effe 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -17,6 +17,7 @@ set(pavucontrol-qt_HDRS
sourcewidget.h
streamwidget.h
elidinglabel.h
+ helper.h
)
set(pavucontrol-qt_SRCS
@@ -33,6 +34,7 @@ set(pavucontrol-qt_SRCS
sourcewidget.cc
streamwidget.cc
elidinglabel.cc
+ helper.cc
)
set(pavucontrol-qt_UI
diff --git a/src/cardwidget.cc b/src/cardwidget.cc
index 3b4c8b3..f401691 100644
--- a/src/cardwidget.cc
+++ b/src/cardwidget.cc
@@ -141,13 +141,11 @@ void ProfileGroup::addEntry(const char *id, const char *name) {
while (*pos != '\0') {
if (!paren && isspace(*pos)) {
if (pos > tokenStart) {
- qDebug() << tokenStart;
pe->tokens.append(QString::fromUtf8(tokenStart, pos - tokenStart));
}
tokenStart = pos + 1;
}
if (paren && *pos == ')') {
- qDebug() << tokenStart;
pe->tokens.append(QString::fromUtf8(tokenStart, pos - tokenStart + 1));
paren = false;
tokenStart = pos + 1;
@@ -158,7 +156,6 @@ void ProfileGroup::addEntry(const char *id, const char *name) {
pos++;
}
if (pos > tokenStart) {
- qDebug() << tokenStart;
pe->tokens.append(QString::fromUtf8(tokenStart, pos - tokenStart));
}
pe->id = id;
diff --git a/src/cardwidget.h b/src/cardwidget.h
index 7bd87d2..92d544d 100644
--- a/src/cardwidget.h
+++ b/src/cardwidget.h
@@ -22,37 +22,13 @@
#define cardwidget_h
#include "pavucontrol.h"
+#include "helper.h"
#include "ui_cardwidget.h"
#include <QWidget>
#include <QMap>
#include <QString>
#include <QList>
-struct ProfileEntry {
- QByteArray id;
- QStringList tokens;
- QString getName() const;
-};
-
-struct ProfileGroup {
- QString name;
- QList<ProfileEntry> entries;
- QString getProfileName();
- void addEntry(const char* id, const char* name);
- bool containsProfile(const QByteArray &pro) const;
-};
-
-class PortInfo {
-public:
- QByteArray name;
- QByteArray description;
- uint32_t priority;
- int available;
- int direction;
- int64_t latency_offset;
- std::vector<QByteArray> profiles;
-};
-
class CardWidget : public QWidget, public Ui::CardWidget {
Q_OBJECT
public:
diff --git a/src/helper.cc b/src/helper.cc
new file mode 100644
index 0000000..ffadbe9
--- /dev/null
+++ b/src/helper.cc
@@ -0,0 +1,75 @@
+#include "helper.h"
+
+#include "mainwindow.h"
+#include <QDebug>
+
+void populatePorts(const pa_card_info &info, std::map<QByteArray, PortInfo> &ports)
+{
+ ports.clear();
+ for (uint32_t i = 0; i < info.n_ports; ++i) {
+ PortInfo p;
+
+ p.name = info.ports[i]->name;
+ p.description = info.ports[i]->description;
+ p.priority = info.ports[i]->priority;
+ p.available = info.ports[i]->available;
+ p.direction = info.ports[i]->direction;
+ p.latency_offset = info.ports[i]->latency_offset;
+ for (pa_card_profile_info2 ** p_profile = info.ports[i]->profiles2; p_profile && *p_profile != nullptr; ++p_profile)
+ p.profiles.push_back((*p_profile)->name);
+
+ ports[p.name] = p;
+ }
+}
+
+void groupProfiles(const std::set<pa_card_profile_info2 *, profile_prio_compare> &profile_priorities,
+ const std::map<QByteArray, PortInfo> &ports,
+ QMap<QString, ProfileGroup> &profiles)
+{
+ profiles.clear();
+ for (auto p_profile : profile_priorities) {
+ bool hasNo = false, hasOther = false;
+ std::map<QByteArray, PortInfo>::const_iterator portIt;
+ QByteArray desc = p_profile->description;
+
+ for (portIt = ports.begin(); portIt != ports.end(); portIt++) {
+ PortInfo port = portIt->second;
+
+ if (std::find(port.profiles.begin(), port.profiles.end(), p_profile->name) == port.profiles.end())
+ continue;
+
+ if (port.available == PA_PORT_AVAILABLE_NO)
+ hasNo = true;
+ else {
+ hasOther = true;
+ break;
+ }
+ }
+ if (hasNo && !hasOther)
+ desc += MainWindow::tr(" (unplugged)").toUtf8().constData();
+
+ if (!p_profile->available)
+ desc += MainWindow::tr(" (unavailable)").toUtf8().constData();
+
+ QString parseId = QString::fromUtf8(p_profile->name);
+ int plus = parseId.indexOf(QLatin1Char('+'));
+ if (plus != -1) {
+ parseId = parseId.left(plus);
+ }
+ auto list = parseId.split(QRegularExpression(QLatin1String("[:\\-]")));
+ parseId.clear();
+ for (auto p : list) {
+ if (p == QLatin1String("input") || p == QLatin1String("output"))
+ continue;
+ if (parseId.isEmpty() || p.startsWith(QLatin1String("extra"))) {
+ parseId.append(p);
+ }
+ }
+ qDebug() << "ParseID:" << parseId;
+ ProfileGroup &group = profiles[parseId];
+ if (p_profile->available) {
+ group.available = true;
+ }
+ group.addEntry(p_profile->name, desc.constData());
+ }
+}
diff --git a/src/helper.h b/src/helper.h
new file mode 100644
index 0000000..823f3e1
--- /dev/null
+++ b/src/helper.h
@@ -0,0 +1,61 @@
+#ifndef _HELPER_H_
+#define _HELPER_H_
+
+#include "pavucontrol.h"
+#include <pulse/ext-stream-restore.h>
+#if HAVE_EXT_DEVICE_RESTORE_API
+# include <pulse/ext-device-restore.h>
+#endif
+
+#include <set>
+#include <map>
+#include <QByteArray>
+#include <QMap>
+#include <vector>
+
+struct ProfileEntry {
+ QByteArray id;
+ QStringList tokens;
+ QString getName() const;
+};
+
+struct ProfileGroup {
+ ProfileGroup() : available(false) {}
+ bool available;
+ QString name;
+ QList<ProfileEntry> entries;
+ QString getProfileName();
+ void addEntry(const char* id, const char* name);
+ bool containsProfile(const QByteArray &pro) const;
+};
+
+struct PortInfo {
+ QByteArray name;
+ QByteArray description;
+ uint32_t priority;
+ int available;
+ int direction;
+ int64_t latency_offset;
+ std::vector<QByteArray> profiles;
+};
+
+
+/* Used for profile sorting */
+struct profile_prio_compare {
+ bool operator() (pa_card_profile_info2 const * const lhs, pa_card_profile_info2 const * const rhs) const {
+
+ if (lhs->priority == rhs->priority)
+ return strcmp(lhs->name, rhs->name) > 0;
+
+ return lhs->priority > rhs->priority;
+ }
+};
+
+void populatePorts(const pa_card_info &info, std::map<QByteArray, PortInfo> &ports);
+
+
+void groupProfiles(const std::set<pa_card_profile_info2 *, profile_prio_compare> &profile_priorities,
+ const std::map<QByteArray, PortInfo> &ports,
+ QMap<QString, ProfileGroup> &profiles);
+
+#endif
diff --git a/src/mainwindow.cc b/src/mainwindow.cc
index 66c0ac4..0b6b310 100644
--- a/src/mainwindow.cc
+++ b/src/mainwindow.cc
@@ -36,17 +36,6 @@
#include <QStyle>
#include <QSettings>
-/* Used for profile sorting */
-struct profile_prio_compare {
- bool operator() (pa_card_profile_info2 const * const lhs, pa_card_profile_info2 const * const rhs) const {
-
- if (lhs->priority == rhs->priority)
- return strcmp(lhs->name, rhs->name) > 0;
-
- return lhs->priority > rhs->priority;
- }
-};
-
struct sink_port_prio_compare {
bool operator() (const pa_sink_port_info& lhs, const pa_sink_port_info& rhs) const {
@@ -211,6 +200,7 @@ void MainWindow::updateCard(const pa_card_info &info) {
setIconByName(w->iconImage, icon, "audio-card");
w->hasSinks = w->hasSources = false;
+
profile_priorities.clear();
for (pa_card_profile_info2 ** p_profile = info.profiles2; p_profile && *p_profile != nullptr; ++p_profile) {
w->hasSinks = w->hasSinks || ((*p_profile)->n_sinks > 0);
@@ -218,65 +208,9 @@ void MainWindow::updateCard(const pa_card_info &info) {
profile_priorities.insert(*p_profile);
}
- w->ports.clear();
- for (uint32_t i = 0; i < info.n_ports; ++i) {
- PortInfo p;
-
- p.name = info.ports[i]->name;
- p.description = info.ports[i]->description;
- p.priority = info.ports[i]->priority;
- p.available = info.ports[i]->available;
- p.direction = info.ports[i]->direction;
- p.latency_offset = info.ports[i]->latency_offset;
- for (pa_card_profile_info2 ** p_profile = info.ports[i]->profiles2; p_profile && *p_profile != nullptr; ++p_profile)
- p.profiles.push_back((*p_profile)->name);
-
- w->ports[p.name] = p;
- }
-
- w->profiles.clear();
- for (auto p_profile : profile_priorities) {
- bool hasNo = false, hasOther = false;
- std::map<QByteArray, PortInfo>::iterator portIt;
- QByteArray desc = p_profile->description;
-
- for (portIt = w->ports.begin(); portIt != w->ports.end(); portIt++) {
- PortInfo port = portIt->second;
+ populatePorts(info, w->ports);
- if (std::find(port.profiles.begin(), port.profiles.end(), p_profile->name) == port.profiles.end())
- continue;
-
- if (port.available == PA_PORT_AVAILABLE_NO)
- hasNo = true;
- else {
- hasOther = true;
- break;
- }
- }
- if (hasNo && !hasOther)
- desc += tr(" (unplugged)").toUtf8().constData();
-
- if (!p_profile->available)
- desc += tr(" (unavailable)").toUtf8().constData();
-
- QString parseId = QString::fromUtf8(p_profile->name);
- int plus = parseId.indexOf(QLatin1Char('+'));
- if (plus != -1) {
- parseId = parseId.left(plus);
- }
- auto list = parseId.split(QRegularExpression(QLatin1String("[:\\-]")));
- parseId.clear();
- for (auto p : list) {
- if (p == QLatin1String("input") || p == QLatin1String("output"))
- continue;
- if (parseId.isEmpty() || p.startsWith(QLatin1String("extra"))) {
- parseId.append(p);
- }
- }
- qDebug() << "ParseID:" << parseId;
- ProfileGroup &group = w->profiles[parseId];
- group.addEntry(p_profile->name, desc.constData());
- }
+ groupProfiles(profile_priorities, w->ports, w->profiles);
w->activeProfile = info.active_profile ? info.active_profile->name : "";
diff --git a/src/pavucontrol.cc b/src/pavucontrol.cc
index f012d08..4b1d502 100644
--- a/src/pavucontrol.cc
+++ b/src/pavucontrol.cc
@@ -31,6 +31,7 @@
// #include <canberra-gtk.h>
+#include "helper.h"
#include "pavucontrol.h"
#include "minimalstreamwidget.h"
#include "channel.h"
@@ -50,6 +51,9 @@
#include <QCommandLineParser>
#include <QCommandLineOption>
#include <QString>
+#include <QRegularExpression>
+
+#include <unistd.h>
static pa_context* context = nullptr;
static pa_mainloop_api* api = nullptr;
@@ -57,6 +61,7 @@ static int n_outstanding = 0;
static int default_tab = 0;
static bool retry = false;
static int reconnect_timeout = 1;
+static QRegularExpression select_output;
void show_error(const char *txt) {
char buf[256];
@@ -77,7 +82,7 @@ static void dec_outstanding(MainWindow *w) {
}
}
-void card_cb(pa_context *, const pa_card_info *i, int eol, void *userdata) {
+static void card_cb(pa_context *, const pa_card_info *i, int eol, void *userdata) {
MainWindow *w = static_cast<MainWindow*>(userdata);
if (eol < 0) {
@@ -96,6 +101,69 @@ void card_cb(pa_context *, const pa_card_info *i, int eol, void *userdata) {
w->updateCard(*i);
}
+static void card_cb_setdef(pa_context * ctx, const pa_card_info *i, int eol, void *) {
+ if (eol < 0) {
+ if (pa_context_errno(context) == PA_ERR_NOENTITY)
+ return;
+
+ show_error(QObject::tr("Card callback failure").toUtf8().constData());
+ return;
+ }
+
+ if (eol > 0) {
+ return;
+ }
+
+ // TODO Check stuff
+
+ std::set<pa_card_profile_info2 *, profile_prio_compare> profile_priorities;
+ std::map<QByteArray, PortInfo> ports;
+ QMap<QString, ProfileGroup> profiles;
+
+ profile_priorities.clear();
+ for (pa_card_profile_info2 ** p_profile = i->profiles2; p_profile && *p_profile != nullptr; ++p_profile) {
+ profile_priorities.insert(*p_profile);
+ }
+
+ populatePorts(*i, ports);
+ groupProfiles(profile_priorities, ports, profiles);
+
+ ProfileGroup *best = nullptr;
+ for (auto &p : profiles) {
+ if (p.getProfileName().contains(select_output)) {
+ // Maybe we should track per-profile availability too and scan the list...
+ if (best == nullptr || p.available) {
+ best = &p;
+ if (p.available)
+ break;
+ }
+ }
+ }
+
+ if (best != nullptr) {
+ // Can we do this inside the callback?
+ pa_operation* o;
+ const auto *entry = best->entries.first().id.constData();
+ printf("Selecting profile %s\n", entry);
+ if (!(o = pa_context_set_card_profile_by_index(ctx, i->index, entry, nullptr, nullptr))) {
+ printf("pa_context_set_card_profile_by_index() failed\n");
+ return;
+ }
+ pa_operation_unref(o);
+ }
+}
+
+static void context_state_callback_setdef(pa_context *c, void *) {
+ if (pa_context_get_state(c) == PA_CONTEXT_READY) {
+ pa_operation *o;
+ if (!(o = pa_context_get_card_info_list(c, card_cb_setdef, nullptr))) {
+ show_error(QObject::tr("pa_context_get_card_info_list() failed").toUtf8().constData());
+ return;
+ }
+ pa_operation_unref(o);
+ }
+}
+
#if HAVE_EXT_DEVICE_RESTORE_API
static void ext_device_restore_subscribe_cb(pa_context *c, pa_device_type_t type, uint32_t idx, void *userdata);
#endif
@@ -597,12 +665,7 @@ pa_context* get_context(void) {
return context;
}
-gboolean connect_to_pulse(gpointer userdata) {
- MainWindow *w = static_cast<MainWindow*>(userdata);
-
- if (context)
- return false;
-
+void connectToPulse(void) {
pa_proplist *proplist = pa_proplist_new();
pa_proplist_sets(proplist, PA_PROP_APPLICATION_NAME, QObject::tr("PulseAudio Volume Control").toUtf8().constData());
pa_proplist_sets(proplist, PA_PROP_APPLICATION_ID, "org.PulseAudio.pavucontrol");
@@ -613,6 +676,15 @@ gboolean connect_to_pulse(gpointer userdata) {
g_assert(context);
pa_proplist_free(proplist);
+}
+
+gboolean connect_to_pulse(gpointer userdata) {
+ MainWindow *w = static_cast<MainWindow*>(userdata);
+
+ if (context)
+ return false;
+
+ connectToPulse();
pa_context_set_state_callback(context, context_state_callback, w);
@@ -643,6 +715,7 @@ gboolean connect_to_pulse(gpointer userdata) {
}
int main(int argc, char *argv[]) {
+ int exit_code = 0, have_regex = 0;
signal(SIGPIPE, SIG_IGN);
@@ -678,35 +751,76 @@ int main(int argc, char *argv[]) {
QCommandLineOption maximizeOption(QStringList() << QStringLiteral("maximize") << QStringLiteral("m"), QObject::tr("Maximize the window."));
parser.addOption(maximizeOption);
+ QCommandLineOption selectOption(QStringList() << QStringLiteral("output") << QStringLiteral("o"), QObject::tr("Select a specific output configuration and quit."), QStringLiteral("regex"));
+ parser.addOption(selectOption);
+
parser.process(app);
default_tab = parser.value(tabOption).toInt();
retry = parser.isSet(retryOption);
+ if (parser.isSet(selectOption)) {
+ select_output = QRegularExpression(parser.value(selectOption));
+ if (!select_output.isValid()) {
+ select_output = QRegularExpression(QRegularExpression::escape(parser.value(selectOption)));
+ }
+ have_regex = select_output.isValid();
+ }
- // ca_context_set_driver(ca_gtk_context_get(), "pulse");
-
- MainWindow* mainWindow = new MainWindow();
- if(parser.isSet(maximizeOption))
- mainWindow->showMaximized();
-
- pa_glib_mainloop *m = pa_glib_mainloop_new(g_main_context_default());
- g_assert(m);
- api = pa_glib_mainloop_get_api(m);
- g_assert(api);
+ if (have_regex) {
+ pa_mainloop *m = pa_mainloop_new();
+ g_assert(m);
+ api = pa_mainloop_get_api(m);
+ g_assert(api);
+
+ connectToPulse();
+ pa_context_set_state_callback(context, context_state_callback_setdef, nullptr);
+ if (pa_context_connect(context, nullptr, PA_CONTEXT_NOFAIL, nullptr) >= 0) {
+ if (pa_context_errno(context) == PA_ERR_INVALID) {
+ printf("pa_context_errno = PA_ERR_INVALID\n");
+ exit_code = 1;
+ } else {
+ int evs;
+ int tries = 10;
+ exit_code = 100;
+ // WTF
+ while ((evs = pa_mainloop_iterate(m, 0, &exit_code)) >= 0) {
+ if (evs == 0) {
+ if (--tries == 0)
+ break;
+ usleep(100000);
+ }
+ }
+ printf("Exit: %d\n", exit_code);
+ }
+ }
- connect_to_pulse(mainWindow);
- if (reconnect_timeout >= 0) {
- mainWindow->show();
- app.exec();
- }
+ if (context)
+ pa_context_unref(context);
+ pa_mainloop_free(m);
+ } else {
+ MainWindow* mainWindow = new MainWindow();
+ if(parser.isSet(maximizeOption))
+ mainWindow->showMaximized();
+
+ pa_glib_mainloop *m = pa_glib_mainloop_new(g_main_context_default());
+ g_assert(m);
+ api = pa_glib_mainloop_get_api(m);
+ g_assert(api);
+
+ connect_to_pulse(mainWindow);
+ if (reconnect_timeout >= 0) {
+ mainWindow->show();
+ app.exec();
+ }
- if (reconnect_timeout < 0)
- show_error(QObject::tr("Fatal Error: Unable to connect to PulseAudio").toUtf8().constData());
+ if (reconnect_timeout < 0)
+ show_error(QObject::tr("Fatal Error: Unable to connect to PulseAudio").toUtf8().constData());
- delete mainWindow;
+ delete mainWindow;
- if (context)
- pa_context_unref(context);
- pa_glib_mainloop_free(m);
+ if (context)
+ pa_context_unref(context);
+ pa_glib_mainloop_free(m);
+ }
- return 0;
+ return exit_code;
}