#include "VideoWindow.h" #include enum : uint8_t { CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT = 2, ACTION_DOWN = 0, ACTION_UP = 1, ACTION_MOVE = 2, }; // Вспомогательная запись BE-целых static void write32BE(QByteArray &buf, uint32_t v) { buf.append(char((v >> 24) & 0xFF)); buf.append(char((v >> 16) & 0xFF)); buf.append(char((v >> 8) & 0xFF)); buf.append(char((v) & 0xFF)); } static void write16BE(QByteArray &buf, uint16_t v) { buf.append(char((v >> 8) & 0xFF)); buf.append(char((v) & 0xFF)); } static constexpr uint8_t SC_CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT = 2; static constexpr uint32_t AMOTION_EVENT_BUTTON_PRIMARY = 1; void VideoWindow::sendTouch(uint8_t action, const QPoint &pos) { if (m_controlSocket->state() != QAbstractSocket::ConnectedState) { return; } // 1) Подготовка буфера нужного размера QByteArray ba; ba.reserve(32); // 2) Хелперы для записи auto write8 = [&](uint8_t v) { ba.append(char(v)); }; auto write16 = [&](uint16_t v) { ba.append(char((v >> 8) & 0xFF)); ba.append(char(v & 0xFF)); }; auto write32 = [&](uint32_t v) { ba.append(char((v >> 24) & 0xFF)); ba.append(char((v >> 16) & 0xFF)); ba.append(char((v >> 8) & 0xFF)); ba.append(char(v & 0xFF)); }; auto write64 = [&](quint64 v) { for (int shift = 56; shift >= 0; shift -= 8) { ba.append(char((v >> shift) & 0xFF)); } }; // 3) Нормализация координат от QLabel → реальные QSize lbl = m_videoLabel->size(); quint32 x = quint32(double(pos.x()) * m_originalWidth / lbl.width()); quint32 y = quint32(double(pos.y()) * m_originalHeight / lbl.height()); // 4) Сборка пакета по спецификации (32 байта): write8(SC_CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT); // 1 байт: тип сообщения write8(action); // 1 байт: action (DOWN=0, UP=1, MOVE=2) write64(0); // 8 байт: pointerId = 0 write32(x); // 4 байта: position.x write32(y); // 4 байта: position.y write16(uint16_t(m_originalWidth)); // 2 байта: screenWidth write16(uint16_t(m_originalHeight)); // 2 байта: screenHeight // pressure: float [0;1] → int16 [0;0x7FFF] { int16_t p16 = int16_t(1.0f * 0x7FFF); write16(uint16_t(p16)); // 2 байта: pressure } write32(AMOTION_EVENT_BUTTON_PRIMARY); // 4 байта: action_button write32(AMOTION_EVENT_BUTTON_PRIMARY); // 4 байта: buttons // 5) Проверка длины и отправка const int len = ba.size(); qDebug() << "Packed touch event size =" << len; Q_ASSERT(len == 32); if (len != 32) { qWarning() << "Wrong packet length, aborting send."; return; } m_controlSocket->write(ba); m_controlSocket->flush(); } void VideoWindow::mousePressEvent(QMouseEvent *ev) { sendTouch(ACTION_DOWN, ev->pos()); } void VideoWindow::mouseMoveEvent(QMouseEvent *ev) { sendTouch(ACTION_MOVE, ev->pos()); } void VideoWindow::mouseReleaseEvent(QMouseEvent *ev) { sendTouch(ACTION_UP, ev->pos()); } VideoWindow::VideoWindow(QWidget *parent): QMainWindow(parent) { setFixedSize(360, 800); QWidget *central = new QWidget(this); setCentralWidget(central); QVBoxLayout *layout = new QVBoxLayout(central); m_videoLabel = new QLabel(this); m_videoLabel->setAlignment(Qt::AlignCenter); m_videoLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); m_videoLabel->setScaledContents(true); layout->addWidget(m_videoLabel); layout->setContentsMargins(0, 0, 0, 0); m_receiver = new VideoReceiver(this); connect(m_receiver, &VideoReceiver::frameReady, this, [&](const QImage &img) { if (m_originalWidth == 0) { m_originalWidth = img.width(); m_originalHeight = img.height(); } QPixmap scaled = QPixmap::fromImage(img).scaled(m_videoLabel->size(), Qt::KeepAspectRatio, Qt::SmoothTransformation); m_videoLabel->setPixmap(scaled); }); m_receiver->start(); // В конструкторе VideoWindow: m_controlSocket = new QTcpSocket(this); m_controlSocket->connectToHost("127.0.0.1", 27184); if (!m_controlSocket->waitForConnected(3000)) { qWarning() << "Cannot connect to control socket"; } else { qDebug() << "Connected to control socket"; } connect(m_controlSocket, &QTcpSocket::readyRead, this, [this]() { m_controlSocket->readAll(); // важно «чистить» буфер }); } VideoWindow::~VideoWindow() { m_receiver->wait(); }