├── scrcpy
├── app
│ ├── x
│ │ ├── meson-private
│ │ │ └── meson.lock
│ │ └── meson-logs
│ │ │ └── meson-log.txt
│ ├── src
│ │ ├── events.h
│ │ ├── tiny_xpm.h
│ │ ├── sys
│ │ │ ├── unix
│ │ │ │ ├── net.c
│ │ │ │ └── command.c
│ │ │ └── win
│ │ │ │ ├── net.c
│ │ │ │ └── command.c
│ │ ├── device.h
│ │ ├── opencv_injection.hpp
│ │ ├── cli.h
│ │ ├── util
│ │ │ ├── log.h
│ │ │ ├── buffer_util.h
│ │ │ ├── net.h
│ │ │ ├── cbuf.h
│ │ │ ├── lock.h
│ │ │ ├── str_util.h
│ │ │ ├── queue.h
│ │ │ ├── net.c
│ │ │ └── str_util.c
│ │ ├── decoder.h
│ │ ├── common.h
│ │ ├── receiver.h
│ │ ├── event_converter.h
│ │ ├── device_msg.h
│ │ ├── device.c
│ │ ├── stream.h
│ │ ├── controller.h
│ │ ├── input_manager.h
│ │ ├── fps_counter.h
│ │ ├── device_msg.c
│ │ ├── file_handler.h
│ │ ├── video_buffer.h
│ │ ├── server.h
│ │ ├── scrcpy.h
│ │ ├── recorder.h
│ │ ├── compat.h
│ │ ├── command.h
│ │ ├── control_msg.h
│ │ ├── main.c
│ │ ├── icon.xpm
│ │ ├── screen.h
│ │ ├── receiver.c
│ │ ├── decoder.c
│ │ ├── video_buffer.c
│ │ ├── control_msg.c
│ │ ├── tiny_xpm.c
│ │ ├── controller.c
│ │ ├── fps_counter.c
│ │ ├── opencv_injection.cpp
│ │ └── event_converter.c
│ ├── tests
│ │ ├── test_device_msg_deserialize.c
│ │ ├── test_queue.c
│ │ ├── test_cbuf.c
│ │ ├── test_buffer_util.c
│ │ └── test_cli.c
│ └── meson.build
├── settings.gradle
├── .gitignore
├── prebuilt-deps
│ ├── .gitignore
│ ├── prepare-dep
│ └── Makefile
├── assets
│ └── screenshot-debian-600.jpg
├── gradle
│ └── wrapper
│ │ ├── gradle-wrapper.jar
│ │ └── gradle-wrapper.properties
├── scripts
│ └── run-scrcpy.sh
├── server
│ ├── .gitignore
│ ├── src
│ │ ├── main
│ │ │ ├── AndroidManifest.xml
│ │ │ ├── java
│ │ │ │ └── com
│ │ │ │ │ └── genymobile
│ │ │ │ │ └── scrcpy
│ │ │ │ │ ├── DisplayInfo.java
│ │ │ │ │ ├── DeviceMessage.java
│ │ │ │ │ ├── StringUtils.java
│ │ │ │ │ ├── ScreenInfo.java
│ │ │ │ │ ├── Point.java
│ │ │ │ │ ├── DeviceMessageSender.java
│ │ │ │ │ ├── wrappers
│ │ │ │ │ ├── DisplayManager.java
│ │ │ │ │ ├── PowerManager.java
│ │ │ │ │ ├── InputManager.java
│ │ │ │ │ ├── StatusBarManager.java
│ │ │ │ │ ├── ServiceManager.java
│ │ │ │ │ ├── ClipboardManager.java
│ │ │ │ │ ├── WindowManager.java
│ │ │ │ │ └── SurfaceControl.java
│ │ │ │ │ ├── Pointer.java
│ │ │ │ │ ├── Size.java
│ │ │ │ │ ├── Position.java
│ │ │ │ │ ├── DeviceMessageWriter.java
│ │ │ │ │ ├── IO.java
│ │ │ │ │ ├── Options.java
│ │ │ │ │ ├── Ln.java
│ │ │ │ │ ├── PointersState.java
│ │ │ │ │ ├── Workarounds.java
│ │ │ │ │ ├── ControlMessage.java
│ │ │ │ │ └── DesktopConnection.java
│ │ │ └── aidl
│ │ │ │ └── android
│ │ │ │ └── view
│ │ │ │ └── IRotationWatcher.aidl
│ │ └── test
│ │ │ └── java
│ │ │ └── com
│ │ │ └── genymobile
│ │ │ └── scrcpy
│ │ │ ├── DeviceMessageWriterTest.java
│ │ │ └── StringUtilsTest.java
│ ├── build.gradle
│ ├── proguard-rules.pro
│ ├── scripts
│ │ └── build-wrapper.sh
│ ├── meson.build
│ └── build_without_gradle.sh
├── build.sh
├── meson.build
├── cross_win32.txt
├── run
├── cross_win64.txt
├── build.gradle
├── config
│ └── android-checkstyle.gradle
├── gradle.properties
├── meson_options.txt
├── release.sh
├── FAQ.ko.md
├── gradlew.bat
├── FAQ.md
└── Makefile.CrossWindows
├── .gitignore
├── detected_circle.png
├── prebuilt
└── scrcpy-server-v1.12.1
└── README.md
/scrcpy/app/x/meson-private/meson.lock:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/scrcpy/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':server'
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | fixtap.sh
2 | main.py
3 | scrcpy/x/*
4 |
--------------------------------------------------------------------------------
/scrcpy/.gitignore:
--------------------------------------------------------------------------------
1 | build/
2 | /dist/
3 | .idea/
4 | .gradle/
5 |
--------------------------------------------------------------------------------
/scrcpy/prebuilt-deps/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !/.gitignore
3 | !/Makefile
4 | !/prepare-dep
5 |
--------------------------------------------------------------------------------
/detected_circle.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RobbertH/scrcpy-opencv/HEAD/detected_circle.png
--------------------------------------------------------------------------------
/prebuilt/scrcpy-server-v1.12.1:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RobbertH/scrcpy-opencv/HEAD/prebuilt/scrcpy-server-v1.12.1
--------------------------------------------------------------------------------
/scrcpy/assets/screenshot-debian-600.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RobbertH/scrcpy-opencv/HEAD/scrcpy/assets/screenshot-debian-600.jpg
--------------------------------------------------------------------------------
/scrcpy/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RobbertH/scrcpy-opencv/HEAD/scrcpy/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/scrcpy/scripts/run-scrcpy.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | SCRCPY_SERVER_PATH="$MESON_BUILD_ROOT/server/scrcpy-server" "$MESON_BUILD_ROOT/app/scrcpy"
3 |
--------------------------------------------------------------------------------
/scrcpy/server/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea/
5 | .DS_Store
6 | /build
7 | /captures
8 | .externalNativeBuild
9 |
--------------------------------------------------------------------------------
/scrcpy/app/src/events.h:
--------------------------------------------------------------------------------
1 | #define EVENT_NEW_SESSION SDL_USEREVENT
2 | #define EVENT_NEW_FRAME (SDL_USEREVENT + 1)
3 | #define EVENT_STREAM_STOPPED (SDL_USEREVENT + 2)
4 |
--------------------------------------------------------------------------------
/scrcpy/server/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/scrcpy/app/src/tiny_xpm.h:
--------------------------------------------------------------------------------
1 | #ifndef TINYXPM_H
2 | #define TINYXPM_H
3 |
4 | #include
5 |
6 | #include "config.h"
7 |
8 | SDL_Surface *
9 | read_xpm(char *xpm[]);
10 |
11 | #endif
12 |
--------------------------------------------------------------------------------
/scrcpy/build.sh:
--------------------------------------------------------------------------------
1 | #!/bin/zsh
2 | # Script that Robbert wrote to bundle three commands
3 | rm -rf x
4 | meson x --buildtype release --strip -Db_lto=true -Dprebuilt_server=/home/robbert/Desktop/soccer-gamer/prebuilt/scrcpy-server-v1.12.1
5 | ninja -Cx
6 |
7 |
--------------------------------------------------------------------------------
/scrcpy/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.5.1-all.zip
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 |
--------------------------------------------------------------------------------
/scrcpy/app/src/sys/unix/net.c:
--------------------------------------------------------------------------------
1 | #include "util/net.h"
2 |
3 | #include
4 |
5 | #include "config.h"
6 |
7 | bool
8 | net_init(void) {
9 | // do nothing
10 | return true;
11 | }
12 |
13 | void
14 | net_cleanup(void) {
15 | // do nothing
16 | }
17 |
18 | bool
19 | net_close(socket_t socket) {
20 | return !close(socket);
21 | }
22 |
--------------------------------------------------------------------------------
/scrcpy/app/src/device.h:
--------------------------------------------------------------------------------
1 | #ifndef DEVICE_H
2 | #define DEVICE_H
3 |
4 | #include
5 |
6 | #include "config.h"
7 | #include "common.h"
8 | #include "util/net.h"
9 |
10 | #define DEVICE_NAME_FIELD_LENGTH 64
11 |
12 | // name must be at least DEVICE_NAME_FIELD_LENGTH bytes
13 | bool
14 | device_read_info(socket_t device_socket, char *device_name, struct size *size);
15 |
16 | #endif
17 |
--------------------------------------------------------------------------------
/scrcpy/app/x/meson-logs/meson-log.txt:
--------------------------------------------------------------------------------
1 | Build started at 2020-03-29T19:34:57.833964
2 | Main binary: /usr/bin/python3
3 | Python system: Linux
4 | The Meson build system
5 | Version: 0.51.2
6 | Source dir: /home/robbert/Desktop/soccer-gamer/scrcpy/app
7 | Build dir: /home/robbert/Desktop/soccer-gamer/scrcpy/app/x
8 | Build type: native build
9 |
10 | ERROR: First statement must be a call to project
11 |
--------------------------------------------------------------------------------
/scrcpy/app/src/opencv_injection.hpp:
--------------------------------------------------------------------------------
1 | #include "video_buffer.h" // AVFrame
2 | #include "screen.h"
3 |
4 | #ifdef __cplusplus
5 |
6 | #include // cv:Mat
7 |
8 | void extract_circle_and_tap(cv::Mat src); // define C++ function outside of extern "C"
9 |
10 | extern "C" {
11 | #endif
12 |
13 | void opencv_injection(AVFrame *frame); // used in screen.c
14 |
15 | #ifdef __cplusplus
16 | }
17 | #endif
18 |
--------------------------------------------------------------------------------
/scrcpy/app/src/cli.h:
--------------------------------------------------------------------------------
1 | #ifndef SCRCPY_CLI_H
2 | #define SCRCPY_CLI_H
3 |
4 | #include
5 |
6 | #include "config.h"
7 | #include "scrcpy.h"
8 |
9 | struct scrcpy_cli_args {
10 | struct scrcpy_options opts;
11 | bool help;
12 | bool version;
13 | };
14 |
15 | void
16 | scrcpy_print_usage(const char *arg0);
17 |
18 | bool
19 | scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]);
20 |
21 | #endif
22 |
--------------------------------------------------------------------------------
/scrcpy/meson.build:
--------------------------------------------------------------------------------
1 | project('scrcpy', 'c', 'cpp',
2 | version: '1.12.1',
3 | meson_version: '>= 0.37',
4 | default_options: [
5 | 'c_std=c11',
6 | 'warning_level=2',
7 | 'cpp_std=c++11'
8 | ])
9 |
10 | if get_option('compile_app')
11 | subdir('app')
12 | endif
13 |
14 | if get_option('compile_server')
15 | subdir('server')
16 | endif
17 |
18 | run_target('run', command: ['scripts/run-scrcpy.sh'])
19 |
--------------------------------------------------------------------------------
/scrcpy/server/src/main/java/com/genymobile/scrcpy/DisplayInfo.java:
--------------------------------------------------------------------------------
1 | package com.genymobile.scrcpy;
2 |
3 | public final class DisplayInfo {
4 | private final Size size;
5 | private final int rotation;
6 |
7 | public DisplayInfo(Size size, int rotation) {
8 | this.size = size;
9 | this.rotation = rotation;
10 | }
11 |
12 | public Size getSize() {
13 | return size;
14 | }
15 |
16 | public int getRotation() {
17 | return rotation;
18 | }
19 | }
20 |
21 |
--------------------------------------------------------------------------------
/scrcpy/app/src/sys/win/net.c:
--------------------------------------------------------------------------------
1 | #include "util/net.h"
2 |
3 | #include "config.h"
4 | #include "util/log.h"
5 |
6 | bool
7 | net_init(void) {
8 | WSADATA wsa;
9 | int res = WSAStartup(MAKEWORD(2, 2), &wsa) < 0;
10 | if (res < 0) {
11 | LOGC("WSAStartup failed with error %d", res);
12 | return false;
13 | }
14 | return true;
15 | }
16 |
17 | void
18 | net_cleanup(void) {
19 | WSACleanup();
20 | }
21 |
22 | bool
23 | net_close(socket_t socket) {
24 | return !closesocket(socket);
25 | }
26 |
--------------------------------------------------------------------------------
/scrcpy/app/src/util/log.h:
--------------------------------------------------------------------------------
1 | #ifndef LOG_H
2 | #define LOG_H
3 |
4 | #include
5 |
6 | #define LOGV(...) SDL_LogVerbose(SDL_LOG_CATEGORY_APPLICATION, __VA_ARGS__)
7 | #define LOGD(...) SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION, __VA_ARGS__)
8 | #define LOGI(...) SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, __VA_ARGS__)
9 | #define LOGW(...) SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, __VA_ARGS__)
10 | #define LOGE(...) SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, __VA_ARGS__)
11 | #define LOGC(...) SDL_LogCritical(SDL_LOG_CATEGORY_APPLICATION, __VA_ARGS__)
12 |
13 | #endif
14 |
--------------------------------------------------------------------------------
/scrcpy/cross_win32.txt:
--------------------------------------------------------------------------------
1 | # apt install mingw-w64 mingw-w64-tools
2 |
3 | [binaries]
4 | name = 'mingw'
5 | c = '/usr/bin/i686-w64-mingw32-gcc'
6 | cpp = '/usr/bin/i686-w64-mingw32-g++'
7 | ar = '/usr/bin/i686-w64-mingw32-ar'
8 | strip = '/usr/bin/i686-w64-mingw32-strip'
9 | pkgconfig = '/usr/bin/i686-w64-mingw32-pkg-config'
10 |
11 | [host_machine]
12 | system = 'windows'
13 | cpu_family = 'x86'
14 | cpu = 'i686'
15 | endian = 'little'
16 |
17 | [properties]
18 | prebuilt_ffmpeg_shared = 'ffmpeg-4.2.1-win32-shared'
19 | prebuilt_ffmpeg_dev = 'ffmpeg-4.2.1-win32-dev'
20 | prebuilt_sdl2 = 'SDL2-2.0.10/i686-w64-mingw32'
21 |
--------------------------------------------------------------------------------
/scrcpy/run:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | # Run scrcpy generated in the specified BUILDDIR.
3 | #
4 | # This provides the same feature as "ninja run", except that it is possible to
5 | # pass arguments to scrcpy.
6 | #
7 | # Syntax: ./run BUILDDIR
8 | if [[ $# = 0 ]]
9 | then
10 | echo "Syntax: $0 BUILDDIR " >&2
11 | exit 1
12 | fi
13 |
14 | BUILDDIR="$1"
15 | shift
16 |
17 | if [[ ! -d "$BUILDDIR" ]]
18 | then
19 | echo "The build dir \"$BUILDDIR\" does not exist." >&2
20 | exit 1
21 | fi
22 |
23 | SCRCPY_SERVER_PATH="$BUILDDIR/server/scrcpy-server" "$BUILDDIR/app/scrcpy" "$@"
24 |
--------------------------------------------------------------------------------
/scrcpy/cross_win64.txt:
--------------------------------------------------------------------------------
1 | # apt install mingw-w64 mingw-w64-tools
2 |
3 | [binaries]
4 | name = 'mingw'
5 | c = '/usr/bin/x86_64-w64-mingw32-gcc'
6 | cpp = '/usr/bin/x86_64-w64-mingw32-g++'
7 | ar = '/usr/bin/x86_64-w64-mingw32-ar'
8 | strip = '/usr/bin/x86_64-w64-mingw32-strip'
9 | pkgconfig = '/usr/bin/x86_64-w64-mingw32-pkg-config'
10 |
11 | [host_machine]
12 | system = 'windows'
13 | cpu_family = 'x86'
14 | cpu = 'x86_64'
15 | endian = 'little'
16 |
17 | [properties]
18 | prebuilt_ffmpeg_shared = 'ffmpeg-4.2.1-win64-shared'
19 | prebuilt_ffmpeg_dev = 'ffmpeg-4.2.1-win64-dev'
20 | prebuilt_sdl2 = 'SDL2-2.0.10/x86_64-w64-mingw32'
21 |
--------------------------------------------------------------------------------
/scrcpy/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 |
3 | buildscript {
4 |
5 | repositories {
6 | google()
7 | jcenter()
8 | }
9 | dependencies {
10 | classpath 'com.android.tools.build:gradle:3.4.2'
11 |
12 | // NOTE: Do not place your application dependencies here; they belong
13 | // in the individual module build.gradle files
14 | }
15 | }
16 |
17 | allprojects {
18 | repositories {
19 | google()
20 | jcenter()
21 | }
22 | }
23 |
24 | task clean(type: Delete) {
25 | delete rootProject.buildDir
26 | }
27 |
--------------------------------------------------------------------------------
/scrcpy/app/src/decoder.h:
--------------------------------------------------------------------------------
1 | #ifndef DECODER_H
2 | #define DECODER_H
3 |
4 | #include
5 | #include
6 |
7 | #include "config.h"
8 |
9 | struct video_buffer;
10 |
11 | struct decoder {
12 | struct video_buffer *video_buffer;
13 | AVCodecContext *codec_ctx;
14 | };
15 |
16 | void
17 | decoder_init(struct decoder *decoder, struct video_buffer *vb);
18 |
19 | bool
20 | decoder_open(struct decoder *decoder, const AVCodec *codec);
21 |
22 | void
23 | decoder_close(struct decoder *decoder);
24 |
25 | bool
26 | decoder_push(struct decoder *decoder, const AVPacket *packet);
27 |
28 | void
29 | decoder_interrupt(struct decoder *decoder);
30 |
31 | #endif
32 |
--------------------------------------------------------------------------------
/scrcpy/server/src/main/java/com/genymobile/scrcpy/DeviceMessage.java:
--------------------------------------------------------------------------------
1 | package com.genymobile.scrcpy;
2 |
3 | public final class DeviceMessage {
4 |
5 | public static final int TYPE_CLIPBOARD = 0;
6 |
7 | private int type;
8 | private String text;
9 |
10 | private DeviceMessage() {
11 | }
12 |
13 | public static DeviceMessage createClipboard(String text) {
14 | DeviceMessage event = new DeviceMessage();
15 | event.type = TYPE_CLIPBOARD;
16 | event.text = text;
17 | return event;
18 | }
19 |
20 | public int getType() {
21 | return type;
22 | }
23 |
24 | public String getText() {
25 | return text;
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/scrcpy/app/src/common.h:
--------------------------------------------------------------------------------
1 | #ifndef COMMON_H
2 | #define COMMON_H
3 |
4 | #include
5 |
6 | #include "config.h"
7 |
8 | #define ARRAY_LEN(a) (sizeof(a) / sizeof(a[0]))
9 | #define MIN(X,Y) (X) < (Y) ? (X) : (Y)
10 | #define MAX(X,Y) (X) > (Y) ? (X) : (Y)
11 |
12 | struct size {
13 | uint16_t width;
14 | uint16_t height;
15 | };
16 |
17 | struct point {
18 | int32_t x;
19 | int32_t y;
20 | };
21 |
22 | struct position {
23 | // The video screen size may be different from the real device screen size,
24 | // so store to which size the absolute position apply, to scale it
25 | // accordingly.
26 | struct size screen_size;
27 | struct point point;
28 | };
29 |
30 | #endif
31 |
--------------------------------------------------------------------------------
/scrcpy/app/src/receiver.h:
--------------------------------------------------------------------------------
1 | #ifndef RECEIVER_H
2 | #define RECEIVER_H
3 |
4 | #include
5 | #include
6 | #include
7 |
8 | #include "config.h"
9 | #include "util/net.h"
10 |
11 | // receive events from the device
12 | // managed by the controller
13 | struct receiver {
14 | socket_t control_socket;
15 | SDL_Thread *thread;
16 | SDL_mutex *mutex;
17 | };
18 |
19 | bool
20 | receiver_init(struct receiver *receiver, socket_t control_socket);
21 |
22 | void
23 | receiver_destroy(struct receiver *receiver);
24 |
25 | bool
26 | receiver_start(struct receiver *receiver);
27 |
28 | // no receiver_stop(), it will automatically stop on control_socket shutdown
29 |
30 | void
31 | receiver_join(struct receiver *receiver);
32 |
33 | #endif
34 |
--------------------------------------------------------------------------------
/scrcpy/app/tests/test_device_msg_deserialize.c:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 |
4 | #include "device_msg.h"
5 |
6 | #include
7 | static void test_deserialize_clipboard(void) {
8 | const unsigned char input[] = {
9 | DEVICE_MSG_TYPE_CLIPBOARD,
10 | 0x00, 0x03, // text length
11 | 0x41, 0x42, 0x43, // "ABC"
12 | };
13 |
14 | struct device_msg msg;
15 | ssize_t r = device_msg_deserialize(input, sizeof(input), &msg);
16 | assert(r == 6);
17 |
18 | assert(msg.type == DEVICE_MSG_TYPE_CLIPBOARD);
19 | assert(msg.clipboard.text);
20 | assert(!strcmp("ABC", msg.clipboard.text));
21 |
22 | device_msg_destroy(&msg);
23 | }
24 |
25 | int main(void) {
26 | test_deserialize_clipboard();
27 | return 0;
28 | }
29 |
--------------------------------------------------------------------------------
/scrcpy/app/src/event_converter.h:
--------------------------------------------------------------------------------
1 | #ifndef CONVERT_H
2 | #define CONVERT_H
3 |
4 | #include
5 | #include
6 |
7 | #include "config.h"
8 | #include "control_msg.h"
9 |
10 | bool
11 | convert_keycode_action(SDL_EventType from, enum android_keyevent_action *to);
12 |
13 | enum android_metastate
14 | convert_meta_state(SDL_Keymod mod);
15 |
16 | bool
17 | convert_keycode(SDL_Keycode from, enum android_keycode *to, uint16_t mod,
18 | bool prefer_text);
19 |
20 | enum android_motionevent_buttons
21 | convert_mouse_buttons(uint32_t state);
22 |
23 | bool
24 | convert_mouse_action(SDL_EventType from, enum android_motionevent_action *to);
25 |
26 | bool
27 | convert_touch_action(SDL_EventType from, enum android_motionevent_action *to);
28 |
29 | #endif
30 |
--------------------------------------------------------------------------------
/scrcpy/config/android-checkstyle.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'checkstyle'
2 | check.dependsOn 'checkstyle'
3 |
4 | checkstyle {
5 | toolVersion = '6.19'
6 | }
7 |
8 | task checkstyle(type: Checkstyle) {
9 | description = "Check Java style with Checkstyle"
10 | configFile = rootProject.file("config/checkstyle/checkstyle.xml")
11 | source = javaSources()
12 | classpath = files()
13 | ignoreFailures = true
14 | }
15 |
16 | def javaSources() {
17 | def files = []
18 | android.sourceSets.each { sourceSet ->
19 | sourceSet.java.each { javaSource ->
20 | javaSource.getSrcDirs().each {
21 | if (it.exists()) {
22 | files.add(it)
23 | }
24 | }
25 | }
26 | }
27 | return files
28 | }
29 |
--------------------------------------------------------------------------------
/scrcpy/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 |
3 | # IDE (e.g. Android Studio) users:
4 | # Gradle settings configured through the IDE *will override*
5 | # any settings specified in this file.
6 |
7 | # For more details on how to configure your build environment visit
8 | # http://www.gradle.org/docs/current/userguide/build_environment.html
9 |
10 | # Specifies the JVM arguments used for the daemon process.
11 | # The setting is particularly useful for tweaking memory settings.
12 | org.gradle.jvmargs=-Xmx1536m
13 |
14 | # When configured, Gradle will run in incubating parallel mode.
15 | # This option should only be used with decoupled projects. More details, visit
16 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
17 | # org.gradle.parallel=true
18 |
19 |
--------------------------------------------------------------------------------
/scrcpy/server/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | android {
4 | compileSdkVersion 29
5 | defaultConfig {
6 | applicationId "com.genymobile.scrcpy"
7 | minSdkVersion 21
8 | targetSdkVersion 29
9 | versionCode 14
10 | versionName "1.12.1"
11 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
12 | }
13 | buildTypes {
14 | release {
15 | minifyEnabled false
16 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
17 | }
18 | }
19 | }
20 |
21 | dependencies {
22 | implementation fileTree(dir: 'libs', include: ['*.jar'])
23 | testImplementation 'junit:junit:4.12'
24 | }
25 |
26 | apply from: "$project.rootDir/config/android-checkstyle.gradle"
27 |
--------------------------------------------------------------------------------
/scrcpy/server/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
22 |
--------------------------------------------------------------------------------
/scrcpy/meson_options.txt:
--------------------------------------------------------------------------------
1 | option('compile_app', type: 'boolean', value: true, description: 'Build the client')
2 | option('compile_server', type: 'boolean', value: true, description: 'Build the server')
3 | option('crossbuild_windows', type: 'boolean', value: false, description: 'Build for Windows from Linux')
4 | option('windows_noconsole', type: 'boolean', value: false, description: 'Disable console on Windows (pass -mwindows flag)')
5 | option('prebuilt_server', type: 'string', description: 'Path of the prebuilt server')
6 | option('portable', type: 'boolean', value: false, description: 'Use scrcpy-server from the same directory as the scrcpy executable')
7 | option('hidpi_support', type: 'boolean', value: true, description: 'Enable High DPI support')
8 | option('server_debugger', type: 'boolean', value: false, description: 'Run a server debugger and wait for a client to be attached')
9 |
--------------------------------------------------------------------------------
/scrcpy/server/src/main/java/com/genymobile/scrcpy/StringUtils.java:
--------------------------------------------------------------------------------
1 | package com.genymobile.scrcpy;
2 |
3 | public final class StringUtils {
4 | private StringUtils() {
5 | // not instantiable
6 | }
7 |
8 | @SuppressWarnings("checkstyle:MagicNumber")
9 | public static int getUtf8TruncationIndex(byte[] utf8, int maxLength) {
10 | int len = utf8.length;
11 | if (len <= maxLength) {
12 | return len;
13 | }
14 | len = maxLength;
15 | // see UTF-8 encoding
16 | while ((utf8[len] & 0x80) != 0 && (utf8[len] & 0xc0) != 0xc0) {
17 | // the next byte is not the start of a new UTF-8 codepoint
18 | // so if we would cut there, the character would be truncated
19 | len--;
20 | }
21 | return len;
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/scrcpy/app/src/device_msg.h:
--------------------------------------------------------------------------------
1 | #ifndef DEVICEMSG_H
2 | #define DEVICEMSG_H
3 |
4 | #include
5 | #include
6 | #include
7 |
8 | #include "config.h"
9 |
10 | #define DEVICE_MSG_TEXT_MAX_LENGTH 4093
11 | #define DEVICE_MSG_SERIALIZED_MAX_SIZE (3 + DEVICE_MSG_TEXT_MAX_LENGTH)
12 |
13 | enum device_msg_type {
14 | DEVICE_MSG_TYPE_CLIPBOARD,
15 | };
16 |
17 | struct device_msg {
18 | enum device_msg_type type;
19 | union {
20 | struct {
21 | char *text; // owned, to be freed by SDL_free()
22 | } clipboard;
23 | };
24 | };
25 |
26 | // return the number of bytes consumed (0 for no msg available, -1 on error)
27 | ssize_t
28 | device_msg_deserialize(const unsigned char *buf, size_t len,
29 | struct device_msg *msg);
30 |
31 | void
32 | device_msg_destroy(struct device_msg *msg);
33 |
34 | #endif
35 |
--------------------------------------------------------------------------------
/scrcpy/app/tests/test_queue.c:
--------------------------------------------------------------------------------
1 | #include
2 |
3 | #include "util/queue.h"
4 |
5 | struct foo {
6 | int value;
7 | struct foo *next;
8 | };
9 |
10 | static void test_queue(void) {
11 | struct my_queue QUEUE(struct foo) queue;
12 | queue_init(&queue);
13 |
14 | assert(queue_is_empty(&queue));
15 |
16 | struct foo v1 = { .value = 42 };
17 | struct foo v2 = { .value = 27 };
18 |
19 | queue_push(&queue, next, &v1);
20 | queue_push(&queue, next, &v2);
21 |
22 | struct foo *foo;
23 |
24 | assert(!queue_is_empty(&queue));
25 | queue_take(&queue, next, &foo);
26 | assert(foo->value == 42);
27 |
28 | assert(!queue_is_empty(&queue));
29 | queue_take(&queue, next, &foo);
30 | assert(foo->value == 27);
31 |
32 | assert(queue_is_empty(&queue));
33 | }
34 |
35 | int main(void) {
36 | test_queue();
37 | return 0;
38 | }
39 |
--------------------------------------------------------------------------------
/scrcpy/server/scripts/build-wrapper.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | # Wrapper script to invoke gradle from meson
3 | set -e
4 |
5 | # Do not execute gradle when ninja is called as root (it would download the
6 | # whole gradle world in /root/.gradle).
7 | # This is typically useful for calling "sudo ninja install" after a "ninja
8 | # install"
9 | if [[ "$EUID" == 0 ]]
10 | then
11 | echo "(not invoking gradle, since we are root)" >&2
12 | exit 0
13 | fi
14 |
15 | PROJECT_ROOT="$1"
16 | OUTPUT="$2"
17 | BUILDTYPE="$3"
18 |
19 | # gradlew is in the parent of the server directory
20 | GRADLE=${GRADLE:-$PROJECT_ROOT/../gradlew}
21 |
22 | if [[ "$BUILDTYPE" == debug ]]
23 | then
24 | "$GRADLE" -p "$PROJECT_ROOT" assembleDebug
25 | cp "$PROJECT_ROOT/build/outputs/apk/debug/server-debug.apk" "$OUTPUT"
26 | else
27 | "$GRADLE" -p "$PROJECT_ROOT" assembleRelease
28 | cp "$PROJECT_ROOT/build/outputs/apk/release/server-release-unsigned.apk" "$OUTPUT"
29 | fi
30 |
--------------------------------------------------------------------------------
/scrcpy/server/src/main/aidl/android/view/IRotationWatcher.aidl:
--------------------------------------------------------------------------------
1 | /* //device/java/android/android/hardware/ISensorListener.aidl
2 | **
3 | ** Copyright 2008, The Android Open Source Project
4 | **
5 | ** Licensed under the Apache License, Version 2.0 (the "License");
6 | ** you may not use this file except in compliance with the License.
7 | ** You may obtain a copy of the License at
8 | **
9 | ** http://www.apache.org/licenses/LICENSE-2.0
10 | **
11 | ** Unless required by applicable law or agreed to in writing, software
12 | ** distributed under the License is distributed on an "AS IS" BASIS,
13 | ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | ** See the License for the specific language governing permissions and
15 | ** limitations under the License.
16 | */
17 |
18 | package android.view;
19 |
20 | /**
21 | * {@hide}
22 | */
23 | interface IRotationWatcher {
24 | oneway void onRotationChanged(int rotation);
25 | }
26 |
--------------------------------------------------------------------------------
/scrcpy/app/src/device.c:
--------------------------------------------------------------------------------
1 | #include "device.h"
2 |
3 | #include "config.h"
4 | #include "util/log.h"
5 |
6 | bool
7 | device_read_info(socket_t device_socket, char *device_name, struct size *size) {
8 | unsigned char buf[DEVICE_NAME_FIELD_LENGTH + 4];
9 | int r = net_recv_all(device_socket, buf, sizeof(buf));
10 | if (r < DEVICE_NAME_FIELD_LENGTH + 4) {
11 | LOGE("Could not retrieve device information");
12 | return false;
13 | }
14 | // in case the client sends garbage
15 | buf[DEVICE_NAME_FIELD_LENGTH - 1] = '\0';
16 | // strcpy is safe here, since name contains at least
17 | // DEVICE_NAME_FIELD_LENGTH bytes and strlen(buf) < DEVICE_NAME_FIELD_LENGTH
18 | strcpy(device_name, (char *) buf);
19 | size->width = (buf[DEVICE_NAME_FIELD_LENGTH] << 8)
20 | | buf[DEVICE_NAME_FIELD_LENGTH + 1];
21 | size->height = (buf[DEVICE_NAME_FIELD_LENGTH + 2] << 8)
22 | | buf[DEVICE_NAME_FIELD_LENGTH + 3];
23 | return true;
24 | }
25 |
--------------------------------------------------------------------------------
/scrcpy/server/src/main/java/com/genymobile/scrcpy/ScreenInfo.java:
--------------------------------------------------------------------------------
1 | package com.genymobile.scrcpy;
2 |
3 | import android.graphics.Rect;
4 |
5 | public final class ScreenInfo {
6 | private final Rect contentRect; // device size, possibly cropped
7 | private final Size videoSize;
8 | private final boolean rotated;
9 |
10 | public ScreenInfo(Rect contentRect, Size videoSize, boolean rotated) {
11 | this.contentRect = contentRect;
12 | this.videoSize = videoSize;
13 | this.rotated = rotated;
14 | }
15 |
16 | public Rect getContentRect() {
17 | return contentRect;
18 | }
19 |
20 | public Size getVideoSize() {
21 | return videoSize;
22 | }
23 |
24 | public ScreenInfo withRotation(int rotation) {
25 | boolean newRotated = (rotation & 1) != 0;
26 | if (rotated == newRotated) {
27 | return this;
28 | }
29 | return new ScreenInfo(Device.flipRect(contentRect), videoSize.rotate(), newRotated);
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/scrcpy/server/src/main/java/com/genymobile/scrcpy/Point.java:
--------------------------------------------------------------------------------
1 | package com.genymobile.scrcpy;
2 |
3 | import java.util.Objects;
4 |
5 | public class Point {
6 | private final int x;
7 | private final int y;
8 |
9 | public Point(int x, int y) {
10 | this.x = x;
11 | this.y = y;
12 | }
13 |
14 | public int getX() {
15 | return x;
16 | }
17 |
18 | public int getY() {
19 | return y;
20 | }
21 |
22 | @Override
23 | public boolean equals(Object o) {
24 | if (this == o) {
25 | return true;
26 | }
27 | if (o == null || getClass() != o.getClass()) {
28 | return false;
29 | }
30 | Point point = (Point) o;
31 | return x == point.x && y == point.y;
32 | }
33 |
34 | @Override
35 | public int hashCode() {
36 | return Objects.hash(x, y);
37 | }
38 |
39 | @Override
40 | public String toString() {
41 | return "Point{" + "x=" + x + ", y=" + y + '}';
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/scrcpy/app/src/stream.h:
--------------------------------------------------------------------------------
1 | #ifndef STREAM_H
2 | #define STREAM_H
3 |
4 | #include
5 | #include
6 | #include
7 | #include
8 | #include
9 |
10 | #include "config.h"
11 | #include "util/net.h"
12 |
13 | struct video_buffer;
14 |
15 | struct stream {
16 | socket_t socket;
17 | struct video_buffer *video_buffer;
18 | SDL_Thread *thread;
19 | struct decoder *decoder;
20 | struct recorder *recorder;
21 | AVCodecContext *codec_ctx;
22 | AVCodecParserContext *parser;
23 | // successive packets may need to be concatenated, until a non-config
24 | // packet is available
25 | bool has_pending;
26 | AVPacket pending;
27 | };
28 |
29 | void
30 | stream_init(struct stream *stream, socket_t socket,
31 | struct decoder *decoder, struct recorder *recorder);
32 |
33 | bool
34 | stream_start(struct stream *stream);
35 |
36 | void
37 | stream_stop(struct stream *stream);
38 |
39 | void
40 | stream_join(struct stream *stream);
41 |
42 | #endif
43 |
--------------------------------------------------------------------------------
/scrcpy/server/src/main/java/com/genymobile/scrcpy/DeviceMessageSender.java:
--------------------------------------------------------------------------------
1 | package com.genymobile.scrcpy;
2 |
3 | import java.io.IOException;
4 |
5 | public final class DeviceMessageSender {
6 |
7 | private final DesktopConnection connection;
8 |
9 | private String clipboardText;
10 |
11 | public DeviceMessageSender(DesktopConnection connection) {
12 | this.connection = connection;
13 | }
14 |
15 | public synchronized void pushClipboardText(String text) {
16 | clipboardText = text;
17 | notify();
18 | }
19 |
20 | public void loop() throws IOException, InterruptedException {
21 | while (true) {
22 | String text;
23 | synchronized (this) {
24 | while (clipboardText == null) {
25 | wait();
26 | }
27 | text = clipboardText;
28 | clipboardText = null;
29 | }
30 | DeviceMessage event = DeviceMessage.createClipboard(text);
31 | connection.sendDeviceMessage(event);
32 | }
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/scrcpy/app/src/controller.h:
--------------------------------------------------------------------------------
1 | #ifndef CONTROLLER_H
2 | #define CONTROLLER_H
3 |
4 | #include
5 | #include
6 | #include
7 |
8 | #include "config.h"
9 | #include "control_msg.h"
10 | #include "receiver.h"
11 | #include "util/cbuf.h"
12 | #include "util/net.h"
13 |
14 | struct control_msg_queue CBUF(struct control_msg, 64);
15 |
16 | struct controller {
17 | socket_t control_socket;
18 | SDL_Thread *thread;
19 | SDL_mutex *mutex;
20 | SDL_cond *msg_cond;
21 | bool stopped;
22 | struct control_msg_queue queue;
23 | struct receiver receiver;
24 | };
25 |
26 | bool
27 | controller_init(struct controller *controller, socket_t control_socket);
28 |
29 | void
30 | controller_destroy(struct controller *controller);
31 |
32 | bool
33 | controller_start(struct controller *controller);
34 |
35 | void
36 | controller_stop(struct controller *controller);
37 |
38 | void
39 | controller_join(struct controller *controller);
40 |
41 | bool
42 | controller_push_msg(struct controller *controller,
43 | const struct control_msg *msg);
44 |
45 | #endif
46 |
--------------------------------------------------------------------------------
/scrcpy/server/src/main/java/com/genymobile/scrcpy/wrappers/DisplayManager.java:
--------------------------------------------------------------------------------
1 | package com.genymobile.scrcpy.wrappers;
2 |
3 | import com.genymobile.scrcpy.DisplayInfo;
4 | import com.genymobile.scrcpy.Size;
5 |
6 | import android.os.IInterface;
7 |
8 | public final class DisplayManager {
9 | private final IInterface manager;
10 |
11 | public DisplayManager(IInterface manager) {
12 | this.manager = manager;
13 | }
14 |
15 | public DisplayInfo getDisplayInfo() {
16 | try {
17 | Object displayInfo = manager.getClass().getMethod("getDisplayInfo", int.class).invoke(manager, 0);
18 | Class> cls = displayInfo.getClass();
19 | // width and height already take the rotation into account
20 | int width = cls.getDeclaredField("logicalWidth").getInt(displayInfo);
21 | int height = cls.getDeclaredField("logicalHeight").getInt(displayInfo);
22 | int rotation = cls.getDeclaredField("rotation").getInt(displayInfo);
23 | return new DisplayInfo(new Size(width, height), rotation);
24 | } catch (Exception e) {
25 | throw new AssertionError(e);
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/scrcpy/server/meson.build:
--------------------------------------------------------------------------------
1 | # It may be useful to use a prebuilt server, so that no Android SDK is required
2 | # to build. If the 'prebuilt_server' option is set, just copy the file as is.
3 | prebuilt_server = get_option('prebuilt_server')
4 | if prebuilt_server == ''
5 | custom_target('scrcpy-server',
6 | build_always: true, # gradle is responsible for tracking source changes
7 | output: 'scrcpy-server',
8 | command: [find_program('./scripts/build-wrapper.sh'), meson.current_source_dir(), '@OUTPUT@', get_option('buildtype')],
9 | console: true,
10 | install: true,
11 | install_dir: 'share/scrcpy')
12 | else
13 | if not prebuilt_server.startswith('/')
14 | # relative path needs some trick
15 | prebuilt_server = meson.source_root() + '/' + prebuilt_server
16 | endif
17 | custom_target('scrcpy-server-prebuilt',
18 | input: prebuilt_server,
19 | output: 'scrcpy-server',
20 | command: ['cp', '@INPUT@', '@OUTPUT@'],
21 | install: true,
22 | install_dir: 'share/scrcpy')
23 | endif
24 |
--------------------------------------------------------------------------------
/scrcpy/app/src/util/buffer_util.h:
--------------------------------------------------------------------------------
1 | #ifndef BUFFER_UTIL_H
2 | #define BUFFER_UTIL_H
3 |
4 | #include
5 | #include
6 |
7 | #include "config.h"
8 |
9 | static inline void
10 | buffer_write16be(uint8_t *buf, uint16_t value) {
11 | buf[0] = value >> 8;
12 | buf[1] = value;
13 | }
14 |
15 | static inline void
16 | buffer_write32be(uint8_t *buf, uint32_t value) {
17 | buf[0] = value >> 24;
18 | buf[1] = value >> 16;
19 | buf[2] = value >> 8;
20 | buf[3] = value;
21 | }
22 |
23 | static inline void
24 | buffer_write64be(uint8_t *buf, uint64_t value) {
25 | buffer_write32be(buf, value >> 32);
26 | buffer_write32be(&buf[4], (uint32_t) value);
27 | }
28 |
29 | static inline uint16_t
30 | buffer_read16be(const uint8_t *buf) {
31 | return (buf[0] << 8) | buf[1];
32 | }
33 |
34 | static inline uint32_t
35 | buffer_read32be(const uint8_t *buf) {
36 | return (buf[0] << 24) | (buf[1] << 16) | (buf[2] << 8) | buf[3];
37 | }
38 |
39 | static inline uint64_t
40 | buffer_read64be(const uint8_t *buf) {
41 | uint32_t msb = buffer_read32be(buf);
42 | uint32_t lsb = buffer_read32be(&buf[4]);
43 | return ((uint64_t) msb << 32) | lsb;
44 | }
45 |
46 | #endif
47 |
--------------------------------------------------------------------------------
/scrcpy/server/src/test/java/com/genymobile/scrcpy/DeviceMessageWriterTest.java:
--------------------------------------------------------------------------------
1 | package com.genymobile.scrcpy;
2 |
3 | import org.junit.Assert;
4 | import org.junit.Test;
5 |
6 | import java.io.ByteArrayOutputStream;
7 | import java.io.DataOutputStream;
8 | import java.io.IOException;
9 | import java.nio.charset.StandardCharsets;
10 |
11 | public class DeviceMessageWriterTest {
12 |
13 | @Test
14 | public void testSerializeClipboard() throws IOException {
15 | DeviceMessageWriter writer = new DeviceMessageWriter();
16 |
17 | String text = "aéûoç";
18 | byte[] data = text.getBytes(StandardCharsets.UTF_8);
19 | ByteArrayOutputStream bos = new ByteArrayOutputStream();
20 | DataOutputStream dos = new DataOutputStream(bos);
21 | dos.writeByte(DeviceMessage.TYPE_CLIPBOARD);
22 | dos.writeShort(data.length);
23 | dos.write(data);
24 |
25 | byte[] expected = bos.toByteArray();
26 |
27 | DeviceMessage msg = DeviceMessage.createClipboard(text);
28 | bos = new ByteArrayOutputStream();
29 | writer.writeTo(msg, bos);
30 |
31 | byte[] actual = bos.toByteArray();
32 |
33 | Assert.assertArrayEquals(expected, actual);
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/scrcpy/prebuilt-deps/prepare-dep:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -e
3 | url="$1"
4 | sum="$2"
5 | dir="$3"
6 |
7 | checksum() {
8 | local file="$1"
9 | local sum="$2"
10 | echo "$file: verifying checksum..."
11 | echo "$sum $file" | sha256sum -c
12 | }
13 |
14 | get_file() {
15 | local url="$1"
16 | local file="$2"
17 | local sum="$3"
18 | if [[ -f "$file" ]]
19 | then
20 | echo "$file: found"
21 | else
22 | echo "$file: not found, downloading..."
23 | wget "$url" -O "$file"
24 | fi
25 | checksum "$file" "$sum"
26 | }
27 |
28 | extract() {
29 | local file="$1"
30 | echo "Extracting $file..."
31 | if [[ "$file" == *.zip ]]
32 | then
33 | unzip -q "$file"
34 | elif [[ "$file" == *.tar.gz ]]
35 | then
36 | tar xf "$file"
37 | else
38 | echo "Unsupported file: $file"
39 | return 1
40 | fi
41 | }
42 |
43 | get_dep() {
44 | local url="$1"
45 | local sum="$2"
46 | local dir="$3"
47 | local file="${url##*/}"
48 | if [[ -d "$dir" ]]
49 | then
50 | echo "$dir: found"
51 | else
52 | echo "$dir: not found"
53 | get_file "$url" "$file" "$sum"
54 | extract "$file"
55 | fi
56 | }
57 |
58 | get_dep "$url" "$sum" "$dir"
59 |
--------------------------------------------------------------------------------
/scrcpy/release.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -e
3 |
4 | # test locally
5 | TESTDIR=build_test
6 | rm -rf "$TESTDIR"
7 | # run client tests with ASAN enabled
8 | meson "$TESTDIR" -Db_sanitize=address
9 | ninja -C"$TESTDIR" test
10 |
11 | # test server
12 | GRADLE=${GRADLE:-./gradlew}
13 | $GRADLE -p server check
14 |
15 | BUILDDIR=build_release
16 | rm -rf "$BUILDDIR"
17 | meson "$BUILDDIR" --buildtype release --strip -Db_lto=true
18 | cd "$BUILDDIR"
19 | ninja
20 | cd -
21 |
22 | # build Windows releases
23 | make -f Makefile.CrossWindows
24 |
25 | # the generated server must be the same everywhere
26 | cmp "$BUILDDIR/server/scrcpy-server" dist/scrcpy-win32/scrcpy-server
27 | cmp "$BUILDDIR/server/scrcpy-server" dist/scrcpy-win64/scrcpy-server
28 |
29 | # get version name
30 | TAG=$(git describe --tags --always)
31 |
32 | # create release directory
33 | mkdir -p "release-$TAG"
34 | cp "$BUILDDIR/server/scrcpy-server" "release-$TAG/scrcpy-server-$TAG"
35 | cp "dist/scrcpy-win32-$TAG.zip" "release-$TAG/"
36 | cp "dist/scrcpy-win64-$TAG.zip" "release-$TAG/"
37 |
38 | # generate checksums
39 | cd "release-$TAG"
40 | sha256sum "scrcpy-server-$TAG" \
41 | "scrcpy-win32-$TAG.zip" \
42 | "scrcpy-win64-$TAG.zip" > SHA256SUMS.txt
43 |
44 | echo "Release generated in release-$TAG/"
45 |
--------------------------------------------------------------------------------
/scrcpy/app/src/util/net.h:
--------------------------------------------------------------------------------
1 | #ifndef NET_H
2 | #define NET_H
3 |
4 | #include
5 | #include
6 | #include
7 |
8 | #ifdef __WINDOWS__
9 | # include
10 | #define SHUT_RD SD_RECEIVE
11 | #define SHUT_WR SD_SEND
12 | #define SHUT_RDWR SD_BOTH
13 | typedef SOCKET socket_t;
14 | #else
15 | # include
16 | # define INVALID_SOCKET -1
17 | typedef int socket_t;
18 | #endif
19 |
20 | #include "config.h"
21 |
22 | bool
23 | net_init(void);
24 |
25 | void
26 | net_cleanup(void);
27 |
28 | socket_t
29 | net_connect(uint32_t addr, uint16_t port);
30 |
31 | socket_t
32 | net_listen(uint32_t addr, uint16_t port, int backlog);
33 |
34 | socket_t
35 | net_accept(socket_t server_socket);
36 |
37 | // the _all versions wait/retry until len bytes have been written/read
38 | ssize_t
39 | net_recv(socket_t socket, void *buf, size_t len);
40 |
41 | ssize_t
42 | net_recv_all(socket_t socket, void *buf, size_t len);
43 |
44 | ssize_t
45 | net_send(socket_t socket, const void *buf, size_t len);
46 |
47 | ssize_t
48 | net_send_all(socket_t socket, const void *buf, size_t len);
49 |
50 | // how is SHUT_RD (read), SHUT_WR (write) or SHUT_RDWR (both)
51 | bool
52 | net_shutdown(socket_t socket, int how);
53 |
54 | bool
55 | net_close(socket_t socket);
56 |
57 | #endif
58 |
--------------------------------------------------------------------------------
/scrcpy/server/src/main/java/com/genymobile/scrcpy/Pointer.java:
--------------------------------------------------------------------------------
1 | package com.genymobile.scrcpy;
2 |
3 | public class Pointer {
4 |
5 | /**
6 | * Pointer id as received from the client.
7 | */
8 | private final long id;
9 |
10 | /**
11 | * Local pointer id, using the lowest possible values to fill the {@link android.view.MotionEvent.PointerProperties PointerProperties}.
12 | */
13 | private final int localId;
14 |
15 | private Point point;
16 | private float pressure;
17 | private boolean up;
18 |
19 | public Pointer(long id, int localId) {
20 | this.id = id;
21 | this.localId = localId;
22 | }
23 |
24 | public long getId() {
25 | return id;
26 | }
27 |
28 | public int getLocalId() {
29 | return localId;
30 | }
31 |
32 | public Point getPoint() {
33 | return point;
34 | }
35 |
36 | public void setPoint(Point point) {
37 | this.point = point;
38 | }
39 |
40 | public float getPressure() {
41 | return pressure;
42 | }
43 |
44 | public void setPressure(float pressure) {
45 | this.pressure = pressure;
46 | }
47 |
48 | public boolean isUp() {
49 | return up;
50 | }
51 |
52 | public void setUp(boolean up) {
53 | this.up = up;
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/scrcpy/server/src/main/java/com/genymobile/scrcpy/Size.java:
--------------------------------------------------------------------------------
1 | package com.genymobile.scrcpy;
2 |
3 | import android.graphics.Rect;
4 |
5 | import java.util.Objects;
6 |
7 | public final class Size {
8 | private final int width;
9 | private final int height;
10 |
11 | public Size(int width, int height) {
12 | this.width = width;
13 | this.height = height;
14 | }
15 |
16 | public int getWidth() {
17 | return width;
18 | }
19 |
20 | public int getHeight() {
21 | return height;
22 | }
23 |
24 | public Size rotate() {
25 | return new Size(height, width);
26 | }
27 |
28 | public Rect toRect() {
29 | return new Rect(0, 0, width, height);
30 | }
31 |
32 | @Override
33 | public boolean equals(Object o) {
34 | if (this == o) {
35 | return true;
36 | }
37 | if (o == null || getClass() != o.getClass()) {
38 | return false;
39 | }
40 | Size size = (Size) o;
41 | return width == size.width && height == size.height;
42 | }
43 |
44 | @Override
45 | public int hashCode() {
46 | return Objects.hash(width, height);
47 | }
48 |
49 | @Override
50 | public String toString() {
51 | return "Size{" + "width=" + width + ", height=" + height + '}';
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/scrcpy/server/src/main/java/com/genymobile/scrcpy/Position.java:
--------------------------------------------------------------------------------
1 | package com.genymobile.scrcpy;
2 |
3 | import java.util.Objects;
4 |
5 | public class Position {
6 | private Point point;
7 | private Size screenSize;
8 |
9 | public Position(Point point, Size screenSize) {
10 | this.point = point;
11 | this.screenSize = screenSize;
12 | }
13 |
14 | public Position(int x, int y, int screenWidth, int screenHeight) {
15 | this(new Point(x, y), new Size(screenWidth, screenHeight));
16 | }
17 |
18 | public Point getPoint() {
19 | return point;
20 | }
21 |
22 | public Size getScreenSize() {
23 | return screenSize;
24 | }
25 |
26 | @Override
27 | public boolean equals(Object o) {
28 | if (this == o) {
29 | return true;
30 | }
31 | if (o == null || getClass() != o.getClass()) {
32 | return false;
33 | }
34 | Position position = (Position) o;
35 | return Objects.equals(point, position.point) && Objects.equals(screenSize, position.screenSize);
36 | }
37 |
38 | @Override
39 | public int hashCode() {
40 | return Objects.hash(point, screenSize);
41 | }
42 |
43 | @Override
44 | public String toString() {
45 | return "Position{" + "point=" + point + ", screenSize=" + screenSize + '}';
46 | }
47 |
48 | }
49 |
--------------------------------------------------------------------------------
/scrcpy/app/src/input_manager.h:
--------------------------------------------------------------------------------
1 | #ifndef INPUTMANAGER_H
2 | #define INPUTMANAGER_H
3 |
4 | #include
5 |
6 | #include "config.h"
7 | #include "common.h"
8 | #include "controller.h"
9 | #include "fps_counter.h"
10 | #include "video_buffer.h"
11 | #include "screen.h"
12 |
13 | struct input_manager {
14 | struct controller *controller;
15 | struct video_buffer *video_buffer;
16 | struct screen *screen;
17 | bool prefer_text;
18 | };
19 |
20 | void
21 | input_manager_process_text_input(struct input_manager *im,
22 | const SDL_TextInputEvent *event);
23 |
24 | void
25 | input_manager_process_key(struct input_manager *im,
26 | const SDL_KeyboardEvent *event,
27 | bool control);
28 |
29 | void
30 | input_manager_process_mouse_motion(struct input_manager *im,
31 | const SDL_MouseMotionEvent *event);
32 |
33 | void
34 | input_manager_process_touch(struct input_manager *im,
35 | const SDL_TouchFingerEvent *event);
36 |
37 | void
38 | input_manager_process_mouse_button(struct input_manager *im,
39 | const SDL_MouseButtonEvent *event,
40 | bool control);
41 |
42 | void
43 | input_manager_process_mouse_wheel(struct input_manager *im,
44 | const SDL_MouseWheelEvent *event);
45 |
46 | #endif
47 |
--------------------------------------------------------------------------------
/scrcpy/server/src/main/java/com/genymobile/scrcpy/DeviceMessageWriter.java:
--------------------------------------------------------------------------------
1 | package com.genymobile.scrcpy;
2 |
3 | import java.io.IOException;
4 | import java.io.OutputStream;
5 | import java.nio.ByteBuffer;
6 | import java.nio.charset.StandardCharsets;
7 |
8 | public class DeviceMessageWriter {
9 |
10 | public static final int CLIPBOARD_TEXT_MAX_LENGTH = 4093;
11 | private static final int MAX_EVENT_SIZE = CLIPBOARD_TEXT_MAX_LENGTH + 3;
12 |
13 | private final byte[] rawBuffer = new byte[MAX_EVENT_SIZE];
14 | private final ByteBuffer buffer = ByteBuffer.wrap(rawBuffer);
15 |
16 | @SuppressWarnings("checkstyle:MagicNumber")
17 | public void writeTo(DeviceMessage msg, OutputStream output) throws IOException {
18 | buffer.clear();
19 | buffer.put((byte) DeviceMessage.TYPE_CLIPBOARD);
20 | switch (msg.getType()) {
21 | case DeviceMessage.TYPE_CLIPBOARD:
22 | String text = msg.getText();
23 | byte[] raw = text.getBytes(StandardCharsets.UTF_8);
24 | int len = StringUtils.getUtf8TruncationIndex(raw, CLIPBOARD_TEXT_MAX_LENGTH);
25 | buffer.putShort((short) len);
26 | buffer.put(raw, 0, len);
27 | output.write(rawBuffer, 0, buffer.position());
28 | break;
29 | default:
30 | Ln.w("Unknown device message: " + msg.getType());
31 | break;
32 | }
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/scrcpy/server/src/main/java/com/genymobile/scrcpy/wrappers/PowerManager.java:
--------------------------------------------------------------------------------
1 | package com.genymobile.scrcpy.wrappers;
2 |
3 | import com.genymobile.scrcpy.Ln;
4 |
5 | import android.annotation.SuppressLint;
6 | import android.os.Build;
7 | import android.os.IInterface;
8 |
9 | import java.lang.reflect.InvocationTargetException;
10 | import java.lang.reflect.Method;
11 |
12 | public final class PowerManager {
13 | private final IInterface manager;
14 | private Method isScreenOnMethod;
15 |
16 | public PowerManager(IInterface manager) {
17 | this.manager = manager;
18 | }
19 |
20 | private Method getIsScreenOnMethod() throws NoSuchMethodException {
21 | if (isScreenOnMethod == null) {
22 | @SuppressLint("ObsoleteSdkInt") // we may lower minSdkVersion in the future
23 | String methodName = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT_WATCH ? "isInteractive" : "isScreenOn";
24 | isScreenOnMethod = manager.getClass().getMethod(methodName);
25 | }
26 | return isScreenOnMethod;
27 | }
28 |
29 | public boolean isScreenOn() {
30 | try {
31 | Method method = getIsScreenOnMethod();
32 | return (boolean) method.invoke(manager);
33 | } catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException e) {
34 | Ln.e("Could not invoke method", e);
35 | return false;
36 | }
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/scrcpy/app/src/fps_counter.h:
--------------------------------------------------------------------------------
1 | #ifndef FPSCOUNTER_H
2 | #define FPSCOUNTER_H
3 |
4 | #include
5 | #include
6 | #include
7 | #include
8 | #include
9 |
10 | #include "config.h"
11 |
12 | struct fps_counter {
13 | SDL_Thread *thread;
14 | SDL_mutex *mutex;
15 | SDL_cond *state_cond;
16 |
17 | // atomic so that we can check without locking the mutex
18 | // if the FPS counter is disabled, we don't want to lock unnecessarily
19 | SDL_atomic_t started;
20 |
21 | // the following fields are protected by the mutex
22 | bool interrupted;
23 | unsigned nr_rendered;
24 | unsigned nr_skipped;
25 | uint32_t next_timestamp;
26 | };
27 |
28 | bool
29 | fps_counter_init(struct fps_counter *counter);
30 |
31 | void
32 | fps_counter_destroy(struct fps_counter *counter);
33 |
34 | bool
35 | fps_counter_start(struct fps_counter *counter);
36 |
37 | void
38 | fps_counter_stop(struct fps_counter *counter);
39 |
40 | bool
41 | fps_counter_is_started(struct fps_counter *counter);
42 |
43 | // request to stop the thread (on quit)
44 | // must be called before fps_counter_join()
45 | void
46 | fps_counter_interrupt(struct fps_counter *counter);
47 |
48 | void
49 | fps_counter_join(struct fps_counter *counter);
50 |
51 | void
52 | fps_counter_add_rendered_frame(struct fps_counter *counter);
53 |
54 | void
55 | fps_counter_add_skipped_frame(struct fps_counter *counter);
56 |
57 | #endif
58 |
--------------------------------------------------------------------------------
/scrcpy/app/src/util/cbuf.h:
--------------------------------------------------------------------------------
1 | // generic circular buffer (bounded queue) implementation
2 | #ifndef CBUF_H
3 | #define CBUF_H
4 |
5 | #include
6 | #include
7 |
8 | #include "config.h"
9 |
10 | // To define a circular buffer type of 20 ints:
11 | // struct cbuf_int CBUF(int, 20);
12 | //
13 | // data has length CAP + 1 to distinguish empty vs full.
14 | #define CBUF(TYPE, CAP) { \
15 | TYPE data[(CAP) + 1]; \
16 | size_t head; \
17 | size_t tail; \
18 | }
19 |
20 | #define cbuf_size_(PCBUF) \
21 | (sizeof((PCBUF)->data) / sizeof(*(PCBUF)->data))
22 |
23 | #define cbuf_is_empty(PCBUF) \
24 | ((PCBUF)->head == (PCBUF)->tail)
25 |
26 | #define cbuf_is_full(PCBUF) \
27 | (((PCBUF)->head + 1) % cbuf_size_(PCBUF) == (PCBUF)->tail)
28 |
29 | #define cbuf_init(PCBUF) \
30 | (void) ((PCBUF)->head = (PCBUF)->tail = 0)
31 |
32 | #define cbuf_push(PCBUF, ITEM) \
33 | ({ \
34 | bool ok = !cbuf_is_full(PCBUF); \
35 | if (ok) { \
36 | (PCBUF)->data[(PCBUF)->head] = (ITEM); \
37 | (PCBUF)->head = ((PCBUF)->head + 1) % cbuf_size_(PCBUF); \
38 | } \
39 | ok; \
40 | })
41 |
42 | #define cbuf_take(PCBUF, PITEM) \
43 | ({ \
44 | bool ok = !cbuf_is_empty(PCBUF); \
45 | if (ok) { \
46 | *(PITEM) = (PCBUF)->data[(PCBUF)->tail]; \
47 | (PCBUF)->tail = ((PCBUF)->tail + 1) % cbuf_size_(PCBUF); \
48 | } \
49 | ok; \
50 | })
51 |
52 | #endif
53 |
--------------------------------------------------------------------------------
/scrcpy/server/src/test/java/com/genymobile/scrcpy/StringUtilsTest.java:
--------------------------------------------------------------------------------
1 | package com.genymobile.scrcpy;
2 |
3 | import org.junit.Assert;
4 | import org.junit.Test;
5 |
6 | import java.nio.charset.StandardCharsets;
7 |
8 | public class StringUtilsTest {
9 |
10 | @Test
11 | @SuppressWarnings("checkstyle:MagicNumber")
12 | public void testUtf8Truncate() {
13 | String s = "aÉbÔc";
14 | byte[] utf8 = s.getBytes(StandardCharsets.UTF_8);
15 | Assert.assertEquals(7, utf8.length);
16 |
17 | int count;
18 |
19 | count = StringUtils.getUtf8TruncationIndex(utf8, 1);
20 | Assert.assertEquals(1, count);
21 |
22 | count = StringUtils.getUtf8TruncationIndex(utf8, 2);
23 | Assert.assertEquals(1, count); // É is 2 bytes-wide
24 |
25 | count = StringUtils.getUtf8TruncationIndex(utf8, 3);
26 | Assert.assertEquals(3, count);
27 |
28 | count = StringUtils.getUtf8TruncationIndex(utf8, 4);
29 | Assert.assertEquals(4, count);
30 |
31 | count = StringUtils.getUtf8TruncationIndex(utf8, 5);
32 | Assert.assertEquals(4, count); // Ô is 2 bytes-wide
33 |
34 | count = StringUtils.getUtf8TruncationIndex(utf8, 6);
35 | Assert.assertEquals(6, count);
36 |
37 | count = StringUtils.getUtf8TruncationIndex(utf8, 7);
38 | Assert.assertEquals(7, count);
39 |
40 | count = StringUtils.getUtf8TruncationIndex(utf8, 8);
41 | Assert.assertEquals(7, count); // no more chars
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/scrcpy/app/src/device_msg.c:
--------------------------------------------------------------------------------
1 | #include "device_msg.h"
2 |
3 | #include
4 |
5 | #include "config.h"
6 | #include "util/buffer_util.h"
7 | #include "util/log.h"
8 |
9 | ssize_t
10 | device_msg_deserialize(const unsigned char *buf, size_t len,
11 | struct device_msg *msg) {
12 | if (len < 3) {
13 | // at least type + empty string length
14 | return 0; // not available
15 | }
16 |
17 | msg->type = buf[0];
18 | switch (msg->type) {
19 | case DEVICE_MSG_TYPE_CLIPBOARD: {
20 | uint16_t clipboard_len = buffer_read16be(&buf[1]);
21 | if (clipboard_len > len - 3) {
22 | return 0; // not available
23 | }
24 | char *text = SDL_malloc(clipboard_len + 1);
25 | if (!text) {
26 | LOGW("Could not allocate text for clipboard");
27 | return -1;
28 | }
29 | if (clipboard_len) {
30 | memcpy(text, &buf[3], clipboard_len);
31 | }
32 | text[clipboard_len] = '\0';
33 |
34 | msg->clipboard.text = text;
35 | return 3 + clipboard_len;
36 | }
37 | default:
38 | LOGW("Unknown device message type: %d", (int) msg->type);
39 | return -1; // error, we cannot recover
40 | }
41 | }
42 |
43 | void
44 | device_msg_destroy(struct device_msg *msg) {
45 | if (msg->type == DEVICE_MSG_TYPE_CLIPBOARD) {
46 | SDL_free(msg->clipboard.text);
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/scrcpy/app/src/file_handler.h:
--------------------------------------------------------------------------------
1 | #ifndef FILE_HANDLER_H
2 | #define FILE_HANDLER_H
3 |
4 | #include
5 | #include
6 | #include
7 |
8 | #include "config.h"
9 | #include "command.h"
10 | #include "util/cbuf.h"
11 |
12 | typedef enum {
13 | ACTION_INSTALL_APK,
14 | ACTION_PUSH_FILE,
15 | } file_handler_action_t;
16 |
17 | struct file_handler_request {
18 | file_handler_action_t action;
19 | char *file;
20 | };
21 |
22 | struct file_handler_request_queue CBUF(struct file_handler_request, 16);
23 |
24 | struct file_handler {
25 | char *serial;
26 | const char *push_target;
27 | SDL_Thread *thread;
28 | SDL_mutex *mutex;
29 | SDL_cond *event_cond;
30 | bool stopped;
31 | bool initialized;
32 | process_t current_process;
33 | struct file_handler_request_queue queue;
34 | };
35 |
36 | bool
37 | file_handler_init(struct file_handler *file_handler, const char *serial,
38 | const char *push_target);
39 |
40 | void
41 | file_handler_destroy(struct file_handler *file_handler);
42 |
43 | bool
44 | file_handler_start(struct file_handler *file_handler);
45 |
46 | void
47 | file_handler_stop(struct file_handler *file_handler);
48 |
49 | void
50 | file_handler_join(struct file_handler *file_handler);
51 |
52 | // take ownership of file, and will SDL_free() it
53 | bool
54 | file_handler_request(struct file_handler *file_handler,
55 | file_handler_action_t action,
56 | char *file);
57 |
58 | #endif
59 |
--------------------------------------------------------------------------------
/scrcpy/server/src/main/java/com/genymobile/scrcpy/wrappers/InputManager.java:
--------------------------------------------------------------------------------
1 | package com.genymobile.scrcpy.wrappers;
2 |
3 | import com.genymobile.scrcpy.Ln;
4 |
5 | import android.os.IInterface;
6 | import android.view.InputEvent;
7 |
8 | import java.lang.reflect.InvocationTargetException;
9 | import java.lang.reflect.Method;
10 |
11 | public final class InputManager {
12 |
13 | public static final int INJECT_INPUT_EVENT_MODE_ASYNC = 0;
14 | public static final int INJECT_INPUT_EVENT_MODE_WAIT_FOR_RESULT = 1;
15 | public static final int INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH = 2;
16 |
17 | private final IInterface manager;
18 | private Method injectInputEventMethod;
19 |
20 | public InputManager(IInterface manager) {
21 | this.manager = manager;
22 | }
23 |
24 | private Method getInjectInputEventMethod() throws NoSuchMethodException {
25 | if (injectInputEventMethod == null) {
26 | injectInputEventMethod = manager.getClass().getMethod("injectInputEvent", InputEvent.class, int.class);
27 | }
28 | return injectInputEventMethod;
29 | }
30 |
31 | public boolean injectInputEvent(InputEvent inputEvent, int mode) {
32 | try {
33 | Method method = getInjectInputEventMethod();
34 | return (boolean) method.invoke(manager, inputEvent, mode);
35 | } catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException e) {
36 | Ln.e("Could not invoke method", e);
37 | return false;
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/scrcpy/app/src/video_buffer.h:
--------------------------------------------------------------------------------
1 | #ifndef VIDEO_BUFFER_H
2 | #define VIDEO_BUFFER_H
3 |
4 | #include
5 | #include
6 |
7 | #include "config.h"
8 | #include "fps_counter.h"
9 |
10 | // forward declarations
11 | typedef struct AVFrame AVFrame;
12 |
13 | struct video_buffer {
14 | AVFrame *decoding_frame;
15 | AVFrame *rendering_frame;
16 | SDL_mutex *mutex;
17 | bool render_expired_frames;
18 | bool interrupted;
19 | SDL_cond *rendering_frame_consumed_cond;
20 | bool rendering_frame_consumed;
21 | struct fps_counter *fps_counter;
22 | };
23 |
24 | bool
25 | video_buffer_init(struct video_buffer *vb, struct fps_counter *fps_counter,
26 | bool render_expired_frames);
27 |
28 | void
29 | video_buffer_destroy(struct video_buffer *vb);
30 |
31 | // set the decoded frame as ready for rendering
32 | // this function locks frames->mutex during its execution
33 | // the output flag is set to report whether the previous frame has been skipped
34 | void
35 | video_buffer_offer_decoded_frame(struct video_buffer *vb,
36 | bool *previous_frame_skipped);
37 |
38 | // mark the rendering frame as consumed and return it
39 | // MUST be called with frames->mutex locked!!!
40 | // the caller is expected to render the returned frame to some texture before
41 | // unlocking frames->mutex
42 | const AVFrame *
43 | video_buffer_consume_rendered_frame(struct video_buffer *vb);
44 |
45 | // wake up and avoid any blocking call
46 | void
47 | video_buffer_interrupt(struct video_buffer *vb);
48 |
49 | #endif
50 |
--------------------------------------------------------------------------------
/scrcpy/server/src/main/java/com/genymobile/scrcpy/IO.java:
--------------------------------------------------------------------------------
1 | package com.genymobile.scrcpy;
2 |
3 | import android.system.ErrnoException;
4 | import android.system.Os;
5 | import android.system.OsConstants;
6 |
7 | import java.io.FileDescriptor;
8 | import java.io.IOException;
9 | import java.nio.ByteBuffer;
10 |
11 | public final class IO {
12 | private IO() {
13 | // not instantiable
14 | }
15 |
16 | public static void writeFully(FileDescriptor fd, ByteBuffer from) throws IOException {
17 | // ByteBuffer position is not updated as expected by Os.write() on old Android versions, so
18 | // count the remaining bytes manually.
19 | // See .
20 | int remaining = from.remaining();
21 | while (remaining > 0) {
22 | try {
23 | int w = Os.write(fd, from);
24 | if (BuildConfig.DEBUG && w < 0) {
25 | // w should not be negative, since an exception is thrown on error
26 | throw new AssertionError("Os.write() returned a negative value (" + w + ")");
27 | }
28 | remaining -= w;
29 | } catch (ErrnoException e) {
30 | if (e.errno != OsConstants.EINTR) {
31 | throw new IOException(e);
32 | }
33 | }
34 | }
35 | }
36 |
37 | public static void writeFully(FileDescriptor fd, byte[] buffer, int offset, int len) throws IOException {
38 | writeFully(fd, ByteBuffer.wrap(buffer, offset, len));
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/scrcpy/app/src/util/lock.h:
--------------------------------------------------------------------------------
1 | #ifndef LOCK_H
2 | #define LOCK_H
3 |
4 | #include
5 | #include
6 |
7 | #include "config.h"
8 | #include "log.h"
9 |
10 | static inline void
11 | mutex_lock(SDL_mutex *mutex) {
12 | int r = SDL_LockMutex(mutex);
13 | #ifndef NDEBUG
14 | if (r) {
15 | LOGC("Could not lock mutex: %s", SDL_GetError());
16 | abort();
17 | }
18 | #else
19 | (void) r;
20 | #endif
21 | }
22 |
23 | static inline void
24 | mutex_unlock(SDL_mutex *mutex) {
25 | int r = SDL_UnlockMutex(mutex);
26 | #ifndef NDEBUG
27 | if (r) {
28 | LOGC("Could not unlock mutex: %s", SDL_GetError());
29 | abort();
30 | }
31 | #else
32 | (void) r;
33 | #endif
34 | }
35 |
36 | static inline void
37 | cond_wait(SDL_cond *cond, SDL_mutex *mutex) {
38 | int r = SDL_CondWait(cond, mutex);
39 | #ifndef NDEBUG
40 | if (r) {
41 | LOGC("Could not wait on condition: %s", SDL_GetError());
42 | abort();
43 | }
44 | #else
45 | (void) r;
46 | #endif
47 | }
48 |
49 | static inline int
50 | cond_wait_timeout(SDL_cond *cond, SDL_mutex *mutex, uint32_t ms) {
51 | int r = SDL_CondWaitTimeout(cond, mutex, ms);
52 | #ifndef NDEBUG
53 | if (r < 0) {
54 | LOGC("Could not wait on condition with timeout: %s", SDL_GetError());
55 | abort();
56 | }
57 | #endif
58 | return r;
59 | }
60 |
61 | static inline void
62 | cond_signal(SDL_cond *cond) {
63 | int r = SDL_CondSignal(cond);
64 | #ifndef NDEBUG
65 | if (r) {
66 | LOGC("Could not signal a condition: %s", SDL_GetError());
67 | abort();
68 | }
69 | #else
70 | (void) r;
71 | #endif
72 | }
73 |
74 | #endif
75 |
--------------------------------------------------------------------------------
/scrcpy/app/src/server.h:
--------------------------------------------------------------------------------
1 | #ifndef SERVER_H
2 | #define SERVER_H
3 |
4 | #include
5 | #include
6 |
7 | #include "config.h"
8 | #include "command.h"
9 | #include "util/net.h"
10 |
11 | struct server {
12 | char *serial;
13 | process_t process;
14 | socket_t server_socket; // only used if !tunnel_forward
15 | socket_t video_socket;
16 | socket_t control_socket;
17 | uint16_t local_port;
18 | bool tunnel_enabled;
19 | bool tunnel_forward; // use "adb forward" instead of "adb reverse"
20 | };
21 |
22 | #define SERVER_INITIALIZER { \
23 | .serial = NULL, \
24 | .process = PROCESS_NONE, \
25 | .server_socket = INVALID_SOCKET, \
26 | .video_socket = INVALID_SOCKET, \
27 | .control_socket = INVALID_SOCKET, \
28 | .local_port = 0, \
29 | .tunnel_enabled = false, \
30 | .tunnel_forward = false, \
31 | }
32 |
33 | struct server_params {
34 | const char *crop;
35 | uint16_t local_port;
36 | uint16_t max_size;
37 | uint32_t bit_rate;
38 | uint16_t max_fps;
39 | bool control;
40 | };
41 |
42 | // init default values
43 | void
44 | server_init(struct server *server);
45 |
46 | // push, enable tunnel et start the server
47 | bool
48 | server_start(struct server *server, const char *serial,
49 | const struct server_params *params);
50 |
51 | // block until the communication with the server is established
52 | bool
53 | server_connect_to(struct server *server);
54 |
55 | // disconnect and kill the server process
56 | void
57 | server_stop(struct server *server);
58 |
59 | // close and release sockets
60 | void
61 | server_destroy(struct server *server);
62 |
63 | #endif
64 |
--------------------------------------------------------------------------------
/scrcpy/server/src/main/java/com/genymobile/scrcpy/Options.java:
--------------------------------------------------------------------------------
1 | package com.genymobile.scrcpy;
2 |
3 | import android.graphics.Rect;
4 |
5 | public class Options {
6 | private int maxSize;
7 | private int bitRate;
8 | private int maxFps;
9 | private boolean tunnelForward;
10 | private Rect crop;
11 | private boolean sendFrameMeta; // send PTS so that the client may record properly
12 | private boolean control;
13 |
14 | public int getMaxSize() {
15 | return maxSize;
16 | }
17 |
18 | public void setMaxSize(int maxSize) {
19 | this.maxSize = maxSize;
20 | }
21 |
22 | public int getBitRate() {
23 | return bitRate;
24 | }
25 |
26 | public void setBitRate(int bitRate) {
27 | this.bitRate = bitRate;
28 | }
29 |
30 | public int getMaxFps() {
31 | return maxFps;
32 | }
33 |
34 | public void setMaxFps(int maxFps) {
35 | this.maxFps = maxFps;
36 | }
37 |
38 | public boolean isTunnelForward() {
39 | return tunnelForward;
40 | }
41 |
42 | public void setTunnelForward(boolean tunnelForward) {
43 | this.tunnelForward = tunnelForward;
44 | }
45 |
46 | public Rect getCrop() {
47 | return crop;
48 | }
49 |
50 | public void setCrop(Rect crop) {
51 | this.crop = crop;
52 | }
53 |
54 | public boolean getSendFrameMeta() {
55 | return sendFrameMeta;
56 | }
57 |
58 | public void setSendFrameMeta(boolean sendFrameMeta) {
59 | this.sendFrameMeta = sendFrameMeta;
60 | }
61 |
62 | public boolean getControl() {
63 | return control;
64 | }
65 |
66 | public void setControl(boolean control) {
67 | this.control = control;
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/scrcpy/app/src/util/str_util.h:
--------------------------------------------------------------------------------
1 | #ifndef STRUTIL_H
2 | #define STRUTIL_H
3 |
4 | #include
5 | #include
6 |
7 | #include "config.h"
8 |
9 | // like strncpy, except:
10 | // - it copies at most n-1 chars
11 | // - the dest string is nul-terminated
12 | // - it does not write useless bytes if strlen(src) < n
13 | // - it returns the number of chars actually written (max n-1) if src has
14 | // been copied completely, or n if src has been truncated
15 | size_t
16 | xstrncpy(char *dest, const char *src, size_t n);
17 |
18 | // join tokens by sep into dst
19 | // returns the number of chars actually written (max n-1) if no trucation
20 | // occurred, or n if truncated
21 | size_t
22 | xstrjoin(char *dst, const char *const tokens[], char sep, size_t n);
23 |
24 | // quote a string
25 | // returns the new allocated string, to be freed by the caller
26 | char *
27 | strquote(const char *src);
28 |
29 | // parse s as an integer into value
30 | // returns true if the conversion succeeded, false otherwise
31 | bool
32 | parse_integer(const char *s, long *out);
33 |
34 | // parse s as an integer into value
35 | // like parse_integer(), but accept 'k'/'K' (x1000) and 'm'/'M' (x1000000) as
36 | // suffix
37 | // returns true if the conversion succeeded, false otherwise
38 | bool
39 | parse_integer_with_suffix(const char *s, long *out);
40 |
41 | // return the index to truncate a UTF-8 string at a valid position
42 | size_t
43 | utf8_truncation_index(const char *utf8, size_t max_len);
44 |
45 | #ifdef _WIN32
46 | // convert a UTF-8 string to a wchar_t string
47 | // returns the new allocated string, to be freed by the caller
48 | wchar_t *
49 | utf8_to_wide_char(const char *utf8);
50 |
51 | char *
52 | utf8_from_wide_char(const wchar_t *s);
53 | #endif
54 |
55 | #endif
56 |
--------------------------------------------------------------------------------
/scrcpy/prebuilt-deps/Makefile:
--------------------------------------------------------------------------------
1 | .PHONY: prepare-win32 prepare-win64 \
2 | prepare-ffmpeg-shared-win32 \
3 | prepare-ffmpeg-dev-win32 \
4 | prepare-ffmpeg-shared-win64 \
5 | prepare-ffmpeg-dev-win64 \
6 | prepare-sdl2 \
7 | prepare-adb
8 |
9 | prepare-win32: prepare-sdl2 prepare-ffmpeg-shared-win32 prepare-ffmpeg-dev-win32 prepare-adb
10 | prepare-win64: prepare-sdl2 prepare-ffmpeg-shared-win64 prepare-ffmpeg-dev-win64 prepare-adb
11 |
12 | prepare-ffmpeg-shared-win32:
13 | @./prepare-dep https://ffmpeg.zeranoe.com/builds/win32/shared/ffmpeg-4.2.1-win32-shared.zip \
14 | 9208255f409410d95147151d7e829b5699bf8d91bfe1e81c3f470f47c2fa66d2 \
15 | ffmpeg-4.2.1-win32-shared
16 |
17 | prepare-ffmpeg-dev-win32:
18 | @./prepare-dep https://ffmpeg.zeranoe.com/builds/win32/dev/ffmpeg-4.2.1-win32-dev.zip \
19 | c3469e6c5f031cbcc8cba88dee92d6548c5c6b6ff14f4097f18f72a92d0d70c4 \
20 | ffmpeg-4.2.1-win32-dev
21 |
22 | prepare-ffmpeg-shared-win64:
23 | @./prepare-dep https://ffmpeg.zeranoe.com/builds/win64/shared/ffmpeg-4.2.1-win64-shared.zip \
24 | 55063d3cf750a75485c7bf196031773d81a1b25d0980c7db48ecfc7701a42331 \
25 | ffmpeg-4.2.1-win64-shared
26 |
27 | prepare-ffmpeg-dev-win64:
28 | @./prepare-dep https://ffmpeg.zeranoe.com/builds/win64/dev/ffmpeg-4.2.1-win64-dev.zip \
29 | 5af393be5f25c0a71aa29efce768e477c35347f7f8e0d9696767d5b9d405b74e \
30 | ffmpeg-4.2.1-win64-dev
31 |
32 | prepare-sdl2:
33 | @./prepare-dep https://libsdl.org/release/SDL2-devel-2.0.10-mingw.tar.gz \
34 | a90a7cddaec4996f4d7be6d80c57ec69b062e132bffc513965f99217f603274a \
35 | SDL2-2.0.10
36 |
37 | prepare-adb:
38 | @./prepare-dep https://dl.google.com/android/repository/platform-tools_r29.0.5-windows.zip \
39 | 2df06160056ec9a84c7334af2a1e42740befbb1a2e34370e7af544a2cc78152c \
40 | platform-tools
41 |
--------------------------------------------------------------------------------
/scrcpy/app/tests/test_cbuf.c:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 |
4 | #include "util/cbuf.h"
5 |
6 | struct int_queue CBUF(int, 32);
7 |
8 | static void test_cbuf_empty(void) {
9 | struct int_queue queue;
10 | cbuf_init(&queue);
11 |
12 | assert(cbuf_is_empty(&queue));
13 |
14 | bool push_ok = cbuf_push(&queue, 42);
15 | assert(push_ok);
16 | assert(!cbuf_is_empty(&queue));
17 |
18 | int item;
19 | bool take_ok = cbuf_take(&queue, &item);
20 | assert(take_ok);
21 | assert(cbuf_is_empty(&queue));
22 |
23 | bool take_empty_ok = cbuf_take(&queue, &item);
24 | assert(!take_empty_ok); // the queue is empty
25 | }
26 |
27 | static void test_cbuf_full(void) {
28 | struct int_queue queue;
29 | cbuf_init(&queue);
30 |
31 | assert(!cbuf_is_full(&queue));
32 |
33 | // fill the queue
34 | for (int i = 0; i < 32; ++i) {
35 | bool ok = cbuf_push(&queue, i);
36 | assert(ok);
37 | }
38 | bool ok = cbuf_push(&queue, 42);
39 | assert(!ok); // the queue if full
40 |
41 | int item;
42 | bool take_ok = cbuf_take(&queue, &item);
43 | assert(take_ok);
44 | assert(!cbuf_is_full(&queue));
45 | }
46 |
47 | static void test_cbuf_push_take(void) {
48 | struct int_queue queue;
49 | cbuf_init(&queue);
50 |
51 | bool push1_ok = cbuf_push(&queue, 42);
52 | assert(push1_ok);
53 |
54 | bool push2_ok = cbuf_push(&queue, 35);
55 | assert(push2_ok);
56 |
57 | int item;
58 |
59 | bool take1_ok = cbuf_take(&queue, &item);
60 | assert(take1_ok);
61 | assert(item == 42);
62 |
63 | bool take2_ok = cbuf_take(&queue, &item);
64 | assert(take2_ok);
65 | assert(item == 35);
66 | }
67 |
68 | int main(void) {
69 | test_cbuf_empty();
70 | test_cbuf_full();
71 | test_cbuf_push_take();
72 | return 0;
73 | }
74 |
--------------------------------------------------------------------------------
/scrcpy/app/src/scrcpy.h:
--------------------------------------------------------------------------------
1 | #ifndef SCRCPY_H
2 | #define SCRCPY_H
3 |
4 | #include
5 | #include
6 |
7 | #include "config.h"
8 | #include "input_manager.h"
9 | #include "recorder.h"
10 |
11 | struct scrcpy_options {
12 | const char *serial;
13 | const char *crop;
14 | const char *record_filename;
15 | const char *window_title;
16 | const char *push_target;
17 | enum recorder_format record_format;
18 | uint16_t port;
19 | uint16_t max_size;
20 | uint32_t bit_rate;
21 | uint16_t max_fps;
22 | int16_t window_x;
23 | int16_t window_y;
24 | uint16_t window_width;
25 | uint16_t window_height;
26 | bool show_touches;
27 | bool fullscreen;
28 | bool always_on_top;
29 | bool control;
30 | bool display;
31 | bool turn_screen_off;
32 | bool render_expired_frames;
33 | bool prefer_text;
34 | bool window_borderless;
35 | };
36 |
37 | #define SCRCPY_OPTIONS_DEFAULT { \
38 | .serial = NULL, \
39 | .crop = NULL, \
40 | .record_filename = NULL, \
41 | .window_title = NULL, \
42 | .push_target = NULL, \
43 | .record_format = RECORDER_FORMAT_AUTO, \
44 | .port = DEFAULT_LOCAL_PORT, \
45 | .max_size = DEFAULT_MAX_SIZE, \
46 | .bit_rate = DEFAULT_BIT_RATE, \
47 | .max_fps = 0, \
48 | .window_x = -1, \
49 | .window_y = -1, \
50 | .window_width = 0, \
51 | .window_height = 0, \
52 | .show_touches = false, \
53 | .fullscreen = false, \
54 | .always_on_top = false, \
55 | .control = true, \
56 | .display = true, \
57 | .turn_screen_off = false, \
58 | .render_expired_frames = false, \
59 | .prefer_text = false, \
60 | .window_borderless = false, \
61 | }
62 |
63 | void opencv_injection_send_tap(SDL_TouchFingerEvent event);
64 |
65 | bool
66 | scrcpy(const struct scrcpy_options *options);
67 |
68 | #endif
69 |
--------------------------------------------------------------------------------
/scrcpy/server/src/main/java/com/genymobile/scrcpy/wrappers/StatusBarManager.java:
--------------------------------------------------------------------------------
1 | package com.genymobile.scrcpy.wrappers;
2 |
3 | import com.genymobile.scrcpy.Ln;
4 |
5 | import android.os.IInterface;
6 |
7 | import java.lang.reflect.InvocationTargetException;
8 | import java.lang.reflect.Method;
9 |
10 | public class StatusBarManager {
11 |
12 | private final IInterface manager;
13 | private Method expandNotificationsPanelMethod;
14 | private Method collapsePanelsMethod;
15 |
16 | public StatusBarManager(IInterface manager) {
17 | this.manager = manager;
18 | }
19 |
20 | private Method getExpandNotificationsPanelMethod() throws NoSuchMethodException {
21 | if (expandNotificationsPanelMethod == null) {
22 | expandNotificationsPanelMethod = manager.getClass().getMethod("expandNotificationsPanel");
23 | }
24 | return expandNotificationsPanelMethod;
25 | }
26 |
27 | private Method getCollapsePanelsMethod() throws NoSuchMethodException {
28 | if (collapsePanelsMethod == null) {
29 | collapsePanelsMethod = manager.getClass().getMethod("collapsePanels");
30 | }
31 | return collapsePanelsMethod;
32 | }
33 |
34 | public void expandNotificationsPanel() {
35 | try {
36 | Method method = getExpandNotificationsPanelMethod();
37 | method.invoke(manager);
38 | } catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException e) {
39 | Ln.e("Could not invoke method", e);
40 | }
41 | }
42 |
43 | public void collapsePanels() {
44 | try {
45 | Method method = getCollapsePanelsMethod();
46 | method.invoke(manager);
47 | } catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException e) {
48 | Ln.e("Could not invoke method", e);
49 | }
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/scrcpy/app/tests/test_buffer_util.c:
--------------------------------------------------------------------------------
1 | #include
2 |
3 | #include "util/buffer_util.h"
4 |
5 | static void test_buffer_write16be(void) {
6 | uint16_t val = 0xABCD;
7 | uint8_t buf[2];
8 |
9 | buffer_write16be(buf, val);
10 |
11 | assert(buf[0] == 0xAB);
12 | assert(buf[1] == 0xCD);
13 | }
14 |
15 | static void test_buffer_write32be(void) {
16 | uint32_t val = 0xABCD1234;
17 | uint8_t buf[4];
18 |
19 | buffer_write32be(buf, val);
20 |
21 | assert(buf[0] == 0xAB);
22 | assert(buf[1] == 0xCD);
23 | assert(buf[2] == 0x12);
24 | assert(buf[3] == 0x34);
25 | }
26 |
27 | static void test_buffer_write64be(void) {
28 | uint64_t val = 0xABCD1234567890EF;
29 | uint8_t buf[8];
30 |
31 | buffer_write64be(buf, val);
32 |
33 | assert(buf[0] == 0xAB);
34 | assert(buf[1] == 0xCD);
35 | assert(buf[2] == 0x12);
36 | assert(buf[3] == 0x34);
37 | assert(buf[4] == 0x56);
38 | assert(buf[5] == 0x78);
39 | assert(buf[6] == 0x90);
40 | assert(buf[7] == 0xEF);
41 | }
42 |
43 | static void test_buffer_read16be(void) {
44 | uint8_t buf[2] = {0xAB, 0xCD};
45 |
46 | uint16_t val = buffer_read16be(buf);
47 |
48 | assert(val == 0xABCD);
49 | }
50 |
51 | static void test_buffer_read32be(void) {
52 | uint8_t buf[4] = {0xAB, 0xCD, 0x12, 0x34};
53 |
54 | uint32_t val = buffer_read32be(buf);
55 |
56 | assert(val == 0xABCD1234);
57 | }
58 |
59 | static void test_buffer_read64be(void) {
60 | uint8_t buf[8] = {0xAB, 0xCD, 0x12, 0x34,
61 | 0x56, 0x78, 0x90, 0xEF};
62 |
63 | uint64_t val = buffer_read64be(buf);
64 |
65 | assert(val == 0xABCD1234567890EF);
66 | }
67 |
68 | int main(void) {
69 | test_buffer_write16be();
70 | test_buffer_write32be();
71 | test_buffer_write64be();
72 | test_buffer_read16be();
73 | test_buffer_read32be();
74 | test_buffer_read64be();
75 | return 0;
76 | }
77 |
--------------------------------------------------------------------------------
/scrcpy/server/src/main/java/com/genymobile/scrcpy/Ln.java:
--------------------------------------------------------------------------------
1 | package com.genymobile.scrcpy;
2 |
3 | import android.util.Log;
4 |
5 | /**
6 | * Log both to Android logger (so that logs are visible in "adb logcat") and standard output/error (so that they are visible in the terminal
7 | * directly).
8 | */
9 | public final class Ln {
10 |
11 | private static final String TAG = "scrcpy";
12 | private static final String PREFIX = "[server] ";
13 |
14 | enum Level {
15 | DEBUG, INFO, WARN, ERROR
16 | }
17 |
18 | private static final Level THRESHOLD = BuildConfig.DEBUG ? Level.DEBUG : Level.INFO;
19 |
20 | private Ln() {
21 | // not instantiable
22 | }
23 |
24 | public static boolean isEnabled(Level level) {
25 | return level.ordinal() >= THRESHOLD.ordinal();
26 | }
27 |
28 | public static void d(String message) {
29 | if (isEnabled(Level.DEBUG)) {
30 | Log.d(TAG, message);
31 | System.out.println(PREFIX + "DEBUG: " + message);
32 | }
33 | }
34 |
35 | public static void i(String message) {
36 | if (isEnabled(Level.INFO)) {
37 | Log.i(TAG, message);
38 | System.out.println(PREFIX + "INFO: " + message);
39 | }
40 | }
41 |
42 | public static void w(String message) {
43 | if (isEnabled(Level.WARN)) {
44 | Log.w(TAG, message);
45 | System.out.println(PREFIX + "WARN: " + message);
46 | }
47 | }
48 |
49 | public static void e(String message, Throwable throwable) {
50 | if (isEnabled(Level.ERROR)) {
51 | Log.e(TAG, message, throwable);
52 | System.out.println(PREFIX + "ERROR: " + message);
53 | if (throwable != null) {
54 | throwable.printStackTrace();
55 | }
56 | }
57 | }
58 |
59 | public static void e(String message) {
60 | e(message, null);
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/scrcpy/app/src/recorder.h:
--------------------------------------------------------------------------------
1 | #ifndef RECORDER_H
2 | #define RECORDER_H
3 |
4 | #include
5 | #include
6 | #include
7 | #include
8 |
9 | #include "config.h"
10 | #include "common.h"
11 | #include "util/queue.h"
12 |
13 | enum recorder_format {
14 | RECORDER_FORMAT_AUTO,
15 | RECORDER_FORMAT_MP4,
16 | RECORDER_FORMAT_MKV,
17 | };
18 |
19 | struct record_packet {
20 | AVPacket packet;
21 | struct record_packet *next;
22 | };
23 |
24 | struct recorder_queue QUEUE(struct record_packet);
25 |
26 | struct recorder {
27 | char *filename;
28 | enum recorder_format format;
29 | AVFormatContext *ctx;
30 | struct size declared_frame_size;
31 | bool header_written;
32 |
33 | SDL_Thread *thread;
34 | SDL_mutex *mutex;
35 | SDL_cond *queue_cond;
36 | bool stopped; // set on recorder_stop() by the stream reader
37 | bool failed; // set on packet write failure
38 | struct recorder_queue queue;
39 |
40 | // we can write a packet only once we received the next one so that we can
41 | // set its duration (next_pts - current_pts)
42 | // "previous" is only accessed from the recorder thread, so it does not
43 | // need to be protected by the mutex
44 | struct record_packet *previous;
45 | };
46 |
47 | bool
48 | recorder_init(struct recorder *recorder, const char *filename,
49 | enum recorder_format format, struct size declared_frame_size);
50 |
51 | void
52 | recorder_destroy(struct recorder *recorder);
53 |
54 | bool
55 | recorder_open(struct recorder *recorder, const AVCodec *input_codec);
56 |
57 | void
58 | recorder_close(struct recorder *recorder);
59 |
60 | bool
61 | recorder_start(struct recorder *recorder);
62 |
63 | void
64 | recorder_stop(struct recorder *recorder);
65 |
66 | void
67 | recorder_join(struct recorder *recorder);
68 |
69 | bool
70 | recorder_push(struct recorder *recorder, const AVPacket *packet);
71 |
72 | #endif
73 |
--------------------------------------------------------------------------------
/scrcpy/app/src/compat.h:
--------------------------------------------------------------------------------
1 | #ifndef COMPAT_H
2 | #define COMPAT_H
3 |
4 | #include
5 | #include
6 |
7 | // In ffmpeg/doc/APIchanges:
8 | // 2016-04-11 - 6f69f7a / 9200514 - lavf 57.33.100 / 57.5.0 - avformat.h
9 | // Add AVStream.codecpar, deprecate AVStream.codec.
10 | #if (LIBAVFORMAT_VERSION_MICRO >= 100 /* FFmpeg */ && \
11 | LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(57, 33, 100)) \
12 | || (LIBAVFORMAT_VERSION_MICRO < 100 && /* Libav */ \
13 | LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(57, 5, 0))
14 | # define SCRCPY_LAVF_HAS_NEW_CODEC_PARAMS_API
15 | #endif
16 |
17 | // In ffmpeg/doc/APIchanges:
18 | // 2018-02-06 - 0694d87024 - lavf 58.9.100 - avformat.h
19 | // Deprecate use of av_register_input_format(), av_register_output_format(),
20 | // av_register_all(), av_iformat_next(), av_oformat_next().
21 | // Add av_demuxer_iterate(), and av_muxer_iterate().
22 | #if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(58, 9, 100)
23 | # define SCRCPY_LAVF_HAS_NEW_MUXER_ITERATOR_API
24 | #else
25 | # define SCRCPY_LAVF_REQUIRES_REGISTER_ALL
26 | #endif
27 |
28 | // In ffmpeg/doc/APIchanges:
29 | // 2016-04-21 - 7fc329e - lavc 57.37.100 - avcodec.h
30 | // Add a new audio/video encoding and decoding API with decoupled input
31 | // and output -- avcodec_send_packet(), avcodec_receive_frame(),
32 | // avcodec_send_frame() and avcodec_receive_packet().
33 | #if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(57, 37, 100)
34 | # define SCRCPY_LAVF_HAS_NEW_ENCODING_DECODING_API
35 | #endif
36 |
37 | #if SDL_VERSION_ATLEAST(2, 0, 5)
38 | //
39 | # define SCRCPY_SDL_HAS_HINT_MOUSE_FOCUS_CLICKTHROUGH
40 | //
41 | # define SCRCPY_SDL_HAS_GET_DISPLAY_USABLE_BOUNDS
42 | //
43 | # define SCRCPY_SDL_HAS_WINDOW_ALWAYS_ON_TOP
44 | #endif
45 |
46 | #if SDL_VERSION_ATLEAST(2, 0, 8)
47 | //
48 | # define SCRCPY_SDL_HAS_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR
49 | #endif
50 |
51 | #endif
52 |
--------------------------------------------------------------------------------
/scrcpy/server/build_without_gradle.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | #
3 | # This script generates the scrcpy binary "manually" (without gradle).
4 | #
5 | # Adapt Android platform and build tools versions (via ANDROID_PLATFORM and
6 | # ANDROID_BUILD_TOOLS environment variables).
7 | #
8 | # Then execute:
9 | #
10 | # BUILD_DIR=my_build_dir ./build_without_gradle.sh
11 |
12 | set -e
13 |
14 | SCRCPY_DEBUG=false
15 | SCRCPY_VERSION_NAME=1.12.1
16 |
17 | PLATFORM=${ANDROID_PLATFORM:-29}
18 | BUILD_TOOLS=${ANDROID_BUILD_TOOLS:-29.0.2}
19 |
20 | BUILD_DIR="$(realpath ${BUILD_DIR:-build_manual})"
21 | CLASSES_DIR="$BUILD_DIR/classes"
22 | SERVER_DIR=$(dirname "$0")
23 | SERVER_BINARY=scrcpy-server
24 |
25 | echo "Platform: android-$PLATFORM"
26 | echo "Build-tools: $BUILD_TOOLS"
27 | echo "Build dir: $BUILD_DIR"
28 |
29 | rm -rf "$CLASSES_DIR" "$BUILD_DIR/$SERVER_BINARY" classes.dex
30 | mkdir -p "$CLASSES_DIR/com/genymobile/scrcpy"
31 |
32 | << EOF cat > "$CLASSES_DIR/com/genymobile/scrcpy/BuildConfig.java"
33 | package com.genymobile.scrcpy;
34 |
35 | public final class BuildConfig {
36 | public static final boolean DEBUG = $SCRCPY_DEBUG;
37 | public static final String VERSION_NAME = "$SCRCPY_VERSION_NAME";
38 | }
39 | EOF
40 |
41 | echo "Generating java from aidl..."
42 | cd "$SERVER_DIR/src/main/aidl"
43 | "$ANDROID_HOME/build-tools/$BUILD_TOOLS/aidl" -o"$CLASSES_DIR" \
44 | android/view/IRotationWatcher.aidl
45 |
46 | echo "Compiling java sources..."
47 | cd ../java
48 | javac -bootclasspath "$ANDROID_HOME/platforms/android-$PLATFORM/android.jar" \
49 | -cp "$CLASSES_DIR" -d "$CLASSES_DIR" -source 1.8 -target 1.8 \
50 | com/genymobile/scrcpy/*.java \
51 | com/genymobile/scrcpy/wrappers/*.java
52 |
53 | echo "Dexing..."
54 | cd "$CLASSES_DIR"
55 | "$ANDROID_HOME/build-tools/$BUILD_TOOLS/dx" --dex \
56 | --output "$BUILD_DIR/classes.dex" \
57 | android/view/*.class \
58 | com/genymobile/scrcpy/*.class \
59 | com/genymobile/scrcpy/wrappers/*.class
60 |
61 | echo "Archiving..."
62 | cd "$BUILD_DIR"
63 | jar cvf "$SERVER_BINARY" classes.dex
64 | rm -rf classes.dex classes
65 |
66 | echo "Server generated in $BUILD_DIR/$SERVER_BINARY"
67 |
--------------------------------------------------------------------------------
/scrcpy/FAQ.ko.md:
--------------------------------------------------------------------------------
1 | # 자주하는 질문 (FAQ)
2 |
3 | 다음은 자주 제보되는 문제들과 그들의 현황입니다.
4 |
5 |
6 | ### Window 운영체제에서, 디바이스가 발견되지 않습니다.
7 |
8 | 가장 흔한 제보는 `adb`에 발견되지 않는 디바이스 혹은 권한 관련 문제입니다.
9 | 다음 명령어를 호출하여 모든 것들에 이상이 없는지 확인하세요:
10 |
11 | adb devices
12 |
13 | Window는 당신의 디바이스를 감지하기 위해 [drivers]가 필요할 수도 있습니다.
14 |
15 | [drivers]: https://developer.android.com/studio/run/oem-usb.html
16 |
17 |
18 | ### 내 디바이스의 미러링만 가능하고, 디바이스와 상호작용을 할 수 없습니다.
19 |
20 | 일부 디바이스에서는, [simulating input]을 허용하기 위해서 한가지 옵션을 활성화해야 할 수도 있습니다.
21 | 개발자 옵션에서 (developer options) 다음을 활성화 하세요:
22 |
23 | > **USB debugging (Security settings)**
24 | > _권한 부여와 USB 디버깅을 통한 simulating input을 허용한다_
25 |
26 | [simulating input]: https://github.com/Genymobile/scrcpy/issues/70#issuecomment-373286323
27 |
28 |
29 | ### 마우스 클릭이 다른 곳에 적용됩니다.
30 |
31 | Mac 운영체제에서, HiDPI support 와 여러 스크린 창이 있는 경우, 입력 위치가 잘못 파악될 수 있습니다.
32 | [issue 15]를 참고하세요.
33 |
34 | [issue 15]: https://github.com/Genymobile/scrcpy/issues/15
35 |
36 | 차선책은 HiDPI support을 비활성화 하고 build하는 방법입니다:
37 |
38 | ```bash
39 | meson x --buildtype release -Dhidpi_support=false
40 | ```
41 |
42 | 하지만, 동영상은 낮은 해상도로 재생될 것 입니다.
43 |
44 |
45 | ### HiDPI display의 화질이 낮습니다.
46 |
47 | Windows에서는, [scaling behavior] 환경을 설정해야 할 수도 있습니다.
48 |
49 | > `scrcpy.exe` > Properties > Compatibility > Change high DPI settings >
50 | > Override high DPI scaling behavior > Scaling performed by: _Application_.
51 |
52 | [scaling behavior]: https://github.com/Genymobile/scrcpy/issues/40#issuecomment-424466723
53 |
54 |
55 | ### KWin compositor가 실행되지 않습니다
56 |
57 | Plasma Desktop에서는,_scrcpy_ 가 실행중에는 compositor가 비활성화 됩니다.
58 |
59 | 차석책으로는, ["Block compositing"를 비활성화하세요][kwin].
60 |
61 | [kwin]: https://github.com/Genymobile/scrcpy/issues/114#issuecomment-378778613
62 |
63 |
64 | ###비디오 스트림을 열 수 없는 에러가 발생합니다.(Could not open video stream).
65 |
66 | 여러가지 원인이 있을 수 있습니다. 가장 흔한 원인은 디바이스의 하드웨어 인코더(hardware encoder)가
67 | 주어진 해상도를 인코딩할 수 없는 경우입니다.
68 |
69 | ```
70 | ERROR: Exception on thread Thread[main,5,main]
71 | android.media.MediaCodec$CodecException: Error 0xfffffc0e
72 | ...
73 | Exit due to uncaughtException in main thread:
74 | ERROR: Could not open video stream
75 | INFO: Initial texture: 1080x2336
76 | ```
77 |
78 | 더 낮은 해상도로 시도 해보세요:
79 |
80 | ```
81 | scrcpy -m 1920
82 | scrcpy -m 1024
83 | scrcpy -m 800
84 | ```
85 |
--------------------------------------------------------------------------------
/scrcpy/app/src/util/queue.h:
--------------------------------------------------------------------------------
1 | // generic intrusive FIFO queue
2 | #ifndef QUEUE_H
3 | #define QUEUE_H
4 |
5 | #include
6 | #include
7 | #include
8 |
9 | #include "config.h"
10 |
11 | // To define a queue type of "struct foo":
12 | // struct queue_foo QUEUE(struct foo);
13 | #define QUEUE(TYPE) { \
14 | TYPE *first; \
15 | TYPE *last; \
16 | }
17 |
18 | #define queue_init(PQ) \
19 | (void) ((PQ)->first = (PQ)->last = NULL)
20 |
21 | #define queue_is_empty(PQ) \
22 | !(PQ)->first
23 |
24 | // NEXTFIELD is the field in the ITEM type used for intrusive linked-list
25 | //
26 | // For example:
27 | // struct foo {
28 | // int value;
29 | // struct foo *next;
30 | // };
31 | //
32 | // // define the type "struct my_queue"
33 | // struct my_queue QUEUE(struct foo);
34 | //
35 | // struct my_queue queue;
36 | // queue_init(&queue);
37 | //
38 | // struct foo v1 = { .value = 42 };
39 | // struct foo v2 = { .value = 27 };
40 | //
41 | // queue_push(&queue, next, v1);
42 | // queue_push(&queue, next, v2);
43 | //
44 | // struct foo *foo;
45 | // queue_take(&queue, next, &foo);
46 | // assert(foo->value == 42);
47 | // queue_take(&queue, next, &foo);
48 | // assert(foo->value == 27);
49 | // assert(queue_is_empty(&queue));
50 | //
51 |
52 | // push a new item into the queue
53 | #define queue_push(PQ, NEXTFIELD, ITEM) \
54 | (void) ({ \
55 | (ITEM)->NEXTFIELD = NULL; \
56 | if (queue_is_empty(PQ)) { \
57 | (PQ)->first = (PQ)->last = (ITEM); \
58 | } else { \
59 | (PQ)->last->NEXTFIELD = (ITEM); \
60 | (PQ)->last = (ITEM); \
61 | } \
62 | })
63 |
64 | // take the next item and remove it from the queue (the queue must not be empty)
65 | // the result is stored in *(PITEM)
66 | // (without typeof(), we could not store a local variable having the correct
67 | // type so that we can "return" it)
68 | #define queue_take(PQ, NEXTFIELD, PITEM) \
69 | (void) ({ \
70 | assert(!queue_is_empty(PQ)); \
71 | *(PITEM) = (PQ)->first; \
72 | (PQ)->first = (PQ)->first->NEXTFIELD; \
73 | })
74 | // no need to update (PQ)->last if the queue is left empty:
75 | // (PQ)->last is undefined if !(PQ)->first anyway
76 |
77 | #endif
78 |
--------------------------------------------------------------------------------
/scrcpy/app/src/command.h:
--------------------------------------------------------------------------------
1 | #ifndef COMMAND_H
2 | #define COMMAND_H
3 |
4 | #include
5 | #include
6 |
7 | #ifdef _WIN32
8 |
9 | // not needed here, but winsock2.h must never be included AFTER windows.h
10 | # include
11 | # include
12 | # define PATH_SEPARATOR '\\'
13 | # define PRIexitcode "lu"
14 | //
15 | # ifdef _WIN64
16 | # define PRIsizet PRIu64
17 | # else
18 | # define PRIsizet PRIu32
19 | # endif
20 | # define PROCESS_NONE NULL
21 | # define NO_EXIT_CODE -1u // max value as unsigned
22 | typedef HANDLE process_t;
23 | typedef DWORD exit_code_t;
24 |
25 | #else
26 |
27 | # include
28 | # define PATH_SEPARATOR '/'
29 | # define PRIsizet "zu"
30 | # define PRIexitcode "d"
31 | # define PROCESS_NONE -1
32 | # define NO_EXIT_CODE -1
33 | typedef pid_t process_t;
34 | typedef int exit_code_t;
35 |
36 | #endif
37 |
38 | #include "config.h"
39 |
40 | enum process_result {
41 | PROCESS_SUCCESS,
42 | PROCESS_ERROR_GENERIC,
43 | PROCESS_ERROR_MISSING_BINARY,
44 | };
45 |
46 | enum process_result
47 | cmd_execute(const char *const argv[], process_t *process);
48 |
49 | bool
50 | cmd_terminate(process_t pid);
51 |
52 | bool
53 | cmd_simple_wait(process_t pid, exit_code_t *exit_code);
54 |
55 | process_t
56 | adb_execute(const char *serial, const char *const adb_cmd[], size_t len);
57 |
58 | process_t
59 | adb_forward(const char *serial, uint16_t local_port,
60 | const char *device_socket_name);
61 |
62 | process_t
63 | adb_forward_remove(const char *serial, uint16_t local_port);
64 |
65 | process_t
66 | adb_reverse(const char *serial, const char *device_socket_name,
67 | uint16_t local_port);
68 |
69 | process_t
70 | adb_reverse_remove(const char *serial, const char *device_socket_name);
71 |
72 | process_t
73 | adb_push(const char *serial, const char *local, const char *remote);
74 |
75 | process_t
76 | adb_install(const char *serial, const char *local);
77 |
78 | // convenience function to wait for a successful process execution
79 | // automatically log process errors with the provided process name
80 | bool
81 | process_check_success(process_t proc, const char *name);
82 |
83 | // return the absolute path of the executable (the scrcpy binary)
84 | // may be NULL on error; to be freed by SDL_free
85 | char *
86 | get_executable_path(void);
87 |
88 | // returns true if the file exists and is not a directory
89 | bool
90 | is_regular_file(const char *path);
91 |
92 | #endif
93 |
--------------------------------------------------------------------------------
/scrcpy/app/src/control_msg.h:
--------------------------------------------------------------------------------
1 | #ifndef CONTROLMSG_H
2 | #define CONTROLMSG_H
3 |
4 | #include
5 | #include
6 | #include
7 |
8 | #include "config.h"
9 | #include "android/input.h"
10 | #include "android/keycodes.h"
11 | #include "common.h"
12 |
13 | #define CONTROL_MSG_TEXT_MAX_LENGTH 300
14 | #define CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH 4093
15 | #define CONTROL_MSG_SERIALIZED_MAX_SIZE \
16 | (3 + CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH)
17 |
18 | #define POINTER_ID_MOUSE UINT64_C(-1);
19 |
20 | enum control_msg_type {
21 | CONTROL_MSG_TYPE_INJECT_KEYCODE,
22 | CONTROL_MSG_TYPE_INJECT_TEXT,
23 | CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT,
24 | CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT,
25 | CONTROL_MSG_TYPE_BACK_OR_SCREEN_ON,
26 | CONTROL_MSG_TYPE_EXPAND_NOTIFICATION_PANEL,
27 | CONTROL_MSG_TYPE_COLLAPSE_NOTIFICATION_PANEL,
28 | CONTROL_MSG_TYPE_GET_CLIPBOARD,
29 | CONTROL_MSG_TYPE_SET_CLIPBOARD,
30 | CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE,
31 | CONTROL_MSG_TYPE_ROTATE_DEVICE,
32 | };
33 |
34 | enum screen_power_mode {
35 | // see
36 | SCREEN_POWER_MODE_OFF = 0,
37 | SCREEN_POWER_MODE_NORMAL = 2,
38 | };
39 |
40 | struct control_msg {
41 | enum control_msg_type type;
42 | union {
43 | struct {
44 | enum android_keyevent_action action;
45 | enum android_keycode keycode;
46 | enum android_metastate metastate;
47 | } inject_keycode;
48 | struct {
49 | char *text; // owned, to be freed by SDL_free()
50 | } inject_text;
51 | struct {
52 | enum android_motionevent_action action;
53 | enum android_motionevent_buttons buttons;
54 | uint64_t pointer_id;
55 | struct position position;
56 | float pressure;
57 | } inject_touch_event;
58 | struct {
59 | struct position position;
60 | int32_t hscroll;
61 | int32_t vscroll;
62 | } inject_scroll_event;
63 | struct {
64 | char *text; // owned, to be freed by SDL_free()
65 | } set_clipboard;
66 | struct {
67 | enum screen_power_mode mode;
68 | } set_screen_power_mode;
69 | };
70 | };
71 |
72 | // buf size must be at least CONTROL_MSG_SERIALIZED_MAX_SIZE
73 | // return the number of bytes written
74 | size_t
75 | control_msg_serialize(const struct control_msg *msg, unsigned char *buf);
76 |
77 | void
78 | control_msg_destroy(struct control_msg *msg);
79 |
80 | #endif
81 |
--------------------------------------------------------------------------------
/scrcpy/app/src/sys/win/command.c:
--------------------------------------------------------------------------------
1 | #include "command.h"
2 |
3 | #include "config.h"
4 | #include "util/log.h"
5 | #include "util/str_util.h"
6 |
7 | static int
8 | build_cmd(char *cmd, size_t len, const char *const argv[]) {
9 | // Windows command-line parsing is WTF:
10 | //
11 | // only make it work for this very specific program
12 | // (don't handle escaping nor quotes)
13 | size_t ret = xstrjoin(cmd, argv, ' ', len);
14 | if (ret >= len) {
15 | LOGE("Command too long (%" PRIsizet " chars)", len - 1);
16 | return -1;
17 | }
18 | return 0;
19 | }
20 |
21 | enum process_result
22 | cmd_execute(const char *const argv[], HANDLE *handle) {
23 | STARTUPINFOW si;
24 | PROCESS_INFORMATION pi;
25 | memset(&si, 0, sizeof(si));
26 | si.cb = sizeof(si);
27 |
28 | char cmd[256];
29 | if (build_cmd(cmd, sizeof(cmd), argv)) {
30 | *handle = NULL;
31 | return PROCESS_ERROR_GENERIC;
32 | }
33 |
34 | wchar_t *wide = utf8_to_wide_char(cmd);
35 | if (!wide) {
36 | LOGC("Could not allocate wide char string");
37 | return PROCESS_ERROR_GENERIC;
38 | }
39 |
40 | #ifdef WINDOWS_NOCONSOLE
41 | int flags = CREATE_NO_WINDOW;
42 | #else
43 | int flags = 0;
44 | #endif
45 | if (!CreateProcessW(NULL, wide, NULL, NULL, FALSE, flags, NULL, NULL, &si,
46 | &pi)) {
47 | SDL_free(wide);
48 | *handle = NULL;
49 | if (GetLastError() == ERROR_FILE_NOT_FOUND) {
50 | return PROCESS_ERROR_MISSING_BINARY;
51 | }
52 | return PROCESS_ERROR_GENERIC;
53 | }
54 |
55 | SDL_free(wide);
56 | *handle = pi.hProcess;
57 | return PROCESS_SUCCESS;
58 | }
59 |
60 | bool
61 | cmd_terminate(HANDLE handle) {
62 | return TerminateProcess(handle, 1) && CloseHandle(handle);
63 | }
64 |
65 | bool
66 | cmd_simple_wait(HANDLE handle, DWORD *exit_code) {
67 | DWORD code;
68 | if (WaitForSingleObject(handle, INFINITE) != WAIT_OBJECT_0
69 | || !GetExitCodeProcess(handle, &code)) {
70 | // could not wait or retrieve the exit code
71 | code = -1; // max value, it's unsigned
72 | }
73 | if (exit_code) {
74 | *exit_code = code;
75 | }
76 | return !code;
77 | }
78 |
79 | char *
80 | get_executable_path(void) {
81 | HMODULE hModule = GetModuleHandleW(NULL);
82 | if (!hModule) {
83 | return NULL;
84 | }
85 | WCHAR buf[MAX_PATH + 1]; // +1 for the null byte
86 | int len = GetModuleFileNameW(hModule, buf, MAX_PATH);
87 | if (!len) {
88 | return NULL;
89 | }
90 | buf[len] = '\0';
91 | return utf8_from_wide_char(buf);
92 | }
93 |
--------------------------------------------------------------------------------
/scrcpy/app/src/main.c:
--------------------------------------------------------------------------------
1 | #include "scrcpy.h"
2 |
3 | #include
4 | #include
5 | #include
6 | #define SDL_MAIN_HANDLED // avoid link error on Linux Windows Subsystem
7 | #include
8 |
9 | #include "config.h"
10 | #include "cli.h"
11 | #include "compat.h"
12 | #include "util/log.h"
13 |
14 | static void
15 | print_version(void) {
16 | fprintf(stderr, "scrcpy %s\n\n", SCRCPY_VERSION);
17 |
18 | fprintf(stderr, "dependencies:\n");
19 | fprintf(stderr, " - SDL %d.%d.%d\n", SDL_MAJOR_VERSION, SDL_MINOR_VERSION,
20 | SDL_PATCHLEVEL);
21 | fprintf(stderr, " - libavcodec %d.%d.%d\n", LIBAVCODEC_VERSION_MAJOR,
22 | LIBAVCODEC_VERSION_MINOR,
23 | LIBAVCODEC_VERSION_MICRO);
24 | fprintf(stderr, " - libavformat %d.%d.%d\n", LIBAVFORMAT_VERSION_MAJOR,
25 | LIBAVFORMAT_VERSION_MINOR,
26 | LIBAVFORMAT_VERSION_MICRO);
27 | fprintf(stderr, " - libavutil %d.%d.%d\n", LIBAVUTIL_VERSION_MAJOR,
28 | LIBAVUTIL_VERSION_MINOR,
29 | LIBAVUTIL_VERSION_MICRO);
30 | }
31 |
32 | int
33 | main(int argc, char *argv[]) {
34 | #ifdef __WINDOWS__
35 | // disable buffering, we want logs immediately
36 | // even line buffering (setvbuf() with mode _IOLBF) is not sufficient
37 | setbuf(stdout, NULL);
38 | setbuf(stderr, NULL);
39 | #endif
40 |
41 | #ifndef NDEBUG
42 | SDL_LogSetAllPriority(SDL_LOG_PRIORITY_DEBUG);
43 | #endif
44 |
45 | struct scrcpy_cli_args args = {
46 | .opts = SCRCPY_OPTIONS_DEFAULT,
47 | .help = false,
48 | .version = false,
49 | };
50 |
51 | if (!scrcpy_parse_args(&args, argc, argv)) {
52 | return 1;
53 | }
54 |
55 | if (args.help) {
56 | scrcpy_print_usage(argv[0]);
57 | return 0;
58 | }
59 |
60 | if (args.version) {
61 | print_version();
62 | return 0;
63 | }
64 |
65 | LOGI("scrcpy " SCRCPY_VERSION " ");
66 |
67 | #ifdef SCRCPY_LAVF_REQUIRES_REGISTER_ALL
68 | av_register_all();
69 | #endif
70 |
71 | if (avformat_network_init()) {
72 | return 1;
73 | }
74 |
75 | int res = scrcpy(&args.opts) ? 0 : 1;
76 |
77 | avformat_network_deinit(); // ignore failure
78 |
79 | #if defined (__WINDOWS__) && ! defined (WINDOWS_NOCONSOLE)
80 | if (res != 0) {
81 | fprintf(stderr, "Press any key to continue...\n");
82 | getchar();
83 | }
84 | #endif
85 | return res;
86 | }
87 |
--------------------------------------------------------------------------------
/scrcpy/app/src/icon.xpm:
--------------------------------------------------------------------------------
1 | /* XPM */
2 | static char * icon_xpm[] = {
3 | "48 48 2 1",
4 | " c None",
5 | ". c #96C13E",
6 | " .. .. ",
7 | " ... ... ",
8 | " ... ...... ... ",
9 | " ................ ",
10 | " .............. ",
11 | " ................ ",
12 | " .................. ",
13 | " .................... ",
14 | " ..... ........ ..... ",
15 | " ..... ........ ..... ",
16 | " ...................... ",
17 | " ........................ ",
18 | " ........................ ",
19 | " ........................ ",
20 | " ",
21 | " ",
22 | " .... ........................ .... ",
23 | " ...... ........................ ...... ",
24 | " ...... ........................ ...... ",
25 | " ...... ........................ ...... ",
26 | " ...... ........................ ...... ",
27 | " ...... ........................ ...... ",
28 | " ...... ........................ ...... ",
29 | " ...... ........................ ...... ",
30 | " ...... ........................ ...... ",
31 | " ...... ........................ ...... ",
32 | " ...... ........................ ...... ",
33 | " ...... ........................ ...... ",
34 | " ...... ........................ ...... ",
35 | " ...... ........................ ...... ",
36 | " ...... ........................ ...... ",
37 | " ...... ........................ ...... ",
38 | " ...... ........................ ...... ",
39 | " ...... ........................ ...... ",
40 | " ...... ........................ ...... ",
41 | " .... ........................ .... ",
42 | " ........................ ",
43 | " ...................... ",
44 | " ...... ...... ",
45 | " ...... ...... ",
46 | " ...... ...... ",
47 | " ...... ...... ",
48 | " ...... ...... ",
49 | " ...... ...... ",
50 | " ...... ...... ",
51 | " ...... ...... ",
52 | " ...... ...... ",
53 | " .... .... "};
54 |
--------------------------------------------------------------------------------
/scrcpy/app/src/screen.h:
--------------------------------------------------------------------------------
1 | #ifndef SCREEN_H
2 | #define SCREEN_H
3 |
4 | #include
5 | #include
6 | #include
7 |
8 | #include "config.h"
9 | #include "common.h"
10 |
11 | struct video_buffer;
12 |
13 | struct screen {
14 | SDL_Window *window;
15 | SDL_Renderer *renderer;
16 | SDL_Texture *texture;
17 | struct size frame_size;
18 | // The window size the last time it was not maximized or fullscreen.
19 | struct size windowed_window_size;
20 | // Since we receive the event SIZE_CHANGED before MAXIMIZED, we must be
21 | // able to revert the size to its non-maximized value.
22 | struct size windowed_window_size_backup;
23 | bool has_frame;
24 | bool fullscreen;
25 | bool maximized;
26 | bool no_window;
27 | };
28 |
29 | #define SCREEN_INITIALIZER { \
30 | .window = NULL, \
31 | .renderer = NULL, \
32 | .texture = NULL, \
33 | .frame_size = { \
34 | .width = 0, \
35 | .height = 0, \
36 | }, \
37 | .windowed_window_size = { \
38 | .width = 0, \
39 | .height = 0, \
40 | }, \
41 | .windowed_window_size_backup = { \
42 | .width = 0, \
43 | .height = 0, \
44 | }, \
45 | .has_frame = false, \
46 | .fullscreen = false, \
47 | .maximized = false, \
48 | .no_window = false, \
49 | }
50 |
51 | // initialize default values
52 | void
53 | screen_init(struct screen *screen);
54 |
55 | // initialize screen, create window, renderer and texture (window is hidden)
56 | bool
57 | screen_init_rendering(struct screen *screen, const char *window_title,
58 | struct size frame_size, bool always_on_top,
59 | int16_t window_x, int16_t window_y, uint16_t window_width,
60 | uint16_t window_height, bool window_borderless);
61 |
62 | // show the window
63 | void
64 | screen_show_window(struct screen *screen);
65 |
66 | // destroy window, renderer and texture (if any)
67 | void
68 | screen_destroy(struct screen *screen);
69 |
70 | // resize if necessary and write the rendered frame into the texture
71 | bool
72 | screen_update_frame(struct screen *screen, struct video_buffer *vb);
73 |
74 | // render the texture to the renderer
75 | void
76 | screen_render(struct screen *screen);
77 |
78 | // switch the fullscreen mode
79 | void
80 | screen_switch_fullscreen(struct screen *screen);
81 |
82 | // resize window to optimal size (remove black borders)
83 | void
84 | screen_resize_to_fit(struct screen *screen);
85 |
86 | // resize window to 1:1 (pixel-perfect)
87 | void
88 | screen_resize_to_pixel_perfect(struct screen *screen);
89 |
90 | // react to window events
91 | void
92 | screen_handle_window_event(struct screen *screen, const SDL_WindowEvent *event);
93 |
94 | #endif
95 |
--------------------------------------------------------------------------------
/scrcpy/app/src/receiver.c:
--------------------------------------------------------------------------------
1 | #include "receiver.h"
2 |
3 | #include
4 | #include
5 |
6 | #include "config.h"
7 | #include "device_msg.h"
8 | #include "util/lock.h"
9 | #include "util/log.h"
10 |
11 | bool
12 | receiver_init(struct receiver *receiver, socket_t control_socket) {
13 | if (!(receiver->mutex = SDL_CreateMutex())) {
14 | return false;
15 | }
16 | receiver->control_socket = control_socket;
17 | return true;
18 | }
19 |
20 | void
21 | receiver_destroy(struct receiver *receiver) {
22 | SDL_DestroyMutex(receiver->mutex);
23 | }
24 |
25 | static void
26 | process_msg(struct device_msg *msg) {
27 | switch (msg->type) {
28 | case DEVICE_MSG_TYPE_CLIPBOARD:
29 | LOGI("Device clipboard copied");
30 | SDL_SetClipboardText(msg->clipboard.text);
31 | break;
32 | }
33 | }
34 |
35 | static ssize_t
36 | process_msgs(const unsigned char *buf, size_t len) {
37 | size_t head = 0;
38 | for (;;) {
39 | struct device_msg msg;
40 | ssize_t r = device_msg_deserialize(&buf[head], len - head, &msg);
41 | if (r == -1) {
42 | return -1;
43 | }
44 | if (r == 0) {
45 | return head;
46 | }
47 |
48 | process_msg(&msg);
49 | device_msg_destroy(&msg);
50 |
51 | head += r;
52 | assert(head <= len);
53 | if (head == len) {
54 | return head;
55 | }
56 | }
57 | }
58 |
59 | static int
60 | run_receiver(void *data) {
61 | struct receiver *receiver = data;
62 |
63 | unsigned char buf[DEVICE_MSG_SERIALIZED_MAX_SIZE];
64 | size_t head = 0;
65 |
66 | for (;;) {
67 | assert(head < DEVICE_MSG_SERIALIZED_MAX_SIZE);
68 | ssize_t r = net_recv(receiver->control_socket, buf,
69 | DEVICE_MSG_SERIALIZED_MAX_SIZE - head);
70 | if (r <= 0) {
71 | LOGD("Receiver stopped");
72 | break;
73 | }
74 |
75 | ssize_t consumed = process_msgs(buf, r);
76 | if (consumed == -1) {
77 | // an error occurred
78 | break;
79 | }
80 |
81 | if (consumed) {
82 | // shift the remaining data in the buffer
83 | memmove(buf, &buf[consumed], r - consumed);
84 | head = r - consumed;
85 | }
86 | }
87 |
88 | return 0;
89 | }
90 |
91 | bool
92 | receiver_start(struct receiver *receiver) {
93 | LOGD("Starting receiver thread");
94 |
95 | receiver->thread = SDL_CreateThread(run_receiver, "receiver", receiver);
96 | if (!receiver->thread) {
97 | LOGC("Could not start receiver thread");
98 | return false;
99 | }
100 |
101 | return true;
102 | }
103 |
104 | void
105 | receiver_join(struct receiver *receiver) {
106 | SDL_WaitThread(receiver->thread, NULL);
107 | }
108 |
--------------------------------------------------------------------------------
/scrcpy/server/src/main/java/com/genymobile/scrcpy/wrappers/ServiceManager.java:
--------------------------------------------------------------------------------
1 | package com.genymobile.scrcpy.wrappers;
2 |
3 | import android.annotation.SuppressLint;
4 | import android.os.IBinder;
5 | import android.os.IInterface;
6 |
7 | import java.lang.reflect.Method;
8 |
9 | @SuppressLint("PrivateApi")
10 | public final class ServiceManager {
11 | private final Method getServiceMethod;
12 |
13 | private WindowManager windowManager;
14 | private DisplayManager displayManager;
15 | private InputManager inputManager;
16 | private PowerManager powerManager;
17 | private StatusBarManager statusBarManager;
18 | private ClipboardManager clipboardManager;
19 |
20 | public ServiceManager() {
21 | try {
22 | getServiceMethod = Class.forName("android.os.ServiceManager").getDeclaredMethod("getService", String.class);
23 | } catch (Exception e) {
24 | throw new AssertionError(e);
25 | }
26 | }
27 |
28 | private IInterface getService(String service, String type) {
29 | try {
30 | IBinder binder = (IBinder) getServiceMethod.invoke(null, service);
31 | Method asInterfaceMethod = Class.forName(type + "$Stub").getMethod("asInterface", IBinder.class);
32 | return (IInterface) asInterfaceMethod.invoke(null, binder);
33 | } catch (Exception e) {
34 | throw new AssertionError(e);
35 | }
36 | }
37 |
38 | public WindowManager getWindowManager() {
39 | if (windowManager == null) {
40 | windowManager = new WindowManager(getService("window", "android.view.IWindowManager"));
41 | }
42 | return windowManager;
43 | }
44 |
45 | public DisplayManager getDisplayManager() {
46 | if (displayManager == null) {
47 | displayManager = new DisplayManager(getService("display", "android.hardware.display.IDisplayManager"));
48 | }
49 | return displayManager;
50 | }
51 |
52 | public InputManager getInputManager() {
53 | if (inputManager == null) {
54 | inputManager = new InputManager(getService("input", "android.hardware.input.IInputManager"));
55 | }
56 | return inputManager;
57 | }
58 |
59 | public PowerManager getPowerManager() {
60 | if (powerManager == null) {
61 | powerManager = new PowerManager(getService("power", "android.os.IPowerManager"));
62 | }
63 | return powerManager;
64 | }
65 |
66 | public StatusBarManager getStatusBarManager() {
67 | if (statusBarManager == null) {
68 | statusBarManager = new StatusBarManager(getService("statusbar", "com.android.internal.statusbar.IStatusBarService"));
69 | }
70 | return statusBarManager;
71 | }
72 |
73 | public ClipboardManager getClipboardManager() {
74 | if (clipboardManager == null) {
75 | clipboardManager = new ClipboardManager(getService("clipboard", "android.content.IClipboard"));
76 | }
77 | return clipboardManager;
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/scrcpy/app/src/util/net.c:
--------------------------------------------------------------------------------
1 | #include "net.h"
2 |
3 | #include
4 |
5 | #include "config.h"
6 | #include "log.h"
7 |
8 | #ifdef __WINDOWS__
9 | typedef int socklen_t;
10 | #else
11 | # include
12 | # include
13 | # include
14 | # include
15 | # include
16 | # define SOCKET_ERROR -1
17 | typedef struct sockaddr_in SOCKADDR_IN;
18 | typedef struct sockaddr SOCKADDR;
19 | typedef struct in_addr IN_ADDR;
20 | #endif
21 |
22 | socket_t
23 | net_connect(uint32_t addr, uint16_t port) {
24 | socket_t sock = socket(AF_INET, SOCK_STREAM, 0);
25 | if (sock == INVALID_SOCKET) {
26 | perror("socket");
27 | return INVALID_SOCKET;
28 | }
29 |
30 | SOCKADDR_IN sin;
31 | sin.sin_family = AF_INET;
32 | sin.sin_addr.s_addr = htonl(addr);
33 | sin.sin_port = htons(port);
34 |
35 | if (connect(sock, (SOCKADDR *) &sin, sizeof(sin)) == SOCKET_ERROR) {
36 | perror("connect");
37 | net_close(sock);
38 | return INVALID_SOCKET;
39 | }
40 |
41 | return sock;
42 | }
43 |
44 | socket_t
45 | net_listen(uint32_t addr, uint16_t port, int backlog) {
46 | socket_t sock = socket(AF_INET, SOCK_STREAM, 0);
47 | if (sock == INVALID_SOCKET) {
48 | perror("socket");
49 | return INVALID_SOCKET;
50 | }
51 |
52 | int reuse = 1;
53 | if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (const void *) &reuse,
54 | sizeof(reuse)) == -1) {
55 | perror("setsockopt(SO_REUSEADDR)");
56 | }
57 |
58 | SOCKADDR_IN sin;
59 | sin.sin_family = AF_INET;
60 | sin.sin_addr.s_addr = htonl(addr); // htonl() harmless on INADDR_ANY
61 | sin.sin_port = htons(port);
62 |
63 | if (bind(sock, (SOCKADDR *) &sin, sizeof(sin)) == SOCKET_ERROR) {
64 | perror("bind");
65 | net_close(sock);
66 | return INVALID_SOCKET;
67 | }
68 |
69 | if (listen(sock, backlog) == SOCKET_ERROR) {
70 | perror("listen");
71 | net_close(sock);
72 | return INVALID_SOCKET;
73 | }
74 |
75 | return sock;
76 | }
77 |
78 | socket_t
79 | net_accept(socket_t server_socket) {
80 | SOCKADDR_IN csin;
81 | socklen_t sinsize = sizeof(csin);
82 | return accept(server_socket, (SOCKADDR *) &csin, &sinsize);
83 | }
84 |
85 | ssize_t
86 | net_recv(socket_t socket, void *buf, size_t len) {
87 | return recv(socket, buf, len, 0);
88 | }
89 |
90 | ssize_t
91 | net_recv_all(socket_t socket, void *buf, size_t len) {
92 | return recv(socket, buf, len, MSG_WAITALL);
93 | }
94 |
95 | ssize_t
96 | net_send(socket_t socket, const void *buf, size_t len) {
97 | return send(socket, buf, len, 0);
98 | }
99 |
100 | ssize_t
101 | net_send_all(socket_t socket, const void *buf, size_t len) {
102 | ssize_t w = 0;
103 | while (len > 0) {
104 | w = send(socket, buf, len, 0);
105 | if (w == -1) {
106 | return -1;
107 | }
108 | len -= w;
109 | buf = (char *) buf + w;
110 | }
111 | return w;
112 | }
113 |
114 | bool
115 | net_shutdown(socket_t socket, int how) {
116 | return !shutdown(socket, how);
117 | }
118 |
--------------------------------------------------------------------------------
/scrcpy/app/src/decoder.c:
--------------------------------------------------------------------------------
1 | #include "decoder.h"
2 |
3 | #include
4 | #include
5 | #include
6 | #include
7 | #include
8 | #include
9 |
10 | #include "config.h"
11 | #include "compat.h"
12 | #include "events.h"
13 | #include "recorder.h"
14 | #include "video_buffer.h"
15 | #include "util/buffer_util.h"
16 | #include "util/log.h"
17 |
18 | // set the decoded frame as ready for rendering, and notify
19 | static void
20 | push_frame(struct decoder *decoder) {
21 | bool previous_frame_skipped;
22 | video_buffer_offer_decoded_frame(decoder->video_buffer,
23 | &previous_frame_skipped);
24 | if (previous_frame_skipped) {
25 | // the previous EVENT_NEW_FRAME will consume this frame
26 | return;
27 | }
28 | static SDL_Event new_frame_event = {
29 | .type = EVENT_NEW_FRAME,
30 | };
31 | SDL_PushEvent(&new_frame_event);
32 | }
33 |
34 | void
35 | decoder_init(struct decoder *decoder, struct video_buffer *vb) {
36 | decoder->video_buffer = vb;
37 | }
38 |
39 | bool
40 | decoder_open(struct decoder *decoder, const AVCodec *codec) {
41 | decoder->codec_ctx = avcodec_alloc_context3(codec);
42 | if (!decoder->codec_ctx) {
43 | LOGC("Could not allocate decoder context");
44 | return false;
45 | }
46 |
47 | if (avcodec_open2(decoder->codec_ctx, codec, NULL) < 0) {
48 | LOGE("Could not open codec");
49 | avcodec_free_context(&decoder->codec_ctx);
50 | return false;
51 | }
52 |
53 | return true;
54 | }
55 |
56 | void
57 | decoder_close(struct decoder *decoder) {
58 | avcodec_close(decoder->codec_ctx);
59 | avcodec_free_context(&decoder->codec_ctx);
60 | }
61 |
62 | bool
63 | decoder_push(struct decoder *decoder, const AVPacket *packet) {
64 | // the new decoding/encoding API has been introduced by:
65 | //
66 | #ifdef SCRCPY_LAVF_HAS_NEW_ENCODING_DECODING_API
67 | int ret;
68 | if ((ret = avcodec_send_packet(decoder->codec_ctx, packet)) < 0) {
69 | LOGE("Could not send video packet: %d", ret);
70 | return false;
71 | }
72 | ret = avcodec_receive_frame(decoder->codec_ctx,
73 | decoder->video_buffer->decoding_frame);
74 | if (!ret) {
75 | // a frame was received
76 | push_frame(decoder);
77 | } else if (ret != AVERROR(EAGAIN)) {
78 | LOGE("Could not receive video frame: %d", ret);
79 | return false;
80 | }
81 | #else
82 | int got_picture;
83 | int len = avcodec_decode_video2(decoder->codec_ctx,
84 | decoder->video_buffer->decoding_frame,
85 | &got_picture,
86 | packet);
87 | if (len < 0) {
88 | LOGE("Could not decode video packet: %d", len);
89 | return false;
90 | }
91 | if (got_picture) {
92 | push_frame(decoder);
93 | }
94 | #endif
95 | return true;
96 | }
97 |
98 | void
99 | decoder_interrupt(struct decoder *decoder) {
100 | video_buffer_interrupt(decoder->video_buffer);
101 | }
102 |
--------------------------------------------------------------------------------
/scrcpy/gradlew.bat:
--------------------------------------------------------------------------------
1 | @rem
2 | @rem Copyright 2015 the original author or authors.
3 | @rem
4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
5 | @rem you may not use this file except in compliance with the License.
6 | @rem You may obtain a copy of the License at
7 | @rem
8 | @rem https://www.apache.org/licenses/LICENSE-2.0
9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 |
17 | @if "%DEBUG%" == "" @echo off
18 | @rem ##########################################################################
19 | @rem
20 | @rem Gradle startup script for Windows
21 | @rem
22 | @rem ##########################################################################
23 |
24 | @rem Set local scope for the variables with windows NT shell
25 | if "%OS%"=="Windows_NT" setlocal
26 |
27 | set DIRNAME=%~dp0
28 | if "%DIRNAME%" == "" set DIRNAME=.
29 | set APP_BASE_NAME=%~n0
30 | set APP_HOME=%DIRNAME%
31 |
32 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
33 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
34 |
35 | @rem Find java.exe
36 | if defined JAVA_HOME goto findJavaFromJavaHome
37 |
38 | set JAVA_EXE=java.exe
39 | %JAVA_EXE% -version >NUL 2>&1
40 | if "%ERRORLEVEL%" == "0" goto init
41 |
42 | echo.
43 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
44 | echo.
45 | echo Please set the JAVA_HOME variable in your environment to match the
46 | echo location of your Java installation.
47 |
48 | goto fail
49 |
50 | :findJavaFromJavaHome
51 | set JAVA_HOME=%JAVA_HOME:"=%
52 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
53 |
54 | if exist "%JAVA_EXE%" goto init
55 |
56 | echo.
57 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
58 | echo.
59 | echo Please set the JAVA_HOME variable in your environment to match the
60 | echo location of your Java installation.
61 |
62 | goto fail
63 |
64 | :init
65 | @rem Get command-line arguments, handling Windows variants
66 |
67 | if not "%OS%" == "Windows_NT" goto win9xME_args
68 |
69 | :win9xME_args
70 | @rem Slurp the command line arguments.
71 | set CMD_LINE_ARGS=
72 | set _SKIP=2
73 |
74 | :win9xME_args_slurp
75 | if "x%~1" == "x" goto execute
76 |
77 | set CMD_LINE_ARGS=%*
78 |
79 | :execute
80 | @rem Setup the command line
81 |
82 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
83 |
84 | @rem Execute Gradle
85 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
86 |
87 | :end
88 | @rem End local scope for the variables with windows NT shell
89 | if "%ERRORLEVEL%"=="0" goto mainEnd
90 |
91 | :fail
92 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
93 | rem the _cmd.exe /c_ return code!
94 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
95 | exit /b 1
96 |
97 | :mainEnd
98 | if "%OS%"=="Windows_NT" endlocal
99 |
100 | :omega
101 |
--------------------------------------------------------------------------------
/scrcpy/server/src/main/java/com/genymobile/scrcpy/PointersState.java:
--------------------------------------------------------------------------------
1 | package com.genymobile.scrcpy;
2 |
3 | import android.view.MotionEvent;
4 |
5 | import java.util.ArrayList;
6 | import java.util.List;
7 |
8 | public class PointersState {
9 |
10 | public static final int MAX_POINTERS = 10;
11 |
12 | private final List pointers = new ArrayList<>();
13 |
14 | private int indexOf(long id) {
15 | for (int i = 0; i < pointers.size(); ++i) {
16 | Pointer pointer = pointers.get(i);
17 | if (pointer.getId() == id) {
18 | return i;
19 | }
20 | }
21 | return -1;
22 | }
23 |
24 | private boolean isLocalIdAvailable(int localId) {
25 | for (int i = 0; i < pointers.size(); ++i) {
26 | Pointer pointer = pointers.get(i);
27 | if (pointer.getLocalId() == localId) {
28 | return false;
29 | }
30 | }
31 | return true;
32 | }
33 |
34 | private int nextUnusedLocalId() {
35 | for (int localId = 0; localId < MAX_POINTERS; ++localId) {
36 | if (isLocalIdAvailable(localId)) {
37 | return localId;
38 | }
39 | }
40 | return -1;
41 | }
42 |
43 | public Pointer get(int index) {
44 | return pointers.get(index);
45 | }
46 |
47 | public int getPointerIndex(long id) {
48 | int index = indexOf(id);
49 | if (index != -1) {
50 | // already exists, return it
51 | return index;
52 | }
53 | if (pointers.size() >= MAX_POINTERS) {
54 | // it's full
55 | return -1;
56 | }
57 | // id 0 is reserved for mouse events
58 | int localId = nextUnusedLocalId();
59 | if (localId == -1) {
60 | throw new AssertionError("pointers.size() < maxFingers implies that a local id is available");
61 | }
62 | Pointer pointer = new Pointer(id, localId);
63 | pointers.add(pointer);
64 | // return the index of the pointer
65 | return pointers.size() - 1;
66 | }
67 |
68 | /**
69 | * Initialize the motion event parameters.
70 | *
71 | * @param props the pointer properties
72 | * @param coords the pointer coordinates
73 | * @return The number of items initialized (the number of pointers).
74 | */
75 | public int update(MotionEvent.PointerProperties[] props, MotionEvent.PointerCoords[] coords) {
76 | int count = pointers.size();
77 | for (int i = 0; i < count; ++i) {
78 | Pointer pointer = pointers.get(i);
79 |
80 | // id 0 is reserved for mouse events
81 | props[i].id = pointer.getLocalId();
82 |
83 | Point point = pointer.getPoint();
84 | coords[i].x = point.getX();
85 | coords[i].y = point.getY();
86 | coords[i].pressure = pointer.getPressure();
87 | }
88 | cleanUp();
89 | return count;
90 | }
91 |
92 | /**
93 | * Remove all pointers which are UP.
94 | */
95 | private void cleanUp() {
96 | for (int i = pointers.size() - 1; i >= 0; --i) {
97 | Pointer pointer = pointers.get(i);
98 | if (pointer.isUp()) {
99 | pointers.remove(i);
100 | }
101 | }
102 | }
103 | }
104 |
--------------------------------------------------------------------------------
/scrcpy/server/src/main/java/com/genymobile/scrcpy/wrappers/ClipboardManager.java:
--------------------------------------------------------------------------------
1 | package com.genymobile.scrcpy.wrappers;
2 |
3 | import com.genymobile.scrcpy.Ln;
4 |
5 | import android.content.ClipData;
6 | import android.os.Build;
7 | import android.os.IInterface;
8 |
9 | import java.lang.reflect.InvocationTargetException;
10 | import java.lang.reflect.Method;
11 |
12 | public class ClipboardManager {
13 |
14 | private static final String PACKAGE_NAME = "com.android.shell";
15 | private static final int USER_ID = 0;
16 |
17 | private final IInterface manager;
18 | private Method getPrimaryClipMethod;
19 | private Method setPrimaryClipMethod;
20 |
21 | public ClipboardManager(IInterface manager) {
22 | this.manager = manager;
23 | }
24 |
25 | private Method getGetPrimaryClipMethod() throws NoSuchMethodException {
26 | if (getPrimaryClipMethod == null) {
27 | if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
28 | getPrimaryClipMethod = manager.getClass().getMethod("getPrimaryClip", String.class);
29 | } else {
30 | getPrimaryClipMethod = manager.getClass().getMethod("getPrimaryClip", String.class, int.class);
31 | }
32 | }
33 | return getPrimaryClipMethod;
34 | }
35 |
36 | private Method getSetPrimaryClipMethod() throws NoSuchMethodException {
37 | if (setPrimaryClipMethod == null) {
38 | if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
39 | setPrimaryClipMethod = manager.getClass().getMethod("setPrimaryClip", ClipData.class, String.class);
40 | } else {
41 | setPrimaryClipMethod = manager.getClass().getMethod("setPrimaryClip", ClipData.class, String.class, int.class);
42 | }
43 | }
44 | return setPrimaryClipMethod;
45 | }
46 |
47 | private static ClipData getPrimaryClip(Method method, IInterface manager) throws InvocationTargetException, IllegalAccessException {
48 | if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
49 | return (ClipData) method.invoke(manager, PACKAGE_NAME);
50 | }
51 | return (ClipData) method.invoke(manager, PACKAGE_NAME, USER_ID);
52 | }
53 |
54 | private static void setPrimaryClip(Method method, IInterface manager, ClipData clipData)
55 | throws InvocationTargetException, IllegalAccessException {
56 | if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
57 | method.invoke(manager, clipData, PACKAGE_NAME);
58 | } else {
59 | method.invoke(manager, clipData, PACKAGE_NAME, USER_ID);
60 | }
61 | }
62 |
63 | public CharSequence getText() {
64 | try {
65 | Method method = getGetPrimaryClipMethod();
66 | ClipData clipData = getPrimaryClip(method, manager);
67 | if (clipData == null || clipData.getItemCount() == 0) {
68 | return null;
69 | }
70 | return clipData.getItemAt(0).getText();
71 | } catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException e) {
72 | Ln.e("Could not invoke method", e);
73 | return null;
74 | }
75 | }
76 |
77 | public void setText(CharSequence text) {
78 | try {
79 | Method method = getSetPrimaryClipMethod();
80 | ClipData clipData = ClipData.newPlainText(null, text);
81 | setPrimaryClip(method, manager, clipData);
82 | } catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException e) {
83 | Ln.e("Could not invoke method", e);
84 | }
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/scrcpy/app/src/video_buffer.c:
--------------------------------------------------------------------------------
1 | #include "video_buffer.h"
2 |
3 | #include
4 | #include
5 | #include
6 | #include
7 |
8 | #include "config.h"
9 | #include "util/lock.h"
10 | #include "util/log.h"
11 |
12 | bool
13 | video_buffer_init(struct video_buffer *vb, struct fps_counter *fps_counter,
14 | bool render_expired_frames) {
15 | vb->fps_counter = fps_counter;
16 |
17 | if (!(vb->decoding_frame = av_frame_alloc())) {
18 | goto error_0;
19 | }
20 |
21 | if (!(vb->rendering_frame = av_frame_alloc())) {
22 | goto error_1;
23 | }
24 |
25 | if (!(vb->mutex = SDL_CreateMutex())) {
26 | goto error_2;
27 | }
28 |
29 | vb->render_expired_frames = render_expired_frames;
30 | if (render_expired_frames) {
31 | if (!(vb->rendering_frame_consumed_cond = SDL_CreateCond())) {
32 | SDL_DestroyMutex(vb->mutex);
33 | goto error_2;
34 | }
35 | // interrupted is not used if expired frames are not rendered
36 | // since offering a frame will never block
37 | vb->interrupted = false;
38 | }
39 |
40 | // there is initially no rendering frame, so consider it has already been
41 | // consumed
42 | vb->rendering_frame_consumed = true;
43 |
44 | return true;
45 |
46 | error_2:
47 | av_frame_free(&vb->rendering_frame);
48 | error_1:
49 | av_frame_free(&vb->decoding_frame);
50 | error_0:
51 | return false;
52 | }
53 |
54 | void
55 | video_buffer_destroy(struct video_buffer *vb) {
56 | if (vb->render_expired_frames) {
57 | SDL_DestroyCond(vb->rendering_frame_consumed_cond);
58 | }
59 | SDL_DestroyMutex(vb->mutex);
60 | av_frame_free(&vb->rendering_frame);
61 | av_frame_free(&vb->decoding_frame);
62 | }
63 |
64 | static void
65 | video_buffer_swap_frames(struct video_buffer *vb) {
66 | AVFrame *tmp = vb->decoding_frame;
67 | vb->decoding_frame = vb->rendering_frame;
68 | vb->rendering_frame = tmp;
69 | }
70 |
71 | void
72 | video_buffer_offer_decoded_frame(struct video_buffer *vb,
73 | bool *previous_frame_skipped) {
74 | mutex_lock(vb->mutex);
75 | if (vb->render_expired_frames) {
76 | // wait for the current (expired) frame to be consumed
77 | while (!vb->rendering_frame_consumed && !vb->interrupted) {
78 | cond_wait(vb->rendering_frame_consumed_cond, vb->mutex);
79 | }
80 | } else if (!vb->rendering_frame_consumed) {
81 | fps_counter_add_skipped_frame(vb->fps_counter);
82 | }
83 |
84 | video_buffer_swap_frames(vb);
85 |
86 | *previous_frame_skipped = !vb->rendering_frame_consumed;
87 | vb->rendering_frame_consumed = false;
88 |
89 | mutex_unlock(vb->mutex);
90 | }
91 |
92 | const AVFrame *
93 | video_buffer_consume_rendered_frame(struct video_buffer *vb) {
94 | assert(!vb->rendering_frame_consumed);
95 | vb->rendering_frame_consumed = true;
96 | fps_counter_add_rendered_frame(vb->fps_counter);
97 | if (vb->render_expired_frames) {
98 | // unblock video_buffer_offer_decoded_frame()
99 | cond_signal(vb->rendering_frame_consumed_cond);
100 | }
101 | return vb->rendering_frame;
102 | }
103 |
104 | void
105 | video_buffer_interrupt(struct video_buffer *vb) {
106 | if (vb->render_expired_frames) {
107 | mutex_lock(vb->mutex);
108 | vb->interrupted = true;
109 | mutex_unlock(vb->mutex);
110 | // wake up blocking wait
111 | cond_signal(vb->rendering_frame_consumed_cond);
112 | }
113 | }
114 |
--------------------------------------------------------------------------------
/scrcpy/app/src/sys/unix/command.c:
--------------------------------------------------------------------------------
1 | // for portability
2 | #define _POSIX_SOURCE // for kill()
3 | #define _BSD_SOURCE // for readlink()
4 |
5 | // modern glibc will complain without this
6 | #define _DEFAULT_SOURCE
7 |
8 | #include "command.h"
9 |
10 | #include "config.h"
11 |
12 | #include
13 | #include
14 | #include
15 | #include
16 | #include
17 | #include
18 | #include
19 | #include
20 |
21 | #include "util/log.h"
22 |
23 | enum process_result
24 | cmd_execute(const char *const argv[], pid_t *pid) {
25 | int fd[2];
26 |
27 | if (pipe(fd) == -1) {
28 | perror("pipe");
29 | return PROCESS_ERROR_GENERIC;
30 | }
31 |
32 | enum process_result ret = PROCESS_SUCCESS;
33 |
34 | *pid = fork();
35 | if (*pid == -1) {
36 | perror("fork");
37 | ret = PROCESS_ERROR_GENERIC;
38 | goto end;
39 | }
40 |
41 | if (*pid > 0) {
42 | // parent close write side
43 | close(fd[1]);
44 | fd[1] = -1;
45 | // wait for EOF or receive errno from child
46 | if (read(fd[0], &ret, sizeof(ret)) == -1) {
47 | perror("read");
48 | ret = PROCESS_ERROR_GENERIC;
49 | goto end;
50 | }
51 | } else if (*pid == 0) {
52 | // child close read side
53 | close(fd[0]);
54 | if (fcntl(fd[1], F_SETFD, FD_CLOEXEC) == 0) {
55 | execvp(argv[0], (char *const *)argv);
56 | if (errno == ENOENT) {
57 | ret = PROCESS_ERROR_MISSING_BINARY;
58 | } else {
59 | ret = PROCESS_ERROR_GENERIC;
60 | }
61 | perror("exec");
62 | } else {
63 | perror("fcntl");
64 | ret = PROCESS_ERROR_GENERIC;
65 | }
66 | // send ret to the parent
67 | if (write(fd[1], &ret, sizeof(ret)) == -1) {
68 | perror("write");
69 | }
70 | // close write side before exiting
71 | close(fd[1]);
72 | _exit(1);
73 | }
74 |
75 | end:
76 | if (fd[0] != -1) {
77 | close(fd[0]);
78 | }
79 | if (fd[1] != -1) {
80 | close(fd[1]);
81 | }
82 | return ret;
83 | }
84 |
85 | bool
86 | cmd_terminate(pid_t pid) {
87 | if (pid <= 0) {
88 | LOGC("Requested to kill %d, this is an error. Please report the bug.\n",
89 | (int) pid);
90 | abort();
91 | }
92 | return kill(pid, SIGTERM) != -1;
93 | }
94 |
95 | bool
96 | cmd_simple_wait(pid_t pid, int *exit_code) {
97 | int status;
98 | int code;
99 | if (waitpid(pid, &status, 0) == -1 || !WIFEXITED(status)) {
100 | // could not wait, or exited unexpectedly, probably by a signal
101 | code = -1;
102 | } else {
103 | code = WEXITSTATUS(status);
104 | }
105 | if (exit_code) {
106 | *exit_code = code;
107 | }
108 | return !code;
109 | }
110 |
111 | char *
112 | get_executable_path(void) {
113 | //
114 | #ifdef __linux__
115 | char buf[PATH_MAX + 1]; // +1 for the null byte
116 | ssize_t len = readlink("/proc/self/exe", buf, PATH_MAX);
117 | if (len == -1) {
118 | perror("readlink");
119 | return NULL;
120 | }
121 | buf[len] = '\0';
122 | return SDL_strdup(buf);
123 | #else
124 | // in practice, we only need this feature for portable builds, only used on
125 | // Windows, so we don't care implementing it for every platform
126 | // (it's useful to have a working version on Linux for debugging though)
127 | return NULL;
128 | #endif
129 | }
130 |
--------------------------------------------------------------------------------
/scrcpy/server/src/main/java/com/genymobile/scrcpy/Workarounds.java:
--------------------------------------------------------------------------------
1 | package com.genymobile.scrcpy;
2 |
3 | import android.annotation.SuppressLint;
4 | import android.app.Application;
5 | import android.app.Instrumentation;
6 | import android.content.Context;
7 | import android.content.pm.ApplicationInfo;
8 | import android.os.Looper;
9 |
10 | import java.lang.reflect.Constructor;
11 | import java.lang.reflect.Field;
12 | import java.lang.reflect.Method;
13 |
14 | public final class Workarounds {
15 | private Workarounds() {
16 | // not instantiable
17 | }
18 |
19 | public static void prepareMainLooper() {
20 | // Some devices internally create a Handler when creating an input Surface, causing an exception:
21 | // "Can't create handler inside thread that has not called Looper.prepare()"
22 | //
23 | //
24 | // Use Looper.prepareMainLooper() instead of Looper.prepare() to avoid a NullPointerException:
25 | // "Attempt to read from field 'android.os.MessageQueue android.os.Looper.mQueue'
26 | // on a null object reference"
27 | //
28 | Looper.prepareMainLooper();
29 | }
30 |
31 | @SuppressLint("PrivateApi")
32 | public static void fillAppInfo() {
33 | try {
34 | // ActivityThread activityThread = new ActivityThread();
35 | Class> activityThreadClass = Class.forName("android.app.ActivityThread");
36 | Constructor> activityThreadConstructor = activityThreadClass.getDeclaredConstructor();
37 | activityThreadConstructor.setAccessible(true);
38 | Object activityThread = activityThreadConstructor.newInstance();
39 |
40 | // ActivityThread.sCurrentActivityThread = activityThread;
41 | Field sCurrentActivityThreadField = activityThreadClass.getDeclaredField("sCurrentActivityThread");
42 | sCurrentActivityThreadField.setAccessible(true);
43 | sCurrentActivityThreadField.set(null, activityThread);
44 |
45 | // ActivityThread.AppBindData appBindData = new ActivityThread.AppBindData();
46 | Class> appBindDataClass = Class.forName("android.app.ActivityThread$AppBindData");
47 | Constructor> appBindDataConstructor = appBindDataClass.getDeclaredConstructor();
48 | appBindDataConstructor.setAccessible(true);
49 | Object appBindData = appBindDataConstructor.newInstance();
50 |
51 | ApplicationInfo applicationInfo = new ApplicationInfo();
52 | applicationInfo.packageName = "com.genymobile.scrcpy";
53 |
54 | // appBindData.appInfo = applicationInfo;
55 | Field appInfoField = appBindDataClass.getDeclaredField("appInfo");
56 | appInfoField.setAccessible(true);
57 | appInfoField.set(appBindData, applicationInfo);
58 |
59 | // activityThread.mBoundApplication = appBindData;
60 | Field mBoundApplicationField = activityThreadClass.getDeclaredField("mBoundApplication");
61 | mBoundApplicationField.setAccessible(true);
62 | mBoundApplicationField.set(activityThread, appBindData);
63 |
64 | // Context ctx = activityThread.getSystemContext();
65 | Method getSystemContextMethod = activityThreadClass.getDeclaredMethod("getSystemContext");
66 | Context ctx = (Context) getSystemContextMethod.invoke(activityThread);
67 |
68 | Application app = Instrumentation.newApplication(Application.class, ctx);
69 |
70 | // activityThread.mInitialApplication = app;
71 | Field mInitialApplicationField = activityThreadClass.getDeclaredField("mInitialApplication");
72 | mInitialApplicationField.setAccessible(true);
73 | mInitialApplicationField.set(activityThread, app);
74 | } catch (Throwable throwable) {
75 | // this is a workaround, so failing is not an error
76 | Ln.w("Could not fill app info: " + throwable.getMessage());
77 | }
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/scrcpy/app/src/control_msg.c:
--------------------------------------------------------------------------------
1 | #include "control_msg.h"
2 |
3 | #include
4 | #include
5 |
6 | #include "config.h"
7 | #include "util/buffer_util.h"
8 | #include "util/log.h"
9 | #include "util/str_util.h"
10 |
11 | static void
12 | write_position(uint8_t *buf, const struct position *position) {
13 | buffer_write32be(&buf[0], position->point.x);
14 | buffer_write32be(&buf[4], position->point.y);
15 | buffer_write16be(&buf[8], position->screen_size.width);
16 | buffer_write16be(&buf[10], position->screen_size.height);
17 | }
18 |
19 | // write length (2 bytes) + string (non nul-terminated)
20 | static size_t
21 | write_string(const char *utf8, size_t max_len, unsigned char *buf) {
22 | size_t len = utf8_truncation_index(utf8, max_len);
23 | buffer_write16be(buf, (uint16_t) len);
24 | memcpy(&buf[2], utf8, len);
25 | return 2 + len;
26 | }
27 |
28 | static uint16_t
29 | to_fixed_point_16(float f) {
30 | assert(f >= 0.0f && f <= 1.0f);
31 | uint32_t u = f * 0x1p16f; // 2^16
32 | if (u >= 0xffff) {
33 | u = 0xffff;
34 | }
35 | return (uint16_t) u;
36 | }
37 |
38 | size_t
39 | control_msg_serialize(const struct control_msg *msg, unsigned char *buf) {
40 | buf[0] = msg->type;
41 | switch (msg->type) {
42 | case CONTROL_MSG_TYPE_INJECT_KEYCODE:
43 | buf[1] = msg->inject_keycode.action;
44 | buffer_write32be(&buf[2], msg->inject_keycode.keycode);
45 | buffer_write32be(&buf[6], msg->inject_keycode.metastate);
46 | return 10;
47 | case CONTROL_MSG_TYPE_INJECT_TEXT: {
48 | size_t len = write_string(msg->inject_text.text,
49 | CONTROL_MSG_TEXT_MAX_LENGTH, &buf[1]);
50 | return 1 + len;
51 | }
52 | case CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT:
53 | buf[1] = msg->inject_touch_event.action;
54 | buffer_write64be(&buf[2], msg->inject_touch_event.pointer_id);
55 | write_position(&buf[10], &msg->inject_touch_event.position);
56 | uint16_t pressure =
57 | to_fixed_point_16(msg->inject_touch_event.pressure);
58 | buffer_write16be(&buf[22], pressure);
59 | buffer_write32be(&buf[24], msg->inject_touch_event.buttons);
60 | return 28;
61 | case CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT:
62 | write_position(&buf[1], &msg->inject_scroll_event.position);
63 | buffer_write32be(&buf[13],
64 | (uint32_t) msg->inject_scroll_event.hscroll);
65 | buffer_write32be(&buf[17],
66 | (uint32_t) msg->inject_scroll_event.vscroll);
67 | return 21;
68 | case CONTROL_MSG_TYPE_SET_CLIPBOARD: {
69 | size_t len = write_string(msg->inject_text.text,
70 | CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH,
71 | &buf[1]);
72 | return 1 + len;
73 | }
74 | case CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE:
75 | buf[1] = msg->set_screen_power_mode.mode;
76 | return 2;
77 | case CONTROL_MSG_TYPE_BACK_OR_SCREEN_ON:
78 | case CONTROL_MSG_TYPE_EXPAND_NOTIFICATION_PANEL:
79 | case CONTROL_MSG_TYPE_COLLAPSE_NOTIFICATION_PANEL:
80 | case CONTROL_MSG_TYPE_GET_CLIPBOARD:
81 | case CONTROL_MSG_TYPE_ROTATE_DEVICE:
82 | // no additional data
83 | return 1;
84 | default:
85 | LOGW("Unknown message type: %u", (unsigned) msg->type);
86 | return 0;
87 | }
88 | }
89 |
90 | void
91 | control_msg_destroy(struct control_msg *msg) {
92 | switch (msg->type) {
93 | case CONTROL_MSG_TYPE_INJECT_TEXT:
94 | SDL_free(msg->inject_text.text);
95 | break;
96 | case CONTROL_MSG_TYPE_SET_CLIPBOARD:
97 | SDL_free(msg->set_clipboard.text);
98 | break;
99 | default:
100 | // do nothing
101 | break;
102 | }
103 | }
104 |
--------------------------------------------------------------------------------
/scrcpy/app/src/tiny_xpm.c:
--------------------------------------------------------------------------------
1 | #include "tiny_xpm.h"
2 |
3 | #include
4 | #include
5 | #include
6 | #include
7 | #include
8 |
9 | #include "config.h"
10 | #include "util/log.h"
11 |
12 | struct index {
13 | char c;
14 | uint32_t color;
15 | };
16 |
17 | static bool
18 | find_color(struct index *index, int len, char c, uint32_t *color) {
19 | // there are typically very few color, so it's ok to iterate over the array
20 | for (int i = 0; i < len; ++i) {
21 | if (index[i].c == c) {
22 | *color = index[i].color;
23 | return true;
24 | }
25 | }
26 | *color = 0;
27 | return false;
28 | }
29 |
30 | // We encounter some problems with SDL2_image on MSYS2 (Windows),
31 | // so here is our own XPM parsing not to depend on SDL_image.
32 | //
33 | // We do not hardcode the binary image to keep some flexibility to replace the
34 | // icon easily (just by replacing icon.xpm).
35 | //
36 | // Parameter is not "const char *" because XPM formats are generally stored in a
37 | // (non-const) "char *"
38 | SDL_Surface *
39 | read_xpm(char *xpm[]) {
40 | #ifndef NDEBUG
41 | // patch the XPM to change the icon color in debug mode
42 | xpm[2] = ". c #CC00CC";
43 | #endif
44 |
45 | char *endptr;
46 | // *** No error handling, assume the XPM source is valid ***
47 | // (it's in our source repo)
48 | // Assertions are only checked in debug
49 | int width = strtol(xpm[0], &endptr, 10);
50 | int height = strtol(endptr + 1, &endptr, 10);
51 | int colors = strtol(endptr + 1, &endptr, 10);
52 | int chars = strtol(endptr + 1, &endptr, 10);
53 |
54 | // sanity checks
55 | assert(0 <= width && width < 256);
56 | assert(0 <= height && height < 256);
57 | assert(0 <= colors && colors < 256);
58 | assert(chars == 1); // this implementation does not support more
59 |
60 | (void) chars;
61 |
62 | // init index
63 | struct index index[colors];
64 | for (int i = 0; i < colors; ++i) {
65 | const char *line = xpm[1+i];
66 | index[i].c = line[0];
67 | assert(line[1] == '\t');
68 | assert(line[2] == 'c');
69 | assert(line[3] == ' ');
70 | if (line[4] == '#') {
71 | index[i].color = 0xff000000 | strtol(&line[5], &endptr, 0x10);
72 | assert(*endptr == '\0');
73 | } else {
74 | assert(!strcmp("None", &line[4]));
75 | index[i].color = 0;
76 | }
77 | }
78 |
79 | // parse image
80 | uint32_t *pixels = SDL_malloc(4 * width * height);
81 | if (!pixels) {
82 | LOGE("Could not allocate icon memory");
83 | return NULL;
84 | }
85 | for (int y = 0; y < height; ++y) {
86 | const char *line = xpm[1 + colors + y];
87 | for (int x = 0; x < width; ++x) {
88 | char c = line[x];
89 | uint32_t color;
90 | bool color_found = find_color(index, colors, c, &color);
91 | assert(color_found);
92 | (void) color_found;
93 | pixels[y * width + x] = color;
94 | }
95 | }
96 |
97 | #if SDL_BYTEORDER == SDL_BIG_ENDIAN
98 | uint32_t amask = 0x000000ff;
99 | uint32_t rmask = 0x0000ff00;
100 | uint32_t gmask = 0x00ff0000;
101 | uint32_t bmask = 0xff000000;
102 | #else // little endian, like x86
103 | uint32_t amask = 0xff000000;
104 | uint32_t rmask = 0x00ff0000;
105 | uint32_t gmask = 0x0000ff00;
106 | uint32_t bmask = 0x000000ff;
107 | #endif
108 |
109 | SDL_Surface *surface = SDL_CreateRGBSurfaceFrom(pixels,
110 | width, height,
111 | 32, 4 * width,
112 | rmask, gmask, bmask, amask);
113 | if (!surface) {
114 | LOGE("Could not create icon surface");
115 | return NULL;
116 | }
117 | // make the surface own the raw pixels
118 | surface->flags &= ~SDL_PREALLOC;
119 | return surface;
120 | }
121 |
--------------------------------------------------------------------------------
/scrcpy/app/tests/test_cli.c:
--------------------------------------------------------------------------------
1 | #include
2 |
3 | #include "cli.h"
4 | #include "common.h"
5 |
6 | static void test_flag_version(void) {
7 | struct scrcpy_cli_args args = {
8 | .opts = SCRCPY_OPTIONS_DEFAULT,
9 | .help = false,
10 | .version = false,
11 | };
12 |
13 | char *argv[] = {"scrcpy", "-v"};
14 |
15 | bool ok = scrcpy_parse_args(&args, 2, argv);
16 | assert(ok);
17 | assert(!args.help);
18 | assert(args.version);
19 | }
20 |
21 | static void test_flag_help(void) {
22 | struct scrcpy_cli_args args = {
23 | .opts = SCRCPY_OPTIONS_DEFAULT,
24 | .help = false,
25 | .version = false,
26 | };
27 |
28 | char *argv[] = {"scrcpy", "-v"};
29 |
30 | bool ok = scrcpy_parse_args(&args, 2, argv);
31 | assert(ok);
32 | assert(!args.help);
33 | assert(args.version);
34 | }
35 |
36 | static void test_options(void) {
37 | struct scrcpy_cli_args args = {
38 | .opts = SCRCPY_OPTIONS_DEFAULT,
39 | .help = false,
40 | .version = false,
41 | };
42 |
43 | char *argv[] = {
44 | "scrcpy",
45 | "--always-on-top",
46 | "--bit-rate", "5M",
47 | "--crop", "100:200:300:400",
48 | "--fullscreen",
49 | "--max-fps", "30",
50 | "--max-size", "1024",
51 | // "--no-control" is not compatible with "--turn-screen-off"
52 | // "--no-display" is not compatible with "--fulscreen"
53 | "--port", "1234",
54 | "--push-target", "/sdcard/Movies",
55 | "--record", "file",
56 | "--record-format", "mkv",
57 | "--render-expired-frames",
58 | "--serial", "0123456789abcdef",
59 | "--show-touches",
60 | "--turn-screen-off",
61 | "--prefer-text",
62 | "--window-title", "my device",
63 | "--window-x", "100",
64 | "--window-y", "-1",
65 | "--window-width", "600",
66 | "--window-height", "0",
67 | "--window-borderless",
68 | };
69 |
70 | bool ok = scrcpy_parse_args(&args, ARRAY_LEN(argv), argv);
71 | assert(ok);
72 |
73 | const struct scrcpy_options *opts = &args.opts;
74 | assert(opts->always_on_top);
75 | fprintf(stderr, "%d\n", (int) opts->bit_rate);
76 | assert(opts->bit_rate == 5000000);
77 | assert(!strcmp(opts->crop, "100:200:300:400"));
78 | assert(opts->fullscreen);
79 | assert(opts->max_fps == 30);
80 | assert(opts->max_size == 1024);
81 | assert(opts->port == 1234);
82 | assert(!strcmp(opts->push_target, "/sdcard/Movies"));
83 | assert(!strcmp(opts->record_filename, "file"));
84 | assert(opts->record_format == RECORDER_FORMAT_MKV);
85 | assert(opts->render_expired_frames);
86 | assert(!strcmp(opts->serial, "0123456789abcdef"));
87 | assert(opts->show_touches);
88 | assert(opts->turn_screen_off);
89 | assert(opts->prefer_text);
90 | assert(!strcmp(opts->window_title, "my device"));
91 | assert(opts->window_x == 100);
92 | assert(opts->window_y == -1);
93 | assert(opts->window_width == 600);
94 | assert(opts->window_height == 0);
95 | assert(opts->window_borderless);
96 | }
97 |
98 | static void test_options2(void) {
99 | struct scrcpy_cli_args args = {
100 | .opts = SCRCPY_OPTIONS_DEFAULT,
101 | .help = false,
102 | .version = false,
103 | };
104 |
105 | char *argv[] = {
106 | "scrcpy",
107 | "--no-control",
108 | "--no-display",
109 | "--record", "file.mp4", // cannot enable --no-display without recording
110 | };
111 |
112 | bool ok = scrcpy_parse_args(&args, ARRAY_LEN(argv), argv);
113 | assert(ok);
114 |
115 | const struct scrcpy_options *opts = &args.opts;
116 | assert(!opts->control);
117 | assert(!opts->display);
118 | assert(!strcmp(opts->record_filename, "file.mp4"));
119 | assert(opts->record_format == RECORDER_FORMAT_MP4);
120 | }
121 |
122 | int main(void) {
123 | test_flag_version();
124 | test_flag_help();
125 | test_options();
126 | test_options2();
127 | return 0;
128 | };
129 |
--------------------------------------------------------------------------------
/scrcpy/app/src/controller.c:
--------------------------------------------------------------------------------
1 | #include "controller.h"
2 |
3 | #include
4 |
5 | #include "config.h"
6 | #include "util/lock.h"
7 | #include "util/log.h"
8 |
9 | bool
10 | controller_init(struct controller *controller, socket_t control_socket) {
11 | cbuf_init(&controller->queue);
12 |
13 | if (!receiver_init(&controller->receiver, control_socket)) {
14 | return false;
15 | }
16 |
17 | if (!(controller->mutex = SDL_CreateMutex())) {
18 | receiver_destroy(&controller->receiver);
19 | return false;
20 | }
21 |
22 | if (!(controller->msg_cond = SDL_CreateCond())) {
23 | receiver_destroy(&controller->receiver);
24 | SDL_DestroyMutex(controller->mutex);
25 | return false;
26 | }
27 |
28 | controller->control_socket = control_socket;
29 | controller->stopped = false;
30 |
31 | return true;
32 | }
33 |
34 | void
35 | controller_destroy(struct controller *controller) {
36 | SDL_DestroyCond(controller->msg_cond);
37 | SDL_DestroyMutex(controller->mutex);
38 |
39 | struct control_msg msg;
40 | while (cbuf_take(&controller->queue, &msg)) {
41 | control_msg_destroy(&msg);
42 | }
43 |
44 | receiver_destroy(&controller->receiver);
45 | }
46 |
47 | bool
48 | controller_push_msg(struct controller *controller,
49 | const struct control_msg *msg) {
50 | mutex_lock(controller->mutex);
51 | bool was_empty = cbuf_is_empty(&controller->queue);
52 | bool res = cbuf_push(&controller->queue, *msg);
53 | if (was_empty) {
54 | cond_signal(controller->msg_cond);
55 | }
56 | mutex_unlock(controller->mutex);
57 | return res;
58 | }
59 |
60 | static bool
61 | process_msg(struct controller *controller,
62 | const struct control_msg *msg) {
63 | unsigned char serialized_msg[CONTROL_MSG_SERIALIZED_MAX_SIZE];
64 | int length = control_msg_serialize(msg, serialized_msg);
65 | if (!length) {
66 | return false;
67 | }
68 | int w = net_send_all(controller->control_socket, serialized_msg, length);
69 | return w == length;
70 | }
71 |
72 | static int
73 | run_controller(void *data) {
74 | struct controller *controller = data;
75 |
76 | for (;;) {
77 | mutex_lock(controller->mutex);
78 | while (!controller->stopped && cbuf_is_empty(&controller->queue)) {
79 | cond_wait(controller->msg_cond, controller->mutex);
80 | }
81 | if (controller->stopped) {
82 | // stop immediately, do not process further msgs
83 | mutex_unlock(controller->mutex);
84 | break;
85 | }
86 | struct control_msg msg;
87 | bool non_empty = cbuf_take(&controller->queue, &msg);
88 | assert(non_empty);
89 | (void) non_empty;
90 | mutex_unlock(controller->mutex);
91 |
92 | bool ok = process_msg(controller, &msg);
93 | control_msg_destroy(&msg);
94 | if (!ok) {
95 | LOGD("Could not write msg to socket");
96 | break;
97 | }
98 | }
99 | return 0;
100 | }
101 |
102 | bool
103 | controller_start(struct controller *controller) {
104 | LOGD("Starting controller thread");
105 |
106 | controller->thread = SDL_CreateThread(run_controller, "controller",
107 | controller);
108 | if (!controller->thread) {
109 | LOGC("Could not start controller thread");
110 | return false;
111 | }
112 |
113 | if (!receiver_start(&controller->receiver)) {
114 | controller_stop(controller);
115 | SDL_WaitThread(controller->thread, NULL);
116 | return false;
117 | }
118 |
119 | return true;
120 | }
121 |
122 | void
123 | controller_stop(struct controller *controller) {
124 | mutex_lock(controller->mutex);
125 | controller->stopped = true;
126 | cond_signal(controller->msg_cond);
127 | mutex_unlock(controller->mutex);
128 | }
129 |
130 | void
131 | controller_join(struct controller *controller) {
132 | SDL_WaitThread(controller->thread, NULL);
133 | receiver_join(&controller->receiver);
134 | }
135 |
--------------------------------------------------------------------------------
/scrcpy/app/src/util/str_util.c:
--------------------------------------------------------------------------------
1 | #include "str_util.h"
2 |
3 | #include
4 | #include
5 | #include
6 | #include
7 |
8 | #ifdef _WIN32
9 | # include
10 | # include
11 | #endif
12 |
13 | #include
14 |
15 | #include "config.h"
16 |
17 | size_t
18 | xstrncpy(char *dest, const char *src, size_t n) {
19 | size_t i;
20 | for (i = 0; i < n - 1 && src[i] != '\0'; ++i)
21 | dest[i] = src[i];
22 | if (n)
23 | dest[i] = '\0';
24 | return src[i] == '\0' ? i : n;
25 | }
26 |
27 | size_t
28 | xstrjoin(char *dst, const char *const tokens[], char sep, size_t n) {
29 | const char *const *remaining = tokens;
30 | const char *token = *remaining++;
31 | size_t i = 0;
32 | while (token) {
33 | if (i) {
34 | dst[i++] = sep;
35 | if (i == n)
36 | goto truncated;
37 | }
38 | size_t w = xstrncpy(dst + i, token, n - i);
39 | if (w >= n - i)
40 | goto truncated;
41 | i += w;
42 | token = *remaining++;
43 | }
44 | return i;
45 |
46 | truncated:
47 | dst[n - 1] = '\0';
48 | return n;
49 | }
50 |
51 | char *
52 | strquote(const char *src) {
53 | size_t len = strlen(src);
54 | char *quoted = SDL_malloc(len + 3);
55 | if (!quoted) {
56 | return NULL;
57 | }
58 | memcpy("ed[1], src, len);
59 | quoted[0] = '"';
60 | quoted[len + 1] = '"';
61 | quoted[len + 2] = '\0';
62 | return quoted;
63 | }
64 |
65 | bool
66 | parse_integer(const char *s, long *out) {
67 | char *endptr;
68 | if (*s == '\0') {
69 | return false;
70 | }
71 | errno = 0;
72 | long value = strtol(s, &endptr, 0);
73 | if (errno == ERANGE) {
74 | return false;
75 | }
76 | if (*endptr != '\0') {
77 | return false;
78 | }
79 |
80 | *out = value;
81 | return true;
82 | }
83 |
84 | bool
85 | parse_integer_with_suffix(const char *s, long *out) {
86 | char *endptr;
87 | if (*s == '\0') {
88 | return false;
89 | }
90 | errno = 0;
91 | long value = strtol(s, &endptr, 0);
92 | if (errno == ERANGE) {
93 | return false;
94 | }
95 | int mul = 1;
96 | if (*endptr != '\0') {
97 | if (s == endptr) {
98 | return false;
99 | }
100 | if ((*endptr == 'M' || *endptr == 'm') && endptr[1] == '\0') {
101 | mul = 1000000;
102 | } else if ((*endptr == 'K' || *endptr == 'k') && endptr[1] == '\0') {
103 | mul = 1000;
104 | } else {
105 | return false;
106 | }
107 | }
108 |
109 | if ((value < 0 && LONG_MIN / mul > value) ||
110 | (value > 0 && LONG_MAX / mul < value)) {
111 | return false;
112 | }
113 |
114 | *out = value * mul;
115 | return true;
116 | }
117 |
118 | size_t
119 | utf8_truncation_index(const char *utf8, size_t max_len) {
120 | size_t len = strlen(utf8);
121 | if (len <= max_len) {
122 | return len;
123 | }
124 | len = max_len;
125 | // see UTF-8 encoding
126 | while ((utf8[len] & 0x80) != 0 && (utf8[len] & 0xc0) != 0xc0) {
127 | // the next byte is not the start of a new UTF-8 codepoint
128 | // so if we would cut there, the character would be truncated
129 | len--;
130 | }
131 | return len;
132 | }
133 |
134 | #ifdef _WIN32
135 |
136 | wchar_t *
137 | utf8_to_wide_char(const char *utf8) {
138 | int len = MultiByteToWideChar(CP_UTF8, 0, utf8, -1, NULL, 0);
139 | if (!len) {
140 | return NULL;
141 | }
142 |
143 | wchar_t *wide = SDL_malloc(len * sizeof(wchar_t));
144 | if (!wide) {
145 | return NULL;
146 | }
147 |
148 | MultiByteToWideChar(CP_UTF8, 0, utf8, -1, wide, len);
149 | return wide;
150 | }
151 |
152 | char *
153 | utf8_from_wide_char(const wchar_t *ws) {
154 | int len = WideCharToMultiByte(CP_UTF8, 0, ws, -1, NULL, 0, NULL, NULL);
155 | if (!len) {
156 | return NULL;
157 | }
158 |
159 | char *utf8 = SDL_malloc(len);
160 | if (!utf8) {
161 | return NULL;
162 | }
163 |
164 | WideCharToMultiByte(CP_UTF8, 0, ws, -1, utf8, len, NULL, NULL);
165 | return utf8;
166 | }
167 |
168 | #endif
169 |
--------------------------------------------------------------------------------
/scrcpy/server/src/main/java/com/genymobile/scrcpy/ControlMessage.java:
--------------------------------------------------------------------------------
1 | package com.genymobile.scrcpy;
2 |
3 | /**
4 | * Union of all supported event types, identified by their {@code type}.
5 | */
6 | public final class ControlMessage {
7 |
8 | public static final int TYPE_INJECT_KEYCODE = 0;
9 | public static final int TYPE_INJECT_TEXT = 1;
10 | public static final int TYPE_INJECT_TOUCH_EVENT = 2;
11 | public static final int TYPE_INJECT_SCROLL_EVENT = 3;
12 | public static final int TYPE_BACK_OR_SCREEN_ON = 4;
13 | public static final int TYPE_EXPAND_NOTIFICATION_PANEL = 5;
14 | public static final int TYPE_COLLAPSE_NOTIFICATION_PANEL = 6;
15 | public static final int TYPE_GET_CLIPBOARD = 7;
16 | public static final int TYPE_SET_CLIPBOARD = 8;
17 | public static final int TYPE_SET_SCREEN_POWER_MODE = 9;
18 | public static final int TYPE_ROTATE_DEVICE = 10;
19 |
20 | private int type;
21 | private String text;
22 | private int metaState; // KeyEvent.META_*
23 | private int action; // KeyEvent.ACTION_* or MotionEvent.ACTION_* or POWER_MODE_*
24 | private int keycode; // KeyEvent.KEYCODE_*
25 | private int buttons; // MotionEvent.BUTTON_*
26 | private long pointerId;
27 | private float pressure;
28 | private Position position;
29 | private int hScroll;
30 | private int vScroll;
31 |
32 | private ControlMessage() {
33 | }
34 |
35 | public static ControlMessage createInjectKeycode(int action, int keycode, int metaState) {
36 | ControlMessage msg = new ControlMessage();
37 | msg.type = TYPE_INJECT_KEYCODE;
38 | msg.action = action;
39 | msg.keycode = keycode;
40 | msg.metaState = metaState;
41 | return msg;
42 | }
43 |
44 | public static ControlMessage createInjectText(String text) {
45 | ControlMessage msg = new ControlMessage();
46 | msg.type = TYPE_INJECT_TEXT;
47 | msg.text = text;
48 | return msg;
49 | }
50 |
51 | public static ControlMessage createInjectTouchEvent(int action, long pointerId, Position position, float pressure, int buttons) {
52 | ControlMessage msg = new ControlMessage();
53 | msg.type = TYPE_INJECT_TOUCH_EVENT;
54 | msg.action = action;
55 | msg.pointerId = pointerId;
56 | msg.pressure = pressure;
57 | msg.position = position;
58 | msg.buttons = buttons;
59 | return msg;
60 | }
61 |
62 | public static ControlMessage createInjectScrollEvent(Position position, int hScroll, int vScroll) {
63 | ControlMessage msg = new ControlMessage();
64 | msg.type = TYPE_INJECT_SCROLL_EVENT;
65 | msg.position = position;
66 | msg.hScroll = hScroll;
67 | msg.vScroll = vScroll;
68 | return msg;
69 | }
70 |
71 | public static ControlMessage createSetClipboard(String text) {
72 | ControlMessage msg = new ControlMessage();
73 | msg.type = TYPE_SET_CLIPBOARD;
74 | msg.text = text;
75 | return msg;
76 | }
77 |
78 | /**
79 | * @param mode one of the {@code Device.SCREEN_POWER_MODE_*} constants
80 | */
81 | public static ControlMessage createSetScreenPowerMode(int mode) {
82 | ControlMessage msg = new ControlMessage();
83 | msg.type = TYPE_SET_SCREEN_POWER_MODE;
84 | msg.action = mode;
85 | return msg;
86 | }
87 |
88 | public static ControlMessage createEmpty(int type) {
89 | ControlMessage msg = new ControlMessage();
90 | msg.type = type;
91 | return msg;
92 | }
93 |
94 | public int getType() {
95 | return type;
96 | }
97 |
98 | public String getText() {
99 | return text;
100 | }
101 |
102 | public int getMetaState() {
103 | return metaState;
104 | }
105 |
106 | public int getAction() {
107 | return action;
108 | }
109 |
110 | public int getKeycode() {
111 | return keycode;
112 | }
113 |
114 | public int getButtons() {
115 | return buttons;
116 | }
117 |
118 | public long getPointerId() {
119 | return pointerId;
120 | }
121 |
122 | public float getPressure() {
123 | return pressure;
124 | }
125 |
126 | public Position getPosition() {
127 | return position;
128 | }
129 |
130 | public int getHScroll() {
131 | return hScroll;
132 | }
133 |
134 | public int getVScroll() {
135 | return vScroll;
136 | }
137 | }
138 |
--------------------------------------------------------------------------------
/scrcpy/server/src/main/java/com/genymobile/scrcpy/wrappers/WindowManager.java:
--------------------------------------------------------------------------------
1 | package com.genymobile.scrcpy.wrappers;
2 |
3 | import com.genymobile.scrcpy.Ln;
4 |
5 | import android.os.IInterface;
6 | import android.view.IRotationWatcher;
7 |
8 | import java.lang.reflect.InvocationTargetException;
9 | import java.lang.reflect.Method;
10 |
11 | public final class WindowManager {
12 | private final IInterface manager;
13 | private Method getRotationMethod;
14 | private Method freezeRotationMethod;
15 | private Method isRotationFrozenMethod;
16 | private Method thawRotationMethod;
17 |
18 | public WindowManager(IInterface manager) {
19 | this.manager = manager;
20 | }
21 |
22 | private Method getGetRotationMethod() throws NoSuchMethodException {
23 | if (getRotationMethod == null) {
24 | Class> cls = manager.getClass();
25 | try {
26 | // method changed since this commit:
27 | // https://android.googlesource.com/platform/frameworks/base/+/8ee7285128c3843401d4c4d0412cd66e86ba49e3%5E%21/#F2
28 | getRotationMethod = cls.getMethod("getDefaultDisplayRotation");
29 | } catch (NoSuchMethodException e) {
30 | // old version
31 | getRotationMethod = cls.getMethod("getRotation");
32 | }
33 | }
34 | return getRotationMethod;
35 | }
36 |
37 | private Method getFreezeRotationMethod() throws NoSuchMethodException {
38 | if (freezeRotationMethod == null) {
39 | freezeRotationMethod = manager.getClass().getMethod("freezeRotation", int.class);
40 | }
41 | return freezeRotationMethod;
42 | }
43 |
44 | private Method getIsRotationFrozenMethod() throws NoSuchMethodException {
45 | if (isRotationFrozenMethod == null) {
46 | isRotationFrozenMethod = manager.getClass().getMethod("isRotationFrozen");
47 | }
48 | return isRotationFrozenMethod;
49 | }
50 |
51 | private Method getThawRotationMethod() throws NoSuchMethodException {
52 | if (thawRotationMethod == null) {
53 | thawRotationMethod = manager.getClass().getMethod("thawRotation");
54 | }
55 | return thawRotationMethod;
56 | }
57 |
58 | public int getRotation() {
59 | try {
60 | Method method = getGetRotationMethod();
61 | return (int) method.invoke(manager);
62 | } catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException e) {
63 | Ln.e("Could not invoke method", e);
64 | return 0;
65 | }
66 | }
67 |
68 | public void freezeRotation(int rotation) {
69 | try {
70 | Method method = getFreezeRotationMethod();
71 | method.invoke(manager, rotation);
72 | } catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException e) {
73 | Ln.e("Could not invoke method", e);
74 | }
75 | }
76 |
77 | public boolean isRotationFrozen() {
78 | try {
79 | Method method = getIsRotationFrozenMethod();
80 | return (boolean) method.invoke(manager);
81 | } catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException e) {
82 | Ln.e("Could not invoke method", e);
83 | return false;
84 | }
85 | }
86 |
87 | public void thawRotation() {
88 | try {
89 | Method method = getThawRotationMethod();
90 | method.invoke(manager);
91 | } catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException e) {
92 | Ln.e("Could not invoke method", e);
93 | }
94 | }
95 |
96 | public void registerRotationWatcher(IRotationWatcher rotationWatcher) {
97 | try {
98 | Class> cls = manager.getClass();
99 | try {
100 | // display parameter added since this commit:
101 | // https://android.googlesource.com/platform/frameworks/base/+/35fa3c26adcb5f6577849fd0df5228b1f67cf2c6%5E%21/#F1
102 | cls.getMethod("watchRotation", IRotationWatcher.class, int.class).invoke(manager, rotationWatcher, 0);
103 | } catch (NoSuchMethodException e) {
104 | // old version
105 | cls.getMethod("watchRotation", IRotationWatcher.class).invoke(manager, rotationWatcher);
106 | }
107 | } catch (Exception e) {
108 | throw new AssertionError(e);
109 | }
110 | }
111 | }
112 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # scrcpy-opencv
2 | [Scrcpy](https://github.com/Genymobile/scrcpy) is a tool that streams your android screen to your computer, and allows you to send mouse or keyboard input back to the android device.
3 | This project modifies scrcpy to also send back automatically generated input by passing the frame on to the OpenCV computer vision library.
4 | To see scrcpy-opencv in action, check out this [Youtube video](https://www.youtube.com/watch?v=4Ikzw7TttuU)!
5 |
6 | 
7 |
8 | # Run it yourself
9 | ## Building scrcpy
10 | Install [dependencies to build scrcpy](https://github.com/Genymobile/scrcpy/blob/master/BUILD.md):
11 | runtime dependencies
12 | `sudo apt install ffmpeg libsdl2-2.0-0 adb`
13 | client build dependencies
14 | `sudo apt install gcc git pkg-config meson ninja-build libavcodec-dev libavformat-dev libavutil-dev libsdl2-dev`
15 | server build dependencies
16 | `sudo apt install openjdk-8-jdk`
17 |
18 | Some more dependencies you might not have installed:
19 | `sudo apt install g++ cmake libswscale-dev `
20 |
21 | Clone this repository:
22 | `git clone https://github.com/RobbertH/scrcpy-opencv.git`
23 | Go into the folder you just cloned:
24 | `cd scrcpy-opencv`
25 | Save the directory of the prebuilt server in an environment variable, we'll need this later:
26 | `PREBUILT_SERVER_PATH=$PWD/prebuilt`
27 | Change directory to the desktop part of the project, called `scrcpy`,
28 | `cd scrcpy`
29 | then run the following command, making sure $PREBUILT_SERVER_PATH contains the prebuilt scrcpy server:
30 | `meson x --buildtype release --strip -Db_lto=true -Dprebuilt_server=$PREBUILT_SERVER_PATH/scrcpy-server-v1.12.1`
31 | to configure the build, and then
32 | `ninja -Cx`
33 | to build the application, so you can finally
34 | `./run x`
35 | to run the modified scrcpy.
36 |
37 | Also make sure your android phone is plugged in over USB and has `adb` enabled.
38 |
39 | ## Building OpenCV
40 | If the `meson` step above fails because of OpenCV, you can install it as follows. It's a slight variation on [the OpenCV tutorial](https://docs.opencv.org/4.5.0/d0/d3d/tutorial_general_install.html).
41 | `wget https://github.com/opencv/opencv/archive/4.4.0.zip`
42 | `unzip 4.4.0.zip`
43 | `cd opencv-4.4.0`
44 | `mkdir build`
45 | `cd build`
46 | `cmake -D OPENCV_GENERATE_PKGCONFIG=ON -D CMAKE_BUILD_TYPE=Release -D CMAKE_INSTALL_PREFIX=/usr/local ..`
47 | `make -j3`
48 | `sudo make install`
49 | `pkg-config --cflags --libs opencv4`
50 | `sudo ldconfig -v` to refresh dynamic linker run-time bindings
51 |
52 | After this, meson should find opencv4 and you're good to go.
53 |
54 | # Making changes
55 | When making changes, make sure to run `meson reconfigure` and rebuild using `ninja -Cx`. Alternatively, you could delete the `x` directory and run the commands above again.
56 |
57 | The files that this repository changes versus the original scrcpy repository are the following:
58 | * added: `app/src/opencv_injection.cpp` This is where all new code is written. It is recommended that you alter this file.
59 | * added: `app/src/opencv_injection.hpp`
60 | * modified: `app/src/scrcpy.c` Defines a method that can send a tap to the phone (used in opencv_injection.cpp)
61 | * modified: `app/src/scrcpy.h`
62 | * modified: `app/src/screen.c` This is where the 'hook' is that sends the AVFrame to the opencv_injection function
63 | * modified: `app/meson.build` to add dependencies and source files to be compiled
64 | * modified: `meson.build` to enable C++ compilation instead of only C
65 |
66 | # Background and context
67 | I wanted to win a soccer game on android, where you have to tap a ball to keep it in the air.
68 | To do so, the 'AVFrame' used in scrcpy is converted to an OpenCV 'mat' so that OpenCV's image processing functions can be run on it.
69 | Then, a circle the size of the football is extracted, using "HoughCircles".
70 | Lastly, an input tap is sent back to the android device, in the center of that circle.
71 |
72 | The main modifications to scrcpy include:
73 | * Modifying the meson config files to compile both scrcpy (C) and the additional functions (C++) that depend on OpenCV (C++).
74 | * Converting the scrcpy image format to one that OpenCV can understand.
75 | * Writing the additional functions to use OpenCV and send input to the android device.
76 |
77 | Since most work done was part of "making it work" rather than writing the actual code, I figured this might help other people in achieving similar goals.
78 | The concrete case of the soccer game is just one example of what can be achieved with this powerful tool:
79 | any OpenCV function can be run to produce input to the android device, based on the frames.
80 |
81 | If you want to read more, there is a [blog post](https://robberthofman.com/projects/2020/03/30/hacking-scrcpy-to-win-fb-soccer-game/).
82 | For questions, simply open an issue.
83 |
--------------------------------------------------------------------------------
/scrcpy/server/src/main/java/com/genymobile/scrcpy/DesktopConnection.java:
--------------------------------------------------------------------------------
1 | package com.genymobile.scrcpy;
2 |
3 | import android.net.LocalServerSocket;
4 | import android.net.LocalSocket;
5 | import android.net.LocalSocketAddress;
6 |
7 | import java.io.Closeable;
8 | import java.io.FileDescriptor;
9 | import java.io.IOException;
10 | import java.io.InputStream;
11 | import java.io.OutputStream;
12 | import java.nio.charset.StandardCharsets;
13 |
14 | public final class DesktopConnection implements Closeable {
15 |
16 | private static final int DEVICE_NAME_FIELD_LENGTH = 64;
17 |
18 | private static final String SOCKET_NAME = "scrcpy";
19 |
20 | private final LocalSocket videoSocket;
21 | private final FileDescriptor videoFd;
22 |
23 | private final LocalSocket controlSocket;
24 | private final InputStream controlInputStream;
25 | private final OutputStream controlOutputStream;
26 |
27 | private final ControlMessageReader reader = new ControlMessageReader();
28 | private final DeviceMessageWriter writer = new DeviceMessageWriter();
29 |
30 | private DesktopConnection(LocalSocket videoSocket, LocalSocket controlSocket) throws IOException {
31 | this.videoSocket = videoSocket;
32 | this.controlSocket = controlSocket;
33 | controlInputStream = controlSocket.getInputStream();
34 | controlOutputStream = controlSocket.getOutputStream();
35 | videoFd = videoSocket.getFileDescriptor();
36 | }
37 |
38 | private static LocalSocket connect(String abstractName) throws IOException {
39 | LocalSocket localSocket = new LocalSocket();
40 | localSocket.connect(new LocalSocketAddress(abstractName));
41 | return localSocket;
42 | }
43 |
44 | public static DesktopConnection open(Device device, boolean tunnelForward) throws IOException {
45 | LocalSocket videoSocket;
46 | LocalSocket controlSocket;
47 | if (tunnelForward) {
48 | LocalServerSocket localServerSocket = new LocalServerSocket(SOCKET_NAME);
49 | try {
50 | videoSocket = localServerSocket.accept();
51 | // send one byte so the client may read() to detect a connection error
52 | videoSocket.getOutputStream().write(0);
53 | try {
54 | controlSocket = localServerSocket.accept();
55 | } catch (IOException | RuntimeException e) {
56 | videoSocket.close();
57 | throw e;
58 | }
59 | } finally {
60 | localServerSocket.close();
61 | }
62 | } else {
63 | videoSocket = connect(SOCKET_NAME);
64 | try {
65 | controlSocket = connect(SOCKET_NAME);
66 | } catch (IOException | RuntimeException e) {
67 | videoSocket.close();
68 | throw e;
69 | }
70 | }
71 |
72 | DesktopConnection connection = new DesktopConnection(videoSocket, controlSocket);
73 | Size videoSize = device.getScreenInfo().getVideoSize();
74 | connection.send(Device.getDeviceName(), videoSize.getWidth(), videoSize.getHeight());
75 | return connection;
76 | }
77 |
78 | public void close() throws IOException {
79 | videoSocket.shutdownInput();
80 | videoSocket.shutdownOutput();
81 | videoSocket.close();
82 | controlSocket.shutdownInput();
83 | controlSocket.shutdownOutput();
84 | controlSocket.close();
85 | }
86 |
87 | @SuppressWarnings("checkstyle:MagicNumber")
88 | private void send(String deviceName, int width, int height) throws IOException {
89 | byte[] buffer = new byte[DEVICE_NAME_FIELD_LENGTH + 4];
90 |
91 | byte[] deviceNameBytes = deviceName.getBytes(StandardCharsets.UTF_8);
92 | int len = StringUtils.getUtf8TruncationIndex(deviceNameBytes, DEVICE_NAME_FIELD_LENGTH - 1);
93 | System.arraycopy(deviceNameBytes, 0, buffer, 0, len);
94 | // byte[] are always 0-initialized in java, no need to set '\0' explicitly
95 |
96 | buffer[DEVICE_NAME_FIELD_LENGTH] = (byte) (width >> 8);
97 | buffer[DEVICE_NAME_FIELD_LENGTH + 1] = (byte) width;
98 | buffer[DEVICE_NAME_FIELD_LENGTH + 2] = (byte) (height >> 8);
99 | buffer[DEVICE_NAME_FIELD_LENGTH + 3] = (byte) height;
100 | IO.writeFully(videoFd, buffer, 0, buffer.length);
101 | }
102 |
103 | public FileDescriptor getVideoFd() {
104 | return videoFd;
105 | }
106 |
107 | public ControlMessage receiveControlMessage() throws IOException {
108 | ControlMessage msg = reader.next();
109 | while (msg == null) {
110 | reader.readFrom(controlInputStream);
111 | msg = reader.next();
112 | }
113 | return msg;
114 | }
115 |
116 | public void sendDeviceMessage(DeviceMessage msg) throws IOException {
117 | writer.writeTo(msg, controlOutputStream);
118 | }
119 | }
120 |
--------------------------------------------------------------------------------
/scrcpy/app/src/fps_counter.c:
--------------------------------------------------------------------------------
1 | #include "fps_counter.h"
2 |
3 | #include
4 | #include
5 |
6 | #include "config.h"
7 | #include "util/lock.h"
8 | #include "util/log.h"
9 |
10 | #define FPS_COUNTER_INTERVAL_MS 1000
11 |
12 | bool
13 | fps_counter_init(struct fps_counter *counter) {
14 | counter->mutex = SDL_CreateMutex();
15 | if (!counter->mutex) {
16 | return false;
17 | }
18 |
19 | counter->state_cond = SDL_CreateCond();
20 | if (!counter->state_cond) {
21 | SDL_DestroyMutex(counter->mutex);
22 | return false;
23 | }
24 |
25 | counter->thread = NULL;
26 | SDL_AtomicSet(&counter->started, 0);
27 | // no need to initialize the other fields, they are unused until started
28 |
29 | return true;
30 | }
31 |
32 | void
33 | fps_counter_destroy(struct fps_counter *counter) {
34 | SDL_DestroyCond(counter->state_cond);
35 | SDL_DestroyMutex(counter->mutex);
36 | }
37 |
38 | // must be called with mutex locked
39 | static void
40 | display_fps(struct fps_counter *counter) {
41 | unsigned rendered_per_second =
42 | counter->nr_rendered * 1000 / FPS_COUNTER_INTERVAL_MS;
43 | if (counter->nr_skipped) {
44 | LOGI("%u fps (+%u frames skipped)", rendered_per_second,
45 | counter->nr_skipped);
46 | } else {
47 | LOGI("%u fps", rendered_per_second);
48 | }
49 | }
50 |
51 | // must be called with mutex locked
52 | static void
53 | check_interval_expired(struct fps_counter *counter, uint32_t now) {
54 | if (now < counter->next_timestamp) {
55 | return;
56 | }
57 |
58 | display_fps(counter);
59 | counter->nr_rendered = 0;
60 | counter->nr_skipped = 0;
61 | // add a multiple of the interval
62 | uint32_t elapsed_slices =
63 | (now - counter->next_timestamp) / FPS_COUNTER_INTERVAL_MS + 1;
64 | counter->next_timestamp += FPS_COUNTER_INTERVAL_MS * elapsed_slices;
65 | }
66 |
67 | static int
68 | run_fps_counter(void *data) {
69 | struct fps_counter *counter = data;
70 |
71 | mutex_lock(counter->mutex);
72 | while (!counter->interrupted) {
73 | while (!counter->interrupted && !SDL_AtomicGet(&counter->started)) {
74 | cond_wait(counter->state_cond, counter->mutex);
75 | }
76 | while (!counter->interrupted && SDL_AtomicGet(&counter->started)) {
77 | uint32_t now = SDL_GetTicks();
78 | check_interval_expired(counter, now);
79 |
80 | assert(counter->next_timestamp > now);
81 | uint32_t remaining = counter->next_timestamp - now;
82 |
83 | // ignore the reason (timeout or signaled), we just loop anyway
84 | cond_wait_timeout(counter->state_cond, counter->mutex, remaining);
85 | }
86 | }
87 | mutex_unlock(counter->mutex);
88 | return 0;
89 | }
90 |
91 | bool
92 | fps_counter_start(struct fps_counter *counter) {
93 | mutex_lock(counter->mutex);
94 | counter->next_timestamp = SDL_GetTicks() + FPS_COUNTER_INTERVAL_MS;
95 | counter->nr_rendered = 0;
96 | counter->nr_skipped = 0;
97 | mutex_unlock(counter->mutex);
98 |
99 | SDL_AtomicSet(&counter->started, 1);
100 | cond_signal(counter->state_cond);
101 |
102 | // counter->thread is always accessed from the same thread, no need to lock
103 | if (!counter->thread) {
104 | counter->thread =
105 | SDL_CreateThread(run_fps_counter, "fps counter", counter);
106 | if (!counter->thread) {
107 | LOGE("Could not start FPS counter thread");
108 | return false;
109 | }
110 | }
111 |
112 | return true;
113 | }
114 |
115 | void
116 | fps_counter_stop(struct fps_counter *counter) {
117 | SDL_AtomicSet(&counter->started, 0);
118 | cond_signal(counter->state_cond);
119 | }
120 |
121 | bool
122 | fps_counter_is_started(struct fps_counter *counter) {
123 | return SDL_AtomicGet(&counter->started);
124 | }
125 |
126 | void
127 | fps_counter_interrupt(struct fps_counter *counter) {
128 | if (!counter->thread) {
129 | return;
130 | }
131 |
132 | mutex_lock(counter->mutex);
133 | counter->interrupted = true;
134 | mutex_unlock(counter->mutex);
135 | // wake up blocking wait
136 | cond_signal(counter->state_cond);
137 | }
138 |
139 | void
140 | fps_counter_join(struct fps_counter *counter) {
141 | if (counter->thread) {
142 | SDL_WaitThread(counter->thread, NULL);
143 | }
144 | }
145 |
146 | void
147 | fps_counter_add_rendered_frame(struct fps_counter *counter) {
148 | if (!SDL_AtomicGet(&counter->started)) {
149 | return;
150 | }
151 |
152 | mutex_lock(counter->mutex);
153 | uint32_t now = SDL_GetTicks();
154 | check_interval_expired(counter, now);
155 | ++counter->nr_rendered;
156 | mutex_unlock(counter->mutex);
157 | }
158 |
159 | void
160 | fps_counter_add_skipped_frame(struct fps_counter *counter) {
161 | if (!SDL_AtomicGet(&counter->started)) {
162 | return;
163 | }
164 |
165 | mutex_lock(counter->mutex);
166 | uint32_t now = SDL_GetTicks();
167 | check_interval_expired(counter, now);
168 | ++counter->nr_skipped;
169 | mutex_unlock(counter->mutex);
170 | }
171 |
--------------------------------------------------------------------------------
/scrcpy/server/src/main/java/com/genymobile/scrcpy/wrappers/SurfaceControl.java:
--------------------------------------------------------------------------------
1 | package com.genymobile.scrcpy.wrappers;
2 |
3 | import com.genymobile.scrcpy.Ln;
4 |
5 | import android.annotation.SuppressLint;
6 | import android.graphics.Rect;
7 | import android.os.Build;
8 | import android.os.IBinder;
9 | import android.view.Surface;
10 |
11 | import java.lang.reflect.InvocationTargetException;
12 | import java.lang.reflect.Method;
13 |
14 | @SuppressLint("PrivateApi")
15 | public final class SurfaceControl {
16 |
17 | private static final Class> CLASS;
18 |
19 | // see
20 | public static final int POWER_MODE_OFF = 0;
21 | public static final int POWER_MODE_NORMAL = 2;
22 |
23 | static {
24 | try {
25 | CLASS = Class.forName("android.view.SurfaceControl");
26 | } catch (ClassNotFoundException e) {
27 | throw new AssertionError(e);
28 | }
29 | }
30 |
31 | private static Method getBuiltInDisplayMethod;
32 | private static Method setDisplayPowerModeMethod;
33 |
34 | private SurfaceControl() {
35 | // only static methods
36 | }
37 |
38 | public static void openTransaction() {
39 | try {
40 | CLASS.getMethod("openTransaction").invoke(null);
41 | } catch (Exception e) {
42 | throw new AssertionError(e);
43 | }
44 | }
45 |
46 | public static void closeTransaction() {
47 | try {
48 | CLASS.getMethod("closeTransaction").invoke(null);
49 | } catch (Exception e) {
50 | throw new AssertionError(e);
51 | }
52 | }
53 |
54 | public static void setDisplayProjection(IBinder displayToken, int orientation, Rect layerStackRect, Rect displayRect) {
55 | try {
56 | CLASS.getMethod("setDisplayProjection", IBinder.class, int.class, Rect.class, Rect.class)
57 | .invoke(null, displayToken, orientation, layerStackRect, displayRect);
58 | } catch (Exception e) {
59 | throw new AssertionError(e);
60 | }
61 | }
62 |
63 | public static void setDisplayLayerStack(IBinder displayToken, int layerStack) {
64 | try {
65 | CLASS.getMethod("setDisplayLayerStack", IBinder.class, int.class).invoke(null, displayToken, layerStack);
66 | } catch (Exception e) {
67 | throw new AssertionError(e);
68 | }
69 | }
70 |
71 | public static void setDisplaySurface(IBinder displayToken, Surface surface) {
72 | try {
73 | CLASS.getMethod("setDisplaySurface", IBinder.class, Surface.class).invoke(null, displayToken, surface);
74 | } catch (Exception e) {
75 | throw new AssertionError(e);
76 | }
77 | }
78 |
79 | public static IBinder createDisplay(String name, boolean secure) {
80 | try {
81 | return (IBinder) CLASS.getMethod("createDisplay", String.class, boolean.class).invoke(null, name, secure);
82 | } catch (Exception e) {
83 | throw new AssertionError(e);
84 | }
85 | }
86 |
87 | private static Method getGetBuiltInDisplayMethod() throws NoSuchMethodException {
88 | if (getBuiltInDisplayMethod == null) {
89 | // the method signature has changed in Android Q
90 | //
91 | if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
92 | getBuiltInDisplayMethod = CLASS.getMethod("getBuiltInDisplay", int.class);
93 | } else {
94 | getBuiltInDisplayMethod = CLASS.getMethod("getInternalDisplayToken");
95 | }
96 | }
97 | return getBuiltInDisplayMethod;
98 | }
99 |
100 | public static IBinder getBuiltInDisplay() {
101 |
102 | try {
103 | Method method = getGetBuiltInDisplayMethod();
104 | if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
105 | // call getBuiltInDisplay(0)
106 | return (IBinder) method.invoke(null, 0);
107 | }
108 |
109 | // call getInternalDisplayToken()
110 | return (IBinder) method.invoke(null);
111 | } catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException e) {
112 | Ln.e("Could not invoke method", e);
113 | return null;
114 | }
115 | }
116 |
117 | private static Method getSetDisplayPowerModeMethod() throws NoSuchMethodException {
118 | if (setDisplayPowerModeMethod == null) {
119 | setDisplayPowerModeMethod = CLASS.getMethod("setDisplayPowerMode", IBinder.class, int.class);
120 | }
121 | return setDisplayPowerModeMethod;
122 | }
123 |
124 | public static void setDisplayPowerMode(IBinder displayToken, int mode) {
125 | try {
126 | Method method = getSetDisplayPowerModeMethod();
127 | method.invoke(null, displayToken, mode);
128 | } catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException e) {
129 | Ln.e("Could not invoke method", e);
130 | }
131 | }
132 |
133 | public static void destroyDisplay(IBinder displayToken) {
134 | try {
135 | CLASS.getMethod("destroyDisplay", IBinder.class).invoke(null, displayToken);
136 | } catch (Exception e) {
137 | throw new AssertionError(e);
138 | }
139 | }
140 | }
141 |
--------------------------------------------------------------------------------
/scrcpy/app/src/opencv_injection.cpp:
--------------------------------------------------------------------------------
1 | #include "opencv_injection.hpp"
2 | #include "input_manager.h"
3 | #include "util/log.h"
4 | #include "video_buffer.h" // AVFrame
5 |
6 | #include
7 | #include
8 | #include
9 | #include
10 | #include
11 |
12 | extern "C" {
13 | #include
14 | #include // av_image_fill_arrays
15 | #include
16 | #include "scrcpy.h" // opencv_send_tap(SDL_TouchFingerEvent event) as defined in scrcpy.c
17 | }
18 |
19 |
20 | #define DRAW_CIRCLES false // Set to true only for debugging. Slows everything down heavily.
21 | #define DEBUG_MESSAGES_ENABLED true
22 |
23 | using namespace cv;
24 | using namespace std;
25 |
26 | int nb_frame = 0;
27 |
28 | /*
29 | * opencv_injection receives an AVFrame and converts it to an OpenCV Mat.
30 | * It then calls further processing of that OpenCV Mat.
31 | * */
32 | void opencv_injection(AVFrame *frame) {
33 | if (DEBUG_MESSAGES_ENABLED) {
34 | LOGI("OpenCV injection function called");
35 | }
36 |
37 | // Converting from AVFrame to OpenCV mat
38 | // based on https://answers.opencv.org/question/36948/cvmat-to-avframe/
39 | AVFrame dst;
40 | cv::Mat m;
41 |
42 | memset(&dst, 0, sizeof(dst));
43 |
44 | int w = frame->width, h = frame->height;
45 | int dst_width = w/4, dst_height = h/4; // less pixels means running faster
46 | m = cv::Mat(dst_height, dst_width, CV_8UC3);
47 | dst.data[0] = (uint8_t *)m.data;
48 | av_image_fill_arrays((&dst)->data, (&dst)->linesize, dst.data[0], AV_PIX_FMT_BGR24, dst_width, dst_height, 1);
49 |
50 | struct SwsContext *convert_ctx = NULL;
51 | enum AVPixelFormat src_pixfmt = (enum AVPixelFormat)frame->format;
52 | enum AVPixelFormat dst_pixfmt = AV_PIX_FMT_BGR24;
53 | convert_ctx = sws_getContext(w, h, src_pixfmt, w/4, h/4, dst_pixfmt,
54 | SWS_FAST_BILINEAR, NULL, NULL, NULL);
55 | sws_scale(convert_ctx, frame->data, frame->linesize, 0, h,
56 | dst.data, dst.linesize);
57 | sws_freeContext(convert_ctx);
58 |
59 | //imshow("frame", m); // Show the OpenCV Mat frame. Slows everything down.
60 | //waitKey(10); // needed to display frame using imshow.
61 |
62 | if (nb_frame % 1 == 0){ // Optionally process only one in n frames.
63 | extract_circle_and_tap(m);
64 | }
65 | nb_frame++;
66 |
67 | }
68 |
69 |
70 | /*
71 | * extract_circle_and_tap extracts a circle using the OpenCV HoughCircles method,
72 | * then sends a tap at a well chosen location.
73 | */
74 |
75 | // Previous ball position
76 | float previous_x = 0.5;
77 | float previous_y = 0.9;
78 | float speed_x;
79 | float speed_y;
80 |
81 | void extract_circle_and_tap(Mat src) {
82 | if (DEBUG_MESSAGES_ENABLED) {
83 | LOGI("extract_circle_and_tap called");
84 | }
85 |
86 | // Check if image is loaded fine
87 | if(src.empty()){
88 | printf(" Error opening image\n");
89 | return; // Exit
90 | }
91 |
92 | // Convert to grayscale and apply blurring
93 | Mat gray;
94 | cvtColor(src, gray, COLOR_BGR2GRAY);
95 | medianBlur(gray, gray, 5);
96 | vector circles;
97 |
98 | // Extract circles
99 | HoughCircles(gray, circles, HOUGH_GRADIENT, 1,
100 | gray.rows/16, // change this value to detect circles with different distances to each other
101 | 100, 30, 30, 70 // change the last two parameters
102 | // (min_radius & max_radius) to detect larger circles
103 | );
104 |
105 | // Calculate where to tap
106 | SDL_TouchFingerEvent event; // This is the event we will use later to tap at a specific coordinate
107 |
108 | for( size_t i = 0; i < circles.size(); i++ ) // Should just be one circle
109 | {
110 | // Store circle information in different format
111 | Vec3i c = circles[i]; // x, y, radius
112 | Point center = Point(c[0], c[1]);
113 | int radius = c[2];
114 |
115 | // Estimate speed for trajectory prediction
116 | speed_x = c[0] - previous_x; // divided by nb frames but that's fixed anyway
117 | speed_y = c[1] - previous_y;
118 | previous_x = c[0];
119 | previous_y = c[1];
120 |
121 | if (DRAW_CIRCLES) {
122 | // Optionally draw circle on 'src', to display
123 | circle( src, center, 1, Scalar(0,100,100), 3, LINE_AA);
124 | // circle outline
125 | circle( src, center, radius, Scalar(255,0,255), 3, LINE_AA);
126 | }
127 |
128 | double opencv_mat_width = src.size().width;
129 | double opencv_mat_height = src.size().height;
130 |
131 | float timedelta = 6; // how much into the future we want to predict
132 | event.x = (c[0]+speed_x*timedelta)/opencv_mat_width;
133 | // int vertical_offset = 40; // y axis starts at top. Add if you want to tap lower.
134 | event.y = (c[1]+speed_y*timedelta)/opencv_mat_height;
135 | }
136 |
137 | if (DRAW_CIRCLES) {
138 | imshow("detected circles", src); // show the frame with detected circle. Slows everything down.
139 | waitKey(); // needed to display frame using imshow. Press a key to advance to the next frame.
140 | }
141 |
142 | // Set some event parameters.
143 | event.fingerId = POINTER_ID_MOUSE;
144 | event.pressure = 1.f;
145 |
146 | // Actual tap
147 | if (event.y > 0.2) { // Don't tap the uppermost pixels, to avoid the ball going out of frame. Y axis starts at top.
148 | if (DEBUG_MESSAGES_ENABLED) {
149 | LOGI("extract_circle_and_tap: Tapping at %f %f ", event.x , event.y);
150 | }
151 | event.type = SDL_FINGERDOWN;
152 | opencv_injection_send_tap(event);
153 | event.type = SDL_FINGERUP;
154 | opencv_injection_send_tap(event);
155 | }
156 |
157 | }
158 |
--------------------------------------------------------------------------------
/scrcpy/FAQ.md:
--------------------------------------------------------------------------------
1 | # Frequently Asked Questions
2 |
3 | Here are the common reported problems and their status.
4 |
5 |
6 | ## `adb` issues
7 |
8 | `scrcpy` execute `adb` commands to initialize the connection with the device. If
9 | `adb` fails, then scrcpy will not work.
10 |
11 | In that case, it will print this error:
12 |
13 | > ERROR: "adb push" returned with value 1
14 |
15 | This is typically not a bug in _scrcpy_, but a problem in your environment.
16 |
17 | To find out the cause, execute:
18 |
19 | ```bash
20 | adb devices
21 | ```
22 |
23 | ### `adb` not found
24 |
25 | You need `adb` accessible from your `PATH`.
26 |
27 | On Windows, the current directory is in your `PATH`, and `adb.exe` is included
28 | in the release, so it should work out-of-the-box.
29 |
30 |
31 | ### Device unauthorized
32 |
33 | Check [stackoverflow][device-unauthorized].
34 |
35 | [device-unauthorized]: https://stackoverflow.com/questions/23081263/adb-android-device-unauthorized
36 |
37 |
38 | ### Device not detected
39 |
40 | If your device is not detected, you may need some [drivers] (on Windows).
41 |
42 | [drivers]: https://developer.android.com/studio/run/oem-usb.html
43 |
44 |
45 | ### Several devices connected
46 |
47 | If several devices are connected, you will encounter this error:
48 |
49 | > adb: error: failed to get feature set: more than one device/emulator
50 |
51 | the identifier of the device you want to mirror must be provided:
52 |
53 | ```bash
54 | scrcpy -s 01234567890abcdef
55 | ```
56 |
57 | Note that if your device is connected over TCP/IP, you'll get this message:
58 |
59 | > adb: error: more than one device/emulator
60 | > ERROR: "adb reverse" returned with value 1
61 | > WARN: 'adb reverse' failed, fallback to 'adb forward'
62 |
63 | This is expected (due to a bug on old Android versions, see [#5]), but in that
64 | case, scrcpy fallbacks to a different method, which should work.
65 |
66 | [#5]: https://github.com/Genymobile/scrcpy/issues/5
67 |
68 |
69 | ### Conflicts between adb versions
70 |
71 | > adb server version (41) doesn't match this client (39); killing...
72 |
73 | This error occurs when you use several `adb` versions simultaneously. You must
74 | find the program using a different `adb` version, and use the same `adb` version
75 | everywhere.
76 |
77 | You could overwrite the `adb` binary in the other program, or ask _scrcpy_ to
78 | use a specific `adb` binary, by setting the `ADB` environment variable:
79 |
80 | ```bash
81 | set ADB=/path/to/your/adb
82 | scrcpy
83 | ```
84 |
85 |
86 | ### Device disconnected
87 |
88 | If _scrcpy_ stops itself with the warning "Device disconnected", then the
89 | `adb` connection has been closed.
90 |
91 | Try with another USB cable or plug it into another USB port. See [#281] and
92 | [#283].
93 |
94 | [#281]: https://github.com/Genymobile/scrcpy/issues/281
95 | [#283]: https://github.com/Genymobile/scrcpy/issues/283
96 |
97 |
98 |
99 | ## Control issues
100 |
101 | ### Mouse and keyboard do not work
102 |
103 | On some devices, you may need to enable an option to allow [simulating input].
104 | In developer options, enable:
105 |
106 | > **USB debugging (Security settings)**
107 | > _Allow granting permissions and simulating input via USB debugging_
108 |
109 | [simulating input]: https://github.com/Genymobile/scrcpy/issues/70#issuecomment-373286323
110 |
111 |
112 | ### Mouse clicks at wrong location
113 |
114 | On MacOS, with HiDPI support and multiple screens, input location are wrongly
115 | scaled. See [#15].
116 |
117 | [#15]: https://github.com/Genymobile/scrcpy/issues/15
118 |
119 | Open _scrcpy_ directly on the monitor you use it.
120 |
121 |
122 | ### Special characters do not work
123 |
124 | Injecting text input is [limited to ASCII characters][text-input]. A trick
125 | allows to also inject some [accented characters][accented-characters], but
126 | that's all. See [#37].
127 |
128 | [text-input]: https://github.com/Genymobile/scrcpy/issues?q=is%3Aopen+is%3Aissue+label%3Aunicode
129 | [accented-characters]: https://blog.rom1v.com/2018/03/introducing-scrcpy/#handle-accented-characters
130 | [#37]: https://github.com/Genymobile/scrcpy/issues/37
131 |
132 |
133 | ## Client issues
134 |
135 | ### The quality is low on HiDPI display
136 |
137 | On Windows, you may need to configure the [scaling behavior].
138 |
139 | > `scrcpy.exe` > Properties > Compatibility > Change high DPI settings >
140 | > Override high DPI scaling behavior > Scaling performed by: _Application_.
141 |
142 | [scaling behavior]: https://github.com/Genymobile/scrcpy/issues/40#issuecomment-424466723
143 |
144 | If your computer definition is far smaller than your screen, then you'll get
145 | poor quality. See [#40].
146 |
147 | [#40]: https://github.com/Genymobile/scrcpy/issues/40
148 |
149 |
150 | ### KWin compositor crashes
151 |
152 | On Plasma Desktop, compositor is disabled while _scrcpy_ is running.
153 |
154 | As a workaround, [disable "Block compositing"][kwin].
155 |
156 | [kwin]: https://github.com/Genymobile/scrcpy/issues/114#issuecomment-378778613
157 |
158 |
159 | ## Crashes
160 |
161 | ### Exception
162 |
163 | There may be many reasons. One common cause is that the hardware encoder of your
164 | device is not able to encode at the given definition:
165 |
166 | > ```
167 | > ERROR: Exception on thread Thread[main,5,main]
168 | > android.media.MediaCodec$CodecException: Error 0xfffffc0e
169 | > ...
170 | > Exit due to uncaughtException in main thread:
171 | > ERROR: Could not open video stream
172 | > INFO: Initial texture: 1080x2336
173 | > ```
174 |
175 | or
176 |
177 | > ```
178 | > ERROR: Exception on thread Thread[main,5,main]
179 | > java.lang.IllegalStateException
180 | > at android.media.MediaCodec.native_dequeueOutputBuffer(Native Method)
181 | > ```
182 |
183 | Just try with a lower definition:
184 |
185 | ```
186 | scrcpy -m 1920
187 | scrcpy -m 1024
188 | scrcpy -m 800
189 | ```
190 |
--------------------------------------------------------------------------------
/scrcpy/app/meson.build:
--------------------------------------------------------------------------------
1 | src = [
2 | 'src/main.c',
3 | 'src/cli.c',
4 | 'src/command.c',
5 | 'src/control_msg.c',
6 | 'src/controller.c',
7 | 'src/decoder.c',
8 | 'src/device.c',
9 | 'src/device_msg.c',
10 | 'src/event_converter.c',
11 | 'src/file_handler.c',
12 | 'src/fps_counter.c',
13 | 'src/input_manager.c',
14 | 'src/opencv_injection.cpp',
15 | 'src/receiver.c',
16 | 'src/recorder.c',
17 | 'src/scrcpy.c',
18 | 'src/screen.c',
19 | 'src/server.c',
20 | 'src/stream.c',
21 | 'src/tiny_xpm.c',
22 | 'src/video_buffer.c',
23 | 'src/util/net.c',
24 | 'src/util/str_util.c'
25 | ]
26 |
27 | if not get_option('crossbuild_windows')
28 |
29 | # native build
30 | dependencies = [
31 | dependency('libavformat'),
32 | dependency('libavcodec'),
33 | dependency('libswscale'),
34 | dependency('libavutil'),
35 | dependency('sdl2'),
36 | dependency('opencv4'),
37 | ]
38 |
39 | else
40 |
41 | # cross-compile mingw32 build (from Linux to Windows)
42 | cc = meson.get_compiler('c')
43 |
44 | prebuilt_sdl2 = meson.get_cross_property('prebuilt_sdl2')
45 | sdl2_bin_dir = meson.current_source_dir() + '/../prebuilt-deps/' + prebuilt_sdl2 + '/bin'
46 | sdl2_lib_dir = meson.current_source_dir() + '/../prebuilt-deps/' + prebuilt_sdl2 + '/lib'
47 | sdl2_include_dir = '../prebuilt-deps/' + prebuilt_sdl2 + '/include'
48 |
49 | sdl2 = declare_dependency(
50 | dependencies: [
51 | cc.find_library('SDL2', dirs: sdl2_bin_dir),
52 | cc.find_library('SDL2main', dirs: sdl2_lib_dir),
53 | ],
54 | include_directories: include_directories(sdl2_include_dir)
55 | )
56 |
57 | prebuilt_ffmpeg_shared = meson.get_cross_property('prebuilt_ffmpeg_shared')
58 | prebuilt_ffmpeg_dev = meson.get_cross_property('prebuilt_ffmpeg_dev')
59 | ffmpeg_bin_dir = meson.current_source_dir() + '/../prebuilt-deps/' + prebuilt_ffmpeg_shared + '/bin'
60 | ffmpeg_include_dir = '../prebuilt-deps/' + prebuilt_ffmpeg_dev + '/include'
61 | ffmpeg = declare_dependency(
62 | dependencies: [
63 | cc.find_library('avcodec-58', dirs: ffmpeg_bin_dir),
64 | cc.find_library('avformat-58', dirs: ffmpeg_bin_dir),
65 | cc.find_library('avutil-56', dirs: ffmpeg_bin_dir),
66 | ],
67 | include_directories: include_directories(ffmpeg_include_dir)
68 | )
69 |
70 | dependencies = [
71 | ffmpeg,
72 | sdl2,
73 | cc.find_library('mingw32')
74 | ]
75 |
76 | endif
77 |
78 | cc = meson.get_compiler('c')
79 |
80 | if host_machine.system() == 'windows'
81 | src += [ 'src/sys/win/command.c' ]
82 | src += [ 'src/sys/win/net.c' ]
83 | dependencies += cc.find_library('ws2_32')
84 | else
85 | src += [ 'src/sys/unix/command.c' ]
86 | src += [ 'src/sys/unix/net.c' ]
87 | endif
88 |
89 | conf = configuration_data()
90 |
91 | # expose the build type
92 | conf.set('NDEBUG', get_option('buildtype') != 'debug')
93 |
94 | # the version, updated on release
95 | conf.set_quoted('SCRCPY_VERSION', meson.project_version())
96 |
97 | # the prefix used during configuration (meson --prefix=PREFIX)
98 | conf.set_quoted('PREFIX', get_option('prefix'))
99 |
100 | # build a "portable" version (with scrcpy-server accessible from the same
101 | # directory as the executable)
102 | conf.set('PORTABLE', get_option('portable'))
103 |
104 | # the default client TCP port for the "adb reverse" tunnel
105 | # overridden by option --port
106 | conf.set('DEFAULT_LOCAL_PORT', '27183')
107 |
108 | # the default max video size for both dimensions, in pixels
109 | # overridden by option --max-size
110 | conf.set('DEFAULT_MAX_SIZE', '0') # 0: unlimited
111 |
112 | # the default video bitrate, in bits/second
113 | # overridden by option --bit-rate
114 | conf.set('DEFAULT_BIT_RATE', '8000000') # 8Mbps
115 |
116 | # enable High DPI support
117 | conf.set('HIDPI_SUPPORT', get_option('hidpi_support'))
118 |
119 | # disable console on Windows
120 | conf.set('WINDOWS_NOCONSOLE', get_option('windows_noconsole'))
121 |
122 | # run a server debugger and wait for a client to be attached
123 | conf.set('SERVER_DEBUGGER', get_option('server_debugger'))
124 |
125 | configure_file(configuration: conf, output: 'config.h')
126 |
127 | src_dir = include_directories('src')
128 |
129 | if get_option('windows_noconsole')
130 | link_args = [ '-Wl,--subsystem,windows' ]
131 | else
132 | link_args = []
133 | endif
134 |
135 | executable('scrcpy', src,
136 | dependencies: dependencies,
137 | include_directories: src_dir,
138 | install: true,
139 | c_args: [],
140 | link_args: link_args)
141 |
142 | install_man('scrcpy.1')
143 |
144 |
145 | ### TESTS
146 |
147 | # do not build tests in release (assertions would not be executed at all)
148 | if get_option('buildtype') == 'debug'
149 | tests = [
150 | ['test_buffer_util', [
151 | 'tests/test_buffer_util.c'
152 | ]],
153 | ['test_cbuf', [
154 | 'tests/test_cbuf.c',
155 | ]],
156 | ['test_cli', [
157 | 'tests/test_cli.c',
158 | 'src/cli.c',
159 | 'src/util/str_util.c',
160 | ]],
161 | ['test_control_event_serialize', [
162 | 'tests/test_control_msg_serialize.c',
163 | 'src/control_msg.c',
164 | 'src/util/str_util.c',
165 | ]],
166 | ['test_device_event_deserialize', [
167 | 'tests/test_device_msg_deserialize.c',
168 | 'src/device_msg.c',
169 | ]],
170 | ['test_queue', [
171 | 'tests/test_queue.c',
172 | ]],
173 | ['test_strutil', [
174 | 'tests/test_strutil.c',
175 | 'src/util/str_util.c',
176 | ]],
177 | ]
178 |
179 | foreach t : tests
180 | exe = executable(t[0], t[1],
181 | include_directories: src_dir,
182 | dependencies: dependencies,
183 | c_args: ['-DSDL_MAIN_HANDLED'])
184 | test(t[0], exe)
185 | endforeach
186 | endif
187 |
--------------------------------------------------------------------------------
/scrcpy/Makefile.CrossWindows:
--------------------------------------------------------------------------------
1 | # This makefile provides recipes to build a "portable" version of scrcpy for
2 | # Windows.
3 | #
4 | # Here, "portable" means that the client and server binaries are expected to be
5 | # anywhere, but in the same directory, instead of well-defined separate
6 | # locations (e.g. /usr/bin/scrcpy and /usr/share/scrcpy/scrcpy-server).
7 | #
8 | # In particular, this implies to change the location from where the client push
9 | # the server to the device.
10 |
11 | .PHONY: default clean \
12 | build-server \
13 | prepare-deps-win32 prepare-deps-win64 \
14 | build-win32 build-win32-noconsole \
15 | build-win64 build-win64-noconsole \
16 | dist-win32 dist-win64 \
17 | zip-win32 zip-win64 \
18 | sums release
19 |
20 | GRADLE ?= ./gradlew
21 |
22 | SERVER_BUILD_DIR := build-server
23 | WIN32_BUILD_DIR := build-win32
24 | WIN32_NOCONSOLE_BUILD_DIR := build-win32-noconsole
25 | WIN64_BUILD_DIR := build-win64
26 | WIN64_NOCONSOLE_BUILD_DIR := build-win64-noconsole
27 |
28 | DIST := dist
29 | WIN32_TARGET_DIR := scrcpy-win32
30 | WIN64_TARGET_DIR := scrcpy-win64
31 |
32 | VERSION := $(shell git describe --tags --always)
33 | WIN32_TARGET := $(WIN32_TARGET_DIR)-$(VERSION).zip
34 | WIN64_TARGET := $(WIN64_TARGET_DIR)-$(VERSION).zip
35 |
36 | release: clean zip-win32 zip-win64 sums
37 | @echo "Windows archives generated in $(DIST)/"
38 |
39 | clean:
40 | $(GRADLE) clean
41 | rm -rf "$(SERVER_BUILD_DIR)" "$(WIN32_BUILD_DIR)" "$(WIN64_BUILD_DIR)" \
42 | "$(WIN32_NOCONSOLE_BUILD_DIR)" "$(WIN64_NOCONSOLE_BUILD_DIR)" "$(DIST)"
43 |
44 | build-server:
45 | [ -d "$(SERVER_BUILD_DIR)" ] || ( mkdir "$(SERVER_BUILD_DIR)" && \
46 | meson "$(SERVER_BUILD_DIR)" \
47 | --buildtype release -Dcompile_app=false )
48 | ninja -C "$(SERVER_BUILD_DIR)"
49 |
50 | prepare-deps-win32:
51 | -$(MAKE) -C prebuilt-deps prepare-win32
52 |
53 | build-win32: prepare-deps-win32
54 | [ -d "$(WIN32_BUILD_DIR)" ] || ( mkdir "$(WIN32_BUILD_DIR)" && \
55 | meson "$(WIN32_BUILD_DIR)" \
56 | --cross-file cross_win32.txt \
57 | --buildtype release --strip -Db_lto=true \
58 | -Dcrossbuild_windows=true \
59 | -Dcompile_server=false \
60 | -Dportable=true )
61 | ninja -C "$(WIN32_BUILD_DIR)"
62 |
63 | build-win32-noconsole: prepare-deps-win32
64 | [ -d "$(WIN32_NOCONSOLE_BUILD_DIR)" ] || ( mkdir "$(WIN32_NOCONSOLE_BUILD_DIR)" && \
65 | meson "$(WIN32_NOCONSOLE_BUILD_DIR)" \
66 | --cross-file cross_win32.txt \
67 | --buildtype release --strip -Db_lto=true \
68 | -Dcrossbuild_windows=true \
69 | -Dcompile_server=false \
70 | -Dwindows_noconsole=true \
71 | -Dportable=true )
72 | ninja -C "$(WIN32_NOCONSOLE_BUILD_DIR)"
73 |
74 | prepare-deps-win64:
75 | -$(MAKE) -C prebuilt-deps prepare-win64
76 |
77 | build-win64: prepare-deps-win64
78 | [ -d "$(WIN64_BUILD_DIR)" ] || ( mkdir "$(WIN64_BUILD_DIR)" && \
79 | meson "$(WIN64_BUILD_DIR)" \
80 | --cross-file cross_win64.txt \
81 | --buildtype release --strip -Db_lto=true \
82 | -Dcrossbuild_windows=true \
83 | -Dcompile_server=false \
84 | -Dportable=true )
85 | ninja -C "$(WIN64_BUILD_DIR)"
86 |
87 | build-win64-noconsole: prepare-deps-win64
88 | [ -d "$(WIN64_NOCONSOLE_BUILD_DIR)" ] || ( mkdir "$(WIN64_NOCONSOLE_BUILD_DIR)" && \
89 | meson "$(WIN64_NOCONSOLE_BUILD_DIR)" \
90 | --cross-file cross_win64.txt \
91 | --buildtype release --strip -Db_lto=true \
92 | -Dcrossbuild_windows=true \
93 | -Dcompile_server=false \
94 | -Dwindows_noconsole=true \
95 | -Dportable=true )
96 | ninja -C "$(WIN64_NOCONSOLE_BUILD_DIR)"
97 |
98 | dist-win32: build-server build-win32 build-win32-noconsole
99 | mkdir -p "$(DIST)/$(WIN32_TARGET_DIR)"
100 | cp "$(SERVER_BUILD_DIR)"/server/scrcpy-server "$(DIST)/$(WIN32_TARGET_DIR)/"
101 | cp "$(WIN32_BUILD_DIR)"/app/scrcpy.exe "$(DIST)/$(WIN32_TARGET_DIR)/"
102 | cp "$(WIN32_NOCONSOLE_BUILD_DIR)"/app/scrcpy.exe "$(DIST)/$(WIN32_TARGET_DIR)/scrcpy-noconsole.exe"
103 | cp prebuilt-deps/ffmpeg-4.2.1-win32-shared/bin/avutil-56.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
104 | cp prebuilt-deps/ffmpeg-4.2.1-win32-shared/bin/avcodec-58.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
105 | cp prebuilt-deps/ffmpeg-4.2.1-win32-shared/bin/avformat-58.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
106 | cp prebuilt-deps/ffmpeg-4.2.1-win32-shared/bin/swresample-3.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
107 | cp prebuilt-deps/ffmpeg-4.2.1-win32-shared/bin/swscale-5.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
108 | cp prebuilt-deps/platform-tools/adb.exe "$(DIST)/$(WIN32_TARGET_DIR)/"
109 | cp prebuilt-deps/platform-tools/AdbWinApi.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
110 | cp prebuilt-deps/platform-tools/AdbWinUsbApi.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
111 | cp prebuilt-deps/SDL2-2.0.10/i686-w64-mingw32/bin/SDL2.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
112 |
113 | dist-win64: build-server build-win64 build-win64-noconsole
114 | mkdir -p "$(DIST)/$(WIN64_TARGET_DIR)"
115 | cp "$(SERVER_BUILD_DIR)"/server/scrcpy-server "$(DIST)/$(WIN64_TARGET_DIR)/"
116 | cp "$(WIN64_BUILD_DIR)"/app/scrcpy.exe "$(DIST)/$(WIN64_TARGET_DIR)/"
117 | cp "$(WIN64_NOCONSOLE_BUILD_DIR)"/app/scrcpy.exe "$(DIST)/$(WIN64_TARGET_DIR)/scrcpy-noconsole.exe"
118 | cp prebuilt-deps/ffmpeg-4.2.1-win64-shared/bin/avutil-56.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
119 | cp prebuilt-deps/ffmpeg-4.2.1-win64-shared/bin/avcodec-58.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
120 | cp prebuilt-deps/ffmpeg-4.2.1-win64-shared/bin/avformat-58.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
121 | cp prebuilt-deps/ffmpeg-4.2.1-win64-shared/bin/swresample-3.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
122 | cp prebuilt-deps/ffmpeg-4.2.1-win64-shared/bin/swscale-5.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
123 | cp prebuilt-deps/platform-tools/adb.exe "$(DIST)/$(WIN64_TARGET_DIR)/"
124 | cp prebuilt-deps/platform-tools/AdbWinApi.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
125 | cp prebuilt-deps/platform-tools/AdbWinUsbApi.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
126 | cp prebuilt-deps/SDL2-2.0.10/x86_64-w64-mingw32/bin/SDL2.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
127 |
128 | zip-win32: dist-win32
129 | cd "$(DIST)/$(WIN32_TARGET_DIR)"; \
130 | zip -r "../$(WIN32_TARGET)" .
131 |
132 | zip-win64: dist-win64
133 | cd "$(DIST)/$(WIN64_TARGET_DIR)"; \
134 | zip -r "../$(WIN64_TARGET)" .
135 |
136 | sums:
137 | cd "$(DIST)"; \
138 | sha256sum *.zip > SHA256SUMS.txt
139 |
--------------------------------------------------------------------------------
/scrcpy/app/src/event_converter.c:
--------------------------------------------------------------------------------
1 | #include "event_converter.h"
2 |
3 | #include "config.h"
4 |
5 | #define MAP(FROM, TO) case FROM: *to = TO; return true
6 | #define FAIL default: return false
7 |
8 | bool
9 | convert_keycode_action(SDL_EventType from, enum android_keyevent_action *to) {
10 | switch (from) {
11 | MAP(SDL_KEYDOWN, AKEY_EVENT_ACTION_DOWN);
12 | MAP(SDL_KEYUP, AKEY_EVENT_ACTION_UP);
13 | FAIL;
14 | }
15 | }
16 |
17 | static enum android_metastate
18 | autocomplete_metastate(enum android_metastate metastate) {
19 | // fill dependant flags
20 | if (metastate & (AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_RIGHT_ON)) {
21 | metastate |= AMETA_SHIFT_ON;
22 | }
23 | if (metastate & (AMETA_CTRL_LEFT_ON | AMETA_CTRL_RIGHT_ON)) {
24 | metastate |= AMETA_CTRL_ON;
25 | }
26 | if (metastate & (AMETA_ALT_LEFT_ON | AMETA_ALT_RIGHT_ON)) {
27 | metastate |= AMETA_ALT_ON;
28 | }
29 | if (metastate & (AMETA_META_LEFT_ON | AMETA_META_RIGHT_ON)) {
30 | metastate |= AMETA_META_ON;
31 | }
32 |
33 | return metastate;
34 | }
35 |
36 | enum android_metastate
37 | convert_meta_state(SDL_Keymod mod) {
38 | enum android_metastate metastate = 0;
39 | if (mod & KMOD_LSHIFT) {
40 | metastate |= AMETA_SHIFT_LEFT_ON;
41 | }
42 | if (mod & KMOD_RSHIFT) {
43 | metastate |= AMETA_SHIFT_RIGHT_ON;
44 | }
45 | if (mod & KMOD_LCTRL) {
46 | metastate |= AMETA_CTRL_LEFT_ON;
47 | }
48 | if (mod & KMOD_RCTRL) {
49 | metastate |= AMETA_CTRL_RIGHT_ON;
50 | }
51 | if (mod & KMOD_LALT) {
52 | metastate |= AMETA_ALT_LEFT_ON;
53 | }
54 | if (mod & KMOD_RALT) {
55 | metastate |= AMETA_ALT_RIGHT_ON;
56 | }
57 | if (mod & KMOD_LGUI) { // Windows key
58 | metastate |= AMETA_META_LEFT_ON;
59 | }
60 | if (mod & KMOD_RGUI) { // Windows key
61 | metastate |= AMETA_META_RIGHT_ON;
62 | }
63 | if (mod & KMOD_NUM) {
64 | metastate |= AMETA_NUM_LOCK_ON;
65 | }
66 | if (mod & KMOD_CAPS) {
67 | metastate |= AMETA_CAPS_LOCK_ON;
68 | }
69 | if (mod & KMOD_MODE) { // Alt Gr
70 | // no mapping?
71 | }
72 |
73 | // fill the dependent fields
74 | return autocomplete_metastate(metastate);
75 | }
76 |
77 | bool
78 | convert_keycode(SDL_Keycode from, enum android_keycode *to, uint16_t mod,
79 | bool prefer_text) {
80 | switch (from) {
81 | MAP(SDLK_RETURN, AKEYCODE_ENTER);
82 | MAP(SDLK_KP_ENTER, AKEYCODE_NUMPAD_ENTER);
83 | MAP(SDLK_ESCAPE, AKEYCODE_ESCAPE);
84 | MAP(SDLK_BACKSPACE, AKEYCODE_DEL);
85 | MAP(SDLK_TAB, AKEYCODE_TAB);
86 | MAP(SDLK_PAGEUP, AKEYCODE_PAGE_UP);
87 | MAP(SDLK_DELETE, AKEYCODE_FORWARD_DEL);
88 | MAP(SDLK_HOME, AKEYCODE_MOVE_HOME);
89 | MAP(SDLK_END, AKEYCODE_MOVE_END);
90 | MAP(SDLK_PAGEDOWN, AKEYCODE_PAGE_DOWN);
91 | MAP(SDLK_RIGHT, AKEYCODE_DPAD_RIGHT);
92 | MAP(SDLK_LEFT, AKEYCODE_DPAD_LEFT);
93 | MAP(SDLK_DOWN, AKEYCODE_DPAD_DOWN);
94 | MAP(SDLK_UP, AKEYCODE_DPAD_UP);
95 | }
96 |
97 | if (prefer_text) {
98 | // do not forward alpha and space key events
99 | return false;
100 | }
101 |
102 | if (mod & (KMOD_LALT | KMOD_RALT | KMOD_LGUI | KMOD_RGUI)) {
103 | return false;
104 | }
105 | // if ALT and META are not pressed, also handle letters and space
106 | switch (from) {
107 | MAP(SDLK_a, AKEYCODE_A);
108 | MAP(SDLK_b, AKEYCODE_B);
109 | MAP(SDLK_c, AKEYCODE_C);
110 | MAP(SDLK_d, AKEYCODE_D);
111 | MAP(SDLK_e, AKEYCODE_E);
112 | MAP(SDLK_f, AKEYCODE_F);
113 | MAP(SDLK_g, AKEYCODE_G);
114 | MAP(SDLK_h, AKEYCODE_H);
115 | MAP(SDLK_i, AKEYCODE_I);
116 | MAP(SDLK_j, AKEYCODE_J);
117 | MAP(SDLK_k, AKEYCODE_K);
118 | MAP(SDLK_l, AKEYCODE_L);
119 | MAP(SDLK_m, AKEYCODE_M);
120 | MAP(SDLK_n, AKEYCODE_N);
121 | MAP(SDLK_o, AKEYCODE_O);
122 | MAP(SDLK_p, AKEYCODE_P);
123 | MAP(SDLK_q, AKEYCODE_Q);
124 | MAP(SDLK_r, AKEYCODE_R);
125 | MAP(SDLK_s, AKEYCODE_S);
126 | MAP(SDLK_t, AKEYCODE_T);
127 | MAP(SDLK_u, AKEYCODE_U);
128 | MAP(SDLK_v, AKEYCODE_V);
129 | MAP(SDLK_w, AKEYCODE_W);
130 | MAP(SDLK_x, AKEYCODE_X);
131 | MAP(SDLK_y, AKEYCODE_Y);
132 | MAP(SDLK_z, AKEYCODE_Z);
133 | MAP(SDLK_SPACE, AKEYCODE_SPACE);
134 | FAIL;
135 | }
136 | }
137 |
138 | enum android_motionevent_buttons
139 | convert_mouse_buttons(uint32_t state) {
140 | enum android_motionevent_buttons buttons = 0;
141 | if (state & SDL_BUTTON_LMASK) {
142 | buttons |= AMOTION_EVENT_BUTTON_PRIMARY;
143 | }
144 | if (state & SDL_BUTTON_RMASK) {
145 | buttons |= AMOTION_EVENT_BUTTON_SECONDARY;
146 | }
147 | if (state & SDL_BUTTON_MMASK) {
148 | buttons |= AMOTION_EVENT_BUTTON_TERTIARY;
149 | }
150 | if (state & SDL_BUTTON_X1MASK) {
151 | buttons |= AMOTION_EVENT_BUTTON_BACK;
152 | }
153 | if (state & SDL_BUTTON_X2MASK) {
154 | buttons |= AMOTION_EVENT_BUTTON_FORWARD;
155 | }
156 | return buttons;
157 | }
158 |
159 | bool
160 | convert_mouse_action(SDL_EventType from, enum android_motionevent_action *to) {
161 | switch (from) {
162 | MAP(SDL_MOUSEBUTTONDOWN, AMOTION_EVENT_ACTION_DOWN);
163 | MAP(SDL_MOUSEBUTTONUP, AMOTION_EVENT_ACTION_UP);
164 | FAIL;
165 | }
166 | }
167 |
168 | bool
169 | convert_touch_action(SDL_EventType from, enum android_motionevent_action *to) {
170 | switch (from) {
171 | MAP(SDL_FINGERMOTION, AMOTION_EVENT_ACTION_MOVE);
172 | MAP(SDL_FINGERDOWN, AMOTION_EVENT_ACTION_DOWN);
173 | MAP(SDL_FINGERUP, AMOTION_EVENT_ACTION_UP);
174 | FAIL;
175 | }
176 | }
177 |
--------------------------------------------------------------------------------