![](http://habrastorage.org/getpro/habr/post_images/1f7/38a/910/1f738a910803f7f4e372d65e8aa28657.jpg)
В перечислении Qt::Key для события QKeyEvent определены 15 типов клавиш управления мультимедиа (см. таблицу в конце статьи). Все они могут быть использованы в фильтре событий (installEventFilter) для обработки нажатия клавиш на мультимедийной клавиатуре (позволяющей управлять аудиоустройством и воспроизведением).
В статье рассмотрена обратная задача — отправка в систему команд управления мультимедиа путем эмуляции нажатия соответствующих клавиш в Windows, Linux и MacOSX (операционные системы упорядочены по времени, потраченному на поиск решения). Представленный в статье материал может быть отправной точкой к дальнейшему изучению вопроса кроссплатформенной отправки событий в цикл обработки системных сообщений.
Прежде чем перейти непосредственно к описанию исходного кода давайте рассмотрим, где вообще может применяться эмуляция мультимедийных клавиш, например:
- создание виджетов управления воспроизведением аудио;
- эмуляция пользовательского ввода при тестировании приложений;
- создание приложения для удаленного управления аудиосистемой. В данном случае приложение на компьютере выступает в качестве сервера, а клиентом является смартфон. Такая связка позволит управлять аудио/видео не вставая с дивана или, например, автоматически ставить на паузу воспроизведение при поступлении входящего звонка;
- создание приложения, «расшаривающего» клавиатуру и мышь на два и более компьютеров по сети (предварительно дополнив представленный код до полного набора клавиш);
- создание систем «умный дом», новых человеко-машинных интерфейсов (управление голосовыми командами и др.).
Так как QEvent позволяет отправлять сообщение только конкретному объекту «внутри» приложения, то стандартными средствами Qt эмулировать нажатия клавиш не получится. Для этого будем использовать системные вызовы Api (в случае с Windows) или соответствующие библиотеки (X Window System в Linux и ряд фреймворков в Mac OS X).
Для удобства описания почти всю реализацию отправки сообщений разместим в функции sendKeyEventToSystem(Qt::Key qtKey), которой передается код клавиши из перечисления Qt::Key. Данная функция и будет вызываться из слотов, например:
void playPauseToogle() { //слот переключения между воспроизведением и паузой postKeyEventToSystem(Qt::Key_MediaTogglePlayPause); }
Платформенно-зависимый код будем отделять директивами #ifdef OS_TYPE и #endif (еще часть кода на Objective-C вынесем в отдельный файл macx.mm, но об этом позже).
Эмуляция нажатия клавиатуры в Windows
В данной операционной системе за отправку сообщений отвечает функция SendInput. Она позволяет отправлять сообщения с кодами, полный перечень которых представлен на странице MSDN Virtual-Key Codes.
Для использования этой функции необходимо подключить заголовочный файл <Windows.h>.
#ifdef Q_OS_WIN32 #define WINVER 0x0500 #include <Windows.h> #endif
Примеров использования данной функции в интернете много и ее применение не должно вызвать проблем, поэтому сразу привожу код (в части Windows):
sendKeyEventToSystem(Qt::Key qtKey) { //функция эмуляции нажатия клавиши. qtKey - тип клавиши #ifdef Q_OS_WIN32 INPUT ip; //устройство ввода ip.type = INPUT_KEYBOARD; ip.ki.wScan = 0; ip.ki.time = 0; ip.ki.dwExtraInfo = 0; //в зависимости от типа клавиши switch (qtKey) { case Qt::Key_MediaPrevious: ip.ki.wVk = VK_MEDIA_PREV_TRACK; //предыдущий трек break; case Qt::Key_MediaTogglePlayPause: ip.ki.wVk = VK_MEDIA_PLAY_PAUSE; //для переключения режима воспроизведения break; case Qt::Key_MediaNext: ip.ki.wVk = VK_MEDIA_NEXT_TRACK; //следующий трек break; default: return; break; } //посылаем событие нажатия клавиши ip.ki.dwFlags = 0; SendInput(1, &ip, sizeof(INPUT)); //а затем отжатия клавиши ip.ki.dwFlags = KEYEVENTF_KEYUP; SendInput(1, &ip, sizeof(INPUT)); #endif }
Здесь и далее в примерах будут использоваться всего 3 клавиши. В конце статьи приведена таблица соответствия кодов.
Эмуляция нажатия клавиатуры в Linux
Для эмуляции клавиш в Linux, на мой взгляд, проще всего использовать библиотеку разработчиков libXtst (X11 Record extension library).
Для ее получения из пакетов необходимо выполнить команду:
sudo apt-get install libxtst-dev
Также будет необходимо подключить библиотеку в файле проекта:
unix:!macx:LIBS += -lXtst -lX11
В начале файла подключим необходимые заголовочные файлы и определим ряд констант, соответствующих кодам мультимедийных клавиш (дело в том, что в файле X11/keysymdef.h коды для мультимедийных клавиш отсутствуют).
#ifdef Q_OS_LINUX #include <X11/Xlib.h> #include <X11/extensions/XTest.h> #define XF86AudioLowerVolume 0x1008ff11 #define XF86AudioMute 0x1008ff12 #define XF86AudioRaiseVolume 0x1008ff13 #define XF86AudioPlay 0x1008ff14 #define XF86AudioStop 0x1008ff15 #define XF86AudioPrev 0x1008ff16 #define XF86AudioNext 0x1008ff17 #define XF86AudioPause 0x1008ff31 #endif
Код эмуляции в части Linux:
#ifdef Q_OS_LINUX unsigned int key; unsigned int keycode; switch (qtKey) { case Qt::Key_MediaPrevious: key = XF86AudioPrev; break; case Qt::Key_MediaTogglePlayPause: key = XF86AudioPlay; break; case Qt::Key_MediaNext: key = XF86AudioNext; break; default: return; break; } // подключаемся к X Display *display; display = XOpenDisplay(NULL); // получаем код клавиши keycode = XKeysymToKeycode(display, key); // эмулируем нажатие клавиши XTestFakeKeyEvent(display, keycode, 1, 0); // эмулируем отжатие клавиши XTestFakeKeyEvent(display, keycode, 0, 0); // очищаем буфер X XFlush(display); // отключаемся от X XCloseDisplay(display); #endif
Эмуляция нажатия клавиатуры в Mac OS X
Попытки найти решение для MacOS X не приносили плодов (скупые примеры были написаны на Objective-C), до тех пор, пока в гугле не натолкнулся на статью с хабра Интеграция приложений Qt в среду Mac OS X (с использованием Cocoa и Objective-C++). Мне уже ранее попадалась англоязычная статья, в которой описывалось, как изолировать код C++ для использования в Objective-C приложении. Мне же нужно было совершенно противоположное — изолировать Objective-C код (который выполнял нужные мне функции, но в то же время на который ругался компиллятор). Все оказалось достаточно просто:
1. создал файл macx.mm и разместил в нем Objective-С код (при этом в файле проекта автоматически появилась строка
OBJECTIVE_SOURCES += macx.mm
2. создал файл macx.h и разместил в нем объявление функции из macx.mm (добавив #include «macx.h» в macx.mm).
3. в файле проекта добавил подключение необходимых фреймворков, в частности:
macx:LIBS += -framework ApplicationServices -framework IOKit
4. Внутри макроса условной компиляции для Mac OS X добавил необходимые хидеры и macx.h.
5. В уже знакомой вам switch-case структуре вставил вызовы новоиспеченной функции.
Таким образом в начале файла у нас появилась конструкция для Mac OS X:
#ifdef Q_OS_MAC #include <ApplicationServices/ApplicationServices.h> //оставил для типа UInt8 #include <IOKit/hidsystem/ev_keymap.h> //коды клавиш #include "mac.h" //определение функции для вызова Objective-C кода #endif
В функцию sendKeyEventToSystem добавляется следующий код:
#ifdef Q_OS_MAC switch (qtKey) { case Qt::Key_MediaPrevious: HIDPostAuxKey( NX_KEYTYPE_PREVIOUS ); break; case Qt::Key_MediaTogglePlayPause: HIDPostAuxKey( NX_KEYTYPE_PLAY ); break; case Qt::Key_MediaNext: HIDPostAuxKey( NX_KEYTYPE_NEXT ); break; default: return; break; } #endif
Cодержимое файла mac.mm:
#import <Cocoa/Cocoa.h> #import <IOKit/hidsystem/IOHIDLib.h> #import <IOKit/hidsystem/ev_keymap.h> #include "macx.h" static io_connect_t get_event_driver(void) { static mach_port_t sEventDrvrRef = 0; mach_port_t masterPort, service, iter; kern_return_t kr; if (!sEventDrvrRef) { // Get master device port kr = IOMasterPort( bootstrap_port, &masterPort ); check( KERN_SUCCESS == kr); kr = IOServiceGetMatchingServices( masterPort, IOServiceMatching( kIOHIDSystemClass ), &iter ); check( KERN_SUCCESS == kr); service = IOIteratorNext( iter ); check( service ); kr = IOServiceOpen( service, mach_task_self(), kIOHIDParamConnectType, &sEventDrvrRef ); check( KERN_SUCCESS == kr ); IOObjectRelease( service ); IOObjectRelease( iter ); } return sEventDrvrRef; } void HIDPostAuxKey(const UInt8 auxKeyCode ) { NXEventData event; kern_return_t kr; IOGPoint loc = { 0, 0 }; // Сообщение о нажатии клавиши UInt32 evtInfo = auxKeyCode << 16 | NX_KEYDOWN << 8; bzero(&event, sizeof(NXEventData)); event.compound.subType = NX_SUBTYPE_AUX_CONTROL_BUTTONS; event.compound.misc.L[0] = evtInfo; kr = IOHIDPostEvent( get_event_driver(), NX_SYSDEFINED, loc, &event, kNXEventDataVersion, 0, FALSE ); check( KERN_SUCCESS == kr ); // Сообщение о отжатии клавиши evtInfo = auxKeyCode << 16 | NX_KEYUP << 8; bzero(&event, sizeof(NXEventData)); event.compound.subType = NX_SUBTYPE_AUX_CONTROL_BUTTONS; event.compound.misc.L[0] = evtInfo; kr = IOHIDPostEvent( get_event_driver(), NX_SYSDEFINED, loc, &event, kNXEventDataVersion, 0, FALSE ); check( KERN_SUCCESS == kr ); }
Заключение:
Для меня было немного странно, что до сих пор не существует открыто доступной кроссплатформенной библиотеки, позволяющей выполнять отправку сообщений (в том числе события клавиатуры, мыши и д.р.). Во всяком случае мне такую библиотеку так и не получилось найти. Представленный код далек от совершенства (представляю, как разрастется switch-case последовательность при добавлении новых клавиш). Но тем не менее пусть это будет небольшой вклад в общую копилку базы знаний о написании кросплатформенных приложений.
В ходе работы над статьей было замечено, что VirtualBox перехватывает нажатия мультимедийных клавиш (проверялось на Ubuntu — c «железа» все работало). Данного недостатка лишена WMWare (проверялось на Mac OS X).
Приложение: Перечень мультимедийных клавиш и их определений (с помощью #define).
Qt::Key | Windows | Linux | Mac OS X |
---|---|---|---|
Qt::Key_VolumeDown | VK_VOLUME_DOWN | XF86AudioLowerVolume | NX_KEYTYPE_SOUND_DOWN |
Qt::Key_VolumeMute | VK_VOLUME_MUTE | XF86AudioMute | NX_KEYTYPE_MUTE |
Qt::Key_VolumeUp | VK_VOLUME_UP | XF86AudioRaiseVolume | NX_KEYTYPE_SOUND_UP |
Qt::Key_BassBoost | |||
Qt::Key_BassUp | |||
Qt::Key_BassDown | |||
Qt::Key_TrebleUp | |||
Qt::Key_TrebleDown | |||
Qt::Key_MediaPlay | VK_MEDIA_PLAY_PAUSE | XF86AudioPlay | NX_KEYTYPE_PLAY |
Qt::Key_MediaStop | VK_MEDIA_STOP | XF86AudioStop | |
Qt::Key_MediaPrevious | VK_MEDIA_PREV_TRACK | XF86AudioPrev | NX_KEYTYPE_PREVIOUS |
Qt::Key_MediaNext | VK_MEDIA_NEXT_TRACK | XF86AudioNext | NX_KEYTYPE_NEXT |
Qt::Key_MediaRecord | |||
Qt::Key_MediaPause | XF86AudioPause | ||
Qt::Key_MediaTogglePlayPause | VK_MEDIA_PLAY_PAUSE | XF86AudioPlay | NX_KEYTYPE_PLAY |
Ссылки по теме:
1. Существующие подходы к решению проблемы: C++ (Qt) cross-platform library for simulating keyboard input, sendkeys, send kestrokes, etc
2. Описание функции SendInput и перечень кодов клавиатуры на сайте MSDN.
3. Simulating Mediakey Presses in C & X11 — в статье описывается, как в Linux с помощью утилиты «xev» (в Ubuntu: «sudo apt-get install x11-utils») можно узнать коды клавиш, а также в дополнение статья с archlinux.org.
4. Эмуляция мультимедийных клавиш в MacOS X на языке программирования Python.
ссылка на оригинал статьи http://habrahabr.ru/post/217779/
Добавить комментарий