mirror of
https://github.com/Genymobile/scrcpy.git
synced 2026-03-06 04:04:34 +01:00
Compare commits
35 Commits
renderer.3
...
disconnect
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8eb87ba10e | ||
|
|
cd757dbe35 | ||
|
|
0122e09199 | ||
|
|
d45aefee26 | ||
|
|
ac1a8b6ef2 | ||
|
|
362daa5bad | ||
|
|
4f63398019 | ||
|
|
f0a6e7658d | ||
|
|
6586f0bc7c | ||
|
|
10e0c3226c | ||
|
|
323549e82f | ||
|
|
90d11810e3 | ||
|
|
1f19ec4aec | ||
|
|
8b0206f7be | ||
|
|
f91fa56593 | ||
|
|
23710e04c1 | ||
|
|
51cbd9a5bc | ||
|
|
82e102e036 | ||
|
|
e39d7a06e4 | ||
|
|
be34f37a57 | ||
|
|
f7e74dd04d | ||
|
|
bb6d1c6348 | ||
|
|
2301f64158 | ||
|
|
d7f0e22067 | ||
|
|
77998f543f | ||
|
|
c43cc2ddc3 | ||
|
|
1617979549 | ||
|
|
58818dc860 | ||
|
|
3ef51e691e | ||
|
|
fb77f8556a | ||
|
|
e3f0c21f2a | ||
|
|
930169af4e | ||
|
|
3ce5feb5cb | ||
|
|
42e2264d67 | ||
|
|
c497718a3e |
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 |
@@ -1,2 +0,0 @@
|
||||
@echo off
|
||||
scrcpy.exe --pause-on-exit=if-error %*
|
||||
|
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,7 +15,7 @@ src = [
|
||||
'src/delay_buffer.c',
|
||||
'src/demuxer.c',
|
||||
'src/device_msg.c',
|
||||
'src/display.c',
|
||||
'src/disconnect.c',
|
||||
'src/events.c',
|
||||
'src/icon.c',
|
||||
'src/file_pusher.c',
|
||||
@@ -33,6 +33,7 @@ src = [
|
||||
'src/scrcpy.c',
|
||||
'src/screen.c',
|
||||
'src/server.c',
|
||||
'src/texture.c',
|
||||
'src/version.c',
|
||||
'src/hid/hid_gamepad.c',
|
||||
'src/hid/hid_keyboard.c',
|
||||
@@ -75,6 +76,7 @@ conf.set('_GNU_SOURCE', true)
|
||||
if host_machine.system() == 'windows'
|
||||
windows = import('windows')
|
||||
src += [
|
||||
'src/util/command.c',
|
||||
'src/sys/win/file.c',
|
||||
'src/sys/win/process.c',
|
||||
windows.compile_resources('scrcpy-windows.rc'),
|
||||
@@ -190,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')
|
||||
@@ -238,6 +239,12 @@ if get_option('buildtype') == 'debug'
|
||||
'src/util/strbuf.c',
|
||||
'src/util/term.c',
|
||||
]],
|
||||
['test_command_windows', [
|
||||
'tests/test_command_windows.c',
|
||||
'src/util/command.c',
|
||||
'src/util/str.c',
|
||||
'src/util/strbuf.c',
|
||||
]],
|
||||
['test_control_msg_serialize', [
|
||||
'tests/test_control_msg_serialize.c',
|
||||
'src/control_msg.c',
|
||||
@@ -282,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
|
||||
|
||||
@@ -331,56 +331,24 @@ sc_adb_reverse_remove(struct sc_intr *intr, const char *serial,
|
||||
bool
|
||||
sc_adb_push(struct sc_intr *intr, const char *serial, const char *local,
|
||||
const char *remote, unsigned flags) {
|
||||
#ifdef _WIN32
|
||||
// Windows will parse the string, so the paths must be quoted
|
||||
// (see sys/win/command.c)
|
||||
local = sc_str_quote(local);
|
||||
if (!local) {
|
||||
return SC_PROCESS_NONE;
|
||||
}
|
||||
remote = sc_str_quote(remote);
|
||||
if (!remote) {
|
||||
free((void *) local);
|
||||
return SC_PROCESS_NONE;
|
||||
}
|
||||
#endif
|
||||
|
||||
assert(serial);
|
||||
const char *const argv[] =
|
||||
SC_ADB_COMMAND("-s", serial, "push", local, remote);
|
||||
|
||||
sc_pid pid = sc_adb_execute(argv, flags);
|
||||
|
||||
#ifdef _WIN32
|
||||
free((void *) remote);
|
||||
free((void *) local);
|
||||
#endif
|
||||
|
||||
return process_check_success_intr(intr, pid, "adb push", flags);
|
||||
}
|
||||
|
||||
bool
|
||||
sc_adb_install(struct sc_intr *intr, const char *serial, const char *local,
|
||||
unsigned flags) {
|
||||
#ifdef _WIN32
|
||||
// Windows will parse the string, so the local name must be quoted
|
||||
// (see sys/win/command.c)
|
||||
local = sc_str_quote(local);
|
||||
if (!local) {
|
||||
return SC_PROCESS_NONE;
|
||||
}
|
||||
#endif
|
||||
|
||||
assert(serial);
|
||||
const char *const argv[] =
|
||||
SC_ADB_COMMAND("-s", serial, "install", "-r", local);
|
||||
|
||||
sc_pid pid = sc_adb_execute(argv, flags);
|
||||
|
||||
#ifdef _WIN32
|
||||
free((void *) local);
|
||||
#endif
|
||||
|
||||
return process_check_success_intr(intr, pid, "adb install", flags);
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
88
app/src/disconnect.c
Normal 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
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
|
||||
@@ -1,429 +0,0 @@
|
||||
#include "display.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <inttypes.h>
|
||||
#include <string.h>
|
||||
#include <libavutil/pixfmt.h>
|
||||
|
||||
#include "util/log.h"
|
||||
#include "util/sdl.h"
|
||||
|
||||
static bool
|
||||
sc_display_init_novideo_icon(struct sc_display *display,
|
||||
SDL_Surface *icon_novideo) {
|
||||
assert(icon_novideo);
|
||||
|
||||
bool ok = SDL_SetRenderLogicalPresentation(display->renderer,
|
||||
icon_novideo->w,
|
||||
icon_novideo->h,
|
||||
SDL_LOGICAL_PRESENTATION_LETTERBOX);
|
||||
if (!ok) {
|
||||
LOGW("Could not set renderer logical size: %s", SDL_GetError());
|
||||
// don't fail
|
||||
}
|
||||
|
||||
display->texture = SDL_CreateTextureFromSurface(display->renderer,
|
||||
icon_novideo);
|
||||
if (!display->texture) {
|
||||
LOGE("Could not create texture: %s", SDL_GetError());
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
sc_display_init(struct sc_display *display, SDL_Window *window,
|
||||
SDL_Surface *icon_novideo, bool mipmaps) {
|
||||
display->renderer = SDL_CreateRenderer(window, NULL);
|
||||
if (!display->renderer) {
|
||||
LOGE("Could not create renderer: %s", SDL_GetError());
|
||||
return false;
|
||||
}
|
||||
|
||||
const char *renderer_name = SDL_GetRendererName(display->renderer);
|
||||
LOGI("Renderer: %s", renderer_name ? renderer_name : "(unknown)");
|
||||
|
||||
display->mipmaps = false;
|
||||
|
||||
#ifdef SC_DISPLAY_FORCE_OPENGL_CORE_PROFILE
|
||||
display->gl_context = NULL;
|
||||
#endif
|
||||
|
||||
// starts with "opengl"
|
||||
bool use_opengl = renderer_name && !strncmp(renderer_name, "opengl", 6);
|
||||
if (use_opengl) {
|
||||
|
||||
#ifdef SC_DISPLAY_FORCE_OPENGL_CORE_PROFILE
|
||||
// Persuade macOS to give us something better than OpenGL 2.1.
|
||||
// If we create a Core Profile context, we get the best OpenGL version.
|
||||
bool ok = SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK,
|
||||
SDL_GL_CONTEXT_PROFILE_CORE);
|
||||
if (!ok) {
|
||||
LOGW("Could not set a GL Core Profile Context");
|
||||
}
|
||||
|
||||
LOGD("Creating OpenGL Core Profile context");
|
||||
display->gl_context = SDL_GL_CreateContext(window);
|
||||
if (!display->gl_context) {
|
||||
LOGE("Could not create OpenGL context: %s", SDL_GetError());
|
||||
SDL_DestroyRenderer(display->renderer);
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
struct sc_opengl *gl = &display->gl;
|
||||
sc_opengl_init(gl);
|
||||
|
||||
LOGI("OpenGL version: %s", gl->version);
|
||||
|
||||
if (mipmaps) {
|
||||
bool supports_mipmaps =
|
||||
sc_opengl_version_at_least(gl, 3, 0, /* OpenGL 3.0+ */
|
||||
2, 0 /* OpenGL ES 2.0+ */);
|
||||
if (supports_mipmaps) {
|
||||
LOGI("Trilinear filtering enabled");
|
||||
display->mipmaps = true;
|
||||
} else {
|
||||
LOGW("Trilinear filtering disabled "
|
||||
"(OpenGL 3.0+ or ES 2.0+ required)");
|
||||
}
|
||||
} else {
|
||||
LOGI("Trilinear filtering disabled");
|
||||
}
|
||||
} else if (mipmaps) {
|
||||
LOGD("Trilinear filtering disabled (not an OpenGL renderer)");
|
||||
}
|
||||
|
||||
display->texture = NULL;
|
||||
display->pending.flags = 0;
|
||||
display->pending.frame = NULL;
|
||||
|
||||
if (icon_novideo) {
|
||||
// Without video, set a static scrcpy icon as window content
|
||||
bool ok = sc_display_init_novideo_icon(display, icon_novideo);
|
||||
if (!ok) {
|
||||
#ifdef SC_DISPLAY_FORCE_OPENGL_CORE_PROFILE
|
||||
SDL_GL_DestroyContext(display->gl_context);
|
||||
#endif
|
||||
SDL_DestroyRenderer(display->renderer);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
sc_display_destroy(struct sc_display *display) {
|
||||
if (display->pending.frame) {
|
||||
av_frame_free(&display->pending.frame);
|
||||
}
|
||||
#ifdef SC_DISPLAY_FORCE_OPENGL_CORE_PROFILE
|
||||
SDL_GL_DestroyContext(display->gl_context);
|
||||
#endif
|
||||
if (display->texture) {
|
||||
SDL_DestroyTexture(display->texture);
|
||||
}
|
||||
SDL_DestroyRenderer(display->renderer);
|
||||
}
|
||||
|
||||
static enum SDL_Colorspace
|
||||
sc_display_to_sdl_color_space(enum AVColorSpace color_space,
|
||||
enum AVColorRange color_range) {
|
||||
bool full_range = color_range == AVCOL_RANGE_JPEG;
|
||||
|
||||
switch (color_space) {
|
||||
case AVCOL_SPC_BT709:
|
||||
case AVCOL_SPC_RGB:
|
||||
return full_range ? SDL_COLORSPACE_BT709_FULL
|
||||
: SDL_COLORSPACE_BT709_LIMITED;
|
||||
case AVCOL_SPC_BT470BG:
|
||||
case AVCOL_SPC_SMPTE170M:
|
||||
return full_range ? SDL_COLORSPACE_BT601_FULL
|
||||
: SDL_COLORSPACE_BT601_LIMITED;
|
||||
case AVCOL_SPC_BT2020_NCL:
|
||||
case AVCOL_SPC_BT2020_CL:
|
||||
return full_range ? SDL_COLORSPACE_BT2020_FULL
|
||||
: SDL_COLORSPACE_BT2020_LIMITED;
|
||||
default:
|
||||
return SDL_COLORSPACE_JPEG;
|
||||
}
|
||||
}
|
||||
|
||||
static SDL_Texture *
|
||||
sc_display_create_texture(struct sc_display *display,
|
||||
struct sc_size size, enum AVColorSpace color_space,
|
||||
enum AVColorRange color_range) {
|
||||
SDL_PropertiesID props = SDL_CreateProperties();
|
||||
if (!props) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
enum SDL_Colorspace sdl_color_space =
|
||||
sc_display_to_sdl_color_space(color_space, color_range);
|
||||
|
||||
bool ok =
|
||||
SDL_SetNumberProperty(props, SDL_PROP_TEXTURE_CREATE_FORMAT_NUMBER,
|
||||
SDL_PIXELFORMAT_YV12);
|
||||
ok &= SDL_SetNumberProperty(props, SDL_PROP_TEXTURE_CREATE_ACCESS_NUMBER,
|
||||
SDL_TEXTUREACCESS_STREAMING);
|
||||
ok &= SDL_SetNumberProperty(props, SDL_PROP_TEXTURE_CREATE_WIDTH_NUMBER,
|
||||
size.width);
|
||||
ok &= SDL_SetNumberProperty(props, SDL_PROP_TEXTURE_CREATE_HEIGHT_NUMBER,
|
||||
size.height);
|
||||
ok &= SDL_SetNumberProperty(props,
|
||||
SDL_PROP_TEXTURE_CREATE_COLORSPACE_NUMBER,
|
||||
sdl_color_space);
|
||||
|
||||
if (!ok) {
|
||||
LOGE("Could not set texture properties");
|
||||
SDL_DestroyProperties(props);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
SDL_Renderer *renderer = display->renderer;
|
||||
SDL_Texture *texture = SDL_CreateTextureWithProperties(renderer, props);
|
||||
SDL_DestroyProperties(props);
|
||||
if (!texture) {
|
||||
LOGD("Could not create texture: %s", SDL_GetError());
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (display->mipmaps) {
|
||||
struct sc_opengl *gl = &display->gl;
|
||||
|
||||
SDL_PropertiesID props = SDL_GetTextureProperties(texture);
|
||||
if (!props) {
|
||||
LOGE("Could not get texture properties: %s", SDL_GetError());
|
||||
SDL_DestroyTexture(texture);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
const char *renderer_name = SDL_GetRendererName(display->renderer);
|
||||
const char *key = !renderer_name || !strcmp(renderer_name, "opengl")
|
||||
? SDL_PROP_TEXTURE_OPENGL_TEXTURE_NUMBER
|
||||
: SDL_PROP_TEXTURE_OPENGLES2_TEXTURE_NUMBER;
|
||||
|
||||
int64_t texture_id = SDL_GetNumberProperty(props, key, 0);
|
||||
SDL_DestroyProperties(props);
|
||||
if (!texture_id) {
|
||||
LOGE("Could not get texture id: %s", SDL_GetError());
|
||||
SDL_DestroyTexture(texture);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
assert(!(texture_id & ~0xFFFFFFFF)); // fits in uint32_t
|
||||
display->texture_id = texture_id;
|
||||
gl->BindTexture(GL_TEXTURE_2D, display->texture_id);
|
||||
|
||||
// Enable trilinear filtering for downscaling
|
||||
gl->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
|
||||
GL_LINEAR_MIPMAP_LINEAR);
|
||||
gl->TexParameterf(GL_TEXTURE_2D, GL_TEXTURE_LOD_BIAS, -1.f);
|
||||
|
||||
gl->BindTexture(GL_TEXTURE_2D, 0);
|
||||
}
|
||||
|
||||
return texture;
|
||||
}
|
||||
|
||||
static inline void
|
||||
sc_display_set_pending_texture(struct sc_display *display,
|
||||
struct sc_size size,
|
||||
enum AVColorRange color_range) {
|
||||
assert(!display->texture);
|
||||
display->pending.texture.size = size;
|
||||
display->pending.texture.color_range = color_range;
|
||||
display->pending.flags |= SC_DISPLAY_PENDING_FLAG_TEXTURE;
|
||||
}
|
||||
|
||||
static bool
|
||||
sc_display_set_pending_frame(struct sc_display *display, const AVFrame *frame) {
|
||||
if (!display->pending.frame) {
|
||||
display->pending.frame = av_frame_alloc();
|
||||
if (!display->pending.frame) {
|
||||
LOG_OOM();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
av_frame_unref(display->pending.frame);
|
||||
int r = av_frame_ref(display->pending.frame, frame);
|
||||
if (r) {
|
||||
LOGE("Could not ref frame: %d", r);
|
||||
return false;
|
||||
}
|
||||
|
||||
display->pending.flags |= SC_DISPLAY_PENDING_FLAG_FRAME;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Forward declaration
|
||||
static bool
|
||||
sc_display_update_texture_internal(struct sc_display *display,
|
||||
const AVFrame *frame);
|
||||
|
||||
static bool
|
||||
sc_display_apply_pending(struct sc_display *display) {
|
||||
if (display->pending.flags & SC_DISPLAY_PENDING_FLAG_TEXTURE) {
|
||||
assert(!display->texture);
|
||||
display->texture =
|
||||
sc_display_create_texture(display,
|
||||
display->pending.texture.size,
|
||||
display->pending.texture.color_space,
|
||||
display->pending.texture.color_range);
|
||||
if (!display->texture) {
|
||||
return false;
|
||||
}
|
||||
|
||||
display->pending.flags &= ~SC_DISPLAY_PENDING_FLAG_TEXTURE;
|
||||
}
|
||||
|
||||
if (display->pending.flags & SC_DISPLAY_PENDING_FLAG_FRAME) {
|
||||
assert(display->pending.frame);
|
||||
bool ok = sc_display_update_texture_internal(display,
|
||||
display->pending.frame);
|
||||
if (!ok) {
|
||||
return false;
|
||||
}
|
||||
|
||||
av_frame_unref(display->pending.frame);
|
||||
display->pending.flags &= ~SC_DISPLAY_PENDING_FLAG_FRAME;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
sc_display_prepare_texture_internal(struct sc_display *display,
|
||||
struct sc_size size,
|
||||
enum AVColorSpace color_space,
|
||||
enum AVColorRange color_range) {
|
||||
assert(size.width && size.height);
|
||||
|
||||
if (display->texture) {
|
||||
SDL_DestroyTexture(display->texture);
|
||||
}
|
||||
|
||||
display->texture =
|
||||
sc_display_create_texture(display, size, color_space, color_range);
|
||||
if (!display->texture) {
|
||||
return false;
|
||||
}
|
||||
|
||||
LOGI("Texture: %" PRIu16 "x%" PRIu16, size.width, size.height);
|
||||
return true;
|
||||
}
|
||||
|
||||
enum sc_display_result
|
||||
sc_display_prepare_texture(struct sc_display *display, struct sc_size size,
|
||||
enum AVColorSpace color_space,
|
||||
enum AVColorRange color_range) {
|
||||
bool ok = sc_display_prepare_texture_internal(display, size, color_space,
|
||||
color_range);
|
||||
if (!ok) {
|
||||
sc_display_set_pending_texture(display, size, color_range);
|
||||
return SC_DISPLAY_RESULT_PENDING;
|
||||
|
||||
}
|
||||
|
||||
return SC_DISPLAY_RESULT_OK;
|
||||
}
|
||||
|
||||
static bool
|
||||
sc_display_update_texture_internal(struct sc_display *display,
|
||||
const AVFrame *frame) {
|
||||
bool ok = SDL_UpdateYUVTexture(display->texture, NULL,
|
||||
frame->data[0], frame->linesize[0],
|
||||
frame->data[1], frame->linesize[1],
|
||||
frame->data[2], frame->linesize[2]);
|
||||
if (!ok) {
|
||||
LOGD("Could not update texture: %s", SDL_GetError());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (display->mipmaps) {
|
||||
assert(display->texture_id);
|
||||
struct sc_opengl *gl = &display->gl;
|
||||
|
||||
gl->BindTexture(GL_TEXTURE_2D, display->texture_id);
|
||||
gl->GenerateMipmap(GL_TEXTURE_2D);
|
||||
gl->BindTexture(GL_TEXTURE_2D, 0);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
enum sc_display_result
|
||||
sc_display_update_texture(struct sc_display *display, const AVFrame *frame) {
|
||||
bool ok = sc_display_update_texture_internal(display, frame);
|
||||
if (!ok) {
|
||||
ok = sc_display_set_pending_frame(display, frame);
|
||||
if (!ok) {
|
||||
LOGE("Could not set pending frame");
|
||||
return SC_DISPLAY_RESULT_ERROR;
|
||||
}
|
||||
|
||||
return SC_DISPLAY_RESULT_PENDING;
|
||||
}
|
||||
|
||||
return SC_DISPLAY_RESULT_OK;
|
||||
}
|
||||
|
||||
enum sc_display_result
|
||||
sc_display_render(struct sc_display *display, const SDL_Rect *geometry,
|
||||
enum sc_orientation orientation) {
|
||||
sc_sdl_render_clear(display->renderer);
|
||||
|
||||
if (display->pending.flags) {
|
||||
bool ok = sc_display_apply_pending(display);
|
||||
if (!ok) {
|
||||
return SC_DISPLAY_RESULT_PENDING;
|
||||
}
|
||||
}
|
||||
|
||||
SDL_Renderer *renderer = display->renderer;
|
||||
SDL_Texture *texture = display->texture;
|
||||
|
||||
if (orientation == SC_ORIENTATION_0) {
|
||||
SDL_FRect frect;
|
||||
SDL_FRect *fgeometry = NULL;
|
||||
if (geometry) {
|
||||
SDL_RectToFRect(geometry, &frect);
|
||||
fgeometry = &frect;
|
||||
}
|
||||
bool ok = SDL_RenderTexture(renderer, texture, NULL, fgeometry);
|
||||
if (!ok) {
|
||||
LOGE("Could not render texture: %s", SDL_GetError());
|
||||
return SC_DISPLAY_RESULT_ERROR;
|
||||
}
|
||||
} else {
|
||||
unsigned cw_rotation = sc_orientation_get_rotation(orientation);
|
||||
double angle = 90 * cw_rotation;
|
||||
|
||||
SDL_FRect frect;
|
||||
if (sc_orientation_is_swap(orientation)) {
|
||||
frect.x = geometry->x + (geometry->w - geometry->h) / 2.f;
|
||||
frect.y = geometry->y + (geometry->h - geometry->w) / 2.f;
|
||||
frect.w = geometry->h;
|
||||
frect.h = geometry->w;
|
||||
} else {
|
||||
SDL_RectToFRect(geometry, &frect);
|
||||
}
|
||||
|
||||
SDL_FlipMode flip = sc_orientation_is_mirror(orientation)
|
||||
? SDL_FLIP_HORIZONTAL : 0;
|
||||
|
||||
bool ok = SDL_RenderTextureRotated(renderer, texture, NULL, &frect,
|
||||
angle, NULL, flip);
|
||||
if (!ok) {
|
||||
LOGE("Could not render texture: %s", SDL_GetError());
|
||||
return SC_DISPLAY_RESULT_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
sc_sdl_render_present(display->renderer);
|
||||
return SC_DISPLAY_RESULT_OK;
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
#ifndef SC_DISPLAY_H
|
||||
#define SC_DISPLAY_H
|
||||
|
||||
#include "common.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <libavutil/frame.h>
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
#include "coords.h"
|
||||
#include "opengl.h"
|
||||
#include "options.h"
|
||||
|
||||
#ifdef __APPLE__
|
||||
# define SC_DISPLAY_FORCE_OPENGL_CORE_PROFILE
|
||||
#endif
|
||||
|
||||
struct sc_display {
|
||||
SDL_Renderer *renderer;
|
||||
SDL_Texture *texture;
|
||||
|
||||
struct sc_opengl gl;
|
||||
#ifdef SC_DISPLAY_FORCE_OPENGL_CORE_PROFILE
|
||||
SDL_GLContext gl_context;
|
||||
#endif
|
||||
|
||||
bool mipmaps;
|
||||
uint32_t texture_id; // only set if mipmaps is enabled
|
||||
|
||||
struct {
|
||||
#define SC_DISPLAY_PENDING_FLAG_TEXTURE 1
|
||||
#define SC_DISPLAY_PENDING_FLAG_FRAME 2
|
||||
int8_t flags;
|
||||
struct {
|
||||
struct sc_size size;
|
||||
enum AVColorSpace color_space;
|
||||
enum AVColorRange color_range;
|
||||
} texture;
|
||||
AVFrame *frame;
|
||||
} pending;
|
||||
};
|
||||
|
||||
enum sc_display_result {
|
||||
SC_DISPLAY_RESULT_OK,
|
||||
SC_DISPLAY_RESULT_PENDING,
|
||||
SC_DISPLAY_RESULT_ERROR,
|
||||
};
|
||||
|
||||
bool
|
||||
sc_display_init(struct sc_display *display, SDL_Window *window,
|
||||
SDL_Surface *icon_novideo, bool mipmaps);
|
||||
|
||||
void
|
||||
sc_display_destroy(struct sc_display *display);
|
||||
|
||||
enum sc_display_result
|
||||
sc_display_prepare_texture(struct sc_display *display, struct sc_size size,
|
||||
enum AVColorSpace color_space,
|
||||
enum AVColorRange color_range);
|
||||
|
||||
enum sc_display_result
|
||||
sc_display_update_texture(struct sc_display *display, const AVFrame *frame);
|
||||
|
||||
enum sc_display_result
|
||||
sc_display_render(struct sc_display *display, const SDL_Rect *geometry,
|
||||
enum sc_orientation orientation);
|
||||
|
||||
#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
|
||||
|
||||
@@ -890,7 +890,7 @@ sc_input_manager_process_mouse_button(struct sc_input_manager *im,
|
||||
int32_t x = event->x;
|
||||
int32_t y = event->y;
|
||||
sc_screen_hidpi_scale_coords(im->screen, &x, &y);
|
||||
SDL_Rect *r = &im->screen->rect;
|
||||
SDL_FRect *r = &im->screen->rect;
|
||||
bool outside = x < r->x || x >= r->x + r->w
|
||||
|| y < r->y || y >= r->y + r->h;
|
||||
if (outside) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -444,7 +444,7 @@ sc_recorder_process_packets(struct sc_recorder *recorder) {
|
||||
int ret = av_write_trailer(recorder->ctx);
|
||||
if (ret < 0) {
|
||||
LOGE("Failed to write trailer to %s", recorder->filename);
|
||||
error = false;
|
||||
error = true;
|
||||
}
|
||||
|
||||
end:
|
||||
|
||||
@@ -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");
|
||||
@@ -199,8 +202,8 @@ event_loop(struct scrcpy *s, bool has_screen) {
|
||||
break;
|
||||
}
|
||||
default:
|
||||
if (has_screen && !sc_screen_handle_event(&s->screen, &event)) {
|
||||
return SCRCPY_EXIT_FAILURE;
|
||||
if (has_screen) {
|
||||
sc_screen_handle_event(&s->screen, &event);
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
400
app/src/screen.c
400
app/src/screen.c
@@ -144,64 +144,112 @@ sc_screen_is_relative_mode(struct sc_screen *screen) {
|
||||
}
|
||||
|
||||
static void
|
||||
sc_screen_update_content_rect(struct sc_screen *screen) {
|
||||
assert(screen->video);
|
||||
|
||||
struct sc_size content_size = screen->content_size;
|
||||
// The drawable size is the window size * the HiDPI scale
|
||||
struct sc_size drawable_size =
|
||||
sc_sdl_get_window_size_in_pixels(screen->window);
|
||||
|
||||
SDL_Rect *rect = &screen->rect;
|
||||
|
||||
if (is_optimal_size(drawable_size, content_size)) {
|
||||
compute_content_rect(struct sc_size render_size, struct sc_size content_size,
|
||||
bool can_upscale, SDL_FRect *rect) {
|
||||
if (is_optimal_size(render_size, content_size)) {
|
||||
rect->x = 0;
|
||||
rect->y = 0;
|
||||
rect->w = drawable_size.width;
|
||||
rect->h = drawable_size.height;
|
||||
rect->w = render_size.width;
|
||||
rect->h = render_size.height;
|
||||
return;
|
||||
}
|
||||
|
||||
bool keep_width = content_size.width * drawable_size.height
|
||||
> content_size.height * drawable_size.width;
|
||||
if (!can_upscale && content_size.width <= render_size.width
|
||||
&& content_size.height <= render_size.height) {
|
||||
// Center without upscaling
|
||||
rect->x = (render_size.width - content_size.width) / 2.f;
|
||||
rect->y = (render_size.height - content_size.height) / 2.f;
|
||||
rect->w = content_size.width;
|
||||
rect->h = content_size.height;
|
||||
return;
|
||||
}
|
||||
|
||||
bool keep_width = content_size.width * render_size.height
|
||||
> content_size.height * render_size.width;
|
||||
if (keep_width) {
|
||||
rect->x = 0;
|
||||
rect->w = drawable_size.width;
|
||||
rect->h = drawable_size.width * content_size.height
|
||||
/ content_size.width;
|
||||
rect->y = (drawable_size.height - rect->h) / 2;
|
||||
rect->w = render_size.width;
|
||||
rect->h = (float) render_size.width * content_size.height
|
||||
/ content_size.width;
|
||||
rect->y = (render_size.height - rect->h) / 2.f;
|
||||
} else {
|
||||
rect->y = 0;
|
||||
rect->h = drawable_size.height;
|
||||
rect->w = drawable_size.height * content_size.width
|
||||
/ content_size.height;
|
||||
rect->x = (drawable_size.width - rect->w) / 2;
|
||||
rect->h = render_size.height;
|
||||
rect->w = (float) render_size.height * content_size.width
|
||||
/ content_size.height;
|
||||
rect->x = (render_size.width - rect->w) / 2.f;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
sc_screen_update_content_rect(struct sc_screen *screen) {
|
||||
// Only upscale video frames, not icon
|
||||
bool can_upscale = screen->video && !screen->disconnected;
|
||||
|
||||
struct sc_size render_size =
|
||||
sc_sdl_get_render_output_size(screen->renderer);
|
||||
compute_content_rect(render_size, screen->content_size, can_upscale,
|
||||
&screen->rect);
|
||||
}
|
||||
|
||||
// render the texture to the renderer
|
||||
//
|
||||
// Set the update_content_rect flag if the window or content size may have
|
||||
// changed, so that the content rectangle is recomputed
|
||||
static void
|
||||
sc_screen_render(struct sc_screen *screen, bool update_content_rect) {
|
||||
assert(screen->video);
|
||||
assert(screen->has_video_window);
|
||||
assert(screen->window_shown);
|
||||
|
||||
if (update_content_rect) {
|
||||
sc_screen_update_content_rect(screen);
|
||||
}
|
||||
|
||||
enum sc_display_result res =
|
||||
sc_display_render(&screen->display, &screen->rect, screen->orientation);
|
||||
(void) res; // any error already logged
|
||||
}
|
||||
SDL_Renderer *renderer = screen->renderer;
|
||||
sc_sdl_render_clear(renderer);
|
||||
|
||||
static void
|
||||
sc_screen_render_novideo(struct sc_screen *screen) {
|
||||
enum sc_display_result res =
|
||||
sc_display_render(&screen->display, NULL, SC_ORIENTATION_0);
|
||||
(void) res; // any error already logged
|
||||
bool ok = false;
|
||||
SDL_Texture *texture = screen->tex.texture;
|
||||
if (!texture) {
|
||||
if (!screen->disconnected) {
|
||||
LOGW("No texture to render");
|
||||
}
|
||||
goto end;
|
||||
}
|
||||
|
||||
SDL_FRect *geometry = &screen->rect;
|
||||
enum sc_orientation orientation = screen->orientation;
|
||||
|
||||
if (orientation == SC_ORIENTATION_0) {
|
||||
ok = SDL_RenderTexture(renderer, texture, NULL, geometry);
|
||||
} else {
|
||||
unsigned cw_rotation = sc_orientation_get_rotation(orientation);
|
||||
double angle = 90 * cw_rotation;
|
||||
|
||||
const SDL_FRect *dstrect = NULL;
|
||||
SDL_FRect rect;
|
||||
if (sc_orientation_is_swap(orientation)) {
|
||||
rect.x = geometry->x + (geometry->w - geometry->h) / 2.f;
|
||||
rect.y = geometry->y + (geometry->h - geometry->w) / 2.f;
|
||||
rect.w = geometry->h;
|
||||
rect.h = geometry->w;
|
||||
dstrect = ▭
|
||||
} else {
|
||||
dstrect = geometry;
|
||||
}
|
||||
|
||||
SDL_FlipMode flip = sc_orientation_is_mirror(orientation)
|
||||
? SDL_FLIP_HORIZONTAL : 0;
|
||||
|
||||
ok = SDL_RenderTextureRotated(renderer, texture, NULL, dstrect, angle,
|
||||
NULL, flip);
|
||||
}
|
||||
|
||||
if (!ok) {
|
||||
LOGE("Could not render texture: %s", SDL_GetError());
|
||||
}
|
||||
|
||||
end:
|
||||
sc_sdl_render_present(renderer);
|
||||
}
|
||||
|
||||
#if defined(__APPLE__) || defined(_WIN32)
|
||||
@@ -296,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;
|
||||
@@ -369,41 +419,79 @@ sc_screen_init(struct sc_screen *screen,
|
||||
goto error_destroy_fps_counter;
|
||||
}
|
||||
|
||||
ok = SDL_StartTextInput(screen->window);
|
||||
if (!ok) {
|
||||
LOGE("Could not enable text input: %s", SDL_GetError());
|
||||
screen->renderer = SDL_CreateRenderer(screen->window, NULL);
|
||||
if (!screen->renderer) {
|
||||
LOGE("Could not create renderer: %s", SDL_GetError());
|
||||
goto error_destroy_window;
|
||||
}
|
||||
|
||||
SDL_Surface *icon = scrcpy_icon_load();
|
||||
#ifdef SC_DISPLAY_FORCE_OPENGL_CORE_PROFILE
|
||||
screen->gl_context = NULL;
|
||||
|
||||
// starts with "opengl"
|
||||
const char *renderer_name = SDL_GetRendererName(screen->renderer);
|
||||
bool use_opengl = renderer_name && !strncmp(renderer_name, "opengl", 6);
|
||||
if (use_opengl) {
|
||||
// Persuade macOS to give us something better than OpenGL 2.1.
|
||||
// If we create a Core Profile context, we get the best OpenGL version.
|
||||
bool ok = SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK,
|
||||
SDL_GL_CONTEXT_PROFILE_CORE);
|
||||
if (!ok) {
|
||||
LOGW("Could not set a GL Core Profile Context");
|
||||
}
|
||||
|
||||
LOGD("Creating OpenGL Core Profile context");
|
||||
screen->gl_context = SDL_GL_CreateContext(screen->window);
|
||||
if (!screen->gl_context) {
|
||||
LOGE("Could not create OpenGL context: %s", SDL_GetError());
|
||||
goto error_destroy_renderer;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
bool mipmaps = params->video;
|
||||
ok = sc_texture_init(&screen->tex, screen->renderer, mipmaps);
|
||||
if (!ok) {
|
||||
goto error_destroy_renderer;
|
||||
}
|
||||
|
||||
ok = SDL_StartTextInput(screen->window);
|
||||
if (!ok) {
|
||||
LOGE("Could not enable text input: %s", SDL_GetError());
|
||||
goto error_destroy_texture;
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
||||
} else if (params->video) {
|
||||
// just a warning
|
||||
LOGW("Could not load icon");
|
||||
} else {
|
||||
// without video, the icon is used as window content, it must be present
|
||||
LOGE("Could not load icon");
|
||||
goto error_destroy_window;
|
||||
}
|
||||
|
||||
SDL_Surface *icon_novideo = params->video ? NULL : icon;
|
||||
bool mipmaps = params->video && params->mipmaps;
|
||||
ok = sc_display_init(&screen->display, screen->window, icon_novideo,
|
||||
mipmaps);
|
||||
if (icon) {
|
||||
scrcpy_icon_destroy(icon);
|
||||
}
|
||||
if (!ok) {
|
||||
goto error_destroy_window;
|
||||
if (!params->video) {
|
||||
screen->content_size.width = icon->w;
|
||||
screen->content_size.height = icon->h;
|
||||
ok = sc_texture_set_from_surface(&screen->tex, icon);
|
||||
if (!ok) {
|
||||
LOGE("Could not set icon: %s", SDL_GetError());
|
||||
}
|
||||
}
|
||||
|
||||
sc_icon_destroy(icon);
|
||||
} else {
|
||||
// not fatal
|
||||
LOGE("Could not load icon");
|
||||
|
||||
if (!params->video) {
|
||||
// Make sure the content size is initialized
|
||||
screen->content_size.width = 256;
|
||||
screen->content_size.height = 256;
|
||||
}
|
||||
}
|
||||
|
||||
screen->frame = av_frame_alloc();
|
||||
if (!screen->frame) {
|
||||
LOG_OOM();
|
||||
goto error_destroy_display;
|
||||
goto error_destroy_texture;
|
||||
}
|
||||
|
||||
struct sc_input_manager_params im_params = {
|
||||
@@ -449,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)) {
|
||||
@@ -459,8 +548,15 @@ sc_screen_init(struct sc_screen *screen,
|
||||
|
||||
return true;
|
||||
|
||||
error_destroy_display:
|
||||
sc_display_destroy(&screen->display);
|
||||
error_destroy_texture:
|
||||
sc_texture_destroy(&screen->tex);
|
||||
error_destroy_renderer:
|
||||
#ifdef SC_DISPLAY_FORCE_OPENGL_CORE_PROFILE
|
||||
if (screen->gl_context) {
|
||||
SDL_GL_DestroyContext(screen->gl_context);
|
||||
}
|
||||
#endif
|
||||
SDL_DestroyRenderer(screen->renderer);
|
||||
error_destroy_window:
|
||||
SDL_DestroyWindow(screen->window);
|
||||
error_destroy_fps_counter:
|
||||
@@ -498,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);
|
||||
}
|
||||
@@ -505,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
|
||||
@@ -512,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
|
||||
@@ -522,11 +630,29 @@ sc_screen_destroy(struct sc_screen *screen) {
|
||||
#ifndef NDEBUG
|
||||
assert(!screen->open);
|
||||
#endif
|
||||
sc_display_destroy(&screen->display);
|
||||
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
|
||||
SDL_GL_DestroyContext(screen->gl_context);
|
||||
#endif
|
||||
SDL_DestroyRenderer(screen->renderer);
|
||||
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
|
||||
@@ -606,6 +732,7 @@ sc_screen_apply_frame(struct sc_screen *screen) {
|
||||
if (!screen->has_frame
|
||||
|| screen->frame_size.width != new_frame_size.width
|
||||
|| screen->frame_size.height != new_frame_size.height) {
|
||||
|
||||
// frame dimension changed
|
||||
screen->frame_size = new_frame_size;
|
||||
|
||||
@@ -619,32 +746,15 @@ sc_screen_apply_frame(struct sc_screen *screen) {
|
||||
screen->has_frame = true;
|
||||
screen->content_size = new_content_size;
|
||||
}
|
||||
|
||||
enum sc_display_result res =
|
||||
sc_display_prepare_texture(&screen->display, screen->frame_size,
|
||||
frame->colorspace, frame->color_range);
|
||||
if (res == SC_DISPLAY_RESULT_ERROR) {
|
||||
return false;
|
||||
}
|
||||
if (res == SC_DISPLAY_RESULT_PENDING) {
|
||||
// Not an error, but do not continue
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
enum sc_display_result res =
|
||||
sc_display_update_texture(&screen->display, frame);
|
||||
if (res == SC_DISPLAY_RESULT_ERROR) {
|
||||
bool ok = sc_texture_set_from_frame(&screen->tex, frame);
|
||||
if (!ok) {
|
||||
return false;
|
||||
}
|
||||
if (res == SC_DISPLAY_RESULT_PENDING) {
|
||||
// Not an error, but do not continue
|
||||
return true;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
@@ -696,7 +806,10 @@ sc_screen_set_paused(struct sc_screen *screen, bool paused) {
|
||||
av_frame_free(&screen->frame);
|
||||
screen->frame = screen->resume_frame;
|
||||
screen->resume_frame = NULL;
|
||||
sc_screen_apply_frame(screen);
|
||||
bool ok = sc_screen_apply_frame(screen);
|
||||
if (!ok) {
|
||||
LOGE("Resume frame update failed");
|
||||
}
|
||||
}
|
||||
|
||||
if (!paused) {
|
||||
@@ -769,59 +882,142 @@ sc_screen_resize_to_pixel_perfect(struct sc_screen *screen) {
|
||||
content_size.height);
|
||||
}
|
||||
|
||||
bool
|
||||
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);
|
||||
if (!ok) {
|
||||
LOGE("Frame update failed\n");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
return;
|
||||
}
|
||||
case SDL_EVENT_WINDOW_EXPOSED:
|
||||
if (!screen->video) {
|
||||
sc_screen_render_novideo(screen);
|
||||
} else if (screen->has_video_window) {
|
||||
sc_screen_render(screen, true);
|
||||
}
|
||||
return 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 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 true;
|
||||
return;
|
||||
case SDL_EVENT_WINDOW_ENTER_FULLSCREEN:
|
||||
LOGD("Switched to fullscreen mode");
|
||||
assert(screen->has_video_window);
|
||||
return true;
|
||||
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 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;
|
||||
}
|
||||
|
||||
if (sc_screen_is_relative_mode(screen)
|
||||
&& sc_mouse_capture_handle_event(&screen->mc, event)) {
|
||||
// The mouse capture handler consumed the event
|
||||
return true;
|
||||
return;
|
||||
}
|
||||
|
||||
sc_input_manager_handle_event(&screen->im, event);
|
||||
return true;
|
||||
}
|
||||
|
||||
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 SDL_EVENT_WINDOW_EXPOSED:
|
||||
sc_screen_render(screen, true);
|
||||
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
|
||||
|
||||
@@ -12,16 +12,21 @@
|
||||
|
||||
#include "controller.h"
|
||||
#include "coords.h"
|
||||
#include "display.h"
|
||||
#include "disconnect.h"
|
||||
#include "fps_counter.h"
|
||||
#include "frame_buffer.h"
|
||||
#include "input_manager.h"
|
||||
#include "mouse_capture.h"
|
||||
#include "options.h"
|
||||
#include "texture.h"
|
||||
#include "trait/key_processor.h"
|
||||
#include "trait/frame_sink.h"
|
||||
#include "trait/mouse_processor.h"
|
||||
|
||||
#ifdef __APPLE__
|
||||
# define SC_DISPLAY_FORCE_OPENGL_CORE_PROFILE
|
||||
#endif
|
||||
|
||||
struct sc_screen {
|
||||
struct sc_frame_sink frame_sink; // frame sink trait
|
||||
|
||||
@@ -32,7 +37,7 @@ struct sc_screen {
|
||||
bool video;
|
||||
bool camera;
|
||||
|
||||
struct sc_display display;
|
||||
struct sc_texture tex;
|
||||
struct sc_input_manager im;
|
||||
struct sc_mouse_capture mc; // only used in mouse relative mode
|
||||
struct sc_frame_buffer fb;
|
||||
@@ -49,6 +54,11 @@ struct sc_screen {
|
||||
} req;
|
||||
|
||||
SDL_Window *window;
|
||||
SDL_Renderer *renderer;
|
||||
#ifdef SC_DISPLAY_FORCE_OPENGL_CORE_PROFILE
|
||||
SDL_GLContext gl_context;
|
||||
#endif
|
||||
|
||||
struct sc_size frame_size;
|
||||
struct sc_size content_size; // rotated frame_size
|
||||
|
||||
@@ -60,14 +70,18 @@ struct sc_screen {
|
||||
// client orientation
|
||||
enum sc_orientation orientation;
|
||||
// rectangle of the content (excluding black borders)
|
||||
struct SDL_Rect rect;
|
||||
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 {
|
||||
@@ -107,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);
|
||||
|
||||
@@ -148,10 +162,13 @@ void
|
||||
sc_screen_set_paused(struct sc_screen *screen, bool paused);
|
||||
|
||||
// react to SDL events
|
||||
// If this function returns false, scrcpy must exit with an error.
|
||||
bool
|
||||
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
|
||||
|
||||
@@ -3,26 +3,14 @@
|
||||
#include <processthreadsapi.h>
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "util/command.h"
|
||||
#include "util/log.h"
|
||||
#include "util/str.h"
|
||||
|
||||
#define CMD_MAX_LEN 8192
|
||||
|
||||
static bool
|
||||
build_cmd(char *cmd, size_t len, const char *const argv[]) {
|
||||
// Windows command-line parsing is WTF:
|
||||
// <http://daviddeley.com/autohotkey/parameters/parameters.htm#WINPASS>
|
||||
// only make it work for this very specific program
|
||||
// (don't handle escaping nor quotes)
|
||||
size_t ret = sc_str_join(cmd, argv, ' ', len);
|
||||
if (ret >= len) {
|
||||
LOGE("Command too long (%" SC_PRIsizet " chars)", len - 1);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
enum sc_process_result
|
||||
sc_process_execute_p(const char *const argv[], HANDLE *handle, unsigned flags,
|
||||
HANDLE *pin, HANDLE *pout, HANDLE *perr) {
|
||||
@@ -137,8 +125,9 @@ sc_process_execute_p(const char *const argv[], HANDLE *handle, unsigned flags,
|
||||
si.lpAttributeList = lpAttributeList;
|
||||
}
|
||||
|
||||
char *cmd = malloc(CMD_MAX_LEN);
|
||||
if (!cmd || !build_cmd(cmd, CMD_MAX_LEN, argv)) {
|
||||
assert(argv && *argv);
|
||||
char *cmd = sc_command_serialize_windows(argv);
|
||||
if (!cmd) {
|
||||
LOG_OOM();
|
||||
goto error_free_attribute_list;
|
||||
}
|
||||
|
||||
235
app/src/texture.c
Normal file
235
app/src/texture.c
Normal file
@@ -0,0 +1,235 @@
|
||||
#include "texture.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <inttypes.h>
|
||||
#include <string.h>
|
||||
#include <libavutil/pixfmt.h>
|
||||
|
||||
#include "util/log.h"
|
||||
|
||||
bool
|
||||
sc_texture_init(struct sc_texture *tex, SDL_Renderer *renderer, bool mipmaps) {
|
||||
const char *renderer_name = SDL_GetRendererName(renderer);
|
||||
LOGI("Renderer: %s", renderer_name ? renderer_name : "(unknown)");
|
||||
|
||||
tex->mipmaps = false;
|
||||
|
||||
// starts with "opengl"
|
||||
bool use_opengl = renderer_name && !strncmp(renderer_name, "opengl", 6);
|
||||
if (use_opengl) {
|
||||
struct sc_opengl *gl = &tex->gl;
|
||||
sc_opengl_init(gl);
|
||||
|
||||
LOGI("OpenGL version: %s", gl->version);
|
||||
|
||||
if (mipmaps) {
|
||||
bool supports_mipmaps =
|
||||
sc_opengl_version_at_least(gl, 3, 0, /* OpenGL 3.0+ */
|
||||
2, 0 /* OpenGL ES 2.0+ */);
|
||||
if (supports_mipmaps) {
|
||||
LOGI("Trilinear filtering enabled");
|
||||
tex->mipmaps = true;
|
||||
} else {
|
||||
LOGW("Trilinear filtering disabled "
|
||||
"(OpenGL 3.0+ or ES 2.0+ required)");
|
||||
}
|
||||
} else {
|
||||
LOGI("Trilinear filtering disabled");
|
||||
}
|
||||
} else if (mipmaps) {
|
||||
LOGD("Trilinear filtering disabled (not an OpenGL renderer)");
|
||||
}
|
||||
|
||||
tex->renderer = renderer;
|
||||
tex->texture = NULL;
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
sc_texture_destroy(struct sc_texture *tex) {
|
||||
if (tex->texture) {
|
||||
SDL_DestroyTexture(tex->texture);
|
||||
}
|
||||
}
|
||||
|
||||
static enum SDL_Colorspace
|
||||
sc_texture_to_sdl_color_space(enum AVColorSpace color_space,
|
||||
enum AVColorRange color_range) {
|
||||
bool full_range = color_range == AVCOL_RANGE_JPEG;
|
||||
|
||||
switch (color_space) {
|
||||
case AVCOL_SPC_BT709:
|
||||
case AVCOL_SPC_RGB:
|
||||
return full_range ? SDL_COLORSPACE_BT709_FULL
|
||||
: SDL_COLORSPACE_BT709_LIMITED;
|
||||
case AVCOL_SPC_BT470BG:
|
||||
case AVCOL_SPC_SMPTE170M:
|
||||
return full_range ? SDL_COLORSPACE_BT601_FULL
|
||||
: SDL_COLORSPACE_BT601_LIMITED;
|
||||
case AVCOL_SPC_BT2020_NCL:
|
||||
case AVCOL_SPC_BT2020_CL:
|
||||
return full_range ? SDL_COLORSPACE_BT2020_FULL
|
||||
: SDL_COLORSPACE_BT2020_LIMITED;
|
||||
default:
|
||||
return SDL_COLORSPACE_JPEG;
|
||||
}
|
||||
}
|
||||
|
||||
static SDL_Texture *
|
||||
sc_texture_create_frame_texture(struct sc_texture *tex,
|
||||
struct sc_size size,
|
||||
enum AVColorSpace color_space,
|
||||
enum AVColorRange color_range) {
|
||||
SDL_PropertiesID props = SDL_CreateProperties();
|
||||
if (!props) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
enum SDL_Colorspace sdl_color_space =
|
||||
sc_texture_to_sdl_color_space(color_space, color_range);
|
||||
|
||||
bool ok =
|
||||
SDL_SetNumberProperty(props, SDL_PROP_TEXTURE_CREATE_FORMAT_NUMBER,
|
||||
SDL_PIXELFORMAT_YV12);
|
||||
ok &= SDL_SetNumberProperty(props, SDL_PROP_TEXTURE_CREATE_ACCESS_NUMBER,
|
||||
SDL_TEXTUREACCESS_STREAMING);
|
||||
ok &= SDL_SetNumberProperty(props, SDL_PROP_TEXTURE_CREATE_WIDTH_NUMBER,
|
||||
size.width);
|
||||
ok &= SDL_SetNumberProperty(props, SDL_PROP_TEXTURE_CREATE_HEIGHT_NUMBER,
|
||||
size.height);
|
||||
ok &= SDL_SetNumberProperty(props,
|
||||
SDL_PROP_TEXTURE_CREATE_COLORSPACE_NUMBER,
|
||||
sdl_color_space);
|
||||
|
||||
if (!ok) {
|
||||
LOGE("Could not set texture properties");
|
||||
SDL_DestroyProperties(props);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
SDL_Renderer *renderer = tex->renderer;
|
||||
SDL_Texture *texture = SDL_CreateTextureWithProperties(renderer, props);
|
||||
SDL_DestroyProperties(props);
|
||||
if (!texture) {
|
||||
LOGD("Could not create texture: %s", SDL_GetError());
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (tex->mipmaps) {
|
||||
struct sc_opengl *gl = &tex->gl;
|
||||
|
||||
SDL_PropertiesID props = SDL_GetTextureProperties(texture);
|
||||
if (!props) {
|
||||
LOGE("Could not get texture properties: %s", SDL_GetError());
|
||||
SDL_DestroyTexture(texture);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
const char *renderer_name = SDL_GetRendererName(tex->renderer);
|
||||
const char *key = !renderer_name || !strcmp(renderer_name, "opengl")
|
||||
? SDL_PROP_TEXTURE_OPENGL_TEXTURE_NUMBER
|
||||
: SDL_PROP_TEXTURE_OPENGLES2_TEXTURE_NUMBER;
|
||||
|
||||
int64_t texture_id = SDL_GetNumberProperty(props, key, 0);
|
||||
SDL_DestroyProperties(props);
|
||||
if (!texture_id) {
|
||||
LOGE("Could not get texture id: %s", SDL_GetError());
|
||||
SDL_DestroyTexture(texture);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
assert(!(texture_id & ~0xFFFFFFFF)); // fits in uint32_t
|
||||
tex->texture_id = texture_id;
|
||||
gl->BindTexture(GL_TEXTURE_2D, tex->texture_id);
|
||||
|
||||
// Enable trilinear filtering for downscaling
|
||||
gl->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
|
||||
GL_LINEAR_MIPMAP_LINEAR);
|
||||
gl->TexParameterf(GL_TEXTURE_2D, GL_TEXTURE_LOD_BIAS, -1.f);
|
||||
|
||||
gl->BindTexture(GL_TEXTURE_2D, 0);
|
||||
}
|
||||
|
||||
return texture;
|
||||
}
|
||||
|
||||
bool
|
||||
sc_texture_set_from_frame(struct sc_texture *tex, const AVFrame *frame) {
|
||||
|
||||
struct sc_size size = {frame->width, frame->height};
|
||||
assert(size.width && size.height);
|
||||
|
||||
if (!tex->texture
|
||||
|| tex->texture_type != SC_TEXTURE_TYPE_FRAME
|
||||
|| tex->texture_size.width != size.width
|
||||
|| tex->texture_size.height != size.height) {
|
||||
// Incompatible texture, recreate it
|
||||
enum AVColorSpace color_space = frame->colorspace;
|
||||
enum AVColorRange color_range = frame->color_range;
|
||||
|
||||
if (tex->texture) {
|
||||
SDL_DestroyTexture(tex->texture);
|
||||
}
|
||||
|
||||
tex->texture = sc_texture_create_frame_texture(tex, size, color_space,
|
||||
color_range);
|
||||
if (!tex->texture) {
|
||||
return false;
|
||||
}
|
||||
|
||||
tex->texture_size = size;
|
||||
tex->texture_type = SC_TEXTURE_TYPE_FRAME;
|
||||
|
||||
LOGI("Texture: %" PRIu16 "x%" PRIu16, size.width, size.height);
|
||||
}
|
||||
|
||||
assert(tex->texture);
|
||||
assert(tex->texture_type == SC_TEXTURE_TYPE_FRAME);
|
||||
|
||||
bool ok = SDL_UpdateYUVTexture(tex->texture, NULL,
|
||||
frame->data[0], frame->linesize[0],
|
||||
frame->data[1], frame->linesize[1],
|
||||
frame->data[2], frame->linesize[2]);
|
||||
if (!ok) {
|
||||
LOGD("Could not update texture: %s", SDL_GetError());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (tex->mipmaps) {
|
||||
assert(tex->texture_id);
|
||||
struct sc_opengl *gl = &tex->gl;
|
||||
|
||||
gl->BindTexture(GL_TEXTURE_2D, tex->texture_id);
|
||||
gl->GenerateMipmap(GL_TEXTURE_2D);
|
||||
gl->BindTexture(GL_TEXTURE_2D, 0);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
sc_texture_set_from_surface(struct sc_texture *tex, SDL_Surface *surface) {
|
||||
if (tex->texture) {
|
||||
SDL_DestroyTexture(tex->texture);
|
||||
}
|
||||
|
||||
tex->texture = SDL_CreateTextureFromSurface(tex->renderer, surface);
|
||||
if (!tex->texture) {
|
||||
LOGE("Could not create texture: %s", SDL_GetError());
|
||||
return false;
|
||||
}
|
||||
|
||||
tex->texture_size.width = surface->w;
|
||||
tex->texture_size.height = surface->h;
|
||||
tex->texture_type = SC_TEXTURE_TYPE_ICON;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
sc_texture_reset(struct sc_texture *tex) {
|
||||
if (tex->texture) {
|
||||
SDL_DestroyTexture(tex->texture);
|
||||
tex->texture = NULL;
|
||||
}
|
||||
}
|
||||
47
app/src/texture.h
Normal file
47
app/src/texture.h
Normal file
@@ -0,0 +1,47 @@
|
||||
#ifndef SC_DISPLAY_H
|
||||
#define SC_DISPLAY_H
|
||||
|
||||
#include "common.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <libavutil/frame.h>
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
#include "coords.h"
|
||||
#include "opengl.h"
|
||||
|
||||
enum sc_texture_type {
|
||||
SC_TEXTURE_TYPE_FRAME,
|
||||
SC_TEXTURE_TYPE_ICON,
|
||||
};
|
||||
|
||||
struct sc_texture {
|
||||
SDL_Renderer *renderer; // owned by the caller
|
||||
SDL_Texture *texture;
|
||||
// Only valid if texture != NULL
|
||||
struct sc_size texture_size;
|
||||
enum sc_texture_type texture_type;
|
||||
|
||||
struct sc_opengl gl;
|
||||
|
||||
bool mipmaps;
|
||||
uint32_t texture_id; // only set if mipmaps is enabled
|
||||
};
|
||||
|
||||
bool
|
||||
sc_texture_init(struct sc_texture *tex, SDL_Renderer *renderer, bool mipmaps);
|
||||
|
||||
void
|
||||
sc_texture_destroy(struct sc_texture *tex);
|
||||
|
||||
bool
|
||||
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
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
98
app/src/util/command.c
Normal file
98
app/src/util/command.c
Normal file
@@ -0,0 +1,98 @@
|
||||
#include "command.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "util/strbuf.h"
|
||||
|
||||
char *
|
||||
sc_command_serialize_windows(const char *const argv[]) {
|
||||
// <https://learn.microsoft.com/en-us/cpp/c-language/parsing-c-command-line-arguments>
|
||||
// <https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-createprocessw>
|
||||
|
||||
struct sc_strbuf buf;
|
||||
bool ok = sc_strbuf_init(&buf, 1024);
|
||||
if (!ok) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
#define BUF_PUSH(C) \
|
||||
do { \
|
||||
if (!sc_strbuf_append_char(&buf, C)) { \
|
||||
goto end; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
while (*argv) {
|
||||
const char *arg = *argv;
|
||||
|
||||
BUF_PUSH('"');
|
||||
|
||||
// <https://learn.microsoft.com/en-us/cpp/c-language/parsing-c-command-line-arguments>
|
||||
//
|
||||
// """
|
||||
// If an even number of backslashes is followed by a double quote mark,
|
||||
// then one backslash (\) is placed in the argv array for every pair of
|
||||
// backslashes (\\), and the double quote mark (") is interpreted as a
|
||||
// string delimiter.
|
||||
//
|
||||
// If an odd number of backslashes is followed by a double quote mark,
|
||||
// then one backslash (\) is placed in the argv array for every pair of
|
||||
// backslashes (\\). The double quote mark is interpreted as an escape
|
||||
// sequence by the remaining backslash, causing a literal double quote
|
||||
// mark (") to be placed in argv.
|
||||
// """
|
||||
//
|
||||
// To produce correct escaping according to what the parser will do, we
|
||||
// must count the number of successive backslashes.
|
||||
unsigned backslashes = 0;
|
||||
|
||||
for (const char *c = arg; *c; c++) {
|
||||
switch (*c) {
|
||||
case '"':
|
||||
while (backslashes) {
|
||||
// Double all backslashes before a quote
|
||||
BUF_PUSH('\\');
|
||||
BUF_PUSH('\\');
|
||||
--backslashes;
|
||||
}
|
||||
BUF_PUSH('\\');
|
||||
BUF_PUSH('"');
|
||||
backslashes = 0;
|
||||
break;
|
||||
case '\\':
|
||||
++backslashes;
|
||||
break;
|
||||
default:
|
||||
while (backslashes) {
|
||||
// Put all backslashes as literals
|
||||
BUF_PUSH('\\');
|
||||
--backslashes;
|
||||
}
|
||||
BUF_PUSH(*c);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
while (backslashes) {
|
||||
// Double all backslashes before a quote
|
||||
BUF_PUSH('\\');
|
||||
BUF_PUSH('\\');
|
||||
--backslashes;
|
||||
}
|
||||
|
||||
BUF_PUSH('"');
|
||||
|
||||
++argv;
|
||||
|
||||
// Argument separator
|
||||
if (*argv) {
|
||||
BUF_PUSH(' ');
|
||||
}
|
||||
}
|
||||
|
||||
return buf.s;
|
||||
|
||||
end:
|
||||
free(buf.s);
|
||||
return NULL;
|
||||
}
|
||||
17
app/src/util/command.h
Normal file
17
app/src/util/command.h
Normal file
@@ -0,0 +1,17 @@
|
||||
#ifndef SC_COMMAND_H
|
||||
#define SC_COMMAND_H
|
||||
|
||||
#include "common.h"
|
||||
|
||||
/**
|
||||
* Serialize an argv array for Windows
|
||||
*
|
||||
* Convert a NULL-terminated argument array into a single escaped string
|
||||
* suitable for massing to CreateProcess() on Windows.
|
||||
*
|
||||
* The returned value must be freed by the caller.
|
||||
*/
|
||||
char *
|
||||
sc_command_serialize_windows(const char *const argv[]);
|
||||
|
||||
#endif
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
@@ -151,6 +151,10 @@ 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);
|
||||
|
||||
@@ -130,6 +130,25 @@ sc_sdl_hide_window(SDL_Window *window) {
|
||||
}
|
||||
}
|
||||
|
||||
struct sc_size
|
||||
sc_sdl_get_render_output_size(SDL_Renderer *renderer) {
|
||||
int width;
|
||||
int height;
|
||||
bool ok = SDL_GetRenderOutputSize(renderer, &width, &height);
|
||||
if (!ok) {
|
||||
LOGE("Could not get render output size: %s", SDL_GetError());
|
||||
LOGE("Please report the error");
|
||||
// fatal error
|
||||
abort();
|
||||
}
|
||||
|
||||
struct sc_size size = {
|
||||
.width = width,
|
||||
.height = height,
|
||||
};
|
||||
return size;
|
||||
}
|
||||
|
||||
bool
|
||||
sc_sdl_render_clear(SDL_Renderer *renderer) {
|
||||
bool ok = SDL_RenderClear(renderer);
|
||||
|
||||
@@ -34,6 +34,9 @@ sc_sdl_show_window(SDL_Window *window);
|
||||
void
|
||||
sc_sdl_hide_window(SDL_Window *window);
|
||||
|
||||
struct sc_size
|
||||
sc_sdl_get_render_output_size(SDL_Renderer *renderer);
|
||||
|
||||
bool
|
||||
sc_sdl_render_clear(SDL_Renderer *renderer);
|
||||
|
||||
|
||||
@@ -49,21 +49,6 @@ truncated:
|
||||
return n;
|
||||
}
|
||||
|
||||
char *
|
||||
sc_str_quote(const char *src) {
|
||||
size_t len = strlen(src);
|
||||
char *quoted = malloc(len + 3);
|
||||
if (!quoted) {
|
||||
LOG_OOM();
|
||||
return NULL;
|
||||
}
|
||||
memcpy("ed[1], src, len);
|
||||
quoted[0] = '"';
|
||||
quoted[len + 1] = '"';
|
||||
quoted[len + 2] = '\0';
|
||||
return quoted;
|
||||
}
|
||||
|
||||
char *
|
||||
sc_str_concat(const char *start, const char *end) {
|
||||
assert(start);
|
||||
|
||||
@@ -32,14 +32,6 @@ sc_strncpy(char *dest, const char *src, size_t n);
|
||||
size_t
|
||||
sc_str_join(char *dst, const char *const tokens[], char sep, size_t n);
|
||||
|
||||
/**
|
||||
* Quote a string
|
||||
*
|
||||
* Return a new allocated string, surrounded with quotes (`"`).
|
||||
*/
|
||||
char *
|
||||
sc_str_quote(const char *src);
|
||||
|
||||
/**
|
||||
* Concat two strings
|
||||
*
|
||||
|
||||
@@ -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();
|
||||
|
||||
61
app/tests/test_command_windows.c
Normal file
61
app/tests/test_command_windows.c
Normal file
@@ -0,0 +1,61 @@
|
||||
#include "common.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "util/command.h"
|
||||
|
||||
static void test_command_with_spaces(void) {
|
||||
const char *const argv[] = {
|
||||
"C:\\Program Files\\scrcpy\\adb",
|
||||
"-s",
|
||||
"serial with spaces",
|
||||
"push",
|
||||
"E:\\some folder\\scrcpy-server",
|
||||
"/data/local/tmp/scrcpy-server.jar",
|
||||
NULL,
|
||||
};
|
||||
char *cmd = sc_command_serialize_windows(argv);
|
||||
const char *expected = "\"C:\\Program Files\\scrcpy\\adb\" "
|
||||
"\"-s\" "
|
||||
"\"serial with spaces\" "
|
||||
"\"push\" "
|
||||
"\"E:\\some folder\\scrcpy-server\" "
|
||||
"\"/data/local/tmp/scrcpy-server.jar\"";
|
||||
|
||||
assert(!strcmp(expected, cmd));
|
||||
free(cmd);
|
||||
}
|
||||
|
||||
static void test_command_with_backslashes(void) {
|
||||
const char *const argv[] = {
|
||||
"a\\\\ b\\",
|
||||
"def \\",
|
||||
"gh\"i\" \\\\",
|
||||
"jkl\\\\",
|
||||
"mno\\",
|
||||
"p\\\"qr",
|
||||
NULL,
|
||||
};
|
||||
|
||||
char *cmd = sc_command_serialize_windows(argv);
|
||||
const char *expected = "\"a\\\\ b\\\\\" "
|
||||
"\"def \\\\\" "
|
||||
"\"gh\\\"i\\\" \\\\\\\\\" "
|
||||
"\"jkl\\\\\\\\\" "
|
||||
"\"mno\\\\\" "
|
||||
"\"p\\\\\\\"qr\"";
|
||||
|
||||
assert(!strcmp(expected, cmd));
|
||||
free(cmd);
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
(void) argc;
|
||||
(void) argv;
|
||||
|
||||
test_command_with_spaces();
|
||||
test_command_with_backslashes();
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -131,16 +131,6 @@ static void test_join_truncated_after_sep(void) {
|
||||
assert(!strcmp("abc de ", s));
|
||||
}
|
||||
|
||||
static void test_quote(void) {
|
||||
const char *s = "abcde";
|
||||
char *out = sc_str_quote(s);
|
||||
|
||||
// add '"' at the beginning and the end
|
||||
assert(!strcmp("\"abcde\"", out));
|
||||
|
||||
free(out);
|
||||
}
|
||||
|
||||
static void test_concat(void) {
|
||||
const char *s = "2024:11";
|
||||
char *out = sc_str_concat("my-prefix:", s);
|
||||
@@ -398,7 +388,6 @@ int main(int argc, char *argv[]) {
|
||||
test_join_truncated_in_token();
|
||||
test_join_truncated_before_sep();
|
||||
test_join_truncated_after_sep();
|
||||
test_quote();
|
||||
test_concat();
|
||||
test_utf8_truncate();
|
||||
test_parse_integer();
|
||||
|
||||
@@ -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` and add the desired arguments.
|
||||
|
||||
@@ -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/"
|
||||
|
||||
@@ -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/"
|
||||
|
||||
@@ -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
2
run
@@ -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" "$@"
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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/>
|
||||
|
||||
Reference in New Issue
Block a user