From d1e7be38f8b150ea7033308fb47ddb6e3cb6b1bf Mon Sep 17 00:00:00 2001 From: Liangent Date: Thu, 18 Jun 2020 17:16:39 +0000 Subject: [PATCH 1/9] Adjust some positions in gameforpeace.json * Key_Shift adjusted so it doesn't trigger the Shoot button * Key_7 adjusted so it doesn't trigger fire-mode when no handgun exists --- keymap/gameforpeace.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/keymap/gameforpeace.json b/keymap/gameforpeace.json index 0ffe340..d06f091 100644 --- a/keymap/gameforpeace.json +++ b/keymap/gameforpeace.json @@ -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 From 1b6a70245dc7f46bfc7c96c3afc3b6c8f3b1d2dd Mon Sep 17 00:00:00 2001 From: rankun Date: Sat, 20 Jun 2020 09:31:43 +0800 Subject: [PATCH 2/9] feat: update Qt 5.15 --- .github/workflows/macos.yml | 4 +- .github/workflows/ubuntu.yml | 4 +- .github/workflows/windows.yml | 21 +++--- QtScrcpy/adb/adbprocess.cpp | 4 +- .../inputconvert/inputconvertnormal.cpp | 66 ++++++++----------- QtScrcpy/device/device.h | 3 +- QtScrcpy/device/ui/videoform.cpp | 12 ++-- QtScrcpy/dialog.cpp | 2 +- README.md | 2 +- README_zh.md | 2 +- 10 files changed, 55 insertions(+), 65 deletions(-) diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index 4a4c0ea..7e05e3d 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -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 }} diff --git a/.github/workflows/ubuntu.yml b/.github/workflows/ubuntu.yml index 4fea04e..cd7e846 100644 --- a/.github/workflows/ubuntu.yml +++ b/.github/workflows/ubuntu.yml @@ -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 }} diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index 7d59805..834320b 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -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 }} diff --git a/QtScrcpy/adb/adbprocess.cpp b/QtScrcpy/adb/adbprocess.cpp index 6d4980d..ad14835 100644 --- a/QtScrcpy/adb/adbprocess.cpp +++ b/QtScrcpy/adb/adbprocess.cpp @@ -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]; } diff --git a/QtScrcpy/device/controller/inputconvert/inputconvertnormal.cpp b/QtScrcpy/device/controller/inputconvert/inputconvertnormal.cpp index 3ad0dc9..adf0566 100644 --- a/QtScrcpy/device/controller/inputconvert/inputconvertnormal.cpp +++ b/QtScrcpy/device/controller/inputconvert/inputconvertnormal.cpp @@ -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() / abs(from->angleDelta().x()) * 2; + qint32 vScroll = 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: //# diff --git a/QtScrcpy/device/device.h b/QtScrcpy/device/device.h index 2463e0a..29d4b15 100644 --- a/QtScrcpy/device/device.h +++ b/QtScrcpy/device/device.h @@ -1,6 +1,7 @@ #ifndef DEVICE_H #define DEVICE_H +#include #include #include @@ -116,7 +117,7 @@ private: // ui QPointer m_videoForm; - QTime m_startTimeCount; + QElapsedTimer m_startTimeCount; DeviceParams m_params; GroupControlState m_controlState = GCS_FREE; diff --git a/QtScrcpy/device/ui/videoform.cpp b/QtScrcpy/device/ui/videoform.cpp index 5185a98..20c9602 100644 --- a/QtScrcpy/device/ui/videoform.cpp +++ b/QtScrcpy/device/ui/videoform.cpp @@ -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()); } } diff --git a/QtScrcpy/dialog.cpp b/QtScrcpy/dialog.cpp index 2417e64..e5f6827 100644 --- a/QtScrcpy/dialog.cpp +++ b/QtScrcpy/dialog.cpp @@ -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) diff --git a/README.md b/README.md index ce1fae3..ea42b23 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/README_zh.md b/README_zh.md index 7f116c5..5fb9c5e 100644 --- a/README_zh.md +++ b/README_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. 编译,运行即可 From b6e3ac184d1b18d25505d4c20249c65a8671b9bd Mon Sep 17 00:00:00 2001 From: rankun Date: Sat, 20 Jun 2020 10:54:10 +0800 Subject: [PATCH 3/9] feat: update server --- server/build.gradle | 25 +- .../genymobile/scrcpy/CodecOptionsTest.java | 114 ++++++ .../scrcpy/ControlMessageReaderTest.java | 374 ++++++++++++++++++ .../scrcpy/DeviceMessageWriterTest.java | 35 ++ .../genymobile/scrcpy/StringUtilsTest.java | 42 ++ 5 files changed, 567 insertions(+), 23 deletions(-) create mode 100644 server/src/test/java/com/genymobile/scrcpy/CodecOptionsTest.java create mode 100644 server/src/test/java/com/genymobile/scrcpy/ControlMessageReaderTest.java create mode 100644 server/src/test/java/com/genymobile/scrcpy/DeviceMessageWriterTest.java create mode 100644 server/src/test/java/com/genymobile/scrcpy/StringUtilsTest.java diff --git a/server/build.gradle b/server/build.gradle index 6670520..c8ff85d 100644 --- a/server/build.gradle +++ b/server/build.gradle @@ -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 { diff --git a/server/src/test/java/com/genymobile/scrcpy/CodecOptionsTest.java b/server/src/test/java/com/genymobile/scrcpy/CodecOptionsTest.java new file mode 100644 index 0000000..ad80225 --- /dev/null +++ b/server/src/test/java/com/genymobile/scrcpy/CodecOptionsTest.java @@ -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 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 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 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 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 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 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 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()); + } +} diff --git a/server/src/test/java/com/genymobile/scrcpy/ControlMessageReaderTest.java b/server/src/test/java/com/genymobile/scrcpy/ControlMessageReaderTest.java new file mode 100644 index 0000000..f5fa4d0 --- /dev/null +++ b/server/src/test/java/com/genymobile/scrcpy/ControlMessageReaderTest.java @@ -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()); + } +} diff --git a/server/src/test/java/com/genymobile/scrcpy/DeviceMessageWriterTest.java b/server/src/test/java/com/genymobile/scrcpy/DeviceMessageWriterTest.java new file mode 100644 index 0000000..df12f64 --- /dev/null +++ b/server/src/test/java/com/genymobile/scrcpy/DeviceMessageWriterTest.java @@ -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); + } +} diff --git a/server/src/test/java/com/genymobile/scrcpy/StringUtilsTest.java b/server/src/test/java/com/genymobile/scrcpy/StringUtilsTest.java new file mode 100644 index 0000000..89799c5 --- /dev/null +++ b/server/src/test/java/com/genymobile/scrcpy/StringUtilsTest.java @@ -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 + } +} From c5ded6fcaa2d36341d8f6aab47771e69d6e85a0f Mon Sep 17 00:00:00 2001 From: shenzhigang Date: Tue, 23 Jun 2020 11:43:17 +0800 Subject: [PATCH 4/9] Fix a misspelling, `keep radio` -> `keep ratio` --- QtScrcpy/device/ui/videoform.cpp | 26 ++++++++-------- QtScrcpy/device/ui/videoform.ui | 6 ++-- QtScrcpy/uibase/keepradiowidget.h | 28 ----------------- ...eepradiowidget.cpp => keepratiowidget.cpp} | 30 +++++++++---------- QtScrcpy/uibase/keepratiowidget.h | 28 +++++++++++++++++ QtScrcpy/uibase/uibase.pri | 4 +-- 6 files changed, 61 insertions(+), 61 deletions(-) delete mode 100644 QtScrcpy/uibase/keepradiowidget.h rename QtScrcpy/uibase/{keepradiowidget.cpp => keepratiowidget.cpp} (53%) create mode 100644 QtScrcpy/uibase/keepratiowidget.h diff --git a/QtScrcpy/device/ui/videoform.cpp b/QtScrcpy/device/ui/videoform.cpp index 20c9602..c0b1bdf 100644 --- a/QtScrcpy/device/ui/videoform.cpp +++ b/QtScrcpy/device/ui/videoform.cpp @@ -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(); @@ -646,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()) { diff --git a/QtScrcpy/device/ui/videoform.ui b/QtScrcpy/device/ui/videoform.ui index 6de4468..405386d 100644 --- a/QtScrcpy/device/ui/videoform.ui +++ b/QtScrcpy/device/ui/videoform.ui @@ -39,15 +39,15 @@ 0 - + - KeepRadioWidget + KeepRatioWidget QWidget -
keepradiowidget.h
+
keepratiowidget.h
1
diff --git a/QtScrcpy/uibase/keepradiowidget.h b/QtScrcpy/uibase/keepradiowidget.h deleted file mode 100644 index ef0877a..0000000 --- a/QtScrcpy/uibase/keepradiowidget.h +++ /dev/null @@ -1,28 +0,0 @@ -#ifndef KEEPRADIOWIDGET_H -#define KEEPRADIOWIDGET_H - -#include -#include - -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 m_subWidget; - QSize m_goodSize; -}; - -#endif // KEEPRADIOWIDGET_H diff --git a/QtScrcpy/uibase/keepradiowidget.cpp b/QtScrcpy/uibase/keepratiowidget.cpp similarity index 53% rename from QtScrcpy/uibase/keepradiowidget.cpp rename to QtScrcpy/uibase/keepratiowidget.cpp index 4e88ab0..4f22b3e 100644 --- a/QtScrcpy/uibase/keepradiowidget.cpp +++ b/QtScrcpy/uibase/keepratiowidget.cpp @@ -1,13 +1,13 @@ #include #include -#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 diff --git a/QtScrcpy/uibase/keepratiowidget.h b/QtScrcpy/uibase/keepratiowidget.h new file mode 100644 index 0000000..8cca3fe --- /dev/null +++ b/QtScrcpy/uibase/keepratiowidget.h @@ -0,0 +1,28 @@ +#ifndef KEEPRATIOWIDGET_H +#define KEEPRATIOWIDGET_H + +#include +#include + +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 m_subWidget; + QSize m_goodSize; +}; + +#endif // KEEPRATIOWIDGET_H diff --git a/QtScrcpy/uibase/uibase.pri b/QtScrcpy/uibase/uibase.pri index ae4858b..cac526a 100644 --- a/QtScrcpy/uibase/uibase.pri +++ b/QtScrcpy/uibase/uibase.pri @@ -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 From 303b017fa9f4a1a212e2c305d46440bf5e46273f Mon Sep 17 00:00:00 2001 From: rankun Date: Sat, 20 Jun 2020 17:10:12 +0800 Subject: [PATCH 5/9] feat: support drop mutil apk install Change-Id: I65d090409208b89e770c11a3ea1fa2867560d214 --- QtScrcpy/device/device.cpp | 2 +- QtScrcpy/device/filehandler/filehandler.cpp | 57 +++++++++++---------- QtScrcpy/device/filehandler/filehandler.h | 8 ++- QtScrcpy/device/ui/videoform.cpp | 24 +++++---- 4 files changed, 49 insertions(+), 42 deletions(-) diff --git a/QtScrcpy/device/device.cpp b/QtScrcpy/device/device.cpp index 4ddce87..86cb318 100644 --- a/QtScrcpy/device/device.cpp +++ b/QtScrcpy/device/device.cpp @@ -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); }); } diff --git a/QtScrcpy/device/filehandler/filehandler.cpp b/QtScrcpy/device/filehandler/filehandler.cpp index a687ee8..c8a8635 100644 --- a/QtScrcpy/device/filehandler/filehandler.cpp +++ b/QtScrcpy/device/filehandler/filehandler.cpp @@ -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; + } } diff --git a/QtScrcpy/device/filehandler/filehandler.h b/QtScrcpy/device/filehandler/filehandler.h index 6dbc9ca..9bd3588 100644 --- a/QtScrcpy/device/filehandler/filehandler.h +++ b/QtScrcpy/device/filehandler/filehandler.h @@ -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 diff --git a/QtScrcpy/device/ui/videoform.cpp b/QtScrcpy/device/ui/videoform.cpp index c0b1bdf..88bc6c3 100644 --- a/QtScrcpy/device/ui/videoform.cpp +++ b/QtScrcpy/device/ui/videoform.cpp @@ -699,17 +699,21 @@ void VideoForm::dropEvent(QDropEvent *event) return; } const QMimeData *qm = event->mimeData(); - QString file = qm->urls()[0].toLocalFile(); - QFileInfo fileInfo(file); + QList 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()); } From a3d1cfb23f94411580dc14e39f7d441b0eb03c54 Mon Sep 17 00:00:00 2001 From: rankun Date: Sun, 21 Jun 2020 01:42:22 +0800 Subject: [PATCH 6/9] fix: mouse wheel crash Change-Id: I1976037cf0872e89fc08c8afdbd3fde79a8df061 --- .../device/controller/inputconvert/inputconvertnormal.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/QtScrcpy/device/controller/inputconvert/inputconvertnormal.cpp b/QtScrcpy/device/controller/inputconvert/inputconvertnormal.cpp index adf0566..63924e1 100644 --- a/QtScrcpy/device/controller/inputconvert/inputconvertnormal.cpp +++ b/QtScrcpy/device/controller/inputconvert/inputconvertnormal.cpp @@ -55,8 +55,8 @@ void InputConvertNormal::wheelEvent(const QWheelEvent *from, const QSize &frameS } // delta - qint32 hScroll = from->angleDelta().x() / abs(from->angleDelta().x()) * 2; - qint32 vScroll = from->angleDelta().y() / abs(from->angleDelta().y()) * 2; + 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->position(); From 4eafdc6577f9e4c9741ab107f448c7d90590e4b1 Mon Sep 17 00:00:00 2001 From: rankun Date: Wed, 8 Jul 2020 11:55:15 +0800 Subject: [PATCH 7/9] feat: add codec options --- QtScrcpy/device/server/server.cpp | 2 +- QtScrcpy/util/config.cpp | 12 ++++++++++++ QtScrcpy/util/config.h | 1 + config/config.ini | 4 ++++ 4 files changed, 18 insertions(+), 1 deletion(-) diff --git a/QtScrcpy/device/server/server.cpp b/QtScrcpy/device/server/server.cpp index ec51093..1ff8c02 100644 --- a/QtScrcpy/device/server/server.cpp +++ b/QtScrcpy/device/server/server.cpp @@ -161,7 +161,7 @@ bool Server::execute() // code option // https://github.com/Genymobile/scrcpy/commit/080a4ee3654a9b7e96c8ffe37474b5c21c02852a // - args << "-"; + args << Config::getInstance().getCodecOptions(); #ifdef SERVER_DEBUGGER qInfo("Server debugger waiting for a client on device port " SERVER_DEBUGGER_PORT "..."); diff --git a/QtScrcpy/util/config.cpp b/QtScrcpy/util/config.cpp index c30fa0c..b067941 100644 --- a/QtScrcpy/util/config.cpp +++ b/QtScrcpy/util/config.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; diff --git a/QtScrcpy/util/config.h b/QtScrcpy/util/config.h index 8f82fda..681b422 100644 --- a/QtScrcpy/util/config.h +++ b/QtScrcpy/util/config.h @@ -22,6 +22,7 @@ public: QString getServerPath(); QString getAdbPath(); QString getLogLevel(); + QString getCodecOptions(); // user data QString getRecordPath(); diff --git a/config/config.ini b/config/config.ini index f72d577..ba11654 100644 --- a/config/config.ini +++ b/config/config.ini @@ -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 From d440a0158c5e6c7ec44ae81ff9bce202feb7a087 Mon Sep 17 00:00:00 2001 From: rankun Date: Wed, 8 Jul 2020 12:03:41 +0800 Subject: [PATCH 8/9] docs: keymap des for english Close #247 --- README.md | 6 +-- README_zh.md | 6 +-- docs/KeyMapDes.md | 66 +++++++++++++++++++++++ docs/{按键映射说明.md => KeyMapDes_zh.md} | 0 4 files changed, 72 insertions(+), 6 deletions(-) create mode 100644 docs/KeyMapDes.md rename docs/{按键映射说明.md => KeyMapDes_zh.md} (100%) diff --git a/README.md b/README.md index ea42b23..6427b5d 100644 --- a/README.md +++ b/README.md @@ -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) diff --git a/README_zh.md b/README_zh.md index 5fb9c5e..1973cb9 100644 --- a/README_zh.md +++ b/README_zh.md @@ -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) diff --git a/docs/KeyMapDes.md b/docs/KeyMapDes.md new file mode 100644 index 0000000..8232a1c --- /dev/null +++ b/docs/KeyMapDes.md @@ -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 \ No newline at end of file diff --git a/docs/按键映射说明.md b/docs/KeyMapDes_zh.md similarity index 100% rename from docs/按键映射说明.md rename to docs/KeyMapDes_zh.md From f6093cb0e7e99b54879acd80bb7ec180380132f4 Mon Sep 17 00:00:00 2001 From: rankun Date: Wed, 8 Jul 2020 12:17:46 +0800 Subject: [PATCH 9/9] feat: optimize process mouse move --- QtScrcpy/device/controller/inputconvert/inputconvertgame.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/QtScrcpy/device/controller/inputconvert/inputconvertgame.cpp b/QtScrcpy/device/controller/inputconvert/inputconvertgame.cpp index a237bdf..5dcf38d 100644 --- a/QtScrcpy/device/controller/inputconvert/inputconvertgame.cpp +++ b/QtScrcpy/device/controller/inputconvert/inputconvertgame.cpp @@ -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;