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

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

Комментарии

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

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

  • Результат:10баллов,
  • Очки рейтинга-10
K
  • KiRi4
  • 7 сентября 2023 г. 17:57

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

  • Результат:41баллов,
  • Очки рейтинга-8
K
  • KiRi4
  • 7 сентября 2023 г. 17:49

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

  • Результат:66баллов,
  • Очки рейтинга-1
Последние комментарии
IscanderChe
IscanderChe13 сентября 2023 г. 19:11
Пример использования QScintilla C++ По горячим следам (с другого форума вопрос задали, пришлось в памяти освежить всё) решил дополнить. Качаем исходники с https://riverbankcomputing.com/software/qscintilla/downlo…
Evgenii Legotckoi
Evgenii Legotckoi6 сентября 2023 г. 17:18
Qt/C++ - Урок 048. QThread - работа с потоками с помощью moveToThread Разве могут взаимодействовать объекты из разных нитей как-то, кроме как через сигнал-слоты?" Могут. Выполняя оператор new , Вы выделяете под объект память в куче (heap), …
AC
Andrei Cherniaev5 сентября 2023 г. 13:37
Qt/C++ - Урок 048. QThread - работа с потоками с помощью moveToThread Я поясню свой вопрос. Выше я писал "Почему же в методе MainWindow::on_write_1_clicked() Можно обращаться к методам exampleObject_1? Разве могут взаимодействовать объекты из разных…
n
nvn31 августа 2023 г. 19:47
QML - Урок 004. Сигналы и слоты в Qt QML Здравствуйте! Прекрасный сайт, отличные статьи. Не хватает только готовых проектов для скачивания. Многих комментариев типа appCore != AppCore просто бы не было )))
NSProject
NSProject24 августа 2023 г. 23:40
Django - Урок 023. Like Dislike система с помощью GenericForeignKey Ваша ошибка связана с gettext from django.utils.translation import gettext_lazy as _ Поле должно выглядеть так vote = models.SmallIntegerField(verbose_name=_("Голос"), choices=VOTES) …
Сейчас обсуждают на форуме
IscanderChe
IscanderChe17 сентября 2023 г. 19:24
Интернационализация строк в QMessageBox Странная картина... Сделал минимально работающий пример - всё работает. Попробую на другой операционке. Может, дело в этом.
NSProject
NSProject17 сентября 2023 г. 18:49
Помогите добавить Ajax в проект В принципе ничего сложного с отправкой на сервер нет. Всё что ты хочешь отобразить на странице передаётся в шаблон и рендерится. Ты просто создаёшь файл forms.py в нём описываешь свою форму и в …
BlinCT
BlinCT15 сентября 2023 г. 22:35
Размеры полей в TreeView Всем привет. Пытаюсь сделать дерево вот такого вида Пытаюсь организовать делегат для каждой строки в дереве. ТО есть отступ какого то размера и если при открытии есть под…
IscanderChe
IscanderChe8 сентября 2023 г. 22:07
Кастомная QAbstractListModel и цвет фона, цвет текста и шрифт Похоже надо не абстрактный , а "реальный" типа QSqlTableModel Да, но не совсем. Решилось с помощью стайлшитов и setFont. Спасибо за отлик!
Evgenii Legotckoi
Evgenii Legotckoi6 сентября 2023 г. 16:35
Вопрос: Нужно ли в деструкторе удалять динамически созданные QT-объекты. Напр: Зависит от того, как эти объекты были созданы. Если вы передаёте указатель на parent объект, то не нужно, Ядро Qt само разрулит удаление, если нет, то нужно удалять вручную, иначе будет ут…

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