mirror of
https://github.com/barry-ran/QtScrcpy.git
synced 2025-08-01 21:38:40 +00:00
add:实现剪切板功能
This commit is contained in:
parent
b44f78f19b
commit
b33b22bf16
27 changed files with 650 additions and 56 deletions
|
@ -1,6 +1,7 @@
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
|
|
||||||
#include "controlevent.h"
|
#include "controlevent.h"
|
||||||
|
#include "bufferutil.h"
|
||||||
|
|
||||||
ControlEvent::ControlEvent(ControlEventType controlEventType)
|
ControlEvent::ControlEvent(ControlEventType controlEventType)
|
||||||
: QScrcpyEvent(Control)
|
: QScrcpyEvent(Control)
|
||||||
|
@ -8,6 +9,19 @@ ControlEvent::ControlEvent(ControlEventType controlEventType)
|
||||||
m_data.type = controlEventType;
|
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)
|
void ControlEvent::setKeycodeEventData(AndroidKeyeventAction action, AndroidKeycode keycode, AndroidMetastate metastate)
|
||||||
{
|
{
|
||||||
m_data.keycodeEvent.action = action;
|
m_data.keycodeEvent.action = action;
|
||||||
|
@ -15,16 +29,17 @@ void ControlEvent::setKeycodeEventData(AndroidKeyeventAction action, AndroidKeyc
|
||||||
m_data.keycodeEvent.metastate = metastate;
|
m_data.keycodeEvent.metastate = metastate;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ControlEvent::setTextEventData(QString text)
|
void ControlEvent::setTextEventData(QString& text)
|
||||||
{
|
{
|
||||||
// write length (2 byte) + string (non nul-terminated)
|
// write length (2 byte) + string (non nul-terminated)
|
||||||
if (TEXT_MAX_CHARACTER_LENGTH < text.length()) {
|
if (CONTROL_EVENT_TEXT_MAX_LENGTH < text.length()) {
|
||||||
// injecting a text takes time, so limit the text length
|
// injecting a text takes time, so limit the text length
|
||||||
text = text.left(TEXT_MAX_CHARACTER_LENGTH);
|
text = text.left(CONTROL_EVENT_TEXT_MAX_LENGTH);
|
||||||
}
|
}
|
||||||
QByteArray tmp = text.toUtf8();
|
QByteArray tmp = text.toUtf8();
|
||||||
memset(m_data.textEvent.text, 0, sizeof (m_data.textEvent.text));
|
m_data.textEvent.text = new char[tmp.length() + 1];
|
||||||
memcpy(m_data.textEvent.text, tmp.data(), tmp.length());
|
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)
|
void ControlEvent::setMouseEventData(AndroidMotioneventAction action, AndroidMotioneventButtons buttons, QRect position)
|
||||||
|
@ -48,26 +63,27 @@ void ControlEvent::setScrollEventData(QRect position, qint32 hScroll, qint32 vSc
|
||||||
m_data.scrollEvent.vScroll = vScroll;
|
m_data.scrollEvent.vScroll = vScroll;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ControlEvent::write32(QBuffer &buffer, quint32 value)
|
void ControlEvent::setSetClipboardEventData(QString &text)
|
||||||
{
|
{
|
||||||
buffer.putChar(value >> 24);
|
if (text.isEmpty()) {
|
||||||
buffer.putChar(value >> 16);
|
return;
|
||||||
buffer.putChar(value >> 8);
|
}
|
||||||
buffer.putChar(value);
|
if (CONTROL_EVENT_CLIPBOARD_TEXT_MAX_LENGTH < text.length()) {
|
||||||
}
|
text = text.left(CONTROL_EVENT_CLIPBOARD_TEXT_MAX_LENGTH);
|
||||||
|
}
|
||||||
|
|
||||||
void ControlEvent::write16(QBuffer &buffer, quint32 value)
|
QByteArray tmp = text.toUtf8();
|
||||||
{
|
m_data.setClipboardEvent.text = new char[tmp.length() + 1];
|
||||||
buffer.putChar(value >> 8);
|
memcpy(m_data.setClipboardEvent.text, tmp.data(), tmp.length());
|
||||||
buffer.putChar(value);
|
m_data.setClipboardEvent.text[tmp.length()] = '\0';
|
||||||
}
|
}
|
||||||
|
|
||||||
void ControlEvent::writePosition(QBuffer &buffer, const QRect& value)
|
void ControlEvent::writePosition(QBuffer &buffer, const QRect& value)
|
||||||
{
|
{
|
||||||
write16(buffer, value.left());
|
BufferUtil::write16(buffer, value.left());
|
||||||
write16(buffer, value.top());
|
BufferUtil::write16(buffer, value.top());
|
||||||
write16(buffer, value.width());
|
BufferUtil::write16(buffer, value.width());
|
||||||
write16(buffer, value.height());
|
BufferUtil::write16(buffer, value.height());
|
||||||
}
|
}
|
||||||
|
|
||||||
QByteArray ControlEvent::serializeData()
|
QByteArray ControlEvent::serializeData()
|
||||||
|
@ -80,18 +96,16 @@ QByteArray ControlEvent::serializeData()
|
||||||
switch (m_data.type) {
|
switch (m_data.type) {
|
||||||
case CET_KEYCODE:
|
case CET_KEYCODE:
|
||||||
buffer.putChar(m_data.keycodeEvent.action);
|
buffer.putChar(m_data.keycodeEvent.action);
|
||||||
write32(buffer, m_data.keycodeEvent.keycode);
|
BufferUtil::write32(buffer, m_data.keycodeEvent.keycode);
|
||||||
write32(buffer, m_data.keycodeEvent.metastate);
|
BufferUtil::write32(buffer, m_data.keycodeEvent.metastate);
|
||||||
break;
|
break;
|
||||||
case CET_TEXT:
|
case CET_TEXT:
|
||||||
{
|
BufferUtil::write16(buffer, strlen(m_data.textEvent.text));
|
||||||
write16(buffer, strlen(m_data.textEvent.text));
|
|
||||||
buffer.write(m_data.textEvent.text, strlen(m_data.textEvent.text));
|
buffer.write(m_data.textEvent.text, strlen(m_data.textEvent.text));
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
case CET_MOUSE:
|
case CET_MOUSE:
|
||||||
buffer.putChar(m_data.mouseEvent.action);
|
buffer.putChar(m_data.mouseEvent.action);
|
||||||
write32(buffer, m_data.mouseEvent.buttons);
|
BufferUtil::write32(buffer, m_data.mouseEvent.buttons);
|
||||||
writePosition(buffer, m_data.mouseEvent.position);
|
writePosition(buffer, m_data.mouseEvent.position);
|
||||||
break;
|
break;
|
||||||
case CET_TOUCH:
|
case CET_TOUCH:
|
||||||
|
@ -101,12 +115,17 @@ QByteArray ControlEvent::serializeData()
|
||||||
break;
|
break;
|
||||||
case CET_SCROLL:
|
case CET_SCROLL:
|
||||||
writePosition(buffer, m_data.scrollEvent.position);
|
writePosition(buffer, m_data.scrollEvent.position);
|
||||||
write32(buffer, m_data.scrollEvent.hScroll);
|
BufferUtil::write32(buffer, m_data.scrollEvent.hScroll);
|
||||||
write32(buffer, m_data.scrollEvent.vScroll);
|
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;
|
break;
|
||||||
case CET_BACK_OR_SCREEN_ON:
|
case CET_BACK_OR_SCREEN_ON:
|
||||||
case CET_EXPAND_NOTIFICATION_PANEL:
|
case CET_EXPAND_NOTIFICATION_PANEL:
|
||||||
case CET_COLLAPSE_NOTIFICATION_PANEL:
|
case CET_COLLAPSE_NOTIFICATION_PANEL:
|
||||||
|
case CET_GET_CLIPBOARD:
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
qDebug() << "Unknown event type:" << m_data.type;
|
qDebug() << "Unknown event type:" << m_data.type;
|
||||||
|
|
|
@ -9,43 +9,48 @@
|
||||||
#include "input.h"
|
#include "input.h"
|
||||||
#include "keycodes.h"
|
#include "keycodes.h"
|
||||||
|
|
||||||
#define TEXT_MAX_CHARACTER_LENGTH 300
|
#define CONTROL_EVENT_TEXT_MAX_LENGTH 300
|
||||||
|
#define CONTROL_EVENT_CLIPBOARD_TEXT_MAX_LENGTH 4093
|
||||||
// ControlEvent
|
// ControlEvent
|
||||||
class ControlEvent : public QScrcpyEvent
|
class ControlEvent : public QScrcpyEvent
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
enum ControlEventType {
|
enum ControlEventType {
|
||||||
|
CET_NULL = -1,
|
||||||
CET_KEYCODE = 0,
|
CET_KEYCODE = 0,
|
||||||
CET_TEXT,
|
CET_TEXT,
|
||||||
CET_MOUSE,
|
CET_MOUSE,
|
||||||
CET_SCROLL,
|
CET_SCROLL,
|
||||||
CET_TOUCH,
|
|
||||||
CET_BACK_OR_SCREEN_ON,
|
CET_BACK_OR_SCREEN_ON,
|
||||||
CET_EXPAND_NOTIFICATION_PANEL,
|
CET_EXPAND_NOTIFICATION_PANEL,
|
||||||
CET_COLLAPSE_NOTIFICATION_PANEL,
|
CET_COLLAPSE_NOTIFICATION_PANEL,
|
||||||
|
CET_GET_CLIPBOARD,
|
||||||
|
CET_SET_CLIPBOARD,
|
||||||
|
|
||||||
|
CET_TOUCH,
|
||||||
};
|
};
|
||||||
|
|
||||||
ControlEvent(ControlEventType controlEventType);
|
ControlEvent(ControlEventType controlEventType);
|
||||||
|
virtual ~ControlEvent();
|
||||||
|
|
||||||
void setKeycodeEventData(AndroidKeyeventAction action, AndroidKeycode keycode, AndroidMetastate metastate);
|
void setKeycodeEventData(AndroidKeyeventAction action, AndroidKeycode keycode, AndroidMetastate metastate);
|
||||||
void setTextEventData(QString text);
|
void setTextEventData(QString& text);
|
||||||
void setMouseEventData(AndroidMotioneventAction action, AndroidMotioneventButtons buttons, QRect position);
|
void setMouseEventData(AndroidMotioneventAction action, AndroidMotioneventButtons buttons, QRect position);
|
||||||
// id 代表一个触摸点,最多支持10个触摸点[0,9]
|
// id 代表一个触摸点,最多支持10个触摸点[0,9]
|
||||||
// action 只能是AMOTION_EVENT_ACTION_DOWN,AMOTION_EVENT_ACTION_UP,AMOTION_EVENT_ACTION_MOVE
|
// action 只能是AMOTION_EVENT_ACTION_DOWN,AMOTION_EVENT_ACTION_UP,AMOTION_EVENT_ACTION_MOVE
|
||||||
// position action动作对应的位置
|
// position action动作对应的位置
|
||||||
void setTouchEventData(quint32 id, AndroidMotioneventAction action, QRect position);
|
void setTouchEventData(quint32 id, AndroidMotioneventAction action, QRect position);
|
||||||
void setScrollEventData(QRect position, qint32 hScroll, qint32 vScroll);
|
void setScrollEventData(QRect position, qint32 hScroll, qint32 vScroll);
|
||||||
|
void setSetClipboardEventData(QString& text);
|
||||||
|
|
||||||
QByteArray serializeData();
|
QByteArray serializeData();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void write32(QBuffer& buffer, quint32 value);
|
|
||||||
void write16(QBuffer& buffer, quint32 value);
|
|
||||||
void writePosition(QBuffer& buffer, const QRect& value);
|
void writePosition(QBuffer& buffer, const QRect& value);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
struct ControlEventData {
|
struct ControlEventData {
|
||||||
ControlEventType type;
|
ControlEventType type = CET_NULL;
|
||||||
union {
|
union {
|
||||||
struct {
|
struct {
|
||||||
AndroidKeyeventAction action;
|
AndroidKeyeventAction action;
|
||||||
|
@ -53,7 +58,7 @@ private:
|
||||||
AndroidMetastate metastate;
|
AndroidMetastate metastate;
|
||||||
} keycodeEvent;
|
} keycodeEvent;
|
||||||
struct {
|
struct {
|
||||||
char text[TEXT_MAX_CHARACTER_LENGTH + 1];
|
char* text = Q_NULLPTR;
|
||||||
} textEvent;
|
} textEvent;
|
||||||
struct {
|
struct {
|
||||||
AndroidMotioneventAction action;
|
AndroidMotioneventAction action;
|
||||||
|
@ -70,6 +75,9 @@ private:
|
||||||
qint32 hScroll;
|
qint32 hScroll;
|
||||||
qint32 vScroll;
|
qint32 vScroll;
|
||||||
} scrollEvent;
|
} scrollEvent;
|
||||||
|
struct {
|
||||||
|
char *text = Q_NULLPTR;
|
||||||
|
} setClipboardEvent;
|
||||||
};
|
};
|
||||||
|
|
||||||
ControlEventData(){}
|
ControlEventData(){}
|
||||||
|
|
|
@ -3,10 +3,11 @@
|
||||||
#include "controller.h"
|
#include "controller.h"
|
||||||
#include "videosocket.h"
|
#include "videosocket.h"
|
||||||
#include "controlevent.h"
|
#include "controlevent.h"
|
||||||
|
#include "receiver.h"
|
||||||
|
|
||||||
Controller::Controller(QObject* parent) : QObject(parent)
|
Controller::Controller(QObject* parent) : QObject(parent)
|
||||||
{
|
{
|
||||||
|
m_receiver = new Receiver(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
Controller::~Controller()
|
Controller::~Controller()
|
||||||
|
@ -16,7 +17,16 @@ Controller::~Controller()
|
||||||
|
|
||||||
void Controller::setControlSocket(QTcpSocket* controlSocket)
|
void Controller::setControlSocket(QTcpSocket* controlSocket)
|
||||||
{
|
{
|
||||||
|
if (m_controlSocket || !controlSocket) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
m_controlSocket = controlSocket;
|
m_controlSocket = controlSocket;
|
||||||
|
connect(controlSocket, &QTcpSocket::readyRead, m_receiver, &Receiver::onReadyRead);
|
||||||
|
}
|
||||||
|
|
||||||
|
QTcpSocket *Controller::getControlSocket()
|
||||||
|
{
|
||||||
|
return m_controlSocket;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Controller::postControlEvent(ControlEvent *controlEvent)
|
void Controller::postControlEvent(ControlEvent *controlEvent)
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
|
|
||||||
class QTcpSocket;
|
class QTcpSocket;
|
||||||
class ControlEvent;
|
class ControlEvent;
|
||||||
|
class Receiver;
|
||||||
class Controller : public QObject
|
class Controller : public QObject
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
@ -14,6 +15,7 @@ public:
|
||||||
virtual ~Controller();
|
virtual ~Controller();
|
||||||
|
|
||||||
void setControlSocket(QTcpSocket* controlSocket);
|
void setControlSocket(QTcpSocket* controlSocket);
|
||||||
|
QTcpSocket* getControlSocket();
|
||||||
void postControlEvent(ControlEvent* controlEvent);
|
void postControlEvent(ControlEvent* controlEvent);
|
||||||
void test(QRect rc);
|
void test(QRect rc);
|
||||||
|
|
||||||
|
@ -25,6 +27,7 @@ private:
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QPointer<QTcpSocket> m_controlSocket;
|
QPointer<QTcpSocket> m_controlSocket;
|
||||||
|
QPointer<Receiver> m_receiver;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // CONTROLLER_H
|
#endif // CONTROLLER_H
|
||||||
|
|
69
QtScrcpy/inputcontrol/deviceevent.cpp
Normal file
69
QtScrcpy/inputcontrol/deviceevent.cpp
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
#include <QDebug>
|
||||||
|
|
||||||
|
#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;
|
||||||
|
}
|
42
QtScrcpy/inputcontrol/deviceevent.h
Normal file
42
QtScrcpy/inputcontrol/deviceevent.h
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
#ifndef DEVICEEVENT_H
|
||||||
|
#define DEVICEEVENT_H
|
||||||
|
|
||||||
|
#include <QBuffer>
|
||||||
|
|
||||||
|
#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
|
|
@ -3,12 +3,16 @@ HEADERS += \
|
||||||
$$PWD/controller.h \
|
$$PWD/controller.h \
|
||||||
$$PWD/inputconvertbase.h \
|
$$PWD/inputconvertbase.h \
|
||||||
$$PWD/inputconvertgame.h \
|
$$PWD/inputconvertgame.h \
|
||||||
$$PWD/inputconvertnormal.h
|
$$PWD/inputconvertnormal.h \
|
||||||
|
$$PWD/deviceevent.h \
|
||||||
|
$$PWD/receiver.h
|
||||||
|
|
||||||
SOURCES += \
|
SOURCES += \
|
||||||
$$PWD/controlevent.cpp \
|
$$PWD/controlevent.cpp \
|
||||||
$$PWD/controller.cpp \
|
$$PWD/controller.cpp \
|
||||||
$$PWD/inputconvertbase.cpp \
|
$$PWD/inputconvertbase.cpp \
|
||||||
$$PWD/inputconvertgame.cpp \
|
$$PWD/inputconvertgame.cpp \
|
||||||
$$PWD/inputconvertnormal.cpp
|
$$PWD/inputconvertnormal.cpp \
|
||||||
|
$$PWD/deviceevent.cpp \
|
||||||
|
$$PWD/receiver.cpp
|
||||||
|
|
||||||
|
|
53
QtScrcpy/inputcontrol/receiver.cpp
Normal file
53
QtScrcpy/inputcontrol/receiver.cpp
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
#include <QTcpSocket>
|
||||||
|
#include <QApplication>
|
||||||
|
#include <QClipboard>
|
||||||
|
|
||||||
|
#include "receiver.h"
|
||||||
|
#include "controller.h"
|
||||||
|
#include "deviceevent.h"
|
||||||
|
|
||||||
|
Receiver::Receiver(Controller* controller) : QObject(controller)
|
||||||
|
{
|
||||||
|
m_controller = controller;
|
||||||
|
Q_ASSERT(controller);
|
||||||
|
}
|
||||||
|
|
||||||
|
Receiver::~Receiver()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void Receiver::onReadyRead()
|
||||||
|
{
|
||||||
|
QTcpSocket* controlSocket = m_controller->getControlSocket();
|
||||||
|
if (!controlSocket) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (controlSocket->bytesAvailable()) {
|
||||||
|
QByteArray byteArray = controlSocket->peek(controlSocket->bytesAvailable());
|
||||||
|
DeviceEvent deviceEvent;
|
||||||
|
qint32 consume = deviceEvent.deserialize(byteArray);
|
||||||
|
if (0 >= consume) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
controlSocket->read(consume);
|
||||||
|
processEvent(&deviceEvent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Receiver::processEvent(DeviceEvent *deviceEvent)
|
||||||
|
{
|
||||||
|
switch (deviceEvent->type()) {
|
||||||
|
case DeviceEvent::DET_GET_CLIPBOARD:
|
||||||
|
{
|
||||||
|
QClipboard *board = QApplication::clipboard();
|
||||||
|
QString text;
|
||||||
|
deviceEvent->getClipboardEventData(text);
|
||||||
|
board->setText(text);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
25
QtScrcpy/inputcontrol/receiver.h
Normal file
25
QtScrcpy/inputcontrol/receiver.h
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
#ifndef RECEIVER_H
|
||||||
|
#define RECEIVER_H
|
||||||
|
|
||||||
|
#include <QPointer>
|
||||||
|
|
||||||
|
class Controller;
|
||||||
|
class DeviceEvent;
|
||||||
|
class Receiver : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
explicit Receiver(Controller *controller);
|
||||||
|
virtual ~Receiver();
|
||||||
|
|
||||||
|
public slots:
|
||||||
|
void onReadyRead();
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void processEvent(DeviceEvent *deviceEvent);
|
||||||
|
|
||||||
|
private:
|
||||||
|
QPointer<Controller> m_controller;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // RECEIVER_H
|
51
QtScrcpy/util/bufferutil.cpp
Normal file
51
QtScrcpy/util/bufferutil.cpp
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
#include "bufferutil.h"
|
||||||
|
|
||||||
|
void BufferUtil::write32(QBuffer &buffer, quint32 value)
|
||||||
|
{
|
||||||
|
buffer.putChar(value >> 24);
|
||||||
|
buffer.putChar(value >> 16);
|
||||||
|
buffer.putChar(value >> 8);
|
||||||
|
buffer.putChar(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void BufferUtil::write16(QBuffer &buffer, quint32 value)
|
||||||
|
{
|
||||||
|
buffer.putChar(value >> 8);
|
||||||
|
buffer.putChar(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
quint16 BufferUtil::read16(QBuffer &buffer)
|
||||||
|
{
|
||||||
|
char c;
|
||||||
|
quint16 ret = 0;
|
||||||
|
buffer.getChar(&c);
|
||||||
|
ret |= (c << 8);
|
||||||
|
buffer.getChar(&c);
|
||||||
|
ret |= c;
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
quint32 BufferUtil::read32(QBuffer &buffer)
|
||||||
|
{
|
||||||
|
char c;
|
||||||
|
quint32 ret = 0;
|
||||||
|
buffer.getChar(&c);
|
||||||
|
ret |= (c << 24);
|
||||||
|
buffer.getChar(&c);
|
||||||
|
ret |= (c << 16);
|
||||||
|
buffer.getChar(&c);
|
||||||
|
ret |= (c << 8);
|
||||||
|
buffer.getChar(&c);
|
||||||
|
ret |= c;
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
quint64 BufferUtil::read64(QBuffer &buffer)
|
||||||
|
{
|
||||||
|
quint32 msb = read32(buffer);
|
||||||
|
quint32 lsb = read32(buffer);
|
||||||
|
|
||||||
|
return ((quint64) msb << 32) | lsb;;
|
||||||
|
}
|
15
QtScrcpy/util/bufferutil.h
Normal file
15
QtScrcpy/util/bufferutil.h
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
#ifndef BUFFERUTIL_H
|
||||||
|
#define BUFFERUTIL_H
|
||||||
|
#include <QBuffer>
|
||||||
|
|
||||||
|
class BufferUtil
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
static void write32(QBuffer& buffer, quint32 value);
|
||||||
|
static void write16(QBuffer& buffer, quint32 value);
|
||||||
|
static quint16 read16(QBuffer& buffer);
|
||||||
|
static quint32 read32(QBuffer& buffer);
|
||||||
|
static quint64 read64(QBuffer& buffer);
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // BUFFERUTIL_H
|
|
@ -1,4 +1,8 @@
|
||||||
include ($$PWD/mousetap/mousetap.pri)
|
include ($$PWD/mousetap/mousetap.pri)
|
||||||
|
|
||||||
HEADERS += \
|
HEADERS += \
|
||||||
$$PWD/compat.h
|
$$PWD/compat.h \
|
||||||
|
$$PWD/bufferutil.h
|
||||||
|
|
||||||
|
SOURCES += \
|
||||||
|
$$PWD/bufferutil.cpp
|
||||||
|
|
|
@ -11,6 +11,7 @@
|
||||||
#include <QMimeData>
|
#include <QMimeData>
|
||||||
#include <QFileInfo>
|
#include <QFileInfo>
|
||||||
#include <QMessageBox>
|
#include <QMessageBox>
|
||||||
|
#include <QClipboard>
|
||||||
|
|
||||||
#include "videoform.h"
|
#include "videoform.h"
|
||||||
#include "recorder.h"
|
#include "recorder.h"
|
||||||
|
@ -365,7 +366,35 @@ void VideoForm::collapseNotificationPanel()
|
||||||
m_inputConvert.sendControlEvent(controlEvent);
|
m_inputConvert.sendControlEvent(controlEvent);
|
||||||
}
|
}
|
||||||
|
|
||||||
void VideoForm::postTextInput(const QString& text)
|
void VideoForm::requestDeviceClipboard()
|
||||||
|
{
|
||||||
|
ControlEvent* controlEvent = new ControlEvent(ControlEvent::CET_GET_CLIPBOARD);
|
||||||
|
if (!controlEvent) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
m_inputConvert.sendControlEvent(controlEvent);
|
||||||
|
}
|
||||||
|
|
||||||
|
void VideoForm::setDeviceClipboard()
|
||||||
|
{
|
||||||
|
QClipboard *board = QApplication::clipboard();
|
||||||
|
QString text = board->text();
|
||||||
|
ControlEvent* controlEvent = new ControlEvent(ControlEvent::CET_SET_CLIPBOARD);
|
||||||
|
if (!controlEvent) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
controlEvent->setSetClipboardEventData(text);
|
||||||
|
m_inputConvert.sendControlEvent(controlEvent);
|
||||||
|
}
|
||||||
|
|
||||||
|
void VideoForm::clipboardPaste()
|
||||||
|
{
|
||||||
|
QClipboard *board = QApplication::clipboard();
|
||||||
|
QString text = board->text();
|
||||||
|
postTextInput(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
void VideoForm::postTextInput(QString& text)
|
||||||
{
|
{
|
||||||
ControlEvent* controlEvent = new ControlEvent(ControlEvent::CET_TEXT);
|
ControlEvent* controlEvent = new ControlEvent(ControlEvent::CET_TEXT);
|
||||||
if (!controlEvent) {
|
if (!controlEvent) {
|
||||||
|
@ -468,6 +497,16 @@ void VideoForm::keyPressEvent(QKeyEvent *event)
|
||||||
&& isFullScreen()) {
|
&& isFullScreen()) {
|
||||||
switchFullScreen();
|
switchFullScreen();
|
||||||
}
|
}
|
||||||
|
if (event->key() == Qt::Key_C && (event->modifiers() & Qt::ControlModifier)) {
|
||||||
|
requestDeviceClipboard();
|
||||||
|
}
|
||||||
|
if (event->key() == Qt::Key_V && (event->modifiers() & Qt::ControlModifier)) {
|
||||||
|
if (event->modifiers() & Qt::ShiftModifier) {
|
||||||
|
setDeviceClipboard();
|
||||||
|
} else {
|
||||||
|
clipboardPaste();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//qDebug() << "keyPressEvent" << event->isAutoRepeat();
|
//qDebug() << "keyPressEvent" << event->isAutoRepeat();
|
||||||
m_inputConvert.keyEvent(event, ui->videoWidget->frameSize(), ui->videoWidget->size());
|
m_inputConvert.keyEvent(event, ui->videoWidget->frameSize(), ui->videoWidget->size());
|
||||||
|
|
|
@ -39,7 +39,10 @@ public:
|
||||||
void postTurnOn();
|
void postTurnOn();
|
||||||
void expandNotificationPanel();
|
void expandNotificationPanel();
|
||||||
void collapseNotificationPanel();
|
void collapseNotificationPanel();
|
||||||
void postTextInput(const QString& text);
|
void requestDeviceClipboard();
|
||||||
|
void setDeviceClipboard();
|
||||||
|
void clipboardPaste();
|
||||||
|
void postTextInput(QString& text);
|
||||||
|
|
||||||
void staysOnTop(bool top = true);
|
void staysOnTop(bool top = true);
|
||||||
|
|
||||||
|
|
|
@ -9,10 +9,13 @@ public final class ControlEvent {
|
||||||
public static final int TYPE_TEXT = 1;
|
public static final int TYPE_TEXT = 1;
|
||||||
public static final int TYPE_MOUSE = 2;
|
public static final int TYPE_MOUSE = 2;
|
||||||
public static final int TYPE_SCROLL = 3;
|
public static final int TYPE_SCROLL = 3;
|
||||||
public static final int TYPE_TOUCH = 4;
|
public static final int TYPE_BACK_OR_SCREEN_ON = 4;
|
||||||
public static final int TYPE_BACK_OR_SCREEN_ON = 5;
|
public static final int TYPE_EXPAND_NOTIFICATION_PANEL = 5;
|
||||||
public static final int TYPE_EXPAND_NOTIFICATION_PANEL = 6;
|
public static final int TYPE_COLLAPSE_NOTIFICATION_PANEL = 6;
|
||||||
public static final int TYPE_COLLAPSE_NOTIFICATION_PANEL = 7;
|
public static final int TYPE_GET_CLIPBOARD = 7;
|
||||||
|
public static final int TYPE_SET_CLIPBOARD = 8;
|
||||||
|
|
||||||
|
public static final int TYPE_TOUCH = 9;
|
||||||
|
|
||||||
|
|
||||||
private int type;
|
private int type;
|
||||||
|
@ -72,6 +75,13 @@ public final class ControlEvent {
|
||||||
return event;
|
return event;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static ControlEvent createSetClipboardControlEvent(String text) {
|
||||||
|
ControlEvent event = new ControlEvent();
|
||||||
|
event.type = TYPE_SET_CLIPBOARD;
|
||||||
|
event.text = text;
|
||||||
|
return event;
|
||||||
|
}
|
||||||
|
|
||||||
public static ControlEvent createSimpleControlEvent(int type) {
|
public static ControlEvent createSimpleControlEvent(int type) {
|
||||||
ControlEvent event = new ControlEvent();
|
ControlEvent event = new ControlEvent();
|
||||||
event.type = type;
|
event.type = type;
|
||||||
|
|
|
@ -14,11 +14,12 @@ public class ControlEventReader {
|
||||||
private static final int SCROLL_PAYLOAD_LENGTH = 16;
|
private static final int SCROLL_PAYLOAD_LENGTH = 16;
|
||||||
|
|
||||||
public static final int TEXT_MAX_LENGTH = 300;
|
public static final int TEXT_MAX_LENGTH = 300;
|
||||||
|
public static final int CLIPBOARD_TEXT_MAX_LENGTH = 4093;
|
||||||
private static final int RAW_BUFFER_SIZE = 1024;
|
private static final int RAW_BUFFER_SIZE = 1024;
|
||||||
|
|
||||||
private final byte[] rawBuffer = new byte[RAW_BUFFER_SIZE];
|
private final byte[] rawBuffer = new byte[RAW_BUFFER_SIZE];
|
||||||
private final ByteBuffer buffer = ByteBuffer.wrap(rawBuffer);
|
private final ByteBuffer buffer = ByteBuffer.wrap(rawBuffer);
|
||||||
private final byte[] textBuffer = new byte[TEXT_MAX_LENGTH];
|
private final byte[] textBuffer = new byte[CLIPBOARD_TEXT_MAX_LENGTH];
|
||||||
|
|
||||||
public ControlEventReader() {
|
public ControlEventReader() {
|
||||||
// invariant: the buffer is always in "get" mode
|
// invariant: the buffer is always in "get" mode
|
||||||
|
@ -66,9 +67,13 @@ public class ControlEventReader {
|
||||||
case ControlEvent.TYPE_SCROLL:
|
case ControlEvent.TYPE_SCROLL:
|
||||||
controlEvent = parseScrollControlEvent();
|
controlEvent = parseScrollControlEvent();
|
||||||
break;
|
break;
|
||||||
|
case ControlEvent.TYPE_SET_CLIPBOARD:
|
||||||
|
controlEvent = parseSetClipboardEvent();
|
||||||
|
break;
|
||||||
case ControlEvent.TYPE_BACK_OR_SCREEN_ON:
|
case ControlEvent.TYPE_BACK_OR_SCREEN_ON:
|
||||||
case ControlEvent.TYPE_EXPAND_NOTIFICATION_PANEL:
|
case ControlEvent.TYPE_EXPAND_NOTIFICATION_PANEL:
|
||||||
case ControlEvent.TYPE_COLLAPSE_NOTIFICATION_PANEL:
|
case ControlEvent.TYPE_COLLAPSE_NOTIFICATION_PANEL:
|
||||||
|
case ControlEvent.TYPE_GET_CLIPBOARD:
|
||||||
controlEvent = ControlEvent.createSimpleControlEvent(type);
|
controlEvent = ControlEvent.createSimpleControlEvent(type);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
@ -94,8 +99,8 @@ public class ControlEventReader {
|
||||||
return ControlEvent.createKeycodeControlEvent(action, keycode, metaState);
|
return ControlEvent.createKeycodeControlEvent(action, keycode, metaState);
|
||||||
}
|
}
|
||||||
|
|
||||||
private ControlEvent parseTextControlEvent() {
|
private String parseString() {
|
||||||
if (buffer.remaining() < 1) {
|
if (buffer.remaining() < 2) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
int len = toUnsigned(buffer.getShort());
|
int len = toUnsigned(buffer.getShort());
|
||||||
|
@ -103,7 +108,14 @@ public class ControlEventReader {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
buffer.get(textBuffer, 0, len);
|
buffer.get(textBuffer, 0, len);
|
||||||
String text = new String(textBuffer, 0, len, StandardCharsets.UTF_8);
|
return new String(textBuffer, 0, len, StandardCharsets.UTF_8);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ControlEvent parseTextControlEvent() {
|
||||||
|
String text = parseString();
|
||||||
|
if (text == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
return ControlEvent.createTextControlEvent(text);
|
return ControlEvent.createTextControlEvent(text);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -137,6 +149,14 @@ public class ControlEventReader {
|
||||||
return ControlEvent.createScrollControlEvent(position, hScroll, vScroll);
|
return ControlEvent.createScrollControlEvent(position, hScroll, vScroll);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private ControlEvent parseSetClipboardEvent() {
|
||||||
|
String text = parseString();
|
||||||
|
if (text == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return ControlEvent.createSetClipboardControlEvent(text);
|
||||||
|
}
|
||||||
|
|
||||||
private static Position readPosition(ByteBuffer buffer) {
|
private static Position readPosition(ByteBuffer buffer) {
|
||||||
int x = toUnsigned(buffer.getShort());
|
int x = toUnsigned(buffer.getShort());
|
||||||
int y = toUnsigned(buffer.getShort());
|
int y = toUnsigned(buffer.getShort());
|
||||||
|
|
|
@ -8,6 +8,7 @@ import java.io.Closeable;
|
||||||
import java.io.FileDescriptor;
|
import java.io.FileDescriptor;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
|
|
||||||
public final class DesktopConnection implements Closeable {
|
public final class DesktopConnection implements Closeable {
|
||||||
|
@ -21,14 +22,17 @@ public final class DesktopConnection implements Closeable {
|
||||||
|
|
||||||
private final LocalSocket controlSocket;
|
private final LocalSocket controlSocket;
|
||||||
private final InputStream controlInputStream;
|
private final InputStream controlInputStream;
|
||||||
|
private final OutputStream controlOutputStream;
|
||||||
|
|
||||||
|
|
||||||
private final ControlEventReader reader = new ControlEventReader();
|
private final ControlEventReader reader = new ControlEventReader();
|
||||||
|
private final DeviceEventWriter writer = new DeviceEventWriter();
|
||||||
|
|
||||||
private DesktopConnection(LocalSocket videoSocket, LocalSocket controlSocket) throws IOException {
|
private DesktopConnection(LocalSocket videoSocket, LocalSocket controlSocket) throws IOException {
|
||||||
this.videoSocket = videoSocket;
|
this.videoSocket = videoSocket;
|
||||||
this.controlSocket = controlSocket;
|
this.controlSocket = controlSocket;
|
||||||
controlInputStream = controlSocket.getInputStream();
|
controlInputStream = controlSocket.getInputStream();
|
||||||
|
controlOutputStream = controlSocket.getOutputStream();
|
||||||
videoFd = videoSocket.getFileDescriptor();
|
videoFd = videoSocket.getFileDescriptor();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -109,4 +113,8 @@ public final class DesktopConnection implements Closeable {
|
||||||
}
|
}
|
||||||
return event;
|
return event;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void sendDeviceEvent(DeviceEvent event) throws IOException {
|
||||||
|
writer.writeTo(event, controlOutputStream);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -180,6 +180,18 @@ public final class Device {
|
||||||
serviceManager.getStatusBarManager().collapsePanels();
|
serviceManager.getStatusBarManager().collapsePanels();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getClipboardText() {
|
||||||
|
CharSequence s = serviceManager.getClipboardManager().getText();
|
||||||
|
if (s == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return s.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setClipboardText(String text) {
|
||||||
|
serviceManager.getClipboardManager().setText(text);
|
||||||
|
}
|
||||||
|
|
||||||
static Rect flipRect(Rect crop) {
|
static Rect flipRect(Rect crop) {
|
||||||
return new Rect(crop.top, crop.left, crop.bottom, crop.right);
|
return new Rect(crop.top, crop.left, crop.bottom, crop.right);
|
||||||
}
|
}
|
||||||
|
|
27
server/src/main/java/com/genymobile/scrcpy/DeviceEvent.java
Normal file
27
server/src/main/java/com/genymobile/scrcpy/DeviceEvent.java
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,34 @@
|
||||||
|
package com.genymobile.scrcpy;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
|
||||||
|
public class DeviceEventWriter {
|
||||||
|
|
||||||
|
public static final int CLIPBOARD_TEXT_MAX_LENGTH = 4093;
|
||||||
|
private static final int MAX_EVENT_SIZE = CLIPBOARD_TEXT_MAX_LENGTH + 3;
|
||||||
|
|
||||||
|
private final byte[] rawBuffer = new byte[MAX_EVENT_SIZE];
|
||||||
|
private final ByteBuffer buffer = ByteBuffer.wrap(rawBuffer);
|
||||||
|
|
||||||
|
@SuppressWarnings("checkstyle:MagicNumber")
|
||||||
|
public void writeTo(DeviceEvent event, 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();
|
||||||
|
byte[] raw = text.getBytes(StandardCharsets.UTF_8);
|
||||||
|
int len = StringUtils.getUtf8TruncationIndex(raw, CLIPBOARD_TEXT_MAX_LENGTH);
|
||||||
|
buffer.putShort((short) len);
|
||||||
|
buffer.put(raw, 0, len);
|
||||||
|
output.write(rawBuffer, 0, buffer.position());
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
Ln.w("Unknown device event: " + event.getType());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -13,11 +13,11 @@ import android.view.MotionEvent;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.Vector;
|
import java.util.Vector;
|
||||||
|
|
||||||
|
|
||||||
public class EventController {
|
public class EventController {
|
||||||
|
|
||||||
private final Device device;
|
private final Device device;
|
||||||
private final DesktopConnection connection;
|
private final DesktopConnection connection;
|
||||||
|
private final EventSender sender;
|
||||||
|
|
||||||
private final KeyCharacterMap charMap = KeyCharacterMap.load(KeyCharacterMap.VIRTUAL_KEYBOARD);
|
private final KeyCharacterMap charMap = KeyCharacterMap.load(KeyCharacterMap.VIRTUAL_KEYBOARD);
|
||||||
|
|
||||||
|
@ -28,6 +28,7 @@ public class EventController {
|
||||||
public EventController(Device device, DesktopConnection connection) {
|
public EventController(Device device, DesktopConnection connection) {
|
||||||
this.device = device;
|
this.device = device;
|
||||||
this.connection = connection;
|
this.connection = connection;
|
||||||
|
sender = new EventSender(connection);
|
||||||
}
|
}
|
||||||
|
|
||||||
private int getPointer(int id) {
|
private int getPointer(int id) {
|
||||||
|
@ -97,6 +98,10 @@ public class EventController {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public EventSender getSender() {
|
||||||
|
return sender;
|
||||||
|
}
|
||||||
|
|
||||||
public void control() throws IOException {
|
public void control() throws IOException {
|
||||||
// on start, turn screen on
|
// on start, turn screen on
|
||||||
turnScreenOn();
|
turnScreenOn();
|
||||||
|
@ -133,6 +138,13 @@ public class EventController {
|
||||||
case ControlEvent.TYPE_COLLAPSE_NOTIFICATION_PANEL:
|
case ControlEvent.TYPE_COLLAPSE_NOTIFICATION_PANEL:
|
||||||
device.collapsePanels();
|
device.collapsePanels();
|
||||||
break;
|
break;
|
||||||
|
case ControlEvent.TYPE_GET_CLIPBOARD:
|
||||||
|
String clipboardText = device.getClipboardText();
|
||||||
|
sender.pushClipboardText(clipboardText);
|
||||||
|
break;
|
||||||
|
case ControlEvent.TYPE_SET_CLIPBOARD:
|
||||||
|
device.setClipboardText(controlEvent.getText());
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
// do nothing
|
// do nothing
|
||||||
}
|
}
|
||||||
|
@ -144,7 +156,7 @@ public class EventController {
|
||||||
|
|
||||||
private boolean injectChar(char c) {
|
private boolean injectChar(char c) {
|
||||||
String decomposed = KeyComposition.decompose(c);
|
String decomposed = KeyComposition.decompose(c);
|
||||||
char[] chars = decomposed != null ? decomposed.toCharArray() : new char[] {c};
|
char[] chars = decomposed != null ? decomposed.toCharArray() : new char[]{c};
|
||||||
KeyEvent[] events = charMap.getEvents(chars);
|
KeyEvent[] events = charMap.getEvents(chars);
|
||||||
if (events == null) {
|
if (events == null) {
|
||||||
return false;
|
return false;
|
||||||
|
|
34
server/src/main/java/com/genymobile/scrcpy/EventSender.java
Normal file
34
server/src/main/java/com/genymobile/scrcpy/EventSender.java
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
package com.genymobile.scrcpy;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
public final class EventSender {
|
||||||
|
|
||||||
|
private final DesktopConnection connection;
|
||||||
|
|
||||||
|
private String clipboardText;
|
||||||
|
|
||||||
|
public EventSender(DesktopConnection connection) {
|
||||||
|
this.connection = connection;
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void pushClipboardText(String text) {
|
||||||
|
clipboardText = text;
|
||||||
|
notify();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void loop() throws IOException, InterruptedException {
|
||||||
|
while (true) {
|
||||||
|
String text;
|
||||||
|
synchronized (this) {
|
||||||
|
while (clipboardText == null) {
|
||||||
|
wait();
|
||||||
|
}
|
||||||
|
text = clipboardText;
|
||||||
|
clipboardText = null;
|
||||||
|
}
|
||||||
|
DeviceEvent event = DeviceEvent.createGetClipboardEvent(text);
|
||||||
|
connection.sendDeviceEvent(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -20,8 +20,11 @@ public final class Server {
|
||||||
try (DesktopConnection connection = DesktopConnection.open(device, tunnelForward)) {
|
try (DesktopConnection connection = DesktopConnection.open(device, tunnelForward)) {
|
||||||
ScreenEncoder screenEncoder = new ScreenEncoder(options.getSendFrameMeta(), options.getBitRate());
|
ScreenEncoder screenEncoder = new ScreenEncoder(options.getSendFrameMeta(), options.getBitRate());
|
||||||
|
|
||||||
|
EventController controller = new EventController(device, connection);
|
||||||
|
|
||||||
// asynchronous
|
// asynchronous
|
||||||
startEventController(device, connection);
|
startEventController(controller);
|
||||||
|
startEventSender(controller.getSender());
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// synchronous
|
// synchronous
|
||||||
|
@ -33,12 +36,12 @@ public final class Server {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void startEventController(final Device device, final DesktopConnection connection) {
|
private static void startEventController(final EventController controller) {
|
||||||
new Thread(new Runnable() {
|
new Thread(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
try {
|
try {
|
||||||
new EventController(device, connection).control();
|
controller.control();
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
// this is expected on close
|
// this is expected on close
|
||||||
Ln.d("Event controller stopped");
|
Ln.d("Event controller stopped");
|
||||||
|
@ -47,6 +50,20 @@ public final class Server {
|
||||||
}).start();
|
}).start();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void startEventSender(final EventSender sender) {
|
||||||
|
new Thread(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
try {
|
||||||
|
sender.loop();
|
||||||
|
} catch (IOException | InterruptedException e) {
|
||||||
|
// this is expected on close
|
||||||
|
Ln.d("Event sender stopped");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}).start();
|
||||||
|
}
|
||||||
|
|
||||||
@SuppressWarnings("checkstyle:MagicNumber")
|
@SuppressWarnings("checkstyle:MagicNumber")
|
||||||
private static Options createOptions(String... args) {
|
private static Options createOptions(String... args) {
|
||||||
if (args.length != 5) {
|
if (args.length != 5) {
|
||||||
|
|
23
server/src/main/java/com/genymobile/scrcpy/StringUtils.java
Normal file
23
server/src/main/java/com/genymobile/scrcpy/StringUtils.java
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
package com.genymobile.scrcpy;
|
||||||
|
|
||||||
|
public final class StringUtils {
|
||||||
|
private StringUtils() {
|
||||||
|
// not instantiable
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("checkstyle:MagicNumber")
|
||||||
|
public static int getUtf8TruncationIndex(byte[] utf8, int maxLength) {
|
||||||
|
int len = utf8.length;
|
||||||
|
if (len <= maxLength) {
|
||||||
|
return len;
|
||||||
|
}
|
||||||
|
len = maxLength;
|
||||||
|
// see UTF-8 encoding <https://en.wikipedia.org/wiki/UTF-8#Description>
|
||||||
|
while ((utf8[len] & 0x80) != 0 && (utf8[len] & 0xc0) != 0xc0) {
|
||||||
|
// the next byte is not the start of a new UTF-8 codepoint
|
||||||
|
// so if we would cut there, the character would be truncated
|
||||||
|
len--;
|
||||||
|
}
|
||||||
|
return len;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,44 @@
|
||||||
|
package com.genymobile.scrcpy.wrappers;
|
||||||
|
|
||||||
|
import android.content.ClipData;
|
||||||
|
import android.os.IInterface;
|
||||||
|
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
|
||||||
|
public class ClipboardManager {
|
||||||
|
private final IInterface manager;
|
||||||
|
private final Method getPrimaryClipMethod;
|
||||||
|
private final Method setPrimaryClipMethod;
|
||||||
|
|
||||||
|
public ClipboardManager(IInterface manager) {
|
||||||
|
this.manager = manager;
|
||||||
|
try {
|
||||||
|
getPrimaryClipMethod = manager.getClass().getMethod("getPrimaryClip", String.class);
|
||||||
|
setPrimaryClipMethod = manager.getClass().getMethod("setPrimaryClip", ClipData.class, String.class);
|
||||||
|
} catch (NoSuchMethodException e) {
|
||||||
|
throw new AssertionError(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public CharSequence getText() {
|
||||||
|
try {
|
||||||
|
ClipData clipData = (ClipData) getPrimaryClipMethod.invoke(manager, "com.android.shell");
|
||||||
|
if (clipData == null || clipData.getItemCount() == 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return clipData.getItemAt(0).getText();
|
||||||
|
} catch (InvocationTargetException | IllegalAccessException e) {
|
||||||
|
throw new AssertionError(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setText(CharSequence text) {
|
||||||
|
ClipData clipData = ClipData.newPlainText(null, text);
|
||||||
|
try {
|
||||||
|
setPrimaryClipMethod.invoke(manager, clipData, "com.android.shell");
|
||||||
|
} catch (InvocationTargetException | IllegalAccessException e) {
|
||||||
|
throw new AssertionError(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -15,6 +15,7 @@ public final class ServiceManager {
|
||||||
private InputManager inputManager;
|
private InputManager inputManager;
|
||||||
private PowerManager powerManager;
|
private PowerManager powerManager;
|
||||||
private StatusBarManager statusBarManager;
|
private StatusBarManager statusBarManager;
|
||||||
|
private ClipboardManager clipboardManager;
|
||||||
|
|
||||||
public ServiceManager() {
|
public ServiceManager() {
|
||||||
try {
|
try {
|
||||||
|
@ -68,4 +69,11 @@ public final class ServiceManager {
|
||||||
}
|
}
|
||||||
return statusBarManager;
|
return statusBarManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ClipboardManager getClipboardManager() {
|
||||||
|
if (clipboardManager == null) {
|
||||||
|
clipboardManager = new ClipboardManager(getService("clipboard", "android.content.IClipboard"));
|
||||||
|
}
|
||||||
|
return clipboardManager;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
BIN
third_party/scrcpy-server.jar
vendored
BIN
third_party/scrcpy-server.jar
vendored
Binary file not shown.
Loading…
Add table
Add a link
Reference in a new issue