#include "trayicon.h" #include #include #include #include #include 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); }