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 хостинг.

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

Комментарии

Только авторизованные пользователи могут публиковать комментарии.
Пожалуйста, авторизуйтесь или зарегистрируйтесь
SH
  • Sak Hax
  • 25 апреля 2024 г. 21:00

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

  • Результат:33баллов,
  • Очки рейтинга-10
г
  • ги
  • 23 апреля 2024 г. 22:51

C++ - Тест 005. Структуры и Классы

  • Результат:41баллов,
  • Очки рейтинга-8
l
  • laei
  • 23 апреля 2024 г. 16:19

C++ - Тест 004. Указатели, Массивы и Циклы

  • Результат:10баллов,
  • Очки рейтинга-10
Последние комментарии
k
kmssr9 февраля 2024 г. 2:43
Qt Linux - Урок 001. Автозапуск Qt приложения под Linux как сделать автозапуск для флэтпака, который не даёт создавать файлы в ~/.config - вот это вопрос ))
АК
Анатолий Кононенко5 февраля 2024 г. 9:50
Qt WinAPI - Урок 007. Работаем с ICMP Ping в Qt Без строки #include <QRegularExpressionValidator> в заголовочном файле не работает валидатор.
EVA
EVA25 декабря 2023 г. 18:30
Boost - статическая линковка в CMake проекте под Windows Ошибка LNK1104 часто возникает, когда компоновщик не может найти или открыть файл библиотеки. В вашем случае, это файл libboost_locale-vc142-mt-gd-x64-1_74.lib из библиотеки Boost для C+…
J
JonnyJo25 декабря 2023 г. 16:38
Boost - статическая линковка в CMake проекте под Windows Сделал всё по-как у вас, но выдаёт ошибку [build] LINK : fatal error LNK1104: не удается открыть файл "libboost_locale-vc142-mt-gd-x64-1_74.lib" Хоть убей, не могу понять в чём дел…
G
Gvozdik19 декабря 2023 г. 5:01
Qt/C++ - Урок 056. Подключение библиотеки Boost в Qt для компиляторов MinGW и MSVC Для решения твой проблемы добавь в файл .pro строчку "LIBS += -lws2_32" она решит проблему , лично мне помогло.
Сейчас обсуждают на форуме
G
Gar22 апреля 2024 г. 12:46
Clipboard Как скопировать окно целиком в clipb?
DA
Dr Gangil Academics20 апреля 2024 г. 14:45
Unlock Your Aesthetic Potential: Explore MSC in Facial Aesthetics and Cosmetology in India Embark on a transformative journey with an msc in facial aesthetics and cosmetology in india . Delve into the intricate world of beauty and rejuvenation, guided by expert faculty and …
a
a_vlasov14 апреля 2024 г. 13:41
Мобильное приложение на C++Qt и бэкенд к нему на Django Rest Framework Евгений, добрый день! Такой вопрос. Верно ли следующее утверждение: Любое Android-приложение, написанное на Java/Kotlin чисто теоретически (пусть и с большими трудностями) можно написать и на C+…
Павел Дорофеев
Павел Дорофеев14 апреля 2024 г. 9:35
QTableWidget с 2 заголовками Вот тут есть кастомный QTableView с многорядностью проект поддерживается, обращайтесь
f
fastrex4 апреля 2024 г. 11:47
Вернуть старое поведение QComboBox, не менять индекс при resetModel Добрый день! У нас много проектов в которых используется QComboBox, в версии 5.5.1, когда модель испускает сигнал resetModel, currentIndex не менялся. В версии 5.15 при resetModel происходит try…

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