Compare commits

..

13 Commits

Author SHA1 Message Date
Romain Vimont
fb05c25528 Merge branches 'windows-process', 'adb-devices-parsing' and 'tcp-mdns' into issue6248.1 2026-02-09 22:56:59 +01:00
Romain Vimont
1350f19f62 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>
2026-02-09 22:54:36 +01:00
Romain Vimont
86e614c9a5 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 key-value pairs (containing ':'),
 - then, the preceding token (not containing ':') is the device state,
 - finally, the remaining leading token is the device serial.
2026-02-09 22:51:31 +01:00
Romain Vimont
96733d8da2 Remove sc_str_quote()
It is no longer used.
2026-02-09 22:36:07 +01:00
Romain Vimont
e77f971677 Use proper argument serialization for Windows
Replace the hacky implementation for executing simple commands on
Windows with the proper serialization mechanism.
2026-02-09 22:36:07 +01:00
Romain Vimont
ed2babafda Add utility to serialize windows arguments
Add a function to convert an argv array into a single escaped string to
be passed to CreateProcess() on Windows.

Refs <https://learn.microsoft.com/en-us/cpp/c-language/parsing-c-command-line-arguments>
Refs <https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-createprocessw>
2026-02-09 22:36:07 +01:00
Romain Vimont
cfa06c8b5b 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-09 18:36:54 +01:00
Romain Vimont
1a43012cca Add utility to push an SDL event with data 2026-02-09 18:36:54 +01:00
Romain Vimont
f249494657 Add function to delete current texture 2026-02-09 18:36:54 +01:00
Romain Vimont
57985c1f13 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-09 18:36:54 +01:00
Romain Vimont
a370b6a7a2 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-09 18:36:54 +01:00
Romain Vimont
f0c5d8824f Extract function to build file paths
Add a utility function to create a full path from a directory and a
filename.
2026-02-09 18:36:54 +01:00
Romain Vimont
a94ac3f4aa Rename icon.png to scrcpy.png
This makes the icon name consistent everywhere.
2026-02-09 18:36:54 +01:00
25 changed files with 133 additions and 280 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.6 KiB

After

Width:  |  Height:  |  Size: 5.3 KiB

View File

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

View File

@@ -33,24 +33,6 @@ locate_last_token(const char *s, size_t len, size_t *start, size_t *end) {
*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:
@@ -75,9 +57,9 @@ sc_adb_parse_device(char *line, struct sc_adb_device *device) {
// 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.
// - first, parse all the trailing key-value pairs (containing ':'),
// - then, the preceding token (not containing ':') is the device state,
// - finally, the remaining leading token is the device serial.
//
// Refs:
// - <https://github.com/Genymobile/scrcpy/issues/6248>
@@ -97,9 +79,9 @@ sc_adb_parse_device(char *line, struct sc_adb_device *device) {
if (!strncmp("model:", token, sizeof("model:") - 1)) {
model = &token[sizeof("model:") - 1];
// We only need the model
} else if (is_device_state(token)) {
} else if (!strchr(token, ':')) {
// The first non-key:value token, it's the device state
state = token;
// The device state is the item immediately after the device serial
break;
}

View File

@@ -1729,7 +1729,7 @@ parse_orientation(const char *s, enum sc_orientation *orientation) {
return true;
}
LOGE("Unsupported orientation: %s (expected 0, 90, 180, 270, flip0, "
"flip90, flip180 or flip270)", s);
"flip90, flip180 or flip270)", optarg);
return false;
}
@@ -3371,7 +3371,7 @@ sc_get_pause_on_exit(int argc, char *argv[]) {
}
if (arg[15] != '=') {
// Invalid parameter, ignore
return SC_PAUSE_ON_EXIT_UNDEFINED;
return SC_PAUSE_ON_EXIT_FALSE;
}
const char *value = &arg[16];
if (!strcmp(value, "true")) {
@@ -3380,44 +3380,14 @@ sc_get_pause_on_exit(int argc, char *argv[]) {
if (!strcmp(value, "if-error")) {
return SC_PAUSE_ON_EXIT_IF_ERROR;
}
if (!strcmp(value, "false")) {
return SC_PAUSE_ON_EXIT_FALSE;
}
return SC_PAUSE_ON_EXIT_UNDEFINED;
// Set to false, including when the value is invalid
return SC_PAUSE_ON_EXIT_FALSE;
}
}
return SC_PAUSE_ON_EXIT_UNDEFINED;
return SC_PAUSE_ON_EXIT_FALSE;
}
#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;
@@ -3431,22 +3401,11 @@ 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_UNDEFINED) {
if (!ret && args->pause_on_exit == SC_PAUSE_ON_EXIT_FALSE) {
// 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,7 +8,6 @@
#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,

View File

@@ -16,16 +16,17 @@ run(void *userdata) {
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 (d->deadline != SC_TICK_NONE) {
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);
}
sc_mutex_unlock(&d->mutex);
if (!interrupted) {
d->cbs->on_timeout(d, d->cbs_userdata);
if (!d->interrupted) {
d->cbs->on_timeout(d, d->cbs_userdata);
}
}
return 0;
@@ -45,6 +46,11 @@ sc_disconnect_start(struct sc_disconnect *d, sc_tick deadline,
goto error_destroy_mutex;
}
ok = sc_thread_create(&d->thread, run, "scrcpy-dis", d);
if (!ok) {
goto error_destroy_cond;
}
d->deadline = deadline;
d->interrupted = false;
@@ -52,17 +58,12 @@ sc_disconnect_start(struct sc_disconnect *d, sc_tick deadline,
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);
error_destroy_cond:
sc_cond_destroy(&d->cond);
return false;
}

View File

@@ -6,7 +6,7 @@
#include "util/thread.h"
bool
sc_push_event_impl(uint32_t type, void *ptr, const char *name) {
sc_push_event_impl(uint32_t type, void* ptr, const char *name) {
SDL_Event event = {
.user = {
.type = type,

View File

@@ -9,7 +9,6 @@
enum {
SC_EVENT_NEW_FRAME = SDL_EVENT_USER,
SC_EVENT_OPEN_WINDOW,
SC_EVENT_RUN_ON_MAIN_THREAD,
SC_EVENT_DEVICE_DISCONNECTED,
SC_EVENT_SERVER_CONNECTION_FAILED,
@@ -24,7 +23,7 @@ enum {
};
bool
sc_push_event_impl(uint32_t type, void *ptr, 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, NULL, # TYPE)
#define sc_push_event_with_data(TYPE, PTR) sc_push_event_impl(TYPE, PTR, # TYPE)

View File

@@ -7,7 +7,6 @@
#include "android/input.h"
#include "android/keycodes.h"
#include "events.h"
#include "input_events.h"
#include "screen.h"
#include "shortcut_mod.h"
@@ -47,8 +46,6 @@ sc_input_manager_init(struct sc_input_manager *im,
im->key_repeat = 0;
im->next_sequence = 1; // 0 is reserved for SC_SEQUENCE_INVALID
im->disconnected = false;
}
static void
@@ -352,7 +349,7 @@ apply_orientation_transform(struct sc_input_manager *im,
static void
sc_input_manager_process_text_input(struct sc_input_manager *im,
const SDL_TextInputEvent *event) {
if (im->camera || !im->kp || im->screen->paused || im->disconnected) {
if (im->camera || !im->kp || im->screen->paused) {
return;
}
@@ -420,7 +417,6 @@ sc_input_manager_process_key(struct sc_input_manager *im,
bool control = im->controller;
bool paused = im->screen->paused;
bool video = im->screen->video;
bool disconnected = im->disconnected;
SDL_Keycode sdl_keycode = event->key;
uint16_t mod = event->mod;
@@ -437,7 +433,7 @@ sc_input_manager_process_key(struct sc_input_manager *im,
bool is_shortcut = sc_shortcut_mods_is_shortcut_mod(mods, mod)
|| sc_shortcut_mods_is_shortcut_key(mods, sdl_keycode);
if (down && !repeat && !disconnected) {
if (down && !repeat) {
if (sdl_keycode == im->last_keycode && mod == im->last_mod) {
++im->key_repeat;
} else {
@@ -514,12 +510,6 @@ sc_input_manager_process_key(struct sc_input_manager *im,
return;
}
if (disconnected) {
// Only handle shortcuts that do not interact with the device (since
// it is disconnected)
return;
}
// Flatten conditions to avoid additional indentation levels
if (control) {
// Controls for all sources
@@ -728,7 +718,7 @@ sc_input_manager_get_position(struct sc_input_manager *im, int32_t x,
static void
sc_input_manager_process_mouse_motion(struct sc_input_manager *im,
const SDL_MouseMotionEvent *event) {
if (im->camera || !im->mp || im->screen->paused || im->disconnected) {
if (im->camera || !im->mp || im->screen->paused) {
return;
}
@@ -767,7 +757,7 @@ sc_input_manager_process_mouse_motion(struct sc_input_manager *im,
static void
sc_input_manager_process_touch(struct sc_input_manager *im,
const SDL_TouchFingerEvent *event) {
if (im->camera || !im->mp || im->screen->paused || im->disconnected) {
if (im->camera || !im->mp || im->screen->paused) {
return;
}
@@ -822,7 +812,7 @@ sc_input_manager_process_mouse_button(struct sc_input_manager *im,
// some mouse events do not interact with the device, so process the event
// even if control is disabled
if (im->camera || im->disconnected) {
if (im->camera) {
return;
}
@@ -993,7 +983,7 @@ sc_input_manager_process_mouse_button(struct sc_input_manager *im,
static void
sc_input_manager_process_mouse_wheel(struct sc_input_manager *im,
const SDL_MouseWheelEvent *event) {
if (im->camera || !im->kp || im->screen->paused || im->disconnected) {
if (im->camera || !im->kp || im->screen->paused) {
return;
}
@@ -1023,7 +1013,7 @@ sc_input_manager_process_gamepad_device(struct sc_input_manager *im,
const SDL_GamepadDeviceEvent *event) {
// Handle device added or removed even if paused
if (im->camera || !im->gp || im->disconnected) {
if (im->camera || !im->gp) {
return;
}
@@ -1068,7 +1058,7 @@ sc_input_manager_process_gamepad_device(struct sc_input_manager *im,
static void
sc_input_manager_process_gamepad_axis(struct sc_input_manager *im,
const SDL_GamepadAxisEvent *event) {
if (im->camera || !im->gp || im->screen->paused || im->disconnected) {
if (im->camera || !im->gp || im->screen->paused) {
return;
}
@@ -1088,7 +1078,7 @@ sc_input_manager_process_gamepad_axis(struct sc_input_manager *im,
static void
sc_input_manager_process_gamepad_button(struct sc_input_manager *im,
const SDL_GamepadButtonEvent *event) {
if (im->camera || !im->gp || im->screen->paused || im->disconnected) {
if (im->camera || !im->gp || im->screen->paused) {
return;
}
@@ -1114,7 +1104,7 @@ is_apk(const char *file) {
static void
sc_input_manager_process_file(struct sc_input_manager *im,
const SDL_DropEvent *event) {
if (im->camera || !im->controller || im->disconnected) {
if (im->camera || !im->controller) {
return;
}
@@ -1137,16 +1127,6 @@ sc_input_manager_process_file(struct sc_input_manager *im,
}
}
static void
sc_input_manager_on_device_disconnected(struct sc_input_manager *im) {
im->disconnected = true;
struct sc_fps_counter *fps_counter = &im->screen->fps_counter;
if (sc_fps_counter_is_started(fps_counter)) {
sc_fps_counter_stop(fps_counter);
}
}
void
sc_input_manager_handle_event(struct sc_input_manager *im,
const SDL_Event *event) {
@@ -1187,8 +1167,5 @@ sc_input_manager_handle_event(struct sc_input_manager *im,
case SDL_EVENT_DROP_FILE:
sc_input_manager_process_file(im, &event->drop);
break;
case SC_EVENT_DEVICE_DISCONNECTED:
sc_input_manager_on_device_disconnected(im);
break;
}
}

View File

@@ -46,8 +46,6 @@ struct sc_input_manager {
uint16_t last_mod;
uint64_t next_sequence; // used for request acknowledgements
bool disconnected;
};
struct sc_input_manager_params {

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_UNDEFINED,
.pause_on_exit = SC_PAUSE_ON_EXIT_FALSE,
};
#ifndef NDEBUG

View File

@@ -212,18 +212,17 @@ event_loop(struct scrcpy *s, bool has_screen) {
}
static void
terminate_runnables_on_event_loop(void) {
terminate_event_loop(void) {
sc_reject_new_runnables();
SDL_Event event;
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);
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);
}
}
}
@@ -951,7 +950,7 @@ aoa_complete:
}
ret = event_loop(s, options->window);
terminate_runnables_on_event_loop();
terminate_event_loop();
disconnected = ret == SCRCPY_EXIT_DISCONNECTED;
end:

View File

@@ -198,30 +198,21 @@ 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->window_shown);
assert(!screen->video || screen->has_video_window);
if (update_content_rect) {
sc_screen_update_content_rect(screen);
}
SDL_Renderer *renderer = screen->renderer;
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 0);
sc_sdl_render_clear(renderer);
bool ok = false;
SDL_Texture *texture = screen->tex.texture;
if (!texture) {
// Draw a dark 10x10 square in the top-right corner to distinguish a
// black frame from the absence of a frame
struct sc_size render_size = sc_sdl_get_render_output_size(renderer);
SDL_SetRenderDrawColor(renderer, 0, 0, 0x33, 0xff);
SDL_FRect rect = {
.x = render_size.width - 20,
.y = 10,
.w = 10,
.h = 10,
};
SDL_RenderFillRect(renderer, &rect);
if (!screen->disconnected) {
LOGW("No texture to render");
}
goto end;
}
@@ -291,8 +282,11 @@ sc_screen_frame_sink_open(struct sc_frame_sink *sink,
const AVCodecContext *ctx,
const struct sc_stream_session *session) {
assert(ctx->pix_fmt == AV_PIX_FMT_YUV420P);
(void) ctx;
(void) session;
struct sc_screen *screen = DOWNCAST(sink);
(void) screen;
if (ctx->width <= 0 || ctx->width > 0xFFFF
|| ctx->height <= 0 || ctx->height > 0xFFFF) {
@@ -300,19 +294,6 @@ sc_screen_frame_sink_open(struct sc_frame_sink *sink,
return false;
}
// content_size can be written from this thread, because it is never read
// from the main thread before handling SC_EVENT_OPEN_WINDOW (which acts as
// a synchronization point) when video is enabled
screen->frame_size.width = session->video.width;
screen->frame_size.height = session->video.height;
screen->content_size = get_oriented_size(screen->frame_size,
screen->orientation);
bool ok = sc_push_event(SC_EVENT_OPEN_WINDOW);
if (!ok) {
return false;
}
#ifndef NDEBUG
screen->open = true;
#endif
@@ -362,7 +343,8 @@ bool
sc_screen_init(struct sc_screen *screen,
const struct sc_screen_params *params) {
screen->resize_pending = false;
screen->window_shown = false;
screen->has_frame = false;
screen->has_video_window = false;
screen->paused = false;
screen->resume_frame = NULL;
screen->orientation = SC_ORIENTATION_0;
@@ -555,7 +537,6 @@ 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)) {
@@ -612,7 +593,6 @@ 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);
}
@@ -620,7 +600,6 @@ 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
@@ -660,17 +639,6 @@ 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
@@ -741,14 +709,14 @@ sc_screen_set_orientation(struct sc_screen *screen,
static bool
sc_screen_apply_frame(struct sc_screen *screen) {
assert(screen->video);
assert(screen->window_shown);
sc_fps_counter_add_rendered_frame(&screen->fps_counter);
AVFrame *frame = screen->frame;
struct sc_size new_frame_size = {frame->width, frame->height};
if (screen->frame_size.width != new_frame_size.width
if (!screen->has_frame
|| screen->frame_size.width != new_frame_size.width
|| screen->frame_size.height != new_frame_size.height) {
// frame dimension changed
@@ -756,8 +724,14 @@ sc_screen_apply_frame(struct sc_screen *screen) {
struct sc_size new_content_size =
get_oriented_size(new_frame_size, screen->orientation);
set_content_size(screen, new_content_size);
sc_screen_update_content_rect(screen);
if (screen->has_frame) {
set_content_size(screen, new_content_size);
sc_screen_update_content_rect(screen);
} else {
// This is the first frame
screen->has_frame = true;
screen->content_size = new_content_size;
}
}
bool ok = sc_texture_set_from_frame(&screen->tex, frame);
@@ -765,6 +739,18 @@ sc_screen_apply_frame(struct sc_screen *screen) {
return false;
}
assert(screen->has_frame);
if (!screen->has_video_window) {
screen->has_video_window = true;
// this is the very first frame, show the window
sc_screen_show_initial_window(screen);
if (sc_screen_is_relative_mode(screen)) {
// Capture mouse on start
sc_mouse_capture_set_active(&screen->mc, true);
}
}
sc_screen_render(screen, false);
return true;
}
@@ -906,17 +892,9 @@ sc_disconnect_on_timeout(struct sc_disconnect *d, void *userdata) {
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_OPEN_WINDOW:
sc_screen_show_initial_window(screen);
if (sc_screen_is_relative_mode(screen)) {
// Capture mouse on start
sc_mouse_capture_set_active(&screen->mc, true);
}
sc_screen_render(screen, false);
return;
case SC_EVENT_NEW_FRAME: {
bool ok = sc_screen_update_frame(screen);
if (!ok) {
@@ -925,27 +903,28 @@ sc_screen_handle_event(struct sc_screen *screen, const SDL_Event *event) {
return;
}
case SDL_EVENT_WINDOW_EXPOSED:
sc_screen_render(screen, true);
if (!screen->video || screen->has_video_window) {
sc_screen_render(screen, true);
}
return;
case SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED:
// This event can be triggered before the window is shown
if (screen->window_shown) {
if (screen->has_video_window) {
sc_screen_render(screen, true);
}
return;
case SDL_EVENT_WINDOW_RESTORED:
if (screen->video && is_windowed(screen)) {
if (screen->has_video_window && 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->video);
assert(screen->has_video_window);
return;
case SDL_EVENT_WINDOW_LEAVE_FULLSCREEN:
LOGD("Switched to windowed mode");
assert(screen->video);
assert(screen->has_video_window);
if (is_windowed(screen)) {
apply_pending_resize(screen);
sc_screen_render(screen, true);
@@ -954,13 +933,11 @@ sc_screen_handle_event(struct sc_screen *screen, const SDL_Event *event) {
case SC_EVENT_DEVICE_DISCONNECTED:
assert(!screen->disconnected);
screen->disconnected = true;
if (!screen->window_shown) {
if (!screen->has_video_window) {
// No window open
return;
}
sc_input_manager_handle_event(&screen->im, event);
sc_texture_reset(&screen->tex);
sc_screen_render(screen, true);
@@ -989,28 +966,19 @@ sc_screen_handle_event(struct sc_screen *screen, const SDL_Event *event) {
void
sc_screen_handle_disconnection(struct sc_screen *screen) {
if (!screen->window_shown) {
if (!screen->has_video_window) {
// 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 SDL_EVENT_WINDOW_EXPOSED:
sc_screen_render(screen, true);
break;
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);
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;
@@ -1025,15 +993,15 @@ sc_screen_handle_disconnection(struct sc_screen *screen) {
}
case SC_EVENT_DISCONNECTED_TIMEOUT:
LOGD("Closing after device disconnection");
return;
goto end;
case SDL_EVENT_QUIT:
LOGD("User requested to quit");
sc_screen_interrupt_disconnect(screen);
return;
default:
sc_input_manager_handle_event(&screen->im, &event);
goto end;
}
}
end:
sc_screen_interrupt_disconnect(screen);
}
struct sc_point

View File

@@ -71,7 +71,8 @@ struct sc_screen {
enum sc_orientation orientation;
// rectangle of the content (excluding black borders)
struct SDL_FRect rect;
bool window_shown;
bool has_frame;
bool has_video_window;
AVFrame *frame;

View File

@@ -67,8 +67,7 @@ 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->ops->push_session(sink, session)) {
if (!sink->ops->push_session(sink, session)) {
return false;
}
}

View File

@@ -17,7 +17,8 @@ sc_command_serialize_windows(const char *const argv[]) {
#define BUF_PUSH(C) \
do { \
if (!sc_strbuf_append_char(&buf, C)) { \
ok = sc_strbuf_append_char(&buf, C); \
if (!ok) { \
goto end; \
} \
} while (0)

View File

@@ -151,10 +151,6 @@ sc_sdl_log_print(void *userdata, int category, SDL_LogPriority priority,
void
sc_log_configure(void) {
#ifdef _WIN32
SetConsoleOutputCP(CP_UTF8);
#endif
SDL_SetLogOutputFunction(sc_sdl_log_print, NULL);
// Redirect FFmpeg logs to SDL logs
av_log_set_callback(sc_av_log_callback);

View File

@@ -6,6 +6,7 @@
#include <stdint.h>
typedef int64_t sc_tick;
#define SC_TICK_NONE INT64_MIN
#define PRItick PRIi64
#define SC_TICK_FREQ 1000000 // microsecond

View File

@@ -166,8 +166,8 @@ static void test_adb_devices_spaces(void) {
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";
"adb-AEORQMSAOWNQ-N7Ger8 (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);
@@ -175,33 +175,13 @@ static void test_adb_devices_serial_with_spaces(void) {
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("adb-AEORQMSAOWNQ-N7Ger8 (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";
@@ -305,7 +285,6 @@ int main(int argc, char *argv[]) {
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,6 +72,18 @@ 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:
@@ -80,17 +92,9 @@ 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 to run it.
Then just double-click on that file.
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` and add the desired arguments.
You could also edit (a copy of) `scrcpy-console.bat` or `scrcpy-noconsole.vbs`
to add some arguments.

View File

@@ -22,12 +22,9 @@ 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 \

View File

@@ -22,12 +22,9 @@ 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 \

View File

@@ -29,12 +29,9 @@ 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 \
@@ -48,6 +45,7 @@ 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/scrcpy.png "$WINXX_BUILD_DIR/dist/"
cp app/data/open_a_terminal_here.bat "$WINXX_BUILD_DIR/dist/"

View File

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

View File

@@ -264,10 +264,6 @@ 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/>