📜 ⬆️ ⬇️

Emulation of pressing multimedia keys in Windows, Linux and Mac OS X


In the Qt :: Key enumeration, 15 types of media control keys are defined for the QKeyEvent event (see the table at the end of the article). All of them can be used in the event filter (installEventFilter) to handle keystrokes on the multimedia keyboard (allowing you to control the audio device and playback).
The article discusses the inverse problem - sending multimedia control commands to the system by emulating key presses on Windows, Linux and MacOSX (operating systems are ordered by the time spent on finding a solution). The material presented in the article can be a starting point for further study of the issue of cross-platform event sending to the system message processing cycle.

Before proceeding directly to the description of the source code, let's look at where emulation of multimedia keys can be used at all, for example:

Since QEvent allows you to send a message only to a specific object "inside" the application, standard means of Qt will not work to emulate keystrokes. For this, we will use the Api system calls (in the case of Windows) or the corresponding libraries (the X Window System on Linux and a number of frameworks on Mac OS X).
For convenience of description, we will place almost the entire implementation of sending messages in the sendKeyEventToSystem function (Qt :: Key qtKey) , which is passed the key code from the Qt :: Key enumeration. This function will be called from slots, for example:
void playPauseToogle() { //      postKeyEventToSystem(Qt::Key_MediaTogglePlayPause); } 
We will separate the platform-dependent code with the #ifdef OS_TYPE and #endif directives ( we will move part of the code in Objective-C to a separate file macx.mm, but more on that later).

Keyboard emulation on Windows


In this operating system, the SendInput function is responsible for sending messages. It allows you to send messages with codes, a complete list of which is presented on the MSDN Virtual-Key Codes page.
To use this feature, you must include the header file <Windows.h>.
 #ifdef Q_OS_WIN32 #define WINVER 0x0500 #include <Windows.h> #endif 

There are many examples of using this function on the Internet and its use should not cause problems, so I immediately cite the code (in the part of 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 } 
Hereinafter, only 3 keys will be used in the examples. At the end of the article is a table of correspondence codes.

Keyboard emulation on Linux


To emulate keys in Linux, in my opinion, the easiest way is to use the libXtst developer library ( X11 Record extension library ).
To get it from the packages, run the command:
 sudo apt-get install libxtst-dev 
It will also be necessary to connect the library in the project file:
 unix:!macx:LIBS += -lXtst -lX11 

At the beginning of the file, we include the necessary header files and define a number of constants corresponding to the codes of multimedia keys (the fact is that there are no codes for multimedia keys in the X11 / keysymdef.h file).
 #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 emulation code:
 #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 

Keyboard emulation on Mac OS X


Attempts to find a solution for MacOS X did not bear fruit (scant examples were written in Objective-C), until Google came across an article from the Habr Integration of Qt applications into Mac OS X (using Cocoa and Objective-C ++) . I already came across an English-language article that described how to isolate C ++ code for use in an Objective-C application. I also needed the exact opposite - to isolate the Objective-C code (which performed the functions I needed, but at the same time the compiler cursed). Everything turned out to be quite simple:
1. created the macx.mm file and placed the Objective-C code in it (the line automatically appeared in the project file
 OBJECTIVE_SOURCES += macx.mm 

2. created the macx.h file and placed the function declaration from macx.mm in it (adding #include "macx.h" in macx.mm).
3. In the project file, add the necessary frameworks, in particular:
 macx:LIBS += -framework ApplicationServices -framework IOKit 

4. Inside the macro conditional compilation for Mac OS X added the necessary headers and macx.h.
5. In the switch-case structure already familiar to you, inserted the calls of the new function.
')
Thus, at the beginning of the file, we had a design for Mac OS X:
 #ifdef Q_OS_MAC #include <ApplicationServices/ApplicationServices.h> //   UInt8 #include <IOKit/hidsystem/ev_keymap.h> //  #include "mac.h" //    Objective-C  #endif 

The following code is added to the sendKeyEventToSystem function:
 #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 


Contents of the mac.mm file:
 #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 ); } 

Conclusion:

It was a bit strange for me that there is still no openly accessible cross-platform library that allows you to send messages (including keyboard events, mouse events, etc.). In any case, I could not find such a library. The presented code is far from perfect (imagine how the switch-case sequence grows when new keys are added). Nevertheless, let it be a small contribution to the common pool of knowledge about writing cross-platform applications.
During the work on the article, it was noticed that VirtualBox intercepts pressing of multimedia keys (tested on Ubuntu - everything worked with c hardware). This flaw is deprived of WMWare (tested on Mac OS X).

Appendix: List of multimedia keys and their definitions (using #define).

Qt :: KeyWindowsLinuxMac os x
Qt :: Key_VolumeDownVK_VOLUME_DOWNXF86AudioLowerVolumeNX_KEYTYPE_SOUND_DOWN
Qt :: Key_VolumeMuteVK_VOLUME_MUTEXF86AudioMuteNX_KEYTYPE_MUTE
Qt :: Key_VolumeUpVK_VOLUME_UPXF86AudioRaiseVolumeNX_KEYTYPE_SOUND_UP
Qt :: Key_BassBoost
Qt :: Key_BassUp
Qt :: Key_BassDown
Qt :: Key_TrebleUp
Qt :: Key_TrebleDown
Qt :: Key_MediaPlayVK_MEDIA_PLAY_PAUSEXF86AudioPlayNX_KEYTYPE_PLAY
Qt :: Key_MediaStopVK_MEDIA_STOPXF86AudioStop
Qt :: Key_MediaPreviousVK_MEDIA_PREV_TRACKXF86AudioPrevNX_KEYTYPE_PREVIOUS
Qt :: Key_MediaNextVK_MEDIA_NEXT_TRACKXF86AudioNextNX_KEYTYPE_NEXT
Qt :: Key_MediaRecord
Qt :: Key_MediaPauseXF86AudioPause
Qt :: Key_MediaTogglePlayPauseVK_MEDIA_PLAY_PAUSEXF86AudioPlayNX_KEYTYPE_PLAY

Related Links:
1. Existing approaches to problem solving: C ++ (Qt) cross-platform library for simulating keyboard input, sendkeys, send kestrokes, etc
2. Description of the SendInput function and the list of keyboard codes on the MSDN website.
3. Simulating Mediakey Presses in C & X11 - this article describes how in Linux using the utility "xev" (in Ubuntu: "sudo apt-get install x11-utils"), you can learn the key codes, as well as in addition to the article with archlinux .org .
4. Emulation of multimedia keys on MacOS X in the Python programming language .

Source: https://habr.com/ru/post/217779/


All Articles