pa-card-profile-tray/trayicon.cpp

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);
}