├── 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 |
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 |
--------------------------------------------------------------------------------