How to track keyboard usage on Windows with Qt in 2020

Qt Qml development
2020-09-28
8 minutes
track keyboard usage on Windows with Qt in 2020

There are many use cases when the frequency of user input needs to be tracked. The simple example is a time-tracking app (like Chronos) with a feature of tracking user activity based on keyboard usage. This way employer can be sure that employees are productive. At first sight, the problem seems to have a simple solution – in case of C++ class just implement eventFilter method, or when dealing with QML simply create a handler for signal Keys.onPressed.

The solution seems to work fine, but under one crucial condition – app window maintain focused. When a user switches to another app or minimises Qt app, tracking of keyboard won’t be occurring, even though the Qt app is still running.

How to track keyboard usage in the background using Qt and QML on Windows, then? We will show you the solution for Windows which has this feature. Let’s break it into pieces to make sure that you will know how it is working from inside out. 

First, if you do not know Qt framework and what features it contains, check out this blog post.

 

The libraries

On the beginning, you should create a C++ class that will take care of tracking keyboard. As the solution needs to be accessible from QML level, the C++ class need to inherit from QObject. Initially, your class should look like this:

 

#include <QObject>

class KeyboardMonitor : public QObject
{
    Q_OBJECT
public:
    explicit KeyboardMonitor(QObject *parent = nullptr);

signals:
};

To make background keyboard tracking possible you will need to include winuser header to your project. This library is part of Win32 API, so no additional library linking is needed. You can read more about it in the documentation. However, to make this library work you need also to add windows header.

 

#include <QObject>
#include <windows.h>
#include <winuser.h>

class KeyboardMonitor : public QObject
{
    Q_OBJECT
public:
    explicit KeyboardMonitor(QObject *parent = nullptr);

signals:
};

 

Checking keys state

Now you have all the necessary components to begin tracking. First of all, you need to check the state of keys, to know if they’re pressed or not. GetAsyncKeyState function will be most suitable for this. How does this function work? The best answer can be found in the documentation:

If the function succeeds, the return value (SHORT type) specifies whether the key was pressed since the last call to GetAsyncKeyState, and whether the key is currently up or down. If the most significant bit is set, the key is down, and if the least significant bit is set, the key was pressed after the previous call to GetAsyncKeyState. However, you should not rely on this last behavior.

The function takes the virtual-key code as a parameter, to determine which key to check. The virtual-key codes range from 1 to 254. As quoted above to determine if the key was pressed, you need to verify if the most significant bit is set. To do so, you can use bitwise AND operation (more about it here) with the value of key state returned by GetAsyncKeyState and expected value. As the returned value is a SHORT type it has a length of 16-bits. The decimal value of short with only most significant bit set is 32 768 which equals to 0x100000 in the hexadecimal positional system.

The first step will be to count the number of keys pressed by the user. The simplest way is to just loop through all the keys and check if the value of their state has the most significant bit set. If the bit is set, the number of pressed keys is incremented. You can store it in some variable like numberOfActions. The function (trackActions sounds like a good name) should look similar to this:

 

void KeyboardMonitor::trackActions() {
    for (int i = 1; i < 255; ++i) {
        if(GetAsyncKeyState(i) & 0x100000) {
            _numberOfActions++;
        }
    }
}

 

Counting

Right now, you can count the number of user key presses, but the function needs to be called manually. If you just put it in the infinite loop the whole app could just freeze due to GUI thread being blocked (if you are not implementing any kind of multi-threading). Do not worry – there is an uncomplicated solution that fits perfectly into the Qt philosophy.

All you need to do is to create an instance of QTimer object (call it _timer for instance) and connect the timeout signal to trackActions function which you just created. This way, every time the timer triggers, trackActions is called. The connection can be created inside class constructor by adding just a single line:

 

connect(_timer, &QTimer::timeout, this, &KeyboardMonitor::trackActions);

Now you should set the timer interval. This will indicate how often the keys states would be checked. According to research done by Kevin Killourhy and Roy Maxion (more information about it here), the statistical average time of a single key-press is about 100 ms. We will stick to this interval. What is left to start a timer. At this point class constructor should look much like this:

 

KeyboardMonitor::KeyboardMonitor(QObject *parent) : QObject(parent)
  , _numberOfActions(0)
  , _timer(new QTimer(this)) //timer object initialization
{
    //Connecting timer to function that handles keboard tracking
    connect(_timer, &QTimer::timeout, this, &KeyboardMonitor::trackActions);

    _timer->setInterval(100);
    _timer->start();
}

 

 

Usage statistics

Key presses counting is not the most elegant way to present user activity. What if the app counts a lot of activity short after launching, and after that, no input would be recorded? The result will look normal, due to the lack of reference to the time when the keys were pressed.

The solution is to create a variable that will store the count of key presses occurred only in a single second – the history of values can be stored any other dynamic data structure. For now, let’s only create a variable to store that value. If you want to see how to implement vector containing history check Scythe-Studio Github page.

APS looks like a good name for a new member variable. This acronym stands for Actions Per Second. While defining variable try to use Q_PROPERTY macro – it will help us a lot when trying to expose this variable to QML later. Simply create a property macro and use refactor option in the context menu to auto-generate setter, getter and signal.

Using Qt Creator refactor option

As we want to update APS value after each second, we will need to wait for several timer iterations to pass before the update. In this case, 1 second passes after 10 of 100-millisecond intervals. The same effect could be achieved by using an additional timer with iteration set to 1 sec, although there is a catch. As every timer works on a separate thread some synchronisation mechanism (like mutex) would be needed. We want to keep code as simple as possible, so we will stick to determining time passing with an iterator.

Now all you need to do inside trackActions function is to add increment instruction for iterator and add APS variable update when the iterator reaches a value of 9 (since counting from 0):

 

void KeyboardMonitor::trackActions() {
    for (int i = 1; i < 255; ++i) {
        // The bitwise and selects the function behavior
        if(GetAsyncKeyState(i) & 0x100000) {
            //If the button is pressed number of action is increased
            _numberOfActions++;
        }
    }

    if(_secIterator == 9) {
        _secIterator = 0;
        setAps(_numberOfActions);
        _numberOfActions = 0;
    }
}

 

Exposing to QML

At this point, you have a fully functional monitor that can be accessed from C++. In case you want to show the result of tracking in QML GUI, you need to make accessing monitor class from the QML level possible. Thanks to previous additions this will be pretty easy.

In the first step, you need to create an instance of a monitor in main.cpp file. After that, you should provide context for the engine to make monitor accessible from QML. This can be done using setContextProperty function, that needs QQmlContext header to be included.

 

KeyboardMonitor* keyboardMonitor = new KeyboardMonitor();

QQmlApplicationEngine engine;

//First parameter is name of object that you will use to access it from QML, second is a pointer to it
engine.rootContext()->setContextProperty("keyboardMonitor", keyboardMonitor);

Now you can access monitor object in any QML file inside your project. Thanks to the Q_PROPERTY macro used before, all the variables decorated with it can be accessed like standard QML properties, including property binding, and using signals like onValueChanged.

 

import QtQuick 2.12
import QtQuick.Window 2.12
import QtQuick.Controls 2.12

ApplicationWindow {
  visible: true
  width: 640
  height: 480
  title: qsTr("Hello World")

  Page {
    id: mainPage
    anchors.fill: parent

    Text {
      id: apsLabel

      anchors {
        bottom: apsValue.top
        horizontalCenter: parent.horizontalCenter
        bottomMargin: 40
      }

      font.pointSize: 30
      text: qsTr("APS")
    }

    Text {
      id: apsValue
      anchors.centerIn: parent

      font.pointSize: 60
      text: keyboardMonitor.aps
    }
  }
}

Now you can see how the app works:

gif with working app

 

Learn more

If you are still hungry for knowledge you can find the whole project and source code with some extra features on Scythe-Studio Github page. Don’t forget to like Scythe-Studio Facebook page to get notifications about new posts!

 

Disclaimer

Scythe-Studio does not bear responsibility for inappropriate or malicious use of presented solutions and code. The whole content of the post has fully educational purpose.

Scythe-Studio - Blog Redactors

Scythe Studio Blog Redactors

Need Qt QML development services?

service partner

Let's face it? It is a challenge to get top Qt QML developers on board. Help yourself and start the collaboration with Scythe Studio - real experts in Qt C++ framework.

Discover our capabilities

Latest posts

[ 134 ]