mirror of
https://github.com/barry-ran/QtScrcpy.git
synced 2025-08-08 00:18:39 +00:00
commit
5dc3c08b05
31 changed files with 826 additions and 202 deletions
4
.github/workflows/macos.yml
vendored
4
.github/workflows/macos.yml
vendored
|
@ -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 }}
|
||||
|
|
4
.github/workflows/ubuntu.yml
vendored
4
.github/workflows/ubuntu.yml
vendored
|
@ -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 }}
|
||||
|
|
21
.github/workflows/windows.yml
vendored
21
.github/workflows/windows.yml
vendored
|
@ -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安装的是vs2019,windows 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-arch,qt-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 }}
|
||||
|
|
|
@ -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];
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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_BraceLeft: //{
|
||||
case Qt::Key_BracketLeft: //[
|
||||
case Qt::Key_BraceLeft: //{
|
||||
keyCode = AKEYCODE_LEFT_BRACKET;
|
||||
break;
|
||||
case Qt::Key_BracketRight://]
|
||||
case Qt::Key_BraceRight: //}
|
||||
case Qt::Key_BracketRight: //]
|
||||
case Qt::Key_BraceRight: //}
|
||||
keyCode = AKEYCODE_RIGHT_BRACKET;
|
||||
break;
|
||||
case Qt::Key_Backslash:// \ ????
|
||||
case Qt::Key_Bar: //|
|
||||
case Qt::Key_Backslash: // \ ????
|
||||
case Qt::Key_Bar: //|
|
||||
keyCode = AKEYCODE_BACKSLASH;
|
||||
break;
|
||||
case Qt::Key_Semicolon://;
|
||||
case Qt::Key_Colon: //:
|
||||
case Qt::Key_Semicolon: //;
|
||||
case Qt::Key_Colon: //:
|
||||
keyCode = AKEYCODE_SEMICOLON;
|
||||
break;
|
||||
case Qt::Key_Apostrophe://'
|
||||
case Qt::Key_QuoteDbl: //"
|
||||
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: //#
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 "...");
|
||||
|
|
|
@ -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();
|
||||
QFileInfo fileInfo(file);
|
||||
QList<QUrl> urls = qm->urls();
|
||||
|
||||
if (!fileInfo.exists()) {
|
||||
QMessageBox::warning(this, "QtScrcpy", tr("file does not exist"), QMessageBox::Ok);
|
||||
return;
|
||||
}
|
||||
for (const QUrl& url : urls) {
|
||||
QString file = url.toLocalFile();
|
||||
QFileInfo fileInfo(file);
|
||||
|
||||
if (fileInfo.isFile() && fileInfo.suffix() == "apk") {
|
||||
emit m_device->installApkRequest(file);
|
||||
return;
|
||||
if (!fileInfo.exists()) {
|
||||
QMessageBox::warning(this, "QtScrcpy", tr("file does not exist"), QMessageBox::Ok);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (fileInfo.isFile() && fileInfo.suffix() == "apk") {
|
||||
emit m_device->installApkRequest(file);
|
||||
continue;
|
||||
}
|
||||
emit m_device->pushFileRequest(file, Config::getInstance().getPushFilePath() + fileInfo.fileName());
|
||||
}
|
||||
emit m_device->pushFileRequest(file, Config::getInstance().getPushFilePath() + fileInfo.fileName());
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
|
@ -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
|
28
QtScrcpy/uibase/keepratiowidget.h
Normal file
28
QtScrcpy/uibase/keepratiowidget.h
Normal 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
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -22,6 +22,7 @@ public:
|
|||
QString getServerPath();
|
||||
QString getAdbPath();
|
||||
QString getLogLevel();
|
||||
QString getCodecOptions();
|
||||
|
||||
// user data
|
||||
QString getRecordPath();
|
||||
|
|
|
@ -33,10 +33,10 @@ It focuses on:
|
|||
|
||||

|
||||
|
||||
## 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:
|
||||
|
||||

|
||||
|
||||
|
@ -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
|
||||
|
|
|
@ -33,10 +33,10 @@ QtScrcpy可以通过USB(或通过TCP/IP)连接Android设备,并进行显示和
|
|||
|
||||

|
||||
|
||||
## 自定义按键映射(仅windows&MacOS平台开启)
|
||||
可以根据需要,自己编写脚本将PC键盘按键映射为手机的触摸点击,编写规则在[这里](docs/按键映射说明.md)。
|
||||
## 自定义按键映射
|
||||
可以根据需要,自己编写脚本将PC键盘按键映射为手机的触摸点击,编写规则在[这里](docs/KeyMapDes_zh.md)。
|
||||
|
||||
默认自带了针对和平精英手游和抖音进行键鼠映射的映射脚本,开启平精英手游后可以用键鼠像玩端游一样玩和平精英手游,开启抖音映射以后可以使用上下左右方向键模拟上下左右滑动,你也可以按照[编写规则](docs/按键映射说明.md)编写其他游戏的映射文件,默认按键映射如下:
|
||||
默认自带了针对和平精英手游和抖音进行键鼠映射的映射脚本,开启平精英手游后可以用键鼠像玩端游一样玩和平精英手游,开启抖音映射以后可以使用上下左右方向键模拟上下左右滑动,你也可以按照[编写规则](docs/KeyMapDes_zh.md)编写其他游戏的映射文件,默认按键映射如下:
|
||||
|
||||

|
||||
|
||||
|
@ -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. 编译,运行即可
|
||||
|
|
|
@ -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
66
docs/KeyMapDes.md
Normal 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
|
||||

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

|
||||
|
||||
### 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
|
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
|
114
server/src/test/java/com/genymobile/scrcpy/CodecOptionsTest.java
Normal file
114
server/src/test/java/com/genymobile/scrcpy/CodecOptionsTest.java
Normal 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());
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue