Compare commits

...

9 Commits

Author SHA1 Message Date
Romain Vimont
6eabe5a8bc Improve portable builds
In portable builds, scrcpy-server.jar was supposed to be present in the
current directory, so in practice it worked only if scrcpy was launched
from its own directory.

Instead, find the absolute path of the executable and build a suitable
path to use scrcpy-server.jar from the same directory.
2019-06-10 16:08:00 +02:00
Romain Vimont
9988d6186e Add functions to convert wide char to UTF-8
There was already utf8_to_wide_char(), used to correctly execute
commands on Windows.

Add the reverse converter: utf8_from_wide_char(). We will need it to
build the scrcpy-server path based on the executable directory.
2019-06-10 16:06:32 +02:00
Romain Vimont
451f3ed309 Extract "scrcpy-server.jar" string
The filename is used at several places.
2019-06-10 16:06:32 +02:00
Romain Vimont
5142e0e621 Simplify portable build configuration
To create a portable build (with scrcpy-server.jar accessible from the
scrcpy directory), replace OVERRIDE_SERVER_PATH by a simple compilation
flag: PORTABLE.

This paves the way to use more complex rules to determine the path of
scrcpy-server.jar in portable builds.
2019-06-10 16:02:57 +02:00
Romain Vimont
e9bacc196a Simplify scrcpy-server path configuration
The full path of scrcpy-server.jar was partially configured from
meson.build then concatenated by C code.

Instead, directly write the path in C.
2019-06-10 15:52:54 +02:00
Romain Vimont
8604f16b30 Truncate device name at UTF-8 code point boundary
Just in case.
2019-06-07 17:45:03 +02:00
Romain Vimont
5d11339259 Inline lock_util functions
They are just tiny wrappers.
2019-06-07 17:19:00 +02:00
Romain Vimont
e2a272bf99 Improve framerate counting
The FPS counter was called only on new frames, so it could not print
values regularly, especially when there are very few FPS (when the
device surface does not change).

To the extreme, it was never able to display 0 fps.

Add a separate thread to print framerate every second.
2019-06-07 17:16:26 +02:00
Romain Vimont
d104d3bda9 Add cond_wait_timeout()
Add a "timed out" version of cond_wait().
2019-06-07 16:54:31 +02:00
18 changed files with 364 additions and 134 deletions

View File

@@ -57,7 +57,7 @@ build-win32: prepare-deps-win32
--buildtype release --strip -Db_lto=true \
-Dcrossbuild_windows=true \
-Dbuild_server=false \
-Doverride_server_path=scrcpy-server.jar )
-Dportable=true )
ninja -C "$(WIN32_BUILD_DIR)"
build-win32-noconsole: prepare-deps-win32
@@ -68,7 +68,7 @@ build-win32-noconsole: prepare-deps-win32
-Dcrossbuild_windows=true \
-Dbuild_server=false \
-Dwindows_noconsole=true \
-Doverride_server_path=scrcpy-server.jar )
-Dportable=true )
ninja -C "$(WIN32_NOCONSOLE_BUILD_DIR)"
prepare-deps-win64:
@@ -81,7 +81,7 @@ build-win64: prepare-deps-win64
--buildtype release --strip -Db_lto=true \
-Dcrossbuild_windows=true \
-Dbuild_server=false \
-Doverride_server_path=scrcpy-server.jar )
-Dportable=true )
ninja -C "$(WIN64_BUILD_DIR)"
build-win64-noconsole: prepare-deps-win64
@@ -92,7 +92,7 @@ build-win64-noconsole: prepare-deps-win64
-Dcrossbuild_windows=true \
-Dbuild_server=false \
-Dwindows_noconsole=true \
-Doverride_server_path=scrcpy-server.jar )
-Dportable=true )
ninja -C "$(WIN64_NOCONSOLE_BUILD_DIR)"
dist-win32: build-server build-win32 build-win32-noconsole

View File

@@ -10,7 +10,6 @@ src = [
'src/file_handler.c',
'src/fps_counter.c',
'src/input_manager.c',
'src/lock_util.c',
'src/net.c',
'src/receiver.c',
'src/recorder.c',
@@ -94,21 +93,9 @@ conf.set_quoted('SCRCPY_VERSION', meson.project_version())
# the prefix used during configuration (meson --prefix=PREFIX)
conf.set_quoted('PREFIX', get_option('prefix'))
# the path of the server, which will be appended to the prefix
# ignored if OVERRIDE_SERVER_PATH if defined
# must be consistent with the install_dir in server/meson.build
conf.set_quoted('PREFIXED_SERVER_PATH', '/share/scrcpy/scrcpy-server.jar')
# the path of the server to be used "as is"
# this is useful for building a "portable" version (with the server in the same
# directory as the client)
override_server_path = get_option('override_server_path')
if override_server_path != ''
conf.set_quoted('OVERRIDE_SERVER_PATH', override_server_path)
else
# undefine it
conf.set('OVERRIDE_SERVER_PATH', false)
endif
# build a "portable" version (with scrcpy-server.jar accessible from the same
# directory as the executable)
conf.set('PORTABLE', get_option('portable'))
# the default client TCP port for the "adb reverse" tunnel
# overridden by option --port

View File

@@ -9,6 +9,7 @@
// not needed here, but winsock2.h must never be included AFTER windows.h
# include <winsock2.h>
# include <windows.h>
# define PATH_SEPARATOR '\\'
# define PRIexitcode "lu"
// <https://stackoverflow.com/a/44383330/1987178>
# ifdef _WIN64
@@ -23,6 +24,7 @@
#else
# include <sys/types.h>
# define PATH_SEPARATOR '/'
# define PRIsizet "zu"
# define PRIexitcode "d"
# define PROCESS_NONE -1
@@ -76,4 +78,9 @@ adb_install(const char *serial, const char *local);
bool
process_check_success(process_t proc, const char *name);
// return the absolute path of the executable (the scrcpy binary)
// may be NULL on error; to be freed by SDL_free
char *
get_executable_path(void);
#endif

View File

@@ -1,60 +1,169 @@
#include "fps_counter.h"
#include <SDL2/SDL_assert.h>
#include <SDL2/SDL_timer.h>
#include "lock_util.h"
#include "log.h"
void
#define FPS_COUNTER_INTERVAL_MS 1000
bool
fps_counter_init(struct fps_counter *counter) {
counter->started = false;
// no need to initialize the other fields, they are meaningful only when
// started is true
counter->mutex = SDL_CreateMutex();
if (!counter->mutex) {
return false;
}
counter->state_cond = SDL_CreateCond();
if (!counter->state_cond) {
SDL_DestroyMutex(counter->mutex);
return false;
}
counter->thread = NULL;
SDL_AtomicSet(&counter->started, 0);
// no need to initialize the other fields, they are unused until started
return true;
}
void
fps_counter_start(struct fps_counter *counter) {
counter->started = true;
counter->slice_start = SDL_GetTicks();
fps_counter_destroy(struct fps_counter *counter) {
SDL_DestroyCond(counter->state_cond);
SDL_DestroyMutex(counter->mutex);
}
// must be called with mutex locked
static void
display_fps(struct fps_counter *counter) {
unsigned rendered_per_second =
counter->nr_rendered * 1000 / FPS_COUNTER_INTERVAL_MS;
if (counter->nr_skipped) {
LOGI("%u fps (+%u frames skipped)", rendered_per_second,
counter->nr_skipped);
} else {
LOGI("%u fps", rendered_per_second);
}
}
// must be called with mutex locked
static void
check_interval_expired(struct fps_counter *counter, uint32_t now) {
if (now < counter->next_timestamp) {
return;
}
display_fps(counter);
counter->nr_rendered = 0;
counter->nr_skipped = 0;
// add a multiple of the interval
uint32_t elapsed_slices =
(now - counter->next_timestamp) / FPS_COUNTER_INTERVAL_MS + 1;
counter->next_timestamp += FPS_COUNTER_INTERVAL_MS * elapsed_slices;
}
static int
run_fps_counter(void *data) {
struct fps_counter *counter = data;
mutex_lock(counter->mutex);
while (!counter->interrupted) {
while (!counter->interrupted && !SDL_AtomicGet(&counter->started)) {
cond_wait(counter->state_cond, counter->mutex);
}
while (!counter->interrupted && SDL_AtomicGet(&counter->started)) {
uint32_t now = SDL_GetTicks();
check_interval_expired(counter, now);
SDL_assert(counter->next_timestamp > now);
uint32_t remaining = counter->next_timestamp - now;
// ignore the reason (timeout or signaled), we just loop anyway
cond_wait_timeout(counter->state_cond, counter->mutex, remaining);
}
}
mutex_unlock(counter->mutex);
return 0;
}
bool
fps_counter_start(struct fps_counter *counter) {
mutex_lock(counter->mutex);
counter->next_timestamp = SDL_GetTicks() + FPS_COUNTER_INTERVAL_MS;
counter->nr_rendered = 0;
counter->nr_skipped = 0;
mutex_unlock(counter->mutex);
SDL_AtomicSet(&counter->started, 1);
cond_signal(counter->state_cond);
// counter->thread is always accessed from the same thread, no need to lock
if (!counter->thread) {
counter->thread =
SDL_CreateThread(run_fps_counter, "fps counter", counter);
if (!counter->thread) {
LOGE("Could not start FPS counter thread");
return false;
}
}
return true;
}
void
fps_counter_stop(struct fps_counter *counter) {
counter->started = false;
SDL_AtomicSet(&counter->started, 0);
cond_signal(counter->state_cond);
}
static void
display_fps(struct fps_counter *counter) {
if (counter->nr_skipped) {
LOGI("%d fps (+%d frames skipped)", counter->nr_rendered,
counter->nr_skipped);
} else {
LOGI("%d fps", counter->nr_rendered);
bool
fps_counter_is_started(struct fps_counter *counter) {
return SDL_AtomicGet(&counter->started);
}
void
fps_counter_interrupt(struct fps_counter *counter) {
if (!counter->thread) {
return;
}
mutex_lock(counter->mutex);
counter->interrupted = true;
mutex_unlock(counter->mutex);
// wake up blocking wait
cond_signal(counter->state_cond);
}
static void
check_expired(struct fps_counter *counter) {
uint32_t now = SDL_GetTicks();
if (now - counter->slice_start >= 1000) {
display_fps(counter);
// add a multiple of one second
uint32_t elapsed_slices = (now - counter->slice_start) / 1000;
counter->slice_start += 1000 * elapsed_slices;
counter->nr_rendered = 0;
counter->nr_skipped = 0;
void
fps_counter_join(struct fps_counter *counter) {
if (counter->thread) {
SDL_WaitThread(counter->thread, NULL);
}
}
void
fps_counter_add_rendered_frame(struct fps_counter *counter) {
check_expired(counter);
if (!SDL_AtomicGet(&counter->started)) {
return;
}
mutex_lock(counter->mutex);
uint32_t now = SDL_GetTicks();
check_interval_expired(counter, now);
++counter->nr_rendered;
mutex_unlock(counter->mutex);
}
void
fps_counter_add_skipped_frame(struct fps_counter *counter) {
check_expired(counter);
if (!SDL_AtomicGet(&counter->started)) {
return;
}
mutex_lock(counter->mutex);
uint32_t now = SDL_GetTicks();
check_interval_expired(counter, now);
++counter->nr_skipped;
mutex_unlock(counter->mutex);
}

View File

@@ -3,23 +3,49 @@
#include <stdbool.h>
#include <stdint.h>
#include <SDL2/SDL_atomic.h>
#include <SDL2/SDL_mutex.h>
#include <SDL2/SDL_thread.h>
struct fps_counter {
bool started;
uint32_t slice_start; // initialized by SDL_GetTicks()
int nr_rendered;
int nr_skipped;
SDL_Thread *thread;
SDL_mutex *mutex;
SDL_cond *state_cond;
// atomic so that we can check without locking the mutex
// if the FPS counter is disabled, we don't want to lock unnecessarily
SDL_atomic_t started;
// the following fields are protected by the mutex
bool interrupted;
unsigned nr_rendered;
unsigned nr_skipped;
uint32_t next_timestamp;
};
void
bool
fps_counter_init(struct fps_counter *counter);
void
fps_counter_destroy(struct fps_counter *counter);
bool
fps_counter_start(struct fps_counter *counter);
void
fps_counter_stop(struct fps_counter *counter);
bool
fps_counter_is_started(struct fps_counter *counter);
// request to stop the thread (on quit)
// must be called before fps_counter_join()
void
fps_counter_interrupt(struct fps_counter *counter);
void
fps_counter_join(struct fps_counter *counter);
void
fps_counter_add_rendered_frame(struct fps_counter *counter);

View File

@@ -172,16 +172,19 @@ set_screen_power_mode(struct controller *controller,
}
static void
switch_fps_counter_state(struct video_buffer *vb) {
mutex_lock(vb->mutex);
if (vb->fps_counter.started) {
switch_fps_counter_state(struct fps_counter *fps_counter) {
// the started state can only be written from the current thread, so there
// is no ToCToU issue
if (fps_counter_is_started(fps_counter)) {
fps_counter_stop(fps_counter);
LOGI("FPS counter stopped");
fps_counter_stop(&vb->fps_counter);
} else {
LOGI("FPS counter started");
fps_counter_start(&vb->fps_counter);
if (fps_counter_start(fps_counter)) {
LOGI("FPS counter started");
} else {
LOGE("FPS counter starting failed");
}
}
mutex_unlock(vb->mutex);
}
static void
@@ -339,7 +342,9 @@ input_manager_process_key(struct input_manager *input_manager,
return;
case SDLK_i:
if (ctrl && !meta && !shift && !repeat && down) {
switch_fps_counter_state(input_manager->video_buffer);
struct fps_counter *fps_counter =
input_manager->video_buffer->fps_counter;
switch_fps_counter_state(fps_counter);
}
return;
case SDLK_n:

View File

@@ -1,38 +0,0 @@
#include <lock_util.h>
#include <stdlib.h>
#include <SDL2/SDL_mutex.h>
#include "log.h"
void
mutex_lock(SDL_mutex *mutex) {
if (SDL_LockMutex(mutex)) {
LOGC("Could not lock mutex");
abort();
}
}
void
mutex_unlock(SDL_mutex *mutex) {
if (SDL_UnlockMutex(mutex)) {
LOGC("Could not unlock mutex");
abort();
}
}
void
cond_wait(SDL_cond *cond, SDL_mutex *mutex) {
if (SDL_CondWait(cond, mutex)) {
LOGC("Could not wait on condition");
abort();
}
}
void
cond_signal(SDL_cond *cond) {
if (SDL_CondSignal(cond)) {
LOGC("Could not signal a condition");
abort();
}
}

View File

@@ -1,20 +1,51 @@
#ifndef LOCKUTIL_H
#define LOCKUTIL_H
// forward declarations
typedef struct SDL_mutex SDL_mutex;
typedef struct SDL_cond SDL_cond;
#include <stdint.h>
#include <SDL2/SDL_mutex.h>
void
mutex_lock(SDL_mutex *mutex);
#include "log.h"
void
mutex_unlock(SDL_mutex *mutex);
static inline void
mutex_lock(SDL_mutex *mutex) {
if (SDL_LockMutex(mutex)) {
LOGC("Could not lock mutex");
abort();
}
}
void
cond_wait(SDL_cond *cond, SDL_mutex *mutex);
static inline void
mutex_unlock(SDL_mutex *mutex) {
if (SDL_UnlockMutex(mutex)) {
LOGC("Could not unlock mutex");
abort();
}
}
void
cond_signal(SDL_cond *cond);
static inline void
cond_wait(SDL_cond *cond, SDL_mutex *mutex) {
if (SDL_CondWait(cond, mutex)) {
LOGC("Could not wait on condition");
abort();
}
}
static inline int
cond_wait_timeout(SDL_cond *cond, SDL_mutex *mutex, uint32_t ms) {
int r = SDL_CondWaitTimeout(cond, mutex, ms);
if (r < 0) {
LOGC("Could not wait on condition with timeout");
abort();
}
return r;
}
static inline void
cond_signal(SDL_cond *cond) {
if (SDL_CondSignal(cond)) {
LOGC("Could not signal a condition");
abort();
}
}
#endif

View File

@@ -29,6 +29,7 @@
static struct server server = SERVER_INITIALIZER;
static struct screen screen = SCREEN_INITIALIZER;
static struct fps_counter fps_counter;
static struct video_buffer video_buffer;
static struct stream stream;
static struct decoder decoder;
@@ -293,6 +294,7 @@ scrcpy(const struct scrcpy_options *options) {
bool ret = false;
bool fps_counter_initialized = false;
bool video_buffer_initialized = false;
bool file_handler_initialized = false;
bool recorder_initialized = false;
@@ -320,7 +322,13 @@ scrcpy(const struct scrcpy_options *options) {
struct decoder *dec = NULL;
if (options->display) {
if (!video_buffer_init(&video_buffer, options->render_expired_frames)) {
if (!fps_counter_init(&fps_counter)) {
goto end;
}
fps_counter_initialized = true;
if (!video_buffer_init(&video_buffer, &fps_counter,
options->render_expired_frames)) {
goto end;
}
video_buffer_initialized = true;
@@ -414,6 +422,9 @@ end:
if (file_handler_initialized) {
file_handler_stop(&file_handler);
}
if (fps_counter_initialized) {
fps_counter_interrupt(&fps_counter);
}
// shutdown the sockets and kill the server
server_stop(&server);
@@ -443,6 +454,11 @@ end:
video_buffer_destroy(&video_buffer);
}
if (fps_counter_initialized) {
fps_counter_join(&fps_counter);
fps_counter_destroy(&fps_counter);
}
if (options->show_touches) {
if (!show_touches_waited) {
// wait the process which enabled "show touches"

View File

@@ -2,31 +2,67 @@
#include <errno.h>
#include <inttypes.h>
#include <libgen.h>
#include <stdio.h>
#include <SDL2/SDL_assert.h>
#include <SDL2/SDL_timer.h>
#include "config.h"
#include "command.h"
#include "log.h"
#include "net.h"
#define SOCKET_NAME "scrcpy"
#define SERVER_FILENAME "scrcpy-server.jar"
#ifdef OVERRIDE_SERVER_PATH
# define DEFAULT_SERVER_PATH OVERRIDE_SERVER_PATH
#else
# define DEFAULT_SERVER_PATH PREFIX PREFIXED_SERVER_PATH
#endif
#define DEVICE_SERVER_PATH "/data/local/tmp/scrcpy-server.jar"
#define DEFAULT_SERVER_PATH PREFIX "/share/scrcpy/" SERVER_FLENAME
#define DEVICE_SERVER_PATH "/data/local/tmp/" SERVER_FILENAME
static const char *
get_server_path(void) {
const char *server_path = getenv("SCRCPY_SERVER_PATH");
if (!server_path) {
server_path = DEFAULT_SERVER_PATH;
const char *server_path_env = getenv("SCRCPY_SERVER_PATH");
if (server_path_env) {
LOGD("Using SCRCPY_SERVER_PATH: %s", server_path_env);
// if the envvar is set, use it
return server_path_env;
}
#ifndef PORTABLE
LOGD("Using server: " DEFAULT_SERVER_PATH);
// the absolute path is hardcoded
return DEFAULT_SERVER_PATH;
#else
// use scrcpy-server.jar in the same directory as the executable
char *executable_path = get_executable_path();
if (!executable_path) {
LOGE("Cannot get executable path, "
"using " SERVER_FILENAME " from current directory");
// not found, use current directory
return SERVER_FILENAME;
}
char *dir = dirname(executable_path);
size_t dirlen = strlen(dir);
// sizeof(SERVER_FILENAME) gives statically the size including the null byte
size_t len = dirlen + 1 + sizeof(SERVER_FILENAME);
char *server_path = SDL_malloc(len);
if (!server_path) {
LOGE("Cannot alloc server path string, "
"using " SERVER_FILENAME " from current directory");
SDL_free(executable_path);
return SERVER_FILENAME;
}
memcpy(server_path, dir, dirlen);
server_path[dirlen] = PATH_SEPARATOR;
memcpy(&server_path[dirlen + 1], SERVER_FILENAME, sizeof(SERVER_FILENAME));
// the final null byte has been copied with SERVER_FILENAME
SDL_free(executable_path);
LOGD("Using server (portable): %s", server_path);
return server_path;
#endif
}
static bool
@@ -86,7 +122,7 @@ execute_server(struct server *server, const struct server_params *params) {
sprintf(bit_rate_string, "%"PRIu32, params->bit_rate);
const char *const cmd[] = {
"shell",
"CLASSPATH=/data/local/tmp/scrcpy-server.jar",
"CLASSPATH=/data/local/tmp/" SERVER_FILENAME,
"app_process",
"/", // unused
"com.genymobile.scrcpy.Server",

View File

@@ -92,4 +92,20 @@ utf8_to_wide_char(const char *utf8) {
return wide;
}
char *
utf8_from_wide_char(const wchar_t *ws) {
int len = WideCharToMultiByte(CP_UTF8, 0, ws, -1, NULL, 0, NULL, NULL);
if (!len) {
return NULL;
}
char *utf8 = SDL_malloc(len);
if (!utf8) {
return NULL;
}
WideCharToMultiByte(CP_UTF8, 0, ws, -1, utf8, len, NULL, NULL);
return utf8;
}
#endif

View File

@@ -32,6 +32,9 @@ utf8_truncation_index(const char *utf8, size_t max_len);
// returns the new allocated string, to be freed by the caller
wchar_t *
utf8_to_wide_char(const char *utf8);
char *
utf8_from_wide_char(const wchar_t *s);
#endif
#endif

View File

@@ -1,9 +1,15 @@
// for portability
#define _POSIX_SOURCE // for kill()
#define _BSD_SOURCE // for readlink()
// modern glibc will complain without this
#define _DEFAULT_SOURCE
#include "command.h"
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <signal.h>
#include <stdlib.h>
#include <sys/types.h>
@@ -98,3 +104,15 @@ cmd_simple_wait(pid_t pid, int *exit_code) {
}
return !code;
}
char *
get_executable_path(void) {
char buf[PATH_MAX + 1]; // +1 for the null byte
ssize_t len = readlink("/proc/self/exe", buf, PATH_MAX);
if (len == -1) {
perror("readlink");
return NULL;
}
buf[len] = '\0';
return SDL_strdup(buf);
}

View File

@@ -75,3 +75,18 @@ cmd_simple_wait(HANDLE handle, DWORD *exit_code) {
}
return !code;
}
char *
get_executable_path(void) {
HMODULE hModule = GetModuleHandleW(NULL);
if (!hModule) {
return NULL;
}
WCHAR buf[MAX_PATH + 1]; // +1 for the null byte
int len = GetModuleFileNameW(hModule, buf, MAX_PATH);
if (!len) {
return NULL;
}
buf[len] = '\0';
return utf8_from_wide_char(buf);
}

View File

@@ -10,7 +10,10 @@
#include "log.h"
bool
video_buffer_init(struct video_buffer *vb, bool render_expired_frames) {
video_buffer_init(struct video_buffer *vb, struct fps_counter *fps_counter,
bool render_expired_frames) {
vb->fps_counter = fps_counter;
if (!(vb->decoding_frame = av_frame_alloc())) {
goto error_0;
}
@@ -37,7 +40,6 @@ video_buffer_init(struct video_buffer *vb, bool render_expired_frames) {
// there is initially no rendering frame, so consider it has already been
// consumed
vb->rendering_frame_consumed = true;
fps_counter_init(&vb->fps_counter);
return true;
@@ -75,10 +77,8 @@ video_buffer_offer_decoded_frame(struct video_buffer *vb,
while (!vb->rendering_frame_consumed && !vb->interrupted) {
cond_wait(vb->rendering_frame_consumed_cond, vb->mutex);
}
} else {
if (vb->fps_counter.started && !vb->rendering_frame_consumed) {
fps_counter_add_skipped_frame(&vb->fps_counter);
}
} else if (!vb->rendering_frame_consumed) {
fps_counter_add_skipped_frame(vb->fps_counter);
}
video_buffer_swap_frames(vb);
@@ -93,9 +93,7 @@ const AVFrame *
video_buffer_consume_rendered_frame(struct video_buffer *vb) {
SDL_assert(!vb->rendering_frame_consumed);
vb->rendering_frame_consumed = true;
if (vb->fps_counter.started) {
fps_counter_add_rendered_frame(&vb->fps_counter);
}
fps_counter_add_rendered_frame(vb->fps_counter);
if (vb->render_expired_frames) {
// unblock video_buffer_offer_decoded_frame()
cond_signal(vb->rendering_frame_consumed_cond);

View File

@@ -17,11 +17,12 @@ struct video_buffer {
bool interrupted;
SDL_cond *rendering_frame_consumed_cond;
bool rendering_frame_consumed;
struct fps_counter fps_counter;
struct fps_counter *fps_counter;
};
bool
video_buffer_init(struct video_buffer *vb, bool render_expired_frames);
video_buffer_init(struct video_buffer *vb, struct fps_counter *fps_counter,
bool render_expired_frames);
void
video_buffer_destroy(struct video_buffer *vb);

View File

@@ -3,6 +3,6 @@ option('build_server', type: 'boolean', value: true, description: 'Build the ser
option('crossbuild_windows', type: 'boolean', value: false, description: 'Build for Windows from Linux')
option('windows_noconsole', type: 'boolean', value: false, description: 'Disable console on Windows (pass -mwindows flag)')
option('prebuilt_server', type: 'string', description: 'Path of the prebuilt server')
option('override_server_path', type: 'string', description: 'Hardcoded path to find the server at runtime')
option('portable', type: 'boolean', description: 'Use scrcpy-server.jar from the same directory as the scrcpy executable')
option('skip_frames', type: 'boolean', value: true, description: 'Always display the most recent frame')
option('hidpi_support', type: 'boolean', value: true, description: 'Enable High DPI support')

View File

@@ -89,7 +89,7 @@ public final class DesktopConnection implements Closeable {
byte[] buffer = new byte[DEVICE_NAME_FIELD_LENGTH + 4];
byte[] deviceNameBytes = deviceName.getBytes(StandardCharsets.UTF_8);
int len = Math.min(DEVICE_NAME_FIELD_LENGTH - 1, deviceNameBytes.length);
int len = StringUtils.getUtf8TruncationIndex(deviceNameBytes, DEVICE_NAME_FIELD_LENGTH - 1);
System.arraycopy(deviceNameBytes, 0, buffer, 0, len);
// byte[] are always 0-initialized in java, no need to set '\0' explicitly