Search code examples
c++qtqt5system-tray

How to write QT system tray app without a window class, and integrate it with another process?


Here is my setup:

  • A background process that keeps running and does it's job.

  • A launcher which launches the aforementioned process and monitors it, relaunches it if crashed or killed.

I wish to add a system tray access to the launcher process (and the launcher process ideally will contain code for system tray display) and enable basic options (start, stop etc) to be triggered from the system tray context menu. The system tray does not need a window of it's own. Just a windowless system tray with a Context menu that contains 2-3 options.

Since the all code written so far is in C/C++ and I need it to run on Windows and Linux, QT comes across as obvious choice. I have found it quite frustrating to get past basic QT launcher tray display. Nearly every example I have seen of QSystemTrayIcon includes a 'mainwindow' inheritance.

Below is the code I am using to create system tray.

#include <QtWidgets/QApplication>
#include <QtCore/QDebug>
#include <QtGui/QIcon>
#include <QtWidgets/QSystemTrayIcon>
#include <QtWidgets/QMainWindow>
#include <QtWidgets/QMenu>


int main(int argc, char **argv)
{
    QApplication app(argc, argv);
    QPixmap oPixmap(32,32);
    //QMenu* menu1 = new QMenu(); // want to get a context menu from system tray
    oPixmap.load ("systemTrayIcon.png");

    QIcon oIcon( oPixmap );

    QSystemTrayIcon *trayIcon = new QSystemTrayIcon(oIcon);
    qDebug() << trayIcon->isSystemTrayAvailable();
    trayIcon->setContextMenu( menu1);
    trayIcon->setVisible(true);
    trayIcon->showMessage("Test Message", "Text", QSystemTrayIcon::Information, 1000);

    return app.exec();
}

The code displays system tray alright, but I haven't been able to get around on how to add menus to it. What I want is:

1) Add the context menu to the system tray above without adding any window class (unless that is not possible)

2) Connect those context menu items to functions in my existing code

3) The app.exec() seems to be an infinite loop that processes QT events. However, since my launcher has it's own event loop, I want to make it so that the QT event loop is integrated with my launcher loop. In other words, add some non-QT tasks to the event loop.


Solution

  • Given the clarification from the comments, you have a couple of options on how to get code called for context menu or activation actions.

    1. A receiver object: basically what the examples where using, just that you don't derive your receiver class from any window type. For macro based signal/slot connections, the base type needs to be QObject or something derived from that, for function pointer based connect it can be any class

      class MyReceiver : public QObject
      {
          Q_OBJECT
      public slots:
          void onActivated(QSystemTrayIcon::ActivationReason reason);
      };
      
      
      // in main()
      MyReceiver receiver;
      
      // macro based connect
      connect(trayIcon, SIGNAL(activated(QSystemTrayIcon::ActivationReason)),
              &receiver, SLOT(onActivated(QSystemTrayIcon::ActivationReason)));
      
      
      // or function pointer based connect
      connect(trayIcon, &QSystemTrayIcon::activated,
              &receiver, &MyReceiver::onActivated);
      
    2. Connect to stand-alone functions

      void onActivated(QSystemTrayIcon::ActivationReason reason);
      
      
      connect(trayIcon, &QSystemTrayIcon::activated, &onActivated);
      
    3. With a C++11 capable environment, connect to a lambda

      connect(trayIcon, &QSystemTrayIcon::activated,
              [](QSystemTrayIcon::ActivationReason reason) {});
      

    For the context menu the same techniques apply, the "sender" objects are the QAction items you add to the menu and their signal is triggered() or toggled(bool) depending on whether the action can be just clicked or toggled between and "on" and "off" state.