Предыдущие части:
Логика воспроизведения событий клавиатуры на Linux-системах в целом не сильно отличается от того, что мы уже реализовали под Windows. Но есть и отличия, о которых мы поговорим по мере необходимости.
Начнем с карты преобразования кодов клавиш из Qt::Key
в специфичные системные коды X11. Займемся классом KeyMapper
, добавив файл keymapper_x11.cpp
(сокращенная версия):
#include "keymapper.h"
#include <X11/keysymdef.h>
#include <X11/Intrinsic.h>
KeyMapper::KeyMapper() {
m_keyMap[ Qt::Key_Escape ] = XK_Escape;
m_keyMap[ Qt::Key_Tab ] = XK_Tab;
m_keyMap[ Qt::Key_Backtab ] = XK_ISO_Left_Tab;
m_keyMap[ Qt::Key_Backspace ] = XK_BackSpace;
m_keyMap[ Qt::Key_Return ] = XK_Return;
m_keyMap[ Qt::Key_Insert ] = XK_Insert;
m_keyMap[ Qt::Key_Delete ] = XK_Delete;
m_keyMap[ Qt::Key_Pause ] = XK_Pause;
m_keyMap[ Qt::Key_Print ] = XK_Print;
m_keyMap[ Qt::Key_SysReq ] = 0x1005FF60;
// И т.д.
}
uint KeyMapper::findNativeVirtualKeyCode( Qt::Key keyCode ) {
uint code = 0;
Display* display = XOpenDisplay( nullptr );
if( display ) {
if( m_keyMap.contains( keyCode ) ) {
code = XKeysymToKeycode( display, m_keyMap[ keyCode ] );
}
XCloseDisplay( display );
}
return code;
}
Отличие этой от Windows-версии заключается в том, что в таблице Qt-кодам клавиш в соответствие ставится не прямой X11-код клавиши, а ее символьный вариант. Нам для заполнения таблицы достаточно специальных клавиш (Delete, Enter и т.д.), букв латинского алфавита A-Z
и цифр 0-9
, для которых в X11/keysymdef.h
предусмотрены значения символьных кодов.
Для преобразования символьного кода клавиши в абсолютный используется функция XKeysymToKeycode()
. Под абсолютным кодом клавиши имеется в виду код, однозначно присвоенный этой клавише, который не зависит от контекста (раскладки, клавиш-модификаторов и т.д.).
Обратите внимание, что для XKeysymToKeycode()
в качестве одного из параметров требуется указатель на текущий дисплей. Его мы получаем с помощью функции XOpenDisplay()
.
Переходим к непосредственному воспроизведению действия нажатия/отпускания клавиши. Функция onKeyAction()
в файле remotecontrolserver.cpp
:
void onKeyAction( bool pressed, int keyCode, const QString& text ) {
static KeyMapper keyMapper;
static bool shiftPressed = false;
if( keyCode == Qt::Key_Shift ) shiftPressed = pressed;
Display* display = XOpenDisplay( nullptr );
if( display == nullptr ) {
return;
}
uint vKeyCode = 0;
if(
( vKeyCode = keyMapper.findNativeVirtualKeyCode( Qt::Key( keyCode ) ) ) == 0 &&
!text.isEmpty()
) {
if( shiftPressed ) onKeyAction( false, Qt::Key_Shift, "" );
QString uSym = QString().sprintf( "U%04X", text.at( 0 ).unicode() );
auto keySym = XStringToKeysym( uSym.toStdString().c_str() );
int min, max, numcodes;
XDisplayKeycodes( display, &min, &max );
if( KeySym* keySyms = XGetKeyboardMapping( display, min, max - min + 1, &numcodes ) ) {
keySyms[ ( max - min - 1 ) * numcodes ] = keySym;
XChangeKeyboardMapping( display, min, numcodes, keySyms, max - min );
XFree( keySyms );
XFlush( display );
vKeyCode = XKeysymToKeycode( display, keySym );
}
if( shiftPressed ) onKeyAction( true, Qt::Key_Shift, "" );
}
XTestFakeKeyEvent( display, vKeyCode, pressed, 0 );
XFlush( display );
XCloseDisplay( display );
}
В простейшем случае код клавиши находится в таблице из keyMapper
. Этот код мы передаем функции XTestFakeKeyEvent()
по аналогии с тем, как делали ранее при воспроизведении событий мыши (функция XTestFakeButtonEvent()
).
Чуть сложнее дела обстоят в ситуации, когда для Qt-кода ничего подходящего найти не удалось. В силу вступает "план Б": используем текст text
, который был получен на клиенте в результате нажатия клавиши.
Основная особенность заключается в том, что мы явно контролируем то, нажат ли Shift. Это связано с тем, что в строке text
уже может быть заглавная буква или специальный символ (например, для Shift + цифра). В результате, если явно не "отжимать" клавишу Shift, мы воспроизведем совсем не то, что хотели. Достигается это повторным вызовом функции onKeyAction()
:
if( shiftPressed ) onKeyAction( false, Qt::Key_Shift, "" );
// …
if( shiftPressed ) onKeyAction( true, Qt::Key_Shift, "" );
Сама имитация нажатия сводится к регистрации дополнительного кода клавиши в таблице символьных кодов клавиш на основании первого Unicode-символа строки text
:
QString uSym = QString().sprintf( "U%04X", text.at( 0 ).unicode() );
auto keySym = XStringToKeysym( uSym.toStdString().c_str() );
int min, max, numcodes;
XDisplayKeycodes( display, &min, &max );
if( KeySym* keySyms = XGetKeyboardMapping( display, min, max - min + 1, &numcodes ) ) {
keySyms[ ( max - min - 1 ) * numcodes ] = keySym;
XChangeKeyboardMapping( display, min, numcodes, keySyms, max - min );
XFree( keySyms );
XFlush( display );
vKeyCode = XKeysymToKeycode( display, keySym );
}
Функция XStringToKeysym()
принимает строку char*
в формате UXXXX
, где последнее - код печатного символа в таблице Unicode (например, U0411
для Б
), а возвращает числовое значение символьного кода клавиши.
Вся дальнейшая магия основана на использовании функций XGetKeyboardMapping()
и XChangeKeyboardMapping()
. После представленных манипуляций вызов XKeysymToKeycode()
возвращает код клавиши, который можно воспроизводить с помощью знакомой нам функции XTestFakeKeyEvent()
.
Как и для Windows-версии, реализация под Linux не лишена недостатков. Они выражаются практически в том же самом: возможны "залипания" клавиш, могут некорректно отрабатывать клавиши-модификаторы, проблемы с раскладками. Также наблюдаются возможные проблемы при быстрой печати символами Unicode. Это связано со смешиванием событий нажатия/отпускания клавиш и особенностями X11 API.
Однако вывод остается прежним: пользоваться таким приложением уже можно. Все дальнейшие улучшения в большей степени заключаются в исправлении ошибок и доработке/оптимизации алгоритмов, а не в добавлении новых функций. Таким образом, хоть мы и оставили множество допущений, на этом работа над проектом окончена.
Скачать исходники примера RemoteControlDemo
Также они доступны на github: https://github.com/itnotesblog/RemoteControlDemo под тэгом v.0.5.0
.
Сборка проекта проверялась под Linux и Windows с Qt 4.8.4 и компиляторами gcc (x64) и msvc.2010 (32-bit) соответственно.
Здравствуйте. Спасибо за комментарий =)
tysik:
Как ее доработать просто надо на удаленной машине выключить приложение ?
Не до конца понял, что имеется в виду. Если требуется полноценный доступ к удаленной машине, то рекомендую воспользоваться TeamViewer или другими аналогами, поскольку проект , описанный в статье, не рассчитан на серьезное использование (по многим причинам) и предназначен в первую очередь для учебных целей
Просто надо получить список приложений на удаленной машине если запущен пасьянс то надо принудительно закрыть программу . Как доработать программу ?
Понял. Т.е. нужен удаленный диспетчер задач? Опять же проще воспользоваться готовым решением, которых вполне хватает. Но если есть желание создать такое приложение самостоятельно, то нужно определиться с целевыми платформами, поскольку придется использовать специфические API конкретных ОС (как и в случае передачи событий мыши/клавиатуры). При этом идея реализации такого проекта полностью аналогична рассмотренной в моих статьях из серии "Удаленное управление". Только с управляющего компьютера отправляем команды на завершение процессов (вместо нажатий кнопок мыши или клавиатуры), а с целевого получаем список запущенных процессов (вместо видеопотока)
Понял буду думать . При Сборке возникла проблема клиету не передается изображение ошибка QPixmap::scaled: Pixmap is a null pixmap как ее устранить .
tysik:
При Сборке возникла проблема клиету не передается изображение ошибка QPixmap::scaled: Pixmap is a null pixmap как ее устранить .
Посмотрите на это обсуждение. Думаю, что должно помочь
не помогло картинки на клиенте нету захват мыши и клавиатуры работает.
А через DesktopRecorderDemo все работает? Какую ОС используете для запуска?
DesktopRecorderDemo все работает ОС Виндовс 7 ?
tysik:
DesktopRecorderDemo все работает ОС Виндовс 7 ?
Это на сервере? А на клиенте какая ОС и работает ли на нем DesktopRecorderDemo?
tysik
Здравствуйте
Крутая программа =) Как ее доработать просто надо на удаленной машине выключить приложение ?