Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 17 additions & 40 deletions src/QtTrayMenu.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,6 @@
#include "WindowsAppearance.h"
#endif

namespace {
int defaultArgc = 1; // NOSONAR(cpp:S5421): This is required for QApplication's argc/argv constructor
char defaultArgv0[] = "TrayMenuApp"; // NOSONAR(cpp:S5421): This is required for QApplication's argc/argv constructor
char *defaultArgv[] = {defaultArgv0, nullptr}; // NOSONAR(cpp:S5421,cpp:S5954): This is required for QApplication's argc/argv constructor
} // namespace

QtTrayMenu::QtTrayMenu(QObject *parent, const bool debug):
QtTrayMenu(-1, nullptr, parent, debug) {
};
Expand All @@ -40,9 +34,9 @@ QtTrayMenu::QtTrayMenu(int argc, char **argv, QObject *parent, const bool debug)
// Note: The following is ugly but QApplication requires an argv containing the application name.
// We might not have access to the real argc/argv here due to being called/pulled as a dependency.
if (argc < 0 && argv == nullptr) {
app = new QApplication(defaultArgc, defaultArgv); // NOSONAR(cpp:S5025): Qt has its own integrated memory management
app = new QApplication(defaultArgc, defaultArgv.data()); // NOSONAR(cpp:S5025): QApplication must remain alive through process teardown
} else {
app = new QApplication(argc, argv); // NOSONAR(cpp:S5025): Qt has its own integrated memory management
app = new QApplication(argc, argv); // NOSONAR(cpp:S5025): QApplication must remain alive through process teardown
}
}
#if defined(_WIN32)
Expand All @@ -53,16 +47,7 @@ QtTrayMenu::QtTrayMenu(int argc, char **argv, QObject *parent, const bool debug)
}
}

QtTrayMenu::~QtTrayMenu() {
// Cleanup app only if it was created within this class
if (app && app != QApplication::instance()) {
// Quit QApplication
QApplication::quit();
// Delete app and clear references
delete app; // NOSONAR(cpp:S5025): Qt has its own integrated memory management
app = nullptr; // Set to nullptr after deletion
}
}
QtTrayMenu::~QtTrayMenu() = default;

int QtTrayMenu::init(struct tray *tray, const bool notification) {
if (trayIcon) {
Expand All @@ -82,18 +67,18 @@ int QtTrayMenu::init(struct tray *tray, const bool notification) {
}

// Create tray icon
trayIcon = new QSystemTrayIcon(lookupIcon(tray->icon), this);
trayIcon = std::make_unique<QSystemTrayIcon>(lookupIcon(tray->icon));
trayIcon->setToolTip(QString::fromUtf8(tray->tooltip));

connect(trayIcon, &QSystemTrayIcon::activated, this, &QtTrayMenu::onTrayActivated);
connect(trayIcon, &QSystemTrayIcon::messageClicked, this, &QtTrayMenu::onMessageClicked);
connect(trayIcon.get(), &QSystemTrayIcon::activated, this, &QtTrayMenu::onTrayActivated);
connect(trayIcon.get(), &QSystemTrayIcon::messageClicked, this, &QtTrayMenu::onMessageClicked);
connect(this, &QtTrayMenu::update, this, &QtTrayMenu::onUpdate);
connect(this, &QtTrayMenu::exit, this, &QtTrayMenu::onExitRequested);
connect(this, &QtTrayMenu::showMenu, this, &QtTrayMenu::onShowMenu);

updateMenu(tray->menu);

trayIcon->setContextMenu(trayTopMenu);
trayIcon->setContextMenu(trayTopMenu.get());
trayIcon->show();

if (notification) {
Expand Down Expand Up @@ -147,14 +132,12 @@ void QtTrayMenu::onExitRequested() {
if (trayIcon) {
trayIcon->setContextMenu(nullptr);
}
delete trayTopMenu; // NOSONAR(cpp:S5025): Qt has its own integrated memory management
trayTopMenu = nullptr; // Set to nullptr after deletion
trayTopMenu.reset();
}
// Remove tray icon references;
if (trayIcon) {
trayIcon->hide();
delete trayIcon; // NOSONAR(cpp:S5025): Qt has its own integrated memory management
trayIcon = nullptr; // Set to nullptr after deletion
trayIcon.reset();
}
// Unset tray structure
trayStruct = nullptr;
Expand All @@ -167,30 +150,24 @@ void QtTrayMenu::onExitRequested() {

void QtTrayMenu::updateMenu(struct tray_menu *items) {
// Create and setup new tray menu instance
const auto newTrayTopMenu = new QMenu(); // NOSONAR(cpp:S5025): Qt has its own integrated memory management
auto newTrayTopMenu = std::make_unique<QMenu>();
#if defined(_WIN32)
connect(newTrayTopMenu, &QMenu::aboutToShow, this, []() {
connect(newTrayTopMenu.get(), &QMenu::aboutToShow, this, []() {
tray_qt::windows::sync_color_scheme();
});
#endif
trayIcon->setContextMenu(newTrayTopMenu);
trayIcon->setContextMenu(newTrayTopMenu.get());
// Fill new tray menu instance
createMenu(items, newTrayTopMenu);
// Clear old, unused trayTopMenu instance
if (trayTopMenu != nullptr) {
trayTopMenu->clear(); // Remove all actions
delete trayTopMenu; // NOSONAR(cpp:S5025): Qt has its own integrated memory management
}
// Store reference for cleanup
trayTopMenu = newTrayTopMenu;
createMenu(items, newTrayTopMenu.get());
trayTopMenu = std::move(newTrayTopMenu);
}

void QtTrayMenu::createMenu(struct tray_menu *items, QMenu *menu) {
while (items && items->text) {
if (strcmp(items->text, "-") == 0) {
menu->addSeparator();
} else {
auto *action = new QAction(QString::fromUtf8(items->text), menu); // NOSONAR(cpp:S5025): Qt has its own integrated memory management
auto *action = menu->addAction(QString::fromUtf8(items->text));
action->setDisabled(items->disabled == 1);
action->setCheckable(items->checkbox == 1);
action->setChecked(items->checked == 1);
Expand Down Expand Up @@ -249,15 +226,15 @@ void QtTrayMenu::onTrayActivated(QSystemTrayIcon::ActivationReason reason) {
}

void QtTrayMenu::onMenuItemTriggered() {
auto *action = qobject_cast<QAction *>(sender());
const auto *action = qobject_cast<const QAction *>(sender());
struct tray_menu *menuItem = getTrayMenuItem(action);

if (menuItem && menuItem->cb) {
menuItem->cb(menuItem);
}
}

struct tray_menu *QtTrayMenu::getTrayMenuItem(QAction *action) { // NOSONAR(cpp:S995): Use as defined in function interface
struct tray_menu *QtTrayMenu::getTrayMenuItem(const QAction *action) {
return static_cast<struct tray_menu *>(action->property("tray_menu_item").value<void *>());
}

Expand Down
13 changes: 10 additions & 3 deletions src/QtTrayMenu.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@
#ifndef TRAYMENU_H
#define TRAYMENU_H

// standard includes
#include <array>
#include <memory>

// qt includes
#include <QMenu>
#include <QObject>
Expand Down Expand Up @@ -135,13 +139,16 @@ class QtTrayMenu: public QObject {
void createNotification();
void updateMenu(struct tray_menu *items);
QIcon lookupIcon(QString icon) const;
int defaultArgc = 1;
std::array<char, 12> defaultArgv0 {'T', 'r', 'a', 'y', 'M', 'e', 'n', 'u', 'A', 'p', 'p', '\0'};
std::array<char *, 2> defaultArgv {defaultArgv0.data(), nullptr};
QApplication *app = nullptr;
QSystemTrayIcon *trayIcon = nullptr;
QMenu *trayTopMenu = nullptr;
std::unique_ptr<QSystemTrayIcon> trayIcon;
std::unique_ptr<QMenu> trayTopMenu;
struct tray *trayStruct = nullptr;
bool running = false;
bool blockingEventLoop = false;
struct tray_menu *getTrayMenuItem(QAction *action);
struct tray_menu *getTrayMenuItem(const QAction *action);
mutable std::function<void()> notificationCallback = nullptr;

private slots:
Expand Down
99 changes: 49 additions & 50 deletions src/tray_qt.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,45 +19,41 @@

namespace tray_qt {
/**
* QtTrayMenu instance
* @brief Process-wide state backing the C tray API.
*/
std::unique_ptr<QtTrayMenu> qt_tray_menu = nullptr; // NOSONAR(cpp:S5421): mutable state, not const
/**
* Logging callback for qt_message_handler
*/
void (*log_callback)(int, const char *) = nullptr; // NOSONAR(cpp:S5421): mutable state, not const
/**
* Explicit Qt application metadata configured through the C API.
*/
bool app_info_configured = false; // NOSONAR(cpp:S5421): mutable state, not const
/**
* Qt application name configured through the C API.
*/
QString app_name; // NOSONAR(cpp:S5421): mutable state, not const
/**
* Qt application display name configured through the C API.
*/
QString app_display_name; // NOSONAR(cpp:S5421): mutable state, not const
struct State {
std::unique_ptr<QtTrayMenu> trayMenu; ///< Active tray menu instance.
void (*logCallback)(int, const char *) = nullptr; ///< Registered C logging callback.
bool appInfoConfigured = false; ///< Whether application metadata was explicitly configured.
QString appName; ///< Configured application name.
QString appDisplayName; ///< Configured application display name.
QString desktopName; ///< Configured desktop file name.
};

/**
* Qt desktop file name configured through the C API.
* @brief Access the process-wide tray API state.
* @return Mutable tray API state.
*/
QString desktop_name; // NOSONAR(cpp:S5421): mutable state, not const
State &state() {
static State instance;
return instance;
}

/**
* @brief Acknowledge/click current notification.
*/
void acknowledge_notification() {
if (qt_tray_menu != nullptr && QtTrayMenu::supportsMessages()) {
qt_tray_menu->clickMessage();
if (state().trayMenu != nullptr && QtTrayMenu::supportsMessages()) {
state().trayMenu->clickMessage();
}
}

/**
* @brief Clear current notification state without invoking callbacks.
*/
void clear_notification() {
if (qt_tray_menu != nullptr) {
qt_tray_menu->clearMessageCallback();
if (state().trayMenu != nullptr) {
state().trayMenu->clearMessageCallback();
}
}

Expand All @@ -70,11 +66,11 @@ namespace tray_qt {
clear_notification();
return;
}
if (qt_tray_menu != nullptr && QtTrayMenu::supportsMessages()) {
if (state().trayMenu != nullptr && QtTrayMenu::supportsMessages()) {
if (tray->notification_icon != nullptr) {
qt_tray_menu->showMessage(tray->notification_title, tray->notification_text, tray->notification_icon, tray->notification_cb);
state().trayMenu->showMessage(tray->notification_title, tray->notification_text, tray->notification_icon, tray->notification_cb);
} else {
qt_tray_menu->showMessage(tray->notification_title, tray->notification_text, tray->notification_cb);
state().trayMenu->showMessage(tray->notification_title, tray->notification_text, tray->notification_cb);
}
}
}
Expand All @@ -84,14 +80,15 @@ namespace tray_qt {
* @param allow_defaults Whether empty app info values should apply fallback defaults.
*/
void apply_app_info(const bool allow_defaults = true) {
if (!app_info_configured || qt_tray_menu == nullptr) {
const auto &current_state = state();
if (!current_state.appInfoConfigured || current_state.trayMenu == nullptr) {
return;
}
if (!allow_defaults && app_name.isEmpty() && app_display_name.isEmpty()) {
if (!allow_defaults && current_state.appName.isEmpty() && current_state.appDisplayName.isEmpty()) {
return;
}

qt_tray_menu->configureAppMetadata(app_name, app_display_name, desktop_name);
current_state.trayMenu->configureAppMetadata(current_state.appName, current_state.appDisplayName, current_state.desktopName);
}

/**
Expand All @@ -114,7 +111,7 @@ namespace tray_qt {
* @param msg The message string.
*/
void qt_message_handler(QtMsgType type, const QMessageLogContext &, const QString &msg) {
if (log_callback == nullptr) {
if (state().logCallback == nullptr) {
return;
}
int level;
Expand All @@ -132,29 +129,31 @@ namespace tray_qt {
level = 3;
break;
}
log_callback(level, msg.toUtf8().constData());
state().logCallback(level, msg.toUtf8().constData());
}
} // namespace tray_qt

extern "C" {
void tray_set_app_info(const char *app_name, const char *app_display_name, const char *desktop_name) {
tray_qt::app_info_configured = true;
tray_qt::app_name = app_name != nullptr ? QString::fromUtf8(app_name) : QString();
tray_qt::app_display_name = app_display_name != nullptr ? QString::fromUtf8(app_display_name) : QString();
tray_qt::desktop_name = desktop_name != nullptr ? QString::fromUtf8(desktop_name) : QString();
auto &state = tray_qt::state();
state.appInfoConfigured = true;
state.appName = app_name != nullptr ? QString::fromUtf8(app_name) : QString();
state.appDisplayName = app_display_name != nullptr ? QString::fromUtf8(app_display_name) : QString();
state.desktopName = desktop_name != nullptr ? QString::fromUtf8(desktop_name) : QString();

tray_qt::apply_app_info();
}

int tray_init(struct tray *tray) {
if (tray_qt::qt_tray_menu == nullptr) {
auto &state = tray_qt::state();
if (state.trayMenu == nullptr) {
tray_qt::configure_platform();
// Create a new unique pointer to QtTrayMenu instance
tray_qt::qt_tray_menu = std::make_unique<QtTrayMenu>();
state.trayMenu = std::make_unique<QtTrayMenu>();
tray_qt::apply_app_info(false);
}

if (const auto result = tray_qt::qt_tray_menu->init(tray, false); result < 0) {
if (const auto result = state.trayMenu->init(tray, false); result < 0) {
// Tray init failed. Clean up and return error.
tray_exit();
return result;
Expand All @@ -173,18 +172,18 @@ extern "C" {
}

int tray_loop(int blocking) {
if (tray_qt::qt_tray_menu == nullptr) {
if (tray_qt::state().trayMenu == nullptr) {
return -1;
}
return tray_qt::qt_tray_menu->loop(blocking);
return tray_qt::state().trayMenu->loop(blocking);
}

void tray_update(struct tray *tray) { // NOSONAR(cpp:S995): C API requires this exact mutable-pointer signature
if (tray_qt::qt_tray_menu == nullptr) {
if (tray_qt::state().trayMenu == nullptr) {
return;
}

auto *const tray_menu = tray_qt::qt_tray_menu.get();
auto *const tray_menu = tray_qt::state().trayMenu.get();
const auto apply_update = [tray_menu, tray]() {
tray_menu->update(tray, false);
tray_qt::notify(tray);
Expand All @@ -200,14 +199,14 @@ extern "C" {
}

void tray_exit(void) {
if (tray_qt::qt_tray_menu == nullptr) {
if (tray_qt::state().trayMenu == nullptr) {
return;
}
tray_qt::qt_tray_menu->exit();
tray_qt::state().trayMenu->exit();
}

void tray_set_log_callback(void (*cb)(int level, const char *msg)) { // NOSONAR(cpp:S5205): C API requires a plain function pointer callback type
tray_qt::log_callback = cb;
tray_qt::state().logCallback = cb;
if (cb != nullptr) {
qInstallMessageHandler(tray_qt::qt_message_handler);
} else {
Expand All @@ -216,17 +215,17 @@ extern "C" {
}

void tray_show_menu(void) {
if (tray_qt::qt_tray_menu == nullptr) {
if (tray_qt::state().trayMenu == nullptr) {
return;
}
tray_qt::qt_tray_menu->showMenu();
tray_qt::state().trayMenu->showMenu();
}

void tray_simulate_menu_item_click(int index) {
if (tray_qt::qt_tray_menu == nullptr) {
if (tray_qt::state().trayMenu == nullptr) {
return;
}
tray_qt::qt_tray_menu->clickMenuItem(index);
tray_qt::state().trayMenu->clickMenuItem(index);
}

void tray_simulate_notification_click(void) {
Expand Down
Loading
Loading