mirror of
https://github.com/Genymobile/scrcpy.git
synced 2026-02-25 15:54:28 +01:00
Compare commits
9 Commits
dev
...
disconnect
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
634755b60c | ||
|
|
d36322c841 | ||
|
|
a7ff2ff6c7 | ||
|
|
2bdefc7663 | ||
|
|
9a079a716d | ||
|
|
cb6eee82cc | ||
|
|
ced45d0ec4 | ||
|
|
7958b297ec | ||
|
|
b4711a5904 |
BIN
app/data/disconnected.png
Normal file
BIN
app/data/disconnected.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.6 KiB |
|
Before Width: | Height: | Size: 6.4 KiB After Width: | Height: | Size: 6.4 KiB |
|
Before Width: | Height: | Size: 4.5 KiB After Width: | Height: | Size: 4.5 KiB |
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
89
app/src/disconnect.c
Normal file
89
app/src/disconnect.c
Normal file
@@ -0,0 +1,89 @@
|
||||
#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");
|
||||
}
|
||||
|
||||
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 (!d->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
47
app/src/disconnect.h
Normal 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
|
||||
@@ -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());
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
145
app/src/screen.c
145
app/src/screen.c
@@ -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,46 @@ 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;
|
||||
}
|
||||
|
||||
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");
|
||||
goto end;
|
||||
case SDL_EVENT_QUIT:
|
||||
LOGD("User requested to quit");
|
||||
goto end;
|
||||
}
|
||||
}
|
||||
|
||||
end:
|
||||
sc_screen_interrupt_disconnect(screen);
|
||||
}
|
||||
|
||||
struct sc_point
|
||||
sc_screen_convert_drawable_to_frame_coords(struct sc_screen *screen,
|
||||
int32_t x, int32_t y) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -93,4 +93,4 @@ Then just double-click on that file to run it.
|
||||
|
||||
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.
|
||||
`scrcpy-noconsole.vbs` add and the desired arguments.
|
||||
|
||||
@@ -41,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/"
|
||||
|
||||
@@ -41,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/"
|
||||
|
||||
@@ -49,7 +49,7 @@ ninja -C "$WINXX_BUILD_DIR"
|
||||
mkdir -p "$WINXX_BUILD_DIR/dist"
|
||||
cp "$WINXX_BUILD_DIR"/app/scrcpy.exe "$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/"
|
||||
|
||||
Reference in New Issue
Block a user