133 lines
4.2 KiB
C++
133 lines
4.2 KiB
C++
#include "trayicon.h"
|
|
|
|
#include <QMenu>
|
|
#include <QApplication>
|
|
#include <QAction>
|
|
#include <QDebug>
|
|
|
|
#include <pulse/pulseaudio.h>
|
|
|
|
TrayIcon::TrayIcon(QObject *parent) :
|
|
QSystemTrayIcon(QIcon::fromTheme("audio-card" /*"preferences-desktop-sound"*/), parent),
|
|
pulse(this)
|
|
{
|
|
this->menu = new QMenu(); // can only be explicitly owned by a QWidget, not any QObject, so we need to
|
|
// manually manage its lifecycle in the d'tor
|
|
|
|
this->quitAction = new QAction("Quit"); // Don't have these owned by this->menu, otherwise we can't delete + readd them in refresh
|
|
this->quitAction->setIcon(QIcon::fromTheme("application-exit"));
|
|
connect(this->quitAction, &QAction::triggered, this, &TrayIcon::on_quitAction_triggered);
|
|
|
|
this->refreshAction = new QAction("Refresh");
|
|
this->refreshAction->setIcon(QIcon::fromTheme("view-refresh"));
|
|
connect(this->refreshAction, &QAction::triggered, this, &TrayIcon::onRefreshAction_triggered);
|
|
|
|
// Set up basic menu only, will be wiped during refresh
|
|
this->menu->addAction(refreshAction);
|
|
this->menu->addAction(quitAction);
|
|
|
|
this->setContextMenu(this->menu); // n.b. does not take ownership
|
|
|
|
connect(&this->pulse, &QPulse::ConnectionStateChanged, [=]() {
|
|
if (pulse.ConnectionState() == PA_CONTEXT_READY) {
|
|
refreshData();
|
|
}
|
|
});
|
|
|
|
connect(&this->pulse, &QPulse::SetCardProfileOperationCompleted, this, &TrayIcon::refreshData); // ignore argument
|
|
|
|
// Need to use BlockingQueuedConnection to pull items off pulseaudio thread before it reallocates them
|
|
connect(&this->pulse, &QPulse::GotCardInfoList, this, &TrayIcon::onGotPulseCardInfo, Qt::BlockingQueuedConnection);
|
|
|
|
// Immediately refresh when popup is shown (normal right-click event), so that data always seems live
|
|
connect(this->menu, &QMenu::aboutToShow, this, &TrayIcon::refreshData);
|
|
|
|
refreshData();
|
|
}
|
|
|
|
TrayIcon::~TrayIcon()
|
|
{
|
|
for (int i = 0; i < cardMenus.length(); ++i) {
|
|
delete cardMenus[i];
|
|
}
|
|
cardMenus.clear();
|
|
delete this->quitAction;
|
|
delete this->menu;
|
|
}
|
|
|
|
void TrayIcon::refreshData()
|
|
{
|
|
// Delete existing card menus, but leave the final clearing for later
|
|
for (int i = 0; i < cardMenus.length(); ++i) {
|
|
delete cardMenus[i];
|
|
}
|
|
cardMenus.clear();
|
|
|
|
// Calling delete() should have removed them from the menu, leaving only the separator / refresh / quit options
|
|
|
|
pulse.RequestCardInfo();
|
|
}
|
|
|
|
void TrayIcon::on_quitAction_triggered()
|
|
{
|
|
QApplication::exit(0);
|
|
}
|
|
|
|
void TrayIcon::onRefreshAction_triggered()
|
|
{
|
|
refreshData();
|
|
}
|
|
|
|
void TrayIcon::onGotPulseCardInfo(const pa_card_info& cardInfo, int eol)
|
|
{
|
|
if (eol) {
|
|
// OK - rebuild the menu
|
|
this->menu->clear();
|
|
|
|
for (int i = 0; i < this->cardMenus.length(); ++i) {
|
|
this->menu->addMenu(this->cardMenus[i]);
|
|
}
|
|
this->menu->addSeparator();
|
|
this->menu->addAction(refreshAction);
|
|
this->menu->addAction(quitAction);
|
|
return;
|
|
}
|
|
|
|
QString name = pa_proplist_gets(cardInfo.proplist, PA_PROP_DEVICE_DESCRIPTION);
|
|
if (name.length() == 0) {
|
|
name = cardInfo.name;
|
|
}
|
|
if (name.length() == 0) {
|
|
name = tr("Default card");
|
|
}
|
|
|
|
QString iconName = pa_proplist_gets(cardInfo.proplist, PA_PROP_DEVICE_ICON_NAME); // e.g. audio-card-pci
|
|
if (iconName.length() == 0) {
|
|
iconName = "audio-card"; // default
|
|
}
|
|
|
|
QMenu* cardMenu = new QMenu(name, this->menu); // recursive ownership
|
|
cardMenu->setIcon(QIcon::fromTheme(iconName));
|
|
|
|
for (size_t i = 0; i < cardInfo.n_profiles; ++i) {
|
|
|
|
bool isActiveProfile = (!strcmp(cardInfo.active_profile->name, cardInfo.profiles[i].name));
|
|
|
|
auto profileAction = new QAction(cardInfo.profiles[i].description, cardMenu);
|
|
profileAction->setCheckable(true);
|
|
if (isActiveProfile) {
|
|
profileAction->setChecked(true);
|
|
}
|
|
profileAction->setData(cardInfo.profiles[i].name);
|
|
|
|
int cardIndex = cardInfo.index;
|
|
connect(profileAction, &QAction::triggered, [=]() {
|
|
this->pulse.SetCardProfile(cardIndex, profileAction->data().toString().toUtf8().data());
|
|
});
|
|
|
|
cardMenu->addAction(profileAction);
|
|
}
|
|
|
|
this->cardMenus.push_back(cardMenu);
|
|
}
|