Compare commits
4 Commits
maximized.
...
window-par
Author | SHA1 | Date | |
---|---|---|---|
ad6b8847d4 | |||
fba89e6f73 | |||
4bf2b80c75 | |||
f983ec67fa |
30
DEVELOP.md
30
DEVELOP.md
@ -268,33 +268,3 @@ For more details, go read the code!
|
||||
|
||||
If you find a bug, or have an awesome idea to implement, please discuss and
|
||||
contribute ;-)
|
||||
|
||||
|
||||
### Debug the server
|
||||
|
||||
The server is pushed to the device by the client on startup.
|
||||
|
||||
To debug it, enable the server debugger during configuration:
|
||||
|
||||
```bash
|
||||
meson x -Dserver_debugger=true
|
||||
# or, if x is already configured
|
||||
meson configure x -Dserver_debugger=true
|
||||
```
|
||||
|
||||
Then recompile.
|
||||
|
||||
When you start scrcpy, it will start a debugger on port 5005 on the device.
|
||||
Redirect that port to the computer:
|
||||
|
||||
```bash
|
||||
adb forward tcp:5005 tcp:5005
|
||||
```
|
||||
|
||||
In Android Studio, _Run_ > _Debug_ > _Edit configurations..._ On the left, click on
|
||||
`+`, _Remote_, and fill the form:
|
||||
|
||||
- Host: `localhost`
|
||||
- Port: `5005`
|
||||
|
||||
Then click on _Debug_.
|
||||
|
@ -143,6 +143,7 @@ This is useful for example to mirror only one eye of the Oculus Go:
|
||||
|
||||
```bash
|
||||
scrcpy --crop 1224:1440:0:0 # 1224x1440 at offset (0,0)
|
||||
scrcpy -c 1224:1440:0:0 # short version
|
||||
```
|
||||
|
||||
If `--max-size` is also specified, resizing is applied after cropping.
|
||||
@ -225,6 +226,7 @@ The window of app can always be above others by:
|
||||
|
||||
```bash
|
||||
scrcpy --always-on-top
|
||||
scrcpy -T # short version
|
||||
```
|
||||
|
||||
|
||||
|
@ -115,9 +115,6 @@ conf.set('HIDPI_SUPPORT', get_option('hidpi_support'))
|
||||
# disable console on Windows
|
||||
conf.set('WINDOWS_NOCONSOLE', get_option('windows_noconsole'))
|
||||
|
||||
# run a server debugger and wait for a client to be attached
|
||||
conf.set('SERVER_DEBUGGER', get_option('server_debugger'))
|
||||
|
||||
configure_file(configuration: conf, output: 'config.h')
|
||||
|
||||
src_dir = include_directories('src')
|
||||
|
30
app/scrcpy.1
30
app/scrcpy.1
@ -61,13 +61,6 @@ Set the TCP port the client listens on.
|
||||
|
||||
Default is 27183.
|
||||
|
||||
.TP
|
||||
.B \-\-prefer\-text
|
||||
Inject alpha characters and space as text events instead of key events.
|
||||
|
||||
This avoids issues when combining multiple keys to enter special characters,
|
||||
but breaks the expected behavior of alpha keys in games (typically WASD).
|
||||
|
||||
.TP
|
||||
.BI "\-\-push\-target " path
|
||||
Set the target directory for pushing files to the device by drag & drop. It is passed as\-is to "adb push".
|
||||
@ -113,6 +106,29 @@ Print the version of scrcpy.
|
||||
.BI \-\-window\-title " text
|
||||
Set a custom window title.
|
||||
|
||||
.TP
|
||||
.BI \-\-window\-x " value
|
||||
Set the initial window horizontal position.
|
||||
|
||||
Default is -1 (automatic).\n
|
||||
|
||||
.TP
|
||||
.BI \-\-window\-y " value
|
||||
Set the initial window vertical position.
|
||||
|
||||
Default is -1 (automatic).\n
|
||||
|
||||
.TP
|
||||
.BI \-\-window\-width " value
|
||||
Set the initial window width.
|
||||
|
||||
Default is 0 (automatic).\n
|
||||
|
||||
.TP
|
||||
.BI \-\-window\-height " value
|
||||
Set the initial window height.
|
||||
|
||||
Default is 0 (automatic).\n
|
||||
|
||||
.SH SHORTCUTS
|
||||
|
||||
|
@ -5,7 +5,7 @@
|
||||
#define MAP(FROM, TO) case FROM: *to = TO; return true
|
||||
#define FAIL default: return false
|
||||
|
||||
bool
|
||||
static bool
|
||||
convert_keycode_action(SDL_EventType from, enum android_keyevent_action *to) {
|
||||
switch (from) {
|
||||
MAP(SDL_KEYDOWN, AKEY_EVENT_ACTION_DOWN);
|
||||
@ -33,7 +33,7 @@ autocomplete_metastate(enum android_metastate metastate) {
|
||||
return metastate;
|
||||
}
|
||||
|
||||
enum android_metastate
|
||||
static enum android_metastate
|
||||
convert_meta_state(SDL_Keymod mod) {
|
||||
enum android_metastate metastate = 0;
|
||||
if (mod & KMOD_LSHIFT) {
|
||||
@ -74,9 +74,8 @@ convert_meta_state(SDL_Keymod mod) {
|
||||
return autocomplete_metastate(metastate);
|
||||
}
|
||||
|
||||
bool
|
||||
convert_keycode(SDL_Keycode from, enum android_keycode *to, uint16_t mod,
|
||||
bool prefer_text) {
|
||||
static bool
|
||||
convert_keycode(SDL_Keycode from, enum android_keycode *to, uint16_t mod) {
|
||||
switch (from) {
|
||||
MAP(SDLK_RETURN, AKEYCODE_ENTER);
|
||||
MAP(SDLK_KP_ENTER, AKEYCODE_NUMPAD_ENTER);
|
||||
@ -93,12 +92,6 @@ convert_keycode(SDL_Keycode from, enum android_keycode *to, uint16_t mod,
|
||||
MAP(SDLK_DOWN, AKEYCODE_DPAD_DOWN);
|
||||
MAP(SDLK_UP, AKEYCODE_DPAD_UP);
|
||||
}
|
||||
|
||||
if (prefer_text) {
|
||||
// do not forward alpha and space key events
|
||||
return false;
|
||||
}
|
||||
|
||||
if (mod & (KMOD_LALT | KMOD_RALT | KMOD_LGUI | KMOD_RGUI)) {
|
||||
return false;
|
||||
}
|
||||
@ -135,7 +128,7 @@ convert_keycode(SDL_Keycode from, enum android_keycode *to, uint16_t mod,
|
||||
}
|
||||
}
|
||||
|
||||
enum android_motionevent_buttons
|
||||
static enum android_motionevent_buttons
|
||||
convert_mouse_buttons(uint32_t state) {
|
||||
enum android_motionevent_buttons buttons = 0;
|
||||
if (state & SDL_BUTTON_LMASK) {
|
||||
@ -157,6 +150,24 @@ convert_mouse_buttons(uint32_t state) {
|
||||
}
|
||||
|
||||
bool
|
||||
convert_input_key(const SDL_KeyboardEvent *from, struct control_msg *to) {
|
||||
to->type = CONTROL_MSG_TYPE_INJECT_KEYCODE;
|
||||
|
||||
if (!convert_keycode_action(from->type, &to->inject_keycode.action)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint16_t mod = from->keysym.mod;
|
||||
if (!convert_keycode(from->keysym.sym, &to->inject_keycode.keycode, mod)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
to->inject_keycode.metastate = convert_meta_state(mod);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
convert_mouse_action(SDL_EventType from, enum android_motionevent_action *to) {
|
||||
switch (from) {
|
||||
MAP(SDL_MOUSEBUTTONDOWN, AMOTION_EVENT_ACTION_DOWN);
|
||||
@ -166,6 +177,41 @@ convert_mouse_action(SDL_EventType from, enum android_motionevent_action *to) {
|
||||
}
|
||||
|
||||
bool
|
||||
convert_mouse_button(const SDL_MouseButtonEvent *from, struct size screen_size,
|
||||
struct control_msg *to) {
|
||||
to->type = CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT;
|
||||
|
||||
if (!convert_mouse_action(from->type, &to->inject_touch_event.action)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
to->inject_touch_event.pointer_id = POINTER_ID_MOUSE;
|
||||
to->inject_touch_event.position.screen_size = screen_size;
|
||||
to->inject_touch_event.position.point.x = from->x;
|
||||
to->inject_touch_event.position.point.y = from->y;
|
||||
to->inject_touch_event.pressure = 1.f;
|
||||
to->inject_touch_event.buttons =
|
||||
convert_mouse_buttons(SDL_BUTTON(from->button));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
convert_mouse_motion(const SDL_MouseMotionEvent *from, struct size screen_size,
|
||||
struct control_msg *to) {
|
||||
to->type = CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT;
|
||||
to->inject_touch_event.action = AMOTION_EVENT_ACTION_MOVE;
|
||||
to->inject_touch_event.pointer_id = POINTER_ID_MOUSE;
|
||||
to->inject_touch_event.position.screen_size = screen_size;
|
||||
to->inject_touch_event.position.point.x = from->x;
|
||||
to->inject_touch_event.position.point.y = from->y;
|
||||
to->inject_touch_event.pressure = 1.f;
|
||||
to->inject_touch_event.buttons = convert_mouse_buttons(from->state);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
convert_touch_action(SDL_EventType from, enum android_motionevent_action *to) {
|
||||
switch (from) {
|
||||
MAP(SDL_FINGERMOTION, AMOTION_EVENT_ACTION_MOVE);
|
||||
@ -174,3 +220,39 @@ convert_touch_action(SDL_EventType from, enum android_motionevent_action *to) {
|
||||
FAIL;
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
convert_touch(const SDL_TouchFingerEvent *from, struct size screen_size,
|
||||
struct control_msg *to) {
|
||||
to->type = CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT;
|
||||
|
||||
if (!convert_touch_action(from->type, &to->inject_touch_event.action)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
to->inject_touch_event.pointer_id = from->fingerId;
|
||||
to->inject_touch_event.position.screen_size = screen_size;
|
||||
// SDL touch event coordinates are normalized in the range [0; 1]
|
||||
to->inject_touch_event.position.point.x = from->x * screen_size.width;
|
||||
to->inject_touch_event.position.point.y = from->y * screen_size.height;
|
||||
to->inject_touch_event.pressure = from->pressure;
|
||||
to->inject_touch_event.buttons = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
convert_mouse_wheel(const SDL_MouseWheelEvent *from, struct position position,
|
||||
struct control_msg *to) {
|
||||
to->type = CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT;
|
||||
|
||||
to->inject_scroll_event.position = position;
|
||||
|
||||
int mul = from->direction == SDL_MOUSEWHEEL_NORMAL ? 1 : -1;
|
||||
// SDL behavior seems inconsistent between horizontal and vertical scrolling
|
||||
// so reverse the horizontal
|
||||
// <https://wiki.libsdl.org/SDL_MouseWheelEvent#Remarks>
|
||||
to->inject_scroll_event.hscroll = -mul * from->x;
|
||||
to->inject_scroll_event.vscroll = mul * from->y;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -7,23 +7,36 @@
|
||||
#include "config.h"
|
||||
#include "control_msg.h"
|
||||
|
||||
bool
|
||||
convert_keycode_action(SDL_EventType from, enum android_keyevent_action *to);
|
||||
struct complete_mouse_motion_event {
|
||||
SDL_MouseMotionEvent *mouse_motion_event;
|
||||
struct size screen_size;
|
||||
};
|
||||
|
||||
enum android_metastate
|
||||
convert_meta_state(SDL_Keymod mod);
|
||||
struct complete_mouse_wheel_event {
|
||||
SDL_MouseWheelEvent *mouse_wheel_event;
|
||||
struct point position;
|
||||
};
|
||||
|
||||
bool
|
||||
convert_keycode(SDL_Keycode from, enum android_keycode *to, uint16_t mod,
|
||||
bool prefer_text);
|
||||
|
||||
enum android_motionevent_buttons
|
||||
convert_mouse_buttons(uint32_t state);
|
||||
convert_input_key(const SDL_KeyboardEvent *from, struct control_msg *to);
|
||||
|
||||
bool
|
||||
convert_mouse_action(SDL_EventType from, enum android_motionevent_action *to);
|
||||
convert_mouse_button(const SDL_MouseButtonEvent *from, struct size screen_size,
|
||||
struct control_msg *to);
|
||||
|
||||
// the video size may be different from the real device size, so we need the
|
||||
// size to which the absolute position apply, to scale it accordingly
|
||||
bool
|
||||
convert_mouse_motion(const SDL_MouseMotionEvent *from, struct size screen_size,
|
||||
struct control_msg *to);
|
||||
|
||||
bool
|
||||
convert_touch_action(SDL_EventType from, enum android_motionevent_action *to);
|
||||
convert_touch(const SDL_TouchFingerEvent *from, struct size screen_size,
|
||||
struct control_msg *to);
|
||||
|
||||
// on Android, a scroll event requires the current mouse position
|
||||
bool
|
||||
convert_mouse_wheel(const SDL_MouseWheelEvent *from, struct position position,
|
||||
struct control_msg *to);
|
||||
|
||||
#endif
|
||||
|
@ -212,17 +212,14 @@ clipboard_paste(struct controller *controller) {
|
||||
}
|
||||
|
||||
void
|
||||
input_manager_process_text_input(struct input_manager *im,
|
||||
input_manager_process_text_input(struct input_manager *input_manager,
|
||||
const SDL_TextInputEvent *event) {
|
||||
if (!im->prefer_text) {
|
||||
char c = event->text[0];
|
||||
if (isalpha(c) || c == ' ') {
|
||||
SDL_assert(event->text[1] == '\0');
|
||||
// letters and space are handled as raw key event
|
||||
return;
|
||||
}
|
||||
char c = event->text[0];
|
||||
if (isalpha(c) || c == ' ') {
|
||||
SDL_assert(event->text[1] == '\0');
|
||||
// letters and space are handled as raw key event
|
||||
return;
|
||||
}
|
||||
|
||||
struct control_msg msg;
|
||||
msg.type = CONTROL_MSG_TYPE_INJECT_TEXT;
|
||||
msg.inject_text.text = SDL_strdup(event->text);
|
||||
@ -230,34 +227,14 @@ input_manager_process_text_input(struct input_manager *im,
|
||||
LOGW("Could not strdup input text");
|
||||
return;
|
||||
}
|
||||
if (!controller_push_msg(im->controller, &msg)) {
|
||||
if (!controller_push_msg(input_manager->controller, &msg)) {
|
||||
SDL_free(msg.inject_text.text);
|
||||
LOGW("Could not request 'inject text'");
|
||||
}
|
||||
}
|
||||
|
||||
static bool
|
||||
convert_input_key(const SDL_KeyboardEvent *from, struct control_msg *to,
|
||||
bool prefer_text) {
|
||||
to->type = CONTROL_MSG_TYPE_INJECT_KEYCODE;
|
||||
|
||||
if (!convert_keycode_action(from->type, &to->inject_keycode.action)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint16_t mod = from->keysym.mod;
|
||||
if (!convert_keycode(from->keysym.sym, &to->inject_keycode.keycode, mod,
|
||||
prefer_text)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
to->inject_keycode.metastate = convert_meta_state(mod);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
input_manager_process_key(struct input_manager *im,
|
||||
input_manager_process_key(struct input_manager *input_manager,
|
||||
const SDL_KeyboardEvent *event,
|
||||
bool control) {
|
||||
// control: indicates the state of the command-line option --no-control
|
||||
@ -284,7 +261,7 @@ input_manager_process_key(struct input_manager *im,
|
||||
return;
|
||||
}
|
||||
|
||||
struct controller *controller = im->controller;
|
||||
struct controller *controller = input_manager->controller;
|
||||
|
||||
// capture all Ctrl events
|
||||
if (ctrl || cmd) {
|
||||
@ -359,23 +336,23 @@ input_manager_process_key(struct input_manager *im,
|
||||
return;
|
||||
case SDLK_f:
|
||||
if (!shift && cmd && !repeat && down) {
|
||||
screen_switch_fullscreen(im->screen);
|
||||
screen_switch_fullscreen(input_manager->screen);
|
||||
}
|
||||
return;
|
||||
case SDLK_x:
|
||||
if (!shift && cmd && !repeat && down) {
|
||||
screen_resize_to_fit(im->screen);
|
||||
screen_resize_to_fit(input_manager->screen);
|
||||
}
|
||||
return;
|
||||
case SDLK_g:
|
||||
if (!shift && cmd && !repeat && down) {
|
||||
screen_resize_to_pixel_perfect(im->screen);
|
||||
screen_resize_to_pixel_perfect(input_manager->screen);
|
||||
}
|
||||
return;
|
||||
case SDLK_i:
|
||||
if (!shift && cmd && !repeat && down) {
|
||||
struct fps_counter *fps_counter =
|
||||
im->video_buffer->fps_counter;
|
||||
input_manager->video_buffer->fps_counter;
|
||||
switch_fps_counter_state(fps_counter);
|
||||
}
|
||||
return;
|
||||
@ -398,30 +375,15 @@ input_manager_process_key(struct input_manager *im,
|
||||
}
|
||||
|
||||
struct control_msg msg;
|
||||
if (convert_input_key(event, &msg, im->prefer_text)) {
|
||||
if (convert_input_key(event, &msg)) {
|
||||
if (!controller_push_msg(controller, &msg)) {
|
||||
LOGW("Could not request 'inject keycode'");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static bool
|
||||
convert_mouse_motion(const SDL_MouseMotionEvent *from, struct screen *screen,
|
||||
struct control_msg *to) {
|
||||
to->type = CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT;
|
||||
to->inject_touch_event.action = AMOTION_EVENT_ACTION_MOVE;
|
||||
to->inject_touch_event.pointer_id = POINTER_ID_MOUSE;
|
||||
to->inject_touch_event.position.screen_size = screen->frame_size;
|
||||
to->inject_touch_event.position.point.x = from->x;
|
||||
to->inject_touch_event.position.point.y = from->y;
|
||||
to->inject_touch_event.pressure = 1.f;
|
||||
to->inject_touch_event.buttons = convert_mouse_buttons(from->state);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
input_manager_process_mouse_motion(struct input_manager *im,
|
||||
input_manager_process_mouse_motion(struct input_manager *input_manager,
|
||||
const SDL_MouseMotionEvent *event) {
|
||||
if (!event->state) {
|
||||
// do not send motion events when no button is pressed
|
||||
@ -432,74 +394,33 @@ input_manager_process_mouse_motion(struct input_manager *im,
|
||||
return;
|
||||
}
|
||||
struct control_msg msg;
|
||||
if (convert_mouse_motion(event, im->screen, &msg)) {
|
||||
if (!controller_push_msg(im->controller, &msg)) {
|
||||
if (convert_mouse_motion(event, input_manager->screen->frame_size, &msg)) {
|
||||
if (!controller_push_msg(input_manager->controller, &msg)) {
|
||||
LOGW("Could not request 'inject mouse motion event'");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static bool
|
||||
convert_touch(const SDL_TouchFingerEvent *from, struct screen *screen,
|
||||
struct control_msg *to) {
|
||||
to->type = CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT;
|
||||
|
||||
if (!convert_touch_action(from->type, &to->inject_touch_event.action)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
struct size frame_size = screen->frame_size;
|
||||
|
||||
to->inject_touch_event.pointer_id = from->fingerId;
|
||||
to->inject_touch_event.position.screen_size = frame_size;
|
||||
// SDL touch event coordinates are normalized in the range [0; 1]
|
||||
to->inject_touch_event.position.point.x = from->x * frame_size.width;
|
||||
to->inject_touch_event.position.point.y = from->y * frame_size.height;
|
||||
to->inject_touch_event.pressure = from->pressure;
|
||||
to->inject_touch_event.buttons = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
input_manager_process_touch(struct input_manager *im,
|
||||
input_manager_process_touch(struct input_manager *input_manager,
|
||||
const SDL_TouchFingerEvent *event) {
|
||||
struct control_msg msg;
|
||||
if (convert_touch(event, im->screen, &msg)) {
|
||||
if (!controller_push_msg(im->controller, &msg)) {
|
||||
if (convert_touch(event, input_manager->screen->frame_size, &msg)) {
|
||||
if (!controller_push_msg(input_manager->controller, &msg)) {
|
||||
LOGW("Could not request 'inject touch event'");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static bool
|
||||
is_outside_device_screen(struct input_manager *im, int x, int y)
|
||||
is_outside_device_screen(struct input_manager *input_manager, int x, int y)
|
||||
{
|
||||
return x < 0 || x >= im->screen->frame_size.width ||
|
||||
y < 0 || y >= im->screen->frame_size.height;
|
||||
}
|
||||
|
||||
static bool
|
||||
convert_mouse_button(const SDL_MouseButtonEvent *from, struct screen *screen,
|
||||
struct control_msg *to) {
|
||||
to->type = CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT;
|
||||
|
||||
if (!convert_mouse_action(from->type, &to->inject_touch_event.action)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
to->inject_touch_event.pointer_id = POINTER_ID_MOUSE;
|
||||
to->inject_touch_event.position.screen_size = screen->frame_size;
|
||||
to->inject_touch_event.position.point.x = from->x;
|
||||
to->inject_touch_event.position.point.y = from->y;
|
||||
to->inject_touch_event.pressure = 1.f;
|
||||
to->inject_touch_event.buttons =
|
||||
convert_mouse_buttons(SDL_BUTTON(from->button));
|
||||
|
||||
return true;
|
||||
return x < 0 || x >= input_manager->screen->frame_size.width ||
|
||||
y < 0 || y >= input_manager->screen->frame_size.height;
|
||||
}
|
||||
|
||||
void
|
||||
input_manager_process_mouse_button(struct input_manager *im,
|
||||
input_manager_process_mouse_button(struct input_manager *input_manager,
|
||||
const SDL_MouseButtonEvent *event,
|
||||
bool control) {
|
||||
if (event->which == SDL_TOUCH_MOUSEID) {
|
||||
@ -508,19 +429,19 @@ input_manager_process_mouse_button(struct input_manager *im,
|
||||
}
|
||||
if (event->type == SDL_MOUSEBUTTONDOWN) {
|
||||
if (control && event->button == SDL_BUTTON_RIGHT) {
|
||||
press_back_or_turn_screen_on(im->controller);
|
||||
press_back_or_turn_screen_on(input_manager->controller);
|
||||
return;
|
||||
}
|
||||
if (control && event->button == SDL_BUTTON_MIDDLE) {
|
||||
action_home(im->controller, ACTION_DOWN | ACTION_UP);
|
||||
action_home(input_manager->controller, ACTION_DOWN | ACTION_UP);
|
||||
return;
|
||||
}
|
||||
// double-click on black borders resize to fit the device screen
|
||||
if (event->button == SDL_BUTTON_LEFT && event->clicks == 2) {
|
||||
bool outside =
|
||||
is_outside_device_screen(im, event->x, event->y);
|
||||
is_outside_device_screen(input_manager, event->x, event->y);
|
||||
if (outside) {
|
||||
screen_resize_to_fit(im->screen);
|
||||
screen_resize_to_fit(input_manager->screen);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@ -532,41 +453,23 @@ input_manager_process_mouse_button(struct input_manager *im,
|
||||
}
|
||||
|
||||
struct control_msg msg;
|
||||
if (convert_mouse_button(event, im->screen, &msg)) {
|
||||
if (!controller_push_msg(im->controller, &msg)) {
|
||||
if (convert_mouse_button(event, input_manager->screen->frame_size, &msg)) {
|
||||
if (!controller_push_msg(input_manager->controller, &msg)) {
|
||||
LOGW("Could not request 'inject mouse button event'");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static bool
|
||||
convert_mouse_wheel(const SDL_MouseWheelEvent *from, struct screen *screen,
|
||||
struct control_msg *to) {
|
||||
struct position position = {
|
||||
.screen_size = screen->frame_size,
|
||||
.point = get_mouse_point(screen),
|
||||
};
|
||||
|
||||
to->type = CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT;
|
||||
|
||||
to->inject_scroll_event.position = position;
|
||||
|
||||
int mul = from->direction == SDL_MOUSEWHEEL_NORMAL ? 1 : -1;
|
||||
// SDL behavior seems inconsistent between horizontal and vertical scrolling
|
||||
// so reverse the horizontal
|
||||
// <https://wiki.libsdl.org/SDL_MouseWheelEvent#Remarks>
|
||||
to->inject_scroll_event.hscroll = -mul * from->x;
|
||||
to->inject_scroll_event.vscroll = mul * from->y;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
input_manager_process_mouse_wheel(struct input_manager *im,
|
||||
input_manager_process_mouse_wheel(struct input_manager *input_manager,
|
||||
const SDL_MouseWheelEvent *event) {
|
||||
struct position position = {
|
||||
.screen_size = input_manager->screen->frame_size,
|
||||
.point = get_mouse_point(input_manager->screen),
|
||||
};
|
||||
struct control_msg msg;
|
||||
if (convert_mouse_wheel(event, im->screen, &msg)) {
|
||||
if (!controller_push_msg(im->controller, &msg)) {
|
||||
if (convert_mouse_wheel(event, position, &msg)) {
|
||||
if (!controller_push_msg(input_manager->controller, &msg)) {
|
||||
LOGW("Could not request 'inject mouse wheel event'");
|
||||
}
|
||||
}
|
||||
|
@ -14,33 +14,32 @@ struct input_manager {
|
||||
struct controller *controller;
|
||||
struct video_buffer *video_buffer;
|
||||
struct screen *screen;
|
||||
bool prefer_text;
|
||||
};
|
||||
|
||||
void
|
||||
input_manager_process_text_input(struct input_manager *im,
|
||||
input_manager_process_text_input(struct input_manager *input_manager,
|
||||
const SDL_TextInputEvent *event);
|
||||
|
||||
void
|
||||
input_manager_process_key(struct input_manager *im,
|
||||
input_manager_process_key(struct input_manager *input_manager,
|
||||
const SDL_KeyboardEvent *event,
|
||||
bool control);
|
||||
|
||||
void
|
||||
input_manager_process_mouse_motion(struct input_manager *im,
|
||||
input_manager_process_mouse_motion(struct input_manager *input_manager,
|
||||
const SDL_MouseMotionEvent *event);
|
||||
|
||||
void
|
||||
input_manager_process_touch(struct input_manager *im,
|
||||
input_manager_process_touch(struct input_manager *input_manager,
|
||||
const SDL_TouchFingerEvent *event);
|
||||
|
||||
void
|
||||
input_manager_process_mouse_button(struct input_manager *im,
|
||||
input_manager_process_mouse_button(struct input_manager *input_manager,
|
||||
const SDL_MouseButtonEvent *event,
|
||||
bool control);
|
||||
|
||||
void
|
||||
input_manager_process_mouse_wheel(struct input_manager *im,
|
||||
input_manager_process_mouse_wheel(struct input_manager *input_manager,
|
||||
const SDL_MouseWheelEvent *event);
|
||||
|
||||
#endif
|
||||
|
257
app/src/main.c
257
app/src/main.c
@ -14,9 +14,29 @@
|
||||
#include "recorder.h"
|
||||
|
||||
struct args {
|
||||
struct scrcpy_options opts;
|
||||
const char *serial;
|
||||
const char *crop;
|
||||
const char *record_filename;
|
||||
const char *window_title;
|
||||
const char *push_target;
|
||||
enum recorder_format record_format;
|
||||
bool fullscreen;
|
||||
bool no_control;
|
||||
bool no_display;
|
||||
bool help;
|
||||
bool version;
|
||||
bool show_touches;
|
||||
uint16_t port;
|
||||
uint16_t max_size;
|
||||
uint32_t bit_rate;
|
||||
int16_t window_x;
|
||||
int16_t window_y;
|
||||
uint16_t window_width;
|
||||
uint16_t window_height;
|
||||
bool always_on_top;
|
||||
bool turn_screen_off;
|
||||
bool render_expired_frames;
|
||||
bool window_borderless;
|
||||
};
|
||||
|
||||
static void usage(const char *arg0) {
|
||||
@ -67,13 +87,6 @@ static void usage(const char *arg0) {
|
||||
" Set the TCP port the client listens on.\n"
|
||||
" Default is %d.\n"
|
||||
"\n"
|
||||
" --prefer-text\n"
|
||||
" Inject alpha characters and space as text events instead of\n"
|
||||
" key events.\n"
|
||||
" This avoids issues when combining multiple keys to enter a\n"
|
||||
" special character, but breaks the expected behavior of alpha\n"
|
||||
" keys in games (typically WASD).\n"
|
||||
"\n"
|
||||
" --push-target path\n"
|
||||
" Set the target directory for pushing files to the device by\n"
|
||||
" drag & drop. It is passed as-is to \"adb push\".\n"
|
||||
@ -107,9 +120,28 @@ static void usage(const char *arg0) {
|
||||
" -v, --version\n"
|
||||
" Print the version of scrcpy.\n"
|
||||
"\n"
|
||||
" --window_borderless\n"
|
||||
" Disable window decorations (display borderless window).\n"
|
||||
"\n"
|
||||
" --window-title text\n"
|
||||
" Set a custom window title.\n"
|
||||
"\n"
|
||||
" --window-x value\n"
|
||||
" Set the initial window horizontal position.\n"
|
||||
" Default is -1 (automatic).\n"
|
||||
"\n"
|
||||
" --window-y value\n"
|
||||
" Set the initial window vertical position.\n"
|
||||
" Default is -1 (automatic).\n"
|
||||
"\n"
|
||||
" --window-width value\n"
|
||||
" Set the initial window width.\n"
|
||||
" Default is -1 (automatic).\n"
|
||||
"\n"
|
||||
" --window-height value\n"
|
||||
" Set the initial window width.\n"
|
||||
" Default is -1 (automatic).\n"
|
||||
"\n"
|
||||
"Shortcuts:\n"
|
||||
"\n"
|
||||
" " CTRL_OR_CMD "+f\n"
|
||||
@ -250,6 +282,48 @@ parse_max_size(char *optarg, uint16_t *max_size) {
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
parse_window_position(char *optarg, int16_t *position) {
|
||||
char *endptr;
|
||||
if (*optarg == '\0') {
|
||||
LOGE("Window position parameter is empty");
|
||||
return false;
|
||||
}
|
||||
long value = strtol(optarg, &endptr, 0);
|
||||
if (*endptr != '\0') {
|
||||
LOGE("Invalid window position: %s", optarg);
|
||||
return false;
|
||||
}
|
||||
if (value < -1 || value > 0x7fff) {
|
||||
LOGE("Window position must be between -1 and 32767: %ld", value);
|
||||
return false;
|
||||
}
|
||||
|
||||
*position = (int16_t) value;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
parse_window_dimension(char *optarg, uint16_t *dimension) {
|
||||
char *endptr;
|
||||
if (*optarg == '\0') {
|
||||
LOGE("Window dimension parameter is empty");
|
||||
return false;
|
||||
}
|
||||
long value = strtol(optarg, &endptr, 0);
|
||||
if (*endptr != '\0') {
|
||||
LOGE("Invalid window dimension: %s", optarg);
|
||||
return false;
|
||||
}
|
||||
if (value & ~0xffff) {
|
||||
LOGE("Window position must be between 0 and 65535: %ld", value);
|
||||
return false;
|
||||
}
|
||||
|
||||
*dimension = (uint16_t) value;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
parse_port(char *optarg, uint16_t *port) {
|
||||
char *endptr;
|
||||
@ -302,65 +376,62 @@ guess_record_format(const char *filename) {
|
||||
}
|
||||
|
||||
#define OPT_RENDER_EXPIRED_FRAMES 1000
|
||||
#define OPT_WINDOW_TITLE 1001
|
||||
#define OPT_PUSH_TARGET 1002
|
||||
#define OPT_ALWAYS_ON_TOP 1003
|
||||
#define OPT_CROP 1004
|
||||
#define OPT_RECORD_FORMAT 1005
|
||||
#define OPT_PREFER_TEXT 1006
|
||||
#define OPT_PUSH_TARGET 1001
|
||||
#define OPT_WINDOW_TITLE 1002
|
||||
#define OPT_WINDOW_X 1003
|
||||
#define OPT_WINDOW_Y 1004
|
||||
#define OPT_WINDOW_WIDTH 1005
|
||||
#define OPT_WINDOW_HEIGHT 1006
|
||||
#define OPT_WINDOW_BORDERLESS 1007
|
||||
|
||||
static bool
|
||||
parse_args(struct args *args, int argc, char *argv[]) {
|
||||
static const struct option long_options[] = {
|
||||
{"always-on-top", no_argument, NULL, OPT_ALWAYS_ON_TOP},
|
||||
{"always-on-top", no_argument, NULL, 'T'},
|
||||
{"bit-rate", required_argument, NULL, 'b'},
|
||||
{"crop", required_argument, NULL, OPT_CROP},
|
||||
{"crop", required_argument, NULL, 'c'},
|
||||
{"fullscreen", no_argument, NULL, 'f'},
|
||||
{"help", no_argument, NULL, 'h'},
|
||||
{"max-size", required_argument, NULL, 'm'},
|
||||
{"no-control", no_argument, NULL, 'n'},
|
||||
{"no-display", no_argument, NULL, 'N'},
|
||||
{"window-borderless", no_argument, NULL,
|
||||
OPT_WINDOW_BORDERLESS},
|
||||
{"port", required_argument, NULL, 'p'},
|
||||
{"push-target", required_argument, NULL, OPT_PUSH_TARGET},
|
||||
{"push-target", required_argument, NULL,
|
||||
OPT_PUSH_TARGET},
|
||||
{"record", required_argument, NULL, 'r'},
|
||||
{"record-format", required_argument, NULL, OPT_RECORD_FORMAT},
|
||||
{"record-format", required_argument, NULL, 'F'},
|
||||
{"render-expired-frames", no_argument, NULL,
|
||||
OPT_RENDER_EXPIRED_FRAMES},
|
||||
OPT_RENDER_EXPIRED_FRAMES},
|
||||
{"serial", required_argument, NULL, 's'},
|
||||
{"show-touches", no_argument, NULL, 't'},
|
||||
{"turn-screen-off", no_argument, NULL, 'S'},
|
||||
{"prefer-text", no_argument, NULL, OPT_PREFER_TEXT},
|
||||
{"version", no_argument, NULL, 'v'},
|
||||
{"window-title", required_argument, NULL,
|
||||
OPT_WINDOW_TITLE},
|
||||
{"window-title", required_argument, NULL, OPT_WINDOW_TITLE},
|
||||
{"window-x", required_argument, NULL, OPT_WINDOW_X},
|
||||
{"window-y", required_argument, NULL, OPT_WINDOW_Y},
|
||||
{"window-width", required_argument, NULL, OPT_WINDOW_WIDTH},
|
||||
{"window-height", required_argument, NULL, OPT_WINDOW_HEIGHT},
|
||||
{NULL, 0, NULL, 0 },
|
||||
};
|
||||
|
||||
struct scrcpy_options *opts = &args->opts;
|
||||
|
||||
int c;
|
||||
while ((c = getopt_long(argc, argv, "b:c:fF:hm:nNp:r:s:StTv", long_options,
|
||||
NULL)) != -1) {
|
||||
switch (c) {
|
||||
case 'b':
|
||||
if (!parse_bit_rate(optarg, &opts->bit_rate)) {
|
||||
if (!parse_bit_rate(optarg, &args->bit_rate)) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case 'c':
|
||||
LOGW("Deprecated option -c. Use --crop instead.");
|
||||
// fall through
|
||||
case OPT_CROP:
|
||||
opts->crop = optarg;
|
||||
args->crop = optarg;
|
||||
break;
|
||||
case 'f':
|
||||
opts->fullscreen = true;
|
||||
args->fullscreen = true;
|
||||
break;
|
||||
case 'F':
|
||||
LOGW("Deprecated option -F. Use --record-format instead.");
|
||||
// fall through
|
||||
case OPT_RECORD_FORMAT:
|
||||
if (!parse_record_format(optarg, &opts->record_format)) {
|
||||
if (!parse_record_format(optarg, &args->record_format)) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
@ -368,53 +439,70 @@ parse_args(struct args *args, int argc, char *argv[]) {
|
||||
args->help = true;
|
||||
break;
|
||||
case 'm':
|
||||
if (!parse_max_size(optarg, &opts->max_size)) {
|
||||
if (!parse_max_size(optarg, &args->max_size)) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case 'n':
|
||||
opts->control = false;
|
||||
args->no_control = true;
|
||||
break;
|
||||
case 'N':
|
||||
opts->display = false;
|
||||
args->no_display = true;
|
||||
break;
|
||||
case 'p':
|
||||
if (!parse_port(optarg, &opts->port)) {
|
||||
if (!parse_port(optarg, &args->port)) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case 'r':
|
||||
opts->record_filename = optarg;
|
||||
args->record_filename = optarg;
|
||||
break;
|
||||
case 's':
|
||||
opts->serial = optarg;
|
||||
args->serial = optarg;
|
||||
break;
|
||||
case 'S':
|
||||
opts->turn_screen_off = true;
|
||||
args->turn_screen_off = true;
|
||||
break;
|
||||
case 't':
|
||||
opts->show_touches = true;
|
||||
args->show_touches = true;
|
||||
break;
|
||||
case 'T':
|
||||
LOGW("Deprecated option -T. Use --always-on-top instead.");
|
||||
// fall through
|
||||
case OPT_ALWAYS_ON_TOP:
|
||||
opts->always_on_top = true;
|
||||
args->always_on_top = true;
|
||||
break;
|
||||
case 'v':
|
||||
args->version = true;
|
||||
break;
|
||||
case OPT_RENDER_EXPIRED_FRAMES:
|
||||
opts->render_expired_frames = true;
|
||||
args->render_expired_frames = true;
|
||||
break;
|
||||
case OPT_WINDOW_TITLE:
|
||||
opts->window_title = optarg;
|
||||
args->window_title = optarg;
|
||||
break;
|
||||
case OPT_WINDOW_X:
|
||||
if (!parse_window_position(optarg, &args->window_x)) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case OPT_WINDOW_Y:
|
||||
if (!parse_window_position(optarg, &args->window_y)) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case OPT_WINDOW_WIDTH:
|
||||
if (!parse_window_dimension(optarg, &args->window_width)) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case OPT_WINDOW_HEIGHT:
|
||||
if (!parse_window_dimension(optarg, &args->window_height)) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case OPT_WINDOW_BORDERLESS:
|
||||
args->window_borderless = true;
|
||||
break;
|
||||
case OPT_PUSH_TARGET:
|
||||
opts->push_target = optarg;
|
||||
break;
|
||||
case OPT_PREFER_TEXT:
|
||||
opts->prefer_text = true;
|
||||
args->push_target = optarg;
|
||||
break;
|
||||
default:
|
||||
// getopt prints the error message on stderr
|
||||
@ -422,12 +510,12 @@ parse_args(struct args *args, int argc, char *argv[]) {
|
||||
}
|
||||
}
|
||||
|
||||
if (!opts->display && !opts->record_filename) {
|
||||
if (args->no_display && !args->record_filename) {
|
||||
LOGE("-N/--no-display requires screen recording (-r/--record)");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!opts->display && opts->fullscreen) {
|
||||
if (args->no_display && args->fullscreen) {
|
||||
LOGE("-f/--fullscreen-window is incompatible with -N/--no-display");
|
||||
return false;
|
||||
}
|
||||
@ -438,21 +526,21 @@ parse_args(struct args *args, int argc, char *argv[]) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (opts->record_format && !opts->record_filename) {
|
||||
if (args->record_format && !args->record_filename) {
|
||||
LOGE("Record format specified without recording");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (opts->record_filename && !opts->record_format) {
|
||||
opts->record_format = guess_record_format(opts->record_filename);
|
||||
if (!opts->record_format) {
|
||||
if (args->record_filename && !args->record_format) {
|
||||
args->record_format = guess_record_format(args->record_filename);
|
||||
if (!args->record_format) {
|
||||
LOGE("No format specified for \"%s\" (try with -F mkv)",
|
||||
opts->record_filename);
|
||||
args->record_filename);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!opts->control && opts->turn_screen_off) {
|
||||
if (args->no_control && args->turn_screen_off) {
|
||||
LOGE("Could not request to turn screen off if control is disabled");
|
||||
return false;
|
||||
}
|
||||
@ -469,11 +557,29 @@ main(int argc, char *argv[]) {
|
||||
setbuf(stderr, NULL);
|
||||
#endif
|
||||
struct args args = {
|
||||
.opts = SCRCPY_OPTIONS_DEFAULT,
|
||||
.serial = NULL,
|
||||
.crop = NULL,
|
||||
.record_filename = NULL,
|
||||
.window_title = NULL,
|
||||
.push_target = NULL,
|
||||
.record_format = 0,
|
||||
.help = false,
|
||||
.version = false,
|
||||
.show_touches = false,
|
||||
.port = DEFAULT_LOCAL_PORT,
|
||||
.max_size = DEFAULT_MAX_SIZE,
|
||||
.bit_rate = DEFAULT_BIT_RATE,
|
||||
.window_x = -1,
|
||||
.window_y = -1,
|
||||
.window_width = 0,
|
||||
.window_height = 0,
|
||||
.always_on_top = false,
|
||||
.no_control = false,
|
||||
.no_display = false,
|
||||
.turn_screen_off = false,
|
||||
.render_expired_frames = false,
|
||||
.window_borderless = false,
|
||||
};
|
||||
|
||||
if (!parse_args(&args, argc, argv)) {
|
||||
return 1;
|
||||
}
|
||||
@ -502,7 +608,30 @@ main(int argc, char *argv[]) {
|
||||
SDL_LogSetAllPriority(SDL_LOG_PRIORITY_DEBUG);
|
||||
#endif
|
||||
|
||||
int res = scrcpy(&args.opts) ? 0 : 1;
|
||||
struct scrcpy_options options = {
|
||||
.serial = args.serial,
|
||||
.crop = args.crop,
|
||||
.port = args.port,
|
||||
.record_filename = args.record_filename,
|
||||
.window_title = args.window_title,
|
||||
.push_target = args.push_target,
|
||||
.record_format = args.record_format,
|
||||
.max_size = args.max_size,
|
||||
.bit_rate = args.bit_rate,
|
||||
.window_x = args.window_x,
|
||||
.window_y = args.window_y,
|
||||
.window_width = args.window_width,
|
||||
.window_height = args.window_height,
|
||||
.show_touches = args.show_touches,
|
||||
.fullscreen = args.fullscreen,
|
||||
.always_on_top = args.always_on_top,
|
||||
.control = !args.no_control,
|
||||
.display = !args.no_display,
|
||||
.turn_screen_off = args.turn_screen_off,
|
||||
.render_expired_frames = args.render_expired_frames,
|
||||
.window_borderless = args.window_borderless,
|
||||
};
|
||||
int res = scrcpy(&options) ? 0 : 1;
|
||||
|
||||
avformat_network_deinit(); // ignore failure
|
||||
|
||||
|
@ -174,14 +174,9 @@ recorder_open(struct recorder *recorder, const AVCodec *input_codec) {
|
||||
|
||||
void
|
||||
recorder_close(struct recorder *recorder) {
|
||||
if (recorder->header_written) {
|
||||
int ret = av_write_trailer(recorder->ctx);
|
||||
if (ret < 0) {
|
||||
LOGE("Failed to write trailer to %s", recorder->filename);
|
||||
recorder->failed = true;
|
||||
}
|
||||
} else {
|
||||
// the recorded file is empty
|
||||
int ret = av_write_trailer(recorder->ctx);
|
||||
if (ret < 0) {
|
||||
LOGE("Failed to write trailer to %s", recorder->filename);
|
||||
recorder->failed = true;
|
||||
}
|
||||
avio_close(recorder->ctx->pb);
|
||||
|
@ -11,8 +11,7 @@
|
||||
#include "queue.h"
|
||||
|
||||
enum recorder_format {
|
||||
RECORDER_FORMAT_AUTO,
|
||||
RECORDER_FORMAT_MP4,
|
||||
RECORDER_FORMAT_MP4 = 1,
|
||||
RECORDER_FORMAT_MKV,
|
||||
};
|
||||
|
||||
|
@ -42,7 +42,6 @@ static struct input_manager input_manager = {
|
||||
.controller = &controller,
|
||||
.video_buffer = &video_buffer,
|
||||
.screen = &screen,
|
||||
.prefer_text = false, // initialized later
|
||||
};
|
||||
|
||||
// init SDL and set appropriate hints
|
||||
@ -144,7 +143,12 @@ handle_event(SDL_Event *event, bool control) {
|
||||
}
|
||||
break;
|
||||
case SDL_WINDOWEVENT:
|
||||
screen_handle_window_event(&screen, &event->window);
|
||||
switch (event->window.event) {
|
||||
case SDL_WINDOWEVENT_EXPOSED:
|
||||
case SDL_WINDOWEVENT_SIZE_CHANGED:
|
||||
screen_render(&screen);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case SDL_TEXTINPUT:
|
||||
if (!control) {
|
||||
@ -213,7 +217,6 @@ event_loop(bool display, bool control) {
|
||||
case EVENT_RESULT_STOPPED_BY_USER:
|
||||
return true;
|
||||
case EVENT_RESULT_STOPPED_BY_EOS:
|
||||
LOGW("Device disconnected");
|
||||
return false;
|
||||
case EVENT_RESULT_CONTINUE:
|
||||
break;
|
||||
@ -387,7 +390,10 @@ scrcpy(const struct scrcpy_options *options) {
|
||||
options->window_title ? options->window_title : device_name;
|
||||
|
||||
if (!screen_init_rendering(&screen, window_title, frame_size,
|
||||
options->always_on_top)) {
|
||||
options->always_on_top, options->window_x,
|
||||
options->window_y, options->window_width,
|
||||
options->window_height,
|
||||
options->window_borderless)) {
|
||||
goto end;
|
||||
}
|
||||
|
||||
@ -411,8 +417,6 @@ scrcpy(const struct scrcpy_options *options) {
|
||||
show_touches_waited = true;
|
||||
}
|
||||
|
||||
input_manager.prefer_text = options->prefer_text;
|
||||
|
||||
ret = event_loop(options->display, options->control);
|
||||
LOGD("quit...");
|
||||
|
||||
|
@ -3,10 +3,9 @@
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <recorder.h>
|
||||
|
||||
#include "config.h"
|
||||
#include "input_manager.h"
|
||||
#include "recorder.h"
|
||||
|
||||
struct scrcpy_options {
|
||||
const char *serial;
|
||||
@ -18,6 +17,10 @@ struct scrcpy_options {
|
||||
uint16_t port;
|
||||
uint16_t max_size;
|
||||
uint32_t bit_rate;
|
||||
int16_t window_x;
|
||||
int16_t window_y;
|
||||
uint16_t window_width;
|
||||
uint16_t window_height;
|
||||
bool show_touches;
|
||||
bool fullscreen;
|
||||
bool always_on_top;
|
||||
@ -25,29 +28,9 @@ struct scrcpy_options {
|
||||
bool display;
|
||||
bool turn_screen_off;
|
||||
bool render_expired_frames;
|
||||
bool prefer_text;
|
||||
bool window_borderless;
|
||||
};
|
||||
|
||||
#define SCRCPY_OPTIONS_DEFAULT { \
|
||||
.serial = NULL, \
|
||||
.crop = NULL, \
|
||||
.record_filename = NULL, \
|
||||
.window_title = NULL, \
|
||||
.push_target = NULL, \
|
||||
.record_format = RECORDER_FORMAT_AUTO, \
|
||||
.port = DEFAULT_LOCAL_PORT, \
|
||||
.max_size = DEFAULT_LOCAL_PORT, \
|
||||
.bit_rate = DEFAULT_BIT_RATE, \
|
||||
.show_touches = false, \
|
||||
.fullscreen = false, \
|
||||
.always_on_top = false, \
|
||||
.control = true, \
|
||||
.display = true, \
|
||||
.turn_screen_off = false, \
|
||||
.render_expired_frames = false, \
|
||||
.prefer_text = false, \
|
||||
}
|
||||
|
||||
bool
|
||||
scrcpy(const struct scrcpy_options *options);
|
||||
|
||||
|
146
app/src/screen.c
146
app/src/screen.c
@ -30,28 +30,23 @@ get_window_size(SDL_Window *window) {
|
||||
// get the windowed window size
|
||||
static struct size
|
||||
get_windowed_window_size(const struct screen *screen) {
|
||||
if (screen->fullscreen || screen->maximized) {
|
||||
if (screen->fullscreen) {
|
||||
return screen->windowed_window_size;
|
||||
}
|
||||
return get_window_size(screen->window);
|
||||
}
|
||||
|
||||
// apply the windowed window size if fullscreen and maximized are disabled
|
||||
static void
|
||||
apply_windowed_size(struct screen *screen) {
|
||||
if (!screen->fullscreen && !screen->maximized) {
|
||||
SDL_SetWindowSize(screen->window, screen->windowed_window_size.width,
|
||||
screen->windowed_window_size.height);
|
||||
}
|
||||
}
|
||||
|
||||
// set the window size to be applied when fullscreen is disabled
|
||||
static void
|
||||
set_window_size(struct screen *screen, struct size new_size) {
|
||||
// setting the window size during fullscreen is implementation defined,
|
||||
// so apply the resize only after fullscreen is disabled
|
||||
screen->windowed_window_size = new_size;
|
||||
apply_windowed_size(screen);
|
||||
if (screen->fullscreen) {
|
||||
// SDL_SetWindowSize will be called when fullscreen will be disabled
|
||||
screen->windowed_window_size = new_size;
|
||||
} else {
|
||||
SDL_SetWindowSize(screen->window, new_size.width, new_size.height);
|
||||
}
|
||||
}
|
||||
|
||||
// get the preferred display bounds (i.e. the screen bounds with some margins)
|
||||
@ -122,9 +117,30 @@ get_optimal_window_size(const struct screen *screen, struct size frame_size) {
|
||||
}
|
||||
|
||||
// initially, there is no current size, so use the frame size as current size
|
||||
// req_width and req_height, if not 0, are the sizes requested by the user
|
||||
static inline struct size
|
||||
get_initial_optimal_size(struct size frame_size) {
|
||||
return get_optimal_size(frame_size, frame_size);
|
||||
get_initial_optimal_size(struct size frame_size, uint16_t req_width,
|
||||
uint16_t req_height) {
|
||||
struct size window_size;
|
||||
if (!req_width && !req_height) {
|
||||
window_size = get_optimal_size(frame_size, frame_size);
|
||||
} else {
|
||||
if (req_width) {
|
||||
window_size.width = req_width;
|
||||
} else {
|
||||
// compute from the requested height
|
||||
window_size.width = (uint32_t) req_height * frame_size.width
|
||||
/ frame_size.height;
|
||||
}
|
||||
if (req_height) {
|
||||
window_size.height = req_height;
|
||||
} else {
|
||||
// compute from the requested width
|
||||
window_size.height = (uint32_t) req_width * frame_size.height
|
||||
/ frame_size.width;
|
||||
}
|
||||
}
|
||||
return window_size;
|
||||
}
|
||||
|
||||
void
|
||||
@ -141,10 +157,13 @@ create_texture(SDL_Renderer *renderer, struct size frame_size) {
|
||||
|
||||
bool
|
||||
screen_init_rendering(struct screen *screen, const char *window_title,
|
||||
struct size frame_size, bool always_on_top) {
|
||||
struct size frame_size, bool always_on_top,
|
||||
int16_t window_x, int16_t window_y, uint16_t window_width,
|
||||
uint16_t window_height, bool window_borderless) {
|
||||
screen->frame_size = frame_size;
|
||||
|
||||
struct size window_size = get_initial_optimal_size(frame_size);
|
||||
struct size window_size =
|
||||
get_initial_optimal_size(frame_size, window_width, window_height);
|
||||
uint32_t window_flags = SDL_WINDOW_HIDDEN | SDL_WINDOW_RESIZABLE;
|
||||
#ifdef HIDPI_SUPPORT
|
||||
window_flags |= SDL_WINDOW_ALLOW_HIGHDPI;
|
||||
@ -157,9 +176,13 @@ screen_init_rendering(struct screen *screen, const char *window_title,
|
||||
"(compile with SDL >= 2.0.5 to enable it)");
|
||||
#endif
|
||||
}
|
||||
if (window_borderless) {
|
||||
window_flags |= SDL_WINDOW_BORDERLESS;
|
||||
}
|
||||
|
||||
screen->window = SDL_CreateWindow(window_title, SDL_WINDOWPOS_UNDEFINED,
|
||||
SDL_WINDOWPOS_UNDEFINED,
|
||||
int x = window_x != -1 ? window_x : SDL_WINDOWPOS_UNDEFINED;
|
||||
int y = window_y != -1 ? window_y : SDL_WINDOWPOS_UNDEFINED;
|
||||
screen->window = SDL_CreateWindow(window_title, x, y,
|
||||
window_size.width, window_size.height,
|
||||
window_flags);
|
||||
if (!screen->window) {
|
||||
@ -199,8 +222,6 @@ screen_init_rendering(struct screen *screen, const char *window_title,
|
||||
return false;
|
||||
}
|
||||
|
||||
screen->windowed_window_size = window_size;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -294,6 +315,10 @@ screen_render(struct screen *screen) {
|
||||
|
||||
void
|
||||
screen_switch_fullscreen(struct screen *screen) {
|
||||
if (!screen->fullscreen) {
|
||||
// going to fullscreen, store the current windowed window size
|
||||
screen->windowed_window_size = get_window_size(screen->window);
|
||||
}
|
||||
uint32_t new_mode = screen->fullscreen ? 0 : SDL_WINDOW_FULLSCREEN_DESKTOP;
|
||||
if (SDL_SetWindowFullscreen(screen->window, new_mode)) {
|
||||
LOGW("Could not switch fullscreen mode: %s", SDL_GetError());
|
||||
@ -301,7 +326,11 @@ screen_switch_fullscreen(struct screen *screen) {
|
||||
}
|
||||
|
||||
screen->fullscreen = !screen->fullscreen;
|
||||
apply_windowed_size(screen);
|
||||
if (!screen->fullscreen) {
|
||||
// fullscreen disabled, restore expected windowed window size
|
||||
SDL_SetWindowSize(screen->window, screen->windowed_window_size.width,
|
||||
screen->windowed_window_size.height);
|
||||
}
|
||||
|
||||
LOGD("Switched to %s mode", screen->fullscreen ? "fullscreen" : "windowed");
|
||||
screen_render(screen);
|
||||
@ -309,75 +338,20 @@ screen_switch_fullscreen(struct screen *screen) {
|
||||
|
||||
void
|
||||
screen_resize_to_fit(struct screen *screen) {
|
||||
if (screen->fullscreen) {
|
||||
return;
|
||||
if (!screen->fullscreen) {
|
||||
struct size optimal_size = get_optimal_window_size(screen,
|
||||
screen->frame_size);
|
||||
SDL_SetWindowSize(screen->window, optimal_size.width,
|
||||
optimal_size.height);
|
||||
LOGD("Resized to optimal size");
|
||||
}
|
||||
|
||||
if (screen->maximized) {
|
||||
SDL_RestoreWindow(screen->window);
|
||||
screen->maximized = false;
|
||||
}
|
||||
|
||||
struct size optimal_size =
|
||||
get_optimal_window_size(screen, screen->frame_size);
|
||||
SDL_SetWindowSize(screen->window, optimal_size.width, optimal_size.height);
|
||||
LOGD("Resized to optimal size");
|
||||
}
|
||||
|
||||
void
|
||||
screen_resize_to_pixel_perfect(struct screen *screen) {
|
||||
if (screen->fullscreen) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (screen->maximized) {
|
||||
SDL_RestoreWindow(screen->window);
|
||||
screen->maximized = false;
|
||||
}
|
||||
|
||||
SDL_SetWindowSize(screen->window, screen->frame_size.width,
|
||||
screen->frame_size.height);
|
||||
LOGD("Resized to pixel-perfect");
|
||||
}
|
||||
|
||||
void
|
||||
screen_handle_window_event(struct screen *screen,
|
||||
const SDL_WindowEvent *event) {
|
||||
switch (event->event) {
|
||||
case SDL_WINDOWEVENT_EXPOSED:
|
||||
screen_render(screen);
|
||||
break;
|
||||
case SDL_WINDOWEVENT_SIZE_CHANGED:
|
||||
if (!screen->fullscreen && !screen->maximized) {
|
||||
// Backup the previous size: if we receive the MAXIMIZED event,
|
||||
// then the new size must be ignored (it's the maximized size).
|
||||
// We could not rely on the window flags due to race conditions
|
||||
// (they could be updated asynchronously, at least on X11).
|
||||
screen->windowed_window_size_backup =
|
||||
screen->windowed_window_size;
|
||||
|
||||
// Save the windowed size, so that it is available once the
|
||||
// window is maximized or fullscreen is enabled.
|
||||
screen->windowed_window_size = get_window_size(screen->window);
|
||||
}
|
||||
screen_render(screen);
|
||||
break;
|
||||
case SDL_WINDOWEVENT_MAXIMIZED:
|
||||
// The backup size must be non-nul.
|
||||
SDL_assert(screen->windowed_window_size_backup.width);
|
||||
SDL_assert(screen->windowed_window_size_backup.height);
|
||||
// Revert the last size, it was updated while screen was maximized.
|
||||
screen->windowed_window_size = screen->windowed_window_size_backup;
|
||||
#ifdef DEBUG
|
||||
// Reset the backup to invalid values to detect unexpected usage
|
||||
screen->windowed_window_size_backup.width = 0;
|
||||
screen->windowed_window_size_backup.height = 0;
|
||||
#endif
|
||||
screen->maximized = true;
|
||||
break;
|
||||
case SDL_WINDOWEVENT_RESTORED:
|
||||
screen->maximized = false;
|
||||
apply_windowed_size(screen);
|
||||
break;
|
||||
if (!screen->fullscreen) {
|
||||
SDL_SetWindowSize(screen->window, screen->frame_size.width,
|
||||
screen->frame_size.height);
|
||||
LOGD("Resized to pixel-perfect");
|
||||
}
|
||||
}
|
||||
|
@ -15,37 +15,28 @@ struct screen {
|
||||
SDL_Renderer *renderer;
|
||||
SDL_Texture *texture;
|
||||
struct size frame_size;
|
||||
// The window size the last time it was not maximized or fullscreen.
|
||||
//used only in fullscreen mode to know the windowed window size
|
||||
struct size windowed_window_size;
|
||||
// Since we receive the event SIZE_CHANGED before MAXIMIZED, we must be
|
||||
// able to revert the size to its non-maximized value.
|
||||
struct size windowed_window_size_backup;
|
||||
bool has_frame;
|
||||
bool fullscreen;
|
||||
bool maximized;
|
||||
bool no_window;
|
||||
};
|
||||
|
||||
#define SCREEN_INITIALIZER { \
|
||||
.window = NULL, \
|
||||
.renderer = NULL, \
|
||||
.texture = NULL, \
|
||||
.frame_size = { \
|
||||
.width = 0, \
|
||||
.height = 0, \
|
||||
}, \
|
||||
#define SCREEN_INITIALIZER { \
|
||||
.window = NULL, \
|
||||
.renderer = NULL, \
|
||||
.texture = NULL, \
|
||||
.frame_size = { \
|
||||
.width = 0, \
|
||||
.height = 0, \
|
||||
}, \
|
||||
.windowed_window_size = { \
|
||||
.width = 0, \
|
||||
.height = 0, \
|
||||
}, \
|
||||
.windowed_window_size_backup = { \
|
||||
.width = 0, \
|
||||
.height = 0, \
|
||||
}, \
|
||||
.has_frame = false, \
|
||||
.fullscreen = false, \
|
||||
.maximized = false, \
|
||||
.no_window = false, \
|
||||
.width = 0, \
|
||||
.height = 0, \
|
||||
}, \
|
||||
.has_frame = false, \
|
||||
.fullscreen = false, \
|
||||
.no_window = false, \
|
||||
}
|
||||
|
||||
// initialize default values
|
||||
@ -55,7 +46,9 @@ screen_init(struct screen *screen);
|
||||
// initialize screen, create window, renderer and texture (window is hidden)
|
||||
bool
|
||||
screen_init_rendering(struct screen *screen, const char *window_title,
|
||||
struct size frame_size, bool always_on_top);
|
||||
struct size frame_size, bool always_on_top,
|
||||
int16_t window_x, int16_t window_y, uint16_t window_width,
|
||||
uint16_t window_height, bool window_borderless);
|
||||
|
||||
// show the window
|
||||
void
|
||||
@ -85,8 +78,4 @@ screen_resize_to_fit(struct screen *screen);
|
||||
void
|
||||
screen_resize_to_pixel_perfect(struct screen *screen);
|
||||
|
||||
// react to window events
|
||||
void
|
||||
screen_handle_window_event(struct screen *screen, const SDL_WindowEvent *event);
|
||||
|
||||
#endif
|
||||
|
@ -124,11 +124,6 @@ execute_server(struct server *server, const struct server_params *params) {
|
||||
"shell",
|
||||
"CLASSPATH=/data/local/tmp/" SERVER_FILENAME,
|
||||
"app_process",
|
||||
#ifdef SERVER_DEBUGGER
|
||||
# define SERVER_DEBUGGER_PORT "5005"
|
||||
"-agentlib:jdwp=transport=dt_socket,suspend=y,server=y,address="
|
||||
SERVER_DEBUGGER_PORT,
|
||||
#endif
|
||||
"/", // unused
|
||||
"com.genymobile.scrcpy.Server",
|
||||
max_size_string,
|
||||
@ -138,17 +133,6 @@ execute_server(struct server *server, const struct server_params *params) {
|
||||
"true", // always send frame meta (packet boundaries + timestamp)
|
||||
params->control ? "true" : "false",
|
||||
};
|
||||
#ifdef SERVER_DEBUGGER
|
||||
LOGI("Server debugger waiting for a client on device port "
|
||||
SERVER_DEBUGGER_PORT "...");
|
||||
// From the computer, run
|
||||
// adb forward tcp:5005 tcp:5005
|
||||
// Then, from Android Studio: Run > Debug > Edit configurations...
|
||||
// On the left, click on '+', "Remote", with:
|
||||
// Host: localhost
|
||||
// Port: 5005
|
||||
// Then click on "Debug"
|
||||
#endif
|
||||
return adb_execute(server->serial, cmd, sizeof(cmd) / sizeof(cmd[0]));
|
||||
}
|
||||
|
||||
|
@ -5,4 +5,3 @@ option('windows_noconsole', type: 'boolean', value: false, description: 'Disable
|
||||
option('prebuilt_server', type: 'string', description: 'Path of the prebuilt server')
|
||||
option('portable', type: 'boolean', value: false, description: 'Use scrcpy-server from the same directory as the scrcpy executable')
|
||||
option('hidpi_support', type: 'boolean', value: true, description: 'Enable High DPI support')
|
||||
option('server_debugger', type: 'boolean', value: false, description: 'Run a server debugger and wait for a client to be attached')
|
||||
|
@ -7,7 +7,6 @@ import android.media.MediaCodec;
|
||||
import android.media.MediaCodecInfo;
|
||||
import android.media.MediaFormat;
|
||||
import android.os.IBinder;
|
||||
import android.os.Looper;
|
||||
import android.view.Surface;
|
||||
|
||||
import java.io.FileDescriptor;
|
||||
@ -55,16 +54,6 @@ public class ScreenEncoder implements Device.RotationListener {
|
||||
}
|
||||
|
||||
public void streamScreen(Device device, FileDescriptor fd) throws IOException {
|
||||
// Some devices internally create a Handler when creating an input Surface, causing an exception:
|
||||
// "Can't create handler inside thread that has not called Looper.prepare()"
|
||||
// <https://github.com/Genymobile/scrcpy/issues/240>
|
||||
//
|
||||
// Use Looper.prepareMainLooper() instead of Looper.prepare() to avoid a NullPointerException:
|
||||
// "Attempt to read from field 'android.os.MessageQueue android.os.Looper.mQueue'
|
||||
// on a null object reference"
|
||||
// <https://github.com/Genymobile/scrcpy/issues/921>
|
||||
Looper.prepareMainLooper();
|
||||
|
||||
MediaFormat format = createFormat(bitRate, frameRate, iFrameInterval);
|
||||
device.setRotationListener(this);
|
||||
boolean alive;
|
||||
|
Reference in New Issue
Block a user