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)
|
||||
|
||||
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