parent
eb06924061
commit
e010199752
@ -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;
|
||||||
|
}
|
||||||
@ -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
|
||||||
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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
|
||||||
@ -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;;
|
||||||
|
}
|
||||||
@ -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
|
||||||
|
|||||||
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in new issue