Compare commits

...

17 Commits

Author SHA1 Message Date
ad6b8847d4 Add option to disable window decoration
Add --window-borderless parameter.

Signed-off-by: Romain Vimont <rom@rom1v.com>
2019-11-03 18:18:41 +01:00
fba89e6f73 Add option to specify the initial window size
Add --window-width and --window-height parameters.

If only one is provided, the other is computed so that the aspect ratio
is preserved.
2019-11-03 18:18:41 +01:00
4bf2b80c75 Update manpage for --window-{x,y} options 2019-11-03 18:18:41 +01:00
f983ec67fa Add option to specify the initial window position
Add --window-x and --window-y parameters.

Signed-off-by: Romain Vimont <rom@rom1v.com>
2019-11-03 18:18:39 +01:00
120f08ee96 Fix manpage option parameter format
The parameter for --window-title was not underlined the same way as
others.
2019-11-03 16:51:47 +01:00
0415672a75 Merge branch 'master' into dev 2019-11-03 15:56:10 +01:00
3ea4742321 Call ninja without changing directory
In build instructions, use:

    ninja -Cx ...

instead of:

    cd x
    ninja ...
2019-10-31 21:05:04 +01:00
95fd64b5de Add scrcpy version in recorded video metadata
It might help to understand problems in recorded videos.
2019-10-31 20:57:57 +01:00
ab205084dc Merge pull request #896 from yangfl/upstream
Add manpage for scrcpy
2019-10-31 13:44:01 +01:00
4696878a97 Add manpage for scrcpy 2019-10-31 18:18:19 +08:00
3da95b52bd Rename scrcpy-server.jar to scrcpy-server
The server name ending with .jar has several drawbacks:
 - meson requires the jar executable to attempt to modify it:
     <https://github.com/Genymobile/scrcpy/issues/404#issuecomment-456065923>
     <https://github.com/mesonbuild/meson/issues/4844>
 - meson warns during "ninja install"
     <https://github.com/Genymobile/scrcpy/issues/458>
 - some users try to execute it on the computer as a java executable

Removing the extension solves all these problems.
2019-10-31 10:54:29 +01:00
c72f677435 Merge branch 'master' into dev 2019-10-30 23:29:44 +01:00
1380f6e00f Fix help for --record-format
Record format requires a parameter.
2019-10-30 22:51:40 +01:00
d841718956 Add a script to build the server without gradle
Gradle versions may sometimes cause problems. This script offers an
alternative.
2019-10-30 21:47:20 +01:00
17d53be3ef Fix mouse events conversion
The conversion from SDL mouse state to Android mouse state used wrong
constants as mask.

Fixes <https://github.com/Genymobile/scrcpy/issues/635>
2019-10-25 11:09:06 +02:00
f9938dbf88 Inject button state for touch/mouse events
The buttons state was forwarded, but ignored by the server.
2019-10-25 11:04:04 +02:00
f6c8460ebb Rename window size functions for clarity
Now, get_window_size() returns the current window size (fullscreen or
not), while get_windowed_window_size() always returned the windowed size
(the size when fullscreen is disabled).
2019-10-20 16:08:16 +02:00
22 changed files with 519 additions and 53 deletions

View File

@ -195,8 +195,7 @@ Then, build:
```bash ```bash
meson x --buildtype release --strip -Db_lto=true meson x --buildtype release --strip -Db_lto=true
cd x ninja -Cx
ninja
``` ```
_Note: `ninja` [must][ninja-user] be run as a non-root user (only `ninja _Note: `ninja` [must][ninja-user] be run as a non-root user (only `ninja
@ -219,13 +218,13 @@ To run without installing:
After a successful build, you can install _scrcpy_ on the system: After a successful build, you can install _scrcpy_ on the system:
```bash ```bash
sudo ninja install # without sudo on Windows sudo ninja -Cx install # without sudo on Windows
``` ```
This installs two files: This installs two files:
- `/usr/local/bin/scrcpy` - `/usr/local/bin/scrcpy`
- `/usr/local/share/scrcpy/scrcpy-server.jar` - `/usr/local/share/scrcpy/scrcpy-server`
Just remove them to "uninstall" the application. Just remove them to "uninstall" the application.
@ -244,8 +243,7 @@ configuration:
```bash ```bash
meson x --buildtype release --strip -Db_lto=true \ meson x --buildtype release --strip -Db_lto=true \
-Dprebuilt_server=/path/to/scrcpy-server.jar -Dprebuilt_server=/path/to/scrcpy-server
cd x ninja -Cx
ninja sudo ninja -Cx install
sudo ninja install
``` ```

View File

@ -3,7 +3,7 @@
## Overview ## Overview
This application is composed of two parts: This application is composed of two parts:
- the server (`scrcpy-server.jar`), to be executed on the device, - the server (`scrcpy-server`), to be executed on the device,
- the client (the `scrcpy` binary), executed on the host computer. - the client (the `scrcpy` binary), executed on the host computer.
The client is responsible to push the server to the device and start its The client is responsible to push the server to the device and start its
@ -49,7 +49,7 @@ application may not replace the server just before the client executes it._
Instead of a raw _dex_ file, `app_process` accepts a _jar_ containing Instead of a raw _dex_ file, `app_process` accepts a _jar_ containing
`classes.dex` (e.g. an [APK]). For simplicity, and to benefit from the gradle `classes.dex` (e.g. an [APK]). For simplicity, and to benefit from the gradle
build system, the server is built to an (unsigned) APK (renamed to build system, the server is built to an (unsigned) APK (renamed to
`scrcpy-server.jar`). `scrcpy-server`).
[dex]: https://en.wikipedia.org/wiki/Dalvik_(software) [dex]: https://en.wikipedia.org/wiki/Dalvik_(software)
[apk]: https://en.wikipedia.org/wiki/Android_application_package [apk]: https://en.wikipedia.org/wiki/Android_application_package

View File

@ -3,7 +3,7 @@
# #
# Here, "portable" means that the client and server binaries are expected to be # Here, "portable" means that the client and server binaries are expected to be
# anywhere, but in the same directory, instead of well-defined separate # anywhere, but in the same directory, instead of well-defined separate
# locations (e.g. /usr/bin/scrcpy and /usr/share/scrcpy/scrcpy-server.jar). # locations (e.g. /usr/bin/scrcpy and /usr/share/scrcpy/scrcpy-server).
# #
# In particular, this implies to change the location from where the client push # In particular, this implies to change the location from where the client push
# the server to the device. # the server to the device.
@ -97,7 +97,7 @@ build-win64-noconsole: prepare-deps-win64
dist-win32: build-server build-win32 build-win32-noconsole dist-win32: build-server build-win32 build-win32-noconsole
mkdir -p "$(DIST)/$(WIN32_TARGET_DIR)" mkdir -p "$(DIST)/$(WIN32_TARGET_DIR)"
cp "$(SERVER_BUILD_DIR)"/server/scrcpy-server.jar "$(DIST)/$(WIN32_TARGET_DIR)/" cp "$(SERVER_BUILD_DIR)"/server/scrcpy-server "$(DIST)/$(WIN32_TARGET_DIR)/"
cp "$(WIN32_BUILD_DIR)"/app/scrcpy.exe "$(DIST)/$(WIN32_TARGET_DIR)/" cp "$(WIN32_BUILD_DIR)"/app/scrcpy.exe "$(DIST)/$(WIN32_TARGET_DIR)/"
cp "$(WIN32_NOCONSOLE_BUILD_DIR)"/app/scrcpy.exe "$(DIST)/$(WIN32_TARGET_DIR)/scrcpy-noconsole.exe" cp "$(WIN32_NOCONSOLE_BUILD_DIR)"/app/scrcpy.exe "$(DIST)/$(WIN32_TARGET_DIR)/scrcpy-noconsole.exe"
cp prebuilt-deps/ffmpeg-4.1.4-win32-shared/bin/avutil-56.dll "$(DIST)/$(WIN32_TARGET_DIR)/" cp prebuilt-deps/ffmpeg-4.1.4-win32-shared/bin/avutil-56.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
@ -112,7 +112,7 @@ dist-win32: build-server build-win32 build-win32-noconsole
dist-win64: build-server build-win64 build-win64-noconsole dist-win64: build-server build-win64 build-win64-noconsole
mkdir -p "$(DIST)/$(WIN64_TARGET_DIR)" mkdir -p "$(DIST)/$(WIN64_TARGET_DIR)"
cp "$(SERVER_BUILD_DIR)"/server/scrcpy-server.jar "$(DIST)/$(WIN64_TARGET_DIR)/" cp "$(SERVER_BUILD_DIR)"/server/scrcpy-server "$(DIST)/$(WIN64_TARGET_DIR)/"
cp "$(WIN64_BUILD_DIR)"/app/scrcpy.exe "$(DIST)/$(WIN64_TARGET_DIR)/" cp "$(WIN64_BUILD_DIR)"/app/scrcpy.exe "$(DIST)/$(WIN64_TARGET_DIR)/"
cp "$(WIN64_NOCONSOLE_BUILD_DIR)"/app/scrcpy.exe "$(DIST)/$(WIN64_TARGET_DIR)/scrcpy-noconsole.exe" cp "$(WIN64_NOCONSOLE_BUILD_DIR)"/app/scrcpy.exe "$(DIST)/$(WIN64_TARGET_DIR)/scrcpy-noconsole.exe"
cp prebuilt-deps/ffmpeg-4.1.4-win64-shared/bin/avutil-56.dll "$(DIST)/$(WIN64_TARGET_DIR)/" cp prebuilt-deps/ffmpeg-4.1.4-win64-shared/bin/avutil-56.dll "$(DIST)/$(WIN64_TARGET_DIR)/"

View File

@ -358,7 +358,7 @@ To use a specific _adb_ binary, configure its path in the environment variable
ADB=/path/to/adb scrcpy ADB=/path/to/adb scrcpy
To override the path of the `scrcpy-server.jar` file, configure its path in To override the path of the `scrcpy-server` file, configure its path in
`SCRCPY_SERVER_PATH`. `SCRCPY_SERVER_PATH`.
[useful]: https://github.com/Genymobile/scrcpy/issues/278#issuecomment-429330345 [useful]: https://github.com/Genymobile/scrcpy/issues/278#issuecomment-429330345

View File

@ -93,7 +93,7 @@ conf.set_quoted('SCRCPY_VERSION', meson.project_version())
# the prefix used during configuration (meson --prefix=PREFIX) # the prefix used during configuration (meson --prefix=PREFIX)
conf.set_quoted('PREFIX', get_option('prefix')) conf.set_quoted('PREFIX', get_option('prefix'))
# build a "portable" version (with scrcpy-server.jar accessible from the same # build a "portable" version (with scrcpy-server accessible from the same
# directory as the executable) # directory as the executable)
conf.set('PORTABLE', get_option('portable')) conf.set('PORTABLE', get_option('portable'))
@ -134,6 +134,8 @@ executable('scrcpy', src,
c_args: c_args, c_args: c_args,
link_args: link_args) link_args: link_args)
install_man('scrcpy.1')
### TESTS ### TESTS

254
app/scrcpy.1 Normal file
View File

@ -0,0 +1,254 @@
.TH "scrcpy" "1"
.SH NAME
scrcpy \- Display and control your Android device
.SH SYNOPSIS
.B scrcpy
.RI [ options ]
.SH DESCRIPTION
.B scrcpy
provides display and control of Android devices connected on USB (or over TCP/IP). It does not require any root access.
.SH OPTIONS
.TP
.BI "\-b, \-\-bit\-rate " value
Encode the video at the given bit\-rate, expressed in bits/s. Unit suffixes are supported: '\fBK\fR' (x1000) and '\fBM\fR' (x1000000).
Default is 8000000.
.TP
.BI "\-c, \-\-crop " width\fR:\fIheight\fR:\fIx\fR:\fIy
Crop the device screen on the server.
The values are expressed in the device natural orientation (typically, portrait for a phone, landscape for a tablet). Any
.B \-\-max\-size
value is computed on the cropped size.
.TP
.B \-f, \-\-fullscreen
Start in fullscreen.
.TP
.BI "\-F, \-\-record\-format " format
Force recording format (either mp4 or mkv).
.TP
.B \-h, \-\-help
Print this help.
.TP
.BI "\-m, \-\-max\-size " value
Limit both the width and height of the video to \fIvalue\fR. The other dimension is computed so that the device aspect\-ratio is preserved.
Default is 0 (unlimited).
.TP
.B \-n, \-\-no\-control
Disable device control (mirror the device in read\-only).
.TP
.B \-N, \-\-no\-display
Do not display device (only when screen recording is enabled).
.TP
.BI "\-p, \-\-port " port
Set the TCP port the client listens on.
Default is 27183.
.TP
.BI "\-\-push\-target " path
Set the target directory for pushing files to the device by drag & drop. It is passed as\-is to "adb push".
Default is "/sdcard/".
.TP
.BI "\-r, \-\-record " file
Record screen to
.IR file .
The format is determined by the
.B \-F/\-\-record\-format
option if set, or by the file extension (.mp4 or .mkv).
.TP
.B \-\-render\-expired\-frames
By default, to minimize latency, scrcpy always renders the last available decoded frame, and drops any previous ones. This flag forces to render all frames, at a cost of a possible increased latency.
.TP
.BI "\-s, \-\-serial " number
The device serial number. Mandatory only if several devices are connected to adb.
.TP
.B \-S, \-\-turn\-screen\-off
Turn the device screen off immediately.
.TP
.B \-t, \-\-show\-touches
Enable "show touches" on start, disable on quit.
It only shows physical touches (not clicks from scrcpy).
.TP
.B \-T, \-\-always\-on\-top
Make scrcpy window always on top (above other windows).
.TP
.B \-v, \-\-version
Print the version of scrcpy.
.TP
.BI \-\-window\-title " text
Set a custom window title.
.TP
.BI \-\-window\-x " value
Set the initial window horizontal position.
Default is -1 (automatic).\n
.TP
.BI \-\-window\-y " value
Set the initial window vertical position.
Default is -1 (automatic).\n
.TP
.BI \-\-window\-width " value
Set the initial window width.
Default is 0 (automatic).\n
.TP
.BI \-\-window\-height " value
Set the initial window height.
Default is 0 (automatic).\n
.SH SHORTCUTS
.TP
.B Ctrl+f
switch fullscreen mode
.TP
.B Ctrl+g
resize window to 1:1 (pixel\-perfect)
.TP
.B Ctrl+x, Double\-click on black borders
resize window to remove black borders
.TP
.B Ctrl+h, Home, Middle\-click
Click on HOME
.TP
.B Ctrl+b, Ctrl+Backspace, Right\-click (when screen is on)
Click on BACK
.TP
.B Ctrl+s
Click on APP_SWITCH
.TP
.B Ctrl+m
Click on MENU
.TP
.B Ctrl+Up
Click on VOLUME_UP
.TP
.B Ctrl+Down
Click on VOLUME_DOWN
.TP
.B Ctrl+p
Click on POWER (turn screen on/off)
.TP
.B Right\-click (when screen is off)
turn screen on
.TP
.B Ctrl+o
turn device screen off (keep mirroring)
.TP
.B Ctrl+n
expand notification panel
.TP
.B Ctrl+Shift+n
collapse notification panel
.TP
.B Ctrl+c
copy device clipboard to computer
.TP
.B Ctrl+v
paste computer clipboard to device
.TP
.B Ctrl+Shift+v
copy computer clipboard to device
.TP
.B Ctrl+i
enable/disable FPS counter (print frames/second in logs)
.TP
.B Drag & drop APK file
install APK from computer
.SH Environment variables
.TP
.B ADB
Specify the path to adb.
.TP
.B SCRCPY_SERVER_PATH
Specify the path to server binary.
.SH AUTHORS
.B scrcpy
is written by Romain Vimont.
This manual page was written by
.MT mmyangfl@gmail.com
Yangfl
.ME
for the Debian Project (and may be used by others).
.SH "REPORTING BUGS"
Report bugs to
.UR https://github.com/Genymobile/scrcpy/issues
.UE .
.SH COPYRIGHT
Copyright \(co 2018 Genymobile
.UR https://www.genymobile.com
Genymobile
.UE
Copyright \(co 2018\-2019
.MT rom@rom1v.com
Romain Vimont
.ME
Licensed under the Apache License, Version 2.0.
.SH WWW
.UR https://github.com/Genymobile/scrcpy
.UE

View File

@ -140,10 +140,10 @@ convert_mouse_buttons(uint32_t state) {
if (state & SDL_BUTTON_MMASK) { if (state & SDL_BUTTON_MMASK) {
buttons |= AMOTION_EVENT_BUTTON_TERTIARY; buttons |= AMOTION_EVENT_BUTTON_TERTIARY;
} }
if (state & SDL_BUTTON_X1) { if (state & SDL_BUTTON_X1MASK) {
buttons |= AMOTION_EVENT_BUTTON_BACK; buttons |= AMOTION_EVENT_BUTTON_BACK;
} }
if (state & SDL_BUTTON_X2) { if (state & SDL_BUTTON_X2MASK) {
buttons |= AMOTION_EVENT_BUTTON_FORWARD; buttons |= AMOTION_EVENT_BUTTON_FORWARD;
} }
return buttons; return buttons;

View File

@ -29,9 +29,14 @@ struct args {
uint16_t port; uint16_t port;
uint16_t max_size; uint16_t max_size;
uint32_t bit_rate; uint32_t bit_rate;
int16_t window_x;
int16_t window_y;
uint16_t window_width;
uint16_t window_height;
bool always_on_top; bool always_on_top;
bool turn_screen_off; bool turn_screen_off;
bool render_expired_frames; bool render_expired_frames;
bool window_borderless;
}; };
static void usage(const char *arg0) { static void usage(const char *arg0) {
@ -59,7 +64,7 @@ static void usage(const char *arg0) {
" -f, --fullscreen\n" " -f, --fullscreen\n"
" Start in fullscreen.\n" " Start in fullscreen.\n"
"\n" "\n"
" -F, --record-format\n" " -F, --record-format format\n"
" Force recording format (either mp4 or mkv).\n" " Force recording format (either mp4 or mkv).\n"
"\n" "\n"
" -h, --help\n" " -h, --help\n"
@ -115,9 +120,28 @@ static void usage(const char *arg0) {
" -v, --version\n" " -v, --version\n"
" Print the version of scrcpy.\n" " Print the version of scrcpy.\n"
"\n" "\n"
" --window_borderless\n"
" Disable window decorations (display borderless window).\n"
"\n"
" --window-title text\n" " --window-title text\n"
" Set a custom window title.\n" " Set a custom window title.\n"
"\n" "\n"
" --window-x value\n"
" Set the initial window horizontal position.\n"
" Default is -1 (automatic).\n"
"\n"
" --window-y value\n"
" Set the initial window vertical position.\n"
" Default is -1 (automatic).\n"
"\n"
" --window-width value\n"
" Set the initial window width.\n"
" Default is -1 (automatic).\n"
"\n"
" --window-height value\n"
" Set the initial window width.\n"
" Default is -1 (automatic).\n"
"\n"
"Shortcuts:\n" "Shortcuts:\n"
"\n" "\n"
" " CTRL_OR_CMD "+f\n" " " CTRL_OR_CMD "+f\n"
@ -258,6 +282,48 @@ parse_max_size(char *optarg, uint16_t *max_size) {
return true; return true;
} }
static bool
parse_window_position(char *optarg, int16_t *position) {
char *endptr;
if (*optarg == '\0') {
LOGE("Window position parameter is empty");
return false;
}
long value = strtol(optarg, &endptr, 0);
if (*endptr != '\0') {
LOGE("Invalid window position: %s", optarg);
return false;
}
if (value < -1 || value > 0x7fff) {
LOGE("Window position must be between -1 and 32767: %ld", value);
return false;
}
*position = (int16_t) value;
return true;
}
static bool
parse_window_dimension(char *optarg, uint16_t *dimension) {
char *endptr;
if (*optarg == '\0') {
LOGE("Window dimension parameter is empty");
return false;
}
long value = strtol(optarg, &endptr, 0);
if (*endptr != '\0') {
LOGE("Invalid window dimension: %s", optarg);
return false;
}
if (value & ~0xffff) {
LOGE("Window position must be between 0 and 65535: %ld", value);
return false;
}
*dimension = (uint16_t) value;
return true;
}
static bool static bool
parse_port(char *optarg, uint16_t *port) { parse_port(char *optarg, uint16_t *port) {
char *endptr; char *endptr;
@ -310,8 +376,13 @@ guess_record_format(const char *filename) {
} }
#define OPT_RENDER_EXPIRED_FRAMES 1000 #define OPT_RENDER_EXPIRED_FRAMES 1000
#define OPT_WINDOW_TITLE 1001 #define OPT_PUSH_TARGET 1001
#define OPT_PUSH_TARGET 1002 #define OPT_WINDOW_TITLE 1002
#define OPT_WINDOW_X 1003
#define OPT_WINDOW_Y 1004
#define OPT_WINDOW_WIDTH 1005
#define OPT_WINDOW_HEIGHT 1006
#define OPT_WINDOW_BORDERLESS 1007
static bool static bool
parse_args(struct args *args, int argc, char *argv[]) { parse_args(struct args *args, int argc, char *argv[]) {
@ -324,6 +395,8 @@ parse_args(struct args *args, int argc, char *argv[]) {
{"max-size", required_argument, NULL, 'm'}, {"max-size", required_argument, NULL, 'm'},
{"no-control", no_argument, NULL, 'n'}, {"no-control", no_argument, NULL, 'n'},
{"no-display", no_argument, NULL, 'N'}, {"no-display", no_argument, NULL, 'N'},
{"window-borderless", no_argument, NULL,
OPT_WINDOW_BORDERLESS},
{"port", required_argument, NULL, 'p'}, {"port", required_argument, NULL, 'p'},
{"push-target", required_argument, NULL, {"push-target", required_argument, NULL,
OPT_PUSH_TARGET}, OPT_PUSH_TARGET},
@ -335,8 +408,11 @@ parse_args(struct args *args, int argc, char *argv[]) {
{"show-touches", no_argument, NULL, 't'}, {"show-touches", no_argument, NULL, 't'},
{"turn-screen-off", no_argument, NULL, 'S'}, {"turn-screen-off", no_argument, NULL, 'S'},
{"version", no_argument, NULL, 'v'}, {"version", no_argument, NULL, 'v'},
{"window-title", required_argument, NULL, {"window-title", required_argument, NULL, OPT_WINDOW_TITLE},
OPT_WINDOW_TITLE}, {"window-x", required_argument, NULL, OPT_WINDOW_X},
{"window-y", required_argument, NULL, OPT_WINDOW_Y},
{"window-width", required_argument, NULL, OPT_WINDOW_WIDTH},
{"window-height", required_argument, NULL, OPT_WINDOW_HEIGHT},
{NULL, 0, NULL, 0 }, {NULL, 0, NULL, 0 },
}; };
int c; int c;
@ -402,6 +478,29 @@ parse_args(struct args *args, int argc, char *argv[]) {
case OPT_WINDOW_TITLE: case OPT_WINDOW_TITLE:
args->window_title = optarg; args->window_title = optarg;
break; break;
case OPT_WINDOW_X:
if (!parse_window_position(optarg, &args->window_x)) {
return false;
}
break;
case OPT_WINDOW_Y:
if (!parse_window_position(optarg, &args->window_y)) {
return false;
}
break;
case OPT_WINDOW_WIDTH:
if (!parse_window_dimension(optarg, &args->window_width)) {
return false;
}
break;
case OPT_WINDOW_HEIGHT:
if (!parse_window_dimension(optarg, &args->window_height)) {
return false;
}
break;
case OPT_WINDOW_BORDERLESS:
args->window_borderless = true;
break;
case OPT_PUSH_TARGET: case OPT_PUSH_TARGET:
args->push_target = optarg; args->push_target = optarg;
break; break;
@ -470,11 +569,16 @@ main(int argc, char *argv[]) {
.port = DEFAULT_LOCAL_PORT, .port = DEFAULT_LOCAL_PORT,
.max_size = DEFAULT_MAX_SIZE, .max_size = DEFAULT_MAX_SIZE,
.bit_rate = DEFAULT_BIT_RATE, .bit_rate = DEFAULT_BIT_RATE,
.window_x = -1,
.window_y = -1,
.window_width = 0,
.window_height = 0,
.always_on_top = false, .always_on_top = false,
.no_control = false, .no_control = false,
.no_display = false, .no_display = false,
.turn_screen_off = false, .turn_screen_off = false,
.render_expired_frames = false, .render_expired_frames = false,
.window_borderless = false,
}; };
if (!parse_args(&args, argc, argv)) { if (!parse_args(&args, argc, argv)) {
return 1; return 1;
@ -514,6 +618,10 @@ main(int argc, char *argv[]) {
.record_format = args.record_format, .record_format = args.record_format,
.max_size = args.max_size, .max_size = args.max_size,
.bit_rate = args.bit_rate, .bit_rate = args.bit_rate,
.window_x = args.window_x,
.window_y = args.window_y,
.window_width = args.window_width,
.window_height = args.window_height,
.show_touches = args.show_touches, .show_touches = args.show_touches,
.fullscreen = args.fullscreen, .fullscreen = args.fullscreen,
.always_on_top = args.always_on_top, .always_on_top = args.always_on_top,
@ -521,6 +629,7 @@ main(int argc, char *argv[]) {
.display = !args.no_display, .display = !args.no_display,
.turn_screen_off = args.turn_screen_off, .turn_screen_off = args.turn_screen_off,
.render_expired_frames = args.render_expired_frames, .render_expired_frames = args.render_expired_frames,
.window_borderless = args.window_borderless,
}; };
int res = scrcpy(&options) ? 0 : 1; int res = scrcpy(&options) ? 0 : 1;

View File

@ -135,6 +135,9 @@ recorder_open(struct recorder *recorder, const AVCodec *input_codec) {
// <https://github.com/FFmpeg/FFmpeg/commit/0694d8702421e7aff1340038559c438b61bb30dd> // <https://github.com/FFmpeg/FFmpeg/commit/0694d8702421e7aff1340038559c438b61bb30dd>
recorder->ctx->oformat = (AVOutputFormat *) format; recorder->ctx->oformat = (AVOutputFormat *) format;
av_dict_set(&recorder->ctx->metadata, "comment",
"Recorded by scrcpy " SCRCPY_VERSION, 0);
AVStream *ostream = avformat_new_stream(recorder->ctx, input_codec); AVStream *ostream = avformat_new_stream(recorder->ctx, input_codec);
if (!ostream) { if (!ostream) {
avformat_free_context(recorder->ctx); avformat_free_context(recorder->ctx);

View File

@ -390,7 +390,10 @@ scrcpy(const struct scrcpy_options *options) {
options->window_title ? options->window_title : device_name; options->window_title ? options->window_title : device_name;
if (!screen_init_rendering(&screen, window_title, frame_size, if (!screen_init_rendering(&screen, window_title, frame_size,
options->always_on_top)) { options->always_on_top, options->window_x,
options->window_y, options->window_width,
options->window_height,
options->window_borderless)) {
goto end; goto end;
} }

View File

@ -17,6 +17,10 @@ struct scrcpy_options {
uint16_t port; uint16_t port;
uint16_t max_size; uint16_t max_size;
uint32_t bit_rate; uint32_t bit_rate;
int16_t window_x;
int16_t window_y;
uint16_t window_width;
uint16_t window_height;
bool show_touches; bool show_touches;
bool fullscreen; bool fullscreen;
bool always_on_top; bool always_on_top;
@ -24,6 +28,7 @@ struct scrcpy_options {
bool display; bool display;
bool turn_screen_off; bool turn_screen_off;
bool render_expired_frames; bool render_expired_frames;
bool window_borderless;
}; };
bool bool

View File

@ -16,7 +16,7 @@
// get the window size in a struct size // get the window size in a struct size
static struct size static struct size
get_native_window_size(SDL_Window *window) { get_window_size(SDL_Window *window) {
int width; int width;
int height; int height;
SDL_GetWindowSize(window, &width, &height); SDL_GetWindowSize(window, &width, &height);
@ -29,11 +29,11 @@ get_native_window_size(SDL_Window *window) {
// get the windowed window size // get the windowed window size
static struct size static struct size
get_window_size(const struct screen *screen) { get_windowed_window_size(const struct screen *screen) {
if (screen->fullscreen) { if (screen->fullscreen) {
return screen->windowed_window_size; return screen->windowed_window_size;
} }
return get_native_window_size(screen->window); return get_window_size(screen->window);
} }
// set the window size to be applied when fullscreen is disabled // set the window size to be applied when fullscreen is disabled
@ -112,14 +112,35 @@ get_optimal_size(struct size current_size, struct size frame_size) {
// same as get_optimal_size(), but read the current size from the window // same as get_optimal_size(), but read the current size from the window
static inline struct size static inline struct size
get_optimal_window_size(const struct screen *screen, struct size frame_size) { get_optimal_window_size(const struct screen *screen, struct size frame_size) {
struct size current_size = get_window_size(screen); struct size windowed_size = get_windowed_window_size(screen);
return get_optimal_size(current_size, frame_size); return get_optimal_size(windowed_size, frame_size);
} }
// initially, there is no current size, so use the frame size as current size // initially, there is no current size, so use the frame size as current size
// req_width and req_height, if not 0, are the sizes requested by the user
static inline struct size static inline struct size
get_initial_optimal_size(struct size frame_size) { get_initial_optimal_size(struct size frame_size, uint16_t req_width,
return get_optimal_size(frame_size, frame_size); uint16_t req_height) {
struct size window_size;
if (!req_width && !req_height) {
window_size = get_optimal_size(frame_size, frame_size);
} else {
if (req_width) {
window_size.width = req_width;
} else {
// compute from the requested height
window_size.width = (uint32_t) req_height * frame_size.width
/ frame_size.height;
}
if (req_height) {
window_size.height = req_height;
} else {
// compute from the requested width
window_size.height = (uint32_t) req_width * frame_size.height
/ frame_size.width;
}
}
return window_size;
} }
void void
@ -136,10 +157,13 @@ create_texture(SDL_Renderer *renderer, struct size frame_size) {
bool bool
screen_init_rendering(struct screen *screen, const char *window_title, screen_init_rendering(struct screen *screen, const char *window_title,
struct size frame_size, bool always_on_top) { struct size frame_size, bool always_on_top,
int16_t window_x, int16_t window_y, uint16_t window_width,
uint16_t window_height, bool window_borderless) {
screen->frame_size = frame_size; screen->frame_size = frame_size;
struct size window_size = get_initial_optimal_size(frame_size); struct size window_size =
get_initial_optimal_size(frame_size, window_width, window_height);
uint32_t window_flags = SDL_WINDOW_HIDDEN | SDL_WINDOW_RESIZABLE; uint32_t window_flags = SDL_WINDOW_HIDDEN | SDL_WINDOW_RESIZABLE;
#ifdef HIDPI_SUPPORT #ifdef HIDPI_SUPPORT
window_flags |= SDL_WINDOW_ALLOW_HIGHDPI; window_flags |= SDL_WINDOW_ALLOW_HIGHDPI;
@ -152,9 +176,13 @@ screen_init_rendering(struct screen *screen, const char *window_title,
"(compile with SDL >= 2.0.5 to enable it)"); "(compile with SDL >= 2.0.5 to enable it)");
#endif #endif
} }
if (window_borderless) {
window_flags |= SDL_WINDOW_BORDERLESS;
}
screen->window = SDL_CreateWindow(window_title, SDL_WINDOWPOS_UNDEFINED, int x = window_x != -1 ? window_x : SDL_WINDOWPOS_UNDEFINED;
SDL_WINDOWPOS_UNDEFINED, int y = window_y != -1 ? window_y : SDL_WINDOWPOS_UNDEFINED;
screen->window = SDL_CreateWindow(window_title, x, y,
window_size.width, window_size.height, window_size.width, window_size.height,
window_flags); window_flags);
if (!screen->window) { if (!screen->window) {
@ -229,11 +257,11 @@ prepare_for_frame(struct screen *screen, struct size new_frame_size) {
// frame dimension changed, destroy texture // frame dimension changed, destroy texture
SDL_DestroyTexture(screen->texture); SDL_DestroyTexture(screen->texture);
struct size current_size = get_window_size(screen); struct size windowed_size = get_windowed_window_size(screen);
struct size target_size = { struct size target_size = {
(uint32_t) current_size.width * new_frame_size.width (uint32_t) windowed_size.width * new_frame_size.width
/ screen->frame_size.width, / screen->frame_size.width,
(uint32_t) current_size.height * new_frame_size.height (uint32_t) windowed_size.height * new_frame_size.height
/ screen->frame_size.height, / screen->frame_size.height,
}; };
target_size = get_optimal_size(target_size, new_frame_size); target_size = get_optimal_size(target_size, new_frame_size);
@ -289,7 +317,7 @@ void
screen_switch_fullscreen(struct screen *screen) { screen_switch_fullscreen(struct screen *screen) {
if (!screen->fullscreen) { if (!screen->fullscreen) {
// going to fullscreen, store the current windowed window size // going to fullscreen, store the current windowed window size
screen->windowed_window_size = get_native_window_size(screen->window); screen->windowed_window_size = get_window_size(screen->window);
} }
uint32_t new_mode = screen->fullscreen ? 0 : SDL_WINDOW_FULLSCREEN_DESKTOP; uint32_t new_mode = screen->fullscreen ? 0 : SDL_WINDOW_FULLSCREEN_DESKTOP;
if (SDL_SetWindowFullscreen(screen->window, new_mode)) { if (SDL_SetWindowFullscreen(screen->window, new_mode)) {

View File

@ -46,7 +46,9 @@ screen_init(struct screen *screen);
// initialize screen, create window, renderer and texture (window is hidden) // initialize screen, create window, renderer and texture (window is hidden)
bool bool
screen_init_rendering(struct screen *screen, const char *window_title, screen_init_rendering(struct screen *screen, const char *window_title,
struct size frame_size, bool always_on_top); struct size frame_size, bool always_on_top,
int16_t window_x, int16_t window_y, uint16_t window_width,
uint16_t window_height, bool window_borderless);
// show the window // show the window
void void

View File

@ -13,7 +13,7 @@
#include "net.h" #include "net.h"
#define SOCKET_NAME "scrcpy" #define SOCKET_NAME "scrcpy"
#define SERVER_FILENAME "scrcpy-server.jar" #define SERVER_FILENAME "scrcpy-server"
#define DEFAULT_SERVER_PATH PREFIX "/share/scrcpy/" SERVER_FILENAME #define DEFAULT_SERVER_PATH PREFIX "/share/scrcpy/" SERVER_FILENAME
#define DEVICE_SERVER_PATH "/data/local/tmp/" SERVER_FILENAME #define DEVICE_SERVER_PATH "/data/local/tmp/" SERVER_FILENAME
@ -32,7 +32,7 @@ get_server_path(void) {
// the absolute path is hardcoded // the absolute path is hardcoded
return DEFAULT_SERVER_PATH; return DEFAULT_SERVER_PATH;
#else #else
// use scrcpy-server.jar in the same directory as the executable // use scrcpy-server in the same directory as the executable
char *executable_path = get_executable_path(); char *executable_path = get_executable_path();
if (!executable_path) { if (!executable_path) {
LOGE("Could not get executable path, " LOGE("Could not get executable path, "

View File

@ -3,5 +3,5 @@ option('compile_server', type: 'boolean', value: true, description: 'Build the s
option('crossbuild_windows', type: 'boolean', value: false, description: 'Build for Windows from Linux') option('crossbuild_windows', type: 'boolean', value: false, description: 'Build for Windows from Linux')
option('windows_noconsole', type: 'boolean', value: false, description: 'Disable console on Windows (pass -mwindows flag)') option('windows_noconsole', type: 'boolean', value: false, description: 'Disable console on Windows (pass -mwindows flag)')
option('prebuilt_server', type: 'string', description: 'Path of the prebuilt server') option('prebuilt_server', type: 'string', description: 'Path of the prebuilt server')
option('portable', type: 'boolean', value: false, description: 'Use scrcpy-server.jar from the same directory as the scrcpy executable') option('portable', type: 'boolean', value: false, description: 'Use scrcpy-server from the same directory as the scrcpy executable')
option('hidpi_support', type: 'boolean', value: true, description: 'Enable High DPI support') option('hidpi_support', type: 'boolean', value: true, description: 'Enable High DPI support')

View File

@ -23,21 +23,21 @@ cd -
make -f Makefile.CrossWindows make -f Makefile.CrossWindows
# the generated server must be the same everywhere # the generated server must be the same everywhere
cmp "$BUILDDIR/server/scrcpy-server.jar" dist/scrcpy-win32/scrcpy-server.jar cmp "$BUILDDIR/server/scrcpy-server" dist/scrcpy-win32/scrcpy-server
cmp "$BUILDDIR/server/scrcpy-server.jar" dist/scrcpy-win64/scrcpy-server.jar cmp "$BUILDDIR/server/scrcpy-server" dist/scrcpy-win64/scrcpy-server
# get version name # get version name
TAG=$(git describe --tags --always) TAG=$(git describe --tags --always)
# create release directory # create release directory
mkdir -p "release-$TAG" mkdir -p "release-$TAG"
cp "$BUILDDIR/server/scrcpy-server.jar" "release-$TAG/scrcpy-server-$TAG.jar" cp "$BUILDDIR/server/scrcpy-server" "release-$TAG/scrcpy-server-$TAG"
cp "dist/scrcpy-win32-$TAG.zip" "release-$TAG/" cp "dist/scrcpy-win32-$TAG.zip" "release-$TAG/"
cp "dist/scrcpy-win64-$TAG.zip" "release-$TAG/" cp "dist/scrcpy-win64-$TAG.zip" "release-$TAG/"
# generate checksums # generate checksums
cd "release-$TAG" cd "release-$TAG"
sha256sum "scrcpy-server-$TAG.jar" \ sha256sum "scrcpy-server-$TAG" \
"scrcpy-win32-$TAG.zip" \ "scrcpy-win32-$TAG.zip" \
"scrcpy-win64-$TAG.zip" > SHA256SUMS.txt "scrcpy-win64-$TAG.zip" > SHA256SUMS.txt

2
run
View File

@ -20,4 +20,4 @@ then
exit 1 exit 1
fi fi
SCRCPY_SERVER_PATH="$BUILDDIR/server/scrcpy-server.jar" "$BUILDDIR/app/scrcpy" "$@" SCRCPY_SERVER_PATH="$BUILDDIR/server/scrcpy-server" "$BUILDDIR/app/scrcpy" "$@"

View File

@ -1,2 +1,2 @@
#!/bin/bash #!/bin/bash
SCRCPY_SERVER_PATH="$MESON_BUILD_ROOT/server/scrcpy-server.jar" "$MESON_BUILD_ROOT/app/scrcpy" SCRCPY_SERVER_PATH="$MESON_BUILD_ROOT/server/scrcpy-server" "$MESON_BUILD_ROOT/app/scrcpy"

62
server/build_without_gradle.sh Executable file
View File

@ -0,0 +1,62 @@
#!/bin/bash
#
# This script generates the scrcpy binary "manually" (without gradle).
#
# Adapt Android platform and build tools versions (via ANDROID_PLATFORM and
# ANDROID_BUILD_TOOLS environment variables).
#
# Then execute:
#
# BUILD_DIR=my_build_dir ./build_without_gradle.sh
set -e
PLATFORM=${ANDROID_PLATFORM:-29}
BUILD_TOOLS=${ANDROID_BUILD_TOOLS:-29.0.2}
BUILD_DIR="$(realpath ${BUILD_DIR:-build_manual})"
CLASSES_DIR="$BUILD_DIR/classes"
SERVER_DIR=$(dirname "$0")
SERVER_BINARY=scrcpy-server
echo "Platform: android-$PLATFORM"
echo "Build-tools: $BUILD_TOOLS"
echo "Build dir: $BUILD_DIR"
rm -rf "$CLASSES_DIR" "$BUILD_DIR/$SERVER_BINARY" classes.dex
mkdir -p "$CLASSES_DIR/com/genymobile/scrcpy"
<< EOF cat > "$CLASSES_DIR/com/genymobile/scrcpy/BuildConfig.java"
package com.genymobile.scrcpy;
public final class BuildConfig {
public static final boolean DEBUG = false;
}
EOF
echo "Generating java from aidl..."
cd "$SERVER_DIR/src/main/aidl"
"$ANDROID_HOME/build-tools/$BUILD_TOOLS/aidl" -o "$CLASSES_DIR" \
android/view/IRotationWatcher.aidl
echo "Compiling java sources..."
cd ../java
javac -bootclasspath "$ANDROID_HOME/platforms/android-$PLATFORM/android.jar" \
-cp "$CLASSES_DIR" -d "$CLASSES_DIR" -source 1.8 -target 1.8 \
com/genymobile/scrcpy/*.java \
com/genymobile/scrcpy/wrappers/*.java
echo "Dexing..."
cd "$CLASSES_DIR"
"$ANDROID_HOME/build-tools/$BUILD_TOOLS/dx" --dex \
--output "$BUILD_DIR/classes.dex" \
android/view/*.class \
com/genymobile/scrcpy/*.class \
com/genymobile/scrcpy/wrappers/*.class
echo "Archiving..."
cd "$BUILD_DIR"
jar cvf "$SERVER_BINARY" classes.dex
rm -rf classes.dex classes
echo "Server generated in $BUILD_DIR/$SERVER_BINARY"

View File

@ -4,7 +4,7 @@ prebuilt_server = get_option('prebuilt_server')
if prebuilt_server == '' if prebuilt_server == ''
custom_target('scrcpy-server', custom_target('scrcpy-server',
build_always: true, # gradle is responsible for tracking source changes build_always: true, # gradle is responsible for tracking source changes
output: 'scrcpy-server.jar', output: 'scrcpy-server',
command: [find_program('./scripts/build-wrapper.sh'), meson.current_source_dir(), '@OUTPUT@', get_option('buildtype')], command: [find_program('./scripts/build-wrapper.sh'), meson.current_source_dir(), '@OUTPUT@', get_option('buildtype')],
console: true, console: true,
install: true, install: true,
@ -16,7 +16,7 @@ else
endif endif
custom_target('scrcpy-server-prebuilt', custom_target('scrcpy-server-prebuilt',
input: prebuilt_server, input: prebuilt_server,
output: 'scrcpy-server.jar', output: 'scrcpy-server',
command: ['cp', '@INPUT@', '@OUTPUT@'], command: ['cp', '@INPUT@', '@OUTPUT@'],
install: true, install: true,
install_dir: 'share/scrcpy') install_dir: 'share/scrcpy')

View File

@ -82,7 +82,7 @@ public class Controller {
injectText(msg.getText()); injectText(msg.getText());
break; break;
case ControlMessage.TYPE_INJECT_TOUCH_EVENT: case ControlMessage.TYPE_INJECT_TOUCH_EVENT:
injectTouch(msg.getAction(), msg.getPointerId(), msg.getPosition(), msg.getPressure(), 0); injectTouch(msg.getAction(), msg.getPointerId(), msg.getPosition(), msg.getPressure(), msg.getButtons());
break; break;
case ControlMessage.TYPE_INJECT_SCROLL_EVENT: case ControlMessage.TYPE_INJECT_SCROLL_EVENT:
injectScroll(msg.getPosition(), msg.getHScroll(), msg.getVScroll()); injectScroll(msg.getPosition(), msg.getHScroll(), msg.getVScroll());
@ -177,7 +177,7 @@ public class Controller {
} }
MotionEvent event = MotionEvent.obtain(lastTouchDown, now, action, pointerCount, pointerProperties, MotionEvent event = MotionEvent.obtain(lastTouchDown, now, action, pointerCount, pointerProperties,
pointerCoords, 0, 0, 1f, 1f, 0, 0, InputDevice.SOURCE_TOUCHSCREEN, 0); pointerCoords, 0, buttons, 1f, 1f, 0, 0, InputDevice.SOURCE_TOUCHSCREEN, 0);
return injectEvent(event); return injectEvent(event);
} }

View File

@ -7,7 +7,7 @@ import java.io.IOException;
public final class Server { public final class Server {
private static final String SERVER_PATH = "/data/local/tmp/scrcpy-server.jar"; private static final String SERVER_PATH = "/data/local/tmp/scrcpy-server";
private Server() { private Server() {
// not instantiable // not instantiable