diff options
-rw-r--r-- | src/main.cpp | 89 | ||||
-rw-r--r-- | src/mainwindow.cpp | 7 | ||||
-rw-r--r-- | src/slxoutput.cpp | 3 |
3 files changed, 81 insertions, 18 deletions
diff --git a/src/main.cpp b/src/main.cpp index a06f6f5..bf22453 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -31,47 +31,64 @@ bool g_IgnoreGui; /** If not empty, on profile change, if profile belings to this card, make its sink default */ static QString _pendingCardDefault; +/** While running, we want to set each port's volume to 100% the first time we see it. Unfortunately + * a port only startes to exist once its according profile is selected, so there is no way to have + * a simple "set everything to 100%" method run on startup, at least not without switching through + * all profiles on all cards, which sounds like it could be annoying and buggy. So do it this way + * for now. + */ static QSet<QString> _donePorts; +/** Whe something changes, (re)start a short timeout first before actually updating the GUI, to + * avoid multiple updates in rapid succession, for example when changing the active profile. + */ static QTimer _updateDelay; static MainWindow *_mainWindow; +/** + * Queue a GUI update. By default, delay it by 50ms. If the timer is already active but hasn't + * expired yet, it will be restarted with the given timeout. + */ static void queueGuiUpdate(int ms = 50) { _updateDelay.start(ms); } +/** + * The actual GUI update logic. Iterate over all sinks and their ports, and add them to the GUI. + * Then iterate over all non-selected profiles, and add them to the GUI too, so we have a simple, flat + * list of "outputs" to select, which should be a bit more easy to grasp for a non-technical person + * trying to make the computer go beep. (Hopefully) + */ static void updateActiveOutput() { - g_IgnoreGui = true; - _mainWindow->mark(); + g_IgnoreGui = true; // Block GUI updates + _mainWindow->mark(); // Mark all items in the lists as unused auto *i = PulseAudioQt::Context::instance(); - //QSet<int> usedCards; auto cards = i->cards(); + printf(".--------------------------------------------.\n"); for (auto *sink : i->sinks()) { QString cardName = sink->cardIndex() < cards.size() ? cards.at(sink->cardIndex())->name() : QString(); int portIndex = -1; - //usedCards.insert(sink->cardIndex()); for (auto *port : sink->ports()) { portIndex++; //if (port->availability() == PulseAudioQt::Port::Unavailable) // continue; bool active = (sink->isDefault() && portIndex == sink->activePortIndex()); + qint64 volume = sink->volume(); printf("[%c] Output: '%s %s', volume: %d, mute: %d\n", active ? 'x' : ' ', sink->description().toLocal8Bit().constData(), port->description().toLocal8Bit().constData(), - (int)sink->volume(), (int)sink->isMuted()); - qint64 volume = sink->volume(); + (int)volume, (int)sink->isMuted()); QString id = cardName + sink->name() + port->name(); - if (active) { - if (!_donePorts.contains(id)) { - _donePorts.insert(id); - if (volume < 60000) { - volume = 65535; - sink->setVolume(volume); - } + // Only once, update port volume to be 100% if it's low + if (active && !_donePorts.contains(id)) { + _donePorts.insert(id); + if (volume < 60000) { + volume = 65535; + sink->setVolume(volume); } } QString title = sink->description() + ", " + port->description(); @@ -79,13 +96,16 @@ static void updateActiveOutput() active, sink->isMuted(), sink->isVolumeWritable() ? sink->volume() : -1, QString(), sink->name(), port->name()); } } + // Now find all the profiles from all cards that are not active and add an entry to the list for each one for (auto *card : cards) { QString cardDescription = card->description(); int profIndex = -1; for (auto *profile : card->profiles()) { profIndex++; + // Ignore the active profile, and profiles that are unavailable if (profIndex == card->activeProfileIndex() || profile->availability() == PulseAudioQt::Profile::Unavailable) continue; + // No point in selecting something that doesn't have any way to output sound if (profile->sinks() == 0) continue; printf("[ ] Output: '%s', sinks: %d\n", @@ -97,25 +117,34 @@ static void updateActiveOutput() } } printf("`--------------------------------------------ยด\n"); - _mainWindow->sweep(); - g_IgnoreGui = false; + _mainWindow->sweep(); // Removes all items from the list that are still marked unused, i.e. updateOutput() was not called on those + g_IgnoreGui = false; // Allow GUI updates again } +/** + * Called after the user set a GUI entry as default that represents a profile, after the active profile + * or the list of available ports changed. Only after this happened can we actually set as default the sink/port + * that was created from selecting that profile as the default sink. + */ static void checkShouldSetDefault() { - if (_pendingCardDefault.isEmpty()) + if (_pendingCardDefault.isEmpty()) // No switch actually pending return; + // Otherwise, it's the ID of the card that the profile belongs to that the user wants to set as default printf("Pending card default to %s\n", _pendingCardDefault.toLocal8Bit().constData()); auto *i = PulseAudioQt::Context::instance(); int cardIdx = -1; + // Find the current index of that card for (auto *card : i->cards()) { cardIdx++; if (card->name() == _pendingCardDefault && !card->ports().isEmpty()) break; } + // Then iterate over all sinks until we find one that belongs to the card for (auto *sink : i->sinks()) { if (sink->cardIndex() == cardIdx) { printf("MATCH SET!\n"); + // Set as default, unmute, and clear the pending switch sink->setDefault(true); sink->setMuted(false); _pendingCardDefault.clear(); @@ -123,6 +152,9 @@ static void checkShouldSetDefault() } } +/** + * Called when PA tells us about a new card; start listening to signals we're interesed in. + */ static void newCardAppeared(PulseAudioQt::Card *card) { //if (_doneCards.contains(card->name())) @@ -148,6 +180,9 @@ static void newCardAppeared(PulseAudioQt::Card *card) */ } +/** + * Called when PA tells us about a new sink; start listening to signals we're interested in. + */ static void newSinkAppeared(PulseAudioQt::Sink *sink) { for (auto *port : sink->ports()) { @@ -170,6 +205,9 @@ static void newSinkAppeared(PulseAudioQt::Sink *sink) queueGuiUpdate(); } +/** + * Mute/unmute sink by its string identifier. + */ void setMuted(const QString &sink, bool muted) { auto *i = PulseAudioQt::Context::instance(); @@ -182,6 +220,9 @@ void setMuted(const QString &sink, bool muted) queueGuiUpdate(); } +/** + * Set a sink's volume, by its string identifier. + */ void setSinkVolume(const QString &sink, int volume) { auto *i = PulseAudioQt::Context::instance(); @@ -194,6 +235,12 @@ void setSinkVolume(const QString &sink, int volume) } } +/** + * Set the given card as default, preferably enabling the given profile + * on it. If this card doesn't have a profile with the given ID, try + * to use a profile that contains the string "Duplex" in its name, + * otherwise, just use the first profile available. + */ void enableCard(const QString &card, const QString &profile) { auto *i = PulseAudioQt::Context::instance(); @@ -242,12 +289,20 @@ void enableCard(const QString &card, const QString &profile) } } else { matchingCard->setActiveProfileIndex(profileIdx); + // Remember the ID of the card we switched to; we need this as only after PA is done + // switching to the desired profile will we see any sink belonging to it, so we can only + // set the according sink as fallback after that happens. See checkShouldSetDefault(). _pendingCardDefault = matchingCard->name(); } } queueGuiUpdate(); } +/** + * Set given sink as default sink, unmute it, and switch to its + * port as passed to this function. If the sink doesn't have a + * port with this ID, nothing happens. + */ void enableSink(const QString &sink, const QString &port) { auto *i = PulseAudioQt::Context::instance(); @@ -277,6 +332,8 @@ int main(int argc, char **argv) printf("Pa is %p = %d\n", i, (int)i->isValid()); printf("Cards: %d\n", (int)i->cards().size()); + // 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. QTimer::singleShot(100, [=]() { for (auto *card : i->cards()) { newCardAppeared(card); diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 4860f2b..9ae66d3 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -12,7 +12,6 @@ MainWindow::~MainWindow() } -// Need to pass newSink as new widget's sink isn't set here yet, so always empty SlxOutput* MainWindow::getOutput(const QString &id, bool isSink, const QString &newTitle) { SlxOutput* w = _widgets.value(id, nullptr); @@ -52,6 +51,9 @@ SlxOutput* MainWindow::getOutput(const QString &id, bool isSink, const QString & return w; } +/** + * Mark all entries as unused. + */ void MainWindow::mark() { for (auto *w : _widgets) { @@ -59,6 +61,9 @@ void MainWindow::mark() } } +/** + * Remove all entries marked as unused. + */ void MainWindow::sweep() { for (QMutableMapIterator<QString, SlxOutput*> it(_widgets); it.hasNext(); ) { diff --git a/src/slxoutput.cpp b/src/slxoutput.cpp index d46b0e9..0ac38b1 100644 --- a/src/slxoutput.cpp +++ b/src/slxoutput.cpp @@ -50,6 +50,7 @@ void SlxOutput::volumeSliderChanged(int value) { if (g_IgnoreGui) return; + // Update slider every 100ms if (_volumeTimer.isActive()) return; _volumeTimer.start(); @@ -78,5 +79,5 @@ void SlxOutput::updateOutput(const QString &name, bool isDefault, bool isMuted, volumeSlider->setEnabled(isSink()); } muteToggleButton->setEnabled(isSink()); - unused = false; + unused = false; // Entry was updated, so it's being used } |