From d2e83da5b10a06e221431bbe144c3eef6efb0bd0 Mon Sep 17 00:00:00 2001 From: Barry <870709864@qq.com> Date: Wed, 19 Jun 2019 16:13:58 +0800 Subject: [PATCH] =?UTF-8?q?update:event=E6=94=B9=E4=B8=BAmessage=E6=9B=B4?= =?UTF-8?q?=E6=81=B0=E5=BD=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- QtScrcpy/inputcontrol/controlevent.cpp | 136 -------------- QtScrcpy/inputcontrol/controlevent.h | 90 --------- QtScrcpy/inputcontrol/controller.cpp | 22 +-- QtScrcpy/inputcontrol/controller.h | 4 +- QtScrcpy/inputcontrol/controlmsg.cpp | 136 ++++++++++++++ QtScrcpy/inputcontrol/controlmsg.h | 90 +++++++++ QtScrcpy/inputcontrol/deviceevent.cpp | 69 ------- QtScrcpy/inputcontrol/deviceevent.h | 42 ----- QtScrcpy/inputcontrol/devicemsg.cpp | 69 +++++++ QtScrcpy/inputcontrol/devicemsg.h | 42 +++++ QtScrcpy/inputcontrol/inputcontrol.pri | 12 +- QtScrcpy/inputcontrol/inputconvertbase.cpp | 6 +- QtScrcpy/inputcontrol/inputconvertbase.h | 4 +- QtScrcpy/inputcontrol/inputconvertgame.cpp | 8 +- QtScrcpy/inputcontrol/inputconvertnormal.cpp | 24 +-- QtScrcpy/inputcontrol/receiver.cpp | 16 +- QtScrcpy/inputcontrol/receiver.h | 4 +- QtScrcpy/videoform.cpp | 54 +++--- ...{ControlEvent.java => ControlMessage.java} | 54 +++--- ...tReader.java => ControlMessageReader.java} | 84 ++++----- .../{EventController.java => Controller.java} | 46 ++--- .../genymobile/scrcpy/DesktopConnection.java | 18 +- .../com/genymobile/scrcpy/DeviceEvent.java | 27 --- .../com/genymobile/scrcpy/DeviceMessage.java | 27 +++ ...ntSender.java => DeviceMessageSender.java} | 8 +- ...ntWriter.java => DeviceMessageWriter.java} | 14 +- .../java/com/genymobile/scrcpy/Server.java | 14 +- .../scrcpy/ControlEventReaderTest.java | 173 ------------------ third_party/scrcpy-server.jar | Bin 52352 -> 52649 bytes 29 files changed, 560 insertions(+), 733 deletions(-) delete mode 100644 QtScrcpy/inputcontrol/controlevent.cpp delete mode 100644 QtScrcpy/inputcontrol/controlevent.h create mode 100644 QtScrcpy/inputcontrol/controlmsg.cpp create mode 100644 QtScrcpy/inputcontrol/controlmsg.h delete mode 100644 QtScrcpy/inputcontrol/deviceevent.cpp delete mode 100644 QtScrcpy/inputcontrol/deviceevent.h create mode 100644 QtScrcpy/inputcontrol/devicemsg.cpp create mode 100644 QtScrcpy/inputcontrol/devicemsg.h rename server/src/main/java/com/genymobile/scrcpy/{ControlEvent.java => ControlMessage.java} (56%) rename server/src/main/java/com/genymobile/scrcpy/{ControlEventReader.java => ControlMessageReader.java} (60%) rename server/src/main/java/com/genymobile/scrcpy/{EventController.java => Controller.java} (87%) delete mode 100644 server/src/main/java/com/genymobile/scrcpy/DeviceEvent.java create mode 100644 server/src/main/java/com/genymobile/scrcpy/DeviceMessage.java rename server/src/main/java/com/genymobile/scrcpy/{EventSender.java => DeviceMessageSender.java} (74%) rename server/src/main/java/com/genymobile/scrcpy/{DeviceEventWriter.java => DeviceMessageWriter.java} (72%) delete mode 100644 server/src/test/java/com/genymobile/scrcpy/ControlEventReaderTest.java diff --git a/QtScrcpy/inputcontrol/controlevent.cpp b/QtScrcpy/inputcontrol/controlevent.cpp deleted file mode 100644 index 0e4c1da..0000000 --- a/QtScrcpy/inputcontrol/controlevent.cpp +++ /dev/null @@ -1,136 +0,0 @@ -#include - -#include "controlevent.h" -#include "bufferutil.h" - -ControlEvent::ControlEvent(ControlEventType controlEventType) - : QScrcpyEvent(Control) -{ - m_data.type = controlEventType; -} - -ControlEvent::~ControlEvent() -{ - if (CET_SET_CLIPBOARD == m_data.type - && Q_NULLPTR != m_data.setClipboardEvent.text) { - delete m_data.setClipboardEvent.text; - m_data.setClipboardEvent.text = Q_NULLPTR; - } else if (CET_TEXT == m_data.type - && Q_NULLPTR != m_data.textEvent.text){ - delete m_data.textEvent.text; - m_data.textEvent.text = Q_NULLPTR; - } -} - -void ControlEvent::setKeycodeEventData(AndroidKeyeventAction action, AndroidKeycode keycode, AndroidMetastate metastate) -{ - m_data.keycodeEvent.action = action; - m_data.keycodeEvent.keycode = keycode; - m_data.keycodeEvent.metastate = metastate; -} - -void ControlEvent::setTextEventData(QString& text) -{ - // write length (2 byte) + string (non nul-terminated) - if (CONTROL_EVENT_TEXT_MAX_LENGTH < text.length()) { - // injecting a text takes time, so limit the text length - text = text.left(CONTROL_EVENT_TEXT_MAX_LENGTH); - } - QByteArray tmp = text.toUtf8(); - m_data.textEvent.text = new char[tmp.length() + 1]; - memcpy(m_data.textEvent.text, tmp.data(), tmp.length()); - m_data.textEvent.text[tmp.length()] = '\0'; -} - -void ControlEvent::setMouseEventData(AndroidMotioneventAction action, AndroidMotioneventButtons buttons, QRect position) -{ - m_data.mouseEvent.action = action; - m_data.mouseEvent.buttons = buttons; - m_data.mouseEvent.position = position; -} - -void ControlEvent::setTouchEventData(quint32 id, AndroidMotioneventAction action, QRect position) -{ - m_data.touchEvent.action = action; - m_data.touchEvent.id = id; - m_data.touchEvent.position = position; -} - -void ControlEvent::setScrollEventData(QRect position, qint32 hScroll, qint32 vScroll) -{ - m_data.scrollEvent.position = position; - m_data.scrollEvent.hScroll = hScroll; - m_data.scrollEvent.vScroll = vScroll; -} - -void ControlEvent::setSetClipboardEventData(QString &text) -{ - if (text.isEmpty()) { - return; - } - if (CONTROL_EVENT_CLIPBOARD_TEXT_MAX_LENGTH < text.length()) { - text = text.left(CONTROL_EVENT_CLIPBOARD_TEXT_MAX_LENGTH); - } - - QByteArray tmp = text.toUtf8(); - m_data.setClipboardEvent.text = new char[tmp.length() + 1]; - memcpy(m_data.setClipboardEvent.text, tmp.data(), tmp.length()); - m_data.setClipboardEvent.text[tmp.length()] = '\0'; -} - -void ControlEvent::writePosition(QBuffer &buffer, const QRect& value) -{ - BufferUtil::write16(buffer, value.left()); - BufferUtil::write16(buffer, value.top()); - BufferUtil::write16(buffer, value.width()); - BufferUtil::write16(buffer, value.height()); -} - -QByteArray ControlEvent::serializeData() -{ - QByteArray byteArray; - QBuffer buffer(&byteArray); - buffer.open(QBuffer::WriteOnly); - buffer.putChar(m_data.type); - - switch (m_data.type) { - case CET_KEYCODE: - buffer.putChar(m_data.keycodeEvent.action); - BufferUtil::write32(buffer, m_data.keycodeEvent.keycode); - BufferUtil::write32(buffer, m_data.keycodeEvent.metastate); - break; - case CET_TEXT: - BufferUtil::write16(buffer, strlen(m_data.textEvent.text)); - buffer.write(m_data.textEvent.text, strlen(m_data.textEvent.text)); - break; - case CET_MOUSE: - buffer.putChar(m_data.mouseEvent.action); - BufferUtil::write32(buffer, m_data.mouseEvent.buttons); - writePosition(buffer, m_data.mouseEvent.position); - break; - case CET_TOUCH: - buffer.putChar(m_data.touchEvent.id); - buffer.putChar(m_data.touchEvent.action); - writePosition(buffer, m_data.touchEvent.position); - break; - case CET_SCROLL: - writePosition(buffer, m_data.scrollEvent.position); - BufferUtil::write32(buffer, m_data.scrollEvent.hScroll); - BufferUtil::write32(buffer, m_data.scrollEvent.vScroll); - break; - case CET_SET_CLIPBOARD: - BufferUtil::write16(buffer, strlen(m_data.setClipboardEvent.text)); - buffer.write(m_data.setClipboardEvent.text, strlen(m_data.setClipboardEvent.text)); - break; - case CET_BACK_OR_SCREEN_ON: - case CET_EXPAND_NOTIFICATION_PANEL: - case CET_COLLAPSE_NOTIFICATION_PANEL: - case CET_GET_CLIPBOARD: - break; - default: - qDebug() << "Unknown event type:" << m_data.type; - break; - } - buffer.close(); - return byteArray; -} diff --git a/QtScrcpy/inputcontrol/controlevent.h b/QtScrcpy/inputcontrol/controlevent.h deleted file mode 100644 index 67f99a0..0000000 --- a/QtScrcpy/inputcontrol/controlevent.h +++ /dev/null @@ -1,90 +0,0 @@ -#ifndef CONTROLEVENT_H -#define CONTROLEVENT_H - -#include -#include -#include - -#include "qscrcpyevent.h" -#include "input.h" -#include "keycodes.h" - -#define CONTROL_EVENT_TEXT_MAX_LENGTH 300 -#define CONTROL_EVENT_CLIPBOARD_TEXT_MAX_LENGTH 4093 -// ControlEvent -class ControlEvent : public QScrcpyEvent -{ -public: - enum ControlEventType { - CET_NULL = -1, - CET_KEYCODE = 0, - CET_TEXT, - CET_MOUSE, - CET_SCROLL, - CET_BACK_OR_SCREEN_ON, - CET_EXPAND_NOTIFICATION_PANEL, - CET_COLLAPSE_NOTIFICATION_PANEL, - CET_GET_CLIPBOARD, - CET_SET_CLIPBOARD, - - CET_TOUCH, - }; - - ControlEvent(ControlEventType controlEventType); - virtual ~ControlEvent(); - - void setKeycodeEventData(AndroidKeyeventAction action, AndroidKeycode keycode, AndroidMetastate metastate); - void setTextEventData(QString& text); - void setMouseEventData(AndroidMotioneventAction action, AndroidMotioneventButtons buttons, QRect position); - // id 代表一个触摸点,最多支持10个触摸点[0,9] - // action 只能是AMOTION_EVENT_ACTION_DOWN,AMOTION_EVENT_ACTION_UP,AMOTION_EVENT_ACTION_MOVE - // position action动作对应的位置 - void setTouchEventData(quint32 id, AndroidMotioneventAction action, QRect position); - void setScrollEventData(QRect position, qint32 hScroll, qint32 vScroll); - void setSetClipboardEventData(QString& text); - - QByteArray serializeData(); - -private: - void writePosition(QBuffer& buffer, const QRect& value); - -private: - struct ControlEventData { - ControlEventType type = CET_NULL; - union { - struct { - AndroidKeyeventAction action; - AndroidKeycode keycode; - AndroidMetastate metastate; - } keycodeEvent; - struct { - char* text = Q_NULLPTR; - } textEvent; - struct { - AndroidMotioneventAction action; - AndroidMotioneventButtons buttons; - QRect position; - } mouseEvent; - struct { - quint32 id; - AndroidMotioneventAction action; - QRect position; - } touchEvent; - struct { - QRect position; - qint32 hScroll; - qint32 vScroll; - } scrollEvent; - struct { - char *text = Q_NULLPTR; - } setClipboardEvent; - }; - - ControlEventData(){} - ~ControlEventData(){} - }; - - ControlEventData m_data; -}; - -#endif // CONTROLEVENT_H diff --git a/QtScrcpy/inputcontrol/controller.cpp b/QtScrcpy/inputcontrol/controller.cpp index dcd867e..5929730 100644 --- a/QtScrcpy/inputcontrol/controller.cpp +++ b/QtScrcpy/inputcontrol/controller.cpp @@ -2,7 +2,7 @@ #include "controller.h" #include "videosocket.h" -#include "controlevent.h" +#include "controlmsg.h" #include "receiver.h" Controller::Controller(QObject* parent) : QObject(parent) @@ -29,26 +29,26 @@ QTcpSocket *Controller::getControlSocket() return m_controlSocket; } -void Controller::postControlEvent(ControlEvent *controlEvent) +void Controller::postControlMsg(ControlMsg *controlMsg) { - if (controlEvent) { - QCoreApplication::postEvent(this, controlEvent); + if (controlMsg) { + QCoreApplication::postEvent(this, controlMsg); } } void Controller::test(QRect rc) { - ControlEvent* controlEvent = new ControlEvent(ControlEvent::CET_MOUSE); - controlEvent->setMouseEventData(AMOTION_EVENT_ACTION_DOWN, AMOTION_EVENT_BUTTON_PRIMARY, rc); - postControlEvent(controlEvent); + ControlMsg* controlMsg = new ControlMsg(ControlMsg::CMT_INJECT_MOUSE); + controlMsg->setInjectMouseMsgData(AMOTION_EVENT_ACTION_DOWN, AMOTION_EVENT_BUTTON_PRIMARY, rc); + postControlMsg(controlMsg); } bool Controller::event(QEvent *event) { - if (event && event->type() == ControlEvent::Control) { - ControlEvent* controlEvent = dynamic_cast(event); - if (controlEvent) { - sendControl(controlEvent->serializeData()); + if (event && event->type() == ControlMsg::Control) { + ControlMsg* controlMsg = dynamic_cast(event); + if (controlMsg) { + sendControl(controlMsg->serializeData()); } return true; } diff --git a/QtScrcpy/inputcontrol/controller.h b/QtScrcpy/inputcontrol/controller.h index 1522f3c..a046284 100644 --- a/QtScrcpy/inputcontrol/controller.h +++ b/QtScrcpy/inputcontrol/controller.h @@ -5,7 +5,7 @@ #include class QTcpSocket; -class ControlEvent; +class ControlMsg; class Receiver; class Controller : public QObject { @@ -16,7 +16,7 @@ public: void setControlSocket(QTcpSocket* controlSocket); QTcpSocket* getControlSocket(); - void postControlEvent(ControlEvent* controlEvent); + void postControlMsg(ControlMsg* controlMsg); void test(QRect rc); protected: diff --git a/QtScrcpy/inputcontrol/controlmsg.cpp b/QtScrcpy/inputcontrol/controlmsg.cpp new file mode 100644 index 0000000..9226ec4 --- /dev/null +++ b/QtScrcpy/inputcontrol/controlmsg.cpp @@ -0,0 +1,136 @@ +#include + +#include "controlmsg.h" +#include "bufferutil.h" + +ControlMsg::ControlMsg(ControlMsgType controlMsgType) + : QScrcpyEvent(Control) +{ + m_data.type = controlMsgType; +} + +ControlMsg::~ControlMsg() +{ + if (CMT_SET_CLIPBOARD == m_data.type + && Q_NULLPTR != m_data.setClipboardMsg.text) { + delete m_data.setClipboardMsg.text; + m_data.setClipboardMsg.text = Q_NULLPTR; + } else if (CMT_INJECT_TEXT == m_data.type + && Q_NULLPTR != m_data.injectTextMsg.text){ + delete m_data.injectTextMsg.text; + m_data.injectTextMsg.text = Q_NULLPTR; + } +} + +void ControlMsg::setInjectKeycodeMsgData(AndroidKeyeventAction action, AndroidKeycode keycode, AndroidMetastate metastate) +{ + m_data.injectKeycodeMsg.action = action; + m_data.injectKeycodeMsg.keycode = keycode; + m_data.injectKeycodeMsg.metastate = metastate; +} + +void ControlMsg::setInjectTextMsgData(QString& text) +{ + // write length (2 byte) + string (non nul-terminated) + if (CONTROL_MSG_TEXT_MAX_LENGTH < text.length()) { + // injecting a text takes time, so limit the text length + text = text.left(CONTROL_MSG_TEXT_MAX_LENGTH); + } + QByteArray tmp = text.toUtf8(); + m_data.injectTextMsg.text = new char[tmp.length() + 1]; + memcpy(m_data.injectTextMsg.text, tmp.data(), tmp.length()); + m_data.injectTextMsg.text[tmp.length()] = '\0'; +} + +void ControlMsg::setInjectMouseMsgData(AndroidMotioneventAction action, AndroidMotioneventButtons buttons, QRect position) +{ + m_data.injectMouseMsg.action = action; + m_data.injectMouseMsg.buttons = buttons; + m_data.injectMouseMsg.position = position; +} + +void ControlMsg::setInjectTouchMsgData(quint32 id, AndroidMotioneventAction action, QRect position) +{ + m_data.injectTouchMsg.action = action; + m_data.injectTouchMsg.id = id; + m_data.injectTouchMsg.position = position; +} + +void ControlMsg::setInjectScrollMsgData(QRect position, qint32 hScroll, qint32 vScroll) +{ + m_data.injectScrollMsg.position = position; + m_data.injectScrollMsg.hScroll = hScroll; + m_data.injectScrollMsg.vScroll = vScroll; +} + +void ControlMsg::setSetClipboardMsgData(QString &text) +{ + if (text.isEmpty()) { + return; + } + if (CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH < text.length()) { + text = text.left(CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH); + } + + QByteArray tmp = text.toUtf8(); + m_data.setClipboardMsg.text = new char[tmp.length() + 1]; + memcpy(m_data.setClipboardMsg.text, tmp.data(), tmp.length()); + m_data.setClipboardMsg.text[tmp.length()] = '\0'; +} + +void ControlMsg::writePosition(QBuffer &buffer, const QRect& value) +{ + BufferUtil::write16(buffer, value.left()); + BufferUtil::write16(buffer, value.top()); + BufferUtil::write16(buffer, value.width()); + BufferUtil::write16(buffer, value.height()); +} + +QByteArray ControlMsg::serializeData() +{ + QByteArray byteArray; + QBuffer buffer(&byteArray); + buffer.open(QBuffer::WriteOnly); + buffer.putChar(m_data.type); + + switch (m_data.type) { + case CMT_INJECT_KEYCODE: + buffer.putChar(m_data.injectKeycodeMsg.action); + BufferUtil::write32(buffer, m_data.injectKeycodeMsg.keycode); + BufferUtil::write32(buffer, m_data.injectKeycodeMsg.metastate); + break; + case CMT_INJECT_TEXT: + BufferUtil::write16(buffer, strlen(m_data.injectTextMsg.text)); + buffer.write(m_data.injectTextMsg.text, strlen(m_data.injectTextMsg.text)); + break; + case CMT_INJECT_MOUSE: + buffer.putChar(m_data.injectMouseMsg.action); + BufferUtil::write32(buffer, m_data.injectMouseMsg.buttons); + writePosition(buffer, m_data.injectMouseMsg.position); + break; + case CMT_INJECT_TOUCH: + buffer.putChar(m_data.injectTouchMsg.id); + buffer.putChar(m_data.injectTouchMsg.action); + writePosition(buffer, m_data.injectTouchMsg.position); + break; + case CMT_INJECT_SCROLL: + writePosition(buffer, m_data.injectScrollMsg.position); + BufferUtil::write32(buffer, m_data.injectScrollMsg.hScroll); + BufferUtil::write32(buffer, m_data.injectScrollMsg.vScroll); + break; + case CMT_SET_CLIPBOARD: + BufferUtil::write16(buffer, strlen(m_data.setClipboardMsg.text)); + buffer.write(m_data.setClipboardMsg.text, strlen(m_data.setClipboardMsg.text)); + break; + case CMT_BACK_OR_SCREEN_ON: + case CMT_EXPAND_NOTIFICATION_PANEL: + case CMT_COLLAPSE_NOTIFICATION_PANEL: + case CMT_GET_CLIPBOARD: + break; + default: + qDebug() << "Unknown event type:" << m_data.type; + break; + } + buffer.close(); + return byteArray; +} diff --git a/QtScrcpy/inputcontrol/controlmsg.h b/QtScrcpy/inputcontrol/controlmsg.h new file mode 100644 index 0000000..d137475 --- /dev/null +++ b/QtScrcpy/inputcontrol/controlmsg.h @@ -0,0 +1,90 @@ +#ifndef CONTROLMSG_H +#define CONTROLMSG_H + +#include +#include +#include + +#include "qscrcpyevent.h" +#include "input.h" +#include "keycodes.h" + +#define CONTROL_MSG_TEXT_MAX_LENGTH 300 +#define CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH 4093 +// ControlMsg +class ControlMsg : public QScrcpyEvent +{ +public: + enum ControlMsgType { + CMT_NULL = -1, + CMT_INJECT_KEYCODE = 0, + CMT_INJECT_TEXT, + CMT_INJECT_MOUSE, + CMT_INJECT_SCROLL, + CMT_BACK_OR_SCREEN_ON, + CMT_EXPAND_NOTIFICATION_PANEL, + CMT_COLLAPSE_NOTIFICATION_PANEL, + CMT_GET_CLIPBOARD, + CMT_SET_CLIPBOARD, + + CMT_INJECT_TOUCH, + }; + + ControlMsg(ControlMsgType controlMsgType); + virtual ~ControlMsg(); + + void setInjectKeycodeMsgData(AndroidKeyeventAction action, AndroidKeycode keycode, AndroidMetastate metastate); + void setInjectTextMsgData(QString& text); + void setInjectMouseMsgData(AndroidMotioneventAction action, AndroidMotioneventButtons buttons, QRect position); + // id 代表一个触摸点,最多支持10个触摸点[0,9] + // action 只能是AMOTION_EVENT_ACTION_DOWN,AMOTION_EVENT_ACTION_UP,AMOTION_EVENT_ACTION_MOVE + // position action动作对应的位置 + void setInjectTouchMsgData(quint32 id, AndroidMotioneventAction action, QRect position); + void setInjectScrollMsgData(QRect position, qint32 hScroll, qint32 vScroll); + void setSetClipboardMsgData(QString& text); + + QByteArray serializeData(); + +private: + void writePosition(QBuffer& buffer, const QRect& value); + +private: + struct ControlMsgData { + ControlMsgType type = CMT_NULL; + union { + struct { + AndroidKeyeventAction action; + AndroidKeycode keycode; + AndroidMetastate metastate; + } injectKeycodeMsg; + struct { + char* text = Q_NULLPTR; + } injectTextMsg; + struct { + AndroidMotioneventAction action; + AndroidMotioneventButtons buttons; + QRect position; + } injectMouseMsg; + struct { + quint32 id; + AndroidMotioneventAction action; + QRect position; + } injectTouchMsg; + struct { + QRect position; + qint32 hScroll; + qint32 vScroll; + } injectScrollMsg; + struct { + char *text = Q_NULLPTR; + } setClipboardMsg; + }; + + ControlMsgData(){} + ~ControlMsgData(){} + }; + + ControlMsgData m_data; +}; + +#endif // CONTROLMSG_H diff --git a/QtScrcpy/inputcontrol/deviceevent.cpp b/QtScrcpy/inputcontrol/deviceevent.cpp deleted file mode 100644 index 668fbcf..0000000 --- a/QtScrcpy/inputcontrol/deviceevent.cpp +++ /dev/null @@ -1,69 +0,0 @@ -#include - -#include "deviceevent.h" -#include "bufferutil.h" - -DeviceEvent::DeviceEvent(QObject *parent) : QObject(parent) -{ - -} - -DeviceEvent::~DeviceEvent() -{ - if (DET_GET_CLIPBOARD == m_data.type - && Q_NULLPTR != m_data.clipboardEvent.text) { - delete m_data.clipboardEvent.text; - m_data.clipboardEvent.text = Q_NULLPTR; - } -} - -DeviceEvent::DeviceEventType DeviceEvent::type() -{ - return m_data.type; -} - -void DeviceEvent::getClipboardEventData(QString& text) -{ - text = QString::fromUtf8(m_data.clipboardEvent.text); -} - -qint32 DeviceEvent::deserialize(QByteArray& byteArray) -{ - QBuffer buf(&byteArray); - buf.open(QBuffer::ReadOnly); - - qint64 len = buf.size(); - char c = 0; - qint32 ret = 0; - - if (len < 3) { - // at least type + empty string length - return 0; // not available - } - - buf.getChar(&c); - m_data.type = (DeviceEventType)c; - switch (m_data.type) { - case DET_GET_CLIPBOARD: { - quint16 clipboardLen = BufferUtil::read16(buf); - if (clipboardLen > len - 3) { - ret = 0; // not available - break; - } - - QByteArray text = buf.readAll(); - m_data.clipboardEvent.text = new char[text.length() + 1]; - memcpy(m_data.clipboardEvent.text, text.data(), text.length()); - m_data.clipboardEvent.text[text.length()] = '\0'; - - ret = 3 + clipboardLen; - break; - } - default: - qWarning("Unsupported device event type: %d", (int) m_data.type); - ret = -1; // error, we cannot recover - } - - buf.close(); - return ret; -} diff --git a/QtScrcpy/inputcontrol/deviceevent.h b/QtScrcpy/inputcontrol/deviceevent.h deleted file mode 100644 index 7f0be20..0000000 --- a/QtScrcpy/inputcontrol/deviceevent.h +++ /dev/null @@ -1,42 +0,0 @@ -#ifndef DEVICEEVENT_H -#define DEVICEEVENT_H - -#include - -#define DEVICE_EVENT_QUEUE_SIZE 64 -#define DEVICE_EVENT_TEXT_MAX_LENGTH 4093 -#define DEVICE_EVENT_SERIALIZED_MAX_SIZE (3 + DEVICE_EVENT_TEXT_MAX_LENGTH) - -class DeviceEvent : public QObject -{ - Q_OBJECT -public: - enum DeviceEventType { - DET_NULL = -1, - // 和服务端对应 - DET_GET_CLIPBOARD = 0, - }; - explicit DeviceEvent(QObject *parent = nullptr); - virtual ~DeviceEvent(); - - DeviceEvent::DeviceEventType type(); - void getClipboardEventData(QString& text); - - qint32 deserialize(QByteArray& byteArray); - -private: - struct DeviceEventData { - DeviceEventType type = DET_NULL; - union { - struct { - char* text = Q_NULLPTR; - } clipboardEvent; - }; - DeviceEventData(){} - ~DeviceEventData(){} - }; - - DeviceEventData m_data; -}; - -#endif // DEVICEEVENT_H diff --git a/QtScrcpy/inputcontrol/devicemsg.cpp b/QtScrcpy/inputcontrol/devicemsg.cpp new file mode 100644 index 0000000..9439802 --- /dev/null +++ b/QtScrcpy/inputcontrol/devicemsg.cpp @@ -0,0 +1,69 @@ +#include + +#include "devicemsg.h" +#include "bufferutil.h" + +DeviceMsg::DeviceMsg(QObject *parent) : QObject(parent) +{ + +} + +DeviceMsg::~DeviceMsg() +{ + if (DMT_GET_CLIPBOARD == m_data.type + && Q_NULLPTR != m_data.clipboardMsg.text) { + delete m_data.clipboardMsg.text; + m_data.clipboardMsg.text = Q_NULLPTR; + } +} + +DeviceMsg::DeviceMsgType DeviceMsg::type() +{ + return m_data.type; +} + +void DeviceMsg::getClipboardMsgData(QString& text) +{ + text = QString::fromUtf8(m_data.clipboardMsg.text); +} + +qint32 DeviceMsg::deserialize(QByteArray& byteArray) +{ + QBuffer buf(&byteArray); + buf.open(QBuffer::ReadOnly); + + qint64 len = buf.size(); + char c = 0; + qint32 ret = 0; + + if (len < 3) { + // at least type + empty string length + return 0; // not available + } + + buf.getChar(&c); + m_data.type = (DeviceMsgType)c; + switch (m_data.type) { + case DMT_GET_CLIPBOARD: { + quint16 clipboardLen = BufferUtil::read16(buf); + if (clipboardLen > len - 3) { + ret = 0; // not available + break; + } + + QByteArray text = buf.readAll(); + m_data.clipboardMsg.text = new char[text.length() + 1]; + memcpy(m_data.clipboardMsg.text, text.data(), text.length()); + m_data.clipboardMsg.text[text.length()] = '\0'; + + ret = 3 + clipboardLen; + break; + } + default: + qWarning("Unsupported device msg type: %d", (int) m_data.type); + ret = -1; // error, we cannot recover + } + + buf.close(); + return ret; +} diff --git a/QtScrcpy/inputcontrol/devicemsg.h b/QtScrcpy/inputcontrol/devicemsg.h new file mode 100644 index 0000000..e7fb68a --- /dev/null +++ b/QtScrcpy/inputcontrol/devicemsg.h @@ -0,0 +1,42 @@ +#ifndef DEVICEMSG_H +#define DEVICEMSG_H + +#include + +#define DEVICE_MSG_QUEUE_SIZE 64 +#define DEVICE_MSG_TEXT_MAX_LENGTH 4093 +#define DEVICE_MSG_SERIALIZED_MAX_SIZE (3 + DEVICE_MSG_TEXT_MAX_LENGTH) + +class DeviceMsg : public QObject +{ + Q_OBJECT +public: + enum DeviceMsgType { + DMT_NULL = -1, + // 和服务端对应 + DMT_GET_CLIPBOARD = 0, + }; + explicit DeviceMsg(QObject *parent = nullptr); + virtual ~DeviceMsg(); + + DeviceMsg::DeviceMsgType type(); + void getClipboardMsgData(QString& text); + + qint32 deserialize(QByteArray& byteArray); + +private: + struct DeviceMsgData { + DeviceMsgType type = DMT_NULL; + union { + struct { + char* text = Q_NULLPTR; + } clipboardMsg; + }; + DeviceMsgData(){} + ~DeviceMsgData(){} + }; + + DeviceMsgData m_data; +}; + +#endif // DEVICEMSG_H diff --git a/QtScrcpy/inputcontrol/inputcontrol.pri b/QtScrcpy/inputcontrol/inputcontrol.pri index af7fc9c..0c3d8bb 100644 --- a/QtScrcpy/inputcontrol/inputcontrol.pri +++ b/QtScrcpy/inputcontrol/inputcontrol.pri @@ -1,18 +1,18 @@ HEADERS += \ - $$PWD/controlevent.h \ $$PWD/controller.h \ $$PWD/inputconvertbase.h \ $$PWD/inputconvertgame.h \ $$PWD/inputconvertnormal.h \ - $$PWD/deviceevent.h \ - $$PWD/receiver.h + $$PWD/receiver.h \ + $$PWD/controlmsg.h \ + $$PWD/devicemsg.h SOURCES += \ - $$PWD/controlevent.cpp \ $$PWD/controller.cpp \ $$PWD/inputconvertbase.cpp \ $$PWD/inputconvertgame.cpp \ $$PWD/inputconvertnormal.cpp \ - $$PWD/deviceevent.cpp \ - $$PWD/receiver.cpp + $$PWD/receiver.cpp \ + $$PWD/controlmsg.cpp \ + $$PWD/devicemsg.cpp diff --git a/QtScrcpy/inputcontrol/inputconvertbase.cpp b/QtScrcpy/inputcontrol/inputconvertbase.cpp index 44bda0b..59282d4 100644 --- a/QtScrcpy/inputcontrol/inputconvertbase.cpp +++ b/QtScrcpy/inputcontrol/inputconvertbase.cpp @@ -15,10 +15,10 @@ void InputConvertBase::setControlSocket(QTcpSocket *controlSocket) m_controller.setControlSocket(controlSocket); } -void InputConvertBase::sendControlEvent(ControlEvent *event) +void InputConvertBase::sendControlMsg(ControlMsg *msg) { - if (event) { - m_controller.postControlEvent(event); + if (msg) { + m_controller.postControlMsg(msg); } } diff --git a/QtScrcpy/inputcontrol/inputconvertbase.h b/QtScrcpy/inputcontrol/inputconvertbase.h index 123c6ec..55fb229 100644 --- a/QtScrcpy/inputcontrol/inputconvertbase.h +++ b/QtScrcpy/inputcontrol/inputconvertbase.h @@ -5,7 +5,7 @@ #include #include -#include "controlevent.h" +#include "controlmsg.h" #include "controller.h" class InputConvertBase @@ -21,7 +21,7 @@ public: virtual void keyEvent(const QKeyEvent* from, const QSize& frameSize, const QSize& showSize) = 0; void setControlSocket(QTcpSocket* controlSocket); - void sendControlEvent(ControlEvent* event); + void sendControlMsg(ControlMsg* msg); private: Controller m_controller; diff --git a/QtScrcpy/inputcontrol/inputconvertgame.cpp b/QtScrcpy/inputcontrol/inputconvertgame.cpp index bfb3f1f..c243993 100644 --- a/QtScrcpy/inputcontrol/inputconvertgame.cpp +++ b/QtScrcpy/inputcontrol/inputconvertgame.cpp @@ -111,12 +111,12 @@ void InputConvertGame::sendTouchEvent(int id, QPointF pos, AndroidMotioneventAct if (0 > id || MULTI_TOUCH_MAX_NUM-1 < id) { return; } - ControlEvent* controlEvent = new ControlEvent(ControlEvent::CET_TOUCH); - if (!controlEvent) { + ControlMsg* controlMsg = new ControlMsg(ControlMsg::CMT_INJECT_TOUCH); + if (!controlMsg) { return; } - controlEvent->setTouchEventData(id, action, QRect(calcFrameAbsolutePos(pos).toPoint(), m_frameSize)); - sendControlEvent(controlEvent); + controlMsg->setInjectTouchMsgData(id, action, QRect(calcFrameAbsolutePos(pos).toPoint(), m_frameSize)); + sendControlMsg(controlMsg); } QPointF InputConvertGame::calcFrameAbsolutePos(QPointF relativePos) diff --git a/QtScrcpy/inputcontrol/inputconvertnormal.cpp b/QtScrcpy/inputcontrol/inputconvertnormal.cpp index 925f52e..5cb46df 100644 --- a/QtScrcpy/inputcontrol/inputconvertnormal.cpp +++ b/QtScrcpy/inputcontrol/inputconvertnormal.cpp @@ -39,12 +39,12 @@ void InputConvertNormal::mouseEvent(const QMouseEvent* from, const QSize& frameS pos.setY(pos.y() * frameSize.height() / showSize.height()); // set data - ControlEvent* controlEvent = new ControlEvent(ControlEvent::CET_MOUSE); - if (!controlEvent) { + ControlMsg* controlMsg = new ControlMsg(ControlMsg::CMT_INJECT_MOUSE); + if (!controlMsg) { return; } - controlEvent->setMouseEventData(action, convertMouseButtons(from->buttons()), QRect(pos.toPoint(), frameSize)); - sendControlEvent(controlEvent); + controlMsg->setInjectMouseMsgData(action, convertMouseButtons(from->buttons()), QRect(pos.toPoint(), frameSize)); + sendControlMsg(controlMsg); } void InputConvertNormal::wheelEvent(const QWheelEvent *from, const QSize& frameSize, const QSize& showSize) @@ -72,12 +72,12 @@ void InputConvertNormal::wheelEvent(const QWheelEvent *from, const QSize& frameS pos.setY(pos.y() * frameSize.height() / showSize.height()); // set data - ControlEvent* controlEvent = new ControlEvent(ControlEvent::CET_SCROLL); - if (!controlEvent) { + ControlMsg* controlMsg = new ControlMsg(ControlMsg::CMT_INJECT_SCROLL); + if (!controlMsg) { return; } - controlEvent->setScrollEventData(QRect(pos.toPoint(), frameSize), hScroll, vScroll); - sendControlEvent(controlEvent); + controlMsg->setInjectScrollMsgData(QRect(pos.toPoint(), frameSize), hScroll, vScroll); + sendControlMsg(controlMsg); } void InputConvertNormal::keyEvent(const QKeyEvent *from, const QSize& frameSize, const QSize& showSize) @@ -108,12 +108,12 @@ void InputConvertNormal::keyEvent(const QKeyEvent *from, const QSize& frameSize, } // set data - ControlEvent* controlEvent = new ControlEvent(ControlEvent::CET_KEYCODE); - if (!controlEvent) { + ControlMsg* controlMsg = new ControlMsg(ControlMsg::CMT_INJECT_KEYCODE); + if (!controlMsg) { return; } - controlEvent->setKeycodeEventData(action, keyCode, convertMetastate(from->modifiers())); - sendControlEvent(controlEvent); + controlMsg->setInjectKeycodeMsgData(action, keyCode, convertMetastate(from->modifiers())); + sendControlMsg(controlMsg); } AndroidMotioneventButtons InputConvertNormal::convertMouseButtons(Qt::MouseButtons buttonState) diff --git a/QtScrcpy/inputcontrol/receiver.cpp b/QtScrcpy/inputcontrol/receiver.cpp index 51c8cc0..d59a443 100644 --- a/QtScrcpy/inputcontrol/receiver.cpp +++ b/QtScrcpy/inputcontrol/receiver.cpp @@ -4,7 +4,7 @@ #include "receiver.h" #include "controller.h" -#include "deviceevent.h" +#include "devicemsg.h" Receiver::Receiver(Controller* controller) : QObject(controller) { @@ -26,24 +26,24 @@ void Receiver::onReadyRead() while (controlSocket->bytesAvailable()) { QByteArray byteArray = controlSocket->peek(controlSocket->bytesAvailable()); - DeviceEvent deviceEvent; - qint32 consume = deviceEvent.deserialize(byteArray); + DeviceMsg deviceMsg; + qint32 consume = deviceMsg.deserialize(byteArray); if (0 >= consume) { break; } controlSocket->read(consume); - processEvent(&deviceEvent); + processMsg(&deviceMsg); } } -void Receiver::processEvent(DeviceEvent *deviceEvent) +void Receiver::processMsg(DeviceMsg *deviceMsg) { - switch (deviceEvent->type()) { - case DeviceEvent::DET_GET_CLIPBOARD: + switch (deviceMsg->type()) { + case DeviceMsg::DMT_GET_CLIPBOARD: { QClipboard *board = QApplication::clipboard(); QString text; - deviceEvent->getClipboardEventData(text); + deviceMsg->getClipboardMsgData(text); board->setText(text); break; } diff --git a/QtScrcpy/inputcontrol/receiver.h b/QtScrcpy/inputcontrol/receiver.h index ba9bc4c..917ff25 100644 --- a/QtScrcpy/inputcontrol/receiver.h +++ b/QtScrcpy/inputcontrol/receiver.h @@ -4,7 +4,7 @@ #include class Controller; -class DeviceEvent; +class DeviceMsg; class Receiver : public QObject { Q_OBJECT @@ -16,7 +16,7 @@ public slots: void onReadyRead(); protected: - void processEvent(DeviceEvent *deviceEvent); + void processMsg(DeviceMsg *deviceMsg); private: QPointer m_controller; diff --git a/QtScrcpy/videoform.cpp b/QtScrcpy/videoform.cpp index fcc455f..4546d9d 100644 --- a/QtScrcpy/videoform.cpp +++ b/QtScrcpy/videoform.cpp @@ -20,7 +20,7 @@ #include "ui_videoform.h" #include "iconhelper.h" #include "toolform.h" -#include "controlevent.h" +#include "controlmsg.h" #include "mousetap/mousetap.h" VideoForm::VideoForm(const QString& serial, quint16 maxSize, quint32 bitRate, const QString& fileName, QWidget *parent) : @@ -341,50 +341,50 @@ void VideoForm::postVolumeDown() void VideoForm::postTurnOn() { - ControlEvent* controlEvent = new ControlEvent(ControlEvent::CET_BACK_OR_SCREEN_ON); - if (!controlEvent) { + ControlMsg* controlMsg = new ControlMsg(ControlMsg::CMT_BACK_OR_SCREEN_ON); + if (!controlMsg) { return; } - m_inputConvert.sendControlEvent(controlEvent); + m_inputConvert.sendControlMsg(controlMsg); } void VideoForm::expandNotificationPanel() { - ControlEvent* controlEvent = new ControlEvent(ControlEvent::CET_EXPAND_NOTIFICATION_PANEL); - if (!controlEvent) { + ControlMsg* controlMsg = new ControlMsg(ControlMsg::CMT_EXPAND_NOTIFICATION_PANEL); + if (!controlMsg) { return; } - m_inputConvert.sendControlEvent(controlEvent); + m_inputConvert.sendControlMsg(controlMsg); } void VideoForm::collapseNotificationPanel() { - ControlEvent* controlEvent = new ControlEvent(ControlEvent::CET_COLLAPSE_NOTIFICATION_PANEL); - if (!controlEvent) { + ControlMsg* controlMsg = new ControlMsg(ControlMsg::CMT_COLLAPSE_NOTIFICATION_PANEL); + if (!controlMsg) { return; } - m_inputConvert.sendControlEvent(controlEvent); + m_inputConvert.sendControlMsg(controlMsg); } void VideoForm::requestDeviceClipboard() { - ControlEvent* controlEvent = new ControlEvent(ControlEvent::CET_GET_CLIPBOARD); - if (!controlEvent) { + ControlMsg* controlMsg = new ControlMsg(ControlMsg::CMT_GET_CLIPBOARD); + if (!controlMsg) { return; } - m_inputConvert.sendControlEvent(controlEvent); + m_inputConvert.sendControlMsg(controlMsg); } void VideoForm::setDeviceClipboard() { QClipboard *board = QApplication::clipboard(); QString text = board->text(); - ControlEvent* controlEvent = new ControlEvent(ControlEvent::CET_SET_CLIPBOARD); - if (!controlEvent) { + ControlMsg* controlMsg = new ControlMsg(ControlMsg::CMT_SET_CLIPBOARD); + if (!controlMsg) { return; } - controlEvent->setSetClipboardEventData(text); - m_inputConvert.sendControlEvent(controlEvent); + controlMsg->setSetClipboardMsgData(text); + m_inputConvert.sendControlMsg(controlMsg); } void VideoForm::clipboardPaste() @@ -396,12 +396,12 @@ void VideoForm::clipboardPaste() void VideoForm::postTextInput(QString& text) { - ControlEvent* controlEvent = new ControlEvent(ControlEvent::CET_TEXT); - if (!controlEvent) { + ControlMsg* controlMsg = new ControlMsg(ControlMsg::CMT_INJECT_TEXT); + if (!controlMsg) { return; } - controlEvent->setTextEventData(text); - m_inputConvert.sendControlEvent(controlEvent); + controlMsg->setInjectTextMsgData(text); + m_inputConvert.sendControlMsg(controlMsg); } void VideoForm::staysOnTop(bool top) @@ -426,19 +426,19 @@ void VideoForm::postGoHome() void VideoForm::postKeyCodeClick(AndroidKeycode keycode) { - ControlEvent* controlEventDown = new ControlEvent(ControlEvent::CET_KEYCODE); + ControlMsg* controlEventDown = new ControlMsg(ControlMsg::CMT_INJECT_KEYCODE); if (!controlEventDown) { return; } - controlEventDown->setKeycodeEventData(AKEY_EVENT_ACTION_DOWN, keycode, AMETA_NONE); - m_inputConvert.sendControlEvent(controlEventDown); + controlEventDown->setInjectKeycodeMsgData(AKEY_EVENT_ACTION_DOWN, keycode, AMETA_NONE); + m_inputConvert.sendControlMsg(controlEventDown); - ControlEvent* controlEventUp = new ControlEvent(ControlEvent::CET_KEYCODE); + ControlMsg* controlEventUp = new ControlMsg(ControlMsg::CMT_INJECT_KEYCODE); if (!controlEventUp) { return; } - controlEventUp->setKeycodeEventData(AKEY_EVENT_ACTION_UP, keycode, AMETA_NONE); - m_inputConvert.sendControlEvent(controlEventUp); + controlEventUp->setInjectKeycodeMsgData(AKEY_EVENT_ACTION_UP, keycode, AMETA_NONE); + m_inputConvert.sendControlMsg(controlEventUp); } void VideoForm::mousePressEvent(QMouseEvent *event) diff --git a/server/src/main/java/com/genymobile/scrcpy/ControlEvent.java b/server/src/main/java/com/genymobile/scrcpy/ControlMessage.java similarity index 56% rename from server/src/main/java/com/genymobile/scrcpy/ControlEvent.java rename to server/src/main/java/com/genymobile/scrcpy/ControlMessage.java index 1196ea1..73bb4b5 100644 --- a/server/src/main/java/com/genymobile/scrcpy/ControlEvent.java +++ b/server/src/main/java/com/genymobile/scrcpy/ControlMessage.java @@ -1,21 +1,21 @@ package com.genymobile.scrcpy; /** - * Union of all supported event types, identified by their {@code type}. + * Union of all supported msg types, identified by their {@code type}. */ -public final class ControlEvent { +public final class ControlMessage { - public static final int TYPE_KEYCODE = 0; - public static final int TYPE_TEXT = 1; - public static final int TYPE_MOUSE = 2; - public static final int TYPE_SCROLL = 3; + public static final int TYPE_INJECT_KEYCODE = 0; + public static final int TYPE_INJECT_TEXT = 1; + public static final int TYPE_INJECT_MOUSE = 2; + public static final int TYPE_INJECT_SCROLL = 3; public static final int TYPE_BACK_OR_SCREEN_ON = 4; public static final int TYPE_EXPAND_NOTIFICATION_PANEL = 5; public static final int TYPE_COLLAPSE_NOTIFICATION_PANEL = 6; public static final int TYPE_GET_CLIPBOARD = 7; public static final int TYPE_SET_CLIPBOARD = 8; - public static final int TYPE_TOUCH = 9; + public static final int TYPE_INJECT_TOUCH = 9; private int type; @@ -29,61 +29,61 @@ public final class ControlEvent { private int hScroll; private int vScroll; - private ControlEvent() { + private ControlMessage() { } - public static ControlEvent createKeycodeControlEvent(int action, int keycode, int metaState) { - ControlEvent event = new ControlEvent(); - event.type = TYPE_KEYCODE; + public static ControlMessage createInjectKeycode(int action, int keycode, int metaState) { + ControlMessage event = new ControlMessage(); + event.type = TYPE_INJECT_KEYCODE; event.action = action; event.keycode = keycode; event.metaState = metaState; return event; } - public static ControlEvent createTextControlEvent(String text) { - ControlEvent event = new ControlEvent(); - event.type = TYPE_TEXT; + public static ControlMessage createInjectText(String text) { + ControlMessage event = new ControlMessage(); + event.type = TYPE_INJECT_TEXT; event.text = text; return event; } - public static ControlEvent createMotionControlEvent(int action, int buttons, Position position) { - ControlEvent event = new ControlEvent(); - event.type = TYPE_MOUSE; + public static ControlMessage createInjectMotion(int action, int buttons, Position position) { + ControlMessage event = new ControlMessage(); + event.type = TYPE_INJECT_MOUSE; event.action = action; event.buttons = buttons; event.position = position; return event; } - public static ControlEvent createMotionTouchEvent(int id, int action, Position position) { - ControlEvent event = new ControlEvent(); - event.type = TYPE_TOUCH; + public static ControlMessage createInjectMotionTouch(int id, int action, Position position) { + ControlMessage event = new ControlMessage(); + event.type = TYPE_INJECT_TOUCH; event.action = action; event.id = id; event.position = position; return event; } - public static ControlEvent createScrollControlEvent(Position position, int hScroll, int vScroll) { - ControlEvent event = new ControlEvent(); - event.type = TYPE_SCROLL; + public static ControlMessage createInjectScroll(Position position, int hScroll, int vScroll) { + ControlMessage event = new ControlMessage(); + event.type = TYPE_INJECT_SCROLL; event.position = position; event.hScroll = hScroll; event.vScroll = vScroll; return event; } - public static ControlEvent createSetClipboardControlEvent(String text) { - ControlEvent event = new ControlEvent(); + public static ControlMessage createSetClipboard(String text) { + ControlMessage event = new ControlMessage(); event.type = TYPE_SET_CLIPBOARD; event.text = text; return event; } - public static ControlEvent createSimpleControlEvent(int type) { - ControlEvent event = new ControlEvent(); + public static ControlMessage createEmpty(int type) { + ControlMessage event = new ControlMessage(); event.type = type; return event; } diff --git a/server/src/main/java/com/genymobile/scrcpy/ControlEventReader.java b/server/src/main/java/com/genymobile/scrcpy/ControlMessageReader.java similarity index 60% rename from server/src/main/java/com/genymobile/scrcpy/ControlEventReader.java rename to server/src/main/java/com/genymobile/scrcpy/ControlMessageReader.java index f4688c0..c06b599 100644 --- a/server/src/main/java/com/genymobile/scrcpy/ControlEventReader.java +++ b/server/src/main/java/com/genymobile/scrcpy/ControlMessageReader.java @@ -6,12 +6,12 @@ import java.io.InputStream; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; -public class ControlEventReader { +public class ControlMessageReader { - private static final int KEYCODE_PAYLOAD_LENGTH = 9; - private static final int MOUSE_PAYLOAD_LENGTH = 13; - private static final int TOUCH_PAYLOAD_LENGTH = 10; - private static final int SCROLL_PAYLOAD_LENGTH = 16; + private static final int INJECT_KEYCODE_PAYLOAD_LENGTH = 9; + private static final int INJECT_MOUSE_PAYLOAD_LENGTH = 13; + private static final int INJECT_SCROLL_PAYLOAD_LENGTH = 16; + private static final int INJECT_TOUCH_PAYLOAD_LENGTH = 10; public static final int TEXT_MAX_LENGTH = 300; public static final int CLIPBOARD_TEXT_MAX_LENGTH = 4093; @@ -21,7 +21,7 @@ public class ControlEventReader { private final ByteBuffer buffer = ByteBuffer.wrap(rawBuffer); private final byte[] textBuffer = new byte[CLIPBOARD_TEXT_MAX_LENGTH]; - public ControlEventReader() { + public ControlMessageReader() { // invariant: the buffer is always in "get" mode buffer.limit(0); } @@ -38,43 +38,43 @@ public class ControlEventReader { int head = buffer.position(); int r = input.read(rawBuffer, head, rawBuffer.length - head); if (r == -1) { - throw new EOFException("Event controller socket closed"); + throw new EOFException("Controller socket closed"); } buffer.position(head + r); buffer.flip(); } - public ControlEvent next() { + public ControlMessage next() { if (!buffer.hasRemaining()) { return null; } int savedPosition = buffer.position(); int type = buffer.get(); - ControlEvent controlEvent; + ControlMessage controlEvent; switch (type) { - case ControlEvent.TYPE_KEYCODE: - controlEvent = parseKeycodeControlEvent(); + case ControlMessage.TYPE_INJECT_KEYCODE: + controlEvent = parseInjectKeycode(); break; - case ControlEvent.TYPE_TEXT: - controlEvent = parseTextControlEvent(); + case ControlMessage.TYPE_INJECT_TEXT: + controlEvent = parseInjectText(); break; - case ControlEvent.TYPE_MOUSE: - controlEvent = parseMouseControlEvent(); + case ControlMessage.TYPE_INJECT_MOUSE: + controlEvent = parseInjectMouse(); break; - case ControlEvent.TYPE_TOUCH: - controlEvent = parseMouseTouchEvent(); + case ControlMessage.TYPE_INJECT_TOUCH: + controlEvent = parseInjectMouseTouch(); break; - case ControlEvent.TYPE_SCROLL: - controlEvent = parseScrollControlEvent(); + case ControlMessage.TYPE_INJECT_SCROLL: + controlEvent = parseInjectScroll(); break; - case ControlEvent.TYPE_SET_CLIPBOARD: - controlEvent = parseSetClipboardEvent(); + case ControlMessage.TYPE_SET_CLIPBOARD: + controlEvent = parseSetClipboard(); break; - case ControlEvent.TYPE_BACK_OR_SCREEN_ON: - case ControlEvent.TYPE_EXPAND_NOTIFICATION_PANEL: - case ControlEvent.TYPE_COLLAPSE_NOTIFICATION_PANEL: - case ControlEvent.TYPE_GET_CLIPBOARD: - controlEvent = ControlEvent.createSimpleControlEvent(type); + case ControlMessage.TYPE_BACK_OR_SCREEN_ON: + case ControlMessage.TYPE_EXPAND_NOTIFICATION_PANEL: + case ControlMessage.TYPE_COLLAPSE_NOTIFICATION_PANEL: + case ControlMessage.TYPE_GET_CLIPBOARD: + controlEvent = ControlMessage.createEmpty(type); break; default: Ln.w("Unknown event type: " + type); @@ -89,14 +89,14 @@ public class ControlEventReader { return controlEvent; } - private ControlEvent parseKeycodeControlEvent() { - if (buffer.remaining() < KEYCODE_PAYLOAD_LENGTH) { + private ControlMessage parseInjectKeycode() { + if (buffer.remaining() < INJECT_KEYCODE_PAYLOAD_LENGTH) { return null; } int action = toUnsigned(buffer.get()); int keycode = buffer.getInt(); int metaState = buffer.getInt(); - return ControlEvent.createKeycodeControlEvent(action, keycode, metaState); + return ControlMessage.createInjectKeycode(action, keycode, metaState); } private String parseString() { @@ -111,50 +111,50 @@ public class ControlEventReader { return new String(textBuffer, 0, len, StandardCharsets.UTF_8); } - private ControlEvent parseTextControlEvent() { + private ControlMessage parseInjectText() { String text = parseString(); if (text == null) { return null; } - return ControlEvent.createTextControlEvent(text); + return ControlMessage.createInjectText(text); } - private ControlEvent parseMouseControlEvent() { - if (buffer.remaining() < MOUSE_PAYLOAD_LENGTH) { + private ControlMessage parseInjectMouse() { + if (buffer.remaining() < INJECT_MOUSE_PAYLOAD_LENGTH) { return null; } int action = toUnsigned(buffer.get()); int buttons = buffer.getInt(); Position position = readPosition(buffer); - return ControlEvent.createMotionControlEvent(action, buttons, position); + return ControlMessage.createInjectMotion(action, buttons, position); } - private ControlEvent parseMouseTouchEvent() { - if (buffer.remaining() < TOUCH_PAYLOAD_LENGTH) { + private ControlMessage parseInjectMouseTouch() { + if (buffer.remaining() < INJECT_TOUCH_PAYLOAD_LENGTH) { return null; } int id = toUnsigned(buffer.get()); int action = toUnsigned(buffer.get()); Position position = readPosition(buffer); - return ControlEvent.createMotionTouchEvent(id, action, position); + return ControlMessage.createInjectMotionTouch(id, action, position); } - private ControlEvent parseScrollControlEvent() { - if (buffer.remaining() < SCROLL_PAYLOAD_LENGTH) { + private ControlMessage parseInjectScroll() { + if (buffer.remaining() < INJECT_SCROLL_PAYLOAD_LENGTH) { return null; } Position position = readPosition(buffer); int hScroll = buffer.getInt(); int vScroll = buffer.getInt(); - return ControlEvent.createScrollControlEvent(position, hScroll, vScroll); + return ControlMessage.createInjectScroll(position, hScroll, vScroll); } - private ControlEvent parseSetClipboardEvent() { + private ControlMessage parseSetClipboard() { String text = parseString(); if (text == null) { return null; } - return ControlEvent.createSetClipboardControlEvent(text); + return ControlMessage.createSetClipboard(text); } private static Position readPosition(ByteBuffer buffer) { diff --git a/server/src/main/java/com/genymobile/scrcpy/EventController.java b/server/src/main/java/com/genymobile/scrcpy/Controller.java similarity index 87% rename from server/src/main/java/com/genymobile/scrcpy/EventController.java rename to server/src/main/java/com/genymobile/scrcpy/Controller.java index 0bf6055..5aeb252 100644 --- a/server/src/main/java/com/genymobile/scrcpy/EventController.java +++ b/server/src/main/java/com/genymobile/scrcpy/Controller.java @@ -13,11 +13,11 @@ import android.view.MotionEvent; import java.io.IOException; import java.util.Vector; -public class EventController { +public class Controller { private final Device device; private final DesktopConnection connection; - private final EventSender sender; + private final DeviceMessageSender sender; private final KeyCharacterMap charMap = KeyCharacterMap.load(KeyCharacterMap.VIRTUAL_KEYBOARD); @@ -25,10 +25,10 @@ public class EventController { private Vector pointerProperties = new Vector(); private Vector pointerCoords = new Vector(); - public EventController(Device device, DesktopConnection connection) { + public Controller(Device device, DesktopConnection connection) { this.device = device; this.connection = connection; - sender = new EventSender(connection); + sender = new DeviceMessageSender(connection); } private int getPointer(int id) { @@ -98,7 +98,7 @@ public class EventController { } } - public EventSender getSender() { + public DeviceMessageSender getSender() { return sender; } @@ -112,38 +112,38 @@ public class EventController { } private void handleEvent() throws IOException { - ControlEvent controlEvent = connection.receiveControlEvent(); - switch (controlEvent.getType()) { - case ControlEvent.TYPE_KEYCODE: - injectKeycode(controlEvent.getAction(), controlEvent.getKeycode(), controlEvent.getMetaState()); + ControlMessage msg = connection.receiveControlMessage(); + switch (msg.getType()) { + case ControlMessage.TYPE_INJECT_KEYCODE: + injectKeycode(msg.getAction(), msg.getKeycode(), msg.getMetaState()); break; - case ControlEvent.TYPE_TEXT: - injectText(controlEvent.getText()); + case ControlMessage.TYPE_INJECT_TEXT: + injectText(msg.getText()); break; - case ControlEvent.TYPE_MOUSE: - injectMouse(controlEvent.getAction(), controlEvent.getButtons(), controlEvent.getPosition()); + case ControlMessage.TYPE_INJECT_MOUSE: + injectMouse(msg.getAction(), msg.getButtons(), msg.getPosition()); break; - case ControlEvent.TYPE_TOUCH: - injectTouch(controlEvent.getId(), controlEvent.getAction(), controlEvent.getPosition()); + case ControlMessage.TYPE_INJECT_TOUCH: + injectTouch(msg.getId(), msg.getAction(), msg.getPosition()); break; - case ControlEvent.TYPE_SCROLL: - injectScroll(controlEvent.getPosition(), controlEvent.getHScroll(), controlEvent.getVScroll()); + case ControlMessage.TYPE_INJECT_SCROLL: + injectScroll(msg.getPosition(), msg.getHScroll(), msg.getVScroll()); break; - case ControlEvent.TYPE_BACK_OR_SCREEN_ON: + case ControlMessage.TYPE_BACK_OR_SCREEN_ON: pressBackOrTurnScreenOn(); break; - case ControlEvent.TYPE_EXPAND_NOTIFICATION_PANEL: + case ControlMessage.TYPE_EXPAND_NOTIFICATION_PANEL: device.expandNotificationPanel(); break; - case ControlEvent.TYPE_COLLAPSE_NOTIFICATION_PANEL: + case ControlMessage.TYPE_COLLAPSE_NOTIFICATION_PANEL: device.collapsePanels(); break; - case ControlEvent.TYPE_GET_CLIPBOARD: + case ControlMessage.TYPE_GET_CLIPBOARD: String clipboardText = device.getClipboardText(); sender.pushClipboardText(clipboardText); break; - case ControlEvent.TYPE_SET_CLIPBOARD: - device.setClipboardText(controlEvent.getText()); + case ControlMessage.TYPE_SET_CLIPBOARD: + device.setClipboardText(msg.getText()); break; default: // do nothing diff --git a/server/src/main/java/com/genymobile/scrcpy/DesktopConnection.java b/server/src/main/java/com/genymobile/scrcpy/DesktopConnection.java index 99aa9d2..e375cf5 100644 --- a/server/src/main/java/com/genymobile/scrcpy/DesktopConnection.java +++ b/server/src/main/java/com/genymobile/scrcpy/DesktopConnection.java @@ -25,8 +25,8 @@ public final class DesktopConnection implements Closeable { private final OutputStream controlOutputStream; - private final ControlEventReader reader = new ControlEventReader(); - private final DeviceEventWriter writer = new DeviceEventWriter(); + private final ControlMessageReader reader = new ControlMessageReader(); + private final DeviceMessageWriter writer = new DeviceMessageWriter(); private DesktopConnection(LocalSocket videoSocket, LocalSocket controlSocket) throws IOException { this.videoSocket = videoSocket; @@ -105,16 +105,16 @@ public final class DesktopConnection implements Closeable { return videoFd; } - public ControlEvent receiveControlEvent() throws IOException { - ControlEvent event = reader.next(); - while (event == null) { + public ControlMessage receiveControlMessage() throws IOException { + ControlMessage msg = reader.next(); + while (msg == null) { reader.readFrom(controlInputStream); - event = reader.next(); + msg = reader.next(); } - return event; + return msg; } - public void sendDeviceEvent(DeviceEvent event) throws IOException { - writer.writeTo(event, controlOutputStream); + public void sendDeviceMessage(DeviceMessage msg) throws IOException { + writer.writeTo(msg, controlOutputStream); } } diff --git a/server/src/main/java/com/genymobile/scrcpy/DeviceEvent.java b/server/src/main/java/com/genymobile/scrcpy/DeviceEvent.java deleted file mode 100644 index 97bcbfc..0000000 --- a/server/src/main/java/com/genymobile/scrcpy/DeviceEvent.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.genymobile.scrcpy; - -public final class DeviceEvent { - - public static final int TYPE_GET_CLIPBOARD = 0; - - private int type; - private String text; - - private DeviceEvent() { - } - - public static DeviceEvent createGetClipboardEvent(String text) { - DeviceEvent event = new DeviceEvent(); - event.type = TYPE_GET_CLIPBOARD; - event.text = text; - return event; - } - - public int getType() { - return type; - } - - public String getText() { - return text; - } -} diff --git a/server/src/main/java/com/genymobile/scrcpy/DeviceMessage.java b/server/src/main/java/com/genymobile/scrcpy/DeviceMessage.java new file mode 100644 index 0000000..c6eebd3 --- /dev/null +++ b/server/src/main/java/com/genymobile/scrcpy/DeviceMessage.java @@ -0,0 +1,27 @@ +package com.genymobile.scrcpy; + +public final class DeviceMessage { + + public static final int TYPE_CLIPBOARD = 0; + + private int type; + private String text; + + private DeviceMessage() { + } + + public static DeviceMessage createClipboard(String text) { + DeviceMessage event = new DeviceMessage(); + event.type = TYPE_CLIPBOARD; + event.text = text; + return event; + } + + public int getType() { + return type; + } + + public String getText() { + return text; + } +} diff --git a/server/src/main/java/com/genymobile/scrcpy/EventSender.java b/server/src/main/java/com/genymobile/scrcpy/DeviceMessageSender.java similarity index 74% rename from server/src/main/java/com/genymobile/scrcpy/EventSender.java rename to server/src/main/java/com/genymobile/scrcpy/DeviceMessageSender.java index 9f50b16..846bc43 100644 --- a/server/src/main/java/com/genymobile/scrcpy/EventSender.java +++ b/server/src/main/java/com/genymobile/scrcpy/DeviceMessageSender.java @@ -2,13 +2,13 @@ package com.genymobile.scrcpy; import java.io.IOException; -public final class EventSender { +public final class DeviceMessageSender { private final DesktopConnection connection; private String clipboardText; - public EventSender(DesktopConnection connection) { + public DeviceMessageSender(DesktopConnection connection) { this.connection = connection; } @@ -27,8 +27,8 @@ public final class EventSender { text = clipboardText; clipboardText = null; } - DeviceEvent event = DeviceEvent.createGetClipboardEvent(text); - connection.sendDeviceEvent(event); + DeviceMessage msg = DeviceMessage.createClipboard(text); + connection.sendDeviceMessage(msg); } } } diff --git a/server/src/main/java/com/genymobile/scrcpy/DeviceEventWriter.java b/server/src/main/java/com/genymobile/scrcpy/DeviceMessageWriter.java similarity index 72% rename from server/src/main/java/com/genymobile/scrcpy/DeviceEventWriter.java rename to server/src/main/java/com/genymobile/scrcpy/DeviceMessageWriter.java index e183a22..6b11a51 100644 --- a/server/src/main/java/com/genymobile/scrcpy/DeviceEventWriter.java +++ b/server/src/main/java/com/genymobile/scrcpy/DeviceMessageWriter.java @@ -5,7 +5,7 @@ import java.io.OutputStream; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; -public class DeviceEventWriter { +public class DeviceMessageWriter { public static final int CLIPBOARD_TEXT_MAX_LENGTH = 4093; private static final int MAX_EVENT_SIZE = CLIPBOARD_TEXT_MAX_LENGTH + 3; @@ -14,12 +14,12 @@ public class DeviceEventWriter { private final ByteBuffer buffer = ByteBuffer.wrap(rawBuffer); @SuppressWarnings("checkstyle:MagicNumber") - public void writeTo(DeviceEvent event, OutputStream output) throws IOException { + public void writeTo(DeviceMessage msg, OutputStream output) throws IOException { buffer.clear(); - buffer.put((byte) DeviceEvent.TYPE_GET_CLIPBOARD); - switch (event.getType()) { - case DeviceEvent.TYPE_GET_CLIPBOARD: - String text = event.getText(); + buffer.put((byte) DeviceMessage.TYPE_CLIPBOARD); + switch (msg.getType()) { + case DeviceMessage.TYPE_CLIPBOARD: + String text = msg.getText(); byte[] raw = text.getBytes(StandardCharsets.UTF_8); int len = StringUtils.getUtf8TruncationIndex(raw, CLIPBOARD_TEXT_MAX_LENGTH); buffer.putShort((short) len); @@ -27,7 +27,7 @@ public class DeviceEventWriter { output.write(rawBuffer, 0, buffer.position()); break; default: - Ln.w("Unknown device event: " + event.getType()); + Ln.w("Unknown device msg: " + msg.getType()); break; } } diff --git a/server/src/main/java/com/genymobile/scrcpy/Server.java b/server/src/main/java/com/genymobile/scrcpy/Server.java index ed8a40f..95dbdb3 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Server.java +++ b/server/src/main/java/com/genymobile/scrcpy/Server.java @@ -20,11 +20,11 @@ public final class Server { try (DesktopConnection connection = DesktopConnection.open(device, tunnelForward)) { ScreenEncoder screenEncoder = new ScreenEncoder(options.getSendFrameMeta(), options.getBitRate()); - EventController controller = new EventController(device, connection); + Controller controller = new Controller(device, connection); // asynchronous - startEventController(controller); - startEventSender(controller.getSender()); + startController(controller); + startDeviceMessageSender(controller.getSender()); try { // synchronous @@ -36,7 +36,7 @@ public final class Server { } } - private static void startEventController(final EventController controller) { + private static void startController(final Controller controller) { new Thread(new Runnable() { @Override public void run() { @@ -44,13 +44,13 @@ public final class Server { controller.control(); } catch (IOException e) { // this is expected on close - Ln.d("Event controller stopped"); + Ln.d("Controller stopped"); } } }).start(); } - private static void startEventSender(final EventSender sender) { + private static void startDeviceMessageSender(final DeviceMessageSender sender) { new Thread(new Runnable() { @Override public void run() { @@ -58,7 +58,7 @@ public final class Server { sender.loop(); } catch (IOException | InterruptedException e) { // this is expected on close - Ln.d("Event sender stopped"); + Ln.d("Devide message sender stopped"); } } }).start(); diff --git a/server/src/test/java/com/genymobile/scrcpy/ControlEventReaderTest.java b/server/src/test/java/com/genymobile/scrcpy/ControlEventReaderTest.java deleted file mode 100644 index 3e97096..0000000 --- a/server/src/test/java/com/genymobile/scrcpy/ControlEventReaderTest.java +++ /dev/null @@ -1,173 +0,0 @@ -package com.genymobile.scrcpy; - -import android.view.KeyEvent; -import android.view.MotionEvent; - -import org.junit.Assert; -import org.junit.Test; - -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.DataOutputStream; -import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.util.Arrays; - - -public class ControlEventReaderTest { - - @Test - public void testParseKeycodeEvent() throws IOException { - ControlEventReader reader = new ControlEventReader(); - - ByteArrayOutputStream bos = new ByteArrayOutputStream(); - DataOutputStream dos = new DataOutputStream(bos); - dos.writeByte(ControlEvent.TYPE_KEYCODE); - dos.writeByte(KeyEvent.ACTION_UP); - dos.writeInt(KeyEvent.KEYCODE_ENTER); - dos.writeInt(KeyEvent.META_CTRL_ON); - byte[] packet = bos.toByteArray(); - - reader.readFrom(new ByteArrayInputStream(packet)); - ControlEvent event = reader.next(); - - Assert.assertEquals(ControlEvent.TYPE_KEYCODE, event.getType()); - Assert.assertEquals(KeyEvent.ACTION_UP, event.getAction()); - Assert.assertEquals(KeyEvent.KEYCODE_ENTER, event.getKeycode()); - Assert.assertEquals(KeyEvent.META_CTRL_ON, event.getMetaState()); - } - - @Test - public void testParseTextEvent() throws IOException { - ControlEventReader reader = new ControlEventReader(); - - ByteArrayOutputStream bos = new ByteArrayOutputStream(); - DataOutputStream dos = new DataOutputStream(bos); - dos.writeByte(ControlEvent.TYPE_TEXT); - byte[] text = "testé".getBytes(StandardCharsets.UTF_8); - dos.writeShort(text.length); - dos.write(text); - byte[] packet = bos.toByteArray(); - - reader.readFrom(new ByteArrayInputStream(packet)); - ControlEvent event = reader.next(); - - Assert.assertEquals(ControlEvent.TYPE_TEXT, event.getType()); - Assert.assertEquals("testé", event.getText()); - } - - @Test - public void testParseLongTextEvent() throws IOException { - ControlEventReader reader = new ControlEventReader(); - - ByteArrayOutputStream bos = new ByteArrayOutputStream(); - DataOutputStream dos = new DataOutputStream(bos); - dos.writeByte(ControlEvent.TYPE_TEXT); - byte[] text = new byte[ControlEventReader.TEXT_MAX_LENGTH]; - Arrays.fill(text, (byte) 'a'); - dos.writeShort(text.length); - dos.write(text); - byte[] packet = bos.toByteArray(); - - reader.readFrom(new ByteArrayInputStream(packet)); - ControlEvent event = reader.next(); - - Assert.assertEquals(ControlEvent.TYPE_TEXT, event.getType()); - Assert.assertEquals(new String(text, StandardCharsets.US_ASCII), event.getText()); - } - - @Test - public void testParseMouseEvent() throws IOException { - ControlEventReader reader = new ControlEventReader(); - - ByteArrayOutputStream bos = new ByteArrayOutputStream(); - DataOutputStream dos = new DataOutputStream(bos); - dos.writeByte(ControlEvent.TYPE_KEYCODE); - dos.writeByte(MotionEvent.ACTION_DOWN); - dos.writeInt(MotionEvent.BUTTON_PRIMARY); - dos.writeInt(KeyEvent.META_CTRL_ON); - byte[] packet = bos.toByteArray(); - - reader.readFrom(new ByteArrayInputStream(packet)); - ControlEvent event = reader.next(); - - Assert.assertEquals(ControlEvent.TYPE_KEYCODE, event.getType()); - Assert.assertEquals(MotionEvent.ACTION_DOWN, event.getAction()); - Assert.assertEquals(MotionEvent.BUTTON_PRIMARY, event.getKeycode()); - Assert.assertEquals(KeyEvent.META_CTRL_ON, event.getMetaState()); - } - - @Test - public void testMultiEvents() throws IOException { - ControlEventReader reader = new ControlEventReader(); - - ByteArrayOutputStream bos = new ByteArrayOutputStream(); - DataOutputStream dos = new DataOutputStream(bos); - - dos.writeByte(ControlEvent.TYPE_KEYCODE); - dos.writeByte(KeyEvent.ACTION_UP); - dos.writeInt(KeyEvent.KEYCODE_ENTER); - dos.writeInt(KeyEvent.META_CTRL_ON); - - dos.writeByte(ControlEvent.TYPE_KEYCODE); - dos.writeByte(MotionEvent.ACTION_DOWN); - dos.writeInt(MotionEvent.BUTTON_PRIMARY); - dos.writeInt(KeyEvent.META_CTRL_ON); - - byte[] packet = bos.toByteArray(); - reader.readFrom(new ByteArrayInputStream(packet)); - - ControlEvent event = reader.next(); - Assert.assertEquals(ControlEvent.TYPE_KEYCODE, event.getType()); - Assert.assertEquals(KeyEvent.ACTION_UP, event.getAction()); - Assert.assertEquals(KeyEvent.KEYCODE_ENTER, event.getKeycode()); - Assert.assertEquals(KeyEvent.META_CTRL_ON, event.getMetaState()); - - event = reader.next(); - Assert.assertEquals(ControlEvent.TYPE_KEYCODE, event.getType()); - Assert.assertEquals(MotionEvent.ACTION_DOWN, event.getAction()); - Assert.assertEquals(MotionEvent.BUTTON_PRIMARY, event.getKeycode()); - Assert.assertEquals(KeyEvent.META_CTRL_ON, event.getMetaState()); - } - - @Test - public void testPartialEvents() throws IOException { - ControlEventReader reader = new ControlEventReader(); - - ByteArrayOutputStream bos = new ByteArrayOutputStream(); - DataOutputStream dos = new DataOutputStream(bos); - - dos.writeByte(ControlEvent.TYPE_KEYCODE); - dos.writeByte(KeyEvent.ACTION_UP); - dos.writeInt(KeyEvent.KEYCODE_ENTER); - dos.writeInt(KeyEvent.META_CTRL_ON); - - dos.writeByte(ControlEvent.TYPE_KEYCODE); - dos.writeByte(MotionEvent.ACTION_DOWN); - - byte[] packet = bos.toByteArray(); - reader.readFrom(new ByteArrayInputStream(packet)); - - ControlEvent event = reader.next(); - Assert.assertEquals(ControlEvent.TYPE_KEYCODE, event.getType()); - Assert.assertEquals(KeyEvent.ACTION_UP, event.getAction()); - Assert.assertEquals(KeyEvent.KEYCODE_ENTER, event.getKeycode()); - Assert.assertEquals(KeyEvent.META_CTRL_ON, event.getMetaState()); - - event = reader.next(); - Assert.assertNull(event); // the event is not complete - - bos.reset(); - dos.writeInt(MotionEvent.BUTTON_PRIMARY); - dos.writeInt(KeyEvent.META_CTRL_ON); - packet = bos.toByteArray(); - reader.readFrom(new ByteArrayInputStream(packet)); - - // the event is now complete - event = reader.next(); - Assert.assertEquals(ControlEvent.TYPE_KEYCODE, event.getType()); - Assert.assertEquals(MotionEvent.ACTION_DOWN, event.getAction()); - Assert.assertEquals(MotionEvent.BUTTON_PRIMARY, event.getKeycode()); - Assert.assertEquals(KeyEvent.META_CTRL_ON, event.getMetaState()); - } -} diff --git a/third_party/scrcpy-server.jar b/third_party/scrcpy-server.jar index 5f8ad87d76fe5bd3ef596e621f2d34b49344f4c8..aaad381392003c928cd698c6db555351e26ae007 100644 GIT binary patch delta 26393 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 delta 26371 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