Implementing Global Hotkey Support in QT on Windows

If your application needs a global hotkey support, QT will leave you hanging. Fortunately, on high-level this is a rather simple function:

bool registerHotkey(QKeySequence keySequence) {
  auto key = Qt::Key(keySequence[0] & static_cast<int>(~Qt::KeyboardModifierMask));
  auto modifiers = Qt::KeyboardModifiers(keySequence[0] & static_cast<int>(Qt::KeyboardModifierMask));

  return nativeRegisterHotkey(key, modifiers);
}

Essentially the only work is splitting key and modifiers into their own variables and then having platform-specific code handling the nasty bits.

For Windows, this is just a simple conversion of modifiers and keys followed by a call to RegisterHotKey API:

bool nativeRegisterHotkey(Qt::Key key, Qt::KeyboardModifiers modifiers) {
  uint modValue = 0;
  if (modifiers &  Qt::AltModifier) { modValue += MOD_ALT; }
  if (modifiers &  Qt::ControlModifier) { modValue += MOD_CONTROL; }
  if (modifiers &  Qt::ShiftModifier) { modValue += MOD_SHIFT; }

  uint keyValue;
  if (((key >= Qt::Key_A) && (key <= Qt::Key_Z)) || ((key >= Qt::Key_0) && (key <= Qt::Key_9))) {
    keyValue = key;
  } else if ((key >= Qt::Key_F1) && (key <= Qt::Key_F24)) {
    keyValue = VK_F1 + (key - Qt::Key_F1);
  } else {
    return false; //unsupported key
  }

  return RegisterHotKey(nullptr, _hotkeyId, modValue, keyValue);
}

But this alone is nothing without actual QAbstractNativeEventFilter-based event filter. Here we need to intercept message and emit the signal:

bool nativeEventFilter(const QByteArray&, void* message, long*) {
  MSG* msg = static_cast<MSG*>(message);
  if (msg->message == WM_HOTKEY) {
    if (msg->wParam == static_cast<WPARAM>(_hotkeyId)) {
      emit activated();
      return true;
    }
  }
  return false;
}

Mind you, this is a rather incomplete and simplified example. Full code (supporting both Windows and Linux) is available for download.

To use it, just assign instance to a long living variable, register a key sequence, and hook into activated signal:

_hotkey = new Hotkey();
_hotkey->registerHotkey(QKeySequence { "Ctrl+Shift+F1" });
connect(_hotkey, SIGNAL(activated()), this, SLOT(onActivated()));


PS: X11 variant of this code is available here.

Leave a Reply

Your email address will not be published.

This site uses Akismet to reduce spam. Learn how your comment data is processed.