Предыдущие части:
События клавиатуры одновременно похожи на события щелчков мышью, но и кардинально отличаются от них. Сходство заключается в том, что нам достаточно передавать всего два сигнала: кнопка нажата и кнопка отпущена. Но все не так просто. Сложность заключается в различных кодировках, клавишах-модификаторах, системных сочетаниях клавиш и т.д.
Как и ранее не будем ничего усложнять. Ограничимся достаточно примитивной поддержкой событий клавиатуры, которой будет достаточно в 95% ситуаций. Пока что с реализацией сервера только для Windows.
На клиентской стороне все очень просто. Достаточно добавить в RemoteControlClientWidget
два обработчика:
void RemoteControlClientWidget::keyPressEvent( QKeyEvent* e ) {
if( m_connected ) {
m_peer.call( KEY_PRESS_SIG, e->key(), e->text() );
}
}
void RemoteControlClientWidget::keyReleaseEvent( QKeyEvent* e ) {
if( m_connected ) {
m_peer.call( KEY_RELEASE_SIG, e->key(), e->text() );
}
}
Функция keyPressEvent()
перехватывает и отправляет на сервер сигнал о нажатии клавиши, а keyReleaseEvent()
уведомляет сервер о том, что клавиша была отпущена.
В Qt событие QKeyEvent
содержит внутренний код клавиши Qt::Key
и строку QString
с текстом, который соответствует клавише (например, буква или цифра).
Мы могли бы просто передавать Qt-код клавиши, но тогда возникнет сложность с его интерпретацией на стороне сервера. Особенно проблематичной выглядит ситуация, когда на клиенте выбрана одна раскладка, а на сервере другая. Причем, клиентская раскладка на сервере может быть не настроена вовсе.
Именно поэтому мы передаем не только код клавиши, но и текст, который должен быть получен в результате ее нажатия.
Заметим, что системные комбинации клавиш (например, Alt+F4) не игнорируются на клиенте, поэтому до сервера многие из них не дойдут, что является одним из допущений нашей реализации.
Когда сигнал о нажатии/отпускании клавиши клавиатуры получен, можно заняться его воспроизведением. Начнем с заготовок слотов-обработчиков в RemoteControlServer
:
void onKeyAction( bool pressed, int keyCode, const QString& text ) {
// …
}
void RemoteControlServer::onKeyPressRequest( quint64, int keyCode, const QString& text ) {
onKeyAction( true, keyCode, text );
}
void RemoteControlServer::onKeyReleaseRequest( quint64, int keyCode, const QString& text ) {
onKeyAction( false, keyCode, text );
}
Реализация сконцентрирована в платформенно-зависимой функции onKeyAction()
. Организуем ее работу следующим образом:
Unicode
-символ входной строки text
.Начнем с класса-преобразователя кода клавиши. Файл keymapper.h
:
#ifndef KEYMAPPER_H
#define KEYMAPPER_H
#include <QHash>
class KeyMapper {
public:
KeyMapper();
uint findNativeVirtualKeyCode( Qt::Key keyCode );
private:
QHash< Qt::Key, uint > m_keyMap;
};
#endif // KEYMAPPER_H
На вход функция findNativeVirtualKeyCode()
принимает Qt-код клавиши, а возвращает "местный" код (или ноль, если код не найден).
Реализация довольно механическая, и сводится к заполнению таблицы соответствий. Вот небольшой фрагмент из файла keymapper_win32.cpp
:
#include "keymapper.h"
#include <Windows.h>
KeyMapper::KeyMapper() {
m_keyMap[ Qt::Key_Cancel ] = VK_CANCEL;
m_keyMap[ Qt::Key_Backspace ] = VK_BACK;
m_keyMap[ Qt::Key_Tab ] = VK_TAB;
m_keyMap[ Qt::Key_Clear ] = VK_CLEAR;
m_keyMap[ Qt::Key_Return ] = VK_RETURN;
m_keyMap[ Qt::Key_Shift ] = VK_SHIFT;
m_keyMap[ Qt::Key_Control ] = VK_CONTROL;
m_keyMap[ Qt::Key_Alt ] = VK_MENU;
m_keyMap[ Qt::Key_Pause ] = VK_PAUSE;
m_keyMap[ Qt::Key_CapsLock ] = VK_CAPITAL;
m_keyMap[ Qt::Key_Escape ] = VK_ESCAPE;
m_keyMap[ Qt::Key_Mode_switch ] = VK_MODECHANGE;
m_keyMap[ Qt::Key_Space ] = VK_SPACE;
// И т.д.
}
uint KeyMapper::findNativeVirtualKeyCode( Qt::Key keyCode ) {
uint code = 0;
if( m_keyMap.contains( keyCode ) ) {
code = m_keyMap[ keyCode ];
}
return code;
}
В эту же таблицу мы вносим коды латинских символов и цифры. Это необходимо для корректной работы сочетаний клавиш на подобии Ctrl+c, которые не работают при использовании Unicode-варианта воспроизведения событий нажатия/отпускания клавиши.
Теперь мы готовы закончить с Win32-версией функции onKeyAction()
:
void onKeyAction( bool pressed, int keyCode, const QString& text ) {
static KeyMapper keyMapper;
INPUT input;
input.type = INPUT_KEYBOARD;
input.ki.dwFlags = pressed ? 0 : KEYEVENTF_KEYUP;
input.ki.time = 0;
input.ki.dwExtraInfo = static_cast< ULONG_PTR >( 0 );
if( ( input.ki.wVk = keyMapper.findNativeVirtualKeyCode( Qt::Key( keyCode ) ) ) != 0 ) {
input.ki.wScan = 0;
} else if( !text.isEmpty() ) {
input.ki.dwFlags |= KEYEVENTF_UNICODE;
input.ki.wScan = static_cast< WORD >( text.at( 0 ).unicode() );
}
SendInput( 1, &input, sizeof( input ) );
}
Для симуляции действия мы вновь воспользовались Win32-функцией SendInput()
, с помощью которой в прошлый раз уже организовали воспроизведение действий мыши.
Настал момент, когда наше приложение уже можно использовать для практически полноценной удаленной работы на другом компьютере (пока что только с Windows-сервером, но любым клиентом).
Однако осталось много мелких недостатков, которые не дают возможности считать это приложение завершенным:
Указанные недостатки исправить не так легко, как может показаться на первый взгляд. Вероятно, в рамках нашего сверх-упрощенного проекта придется с ними смириться. Но мы еще не закончили. В следующей части добавляем Linux-версию сервера…
Скачать исходники примера RemoteControlDemo
Также они доступны на github: https://github.com/itnotesblog/RemoteControlDemo под тэгом v.0.4.0
.
Сборка проекта проверялась под Linux и Windows с Qt 4.8.4 и компиляторами gcc (x64) и msvc.2010 (32-bit) соответственно.