Merge pull request #249 from barry-ran/dev

Dev
This commit is contained in:
Barry 2020-07-08 12:24:02 +08:00 committed by GitHub
commit 5dc3c08b05
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
31 changed files with 826 additions and 202 deletions

View file

@ -16,7 +16,7 @@ jobs:
runs-on: macos-latest
strategy:
matrix:
qt-ver: [5.12.6]
qt-ver: [5.15.0]
qt-arch-install: [clang_64]
clang-arch: [x64]
env:
@ -31,7 +31,7 @@ jobs:
path: ${{ env.qt-install-path }}/${{ matrix.qt-arch-install }}
key: ${{ runner.os }}/${{ matrix.qt-ver }}/${{ matrix.qt-arch-install }}
- name: Install Qt
uses: jurplel/install-qt-action@v2.6.2
uses: jurplel/install-qt-action@v2.7.1
with:
version: ${{ matrix.qt-ver }}
cached: ${{ steps.cache-qt.outputs.cache-hit }}

View file

@ -18,7 +18,7 @@ jobs:
strategy:
matrix:
os: [ubuntu-16.04,ubuntu-18.04]
qt-ver: [5.12.6]
qt-ver: [5.15.0]
qt-arch-install: [gcc_64]
gcc-arch: [x64]
env:
@ -33,7 +33,7 @@ jobs:
path: ${{ env.qt-install-path }}/${{ matrix.qt-arch-install }}
key: ${{ runner.os }}/${{ matrix.qt-ver }}/${{ matrix.qt-arch-install }}
- name: Install Qt
uses: jurplel/install-qt-action@v2.6.2
uses: jurplel/install-qt-action@v2.7.1
with:
version: ${{ matrix.qt-ver }}
cached: ${{ steps.cache-qt.outputs.cache-hit }}

View file

@ -14,29 +14,30 @@ on:
jobs:
build:
name: Build
# windows-latest目前是windows server 2019,选择2016是2016安装的是vs2017
# windows-latest目前是windows server 2019
# windows server 2019安装的是vs2019windows server 2016安装的是vs2017
# https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idruns-on
runs-on: windows-2016
runs-on: windows-2019
# 矩阵配置 https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idstrategymatrix
strategy:
matrix:
qt-ver: [5.12.6]
qt-arch: [win64_msvc2017_64, win32_msvc2017]
qt-ver: [5.15.0]
qt-arch: [win64_msvc2019_64, win32_msvc2019]
# 配置qt-arch的额外设置msvc-archqt-arch-install
include:
- qt-arch: win64_msvc2017_64
- qt-arch: win64_msvc2019_64
msvc-arch: x64
qt-arch-install: msvc2017_64
- qt-arch: win32_msvc2017
qt-arch-install: msvc2019_64
- qt-arch: win32_msvc2019
msvc-arch: x86
qt-arch-install: msvc2017
qt-arch-install: msvc2019
# job env,所有steps都可以访问
# 不同级别env详解 https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#env
# 使用表达式语法${{}}访问上下文 https://help.github.com/en/actions/reference/context-and-expression-syntax-for-github-actions
env:
target-name: QtScrcpy
vcvarsall-path: 'C:\Program Files (x86)\Microsoft Visual Studio\2017\Enterprise\VC\Auxiliary\Build\vcvarsall.bat'
vcvarsall-path: 'C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Auxiliary\Build\vcvarsall.bat'
qt-install-path: ${{ github.workspace }}/${{ matrix.qt-ver }}
plantform-des: win
# 步骤
@ -50,7 +51,7 @@ jobs:
# 安装Qt
- name: Install Qt
# 使用外部action。这个action专门用来安装Qt
uses: jurplel/install-qt-action@v2.6.2
uses: jurplel/install-qt-action@v2.7.1
with:
# Version of Qt to install
version: ${{ matrix.qt-ver }}

View file

@ -116,9 +116,9 @@ QStringList AdbProcess::getDevicesSerialFromStdOut()
{
// get devices serial by adb devices
QStringList serials;
QStringList devicesInfoList = m_standardOutput.split(QRegExp("\r\n|\n"), QString::SkipEmptyParts);
QStringList devicesInfoList = m_standardOutput.split(QRegExp("\r\n|\n"), Qt::SkipEmptyParts);
for (QString deviceInfo : devicesInfoList) {
QStringList deviceInfos = deviceInfo.split(QRegExp("\t"), QString::SkipEmptyParts);
QStringList deviceInfos = deviceInfo.split(QRegExp("\t"), Qt::SkipEmptyParts);
if (2 == deviceInfos.count() && 0 == deviceInfos[1].compare("device")) {
serials << deviceInfos[0];
}

View file

@ -361,8 +361,8 @@ bool InputConvertGame::processMouseMove(const QMouseEvent *from)
m_ctrlMouseMove.lastConverPos.setX(m_ctrlMouseMove.lastConverPos.x() + distance.x() / m_showSize.width());
m_ctrlMouseMove.lastConverPos.setY(m_ctrlMouseMove.lastConverPos.y() + distance.y() / m_showSize.height());
if (m_ctrlMouseMove.lastConverPos.x() < 0.1 || m_ctrlMouseMove.lastConverPos.x() > 0.98 || m_ctrlMouseMove.lastConverPos.y() < 0.1
|| m_ctrlMouseMove.lastConverPos.y() > 0.98) {
if (m_ctrlMouseMove.lastConverPos.x() < 0.1 || m_ctrlMouseMove.lastConverPos.x() > 0.8 || m_ctrlMouseMove.lastConverPos.y() < 0.1
|| m_ctrlMouseMove.lastConverPos.y() > 0.8) {
if (m_ctrlMouseMove.smallEyes) {
m_processMouseMove = false;
int delay = 30;

View file

@ -50,24 +50,16 @@ void InputConvertNormal::mouseEvent(const QMouseEvent *from, const QSize &frameS
void InputConvertNormal::wheelEvent(const QWheelEvent *from, const QSize &frameSize, const QSize &showSize)
{
if (!from || 0 == from->delta()) {
if (!from || from->angleDelta().isNull()) {
return;
}
// delta
qint32 hScroll = 0;
qint32 vScroll = 0;
switch (from->orientation()) {
case Qt::Horizontal:
hScroll = from->delta() / abs(from->delta()) * 2;
break;
case Qt::Vertical:
vScroll = from->delta() / abs(from->delta()) * 2;
break;
}
qint32 hScroll = from->angleDelta().x() == 0 ? 0 : from->angleDelta().x() / abs(from->angleDelta().x()) * 2;
qint32 vScroll = from->angleDelta().y() == 0 ? 0 : from->angleDelta().y() / abs(from->angleDelta().y()) * 2;
// pos
QPointF pos = from->posF();
QPointF pos = from->position();
// convert pos
pos.setX(pos.x() * frameSize.width() / showSize.width());
pos.setY(pos.y() * frameSize.height() / showSize.height());
@ -279,7 +271,7 @@ AndroidKeycode InputConvertNormal::convertKeyCode(int key, Qt::KeyboardModifiers
keyCode = AKEYCODE_0;
break;
case Qt::Key_1:
case Qt::Key_Exclam:// !
case Qt::Key_Exclam: // !
keyCode = AKEYCODE_1;
break;
case Qt::Key_2:
@ -289,11 +281,11 @@ AndroidKeycode InputConvertNormal::convertKeyCode(int key, Qt::KeyboardModifiers
keyCode = AKEYCODE_3;
break;
case Qt::Key_4:
case Qt::Key_Dollar://$
case Qt::Key_Dollar: //$
keyCode = AKEYCODE_4;
break;
case Qt::Key_5:
case Qt::Key_Percent:// %
case Qt::Key_Percent: // %
keyCode = AKEYCODE_5;
break;
case Qt::Key_6:
@ -313,53 +305,53 @@ AndroidKeycode InputConvertNormal::convertKeyCode(int key, Qt::KeyboardModifiers
case Qt::Key_Space:
keyCode = AKEYCODE_SPACE;
break;
case Qt::Key_Comma://,
case Qt::Key_Less://<
case Qt::Key_Comma: //,
case Qt::Key_Less: //<
keyCode = AKEYCODE_COMMA;
break;
case Qt::Key_Period://.
case Qt::Key_Greater://>
case Qt::Key_Period: //.
case Qt::Key_Greater: //>
keyCode = AKEYCODE_PERIOD;
break;
case Qt::Key_Minus://-
case Qt::Key_Minus: //-
case Qt::Key_Underscore: //_
keyCode = AKEYCODE_MINUS;
break;
case Qt::Key_Equal://=
case Qt::Key_Equal: //=
keyCode = AKEYCODE_EQUALS;
break;
case Qt::Key_BracketLeft://[
case Qt::Key_BracketLeft: //[
case Qt::Key_BraceLeft: //{
keyCode = AKEYCODE_LEFT_BRACKET;
break;
case Qt::Key_BracketRight://]
case Qt::Key_BracketRight: //]
case Qt::Key_BraceRight: //}
keyCode = AKEYCODE_RIGHT_BRACKET;
break;
case Qt::Key_Backslash:// \ ????
case Qt::Key_Backslash: // \ ????
case Qt::Key_Bar: //|
keyCode = AKEYCODE_BACKSLASH;
break;
case Qt::Key_Semicolon://;
case Qt::Key_Semicolon: //;
case Qt::Key_Colon: //:
keyCode = AKEYCODE_SEMICOLON;
break;
case Qt::Key_Apostrophe://'
case Qt::Key_Apostrophe: //'
case Qt::Key_QuoteDbl: //"
keyCode = AKEYCODE_APOSTROPHE;
break;
case Qt::Key_Slash:// /
case Qt::Key_Question://?
case Qt::Key_Slash: // /
case Qt::Key_Question: //?
keyCode = AKEYCODE_SLASH;
break;
case Qt::Key_At://@
case Qt::Key_At: //@
keyCode = AKEYCODE_AT;
break;
case Qt::Key_Plus://+
case Qt::Key_Plus: //+
keyCode = AKEYCODE_PLUS;
break;
case Qt::Key_QuoteLeft://`
case Qt::Key_AsciiTilde://~
case Qt::Key_QuoteLeft: //`
case Qt::Key_AsciiTilde: //~
keyCode = AKEYCODE_GRAVE;
break;
case Qt::Key_NumberSign: //#

View file

@ -198,7 +198,7 @@ void Device::initSignals()
if (m_controlState == GCS_CLIENT) {
return;
}
QMessageBox::information(m_videoForm, "QtScrcpy", tips, QMessageBox::Ok);
//QMessageBox::information(m_videoForm, "QtScrcpy", tips, QMessageBox::Ok);
});
}

View file

@ -1,6 +1,7 @@
#ifndef DEVICE_H
#define DEVICE_H
#include <QElapsedTimer>
#include <QPointer>
#include <QTime>
@ -116,7 +117,7 @@ private:
// ui
QPointer<VideoForm> m_videoForm;
QTime m_startTimeCount;
QElapsedTimer m_startTimeCount;
DeviceParams m_params;
GroupControlState m_controlState = GCS_FREE;

View file

@ -2,41 +2,46 @@
FileHandler::FileHandler(QObject *parent) : QObject(parent)
{
connect(&m_adb, &AdbProcess::adbProcessResult, this, [this](AdbProcess::ADB_EXEC_RESULT processResult) {
switch (processResult) {
case AdbProcess::AER_ERROR_START:
case AdbProcess::AER_ERROR_EXEC:
case AdbProcess::AER_ERROR_MISSING_BINARY:
emit fileHandlerResult(FAR_ERROR_EXEC, m_isApk);
break;
case AdbProcess::AER_SUCCESS_EXEC:
emit fileHandlerResult(FAR_SUCCESS_EXEC, m_isApk);
break;
default:
break;
}
});
}
FileHandler::~FileHandler() {}
void FileHandler::onPushFileRequest(const QString &serial, const QString &file, const QString &devicePath)
{
if (m_adb.isRuning()) {
emit fileHandlerResult(FAR_IS_RUNNING, false);
return;
}
AdbProcess* adb = new AdbProcess;
bool isApk = false;
connect(adb, &AdbProcess::adbProcessResult, this, [this, adb, isApk](AdbProcess::ADB_EXEC_RESULT processResult) {
onAdbProcessResult(adb, isApk, processResult);
});
m_isApk = false;
m_adb.push(serial, file, devicePath);
adb->push(serial, file, devicePath);
}
void FileHandler::onInstallApkRequest(const QString &serial, const QString &apkFile)
{
if (m_adb.isRuning()) {
emit fileHandlerResult(FAR_IS_RUNNING, true);
return;
}
m_isApk = true;
m_adb.install(serial, apkFile);
AdbProcess* adb = new AdbProcess;
bool isApk = true;
connect(adb, &AdbProcess::adbProcessResult, this, [this, adb, isApk](AdbProcess::ADB_EXEC_RESULT processResult) {
onAdbProcessResult(adb, isApk, processResult);
});
adb->install(serial, apkFile);
}
void FileHandler::onAdbProcessResult(AdbProcess *adb, bool isApk, AdbProcess::ADB_EXEC_RESULT processResult)
{
switch (processResult) {
case AdbProcess::AER_ERROR_START:
case AdbProcess::AER_ERROR_EXEC:
case AdbProcess::AER_ERROR_MISSING_BINARY:
emit fileHandlerResult(FAR_ERROR_EXEC, isApk);
adb->deleteLater();
break;
case AdbProcess::AER_SUCCESS_EXEC:
emit fileHandlerResult(FAR_SUCCESS_EXEC, isApk);
adb->deleteLater();
break;
default:
break;
}
}

View file

@ -24,13 +24,11 @@ public slots:
void onPushFileRequest(const QString &serial, const QString &file, const QString &devicePath = "");
void onInstallApkRequest(const QString &serial, const QString &apkFile);
protected:
void onAdbProcessResult(AdbProcess* adb, bool isApk, AdbProcess::ADB_EXEC_RESULT processResult);
signals:
void fileHandlerResult(FILE_HANDLER_RESULT processResult, bool isApk = false);
private:
AdbProcess m_adb;
bool m_isApk = false;
QString m_devicePath = "";
};
#endif // FILEHANDLER_H

View file

@ -161,7 +161,7 @@ bool Server::execute()
// code option
// https://github.com/Genymobile/scrcpy/commit/080a4ee3654a9b7e96c8ffe37474b5c21c02852a
// <https://d.android.com/reference/android/media/MediaFormat>
args << "-";
args << Config::getInstance().getCodecOptions();
#ifdef SERVER_DEBUGGER
qInfo("Server debugger waiting for a client on device port " SERVER_DEBUGGER_PORT "...");

View file

@ -66,8 +66,8 @@ void VideoForm::initUI()
m_videoWidget = new QYUVOpenGLWidget();
m_videoWidget->hide();
ui->keepRadioWidget->setWidget(m_videoWidget);
ui->keepRadioWidget->setWidthHeightRadio(m_widthHeightRatio);
ui->keepRatioWidget->setWidget(m_videoWidget);
ui->keepRatioWidget->setWidthHeightRatio(m_widthHeightRatio);
m_fpsLabel = new QLabel(m_videoWidget);
QFont ft;
@ -81,14 +81,14 @@ void VideoForm::initUI()
setMouseTracking(true);
m_videoWidget->setMouseTracking(true);
ui->keepRadioWidget->setMouseTracking(true);
ui->keepRatioWidget->setMouseTracking(true);
}
QRect VideoForm::getGrabCursorRect()
{
QRect rc;
#if defined(Q_OS_WIN32)
rc = QRect(ui->keepRadioWidget->mapToGlobal(m_videoWidget->pos()), m_videoWidget->size());
rc = QRect(ui->keepRatioWidget->mapToGlobal(m_videoWidget->pos()), m_videoWidget->size());
// high dpi support
rc.setTopLeft(rc.topLeft() * m_videoWidget->devicePixelRatio());
rc.setBottomRight(rc.bottomRight() * m_videoWidget->devicePixelRatio());
@ -99,15 +99,15 @@ QRect VideoForm::getGrabCursorRect()
rc.setHeight(rc.height() - 20);
#elif defined(Q_OS_OSX)
rc = m_videoWidget->geometry();
rc.setTopLeft(ui->keepRadioWidget->mapToGlobal(rc.topLeft()));
rc.setBottomRight(ui->keepRadioWidget->mapToGlobal(rc.bottomRight()));
rc.setTopLeft(ui->keepRatioWidget->mapToGlobal(rc.topLeft()));
rc.setBottomRight(ui->keepRatioWidget->mapToGlobal(rc.bottomRight()));
rc.setX(rc.x() + 10);
rc.setY(rc.y() + 10);
rc.setWidth(rc.width() - 20);
rc.setHeight(rc.height() - 20);
#elif defined(Q_OS_LINUX)
rc = QRect(ui->keepRadioWidget->mapToGlobal(m_videoWidget->pos()), m_videoWidget->size());
rc = QRect(ui->keepRatioWidget->mapToGlobal(m_videoWidget->pos()), m_videoWidget->size());
// high dpi support -- taken from the WIN32 section and untested
rc.setTopLeft(rc.topLeft() * m_videoWidget->devicePixelRatio());
rc.setBottomRight(rc.bottomRight() * m_videoWidget->devicePixelRatio());
@ -137,7 +137,7 @@ void VideoForm::resizeSquare()
void VideoForm::removeBlackRect()
{
resize(ui->keepRadioWidget->goodSize());
resize(ui->keepRatioWidget->goodSize());
}
void VideoForm::showFPS(bool show)
@ -380,7 +380,7 @@ void VideoForm::updateShowSize(const QSize &newSize)
m_frameSize = newSize;
m_widthHeightRatio = 1.0f * newSize.width() / newSize.height();
ui->keepRadioWidget->setWidthHeightRadio(m_widthHeightRatio);
ui->keepRatioWidget->setWidthHeightRatio(m_widthHeightRatio);
bool vertical = m_widthHeightRatio < 1.0f ? true : false;
QSize showSize = newSize;
@ -426,7 +426,7 @@ void VideoForm::onSwitchFullScreen()
if (isFullScreen()) {
// 横屏全屏铺满全屏,恢复时,恢复保持宽高比
if (m_widthHeightRatio > 1.0f) {
ui->keepRadioWidget->setWidthHeightRadio(m_widthHeightRatio);
ui->keepRatioWidget->setWidthHeightRatio(m_widthHeightRatio);
}
showNormal();
@ -447,7 +447,7 @@ void VideoForm::onSwitchFullScreen()
} else {
// 横屏全屏铺满全屏,不保持宽高比
if (m_widthHeightRatio > 1.0f) {
ui->keepRadioWidget->setWidthHeightRadio(-1.0f);
ui->keepRatioWidget->setWidthHeightRatio(-1.0f);
}
m_fullScreenBeforePos = pos();
@ -595,17 +595,13 @@ void VideoForm::mouseDoubleClickEvent(QMouseEvent *event)
void VideoForm::wheelEvent(QWheelEvent *event)
{
if (m_videoWidget->geometry().contains(event->pos())) {
if (m_videoWidget->geometry().contains(event->position().toPoint())) {
if (!m_device) {
return;
}
QPointF pos = m_videoWidget->mapFrom(this, event->pos());
/*
QWheelEvent(const QPointF &pos, const QPointF& globalPos, int delta,
Qt::MouseButtons buttons, Qt::KeyboardModifiers modifiers,
Qt::Orientation orient = Qt::Vertical);
*/
QWheelEvent wheelEvent(pos, event->globalPosF(), event->delta(), event->buttons(), event->modifiers(), event->orientation());
QPointF pos = m_videoWidget->mapFrom(this, event->position().toPoint());
QWheelEvent wheelEvent(
pos, event->globalPosition(), event->pixelDelta(), event->angleDelta(), event->buttons(), event->modifiers(), event->phase(), event->inverted());
emit m_device->wheelEvent(&wheelEvent, m_videoWidget->frameSize(), m_videoWidget->size());
}
}
@ -650,12 +646,12 @@ void VideoForm::showEvent(QShowEvent *event)
void VideoForm::resizeEvent(QResizeEvent *event)
{
Q_UNUSED(event)
QSize goodSize = ui->keepRadioWidget->goodSize();
QSize goodSize = ui->keepRatioWidget->goodSize();
if (goodSize.isEmpty()) {
return;
}
QSize curSize = size();
// 限制VideoForm尺寸不能小于keepRadioWidget good size
// 限制VideoForm尺寸不能小于keepRatioWidget good size
if (m_widthHeightRatio > 1.0f) {
// hor
if (curSize.height() <= goodSize.height()) {
@ -703,17 +699,21 @@ void VideoForm::dropEvent(QDropEvent *event)
return;
}
const QMimeData *qm = event->mimeData();
QString file = qm->urls()[0].toLocalFile();
QList<QUrl> urls = qm->urls();
for (const QUrl& url : urls) {
QString file = url.toLocalFile();
QFileInfo fileInfo(file);
if (!fileInfo.exists()) {
QMessageBox::warning(this, "QtScrcpy", tr("file does not exist"), QMessageBox::Ok);
return;
continue;
}
if (fileInfo.isFile() && fileInfo.suffix() == "apk") {
emit m_device->installApkRequest(file);
return;
continue;
}
emit m_device->pushFileRequest(file, Config::getInstance().getPushFilePath() + fileInfo.fileName());
}
}

View file

@ -39,15 +39,15 @@
<number>0</number>
</property>
<item>
<widget class="KeepRadioWidget" name="keepRadioWidget" native="true"/>
<widget class="KeepRatioWidget" name="keepRatioWidget" native="true"/>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>KeepRadioWidget</class>
<class>KeepRatioWidget</class>
<extends>QWidget</extends>
<header location="global">keepradiowidget.h</header>
<header location="global">keepratiowidget.h</header>
<container>1</container>
</customwidget>
</customwidgets>

View file

@ -137,7 +137,7 @@ void Dialog::execAdbCmd()
}
QString cmd = ui->adbCommandEdt->text().trimmed();
outLog("adb " + cmd, false);
m_adb.execute(ui->serialBox->currentText().trimmed(), cmd.split(" ", QString::SkipEmptyParts));
m_adb.execute(ui->serialBox->currentText().trimmed(), cmd.split(" ", Qt::SkipEmptyParts));
}
QString Dialog::getGameScript(const QString &fileName)

View file

@ -1,28 +0,0 @@
#ifndef KEEPRADIOWIDGET_H
#define KEEPRADIOWIDGET_H
#include <QPointer>
#include <QWidget>
class KeepRadioWidget : public QWidget
{
Q_OBJECT
public:
explicit KeepRadioWidget(QWidget *parent = nullptr);
~KeepRadioWidget();
void setWidget(QWidget *w);
void setWidthHeightRadio(float widthHeightRadio);
const QSize goodSize();
protected:
void resizeEvent(QResizeEvent *event);
void adjustSubWidget();
private:
float m_widthHeightRadio = -1.0f;
QPointer<QWidget> m_subWidget;
QSize m_goodSize;
};
#endif // KEEPRADIOWIDGET_H

View file

@ -1,13 +1,13 @@
#include <QResizeEvent>
#include <cmath>
#include "keepradiowidget.h"
#include "keepratiowidget.h"
KeepRadioWidget::KeepRadioWidget(QWidget *parent) : QWidget(parent) {}
KeepRatioWidget::KeepRatioWidget(QWidget *parent) : QWidget(parent) {}
KeepRadioWidget::~KeepRadioWidget() {}
KeepRatioWidget::~KeepRatioWidget() {}
void KeepRadioWidget::setWidget(QWidget *w)
void KeepRatioWidget::setWidget(QWidget *w)
{
if (!w) {
return;
@ -16,30 +16,30 @@ void KeepRadioWidget::setWidget(QWidget *w)
m_subWidget = w;
}
void KeepRadioWidget::setWidthHeightRadio(float widthHeightRadio)
void KeepRatioWidget::setWidthHeightRatio(float widthHeightRatio)
{
if (fabs(m_widthHeightRadio - widthHeightRadio) < 0.000001f) {
if (fabs(m_widthHeightRatio - widthHeightRatio) < 0.000001f) {
return;
}
m_widthHeightRadio = widthHeightRadio;
m_widthHeightRatio = widthHeightRatio;
adjustSubWidget();
}
const QSize KeepRadioWidget::goodSize()
const QSize KeepRatioWidget::goodSize()
{
if (!m_subWidget || m_widthHeightRadio < 0.0f) {
if (!m_subWidget || m_widthHeightRatio < 0.0f) {
return QSize();
}
return m_subWidget->size();
}
void KeepRadioWidget::resizeEvent(QResizeEvent *event)
void KeepRatioWidget::resizeEvent(QResizeEvent *event)
{
Q_UNUSED(event)
adjustSubWidget();
}
void KeepRadioWidget::adjustSubWidget()
void KeepRatioWidget::adjustSubWidget()
{
if (!m_subWidget) {
return;
@ -49,15 +49,15 @@ void KeepRadioWidget::adjustSubWidget()
QPoint pos(0, 0);
int width = 0;
int height = 0;
if (m_widthHeightRadio > 1.0f) {
if (m_widthHeightRatio > 1.0f) {
// base width
width = curSize.width();
height = curSize.width() / m_widthHeightRadio;
height = curSize.width() / m_widthHeightRatio;
pos.setY((curSize.height() - height) / 2);
} else if (m_widthHeightRadio > 0.0f) {
} else if (m_widthHeightRatio > 0.0f) {
// base height
height = curSize.height();
width = curSize.height() * m_widthHeightRadio;
width = curSize.height() * m_widthHeightRatio;
pos.setX((curSize.width() - width) / 2);
} else {
// full widget

View file

@ -0,0 +1,28 @@
#ifndef KEEPRATIOWIDGET_H
#define KEEPRATIOWIDGET_H
#include <QPointer>
#include <QWidget>
class KeepRatioWidget : public QWidget
{
Q_OBJECT
public:
explicit KeepRatioWidget(QWidget *parent = nullptr);
~KeepRatioWidget();
void setWidget(QWidget *w);
void setWidthHeightRatio(float widthHeightRatio);
const QSize goodSize();
protected:
void resizeEvent(QResizeEvent *event);
void adjustSubWidget();
private:
float m_widthHeightRatio = -1.0f;
QPointer<QWidget> m_subWidget;
QSize m_goodSize;
};
#endif // KEEPRATIOWIDGET_H

View file

@ -1,9 +1,9 @@
FORMS +=
HEADERS += \
$$PWD/keepradiowidget.h \
$$PWD/keepratiowidget.h \
$$PWD/magneticwidget.h
SOURCES += \
$$PWD/keepradiowidget.cpp \
$$PWD/keepratiowidget.cpp \
$$PWD/magneticwidget.cpp

View file

@ -37,6 +37,9 @@
#define COMMON_LOG_LEVEL_KEY "LogLevel"
#define COMMON_LOG_LEVEL_DEF "info"
#define COMMON_CODEC_OPTIONS_KEY "CodecOptions"
#define COMMON_CODEC_OPTIONS_DEF "-"
// user data
#define COMMON_RECORD_KEY "RecordPath"
#define COMMON_RECORD_DEF ""
@ -277,6 +280,15 @@ QString Config::getLogLevel()
return logLevel;
}
QString Config::getCodecOptions()
{
QString codecOptions;
m_settings->beginGroup(GROUP_COMMON);
codecOptions = m_settings->value(COMMON_CODEC_OPTIONS_KEY, COMMON_CODEC_OPTIONS_DEF).toString();
m_settings->endGroup();
return codecOptions;
}
QString Config::getTitle()
{
QString title;

View file

@ -22,6 +22,7 @@ public:
QString getServerPath();
QString getAdbPath();
QString getLogLevel();
QString getCodecOptions();
// user data
QString getRecordPath();

View file

@ -33,10 +33,10 @@ It focuses on:
![linux](screenshot/ubuntu.png)
## Customized key mapping (Windows&MacOS only)
You can write your own script to map keyboard and mouse actions to touches and clicks of the mobile phone according to your needs. [Here](docs/按键映射说明.md) are the rules.
## Customized key mapping
You can write your own script to map keyboard and mouse actions to touches and clicks of the mobile phone according to your needs. [Here](docs/KeyMapDes.md) are the rules.
A script for "PUBG mobile" and TikTok mapping is provided by default. Once enabled, you can play the game with your keyboard and mouse as the PC version. or you can use up/down/left/right direction keys to simulate up/down/left/right sliding. You can also write your own mapping files for other games according to [writing rules](docs/按键映射说明.md). The default key mapping is as follows:
A script for "PUBG mobile" and TikTok mapping is provided by default. Once enabled, you can play the game with your keyboard and mouse as the PC version. or you can use up/down/left/right direction keys to simulate up/down/left/right sliding. You can also write your own mapping files for other games according to [writing rules](docs/KeyMapDes.md). The default key mapping is as follows:
![game](screenshot/game.jpg)
@ -241,7 +241,7 @@ There are several reasons listed as below according to importance (high to low).
All the dependencies are provided and it is easy to compile.
### PC client
1. Set up the Qt development environment on the target platform (Qt >= 5.12.0, vs >= 2017 (mingw not supported))
1. Set up the Qt development environment on the target platform (Qt == 5.15.0, vs == 2017 (mingw not supported))
2. Clone the project
3. Open the project root directory all.pro with QtCreator
4. Compile and run

View file

@ -33,10 +33,10 @@ QtScrcpy可以通过USB(或通过TCP/IP)连接Android设备并进行显示和
![linux](screenshot/ubuntu.png)
## 自定义按键映射仅windows&MacOS平台开启
可以根据需要自己编写脚本将PC键盘按键映射为手机的触摸点击编写规则在[这里](docs/按键映射说明.md)。
## 自定义按键映射
可以根据需要自己编写脚本将PC键盘按键映射为手机的触摸点击编写规则在[这里](docs/KeyMapDes_zh.md)。
默认自带了针对和平精英手游和抖音进行键鼠映射的映射脚本,开启平精英手游后可以用键鼠像玩端游一样玩和平精英手游,开启抖音映射以后可以使用上下左右方向键模拟上下左右滑动,你也可以按照[编写规则](docs/按键映射说明.md)编写其他游戏的映射文件,默认按键映射如下:
默认自带了针对和平精英手游和抖音进行键鼠映射的映射脚本,开启平精英手游后可以用键鼠像玩端游一样玩和平精英手游,开启抖音映射以后可以使用上下左右方向键模拟上下左右滑动,你也可以按照[编写规则](docs/KeyMapDes_zh.md)编写其他游戏的映射文件,默认按键映射如下:
![game](screenshot/game.jpg)
@ -240,7 +240,7 @@ Mac OS平台你可以直接使用我编译好的可执行程序:
尽量提供了所有依赖资源,方便傻瓜式编译。
### PC端
1. 目标平台上搭建Qt开发环境(Qt >= 5.12.0, vs >= 2017 (**不支持mingw**))
1. 目标平台上搭建Qt开发环境(Qt == 5.15, vs == 2017 (**不支持mingw**))
2. 克隆该项目
3. 使用QtCreator打开项目根目录all.pro
4. 编译,运行即可

View file

@ -15,6 +15,10 @@ ServerVersion=1.14
ServerPath=/data/local/tmp/scrcpy-server.jar
# 自定义adb路径例如D:/android/tools/adb.exe
AdbPath=
# 编码选项 "-"表示默认
# 例如 CodecOptions="profile=1,level=2"
# 更多编码选项参考 https://d.android.com/reference/android/media/MediaFormat
CodecOptions="-"
# Set the log level (debug, info, warn, error)
LogLevel=info

66
docs/KeyMapDes.md Normal file
View file

@ -0,0 +1,66 @@
# Custom key mapping instructions
The key map file is in json format, and the new key map file needs to be placed in the keymap directory to be recognized by QtScrcpy.
The specific writing format of the button mapping file will be introduced below, and you can also refer to the button mapping file that comes with it.
## Key mapping script format description
### General Instructions
-The coordinate positions in the key map are all expressed by relative positions, and the width and height of the screen are expressed by 1, for example, the pixels of the screen are 1920x1080, then the coordinates (0.5,0.5) indicate
Taking the upper left corner of the screen as the origin, the position of the pixel coordinates (1920,1080)*(0.5,0.5)=(960,540).
Or when the left mouse button clicks, the console will output the pos at this time, just use this pos directly
![](image/debug-keymap-pos.png)
-The key codes in the key map are represented by Qt enumerations, detailed description can be [refer to Qt documentation](https://doc.qt.io/qt-5/qt.html) (search for The key names used by Qt. can be quickly located).
-Open the following two settings in the developer options, you can easily observe the coordinates of the touch point:
![](image/display pointer position.jpg)
### Mapping type description
-switchKey: Switch the key of the custom key mapping. The default is the normal mapping. You need to use this key to switch between the normal mapping and the custom mapping.
-mouseMoveMap: mouse movement mapping, the movement of the mouse will be mapped to startPos as the starting point, and the direction of the mouse movement as the direction of the finger drag operation (after the mouse movement map is turned on, the mouse will be hidden, limiting the range of mouse movement).
Generally used to adjust the character field of vision in FPS mobile games.
-startPos finger drag starting point
-speedRatio mouse movement is mapped to the ratio of finger dragging, you can control the mouse sensitivity, the value should be greater than 0.00, the greater the value, the lower the sensitivity
-smallEyes The button that triggers the small eyes. After pressing this button, the mouse movement will be mapped to the finger drag operation with the smallEyes.pos as the starting point and the mouse movement direction as the movement direction
-keyMapNodes general key map, json array, all general key maps are placed in this array, map the keys of the keyboard to ordinary finger clicks.
There are several types of key mapping as follows:
-type The type of key mapping, each element in keyMapNodes needs to be specified, and can be of the following types:
-KMT_CLICK Ordinary click, key press simulates finger press, key lift simulates finger lift
-KMT_CLICK_TWICE Double click, key press simulates finger press and then lift, key lift simulates finger press and then lift
-KMT_DRAG drag and drop, the key press is simulated as a finger press and drag a distance, the key lift is simulated as a finger lift
-KMT_STEER_WHEEL steering wheel mapping, which is dedicated to the mapping of the steering wheel for moving characters in FPS games, requires 4 buttons to cooperate.
Description of the unique attributes of different key mapping types:
-KMT_CLICK
-key The key code to be mapped
-pos simulates the location of the touch
-Whether the switchMap releases the mouse. After clicking this button, besides the default simulated touch map, whether the mouse operation is released. (You can refer to the effect of M map mapping in Peace Elite Map)
-KMT_CLICK_TWICE
-key The key code to be mapped
-pos simulates the location of the touch
-KMT_DRAG
-key The key code to be mapped
-startPos Simulate the start position of touch drag
-endPos Simulate the end position of touch drag
-KMT_STEER_WHEEL
-centerPos steering wheel center point
-leftKey key control in the left direction
-rightKey Right key control
-UpKey key control
-downKey key control in down direction
-leftOffset After dragging the left arrow key, drag to the leftOffset horizontally to the centerPos
-rightOffset After pressing the right direction key, drag it to the right offset of the center to the right of the centerPos position
-upOffset After pressing the up arrow key, drag it to the upper offset position horizontally relative to the centerPos position
-downOffset Press the down arrow key and drag it to the downOffset position horizontally relative to the centerPos position

View file

@ -227,7 +227,7 @@
"type": "KMT_CLICK",
"key": "Key_7",
"pos": {
"x": 0.61,
"x": 0.63,
"y": 0.82
},
"switchMap": false
@ -237,7 +237,7 @@
"type": "KMT_CLICK",
"key": "Key_Shift",
"pos": {
"x": 0.82,
"x": 0.8,
"y": 0.8
},
"switchMap": false

View file

@ -1,34 +1,13 @@
apply plugin: 'com.android.application'
buildscript {
repositories {
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.1.1'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
allprojects {
repositories {
google()
jcenter()
}
}
android {
compileSdkVersion 29
defaultConfig {
applicationId "com.genymobile.scrcpy"
minSdkVersion 21
targetSdkVersion 29
versionCode 5
versionName "1.12.1"
versionCode 16
versionName "1.14"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {

View file

@ -0,0 +1,114 @@
package com.genymobile.scrcpy;
import org.junit.Assert;
import org.junit.Test;
import java.util.List;
public class CodecOptionsTest {
@Test
public void testIntegerImplicit() {
List<CodecOption> codecOptions = CodecOption.parse("some_key=5");
Assert.assertEquals(1, codecOptions.size());
CodecOption option = codecOptions.get(0);
Assert.assertEquals("some_key", option.getKey());
Assert.assertEquals(5, option.getValue());
}
@Test
public void testInteger() {
List<CodecOption> codecOptions = CodecOption.parse("some_key:int=5");
Assert.assertEquals(1, codecOptions.size());
CodecOption option = codecOptions.get(0);
Assert.assertEquals("some_key", option.getKey());
Assert.assertTrue(option.getValue() instanceof Integer);
Assert.assertEquals(5, option.getValue());
}
@Test
public void testLong() {
List<CodecOption> codecOptions = CodecOption.parse("some_key:long=5");
Assert.assertEquals(1, codecOptions.size());
CodecOption option = codecOptions.get(0);
Assert.assertEquals("some_key", option.getKey());
Assert.assertTrue(option.getValue() instanceof Long);
Assert.assertEquals(5L, option.getValue());
}
@Test
public void testFloat() {
List<CodecOption> codecOptions = CodecOption.parse("some_key:float=4.5");
Assert.assertEquals(1, codecOptions.size());
CodecOption option = codecOptions.get(0);
Assert.assertEquals("some_key", option.getKey());
Assert.assertTrue(option.getValue() instanceof Float);
Assert.assertEquals(4.5f, option.getValue());
}
@Test
public void testString() {
List<CodecOption> codecOptions = CodecOption.parse("some_key:string=some_value");
Assert.assertEquals(1, codecOptions.size());
CodecOption option = codecOptions.get(0);
Assert.assertEquals("some_key", option.getKey());
Assert.assertTrue(option.getValue() instanceof String);
Assert.assertEquals("some_value", option.getValue());
}
@Test
public void testStringEscaped() {
List<CodecOption> codecOptions = CodecOption.parse("some_key:string=warning\\,this_is_not=a_new_key");
Assert.assertEquals(1, codecOptions.size());
CodecOption option = codecOptions.get(0);
Assert.assertEquals("some_key", option.getKey());
Assert.assertTrue(option.getValue() instanceof String);
Assert.assertEquals("warning,this_is_not=a_new_key", option.getValue());
}
@Test
public void testList() {
List<CodecOption> codecOptions = CodecOption.parse("a=1,b:int=2,c:long=3,d:float=4.5,e:string=a\\,b=c");
Assert.assertEquals(5, codecOptions.size());
CodecOption option;
option = codecOptions.get(0);
Assert.assertEquals("a", option.getKey());
Assert.assertTrue(option.getValue() instanceof Integer);
Assert.assertEquals(1, option.getValue());
option = codecOptions.get(1);
Assert.assertEquals("b", option.getKey());
Assert.assertTrue(option.getValue() instanceof Integer);
Assert.assertEquals(2, option.getValue());
option = codecOptions.get(2);
Assert.assertEquals("c", option.getKey());
Assert.assertTrue(option.getValue() instanceof Long);
Assert.assertEquals(3L, option.getValue());
option = codecOptions.get(3);
Assert.assertEquals("d", option.getKey());
Assert.assertTrue(option.getValue() instanceof Float);
Assert.assertEquals(4.5f, option.getValue());
option = codecOptions.get(4);
Assert.assertEquals("e", option.getKey());
Assert.assertTrue(option.getValue() instanceof String);
Assert.assertEquals("a,b=c", option.getValue());
}
}

View file

@ -0,0 +1,374 @@
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 ControlMessageReaderTest {
@Test
public void testParseKeycodeEvent() throws IOException {
ControlMessageReader reader = new ControlMessageReader();
ByteArrayOutputStream bos = new ByteArrayOutputStream();
DataOutputStream dos = new DataOutputStream(bos);
dos.writeByte(ControlMessage.TYPE_INJECT_KEYCODE);
dos.writeByte(KeyEvent.ACTION_UP);
dos.writeInt(KeyEvent.KEYCODE_ENTER);
dos.writeInt(KeyEvent.META_CTRL_ON);
byte[] packet = bos.toByteArray();
// The message type (1 byte) does not count
Assert.assertEquals(ControlMessageReader.INJECT_KEYCODE_PAYLOAD_LENGTH, packet.length - 1);
reader.readFrom(new ByteArrayInputStream(packet));
ControlMessage event = reader.next();
Assert.assertEquals(ControlMessage.TYPE_INJECT_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 {
ControlMessageReader reader = new ControlMessageReader();
ByteArrayOutputStream bos = new ByteArrayOutputStream();
DataOutputStream dos = new DataOutputStream(bos);
dos.writeByte(ControlMessage.TYPE_INJECT_TEXT);
byte[] text = "testé".getBytes(StandardCharsets.UTF_8);
dos.writeShort(text.length);
dos.write(text);
byte[] packet = bos.toByteArray();
reader.readFrom(new ByteArrayInputStream(packet));
ControlMessage event = reader.next();
Assert.assertEquals(ControlMessage.TYPE_INJECT_TEXT, event.getType());
Assert.assertEquals("testé", event.getText());
}
@Test
public void testParseLongTextEvent() throws IOException {
ControlMessageReader reader = new ControlMessageReader();
ByteArrayOutputStream bos = new ByteArrayOutputStream();
DataOutputStream dos = new DataOutputStream(bos);
dos.writeByte(ControlMessage.TYPE_INJECT_TEXT);
byte[] text = new byte[ControlMessageReader.INJECT_TEXT_MAX_LENGTH];
Arrays.fill(text, (byte) 'a');
dos.writeShort(text.length);
dos.write(text);
byte[] packet = bos.toByteArray();
reader.readFrom(new ByteArrayInputStream(packet));
ControlMessage event = reader.next();
Assert.assertEquals(ControlMessage.TYPE_INJECT_TEXT, event.getType());
Assert.assertEquals(new String(text, StandardCharsets.US_ASCII), event.getText());
}
@Test
public void testParseTouchEvent() throws IOException {
ControlMessageReader reader = new ControlMessageReader();
ByteArrayOutputStream bos = new ByteArrayOutputStream();
DataOutputStream dos = new DataOutputStream(bos);
dos.writeByte(ControlMessage.TYPE_INJECT_TOUCH_EVENT);
dos.writeByte(MotionEvent.ACTION_DOWN);
dos.writeLong(-42); // pointerId
dos.writeInt(100);
dos.writeInt(200);
dos.writeShort(1080);
dos.writeShort(1920);
dos.writeShort(0xffff); // pressure
dos.writeInt(MotionEvent.BUTTON_PRIMARY);
byte[] packet = bos.toByteArray();
// The message type (1 byte) does not count
Assert.assertEquals(ControlMessageReader.INJECT_TOUCH_EVENT_PAYLOAD_LENGTH, packet.length - 1);
reader.readFrom(new ByteArrayInputStream(packet));
ControlMessage event = reader.next();
Assert.assertEquals(ControlMessage.TYPE_INJECT_TOUCH_EVENT, event.getType());
Assert.assertEquals(MotionEvent.ACTION_DOWN, event.getAction());
Assert.assertEquals(-42, event.getPointerId());
Assert.assertEquals(100, event.getPosition().getPoint().getX());
Assert.assertEquals(200, event.getPosition().getPoint().getY());
Assert.assertEquals(1080, event.getPosition().getScreenSize().getWidth());
Assert.assertEquals(1920, event.getPosition().getScreenSize().getHeight());
Assert.assertEquals(1f, event.getPressure(), 0f); // must be exact
Assert.assertEquals(MotionEvent.BUTTON_PRIMARY, event.getButtons());
}
@Test
public void testParseScrollEvent() throws IOException {
ControlMessageReader reader = new ControlMessageReader();
ByteArrayOutputStream bos = new ByteArrayOutputStream();
DataOutputStream dos = new DataOutputStream(bos);
dos.writeByte(ControlMessage.TYPE_INJECT_SCROLL_EVENT);
dos.writeInt(260);
dos.writeInt(1026);
dos.writeShort(1080);
dos.writeShort(1920);
dos.writeInt(1);
dos.writeInt(-1);
byte[] packet = bos.toByteArray();
// The message type (1 byte) does not count
Assert.assertEquals(ControlMessageReader.INJECT_SCROLL_EVENT_PAYLOAD_LENGTH, packet.length - 1);
reader.readFrom(new ByteArrayInputStream(packet));
ControlMessage event = reader.next();
Assert.assertEquals(ControlMessage.TYPE_INJECT_SCROLL_EVENT, event.getType());
Assert.assertEquals(260, event.getPosition().getPoint().getX());
Assert.assertEquals(1026, event.getPosition().getPoint().getY());
Assert.assertEquals(1080, event.getPosition().getScreenSize().getWidth());
Assert.assertEquals(1920, event.getPosition().getScreenSize().getHeight());
Assert.assertEquals(1, event.getHScroll());
Assert.assertEquals(-1, event.getVScroll());
}
@Test
public void testParseBackOrScreenOnEvent() throws IOException {
ControlMessageReader reader = new ControlMessageReader();
ByteArrayOutputStream bos = new ByteArrayOutputStream();
DataOutputStream dos = new DataOutputStream(bos);
dos.writeByte(ControlMessage.TYPE_BACK_OR_SCREEN_ON);
byte[] packet = bos.toByteArray();
reader.readFrom(new ByteArrayInputStream(packet));
ControlMessage event = reader.next();
Assert.assertEquals(ControlMessage.TYPE_BACK_OR_SCREEN_ON, event.getType());
}
@Test
public void testParseExpandNotificationPanelEvent() throws IOException {
ControlMessageReader reader = new ControlMessageReader();
ByteArrayOutputStream bos = new ByteArrayOutputStream();
DataOutputStream dos = new DataOutputStream(bos);
dos.writeByte(ControlMessage.TYPE_EXPAND_NOTIFICATION_PANEL);
byte[] packet = bos.toByteArray();
reader.readFrom(new ByteArrayInputStream(packet));
ControlMessage event = reader.next();
Assert.assertEquals(ControlMessage.TYPE_EXPAND_NOTIFICATION_PANEL, event.getType());
}
@Test
public void testParseCollapseNotificationPanelEvent() throws IOException {
ControlMessageReader reader = new ControlMessageReader();
ByteArrayOutputStream bos = new ByteArrayOutputStream();
DataOutputStream dos = new DataOutputStream(bos);
dos.writeByte(ControlMessage.TYPE_COLLAPSE_NOTIFICATION_PANEL);
byte[] packet = bos.toByteArray();
reader.readFrom(new ByteArrayInputStream(packet));
ControlMessage event = reader.next();
Assert.assertEquals(ControlMessage.TYPE_COLLAPSE_NOTIFICATION_PANEL, event.getType());
}
@Test
public void testParseGetClipboardEvent() throws IOException {
ControlMessageReader reader = new ControlMessageReader();
ByteArrayOutputStream bos = new ByteArrayOutputStream();
DataOutputStream dos = new DataOutputStream(bos);
dos.writeByte(ControlMessage.TYPE_GET_CLIPBOARD);
byte[] packet = bos.toByteArray();
reader.readFrom(new ByteArrayInputStream(packet));
ControlMessage event = reader.next();
Assert.assertEquals(ControlMessage.TYPE_GET_CLIPBOARD, event.getType());
}
@Test
public void testParseSetClipboardEvent() throws IOException {
ControlMessageReader reader = new ControlMessageReader();
ByteArrayOutputStream bos = new ByteArrayOutputStream();
DataOutputStream dos = new DataOutputStream(bos);
dos.writeByte(ControlMessage.TYPE_SET_CLIPBOARD);
dos.writeByte(1); // paste
byte[] text = "testé".getBytes(StandardCharsets.UTF_8);
dos.writeShort(text.length);
dos.write(text);
byte[] packet = bos.toByteArray();
reader.readFrom(new ByteArrayInputStream(packet));
ControlMessage event = reader.next();
Assert.assertEquals(ControlMessage.TYPE_SET_CLIPBOARD, event.getType());
Assert.assertEquals("testé", event.getText());
boolean parse = (event.getFlags() & ControlMessage.FLAGS_PASTE) != 0;
Assert.assertTrue(parse);
}
@Test
public void testParseBigSetClipboardEvent() throws IOException {
ControlMessageReader reader = new ControlMessageReader();
ByteArrayOutputStream bos = new ByteArrayOutputStream();
DataOutputStream dos = new DataOutputStream(bos);
dos.writeByte(ControlMessage.TYPE_SET_CLIPBOARD);
byte[] rawText = new byte[ControlMessageReader.CLIPBOARD_TEXT_MAX_LENGTH];
dos.writeByte(1); // paste
Arrays.fill(rawText, (byte) 'a');
String text = new String(rawText, 0, rawText.length);
dos.writeShort(rawText.length);
dos.write(rawText);
byte[] packet = bos.toByteArray();
reader.readFrom(new ByteArrayInputStream(packet));
ControlMessage event = reader.next();
Assert.assertEquals(ControlMessage.TYPE_SET_CLIPBOARD, event.getType());
Assert.assertEquals(text, event.getText());
boolean parse = (event.getFlags() & ControlMessage.FLAGS_PASTE) != 0;
Assert.assertTrue(parse);
}
@Test
public void testParseSetScreenPowerMode() throws IOException {
ControlMessageReader reader = new ControlMessageReader();
ByteArrayOutputStream bos = new ByteArrayOutputStream();
DataOutputStream dos = new DataOutputStream(bos);
dos.writeByte(ControlMessage.TYPE_SET_SCREEN_POWER_MODE);
dos.writeByte(Device.POWER_MODE_NORMAL);
byte[] packet = bos.toByteArray();
// The message type (1 byte) does not count
Assert.assertEquals(ControlMessageReader.SET_SCREEN_POWER_MODE_PAYLOAD_LENGTH, packet.length - 1);
reader.readFrom(new ByteArrayInputStream(packet));
ControlMessage event = reader.next();
Assert.assertEquals(ControlMessage.TYPE_SET_SCREEN_POWER_MODE, event.getType());
Assert.assertEquals(Device.POWER_MODE_NORMAL, event.getAction());
}
@Test
public void testParseRotateDevice() throws IOException {
ControlMessageReader reader = new ControlMessageReader();
ByteArrayOutputStream bos = new ByteArrayOutputStream();
DataOutputStream dos = new DataOutputStream(bos);
dos.writeByte(ControlMessage.TYPE_ROTATE_DEVICE);
byte[] packet = bos.toByteArray();
reader.readFrom(new ByteArrayInputStream(packet));
ControlMessage event = reader.next();
Assert.assertEquals(ControlMessage.TYPE_ROTATE_DEVICE, event.getType());
}
@Test
public void testMultiEvents() throws IOException {
ControlMessageReader reader = new ControlMessageReader();
ByteArrayOutputStream bos = new ByteArrayOutputStream();
DataOutputStream dos = new DataOutputStream(bos);
dos.writeByte(ControlMessage.TYPE_INJECT_KEYCODE);
dos.writeByte(KeyEvent.ACTION_UP);
dos.writeInt(KeyEvent.KEYCODE_ENTER);
dos.writeInt(KeyEvent.META_CTRL_ON);
dos.writeByte(ControlMessage.TYPE_INJECT_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));
ControlMessage event = reader.next();
Assert.assertEquals(ControlMessage.TYPE_INJECT_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(ControlMessage.TYPE_INJECT_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 {
ControlMessageReader reader = new ControlMessageReader();
ByteArrayOutputStream bos = new ByteArrayOutputStream();
DataOutputStream dos = new DataOutputStream(bos);
dos.writeByte(ControlMessage.TYPE_INJECT_KEYCODE);
dos.writeByte(KeyEvent.ACTION_UP);
dos.writeInt(KeyEvent.KEYCODE_ENTER);
dos.writeInt(KeyEvent.META_CTRL_ON);
dos.writeByte(ControlMessage.TYPE_INJECT_KEYCODE);
dos.writeByte(MotionEvent.ACTION_DOWN);
byte[] packet = bos.toByteArray();
reader.readFrom(new ByteArrayInputStream(packet));
ControlMessage event = reader.next();
Assert.assertEquals(ControlMessage.TYPE_INJECT_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(ControlMessage.TYPE_INJECT_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());
}
}

View file

@ -0,0 +1,35 @@
package com.genymobile.scrcpy;
import org.junit.Assert;
import org.junit.Test;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
public class DeviceMessageWriterTest {
@Test
public void testSerializeClipboard() throws IOException {
DeviceMessageWriter writer = new DeviceMessageWriter();
String text = "aéûoç";
byte[] data = text.getBytes(StandardCharsets.UTF_8);
ByteArrayOutputStream bos = new ByteArrayOutputStream();
DataOutputStream dos = new DataOutputStream(bos);
dos.writeByte(DeviceMessage.TYPE_CLIPBOARD);
dos.writeShort(data.length);
dos.write(data);
byte[] expected = bos.toByteArray();
DeviceMessage msg = DeviceMessage.createClipboard(text);
bos = new ByteArrayOutputStream();
writer.writeTo(msg, bos);
byte[] actual = bos.toByteArray();
Assert.assertArrayEquals(expected, actual);
}
}

View file

@ -0,0 +1,42 @@
package com.genymobile.scrcpy;
import org.junit.Assert;
import org.junit.Test;
import java.nio.charset.StandardCharsets;
public class StringUtilsTest {
@Test
public void testUtf8Truncate() {
String s = "aÉbÔc";
byte[] utf8 = s.getBytes(StandardCharsets.UTF_8);
Assert.assertEquals(7, utf8.length);
int count;
count = StringUtils.getUtf8TruncationIndex(utf8, 1);
Assert.assertEquals(1, count);
count = StringUtils.getUtf8TruncationIndex(utf8, 2);
Assert.assertEquals(1, count); // É is 2 bytes-wide
count = StringUtils.getUtf8TruncationIndex(utf8, 3);
Assert.assertEquals(3, count);
count = StringUtils.getUtf8TruncationIndex(utf8, 4);
Assert.assertEquals(4, count);
count = StringUtils.getUtf8TruncationIndex(utf8, 5);
Assert.assertEquals(4, count); // Ô is 2 bytes-wide
count = StringUtils.getUtf8TruncationIndex(utf8, 6);
Assert.assertEquals(6, count);
count = StringUtils.getUtf8TruncationIndex(utf8, 7);
Assert.assertEquals(7, count);
count = StringUtils.getUtf8TruncationIndex(utf8, 8);
Assert.assertEquals(7, count); // no more chars
}
}