├── 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 | ![detected_circle.png](detected_circle.png) 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 | --------------------------------------------------------------------------------