├── app ├── deps │ ├── .gitignore │ ├── adb.sh │ ├── README │ ├── libusb.sh │ ├── common │ ├── sdl.sh │ └── ffmpeg.sh ├── data │ ├── open_a_terminal_here.bat │ ├── icon.ico │ ├── icon.png │ ├── scrcpy-console.bat │ ├── scrcpy-noconsole.vbs │ ├── scrcpy.desktop │ └── scrcpy-console.desktop ├── src │ ├── version.h │ ├── usb │ │ ├── scrcpy_otg.h │ │ ├── mouse_aoa.h │ │ ├── gamepad_aoa.h │ │ ├── keyboard_aoa.h │ │ ├── screen_otg.h │ │ ├── usb.h │ │ └── aoa_hid.h │ ├── icon.h │ ├── util │ │ ├── memory.c │ │ ├── rand.h │ │ ├── memory.h │ │ ├── intmap.c │ │ ├── process_intr.h │ │ ├── term.h │ │ ├── average.c │ │ ├── intmap.h │ │ ├── tick.h │ │ ├── rand.c │ │ ├── process_intr.c │ │ ├── timeout.h │ │ ├── net_intr.h │ │ ├── average.h │ │ ├── term.c │ │ ├── log.h │ │ ├── file.h │ │ ├── file.c │ │ ├── acksync.h │ │ ├── strbuf.h │ │ ├── tick.c │ │ ├── net.h │ │ ├── intr.h │ │ ├── acksync.c │ │ ├── audiobuf.h │ │ ├── timeout.c │ │ ├── thread.h │ │ ├── strbuf.c │ │ ├── intr.c │ │ ├── binary.h │ │ └── net_intr.c │ ├── common.h │ ├── uhid │ │ ├── mouse_uhid.h │ │ ├── gamepad_uhid.h │ │ ├── uhid_output.h │ │ ├── keyboard_uhid.h │ │ └── uhid_output.c │ ├── scrcpy.h │ ├── coords.h │ ├── mouse_sdk.h │ ├── hid │ │ ├── hid_event.h │ │ ├── hid_mouse.h │ │ ├── hid_gamepad.h │ │ └── hid_keyboard.h │ ├── trait │ │ ├── frame_sink.h │ │ ├── frame_source.h │ │ ├── packet_source.h │ │ ├── packet_sink.h │ │ ├── gamepad_processor.h │ │ ├── mouse_processor.h │ │ ├── frame_source.c │ │ ├── key_processor.h │ │ └── packet_source.c │ ├── cli.h │ ├── decoder.h │ ├── adb │ │ ├── adb_parser.h │ │ ├── adb_device.h │ │ ├── adb_device.c │ │ └── adb_tunnel.h │ ├── opengl.h │ ├── keyboard_sdk.h │ ├── v4l2_sink.h │ ├── events.h │ ├── sys │ │ ├── win │ │ │ └── file.c │ │ └── unix │ │ │ └── file.c │ ├── clock.c │ ├── frame_buffer.h │ ├── receiver.h │ ├── clock.h │ ├── device_msg.h │ ├── demuxer.h │ ├── packet_merger.h │ ├── packet_merger.c │ ├── file_pusher.h │ ├── fps_counter.h │ ├── display.h │ ├── delay_buffer.h │ ├── controller.h │ ├── events.c │ ├── opengl.c │ ├── input_manager.h │ ├── version.c │ ├── recorder.h │ └── frame_buffer.c ├── scrcpy-windows.manifest ├── scrcpy-windows.rc └── tests │ └── test_strbuf.c ├── settings.gradle ├── release.sh ├── .github ├── FUNDING.yml └── ISSUE_TEMPLATE │ ├── question.md │ ├── feature_request.md │ └── bug_report.md ├── assets └── screenshot-debian-600.jpg ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── server ├── src │ ├── main │ │ ├── AndroidManifest.xml │ │ ├── java │ │ │ └── com │ │ │ │ └── genymobile │ │ │ │ └── scrcpy │ │ │ │ ├── device │ │ │ │ ├── ConfigurationException.java │ │ │ │ ├── Point.java │ │ │ │ ├── DisplayInfo.java │ │ │ │ ├── Size.java │ │ │ │ └── Position.java │ │ │ │ ├── control │ │ │ │ ├── ControlProtocolException.java │ │ │ │ ├── ControlChannel.java │ │ │ │ ├── Pointer.java │ │ │ │ ├── DeviceMessage.java │ │ │ │ ├── DeviceMessageSender.java │ │ │ │ └── DeviceMessageWriter.java │ │ │ │ ├── util │ │ │ │ ├── Codec.java │ │ │ │ ├── SettingsException.java │ │ │ │ ├── HandlerExecutor.java │ │ │ │ ├── StringUtils.java │ │ │ │ ├── Binary.java │ │ │ │ ├── Command.java │ │ │ │ └── IO.java │ │ │ │ ├── AsyncProcessor.java │ │ │ │ ├── audio │ │ │ │ ├── AudioCaptureException.java │ │ │ │ ├── AudioCapture.java │ │ │ │ ├── AudioSource.java │ │ │ │ ├── AudioConfig.java │ │ │ │ ├── AudioCodec.java │ │ │ │ └── AudioRecordReader.java │ │ │ │ ├── video │ │ │ │ ├── VideoSource.java │ │ │ │ ├── CameraFacing.java │ │ │ │ ├── CameraAspectRatio.java │ │ │ │ ├── VideoCodec.java │ │ │ │ └── SurfaceCapture.java │ │ │ │ ├── wrappers │ │ │ │ └── PowerManager.java │ │ │ │ └── FakeContext.java │ │ └── aidl │ │ │ └── android │ │ │ ├── content │ │ │ └── IOnPrimaryClipChangedListener.aidl │ │ │ └── view │ │ │ ├── IRotationWatcher.aidl │ │ │ └── IDisplayFoldListener.aidl │ └── test │ │ └── java │ │ └── com │ │ └── genymobile │ │ └── scrcpy │ │ ├── util │ │ ├── StringUtilsTest.java │ │ └── BinaryTest.java │ │ └── control │ │ └── DeviceMessageWriterTest.java ├── .gitignore ├── proguard-rules.pro ├── build.gradle ├── scripts │ └── build-wrapper.sh └── meson.build ├── .gitignore ├── cross_win32.txt ├── cross_win64.txt ├── meson.build ├── run ├── install_release.sh ├── config └── android-checkstyle.gradle ├── gradle.properties ├── meson_options.txt ├── bump_version └── doc ├── macos.md ├── window.md ├── gamepad.md ├── v4l2.md ├── device.md ├── otg.md ├── linux.md └── recording.md /app/deps/.gitignore: -------------------------------------------------------------------------------- 1 | /work 2 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':server' 2 | -------------------------------------------------------------------------------- /app/data/open_a_terminal_here.bat: -------------------------------------------------------------------------------- 1 | @cmd 2 | -------------------------------------------------------------------------------- /release.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | make -f release.mk 3 | -------------------------------------------------------------------------------- /app/data/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lingyicute/scrcpy/HEAD/app/data/icon.ico -------------------------------------------------------------------------------- /app/data/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lingyicute/scrcpy/HEAD/app/data/icon.png -------------------------------------------------------------------------------- /app/data/scrcpy-console.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | scrcpy.exe --pause-on-exit=if-error %* 3 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [rom1v] 2 | liberapay: rom1v 3 | custom: ["https://paypal.me/rom2v"] 4 | -------------------------------------------------------------------------------- /assets/screenshot-debian-600.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lingyicute/scrcpy/HEAD/assets/screenshot-debian-600.jpg -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lingyicute/scrcpy/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /server/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /server/.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/ 5 | .DS_Store 6 | /build 7 | /captures 8 | .externalNativeBuild 9 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/question.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Question 3 | about: Ask a question about scrcpy 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | /dist/ 3 | /build-*/ 4 | /build_*/ 5 | /release-*/ 6 | .idea/ 7 | .gradle/ 8 | /x/ 9 | local.properties 10 | /scrcpy-server 11 | -------------------------------------------------------------------------------- /app/src/version.h: -------------------------------------------------------------------------------- 1 | #ifndef SC_VERSION_H 2 | #define SC_VERSION_H 3 | 4 | #include "common.h" 5 | 6 | void 7 | scrcpy_print_version(void); 8 | 9 | #endif 10 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /app/src/usb/scrcpy_otg.h: -------------------------------------------------------------------------------- 1 | #ifndef SCRCPY_OTG_H 2 | #define SCRCPY_OTG_H 3 | 4 | #include "common.h" 5 | 6 | #include "options.h" 7 | #include "scrcpy.h" 8 | 9 | enum scrcpy_exit_code 10 | scrcpy_otg(struct scrcpy_options *options); 11 | 12 | #endif 13 | -------------------------------------------------------------------------------- /app/data/scrcpy-noconsole.vbs: -------------------------------------------------------------------------------- 1 | strCommand = "cmd /c scrcpy.exe" 2 | 3 | For Each Arg In WScript.Arguments 4 | strCommand = strCommand & " """ & replace(Arg, """", """""""""") & """" 5 | Next 6 | 7 | CreateObject("Wscript.Shell").Run strCommand, 0, false 8 | -------------------------------------------------------------------------------- /server/src/main/java/com/genymobile/scrcpy/device/ConfigurationException.java: -------------------------------------------------------------------------------- 1 | package com.genymobile.scrcpy.device; 2 | 3 | public class ConfigurationException extends Exception { 4 | public ConfigurationException(String message) { 5 | super(message); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /app/src/icon.h: -------------------------------------------------------------------------------- 1 | #ifndef SC_ICON_H 2 | #define SC_ICON_H 3 | 4 | #include "common.h" 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | SDL_Surface * 11 | scrcpy_icon_load(void); 12 | 13 | void 14 | scrcpy_icon_destroy(SDL_Surface *icon); 15 | 16 | #endif 17 | -------------------------------------------------------------------------------- /server/src/main/java/com/genymobile/scrcpy/control/ControlProtocolException.java: -------------------------------------------------------------------------------- 1 | package com.genymobile.scrcpy.control; 2 | 3 | import java.io.IOException; 4 | 5 | public class ControlProtocolException extends IOException { 6 | public ControlProtocolException(String message) { 7 | super(message); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /app/src/util/memory.c: -------------------------------------------------------------------------------- 1 | #include "memory.h" 2 | 3 | #include 4 | #include 5 | 6 | void * 7 | sc_allocarray(size_t nmemb, size_t size) { 8 | size_t bytes; 9 | if (__builtin_mul_overflow(nmemb, size, &bytes)) { 10 | errno = ENOMEM; 11 | return NULL; 12 | } 13 | return malloc(bytes); 14 | } 15 | -------------------------------------------------------------------------------- /server/src/main/java/com/genymobile/scrcpy/util/Codec.java: -------------------------------------------------------------------------------- 1 | package com.genymobile.scrcpy.util; 2 | 3 | public interface Codec { 4 | 5 | enum Type { 6 | VIDEO, 7 | AUDIO, 8 | } 9 | 10 | Type getType(); 11 | 12 | int getId(); 13 | 14 | String getName(); 15 | 16 | String getMimeType(); 17 | } 18 | -------------------------------------------------------------------------------- /app/src/util/rand.h: -------------------------------------------------------------------------------- 1 | #ifndef SC_RAND_H 2 | #define SC_RAND_H 3 | 4 | #include "common.h" 5 | 6 | #include 7 | 8 | struct sc_rand { 9 | unsigned short xsubi[3]; 10 | }; 11 | 12 | void sc_rand_init(struct sc_rand *rand); 13 | uint32_t sc_rand_u32(struct sc_rand *rand); 14 | uint64_t sc_rand_u64(struct sc_rand *rand); 15 | 16 | #endif 17 | -------------------------------------------------------------------------------- /app/src/util/memory.h: -------------------------------------------------------------------------------- 1 | #ifndef SC_MEMORY_H 2 | #define SC_MEMORY_H 3 | 4 | #include 5 | 6 | /** 7 | * Allocate an array of `nmemb` items of `size` bytes each 8 | * 9 | * Like calloc(), but without initialization. 10 | * Like reallocarray(), but without reallocation. 11 | */ 12 | void * 13 | sc_allocarray(size_t nmemb, size_t size); 14 | 15 | #endif 16 | -------------------------------------------------------------------------------- /app/src/util/intmap.c: -------------------------------------------------------------------------------- 1 | #include "intmap.h" 2 | 3 | const struct sc_intmap_entry * 4 | sc_intmap_find_entry(const struct sc_intmap_entry entries[], size_t len, 5 | int32_t key) { 6 | for (size_t i = 0; i < len; ++i) { 7 | const struct sc_intmap_entry *entry = &entries[i]; 8 | if (entry->key == key) { 9 | return entry; 10 | } 11 | } 12 | return NULL; 13 | } 14 | -------------------------------------------------------------------------------- /cross_win32.txt: -------------------------------------------------------------------------------- 1 | # apt install mingw-w64 mingw-w64-tools 2 | 3 | [binaries] 4 | name = 'mingw' 5 | c = 'i686-w64-mingw32-gcc' 6 | cpp = 'i686-w64-mingw32-g++' 7 | ar = 'i686-w64-mingw32-ar' 8 | strip = 'i686-w64-mingw32-strip' 9 | pkg-config = 'i686-w64-mingw32-pkg-config' 10 | windres = 'i686-w64-mingw32-windres' 11 | 12 | [host_machine] 13 | system = 'windows' 14 | cpu_family = 'x86' 15 | cpu = 'i686' 16 | endian = 'little' 17 | -------------------------------------------------------------------------------- /cross_win64.txt: -------------------------------------------------------------------------------- 1 | # apt install mingw-w64 mingw-w64-tools 2 | 3 | [binaries] 4 | name = 'mingw' 5 | c = 'x86_64-w64-mingw32-gcc' 6 | cpp = 'x86_64-w64-mingw32-g++' 7 | ar = 'x86_64-w64-mingw32-ar' 8 | strip = 'x86_64-w64-mingw32-strip' 9 | pkg-config = 'x86_64-w64-mingw32-pkg-config' 10 | windres = 'x86_64-w64-mingw32-windres' 11 | 12 | [host_machine] 13 | system = 'windows' 14 | cpu_family = 'x86' 15 | cpu = 'x86_64' 16 | endian = 'little' 17 | -------------------------------------------------------------------------------- /app/src/common.h: -------------------------------------------------------------------------------- 1 | #ifndef SC_COMMON_H 2 | #define SC_COMMON_H 3 | 4 | #include "config.h" 5 | #include "compat.h" 6 | 7 | #define ARRAY_LEN(a) (sizeof(a) / sizeof(a[0])) 8 | #define MIN(X,Y) ((X) < (Y) ? (X) : (Y)) 9 | #define MAX(X,Y) ((X) > (Y) ? (X) : (Y)) 10 | #define CLAMP(V,X,Y) MIN( MAX((V),(X)), (Y) ) 11 | 12 | #define container_of(ptr, type, member) \ 13 | ((type *) (((char *) (ptr)) - offsetof(type, member))) 14 | 15 | #endif 16 | -------------------------------------------------------------------------------- /app/src/util/process_intr.h: -------------------------------------------------------------------------------- 1 | #ifndef SC_PROCESS_INTR_H 2 | #define SC_PROCESS_INTR_H 3 | 4 | #include "common.h" 5 | 6 | #include "intr.h" 7 | #include "process.h" 8 | 9 | ssize_t 10 | sc_pipe_read_intr(struct sc_intr *intr, sc_pid pid, sc_pipe pipe, char *data, 11 | size_t len); 12 | 13 | ssize_t 14 | sc_pipe_read_all_intr(struct sc_intr *intr, sc_pid pid, sc_pipe pipe, 15 | char *data, size_t len); 16 | 17 | #endif 18 | -------------------------------------------------------------------------------- /meson.build: -------------------------------------------------------------------------------- 1 | project('scrcpy', 'c', 2 | version: '2.7', 3 | meson_version: '>= 0.48', 4 | default_options: [ 5 | 'c_std=c11', 6 | 'warning_level=2', 7 | 'b_ndebug=if-release', 8 | ]) 9 | 10 | add_project_arguments('-Wmissing-prototypes', language: 'c') 11 | 12 | if get_option('compile_app') 13 | subdir('app') 14 | endif 15 | 16 | if get_option('compile_server') 17 | subdir('server') 18 | endif 19 | -------------------------------------------------------------------------------- /app/src/uhid/mouse_uhid.h: -------------------------------------------------------------------------------- 1 | #ifndef SC_MOUSE_UHID_H 2 | #define SC_MOUSE_UHID_H 3 | 4 | #include 5 | 6 | #include "controller.h" 7 | #include "trait/mouse_processor.h" 8 | 9 | struct sc_mouse_uhid { 10 | struct sc_mouse_processor mouse_processor; // mouse processor trait 11 | 12 | struct sc_controller *controller; 13 | }; 14 | 15 | bool 16 | sc_mouse_uhid_init(struct sc_mouse_uhid *mouse, 17 | struct sc_controller *controller); 18 | 19 | #endif 20 | -------------------------------------------------------------------------------- /app/data/scrcpy.desktop: -------------------------------------------------------------------------------- 1 | [Desktop Entry] 2 | Name=scrcpy 3 | GenericName=Android Remote Control 4 | Comment=Display and control your Android device 5 | # For some users, the PATH or ADB environment variables are set from the shell 6 | # startup file, like .bashrc or .zshrc… Run an interactive shell to get 7 | # environment correctly initialized. 8 | Exec=/bin/sh -c "\\$SHELL -i -c scrcpy" 9 | Icon=scrcpy 10 | Terminal=false 11 | Type=Application 12 | Categories=Utility;RemoteAccess; 13 | StartupNotify=false 14 | -------------------------------------------------------------------------------- /app/src/scrcpy.h: -------------------------------------------------------------------------------- 1 | #ifndef SCRCPY_H 2 | #define SCRCPY_H 3 | 4 | #include "common.h" 5 | 6 | #include 7 | #include "options.h" 8 | 9 | enum scrcpy_exit_code { 10 | // Normal program termination 11 | SCRCPY_EXIT_SUCCESS, 12 | 13 | // No connection could be established 14 | SCRCPY_EXIT_FAILURE, 15 | 16 | // Device was disconnected while running 17 | SCRCPY_EXIT_DISCONNECTED, 18 | }; 19 | 20 | enum scrcpy_exit_code 21 | scrcpy(struct scrcpy_options *options); 22 | 23 | #endif 24 | -------------------------------------------------------------------------------- /app/src/usb/mouse_aoa.h: -------------------------------------------------------------------------------- 1 | #ifndef SC_MOUSE_AOA_H 2 | #define SC_MOUSE_AOA_H 3 | 4 | #include "common.h" 5 | 6 | #include 7 | 8 | #include "aoa_hid.h" 9 | #include "trait/mouse_processor.h" 10 | 11 | struct sc_mouse_aoa { 12 | struct sc_mouse_processor mouse_processor; // mouse processor trait 13 | 14 | struct sc_aoa *aoa; 15 | }; 16 | 17 | bool 18 | sc_mouse_aoa_init(struct sc_mouse_aoa *mouse, struct sc_aoa *aoa); 19 | 20 | void 21 | sc_mouse_aoa_destroy(struct sc_mouse_aoa *mouse); 22 | 23 | #endif 24 | -------------------------------------------------------------------------------- /app/scrcpy-windows.manifest: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | true 6 | PerMonitorV2 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /app/data/scrcpy-console.desktop: -------------------------------------------------------------------------------- 1 | [Desktop Entry] 2 | Name=scrcpy (console) 3 | GenericName=Android Remote Control 4 | Comment=Display and control your Android device 5 | # For some users, the PATH or ADB environment variables are set from the shell 6 | # startup file, like .bashrc or .zshrc… Run an interactive shell to get 7 | # environment correctly initialized. 8 | Exec=/bin/sh -c "\\$SHELL -i -c 'scrcpy --pause-on-exit=if-error'" 9 | Icon=scrcpy 10 | Terminal=true 11 | Type=Application 12 | Categories=Utility;RemoteAccess; 13 | StartupNotify=false 14 | -------------------------------------------------------------------------------- /app/src/coords.h: -------------------------------------------------------------------------------- 1 | #ifndef SC_COORDS 2 | #define SC_COORDS 3 | 4 | #include 5 | 6 | struct sc_size { 7 | uint16_t width; 8 | uint16_t height; 9 | }; 10 | 11 | struct sc_point { 12 | int32_t x; 13 | int32_t y; 14 | }; 15 | 16 | struct sc_position { 17 | // The video screen size may be different from the real device screen size, 18 | // so store to which size the absolute position apply, to scale it 19 | // accordingly. 20 | struct sc_size screen_size; 21 | struct sc_point point; 22 | }; 23 | 24 | #endif 25 | -------------------------------------------------------------------------------- /app/src/util/term.h: -------------------------------------------------------------------------------- 1 | #ifndef SC_TERM_H 2 | #define SC_TERM_H 3 | 4 | #include "common.h" 5 | 6 | #include 7 | 8 | /** 9 | * Return the terminal dimensions 10 | * 11 | * Return false if the dimensions could not be retrieved. 12 | * 13 | * Otherwise, return true, and: 14 | * - if `rows` is not NULL, then the number of rows is written to `*rows`. 15 | * - if `columns` is not NULL, then the number of columns is written to 16 | * `*columns`. 17 | */ 18 | bool 19 | sc_term_get_size(unsigned *rows, unsigned *cols); 20 | 21 | #endif 22 | -------------------------------------------------------------------------------- /app/src/mouse_sdk.h: -------------------------------------------------------------------------------- 1 | #ifndef SC_MOUSE_SDK_H 2 | #define SC_MOUSE_SDK_H 3 | 4 | #include "common.h" 5 | 6 | #include 7 | 8 | #include "controller.h" 9 | #include "screen.h" 10 | #include "trait/mouse_processor.h" 11 | 12 | struct sc_mouse_sdk { 13 | struct sc_mouse_processor mouse_processor; // mouse processor trait 14 | 15 | struct sc_controller *controller; 16 | bool mouse_hover; 17 | }; 18 | 19 | void 20 | sc_mouse_sdk_init(struct sc_mouse_sdk *m, struct sc_controller *controller, 21 | bool mouse_hover); 22 | 23 | #endif 24 | -------------------------------------------------------------------------------- /server/src/main/java/com/genymobile/scrcpy/AsyncProcessor.java: -------------------------------------------------------------------------------- 1 | package com.genymobile.scrcpy; 2 | 3 | public interface AsyncProcessor { 4 | interface TerminationListener { 5 | /** 6 | * Notify processor termination 7 | * 8 | * @param fatalError {@code true} if this must cause the termination of the whole scrcpy-server. 9 | */ 10 | void onTerminated(boolean fatalError); 11 | } 12 | 13 | void start(TerminationListener listener); 14 | 15 | void stop(); 16 | 17 | void join() throws InterruptedException; 18 | } 19 | -------------------------------------------------------------------------------- /server/src/main/java/com/genymobile/scrcpy/audio/AudioCaptureException.java: -------------------------------------------------------------------------------- 1 | package com.genymobile.scrcpy.audio; 2 | 3 | /** 4 | * Exception for any audio capture issue. 5 | *

6 | * This includes the case where audio capture failed on Android 11 specifically because the running App (Shell) was not in foreground. 7 | *

8 | * Its purpose is to disable audio without errors (that's why the exception is empty, any error message must be printed by the caller before 9 | * throwing the exception). 10 | */ 11 | public class AudioCaptureException extends Exception { 12 | } 13 | -------------------------------------------------------------------------------- /server/src/main/java/com/genymobile/scrcpy/util/SettingsException.java: -------------------------------------------------------------------------------- 1 | package com.genymobile.scrcpy.util; 2 | 3 | public class SettingsException extends Exception { 4 | private static String createMessage(String method, String table, String key, String value) { 5 | return "Could not access settings: " + method + " " + table + " " + key + (value != null ? " " + value : ""); 6 | } 7 | 8 | public SettingsException(String method, String table, String key, String value, Throwable cause) { 9 | super(createMessage(method, table, key, value), cause); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /app/src/hid/hid_event.h: -------------------------------------------------------------------------------- 1 | #ifndef SC_HID_EVENT_H 2 | #define SC_HID_EVENT_H 3 | 4 | #include "common.h" 5 | 6 | #include 7 | 8 | #define SC_HID_MAX_SIZE 15 9 | 10 | struct sc_hid_input { 11 | uint16_t hid_id; 12 | uint8_t data[SC_HID_MAX_SIZE]; 13 | uint8_t size; 14 | }; 15 | 16 | struct sc_hid_open { 17 | uint16_t hid_id; 18 | const char *name; // pointer to static memory 19 | const uint8_t *report_desc; // pointer to static memory 20 | size_t report_desc_size; 21 | }; 22 | 23 | struct sc_hid_close { 24 | uint16_t hid_id; 25 | }; 26 | 27 | #endif 28 | -------------------------------------------------------------------------------- /app/src/uhid/gamepad_uhid.h: -------------------------------------------------------------------------------- 1 | #ifndef SC_GAMEPAD_UHID_H 2 | #define SC_GAMEPAD_UHID_H 3 | 4 | #include "common.h" 5 | 6 | #include 7 | 8 | #include "controller.h" 9 | #include "hid/hid_gamepad.h" 10 | #include "trait/gamepad_processor.h" 11 | 12 | struct sc_gamepad_uhid { 13 | struct sc_gamepad_processor gamepad_processor; // gamepad processor trait 14 | 15 | struct sc_hid_gamepad hid; 16 | struct sc_controller *controller; 17 | }; 18 | 19 | void 20 | sc_gamepad_uhid_init(struct sc_gamepad_uhid *mouse, 21 | struct sc_controller *controller); 22 | 23 | #endif 24 | -------------------------------------------------------------------------------- /app/src/util/average.c: -------------------------------------------------------------------------------- 1 | #include "average.h" 2 | 3 | #include 4 | 5 | void 6 | sc_average_init(struct sc_average *avg, unsigned range) { 7 | avg->range = range; 8 | avg->avg = 0; 9 | avg->count = 0; 10 | } 11 | 12 | void 13 | sc_average_push(struct sc_average *avg, float value) { 14 | if (avg->count < avg->range) { 15 | ++avg->count; 16 | } 17 | 18 | assert(avg->count); 19 | avg->avg = ((avg->count - 1) * avg->avg + value) / avg->count; 20 | } 21 | 22 | float 23 | sc_average_get(struct sc_average *avg) { 24 | assert(avg->count); 25 | return avg->avg; 26 | } 27 | -------------------------------------------------------------------------------- /app/src/usb/gamepad_aoa.h: -------------------------------------------------------------------------------- 1 | #ifndef SC_GAMEPAD_AOA_H 2 | #define SC_GAMEPAD_AOA_H 3 | 4 | #include "common.h" 5 | 6 | #include 7 | 8 | #include "aoa_hid.h" 9 | #include "hid/hid_gamepad.h" 10 | #include "trait/gamepad_processor.h" 11 | 12 | struct sc_gamepad_aoa { 13 | struct sc_gamepad_processor gamepad_processor; // gamepad processor trait 14 | 15 | struct sc_hid_gamepad hid; 16 | struct sc_aoa *aoa; 17 | }; 18 | 19 | void 20 | sc_gamepad_aoa_init(struct sc_gamepad_aoa *gamepad, struct sc_aoa *aoa); 21 | 22 | void 23 | sc_gamepad_aoa_destroy(struct sc_gamepad_aoa *gamepad); 24 | 25 | #endif 26 | -------------------------------------------------------------------------------- /server/src/main/java/com/genymobile/scrcpy/video/VideoSource.java: -------------------------------------------------------------------------------- 1 | package com.genymobile.scrcpy.video; 2 | 3 | public enum VideoSource { 4 | DISPLAY("display"), 5 | CAMERA("camera"); 6 | 7 | private final String name; 8 | 9 | VideoSource(String name) { 10 | this.name = name; 11 | } 12 | 13 | public static VideoSource findByName(String name) { 14 | for (VideoSource videoSource : VideoSource.values()) { 15 | if (name.equals(videoSource.name)) { 16 | return videoSource; 17 | } 18 | } 19 | 20 | return null; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /app/src/util/intmap.h: -------------------------------------------------------------------------------- 1 | #ifndef SC_ARRAYMAP_H 2 | #define SC_ARRAYMAP_H 3 | 4 | #include "common.h" 5 | 6 | #include 7 | 8 | struct sc_intmap_entry { 9 | int32_t key; 10 | int32_t value; 11 | }; 12 | 13 | const struct sc_intmap_entry * 14 | sc_intmap_find_entry(const struct sc_intmap_entry entries[], size_t len, 15 | int32_t key); 16 | 17 | /** 18 | * MAP is expected to be a static array of sc_intmap_entry, so that 19 | * ARRAY_LEN(MAP) can be computed statically. 20 | */ 21 | #define SC_INTMAP_FIND_ENTRY(MAP, KEY) \ 22 | sc_intmap_find_entry(MAP, ARRAY_LEN(MAP), KEY) 23 | 24 | #endif 25 | -------------------------------------------------------------------------------- /app/src/usb/keyboard_aoa.h: -------------------------------------------------------------------------------- 1 | #ifndef SC_KEYBOARD_AOA_H 2 | #define SC_KEYBOARD_AOA_H 3 | 4 | #include "common.h" 5 | 6 | #include 7 | 8 | #include "aoa_hid.h" 9 | #include "hid/hid_keyboard.h" 10 | #include "trait/key_processor.h" 11 | 12 | struct sc_keyboard_aoa { 13 | struct sc_key_processor key_processor; // key processor trait 14 | 15 | struct sc_hid_keyboard hid; 16 | struct sc_aoa *aoa; 17 | 18 | bool mod_lock_synchronized; 19 | }; 20 | 21 | bool 22 | sc_keyboard_aoa_init(struct sc_keyboard_aoa *kb, struct sc_aoa *aoa); 23 | 24 | void 25 | sc_keyboard_aoa_destroy(struct sc_keyboard_aoa *kb); 26 | 27 | #endif 28 | -------------------------------------------------------------------------------- /app/scrcpy-windows.rc: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | 0 ICON "data/icon.ico" 4 | 1 RT_MANIFEST "scrcpy-windows.manifest" 5 | 2 VERSIONINFO 6 | BEGIN 7 | BLOCK "StringFileInfo" 8 | BEGIN 9 | BLOCK "040904E4" 10 | BEGIN 11 | VALUE "FileDescription", "Display and control your Android device" 12 | VALUE "InternalName", "scrcpy" 13 | VALUE "LegalCopyright", "Romain Vimont, Genymobile" 14 | VALUE "OriginalFilename", "scrcpy.exe" 15 | VALUE "ProductName", "scrcpy" 16 | VALUE "ProductVersion", "2.7" 17 | END 18 | END 19 | BLOCK "VarFileInfo" 20 | BEGIN 21 | VALUE "Translation", 0x409, 1252 22 | END 23 | END 24 | -------------------------------------------------------------------------------- /run: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env 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_ICON_PATH="app/data/icon.png" \ 24 | SCRCPY_SERVER_PATH="$BUILDDIR/server/scrcpy-server" \ 25 | "$BUILDDIR/app/scrcpy" "$@" 26 | -------------------------------------------------------------------------------- /app/src/util/tick.h: -------------------------------------------------------------------------------- 1 | #ifndef SC_TICK_H 2 | #define SC_TICK_H 3 | 4 | #include "common.h" 5 | 6 | #include 7 | 8 | typedef int64_t sc_tick; 9 | #define PRItick PRIi64 10 | #define SC_TICK_FREQ 1000000 // microsecond 11 | 12 | // To be adapted if SC_TICK_FREQ changes 13 | #define SC_TICK_TO_NS(tick) ((tick) * 1000) 14 | #define SC_TICK_TO_US(tick) (tick) 15 | #define SC_TICK_TO_MS(tick) ((tick) / 1000) 16 | #define SC_TICK_TO_SEC(tick) ((tick) / 1000000) 17 | #define SC_TICK_FROM_NS(ns) ((ns) / 1000) 18 | #define SC_TICK_FROM_US(us) (us) 19 | #define SC_TICK_FROM_MS(ms) ((ms) * 1000) 20 | #define SC_TICK_FROM_SEC(sec) ((sec) * 1000000) 21 | 22 | sc_tick 23 | sc_tick_now(void); 24 | 25 | #endif 26 | -------------------------------------------------------------------------------- /app/src/trait/frame_sink.h: -------------------------------------------------------------------------------- 1 | #ifndef SC_FRAME_SINK_H 2 | #define SC_FRAME_SINK_H 3 | 4 | #include "common.h" 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | /** 11 | * Frame sink trait. 12 | * 13 | * Component able to receive AVFrames should implement this trait. 14 | */ 15 | struct sc_frame_sink { 16 | const struct sc_frame_sink_ops *ops; 17 | }; 18 | 19 | struct sc_frame_sink_ops { 20 | /* The codec context is valid until the sink is closed */ 21 | bool (*open)(struct sc_frame_sink *sink, const AVCodecContext *ctx); 22 | void (*close)(struct sc_frame_sink *sink); 23 | bool (*push)(struct sc_frame_sink *sink, const AVFrame *frame); 24 | }; 25 | 26 | #endif 27 | -------------------------------------------------------------------------------- /server/src/main/java/com/genymobile/scrcpy/audio/AudioCapture.java: -------------------------------------------------------------------------------- 1 | package com.genymobile.scrcpy.audio; 2 | 3 | import android.media.MediaCodec; 4 | 5 | import java.nio.ByteBuffer; 6 | 7 | public interface AudioCapture { 8 | void checkCompatibility() throws AudioCaptureException; 9 | void start() throws AudioCaptureException; 10 | void stop(); 11 | 12 | /** 13 | * Read a chunk of {@link AudioConfig#MAX_READ_SIZE} samples. 14 | * 15 | * @param outDirectBuffer The target buffer 16 | * @param outBufferInfo The info to provide to MediaCodec 17 | * @return the number of bytes actually read. 18 | */ 19 | int read(ByteBuffer outDirectBuffer, MediaCodec.BufferInfo outBufferInfo); 20 | } 21 | -------------------------------------------------------------------------------- /server/src/main/java/com/genymobile/scrcpy/audio/AudioSource.java: -------------------------------------------------------------------------------- 1 | package com.genymobile.scrcpy.audio; 2 | 3 | public enum AudioSource { 4 | OUTPUT("output"), 5 | MIC("mic"), 6 | PLAYBACK("playback"); 7 | 8 | private final String name; 9 | 10 | AudioSource(String name) { 11 | this.name = name; 12 | } 13 | 14 | public boolean isDirect() { 15 | return this != PLAYBACK; 16 | } 17 | 18 | public static AudioSource findByName(String name) { 19 | for (AudioSource audioSource : AudioSource.values()) { 20 | if (name.equals(audioSource.name)) { 21 | return audioSource; 22 | } 23 | } 24 | 25 | return null; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /app/src/cli.h: -------------------------------------------------------------------------------- 1 | #ifndef SCRCPY_CLI_H 2 | #define SCRCPY_CLI_H 3 | 4 | #include "common.h" 5 | 6 | #include 7 | 8 | #include "options.h" 9 | 10 | enum sc_pause_on_exit { 11 | SC_PAUSE_ON_EXIT_TRUE, 12 | SC_PAUSE_ON_EXIT_FALSE, 13 | SC_PAUSE_ON_EXIT_IF_ERROR, 14 | }; 15 | 16 | struct scrcpy_cli_args { 17 | struct scrcpy_options opts; 18 | bool help; 19 | bool version; 20 | enum sc_pause_on_exit pause_on_exit; 21 | }; 22 | 23 | void 24 | scrcpy_print_usage(const char *arg0); 25 | 26 | bool 27 | scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]); 28 | 29 | #ifdef SC_TEST 30 | bool 31 | sc_parse_shortcut_mods(const char *s, uint8_t *shortcut_mods); 32 | #endif 33 | 34 | #endif 35 | -------------------------------------------------------------------------------- /app/src/uhid/uhid_output.h: -------------------------------------------------------------------------------- 1 | #ifndef SC_UHID_OUTPUT_H 2 | #define SC_UHID_OUTPUT_H 3 | 4 | #include "common.h" 5 | 6 | #include 7 | #include 8 | 9 | /** 10 | * The communication with UHID devices is bidirectional. 11 | * 12 | * This component dispatches HID outputs to the expected processor. 13 | */ 14 | 15 | struct sc_uhid_devices { 16 | struct sc_keyboard_uhid *keyboard; 17 | }; 18 | 19 | void 20 | sc_uhid_devices_init(struct sc_uhid_devices *devices, 21 | struct sc_keyboard_uhid *keyboard); 22 | 23 | void 24 | sc_uhid_devices_process_hid_output(struct sc_uhid_devices *devices, uint16_t id, 25 | const uint8_t *data, size_t size); 26 | 27 | #endif 28 | -------------------------------------------------------------------------------- /server/src/main/java/com/genymobile/scrcpy/util/HandlerExecutor.java: -------------------------------------------------------------------------------- 1 | package com.genymobile.scrcpy.util; 2 | 3 | import android.os.Handler; 4 | 5 | import java.util.concurrent.Executor; 6 | import java.util.concurrent.RejectedExecutionException; 7 | 8 | // Inspired from hidden android.os.HandlerExecutor 9 | 10 | public class HandlerExecutor implements Executor { 11 | private final Handler handler; 12 | 13 | public HandlerExecutor(Handler handler) { 14 | this.handler = handler; 15 | } 16 | 17 | @Override 18 | public void execute(Runnable command) { 19 | if (!handler.post(command)) { 20 | throw new RejectedExecutionException(handler + " is shutting down"); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /app/src/decoder.h: -------------------------------------------------------------------------------- 1 | #ifndef SC_DECODER_H 2 | #define SC_DECODER_H 3 | 4 | #include "common.h" 5 | 6 | #include "trait/frame_source.h" 7 | #include "trait/packet_sink.h" 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | struct sc_decoder { 14 | struct sc_packet_sink packet_sink; // packet sink trait 15 | struct sc_frame_source frame_source; // frame source trait 16 | 17 | const char *name; // must be statically allocated (e.g. a string literal) 18 | 19 | AVCodecContext *ctx; 20 | AVFrame *frame; 21 | }; 22 | 23 | // The name must be statically allocated (e.g. a string literal) 24 | void 25 | sc_decoder_init(struct sc_decoder *decoder, const char *name); 26 | 27 | #endif 28 | -------------------------------------------------------------------------------- /app/src/util/rand.c: -------------------------------------------------------------------------------- 1 | #include "rand.h" 2 | 3 | #include 4 | 5 | #include "tick.h" 6 | 7 | void sc_rand_init(struct sc_rand *rand) { 8 | sc_tick seed = sc_tick_now(); // microsecond precision 9 | rand->xsubi[0] = (seed >> 32) & 0xFFFF; 10 | rand->xsubi[1] = (seed >> 16) & 0xFFFF; 11 | rand->xsubi[2] = seed & 0xFFFF; 12 | } 13 | 14 | uint32_t sc_rand_u32(struct sc_rand *rand) { 15 | // jrand returns a value in range [-2^31, 2^31] 16 | // conversion from signed to unsigned is well-defined to wrap-around 17 | return jrand48(rand->xsubi); 18 | } 19 | 20 | uint64_t sc_rand_u64(struct sc_rand *rand) { 21 | uint32_t msb = sc_rand_u32(rand); 22 | uint32_t lsb = sc_rand_u32(rand); 23 | return ((uint64_t) msb << 32) | lsb; 24 | } 25 | -------------------------------------------------------------------------------- /install_release.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -e 3 | 4 | BUILDDIR=build-auto 5 | PREBUILT_SERVER_URL=https://github.com/Genymobile/scrcpy/releases/download/v2.7/scrcpy-server-v2.7 6 | PREBUILT_SERVER_SHA256=a23c5659f36c260f105c022d27bcb3eafffa26070e7baa9eda66d01377a1adba 7 | 8 | echo "[scrcpy] Downloading prebuilt server..." 9 | wget "$PREBUILT_SERVER_URL" -O scrcpy-server 10 | echo "[scrcpy] Verifying prebuilt server..." 11 | echo "$PREBUILT_SERVER_SHA256 scrcpy-server" | sha256sum --check 12 | 13 | echo "[scrcpy] Building client..." 14 | rm -rf "$BUILDDIR" 15 | meson setup "$BUILDDIR" --buildtype=release --strip -Db_lto=true \ 16 | -Dprebuilt_server=scrcpy-server 17 | cd "$BUILDDIR" 18 | ninja 19 | 20 | echo "[scrcpy] Installing (sudo)..." 21 | sudo ninja install 22 | -------------------------------------------------------------------------------- /app/src/adb/adb_parser.h: -------------------------------------------------------------------------------- 1 | #ifndef SC_ADB_PARSER_H 2 | #define SC_ADB_PARSER_H 3 | 4 | #include "common.h" 5 | 6 | #include 7 | 8 | #include "adb_device.h" 9 | 10 | /** 11 | * Parse the available devices from the output of `adb devices` 12 | * 13 | * The parameter must be a NUL-terminated string. 14 | * 15 | * Warning: this function modifies the buffer for optimization purposes. 16 | */ 17 | bool 18 | sc_adb_parse_devices(char *str, struct sc_vec_adb_devices *out_vec); 19 | 20 | /** 21 | * Parse the ip from the output of `adb shell ip route` 22 | * 23 | * The parameter must be a NUL-terminated string. 24 | * 25 | * Warning: this function modifies the buffer for optimization purposes. 26 | */ 27 | char * 28 | sc_adb_parse_device_ip(char *str); 29 | 30 | #endif 31 | -------------------------------------------------------------------------------- /app/src/uhid/keyboard_uhid.h: -------------------------------------------------------------------------------- 1 | #ifndef SC_KEYBOARD_UHID_H 2 | #define SC_KEYBOARD_UHID_H 3 | 4 | #include "common.h" 5 | 6 | #include 7 | 8 | #include "controller.h" 9 | #include "hid/hid_keyboard.h" 10 | #include "trait/key_processor.h" 11 | 12 | struct sc_keyboard_uhid { 13 | struct sc_key_processor key_processor; // key processor trait 14 | 15 | struct sc_hid_keyboard hid; 16 | struct sc_controller *controller; 17 | uint16_t device_mod; 18 | }; 19 | 20 | bool 21 | sc_keyboard_uhid_init(struct sc_keyboard_uhid *kb, 22 | struct sc_controller *controller); 23 | 24 | void 25 | sc_keyboard_uhid_process_hid_output(struct sc_keyboard_uhid *kb, 26 | const uint8_t *data, size_t size); 27 | 28 | #endif 29 | -------------------------------------------------------------------------------- /config/android-checkstyle.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'checkstyle' 2 | check.dependsOn 'checkstyle' 3 | 4 | checkstyle { 5 | toolVersion = '10.12.5' 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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /server/src/main/java/com/genymobile/scrcpy/control/ControlChannel.java: -------------------------------------------------------------------------------- 1 | package com.genymobile.scrcpy.control; 2 | 3 | import android.net.LocalSocket; 4 | 5 | import java.io.IOException; 6 | 7 | public final class ControlChannel { 8 | 9 | private final ControlMessageReader reader; 10 | private final DeviceMessageWriter writer; 11 | 12 | public ControlChannel(LocalSocket controlSocket) throws IOException { 13 | reader = new ControlMessageReader(controlSocket.getInputStream()); 14 | writer = new DeviceMessageWriter(controlSocket.getOutputStream()); 15 | } 16 | 17 | public ControlMessage recv() throws IOException { 18 | return reader.read(); 19 | } 20 | 21 | public void send(DeviceMessage msg) throws IOException { 22 | writer.write(msg); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /server/src/main/java/com/genymobile/scrcpy/util/StringUtils.java: -------------------------------------------------------------------------------- 1 | package com.genymobile.scrcpy.util; 2 | 3 | public final class StringUtils { 4 | private StringUtils() { 5 | // not instantiable 6 | } 7 | 8 | public static int getUtf8TruncationIndex(byte[] utf8, int maxLength) { 9 | int len = utf8.length; 10 | if (len <= maxLength) { 11 | return len; 12 | } 13 | len = maxLength; 14 | // see UTF-8 encoding 15 | while ((utf8[len] & 0x80) != 0 && (utf8[len] & 0xc0) != 0xc0) { 16 | // the next byte is not the start of a new UTF-8 codepoint 17 | // so if we would cut there, the character would be truncated 18 | len--; 19 | } 20 | return len; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /server/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | namespace 'com.genymobile.scrcpy' 5 | compileSdk 34 6 | defaultConfig { 7 | applicationId "com.genymobile.scrcpy" 8 | minSdkVersion 21 9 | targetSdkVersion 34 10 | versionCode 20700 11 | versionName "2.7" 12 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 13 | } 14 | buildTypes { 15 | release { 16 | minifyEnabled false 17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 18 | } 19 | } 20 | buildFeatures { 21 | buildConfig true 22 | aidl true 23 | } 24 | } 25 | 26 | dependencies { 27 | testImplementation 'junit:junit:4.13.2' 28 | } 29 | 30 | apply from: "$project.rootDir/config/android-checkstyle.gradle" 31 | -------------------------------------------------------------------------------- /app/src/opengl.h: -------------------------------------------------------------------------------- 1 | #ifndef SC_OPENGL_H 2 | #define SC_OPENGL_H 3 | 4 | #include "common.h" 5 | 6 | #include 7 | #include 8 | 9 | struct sc_opengl { 10 | const char *version; 11 | bool is_opengles; 12 | int version_major; 13 | int version_minor; 14 | 15 | const GLubyte * 16 | (*GetString)(GLenum name); 17 | 18 | void 19 | (*TexParameterf)(GLenum target, GLenum pname, GLfloat param); 20 | 21 | void 22 | (*TexParameteri)(GLenum target, GLenum pname, GLint param); 23 | 24 | void 25 | (*GenerateMipmap)(GLenum target); 26 | }; 27 | 28 | void 29 | sc_opengl_init(struct sc_opengl *gl); 30 | 31 | bool 32 | sc_opengl_version_at_least(struct sc_opengl *gl, 33 | int minver_major, int minver_minor, 34 | int minver_es_major, int minver_es_minor); 35 | 36 | #endif 37 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | - [ ] I have checked that a similar [feature request](https://github.com/Genymobile/scrcpy/issues?q=is%3Aopen+is%3Aissue+label%3A%22feature+request%22) does not already exist. 11 | 12 | **Is your feature request related to a problem? Please describe.** 13 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 14 | 15 | **Describe the solution you'd like** 16 | A clear and concise description of what you want to happen. 17 | 18 | **Describe alternatives you've considered** 19 | A clear and concise description of any alternative solutions or features you've considered. 20 | 21 | **Additional context** 22 | Add any other context or screenshots about the feature request here. 23 | -------------------------------------------------------------------------------- /app/src/uhid/uhid_output.c: -------------------------------------------------------------------------------- 1 | #include "uhid_output.h" 2 | 3 | #include 4 | #include 5 | 6 | #include "uhid/keyboard_uhid.h" 7 | #include "util/log.h" 8 | 9 | void 10 | sc_uhid_devices_init(struct sc_uhid_devices *devices, 11 | struct sc_keyboard_uhid *keyboard) { 12 | devices->keyboard = keyboard; 13 | } 14 | 15 | void 16 | sc_uhid_devices_process_hid_output(struct sc_uhid_devices *devices, uint16_t id, 17 | const uint8_t *data, size_t size) { 18 | if (id == SC_HID_ID_KEYBOARD) { 19 | if (devices->keyboard) { 20 | sc_keyboard_uhid_process_hid_output(devices->keyboard, data, size); 21 | } else { 22 | LOGW("Unexpected keyboard HID output without UHID keyboard"); 23 | } 24 | } else { 25 | LOGW("HID output ignored for id %" PRIu16, id); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /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('prebuilt_server', type: 'string', description: 'Path of the prebuilt server') 4 | option('portable', type: 'boolean', value: false, description: 'Use scrcpy-server from the same directory as the scrcpy executable') 5 | option('server_debugger', type: 'boolean', value: false, description: 'Run a server debugger and wait for a client to be attached') 6 | option('server_debugger_method', type: 'combo', choices: ['old', 'new'], value: 'new', description: 'Select the debugger method (Android < 9: "old", Android >= 9: "new")') 7 | option('v4l2', type: 'boolean', value: true, description: 'Enable V4L2 feature when supported') 8 | option('usb', type: 'boolean', value: true, description: 'Enable HID/OTG features when supported') 9 | -------------------------------------------------------------------------------- /server/src/main/aidl/android/content/IOnPrimaryClipChangedListener.aidl: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2008, The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package android.content; 18 | 19 | /** 20 | * {@hide} 21 | */ 22 | oneway interface IOnPrimaryClipChangedListener { 23 | void dispatchPrimaryClipChanged(); 24 | } 25 | -------------------------------------------------------------------------------- /app/src/hid/hid_mouse.h: -------------------------------------------------------------------------------- 1 | #ifndef SC_HID_MOUSE_H 2 | #define SC_HID_MOUSE_H 3 | 4 | #include "common.h" 5 | 6 | #include 7 | 8 | #include "hid/hid_event.h" 9 | #include "input_events.h" 10 | 11 | #define SC_HID_ID_MOUSE 2 12 | 13 | void 14 | sc_hid_mouse_generate_open(struct sc_hid_open *hid_open); 15 | 16 | void 17 | sc_hid_mouse_generate_close(struct sc_hid_close *hid_close); 18 | 19 | void 20 | sc_hid_mouse_generate_input_from_motion(struct sc_hid_input *hid_input, 21 | const struct sc_mouse_motion_event *event); 22 | 23 | void 24 | sc_hid_mouse_generate_input_from_click(struct sc_hid_input *hid_input, 25 | const struct sc_mouse_click_event *event); 26 | 27 | void 28 | sc_hid_mouse_generate_input_from_scroll(struct sc_hid_input *hid_input, 29 | const struct sc_mouse_scroll_event *event); 30 | 31 | #endif 32 | -------------------------------------------------------------------------------- /app/src/keyboard_sdk.h: -------------------------------------------------------------------------------- 1 | #ifndef SC_KEYBOARD_SDK_H 2 | #define SC_KEYBOARD_SDK_H 3 | 4 | #include "common.h" 5 | 6 | #include 7 | 8 | #include "controller.h" 9 | #include "options.h" 10 | #include "trait/key_processor.h" 11 | 12 | struct sc_keyboard_sdk { 13 | struct sc_key_processor key_processor; // key processor trait 14 | 15 | struct sc_controller *controller; 16 | 17 | // SDL reports repeated events as a boolean, but Android expects the actual 18 | // number of repetitions. This variable keeps track of the count. 19 | unsigned repeat; 20 | 21 | enum sc_key_inject_mode key_inject_mode; 22 | bool forward_key_repeat; 23 | }; 24 | 25 | void 26 | sc_keyboard_sdk_init(struct sc_keyboard_sdk *kb, 27 | struct sc_controller *controller, 28 | enum sc_key_inject_mode key_inject_mode, 29 | bool forward_key_repeat); 30 | 31 | #endif 32 | -------------------------------------------------------------------------------- /app/src/v4l2_sink.h: -------------------------------------------------------------------------------- 1 | #ifndef SC_V4L2_SINK_H 2 | #define SC_V4L2_SINK_H 3 | 4 | #include "common.h" 5 | 6 | #include 7 | #include 8 | 9 | #include "coords.h" 10 | #include "trait/frame_sink.h" 11 | #include "frame_buffer.h" 12 | #include "util/tick.h" 13 | 14 | struct sc_v4l2_sink { 15 | struct sc_frame_sink frame_sink; // frame sink trait 16 | 17 | struct sc_frame_buffer fb; 18 | AVFormatContext *format_ctx; 19 | AVCodecContext *encoder_ctx; 20 | 21 | char *device_name; 22 | 23 | sc_thread thread; 24 | sc_mutex mutex; 25 | sc_cond cond; 26 | bool has_frame; 27 | bool stopped; 28 | bool header_written; 29 | 30 | AVFrame *frame; 31 | AVPacket *packet; 32 | }; 33 | 34 | bool 35 | sc_v4l2_sink_init(struct sc_v4l2_sink *vs, const char *device_name); 36 | 37 | void 38 | sc_v4l2_sink_destroy(struct sc_v4l2_sink *vs); 39 | 40 | #endif 41 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /app/src/util/process_intr.c: -------------------------------------------------------------------------------- 1 | #include "process_intr.h" 2 | 3 | ssize_t 4 | sc_pipe_read_intr(struct sc_intr *intr, sc_pid pid, sc_pipe pipe, char *data, 5 | size_t len) { 6 | if (intr && !sc_intr_set_process(intr, pid)) { 7 | // Already interrupted 8 | return false; 9 | } 10 | 11 | ssize_t ret = sc_pipe_read(pipe, data, len); 12 | 13 | if (intr) { 14 | sc_intr_set_process(intr, SC_PROCESS_NONE); 15 | } 16 | 17 | return ret; 18 | } 19 | 20 | ssize_t 21 | sc_pipe_read_all_intr(struct sc_intr *intr, sc_pid pid, sc_pipe pipe, 22 | char *data, size_t len) { 23 | if (intr && !sc_intr_set_process(intr, pid)) { 24 | // Already interrupted 25 | return false; 26 | } 27 | 28 | ssize_t ret = sc_pipe_read_all(pipe, data, len); 29 | 30 | if (intr) { 31 | sc_intr_set_process(intr, SC_PROCESS_NONE); 32 | } 33 | 34 | return ret; 35 | } 36 | -------------------------------------------------------------------------------- /server/scripts/build-wrapper.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env 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 | -------------------------------------------------------------------------------- /app/deps/adb.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -ex 3 | DEPS_DIR=$(dirname ${BASH_SOURCE[0]}) 4 | cd "$DEPS_DIR" 5 | . common 6 | 7 | VERSION=35.0.0 8 | FILENAME=platform-tools_r$VERSION-windows.zip 9 | PROJECT_DIR=platform-tools-$VERSION 10 | SHA256SUM=7ab78a8f8b305ae4d0de647d99c43599744de61a0838d3a47bda0cdffefee87e 11 | 12 | cd "$SOURCES_DIR" 13 | 14 | if [[ -d "$PROJECT_DIR" ]] 15 | then 16 | echo "$PWD/$PROJECT_DIR" found 17 | else 18 | get_file "https://dl.google.com/android/repository/$FILENAME" "$FILENAME" "$SHA256SUM" 19 | mkdir -p "$PROJECT_DIR" 20 | cd "$PROJECT_DIR" 21 | ZIP_PREFIX=platform-tools 22 | unzip "../$FILENAME" \ 23 | "$ZIP_PREFIX"/AdbWinApi.dll \ 24 | "$ZIP_PREFIX"/AdbWinUsbApi.dll \ 25 | "$ZIP_PREFIX"/adb.exe 26 | mv "$ZIP_PREFIX"/* . 27 | rmdir "$ZIP_PREFIX" 28 | fi 29 | 30 | mkdir -p "$INSTALL_DIR/$HOST/bin" 31 | cd "$INSTALL_DIR/$HOST/bin" 32 | cp -r "$SOURCES_DIR/$PROJECT_DIR"/. "$INSTALL_DIR/$HOST/bin/" 33 | -------------------------------------------------------------------------------- /server/src/main/aidl/android/view/IDisplayFoldListener.aidl: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package android.view; 18 | 19 | /** 20 | * {@hide} 21 | */ 22 | oneway interface IDisplayFoldListener 23 | { 24 | /** Called when the foldedness of a display changes */ 25 | void onDisplayFoldChanged(int displayId, boolean folded); 26 | } 27 | -------------------------------------------------------------------------------- /app/src/events.h: -------------------------------------------------------------------------------- 1 | #ifndef SC_EVENTS_H 2 | #define SC_EVENTS_H 3 | 4 | #include "common.h" 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | enum { 11 | SC_EVENT_NEW_FRAME = SDL_USEREVENT, 12 | SC_EVENT_RUN_ON_MAIN_THREAD, 13 | SC_EVENT_DEVICE_DISCONNECTED, 14 | SC_EVENT_SERVER_CONNECTION_FAILED, 15 | SC_EVENT_SERVER_CONNECTED, 16 | SC_EVENT_USB_DEVICE_DISCONNECTED, 17 | SC_EVENT_DEMUXER_ERROR, 18 | SC_EVENT_RECORDER_ERROR, 19 | SC_EVENT_SCREEN_INIT_SIZE, 20 | SC_EVENT_TIME_LIMIT_REACHED, 21 | SC_EVENT_CONTROLLER_ERROR, 22 | SC_EVENT_AOA_OPEN_ERROR, 23 | }; 24 | 25 | bool 26 | sc_push_event_impl(uint32_t type, const char *name); 27 | 28 | #define sc_push_event(TYPE) sc_push_event_impl(TYPE, # TYPE) 29 | 30 | typedef void (*sc_runnable_fn)(void *userdata); 31 | 32 | bool 33 | sc_post_to_main_thread(sc_runnable_fn run, void *userdata); 34 | 35 | void 36 | sc_reject_new_runnables(void); 37 | 38 | #endif 39 | -------------------------------------------------------------------------------- /app/src/util/timeout.h: -------------------------------------------------------------------------------- 1 | #ifndef SC_TIMEOUT_H 2 | #define SC_TIMEOUT_H 3 | 4 | #include "common.h" 5 | 6 | #include 7 | 8 | #include "thread.h" 9 | #include "tick.h" 10 | 11 | struct sc_timeout { 12 | sc_thread thread; 13 | sc_tick deadline; 14 | 15 | sc_mutex mutex; 16 | sc_cond cond; 17 | bool stopped; 18 | 19 | const struct sc_timeout_callbacks *cbs; 20 | void *cbs_userdata; 21 | }; 22 | 23 | struct sc_timeout_callbacks { 24 | void (*on_timeout)(struct sc_timeout *timeout, void *userdata); 25 | }; 26 | 27 | bool 28 | sc_timeout_init(struct sc_timeout *timeout); 29 | 30 | void 31 | sc_timeout_destroy(struct sc_timeout *timeout); 32 | 33 | bool 34 | sc_timeout_start(struct sc_timeout *timeout, sc_tick deadline, 35 | const struct sc_timeout_callbacks *cbs, void *cbs_userdata); 36 | 37 | void 38 | sc_timeout_stop(struct sc_timeout *timeout); 39 | 40 | void 41 | sc_timeout_join(struct sc_timeout *timeout); 42 | 43 | #endif 44 | -------------------------------------------------------------------------------- /app/src/util/net_intr.h: -------------------------------------------------------------------------------- 1 | #ifndef SC_NET_INTR_H 2 | #define SC_NET_INTR_H 3 | 4 | #include "common.h" 5 | 6 | #include "intr.h" 7 | #include "net.h" 8 | 9 | bool 10 | net_connect_intr(struct sc_intr *intr, sc_socket socket, uint32_t addr, 11 | uint16_t port); 12 | 13 | bool 14 | net_listen_intr(struct sc_intr *intr, sc_socket server_socket, uint32_t addr, 15 | uint16_t port, int backlog); 16 | 17 | sc_socket 18 | net_accept_intr(struct sc_intr *intr, sc_socket server_socket); 19 | 20 | ssize_t 21 | net_recv_intr(struct sc_intr *intr, sc_socket socket, void *buf, size_t len); 22 | 23 | ssize_t 24 | net_recv_all_intr(struct sc_intr *intr, sc_socket socket, void *buf, 25 | size_t len); 26 | 27 | ssize_t 28 | net_send_intr(struct sc_intr *intr, sc_socket socket, const void *buf, 29 | size_t len); 30 | 31 | ssize_t 32 | net_send_all_intr(struct sc_intr *intr, sc_socket socket, const void *buf, 33 | size_t len); 34 | 35 | #endif 36 | -------------------------------------------------------------------------------- /app/src/trait/frame_source.h: -------------------------------------------------------------------------------- 1 | #ifndef SC_FRAME_SOURCE_H 2 | #define SC_FRAME_SOURCE_H 3 | 4 | #include "common.h" 5 | 6 | #include "frame_sink.h" 7 | 8 | #define SC_FRAME_SOURCE_MAX_SINKS 2 9 | 10 | /** 11 | * Frame source trait 12 | * 13 | * Component able to send AVFrames should implement this trait. 14 | */ 15 | struct sc_frame_source { 16 | struct sc_frame_sink *sinks[SC_FRAME_SOURCE_MAX_SINKS]; 17 | unsigned sink_count; 18 | }; 19 | 20 | void 21 | sc_frame_source_init(struct sc_frame_source *source); 22 | 23 | void 24 | sc_frame_source_add_sink(struct sc_frame_source *source, 25 | struct sc_frame_sink *sink); 26 | 27 | bool 28 | sc_frame_source_sinks_open(struct sc_frame_source *source, 29 | const AVCodecContext *ctx); 30 | 31 | void 32 | sc_frame_source_sinks_close(struct sc_frame_source *source); 33 | 34 | bool 35 | sc_frame_source_sinks_push(struct sc_frame_source *source, 36 | const AVFrame *frame); 37 | 38 | #endif 39 | -------------------------------------------------------------------------------- /app/src/sys/win/file.c: -------------------------------------------------------------------------------- 1 | #include "util/file.h" 2 | 3 | #include 4 | 5 | #include 6 | 7 | #include "util/log.h" 8 | #include "util/str.h" 9 | 10 | char * 11 | sc_file_get_executable_path(void) { 12 | HMODULE hModule = GetModuleHandleW(NULL); 13 | if (!hModule) { 14 | return NULL; 15 | } 16 | WCHAR buf[MAX_PATH + 1]; // +1 for the null byte 17 | int len = GetModuleFileNameW(hModule, buf, MAX_PATH); 18 | if (!len) { 19 | return NULL; 20 | } 21 | buf[len] = '\0'; 22 | return sc_str_from_wchars(buf); 23 | } 24 | 25 | bool 26 | sc_file_is_regular(const char *path) { 27 | wchar_t *wide_path = sc_str_to_wchars(path); 28 | if (!wide_path) { 29 | LOG_OOM(); 30 | return false; 31 | } 32 | 33 | struct _stat path_stat; 34 | int r = _wstat(wide_path, &path_stat); 35 | free(wide_path); 36 | 37 | if (r) { 38 | perror("stat"); 39 | return false; 40 | } 41 | return S_ISREG(path_stat.st_mode); 42 | } 43 | 44 | -------------------------------------------------------------------------------- /app/src/util/average.h: -------------------------------------------------------------------------------- 1 | #ifndef SC_AVERAGE 2 | #define SC_AVERAGE 3 | 4 | #include "common.h" 5 | 6 | #include 7 | #include 8 | 9 | struct sc_average { 10 | // Current average value 11 | float avg; 12 | 13 | // Target range, to update the average as follow: 14 | // avg = ((range - 1) * avg + new_value) / range 15 | unsigned range; 16 | 17 | // Number of values pushed when less than range (count <= range). 18 | // The purpose is to handle the first (range - 1) values properly. 19 | unsigned count; 20 | }; 21 | 22 | void 23 | sc_average_init(struct sc_average *avg, unsigned range); 24 | 25 | /** 26 | * Push a new value to update the "rolling" average 27 | */ 28 | void 29 | sc_average_push(struct sc_average *avg, float value); 30 | 31 | /** 32 | * Get the current average value 33 | * 34 | * It is an error to call this function if sc_average_push() has not been 35 | * called at least once. 36 | */ 37 | float 38 | sc_average_get(struct sc_average *avg); 39 | 40 | #endif 41 | -------------------------------------------------------------------------------- /server/src/main/java/com/genymobile/scrcpy/device/Point.java: -------------------------------------------------------------------------------- 1 | package com.genymobile.scrcpy.device; 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 | -------------------------------------------------------------------------------- /server/src/main/java/com/genymobile/scrcpy/video/CameraFacing.java: -------------------------------------------------------------------------------- 1 | package com.genymobile.scrcpy.video; 2 | 3 | import android.annotation.SuppressLint; 4 | import android.hardware.camera2.CameraCharacteristics; 5 | 6 | public enum CameraFacing { 7 | FRONT("front", CameraCharacteristics.LENS_FACING_FRONT), 8 | BACK("back", CameraCharacteristics.LENS_FACING_BACK), 9 | @SuppressLint("InlinedApi") // introduced in API 23 10 | EXTERNAL("external", CameraCharacteristics.LENS_FACING_EXTERNAL); 11 | 12 | private final String name; 13 | private final int value; 14 | 15 | CameraFacing(String name, int value) { 16 | this.name = name; 17 | this.value = value; 18 | } 19 | 20 | int value() { 21 | return value; 22 | } 23 | 24 | public static CameraFacing findByName(String name) { 25 | for (CameraFacing facing : CameraFacing.values()) { 26 | if (name.equals(facing.name)) { 27 | return facing; 28 | } 29 | } 30 | 31 | return null; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /app/deps/README: -------------------------------------------------------------------------------- 1 | This directory (app/deps/) contains: 2 | 3 | *.sh : shell scripts to download and build dependencies 4 | 5 | patches/ : patches to fix dependencies (used by scripts) 6 | 7 | work/sources/ : downloaded tarballs and extracted folders 8 | ffmpeg-6.1.1.tar.xz 9 | ffmpeg-6.1.1/ 10 | libusb-1.0.27.tar.gz 11 | libusb-1.0.27/ 12 | ... 13 | work/build/ : build dirs for each dependency/version/architecture 14 | ffmpeg-6.1.1/win32/ 15 | ffmpeg-6.1.1/win64/ 16 | libusb-1.0.27/win32/ 17 | libusb-1.0.27/win64/ 18 | ... 19 | work/install/ : install dirs for each architexture 20 | win32/bin/ 21 | win32/include/ 22 | win32/lib/ 23 | win32/share/ 24 | win64/bin/ 25 | win64/include/ 26 | win64/lib/ 27 | win64/share/ 28 | -------------------------------------------------------------------------------- /bump_version: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # 3 | # This script bump scrcpy version by editing all the necessary files. 4 | # 5 | # Usage: 6 | # 7 | # ./bump_version 1.23.4 8 | # 9 | # Then check the diff manually to confirm that everything is ok. 10 | 11 | set -e 12 | 13 | if [[ $# != 1 ]] 14 | then 15 | echo "Syntax: $0 " >&2 16 | exit 1 17 | fi 18 | 19 | VERSION="$1" 20 | 21 | a=( ${VERSION//./ } ) 22 | MAJOR="${a[0]:-0}" 23 | MINOR="${a[1]:-0}" 24 | PATCH="${a[2]:-0}" 25 | 26 | # If VERSION is 1.23.4, then VERSION_CODE is 12304 27 | VERSION_CODE="$(( $MAJOR * 10000 + $MINOR * 100 + "$PATCH" ))" 28 | 29 | echo "$VERSION: major=$MAJOR minor=$MINOR patch=$PATCH [versionCode=$VERSION_CODE]" 30 | sed -i "s/^\(\s*version: \)'[^']*'/\1'$VERSION'/" meson.build 31 | sed -i "s/^\(\s*versionCode \).*/\1$VERSION_CODE/;s/^\(\s*versionName \).*/\1\"$VERSION\"/" server/build.gradle 32 | sed -i "s/^\(SCRCPY_VERSION_NAME=\).*/\1$VERSION/" server/build_without_gradle.sh 33 | sed -i "s/^\(\s*VALUE \"ProductVersion\", \)\"[^\"]*\"/\1\"$VERSION\"/" app/scrcpy-windows.rc 34 | echo done 35 | -------------------------------------------------------------------------------- /doc/macos.md: -------------------------------------------------------------------------------- 1 | # On macOS 2 | 3 | ## Install 4 | 5 | Scrcpy is available in [Homebrew]: 6 | 7 | ```bash 8 | brew install scrcpy 9 | ``` 10 | 11 | [Homebrew]: https://brew.sh/ 12 | 13 | You need `adb`, accessible from your `PATH`. If you don't have it yet: 14 | 15 | ```bash 16 | brew install android-platform-tools 17 | ``` 18 | 19 | Alternatively, Scrcpy is also available in [MacPorts], which sets up `adb` for you: 20 | 21 | ```bash 22 | sudo port install scrcpy 23 | ``` 24 | 25 | [MacPorts]: https://www.macports.org/ 26 | 27 | _See [build.md](build.md) to build and install the app manually._ 28 | 29 | 30 | ## Run 31 | 32 | _Make sure that your device meets the [prerequisites](/README.md#prerequisites)._ 33 | 34 | Once installed, run from a terminal: 35 | 36 | ```bash 37 | scrcpy 38 | ``` 39 | 40 | or with arguments (here to disable audio and record to `file.mkv`): 41 | 42 | ```bash 43 | scrcpy --no-audio --record=file.mkv 44 | ``` 45 | 46 | Documentation for command line arguments is available: 47 | - `man scrcpy` 48 | - `scrcpy --help` 49 | - on [github](/README.md) 50 | -------------------------------------------------------------------------------- /app/src/clock.c: -------------------------------------------------------------------------------- 1 | #include "clock.h" 2 | 3 | #include 4 | 5 | #include "util/log.h" 6 | 7 | //#define SC_CLOCK_DEBUG // uncomment to debug 8 | 9 | #define SC_CLOCK_RANGE 32 10 | 11 | void 12 | sc_clock_init(struct sc_clock *clock) { 13 | clock->range = 0; 14 | clock->offset = 0; 15 | } 16 | 17 | void 18 | sc_clock_update(struct sc_clock *clock, sc_tick system, sc_tick stream) { 19 | if (clock->range < SC_CLOCK_RANGE) { 20 | ++clock->range; 21 | } 22 | 23 | sc_tick offset = system - stream; 24 | unsigned clock_weight = clock->range - 1; 25 | unsigned value_weight = SC_CLOCK_RANGE - clock->range + 1; 26 | clock->offset = (clock->offset * clock_weight + offset * value_weight) 27 | / SC_CLOCK_RANGE; 28 | 29 | #ifdef SC_CLOCK_DEBUG 30 | LOGD("Clock estimation: pts + %" PRItick, clock->offset); 31 | #endif 32 | } 33 | 34 | sc_tick 35 | sc_clock_to_system_time(struct sc_clock *clock, sc_tick stream) { 36 | assert(clock->range); // sc_clock_update() must have been called 37 | return stream + clock->offset; 38 | } 39 | -------------------------------------------------------------------------------- /server/src/main/java/com/genymobile/scrcpy/device/DisplayInfo.java: -------------------------------------------------------------------------------- 1 | package com.genymobile.scrcpy.device; 2 | 3 | public final class DisplayInfo { 4 | private final int displayId; 5 | private final Size size; 6 | private final int rotation; 7 | private final int layerStack; 8 | private final int flags; 9 | 10 | public static final int FLAG_SUPPORTS_PROTECTED_BUFFERS = 0x00000001; 11 | 12 | public DisplayInfo(int displayId, Size size, int rotation, int layerStack, int flags) { 13 | this.displayId = displayId; 14 | this.size = size; 15 | this.rotation = rotation; 16 | this.layerStack = layerStack; 17 | this.flags = flags; 18 | } 19 | 20 | public int getDisplayId() { 21 | return displayId; 22 | } 23 | 24 | public Size getSize() { 25 | return size; 26 | } 27 | 28 | public int getRotation() { 29 | return rotation; 30 | } 31 | 32 | public int getLayerStack() { 33 | return layerStack; 34 | } 35 | 36 | public int getFlags() { 37 | return flags; 38 | } 39 | } 40 | 41 | -------------------------------------------------------------------------------- /app/src/trait/packet_source.h: -------------------------------------------------------------------------------- 1 | #ifndef SC_PACKET_SOURCE_H 2 | #define SC_PACKET_SOURCE_H 3 | 4 | #include "common.h" 5 | 6 | #include "packet_sink.h" 7 | 8 | #define SC_PACKET_SOURCE_MAX_SINKS 2 9 | 10 | /** 11 | * Packet source trait 12 | * 13 | * Component able to send AVPackets should implement this trait. 14 | */ 15 | struct sc_packet_source { 16 | struct sc_packet_sink *sinks[SC_PACKET_SOURCE_MAX_SINKS]; 17 | unsigned sink_count; 18 | }; 19 | 20 | void 21 | sc_packet_source_init(struct sc_packet_source *source); 22 | 23 | void 24 | sc_packet_source_add_sink(struct sc_packet_source *source, 25 | struct sc_packet_sink *sink); 26 | 27 | bool 28 | sc_packet_source_sinks_open(struct sc_packet_source *source, 29 | AVCodecContext *ctx); 30 | 31 | void 32 | sc_packet_source_sinks_close(struct sc_packet_source *source); 33 | 34 | bool 35 | sc_packet_source_sinks_push(struct sc_packet_source *source, 36 | const AVPacket *packet); 37 | 38 | void 39 | sc_packet_source_sinks_disable(struct sc_packet_source *source); 40 | 41 | #endif 42 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | _Please read the [prerequisites] to run scrcpy._ 11 | 12 | [prerequisites]: https://github.com/Genymobile/scrcpy#prerequisites 13 | 14 | _Also read the [FAQ] and check if your [issue][issues] already exists._ 15 | 16 | [FAQ]: https://github.com/Genymobile/scrcpy/blob/master/FAQ.md 17 | [issues]: https://github.com/Genymobile/scrcpy/issues 18 | 19 | ## Environment 20 | 21 | - **OS:** [e.g. Debian, Windows, macOS...] 22 | - **Scrcpy version:** [e.g. 2.5] 23 | - **Installation method:** [e.g. manual build, apt, snap, brew, Windows release...] 24 | - **Device model:** 25 | - **Android version:** [e.g. 14] 26 | 27 | ## Describe the bug 28 | 29 | A clear and concise description of what the bug is. 30 | 31 | On errors, please provide the output of the console (and `adb logcat` if relevant). 32 | 33 | ``` 34 | Please paste terminal output in a code block. 35 | ``` 36 | 37 | Please do not post screenshots of your terminal, just post the content as text instead. 38 | -------------------------------------------------------------------------------- /app/src/trait/packet_sink.h: -------------------------------------------------------------------------------- 1 | #ifndef SC_PACKET_SINK_H 2 | #define SC_PACKET_SINK_H 3 | 4 | #include "common.h" 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | /** 11 | * Packet sink trait. 12 | * 13 | * Component able to receive AVPackets should implement this trait. 14 | */ 15 | struct sc_packet_sink { 16 | const struct sc_packet_sink_ops *ops; 17 | }; 18 | 19 | struct sc_packet_sink_ops { 20 | /* The codec context is valid until the sink is closed */ 21 | bool (*open)(struct sc_packet_sink *sink, AVCodecContext *ctx); 22 | void (*close)(struct sc_packet_sink *sink); 23 | bool (*push)(struct sc_packet_sink *sink, const AVPacket *packet); 24 | 25 | /*/ 26 | * Called when the input stream has been disabled at runtime. 27 | * 28 | * If it is called, then open(), close() and push() will never be called. 29 | * 30 | * It is useful to notify the recorder that the requested audio stream has 31 | * finally been disabled because the device could not capture it. 32 | */ 33 | void (*disable)(struct sc_packet_sink *sink); 34 | }; 35 | 36 | #endif 37 | -------------------------------------------------------------------------------- /server/src/main/java/com/genymobile/scrcpy/video/CameraAspectRatio.java: -------------------------------------------------------------------------------- 1 | package com.genymobile.scrcpy.video; 2 | 3 | public final class CameraAspectRatio { 4 | private static final float SENSOR = -1; 5 | 6 | private float ar; 7 | 8 | private CameraAspectRatio(float ar) { 9 | this.ar = ar; 10 | } 11 | 12 | public static CameraAspectRatio fromFloat(float ar) { 13 | if (ar < 0) { 14 | throw new IllegalArgumentException("Invalid aspect ratio: " + ar); 15 | } 16 | return new CameraAspectRatio(ar); 17 | } 18 | 19 | public static CameraAspectRatio fromFraction(int w, int h) { 20 | if (w <= 0 || h <= 0) { 21 | throw new IllegalArgumentException("Invalid aspect ratio: " + w + ":" + h); 22 | } 23 | return new CameraAspectRatio((float) w / h); 24 | } 25 | 26 | public static CameraAspectRatio sensorAspectRatio() { 27 | return new CameraAspectRatio(SENSOR); 28 | } 29 | 30 | public boolean isSensor() { 31 | return ar == SENSOR; 32 | } 33 | 34 | public float getAspectRatio() { 35 | return ar; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /app/src/util/term.c: -------------------------------------------------------------------------------- 1 | #include "term.h" 2 | 3 | #include 4 | 5 | #ifdef _WIN32 6 | # include 7 | #else 8 | # include 9 | # include 10 | #endif 11 | 12 | bool 13 | sc_term_get_size(unsigned *rows, unsigned *cols) { 14 | #ifdef _WIN32 15 | CONSOLE_SCREEN_BUFFER_INFO csbi; 16 | 17 | bool ok = 18 | GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &csbi); 19 | if (!ok) { 20 | return false; 21 | } 22 | 23 | if (rows) { 24 | assert(csbi.srWindow.Bottom >= csbi.srWindow.Top); 25 | *rows = csbi.srWindow.Bottom - csbi.srWindow.Top + 1; 26 | } 27 | 28 | if (cols) { 29 | assert(csbi.srWindow.Right >= csbi.srWindow.Left); 30 | *cols = csbi.srWindow.Right - csbi.srWindow.Left + 1; 31 | } 32 | 33 | return true; 34 | #else 35 | struct winsize ws; 36 | int r = ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws); 37 | if (r == -1) { 38 | return false; 39 | } 40 | 41 | if (rows) { 42 | *rows = ws.ws_row; 43 | } 44 | 45 | if (cols) { 46 | *cols = ws.ws_col; 47 | } 48 | 49 | return true; 50 | #endif 51 | } 52 | -------------------------------------------------------------------------------- /app/src/frame_buffer.h: -------------------------------------------------------------------------------- 1 | #ifndef SC_FRAME_BUFFER_H 2 | #define SC_FRAME_BUFFER_H 3 | 4 | #include "common.h" 5 | 6 | #include 7 | 8 | #include "util/thread.h" 9 | 10 | // forward declarations 11 | typedef struct AVFrame AVFrame; 12 | 13 | /** 14 | * A frame buffer holds 1 pending frame, which is the last frame received from 15 | * the producer (typically, the decoder). 16 | * 17 | * If a pending frame has not been consumed when the producer pushes a new 18 | * frame, then it is lost. The intent is to always provide access to the very 19 | * last frame to minimize latency. 20 | */ 21 | 22 | struct sc_frame_buffer { 23 | AVFrame *pending_frame; 24 | AVFrame *tmp_frame; // To preserve the pending frame on error 25 | 26 | sc_mutex mutex; 27 | 28 | bool pending_frame_consumed; 29 | }; 30 | 31 | bool 32 | sc_frame_buffer_init(struct sc_frame_buffer *fb); 33 | 34 | void 35 | sc_frame_buffer_destroy(struct sc_frame_buffer *fb); 36 | 37 | bool 38 | sc_frame_buffer_push(struct sc_frame_buffer *fb, const AVFrame *frame, 39 | bool *skipped); 40 | 41 | void 42 | sc_frame_buffer_consume(struct sc_frame_buffer *fb, AVFrame *dst); 43 | 44 | #endif 45 | -------------------------------------------------------------------------------- /app/src/util/log.h: -------------------------------------------------------------------------------- 1 | #ifndef SC_LOG_H 2 | #define SC_LOG_H 3 | 4 | #include "common.h" 5 | 6 | #include 7 | 8 | #include "options.h" 9 | 10 | #define LOG_STR_IMPL_(x) # x 11 | #define LOG_STR(x) LOG_STR_IMPL_(x) 12 | 13 | #define LOGV(...) SDL_LogVerbose(SDL_LOG_CATEGORY_APPLICATION, __VA_ARGS__) 14 | #define LOGD(...) SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION, __VA_ARGS__) 15 | #define LOGI(...) SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, __VA_ARGS__) 16 | #define LOGW(...) SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, __VA_ARGS__) 17 | #define LOGE(...) SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, __VA_ARGS__) 18 | 19 | #define LOG_OOM() \ 20 | LOGE("OOM: %s:%d %s()", __FILE__, __LINE__, __func__) 21 | 22 | void 23 | sc_set_log_level(enum sc_log_level level); 24 | 25 | enum sc_log_level 26 | sc_get_log_level(void); 27 | 28 | void 29 | sc_log(enum sc_log_level level, const char *fmt, ...); 30 | #define LOG(LEVEL, ...) sc_log((LEVEL), __VA_ARGS__) 31 | 32 | #ifdef _WIN32 33 | // Log system error (typically returned by GetLastError() or similar) 34 | bool 35 | sc_log_windows_error(const char *prefix, int error); 36 | #endif 37 | 38 | void 39 | sc_log_configure(void); 40 | 41 | #endif 42 | -------------------------------------------------------------------------------- /app/tests/test_strbuf.c: -------------------------------------------------------------------------------- 1 | #include "common.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "util/strbuf.h" 9 | 10 | static void test_strbuf_simple(void) { 11 | struct sc_strbuf buf; 12 | bool ok = sc_strbuf_init(&buf, 10); 13 | assert(ok); 14 | 15 | ok = sc_strbuf_append_staticstr(&buf, "Hello"); 16 | assert(ok); 17 | 18 | ok = sc_strbuf_append_char(&buf, ' '); 19 | assert(ok); 20 | 21 | ok = sc_strbuf_append_staticstr(&buf, "world"); 22 | assert(ok); 23 | 24 | ok = sc_strbuf_append_staticstr(&buf, "!\n"); 25 | assert(ok); 26 | 27 | ok = sc_strbuf_append_staticstr(&buf, "This is a test"); 28 | assert(ok); 29 | 30 | ok = sc_strbuf_append_n(&buf, '.', 3); 31 | assert(ok); 32 | 33 | assert(!strcmp(buf.s, "Hello world!\nThis is a test...")); 34 | 35 | sc_strbuf_shrink(&buf); 36 | assert(buf.len == buf.cap); 37 | assert(!strcmp(buf.s, "Hello world!\nThis is a test...")); 38 | 39 | free(buf.s); 40 | } 41 | 42 | int main(int argc, char *argv[]) { 43 | (void) argc; 44 | (void) argv; 45 | 46 | test_strbuf_simple(); 47 | return 0; 48 | } 49 | -------------------------------------------------------------------------------- /app/src/util/file.h: -------------------------------------------------------------------------------- 1 | #ifndef SC_FILE_H 2 | #define SC_FILE_H 3 | 4 | #include "common.h" 5 | 6 | #include 7 | 8 | #ifdef _WIN32 9 | # define SC_PATH_SEPARATOR '\\' 10 | #else 11 | # define SC_PATH_SEPARATOR '/' 12 | #endif 13 | 14 | #ifndef _WIN32 15 | /** 16 | * Indicate if an executable exists using $PATH 17 | * 18 | * In practice, it is only used to know if a package manager is available on 19 | * the system. It is only implemented on Linux. 20 | */ 21 | bool 22 | sc_file_executable_exists(const char *file); 23 | #endif 24 | 25 | /** 26 | * Return the absolute path of the executable (the scrcpy binary) 27 | * 28 | * The result must be freed by the caller using free(). It may return NULL on 29 | * error. 30 | */ 31 | char * 32 | sc_file_get_executable_path(void); 33 | 34 | /** 35 | * Return the absolute path of a file in the same directory as the executable 36 | * 37 | * The result must be freed by the caller using free(). It may return NULL on 38 | * error. 39 | */ 40 | char * 41 | sc_file_get_local_path(const char *name); 42 | 43 | /** 44 | * Indicate if the file exists and is not a directory 45 | */ 46 | bool 47 | sc_file_is_regular(const char *path); 48 | 49 | #endif 50 | -------------------------------------------------------------------------------- /app/deps/libusb.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -ex 3 | DEPS_DIR=$(dirname ${BASH_SOURCE[0]}) 4 | cd "$DEPS_DIR" 5 | . common 6 | 7 | VERSION=1.0.27 8 | FILENAME=libusb-$VERSION.tar.gz 9 | PROJECT_DIR=libusb-$VERSION 10 | SHA256SUM=e8f18a7a36ecbb11fb820bd71540350d8f61bcd9db0d2e8c18a6fb80b214a3de 11 | 12 | cd "$SOURCES_DIR" 13 | 14 | if [[ -d "$PROJECT_DIR" ]] 15 | then 16 | echo "$PWD/$PROJECT_DIR" found 17 | else 18 | get_file "https://github.com/libusb/libusb/archive/refs/tags/v$VERSION.tar.gz" "$FILENAME" "$SHA256SUM" 19 | tar xf "$FILENAME" # First level directory is "$PROJECT_DIR" 20 | fi 21 | 22 | mkdir -p "$BUILD_DIR/$PROJECT_DIR" 23 | cd "$BUILD_DIR/$PROJECT_DIR" 24 | 25 | export CFLAGS='-O2' 26 | export CXXFLAGS="$CFLAGS" 27 | 28 | if [[ -d "$HOST" ]] 29 | then 30 | echo "'$PWD/$HOST' already exists, not reconfigured" 31 | cd "$HOST" 32 | else 33 | mkdir "$HOST" 34 | cd "$HOST" 35 | 36 | "$SOURCES_DIR/$PROJECT_DIR"/bootstrap.sh 37 | "$SOURCES_DIR/$PROJECT_DIR"/configure \ 38 | --prefix="$INSTALL_DIR/$HOST" \ 39 | --host="$HOST_TRIPLET" \ 40 | --enable-shared \ 41 | --disable-static 42 | fi 43 | 44 | make -j 45 | make install-strip 46 | -------------------------------------------------------------------------------- /app/src/adb/adb_device.h: -------------------------------------------------------------------------------- 1 | #ifndef SC_ADB_DEVICE_H 2 | #define SC_ADB_DEVICE_H 3 | 4 | #include "common.h" 5 | 6 | #include 7 | #include 8 | 9 | #include "util/vector.h" 10 | 11 | struct sc_adb_device { 12 | char *serial; 13 | char *state; 14 | char *model; 15 | bool selected; 16 | }; 17 | 18 | enum sc_adb_device_type { 19 | SC_ADB_DEVICE_TYPE_USB, 20 | SC_ADB_DEVICE_TYPE_TCPIP, 21 | SC_ADB_DEVICE_TYPE_EMULATOR, 22 | }; 23 | 24 | struct sc_vec_adb_devices SC_VECTOR(struct sc_adb_device); 25 | 26 | void 27 | sc_adb_device_destroy(struct sc_adb_device *device); 28 | 29 | /** 30 | * Move src to dst 31 | * 32 | * After this call, the content of src is undefined, except that 33 | * sc_adb_device_destroy() can be called. 34 | * 35 | * This is useful to take a device from a list that will be destroyed, without 36 | * making unnecessary copies. 37 | */ 38 | void 39 | sc_adb_device_move(struct sc_adb_device *dst, struct sc_adb_device *src); 40 | 41 | void 42 | sc_adb_devices_destroy(struct sc_vec_adb_devices *devices); 43 | 44 | /** 45 | * Deduce the device type from the serial 46 | */ 47 | enum sc_adb_device_type 48 | sc_adb_device_get_type(const char *serial); 49 | 50 | #endif 51 | -------------------------------------------------------------------------------- /app/src/receiver.h: -------------------------------------------------------------------------------- 1 | #ifndef SC_RECEIVER_H 2 | #define SC_RECEIVER_H 3 | 4 | #include "common.h" 5 | 6 | #include 7 | 8 | #include "uhid/uhid_output.h" 9 | #include "util/acksync.h" 10 | #include "util/net.h" 11 | #include "util/thread.h" 12 | 13 | // receive events from the device 14 | // managed by the controller 15 | struct sc_receiver { 16 | sc_socket control_socket; 17 | sc_thread thread; 18 | sc_mutex mutex; 19 | 20 | struct sc_acksync *acksync; 21 | struct sc_uhid_devices *uhid_devices; 22 | 23 | const struct sc_receiver_callbacks *cbs; 24 | void *cbs_userdata; 25 | }; 26 | 27 | struct sc_receiver_callbacks { 28 | void (*on_ended)(struct sc_receiver *receiver, bool error, void *userdata); 29 | }; 30 | 31 | bool 32 | sc_receiver_init(struct sc_receiver *receiver, sc_socket control_socket, 33 | const struct sc_receiver_callbacks *cbs, void *cbs_userdata); 34 | 35 | void 36 | sc_receiver_destroy(struct sc_receiver *receiver); 37 | 38 | bool 39 | sc_receiver_start(struct sc_receiver *receiver); 40 | 41 | // no sc_receiver_stop(), it will automatically stop on control_socket shutdown 42 | 43 | void 44 | sc_receiver_join(struct sc_receiver *receiver); 45 | 46 | #endif 47 | -------------------------------------------------------------------------------- /server/src/main/java/com/genymobile/scrcpy/util/Binary.java: -------------------------------------------------------------------------------- 1 | package com.genymobile.scrcpy.util; 2 | 3 | public final class Binary { 4 | private Binary() { 5 | // not instantiable 6 | } 7 | 8 | public static int toUnsigned(short value) { 9 | return value & 0xffff; 10 | } 11 | 12 | public static int toUnsigned(byte value) { 13 | return value & 0xff; 14 | } 15 | 16 | /** 17 | * Convert unsigned 16-bit fixed-point to a float between 0 and 1 18 | * 19 | * @param value encoded value 20 | * @return Float value between 0 and 1 21 | */ 22 | public static float u16FixedPointToFloat(short value) { 23 | int unsignedShort = Binary.toUnsigned(value); 24 | // 0x1p16f is 2^16 as float 25 | return unsignedShort == 0xffff ? 1f : (unsignedShort / 0x1p16f); 26 | } 27 | 28 | /** 29 | * Convert signed 16-bit fixed-point to a float between -1 and 1 30 | * 31 | * @param value encoded value 32 | * @return Float value between -1 and 1 33 | */ 34 | public static float i16FixedPointToFloat(short value) { 35 | // 0x1p15f is 2^15 as float 36 | return value == 0x7fff ? 1f : (value / 0x1p15f); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /app/src/adb/adb_device.c: -------------------------------------------------------------------------------- 1 | #include "adb_device.h" 2 | 3 | #include 4 | #include 5 | 6 | void 7 | sc_adb_device_destroy(struct sc_adb_device *device) { 8 | free(device->serial); 9 | free(device->state); 10 | free(device->model); 11 | } 12 | 13 | void 14 | sc_adb_device_move(struct sc_adb_device *dst, struct sc_adb_device *src) { 15 | *dst = *src; 16 | src->serial = NULL; 17 | src->state = NULL; 18 | src->model = NULL; 19 | } 20 | 21 | void 22 | sc_adb_devices_destroy(struct sc_vec_adb_devices *devices) { 23 | for (size_t i = 0; i < devices->size; ++i) { 24 | sc_adb_device_destroy(&devices->data[i]); 25 | } 26 | sc_vector_destroy(devices); 27 | } 28 | 29 | enum sc_adb_device_type 30 | sc_adb_device_get_type(const char *serial) { 31 | // Starts with "emulator-" 32 | if (!strncmp(serial, "emulator-", sizeof("emulator-") - 1)) { 33 | return SC_ADB_DEVICE_TYPE_EMULATOR; 34 | } 35 | 36 | // If the serial contains a ':', then it is a TCP/IP device (it is 37 | // sufficient to distinguish an ip:port from a real USB serial) 38 | if (strchr(serial, ':')) { 39 | return SC_ADB_DEVICE_TYPE_TCPIP; 40 | } 41 | 42 | return SC_ADB_DEVICE_TYPE_USB; 43 | } 44 | -------------------------------------------------------------------------------- /app/src/clock.h: -------------------------------------------------------------------------------- 1 | #ifndef SC_CLOCK_H 2 | #define SC_CLOCK_H 3 | 4 | #include "common.h" 5 | 6 | #include "util/tick.h" 7 | 8 | struct sc_clock_point { 9 | sc_tick system; 10 | sc_tick stream; 11 | }; 12 | 13 | /** 14 | * The clock aims to estimate the affine relation between the stream (device) 15 | * time and the system time: 16 | * 17 | * f(stream) = slope * stream + offset 18 | * 19 | * Theoretically, the slope encodes the drift between the device clock and the 20 | * computer clock. It is expected to be very close to 1. 21 | * 22 | * Since the clock is used to estimate very close points in the future (which 23 | * are reestimated on every clock update, see delay_buffer), the error caused 24 | * by clock drift is totally negligible, so it is better to assume that the 25 | * slope is 1 than to estimate it (the estimation error would be larger). 26 | * 27 | * Therefore, only the offset is estimated. 28 | */ 29 | struct sc_clock { 30 | unsigned range; 31 | sc_tick offset; 32 | }; 33 | 34 | void 35 | sc_clock_init(struct sc_clock *clock); 36 | 37 | void 38 | sc_clock_update(struct sc_clock *clock, sc_tick system, sc_tick stream); 39 | 40 | sc_tick 41 | sc_clock_to_system_time(struct sc_clock *clock, sc_tick stream); 42 | 43 | #endif 44 | -------------------------------------------------------------------------------- /app/src/device_msg.h: -------------------------------------------------------------------------------- 1 | #ifndef SC_DEVICEMSG_H 2 | #define SC_DEVICEMSG_H 3 | 4 | #include "common.h" 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | #define DEVICE_MSG_MAX_SIZE (1 << 18) // 256k 11 | // type: 1 byte; length: 4 bytes 12 | #define DEVICE_MSG_TEXT_MAX_LENGTH (DEVICE_MSG_MAX_SIZE - 5) 13 | 14 | enum sc_device_msg_type { 15 | DEVICE_MSG_TYPE_CLIPBOARD, 16 | DEVICE_MSG_TYPE_ACK_CLIPBOARD, 17 | DEVICE_MSG_TYPE_UHID_OUTPUT, 18 | }; 19 | 20 | struct sc_device_msg { 21 | enum sc_device_msg_type type; 22 | union { 23 | struct { 24 | char *text; // owned, to be freed by free() 25 | } clipboard; 26 | struct { 27 | uint64_t sequence; 28 | } ack_clipboard; 29 | struct { 30 | uint16_t id; 31 | uint16_t size; 32 | uint8_t *data; // owned, to be freed by free() 33 | } uhid_output; 34 | }; 35 | }; 36 | 37 | // return the number of bytes consumed (0 for no msg available, -1 on error) 38 | ssize_t 39 | sc_device_msg_deserialize(const uint8_t *buf, size_t len, 40 | struct sc_device_msg *msg); 41 | 42 | void 43 | sc_device_msg_destroy(struct sc_device_msg *msg); 44 | 45 | #endif 46 | -------------------------------------------------------------------------------- /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 | # gradle is responsible for tracking source changes 7 | build_by_default: true, 8 | build_always_stale: true, 9 | output: 'scrcpy-server', 10 | command: [find_program('./scripts/build-wrapper.sh'), meson.current_source_dir(), '@OUTPUT@', get_option('buildtype')], 11 | console: true, 12 | install: true, 13 | install_dir: 'share/scrcpy') 14 | else 15 | if not prebuilt_server.startswith('/') 16 | # prebuilt server path is relative to the root scrcpy directory 17 | prebuilt_server = '../' + prebuilt_server 18 | endif 19 | custom_target('scrcpy-server-prebuilt', 20 | input: prebuilt_server, 21 | output: 'scrcpy-server', 22 | command: ['cp', '@INPUT@', '@OUTPUT@'], 23 | install: true, 24 | install_dir: 'share/scrcpy') 25 | endif 26 | -------------------------------------------------------------------------------- /app/deps/common: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # This file is intended to be sourced by other scripts, not executed 3 | 4 | if [[ $# != 1 ]] 5 | then 6 | # : win32 or win64 7 | echo "Syntax: $0 " >&2 8 | exit 1 9 | fi 10 | 11 | HOST="$1" 12 | 13 | if [[ "$HOST" = win32 ]] 14 | then 15 | HOST_TRIPLET=i686-w64-mingw32 16 | elif [[ "$HOST" = win64 ]] 17 | then 18 | HOST_TRIPLET=x86_64-w64-mingw32 19 | else 20 | echo "Unsupported host: $HOST" >&2 21 | exit 1 22 | fi 23 | 24 | DEPS_DIR=$(dirname ${BASH_SOURCE[0]}) 25 | cd "$DEPS_DIR" 26 | 27 | PATCHES_DIR="$PWD/patches" 28 | 29 | WORK_DIR="$PWD/work" 30 | SOURCES_DIR="$WORK_DIR/sources" 31 | BUILD_DIR="$WORK_DIR/build" 32 | INSTALL_DIR="$WORK_DIR/install" 33 | 34 | mkdir -p "$INSTALL_DIR" "$SOURCES_DIR" "$WORK_DIR" 35 | 36 | checksum() { 37 | local file="$1" 38 | local sum="$2" 39 | echo "$file: verifying checksum..." 40 | echo "$sum $file" | sha256sum -c 41 | } 42 | 43 | get_file() { 44 | local url="$1" 45 | local file="$2" 46 | local sum="$3" 47 | if [[ -f "$file" ]] 48 | then 49 | echo "$file: found" 50 | else 51 | echo "$file: not found, downloading..." 52 | wget "$url" -O "$file" 53 | fi 54 | checksum "$file" "$sum" 55 | } 56 | -------------------------------------------------------------------------------- /app/deps/sdl.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -ex 3 | DEPS_DIR=$(dirname ${BASH_SOURCE[0]}) 4 | cd "$DEPS_DIR" 5 | . common 6 | 7 | VERSION=2.30.7 8 | FILENAME=SDL-$VERSION.tar.gz 9 | PROJECT_DIR=SDL-release-$VERSION 10 | SHA256SUM=1578c96f62c9ae36b64e431b2aa0e0b0fd07c275dedbc694afc38e19056688f5 11 | 12 | cd "$SOURCES_DIR" 13 | 14 | if [[ -d "$PROJECT_DIR" ]] 15 | then 16 | echo "$PWD/$PROJECT_DIR" found 17 | else 18 | get_file "https://github.com/libsdl-org/SDL/archive/refs/tags/release-$VERSION.tar.gz" "$FILENAME" "$SHA256SUM" 19 | tar xf "$FILENAME" # First level directory is "$PROJECT_DIR" 20 | fi 21 | 22 | mkdir -p "$BUILD_DIR/$PROJECT_DIR" 23 | cd "$BUILD_DIR/$PROJECT_DIR" 24 | 25 | export CFLAGS='-O2' 26 | export CXXFLAGS="$CFLAGS" 27 | 28 | if [[ -d "$HOST" ]] 29 | then 30 | echo "'$PWD/$HOST' already exists, not reconfigured" 31 | cd "$HOST" 32 | else 33 | mkdir "$HOST" 34 | cd "$HOST" 35 | 36 | "$SOURCES_DIR/$PROJECT_DIR"/configure \ 37 | --prefix="$INSTALL_DIR/$HOST" \ 38 | --host="$HOST_TRIPLET" \ 39 | --enable-shared \ 40 | --disable-static 41 | fi 42 | 43 | make -j 44 | # There is no "make install-strip" 45 | make install 46 | # Strip manually 47 | ${HOST_TRIPLET}-strip "$INSTALL_DIR/$HOST/bin/SDL2.dll" 48 | -------------------------------------------------------------------------------- /doc/window.md: -------------------------------------------------------------------------------- 1 | # Window 2 | 3 | ## Disable window 4 | 5 | To disable window (may be useful for recording or for playing audio only): 6 | 7 | ```bash 8 | scrcpy --no-window --record=file.mp4 9 | # Ctrl+C to interrupt 10 | ``` 11 | 12 | ## Title 13 | 14 | By default, the window title is the device model. It can be changed: 15 | 16 | ```bash 17 | scrcpy --window-title='My device' 18 | ``` 19 | 20 | ## Position and size 21 | 22 | The initial window position and size may be specified: 23 | 24 | ```bash 25 | scrcpy --window-x=100 --window-y=100 --window-width=800 --window-height=600 26 | ``` 27 | 28 | ## Borderless 29 | 30 | To disable window decorations: 31 | 32 | ```bash 33 | scrcpy --window-borderless 34 | ``` 35 | 36 | ## Always on top 37 | 38 | To keep the window always on top: 39 | 40 | ```bash 41 | scrcpy --always-on-top 42 | ``` 43 | 44 | ## Fullscreen 45 | 46 | The app may be started directly in fullscreen: 47 | 48 | ```bash 49 | scrcpy --fullscreen 50 | scrcpy -f # short version 51 | ``` 52 | 53 | Fullscreen mode can then be toggled dynamically with MOD+f 54 | (see [shortcuts](shortcuts.md)). 55 | 56 | 57 | ## Disable screensaver 58 | 59 | By default, _scrcpy_ does not prevent the screensaver from running on the 60 | computer. To disable it: 61 | 62 | ```bash 63 | scrcpy --disable-screensaver 64 | ``` 65 | -------------------------------------------------------------------------------- /server/src/main/java/com/genymobile/scrcpy/device/Size.java: -------------------------------------------------------------------------------- 1 | package com.genymobile.scrcpy.device; 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 | -------------------------------------------------------------------------------- /app/src/trait/gamepad_processor.h: -------------------------------------------------------------------------------- 1 | #ifndef SC_GAMEPAD_PROCESSOR_H 2 | #define SC_GAMEPAD_PROCESSOR_H 3 | 4 | #include "common.h" 5 | 6 | #include 7 | #include 8 | 9 | #include "input_events.h" 10 | 11 | /** 12 | * Gamepad processor trait. 13 | * 14 | * Component able to handle gamepads devices and inject buttons and axis events. 15 | */ 16 | struct sc_gamepad_processor { 17 | const struct sc_gamepad_processor_ops *ops; 18 | }; 19 | 20 | struct sc_gamepad_processor_ops { 21 | 22 | /** 23 | * Process a gamepad device added or removed 24 | * 25 | * This function is mandatory. 26 | */ 27 | void 28 | (*process_gamepad_device)(struct sc_gamepad_processor *gp, 29 | const struct sc_gamepad_device_event *event); 30 | 31 | /** 32 | * Process a gamepad axis event 33 | * 34 | * This function is mandatory. 35 | */ 36 | void 37 | (*process_gamepad_axis)(struct sc_gamepad_processor *gp, 38 | const struct sc_gamepad_axis_event *event); 39 | 40 | /** 41 | * Process a gamepad button event 42 | * 43 | * This function is mandatory. 44 | */ 45 | void 46 | (*process_gamepad_button)(struct sc_gamepad_processor *gp, 47 | const struct sc_gamepad_button_event *event); 48 | }; 49 | 50 | #endif 51 | -------------------------------------------------------------------------------- /app/src/usb/screen_otg.h: -------------------------------------------------------------------------------- 1 | #ifndef SC_SCREEN_OTG_H 2 | #define SC_SCREEN_OTG_H 3 | 4 | #include "common.h" 5 | 6 | #include 7 | #include 8 | 9 | #include "keyboard_aoa.h" 10 | #include "mouse_aoa.h" 11 | #include "gamepad_aoa.h" 12 | 13 | struct sc_screen_otg { 14 | struct sc_keyboard_aoa *keyboard; 15 | struct sc_mouse_aoa *mouse; 16 | struct sc_gamepad_aoa *gamepad; 17 | 18 | SDL_Window *window; 19 | SDL_Renderer *renderer; 20 | SDL_Texture *texture; 21 | 22 | // See equivalent mechanism in screen.h 23 | SDL_Keycode mouse_capture_key_pressed; 24 | }; 25 | 26 | struct sc_screen_otg_params { 27 | struct sc_keyboard_aoa *keyboard; 28 | struct sc_mouse_aoa *mouse; 29 | struct sc_gamepad_aoa *gamepad; 30 | 31 | const char *window_title; 32 | bool always_on_top; 33 | int16_t window_x; // accepts SC_WINDOW_POSITION_UNDEFINED 34 | int16_t window_y; // accepts SC_WINDOW_POSITION_UNDEFINED 35 | uint16_t window_width; 36 | uint16_t window_height; 37 | bool window_borderless; 38 | }; 39 | 40 | bool 41 | sc_screen_otg_init(struct sc_screen_otg *screen, 42 | const struct sc_screen_otg_params *params); 43 | 44 | void 45 | sc_screen_otg_destroy(struct sc_screen_otg *screen); 46 | 47 | void 48 | sc_screen_otg_handle_event(struct sc_screen_otg *screen, SDL_Event *event); 49 | 50 | #endif 51 | -------------------------------------------------------------------------------- /app/src/demuxer.h: -------------------------------------------------------------------------------- 1 | #ifndef SC_DEMUXER_H 2 | #define SC_DEMUXER_H 3 | 4 | #include "common.h" 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include "trait/packet_source.h" 12 | #include "trait/packet_sink.h" 13 | #include "util/net.h" 14 | #include "util/thread.h" 15 | 16 | struct sc_demuxer { 17 | struct sc_packet_source packet_source; // packet source trait 18 | 19 | const char *name; // must be statically allocated (e.g. a string literal) 20 | 21 | sc_socket socket; 22 | sc_thread thread; 23 | 24 | const struct sc_demuxer_callbacks *cbs; 25 | void *cbs_userdata; 26 | }; 27 | 28 | enum sc_demuxer_status { 29 | SC_DEMUXER_STATUS_EOS, 30 | SC_DEMUXER_STATUS_DISABLED, 31 | SC_DEMUXER_STATUS_ERROR, 32 | }; 33 | 34 | struct sc_demuxer_callbacks { 35 | void (*on_ended)(struct sc_demuxer *demuxer, enum sc_demuxer_status, 36 | void *userdata); 37 | }; 38 | 39 | // The name must be statically allocated (e.g. a string literal) 40 | void 41 | sc_demuxer_init(struct sc_demuxer *demuxer, const char *name, sc_socket socket, 42 | const struct sc_demuxer_callbacks *cbs, void *cbs_userdata); 43 | 44 | bool 45 | sc_demuxer_start(struct sc_demuxer *demuxer); 46 | 47 | void 48 | sc_demuxer_join(struct sc_demuxer *demuxer); 49 | 50 | #endif 51 | -------------------------------------------------------------------------------- /server/src/main/java/com/genymobile/scrcpy/control/Pointer.java: -------------------------------------------------------------------------------- 1 | package com.genymobile.scrcpy.control; 2 | 3 | import com.genymobile.scrcpy.device.Point; 4 | 5 | public class Pointer { 6 | 7 | /** 8 | * Pointer id as received from the client. 9 | */ 10 | private final long id; 11 | 12 | /** 13 | * Local pointer id, using the lowest possible values to fill the {@link android.view.MotionEvent.PointerProperties PointerProperties}. 14 | */ 15 | private final int localId; 16 | 17 | private Point point; 18 | private float pressure; 19 | private boolean up; 20 | 21 | public Pointer(long id, int localId) { 22 | this.id = id; 23 | this.localId = localId; 24 | } 25 | 26 | public long getId() { 27 | return id; 28 | } 29 | 30 | public int getLocalId() { 31 | return localId; 32 | } 33 | 34 | public Point getPoint() { 35 | return point; 36 | } 37 | 38 | public void setPoint(Point point) { 39 | this.point = point; 40 | } 41 | 42 | public float getPressure() { 43 | return pressure; 44 | } 45 | 46 | public void setPressure(float pressure) { 47 | this.pressure = pressure; 48 | } 49 | 50 | public boolean isUp() { 51 | return up; 52 | } 53 | 54 | public void setUp(boolean up) { 55 | this.up = up; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /app/src/adb/adb_tunnel.h: -------------------------------------------------------------------------------- 1 | #ifndef SC_ADB_TUNNEL_H 2 | #define SC_ADB_TUNNEL_H 3 | 4 | #include "common.h" 5 | 6 | #include 7 | #include 8 | 9 | #include "options.h" 10 | #include "util/intr.h" 11 | #include "util/net.h" 12 | 13 | struct sc_adb_tunnel { 14 | bool enabled; 15 | bool forward; // use "adb forward" instead of "adb reverse" 16 | sc_socket server_socket; // only used if !forward 17 | uint16_t local_port; 18 | }; 19 | 20 | /** 21 | * Initialize the adb tunnel struct to default values 22 | */ 23 | void 24 | sc_adb_tunnel_init(struct sc_adb_tunnel *tunnel); 25 | 26 | /** 27 | * Open a tunnel 28 | * 29 | * Blocking calls may be interrupted asynchronously via `intr`. 30 | * 31 | * If `force_adb_forward` is not set, then attempts to set up an "adb reverse" 32 | * tunnel first. Only if it fails (typical on old Android version connected via 33 | * TCP/IP), use "adb forward". 34 | */ 35 | bool 36 | sc_adb_tunnel_open(struct sc_adb_tunnel *tunnel, struct sc_intr *intr, 37 | const char *serial, const char *device_socket_name, 38 | struct sc_port_range port_range, bool force_adb_forward); 39 | 40 | /** 41 | * Close the tunnel 42 | */ 43 | bool 44 | sc_adb_tunnel_close(struct sc_adb_tunnel *tunnel, struct sc_intr *intr, 45 | const char *serial, const char *device_socket_name); 46 | 47 | #endif 48 | -------------------------------------------------------------------------------- /app/src/packet_merger.h: -------------------------------------------------------------------------------- 1 | #ifndef SC_PACKET_MERGER_H 2 | #define SC_PACKET_MERGER_H 3 | 4 | #include "common.h" 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | /** 11 | * Config packets (containing the SPS/PPS) are sent in-band. A new config 12 | * packet is sent whenever a new encoding session is started (on start and on 13 | * device orientation change). 14 | * 15 | * Every time a config packet is received, it must be sent alone (for recorder 16 | * extradata), then concatenated to the next media packet (for correct decoding 17 | * and recording). 18 | * 19 | * This helper reads every input packet and modifies each media packet which 20 | * immediately follows a config packet to prepend the config packet payload. 21 | */ 22 | 23 | struct sc_packet_merger { 24 | uint8_t *config; 25 | size_t config_size; 26 | }; 27 | 28 | void 29 | sc_packet_merger_init(struct sc_packet_merger *merger); 30 | 31 | void 32 | sc_packet_merger_destroy(struct sc_packet_merger *merger); 33 | 34 | /** 35 | * If the packet is a config packet, then keep its data for later. 36 | * Otherwise (if the packet is a media packet), then if a config packet is 37 | * pending, prepend the config packet to this packet (so the packet is 38 | * modified!). 39 | */ 40 | bool 41 | sc_packet_merger_merge(struct sc_packet_merger *merger, AVPacket *packet); 42 | 43 | #endif 44 | -------------------------------------------------------------------------------- /app/src/packet_merger.c: -------------------------------------------------------------------------------- 1 | #include "packet_merger.h" 2 | 3 | #include "util/log.h" 4 | 5 | void 6 | sc_packet_merger_init(struct sc_packet_merger *merger) { 7 | merger->config = NULL; 8 | } 9 | 10 | void 11 | sc_packet_merger_destroy(struct sc_packet_merger *merger) { 12 | free(merger->config); 13 | } 14 | 15 | bool 16 | sc_packet_merger_merge(struct sc_packet_merger *merger, AVPacket *packet) { 17 | bool is_config = packet->pts == AV_NOPTS_VALUE; 18 | 19 | if (is_config) { 20 | free(merger->config); 21 | 22 | merger->config = malloc(packet->size); 23 | if (!merger->config) { 24 | LOG_OOM(); 25 | return false; 26 | } 27 | 28 | memcpy(merger->config, packet->data, packet->size); 29 | merger->config_size = packet->size; 30 | } else if (merger->config) { 31 | size_t config_size = merger->config_size; 32 | size_t media_size = packet->size; 33 | 34 | if (av_grow_packet(packet, config_size)) { 35 | LOG_OOM(); 36 | return false; 37 | } 38 | 39 | memmove(packet->data + config_size, packet->data, media_size); 40 | memcpy(packet->data, merger->config, config_size); 41 | 42 | free(merger->config); 43 | merger->config = NULL; 44 | // merger->size is meaningless when merger->config is NULL 45 | } 46 | 47 | return true; 48 | } 49 | -------------------------------------------------------------------------------- /server/src/main/java/com/genymobile/scrcpy/audio/AudioConfig.java: -------------------------------------------------------------------------------- 1 | package com.genymobile.scrcpy.audio; 2 | 3 | import android.media.AudioFormat; 4 | 5 | public final class AudioConfig { 6 | public static final int SAMPLE_RATE = 48000; 7 | public static final int CHANNEL_CONFIG = AudioFormat.CHANNEL_IN_STEREO; 8 | public static final int CHANNELS = 2; 9 | public static final int CHANNEL_MASK = AudioFormat.CHANNEL_IN_LEFT | AudioFormat.CHANNEL_IN_RIGHT; 10 | public static final int ENCODING = AudioFormat.ENCODING_PCM_16BIT; 11 | public static final int BYTES_PER_SAMPLE = 2; 12 | 13 | // Never read more than 1024 samples, even if the buffer is bigger (that would increase latency). 14 | // A lower value is useless, since the system captures audio samples by blocks of 1024 (so for example if we read by blocks of 256 samples, we 15 | // receive 4 successive blocks without waiting, then we wait for the 4 next ones). 16 | public static final int MAX_READ_SIZE = 1024 * CHANNELS * BYTES_PER_SAMPLE; 17 | 18 | private AudioConfig() { 19 | // Not instantiable 20 | } 21 | 22 | public static AudioFormat createAudioFormat() { 23 | AudioFormat.Builder builder = new AudioFormat.Builder(); 24 | builder.setEncoding(ENCODING); 25 | builder.setSampleRate(SAMPLE_RATE); 26 | builder.setChannelMask(CHANNEL_CONFIG); 27 | return builder.build(); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /app/src/util/file.c: -------------------------------------------------------------------------------- 1 | #include "file.h" 2 | 3 | #include 4 | #include 5 | 6 | #include "util/log.h" 7 | 8 | char * 9 | sc_file_get_local_path(const char *name) { 10 | char *executable_path = sc_file_get_executable_path(); 11 | if (!executable_path) { 12 | return NULL; 13 | } 14 | 15 | // dirname() does not work correctly everywhere, so get the parent 16 | // directory manually. 17 | // See 18 | char *p = strrchr(executable_path, SC_PATH_SEPARATOR); 19 | if (!p) { 20 | LOGE("Unexpected executable path: \"%s\" (it should contain a '%c')", 21 | executable_path, SC_PATH_SEPARATOR); 22 | free(executable_path); 23 | return NULL; 24 | } 25 | 26 | *p = '\0'; // modify executable_path in place 27 | char *dir = executable_path; 28 | size_t dirlen = strlen(dir); 29 | size_t namelen = strlen(name); 30 | 31 | size_t len = dirlen + namelen + 2; // +2: '/' and '\0' 32 | char *file_path = malloc(len); 33 | if (!file_path) { 34 | LOG_OOM(); 35 | free(executable_path); 36 | return NULL; 37 | } 38 | 39 | memcpy(file_path, dir, dirlen); 40 | file_path[dirlen] = SC_PATH_SEPARATOR; 41 | // namelen + 1 to copy the final '\0' 42 | memcpy(&file_path[dirlen + 1], name, namelen + 1); 43 | 44 | free(executable_path); 45 | 46 | return file_path; 47 | } 48 | 49 | -------------------------------------------------------------------------------- /server/src/test/java/com/genymobile/scrcpy/util/StringUtilsTest.java: -------------------------------------------------------------------------------- 1 | package com.genymobile.scrcpy.util; 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 | public void testUtf8Truncate() { 12 | String s = "aÉbÔc"; 13 | byte[] utf8 = s.getBytes(StandardCharsets.UTF_8); 14 | Assert.assertEquals(7, utf8.length); 15 | 16 | int count; 17 | 18 | count = StringUtils.getUtf8TruncationIndex(utf8, 1); 19 | Assert.assertEquals(1, count); 20 | 21 | count = StringUtils.getUtf8TruncationIndex(utf8, 2); 22 | Assert.assertEquals(1, count); // É is 2 bytes-wide 23 | 24 | count = StringUtils.getUtf8TruncationIndex(utf8, 3); 25 | Assert.assertEquals(3, count); 26 | 27 | count = StringUtils.getUtf8TruncationIndex(utf8, 4); 28 | Assert.assertEquals(4, count); 29 | 30 | count = StringUtils.getUtf8TruncationIndex(utf8, 5); 31 | Assert.assertEquals(4, count); // Ô is 2 bytes-wide 32 | 33 | count = StringUtils.getUtf8TruncationIndex(utf8, 6); 34 | Assert.assertEquals(6, count); 35 | 36 | count = StringUtils.getUtf8TruncationIndex(utf8, 7); 37 | Assert.assertEquals(7, count); 38 | 39 | count = StringUtils.getUtf8TruncationIndex(utf8, 8); 40 | Assert.assertEquals(7, count); // no more chars 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /app/src/file_pusher.h: -------------------------------------------------------------------------------- 1 | #ifndef SC_FILE_PUSHER_H 2 | #define SC_FILE_PUSHER_H 3 | 4 | #include "common.h" 5 | 6 | #include 7 | 8 | #include "util/intr.h" 9 | #include "util/thread.h" 10 | #include "util/vecdeque.h" 11 | 12 | enum sc_file_pusher_action { 13 | SC_FILE_PUSHER_ACTION_INSTALL_APK, 14 | SC_FILE_PUSHER_ACTION_PUSH_FILE, 15 | }; 16 | 17 | struct sc_file_pusher_request { 18 | enum sc_file_pusher_action action; 19 | char *file; 20 | }; 21 | 22 | struct sc_file_pusher_request_queue SC_VECDEQUE(struct sc_file_pusher_request); 23 | 24 | struct sc_file_pusher { 25 | char *serial; 26 | const char *push_target; 27 | sc_thread thread; 28 | sc_mutex mutex; 29 | sc_cond event_cond; 30 | bool stopped; 31 | bool initialized; 32 | struct sc_file_pusher_request_queue queue; 33 | 34 | struct sc_intr intr; 35 | }; 36 | 37 | bool 38 | sc_file_pusher_init(struct sc_file_pusher *fp, const char *serial, 39 | const char *push_target); 40 | 41 | void 42 | sc_file_pusher_destroy(struct sc_file_pusher *fp); 43 | 44 | bool 45 | sc_file_pusher_start(struct sc_file_pusher *fp); 46 | 47 | void 48 | sc_file_pusher_stop(struct sc_file_pusher *fp); 49 | 50 | void 51 | sc_file_pusher_join(struct sc_file_pusher *fp); 52 | 53 | // take ownership of file, and will free() it 54 | bool 55 | sc_file_pusher_request(struct sc_file_pusher *fp, 56 | enum sc_file_pusher_action action, char *file); 57 | 58 | #endif 59 | -------------------------------------------------------------------------------- /server/src/main/java/com/genymobile/scrcpy/audio/AudioCodec.java: -------------------------------------------------------------------------------- 1 | package com.genymobile.scrcpy.audio; 2 | 3 | import com.genymobile.scrcpy.util.Codec; 4 | 5 | import android.media.MediaFormat; 6 | 7 | public enum AudioCodec implements Codec { 8 | OPUS(0x6f_70_75_73, "opus", MediaFormat.MIMETYPE_AUDIO_OPUS), 9 | AAC(0x00_61_61_63, "aac", MediaFormat.MIMETYPE_AUDIO_AAC), 10 | FLAC(0x66_6c_61_63, "flac", MediaFormat.MIMETYPE_AUDIO_FLAC), 11 | RAW(0x00_72_61_77, "raw", MediaFormat.MIMETYPE_AUDIO_RAW); 12 | 13 | private final int id; // 4-byte ASCII representation of the name 14 | private final String name; 15 | private final String mimeType; 16 | 17 | AudioCodec(int id, String name, String mimeType) { 18 | this.id = id; 19 | this.name = name; 20 | this.mimeType = mimeType; 21 | } 22 | 23 | @Override 24 | public Type getType() { 25 | return Type.AUDIO; 26 | } 27 | 28 | @Override 29 | public int getId() { 30 | return id; 31 | } 32 | 33 | @Override 34 | public String getName() { 35 | return name; 36 | } 37 | 38 | @Override 39 | public String getMimeType() { 40 | return mimeType; 41 | } 42 | 43 | public static AudioCodec findByName(String name) { 44 | for (AudioCodec codec : values()) { 45 | if (codec.name.equals(name)) { 46 | return codec; 47 | } 48 | } 49 | return null; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /server/src/main/java/com/genymobile/scrcpy/video/VideoCodec.java: -------------------------------------------------------------------------------- 1 | package com.genymobile.scrcpy.video; 2 | 3 | import com.genymobile.scrcpy.util.Codec; 4 | 5 | import android.annotation.SuppressLint; 6 | import android.media.MediaFormat; 7 | 8 | public enum VideoCodec implements Codec { 9 | H264(0x68_32_36_34, "h264", MediaFormat.MIMETYPE_VIDEO_AVC), 10 | H265(0x68_32_36_35, "h265", MediaFormat.MIMETYPE_VIDEO_HEVC), 11 | @SuppressLint("InlinedApi") // introduced in API 29 12 | AV1(0x00_61_76_31, "av1", MediaFormat.MIMETYPE_VIDEO_AV1); 13 | 14 | private final int id; // 4-byte ASCII representation of the name 15 | private final String name; 16 | private final String mimeType; 17 | 18 | VideoCodec(int id, String name, String mimeType) { 19 | this.id = id; 20 | this.name = name; 21 | this.mimeType = mimeType; 22 | } 23 | 24 | @Override 25 | public Type getType() { 26 | return Type.VIDEO; 27 | } 28 | 29 | @Override 30 | public int getId() { 31 | return id; 32 | } 33 | 34 | @Override 35 | public String getName() { 36 | return name; 37 | } 38 | 39 | @Override 40 | public String getMimeType() { 41 | return mimeType; 42 | } 43 | 44 | public static VideoCodec findByName(String name) { 45 | for (VideoCodec codec : values()) { 46 | if (codec.name.equals(name)) { 47 | return codec; 48 | } 49 | } 50 | return null; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /app/src/fps_counter.h: -------------------------------------------------------------------------------- 1 | #ifndef SC_FPSCOUNTER_H 2 | #define SC_FPSCOUNTER_H 3 | 4 | #include "common.h" 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | #include "util/thread.h" 11 | 12 | struct sc_fps_counter { 13 | sc_thread thread; 14 | sc_mutex mutex; 15 | sc_cond state_cond; 16 | 17 | bool thread_started; 18 | 19 | // atomic so that we can check without locking the mutex 20 | // if the FPS counter is disabled, we don't want to lock unnecessarily 21 | atomic_bool started; 22 | 23 | // the following fields are protected by the mutex 24 | bool interrupted; 25 | unsigned nr_rendered; 26 | unsigned nr_skipped; 27 | sc_tick next_timestamp; 28 | }; 29 | 30 | bool 31 | sc_fps_counter_init(struct sc_fps_counter *counter); 32 | 33 | void 34 | sc_fps_counter_destroy(struct sc_fps_counter *counter); 35 | 36 | bool 37 | sc_fps_counter_start(struct sc_fps_counter *counter); 38 | 39 | void 40 | sc_fps_counter_stop(struct sc_fps_counter *counter); 41 | 42 | bool 43 | sc_fps_counter_is_started(struct sc_fps_counter *counter); 44 | 45 | // request to stop the thread (on quit) 46 | // must be called before sc_fps_counter_join() 47 | void 48 | sc_fps_counter_interrupt(struct sc_fps_counter *counter); 49 | 50 | void 51 | sc_fps_counter_join(struct sc_fps_counter *counter); 52 | 53 | void 54 | sc_fps_counter_add_rendered_frame(struct sc_fps_counter *counter); 55 | 56 | void 57 | sc_fps_counter_add_skipped_frame(struct sc_fps_counter *counter); 58 | 59 | #endif 60 | -------------------------------------------------------------------------------- /server/src/main/java/com/genymobile/scrcpy/wrappers/PowerManager.java: -------------------------------------------------------------------------------- 1 | package com.genymobile.scrcpy.wrappers; 2 | 3 | import com.genymobile.scrcpy.util.Ln; 4 | 5 | import android.annotation.SuppressLint; 6 | import android.os.Build; 7 | import android.os.IInterface; 8 | 9 | import java.lang.reflect.Method; 10 | 11 | public final class PowerManager { 12 | private final IInterface manager; 13 | private Method isScreenOnMethod; 14 | 15 | static PowerManager create() { 16 | IInterface manager = ServiceManager.getService("power", "android.os.IPowerManager"); 17 | return new PowerManager(manager); 18 | } 19 | 20 | private PowerManager(IInterface manager) { 21 | this.manager = manager; 22 | } 23 | 24 | private Method getIsScreenOnMethod() throws NoSuchMethodException { 25 | if (isScreenOnMethod == null) { 26 | @SuppressLint("ObsoleteSdkInt") // we may lower minSdkVersion in the future 27 | String methodName = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT_WATCH ? "isInteractive" : "isScreenOn"; 28 | isScreenOnMethod = manager.getClass().getMethod(methodName); 29 | } 30 | return isScreenOnMethod; 31 | } 32 | 33 | public boolean isScreenOn() { 34 | try { 35 | Method method = getIsScreenOnMethod(); 36 | return (boolean) method.invoke(manager); 37 | } catch (ReflectiveOperationException e) { 38 | Ln.e("Could not invoke method", e); 39 | return false; 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /app/src/display.h: -------------------------------------------------------------------------------- 1 | #ifndef SC_DISPLAY_H 2 | #define SC_DISPLAY_H 3 | 4 | #include "common.h" 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | #include "coords.h" 11 | #include "opengl.h" 12 | #include "options.h" 13 | 14 | #ifdef __APPLE__ 15 | # define SC_DISPLAY_FORCE_OPENGL_CORE_PROFILE 16 | #endif 17 | 18 | struct sc_display { 19 | SDL_Renderer *renderer; 20 | SDL_Texture *texture; 21 | 22 | struct sc_opengl gl; 23 | #ifdef SC_DISPLAY_FORCE_OPENGL_CORE_PROFILE 24 | SDL_GLContext *gl_context; 25 | #endif 26 | 27 | bool mipmaps; 28 | 29 | struct { 30 | #define SC_DISPLAY_PENDING_FLAG_SIZE 1 31 | #define SC_DISPLAY_PENDING_FLAG_FRAME 2 32 | int8_t flags; 33 | struct sc_size size; 34 | AVFrame *frame; 35 | } pending; 36 | 37 | bool has_frame; 38 | }; 39 | 40 | enum sc_display_result { 41 | SC_DISPLAY_RESULT_OK, 42 | SC_DISPLAY_RESULT_PENDING, 43 | SC_DISPLAY_RESULT_ERROR, 44 | }; 45 | 46 | bool 47 | sc_display_init(struct sc_display *display, SDL_Window *window, 48 | SDL_Surface *icon_novideo, bool mipmaps); 49 | 50 | void 51 | sc_display_destroy(struct sc_display *display); 52 | 53 | enum sc_display_result 54 | sc_display_set_texture_size(struct sc_display *display, struct sc_size size); 55 | 56 | enum sc_display_result 57 | sc_display_update_texture(struct sc_display *display, const AVFrame *frame); 58 | 59 | enum sc_display_result 60 | sc_display_render(struct sc_display *display, const SDL_Rect *geometry, 61 | enum sc_orientation orientation); 62 | 63 | #endif 64 | -------------------------------------------------------------------------------- /server/src/main/java/com/genymobile/scrcpy/control/DeviceMessage.java: -------------------------------------------------------------------------------- 1 | package com.genymobile.scrcpy.control; 2 | 3 | public final class DeviceMessage { 4 | 5 | public static final int TYPE_CLIPBOARD = 0; 6 | public static final int TYPE_ACK_CLIPBOARD = 1; 7 | public static final int TYPE_UHID_OUTPUT = 2; 8 | 9 | private int type; 10 | private String text; 11 | private long sequence; 12 | private int id; 13 | private byte[] data; 14 | 15 | private DeviceMessage() { 16 | } 17 | 18 | public static DeviceMessage createClipboard(String text) { 19 | DeviceMessage event = new DeviceMessage(); 20 | event.type = TYPE_CLIPBOARD; 21 | event.text = text; 22 | return event; 23 | } 24 | 25 | public static DeviceMessage createAckClipboard(long sequence) { 26 | DeviceMessage event = new DeviceMessage(); 27 | event.type = TYPE_ACK_CLIPBOARD; 28 | event.sequence = sequence; 29 | return event; 30 | } 31 | 32 | public static DeviceMessage createUhidOutput(int id, byte[] data) { 33 | DeviceMessage event = new DeviceMessage(); 34 | event.type = TYPE_UHID_OUTPUT; 35 | event.id = id; 36 | event.data = data; 37 | return event; 38 | } 39 | 40 | public int getType() { 41 | return type; 42 | } 43 | 44 | public String getText() { 45 | return text; 46 | } 47 | 48 | public long getSequence() { 49 | return sequence; 50 | } 51 | 52 | public int getId() { 53 | return id; 54 | } 55 | 56 | public byte[] getData() { 57 | return data; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /app/src/delay_buffer.h: -------------------------------------------------------------------------------- 1 | #ifndef SC_DELAY_BUFFER_H 2 | #define SC_DELAY_BUFFER_H 3 | 4 | #include "common.h" 5 | 6 | #include 7 | 8 | #include "clock.h" 9 | #include "trait/frame_source.h" 10 | #include "trait/frame_sink.h" 11 | #include "util/thread.h" 12 | #include "util/tick.h" 13 | #include "util/vecdeque.h" 14 | 15 | //#define SC_BUFFERING_DEBUG // uncomment to debug 16 | 17 | // forward declarations 18 | typedef struct AVFrame AVFrame; 19 | 20 | struct sc_delayed_frame { 21 | AVFrame *frame; 22 | #ifdef SC_BUFFERING_DEBUG 23 | sc_tick push_date; 24 | #endif 25 | }; 26 | 27 | struct sc_delayed_frame_queue SC_VECDEQUE(struct sc_delayed_frame); 28 | 29 | struct sc_delay_buffer { 30 | struct sc_frame_source frame_source; // frame source trait 31 | struct sc_frame_sink frame_sink; // frame sink trait 32 | 33 | sc_tick delay; 34 | bool first_frame_asap; 35 | 36 | sc_thread thread; 37 | sc_mutex mutex; 38 | sc_cond queue_cond; 39 | sc_cond wait_cond; 40 | 41 | struct sc_clock clock; 42 | struct sc_delayed_frame_queue queue; 43 | bool stopped; 44 | }; 45 | 46 | struct sc_delay_buffer_callbacks { 47 | bool (*on_new_frame)(struct sc_delay_buffer *db, const AVFrame *frame, 48 | void *userdata); 49 | }; 50 | 51 | /** 52 | * Initialize a delay buffer. 53 | * 54 | * \param delay a (strictly) positive delay 55 | * \param first_frame_asap if true, do not delay the first frame (useful for 56 | a video stream). 57 | */ 58 | void 59 | sc_delay_buffer_init(struct sc_delay_buffer *db, sc_tick delay, 60 | bool first_frame_asap); 61 | 62 | #endif 63 | -------------------------------------------------------------------------------- /app/src/util/acksync.h: -------------------------------------------------------------------------------- 1 | #ifndef SC_ACK_SYNC_H 2 | #define SC_ACK_SYNC_H 3 | 4 | #include "common.h" 5 | 6 | #include "thread.h" 7 | 8 | #define SC_SEQUENCE_INVALID 0 9 | 10 | /** 11 | * Helper to wait for acknowledgments 12 | * 13 | * In practice, it is used to wait for device clipboard acknowledgement from the 14 | * server before injecting Ctrl+v via AOA HID, in order to avoid pasting the 15 | * content of the old device clipboard (if Ctrl+v was injected before the 16 | * clipboard content was actually set). 17 | */ 18 | struct sc_acksync { 19 | sc_mutex mutex; 20 | sc_cond cond; 21 | 22 | bool stopped; 23 | 24 | // Last acked value, initially SC_SEQUENCE_INVALID 25 | uint64_t ack; 26 | }; 27 | 28 | enum sc_acksync_wait_result { 29 | // Acknowledgment received 30 | SC_ACKSYNC_WAIT_OK, 31 | 32 | // Timeout expired 33 | SC_ACKSYNC_WAIT_TIMEOUT, 34 | 35 | // Interrupted from another thread by sc_acksync_interrupt() 36 | SC_ACKSYNC_WAIT_INTR, 37 | }; 38 | 39 | bool 40 | sc_acksync_init(struct sc_acksync *as); 41 | 42 | void 43 | sc_acksync_destroy(struct sc_acksync *as); 44 | 45 | /** 46 | * Acknowledge `sequence` 47 | * 48 | * The `sequence` must be greater than (or equal to) any previous acknowledged 49 | * sequence. 50 | */ 51 | void 52 | sc_acksync_ack(struct sc_acksync *as, uint64_t sequence); 53 | 54 | /** 55 | * Wait for acknowledgment of sequence `ack` (or higher) 56 | */ 57 | enum sc_acksync_wait_result 58 | sc_acksync_wait(struct sc_acksync *as, uint64_t ack, sc_tick deadline); 59 | 60 | /** 61 | * Interrupt any `sc_acksync_wait()` 62 | */ 63 | void 64 | sc_acksync_interrupt(struct sc_acksync *as); 65 | 66 | #endif 67 | -------------------------------------------------------------------------------- /app/src/util/strbuf.h: -------------------------------------------------------------------------------- 1 | #ifndef SC_STRBUF_H 2 | #define SC_STRBUF_H 3 | 4 | #include "common.h" 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | struct sc_strbuf { 11 | char *s; 12 | size_t len; 13 | size_t cap; 14 | }; 15 | 16 | /** 17 | * Initialize the string buffer 18 | * 19 | * `buf->s` must be manually freed by the caller. 20 | */ 21 | bool 22 | sc_strbuf_init(struct sc_strbuf *buf, size_t init_cap); 23 | 24 | /** 25 | * Append a string 26 | * 27 | * Append `len` characters from `s` to the buffer. 28 | */ 29 | bool 30 | sc_strbuf_append(struct sc_strbuf *buf, const char *s, size_t len); 31 | 32 | /** 33 | * Append a char 34 | * 35 | * Append a single character to the buffer. 36 | */ 37 | bool 38 | sc_strbuf_append_char(struct sc_strbuf *buf, const char c); 39 | 40 | /** 41 | * Append a char `n` times 42 | * 43 | * Append the same characters `n` times to the buffer. 44 | */ 45 | bool 46 | sc_strbuf_append_n(struct sc_strbuf *buf, const char c, size_t n); 47 | 48 | /** 49 | * Append a NUL-terminated string 50 | */ 51 | static inline bool 52 | sc_strbuf_append_str(struct sc_strbuf *buf, const char *s) { 53 | return sc_strbuf_append(buf, s, strlen(s)); 54 | } 55 | 56 | /** 57 | * Append a static string 58 | * 59 | * Append a string whose size is known at compile time (for 60 | * example a string literal). 61 | */ 62 | #define sc_strbuf_append_staticstr(BUF, S) \ 63 | sc_strbuf_append(BUF, S, sizeof(S) - 1) 64 | 65 | /** 66 | * Shrink the buffer capacity to its current length 67 | * 68 | * This resizes `buf->s` to fit the content. 69 | */ 70 | void 71 | sc_strbuf_shrink(struct sc_strbuf *buf); 72 | 73 | #endif 74 | -------------------------------------------------------------------------------- /app/src/hid/hid_gamepad.h: -------------------------------------------------------------------------------- 1 | #ifndef SC_HID_GAMEPAD_H 2 | #define SC_HID_GAMEPAD_H 3 | 4 | #include "common.h" 5 | 6 | #include 7 | 8 | #include "hid/hid_event.h" 9 | #include "input_events.h" 10 | 11 | #define SC_MAX_GAMEPADS 8 12 | #define SC_HID_ID_GAMEPAD_FIRST 3 13 | #define SC_HID_ID_GAMEPAD_LAST (SC_HID_ID_GAMEPAD_FIRST + SC_MAX_GAMEPADS - 1) 14 | 15 | struct sc_hid_gamepad_slot { 16 | uint32_t gamepad_id; 17 | uint32_t buttons; 18 | uint16_t axis_left_x; 19 | uint16_t axis_left_y; 20 | uint16_t axis_right_x; 21 | uint16_t axis_right_y; 22 | uint16_t axis_left_trigger; 23 | uint16_t axis_right_trigger; 24 | }; 25 | 26 | struct sc_hid_gamepad { 27 | struct sc_hid_gamepad_slot slots[SC_MAX_GAMEPADS]; 28 | }; 29 | 30 | void 31 | sc_hid_gamepad_init(struct sc_hid_gamepad *hid); 32 | 33 | bool 34 | sc_hid_gamepad_generate_open(struct sc_hid_gamepad *hid, 35 | struct sc_hid_open *hid_open, 36 | uint32_t gamepad_id); 37 | 38 | bool 39 | sc_hid_gamepad_generate_close(struct sc_hid_gamepad *hid, 40 | struct sc_hid_close *hid_close, 41 | uint32_t gamepad_id); 42 | 43 | bool 44 | sc_hid_gamepad_generate_input_from_button(struct sc_hid_gamepad *hid, 45 | struct sc_hid_input *hid_input, 46 | const struct sc_gamepad_button_event *event); 47 | 48 | bool 49 | sc_hid_gamepad_generate_input_from_axis(struct sc_hid_gamepad *hid, 50 | struct sc_hid_input *hid_input, 51 | const struct sc_gamepad_axis_event *event); 52 | 53 | #endif 54 | -------------------------------------------------------------------------------- /app/src/controller.h: -------------------------------------------------------------------------------- 1 | #ifndef SC_CONTROLLER_H 2 | #define SC_CONTROLLER_H 3 | 4 | #include "common.h" 5 | 6 | #include 7 | 8 | #include "control_msg.h" 9 | #include "receiver.h" 10 | #include "util/acksync.h" 11 | #include "util/net.h" 12 | #include "util/thread.h" 13 | #include "util/vecdeque.h" 14 | 15 | struct sc_control_msg_queue SC_VECDEQUE(struct sc_control_msg); 16 | 17 | struct sc_controller { 18 | sc_socket control_socket; 19 | sc_thread thread; 20 | sc_mutex mutex; 21 | sc_cond msg_cond; 22 | bool stopped; 23 | struct sc_control_msg_queue queue; 24 | struct sc_receiver receiver; 25 | 26 | const struct sc_controller_callbacks *cbs; 27 | void *cbs_userdata; 28 | }; 29 | 30 | struct sc_controller_callbacks { 31 | void (*on_ended)(struct sc_controller *controller, bool error, 32 | void *userdata); 33 | }; 34 | 35 | bool 36 | sc_controller_init(struct sc_controller *controller, sc_socket control_socket, 37 | const struct sc_controller_callbacks *cbs, 38 | void *cbs_userdata); 39 | 40 | void 41 | sc_controller_configure(struct sc_controller *controller, 42 | struct sc_acksync *acksync, 43 | struct sc_uhid_devices *uhid_devices); 44 | 45 | void 46 | sc_controller_destroy(struct sc_controller *controller); 47 | 48 | bool 49 | sc_controller_start(struct sc_controller *controller); 50 | 51 | void 52 | sc_controller_stop(struct sc_controller *controller); 53 | 54 | void 55 | sc_controller_join(struct sc_controller *controller); 56 | 57 | bool 58 | sc_controller_push_msg(struct sc_controller *controller, 59 | const struct sc_control_msg *msg); 60 | 61 | #endif 62 | -------------------------------------------------------------------------------- /server/src/main/java/com/genymobile/scrcpy/util/Command.java: -------------------------------------------------------------------------------- 1 | package com.genymobile.scrcpy.util; 2 | 3 | import java.io.IOException; 4 | import java.util.Arrays; 5 | import java.util.Scanner; 6 | 7 | public final class Command { 8 | private Command() { 9 | // not instantiable 10 | } 11 | 12 | public static void exec(String... cmd) throws IOException, InterruptedException { 13 | Process process = Runtime.getRuntime().exec(cmd); 14 | int exitCode = process.waitFor(); 15 | if (exitCode != 0) { 16 | throw new IOException("Command " + Arrays.toString(cmd) + " returned with value " + exitCode); 17 | } 18 | } 19 | 20 | public static String execReadLine(String... cmd) throws IOException, InterruptedException { 21 | String result = null; 22 | Process process = Runtime.getRuntime().exec(cmd); 23 | Scanner scanner = new Scanner(process.getInputStream()); 24 | if (scanner.hasNextLine()) { 25 | result = scanner.nextLine(); 26 | } 27 | int exitCode = process.waitFor(); 28 | if (exitCode != 0) { 29 | throw new IOException("Command " + Arrays.toString(cmd) + " returned with value " + exitCode); 30 | } 31 | return result; 32 | } 33 | 34 | public static String execReadOutput(String... cmd) throws IOException, InterruptedException { 35 | Process process = Runtime.getRuntime().exec(cmd); 36 | String output = IO.toString(process.getInputStream()); 37 | int exitCode = process.waitFor(); 38 | if (exitCode != 0) { 39 | throw new IOException("Command " + Arrays.toString(cmd) + " returned with value " + exitCode); 40 | } 41 | return output; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /app/src/util/tick.c: -------------------------------------------------------------------------------- 1 | #include "tick.h" 2 | 3 | #include 4 | #include 5 | #ifdef _WIN32 6 | # include 7 | #endif 8 | 9 | sc_tick 10 | sc_tick_now(void) { 11 | #ifndef _WIN32 12 | // Maximum sc_tick precision (microsecond) 13 | struct timespec ts; 14 | int ret = clock_gettime(CLOCK_MONOTONIC, &ts); 15 | if (ret) { 16 | abort(); 17 | } 18 | 19 | return SC_TICK_FROM_SEC(ts.tv_sec) + SC_TICK_FROM_NS(ts.tv_nsec); 20 | #else 21 | LARGE_INTEGER c; 22 | 23 | // On systems that run Windows XP or later, the function will always 24 | // succeed and will thus never return zero. 25 | // 26 | // 27 | 28 | BOOL ok = QueryPerformanceCounter(&c); 29 | assert(ok); 30 | (void) ok; 31 | 32 | LONGLONG counter = c.QuadPart; 33 | 34 | static LONGLONG frequency; 35 | if (!frequency) { 36 | // Initialize on first call 37 | LARGE_INTEGER f; 38 | ok = QueryPerformanceFrequency(&f); 39 | assert(ok); 40 | frequency = f.QuadPart; 41 | assert(frequency); 42 | } 43 | 44 | if (frequency % SC_TICK_FREQ == 0) { 45 | // Expected case (typically frequency = 10000000, i.e. 100ns precision) 46 | sc_tick div = frequency / SC_TICK_FREQ; 47 | return SC_TICK_FROM_US(counter / div); 48 | } 49 | 50 | // Split the division to avoid overflow 51 | sc_tick secs = SC_TICK_FROM_SEC(counter / frequency); 52 | sc_tick subsec = SC_TICK_FREQ * (counter % frequency) / frequency; 53 | return secs + subsec; 54 | #endif 55 | } 56 | -------------------------------------------------------------------------------- /server/src/main/java/com/genymobile/scrcpy/FakeContext.java: -------------------------------------------------------------------------------- 1 | package com.genymobile.scrcpy; 2 | 3 | import android.annotation.TargetApi; 4 | import android.content.AttributionSource; 5 | import android.content.Context; 6 | import android.content.ContextWrapper; 7 | import android.os.Build; 8 | import android.os.Process; 9 | import android.system.Os; 10 | 11 | public final class FakeContext extends ContextWrapper { 12 | 13 | public static final String PACKAGE_NAME = Os.getuid() == 1000 ? "android" : "com.android.shell"; 14 | public static final String PACKAGE_SHELL = "com.android.shell"; 15 | public static final int ROOT_UID = 0; // Like android.os.Process.ROOT_UID, but before API 29 16 | 17 | private static final FakeContext INSTANCE = new FakeContext(); 18 | 19 | public static FakeContext get() { 20 | return INSTANCE; 21 | } 22 | 23 | private FakeContext() { 24 | super(Workarounds.getSystemContext()); 25 | } 26 | 27 | @Override 28 | public String getPackageName() { 29 | return PACKAGE_NAME; 30 | } 31 | 32 | @Override 33 | public String getOpPackageName() { 34 | return PACKAGE_NAME; 35 | } 36 | 37 | 38 | @TargetApi(Build.VERSION_CODES.S) 39 | @Override 40 | public AttributionSource getAttributionSource() { 41 | AttributionSource.Builder builder = new AttributionSource.Builder(Process.SHELL_UID); 42 | builder.setPackageName(PACKAGE_NAME); 43 | return builder.build(); 44 | } 45 | 46 | // @Override to be added on SDK upgrade for Android 14 47 | @SuppressWarnings("unused") 48 | public int getDeviceId() { 49 | return 0; 50 | } 51 | 52 | @Override 53 | public Context getApplicationContext() { 54 | return this; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /server/src/main/java/com/genymobile/scrcpy/control/DeviceMessageSender.java: -------------------------------------------------------------------------------- 1 | package com.genymobile.scrcpy.control; 2 | 3 | import com.genymobile.scrcpy.util.Ln; 4 | 5 | import java.io.IOException; 6 | import java.util.concurrent.ArrayBlockingQueue; 7 | import java.util.concurrent.BlockingQueue; 8 | 9 | public final class DeviceMessageSender { 10 | 11 | private final ControlChannel controlChannel; 12 | 13 | private Thread thread; 14 | private final BlockingQueue queue = new ArrayBlockingQueue<>(16); 15 | 16 | public DeviceMessageSender(ControlChannel controlChannel) { 17 | this.controlChannel = controlChannel; 18 | } 19 | 20 | public void send(DeviceMessage msg) { 21 | if (!queue.offer(msg)) { 22 | Ln.w("Device message dropped: " + msg.getType()); 23 | } 24 | } 25 | 26 | private void loop() throws IOException, InterruptedException { 27 | while (!Thread.currentThread().isInterrupted()) { 28 | DeviceMessage msg = queue.take(); 29 | controlChannel.send(msg); 30 | } 31 | } 32 | 33 | public void start() { 34 | thread = new Thread(() -> { 35 | try { 36 | loop(); 37 | } catch (IOException | InterruptedException e) { 38 | // this is expected on close 39 | } finally { 40 | Ln.d("Device message sender stopped"); 41 | } 42 | }, "control-send"); 43 | thread.start(); 44 | } 45 | 46 | public void stop() { 47 | if (thread != null) { 48 | thread.interrupt(); 49 | } 50 | } 51 | 52 | public void join() throws InterruptedException { 53 | if (thread != null) { 54 | thread.join(); 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /app/src/trait/mouse_processor.h: -------------------------------------------------------------------------------- 1 | #ifndef SC_MOUSE_PROCESSOR_H 2 | #define SC_MOUSE_PROCESSOR_H 3 | 4 | #include "common.h" 5 | 6 | #include 7 | #include 8 | 9 | #include "input_events.h" 10 | 11 | /** 12 | * Mouse processor trait. 13 | * 14 | * Component able to process and inject mouse events should implement this 15 | * trait. 16 | */ 17 | struct sc_mouse_processor { 18 | const struct sc_mouse_processor_ops *ops; 19 | 20 | /** 21 | * If set, the mouse processor works in relative mode (the absolute 22 | * position is irrelevant). In particular, it indicates that the mouse 23 | * pointer must be "captured" by the UI. 24 | */ 25 | bool relative_mode; 26 | }; 27 | 28 | struct sc_mouse_processor_ops { 29 | /** 30 | * Process a mouse motion event 31 | * 32 | * This function is mandatory. 33 | */ 34 | void 35 | (*process_mouse_motion)(struct sc_mouse_processor *mp, 36 | const struct sc_mouse_motion_event *event); 37 | 38 | /** 39 | * Process a mouse click event 40 | * 41 | * This function is mandatory. 42 | */ 43 | void 44 | (*process_mouse_click)(struct sc_mouse_processor *mp, 45 | const struct sc_mouse_click_event *event); 46 | 47 | /** 48 | * Process a mouse scroll event 49 | * 50 | * This function is optional. 51 | */ 52 | void 53 | (*process_mouse_scroll)(struct sc_mouse_processor *mp, 54 | const struct sc_mouse_scroll_event *event); 55 | 56 | /** 57 | * Process a touch event 58 | * 59 | * This function is optional. 60 | */ 61 | void 62 | (*process_touch)(struct sc_mouse_processor *mp, 63 | const struct sc_touch_event *event); 64 | }; 65 | 66 | #endif 67 | -------------------------------------------------------------------------------- /app/src/events.c: -------------------------------------------------------------------------------- 1 | #include "events.h" 2 | 3 | #include "util/log.h" 4 | #include "util/thread.h" 5 | 6 | bool 7 | sc_push_event_impl(uint32_t type, const char *name) { 8 | SDL_Event event; 9 | event.type = type; 10 | int ret = SDL_PushEvent(&event); 11 | // ret < 0: error (queue full) 12 | // ret == 0: event was filtered 13 | // ret == 1: success 14 | if (ret != 1) { 15 | LOGE("Could not post %s event: %s", name, SDL_GetError()); 16 | return false; 17 | } 18 | 19 | return true; 20 | } 21 | 22 | bool 23 | sc_post_to_main_thread(sc_runnable_fn run, void *userdata) { 24 | SDL_Event event = { 25 | .user = { 26 | .type = SC_EVENT_RUN_ON_MAIN_THREAD, 27 | .data1 = run, 28 | .data2 = userdata, 29 | }, 30 | }; 31 | int ret = SDL_PushEvent(&event); 32 | // ret < 0: error (queue full) 33 | // ret == 0: event was filtered 34 | // ret == 1: success 35 | if (ret != 1) { 36 | if (ret == 0) { 37 | // if ret == 0, this is expected on exit, log in debug mode 38 | LOGD("Could not post runnable to main thread (filtered)"); 39 | } else { 40 | assert(ret < 0); 41 | LOGW("Could not post runnable to main thread: %s", SDL_GetError()); 42 | } 43 | return false; 44 | } 45 | 46 | return true; 47 | } 48 | 49 | static int SDLCALL 50 | task_event_filter(void *userdata, SDL_Event *event) { 51 | (void) userdata; 52 | 53 | if (event->type == SC_EVENT_RUN_ON_MAIN_THREAD) { 54 | // Reject this event type from now on 55 | return 0; 56 | } 57 | 58 | return 1; 59 | } 60 | 61 | void 62 | sc_reject_new_runnables(void) { 63 | assert(sc_thread_get_id() == SC_MAIN_THREAD_ID); 64 | 65 | SDL_SetEventFilter(task_event_filter, NULL); 66 | } 67 | -------------------------------------------------------------------------------- /doc/gamepad.md: -------------------------------------------------------------------------------- 1 | # Gamepad 2 | 3 | Several gamepad input modes are available: 4 | 5 | - `--gamepad=disabled` (default) 6 | - `--gamepad=uhid` (or `-G`): simulates physical HID gamepads using the UHID 7 | kernel module on the device 8 | - `--gamepad=aoa`: simulates physical HID gamepads using the AOAv2 protocol 9 | 10 | 11 | ## Physical gamepad simulation 12 | 13 | Two modes allow to simulate physical HID gamepads on the device, one for each 14 | physical gamepad plugged into the computer. 15 | 16 | 17 | ### UHID 18 | 19 | This mode simulates physical HID gamepads using the [UHID] kernel module on the 20 | device. 21 | 22 | [UHID]: https://kernel.org/doc/Documentation/hid/uhid.txt 23 | 24 | To enable UHID gamepads, use: 25 | 26 | ```bash 27 | scrcpy --gamepad=uhid 28 | scrcpy -G # short version 29 | ``` 30 | 31 | Note: UHID may not work on old Android versions due to permission errors. 32 | 33 | 34 | ### AOA 35 | 36 | This mode simulates physical HID gamepads using the [AOAv2] protocol. 37 | 38 | [AOAv2]: https://source.android.com/devices/accessories/aoa2#hid-support 39 | 40 | To enable AOA gamepads, use: 41 | 42 | ```bash 43 | scrcpy --gamepad=aoa 44 | ``` 45 | 46 | Contrary to the other mode, it works at the USB level directly (so it only works 47 | over USB). 48 | 49 | It does not use the scrcpy server, and does not require `adb` (USB debugging). 50 | Therefore, it is possible to control the device (but not mirror) even with USB 51 | debugging disabled (see [OTG](otg.md)). 52 | 53 | Note: For some reason, in this mode, Android detects multiple physical gamepads 54 | as a single misbehaving one. Use UHID if you need multiple gamepads. 55 | 56 | Note: On Windows, it may only work in [OTG mode](otg.md), not while mirroring 57 | (it is not possible to open a USB device if it is already open by another 58 | process like the _adb daemon_). 59 | -------------------------------------------------------------------------------- /app/src/trait/frame_source.c: -------------------------------------------------------------------------------- 1 | #include "frame_source.h" 2 | 3 | void 4 | sc_frame_source_init(struct sc_frame_source *source) { 5 | source->sink_count = 0; 6 | } 7 | 8 | void 9 | sc_frame_source_add_sink(struct sc_frame_source *source, 10 | struct sc_frame_sink *sink) { 11 | assert(source->sink_count < SC_FRAME_SOURCE_MAX_SINKS); 12 | assert(sink); 13 | assert(sink->ops); 14 | source->sinks[source->sink_count++] = sink; 15 | } 16 | 17 | static void 18 | sc_frame_source_sinks_close_firsts(struct sc_frame_source *source, 19 | unsigned count) { 20 | while (count) { 21 | struct sc_frame_sink *sink = source->sinks[--count]; 22 | sink->ops->close(sink); 23 | } 24 | } 25 | 26 | bool 27 | sc_frame_source_sinks_open(struct sc_frame_source *source, 28 | const AVCodecContext *ctx) { 29 | assert(source->sink_count); 30 | for (unsigned i = 0; i < source->sink_count; ++i) { 31 | struct sc_frame_sink *sink = source->sinks[i]; 32 | if (!sink->ops->open(sink, ctx)) { 33 | sc_frame_source_sinks_close_firsts(source, i); 34 | return false; 35 | } 36 | } 37 | 38 | return true; 39 | } 40 | 41 | void 42 | sc_frame_source_sinks_close(struct sc_frame_source *source) { 43 | assert(source->sink_count); 44 | sc_frame_source_sinks_close_firsts(source, source->sink_count); 45 | } 46 | 47 | bool 48 | sc_frame_source_sinks_push(struct sc_frame_source *source, 49 | const AVFrame *frame) { 50 | assert(source->sink_count); 51 | for (unsigned i = 0; i < source->sink_count; ++i) { 52 | struct sc_frame_sink *sink = source->sinks[i]; 53 | if (!sink->ops->push(sink, frame)) { 54 | return false; 55 | } 56 | } 57 | 58 | return true; 59 | } 60 | -------------------------------------------------------------------------------- /app/src/opengl.c: -------------------------------------------------------------------------------- 1 | #include "opengl.h" 2 | 3 | #include 4 | #include 5 | #include "SDL2/SDL.h" 6 | 7 | void 8 | sc_opengl_init(struct sc_opengl *gl) { 9 | gl->GetString = SDL_GL_GetProcAddress("glGetString"); 10 | assert(gl->GetString); 11 | 12 | gl->TexParameterf = SDL_GL_GetProcAddress("glTexParameterf"); 13 | assert(gl->TexParameterf); 14 | 15 | gl->TexParameteri = SDL_GL_GetProcAddress("glTexParameteri"); 16 | assert(gl->TexParameteri); 17 | 18 | // optional 19 | gl->GenerateMipmap = SDL_GL_GetProcAddress("glGenerateMipmap"); 20 | 21 | const char *version = (const char *) gl->GetString(GL_VERSION); 22 | assert(version); 23 | gl->version = version; 24 | 25 | #define OPENGL_ES_PREFIX "OpenGL ES " 26 | /* starts with "OpenGL ES " */ 27 | gl->is_opengles = !strncmp(gl->version, OPENGL_ES_PREFIX, 28 | sizeof(OPENGL_ES_PREFIX) - 1); 29 | if (gl->is_opengles) { 30 | /* skip the prefix */ 31 | version += sizeof(OPENGL_ES_PREFIX) - 1; 32 | } 33 | 34 | int r = sscanf(version, "%d.%d", &gl->version_major, &gl->version_minor); 35 | if (r != 2) { 36 | // failed to parse the version 37 | gl->version_major = 0; 38 | gl->version_minor = 0; 39 | } 40 | } 41 | 42 | bool 43 | sc_opengl_version_at_least(struct sc_opengl *gl, 44 | int minver_major, int minver_minor, 45 | int minver_es_major, int minver_es_minor) 46 | { 47 | if (gl->is_opengles) { 48 | return gl->version_major > minver_es_major 49 | || (gl->version_major == minver_es_major 50 | && gl->version_minor >= minver_es_minor); 51 | } 52 | 53 | return gl->version_major > minver_major 54 | || (gl->version_major == minver_major 55 | && gl->version_minor >= minver_minor); 56 | } 57 | -------------------------------------------------------------------------------- /app/src/util/net.h: -------------------------------------------------------------------------------- 1 | #ifndef SC_NET_H 2 | #define SC_NET_H 3 | 4 | #include "common.h" 5 | 6 | #include 7 | #include 8 | 9 | #ifdef _WIN32 10 | 11 | # include 12 | # include 13 | # define SC_SOCKET_NONE NULL 14 | typedef struct sc_socket_windows { 15 | SOCKET socket; 16 | atomic_flag closed; 17 | } *sc_socket; 18 | 19 | #else // not _WIN32 20 | 21 | # include 22 | # define SC_SOCKET_NONE -1 23 | typedef int sc_socket; 24 | 25 | #endif 26 | 27 | #define IPV4_LOCALHOST 0x7F000001 28 | 29 | bool 30 | net_init(void); 31 | 32 | void 33 | net_cleanup(void); 34 | 35 | sc_socket 36 | net_socket(void); 37 | 38 | bool 39 | net_connect(sc_socket socket, uint32_t addr, uint16_t port); 40 | 41 | bool 42 | net_listen(sc_socket server_socket, uint32_t addr, uint16_t port, int backlog); 43 | 44 | sc_socket 45 | net_accept(sc_socket server_socket); 46 | 47 | // the _all versions wait/retry until len bytes have been written/read 48 | ssize_t 49 | net_recv(sc_socket socket, void *buf, size_t len); 50 | 51 | ssize_t 52 | net_recv_all(sc_socket socket, void *buf, size_t len); 53 | 54 | ssize_t 55 | net_send(sc_socket socket, const void *buf, size_t len); 56 | 57 | ssize_t 58 | net_send_all(sc_socket socket, const void *buf, size_t len); 59 | 60 | // Shutdown the socket (or close on Windows) so that any blocking send() or 61 | // recv() are interrupted. 62 | bool 63 | net_interrupt(sc_socket socket); 64 | 65 | // Close the socket. 66 | // A socket must always be closed, even if net_interrupt() has been called. 67 | bool 68 | net_close(sc_socket socket); 69 | 70 | // Disable Nagle's algorithm (if tcp_nodelay is true) 71 | bool 72 | net_set_tcp_nodelay(sc_socket socket, bool tcp_nodelay); 73 | 74 | /** 75 | * Parse `ip` "xxx.xxx.xxx.xxx" to an IPv4 host representation 76 | */ 77 | bool 78 | net_parse_ipv4(const char *ip, uint32_t *ipv4); 79 | 80 | #endif 81 | -------------------------------------------------------------------------------- /server/src/main/java/com/genymobile/scrcpy/control/DeviceMessageWriter.java: -------------------------------------------------------------------------------- 1 | package com.genymobile.scrcpy.control; 2 | 3 | import com.genymobile.scrcpy.util.StringUtils; 4 | 5 | import java.io.BufferedOutputStream; 6 | import java.io.DataOutputStream; 7 | import java.io.IOException; 8 | import java.io.OutputStream; 9 | import java.nio.charset.StandardCharsets; 10 | 11 | public class DeviceMessageWriter { 12 | 13 | private static final int MESSAGE_MAX_SIZE = 1 << 18; // 256k 14 | public static final int CLIPBOARD_TEXT_MAX_LENGTH = MESSAGE_MAX_SIZE - 5; // type: 1 byte; length: 4 bytes 15 | 16 | private final DataOutputStream dos; 17 | 18 | public DeviceMessageWriter(OutputStream rawOutputStream) { 19 | dos = new DataOutputStream(new BufferedOutputStream(rawOutputStream)); 20 | } 21 | 22 | public void write(DeviceMessage msg) throws IOException { 23 | int type = msg.getType(); 24 | dos.writeByte(type); 25 | switch (type) { 26 | case DeviceMessage.TYPE_CLIPBOARD: 27 | String text = msg.getText(); 28 | byte[] raw = text.getBytes(StandardCharsets.UTF_8); 29 | int len = StringUtils.getUtf8TruncationIndex(raw, CLIPBOARD_TEXT_MAX_LENGTH); 30 | dos.writeInt(len); 31 | dos.write(raw, 0, len); 32 | break; 33 | case DeviceMessage.TYPE_ACK_CLIPBOARD: 34 | dos.writeLong(msg.getSequence()); 35 | break; 36 | case DeviceMessage.TYPE_UHID_OUTPUT: 37 | dos.writeShort(msg.getId()); 38 | byte[] data = msg.getData(); 39 | dos.writeShort(data.length); 40 | dos.write(data); 41 | break; 42 | default: 43 | throw new ControlProtocolException("Unknown event type: " + type); 44 | } 45 | dos.flush(); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /app/src/util/intr.h: -------------------------------------------------------------------------------- 1 | #ifndef SC_INTR_H 2 | #define SC_INTR_H 3 | 4 | #include "common.h" 5 | 6 | #include 7 | #include 8 | 9 | #include "net.h" 10 | #include "process.h" 11 | #include "thread.h" 12 | 13 | /** 14 | * Interruptor to wake up a blocking call from another thread 15 | * 16 | * It allows to register a socket or a process before a blocking call, and 17 | * interrupt/close from another thread to wake up the blocking call. 18 | */ 19 | struct sc_intr { 20 | sc_mutex mutex; 21 | 22 | sc_socket socket; 23 | sc_pid process; 24 | 25 | // Written protected by the mutex to avoid race conditions against 26 | // sc_intr_set_socket() and sc_intr_set_process(), but can be read 27 | // (atomically) without mutex 28 | atomic_bool interrupted; 29 | }; 30 | 31 | /** 32 | * Initialize an interruptor 33 | */ 34 | bool 35 | sc_intr_init(struct sc_intr *intr); 36 | 37 | /** 38 | * Set a socket as the interruptible component 39 | * 40 | * Call with SC_SOCKET_NONE to unset. 41 | */ 42 | bool 43 | sc_intr_set_socket(struct sc_intr *intr, sc_socket socket); 44 | 45 | /** 46 | * Set a process as the interruptible component 47 | * 48 | * Call with SC_PROCESS_NONE to unset. 49 | */ 50 | bool 51 | sc_intr_set_process(struct sc_intr *intr, sc_pid socket); 52 | 53 | /** 54 | * Interrupt the current interruptible component 55 | * 56 | * Must be called from a different thread. 57 | */ 58 | void 59 | sc_intr_interrupt(struct sc_intr *intr); 60 | 61 | /** 62 | * Read the interrupted state 63 | * 64 | * It is exposed as a static inline function because it just loads from an 65 | * atomic. 66 | */ 67 | static inline bool 68 | sc_intr_is_interrupted(struct sc_intr *intr) { 69 | return atomic_load_explicit(&intr->interrupted, memory_order_relaxed); 70 | } 71 | 72 | /** 73 | * Destroy the interruptor 74 | */ 75 | void 76 | sc_intr_destroy(struct sc_intr *intr); 77 | 78 | #endif 79 | -------------------------------------------------------------------------------- /app/src/util/acksync.c: -------------------------------------------------------------------------------- 1 | #include "acksync.h" 2 | 3 | #include 4 | #include "util/log.h" 5 | 6 | bool 7 | sc_acksync_init(struct sc_acksync *as) { 8 | bool ok = sc_mutex_init(&as->mutex); 9 | if (!ok) { 10 | return false; 11 | } 12 | 13 | ok = sc_cond_init(&as->cond); 14 | if (!ok) { 15 | sc_mutex_destroy(&as->mutex); 16 | return false; 17 | } 18 | 19 | as->stopped = false; 20 | as->ack = SC_SEQUENCE_INVALID; 21 | 22 | return true; 23 | } 24 | 25 | void 26 | sc_acksync_destroy(struct sc_acksync *as) { 27 | sc_cond_destroy(&as->cond); 28 | sc_mutex_destroy(&as->mutex); 29 | } 30 | 31 | void 32 | sc_acksync_ack(struct sc_acksync *as, uint64_t sequence) { 33 | sc_mutex_lock(&as->mutex); 34 | 35 | // Acknowledgements must be monotonic 36 | assert(sequence >= as->ack); 37 | 38 | as->ack = sequence; 39 | sc_cond_signal(&as->cond); 40 | 41 | sc_mutex_unlock(&as->mutex); 42 | } 43 | 44 | enum sc_acksync_wait_result 45 | sc_acksync_wait(struct sc_acksync *as, uint64_t ack, sc_tick deadline) { 46 | sc_mutex_lock(&as->mutex); 47 | 48 | bool timed_out = false; 49 | while (!as->stopped && as->ack < ack && !timed_out) { 50 | timed_out = !sc_cond_timedwait(&as->cond, &as->mutex, deadline); 51 | } 52 | 53 | enum sc_acksync_wait_result ret; 54 | if (as->stopped) { 55 | ret = SC_ACKSYNC_WAIT_INTR; 56 | } else if (as->ack >= ack) { 57 | ret = SC_ACKSYNC_WAIT_OK; 58 | } else { 59 | assert(timed_out); 60 | ret = SC_ACKSYNC_WAIT_TIMEOUT; 61 | } 62 | sc_mutex_unlock(&as->mutex); 63 | 64 | return ret; 65 | } 66 | 67 | /** 68 | * Interrupt any `sc_acksync_wait()` 69 | */ 70 | void 71 | sc_acksync_interrupt(struct sc_acksync *as) { 72 | sc_mutex_lock(&as->mutex); 73 | as->stopped = true; 74 | sc_cond_signal(&as->cond); 75 | sc_mutex_unlock(&as->mutex); 76 | } 77 | -------------------------------------------------------------------------------- /app/src/util/audiobuf.h: -------------------------------------------------------------------------------- 1 | #ifndef SC_AUDIOBUF_H 2 | #define SC_AUDIOBUF_H 3 | 4 | #include "common.h" 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | /** 12 | * Wrapper around bytebuf to read and write samples 13 | * 14 | * Each sample takes sample_size bytes. 15 | */ 16 | struct sc_audiobuf { 17 | uint8_t *data; 18 | uint32_t alloc_size; // in samples 19 | size_t sample_size; 20 | 21 | atomic_uint_least32_t head; // writer cursor, in samples 22 | atomic_uint_least32_t tail; // reader cursor, in samples 23 | // empty: tail == head 24 | // full: ((tail + 1) % alloc_size) == head 25 | }; 26 | 27 | static inline uint32_t 28 | sc_audiobuf_to_samples(struct sc_audiobuf *buf, size_t bytes) { 29 | assert(bytes % buf->sample_size == 0); 30 | return bytes / buf->sample_size; 31 | } 32 | 33 | static inline size_t 34 | sc_audiobuf_to_bytes(struct sc_audiobuf *buf, uint32_t samples) { 35 | return samples * buf->sample_size; 36 | } 37 | 38 | bool 39 | sc_audiobuf_init(struct sc_audiobuf *buf, size_t sample_size, 40 | uint32_t capacity); 41 | 42 | void 43 | sc_audiobuf_destroy(struct sc_audiobuf *buf); 44 | 45 | uint32_t 46 | sc_audiobuf_read(struct sc_audiobuf *buf, void *to, uint32_t samples_count); 47 | 48 | uint32_t 49 | sc_audiobuf_write(struct sc_audiobuf *buf, const void *from, 50 | uint32_t samples_count); 51 | 52 | static inline uint32_t 53 | sc_audiobuf_capacity(struct sc_audiobuf *buf) { 54 | assert(buf->alloc_size); 55 | return buf->alloc_size - 1; 56 | } 57 | 58 | static inline uint32_t 59 | sc_audiobuf_can_read(struct sc_audiobuf *buf) { 60 | uint32_t head = atomic_load_explicit(&buf->head, memory_order_acquire); 61 | uint32_t tail = atomic_load_explicit(&buf->tail, memory_order_acquire); 62 | return (buf->alloc_size + head - tail) % buf->alloc_size; 63 | } 64 | 65 | #endif 66 | -------------------------------------------------------------------------------- /app/src/trait/key_processor.h: -------------------------------------------------------------------------------- 1 | #ifndef SC_KEY_PROCESSOR_H 2 | #define SC_KEY_PROCESSOR_H 3 | 4 | #include "common.h" 5 | 6 | #include 7 | #include 8 | 9 | #include "input_events.h" 10 | 11 | /** 12 | * Key processor trait. 13 | * 14 | * Component able to process and inject keys should implement this trait. 15 | */ 16 | struct sc_key_processor { 17 | /** 18 | * Set by the implementation to indicate that it must explicitly wait for 19 | * the clipboard to be set on the device before injecting Ctrl+v to avoid 20 | * race conditions. If it is set, the input_manager will pass a valid 21 | * ack_to_wait to process_key() in case of clipboard synchronization 22 | * resulting of the key event. 23 | */ 24 | bool async_paste; 25 | 26 | /** 27 | * Set by the implementation to indicate that the keyboard is HID. In 28 | * practice, it is used to react on a shortcut to open the hard keyboard 29 | * settings only if the keyboard is HID. 30 | */ 31 | bool hid; 32 | 33 | const struct sc_key_processor_ops *ops; 34 | }; 35 | 36 | struct sc_key_processor_ops { 37 | 38 | /** 39 | * Process a keyboard event 40 | * 41 | * The `sequence` number (if different from `SC_SEQUENCE_INVALID`) indicates 42 | * the acknowledgement number to wait for before injecting this event. 43 | * This allows to ensure that the device clipboard is set before injecting 44 | * Ctrl+v on the device. 45 | * 46 | * This function is mandatory. 47 | */ 48 | void 49 | (*process_key)(struct sc_key_processor *kp, 50 | const struct sc_key_event *event, uint64_t ack_to_wait); 51 | 52 | /** 53 | * Process an input text 54 | * 55 | * This function is optional. 56 | */ 57 | void 58 | (*process_text)(struct sc_key_processor *kp, 59 | const struct sc_text_event *event); 60 | }; 61 | 62 | #endif 63 | -------------------------------------------------------------------------------- /app/src/hid/hid_keyboard.h: -------------------------------------------------------------------------------- 1 | #ifndef SC_HID_KEYBOARD_H 2 | #define SC_HID_KEYBOARD_H 3 | 4 | #include "common.h" 5 | 6 | #include 7 | 8 | #include "hid/hid_event.h" 9 | #include "input_events.h" 10 | 11 | // See "SDL2/SDL_scancode.h". 12 | // Maybe SDL_Keycode is used by most people, but SDL_Scancode is taken from USB 13 | // HID protocol. 14 | // 0x65 is Application, typically AT-101 Keyboard ends here. 15 | #define SC_HID_KEYBOARD_KEYS 0x66 16 | 17 | #define SC_HID_ID_KEYBOARD 1 18 | 19 | /** 20 | * HID keyboard events are sequence-based, every time keyboard state changes 21 | * it sends an array of currently pressed keys, the host is responsible for 22 | * compare events and determine which key becomes pressed and which key becomes 23 | * released. In order to convert SDL_KeyboardEvent to HID events, we first use 24 | * an array of keys to save each keys' state. And when a SDL_KeyboardEvent was 25 | * emitted, we updated our state, and then we use a loop to generate HID 26 | * events. The sequence of array elements is unimportant and when too much keys 27 | * pressed at the same time (more than report count), we should generate 28 | * phantom state. Don't forget that modifiers should be updated too, even for 29 | * phantom state. 30 | */ 31 | struct sc_hid_keyboard { 32 | bool keys[SC_HID_KEYBOARD_KEYS]; 33 | }; 34 | 35 | void 36 | sc_hid_keyboard_init(struct sc_hid_keyboard *hid); 37 | 38 | void 39 | sc_hid_keyboard_generate_open(struct sc_hid_open *hid_open); 40 | 41 | void 42 | sc_hid_keyboard_generate_close(struct sc_hid_close *hid_close); 43 | 44 | bool 45 | sc_hid_keyboard_generate_input_from_key(struct sc_hid_keyboard *hid, 46 | struct sc_hid_input *hid_input, 47 | const struct sc_key_event *event); 48 | 49 | bool 50 | sc_hid_keyboard_generate_input_from_mods(struct sc_hid_input *hid_input, 51 | uint16_t mods_state); 52 | 53 | #endif 54 | -------------------------------------------------------------------------------- /doc/v4l2.md: -------------------------------------------------------------------------------- 1 | # Video4Linux 2 | 3 | On Linux, it is possible to send the video stream to a [v4l2] loopback device, 4 | so that the Android device can be opened like a webcam by any v4l2-capable tool. 5 | 6 | [v4l2]: https://en.wikipedia.org/wiki/Video4Linux 7 | 8 | The module `v4l2loopback` must be installed: 9 | 10 | ```bash 11 | sudo apt install v4l2loopback-dkms 12 | ``` 13 | 14 | To create a v4l2 device: 15 | 16 | ```bash 17 | sudo modprobe v4l2loopback 18 | ``` 19 | 20 | This will create a new video device in `/dev/videoN`, where `N` is an integer 21 | (more [options](https://github.com/umlaeute/v4l2loopback#options) are available 22 | to create several devices or devices with specific IDs). 23 | 24 | If you encounter problems detecting your device with Chrome/WebRTC, you can try 25 | `exclusive_caps` mode: 26 | 27 | ``` 28 | sudo modprobe v4l2loopback exclusive_caps=1 29 | ``` 30 | 31 | To list the enabled devices: 32 | 33 | ```bash 34 | # requires v4l-utils package 35 | v4l2-ctl --list-devices 36 | 37 | # simple but might be sufficient 38 | ls /dev/video* 39 | ``` 40 | 41 | To start `scrcpy` using a v4l2 sink: 42 | 43 | ```bash 44 | scrcpy --v4l2-sink=/dev/videoN 45 | scrcpy --v4l2-sink=/dev/videoN --no-video-playback # disable playback window 46 | ``` 47 | 48 | (replace `N` with the device ID, check with `ls /dev/video*`) 49 | 50 | Once enabled, you can open your video stream with a v4l2-capable tool: 51 | 52 | ```bash 53 | ffplay -i /dev/videoN 54 | vlc v4l2:///dev/videoN # VLC might add some buffering delay 55 | ``` 56 | 57 | For example, you could capture the video within [OBS] or within your video 58 | conference tool. 59 | 60 | [OBS]: https://obsproject.com/ 61 | 62 | 63 | ## Buffering 64 | 65 | By default, there is no video buffering, to get the lowest possible latency. 66 | 67 | As for the [video display](video.md#buffering), it is possible to add 68 | buffering to delay the v4l2 stream: 69 | 70 | ```bash 71 | scrcpy --v4l2-buffer=300 # add 300ms buffering for v4l2 sink 72 | ``` 73 | -------------------------------------------------------------------------------- /server/src/main/java/com/genymobile/scrcpy/device/Position.java: -------------------------------------------------------------------------------- 1 | package com.genymobile.scrcpy.device; 2 | 3 | import java.util.Objects; 4 | 5 | public class Position { 6 | private final Point point; 7 | private final 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 | public Position rotate(int rotation) { 27 | switch (rotation) { 28 | case 1: 29 | return new Position(new Point(screenSize.getHeight() - point.getY(), point.getX()), screenSize.rotate()); 30 | case 2: 31 | return new Position(new Point(screenSize.getWidth() - point.getX(), screenSize.getHeight() - point.getY()), screenSize); 32 | case 3: 33 | return new Position(new Point(point.getY(), screenSize.getWidth() - point.getX()), screenSize.rotate()); 34 | default: 35 | return this; 36 | } 37 | } 38 | 39 | @Override 40 | public boolean equals(Object o) { 41 | if (this == o) { 42 | return true; 43 | } 44 | if (o == null || getClass() != o.getClass()) { 45 | return false; 46 | } 47 | Position position = (Position) o; 48 | return Objects.equals(point, position.point) && Objects.equals(screenSize, position.screenSize); 49 | } 50 | 51 | @Override 52 | public int hashCode() { 53 | return Objects.hash(point, screenSize); 54 | } 55 | 56 | @Override 57 | public String toString() { 58 | return "Position{" + "point=" + point + ", screenSize=" + screenSize + '}'; 59 | } 60 | 61 | } 62 | -------------------------------------------------------------------------------- /app/src/util/timeout.c: -------------------------------------------------------------------------------- 1 | #include "timeout.h" 2 | 3 | #include 4 | 5 | #include "log.h" 6 | 7 | bool 8 | sc_timeout_init(struct sc_timeout *timeout) { 9 | bool ok = sc_mutex_init(&timeout->mutex); 10 | if (!ok) { 11 | return false; 12 | } 13 | 14 | ok = sc_cond_init(&timeout->cond); 15 | if (!ok) { 16 | return false; 17 | } 18 | 19 | timeout->stopped = false; 20 | 21 | return true; 22 | } 23 | 24 | static int 25 | run_timeout(void *data) { 26 | struct sc_timeout *timeout = data; 27 | sc_tick deadline = timeout->deadline; 28 | 29 | sc_mutex_lock(&timeout->mutex); 30 | bool timed_out = false; 31 | while (!timeout->stopped && !timed_out) { 32 | timed_out = !sc_cond_timedwait(&timeout->cond, &timeout->mutex, 33 | deadline); 34 | } 35 | sc_mutex_unlock(&timeout->mutex); 36 | 37 | timeout->cbs->on_timeout(timeout, timeout->cbs_userdata); 38 | 39 | return 0; 40 | } 41 | 42 | bool 43 | sc_timeout_start(struct sc_timeout *timeout, sc_tick deadline, 44 | const struct sc_timeout_callbacks *cbs, void *cbs_userdata) { 45 | bool ok = sc_thread_create(&timeout->thread, run_timeout, "scrcpy-timeout", 46 | timeout); 47 | if (!ok) { 48 | LOGE("Timeout: could not start thread"); 49 | return false; 50 | } 51 | 52 | timeout->deadline = deadline; 53 | 54 | assert(cbs && cbs->on_timeout); 55 | timeout->cbs = cbs; 56 | timeout->cbs_userdata = cbs_userdata; 57 | 58 | return true; 59 | } 60 | 61 | void 62 | sc_timeout_stop(struct sc_timeout *timeout) { 63 | sc_mutex_lock(&timeout->mutex); 64 | timeout->stopped = true; 65 | sc_mutex_unlock(&timeout->mutex); 66 | } 67 | 68 | void 69 | sc_timeout_join(struct sc_timeout *timeout) { 70 | sc_thread_join(&timeout->thread, NULL); 71 | } 72 | 73 | void 74 | sc_timeout_destroy(struct sc_timeout *timeout) { 75 | sc_mutex_destroy(&timeout->mutex); 76 | sc_cond_destroy(&timeout->cond); 77 | } 78 | -------------------------------------------------------------------------------- /app/src/input_manager.h: -------------------------------------------------------------------------------- 1 | #ifndef SC_INPUTMANAGER_H 2 | #define SC_INPUTMANAGER_H 3 | 4 | #include "common.h" 5 | 6 | #include 7 | 8 | #include 9 | 10 | #include "controller.h" 11 | #include "file_pusher.h" 12 | #include "fps_counter.h" 13 | #include "options.h" 14 | #include "trait/gamepad_processor.h" 15 | #include "trait/key_processor.h" 16 | #include "trait/mouse_processor.h" 17 | 18 | struct sc_input_manager { 19 | struct sc_controller *controller; 20 | struct sc_file_pusher *fp; 21 | struct sc_screen *screen; 22 | 23 | struct sc_key_processor *kp; 24 | struct sc_mouse_processor *mp; 25 | struct sc_gamepad_processor *gp; 26 | 27 | struct sc_mouse_bindings mouse_bindings; 28 | bool legacy_paste; 29 | bool clipboard_autosync; 30 | 31 | uint16_t sdl_shortcut_mods; 32 | 33 | bool vfinger_down; 34 | bool vfinger_invert_x; 35 | bool vfinger_invert_y; 36 | 37 | uint8_t mouse_buttons_state; // OR of enum sc_mouse_button values 38 | 39 | // Tracks the number of identical consecutive shortcut key down events. 40 | // Not to be confused with event->repeat, which counts the number of 41 | // system-generated repeated key presses. 42 | unsigned key_repeat; 43 | SDL_Keycode last_keycode; 44 | uint16_t last_mod; 45 | 46 | uint64_t next_sequence; // used for request acknowledgements 47 | }; 48 | 49 | struct sc_input_manager_params { 50 | struct sc_controller *controller; 51 | struct sc_file_pusher *fp; 52 | struct sc_screen *screen; 53 | struct sc_key_processor *kp; 54 | struct sc_mouse_processor *mp; 55 | struct sc_gamepad_processor *gp; 56 | 57 | struct sc_mouse_bindings mouse_bindings; 58 | bool legacy_paste; 59 | bool clipboard_autosync; 60 | uint8_t shortcut_mods; // OR of enum sc_shortcut_mod values 61 | }; 62 | 63 | void 64 | sc_input_manager_init(struct sc_input_manager *im, 65 | const struct sc_input_manager_params *params); 66 | 67 | void 68 | sc_input_manager_handle_event(struct sc_input_manager *im, 69 | const SDL_Event *event); 70 | 71 | #endif 72 | -------------------------------------------------------------------------------- /app/src/util/thread.h: -------------------------------------------------------------------------------- 1 | #ifndef SC_THREAD_H 2 | #define SC_THREAD_H 3 | 4 | #include "common.h" 5 | 6 | #include 7 | #include 8 | 9 | #include "tick.h" 10 | 11 | /* Forward declarations */ 12 | typedef struct SDL_Thread SDL_Thread; 13 | typedef struct SDL_mutex SDL_mutex; 14 | typedef struct SDL_cond SDL_cond; 15 | 16 | typedef int sc_thread_fn(void *); 17 | typedef unsigned sc_thread_id; 18 | typedef atomic_uint sc_atomic_thread_id; 19 | 20 | typedef struct sc_thread { 21 | SDL_Thread *thread; 22 | } sc_thread; 23 | 24 | enum sc_thread_priority { 25 | SC_THREAD_PRIORITY_LOW, 26 | SC_THREAD_PRIORITY_NORMAL, 27 | SC_THREAD_PRIORITY_HIGH, 28 | SC_THREAD_PRIORITY_TIME_CRITICAL, 29 | }; 30 | 31 | typedef struct sc_mutex { 32 | SDL_mutex *mutex; 33 | #ifndef NDEBUG 34 | sc_atomic_thread_id locker; 35 | #endif 36 | } sc_mutex; 37 | 38 | typedef struct sc_cond { 39 | SDL_cond *cond; 40 | } sc_cond; 41 | 42 | extern sc_thread_id SC_MAIN_THREAD_ID; 43 | 44 | bool 45 | sc_thread_create(sc_thread *thread, sc_thread_fn fn, const char *name, 46 | void *userdata); 47 | 48 | void 49 | sc_thread_join(sc_thread *thread, int *status); 50 | 51 | bool 52 | sc_thread_set_priority(enum sc_thread_priority priority); 53 | 54 | bool 55 | sc_mutex_init(sc_mutex *mutex); 56 | 57 | void 58 | sc_mutex_destroy(sc_mutex *mutex); 59 | 60 | void 61 | sc_mutex_lock(sc_mutex *mutex); 62 | 63 | void 64 | sc_mutex_unlock(sc_mutex *mutex); 65 | 66 | sc_thread_id 67 | sc_thread_get_id(void); 68 | 69 | #ifndef NDEBUG 70 | bool 71 | sc_mutex_held(struct sc_mutex *mutex); 72 | # define sc_mutex_assert(mutex) assert(sc_mutex_held(mutex)) 73 | #else 74 | # define sc_mutex_assert(mutex) 75 | #endif 76 | 77 | bool 78 | sc_cond_init(sc_cond *cond); 79 | 80 | void 81 | sc_cond_destroy(sc_cond *cond); 82 | 83 | void 84 | sc_cond_wait(sc_cond *cond, sc_mutex *mutex); 85 | 86 | // return true on signaled, false on timeout 87 | bool 88 | sc_cond_timedwait(sc_cond *cond, sc_mutex *mutex, sc_tick deadline); 89 | 90 | void 91 | sc_cond_signal(sc_cond *cond); 92 | 93 | void 94 | sc_cond_broadcast(sc_cond *cond); 95 | 96 | #endif 97 | -------------------------------------------------------------------------------- /doc/device.md: -------------------------------------------------------------------------------- 1 | # Device 2 | 3 | Some command line arguments perform actions on the device itself while scrcpy is 4 | running. 5 | 6 | ## Stay awake 7 | 8 | To prevent the device from sleeping after a delay **when the device is plugged 9 | in**: 10 | 11 | ```bash 12 | scrcpy --stay-awake 13 | scrcpy -w 14 | ``` 15 | 16 | The initial state is restored when _scrcpy_ is closed. 17 | 18 | If the device is not plugged in (i.e. only connected over TCP/IP), 19 | `--stay-awake` has no effect (this is the Android behavior). 20 | 21 | 22 | ## Turn screen off 23 | 24 | It is possible to turn the device screen off while mirroring on start with a 25 | command-line option: 26 | 27 | ```bash 28 | scrcpy --turn-screen-off 29 | scrcpy -S # short version 30 | ``` 31 | 32 | Or by pressing MOD+o at any time (see 33 | [shortcuts](shortcuts.md)). 34 | 35 | To turn it back on, press MOD+Shift+o. 36 | 37 | On Android, the `POWER` button always turns the screen on. For convenience, if 38 | `POWER` is sent via _scrcpy_ (via right-click or MOD+p), 39 | it will force to turn the screen off after a small delay (on a best effort 40 | basis). The physical `POWER` button will still cause the screen to be turned on. 41 | 42 | It can also be useful to prevent the device from sleeping: 43 | 44 | ```bash 45 | scrcpy --turn-screen-off --stay-awake 46 | scrcpy -Sw # short version 47 | ``` 48 | 49 | 50 | ## Show touches 51 | 52 | For presentations, it may be useful to show physical touches (on the physical 53 | device). Android exposes this feature in _Developers options_. 54 | 55 | _Scrcpy_ provides an option to enable this feature on start and restore the 56 | initial value on exit: 57 | 58 | ```bash 59 | scrcpy --show-touches 60 | scrcpy -t # short version 61 | ``` 62 | 63 | Note that it only shows _physical_ touches (by a finger on the device). 64 | 65 | 66 | ## Power off on close 67 | 68 | To turn the device screen off when closing _scrcpy_: 69 | 70 | ```bash 71 | scrcpy --power-off-on-close 72 | ``` 73 | 74 | ## Power on on start 75 | 76 | By default, on start, the device is powered on. To prevent this behavior: 77 | 78 | ```bash 79 | scrcpy --no-power-on 80 | ``` 81 | -------------------------------------------------------------------------------- /app/src/trait/packet_source.c: -------------------------------------------------------------------------------- 1 | #include "packet_source.h" 2 | 3 | void 4 | sc_packet_source_init(struct sc_packet_source *source) { 5 | source->sink_count = 0; 6 | } 7 | 8 | void 9 | sc_packet_source_add_sink(struct sc_packet_source *source, 10 | struct sc_packet_sink *sink) { 11 | assert(source->sink_count < SC_PACKET_SOURCE_MAX_SINKS); 12 | assert(sink); 13 | assert(sink->ops); 14 | source->sinks[source->sink_count++] = sink; 15 | } 16 | 17 | static void 18 | sc_packet_source_sinks_close_firsts(struct sc_packet_source *source, 19 | unsigned count) { 20 | while (count) { 21 | struct sc_packet_sink *sink = source->sinks[--count]; 22 | sink->ops->close(sink); 23 | } 24 | } 25 | 26 | bool 27 | sc_packet_source_sinks_open(struct sc_packet_source *source, 28 | AVCodecContext *ctx) { 29 | assert(source->sink_count); 30 | for (unsigned i = 0; i < source->sink_count; ++i) { 31 | struct sc_packet_sink *sink = source->sinks[i]; 32 | if (!sink->ops->open(sink, ctx)) { 33 | sc_packet_source_sinks_close_firsts(source, i); 34 | return false; 35 | } 36 | } 37 | 38 | return true; 39 | } 40 | 41 | void 42 | sc_packet_source_sinks_close(struct sc_packet_source *source) { 43 | assert(source->sink_count); 44 | sc_packet_source_sinks_close_firsts(source, source->sink_count); 45 | } 46 | 47 | bool 48 | sc_packet_source_sinks_push(struct sc_packet_source *source, 49 | const AVPacket *packet) { 50 | assert(source->sink_count); 51 | for (unsigned i = 0; i < source->sink_count; ++i) { 52 | struct sc_packet_sink *sink = source->sinks[i]; 53 | if (!sink->ops->push(sink, packet)) { 54 | return false; 55 | } 56 | } 57 | 58 | return true; 59 | } 60 | 61 | void 62 | sc_packet_source_sinks_disable(struct sc_packet_source *source) { 63 | assert(source->sink_count); 64 | for (unsigned i = 0; i < source->sink_count; ++i) { 65 | struct sc_packet_sink *sink = source->sinks[i]; 66 | if (sink->ops->disable) { 67 | sink->ops->disable(sink); 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /app/src/util/strbuf.c: -------------------------------------------------------------------------------- 1 | #include "strbuf.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "log.h" 9 | 10 | bool 11 | sc_strbuf_init(struct sc_strbuf *buf, size_t init_cap) { 12 | buf->s = malloc(init_cap + 1); // +1 for '\0' 13 | if (!buf->s) { 14 | LOG_OOM(); 15 | return false; 16 | } 17 | 18 | buf->len = 0; 19 | buf->cap = init_cap; 20 | return true; 21 | } 22 | 23 | static bool 24 | sc_strbuf_reserve(struct sc_strbuf *buf, size_t len) { 25 | if (buf->len + len > buf->cap) { 26 | size_t new_cap = buf->cap * 3 / 2 + len; 27 | char *s = realloc(buf->s, new_cap + 1); // +1 for '\0' 28 | if (!s) { 29 | // Leave the old buf->s 30 | LOG_OOM(); 31 | return false; 32 | } 33 | buf->s = s; 34 | buf->cap = new_cap; 35 | } 36 | return true; 37 | } 38 | 39 | bool 40 | sc_strbuf_append(struct sc_strbuf *buf, const char *s, size_t len) { 41 | assert(s); 42 | assert(*s); 43 | assert(strlen(s) >= len); 44 | if (!sc_strbuf_reserve(buf, len)) { 45 | return false; 46 | } 47 | 48 | memcpy(&buf->s[buf->len], s, len); 49 | buf->len += len; 50 | buf->s[buf->len] = '\0'; 51 | 52 | return true; 53 | } 54 | 55 | bool 56 | sc_strbuf_append_char(struct sc_strbuf *buf, const char c) { 57 | if (!sc_strbuf_reserve(buf, 1)) { 58 | return false; 59 | } 60 | 61 | buf->s[buf->len] = c; 62 | buf->len ++; 63 | buf->s[buf->len] = '\0'; 64 | 65 | return true; 66 | } 67 | 68 | bool 69 | sc_strbuf_append_n(struct sc_strbuf *buf, const char c, size_t n) { 70 | if (!sc_strbuf_reserve(buf, n)) { 71 | return false; 72 | } 73 | 74 | memset(&buf->s[buf->len], c, n); 75 | buf->len += n; 76 | buf->s[buf->len] = '\0'; 77 | 78 | return true; 79 | } 80 | 81 | void 82 | sc_strbuf_shrink(struct sc_strbuf *buf) { 83 | assert(buf->len <= buf->cap); 84 | if (buf->len != buf->cap) { 85 | char *s = realloc(buf->s, buf->len + 1); // +1 for '\0' 86 | assert(s); // decreasing the size may not fail 87 | buf->s = s; 88 | buf->cap = buf->len; 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /server/src/main/java/com/genymobile/scrcpy/video/SurfaceCapture.java: -------------------------------------------------------------------------------- 1 | package com.genymobile.scrcpy.video; 2 | 3 | import com.genymobile.scrcpy.device.Size; 4 | 5 | import android.view.Surface; 6 | 7 | import java.io.IOException; 8 | import java.util.concurrent.atomic.AtomicBoolean; 9 | 10 | /** 11 | * A video source which can be rendered on a Surface for encoding. 12 | */ 13 | public abstract class SurfaceCapture { 14 | 15 | private final AtomicBoolean resetCapture = new AtomicBoolean(); 16 | 17 | /** 18 | * Request the encoding session to be restarted, for example if the capture implementation detects that the video source size has changed (on 19 | * device rotation for example). 20 | */ 21 | protected void requestReset() { 22 | resetCapture.set(true); 23 | } 24 | 25 | /** 26 | * Consume the reset request (intended to be called by the encoder). 27 | * 28 | * @return {@code true} if a reset request was pending, {@code false} otherwise. 29 | */ 30 | public boolean consumeReset() { 31 | return resetCapture.getAndSet(false); 32 | } 33 | 34 | /** 35 | * Called once before the capture starts. 36 | */ 37 | public abstract void init() throws IOException; 38 | 39 | /** 40 | * Called after the capture ends (if and only if {@link #init()} has been called). 41 | */ 42 | public abstract void release(); 43 | 44 | /** 45 | * Start the capture to the target surface. 46 | * 47 | * @param surface the surface which will be encoded 48 | */ 49 | public abstract void start(Surface surface) throws IOException; 50 | 51 | /** 52 | * Return the video size 53 | * 54 | * @return the video size 55 | */ 56 | public abstract Size getSize(); 57 | 58 | /** 59 | * Set the maximum capture size (set by the encoder if it does not support the current size). 60 | * 61 | * @param maxSize Maximum size 62 | */ 63 | public abstract boolean setMaxSize(int maxSize); 64 | 65 | /** 66 | * Indicate if the capture has been closed internally. 67 | * 68 | * @return {@code true} is the capture is closed, {@code false} otherwise. 69 | */ 70 | public boolean isClosed() { 71 | return false; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /app/src/sys/unix/file.c: -------------------------------------------------------------------------------- 1 | #include "util/file.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include "util/log.h" 11 | 12 | bool 13 | sc_file_executable_exists(const char *file) { 14 | char *path = getenv("PATH"); 15 | if (!path) 16 | return false; 17 | path = strdup(path); 18 | if (!path) 19 | return false; 20 | 21 | bool ret = false; 22 | size_t file_len = strlen(file); 23 | char *saveptr; 24 | for (char *dir = strtok_r(path, ":", &saveptr); dir; 25 | dir = strtok_r(NULL, ":", &saveptr)) { 26 | size_t dir_len = strlen(dir); 27 | char *fullpath = malloc(dir_len + file_len + 2); 28 | if (!fullpath) 29 | { 30 | LOG_OOM(); 31 | continue; 32 | } 33 | memcpy(fullpath, dir, dir_len); 34 | fullpath[dir_len] = '/'; 35 | memcpy(fullpath + dir_len + 1, file, file_len + 1); 36 | 37 | struct stat sb; 38 | bool fullpath_executable = stat(fullpath, &sb) == 0 && 39 | sb.st_mode & S_IXUSR; 40 | free(fullpath); 41 | if (fullpath_executable) { 42 | ret = true; 43 | break; 44 | } 45 | } 46 | 47 | free(path); 48 | return ret; 49 | } 50 | 51 | char * 52 | sc_file_get_executable_path(void) { 53 | // 54 | #ifdef __linux__ 55 | char buf[PATH_MAX + 1]; // +1 for the null byte 56 | ssize_t len = readlink("/proc/self/exe", buf, PATH_MAX); 57 | if (len == -1) { 58 | perror("readlink"); 59 | return NULL; 60 | } 61 | buf[len] = '\0'; 62 | return strdup(buf); 63 | #else 64 | // in practice, we only need this feature for portable builds, only used on 65 | // Windows, so we don't care implementing it for every platform 66 | // (it's useful to have a working version on Linux for debugging though) 67 | return NULL; 68 | #endif 69 | } 70 | 71 | bool 72 | sc_file_is_regular(const char *path) { 73 | struct stat path_stat; 74 | 75 | if (stat(path, &path_stat)) { 76 | perror("stat"); 77 | return false; 78 | } 79 | return S_ISREG(path_stat.st_mode); 80 | } 81 | 82 | -------------------------------------------------------------------------------- /server/src/main/java/com/genymobile/scrcpy/util/IO.java: -------------------------------------------------------------------------------- 1 | package com.genymobile.scrcpy.util; 2 | 3 | import com.genymobile.scrcpy.BuildConfig; 4 | 5 | import android.system.ErrnoException; 6 | import android.system.Os; 7 | import android.system.OsConstants; 8 | 9 | import java.io.FileDescriptor; 10 | import java.io.IOException; 11 | import java.io.InputStream; 12 | import java.nio.ByteBuffer; 13 | import java.util.Scanner; 14 | 15 | public final class IO { 16 | private IO() { 17 | // not instantiable 18 | } 19 | 20 | public static void writeFully(FileDescriptor fd, ByteBuffer from) throws IOException { 21 | // ByteBuffer position is not updated as expected by Os.write() on old Android versions, so 22 | // count the remaining bytes manually. 23 | // See . 24 | int remaining = from.remaining(); 25 | while (remaining > 0) { 26 | try { 27 | int w = Os.write(fd, from); 28 | if (BuildConfig.DEBUG && w < 0) { 29 | // w should not be negative, since an exception is thrown on error 30 | throw new AssertionError("Os.write() returned a negative value (" + w + ")"); 31 | } 32 | remaining -= w; 33 | } catch (ErrnoException e) { 34 | if (e.errno != OsConstants.EINTR) { 35 | throw new IOException(e); 36 | } 37 | } 38 | } 39 | } 40 | 41 | public static void writeFully(FileDescriptor fd, byte[] buffer, int offset, int len) throws IOException { 42 | writeFully(fd, ByteBuffer.wrap(buffer, offset, len)); 43 | } 44 | 45 | public static String toString(InputStream inputStream) { 46 | StringBuilder builder = new StringBuilder(); 47 | Scanner scanner = new Scanner(inputStream); 48 | while (scanner.hasNextLine()) { 49 | builder.append(scanner.nextLine()).append('\n'); 50 | } 51 | return builder.toString(); 52 | } 53 | 54 | public static boolean isBrokenPipe(IOException e) { 55 | Throwable cause = e.getCause(); 56 | return cause instanceof ErrnoException && ((ErrnoException) cause).errno == OsConstants.EPIPE; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /app/src/usb/usb.h: -------------------------------------------------------------------------------- 1 | #ifndef SC_USB_H 2 | #define SC_USB_H 3 | 4 | #include "common.h" 5 | 6 | #include 7 | #include 8 | 9 | #include "util/thread.h" 10 | 11 | struct sc_usb { 12 | libusb_context *context; 13 | libusb_device_handle *handle; 14 | 15 | const struct sc_usb_callbacks *cbs; 16 | void *cbs_userdata; 17 | 18 | bool has_callback_handle; 19 | libusb_hotplug_callback_handle callback_handle; 20 | 21 | bool has_libusb_event_thread; 22 | sc_thread libusb_event_thread; 23 | 24 | atomic_bool stopped; // only used if cbs != NULL 25 | atomic_flag disconnection_notified; 26 | }; 27 | 28 | struct sc_usb_callbacks { 29 | void (*on_disconnected)(struct sc_usb *usb, void *userdata); 30 | }; 31 | 32 | struct sc_usb_device { 33 | libusb_device *device; 34 | char *serial; 35 | char *manufacturer; 36 | char *product; 37 | uint16_t vid; 38 | uint16_t pid; 39 | bool selected; 40 | }; 41 | 42 | void 43 | sc_usb_device_destroy(struct sc_usb_device *usb_device); 44 | 45 | /** 46 | * Move src to dst 47 | * 48 | * After this call, the content of src is undefined, except that 49 | * sc_usb_device_destroy() can be called. 50 | * 51 | * This is useful to take a device from a list that will be destroyed, without 52 | * making unnecessary copies. 53 | */ 54 | void 55 | sc_usb_device_move(struct sc_usb_device *dst, struct sc_usb_device *src); 56 | 57 | void 58 | sc_usb_devices_destroy_all(struct sc_usb_device *usb_devices, size_t count); 59 | 60 | bool 61 | sc_usb_init(struct sc_usb *usb); 62 | 63 | void 64 | sc_usb_destroy(struct sc_usb *usb); 65 | 66 | bool 67 | sc_usb_select_device(struct sc_usb *usb, const char *serial, 68 | struct sc_usb_device *out_device); 69 | 70 | bool 71 | sc_usb_connect(struct sc_usb *usb, libusb_device *device, 72 | const struct sc_usb_callbacks *cbs, void *cbs_userdata); 73 | 74 | void 75 | sc_usb_disconnect(struct sc_usb *usb); 76 | 77 | // A client should call this function with the return value of a libusb call 78 | // to detect disconnection immediately 79 | bool 80 | sc_usb_check_disconnected(struct sc_usb *usb, int result); 81 | 82 | void 83 | sc_usb_stop(struct sc_usb *usb); 84 | 85 | void 86 | sc_usb_join(struct sc_usb *usb); 87 | 88 | #endif 89 | -------------------------------------------------------------------------------- /app/src/util/intr.c: -------------------------------------------------------------------------------- 1 | #include "intr.h" 2 | 3 | #include "util/log.h" 4 | 5 | #include 6 | 7 | bool 8 | sc_intr_init(struct sc_intr *intr) { 9 | bool ok = sc_mutex_init(&intr->mutex); 10 | if (!ok) { 11 | LOG_OOM(); 12 | return false; 13 | } 14 | 15 | intr->socket = SC_SOCKET_NONE; 16 | intr->process = SC_PROCESS_NONE; 17 | 18 | atomic_store_explicit(&intr->interrupted, false, memory_order_relaxed); 19 | 20 | return true; 21 | } 22 | 23 | bool 24 | sc_intr_set_socket(struct sc_intr *intr, sc_socket socket) { 25 | assert(intr->process == SC_PROCESS_NONE); 26 | 27 | sc_mutex_lock(&intr->mutex); 28 | bool interrupted = 29 | atomic_load_explicit(&intr->interrupted, memory_order_relaxed); 30 | if (!interrupted) { 31 | intr->socket = socket; 32 | } 33 | sc_mutex_unlock(&intr->mutex); 34 | 35 | return !interrupted; 36 | } 37 | 38 | bool 39 | sc_intr_set_process(struct sc_intr *intr, sc_pid pid) { 40 | assert(intr->socket == SC_SOCKET_NONE); 41 | 42 | sc_mutex_lock(&intr->mutex); 43 | bool interrupted = 44 | atomic_load_explicit(&intr->interrupted, memory_order_relaxed); 45 | if (!interrupted) { 46 | intr->process = pid; 47 | } 48 | sc_mutex_unlock(&intr->mutex); 49 | 50 | return !interrupted; 51 | } 52 | 53 | void 54 | sc_intr_interrupt(struct sc_intr *intr) { 55 | sc_mutex_lock(&intr->mutex); 56 | 57 | atomic_store_explicit(&intr->interrupted, true, memory_order_relaxed); 58 | 59 | // No more than one component to interrupt 60 | assert(intr->socket == SC_SOCKET_NONE || 61 | intr->process == SC_PROCESS_NONE); 62 | 63 | if (intr->socket != SC_SOCKET_NONE) { 64 | LOGD("Interrupting socket"); 65 | net_interrupt(intr->socket); 66 | intr->socket = SC_SOCKET_NONE; 67 | } 68 | if (intr->process != SC_PROCESS_NONE) { 69 | LOGD("Interrupting process"); 70 | sc_process_terminate(intr->process); 71 | intr->process = SC_PROCESS_NONE; 72 | } 73 | 74 | sc_mutex_unlock(&intr->mutex); 75 | } 76 | 77 | void 78 | sc_intr_destroy(struct sc_intr *intr) { 79 | assert(intr->socket == SC_SOCKET_NONE); 80 | assert(intr->process == SC_PROCESS_NONE); 81 | 82 | sc_mutex_destroy(&intr->mutex); 83 | } 84 | -------------------------------------------------------------------------------- /doc/otg.md: -------------------------------------------------------------------------------- 1 | # OTG 2 | 3 | By default, _scrcpy_ injects input events at the Android API level. As an 4 | alternative, it is possible to send HID events, so that scrcpy behaves as if it 5 | was a [physical keyboard] and/or a [physical mouse] connected to the Android 6 | device (see [keyboard](keyboard.md) and [mouse](mouse.md)). 7 | 8 | [physical keyboard]: keyboard.md#physical-keyboard-simulation 9 | [physical mouse]: mouse.md#physical-mouse-simulation 10 | 11 | A special mode (OTG) allows to control the device using AOA 12 | [keyboard](keyboard.md#aoa), [mouse](mouse.md#aoa) and 13 | [gamepad](gamepad.md#aoa), without using _adb_ at all (so USB debugging is not 14 | necessary). In this mode, video and audio are disabled, and `--keyboard=aoa` and 15 | `--mouse=aoa` are implicitly set. However, gamepads are disabled by default, so 16 | `--gamepad=aoa` (or `-G` in OTG mode) must be explicitly set. 17 | 18 | Therefore, it is possible to run _scrcpy_ with only physical keyboard, mouse and 19 | gamepad simulation, as if the computer keyboard, mouse and gamepads were plugged 20 | directly to the device via an OTG cable. 21 | 22 | To enable OTG mode: 23 | 24 | ```bash 25 | scrcpy --otg 26 | # Pass the serial if several USB devices are available 27 | scrcpy --otg -s 0123456789abcdef 28 | ``` 29 | 30 | It is possible to disable keyboard or mouse: 31 | 32 | ```bash 33 | scrcpy --otg --keyboard=disabled 34 | scrcpy --otg --mouse=disabled 35 | ``` 36 | 37 | and to enable gamepads: 38 | 39 | ```bash 40 | scrcpy --otg --gamepad=aoa 41 | scrcpy --otg -G # short version 42 | ``` 43 | 44 | It only works if the device is connected over USB. 45 | 46 | ## OTG issues on Windows 47 | 48 | See [FAQ](/FAQ.md#otg-issues-on-windows). 49 | 50 | 51 | ## Control only 52 | 53 | Note that the purpose of OTG is to control the device without USB debugging 54 | (adb). 55 | 56 | If you want to solely control the device without mirroring while USB debugging 57 | is enabled, then OTG mode is not necessary. 58 | 59 | Instead, disable video and audio, and select UHID (or AOA): 60 | 61 | ```bash 62 | scrcpy --no-video --no-audio --keyboard=uhid --mouse=uhid --gamepad=uhid 63 | scrcpy --no-video --no-audio -KMG # short version 64 | scrcpy --no-video --no-audio --keyboard=aoa --mouse=aoa --gamepad=aoa 65 | ``` 66 | 67 | One benefit of UHID is that it also works wirelessly. 68 | -------------------------------------------------------------------------------- /doc/linux.md: -------------------------------------------------------------------------------- 1 | # On Linux 2 | 3 | ## Install 4 | 5 | Packaging status 6 | 7 | Scrcpy is packaged in several distributions and package managers: 8 | 9 | - Debian/Ubuntu: ~~`apt install scrcpy`~~ _(obsolete version)_ 10 | - Arch Linux: `pacman -S scrcpy` 11 | - Fedora: `dnf copr enable zeno/scrcpy && dnf install scrcpy` 12 | - Gentoo: `emerge scrcpy` 13 | - Snap: `snap install scrcpy` 14 | - … (see [repology](https://repology.org/project/scrcpy/versions)) 15 | 16 | ### Latest version 17 | 18 | However, the packaged version is not always the latest release. To install the 19 | latest release from `master`, follow this simplified process. 20 | 21 | First, you need to install the required packages: 22 | 23 | ```bash 24 | # for Debian/Ubuntu 25 | sudo apt install ffmpeg libsdl2-2.0-0 adb wget \ 26 | gcc git pkg-config meson ninja-build libsdl2-dev \ 27 | libavcodec-dev libavdevice-dev libavformat-dev libavutil-dev \ 28 | libswresample-dev libusb-1.0-0 libusb-1.0-0-dev 29 | ``` 30 | 31 | Then clone the repo and execute the installation script 32 | ([source](/install_release.sh)): 33 | 34 | ```bash 35 | git clone https://github.com/Genymobile/scrcpy 36 | cd scrcpy 37 | ./install_release.sh 38 | ``` 39 | 40 | When a new release is out, update the repo and reinstall: 41 | 42 | ```bash 43 | git pull 44 | ./install_release.sh 45 | ``` 46 | 47 | To uninstall: 48 | 49 | ```bash 50 | sudo ninja -Cbuild-auto uninstall 51 | ``` 52 | 53 | _Note that this simplified process only works for released versions (it 54 | downloads a prebuilt server binary), so for example you can't use it for testing 55 | the development branch (`dev`)._ 56 | 57 | _See [build.md](build.md) to build and install the app manually._ 58 | 59 | 60 | ## Run 61 | 62 | _Make sure that your device meets the [prerequisites](/README.md#prerequisites)._ 63 | 64 | Once installed, run from a terminal: 65 | 66 | ```bash 67 | scrcpy 68 | ``` 69 | 70 | or with arguments (here to disable audio and record to `file.mkv`): 71 | 72 | ```bash 73 | scrcpy --no-audio --record=file.mkv 74 | ``` 75 | 76 | Documentation for command line arguments is available: 77 | - `man scrcpy` 78 | - `scrcpy --help` 79 | - on [github](/README.md) 80 | -------------------------------------------------------------------------------- /app/src/version.c: -------------------------------------------------------------------------------- 1 | #include "version.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #ifdef HAVE_V4L2 7 | # include 8 | #endif 9 | #ifdef HAVE_USB 10 | # include 11 | #endif 12 | 13 | void 14 | scrcpy_print_version(void) { 15 | printf("\nDependencies (compiled / linked):\n"); 16 | 17 | SDL_version sdl; 18 | SDL_GetVersion(&sdl); 19 | printf(" - SDL: %u.%u.%u / %u.%u.%u\n", 20 | SDL_MAJOR_VERSION, SDL_MINOR_VERSION, SDL_PATCHLEVEL, 21 | (unsigned) sdl.major, (unsigned) sdl.minor, (unsigned) sdl.patch); 22 | 23 | unsigned avcodec = avcodec_version(); 24 | printf(" - libavcodec: %u.%u.%u / %u.%u.%u\n", 25 | LIBAVCODEC_VERSION_MAJOR, 26 | LIBAVCODEC_VERSION_MINOR, 27 | LIBAVCODEC_VERSION_MICRO, 28 | AV_VERSION_MAJOR(avcodec), 29 | AV_VERSION_MINOR(avcodec), 30 | AV_VERSION_MICRO(avcodec)); 31 | 32 | unsigned avformat = avformat_version(); 33 | printf(" - libavformat: %u.%u.%u / %u.%u.%u\n", 34 | LIBAVFORMAT_VERSION_MAJOR, 35 | LIBAVFORMAT_VERSION_MINOR, 36 | LIBAVFORMAT_VERSION_MICRO, 37 | AV_VERSION_MAJOR(avformat), 38 | AV_VERSION_MINOR(avformat), 39 | AV_VERSION_MICRO(avformat)); 40 | 41 | unsigned avutil = avutil_version(); 42 | printf(" - libavutil: %u.%u.%u / %u.%u.%u\n", 43 | LIBAVUTIL_VERSION_MAJOR, 44 | LIBAVUTIL_VERSION_MINOR, 45 | LIBAVUTIL_VERSION_MICRO, 46 | AV_VERSION_MAJOR(avutil), 47 | AV_VERSION_MINOR(avutil), 48 | AV_VERSION_MICRO(avutil)); 49 | 50 | #ifdef HAVE_V4L2 51 | unsigned avdevice = avdevice_version(); 52 | printf(" - libavdevice: %u.%u.%u / %u.%u.%u\n", 53 | LIBAVDEVICE_VERSION_MAJOR, 54 | LIBAVDEVICE_VERSION_MINOR, 55 | LIBAVDEVICE_VERSION_MICRO, 56 | AV_VERSION_MAJOR(avdevice), 57 | AV_VERSION_MINOR(avdevice), 58 | AV_VERSION_MICRO(avdevice)); 59 | #endif 60 | 61 | #ifdef HAVE_USB 62 | const struct libusb_version *usb = libusb_get_version(); 63 | // The compiled version may not be known 64 | printf(" - libusb: - / %u.%u.%u\n", 65 | (unsigned) usb->major, (unsigned) usb->minor, (unsigned) usb->micro); 66 | #endif 67 | } 68 | -------------------------------------------------------------------------------- /doc/recording.md: -------------------------------------------------------------------------------- 1 | # Recording 2 | 3 | To record video and audio streams while mirroring: 4 | 5 | ```bash 6 | scrcpy --record=file.mp4 7 | scrcpy -r file.mkv 8 | ``` 9 | 10 | To record only the video: 11 | 12 | ```bash 13 | scrcpy --no-audio --record=file.mp4 14 | ``` 15 | 16 | To record only the audio: 17 | 18 | ```bash 19 | scrcpy --no-video --record=file.opus 20 | scrcpy --no-video --audio-codec=aac --record=file.aac 21 | scrcpy --no-video --audio-codec=flac --record=file.flac 22 | scrcpy --no-video --audio-codec=raw --record=file.wav 23 | # .m4a/.mp4 and .mka/.mkv are also supported for opus, aac and flac 24 | ``` 25 | 26 | Timestamps are captured on the device, so [packet delay variation] does not 27 | impact the recorded file, which is always clean (only if you use `--record` of 28 | course, not if you capture your scrcpy window and audio output on the computer). 29 | 30 | [packet delay variation]: https://en.wikipedia.org/wiki/Packet_delay_variation 31 | 32 | 33 | ## Format 34 | 35 | The video and audio streams are encoded on the device, but are muxed on the 36 | client side. Several formats (containers) are supported: 37 | - MP4 (`.mp4`, `.m4a`, `.aac`) 38 | - Matroska (`.mkv`, `.mka`) 39 | - OPUS (`.opus`) 40 | - FLAC (`.flac`) 41 | - WAV (`.wav`) 42 | 43 | The container is automatically selected based on the filename. 44 | 45 | It is also possible to explicitly select a container (in that case the filename 46 | needs not end with a known extension): 47 | 48 | ``` 49 | scrcpy --record=file --record-format=mkv 50 | ``` 51 | 52 | 53 | ## Rotation 54 | 55 | The video can be recorded rotated. See [video 56 | orientation](video.md#orientation). 57 | 58 | 59 | ## No playback 60 | 61 | To disable playback and control while recording: 62 | 63 | ```bash 64 | scrcpy --no-playback --no-control --record=file.mp4 65 | ``` 66 | 67 | It is also possible to disable video and audio playback separately: 68 | 69 | ```bash 70 | # Record both video and audio, but only play video 71 | scrcpy --record=file.mkv --no-audio-playback 72 | ``` 73 | 74 | To also disable the window: 75 | 76 | ```bash 77 | scrcpy --no-playback --no-window --record=file.mp4 78 | # interrupt recording with Ctrl+C 79 | ``` 80 | 81 | ## Time limit 82 | 83 | To limit the recording time: 84 | 85 | ```bash 86 | scrcpy --record=file.mkv --time-limit=20 # in seconds 87 | ``` 88 | 89 | The `--time-limit` option is not limited to recording, it also impacts simple 90 | mirroring: 91 | 92 | ``` 93 | scrcpy --time-limit=20 94 | ``` 95 | -------------------------------------------------------------------------------- /app/src/util/binary.h: -------------------------------------------------------------------------------- 1 | #ifndef SC_BINARY_H 2 | #define SC_BINARY_H 3 | 4 | #include "common.h" 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | static inline void 11 | sc_write16be(uint8_t *buf, uint16_t value) { 12 | buf[0] = value >> 8; 13 | buf[1] = value; 14 | } 15 | 16 | static inline void 17 | sc_write16le(uint8_t *buf, uint16_t value) { 18 | buf[0] = value; 19 | buf[1] = value >> 8; 20 | } 21 | 22 | static inline void 23 | sc_write32be(uint8_t *buf, uint32_t value) { 24 | buf[0] = value >> 24; 25 | buf[1] = value >> 16; 26 | buf[2] = value >> 8; 27 | buf[3] = value; 28 | } 29 | 30 | static inline void 31 | sc_write32le(uint8_t *buf, uint32_t value) { 32 | buf[0] = value; 33 | buf[1] = value >> 8; 34 | buf[2] = value >> 16; 35 | buf[3] = value >> 24; 36 | } 37 | 38 | static inline void 39 | sc_write64be(uint8_t *buf, uint64_t value) { 40 | sc_write32be(buf, value >> 32); 41 | sc_write32be(&buf[4], (uint32_t) value); 42 | } 43 | 44 | static inline void 45 | sc_write64le(uint8_t *buf, uint64_t value) { 46 | sc_write32le(buf, (uint32_t) value); 47 | sc_write32le(&buf[4], value >> 32); 48 | } 49 | 50 | static inline uint16_t 51 | sc_read16be(const uint8_t *buf) { 52 | return (buf[0] << 8) | buf[1]; 53 | } 54 | 55 | static inline uint32_t 56 | sc_read32be(const uint8_t *buf) { 57 | return ((uint32_t) buf[0] << 24) | (buf[1] << 16) | (buf[2] << 8) | buf[3]; 58 | } 59 | 60 | static inline uint64_t 61 | sc_read64be(const uint8_t *buf) { 62 | uint32_t msb = sc_read32be(buf); 63 | uint32_t lsb = sc_read32be(&buf[4]); 64 | return ((uint64_t) msb << 32) | lsb; 65 | } 66 | 67 | /** 68 | * Convert a float between 0 and 1 to an unsigned 16-bit fixed-point value 69 | */ 70 | static inline uint16_t 71 | sc_float_to_u16fp(float f) { 72 | assert(f >= 0.0f && f <= 1.0f); 73 | uint32_t u = f * 0x1p16f; // 2^16 74 | if (u >= 0xffff) { 75 | assert(u == 0x10000); // for f == 1.0f 76 | u = 0xffff; 77 | } 78 | return (uint16_t) u; 79 | } 80 | 81 | /** 82 | * Convert a float between -1 and 1 to a signed 16-bit fixed-point value 83 | */ 84 | static inline int16_t 85 | sc_float_to_i16fp(float f) { 86 | assert(f >= -1.0f && f <= 1.0f); 87 | int32_t i = f * 0x1p15f; // 2^15 88 | assert(i >= -0x8000); 89 | if (i >= 0x7fff) { 90 | assert(i == 0x8000); // for f == 1.0f 91 | i = 0x7fff; 92 | } 93 | return (int16_t) i; 94 | } 95 | 96 | #endif 97 | -------------------------------------------------------------------------------- /app/src/recorder.h: -------------------------------------------------------------------------------- 1 | #ifndef SC_RECORDER_H 2 | #define SC_RECORDER_H 3 | 4 | #include "common.h" 5 | 6 | #include 7 | #include 8 | 9 | #include "coords.h" 10 | #include "options.h" 11 | #include "trait/packet_sink.h" 12 | #include "util/thread.h" 13 | #include "util/vecdeque.h" 14 | 15 | struct sc_recorder_queue SC_VECDEQUE(AVPacket *); 16 | 17 | struct sc_recorder_stream { 18 | int index; 19 | int64_t last_pts; 20 | }; 21 | 22 | struct sc_recorder { 23 | struct sc_packet_sink video_packet_sink; 24 | struct sc_packet_sink audio_packet_sink; 25 | 26 | /* The audio flag is unprotected: 27 | * - it is initialized from sc_recorder_init() from the main thread; 28 | * - it may be reset once from the recorder thread if the audio is 29 | * disabled dynamically. 30 | * 31 | * Therefore, once the recorder thread is started, only the recorder thread 32 | * may access it without data races. 33 | */ 34 | bool audio; 35 | bool video; 36 | 37 | enum sc_orientation orientation; 38 | 39 | char *filename; 40 | enum sc_record_format format; 41 | AVFormatContext *ctx; 42 | 43 | sc_thread thread; 44 | sc_mutex mutex; 45 | sc_cond cond; 46 | // set on sc_recorder_stop(), packet_sink close or recording failure 47 | bool stopped; 48 | struct sc_recorder_queue video_queue; 49 | struct sc_recorder_queue audio_queue; 50 | 51 | // wake up the recorder thread once the video or audio codec is known 52 | bool video_init; 53 | bool audio_init; 54 | 55 | bool audio_expects_config_packet; 56 | 57 | struct sc_recorder_stream video_stream; 58 | struct sc_recorder_stream audio_stream; 59 | 60 | const struct sc_recorder_callbacks *cbs; 61 | void *cbs_userdata; 62 | }; 63 | 64 | struct sc_recorder_callbacks { 65 | void (*on_ended)(struct sc_recorder *recorder, bool success, 66 | void *userdata); 67 | }; 68 | 69 | bool 70 | sc_recorder_init(struct sc_recorder *recorder, const char *filename, 71 | enum sc_record_format format, bool video, bool audio, 72 | enum sc_orientation orientation, 73 | const struct sc_recorder_callbacks *cbs, void *cbs_userdata); 74 | 75 | bool 76 | sc_recorder_start(struct sc_recorder *recorder); 77 | 78 | void 79 | sc_recorder_stop(struct sc_recorder *recorder); 80 | 81 | void 82 | sc_recorder_join(struct sc_recorder *recorder); 83 | 84 | void 85 | sc_recorder_destroy(struct sc_recorder *recorder); 86 | 87 | #endif 88 | -------------------------------------------------------------------------------- /app/src/usb/aoa_hid.h: -------------------------------------------------------------------------------- 1 | #ifndef SC_AOA_HID_H 2 | #define SC_AOA_HID_H 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | #include "hid/hid_event.h" 10 | #include "usb.h" 11 | #include "util/acksync.h" 12 | #include "util/thread.h" 13 | #include "util/tick.h" 14 | #include "util/vecdeque.h" 15 | 16 | enum sc_aoa_event_type { 17 | SC_AOA_EVENT_TYPE_OPEN, 18 | SC_AOA_EVENT_TYPE_INPUT, 19 | SC_AOA_EVENT_TYPE_CLOSE, 20 | }; 21 | 22 | struct sc_aoa_event { 23 | enum sc_aoa_event_type type; 24 | union { 25 | struct { 26 | struct sc_hid_open hid; 27 | bool exit_on_error; 28 | } open; 29 | struct { 30 | struct sc_hid_close hid; 31 | } close; 32 | struct { 33 | struct sc_hid_input hid; 34 | uint64_t ack_to_wait; 35 | } input; 36 | }; 37 | }; 38 | 39 | struct sc_aoa_event_queue SC_VECDEQUE(struct sc_aoa_event); 40 | 41 | struct sc_aoa { 42 | struct sc_usb *usb; 43 | sc_thread thread; 44 | sc_mutex mutex; 45 | sc_cond event_cond; 46 | bool stopped; 47 | struct sc_aoa_event_queue queue; 48 | 49 | struct sc_acksync *acksync; 50 | }; 51 | 52 | bool 53 | sc_aoa_init(struct sc_aoa *aoa, struct sc_usb *usb, struct sc_acksync *acksync); 54 | 55 | void 56 | sc_aoa_destroy(struct sc_aoa *aoa); 57 | 58 | bool 59 | sc_aoa_start(struct sc_aoa *aoa); 60 | 61 | void 62 | sc_aoa_stop(struct sc_aoa *aoa); 63 | 64 | void 65 | sc_aoa_join(struct sc_aoa *aoa); 66 | 67 | //bool 68 | //sc_aoa_setup_hid(struct sc_aoa *aoa, uint16_t accessory_id, 69 | // const uint8_t *report_desc, uint16_t report_desc_size); 70 | // 71 | //bool 72 | //sc_aoa_unregister_hid(struct sc_aoa *aoa, uint16_t accessory_id); 73 | 74 | // report_desc must be a pointer to static memory, accessed at any time from 75 | // another thread 76 | bool 77 | sc_aoa_push_open(struct sc_aoa *aoa, const struct sc_hid_open *hid_open, 78 | bool exit_on_open_error); 79 | 80 | bool 81 | sc_aoa_push_close(struct sc_aoa *aoa, const struct sc_hid_close *hid_close); 82 | 83 | bool 84 | sc_aoa_push_input_with_ack_to_wait(struct sc_aoa *aoa, 85 | const struct sc_hid_input *hid_input, 86 | uint64_t ack_to_wait); 87 | 88 | static inline bool 89 | sc_aoa_push_input(struct sc_aoa *aoa, const struct sc_hid_input *hid_input) { 90 | return sc_aoa_push_input_with_ack_to_wait(aoa, hid_input, 91 | SC_SEQUENCE_INVALID); 92 | } 93 | 94 | #endif 95 | -------------------------------------------------------------------------------- /app/deps/ffmpeg.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -ex 3 | DEPS_DIR=$(dirname ${BASH_SOURCE[0]}) 4 | cd "$DEPS_DIR" 5 | . common 6 | 7 | VERSION=7.0.2 8 | FILENAME=ffmpeg-$VERSION.tar.xz 9 | PROJECT_DIR=ffmpeg-$VERSION 10 | SHA256SUM=8646515b638a3ad303e23af6a3587734447cb8fc0a0c064ecdb8e95c4fd8b389 11 | 12 | cd "$SOURCES_DIR" 13 | 14 | if [[ -d "$PROJECT_DIR" ]] 15 | then 16 | echo "$PWD/$PROJECT_DIR" found 17 | else 18 | get_file "https://ffmpeg.org/releases/$FILENAME" "$FILENAME" "$SHA256SUM" 19 | tar xf "$FILENAME" # First level directory is "$PROJECT_DIR" 20 | fi 21 | 22 | mkdir -p "$BUILD_DIR/$PROJECT_DIR" 23 | cd "$BUILD_DIR/$PROJECT_DIR" 24 | 25 | if [[ "$HOST" = win32 ]] 26 | then 27 | ARCH=x86 28 | elif [[ "$HOST" = win64 ]] 29 | then 30 | ARCH=x86_64 31 | else 32 | echo "Unsupported host: $HOST" >&2 33 | exit 1 34 | fi 35 | 36 | # -static-libgcc to avoid missing libgcc_s_dw2-1.dll 37 | # -static to avoid dynamic dependency to zlib 38 | export CFLAGS='-static-libgcc -static' 39 | export CXXFLAGS="$CFLAGS" 40 | export LDFLAGS='-static-libgcc -static' 41 | 42 | if [[ -d "$HOST" ]] 43 | then 44 | echo "'$PWD/$HOST' already exists, not reconfigured" 45 | cd "$HOST" 46 | else 47 | mkdir "$HOST" 48 | cd "$HOST" 49 | 50 | "$SOURCES_DIR/$PROJECT_DIR"/configure \ 51 | --prefix="$INSTALL_DIR/$HOST" \ 52 | --enable-cross-compile \ 53 | --target-os=mingw32 \ 54 | --arch="$ARCH" \ 55 | --cross-prefix="${HOST_TRIPLET}-" \ 56 | --cc="${HOST_TRIPLET}-gcc" \ 57 | --extra-cflags="-O2 -fPIC" \ 58 | --enable-shared \ 59 | --disable-static \ 60 | --disable-programs \ 61 | --disable-doc \ 62 | --disable-swscale \ 63 | --disable-postproc \ 64 | --disable-avfilter \ 65 | --disable-avdevice \ 66 | --disable-network \ 67 | --disable-everything \ 68 | --enable-swresample \ 69 | --enable-decoder=h264 \ 70 | --enable-decoder=hevc \ 71 | --enable-decoder=av1 \ 72 | --enable-decoder=pcm_s16le \ 73 | --enable-decoder=opus \ 74 | --enable-decoder=aac \ 75 | --enable-decoder=flac \ 76 | --enable-decoder=png \ 77 | --enable-protocol=file \ 78 | --enable-demuxer=image2 \ 79 | --enable-parser=png \ 80 | --enable-zlib \ 81 | --enable-muxer=matroska \ 82 | --enable-muxer=mp4 \ 83 | --enable-muxer=opus \ 84 | --enable-muxer=flac \ 85 | --enable-muxer=wav \ 86 | --disable-vulkan 87 | fi 88 | 89 | make -j 90 | make install 91 | -------------------------------------------------------------------------------- /server/src/test/java/com/genymobile/scrcpy/util/BinaryTest.java: -------------------------------------------------------------------------------- 1 | package com.genymobile.scrcpy.util; 2 | 3 | import org.junit.Assert; 4 | import org.junit.Test; 5 | 6 | public class BinaryTest { 7 | 8 | @Test 9 | public void testU16FixedPointToFloat() { 10 | final float delta = 0.0f; // on these values, there MUST be no rounding error 11 | Assert.assertEquals(0.0f, Binary.u16FixedPointToFloat((short) 0), delta); 12 | Assert.assertEquals(0.03125f, Binary.u16FixedPointToFloat((short) 0x800), delta); 13 | Assert.assertEquals(0.0625f, Binary.u16FixedPointToFloat((short) 0x1000), delta); 14 | Assert.assertEquals(0.125f, Binary.u16FixedPointToFloat((short) 0x2000), delta); 15 | Assert.assertEquals(0.25f, Binary.u16FixedPointToFloat((short) 0x4000), delta); 16 | Assert.assertEquals(0.5f, Binary.u16FixedPointToFloat((short) 0x8000), delta); 17 | Assert.assertEquals(0.75f, Binary.u16FixedPointToFloat((short) 0xc000), delta); 18 | Assert.assertEquals(1.0f, Binary.u16FixedPointToFloat((short) 0xffff), delta); 19 | } 20 | 21 | @Test 22 | public void testI16FixedPointToFloat() { 23 | final float delta = 0.0f; // on these values, there MUST be no rounding error 24 | 25 | Assert.assertEquals(0.0f, Binary.i16FixedPointToFloat((short) 0), delta); 26 | Assert.assertEquals(0.03125f, Binary.i16FixedPointToFloat((short) 0x400), delta); 27 | Assert.assertEquals(0.0625f, Binary.i16FixedPointToFloat((short) 0x800), delta); 28 | Assert.assertEquals(0.125f, Binary.i16FixedPointToFloat((short) 0x1000), delta); 29 | Assert.assertEquals(0.25f, Binary.i16FixedPointToFloat((short) 0x2000), delta); 30 | Assert.assertEquals(0.5f, Binary.i16FixedPointToFloat((short) 0x4000), delta); 31 | Assert.assertEquals(0.75f, Binary.i16FixedPointToFloat((short) 0x6000), delta); 32 | Assert.assertEquals(1.0f, Binary.i16FixedPointToFloat((short) 0x7fff), delta); 33 | 34 | Assert.assertEquals(-0.03125f, Binary.i16FixedPointToFloat((short) -0x400), delta); 35 | Assert.assertEquals(-0.0625f, Binary.i16FixedPointToFloat((short) -0x800), delta); 36 | Assert.assertEquals(-0.125f, Binary.i16FixedPointToFloat((short) -0x1000), delta); 37 | Assert.assertEquals(-0.25f, Binary.i16FixedPointToFloat((short) -0x2000), delta); 38 | Assert.assertEquals(-0.5f, Binary.i16FixedPointToFloat((short) -0x4000), delta); 39 | Assert.assertEquals(-0.75f, Binary.i16FixedPointToFloat((short) -0x6000), delta); 40 | Assert.assertEquals(-1.0f, Binary.i16FixedPointToFloat((short) -0x8000), delta); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /app/src/frame_buffer.c: -------------------------------------------------------------------------------- 1 | #include "frame_buffer.h" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "util/log.h" 8 | 9 | bool 10 | sc_frame_buffer_init(struct sc_frame_buffer *fb) { 11 | fb->pending_frame = av_frame_alloc(); 12 | if (!fb->pending_frame) { 13 | LOG_OOM(); 14 | return false; 15 | } 16 | 17 | fb->tmp_frame = av_frame_alloc(); 18 | if (!fb->tmp_frame) { 19 | LOG_OOM(); 20 | av_frame_free(&fb->pending_frame); 21 | return false; 22 | } 23 | 24 | bool ok = sc_mutex_init(&fb->mutex); 25 | if (!ok) { 26 | av_frame_free(&fb->pending_frame); 27 | av_frame_free(&fb->tmp_frame); 28 | return false; 29 | } 30 | 31 | // there is initially no frame, so consider it has already been consumed 32 | fb->pending_frame_consumed = true; 33 | 34 | return true; 35 | } 36 | 37 | void 38 | sc_frame_buffer_destroy(struct sc_frame_buffer *fb) { 39 | sc_mutex_destroy(&fb->mutex); 40 | av_frame_free(&fb->pending_frame); 41 | av_frame_free(&fb->tmp_frame); 42 | } 43 | 44 | static inline void 45 | swap_frames(AVFrame **lhs, AVFrame **rhs) { 46 | AVFrame *tmp = *lhs; 47 | *lhs = *rhs; 48 | *rhs = tmp; 49 | } 50 | 51 | bool 52 | sc_frame_buffer_push(struct sc_frame_buffer *fb, const AVFrame *frame, 53 | bool *previous_frame_skipped) { 54 | // Use a temporary frame to preserve pending_frame in case of error. 55 | // tmp_frame is an empty frame, no need to call av_frame_unref() beforehand. 56 | int r = av_frame_ref(fb->tmp_frame, frame); 57 | if (r) { 58 | LOGE("Could not ref frame: %d", r); 59 | return false; 60 | } 61 | 62 | sc_mutex_lock(&fb->mutex); 63 | 64 | // Now that av_frame_ref() succeeded, we can replace the previous 65 | // pending_frame 66 | swap_frames(&fb->pending_frame, &fb->tmp_frame); 67 | av_frame_unref(fb->tmp_frame); 68 | 69 | if (previous_frame_skipped) { 70 | *previous_frame_skipped = !fb->pending_frame_consumed; 71 | } 72 | fb->pending_frame_consumed = false; 73 | 74 | sc_mutex_unlock(&fb->mutex); 75 | 76 | return true; 77 | } 78 | 79 | void 80 | sc_frame_buffer_consume(struct sc_frame_buffer *fb, AVFrame *dst) { 81 | sc_mutex_lock(&fb->mutex); 82 | assert(!fb->pending_frame_consumed); 83 | fb->pending_frame_consumed = true; 84 | 85 | av_frame_move_ref(dst, fb->pending_frame); 86 | // av_frame_move_ref() resets its source frame, so no need to call 87 | // av_frame_unref() 88 | 89 | sc_mutex_unlock(&fb->mutex); 90 | } 91 | -------------------------------------------------------------------------------- /server/src/main/java/com/genymobile/scrcpy/audio/AudioRecordReader.java: -------------------------------------------------------------------------------- 1 | package com.genymobile.scrcpy.audio; 2 | 3 | import com.genymobile.scrcpy.util.Ln; 4 | 5 | import android.annotation.TargetApi; 6 | import android.media.AudioRecord; 7 | import android.media.AudioTimestamp; 8 | import android.media.MediaCodec; 9 | import android.os.Build; 10 | 11 | import java.nio.ByteBuffer; 12 | 13 | public class AudioRecordReader { 14 | 15 | private static final long ONE_SAMPLE_US = 16 | (1000000 + AudioConfig.SAMPLE_RATE - 1) / AudioConfig.SAMPLE_RATE; // 1 sample in microseconds (used for fixing PTS) 17 | 18 | private final AudioRecord recorder; 19 | 20 | private final AudioTimestamp timestamp = new AudioTimestamp(); 21 | private long previousRecorderTimestamp = -1; 22 | private long previousPts = 0; 23 | private long nextPts = 0; 24 | 25 | public AudioRecordReader(AudioRecord recorder) { 26 | this.recorder = recorder; 27 | } 28 | 29 | @TargetApi(Build.VERSION_CODES.N) 30 | public int read(ByteBuffer outDirectBuffer, MediaCodec.BufferInfo outBufferInfo) { 31 | int r = recorder.read(outDirectBuffer, AudioConfig.MAX_READ_SIZE); 32 | if (r <= 0) { 33 | return r; 34 | } 35 | 36 | long pts; 37 | 38 | int ret = recorder.getTimestamp(timestamp, AudioTimestamp.TIMEBASE_MONOTONIC); 39 | if (ret == AudioRecord.SUCCESS && timestamp.nanoTime != previousRecorderTimestamp) { 40 | pts = timestamp.nanoTime / 1000; 41 | previousRecorderTimestamp = timestamp.nanoTime; 42 | } else { 43 | if (nextPts == 0) { 44 | Ln.w("Could not get initial audio timestamp"); 45 | nextPts = System.nanoTime() / 1000; 46 | } 47 | // compute from previous timestamp and packet size 48 | pts = nextPts; 49 | } 50 | 51 | long durationUs = r * 1000000L / (AudioConfig.CHANNELS * AudioConfig.BYTES_PER_SAMPLE * AudioConfig.SAMPLE_RATE); 52 | nextPts = pts + durationUs; 53 | 54 | if (previousPts != 0 && pts < previousPts + ONE_SAMPLE_US) { 55 | // Audio PTS may come from two sources: 56 | // - recorder.getTimestamp() if the call works; 57 | // - an estimation from the previous PTS and the packet size as a fallback. 58 | // 59 | // Therefore, the property that PTS are monotonically increasing is no guaranteed in corner cases, so enforce it. 60 | pts = previousPts + ONE_SAMPLE_US; 61 | } 62 | previousPts = pts; 63 | 64 | outBufferInfo.set(0, r, pts, 0); 65 | return r; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /app/src/util/net_intr.c: -------------------------------------------------------------------------------- 1 | #include "net_intr.h" 2 | 3 | bool 4 | net_connect_intr(struct sc_intr *intr, sc_socket socket, uint32_t addr, 5 | uint16_t port) { 6 | if (!sc_intr_set_socket(intr, socket)) { 7 | // Already interrupted 8 | return false; 9 | } 10 | 11 | bool ret = net_connect(socket, addr, port); 12 | 13 | sc_intr_set_socket(intr, SC_SOCKET_NONE); 14 | return ret; 15 | } 16 | 17 | bool 18 | net_listen_intr(struct sc_intr *intr, sc_socket server_socket, uint32_t addr, 19 | uint16_t port, int backlog) { 20 | if (!sc_intr_set_socket(intr, server_socket)) { 21 | // Already interrupted 22 | return false; 23 | } 24 | 25 | bool ret = net_listen(server_socket, addr, port, backlog); 26 | 27 | sc_intr_set_socket(intr, SC_SOCKET_NONE); 28 | return ret; 29 | } 30 | 31 | sc_socket 32 | net_accept_intr(struct sc_intr *intr, sc_socket server_socket) { 33 | if (!sc_intr_set_socket(intr, server_socket)) { 34 | // Already interrupted 35 | return SC_SOCKET_NONE; 36 | } 37 | 38 | sc_socket socket = net_accept(server_socket); 39 | 40 | sc_intr_set_socket(intr, SC_SOCKET_NONE); 41 | return socket; 42 | } 43 | 44 | ssize_t 45 | net_recv_intr(struct sc_intr *intr, sc_socket socket, void *buf, size_t len) { 46 | if (!sc_intr_set_socket(intr, socket)) { 47 | // Already interrupted 48 | return -1; 49 | } 50 | 51 | ssize_t r = net_recv(socket, buf, len); 52 | 53 | sc_intr_set_socket(intr, SC_SOCKET_NONE); 54 | return r; 55 | } 56 | 57 | ssize_t 58 | net_recv_all_intr(struct sc_intr *intr, sc_socket socket, void *buf, 59 | size_t len) { 60 | if (!sc_intr_set_socket(intr, socket)) { 61 | // Already interrupted 62 | return -1; 63 | } 64 | 65 | ssize_t r = net_recv_all(socket, buf, len); 66 | 67 | sc_intr_set_socket(intr, SC_SOCKET_NONE); 68 | return r; 69 | } 70 | 71 | ssize_t 72 | net_send_intr(struct sc_intr *intr, sc_socket socket, const void *buf, 73 | size_t len) { 74 | if (!sc_intr_set_socket(intr, socket)) { 75 | // Already interrupted 76 | return -1; 77 | } 78 | 79 | ssize_t w = net_send(socket, buf, len); 80 | 81 | sc_intr_set_socket(intr, SC_SOCKET_NONE); 82 | return w; 83 | } 84 | 85 | ssize_t 86 | net_send_all_intr(struct sc_intr *intr, sc_socket socket, const void *buf, 87 | size_t len) { 88 | if (!sc_intr_set_socket(intr, socket)) { 89 | // Already interrupted 90 | return -1; 91 | } 92 | 93 | ssize_t w = net_send_all(socket, buf, len); 94 | 95 | sc_intr_set_socket(intr, SC_SOCKET_NONE); 96 | return w; 97 | } 98 | -------------------------------------------------------------------------------- /server/src/test/java/com/genymobile/scrcpy/control/DeviceMessageWriterTest.java: -------------------------------------------------------------------------------- 1 | package com.genymobile.scrcpy.control; 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 | String text = "aéûoç"; 16 | byte[] data = text.getBytes(StandardCharsets.UTF_8); 17 | ByteArrayOutputStream bos = new ByteArrayOutputStream(); 18 | DataOutputStream dos = new DataOutputStream(bos); 19 | dos.writeByte(DeviceMessage.TYPE_CLIPBOARD); 20 | dos.writeInt(data.length); 21 | dos.write(data); 22 | byte[] expected = bos.toByteArray(); 23 | 24 | bos = new ByteArrayOutputStream(); 25 | DeviceMessageWriter writer = new DeviceMessageWriter(bos); 26 | 27 | DeviceMessage msg = DeviceMessage.createClipboard(text); 28 | writer.write(msg); 29 | 30 | byte[] actual = bos.toByteArray(); 31 | 32 | Assert.assertArrayEquals(expected, actual); 33 | } 34 | 35 | @Test 36 | public void testSerializeAckSetClipboard() throws IOException { 37 | ByteArrayOutputStream bos = new ByteArrayOutputStream(); 38 | DataOutputStream dos = new DataOutputStream(bos); 39 | dos.writeByte(DeviceMessage.TYPE_ACK_CLIPBOARD); 40 | dos.writeLong(0x0102030405060708L); 41 | byte[] expected = bos.toByteArray(); 42 | 43 | bos = new ByteArrayOutputStream(); 44 | DeviceMessageWriter writer = new DeviceMessageWriter(bos); 45 | 46 | DeviceMessage msg = DeviceMessage.createAckClipboard(0x0102030405060708L); 47 | writer.write(msg); 48 | 49 | byte[] actual = bos.toByteArray(); 50 | 51 | Assert.assertArrayEquals(expected, actual); 52 | } 53 | 54 | @Test 55 | public void testSerializeUhidOutput() throws IOException { 56 | ByteArrayOutputStream bos = new ByteArrayOutputStream(); 57 | DataOutputStream dos = new DataOutputStream(bos); 58 | dos.writeByte(DeviceMessage.TYPE_UHID_OUTPUT); 59 | dos.writeShort(42); // id 60 | byte[] data = {1, 2, 3, 4, 5}; 61 | dos.writeShort(data.length); 62 | dos.write(data); 63 | byte[] expected = bos.toByteArray(); 64 | 65 | bos = new ByteArrayOutputStream(); 66 | DeviceMessageWriter writer = new DeviceMessageWriter(bos); 67 | 68 | DeviceMessage msg = DeviceMessage.createUhidOutput(42, data); 69 | writer.write(msg); 70 | 71 | byte[] actual = bos.toByteArray(); 72 | 73 | Assert.assertArrayEquals(expected, actual); 74 | } 75 | } 76 | --------------------------------------------------------------------------------