Global hotkey

Daniele Esposti's Blog
, in 03 May 2012

Let start by explaining the goal: we want to send a custom signal when the user press a combination of keys. Sound pretty easy, your are already thinking to override the keyPressEvent() and keyReleaseEvent() virtual methods of your QMainWindow with your custom code.

It’s a fair solution, but not the so elegant, and what’s happen if you have more than one QMainWindow instances or none at all?

To solve the problem once for all I’ll use a custom QApplication instance to catch the key press and release and to emit the signal. QApplication is a singleton and easy to retrieve its instance’s pointer anywhere in your application; also it process all the events, including keyboard events, so we can plug our event filter to catch the hotkey.

I’ll start by defining a subclass of QApplication with our custom signal:

// ** myapplication.h **

#ifndef MYAPPLICATION_H
#define MYAPPLICATION_H

#include <qapplication>

class MyApplication : public QApplication
{
    Q_OBJECT

public:
    MyApplication(int& argc, char** argv);
    void notifyHotkeyStatus(bool value);

signals:
    void hotkey(bool);
};

#endif // MYAPPLICATION_H
// ** myapplication.cpp **

#include "myapplication.h"


MyApplication::MyApplication(int& argc, char** argv):
    QApplication(argc, argv)
{
}


void MyApplication::notifyHotkeyStatus(bool value) {
    emit hotkey(value);
}

The notifyHotkeyStatus() method will emit a hotkey(bool) signal once when the hotkey is pressed (with a true value) and once when the hotkey is released (passing a false value).

Now I create a lame QMainWindow subclass which changes its background color when the hotkey is pressed. I’ll show only the relevant parts, the rest of the code is the default stuff generated by Qt Creator:

// ** mainwindow.h **

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    explicit MainWindow(QWidget *parent = 0);
    ~MainWindow();

public slots:
    void onHotkey(bool state);

private:
    Ui::MainWindow *ui;
};
// ** mainwindow.cpp **

#include "mainwindow.h"


MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    // Setup UI
    ui->setupUi(this);

    // Connect signals
    connect(qApp, SIGNAL(hotkey(bool)), this, SLOT(onHotkey(bool)));
}

MainWindow::~MainWindow()
{
    delete ui;
}

void MainWindow::onHotkey(bool state) {
    if (state) {
        setStyleSheet("background-color: green");
    } else {
        setStyleSheet("");
    }
}

As you see I’m retrieving a pointer to the QApplication by using the qApp macro and connecting the onHotkey() slot at the hotkey(bool) signal. That’s all.

And now the interesting part: our event filter which will catch the hotkey press and release events. The header is pretty simple:

#ifndef GLOBALEVENTFILTER_H
#define GLOBALEVENTFILTER_H

#include <qobject>


const Qt::KeyboardModifiers HOTKEY = Qt::ShiftModifier | Qt::ControlModifier;


class GlobalEventFilter : public QObject
{
    Q_OBJECT

public:
    GlobalEventFilter(QObject* parent);
    bool eventFilter(QObject* object, QEvent* event);

private:
    bool hotkey;
};

#endif // GLOBALEVENTFILTER_H

The implementation instead needs to be explained step. The constructor just initialise the hotkey private flag, I’ll explain its purpose later.

GlobalEventFilter::GlobalEventFilter(QObject* parent):
    QObject(parent)
{
    hotkey = false;
}

The first part of the eventFilter() method implementation just casts the parent and exits if it’s not a instance of MyApplication

bool GlobalEventFilter::eventFilter(QObject* object, QEvent* event) {
    // Skip if parent is not a custom application
    MyApplication* app = dynamic_cast<myapplication *>(parent());

    if (app == NULL) {
        return false;
    }

Now I check if the event is a KeyPressEvent and if the current modifiers flags matches our hotkey.

    // Check key press event
    if (event->type() == QEvent::KeyPress) {
        if (!hotkey && static_cast<qkeyevent *>(event)->modifiers() == HOTKEY) {
            // Emit signal
            app->notifyHotkeyStatus(true);

            // Store highlight status
            hotkey = true;

            // Stop propagation
            return true;
        }
    }

I’m using the value of the hotkey flag to emit the hotkey(bool) signal only once on key press; no more hotkey(bool) signals will be emitted until the hotkey will be release.

    // Check key release event
    if (event->type() == QEvent::KeyRelease) {
        // Emit highlightHelp(false) if hotkey's combination is not pressed
        // and they where active before
        if (hotkey && static_cast<qkeyevent *>(event)->modifiers() != HOTKEY) {
            // Emit signal
            app->notifyHotkeyStatus(false);

            // Reset highlight status
            hotkey = false;

            // Stop propagation
            return true;
        }
    }

The key release event check its specular to the key press event check: I check if the hotkey was pressed before and, if the current modifiers doesn’t matches the hotkey, we emit the hotkey(bool) signal and reset the flag.

The last line of code of the eventFilter() is trivial:

    // Propagate event
    return false;
}

Now it’s time to put al together and run a small test. I’ll create a MyApplication instance with my custom event filter, and two main windows instances as demonstration:

// ** main.cpp **

int main(int argc, char *argv[])
{
    MyApplication a(argc, argv);
    a.installEventFilter(new GlobalEventFilter(a.instance()));

    MainWindow w1, w2;

    w1.show();
    w2.show();

    return a.exec();
}

Run the code and when you will press the hotkey combination (SHIFT+CTRL or SHIFT+CMD on macOS) the windows’ background will change to green.

Well, anyway I didn’t all this work just to change a window’s backgrounds :-)!

Here you can download the source code of this article. Have fun with it!