summaryrefslogtreecommitdiffstats
path: root/src/setdefault.cpp
blob: 21b368e76cfaff3c854160fb0997a13cfdd232d7 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
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;
}