Compare commits

...

17 Commits

Author SHA1 Message Date
Romain Vimont
1f237a4950 Display disconnected icon before closing
When the connection to the device is lost while mirroring, the window
closed immediately, suggesting scrcpy had crashed.

To make it clear that a disconnection occurred, display a disconnected
icon for 2 seconds before closing the window.
2026-02-24 23:03:33 +01:00
Romain Vimont
d36322c841 Track window shown state
Use a flag to determine whether the window is currently shown.

This replaces the old has_video_window flag, which was true only when
the window was shown and video was enabled.

This will simplify displaying a "disconnected" icon on device
disconnection when the window is currently shown.
2026-02-24 00:25:54 +01:00
Romain Vimont
a7ff2ff6c7 Only reject RUN_ON_MAIN_THREAD events on quit
Use SDL_PeepEvents() to consume only SC_EVENT_RUN_ON_MAIN_THREAD events.
Other events are not dropped and can still be processed later.
2026-02-24 00:25:52 +01:00
Romain Vimont
2bdefc7663 Add utility to push an SDL event with data 2026-02-22 10:00:40 +01:00
Romain Vimont
9a079a716d Add function to delete current texture 2026-02-22 10:00:40 +01:00
Romain Vimont
cb6eee82cc Add filename parameter to icon loading
Replace scrcpy_icon_load(), which loaded the unique scrcpy app icon,
with sc_icon_load(filename), which can load any icon from the icons
directory.
2026-02-22 10:00:40 +01:00
Romain Vimont
ced45d0ec4 Replace SCRCPY_ICON_PATH with SCRCPY_ICON_DIR
SCRCPY_ICON_PATH defined the path of the scrcpy app icon.
SCRCPY_ICON_DIR defines the directory where scrcpy icons reside.

This change prepares for the addition of other icons.
2026-02-22 10:00:40 +01:00
Romain Vimont
7958b297ec Extract function to build file paths
Add a utility function to create a full path from a directory and a
filename.
2026-02-22 10:00:40 +01:00
Romain Vimont
b4711a5904 Rename icon.png to scrcpy.png
This makes the icon name consistent everywhere.
2026-02-22 10:00:40 +01:00
Romain Vimont
323549e82f Prevent build from falling back to system libs
Ensure that if a file or function is not found, the build does not
attempt to use system libraries. Falling back could result in using
libraries that are incompatible due to wrong versions or features.

PR #6671 <https://github.com/Genymobile/scrcpy/pull/6671>
2026-02-21 12:24:41 +01:00
Romain Vimont
90d11810e3 Set MediaCodec KEY_LATENCY to the minimum value
The encoder must output a frame as soon as one frame is queued.

Refs <https://developer.android.com/reference/android/media/MediaFormat#KEY_LATENCY>
Refs #6238 comment <https://github.com/Genymobile/scrcpy/issues/6238#issuecomment-3828402687>
PR #6670 <https://github.com/Genymobile/scrcpy/pull/6670>
2026-02-21 12:21:54 +01:00
Romain Vimont
1f19ec4aec Set MediaCodec KEY_PRIORITY to real-time (0)
Refs <https://developer.android.com/reference/android/media/MediaFormat#KEY_PRIORITY>
Refs #6238 comment <https://github.com/Genymobile/scrcpy/issues/6238#issuecomment-3828402687>
PR #6670 <https://github.com/Genymobile/scrcpy/pull/6670>
2026-02-21 12:21:44 +01:00
Romain Vimont
8b0206f7be Keep Windows terminal open on error
If scrcpy is launched by double-clicking scrcpy.exe in Windows Explorer,
automatically set --pause-on-exit=if-error.

Without this, the terminal would close immediately, preventing the user
from seeing the error.

Also remove scrcpy-console.bat, which is now useless.

PR #6667 <https://github.com/Genymobile/scrcpy/pull/6667>
2026-02-21 12:17:31 +01:00
Romain Vimont
f91fa56593 Fix segfault on rotation while recording
The packet sink push_session() callback is optional, but it was called
unconditionnally even when not implemented, leading to a segfault.

Bug introduced by commit 78cba1b7c2.

Fixes #6687 <https://github.com/Genymobile/scrcpy/issues/6687>
2026-02-21 10:45:12 +01:00
Romain Vimont
23710e04c1 Disable audio stream with fatal error Throwable
Only an AudioCaptureException should disable the audio stream without
making scrcpy exit (it is not a fatal error).

A ConfigurationException or any other Throwable must be considered a
fatal error.

                               BEFORE      AFTER
  AudioCaptureException:    non-fatal  non-fatal
  ConfigurationException:       fatal      fatal
  any other Throwable:      non-fatal      fatal

Refs #6600 comment <https://github.com/Genymobile/scrcpy/issues/6600#issuecomment-3744934826>
2026-02-12 20:20:52 +01:00
Romain Vimont
51cbd9a5bc Detect TCP devices provided by mDNS
Their serial is not in the form ip:port, but rather a complex string
containing "adb-tls-connect".

Fixes #6248 <https://github.com/Genymobile/scrcpy/issues/6248>
PR #6665 <https://github.com/Genymobile/scrcpy/pull/6665>
2026-02-12 20:08:46 +01:00
Romain Vimont
82e102e036 Improve adb devices parsing
`adb devices -l` prints one device per line, containing, separated by
spaces:
 - the device serial,
 - the device state,
 - a list of key:value pairs.

However, the device serial itself may contain spaces, making a simple
split ambiguous.

To avoid ambiguity, parse the string backwards:
 - first, parse all the trailing values until the device state,
   identified using a list of well-known values;
 - finally, treat the remaining leading token as the device serial.

Fixes #3537 <https://github.com/Genymobile/scrcpy/issues/3537>
Refs #6248 <https://github.com/Genymobile/scrcpy/issues/6248>
PR #6664 <https://github.com/Genymobile/scrcpy/pull/6664>
2026-02-12 20:06:11 +01:00
34 changed files with 621 additions and 178 deletions

BIN
app/data/disconnected.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

View File

@@ -1,2 +0,0 @@
@echo off
scrcpy.exe --pause-on-exit=if-error %*

View File

Before

Width:  |  Height:  |  Size: 6.4 KiB

After

Width:  |  Height:  |  Size: 6.4 KiB

View File

Before

Width:  |  Height:  |  Size: 4.5 KiB

After

Width:  |  Height:  |  Size: 4.5 KiB

View File

@@ -15,6 +15,7 @@ src = [
'src/delay_buffer.c',
'src/demuxer.c',
'src/device_msg.c',
'src/disconnect.c',
'src/events.c',
'src/icon.c',
'src/file_pusher.c',
@@ -191,8 +192,7 @@ executable('scrcpy', src,
datadir = get_option('datadir') # by default 'share'
install_man('scrcpy.1')
install_data('data/icon.png',
rename: 'scrcpy.png',
install_data('data/scrcpy.png',
install_dir: datadir / 'icons/hicolor/256x256/apps')
install_data('data/zsh-completion/_scrcpy',
install_dir: datadir / 'zsh/site-functions')
@@ -289,6 +289,6 @@ endif
if meson.version().version_compare('>= 0.58.0')
devenv = environment()
devenv.set('SCRCPY_ICON_PATH', meson.current_source_dir() / 'data/icon.png')
devenv.set('SCRCPY_ICON_DIR', meson.current_source_dir() / 'data')
meson.add_devenv(devenv)
endif

View File

@@ -851,8 +851,8 @@ Path to adb.
Device serial to use if no selector (\fB-s\fR, \fB-d\fR, \fB-e\fR or \fB\-\-tcpip=\fIaddr\fR) is specified.
.TP
.B SCRCPY_ICON_PATH
Path to the program icon.
.B SCRCPY_ICON_DIR
Path to the icon directory.
.TP
.B SCRCPY_SERVER_PATH

View File

@@ -39,5 +39,11 @@ sc_adb_device_get_type(const char *serial) {
return SC_ADB_DEVICE_TYPE_TCPIP;
}
// TCP/IP devices provided by mDNS contain "adb-tls-connect"
// <https://github.com/Genymobile/scrcpy/issues/6248>
if (strstr(serial, "adb-tls-connect")) {
return SC_ADB_DEVICE_TYPE_TCPIP;
}
return SC_ADB_DEVICE_TYPE_USB;
}

View File

@@ -8,6 +8,49 @@
#include "util/log.h"
#include "util/str.h"
static size_t
rstrip_len(const char *s, size_t len) {
size_t i = len;
// Ignore trailing whitespaces
while (i > 0 && (s[i-1] == ' ' || s[i-1] == '\t')) {
--i;
}
return i;
}
static void
locate_last_token(const char *s, size_t len, size_t *start, size_t *end) {
size_t i = rstrip_len(s, len);
*end = i; // excluded
// The token contains non-whitespace chars
while (i > 0 && (s[i-1] != ' ' && s[i-1] != '\t')) {
--i;
}
*start = i; // included
}
static bool
is_device_state(const char *s) {
// <https://android.googlesource.com/platform/packages/modules/adb/+/1cf2f017d312f73b3dc53bda85ef2610e35a80e9/adb.cpp#144>
// "device", "unauthorized" and "offline" are the most common states, so
// check them first.
return !strcmp(s, "device")
|| !strcmp(s, "unauthorized")
|| !strcmp(s, "offline")
|| !strcmp(s, "bootloader")
|| !strcmp(s, "host")
|| !strcmp(s, "recovery")
|| !strcmp(s, "rescue")
|| !strcmp(s, "sideload")
|| !strcmp(s, "authorizing")
|| !strcmp(s, "connecting")
|| !strcmp(s, "detached");
}
static bool
sc_adb_parse_device(char *line, struct sc_adb_device *device) {
// One device line looks like:
@@ -25,64 +68,54 @@ sc_adb_parse_device(char *line, struct sc_adb_device *device) {
return false;
}
char *s = line; // cursor in the line
size_t len = strlen(line);
// After the serial:
// - "adb devices" writes a single '\t'
// - "adb devices -l" writes multiple spaces
// For flexibility, accept both.
size_t serial_len = strcspn(s, " \t");
size_t start;
size_t end;
// The serial (the first token) may contain spaces, which are also token
// separators. To avoid ambiguity, parse the string backwards:
// - first, parse all the trailing values until the device state,
// identified using a list of well-known values;
// - finally, treat the remaining leading token as the device serial.
//
// Refs:
// - <https://github.com/Genymobile/scrcpy/issues/6248>
// - <https://github.com/Genymobile/scrcpy/issues/3537>
const char *state;
const char *model = NULL;
for (;;) {
locate_last_token(line, len, &start, &end);
if (start == end) {
// No more tokens, unexpected
return false;
}
const char *token = &line[start];
line[end] = '\0';
if (!strncmp("model:", token, sizeof("model:") - 1)) {
model = &token[sizeof("model:") - 1];
// We only need the model
} else if (is_device_state(token)) {
state = token;
// The device state is the item immediately after the device serial
break;
}
// Remove the trailing parts already handled
len = start;
}
assert(state);
size_t serial_len = rstrip_len(line, start);
if (!serial_len) {
// empty serial
return false;
}
bool eol = s[serial_len] == '\0';
if (eol) {
// serial alone is unexpected
return false;
}
s[serial_len] = '\0';
char *serial = s;
s += serial_len + 1;
// After the serial, there might be several spaces
s += strspn(s, " \t"); // consume all separators
size_t state_len = strcspn(s, " ");
if (!state_len) {
// empty state
return false;
}
eol = s[state_len] == '\0';
s[state_len] = '\0';
char *state = s;
char *model = NULL;
if (!eol) {
s += state_len + 1;
// Iterate over all properties "key:value key:value ..."
for (;;) {
size_t token_len = strcspn(s, " ");
if (!token_len) {
break;
}
eol = s[token_len] == '\0';
s[token_len] = '\0';
char *token = s;
if (!strncmp("model:", token, sizeof("model:") - 1)) {
model = &token[sizeof("model:") - 1];
// We only need the model
break;
}
if (eol) {
break;
} else {
s+= token_len + 1;
}
}
}
char *serial = line;
line[serial_len] = '\0';
device->serial = strdup(serial);
if (!device->serial) {

View File

@@ -1249,8 +1249,8 @@ static const struct sc_envvar envvars[] = {
"--tcpip=<addr>) is specified",
},
{
.name = "SCRCPY_ICON_PATH",
.text = "Path to the program icon",
.name = "SCRCPY_ICON_DIR",
.text = "Path to the icon directory",
},
{
.name = "SCRCPY_SERVER_PATH",
@@ -3371,7 +3371,7 @@ sc_get_pause_on_exit(int argc, char *argv[]) {
}
if (arg[15] != '=') {
// Invalid parameter, ignore
return SC_PAUSE_ON_EXIT_FALSE;
return SC_PAUSE_ON_EXIT_UNDEFINED;
}
const char *value = &arg[16];
if (!strcmp(value, "true")) {
@@ -3380,14 +3380,44 @@ sc_get_pause_on_exit(int argc, char *argv[]) {
if (!strcmp(value, "if-error")) {
return SC_PAUSE_ON_EXIT_IF_ERROR;
}
// Set to false, including when the value is invalid
return SC_PAUSE_ON_EXIT_FALSE;
if (!strcmp(value, "false")) {
return SC_PAUSE_ON_EXIT_FALSE;
}
return SC_PAUSE_ON_EXIT_UNDEFINED;
}
}
return SC_PAUSE_ON_EXIT_FALSE;
return SC_PAUSE_ON_EXIT_UNDEFINED;
}
#ifdef _WIN32
/**
* Attempt to detect whether the user launched scrcpy by double-clicking
* scrcpy.exe in Windows Explorer.
*
* If so, the console should remain open on error.
*/
static bool
scrcpy_launched_by_double_click(void) {
// No console window
if (GetConsoleWindow() == NULL) {
return false;
}
// Must be interactive
if (!_isatty(_fileno(stdin)) || !_isatty(_fileno(stdout))) {
return false;
}
// Check how many processes share the console
DWORD dummy;
DWORD count = GetConsoleProcessList(&dummy, 1);
// Only this process attached, assume it was started by double-clicking
return count == 1;
}
#endif
bool
scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) {
struct sc_getopt_adapter adapter;
@@ -3401,11 +3431,22 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) {
sc_getopt_adapter_destroy(&adapter);
if (!ret && args->pause_on_exit == SC_PAUSE_ON_EXIT_FALSE) {
if (!ret && args->pause_on_exit == SC_PAUSE_ON_EXIT_UNDEFINED) {
// Check if "--pause-on-exit" is present in the arguments list, because
// it must be taken into account even if command line parsing failed
args->pause_on_exit = sc_get_pause_on_exit(argc, argv);
}
if (args->pause_on_exit == SC_PAUSE_ON_EXIT_UNDEFINED) {
args->pause_on_exit = SC_PAUSE_ON_EXIT_FALSE;
#ifdef _WIN32
if (scrcpy_launched_by_double_click()) {
args->pause_on_exit = SC_PAUSE_ON_EXIT_IF_ERROR;
}
#endif
}
assert(args->pause_on_exit != SC_PAUSE_ON_EXIT_UNDEFINED);
return ret;
}

View File

@@ -8,6 +8,7 @@
#include "options.h"
enum sc_pause_on_exit {
SC_PAUSE_ON_EXIT_UNDEFINED,
SC_PAUSE_ON_EXIT_TRUE,
SC_PAUSE_ON_EXIT_FALSE,
SC_PAUSE_ON_EXIT_IF_ERROR,

88
app/src/disconnect.c Normal file
View File

@@ -0,0 +1,88 @@
#include "disconnect.h"
#include <assert.h>
#include "icon.h"
#include "util/log.h"
static int
run(void *userdata) {
struct sc_disconnect *d = userdata;
SDL_Surface *icon = sc_icon_load(SC_ICON_FILENAME_DISCONNECTED);
if (icon) {
d->cbs->on_icon_loaded(d, icon, d->cbs_userdata);
} else {
LOGE("Could not load disconnected icon");
}
sc_mutex_lock(&d->mutex);
bool timed_out = false;
while (!d->interrupted && !timed_out) {
timed_out = !sc_cond_timedwait(&d->cond, &d->mutex, d->deadline);
}
bool interrupted = d->interrupted;
sc_mutex_unlock(&d->mutex);
if (!interrupted) {
d->cbs->on_timeout(d, d->cbs_userdata);
}
return 0;
}
bool
sc_disconnect_start(struct sc_disconnect *d, sc_tick deadline,
const struct sc_disconnect_callbacks *cbs,
void *cbs_userdata) {
bool ok = sc_mutex_init(&d->mutex);
if (!ok) {
return false;
}
ok = sc_cond_init(&d->cond);
if (!ok) {
goto error_destroy_mutex;
}
d->deadline = deadline;
d->interrupted = false;
assert(cbs && cbs->on_icon_loaded && cbs->on_timeout);
d->cbs = cbs;
d->cbs_userdata = cbs_userdata;
ok = sc_thread_create(&d->thread, run, "scrcpy-dis", d);
if (!ok) {
goto error_destroy_cond;
}
return true;
error_destroy_cond:
sc_cond_destroy(&d->cond);
error_destroy_mutex:
sc_mutex_destroy(&d->mutex);
return false;
}
void
sc_disconnect_interrupt(struct sc_disconnect *d) {
sc_mutex_lock(&d->mutex);
d->interrupted = true;
sc_mutex_unlock(&d->mutex);
// wake up blocking wait
sc_cond_signal(&d->cond);
}
void
sc_disconnect_join(struct sc_disconnect *d) {
sc_thread_join(&d->thread, NULL);
}
void
sc_disconnect_destroy(struct sc_disconnect *d) {
sc_cond_destroy(&d->cond);
sc_mutex_destroy(&d->mutex);
}

47
app/src/disconnect.h Normal file
View File

@@ -0,0 +1,47 @@
#ifndef SC_DISCONNECT
#define SC_DISCONNECT
#include "common.h"
#include "SDL3/SDL_surface.h"
#include "util/tick.h"
#include "util/thread.h"
// Tool to handle loading the icon and signal timeout when the device is
// unexpectedly disconnected
struct sc_disconnect {
sc_tick deadline;
struct sc_thread thread;
struct sc_mutex mutex;
struct sc_cond cond;
bool interrupted;
const struct sc_disconnect_callbacks *cbs;
void *cbs_userdata;
};
struct sc_disconnect_callbacks {
// Called when the disconnected icon is loaded
void (*on_icon_loaded)(struct sc_disconnect *d, SDL_Surface *icon,
void *userdata);
// Called when the timeout expired (the scrcpy window must be closed)
void (*on_timeout)(struct sc_disconnect *d, void *userdata);
};
bool
sc_disconnect_start(struct sc_disconnect *d, sc_tick deadline,
const struct sc_disconnect_callbacks *cbs,
void *cbs_userdata);
void
sc_disconnect_interrupt(struct sc_disconnect *d);
void
sc_disconnect_join(struct sc_disconnect *d);
void
sc_disconnect_destroy(struct sc_disconnect *d);
#endif

View File

@@ -6,9 +6,13 @@
#include "util/thread.h"
bool
sc_push_event_impl(uint32_t type, const char *name) {
SDL_Event event;
event.type = type;
sc_push_event_impl(uint32_t type, void* ptr, const char *name) {
SDL_Event event = {
.user = {
.type = type,
.data1 = ptr,
}
};
bool ok = SDL_PushEvent(&event);
if (!ok) {
LOGE("Could not post %s event: %s", name, SDL_GetError());

View File

@@ -13,18 +13,20 @@ enum {
SC_EVENT_DEVICE_DISCONNECTED,
SC_EVENT_SERVER_CONNECTION_FAILED,
SC_EVENT_SERVER_CONNECTED,
SC_EVENT_USB_DEVICE_DISCONNECTED,
SC_EVENT_DEMUXER_ERROR,
SC_EVENT_RECORDER_ERROR,
SC_EVENT_TIME_LIMIT_REACHED,
SC_EVENT_CONTROLLER_ERROR,
SC_EVENT_AOA_OPEN_ERROR,
SC_EVENT_DISCONNECTED_ICON_LOADED,
SC_EVENT_DISCONNECTED_TIMEOUT,
};
bool
sc_push_event_impl(uint32_t type, const char *name);
sc_push_event_impl(uint32_t type, void* ptr, const char *name);
#define sc_push_event(TYPE) sc_push_event_impl(TYPE, # TYPE)
#define sc_push_event(TYPE) sc_push_event_impl(TYPE, NULL, # TYPE)
#define sc_push_event_with_data(TYPE, PTR) sc_push_event_impl(TYPE, PTR, # TYPE)
typedef void (*sc_runnable_fn)(void *userdata);

View File

@@ -14,33 +14,37 @@
#include "config.h"
#include "util/env.h"
#ifdef PORTABLE
# include "util/file.h"
#endif
#include "util/file.h"
#include "util/log.h"
#define SCRCPY_PORTABLE_ICON_FILENAME "icon.png"
#define SCRCPY_DEFAULT_ICON_PATH \
PREFIX "/share/icons/hicolor/256x256/apps/scrcpy.png"
#define SCRCPY_DEFAULT_ICON_DIR PREFIX "/share/icons/hicolor/256x256/apps"
static char *
get_icon_path(void) {
char *icon_path = sc_get_env("SCRCPY_ICON_PATH");
if (icon_path) {
get_icon_path(const char *filename) {
char *icon_path;
char *icon_dir = sc_get_env("SCRCPY_ICON_DIR");
if (icon_dir) {
// if the envvar is set, use it
LOGD("Using SCRCPY_ICON_PATH: %s", icon_path);
icon_path = sc_file_build_path(icon_dir, filename);
free(icon_dir);
if (!icon_path) {
LOG_OOM();
return NULL;
}
LOGD("Using icon from SCRCPY_ICON_DIR: %s", icon_path);
return icon_path;
}
#ifndef PORTABLE
LOGD("Using icon: " SCRCPY_DEFAULT_ICON_PATH);
icon_path = strdup(SCRCPY_DEFAULT_ICON_PATH);
icon_path = sc_file_build_path(SCRCPY_DEFAULT_ICON_DIR, filename);
if (!icon_path) {
LOG_OOM();
return NULL;
}
LOGD("Using icon: %s", icon_path);
#else
icon_path = sc_file_get_local_path(SCRCPY_PORTABLE_ICON_FILENAME);
icon_path = sc_file_get_local_path(filename);
if (!icon_path) {
LOGE("Could not get icon path");
return NULL;
@@ -177,7 +181,7 @@ to_sdl_pixel_format(enum AVPixelFormat fmt) {
}
static SDL_Surface *
load_from_path(const char *path) {
sc_icon_load_from_full_path(const char *path) {
AVFrame *frame = decode_image(path);
if (!frame) {
return NULL;
@@ -274,19 +278,19 @@ error:
}
SDL_Surface *
scrcpy_icon_load(void) {
char *icon_path = get_icon_path();
sc_icon_load(const char *filename) {
char *icon_path = get_icon_path(filename);
if (!icon_path) {
return NULL;
}
SDL_Surface *icon = load_from_path(icon_path);
SDL_Surface *icon = sc_icon_load_from_full_path(icon_path);
free(icon_path);
return icon;
}
void
scrcpy_icon_destroy(SDL_Surface *icon) {
sc_icon_destroy(SDL_Surface *icon) {
SDL_PropertiesID props = SDL_GetSurfaceProperties(icon);
assert(props);
AVFrame *frame = SDL_GetPointerProperty(props, "sc_frame", NULL);

View File

@@ -5,10 +5,13 @@
#include <SDL3/SDL_surface.h>
#define SC_ICON_FILENAME_SCRCPY "scrcpy.png"
#define SC_ICON_FILENAME_DISCONNECTED "disconnected.png"
SDL_Surface *
scrcpy_icon_load(void);
sc_icon_load(const char *filename);
void
scrcpy_icon_destroy(SDL_Surface *icon);
sc_icon_destroy(SDL_Surface *icon);
#endif

View File

@@ -39,7 +39,7 @@ main_scrcpy(int argc, char *argv[]) {
.opts = scrcpy_options_default,
.help = false,
.version = false,
.pause_on_exit = SC_PAUSE_ON_EXIT_FALSE,
.pause_on_exit = SC_PAUSE_ON_EXIT_UNDEFINED,
};
#ifndef NDEBUG

View File

@@ -173,6 +173,9 @@ event_loop(struct scrcpy *s, bool has_screen) {
switch (event.type) {
case SC_EVENT_DEVICE_DISCONNECTED:
LOGW("Device disconnected");
if (has_screen) {
sc_screen_handle_event(&s->screen, &event);
}
return SCRCPY_EXIT_DISCONNECTED;
case SC_EVENT_DEMUXER_ERROR:
LOGE("Demuxer error");
@@ -209,17 +212,18 @@ event_loop(struct scrcpy *s, bool has_screen) {
}
static void
terminate_event_loop(void) {
terminate_runnables_on_event_loop(void) {
sc_reject_new_runnables();
SDL_Event event;
while (SDL_PollEvent(&event)) {
if (event.type == SC_EVENT_RUN_ON_MAIN_THREAD) {
// Make sure all posted runnables are run, to avoid memory leaks
sc_runnable_fn run = event.user.data1;
void *userdata = event.user.data2;
run(userdata);
}
while (SDL_PeepEvents(&event, 1, SDL_GETEVENT,
SC_EVENT_RUN_ON_MAIN_THREAD,
SC_EVENT_RUN_ON_MAIN_THREAD) == 1) {
assert(event.type == SC_EVENT_RUN_ON_MAIN_THREAD);
// Make sure all posted runnables are run, to avoid memory leaks
sc_runnable_fn run = event.user.data1;
void *userdata = event.user.data2;
run(userdata);
}
}
@@ -414,6 +418,7 @@ scrcpy(struct scrcpy_options *options) {
bool screen_initialized = false;
bool timeout_initialized = false;
bool timeout_started = false;
bool disconnected = false;
struct sc_acksync *acksync = NULL;
@@ -946,15 +951,8 @@ aoa_complete:
}
ret = event_loop(s, options->window);
terminate_event_loop();
LOGD("quit...");
if (options->window) {
// Close the window immediately on closing, because screen_destroy()
// may only be called once the video demuxer thread is joined (it may
// take time)
sc_screen_hide_window(&s->screen);
}
terminate_runnables_on_event_loop();
disconnected = ret == SCRCPY_EXIT_DISCONNECTED;
end:
if (timeout_started) {
@@ -999,6 +997,17 @@ end:
sc_server_stop(&s->server);
}
if (screen_initialized) {
if (disconnected) {
sc_screen_handle_disconnection(&s->screen);
}
LOGD("Quit...");
// Close the window immediately, because sc_screen_destroy() may only be
// called once the video demuxer thread is joined (it may take time)
sc_screen_hide_window(&s->screen);
}
if (timeout_started) {
sc_timeout_join(&s->timeout);
}

View File

@@ -184,7 +184,7 @@ compute_content_rect(struct sc_size render_size, struct sc_size content_size,
static void
sc_screen_update_content_rect(struct sc_screen *screen) {
// Only upscale video frames, not icon
bool can_upscale = screen->video;
bool can_upscale = screen->video && !screen->disconnected;
struct sc_size render_size =
sc_sdl_get_render_output_size(screen->renderer);
@@ -198,7 +198,7 @@ sc_screen_update_content_rect(struct sc_screen *screen) {
// changed, so that the content rectangle is recomputed
static void
sc_screen_render(struct sc_screen *screen, bool update_content_rect) {
assert(!screen->video || screen->has_video_window);
assert(screen->window_shown);
if (update_content_rect) {
sc_screen_update_content_rect(screen);
@@ -210,7 +210,9 @@ sc_screen_render(struct sc_screen *screen, bool update_content_rect) {
bool ok = false;
SDL_Texture *texture = screen->tex.texture;
if (!texture) {
LOGW("No texture to render");
if (!screen->disconnected) {
LOGW("No texture to render");
}
goto end;
}
@@ -342,10 +344,12 @@ sc_screen_init(struct sc_screen *screen,
const struct sc_screen_params *params) {
screen->resize_pending = false;
screen->has_frame = false;
screen->has_video_window = false;
screen->window_shown = false;
screen->paused = false;
screen->resume_frame = NULL;
screen->orientation = SC_ORIENTATION_0;
screen->disconnected = false;
screen->disconnect_started = false;
screen->video = params->video;
screen->camera = params->camera;
@@ -457,7 +461,7 @@ sc_screen_init(struct sc_screen *screen,
goto error_destroy_texture;
}
SDL_Surface *icon = scrcpy_icon_load();
SDL_Surface *icon = sc_icon_load(SC_ICON_FILENAME_SCRCPY);
if (icon) {
if (!SDL_SetWindowIcon(screen->window, icon)) {
LOGW("Could not set window icon: %s", SDL_GetError());
@@ -472,7 +476,7 @@ sc_screen_init(struct sc_screen *screen,
}
}
scrcpy_icon_destroy(icon);
sc_icon_destroy(icon);
} else {
// not fatal
LOGE("Could not load icon");
@@ -533,6 +537,7 @@ sc_screen_init(struct sc_screen *screen,
if (!screen->video) {
// Show the window immediately
screen->window_shown = true;
sc_sdl_show_window(screen->window);
if (sc_screen_is_relative_mode(screen)) {
@@ -589,6 +594,7 @@ sc_screen_show_initial_window(struct sc_screen *screen) {
sc_fps_counter_start(&screen->fps_counter);
}
screen->window_shown = true;
sc_sdl_show_window(screen->window);
sc_screen_update_content_rect(screen);
}
@@ -596,6 +602,7 @@ sc_screen_show_initial_window(struct sc_screen *screen) {
void
sc_screen_hide_window(struct sc_screen *screen) {
sc_sdl_hide_window(screen->window);
screen->window_shown = false;
}
void
@@ -603,9 +610,19 @@ sc_screen_interrupt(struct sc_screen *screen) {
sc_fps_counter_interrupt(&screen->fps_counter);
}
static void
sc_screen_interrupt_disconnect(struct sc_screen *screen) {
if (screen->disconnect_started) {
sc_disconnect_interrupt(&screen->disconnect);
}
}
void
sc_screen_join(struct sc_screen *screen) {
sc_fps_counter_join(&screen->fps_counter);
if (screen->disconnect_started) {
sc_disconnect_join(&screen->disconnect);
}
}
void
@@ -613,6 +630,9 @@ sc_screen_destroy(struct sc_screen *screen) {
#ifndef NDEBUG
assert(!screen->open);
#endif
if (screen->disconnect_started) {
sc_disconnect_destroy(&screen->disconnect);
}
sc_texture_destroy(&screen->tex);
av_frame_free(&screen->frame);
#ifdef SC_DISPLAY_FORCE_OPENGL_CORE_PROFILE
@@ -622,6 +642,17 @@ sc_screen_destroy(struct sc_screen *screen) {
SDL_DestroyWindow(screen->window);
sc_fps_counter_destroy(&screen->fps_counter);
sc_frame_buffer_destroy(&screen->fb);
SDL_Event event;
int nevents = SDL_PeepEvents(&event, 1, SDL_GETEVENT,
SC_EVENT_DISCONNECTED_ICON_LOADED,
SC_EVENT_DISCONNECTED_ICON_LOADED);
if (nevents == 1) {
assert(event.type == SC_EVENT_DISCONNECTED_ICON_LOADED);
// The event was posted, but not handled, the icon must be freed
SDL_Surface *dangling_icon = event.user.data1;
sc_icon_destroy(dangling_icon);
}
}
static void
@@ -723,8 +754,7 @@ sc_screen_apply_frame(struct sc_screen *screen) {
}
assert(screen->has_frame);
if (!screen->has_video_window) {
screen->has_video_window = true;
if (!screen->window_shown) {
// this is the very first frame, show the window
sc_screen_show_initial_window(screen);
@@ -852,10 +882,29 @@ sc_screen_resize_to_pixel_perfect(struct sc_screen *screen) {
content_size.height);
}
static void
sc_disconnect_on_icon_loaded(struct sc_disconnect *d, SDL_Surface *icon,
void *userdata) {
(void) d;
(void) userdata;
bool ok = sc_push_event_with_data(SC_EVENT_DISCONNECTED_ICON_LOADED, icon);
if (!ok) {
sc_icon_destroy(icon);
}
}
static void
sc_disconnect_on_timeout(struct sc_disconnect *d, void *userdata) {
(void) d;
(void) userdata;
bool ok = sc_push_event(SC_EVENT_DISCONNECTED_TIMEOUT);
(void) ok; // ignore failure
}
void
sc_screen_handle_event(struct sc_screen *screen, const SDL_Event *event) {
// !video implies !has_video_window
assert(screen->video || !screen->has_video_window);
switch (event->type) {
case SC_EVENT_NEW_FRAME: {
bool ok = sc_screen_update_frame(screen);
@@ -865,32 +914,54 @@ sc_screen_handle_event(struct sc_screen *screen, const SDL_Event *event) {
return;
}
case SDL_EVENT_WINDOW_EXPOSED:
if (!screen->video || screen->has_video_window) {
sc_screen_render(screen, true);
}
sc_screen_render(screen, true);
return;
case SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED:
if (screen->has_video_window) {
// This event can be triggered before the window is shown
if (screen->window_shown) {
sc_screen_render(screen, true);
}
return;
case SDL_EVENT_WINDOW_RESTORED:
if (screen->has_video_window && is_windowed(screen)) {
if (screen->video && is_windowed(screen)) {
apply_pending_resize(screen);
sc_screen_render(screen, true);
}
return;
case SDL_EVENT_WINDOW_ENTER_FULLSCREEN:
LOGD("Switched to fullscreen mode");
assert(screen->has_video_window);
assert(screen->video);
return;
case SDL_EVENT_WINDOW_LEAVE_FULLSCREEN:
LOGD("Switched to windowed mode");
assert(screen->has_video_window);
assert(screen->video);
if (is_windowed(screen)) {
apply_pending_resize(screen);
sc_screen_render(screen, true);
}
return;
case SC_EVENT_DEVICE_DISCONNECTED:
assert(!screen->disconnected);
screen->disconnected = true;
if (!screen->window_shown) {
// No window open
return;
}
sc_texture_reset(&screen->tex);
sc_screen_render(screen, true);
sc_tick deadline = sc_tick_now() + SC_TICK_FROM_SEC(2);
static const struct sc_disconnect_callbacks cbs = {
.on_icon_loaded = sc_disconnect_on_icon_loaded,
.on_timeout = sc_disconnect_on_timeout,
};
bool ok =
sc_disconnect_start(&screen->disconnect, deadline, &cbs, NULL);
if (ok) {
screen->disconnect_started = true;
}
return;
}
@@ -903,6 +974,49 @@ sc_screen_handle_event(struct sc_screen *screen, const SDL_Event *event) {
sc_input_manager_handle_event(&screen->im, event);
}
void
sc_screen_handle_disconnection(struct sc_screen *screen) {
if (!screen->window_shown) {
// No window open, quit immediately
return;
}
if (!screen->disconnect_started) {
// If sc_disconnect_start() failed, quit immediately
return;
}
SDL_Event event;
while (SDL_WaitEvent(&event)) {
switch (event.type) {
case SC_EVENT_DISCONNECTED_ICON_LOADED: {
SDL_Surface *icon_disconnected = event.user.data1;
assert(icon_disconnected);
bool ok = sc_texture_set_from_surface(&screen->tex, icon_disconnected);
if (ok) {
screen->content_size.width = icon_disconnected->w;
screen->content_size.height = icon_disconnected->h;
sc_screen_render(screen, true);
} else {
// not fatal
LOGE("Could not set disconnected icon");
}
sc_icon_destroy(icon_disconnected);
break;
}
case SC_EVENT_DISCONNECTED_TIMEOUT:
LOGD("Closing after device disconnection");
return;
case SDL_EVENT_QUIT:
LOGD("User requested to quit");
sc_screen_interrupt_disconnect(screen);
return;
}
}
}
struct sc_point
sc_screen_convert_drawable_to_frame_coords(struct sc_screen *screen,
int32_t x, int32_t y) {

View File

@@ -12,6 +12,7 @@
#include "controller.h"
#include "coords.h"
#include "disconnect.h"
#include "fps_counter.h"
#include "frame_buffer.h"
#include "input_manager.h"
@@ -71,12 +72,16 @@ struct sc_screen {
// rectangle of the content (excluding black borders)
struct SDL_FRect rect;
bool has_frame;
bool has_video_window;
bool window_shown;
AVFrame *frame;
bool paused;
AVFrame *resume_frame;
bool disconnected;
bool disconnect_started;
struct sc_disconnect disconnect;
};
struct sc_screen_params {
@@ -116,7 +121,7 @@ bool
sc_screen_init(struct sc_screen *screen, const struct sc_screen_params *params);
// request to interrupt any inner thread
// must be called before screen_join()
// must be called before sc_screen_join()
void
sc_screen_interrupt(struct sc_screen *screen);
@@ -160,6 +165,10 @@ sc_screen_set_paused(struct sc_screen *screen, bool paused);
void
sc_screen_handle_event(struct sc_screen *screen, const SDL_Event *event);
// run the event loop once the device is disconnected
void
sc_screen_handle_disconnection(struct sc_screen *screen);
// convert point from window coordinates to frame coordinates
// x and y are expressed in pixels
struct sc_point

View File

@@ -225,3 +225,11 @@ sc_texture_set_from_surface(struct sc_texture *tex, SDL_Surface *surface) {
return true;
}
void
sc_texture_reset(struct sc_texture *tex) {
if (tex->texture) {
SDL_DestroyTexture(tex->texture);
tex->texture = NULL;
}
}

View File

@@ -41,4 +41,7 @@ sc_texture_set_from_frame(struct sc_texture *tex, const AVFrame *frame);
bool
sc_texture_set_from_surface(struct sc_texture *tex, SDL_Surface *surface);
void
sc_texture_reset(struct sc_texture *tex);
#endif

View File

@@ -67,7 +67,8 @@ sc_packet_source_sinks_push_session(struct sc_packet_source *source,
assert(source->sink_count);
for (unsigned i = 0; i < source->sink_count; ++i) {
struct sc_packet_sink *sink = source->sinks[i];
if (!sink->ops->push_session(sink, session)) {
if (sink->ops->push_session
&& !sink->ops->push_session(sink, session)) {
return false;
}
}

View File

@@ -31,7 +31,7 @@ sc_usb_on_disconnected(struct sc_usb *usb, void *userdata) {
(void) usb;
(void) userdata;
sc_push_event(SC_EVENT_USB_DEVICE_DISCONNECTED);
sc_push_event(SC_EVENT_DEVICE_DISCONNECTED);
}
static enum scrcpy_exit_code
@@ -39,8 +39,9 @@ event_loop(struct scrcpy_otg *s) {
SDL_Event event;
while (SDL_WaitEvent(&event)) {
switch (event.type) {
case SC_EVENT_USB_DEVICE_DISCONNECTED:
case SC_EVENT_DEVICE_DISCONNECTED:
LOGW("Device disconnected");
sc_screen_handle_event(&s->screen, &event);
return SCRCPY_EXIT_DISCONNECTED;
case SC_EVENT_AOA_OPEN_ERROR:
LOGE("AOA open error");
@@ -96,6 +97,7 @@ scrcpy_otg(struct scrcpy_options *options) {
bool aoa_started = false;
bool aoa_initialized = false;
bool screen_initialized = false;
bool disconnected = false;
#ifdef _WIN32
// On Windows, only one process could open a USB device
@@ -221,7 +223,7 @@ scrcpy_otg(struct scrcpy_options *options) {
usb_device_initialized = false;
ret = event_loop(s);
LOGD("quit...");
disconnected = ret == SCRCPY_EXIT_DISCONNECTED;
end:
if (aoa_started) {
@@ -231,6 +233,14 @@ end:
if (screen_initialized) {
sc_screen_interrupt(&s->screen);
if (disconnected) {
sc_screen_handle_disconnection(&s->screen);
}
LOGD("Quit...");
// Close the window immediately
sc_screen_hide_window(&s->screen);
}
if (mp) {

View File

@@ -5,6 +5,25 @@
#include "util/log.h"
char *
sc_file_build_path(const char *dir, const char *name) {
size_t dir_len = strlen(dir);
size_t name_len = strlen(name);
size_t len = dir_len + name_len + 2; // +2: '/' and '\0'
char *path = malloc(len);
if (!path) {
LOG_OOM();
return NULL;
}
memcpy(path, dir, dir_len);
path[dir_len] = SC_PATH_SEPARATOR;
// namelen + 1 to copy the final '\0'
memcpy(&path[dir_len + 1], name, name_len + 1);
return path;
}
char *
sc_file_get_local_path(const char *name) {
char *executable_path = sc_file_get_executable_path();
@@ -25,24 +44,9 @@ sc_file_get_local_path(const char *name) {
*p = '\0'; // modify executable_path in place
char *dir = executable_path;
size_t dirlen = strlen(dir);
size_t namelen = strlen(name);
size_t len = dirlen + namelen + 2; // +2: '/' and '\0'
char *file_path = malloc(len);
if (!file_path) {
LOG_OOM();
free(executable_path);
return NULL;
}
memcpy(file_path, dir, dirlen);
file_path[dirlen] = SC_PATH_SEPARATOR;
// namelen + 1 to copy the final '\0'
memcpy(&file_path[dirlen + 1], name, namelen + 1);
char *file_path = sc_file_build_path(dir, name);
free(executable_path);
return file_path;
}

View File

@@ -40,6 +40,15 @@ sc_file_get_executable_path(void);
char *
sc_file_get_local_path(const char *name);
/**
* Return the concatenation of dir, the path separator and the filename.
*
* The result must be freed by the caller using free(). It may return NULL on
* error.
*/
char *
sc_file_build_path(const char *dir, const char *filename);
/**
* Indicate if the file exists and is not a directory
*/

View File

@@ -163,6 +163,45 @@ static void test_adb_devices_spaces(void) {
sc_adb_devices_destroy(&vec);
}
static void test_adb_devices_serial_with_spaces(void) {
char output[] =
"List of devices attached\n"
"adb-0123456789AB-CDdefg (2)._adb-tls-connect._tcp device "
"product:blazer model:Pixel_10_Pro device:blazer transport_id:3\n";
struct sc_vec_adb_devices vec = SC_VECTOR_INITIALIZER;
bool ok = sc_adb_parse_devices(output, &vec);
assert(ok);
assert(vec.size == 1);
struct sc_adb_device *device = &vec.data[0];
assert(!strcmp("adb-0123456789AB-CDdefg (2)._adb-tls-connect._tcp",
device->serial));
assert(!strcmp("device", device->state));
assert(!strcmp("Pixel_10_Pro", device->model));
sc_adb_devices_destroy(&vec);
}
static void test_adb_devices_with_devpath_without_colon(void) {
char output[] =
"List of devices attached\n"
"12345678 device 3-1 product:manet model:23117RK66C "
"device:manet transport_id:2\n";
struct sc_vec_adb_devices vec = SC_VECTOR_INITIALIZER;
bool ok = sc_adb_parse_devices(output, &vec);
assert(ok);
assert(vec.size == 1);
struct sc_adb_device *device = &vec.data[0];
assert(!strcmp("12345678", device->serial));
assert(!strcmp("device", device->state));
assert(!strcmp("23117RK66C", device->model));
sc_adb_devices_destroy(&vec);
}
static void test_get_ip_single_line(void) {
char ip_route[] = "192.168.1.0/24 dev wlan0 proto kernel scope link src "
"192.168.12.34\r\r\n";
@@ -265,6 +304,8 @@ int main(int argc, char *argv[]) {
test_adb_devices_without_header();
test_adb_devices_corrupted();
test_adb_devices_spaces();
test_adb_devices_serial_with_spaces();
test_adb_devices_with_devpath_without_colon();
test_get_ip_single_line();
test_get_ip_single_line_without_eol();

View File

@@ -72,18 +72,6 @@ Documentation for command line arguments is available:
- `scrcpy --help`
- on [github](/README.md)
To start scrcpy directly without opening a terminal, double-click on one of
these files:
- `scrcpy-console.bat`: start with a terminal open (it will close when scrcpy
terminates, unless an error occurs);
- `scrcpy-noconsole.vbs`: start without a terminal (but you won't see any error
message).
_Avoid double-clicking on `scrcpy.exe` directly: on error, the terminal would
close immediately and you won't have time to read any error message (this
executable is intended to be run from the terminal). Use `scrcpy-console.bat`
instead._
If you plan to always use the same arguments, create a file `myscrcpy.bat`
(enable [show file extensions] to avoid confusion) containing your command, For
example:
@@ -92,9 +80,17 @@ example:
scrcpy --prefer-text --turn-screen-off --stay-awake
```
Add `--pause-on-exit=if-error` if you want the console to remain open when
scrcpy fails:
```bash
scrcpy --prefer-text --turn-screen-off --stay-awake --pause-on-exit=if-error
```
[show file extensions]: https://www.howtogeek.com/205086/beginner-how-to-make-windows-show-file-extensions/
Then just double-click on that file.
Then just double-click on that file to run it.
You could also edit (a copy of) `scrcpy-console.bat` or `scrcpy-noconsole.vbs`
to add some arguments.
To start scrcpy without opening a terminal, double-click `scrcpy-noconsole.vbs`
(note that errors won't be shown). To pass arguments, edit (a copy of)
`scrcpy-noconsole.vbs` add and the desired arguments.

View File

@@ -22,9 +22,12 @@ app/deps/libusb.sh linux native static
DEPS_INSTALL_DIR="$PWD/app/deps/work/install/linux-native-static"
ADB_INSTALL_DIR="$PWD/app/deps/work/install/adb-linux"
# Never fall back to system libs
unset PKG_CONFIG_PATH
export PKG_CONFIG_LIBDIR="$DEPS_INSTALL_DIR/lib/pkgconfig"
rm -rf "$LINUX_BUILD_DIR"
meson setup "$LINUX_BUILD_DIR" \
--pkg-config-path="$DEPS_INSTALL_DIR/lib/pkgconfig" \
-Dc_args="-I$DEPS_INSTALL_DIR/include" \
-Dc_link_args="-L$DEPS_INSTALL_DIR/lib" \
--buildtype=release \
@@ -38,6 +41,6 @@ ninja -C "$LINUX_BUILD_DIR"
# Group intermediate outputs into a 'dist' directory
mkdir -p "$LINUX_BUILD_DIR/dist"
cp "$LINUX_BUILD_DIR"/app/scrcpy "$LINUX_BUILD_DIR/dist/"
cp app/data/icon.png "$LINUX_BUILD_DIR/dist/"
cp app/data/scrcpy.png "$LINUX_BUILD_DIR/dist/"
cp app/scrcpy.1 "$LINUX_BUILD_DIR/dist/"
cp -r "$ADB_INSTALL_DIR"/. "$LINUX_BUILD_DIR/dist/"

View File

@@ -22,9 +22,12 @@ app/deps/libusb.sh macos native static
DEPS_INSTALL_DIR="$PWD/app/deps/work/install/macos-native-static"
ADB_INSTALL_DIR="$PWD/app/deps/work/install/adb-macos"
# Never fall back to system libs
unset PKG_CONFIG_PATH
export PKG_CONFIG_LIBDIR="$DEPS_INSTALL_DIR/lib/pkgconfig"
rm -rf "$MACOS_BUILD_DIR"
meson setup "$MACOS_BUILD_DIR" \
--pkg-config-path="$DEPS_INSTALL_DIR/lib/pkgconfig" \
-Dc_args="-I$DEPS_INSTALL_DIR/include" \
-Dc_link_args="-L$DEPS_INSTALL_DIR/lib" \
--buildtype=release \
@@ -38,6 +41,6 @@ ninja -C "$MACOS_BUILD_DIR"
# Group intermediate outputs into a 'dist' directory
mkdir -p "$MACOS_BUILD_DIR/dist"
cp "$MACOS_BUILD_DIR"/app/scrcpy "$MACOS_BUILD_DIR/dist/"
cp app/data/icon.png "$MACOS_BUILD_DIR/dist/"
cp app/data/scrcpy.png "$MACOS_BUILD_DIR/dist/"
cp app/scrcpy.1 "$MACOS_BUILD_DIR/dist/"
cp -r "$ADB_INSTALL_DIR"/. "$MACOS_BUILD_DIR/dist/"

View File

@@ -29,9 +29,12 @@ app/deps/libusb.sh $WINXX cross shared
DEPS_INSTALL_DIR="$PWD/app/deps/work/install/$WINXX-cross-shared"
ADB_INSTALL_DIR="$PWD/app/deps/work/install/adb-windows"
# Never fall back to system libs
unset PKG_CONFIG_PATH
export PKG_CONFIG_LIBDIR="$DEPS_INSTALL_DIR/lib/pkgconfig"
rm -rf "$WINXX_BUILD_DIR"
meson setup "$WINXX_BUILD_DIR" \
--pkg-config-path="$DEPS_INSTALL_DIR/lib/pkgconfig" \
-Dc_args="-I$DEPS_INSTALL_DIR/include" \
-Dc_link_args="-L$DEPS_INSTALL_DIR/lib" \
--cross-file=cross_$WINXX.txt \
@@ -45,9 +48,8 @@ ninja -C "$WINXX_BUILD_DIR"
# Group intermediate outputs into a 'dist' directory
mkdir -p "$WINXX_BUILD_DIR/dist"
cp "$WINXX_BUILD_DIR"/app/scrcpy.exe "$WINXX_BUILD_DIR/dist/"
cp app/data/scrcpy-console.bat "$WINXX_BUILD_DIR/dist/"
cp app/data/scrcpy-noconsole.vbs "$WINXX_BUILD_DIR/dist/"
cp app/data/icon.png "$WINXX_BUILD_DIR/dist/"
cp app/data/scrcpy.png "$WINXX_BUILD_DIR/dist/"
cp app/data/open_a_terminal_here.bat "$WINXX_BUILD_DIR/dist/"
cp "$DEPS_INSTALL_DIR"/bin/*.dll "$WINXX_BUILD_DIR/dist/"
cp -r "$ADB_INSTALL_DIR"/. "$WINXX_BUILD_DIR/dist/"

2
run
View File

@@ -20,6 +20,6 @@ then
exit 1
fi
SCRCPY_ICON_PATH="app/data/icon.png" \
SCRCPY_ICON_DIR="app/data" \
SCRCPY_SERVER_PATH="$BUILDDIR/server/scrcpy-server" \
"$BUILDDIR/app/scrcpy" "$@"

View File

@@ -266,14 +266,14 @@ public final class AudioEncoder implements AsyncProcessor {
outputThread.start();
waitEnded();
} catch (ConfigurationException e) {
// Notify the error to make scrcpy exit
streamer.writeDisableStream(true);
throw e;
} catch (Throwable e) {
} catch (AudioCaptureException e) {
// Notify the client that the audio could not be captured
streamer.writeDisableStream(false);
throw e;
} catch (Throwable e) {
// Notify the error to make scrcpy exit
streamer.writeDisableStream(true);
throw e;
} finally {
// Cleanup everything (either at the end or on error at any step of the initialization)
if (mediaCodecThread != null) {

View File

@@ -264,6 +264,10 @@ public class SurfaceEncoder implements AsyncProcessor {
format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, DEFAULT_I_FRAME_INTERVAL);
// display the very first frame, and recover from bad quality when no new frames
format.setLong(MediaFormat.KEY_REPEAT_PREVIOUS_FRAME_AFTER, REPEAT_FRAME_DELAY_US); // µs
// real-time priority
format.setInteger(MediaFormat.KEY_PRIORITY, 0);
// output 1 frame as soon as 1 frame is queued
format.setInteger(MediaFormat.KEY_LATENCY, 1);
if (maxFps > 0) {
// The key existed privately before Android 10:
// <https://android.googlesource.com/platform/frameworks/base/+/625f0aad9f7a259b6881006ad8710adce57d1384%5E%21/>