Evgenii Legotckoi
Evgenii Legotckoi28 февраля 2016 г. 10:17

Qt Linux - Урок 002. Глобальный HotKey в Linux в приложении на Qt 5

Работа с глобальными хоткеями в Windows является более тривиальной задачей, чем в Linux, поскольку в WinAPI имеются для этого подготовленные методы, которые не требуют большого количества кода. А также каждому хоткею устанавливается в соответствие ID , по которому данный хоткей можно удалить.

В Linux/Unix , которые используют графический сервер X11, применительно к Qt необходимо использовать функции для регистрации/разрегистрации хоткеев из библиотеки XLib, но обрабатывать приходится получаемые значения с помощью функционала библиотеки XCB, которая разрабатывается как аналог библиотеки XLib , но является более низкоуровневой и написана на языке программирования Си. Как и в случае с Windows, для обработки глобальных хоткеев в Qt 5.5 используется метод nativeEventFilter. Предлагаю сделать отдельный класс, наследованный от QAbstractNativeEventFilter для обработки хоткеев и установить данный фильтр на всё приложение.

Для установки хоткеев будут использоваться функции XKeysymToKeycode (для получения кода клавиши из последовательности KeySym) и XGrabKey (для установки хоткея).

Для разрегистрации хоткея будет использоваться функция XUngrabKey .

Структура проекта

  • GlobalHotkeyLinux.pro - профайл проекта;
  • mainwindow.h - заголовочный файл главного окна приложения;
  • mainwindow.cpp - файл исходных кодов главного окна приложения;
  • mainwindow.ui - файл формы главного окна приложения;
  • main.cpp - основной файл исходных кодов;
  • nativeeventfilter.h - заголовочный файл фильтра событий хоткеев;
  • nativeeventfilter.cpp - файл исходных кодов фильтра событий хоткеев.

GlobalHotkeyLinux.pro

Обращаю Ваше внимание на то, что для получения информации о событиях от оконного сервера X11 в Qt используется класс QX11Info , а для его использования необходимо подключить модуль x11extras, но и этого может быть недостаточно, поскольку в стандартной поставке Qt5.5 этих библиотек попросту не будет, поэтому их нужно будет установить следующей командой:

sudo apt-get install qtx11extras5-dev-tools

Помимо этого для работы с библиотеками xcb и xlib будет необходимо указать конфигурацию проекта с учётом X11:

CONFIG    += link_pkgconfig
PKGCONFIG += x11

После чего получим профайл со следующим содержанием.

#-------------------------------------------------
#
# Project created by QtCreator 2016-02-12T23:41:57
#
#-------------------------------------------------

QT       += core gui
QT       += x11extras

greaterThan(QT_MAJOR_VERSION, 4): QT += widgets

CONFIG    += c++11
CONFIG    += link_pkgconfig
PKGCONFIG += x11

TARGET = GlobalHotkeyLinux
TEMPLATE = app


SOURCES += main.cpp\
        mainwindow.cpp \
    nativeeventfilter.cpp

HEADERS  += mainwindow.h \
    nativeeventfilter.h

FORMS    += mainwindow.ui

nativeeventfilter.h

В Qt существуют классы фильтров событий, которые можно устанавливать на окна приложения, компоненты или сразу на всё приложение, как Мы и поступим в этот раз. Для этого потребуется создать класс, наследованный от QAbstractNativeEventFilter, который служит для получения событий от операционной системы, в которой запускается приложение на Qt. Переопределим метод natvieEventFilter, в котором и будем обрабатывать хоткей, а также сделаем два метода: один для установки хоткея, а второй для его отключения.

#ifndef NATIVEEVENTFILTER_H
#define NATIVEEVENTFILTER_H

#include <QObject>
#include <QAbstractNativeEventFilter>

class NativeEventFilter : public QObject, public QAbstractNativeEventFilter
{
    Q_OBJECT
public:
    explicit NativeEventFilter(QObject *parent = 0);

    // переопределяем метод nativeEventFilter
    bool nativeEventFilter(const QByteArray &eventType, void *message, long *result);
    void setShortcut();     // Добавляем метод установки хоткея
    void unsetShortcut();   // и метод удаления хоткея для полноты картины

signals:
    void activated();

public slots:
};

#endif // NATIVEEVENTFILTER_H

nativeeventfilter.cpp

Вся соль создания фильтра в том, что мы без проблем можем зарегистрировать хоткей, используя библиотеку XLib, тогда как разбирать события придётся с применением библиотеки XCB, поскольку Qt, получая данные от X11, понятия не имеет о них в разрезе Xlib, а понимает эти данные как XCB.

#include "nativeeventfilter.h"
#include <QVector>
#include <QX11Info>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <xcb/xcb.h>

namespace
{
    Display * m_display;        // Соединение с сервером X11
    Window m_win;               // Захватываемое окно - в данном случае будет вся система
    int keycode;                // код клавиши
    unsigned int modifier;      // код модификаторов

    /* Вектор дополнительных модификаторов Num Lock, Caps lock
     * Они тоже учитываются в X11, поэтому понадобяться все возможные комбинации
     * */
    QVector<quint32> maskModifiers(){
        return QVector<quint32>() << 0 << Mod2Mask << LockMask << (Mod2Mask | LockMask);
    }
}

NativeEventFilter::NativeEventFilter(QObject *parent) : QObject(parent)
{
    m_display = QX11Info::display();        // Создадим подключение к серверу
    m_win = DefaultRootWindow(m_display);   // и вытащим из него захватываемое окно с помощью макроса
}

bool NativeEventFilter::nativeEventFilter(const QByteArray &eventType, void *message, long *result)
{
    Q_UNUSED(eventType)
    Q_UNUSED(result)

    /* В вот обработка события строится уже на библиотеке XCB вместо Xlib.
     * Вроде как, получая событие Qt знает его в качестве XCB события,
     * но не знает его в качестве события Xlib, хотя использовать более
     * легкий синтаксис Xlib для установки хоткеев нам никто не запрещает
     * */
    xcb_key_press_event_t *keyEvent = 0;

    // Итак проверяем, что это было xcb событие
    if (eventType == "xcb_generic_event_t") {
        // кастуем сообщение собственно в xcb событие
        xcb_generic_event_t *event = static_cast<xcb_generic_event_t *>(message);

        // проверяем, что произошло нажатие клавиши
        if ((event->response_type & 127) == XCB_KEY_PRESS){

            // Если так, то кастуем сообщение в событие нажатия клавиши
            keyEvent = static_cast<xcb_key_press_event_t *>(message);

            // Далее проверям, является ли это событие нужным хоткее
            foreach (quint32 maskMods, maskModifiers()) {
                if((keyEvent->state == (modifier | maskMods ))
                        &&  keyEvent->detail == keycode){
                    emit activated();   // и посылаем сигнал
                    return true;
                }
            }
        }
    }
    return false;
}

void NativeEventFilter::setShortcut()
{
    unsetShortcut();        /* Вначале для перестраховки отключим предполагаемый хоткей,
                             * даже несмотря на то, что будет мусор в первый раз в параметрах хоткея.
                             * */

    // получим код клавиши из KeySym определения и соединения с сервером X11
    keycode = XKeysymToKeycode(m_display, XK_E);
    modifier = ControlMask; // Зададим модификатор

    /* А теперь пройдемся по всем возможным комбинациям с учётом Num Lock и Caps Lock
     * устанавливая хоткеи
     * */
    foreach (quint32 maskMods, maskModifiers()) {
        XGrabKey(m_display,         // указываем соединение с X11
                 keycode ,          // код клавиши
                 modifier | maskMods,   // модификатор со всеми возможными масками
                 m_win,             // Захватываемое окно
                 True,              // Является ли приложение владельцем события. в данном примере не принципиально.
                 GrabModeAsync,     // Обязательно Ассинхронный режим обработки, иначе, рискуете встрять
                 GrabModeAsync);    // с замороженной системой, не реагирующей ни на какие воздействия, если
                                    // заранее не напишите корректную передачу события обратно в систему,
                                    // а скорее всего так и будет
    }
}

void NativeEventFilter::unsetShortcut()
{
    // Проходим по всем возможным комбинациям и удаляем хоткей
    foreach (quint32 maskMods, maskModifiers()) {
        XUngrabKey(m_display,
                   keycode,
                   modifier | maskMods,
                   m_win);
    }
}

mainwindow.h

В заголовочном файле приложения ничего особенного не видим. Только слот для приёма сигнала и сам фильтр, который будет отдавать этот сигнал.

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include "nativeeventfilter.h"

namespace Ui {
class MainWindow;
}

class MainWindow : public QMainWindow
{
    Q_OBJECT

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

private slots:
    void slotGlobalHotkey();

private:
    Ui::MainWindow *ui;
    NativeEventFilter *nativeEventFilter;
};

#endif // MAINWINDOW_H

mainwindow.cpp

Всё, что останется сделать, это инициализировать фильтр, установить его на всё приложение, подключить сигнал к слоту обработчику события и установить хоткей. Таким образом получим рабочий вариант глобального хоткея для Qt приложения для linux.

#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QApplication>
#include <QMessageBox>

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);
    nativeEventFilter = new NativeEventFilter(this);    // Инициализируем фильтр
    qApp->installNativeEventFilter(nativeEventFilter);  // Устанавилваем его на приложение
    // подключаем сигнал фильтра к слоту
    connect(nativeEventFilter, &NativeEventFilter::activated, this, &MainWindow::slotGlobalHotkey);
    nativeEventFilter->setShortcut();   // Устанавилваем хоткей
}

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

void MainWindow::slotGlobalHotkey()
{
    // И сообщаем пользователю, если он нажал нужный нам HotKey
    QMessageBox::information(this,"Global Hotkey", "Global Hotkey is worked", QMessageBox::Ok);
}

Архив с проектом: GlobalHotkeyLinux

Итог

В результате получим приложение, которое будет реагировать на глобальный хоткей Ctrl+E и показывать сообщение, о том, что хоткей работает, даже если Вы свернёте приложение или будете работать с другим приложением. Только не пытайтесь захватить нажатие клавиши PrintScreen . Это не сработает скорее всего. Причина в том, что когда с помощью XGrabKey попытаться захватить какой-то хоткей, то он будет захвачен только в том случае, если какое-либо другое клиентское приложение ещё не захватило этот хоткей на целевом захватываемом окне. А если учесть, что в том же самом дистрибутиве Ubuntu уже есть приложение для создания скриншотов, которое как раз и реагирует на клавишу PrintScreen, то Вы со 100% вероятностью не сможете перехватить эту клавишу, пока не убъёте эту программу или каким-либо иным способом не заставите её отпустить этот хоткей.

Рекомендуем хостинг TIMEWEB
Рекомендуем хостинг TIMEWEB
Стабильный хостинг, на котором располагается социальная сеть EVILEG. Для проектов на Django рекомендуем VDS хостинг.

Вам это нравится? Поделитесь в социальных сетях!

Комментарии

Только авторизованные пользователи могут публиковать комментарии.
Пожалуйста, авторизуйтесь или зарегистрируйтесь
B

C++ - Тест 002. Константы

  • Результат:16баллов,
  • Очки рейтинга-10
B

C++ - Тест 001. Первая программа и типы данных

  • Результат:46баллов,
  • Очки рейтинга-6
FL

C++ - Тест 006. Перечисления

  • Результат:80баллов,
  • Очки рейтинга4
Последние комментарии
k
kmssr9 февраля 2024 г. 5:43
Qt Linux - Урок 001. Автозапуск Qt приложения под Linux как сделать автозапуск для флэтпака, который не даёт создавать файлы в ~/.config - вот это вопрос ))
АК
Анатолий Кононенко5 февраля 2024 г. 12:50
Qt WinAPI - Урок 007. Работаем с ICMP Ping в Qt Без строки #include <QRegularExpressionValidator> в заголовочном файле не работает валидатор.
EVA
EVA25 декабря 2023 г. 21:30
Boost - статическая линковка в CMake проекте под Windows Ошибка LNK1104 часто возникает, когда компоновщик не может найти или открыть файл библиотеки. В вашем случае, это файл libboost_locale-vc142-mt-gd-x64-1_74.lib из библиотеки Boost для C+…
J
JonnyJo25 декабря 2023 г. 19:38
Boost - статическая линковка в CMake проекте под Windows Сделал всё по-как у вас, но выдаёт ошибку [build] LINK : fatal error LNK1104: не удается открыть файл "libboost_locale-vc142-mt-gd-x64-1_74.lib" Хоть убей, не могу понять в чём дел…
G
Gvozdik19 декабря 2023 г. 8:01
Qt/C++ - Урок 056. Подключение библиотеки Boost в Qt для компиляторов MinGW и MSVC Для решения твой проблемы добавь в файл .pro строчку "LIBS += -lws2_32" она решит проблему , лично мне помогло.
Сейчас обсуждают на форуме
P
Pisych27 февраля 2023 г. 15:04
Как получить в массив значения из связанной модели? Спасибо, разобрался:))
AC
Alexandru Codreanu19 января 2024 г. 22:57
QML Обнулить значения SpinBox Доброго времени суток, не могу разобраться с обнулением значение SpinBox находящего в делегате. import QtQuickimport QtQuick.ControlsWindow { width: 640 height: 480 visible: tr…
BlinCT
BlinCT27 декабря 2023 г. 19:57
Растягивать Image на парент по высоте Ну и само собою дял включения scrollbar надо чтобы был Flickable. Так что выходит как то так Flickable{ id: root anchors.fill: parent clip: true property url linkFile p…
Дмитрий
Дмитрий10 января 2024 г. 15:18
Qt Creator загружает всю оперативную память Проблема решена. Удалось разобраться с помощью утилиты strace. Запустил ее: strace ./qtcreator Начал выводиться весь лог работы креатора. В один момент он начал считывать фай…
Evgenii Legotckoi
Evgenii Legotckoi12 декабря 2023 г. 17:48
Побуквенное сравнение двух строк Добрый день. Там случайно не высылается этот сигнал textChanged ещё и при форматировани текста? Если решиать в лоб, то можно просто отключать сигнал/слотовое соединение внутри слота и …

Следите за нами в социальных сетях