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

2

7

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.

Grout answered 21/1, 2017 at 14:40 Comment(8)
For the 3. point see here. Though I'd advise you to create a thread for your own thing, if it constantly needs to process something. Also, why is this question tagged with both C and C++? I don't see any problems about language interoperability. Please fix the tags accordingly.Tumefaction
Thanks for the info. It's tagged as C and C++ since those are the languages I am developing in. As opposed to python. Probably for the same reason that the question you linked is tagged as C++ too?Grout
Well, of course you should tag the appropriate language, but I'm unable to see any C code here, so I think the C tag is irrelevant.Tumefaction
I don't quite understand what you mean by "without adding any window class". Is the code you posted not working?Evidence
Thanks @KevinKrammer The code I posted works (displays the icon in the tray). The trouble starts when I try to use connect on my tray icon, it's quite confusing how to connect different actions (e.g. left click) to it. The QObject::connect examples I have seen all rely on a class inherited from MainWindow sort of classes and pass this pointer to use connect. For example, how do I make it so that the left click on system try icon calls a function?Grout
@Grout ah, I see. I don't think that actually has anything to do with needing a window, it is just that in those example the window is a convenient action "receiver" as it already exists. I'll write an answer with some other optionsEvidence
Thanks, I was actually able to figure out and make it work in my code yesterday using the 3 parameter connect! :)Grout
Possible duplicate of c++ qt tray icon menu actionFraktur
E
5

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.

Evidence answered 23/1, 2017 at 17:0 Comment(0)
D
0

Sorry for the late response. You can create manually using QMenu and QSystemTrayIcon Classes. This is a part of source code I have made a simple tray Icon for app, I wish it would be helpful for you.

QMenu *menu = new QMenu();
QAction *miniAction = menu->addAction("&Hide");
QObject::connect(miniAction, &QAction::triggered, &view, &QWebEngineView::hide);
QAction *maxiAction = menu->addAction("Sho&w");
QObject::connect(maxiAction, &QAction::triggered, &view, &QWebEngineView::show);
QAction *closeAction = menu->addAction("&Close");
QObject::connect(closeAction, &QAction::triggered, &a, &QApplication::exit);

QSystemTrayIcon trayIcon;
trayIcon.setIcon(QIcon(":/logo.ico"));
trayIcon.setContextMenu(menu);
trayIcon.show();
Dundee answered 19/7, 2024 at 19:59 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.