From 643ea64b6382ca18fd5a29c1e1e01b8548725a10 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 25 Jan 2026 16:00:22 +0100 Subject: [PATCH] disconnected --- app/data/disconnected.png | Bin 0 -> 5381 bytes app/src/events.h | 1 + app/src/icon.h | 1 + app/src/scrcpy.c | 52 ++++++++++++++++++----- app/src/screen.c | 84 +++++++++++++++++++++++++++++++++++++- app/src/screen.h | 12 +++++- 6 files changed, 137 insertions(+), 13 deletions(-) create mode 100644 app/data/disconnected.png diff --git a/app/data/disconnected.png b/app/data/disconnected.png new file mode 100644 index 0000000000000000000000000000000000000000..e788cd96a16837467febb8b9e5c6028eebe94496 GIT binary patch literal 5381 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 #define SC_ICON_FILENAME_SCRCPY "scrcpy.png" +#define SC_ICON_FILENAME_DISCONNECTED "disconnected.png" SDL_Surface * sc_icon_load(const char *filename); diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index acf8d475..f272d69e 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -167,23 +167,41 @@ sdl_configure(bool video_playback, bool disable_screensaver) { } static enum scrcpy_exit_code -event_loop(struct scrcpy *s, bool has_screen) { +event_loop(struct scrcpy *s, bool has_screen, bool disconnected) { SDL_Event event; while (SDL_WaitEvent(&event)) { switch (event.type) { case SC_EVENT_DEVICE_DISCONNECTED: + if (disconnected) { + break; + } LOGW("Device disconnected"); + if (has_screen && !sc_screen_handle_event(&s->screen, &event)) { + return SCRCPY_EXIT_FAILURE; + } return SCRCPY_EXIT_DISCONNECTED; case SC_EVENT_DEMUXER_ERROR: + if (disconnected) { + break; + } LOGE("Demuxer error"); return SCRCPY_EXIT_FAILURE; case SC_EVENT_CONTROLLER_ERROR: + if (disconnected) { + break; + } LOGE("Controller error"); return SCRCPY_EXIT_FAILURE; case SC_EVENT_RECORDER_ERROR: + if (disconnected) { + break; + } LOGE("Recorder error"); return SCRCPY_EXIT_FAILURE; case SC_EVENT_AOA_OPEN_ERROR: + if (disconnected) { + break; + } LOGE("AOA open error"); return SCRCPY_EXIT_FAILURE; case SC_EVENT_TIME_LIMIT_REACHED: @@ -192,6 +210,9 @@ event_loop(struct scrcpy *s, bool has_screen) { case SDL_EVENT_QUIT: LOGD("User requested to quit"); return SCRCPY_EXIT_SUCCESS; + case SC_EVENT_DISCONNECTED_TIMEOUT: + LOGD("Closing after device disconnection"); + return SCRCPY_EXIT_DISCONNECTED; case SC_EVENT_RUN_ON_MAIN_THREAD: { sc_runnable_fn run = event.user.data1; void *userdata = event.user.data2; @@ -945,16 +966,9 @@ aoa_complete: } } - ret = event_loop(s, options->window); + ret = event_loop(s, options->window, false); 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); - } + bool disconnected = ret == SCRCPY_EXIT_DISCONNECTED; end: if (timeout_started) { @@ -999,6 +1013,24 @@ end: sc_server_stop(&s->server); } + if (options->window && ret != SCRCPY_EXIT_DISCONNECTED) { + // 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 (screen_initialized && options->window) { + if (disconnected) { + ret = event_loop(s, options->window, true); + sc_screen_interrupt_disconnect(&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); } diff --git a/app/src/screen.c b/app/src/screen.c index f45f250a..f24bbbfa 100644 --- a/app/src/screen.c +++ b/app/src/screen.c @@ -137,7 +137,7 @@ sc_screen_is_relative_mode(struct sc_screen *screen) { 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); @@ -163,7 +163,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; } @@ -299,6 +301,8 @@ sc_screen_init(struct sc_screen *screen, 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; @@ -556,9 +560,19 @@ sc_screen_interrupt(struct sc_screen *screen) { sc_fps_counter_interrupt(&screen->fps_counter); } +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 @@ -566,6 +580,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 @@ -805,12 +822,37 @@ 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 +} + bool 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: { + if (screen->disconnected) { + // ignore + return true; + } bool ok = sc_screen_update_frame(screen); if (!ok) { LOGE("Frame update failed\n"); @@ -845,6 +887,44 @@ sc_screen_handle_event(struct sc_screen *screen, const SDL_Event *event) { sc_screen_render(screen, true); } return true; + case SC_EVENT_DEVICE_DISCONNECTED: + if (screen->disconnected) { + return true; + } + screen->disconnected = true; + 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; + } + + // else not fatal + return true; + 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); + return true; + } } if (sc_screen_is_relative_mode(screen) diff --git a/app/src/screen.h b/app/src/screen.h index 330e41a6..8bb932b0 100644 --- a/app/src/screen.h +++ b/app/src/screen.h @@ -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" @@ -77,6 +78,10 @@ struct sc_screen { bool paused; AVFrame *resume_frame; + + bool disconnected; + bool disconnect_started; + struct sc_disconnect disconnect; }; struct sc_screen_params { @@ -116,10 +121,15 @@ 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); +// request to interrupt the disconnected state (before closing the window) +// must be called before sc_screen_join(); +void +sc_screen_interrupt_disconnect(struct sc_screen *screen); + // join any inner thread void sc_screen_join(struct sc_screen *screen);