Implementing Global Hotkey Support in QT under X11

High-level description of global hotkey support is easy enough:

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 one has to split key sequence into a key and modifiers and get platform-specific code to do the actual work. For X11 this is a bit more involved and full of traps.

Inevitably, X11-specific code will have a section with conversion of key and modifiers into a X11-compatible values. For key value this has to be additionally converted from key symbols into 8-bit key codes:

bool Hotkey::nativeRegisterHotkey(Qt::Key key, Qt::KeyboardModifiers modifiers) {
  uint16_t modValue = 0;
  if (modifiers & Qt::AltModifier)     { modValue |= XCB_MOD_MASK_1; }
  if (modifiers & Qt::ControlModifier) { modValue |= XCB_MOD_MASK_CONTROL; }
  if (modifiers & Qt::ShiftModifier)   { modValue |= XCB_MOD_MASK_SHIFT; }

  KeySym keySymbol;
  if (((key >= Qt::Key_A) && (key <= Qt::Key_Z)) || ((key >= Qt::Key_0) && (key <= Qt::Key_9))) {
    keySymbol = key;
  } else if ((key >= Qt::Key_F1) && (key <= Qt::Key_F35)) {
    keySymbol = XK_F1 + (key - Qt::Key_F1);
  } else {
    return false; //unsupported key
  }
  xcb_keycode_t keyValue = XKeysymToKeycode(QX11Info::display(), keySymbol);

  xcb_connection_t* connection = QX11Info::connection();
  auto cookie = xcb_grab_key_checked(connection, 1,
                static_cast<xcb_window_t>(QX11Info::appRootWindow()),
                modValue, keyValue, XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC);
  auto cookieError = xcb_request_check(connection, cookie);
  if (cookieError == nullptr) {
    return true;
  } else {
    free(cookieError);
    return false;
  }
}

With key code and modifier bitmask ready, a call to xcb_grab_key_checked will actually do the deed, followed by some boiler plate code for error detection.

At last, we can use event filter to actually capture the key press and emit activated signal:

bool Hotkey::nativeEventFilter(const QByteArray&, void* message, long*) {
  xcb_generic_event_t* e = static_cast<xcb_generic_event_t*>(message);
  if ((e->response_type & ~0x80) == XCB_KEY_PRESS) {
    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: Windows 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.