Compare commits
21 Commits
display_pa
...
icon_decod
Author | SHA1 | Date | |
---|---|---|---|
77ebafd96c | |||
9ea4446369 | |||
5d1d5bdc16 | |||
fd9498e07c | |||
09e8c20168 | |||
da484b7ab9 | |||
063a8339ed | |||
b5c8de08e0 | |||
45fe6b602b | |||
cca2c9ffb7 | |||
22d78e8a82 | |||
bcb8503b26 | |||
9aa6cc71be | |||
54e08b4eae | |||
bd8b945bb3 | |||
a73bf932d6 | |||
7011dd1ef0 | |||
ee6620d123 | |||
aa34d63171 | |||
bf625790fa | |||
db55edb196 |
@ -316,6 +316,10 @@ Disable video forwarding.
|
|||||||
.B \-\-no\-video\-playback
|
.B \-\-no\-video\-playback
|
||||||
Disable video playback on the computer.
|
Disable video playback on the computer.
|
||||||
|
|
||||||
|
.TP
|
||||||
|
.B \-\-no\-window
|
||||||
|
Disable scrcpy window. Implies --no-video-playback and --no-control.
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.BI "\-\-orientation " value
|
.BI "\-\-orientation " value
|
||||||
Same as --display-orientation=value --record-orientation=value.
|
Same as --display-orientation=value --record-orientation=value.
|
||||||
|
@ -194,7 +194,11 @@ sc_audio_player_frame_sink_push(struct sc_frame_sink *sink,
|
|||||||
// Still insufficient, drop old samples to make space
|
// Still insufficient, drop old samples to make space
|
||||||
skipped_samples = sc_audiobuf_read(&ap->buf, NULL, remaining);
|
skipped_samples = sc_audiobuf_read(&ap->buf, NULL, remaining);
|
||||||
assert(skipped_samples == remaining);
|
assert(skipped_samples == remaining);
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_UnlockAudioDevice(ap->device);
|
||||||
|
|
||||||
|
if (written < samples) {
|
||||||
// Now there is enough space
|
// Now there is enough space
|
||||||
uint32_t w = sc_audiobuf_write(&ap->buf,
|
uint32_t w = sc_audiobuf_write(&ap->buf,
|
||||||
swr_buf + TO_BYTES(written),
|
swr_buf + TO_BYTES(written),
|
||||||
@ -202,8 +206,6 @@ sc_audio_player_frame_sink_push(struct sc_frame_sink *sink,
|
|||||||
assert(w == remaining);
|
assert(w == remaining);
|
||||||
(void) w;
|
(void) w;
|
||||||
}
|
}
|
||||||
|
|
||||||
SDL_UnlockAudioDevice(ap->device);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t underflow = 0;
|
uint32_t underflow = 0;
|
||||||
|
@ -97,6 +97,7 @@ enum {
|
|||||||
OPT_MOUSE,
|
OPT_MOUSE,
|
||||||
OPT_HID_KEYBOARD_DEPRECATED,
|
OPT_HID_KEYBOARD_DEPRECATED,
|
||||||
OPT_HID_MOUSE_DEPRECATED,
|
OPT_HID_MOUSE_DEPRECATED,
|
||||||
|
OPT_NO_WINDOW,
|
||||||
};
|
};
|
||||||
|
|
||||||
struct sc_option {
|
struct sc_option {
|
||||||
@ -566,6 +567,12 @@ static const struct sc_option options[] = {
|
|||||||
.longopt = "no-video-playback",
|
.longopt = "no-video-playback",
|
||||||
.text = "Disable video playback on the computer.",
|
.text = "Disable video playback on the computer.",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
.longopt_id = OPT_NO_WINDOW,
|
||||||
|
.longopt = "no-window",
|
||||||
|
.text = "Disable scrcpy window. Implies --no-video-playback and "
|
||||||
|
"--no-control.",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
.longopt_id = OPT_ORIENTATION,
|
.longopt_id = OPT_ORIENTATION,
|
||||||
.longopt = "orientation",
|
.longopt = "orientation",
|
||||||
@ -2486,6 +2493,9 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
|
|||||||
case OPT_CAMERA_HIGH_SPEED:
|
case OPT_CAMERA_HIGH_SPEED:
|
||||||
opts->camera_high_speed = true;
|
opts->camera_high_speed = true;
|
||||||
break;
|
break;
|
||||||
|
case OPT_NO_WINDOW:
|
||||||
|
opts->window = false;
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
// getopt prints the error message on stderr
|
// getopt prints the error message on stderr
|
||||||
return false;
|
return false;
|
||||||
@ -2523,6 +2533,12 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
|
|||||||
v4l2 = !!opts->v4l2_device;
|
v4l2 = !!opts->v4l2_device;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
if (!opts->window) {
|
||||||
|
// Without window, there cannot be any video playback or control
|
||||||
|
opts->video_playback = false;
|
||||||
|
opts->control = false;
|
||||||
|
}
|
||||||
|
|
||||||
if (!opts->video) {
|
if (!opts->video) {
|
||||||
opts->video_playback = false;
|
opts->video_playback = false;
|
||||||
// Do not power on the device on start if video capture is disabled
|
// Do not power on the device on start if video capture is disabled
|
||||||
@ -2544,8 +2560,8 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
|
|||||||
opts->audio = false;
|
opts->audio = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!opts->video && !opts->audio && !otg) {
|
if (!opts->video && !opts->audio && !opts->control && !otg) {
|
||||||
LOGE("No video, no audio, no OTG: nothing to do");
|
LOGE("No video, no audio, no control, no OTG: nothing to do");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2556,9 +2572,9 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
|
|||||||
|
|
||||||
if (opts->audio_playback && opts->audio_buffer == -1) {
|
if (opts->audio_playback && opts->audio_buffer == -1) {
|
||||||
if (opts->audio_codec == SC_CODEC_FLAC) {
|
if (opts->audio_codec == SC_CODEC_FLAC) {
|
||||||
// Use 50 ms audio buffer by default, but use a higher value for FLAC,
|
// Use 50 ms audio buffer by default, but use a higher value for
|
||||||
// which is not low latency (the default encoder produces blocks of
|
// FLAC, which is not low latency (the default encoder produces
|
||||||
// 4096 samples, which represent ~85.333ms).
|
// blocks of 4096 samples, which represent ~85.333ms).
|
||||||
LOGI("FLAC audio: audio buffer increased to 120 ms (use "
|
LOGI("FLAC audio: audio buffer increased to 120 ms (use "
|
||||||
"--audio-buffer to set a custom value)");
|
"--audio-buffer to set a custom value)");
|
||||||
opts->audio_buffer = SC_TICK_FROM_MS(120);
|
opts->audio_buffer = SC_TICK_FROM_MS(120);
|
||||||
@ -2569,6 +2585,11 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
|
|||||||
|
|
||||||
#ifdef HAVE_V4L2
|
#ifdef HAVE_V4L2
|
||||||
if (v4l2) {
|
if (v4l2) {
|
||||||
|
if (!opts->video) {
|
||||||
|
LOGE("V4L2 sink requires video capture, but --no-video was set.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (opts->lock_video_orientation ==
|
if (opts->lock_video_orientation ==
|
||||||
SC_LOCK_VIDEO_ORIENTATION_UNLOCKED) {
|
SC_LOCK_VIDEO_ORIENTATION_UNLOCKED) {
|
||||||
LOGI("Video orientation is locked for v4l2 sink. "
|
LOGI("Video orientation is locked for v4l2 sink. "
|
||||||
@ -2588,16 +2609,33 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if (opts->keyboard_input_mode == SC_KEYBOARD_INPUT_MODE_AUTO) {
|
if (opts->control) {
|
||||||
opts->keyboard_input_mode = otg ? SC_KEYBOARD_INPUT_MODE_AOA
|
if (opts->keyboard_input_mode == SC_KEYBOARD_INPUT_MODE_AUTO) {
|
||||||
: SC_KEYBOARD_INPUT_MODE_SDK;
|
opts->keyboard_input_mode = otg ? SC_KEYBOARD_INPUT_MODE_AOA
|
||||||
}
|
: SC_KEYBOARD_INPUT_MODE_SDK;
|
||||||
if (opts->mouse_input_mode == SC_MOUSE_INPUT_MODE_AUTO) {
|
}
|
||||||
opts->mouse_input_mode = otg ? SC_MOUSE_INPUT_MODE_AOA
|
if (opts->mouse_input_mode == SC_MOUSE_INPUT_MODE_AUTO) {
|
||||||
: SC_MOUSE_INPUT_MODE_SDK;
|
if (otg) {
|
||||||
|
opts->mouse_input_mode = SC_MOUSE_INPUT_MODE_AOA;
|
||||||
|
} else if (!opts->video_playback) {
|
||||||
|
LOGI("No video mirroring, mouse mode switched to UHID");
|
||||||
|
opts->mouse_input_mode = SC_MOUSE_INPUT_MODE_UHID;
|
||||||
|
} else {
|
||||||
|
opts->mouse_input_mode = SC_MOUSE_INPUT_MODE_SDK;
|
||||||
|
}
|
||||||
|
} else if (opts->mouse_input_mode == SC_MOUSE_INPUT_MODE_SDK
|
||||||
|
&& !opts->video_playback) {
|
||||||
|
LOGE("SDK mouse mode requires video playback. Try --mouse=uhid.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (otg) {
|
if (otg) {
|
||||||
|
if (!opts->control) {
|
||||||
|
LOGE("--no-control is not allowed in OTG mode");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
enum sc_keyboard_input_mode kmode = opts->keyboard_input_mode;
|
enum sc_keyboard_input_mode kmode = opts->keyboard_input_mode;
|
||||||
if (kmode != SC_KEYBOARD_INPUT_MODE_AOA
|
if (kmode != SC_KEYBOARD_INPUT_MODE_AOA
|
||||||
&& kmode != SC_KEYBOARD_INPUT_MODE_DISABLED) {
|
&& kmode != SC_KEYBOARD_INPUT_MODE_DISABLED) {
|
||||||
@ -2700,6 +2738,11 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (opts->record_filename) {
|
if (opts->record_filename) {
|
||||||
|
if (!opts->video && !opts->audio) {
|
||||||
|
LOGE("Video and audio disabled, nothing to record");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (!opts->record_format) {
|
if (!opts->record_format) {
|
||||||
opts->record_format = guess_record_format(opts->record_filename);
|
opts->record_format = guess_record_format(opts->record_filename);
|
||||||
if (!opts->record_format) {
|
if (!opts->record_format) {
|
||||||
@ -2806,6 +2849,11 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
|
|||||||
}
|
}
|
||||||
# endif
|
# endif
|
||||||
|
|
||||||
|
if (opts->start_fps_counter && !opts->video_playback) {
|
||||||
|
LOGW("--print-fps has no effect without video playback");
|
||||||
|
opts->start_fps_counter = false;
|
||||||
|
}
|
||||||
|
|
||||||
if (otg) {
|
if (otg) {
|
||||||
// OTG mode is compatible with only very few options.
|
// OTG mode is compatible with only very few options.
|
||||||
// Only report obvious errors.
|
// Only report obvious errors.
|
||||||
|
@ -6,8 +6,19 @@
|
|||||||
|
|
||||||
#define SC_CONTROL_MSG_QUEUE_MAX 64
|
#define SC_CONTROL_MSG_QUEUE_MAX 64
|
||||||
|
|
||||||
|
static void
|
||||||
|
sc_controller_receiver_on_error(struct sc_receiver *receiver, void *userdata) {
|
||||||
|
(void) receiver;
|
||||||
|
|
||||||
|
struct sc_controller *controller = userdata;
|
||||||
|
// Forward the event to the controller listener
|
||||||
|
controller->cbs->on_error(controller, controller->cbs_userdata);
|
||||||
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
sc_controller_init(struct sc_controller *controller, sc_socket control_socket) {
|
sc_controller_init(struct sc_controller *controller, sc_socket control_socket,
|
||||||
|
const struct sc_controller_callbacks *cbs,
|
||||||
|
void *cbs_userdata) {
|
||||||
sc_vecdeque_init(&controller->queue);
|
sc_vecdeque_init(&controller->queue);
|
||||||
|
|
||||||
bool ok = sc_vecdeque_reserve(&controller->queue, SC_CONTROL_MSG_QUEUE_MAX);
|
bool ok = sc_vecdeque_reserve(&controller->queue, SC_CONTROL_MSG_QUEUE_MAX);
|
||||||
@ -15,7 +26,12 @@ sc_controller_init(struct sc_controller *controller, sc_socket control_socket) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
ok = sc_receiver_init(&controller->receiver, control_socket);
|
static const struct sc_receiver_callbacks receiver_cbs = {
|
||||||
|
.on_error = sc_controller_receiver_on_error,
|
||||||
|
};
|
||||||
|
|
||||||
|
ok = sc_receiver_init(&controller->receiver, control_socket, &receiver_cbs,
|
||||||
|
controller);
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
sc_vecdeque_destroy(&controller->queue);
|
sc_vecdeque_destroy(&controller->queue);
|
||||||
return false;
|
return false;
|
||||||
@ -39,6 +55,10 @@ sc_controller_init(struct sc_controller *controller, sc_socket control_socket) {
|
|||||||
controller->control_socket = control_socket;
|
controller->control_socket = control_socket;
|
||||||
controller->stopped = false;
|
controller->stopped = false;
|
||||||
|
|
||||||
|
assert(cbs && cbs->on_error);
|
||||||
|
controller->cbs = cbs;
|
||||||
|
controller->cbs_userdata = cbs_userdata;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -125,10 +145,16 @@ run_controller(void *data) {
|
|||||||
sc_control_msg_destroy(&msg);
|
sc_control_msg_destroy(&msg);
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
LOGD("Could not write msg to socket");
|
LOGD("Could not write msg to socket");
|
||||||
break;
|
goto error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
|
error:
|
||||||
|
controller->cbs->on_error(controller, controller->cbs_userdata);
|
||||||
|
|
||||||
|
return 1; // ignored
|
||||||
}
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
|
@ -22,10 +22,19 @@ struct sc_controller {
|
|||||||
bool stopped;
|
bool stopped;
|
||||||
struct sc_control_msg_queue queue;
|
struct sc_control_msg_queue queue;
|
||||||
struct sc_receiver receiver;
|
struct sc_receiver receiver;
|
||||||
|
|
||||||
|
const struct sc_controller_callbacks *cbs;
|
||||||
|
void *cbs_userdata;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct sc_controller_callbacks {
|
||||||
|
void (*on_error)(struct sc_controller *controller, void *userdata);
|
||||||
};
|
};
|
||||||
|
|
||||||
bool
|
bool
|
||||||
sc_controller_init(struct sc_controller *controller, sc_socket control_socket);
|
sc_controller_init(struct sc_controller *controller, sc_socket control_socket,
|
||||||
|
const struct sc_controller_callbacks *cbs,
|
||||||
|
void *cbs_userdata);
|
||||||
|
|
||||||
void
|
void
|
||||||
sc_controller_configure(struct sc_controller *controller,
|
sc_controller_configure(struct sc_controller *controller,
|
||||||
|
@ -1,11 +1,34 @@
|
|||||||
#include "display.h"
|
#include "display.h"
|
||||||
|
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
|
#include <libavutil/pixfmt.h>
|
||||||
|
|
||||||
#include "util/log.h"
|
#include "util/log.h"
|
||||||
|
|
||||||
|
static bool
|
||||||
|
sc_display_init_novideo_icon(struct sc_display *display,
|
||||||
|
SDL_Surface *icon_novideo) {
|
||||||
|
assert(icon_novideo);
|
||||||
|
|
||||||
|
if (SDL_RenderSetLogicalSize(display->renderer,
|
||||||
|
icon_novideo->w, icon_novideo->h)) {
|
||||||
|
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
|
bool
|
||||||
sc_display_init(struct sc_display *display, SDL_Window *window, bool mipmaps) {
|
sc_display_init(struct sc_display *display, SDL_Window *window,
|
||||||
|
SDL_Surface *icon_novideo, bool mipmaps) {
|
||||||
display->renderer =
|
display->renderer =
|
||||||
SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);
|
SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);
|
||||||
if (!display->renderer) {
|
if (!display->renderer) {
|
||||||
@ -65,6 +88,19 @@ sc_display_init(struct sc_display *display, SDL_Window *window, bool mipmaps) {
|
|||||||
display->texture = NULL;
|
display->texture = NULL;
|
||||||
display->pending.flags = 0;
|
display->pending.flags = 0;
|
||||||
display->pending.frame = NULL;
|
display->pending.frame = NULL;
|
||||||
|
display->has_frame = false;
|
||||||
|
|
||||||
|
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_DeleteContext(display->gl_context);
|
||||||
|
#endif
|
||||||
|
SDL_DestroyRenderer(display->renderer);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -196,9 +232,25 @@ sc_display_set_texture_size(struct sc_display *display, struct sc_size size) {
|
|||||||
return SC_DISPLAY_RESULT_OK;
|
return SC_DISPLAY_RESULT_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static SDL_YUV_CONVERSION_MODE
|
||||||
|
sc_display_to_sdl_color_range(enum AVColorRange color_range) {
|
||||||
|
return color_range == AVCOL_RANGE_JPEG ? SDL_YUV_CONVERSION_JPEG
|
||||||
|
: SDL_YUV_CONVERSION_AUTOMATIC;
|
||||||
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
sc_display_update_texture_internal(struct sc_display *display,
|
sc_display_update_texture_internal(struct sc_display *display,
|
||||||
const AVFrame *frame) {
|
const AVFrame *frame) {
|
||||||
|
if (!display->has_frame) {
|
||||||
|
// First frame
|
||||||
|
display->has_frame = true;
|
||||||
|
|
||||||
|
// Configure YUV color range conversion
|
||||||
|
SDL_YUV_CONVERSION_MODE sdl_color_range =
|
||||||
|
sc_display_to_sdl_color_range(frame->color_range);
|
||||||
|
SDL_SetYUVConversionMode(sdl_color_range);
|
||||||
|
}
|
||||||
|
|
||||||
int ret = SDL_UpdateYUVTexture(display->texture, NULL,
|
int ret = SDL_UpdateYUVTexture(display->texture, NULL,
|
||||||
frame->data[0], frame->linesize[0],
|
frame->data[0], frame->linesize[0],
|
||||||
frame->data[1], frame->linesize[1],
|
frame->data[1], frame->linesize[1],
|
||||||
|
@ -33,6 +33,8 @@ struct sc_display {
|
|||||||
struct sc_size size;
|
struct sc_size size;
|
||||||
AVFrame *frame;
|
AVFrame *frame;
|
||||||
} pending;
|
} pending;
|
||||||
|
|
||||||
|
bool has_frame;
|
||||||
};
|
};
|
||||||
|
|
||||||
enum sc_display_result {
|
enum sc_display_result {
|
||||||
@ -42,7 +44,8 @@ enum sc_display_result {
|
|||||||
};
|
};
|
||||||
|
|
||||||
bool
|
bool
|
||||||
sc_display_init(struct sc_display *display, SDL_Window *window, bool mipmaps);
|
sc_display_init(struct sc_display *display, SDL_Window *window,
|
||||||
|
SDL_Surface *icon_novideo, bool mipmaps);
|
||||||
|
|
||||||
void
|
void
|
||||||
sc_display_destroy(struct sc_display *display);
|
sc_display_destroy(struct sc_display *display);
|
||||||
|
@ -7,3 +7,4 @@
|
|||||||
#define SC_EVENT_RECORDER_ERROR (SDL_USEREVENT + 6)
|
#define SC_EVENT_RECORDER_ERROR (SDL_USEREVENT + 6)
|
||||||
#define SC_EVENT_SCREEN_INIT_SIZE (SDL_USEREVENT + 7)
|
#define SC_EVENT_SCREEN_INIT_SIZE (SDL_USEREVENT + 7)
|
||||||
#define SC_EVENT_TIME_LIMIT_REACHED (SDL_USEREVENT + 8)
|
#define SC_EVENT_TIME_LIMIT_REACHED (SDL_USEREVENT + 8)
|
||||||
|
#define SC_EVENT_CONTROLLER_ERROR (SDL_USEREVENT + 9)
|
||||||
|
@ -78,7 +78,10 @@ decode_image(const char *path) {
|
|||||||
goto close_input;
|
goto close_input;
|
||||||
}
|
}
|
||||||
|
|
||||||
int stream = av_find_best_stream(ctx, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);
|
const AVCodec *codec;
|
||||||
|
|
||||||
|
int stream =
|
||||||
|
av_find_best_stream(ctx, AVMEDIA_TYPE_VIDEO, -1, -1, &codec, 0);
|
||||||
if (stream < 0 ) {
|
if (stream < 0 ) {
|
||||||
LOGE("Could not find best image stream");
|
LOGE("Could not find best image stream");
|
||||||
goto close_input;
|
goto close_input;
|
||||||
@ -86,12 +89,6 @@ decode_image(const char *path) {
|
|||||||
|
|
||||||
AVCodecParameters *params = ctx->streams[stream]->codecpar;
|
AVCodecParameters *params = ctx->streams[stream]->codecpar;
|
||||||
|
|
||||||
const AVCodec *codec = avcodec_find_decoder(params->codec_id);
|
|
||||||
if (!codec) {
|
|
||||||
LOGE("Could not find image decoder");
|
|
||||||
goto close_input;
|
|
||||||
}
|
|
||||||
|
|
||||||
AVCodecContext *codec_ctx = avcodec_alloc_context3(codec);
|
AVCodecContext *codec_ctx = avcodec_alloc_context3(codec);
|
||||||
if (!codec_ctx) {
|
if (!codec_ctx) {
|
||||||
LOG_OOM();
|
LOG_OOM();
|
||||||
|
@ -403,6 +403,7 @@ sc_input_manager_process_key(struct sc_input_manager *im,
|
|||||||
// controller is NULL if --no-control is requested
|
// controller is NULL if --no-control is requested
|
||||||
bool control = im->controller;
|
bool control = im->controller;
|
||||||
bool paused = im->screen->paused;
|
bool paused = im->screen->paused;
|
||||||
|
bool video = im->screen->video;
|
||||||
|
|
||||||
SDL_Keycode keycode = event->keysym.sym;
|
SDL_Keycode keycode = event->keysym.sym;
|
||||||
uint16_t mod = event->keysym.mod;
|
uint16_t mod = event->keysym.mod;
|
||||||
@ -462,13 +463,13 @@ sc_input_manager_process_key(struct sc_input_manager *im,
|
|||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
case SDLK_z:
|
case SDLK_z:
|
||||||
if (down && !repeat) {
|
if (video && down && !repeat) {
|
||||||
sc_screen_set_paused(im->screen, !shift);
|
sc_screen_set_paused(im->screen, !shift);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
case SDLK_DOWN:
|
case SDLK_DOWN:
|
||||||
if (shift) {
|
if (shift) {
|
||||||
if (!repeat & down) {
|
if (video && !repeat && down) {
|
||||||
apply_orientation_transform(im,
|
apply_orientation_transform(im,
|
||||||
SC_ORIENTATION_FLIP_180);
|
SC_ORIENTATION_FLIP_180);
|
||||||
}
|
}
|
||||||
@ -479,7 +480,7 @@ sc_input_manager_process_key(struct sc_input_manager *im,
|
|||||||
return;
|
return;
|
||||||
case SDLK_UP:
|
case SDLK_UP:
|
||||||
if (shift) {
|
if (shift) {
|
||||||
if (!repeat & down) {
|
if (video && !repeat && down) {
|
||||||
apply_orientation_transform(im,
|
apply_orientation_transform(im,
|
||||||
SC_ORIENTATION_FLIP_180);
|
SC_ORIENTATION_FLIP_180);
|
||||||
}
|
}
|
||||||
@ -489,7 +490,7 @@ sc_input_manager_process_key(struct sc_input_manager *im,
|
|||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
case SDLK_LEFT:
|
case SDLK_LEFT:
|
||||||
if (!repeat && down) {
|
if (video && !repeat && down) {
|
||||||
if (shift) {
|
if (shift) {
|
||||||
apply_orientation_transform(im,
|
apply_orientation_transform(im,
|
||||||
SC_ORIENTATION_FLIP_0);
|
SC_ORIENTATION_FLIP_0);
|
||||||
@ -500,7 +501,7 @@ sc_input_manager_process_key(struct sc_input_manager *im,
|
|||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
case SDLK_RIGHT:
|
case SDLK_RIGHT:
|
||||||
if (!repeat && down) {
|
if (video && !repeat && down) {
|
||||||
if (shift) {
|
if (shift) {
|
||||||
apply_orientation_transform(im,
|
apply_orientation_transform(im,
|
||||||
SC_ORIENTATION_FLIP_0);
|
SC_ORIENTATION_FLIP_0);
|
||||||
@ -533,22 +534,22 @@ sc_input_manager_process_key(struct sc_input_manager *im,
|
|||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
case SDLK_f:
|
case SDLK_f:
|
||||||
if (!shift && !repeat && down) {
|
if (video && !shift && !repeat && down) {
|
||||||
sc_screen_switch_fullscreen(im->screen);
|
sc_screen_switch_fullscreen(im->screen);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
case SDLK_w:
|
case SDLK_w:
|
||||||
if (!shift && !repeat && down) {
|
if (video && !shift && !repeat && down) {
|
||||||
sc_screen_resize_to_fit(im->screen);
|
sc_screen_resize_to_fit(im->screen);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
case SDLK_g:
|
case SDLK_g:
|
||||||
if (!shift && !repeat && down) {
|
if (video && !shift && !repeat && down) {
|
||||||
sc_screen_resize_to_pixel_perfect(im->screen);
|
sc_screen_resize_to_pixel_perfect(im->screen);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
case SDLK_i:
|
case SDLK_i:
|
||||||
if (!shift && !repeat && down) {
|
if (video && !shift && !repeat && down) {
|
||||||
switch_fps_counter_state(im);
|
switch_fps_counter_state(im);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
@ -625,6 +626,23 @@ sc_input_manager_process_key(struct sc_input_manager *im,
|
|||||||
im->kp->ops->process_key(im->kp, &evt, ack_to_wait);
|
im->kp->ops->process_key(im->kp, &evt, ack_to_wait);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static struct sc_position
|
||||||
|
sc_input_manager_get_position(struct sc_input_manager *im, int32_t x,
|
||||||
|
int32_t y) {
|
||||||
|
if (im->mp->relative_mode) {
|
||||||
|
// No absolute position
|
||||||
|
return (struct sc_position) {
|
||||||
|
.screen_size = {0, 0},
|
||||||
|
.point = {0, 0},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return (struct sc_position) {
|
||||||
|
.screen_size = im->screen->frame_size,
|
||||||
|
.point = sc_screen_convert_window_to_frame_coords(im->screen, x, y),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
sc_input_manager_process_mouse_motion(struct sc_input_manager *im,
|
sc_input_manager_process_mouse_motion(struct sc_input_manager *im,
|
||||||
const SDL_MouseMotionEvent *event) {
|
const SDL_MouseMotionEvent *event) {
|
||||||
@ -634,12 +652,7 @@ sc_input_manager_process_mouse_motion(struct sc_input_manager *im,
|
|||||||
}
|
}
|
||||||
|
|
||||||
struct sc_mouse_motion_event evt = {
|
struct sc_mouse_motion_event evt = {
|
||||||
.position = {
|
.position = sc_input_manager_get_position(im, event->x, event->y),
|
||||||
.screen_size = im->screen->frame_size,
|
|
||||||
.point = sc_screen_convert_window_to_frame_coords(im->screen,
|
|
||||||
event->x,
|
|
||||||
event->y),
|
|
||||||
},
|
|
||||||
.pointer_id = im->forward_all_clicks ? POINTER_ID_MOUSE
|
.pointer_id = im->forward_all_clicks ? POINTER_ID_MOUSE
|
||||||
: POINTER_ID_GENERIC_FINGER,
|
: POINTER_ID_GENERIC_FINGER,
|
||||||
.xrel = event->xrel,
|
.xrel = event->xrel,
|
||||||
@ -735,7 +748,8 @@ sc_input_manager_process_mouse_button(struct sc_input_manager *im,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// double-click on black borders resize to fit the device screen
|
// double-click on black borders resize to fit the device screen
|
||||||
if (event->button == SDL_BUTTON_LEFT && event->clicks == 2) {
|
bool video = im->screen->video;
|
||||||
|
if (video && event->button == SDL_BUTTON_LEFT && event->clicks == 2) {
|
||||||
int32_t x = event->x;
|
int32_t x = event->x;
|
||||||
int32_t y = event->y;
|
int32_t y = event->y;
|
||||||
sc_screen_hidpi_scale_coords(im->screen, &x, &y);
|
sc_screen_hidpi_scale_coords(im->screen, &x, &y);
|
||||||
@ -759,12 +773,7 @@ sc_input_manager_process_mouse_button(struct sc_input_manager *im,
|
|||||||
uint32_t sdl_buttons_state = SDL_GetMouseState(NULL, NULL);
|
uint32_t sdl_buttons_state = SDL_GetMouseState(NULL, NULL);
|
||||||
|
|
||||||
struct sc_mouse_click_event evt = {
|
struct sc_mouse_click_event evt = {
|
||||||
.position = {
|
.position = sc_input_manager_get_position(im, event->x, event->y),
|
||||||
.screen_size = im->screen->frame_size,
|
|
||||||
.point = sc_screen_convert_window_to_frame_coords(im->screen,
|
|
||||||
event->x,
|
|
||||||
event->y),
|
|
||||||
},
|
|
||||||
.action = sc_action_from_sdl_mousebutton_type(event->type),
|
.action = sc_action_from_sdl_mousebutton_type(event->type),
|
||||||
.button = sc_mouse_button_from_sdl(event->button),
|
.button = sc_mouse_button_from_sdl(event->button),
|
||||||
.pointer_id = im->forward_all_clicks ? POINTER_ID_MOUSE
|
.pointer_id = im->forward_all_clicks ? POINTER_ID_MOUSE
|
||||||
@ -839,11 +848,7 @@ sc_input_manager_process_mouse_wheel(struct sc_input_manager *im,
|
|||||||
uint32_t buttons = SDL_GetMouseState(&mouse_x, &mouse_y);
|
uint32_t buttons = SDL_GetMouseState(&mouse_x, &mouse_y);
|
||||||
|
|
||||||
struct sc_mouse_scroll_event evt = {
|
struct sc_mouse_scroll_event evt = {
|
||||||
.position = {
|
.position = sc_input_manager_get_position(im, mouse_x, mouse_y),
|
||||||
.screen_size = im->screen->frame_size,
|
|
||||||
.point = sc_screen_convert_window_to_frame_coords(im->screen,
|
|
||||||
mouse_x, mouse_y),
|
|
||||||
},
|
|
||||||
#if SDL_VERSION_ATLEAST(2, 0, 18)
|
#if SDL_VERSION_ATLEAST(2, 0, 18)
|
||||||
.hscroll = CLAMP(event->preciseX, -1.0f, 1.0f),
|
.hscroll = CLAMP(event->preciseX, -1.0f, 1.0f),
|
||||||
.vscroll = CLAMP(event->preciseY, -1.0f, 1.0f),
|
.vscroll = CLAMP(event->preciseY, -1.0f, 1.0f),
|
||||||
|
@ -89,6 +89,7 @@ const struct scrcpy_options scrcpy_options_default = {
|
|||||||
.kill_adb_on_close = false,
|
.kill_adb_on_close = false,
|
||||||
.camera_high_speed = false,
|
.camera_high_speed = false,
|
||||||
.list = 0,
|
.list = 0,
|
||||||
|
.window = true,
|
||||||
};
|
};
|
||||||
|
|
||||||
enum sc_orientation
|
enum sc_orientation
|
||||||
|
@ -279,6 +279,7 @@ struct scrcpy_options {
|
|||||||
#define SC_OPTION_LIST_CAMERAS 0x4
|
#define SC_OPTION_LIST_CAMERAS 0x4
|
||||||
#define SC_OPTION_LIST_CAMERA_SIZES 0x8
|
#define SC_OPTION_LIST_CAMERA_SIZES 0x8
|
||||||
uint8_t list;
|
uint8_t list;
|
||||||
|
bool window;
|
||||||
};
|
};
|
||||||
|
|
||||||
extern const struct scrcpy_options scrcpy_options_default;
|
extern const struct scrcpy_options scrcpy_options_default;
|
||||||
|
@ -10,7 +10,8 @@
|
|||||||
#include "util/str.h"
|
#include "util/str.h"
|
||||||
|
|
||||||
bool
|
bool
|
||||||
sc_receiver_init(struct sc_receiver *receiver, sc_socket control_socket) {
|
sc_receiver_init(struct sc_receiver *receiver, sc_socket control_socket,
|
||||||
|
const struct sc_receiver_callbacks *cbs, void *cbs_userdata) {
|
||||||
bool ok = sc_mutex_init(&receiver->mutex);
|
bool ok = sc_mutex_init(&receiver->mutex);
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
return false;
|
return false;
|
||||||
@ -20,6 +21,10 @@ sc_receiver_init(struct sc_receiver *receiver, sc_socket control_socket) {
|
|||||||
receiver->acksync = NULL;
|
receiver->acksync = NULL;
|
||||||
receiver->uhid_devices = NULL;
|
receiver->uhid_devices = NULL;
|
||||||
|
|
||||||
|
assert(cbs && cbs->on_error);
|
||||||
|
receiver->cbs = cbs;
|
||||||
|
receiver->cbs_userdata = cbs_userdata;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -152,6 +157,8 @@ run_receiver(void *data) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
receiver->cbs->on_error(receiver, receiver->cbs_userdata);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -19,10 +19,18 @@ struct sc_receiver {
|
|||||||
|
|
||||||
struct sc_acksync *acksync;
|
struct sc_acksync *acksync;
|
||||||
struct sc_uhid_devices *uhid_devices;
|
struct sc_uhid_devices *uhid_devices;
|
||||||
|
|
||||||
|
const struct sc_receiver_callbacks *cbs;
|
||||||
|
void *cbs_userdata;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct sc_receiver_callbacks {
|
||||||
|
void (*on_error)(struct sc_receiver *receiver, void *userdata);
|
||||||
};
|
};
|
||||||
|
|
||||||
bool
|
bool
|
||||||
sc_receiver_init(struct sc_receiver *receiver, sc_socket control_socket);
|
sc_receiver_init(struct sc_receiver *receiver, sc_socket control_socket,
|
||||||
|
const struct sc_receiver_callbacks *cbs, void *cbs_userdata);
|
||||||
|
|
||||||
void
|
void
|
||||||
sc_receiver_destroy(struct sc_receiver *receiver);
|
sc_receiver_destroy(struct sc_receiver *receiver);
|
||||||
|
@ -174,6 +174,9 @@ event_loop(struct scrcpy *s) {
|
|||||||
case SC_EVENT_DEMUXER_ERROR:
|
case SC_EVENT_DEMUXER_ERROR:
|
||||||
LOGE("Demuxer error");
|
LOGE("Demuxer error");
|
||||||
return SCRCPY_EXIT_FAILURE;
|
return SCRCPY_EXIT_FAILURE;
|
||||||
|
case SC_EVENT_CONTROLLER_ERROR:
|
||||||
|
LOGE("Controller error");
|
||||||
|
return SCRCPY_EXIT_FAILURE;
|
||||||
case SC_EVENT_RECORDER_ERROR:
|
case SC_EVENT_RECORDER_ERROR:
|
||||||
LOGE("Recorder error");
|
LOGE("Recorder error");
|
||||||
return SCRCPY_EXIT_FAILURE;
|
return SCRCPY_EXIT_FAILURE;
|
||||||
@ -265,6 +268,16 @@ sc_audio_demuxer_on_ended(struct sc_demuxer *demuxer,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
sc_controller_on_error(struct sc_controller *controller, void *userdata) {
|
||||||
|
// Note: this function may be called twice, once from the controller thread
|
||||||
|
// and once from the receiver thread
|
||||||
|
(void) controller;
|
||||||
|
(void) userdata;
|
||||||
|
|
||||||
|
PUSH_EVENT(SC_EVENT_CONTROLLER_ERROR);
|
||||||
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
sc_server_on_connection_failed(struct sc_server *server, void *userdata) {
|
sc_server_on_connection_failed(struct sc_server *server, void *userdata) {
|
||||||
(void) server;
|
(void) server;
|
||||||
@ -408,7 +421,7 @@ scrcpy(struct scrcpy_options *options) {
|
|||||||
return SCRCPY_EXIT_FAILURE;
|
return SCRCPY_EXIT_FAILURE;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (options->video_playback) {
|
if (options->window) {
|
||||||
// Set hints before starting the server thread to avoid race conditions
|
// Set hints before starting the server thread to avoid race conditions
|
||||||
// in SDL
|
// in SDL
|
||||||
sdl_set_hints(options->render_driver);
|
sdl_set_hints(options->render_driver);
|
||||||
@ -430,7 +443,7 @@ scrcpy(struct scrcpy_options *options) {
|
|||||||
assert(!options->video_playback || options->video);
|
assert(!options->video_playback || options->video);
|
||||||
assert(!options->audio_playback || options->audio);
|
assert(!options->audio_playback || options->audio);
|
||||||
|
|
||||||
if (options->video_playback ||
|
if (options->window ||
|
||||||
(options->control && options->clipboard_autosync)) {
|
(options->control && options->clipboard_autosync)) {
|
||||||
// Initialize the video subsystem even if --no-video or
|
// Initialize the video subsystem even if --no-video or
|
||||||
// --no-video-playback is passed so that clipboard synchronization
|
// --no-video-playback is passed so that clipboard synchronization
|
||||||
@ -553,7 +566,12 @@ scrcpy(struct scrcpy_options *options) {
|
|||||||
struct sc_mouse_processor *mp = NULL;
|
struct sc_mouse_processor *mp = NULL;
|
||||||
|
|
||||||
if (options->control) {
|
if (options->control) {
|
||||||
if (!sc_controller_init(&s->controller, s->server.control_socket)) {
|
static const struct sc_controller_callbacks controller_cbs = {
|
||||||
|
.on_error = sc_controller_on_error,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!sc_controller_init(&s->controller, s->server.control_socket,
|
||||||
|
&controller_cbs, NULL)) {
|
||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
controller_initialized = true;
|
controller_initialized = true;
|
||||||
@ -684,11 +702,12 @@ scrcpy(struct scrcpy_options *options) {
|
|||||||
// There is a controller if and only if control is enabled
|
// There is a controller if and only if control is enabled
|
||||||
assert(options->control == !!controller);
|
assert(options->control == !!controller);
|
||||||
|
|
||||||
if (options->video_playback) {
|
if (options->window) {
|
||||||
const char *window_title =
|
const char *window_title =
|
||||||
options->window_title ? options->window_title : info->device_name;
|
options->window_title ? options->window_title : info->device_name;
|
||||||
|
|
||||||
struct sc_screen_params screen_params = {
|
struct sc_screen_params screen_params = {
|
||||||
|
.video = options->video_playback,
|
||||||
.controller = controller,
|
.controller = controller,
|
||||||
.fp = fp,
|
.fp = fp,
|
||||||
.kp = kp,
|
.kp = kp,
|
||||||
@ -710,12 +729,15 @@ scrcpy(struct scrcpy_options *options) {
|
|||||||
.start_fps_counter = options->start_fps_counter,
|
.start_fps_counter = options->start_fps_counter,
|
||||||
};
|
};
|
||||||
|
|
||||||
struct sc_frame_source *src = &s->video_decoder.frame_source;
|
struct sc_frame_source *src;
|
||||||
if (options->display_buffer) {
|
if (options->video_playback) {
|
||||||
sc_delay_buffer_init(&s->display_buffer, options->display_buffer,
|
src = &s->video_decoder.frame_source;
|
||||||
true);
|
if (options->display_buffer) {
|
||||||
sc_frame_source_add_sink(src, &s->display_buffer.frame_sink);
|
sc_delay_buffer_init(&s->display_buffer,
|
||||||
src = &s->display_buffer.frame_source;
|
options->display_buffer, true);
|
||||||
|
sc_frame_source_add_sink(src, &s->display_buffer.frame_sink);
|
||||||
|
src = &s->display_buffer.frame_source;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!sc_screen_init(&s->screen, &screen_params)) {
|
if (!sc_screen_init(&s->screen, &screen_params)) {
|
||||||
@ -723,7 +745,9 @@ scrcpy(struct scrcpy_options *options) {
|
|||||||
}
|
}
|
||||||
screen_initialized = true;
|
screen_initialized = true;
|
||||||
|
|
||||||
sc_frame_source_add_sink(src, &s->screen.frame_sink);
|
if (options->video_playback) {
|
||||||
|
sc_frame_source_add_sink(src, &s->screen.frame_sink);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (options->audio_playback) {
|
if (options->audio_playback) {
|
||||||
@ -805,9 +829,12 @@ scrcpy(struct scrcpy_options *options) {
|
|||||||
ret = event_loop(s);
|
ret = event_loop(s);
|
||||||
LOGD("quit...");
|
LOGD("quit...");
|
||||||
|
|
||||||
// Close the window immediately on closing, because screen_destroy() may
|
if (options->video_playback) {
|
||||||
// only be called once the video demuxer thread is joined (it may take time)
|
// Close the window immediately on closing, because screen_destroy()
|
||||||
sc_screen_hide_window(&s->screen);
|
// may only be called once the video demuxer thread is joined (it may
|
||||||
|
// take time)
|
||||||
|
sc_screen_hide_window(&s->screen);
|
||||||
|
}
|
||||||
|
|
||||||
end:
|
end:
|
||||||
if (timeout_started) {
|
if (timeout_started) {
|
||||||
|
123
app/src/screen.c
123
app/src/screen.c
@ -205,6 +205,8 @@ sc_screen_toggle_mouse_capture(struct sc_screen *screen) {
|
|||||||
|
|
||||||
static void
|
static void
|
||||||
sc_screen_update_content_rect(struct sc_screen *screen) {
|
sc_screen_update_content_rect(struct sc_screen *screen) {
|
||||||
|
assert(screen->video);
|
||||||
|
|
||||||
int dw;
|
int dw;
|
||||||
int dh;
|
int dh;
|
||||||
SDL_GL_GetDrawableSize(screen->window, &dw, &dh);
|
SDL_GL_GetDrawableSize(screen->window, &dw, &dh);
|
||||||
@ -246,6 +248,8 @@ sc_screen_update_content_rect(struct sc_screen *screen) {
|
|||||||
// changed, so that the content rectangle is recomputed
|
// changed, so that the content rectangle is recomputed
|
||||||
static void
|
static void
|
||||||
sc_screen_render(struct sc_screen *screen, bool update_content_rect) {
|
sc_screen_render(struct sc_screen *screen, bool update_content_rect) {
|
||||||
|
assert(screen->video);
|
||||||
|
|
||||||
if (update_content_rect) {
|
if (update_content_rect) {
|
||||||
sc_screen_update_content_rect(screen);
|
sc_screen_update_content_rect(screen);
|
||||||
}
|
}
|
||||||
@ -255,6 +259,13 @@ sc_screen_render(struct sc_screen *screen, bool update_content_rect) {
|
|||||||
(void) res; // any error already logged
|
(void) res; // any error already logged
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
#if defined(__APPLE__) || defined(__WINDOWS__)
|
#if defined(__APPLE__) || defined(__WINDOWS__)
|
||||||
# define CONTINUOUS_RESIZING_WORKAROUND
|
# define CONTINUOUS_RESIZING_WORKAROUND
|
||||||
#endif
|
#endif
|
||||||
@ -268,6 +279,8 @@ sc_screen_render(struct sc_screen *screen, bool update_content_rect) {
|
|||||||
static int
|
static int
|
||||||
event_watcher(void *data, SDL_Event *event) {
|
event_watcher(void *data, SDL_Event *event) {
|
||||||
struct sc_screen *screen = data;
|
struct sc_screen *screen = data;
|
||||||
|
assert(screen->video);
|
||||||
|
|
||||||
if (event->type == SDL_WINDOWEVENT
|
if (event->type == SDL_WINDOWEVENT
|
||||||
&& event->window.event == SDL_WINDOWEVENT_RESIZED) {
|
&& event->window.event == SDL_WINDOWEVENT_RESIZED) {
|
||||||
// In practice, it seems to always be called from the same thread in
|
// In practice, it seems to always be called from the same thread in
|
||||||
@ -326,6 +339,7 @@ sc_screen_frame_sink_close(struct sc_frame_sink *sink) {
|
|||||||
static bool
|
static bool
|
||||||
sc_screen_frame_sink_push(struct sc_frame_sink *sink, const AVFrame *frame) {
|
sc_screen_frame_sink_push(struct sc_frame_sink *sink, const AVFrame *frame) {
|
||||||
struct sc_screen *screen = DOWNCAST(sink);
|
struct sc_screen *screen = DOWNCAST(sink);
|
||||||
|
assert(screen->video);
|
||||||
|
|
||||||
bool previous_skipped;
|
bool previous_skipped;
|
||||||
bool ok = sc_frame_buffer_push(&screen->fb, frame, &previous_skipped);
|
bool ok = sc_frame_buffer_push(&screen->fb, frame, &previous_skipped);
|
||||||
@ -364,6 +378,9 @@ sc_screen_init(struct sc_screen *screen,
|
|||||||
screen->mouse_capture_key_pressed = 0;
|
screen->mouse_capture_key_pressed = 0;
|
||||||
screen->paused = false;
|
screen->paused = false;
|
||||||
screen->resume_frame = NULL;
|
screen->resume_frame = NULL;
|
||||||
|
screen->orientation = SC_ORIENTATION_0;
|
||||||
|
|
||||||
|
screen->video = params->video;
|
||||||
|
|
||||||
screen->req.x = params->window_x;
|
screen->req.x = params->window_x;
|
||||||
screen->req.y = params->window_y;
|
screen->req.y = params->window_y;
|
||||||
@ -381,41 +398,75 @@ sc_screen_init(struct sc_screen *screen,
|
|||||||
goto error_destroy_frame_buffer;
|
goto error_destroy_frame_buffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
screen->orientation = params->orientation;
|
if (screen->video) {
|
||||||
if (screen->orientation != SC_ORIENTATION_0) {
|
screen->orientation = params->orientation;
|
||||||
LOGI("Initial display orientation set to %s",
|
if (screen->orientation != SC_ORIENTATION_0) {
|
||||||
sc_orientation_get_name(screen->orientation));
|
LOGI("Initial display orientation set to %s",
|
||||||
|
sc_orientation_get_name(screen->orientation));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t window_flags = SDL_WINDOW_HIDDEN
|
uint32_t window_flags = SDL_WINDOW_ALLOW_HIGHDPI;
|
||||||
| SDL_WINDOW_RESIZABLE
|
|
||||||
| SDL_WINDOW_ALLOW_HIGHDPI;
|
|
||||||
if (params->always_on_top) {
|
if (params->always_on_top) {
|
||||||
window_flags |= SDL_WINDOW_ALWAYS_ON_TOP;
|
window_flags |= SDL_WINDOW_ALWAYS_ON_TOP;
|
||||||
}
|
}
|
||||||
if (params->window_borderless) {
|
if (params->window_borderless) {
|
||||||
window_flags |= SDL_WINDOW_BORDERLESS;
|
window_flags |= SDL_WINDOW_BORDERLESS;
|
||||||
}
|
}
|
||||||
|
if (params->video) {
|
||||||
|
// The window will be shown on first frame
|
||||||
|
window_flags |= SDL_WINDOW_HIDDEN
|
||||||
|
| SDL_WINDOW_RESIZABLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *title = params->window_title;
|
||||||
|
assert(title);
|
||||||
|
|
||||||
|
int x = SDL_WINDOWPOS_UNDEFINED;
|
||||||
|
int y = SDL_WINDOWPOS_UNDEFINED;
|
||||||
|
int width = 256;
|
||||||
|
int height = 256;
|
||||||
|
if (params->window_x != SC_WINDOW_POSITION_UNDEFINED) {
|
||||||
|
x = params->window_x;
|
||||||
|
}
|
||||||
|
if (params->window_y != SC_WINDOW_POSITION_UNDEFINED) {
|
||||||
|
y = params->window_y;
|
||||||
|
}
|
||||||
|
if (params->window_width) {
|
||||||
|
width = params->window_width;
|
||||||
|
}
|
||||||
|
if (params->window_height) {
|
||||||
|
height = params->window_height;
|
||||||
|
}
|
||||||
|
|
||||||
// The window will be positioned and sized on first video frame
|
// The window will be positioned and sized on first video frame
|
||||||
screen->window =
|
screen->window = SDL_CreateWindow(title, x, y, width, height, window_flags);
|
||||||
SDL_CreateWindow(params->window_title, 0, 0, 0, 0, window_flags);
|
|
||||||
if (!screen->window) {
|
if (!screen->window) {
|
||||||
LOGE("Could not create window: %s", SDL_GetError());
|
LOGE("Could not create window: %s", SDL_GetError());
|
||||||
goto error_destroy_fps_counter;
|
goto error_destroy_fps_counter;
|
||||||
}
|
}
|
||||||
|
|
||||||
ok = sc_display_init(&screen->display, screen->window, params->mipmaps);
|
|
||||||
if (!ok) {
|
|
||||||
goto error_destroy_window;
|
|
||||||
}
|
|
||||||
|
|
||||||
SDL_Surface *icon = scrcpy_icon_load();
|
SDL_Surface *icon = scrcpy_icon_load();
|
||||||
if (icon) {
|
if (icon) {
|
||||||
SDL_SetWindowIcon(screen->window, icon);
|
SDL_SetWindowIcon(screen->window, icon);
|
||||||
scrcpy_icon_destroy(icon);
|
} else if (params->video) {
|
||||||
} else {
|
// just a warning
|
||||||
LOGW("Could not load icon");
|
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_fps_counter;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
screen->frame = av_frame_alloc();
|
screen->frame = av_frame_alloc();
|
||||||
@ -439,7 +490,9 @@ sc_screen_init(struct sc_screen *screen,
|
|||||||
sc_input_manager_init(&screen->im, &im_params);
|
sc_input_manager_init(&screen->im, &im_params);
|
||||||
|
|
||||||
#ifdef CONTINUOUS_RESIZING_WORKAROUND
|
#ifdef CONTINUOUS_RESIZING_WORKAROUND
|
||||||
SDL_AddEventWatch(event_watcher, screen);
|
if (screen->video) {
|
||||||
|
SDL_AddEventWatch(event_watcher, screen);
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
static const struct sc_frame_sink_ops ops = {
|
static const struct sc_frame_sink_ops ops = {
|
||||||
@ -454,6 +507,11 @@ sc_screen_init(struct sc_screen *screen,
|
|||||||
screen->open = false;
|
screen->open = false;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
if (!screen->video && sc_screen_is_relative_mode(screen)) {
|
||||||
|
// Capture mouse immediately if video mirroring is disabled
|
||||||
|
sc_screen_set_mouse_capture(screen, true);
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
error_destroy_display:
|
error_destroy_display:
|
||||||
@ -524,6 +582,8 @@ sc_screen_destroy(struct sc_screen *screen) {
|
|||||||
static void
|
static void
|
||||||
resize_for_content(struct sc_screen *screen, struct sc_size old_content_size,
|
resize_for_content(struct sc_screen *screen, struct sc_size old_content_size,
|
||||||
struct sc_size new_content_size) {
|
struct sc_size new_content_size) {
|
||||||
|
assert(screen->video);
|
||||||
|
|
||||||
struct sc_size window_size = get_window_size(screen);
|
struct sc_size window_size = get_window_size(screen);
|
||||||
struct sc_size target_size = {
|
struct sc_size target_size = {
|
||||||
.width = (uint32_t) window_size.width * new_content_size.width
|
.width = (uint32_t) window_size.width * new_content_size.width
|
||||||
@ -537,6 +597,8 @@ resize_for_content(struct sc_screen *screen, struct sc_size old_content_size,
|
|||||||
|
|
||||||
static void
|
static void
|
||||||
set_content_size(struct sc_screen *screen, struct sc_size new_content_size) {
|
set_content_size(struct sc_screen *screen, struct sc_size new_content_size) {
|
||||||
|
assert(screen->video);
|
||||||
|
|
||||||
if (!screen->fullscreen && !screen->maximized && !screen->minimized) {
|
if (!screen->fullscreen && !screen->maximized && !screen->minimized) {
|
||||||
resize_for_content(screen, screen->content_size, new_content_size);
|
resize_for_content(screen, screen->content_size, new_content_size);
|
||||||
} else if (!screen->resize_pending) {
|
} else if (!screen->resize_pending) {
|
||||||
@ -551,6 +613,8 @@ set_content_size(struct sc_screen *screen, struct sc_size new_content_size) {
|
|||||||
|
|
||||||
static void
|
static void
|
||||||
apply_pending_resize(struct sc_screen *screen) {
|
apply_pending_resize(struct sc_screen *screen) {
|
||||||
|
assert(screen->video);
|
||||||
|
|
||||||
assert(!screen->fullscreen);
|
assert(!screen->fullscreen);
|
||||||
assert(!screen->maximized);
|
assert(!screen->maximized);
|
||||||
assert(!screen->minimized);
|
assert(!screen->minimized);
|
||||||
@ -564,6 +628,8 @@ apply_pending_resize(struct sc_screen *screen) {
|
|||||||
void
|
void
|
||||||
sc_screen_set_orientation(struct sc_screen *screen,
|
sc_screen_set_orientation(struct sc_screen *screen,
|
||||||
enum sc_orientation orientation) {
|
enum sc_orientation orientation) {
|
||||||
|
assert(screen->video);
|
||||||
|
|
||||||
if (orientation == screen->orientation) {
|
if (orientation == screen->orientation) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -598,6 +664,8 @@ sc_screen_init_size(struct sc_screen *screen) {
|
|||||||
// recreate the texture and resize the window if the frame size has changed
|
// recreate the texture and resize the window if the frame size has changed
|
||||||
static enum sc_display_result
|
static enum sc_display_result
|
||||||
prepare_for_frame(struct sc_screen *screen, struct sc_size new_frame_size) {
|
prepare_for_frame(struct sc_screen *screen, struct sc_size new_frame_size) {
|
||||||
|
assert(screen->video);
|
||||||
|
|
||||||
if (screen->frame_size.width == new_frame_size.width
|
if (screen->frame_size.width == new_frame_size.width
|
||||||
&& screen->frame_size.height == new_frame_size.height) {
|
&& screen->frame_size.height == new_frame_size.height) {
|
||||||
return SC_DISPLAY_RESULT_OK;
|
return SC_DISPLAY_RESULT_OK;
|
||||||
@ -617,6 +685,8 @@ prepare_for_frame(struct sc_screen *screen, struct sc_size new_frame_size) {
|
|||||||
|
|
||||||
static bool
|
static bool
|
||||||
sc_screen_apply_frame(struct sc_screen *screen) {
|
sc_screen_apply_frame(struct sc_screen *screen) {
|
||||||
|
assert(screen->video);
|
||||||
|
|
||||||
sc_fps_counter_add_rendered_frame(&screen->fps_counter);
|
sc_fps_counter_add_rendered_frame(&screen->fps_counter);
|
||||||
|
|
||||||
AVFrame *frame = screen->frame;
|
AVFrame *frame = screen->frame;
|
||||||
@ -656,6 +726,8 @@ sc_screen_apply_frame(struct sc_screen *screen) {
|
|||||||
|
|
||||||
static bool
|
static bool
|
||||||
sc_screen_update_frame(struct sc_screen *screen) {
|
sc_screen_update_frame(struct sc_screen *screen) {
|
||||||
|
assert(screen->video);
|
||||||
|
|
||||||
if (screen->paused) {
|
if (screen->paused) {
|
||||||
if (!screen->resume_frame) {
|
if (!screen->resume_frame) {
|
||||||
screen->resume_frame = av_frame_alloc();
|
screen->resume_frame = av_frame_alloc();
|
||||||
@ -677,6 +749,8 @@ sc_screen_update_frame(struct sc_screen *screen) {
|
|||||||
|
|
||||||
void
|
void
|
||||||
sc_screen_set_paused(struct sc_screen *screen, bool paused) {
|
sc_screen_set_paused(struct sc_screen *screen, bool paused) {
|
||||||
|
assert(screen->video);
|
||||||
|
|
||||||
if (!paused && !screen->paused) {
|
if (!paused && !screen->paused) {
|
||||||
// nothing to do
|
// nothing to do
|
||||||
return;
|
return;
|
||||||
@ -704,6 +778,8 @@ sc_screen_set_paused(struct sc_screen *screen, bool paused) {
|
|||||||
|
|
||||||
void
|
void
|
||||||
sc_screen_switch_fullscreen(struct sc_screen *screen) {
|
sc_screen_switch_fullscreen(struct sc_screen *screen) {
|
||||||
|
assert(screen->video);
|
||||||
|
|
||||||
uint32_t new_mode = screen->fullscreen ? 0 : SDL_WINDOW_FULLSCREEN_DESKTOP;
|
uint32_t new_mode = screen->fullscreen ? 0 : SDL_WINDOW_FULLSCREEN_DESKTOP;
|
||||||
if (SDL_SetWindowFullscreen(screen->window, new_mode)) {
|
if (SDL_SetWindowFullscreen(screen->window, new_mode)) {
|
||||||
LOGW("Could not switch fullscreen mode: %s", SDL_GetError());
|
LOGW("Could not switch fullscreen mode: %s", SDL_GetError());
|
||||||
@ -721,6 +797,8 @@ sc_screen_switch_fullscreen(struct sc_screen *screen) {
|
|||||||
|
|
||||||
void
|
void
|
||||||
sc_screen_resize_to_fit(struct sc_screen *screen) {
|
sc_screen_resize_to_fit(struct sc_screen *screen) {
|
||||||
|
assert(screen->video);
|
||||||
|
|
||||||
if (screen->fullscreen || screen->maximized || screen->minimized) {
|
if (screen->fullscreen || screen->maximized || screen->minimized) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -745,6 +823,8 @@ sc_screen_resize_to_fit(struct sc_screen *screen) {
|
|||||||
|
|
||||||
void
|
void
|
||||||
sc_screen_resize_to_pixel_perfect(struct sc_screen *screen) {
|
sc_screen_resize_to_pixel_perfect(struct sc_screen *screen) {
|
||||||
|
assert(screen->video);
|
||||||
|
|
||||||
if (screen->fullscreen || screen->minimized) {
|
if (screen->fullscreen || screen->minimized) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -788,6 +868,13 @@ sc_screen_handle_event(struct sc_screen *screen, const SDL_Event *event) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
case SDL_WINDOWEVENT:
|
case SDL_WINDOWEVENT:
|
||||||
|
if (!screen->video
|
||||||
|
&& event->window.event == SDL_WINDOWEVENT_EXPOSED) {
|
||||||
|
sc_screen_render_novideo(screen);
|
||||||
|
}
|
||||||
|
|
||||||
|
// !video implies !has_frame
|
||||||
|
assert(screen->video || !screen->has_frame);
|
||||||
if (!screen->has_frame) {
|
if (!screen->has_frame) {
|
||||||
// Do nothing
|
// Do nothing
|
||||||
return true;
|
return true;
|
||||||
@ -891,6 +978,8 @@ sc_screen_handle_event(struct sc_screen *screen, const SDL_Event *event) {
|
|||||||
struct sc_point
|
struct sc_point
|
||||||
sc_screen_convert_drawable_to_frame_coords(struct sc_screen *screen,
|
sc_screen_convert_drawable_to_frame_coords(struct sc_screen *screen,
|
||||||
int32_t x, int32_t y) {
|
int32_t x, int32_t y) {
|
||||||
|
assert(screen->video);
|
||||||
|
|
||||||
enum sc_orientation orientation = screen->orientation;
|
enum sc_orientation orientation = screen->orientation;
|
||||||
|
|
||||||
int32_t w = screen->content_size.width;
|
int32_t w = screen->content_size.width;
|
||||||
|
@ -26,6 +26,8 @@ struct sc_screen {
|
|||||||
bool open; // track the open/close state to assert correct behavior
|
bool open; // track the open/close state to assert correct behavior
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
bool video;
|
||||||
|
|
||||||
struct sc_display display;
|
struct sc_display display;
|
||||||
struct sc_input_manager im;
|
struct sc_input_manager im;
|
||||||
struct sc_frame_buffer fb;
|
struct sc_frame_buffer fb;
|
||||||
@ -70,6 +72,8 @@ struct sc_screen {
|
|||||||
};
|
};
|
||||||
|
|
||||||
struct sc_screen_params {
|
struct sc_screen_params {
|
||||||
|
bool video;
|
||||||
|
|
||||||
struct sc_controller *controller;
|
struct sc_controller *controller;
|
||||||
struct sc_file_pusher *fp;
|
struct sc_file_pusher *fp;
|
||||||
struct sc_key_processor *kp;
|
struct sc_key_processor *kp;
|
||||||
|
@ -176,6 +176,8 @@ sc_process_execute_p(const char *const argv[], HANDLE *handle, unsigned flags,
|
|||||||
free(lpAttributeList);
|
free(lpAttributeList);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CloseHandle(pi.hThread);
|
||||||
|
|
||||||
// These handles are used by the child process, close them for this process
|
// These handles are used by the child process, close them for this process
|
||||||
if (pin) {
|
if (pin) {
|
||||||
CloseHandle(stdin_read_handle);
|
CloseHandle(stdin_read_handle);
|
||||||
|
@ -46,6 +46,9 @@ sc_audiobuf_read(struct sc_audiobuf *buf, void *to_, uint32_t samples_count) {
|
|||||||
uint32_t head = atomic_load_explicit(&buf->head, memory_order_acquire);
|
uint32_t head = atomic_load_explicit(&buf->head, memory_order_acquire);
|
||||||
|
|
||||||
uint32_t can_read = (buf->alloc_size + head - tail) % buf->alloc_size;
|
uint32_t can_read = (buf->alloc_size + head - tail) % buf->alloc_size;
|
||||||
|
if (!can_read) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
if (samples_count > can_read) {
|
if (samples_count > can_read) {
|
||||||
samples_count = can_read;
|
samples_count = can_read;
|
||||||
}
|
}
|
||||||
@ -86,6 +89,9 @@ sc_audiobuf_write(struct sc_audiobuf *buf, const void *from_,
|
|||||||
uint32_t tail = atomic_load_explicit(&buf->tail, memory_order_acquire);
|
uint32_t tail = atomic_load_explicit(&buf->tail, memory_order_acquire);
|
||||||
|
|
||||||
uint32_t can_write = (buf->alloc_size + tail - head - 1) % buf->alloc_size;
|
uint32_t can_write = (buf->alloc_size + tail - head - 1) % buf->alloc_size;
|
||||||
|
if (!can_write) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
if (samples_count > can_write) {
|
if (samples_count > can_write) {
|
||||||
samples_count = can_write;
|
samples_count = can_write;
|
||||||
}
|
}
|
||||||
|
11
doc/audio.md
11
doc/audio.md
@ -28,10 +28,17 @@ To disable only the audio playback, see [no playback](video.md#no-playback).
|
|||||||
|
|
||||||
## Audio only
|
## Audio only
|
||||||
|
|
||||||
To play audio only, disable the video:
|
To play audio only, disable video and control:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
scrcpy --no-video
|
scrcpy --no-video --no-control
|
||||||
|
```
|
||||||
|
|
||||||
|
To play audio without a window:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# --no-video and --no-control are implied by --no-window
|
||||||
|
scrcpy --no-window
|
||||||
# interrupt with Ctrl+C
|
# interrupt with Ctrl+C
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -15,6 +15,31 @@ scrcpy -n # short version
|
|||||||
Read [keyboard](keyboard.md) and [mouse](mouse.md).
|
Read [keyboard](keyboard.md) and [mouse](mouse.md).
|
||||||
|
|
||||||
|
|
||||||
|
## Control only
|
||||||
|
|
||||||
|
To control the device without mirroring:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scrcpy --no-video --no-audio
|
||||||
|
```
|
||||||
|
|
||||||
|
By default, mouse mode is switched to UHID if video mirroring is disabled (a
|
||||||
|
relative mouse mode is required).
|
||||||
|
|
||||||
|
To also use a UHID keyboard, set it explicitly:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scrcpy --no-video --no-audio --keyboard=uhid
|
||||||
|
scrcpy --no-video --no-audio -K # short version
|
||||||
|
```
|
||||||
|
|
||||||
|
To use AOA instead (over USB only):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scrcpy --no-video --no-audio --keyboard=aoa --mouse=aoa
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
## Copy-paste
|
## Copy-paste
|
||||||
|
|
||||||
Any time the Android clipboard changes, it is automatically synchronized to the
|
Any time the Android clipboard changes, it is automatically synchronized to the
|
||||||
|
43
doc/otg.md
43
doc/otg.md
@ -1,19 +1,21 @@
|
|||||||
# OTG
|
# OTG
|
||||||
|
|
||||||
By default, _scrcpy_ injects input events at the Android API level. As an
|
By default, _scrcpy_ injects input events at the Android API level. As an
|
||||||
alternative, when connected over USB, it is possible to send HID events, so that
|
alternative, it is possible to send HID events, so that scrcpy behaves as if it
|
||||||
scrcpy behaves as if it was a physical keyboard and/or mouse connected to the
|
was a [physical keyboard] and/or a [physical mouse] connected to the Android
|
||||||
Android device.
|
device (see [keyboard](keyboard.md) and [mouse](mouse.md)).
|
||||||
|
|
||||||
A special mode allows to control the device without mirroring, using AOA
|
[physical keyboard]: keyboard.md#physical-keyboard-simulation
|
||||||
[keyboard](keyboard.md#aoa) and [mouse](mouse.md#aoa). Therefore, it is possible
|
[physical mouse]: physical-keyboard-simulation
|
||||||
to run _scrcpy_ with only physical keyboard and mouse simulation (HID), as if
|
|
||||||
the computer keyboard and mouse were plugged directly to the device via an OTG
|
|
||||||
cable.
|
|
||||||
|
|
||||||
In this mode, `adb` (USB debugging) is not necessary, and mirroring is disabled.
|
A special mode (OTG) allows to control the device using AOA
|
||||||
|
[keyboard](keyboard.md#aoa) and [mouse](mouse.md#aoa), without using _adb_ at
|
||||||
|
all (so USB debugging is not necessary). In this mode, video and audio are
|
||||||
|
disabled, and `--keyboard=aoa and `--mouse=aoa` are implicitly set.
|
||||||
|
|
||||||
This is similar to `--keyboard=aoa --mouse=aoa`, but without mirroring.
|
Therefore, it is possible to run _scrcpy_ with only physical keyboard and mouse
|
||||||
|
simulation, as if the computer keyboard and mouse were plugged directly to the
|
||||||
|
device via an OTG cable.
|
||||||
|
|
||||||
To enable OTG mode:
|
To enable OTG mode:
|
||||||
|
|
||||||
@ -23,7 +25,7 @@ scrcpy --otg
|
|||||||
scrcpy --otg -s 0123456789abcdef
|
scrcpy --otg -s 0123456789abcdef
|
||||||
```
|
```
|
||||||
|
|
||||||
It is possible to disable HID keyboard or HID mouse:
|
It is possible to disable keyboard or mouse:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
scrcpy --otg --keyboard=disabled
|
scrcpy --otg --keyboard=disabled
|
||||||
@ -35,3 +37,22 @@ It only works if the device is connected over USB.
|
|||||||
## OTG issues on Windows
|
## OTG issues on Windows
|
||||||
|
|
||||||
See [FAQ](/FAQ.md#otg-issues-on-windows).
|
See [FAQ](/FAQ.md#otg-issues-on-windows).
|
||||||
|
|
||||||
|
|
||||||
|
## Control only
|
||||||
|
|
||||||
|
Note that the purpose of OTG is to control the device without USB debugging
|
||||||
|
(adb).
|
||||||
|
|
||||||
|
If you want to solely control the device without mirroring while USB debugging
|
||||||
|
is enabled, then OTG mode is not necessary.
|
||||||
|
|
||||||
|
Instead, disable video and audio, and select UHID (or AOA):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scrcpy --no-video --no-audio --keyboard=uhid --mouse=uhid
|
||||||
|
scrcpy --no-video --no-audio -KM # short version
|
||||||
|
scrcpy --no-video --no-audio --keyboard=aoa --mouse=aoa
|
||||||
|
```
|
||||||
|
|
||||||
|
One benefit of UHID is that it also works wirelessly.
|
||||||
|
@ -58,12 +58,10 @@ orientation](video.md#orientation).
|
|||||||
|
|
||||||
## No playback
|
## No playback
|
||||||
|
|
||||||
To disable playback while recording:
|
To disable playback and control while recording:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
scrcpy --no-playback --record=file.mp4
|
scrcpy --no-playback --no-control --record=file.mp4
|
||||||
scrcpy -Nr file.mkv
|
|
||||||
# interrupt recording with Ctrl+C
|
|
||||||
```
|
```
|
||||||
|
|
||||||
It is also possible to disable video and audio playback separately:
|
It is also possible to disable video and audio playback separately:
|
||||||
@ -73,6 +71,13 @@ It is also possible to disable video and audio playback separately:
|
|||||||
scrcpy --record=file.mkv --no-audio-playback
|
scrcpy --record=file.mkv --no-audio-playback
|
||||||
```
|
```
|
||||||
|
|
||||||
|
To also disable the window:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scrcpy --no-playback --no-window --record=file.mp4
|
||||||
|
# interrupt recording with Ctrl+C
|
||||||
|
```
|
||||||
|
|
||||||
## Time limit
|
## Time limit
|
||||||
|
|
||||||
To limit the recording time:
|
To limit the recording time:
|
||||||
|
@ -1,5 +1,14 @@
|
|||||||
# Window
|
# Window
|
||||||
|
|
||||||
|
## Disable window
|
||||||
|
|
||||||
|
To disable window (may be useful for recording or for playing audio only):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scrcpy --no-window --record=file.mp4
|
||||||
|
# Ctrl+C to interrupt
|
||||||
|
```
|
||||||
|
|
||||||
## Title
|
## Title
|
||||||
|
|
||||||
By default, the window title is the device model. It can be changed:
|
By default, the window title is the device model. It can be changed:
|
||||||
|
@ -127,6 +127,10 @@ public class CameraCapture extends SurfaceCapture {
|
|||||||
|
|
||||||
StreamConfigurationMap configs = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
|
StreamConfigurationMap configs = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
|
||||||
android.util.Size[] sizes = highSpeed ? configs.getHighSpeedVideoSizes() : configs.getOutputSizes(MediaCodec.class);
|
android.util.Size[] sizes = highSpeed ? configs.getHighSpeedVideoSizes() : configs.getOutputSizes(MediaCodec.class);
|
||||||
|
if (sizes == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
Stream<android.util.Size> stream = Arrays.stream(sizes);
|
Stream<android.util.Size> stream = Arrays.stream(sizes);
|
||||||
if (maxSize > 0) {
|
if (maxSize > 0) {
|
||||||
stream = stream.filter(it -> it.getWidth() <= maxSize && it.getHeight() <= maxSize);
|
stream = stream.filter(it -> it.getWidth() <= maxSize && it.getHeight() <= maxSize);
|
||||||
|
@ -118,12 +118,16 @@ public final class LogUtils {
|
|||||||
StreamConfigurationMap configs = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
|
StreamConfigurationMap configs = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
|
||||||
|
|
||||||
android.util.Size[] sizes = configs.getOutputSizes(MediaCodec.class);
|
android.util.Size[] sizes = configs.getOutputSizes(MediaCodec.class);
|
||||||
for (android.util.Size size : sizes) {
|
if (sizes == null || sizes.length == 0) {
|
||||||
builder.append("\n - ").append(size.getWidth()).append('x').append(size.getHeight());
|
builder.append("\n (none)");
|
||||||
|
} else {
|
||||||
|
for (android.util.Size size : sizes) {
|
||||||
|
builder.append("\n - ").append(size.getWidth()).append('x').append(size.getHeight());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
android.util.Size[] highSpeedSizes = configs.getHighSpeedVideoSizes();
|
android.util.Size[] highSpeedSizes = configs.getHighSpeedVideoSizes();
|
||||||
if (highSpeedSizes.length > 0) {
|
if (highSpeedSizes != null && highSpeedSizes.length > 0) {
|
||||||
builder.append("\n High speed capture (--camera-high-speed):");
|
builder.append("\n High speed capture (--camera-high-speed):");
|
||||||
for (android.util.Size size : highSpeedSizes) {
|
for (android.util.Size size : highSpeedSizes) {
|
||||||
Range<Integer>[] highFpsRanges = configs.getHighSpeedVideoFpsRanges();
|
Range<Integer>[] highFpsRanges = configs.getHighSpeedVideoFpsRanges();
|
||||||
|
@ -3,6 +3,7 @@ package com.genymobile.scrcpy;
|
|||||||
import android.media.MediaCodec;
|
import android.media.MediaCodec;
|
||||||
import android.media.MediaCodecInfo;
|
import android.media.MediaCodecInfo;
|
||||||
import android.media.MediaFormat;
|
import android.media.MediaFormat;
|
||||||
|
import android.os.Build;
|
||||||
import android.os.Looper;
|
import android.os.Looper;
|
||||||
import android.os.SystemClock;
|
import android.os.SystemClock;
|
||||||
import android.view.Surface;
|
import android.view.Surface;
|
||||||
@ -47,7 +48,7 @@ public class SurfaceEncoder implements AsyncProcessor {
|
|||||||
this.downsizeOnError = downsizeOnError;
|
this.downsizeOnError = downsizeOnError;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void streamScreen() throws IOException, ConfigurationException {
|
private void streamCapture() throws IOException, ConfigurationException {
|
||||||
Codec codec = streamer.getCodec();
|
Codec codec = streamer.getCodec();
|
||||||
MediaCodec mediaCodec = createMediaCodec(codec, encoderName);
|
MediaCodec mediaCodec = createMediaCodec(codec, encoderName);
|
||||||
MediaFormat format = createFormat(codec.getMimeType(), videoBitRate, maxFps, codecOptions);
|
MediaFormat format = createFormat(codec.getMimeType(), videoBitRate, maxFps, codecOptions);
|
||||||
@ -220,6 +221,9 @@ public class SurfaceEncoder implements AsyncProcessor {
|
|||||||
// must be present to configure the encoder, but does not impact the actual frame rate, which is variable
|
// must be present to configure the encoder, but does not impact the actual frame rate, which is variable
|
||||||
format.setInteger(MediaFormat.KEY_FRAME_RATE, 60);
|
format.setInteger(MediaFormat.KEY_FRAME_RATE, 60);
|
||||||
format.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);
|
format.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||||
|
format.setInteger(MediaFormat.KEY_COLOR_RANGE, MediaFormat.COLOR_RANGE_LIMITED);
|
||||||
|
}
|
||||||
format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, DEFAULT_I_FRAME_INTERVAL);
|
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
|
// 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
|
format.setLong(MediaFormat.KEY_REPEAT_PREVIOUS_FRAME_AFTER, REPEAT_FRAME_DELAY_US); // µs
|
||||||
@ -250,7 +254,7 @@ public class SurfaceEncoder implements AsyncProcessor {
|
|||||||
Looper.prepare();
|
Looper.prepare();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
streamScreen();
|
streamCapture();
|
||||||
} catch (ConfigurationException e) {
|
} catch (ConfigurationException e) {
|
||||||
// Do not print stack trace, a user-friendly error-message has already been logged
|
// Do not print stack trace, a user-friendly error-message has already been logged
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
|
@ -12,12 +12,15 @@ import java.lang.reflect.Method;
|
|||||||
public final class WindowManager {
|
public final class WindowManager {
|
||||||
private final IInterface manager;
|
private final IInterface manager;
|
||||||
private Method getRotationMethod;
|
private Method getRotationMethod;
|
||||||
private Method freezeRotationMethod;
|
|
||||||
private Method freezeDisplayRotationMethod;
|
private Method freezeDisplayRotationMethod;
|
||||||
private Method isRotationFrozenMethod;
|
private int freezeDisplayRotationMethodVersion;
|
||||||
|
|
||||||
private Method isDisplayRotationFrozenMethod;
|
private Method isDisplayRotationFrozenMethod;
|
||||||
private Method thawRotationMethod;
|
private int isDisplayRotationFrozenMethodVersion;
|
||||||
|
|
||||||
private Method thawDisplayRotationMethod;
|
private Method thawDisplayRotationMethod;
|
||||||
|
private int thawDisplayRotationMethodVersion;
|
||||||
|
|
||||||
static WindowManager create() {
|
static WindowManager create() {
|
||||||
IInterface manager = ServiceManager.getService("window", "android.view.IWindowManager");
|
IInterface manager = ServiceManager.getService("window", "android.view.IWindowManager");
|
||||||
@ -43,50 +46,61 @@ public final class WindowManager {
|
|||||||
return getRotationMethod;
|
return getRotationMethod;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Method getFreezeRotationMethod() throws NoSuchMethodException {
|
|
||||||
if (freezeRotationMethod == null) {
|
|
||||||
freezeRotationMethod = manager.getClass().getMethod("freezeRotation", int.class);
|
|
||||||
}
|
|
||||||
return freezeRotationMethod;
|
|
||||||
}
|
|
||||||
|
|
||||||
// New method added by this commit:
|
|
||||||
// <https://android.googlesource.com/platform/frameworks/base/+/90c9005e687aa0f63f1ac391adc1e8878ab31759%5E%21/>
|
|
||||||
private Method getFreezeDisplayRotationMethod() throws NoSuchMethodException {
|
private Method getFreezeDisplayRotationMethod() throws NoSuchMethodException {
|
||||||
if (freezeDisplayRotationMethod == null) {
|
if (freezeDisplayRotationMethod == null) {
|
||||||
freezeDisplayRotationMethod = manager.getClass().getMethod("freezeDisplayRotation", int.class, int.class);
|
try {
|
||||||
|
// Android 15 preview and 14 QPR3 Beta added a String caller parameter for debugging:
|
||||||
|
// <https://android.googlesource.com/platform/frameworks/base/+/670fb7f5c0d23cf51ead25538bcb017e03ed73ac%5E%21/>
|
||||||
|
freezeDisplayRotationMethod = manager.getClass().getMethod("freezeDisplayRotation", int.class, int.class, String.class);
|
||||||
|
freezeDisplayRotationMethodVersion = 0;
|
||||||
|
} catch (NoSuchMethodException e) {
|
||||||
|
try {
|
||||||
|
// New method added by this commit:
|
||||||
|
// <https://android.googlesource.com/platform/frameworks/base/+/90c9005e687aa0f63f1ac391adc1e8878ab31759%5E%21/>
|
||||||
|
freezeDisplayRotationMethod = manager.getClass().getMethod("freezeDisplayRotation", int.class, int.class);
|
||||||
|
freezeDisplayRotationMethodVersion = 1;
|
||||||
|
} catch (NoSuchMethodException e1) {
|
||||||
|
freezeDisplayRotationMethod = manager.getClass().getMethod("freezeRotation", int.class);
|
||||||
|
freezeDisplayRotationMethodVersion = 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return freezeDisplayRotationMethod;
|
return freezeDisplayRotationMethod;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Method getIsRotationFrozenMethod() throws NoSuchMethodException {
|
|
||||||
if (isRotationFrozenMethod == null) {
|
|
||||||
isRotationFrozenMethod = manager.getClass().getMethod("isRotationFrozen");
|
|
||||||
}
|
|
||||||
return isRotationFrozenMethod;
|
|
||||||
}
|
|
||||||
|
|
||||||
// New method added by this commit:
|
|
||||||
// <https://android.googlesource.com/platform/frameworks/base/+/90c9005e687aa0f63f1ac391adc1e8878ab31759%5E%21/>
|
|
||||||
private Method getIsDisplayRotationFrozenMethod() throws NoSuchMethodException {
|
private Method getIsDisplayRotationFrozenMethod() throws NoSuchMethodException {
|
||||||
if (isDisplayRotationFrozenMethod == null) {
|
if (isDisplayRotationFrozenMethod == null) {
|
||||||
isDisplayRotationFrozenMethod = manager.getClass().getMethod("isDisplayRotationFrozen", int.class);
|
try {
|
||||||
|
// New method added by this commit:
|
||||||
|
// <https://android.googlesource.com/platform/frameworks/base/+/90c9005e687aa0f63f1ac391adc1e8878ab31759%5E%21/>
|
||||||
|
isDisplayRotationFrozenMethod = manager.getClass().getMethod("isDisplayRotationFrozen", int.class);
|
||||||
|
isDisplayRotationFrozenMethodVersion = 0;
|
||||||
|
} catch (NoSuchMethodException e) {
|
||||||
|
isDisplayRotationFrozenMethod = manager.getClass().getMethod("isRotationFrozen");
|
||||||
|
isDisplayRotationFrozenMethodVersion = 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return isDisplayRotationFrozenMethod;
|
return isDisplayRotationFrozenMethod;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Method getThawRotationMethod() throws NoSuchMethodException {
|
|
||||||
if (thawRotationMethod == null) {
|
|
||||||
thawRotationMethod = manager.getClass().getMethod("thawRotation");
|
|
||||||
}
|
|
||||||
return thawRotationMethod;
|
|
||||||
}
|
|
||||||
|
|
||||||
// New method added by this commit:
|
|
||||||
// <https://android.googlesource.com/platform/frameworks/base/+/90c9005e687aa0f63f1ac391adc1e8878ab31759%5E%21/>
|
|
||||||
private Method getThawDisplayRotationMethod() throws NoSuchMethodException {
|
private Method getThawDisplayRotationMethod() throws NoSuchMethodException {
|
||||||
if (thawDisplayRotationMethod == null) {
|
if (thawDisplayRotationMethod == null) {
|
||||||
thawDisplayRotationMethod = manager.getClass().getMethod("thawDisplayRotation", int.class);
|
try {
|
||||||
|
// Android 15 preview and 14 QPR3 Beta added a String caller parameter for debugging:
|
||||||
|
// <https://android.googlesource.com/platform/frameworks/base/+/670fb7f5c0d23cf51ead25538bcb017e03ed73ac%5E%21/>
|
||||||
|
thawDisplayRotationMethod = manager.getClass().getMethod("thawDisplayRotation", int.class, String.class);
|
||||||
|
thawDisplayRotationMethodVersion = 0;
|
||||||
|
} catch (NoSuchMethodException e) {
|
||||||
|
try {
|
||||||
|
// New method added by this commit:
|
||||||
|
// <https://android.googlesource.com/platform/frameworks/base/+/90c9005e687aa0f63f1ac391adc1e8878ab31759%5E%21/>
|
||||||
|
thawDisplayRotationMethod = manager.getClass().getMethod("thawDisplayRotation", int.class);
|
||||||
|
thawDisplayRotationMethodVersion = 1;
|
||||||
|
} catch (NoSuchMethodException e1) {
|
||||||
|
thawDisplayRotationMethod = manager.getClass().getMethod("thawRotation");
|
||||||
|
thawDisplayRotationMethodVersion = 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return thawDisplayRotationMethod;
|
return thawDisplayRotationMethod;
|
||||||
}
|
}
|
||||||
@ -103,16 +117,21 @@ public final class WindowManager {
|
|||||||
|
|
||||||
public void freezeRotation(int displayId, int rotation) {
|
public void freezeRotation(int displayId, int rotation) {
|
||||||
try {
|
try {
|
||||||
try {
|
Method method = getFreezeDisplayRotationMethod();
|
||||||
Method method = getFreezeDisplayRotationMethod();
|
switch (freezeDisplayRotationMethodVersion) {
|
||||||
method.invoke(manager, displayId, rotation);
|
case 0:
|
||||||
} catch (ReflectiveOperationException e) {
|
method.invoke(manager, displayId, rotation, "scrcpy#freezeRotation");
|
||||||
if (displayId == 0) {
|
break;
|
||||||
Method method = getFreezeRotationMethod();
|
case 1:
|
||||||
|
method.invoke(manager, displayId, rotation);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
if (displayId != 0) {
|
||||||
|
Ln.e("Secondary display rotation not supported on this device");
|
||||||
|
return;
|
||||||
|
}
|
||||||
method.invoke(manager, rotation);
|
method.invoke(manager, rotation);
|
||||||
} else {
|
break;
|
||||||
Ln.e("Could not invoke method", e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} catch (ReflectiveOperationException e) {
|
} catch (ReflectiveOperationException e) {
|
||||||
Ln.e("Could not invoke method", e);
|
Ln.e("Could not invoke method", e);
|
||||||
@ -121,17 +140,16 @@ public final class WindowManager {
|
|||||||
|
|
||||||
public boolean isRotationFrozen(int displayId) {
|
public boolean isRotationFrozen(int displayId) {
|
||||||
try {
|
try {
|
||||||
try {
|
Method method = getIsDisplayRotationFrozenMethod();
|
||||||
Method method = getIsDisplayRotationFrozenMethod();
|
switch (isDisplayRotationFrozenMethodVersion) {
|
||||||
return (boolean) method.invoke(manager, displayId);
|
case 0:
|
||||||
} catch (ReflectiveOperationException e) {
|
return (boolean) method.invoke(manager, displayId);
|
||||||
if (displayId == 0) {
|
default:
|
||||||
Method method = getIsRotationFrozenMethod();
|
if (displayId != 0) {
|
||||||
|
Ln.e("Secondary display rotation not supported on this device");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
return (boolean) method.invoke(manager);
|
return (boolean) method.invoke(manager);
|
||||||
} else {
|
|
||||||
Ln.e("Could not invoke method", e);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} catch (ReflectiveOperationException e) {
|
} catch (ReflectiveOperationException e) {
|
||||||
Ln.e("Could not invoke method", e);
|
Ln.e("Could not invoke method", e);
|
||||||
@ -141,16 +159,21 @@ public final class WindowManager {
|
|||||||
|
|
||||||
public void thawRotation(int displayId) {
|
public void thawRotation(int displayId) {
|
||||||
try {
|
try {
|
||||||
try {
|
Method method = getThawDisplayRotationMethod();
|
||||||
Method method = getThawDisplayRotationMethod();
|
switch (thawDisplayRotationMethodVersion) {
|
||||||
method.invoke(manager, displayId);
|
case 0:
|
||||||
} catch (ReflectiveOperationException e) {
|
method.invoke(manager, displayId, "scrcpy#thawRotation");
|
||||||
if (displayId == 0) {
|
break;
|
||||||
Method method = getThawRotationMethod();
|
case 1:
|
||||||
|
method.invoke(manager, displayId);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
if (displayId != 0) {
|
||||||
|
Ln.e("Secondary display rotation not supported on this device");
|
||||||
|
return;
|
||||||
|
}
|
||||||
method.invoke(manager);
|
method.invoke(manager);
|
||||||
} else {
|
break;
|
||||||
Ln.e("Could not invoke method", e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} catch (ReflectiveOperationException e) {
|
} catch (ReflectiveOperationException e) {
|
||||||
Ln.e("Could not invoke method", e);
|
Ln.e("Could not invoke method", e);
|
||||||
@ -166,6 +189,10 @@ public final class WindowManager {
|
|||||||
cls.getMethod("watchRotation", IRotationWatcher.class, int.class).invoke(manager, rotationWatcher, displayId);
|
cls.getMethod("watchRotation", IRotationWatcher.class, int.class).invoke(manager, rotationWatcher, displayId);
|
||||||
} catch (NoSuchMethodException e) {
|
} catch (NoSuchMethodException e) {
|
||||||
// old version
|
// old version
|
||||||
|
if (displayId != 0) {
|
||||||
|
Ln.e("Secondary display rotation not supported on this device");
|
||||||
|
return;
|
||||||
|
}
|
||||||
cls.getMethod("watchRotation", IRotationWatcher.class).invoke(manager, rotationWatcher);
|
cls.getMethod("watchRotation", IRotationWatcher.class).invoke(manager, rotationWatcher);
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
Reference in New Issue
Block a user