Compare commits
17 Commits
v2.4-insta
...
rtmp
Author | SHA1 | Date | |
---|---|---|---|
84be93ce3f | |||
22d78e8a82 | |||
bcb8503b26 | |||
9aa6cc71be | |||
54e08b4eae | |||
bd8b945bb3 | |||
a73bf932d6 | |||
7011dd1ef0 | |||
ee6620d123 | |||
aa34d63171 | |||
bf625790fa | |||
db55edb196 | |||
1c3801a0b1 | |||
be3d357a6d | |||
79968a0ae6 | |||
7f23ff3f2c | |||
cc7719079a |
@ -5,9 +5,9 @@ cd "$DEPS_DIR"
|
||||
. common
|
||||
|
||||
VERSION=1.0.27
|
||||
FILENAME=libusb-$VERSION.tar.bz2
|
||||
FILENAME=libusb-$VERSION.tar.gz
|
||||
PROJECT_DIR=libusb-$VERSION
|
||||
SHA256SUM=ffaa41d741a8a3bee244ac8e54a72ea05bf2879663c098c82fc5757853441575
|
||||
SHA256SUM=e8f18a7a36ecbb11fb820bd71540350d8f61bcd9db0d2e8c18a6fb80b214a3de
|
||||
|
||||
cd "$SOURCES_DIR"
|
||||
|
||||
@ -15,7 +15,7 @@ if [[ -d "$PROJECT_DIR" ]]
|
||||
then
|
||||
echo "$PWD/$PROJECT_DIR" found
|
||||
else
|
||||
get_file "https://github.com/libusb/libusb/releases/download/v$VERSION/libusb-$VERSION.tar.bz2" "$FILENAME" "$SHA256SUM"
|
||||
get_file "https://github.com/libusb/libusb/archive/refs/tags/v$VERSION.tar.gz" "$FILENAME" "$SHA256SUM"
|
||||
tar xf "$FILENAME" # First level directory is "$PROJECT_DIR"
|
||||
fi
|
||||
|
||||
@ -33,6 +33,7 @@ else
|
||||
mkdir "$HOST"
|
||||
cd "$HOST"
|
||||
|
||||
"$SOURCES_DIR/$PROJECT_DIR"/bootstrap.sh
|
||||
"$SOURCES_DIR/$PROJECT_DIR"/configure \
|
||||
--prefix="$INSTALL_DIR/$HOST" \
|
||||
--host="$HOST_TRIPLET" \
|
||||
|
@ -577,6 +577,14 @@ Flip display horizontally
|
||||
.B MOD+Shift+Up, MOD+Shift+Down
|
||||
Flip display vertically
|
||||
|
||||
.TP
|
||||
.B MOD+z
|
||||
Pause or re-pause display
|
||||
|
||||
.TP
|
||||
.B MOD+Shift+z
|
||||
Unpause display
|
||||
|
||||
.TP
|
||||
.B MOD+g
|
||||
Resize window to 1:1 (pixel\-perfect)
|
||||
|
@ -900,6 +900,14 @@ static const struct sc_shortcut shortcuts[] = {
|
||||
.shortcuts = { "MOD+Shift+Up", "MOD+Shift+Down" },
|
||||
.text = "Flip display vertically",
|
||||
},
|
||||
{
|
||||
.shortcuts = { "MOD+z" },
|
||||
.text = "Pause or re-pause display",
|
||||
},
|
||||
{
|
||||
.shortcuts = { "MOD+Shift+z" },
|
||||
.text = "Unpause display",
|
||||
},
|
||||
{
|
||||
.shortcuts = { "MOD+g" },
|
||||
.text = "Resize window to 1:1 (pixel-perfect)",
|
||||
@ -2548,9 +2556,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_codec == SC_CODEC_FLAC) {
|
||||
// Use 50 ms audio buffer by default, but use a higher value for FLAC,
|
||||
// which is not low latency (the default encoder produces blocks of
|
||||
// 4096 samples, which represent ~85.333ms).
|
||||
// Use 50 ms audio buffer by default, but use a higher value for
|
||||
// FLAC, which is not low latency (the default encoder produces
|
||||
// blocks of 4096 samples, which represent ~85.333ms).
|
||||
LOGI("FLAC audio: audio buffer increased to 120 ms (use "
|
||||
"--audio-buffer to set a custom value)");
|
||||
opts->audio_buffer = SC_TICK_FROM_MS(120);
|
||||
@ -2590,6 +2598,11 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
|
||||
}
|
||||
|
||||
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;
|
||||
if (kmode != SC_KEYBOARD_INPUT_MODE_AOA
|
||||
&& kmode != SC_KEYBOARD_INPUT_MODE_DISABLED) {
|
||||
|
@ -1,6 +1,7 @@
|
||||
#include "display.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <libavutil/pixfmt.h>
|
||||
|
||||
#include "util/log.h"
|
||||
|
||||
@ -65,6 +66,7 @@ sc_display_init(struct sc_display *display, SDL_Window *window, bool mipmaps) {
|
||||
display->texture = NULL;
|
||||
display->pending.flags = 0;
|
||||
display->pending.frame = NULL;
|
||||
display->has_frame = false;
|
||||
|
||||
return true;
|
||||
}
|
||||
@ -196,9 +198,25 @@ sc_display_set_texture_size(struct sc_display *display, struct sc_size size) {
|
||||
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
|
||||
sc_display_update_texture_internal(struct sc_display *display,
|
||||
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,
|
||||
frame->data[0], frame->linesize[0],
|
||||
frame->data[1], frame->linesize[1],
|
||||
|
@ -33,6 +33,8 @@ struct sc_display {
|
||||
struct sc_size size;
|
||||
AVFrame *frame;
|
||||
} pending;
|
||||
|
||||
bool has_frame;
|
||||
};
|
||||
|
||||
enum sc_display_result {
|
||||
|
@ -402,6 +402,7 @@ sc_input_manager_process_key(struct sc_input_manager *im,
|
||||
const SDL_KeyboardEvent *event) {
|
||||
// controller is NULL if --no-control is requested
|
||||
bool control = im->controller;
|
||||
bool paused = im->screen->paused;
|
||||
|
||||
SDL_Keycode keycode = event->keysym.sym;
|
||||
uint16_t mod = event->keysym.mod;
|
||||
@ -427,57 +428,62 @@ sc_input_manager_process_key(struct sc_input_manager *im,
|
||||
enum sc_action action = down ? SC_ACTION_DOWN : SC_ACTION_UP;
|
||||
switch (keycode) {
|
||||
case SDLK_h:
|
||||
if (im->kp && !shift && !repeat) {
|
||||
if (im->kp && !shift && !repeat && !paused) {
|
||||
action_home(im, action);
|
||||
}
|
||||
return;
|
||||
case SDLK_b: // fall-through
|
||||
case SDLK_BACKSPACE:
|
||||
if (im->kp && !shift && !repeat) {
|
||||
if (im->kp && !shift && !repeat && !paused) {
|
||||
action_back(im, action);
|
||||
}
|
||||
return;
|
||||
case SDLK_s:
|
||||
if (im->kp && !shift && !repeat) {
|
||||
if (im->kp && !shift && !repeat && !paused) {
|
||||
action_app_switch(im, action);
|
||||
}
|
||||
return;
|
||||
case SDLK_m:
|
||||
if (im->kp && !shift && !repeat) {
|
||||
if (im->kp && !shift && !repeat && !paused) {
|
||||
action_menu(im, action);
|
||||
}
|
||||
return;
|
||||
case SDLK_p:
|
||||
if (im->kp && !shift && !repeat) {
|
||||
if (im->kp && !shift && !repeat && !paused) {
|
||||
action_power(im, action);
|
||||
}
|
||||
return;
|
||||
case SDLK_o:
|
||||
if (control && !repeat && down) {
|
||||
if (control && !repeat && down && !paused) {
|
||||
enum sc_screen_power_mode mode = shift
|
||||
? SC_SCREEN_POWER_MODE_NORMAL
|
||||
: SC_SCREEN_POWER_MODE_OFF;
|
||||
set_screen_power_mode(im, mode);
|
||||
}
|
||||
return;
|
||||
case SDLK_z:
|
||||
if (down && !repeat) {
|
||||
sc_screen_set_paused(im->screen, !shift);
|
||||
}
|
||||
return;
|
||||
case SDLK_DOWN:
|
||||
if (shift) {
|
||||
if (!repeat & down) {
|
||||
if (!repeat && down) {
|
||||
apply_orientation_transform(im,
|
||||
SC_ORIENTATION_FLIP_180);
|
||||
}
|
||||
} else if (im->kp) {
|
||||
} else if (im->kp && !paused) {
|
||||
// forward repeated events
|
||||
action_volume_down(im, action);
|
||||
}
|
||||
return;
|
||||
case SDLK_UP:
|
||||
if (shift) {
|
||||
if (!repeat & down) {
|
||||
if (!repeat && down) {
|
||||
apply_orientation_transform(im,
|
||||
SC_ORIENTATION_FLIP_180);
|
||||
}
|
||||
} else if (im->kp) {
|
||||
} else if (im->kp && !paused) {
|
||||
// forward repeated events
|
||||
action_volume_up(im, action);
|
||||
}
|
||||
@ -505,17 +511,17 @@ sc_input_manager_process_key(struct sc_input_manager *im,
|
||||
}
|
||||
return;
|
||||
case SDLK_c:
|
||||
if (im->kp && !shift && !repeat && down) {
|
||||
if (im->kp && !shift && !repeat && down && !paused) {
|
||||
get_device_clipboard(im, SC_COPY_KEY_COPY);
|
||||
}
|
||||
return;
|
||||
case SDLK_x:
|
||||
if (im->kp && !shift && !repeat && down) {
|
||||
if (im->kp && !shift && !repeat && down && !paused) {
|
||||
get_device_clipboard(im, SC_COPY_KEY_CUT);
|
||||
}
|
||||
return;
|
||||
case SDLK_v:
|
||||
if (im->kp && !repeat && down) {
|
||||
if (im->kp && !repeat && down && !paused) {
|
||||
if (shift || im->legacy_paste) {
|
||||
// inject the text as input events
|
||||
clipboard_paste(im);
|
||||
@ -547,7 +553,7 @@ sc_input_manager_process_key(struct sc_input_manager *im,
|
||||
}
|
||||
return;
|
||||
case SDLK_n:
|
||||
if (control && !repeat && down) {
|
||||
if (control && !repeat && down && !paused) {
|
||||
if (shift) {
|
||||
collapse_panels(im);
|
||||
} else if (im->key_repeat == 0) {
|
||||
@ -558,12 +564,12 @@ sc_input_manager_process_key(struct sc_input_manager *im,
|
||||
}
|
||||
return;
|
||||
case SDLK_r:
|
||||
if (control && !shift && !repeat && down) {
|
||||
if (control && !shift && !repeat && down && !paused) {
|
||||
rotate_device(im);
|
||||
}
|
||||
return;
|
||||
case SDLK_k:
|
||||
if (control && !shift && !repeat && down
|
||||
if (control && !shift && !repeat && down && !paused
|
||||
&& im->kp && im->kp->hid) {
|
||||
// Only if the current keyboard is hid
|
||||
open_hard_keyboard_settings(im);
|
||||
@ -574,7 +580,7 @@ sc_input_manager_process_key(struct sc_input_manager *im,
|
||||
return;
|
||||
}
|
||||
|
||||
if (!im->kp) {
|
||||
if (!im->kp || paused) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -622,7 +628,6 @@ sc_input_manager_process_key(struct sc_input_manager *im,
|
||||
static void
|
||||
sc_input_manager_process_mouse_motion(struct sc_input_manager *im,
|
||||
const SDL_MouseMotionEvent *event) {
|
||||
|
||||
if (event->which == SDL_TOUCH_MOUSEID) {
|
||||
// simulated from touch events, so it's a duplicate
|
||||
return;
|
||||
@ -695,16 +700,16 @@ sc_input_manager_process_touch(struct sc_input_manager *im,
|
||||
static void
|
||||
sc_input_manager_process_mouse_button(struct sc_input_manager *im,
|
||||
const SDL_MouseButtonEvent *event) {
|
||||
bool control = im->controller;
|
||||
|
||||
if (event->which == SDL_TOUCH_MOUSEID) {
|
||||
// simulated from touch events, so it's a duplicate
|
||||
return;
|
||||
}
|
||||
|
||||
bool control = im->controller;
|
||||
bool paused = im->screen->paused;
|
||||
bool down = event->type == SDL_MOUSEBUTTONDOWN;
|
||||
if (!im->forward_all_clicks) {
|
||||
if (control) {
|
||||
if (control && !paused) {
|
||||
enum sc_action action = down ? SC_ACTION_DOWN : SC_ACTION_UP;
|
||||
|
||||
if (im->kp && event->button == SDL_BUTTON_X1) {
|
||||
@ -747,7 +752,7 @@ sc_input_manager_process_mouse_button(struct sc_input_manager *im,
|
||||
// otherwise, send the click event to the device
|
||||
}
|
||||
|
||||
if (!im->mp) {
|
||||
if (!im->mp || paused) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -885,9 +890,10 @@ void
|
||||
sc_input_manager_handle_event(struct sc_input_manager *im,
|
||||
const SDL_Event *event) {
|
||||
bool control = im->controller;
|
||||
bool paused = im->screen->paused;
|
||||
switch (event->type) {
|
||||
case SDL_TEXTINPUT:
|
||||
if (!im->kp) {
|
||||
if (!im->kp || paused) {
|
||||
break;
|
||||
}
|
||||
sc_input_manager_process_text_input(im, &event->text);
|
||||
@ -899,13 +905,13 @@ sc_input_manager_handle_event(struct sc_input_manager *im,
|
||||
sc_input_manager_process_key(im, &event->key);
|
||||
break;
|
||||
case SDL_MOUSEMOTION:
|
||||
if (!im->mp) {
|
||||
if (!im->mp || paused) {
|
||||
break;
|
||||
}
|
||||
sc_input_manager_process_mouse_motion(im, &event->motion);
|
||||
break;
|
||||
case SDL_MOUSEWHEEL:
|
||||
if (!im->mp) {
|
||||
if (!im->mp || paused) {
|
||||
break;
|
||||
}
|
||||
sc_input_manager_process_mouse_wheel(im, &event->wheel);
|
||||
@ -919,7 +925,7 @@ sc_input_manager_handle_event(struct sc_input_manager *im,
|
||||
case SDL_FINGERMOTION:
|
||||
case SDL_FINGERDOWN:
|
||||
case SDL_FINGERUP:
|
||||
if (!im->mp) {
|
||||
if (!im->mp || paused) {
|
||||
break;
|
||||
}
|
||||
sc_input_manager_process_touch(im, &event->tfinger);
|
||||
|
@ -9,6 +9,8 @@
|
||||
#include "util/log.h"
|
||||
#include "util/str.h"
|
||||
|
||||
#define RTMP_URL "rtmp://localhost/live/stream"
|
||||
|
||||
/** Downcast packet sinks to recorder */
|
||||
#define DOWNCAST_VIDEO(SINK) \
|
||||
container_of(SINK, struct sc_recorder, video_packet_sink)
|
||||
@ -131,7 +133,7 @@ static bool
|
||||
sc_recorder_open_output_file(struct sc_recorder *recorder) {
|
||||
const char *format_name = sc_recorder_get_format_name(recorder->format);
|
||||
assert(format_name);
|
||||
const AVOutputFormat *format = find_muxer(format_name);
|
||||
const AVOutputFormat *format = find_muxer("flv");
|
||||
if (!format) {
|
||||
LOGE("Could not find muxer");
|
||||
return false;
|
||||
@ -143,8 +145,11 @@ sc_recorder_open_output_file(struct sc_recorder *recorder) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int ret = avio_open(&recorder->ctx->pb, recorder->filename,
|
||||
AVIO_FLAG_WRITE);
|
||||
AVDictionary *options = NULL;
|
||||
av_dict_set(&options, "listen", "1", 0); // Enable listening
|
||||
|
||||
int ret = avio_open2(&recorder->ctx->pb, RTMP_URL,
|
||||
AVIO_FLAG_WRITE, NULL, &options);
|
||||
if (ret < 0) {
|
||||
LOGE("Failed to open output file: %s", recorder->filename);
|
||||
avformat_free_context(recorder->ctx);
|
||||
|
@ -805,9 +805,12 @@ scrcpy(struct scrcpy_options *options) {
|
||||
ret = event_loop(s);
|
||||
LOGD("quit...");
|
||||
|
||||
// Close the window immediately on closing, because screen_destroy() may
|
||||
// only be called once the video demuxer thread is joined (it may take time)
|
||||
sc_screen_hide_window(&s->screen);
|
||||
if (options->video_playback) {
|
||||
// Close the window immediately on closing, because screen_destroy()
|
||||
// may only be called once the video demuxer thread is joined (it may
|
||||
// take time)
|
||||
sc_screen_hide_window(&s->screen);
|
||||
}
|
||||
|
||||
end:
|
||||
if (timeout_started) {
|
||||
|
@ -362,6 +362,8 @@ sc_screen_init(struct sc_screen *screen,
|
||||
screen->maximized = false;
|
||||
screen->minimized = false;
|
||||
screen->mouse_capture_key_pressed = 0;
|
||||
screen->paused = false;
|
||||
screen->resume_frame = NULL;
|
||||
|
||||
screen->req.x = params->window_x;
|
||||
screen->req.y = params->window_y;
|
||||
@ -614,13 +616,10 @@ prepare_for_frame(struct sc_screen *screen, struct sc_size new_frame_size) {
|
||||
}
|
||||
|
||||
static bool
|
||||
sc_screen_update_frame(struct sc_screen *screen) {
|
||||
av_frame_unref(screen->frame);
|
||||
sc_frame_buffer_consume(&screen->fb, screen->frame);
|
||||
AVFrame *frame = screen->frame;
|
||||
|
||||
sc_screen_apply_frame(struct sc_screen *screen) {
|
||||
sc_fps_counter_add_rendered_frame(&screen->fps_counter);
|
||||
|
||||
AVFrame *frame = screen->frame;
|
||||
struct sc_size new_frame_size = {frame->width, frame->height};
|
||||
enum sc_display_result res = prepare_for_frame(screen, new_frame_size);
|
||||
if (res == SC_DISPLAY_RESULT_ERROR) {
|
||||
@ -655,6 +654,54 @@ sc_screen_update_frame(struct sc_screen *screen) {
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
sc_screen_update_frame(struct sc_screen *screen) {
|
||||
if (screen->paused) {
|
||||
if (!screen->resume_frame) {
|
||||
screen->resume_frame = av_frame_alloc();
|
||||
if (!screen->resume_frame) {
|
||||
LOG_OOM();
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
av_frame_unref(screen->resume_frame);
|
||||
}
|
||||
sc_frame_buffer_consume(&screen->fb, screen->resume_frame);
|
||||
return true;
|
||||
}
|
||||
|
||||
av_frame_unref(screen->frame);
|
||||
sc_frame_buffer_consume(&screen->fb, screen->frame);
|
||||
return sc_screen_apply_frame(screen);
|
||||
}
|
||||
|
||||
void
|
||||
sc_screen_set_paused(struct sc_screen *screen, bool paused) {
|
||||
if (!paused && !screen->paused) {
|
||||
// nothing to do
|
||||
return;
|
||||
}
|
||||
|
||||
if (screen->paused && screen->resume_frame) {
|
||||
// If display screen was paused, refresh the frame immediately, even if
|
||||
// the new state is also paused.
|
||||
av_frame_free(&screen->frame);
|
||||
screen->frame = screen->resume_frame;
|
||||
screen->resume_frame = NULL;
|
||||
sc_screen_apply_frame(screen);
|
||||
}
|
||||
|
||||
if (!paused) {
|
||||
LOGI("Display screen unpaused");
|
||||
} else if (!screen->paused) {
|
||||
LOGI("Display screen paused");
|
||||
} else {
|
||||
LOGI("Display screen re-paused");
|
||||
}
|
||||
|
||||
screen->paused = paused;
|
||||
}
|
||||
|
||||
void
|
||||
sc_screen_switch_fullscreen(struct sc_screen *screen) {
|
||||
uint32_t new_mode = screen->fullscreen ? 0 : SDL_WINDOW_FULLSCREEN_DESKTOP;
|
||||
|
@ -64,6 +64,9 @@ struct sc_screen {
|
||||
SDL_Keycode mouse_capture_key_pressed;
|
||||
|
||||
AVFrame *frame;
|
||||
|
||||
bool paused;
|
||||
AVFrame *resume_frame;
|
||||
};
|
||||
|
||||
struct sc_screen_params {
|
||||
@ -135,6 +138,10 @@ void
|
||||
sc_screen_set_orientation(struct sc_screen *screen,
|
||||
enum sc_orientation orientation);
|
||||
|
||||
// set the display pause state
|
||||
void
|
||||
sc_screen_set_paused(struct sc_screen *screen, bool paused);
|
||||
|
||||
// react to SDL events
|
||||
// If this function returns false, scrcpy must exit with an error.
|
||||
bool
|
||||
|
@ -67,14 +67,6 @@ computer.
|
||||
An option `--tcpip` allows to configure the connection automatically. There are
|
||||
two variants.
|
||||
|
||||
If the device (accessible at 192.168.1.1 in this example) already listens on a
|
||||
port (typically 5555) for incoming _adb_ connections, then run:
|
||||
|
||||
```bash
|
||||
scrcpy --tcpip=192.168.1.1 # default port is 5555
|
||||
scrcpy --tcpip=192.168.1.1:5555
|
||||
```
|
||||
|
||||
If _adb_ TCP/IP mode is disabled on the device (or if you don't know the IP
|
||||
address), connect the device over USB, then run:
|
||||
|
||||
@ -85,6 +77,14 @@ scrcpy --tcpip # without arguments
|
||||
It will automatically find the device IP address and adb port, enable TCP/IP
|
||||
mode if necessary, then connect to the device before starting.
|
||||
|
||||
If the device (accessible at 192.168.1.1 in this example) already listens on a
|
||||
port (typically 5555) for incoming _adb_ connections, then run:
|
||||
|
||||
```bash
|
||||
scrcpy --tcpip=192.168.1.1 # default port is 5555
|
||||
scrcpy --tcpip=192.168.1.1:5555
|
||||
```
|
||||
|
||||
|
||||
### Manual
|
||||
|
||||
|
@ -67,12 +67,16 @@ More precisely, hold down <kbd>Ctrl</kbd> while pressing the left-click button.
|
||||
Until the left-click button is released, all mouse movements scale and rotate
|
||||
the content (if supported by the app) relative to the center of the screen.
|
||||
|
||||
https://github.com/Genymobile/scrcpy/assets/543275/26c4a920-9805-43f1-8d4c-608752d04767
|
||||
|
||||
To simulate a tilt gesture: <kbd>Shift</kbd>+_click-and-move-up-or-down_.
|
||||
|
||||
https://github.com/Genymobile/scrcpy/assets/543275/1e252341-4a90-4b29-9d11-9153b324669f
|
||||
|
||||
Technically, _scrcpy_ generates additional touch events from a "virtual finger"
|
||||
at a location inverted through the center of the screen. When pressing
|
||||
<kbd>Ctrl</kbd> the x and y coordinates are inverted. Using <kbd>Shift</kbd>
|
||||
only inverts x.
|
||||
<kbd>Ctrl</kbd> the _x_ and _y_ coordinates are inverted. Using <kbd>Shift</kbd>
|
||||
only inverts _x_.
|
||||
|
||||
This only works for the default mouse mode (`--mouse=sdk`).
|
||||
|
||||
|
@ -28,6 +28,8 @@ _<kbd>[Super]</kbd> is typically the <kbd>Windows</kbd> or <kbd>Cmd</kbd> key._
|
||||
| Rotate display right | <kbd>MOD</kbd>+<kbd>→</kbd> _(right)_
|
||||
| Flip display horizontally | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>←</kbd> _(left)_ \| <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>→</kbd> _(right)_
|
||||
| Flip display vertically | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>↑</kbd> _(up)_ \| <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>↓</kbd> _(down)_
|
||||
| Pause or re-pause display | <kbd>MOD</kbd>+<kbd>z</kbd>
|
||||
| Unpause display | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>z</kbd>
|
||||
| Resize window to 1:1 (pixel-perfect) | <kbd>MOD</kbd>+<kbd>g</kbd>
|
||||
| Resize window to remove black borders | <kbd>MOD</kbd>+<kbd>w</kbd> \| _Double-left-click¹_
|
||||
| Click on `HOME` | <kbd>MOD</kbd>+<kbd>h</kbd> \| _Middle-click_
|
||||
|
@ -127,6 +127,10 @@ public class CameraCapture extends SurfaceCapture {
|
||||
|
||||
StreamConfigurationMap configs = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
|
||||
android.util.Size[] sizes = highSpeed ? configs.getHighSpeedVideoSizes() : configs.getOutputSizes(MediaCodec.class);
|
||||
if (sizes == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Stream<android.util.Size> stream = Arrays.stream(sizes);
|
||||
if (maxSize > 0) {
|
||||
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);
|
||||
|
||||
android.util.Size[] sizes = configs.getOutputSizes(MediaCodec.class);
|
||||
for (android.util.Size size : sizes) {
|
||||
builder.append("\n - ").append(size.getWidth()).append('x').append(size.getHeight());
|
||||
if (sizes == null || sizes.length == 0) {
|
||||
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();
|
||||
if (highSpeedSizes.length > 0) {
|
||||
if (highSpeedSizes != null && highSpeedSizes.length > 0) {
|
||||
builder.append("\n High speed capture (--camera-high-speed):");
|
||||
for (android.util.Size size : highSpeedSizes) {
|
||||
Range<Integer>[] highFpsRanges = configs.getHighSpeedVideoFpsRanges();
|
||||
|
@ -3,6 +3,7 @@ package com.genymobile.scrcpy;
|
||||
import android.media.MediaCodec;
|
||||
import android.media.MediaCodecInfo;
|
||||
import android.media.MediaFormat;
|
||||
import android.os.Build;
|
||||
import android.os.Looper;
|
||||
import android.os.SystemClock;
|
||||
import android.view.Surface;
|
||||
@ -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
|
||||
format.setInteger(MediaFormat.KEY_FRAME_RATE, 60);
|
||||
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);
|
||||
// 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
|
||||
|
@ -12,12 +12,15 @@ import java.lang.reflect.Method;
|
||||
public final class WindowManager {
|
||||
private final IInterface manager;
|
||||
private Method getRotationMethod;
|
||||
private Method freezeRotationMethod;
|
||||
|
||||
private Method freezeDisplayRotationMethod;
|
||||
private Method isRotationFrozenMethod;
|
||||
private int freezeDisplayRotationMethodVersion;
|
||||
|
||||
private Method isDisplayRotationFrozenMethod;
|
||||
private Method thawRotationMethod;
|
||||
private int isDisplayRotationFrozenMethodVersion;
|
||||
|
||||
private Method thawDisplayRotationMethod;
|
||||
private int thawDisplayRotationMethodVersion;
|
||||
|
||||
static WindowManager create() {
|
||||
IInterface manager = ServiceManager.getService("window", "android.view.IWindowManager");
|
||||
@ -43,50 +46,61 @@ public final class WindowManager {
|
||||
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 {
|
||||
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;
|
||||
}
|
||||
|
||||
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 {
|
||||
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;
|
||||
}
|
||||
|
||||
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 {
|
||||
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;
|
||||
}
|
||||
@ -103,16 +117,21 @@ public final class WindowManager {
|
||||
|
||||
public void freezeRotation(int displayId, int rotation) {
|
||||
try {
|
||||
try {
|
||||
Method method = getFreezeDisplayRotationMethod();
|
||||
method.invoke(manager, displayId, rotation);
|
||||
} catch (ReflectiveOperationException e) {
|
||||
if (displayId == 0) {
|
||||
Method method = getFreezeRotationMethod();
|
||||
Method method = getFreezeDisplayRotationMethod();
|
||||
switch (freezeDisplayRotationMethodVersion) {
|
||||
case 0:
|
||||
method.invoke(manager, displayId, rotation, "scrcpy#freezeRotation");
|
||||
break;
|
||||
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);
|
||||
} else {
|
||||
Ln.e("Could not invoke method", e);
|
||||
}
|
||||
break;
|
||||
}
|
||||
} catch (ReflectiveOperationException e) {
|
||||
Ln.e("Could not invoke method", e);
|
||||
@ -121,17 +140,16 @@ public final class WindowManager {
|
||||
|
||||
public boolean isRotationFrozen(int displayId) {
|
||||
try {
|
||||
try {
|
||||
Method method = getIsDisplayRotationFrozenMethod();
|
||||
return (boolean) method.invoke(manager, displayId);
|
||||
} catch (ReflectiveOperationException e) {
|
||||
if (displayId == 0) {
|
||||
Method method = getIsRotationFrozenMethod();
|
||||
Method method = getIsDisplayRotationFrozenMethod();
|
||||
switch (isDisplayRotationFrozenMethodVersion) {
|
||||
case 0:
|
||||
return (boolean) method.invoke(manager, displayId);
|
||||
default:
|
||||
if (displayId != 0) {
|
||||
Ln.e("Secondary display rotation not supported on this device");
|
||||
return false;
|
||||
}
|
||||
return (boolean) method.invoke(manager);
|
||||
} else {
|
||||
Ln.e("Could not invoke method", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} catch (ReflectiveOperationException e) {
|
||||
Ln.e("Could not invoke method", e);
|
||||
@ -141,16 +159,21 @@ public final class WindowManager {
|
||||
|
||||
public void thawRotation(int displayId) {
|
||||
try {
|
||||
try {
|
||||
Method method = getThawDisplayRotationMethod();
|
||||
method.invoke(manager, displayId);
|
||||
} catch (ReflectiveOperationException e) {
|
||||
if (displayId == 0) {
|
||||
Method method = getThawRotationMethod();
|
||||
Method method = getThawDisplayRotationMethod();
|
||||
switch (thawDisplayRotationMethodVersion) {
|
||||
case 0:
|
||||
method.invoke(manager, displayId, "scrcpy#thawRotation");
|
||||
break;
|
||||
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);
|
||||
} else {
|
||||
Ln.e("Could not invoke method", e);
|
||||
}
|
||||
break;
|
||||
}
|
||||
} catch (ReflectiveOperationException 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);
|
||||
} catch (NoSuchMethodException e) {
|
||||
// old version
|
||||
if (displayId != 0) {
|
||||
Ln.e("Secondary display rotation not supported on this device");
|
||||
return;
|
||||
}
|
||||
cls.getMethod("watchRotation", IRotationWatcher.class).invoke(manager, rotationWatcher);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
|
Reference in New Issue
Block a user