Работа с глобальными хоткеями в 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% вероятностью не сможете перехватить эту клавишу, пока не убъёте эту программу или каким-либо иным способом не заставите её отпустить этот хоткей.