IT Notes

Удаленное управление компьютером по сети: Передача событий клавиатуры (Win32)

Предыдущие части:

  1. Удаленное управление компьютером по сети: Введение
  2. Удаленное управление компьютером по сети: Формирование видео-потока
  3. Удаленное управление компьютером по сети: Наблюдение

События клавиатуры одновременно похожи на события щелчков мышью, но и кардинально отличаются от них. Сходство заключается в том, что нам достаточно передавать всего два сигнала: кнопка нажата и кнопка отпущена. Но все не так просто. Сложность заключается в различных кодировках, клавишах-модификаторах, системных сочетаниях клавиш и т.д.

Как и ранее не будем ничего усложнять. Ограничимся достаточно примитивной поддержкой событий клавиатуры, которой будет достаточно в 95% ситуаций. Пока что с реализацией сервера только для Windows.

Отслеживание и передача событий клавиатуры в Qt со стороны клиента

На клиентской стороне все очень просто. Достаточно добавить в 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(). Организуем ее работу следующим образом:

  1. Проверим Qt-код клавиши (по таблице) на соответствие местному коду для конкретной операционной системы (в нашем случае - Windows). Особенно нас интересуют различные клавиши-модификаторы (Shift, Alt и Ctrl);
  2. Если код удалось найти по таблице, то воспроизводим его;
  3. Иначе воспроизводим первый 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) соответственно.

Похожие публикации