Compare commits

..

15 Commits

Author SHA1 Message Date
Romain Vimont
b0ead07f61 fix issue 15? 2020-04-24 22:52:13 +02:00
Romain Vimont
8581d6850b Stabilize auto-resize
The window dimensions are integers, so resizing to fit the content may
not be exact.

When computing the optimal size, it could cause to reduce alternatively
the width and height by few pixels, making the "optimal size" unstable.

To avoid this problem, check if the optimal size is already correct
either by keeping the width or the height.
2020-04-24 22:52:02 +02:00
Romain Vimont
92cb3a6661 Improve resizing workaround
Call the same method as when the event is received on the event loop, so
that the behavior is the same in both cases.
2020-04-24 21:36:25 +02:00
Romain Vimont
3c9ae99dda Move rotation coordinates to screen
Move the window-to-frame coordinates conversion from the input manager
to the screen.

This will allow to apply more screen-related transformations without
impacting the input manager.
2020-04-18 02:15:22 +02:00
Romain Vimont
44f720e4a4 Log new size on auto-resize request
On "resize to fit" and "resize to pixel-perfect", log the new size.
2020-04-18 02:15:22 +02:00
Tzah Mazuz
125c5561e8 Use MediaFormat constant for MIME type
Replace "video/avc" by MIMETYPE_VIDEO_AVC.

Signed-off-by: Romain Vimont <rom@rom1v.com>
2020-04-18 02:14:42 +02:00
Romain Vimont
14ead499fd Fix touch coordinates on rotated display
The touch coordinates were not rotated.
2020-04-17 18:17:12 +02:00
Romain Vimont
94a7f1a0f8 Disable input events when necessary
Disable input events on secondary displays before Android 10, even if
FLAG_PRESENTATION is not set.

Refs #1288 <https://github.com/Genymobile/scrcpy/issues/1288>
2020-04-16 20:54:00 +02:00
Romain Vimont
cc22f4622a Mention mipmapping in FAQ 2020-04-15 17:39:51 +02:00
Romain Vimont
11a61b2cb3 Add option --no-mipmaps
Add an option to disable trilinear filtering even if mipmapping is
available.
2020-04-15 17:39:51 +02:00
Romain Vimont
bea7658807 Enable trilinear filtering for OpenGL
Improve downscaling quality if mipmapping is available.

Suggested-by: Giumo Clanjor (哆啦比猫/兰威举) <cjxgm2@gmail.com>

Fixes #40 <https://github.com/Genymobile/scrcpy/issues/40>
Ref: <https://github.com/Genymobile/scrcpy/issues/40#issuecomment-591917787>
2020-04-15 17:39:51 +02:00
Romain Vimont
8a9b20b27e Add --render-driver command-line option
Add an option to set a render driver hint (SDL_HINT_RENDER_DRIVER).
2020-04-15 17:39:51 +02:00
Romain Vimont
d62eb2b11c Fix typo in README 2020-04-15 09:57:59 +02:00
Romain Vimont
eb8f7a1f28 Require Meson 0.48 to get rid of warnings
Debian buster (stable) provides Meson 0.49, which is also available in
stretch (oldstable) backports. It's time to abandon Meson 0.37.

Ref: 20b3f101a4
2020-04-13 22:47:03 +02:00
Romain Vimont
270d0bf639 Rename max length constant for text injection
To avoid confusion with the max text size for clipboard, rename the
constant limiting the text injection length.
2020-04-13 19:38:43 +02:00
17 changed files with 97 additions and 74 deletions

4
FAQ.md
View File

@@ -142,13 +142,13 @@ screen, then you might get poor quality, especially visible on text (see [#40]).
To improve downscaling quality, trilinear filtering is enabled automatically
if the renderer is OpenGL and if it supports mipmapping.
On Windows or macOS, you might need to force OpenGL:
On Windows, you might want to force OpenGL:
```
scrcpy --render-driver=opengl
```
On Windows, you may also need to configure the [scaling behavior].
You may also need to configure the [scaling behavior]:
> `scrcpy.exe` > Properties > Compatibility > Change high DPI settings >
> Override high DPI scaling behavior > Scaling performed by: _Application_.

View File

@@ -363,7 +363,7 @@ Note that _scrcpy_ manages 3 different rotations:
current running app may refuse, if it does support the requested
orientation).
- `--lock-video-orientation` changes the mirroring orientation (the orientation
of the video sent from the device to the computer. This affects the
of the video sent from the device to the computer). This affects the
recording.
- `--rotation` (or `Ctrl`+`←`/`Ctrl`+`→`) rotates only the window content. This
affects only the display, not the recording.

View File

@@ -45,8 +45,9 @@ control_msg_serialize(const struct control_msg *msg, unsigned char *buf) {
buffer_write32be(&buf[6], msg->inject_keycode.metastate);
return 10;
case CONTROL_MSG_TYPE_INJECT_TEXT: {
size_t len = write_string(msg->inject_text.text,
CONTROL_MSG_TEXT_MAX_LENGTH, &buf[1]);
size_t len =
write_string(msg->inject_text.text,
CONTROL_MSG_INJECT_TEXT_MAX_LENGTH, &buf[1]);
return 1 + len;
}
case CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT:

View File

@@ -10,7 +10,7 @@
#include "android/keycodes.h"
#include "common.h"
#define CONTROL_MSG_TEXT_MAX_LENGTH 300
#define CONTROL_MSG_INJECT_TEXT_MAX_LENGTH 300
#define CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH 4093
#define CONTROL_MSG_SERIALIZED_MAX_SIZE \
(3 + CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH)

View File

@@ -442,36 +442,6 @@ input_manager_process_key(struct input_manager *im,
}
}
static struct point
rotate_position(struct screen *screen, int32_t x, int32_t y) {
unsigned rotation = screen->rotation;
assert(rotation < 4);
int32_t w = screen->content_size.width;
int32_t h = screen->content_size.height;
struct point result;
switch (rotation) {
case 0:
result.x = x;
result.y = y;
break;
case 1:
result.x = h - y;
result.y = x;
break;
case 2:
result.x = w - x;
result.y = h - y;
break;
default:
assert(rotation == 3);
result.x = y;
result.y = w - x;
break;
}
return result;
}
static bool
convert_mouse_motion(const SDL_MouseMotionEvent *from, struct screen *screen,
struct control_msg *to) {
@@ -480,7 +450,7 @@ convert_mouse_motion(const SDL_MouseMotionEvent *from, struct screen *screen,
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 =
rotate_position(screen, from->x, from->y);
screen_convert_to_frame_coords(screen, from->x, from->y);
to->inject_touch_event.pressure = 1.f;
to->inject_touch_event.buttons = convert_mouse_buttons(from->state);
@@ -515,14 +485,13 @@ convert_touch(const SDL_TouchFingerEvent *from, struct screen *screen,
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;
to->inject_touch_event.position.screen_size = screen->frame_size;
// SDL touch event coordinates are normalized in the range [0; 1]
float x = from->x * frame_size.width;
float y = from->y * frame_size.height;
to->inject_touch_event.position.point = rotate_position(screen, x, y);
float x = from->x * screen->content_size.width;
float y = from->y * screen->content_size.height;
to->inject_touch_event.position.point =
screen_convert_to_frame_coords(screen, x, y);
to->inject_touch_event.pressure = from->pressure;
to->inject_touch_event.buttons = 0;
return true;
@@ -558,7 +527,7 @@ convert_mouse_button(const SDL_MouseButtonEvent *from, struct screen *screen,
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 =
rotate_position(screen, from->x, from->y);
screen_convert_to_frame_coords(screen, from->x, from->y);
to->inject_touch_event.pressure = 1.f;
to->inject_touch_event.buttons =
convert_mouse_buttons(SDL_BUTTON(from->button));

View File

@@ -109,9 +109,10 @@ static int
event_watcher(void *data, SDL_Event *event) {
(void) data;
if (event->type == SDL_WINDOWEVENT
&& event->window.event == SDL_WINDOWEVENT_RESIZED) {
// called from another thread, not very safe, but it's a workaround!
screen_render(&screen);
&& event->window.event == SDL_WINDOWEVENT_SIZE_CHANGED) {
// In practice, it seems to always be called from the same thread in
// that specific case. Anyway, it's just a workaround.
screen_handle_window_event(&screen, &event->window);
}
return 0;
}

View File

@@ -113,6 +113,13 @@ get_optimal_size(struct size current_size, struct size content_size) {
h = MIN(current_size.height, display_size.height);
}
if (h == w * content_size.height / content_size.width
|| w == h * content_size.width / content_size.height) {
// The size is already optimal, if we ignore rounding errors due to
// integer window dimensions
return (struct size) {w, h};
}
bool keep_width = content_size.width * h > content_size.height * w;
if (keep_width) {
// remove black borders on top and bottom
@@ -491,7 +498,8 @@ screen_resize_to_fit(struct screen *screen) {
struct size optimal_size =
get_optimal_window_size(screen, screen->content_size);
SDL_SetWindowSize(screen->window, optimal_size.width, optimal_size.height);
LOGD("Resized to optimal size");
LOGD("Resized to optimal size: %ux%u", optimal_size.width,
optimal_size.height);
}
void
@@ -507,7 +515,17 @@ screen_resize_to_pixel_perfect(struct screen *screen) {
struct size content_size = screen->content_size;
SDL_SetWindowSize(screen->window, content_size.width, content_size.height);
LOGD("Resized to pixel-perfect");
LOGD("Resized to pixel-perfect: %ux%u", content_size.width,
content_size.height);
}
static void
screen_reset_logical_size(struct screen *screen) {
// Re-apply the current logical size.
if (SDL_RenderSetLogicalSize(screen->renderer, screen->content_size.width,
screen->content_size.height)) {
LOGE("Could not reset renderer logical size: %s", SDL_GetError());
}
}
void
@@ -515,6 +533,10 @@ screen_handle_window_event(struct screen *screen,
const SDL_WindowEvent *event) {
switch (event->event) {
case SDL_WINDOWEVENT_EXPOSED:
// Re-apply the current logical size, in case the window has been
// moved to a screen with a different HiDPI scaling
// <https://github.com/Genymobile/scrcpy/issues/15>
screen_reset_logical_size(screen);
screen_render(screen);
break;
case SDL_WINDOWEVENT_SIZE_CHANGED:
@@ -551,3 +573,33 @@ screen_handle_window_event(struct screen *screen,
break;
}
}
struct point
screen_convert_to_frame_coords(struct screen *screen, int32_t x, int32_t y) {
unsigned rotation = screen->rotation;
assert(rotation < 4);
int32_t w = screen->content_size.width;
int32_t h = screen->content_size.height;
struct point result;
switch (rotation) {
case 0:
result.x = x;
result.y = y;
break;
case 1:
result.x = h - y;
result.y = x;
break;
case 2:
result.x = w - x;
result.y = h - y;
break;
default:
assert(rotation == 3);
result.x = y;
result.y = w - x;
break;
}
return result;
}

View File

@@ -114,4 +114,9 @@ screen_set_rotation(struct screen *screen, unsigned rotation);
void
screen_handle_window_event(struct screen *screen, const SDL_WindowEvent *event);
// convert point from window coordinates to frame coordinates
// x and y are expressed in pixels
struct point
screen_convert_to_frame_coords(struct screen *screen, int32_t x, int32_t y);
#endif

View File

@@ -49,20 +49,20 @@ static void test_serialize_inject_text(void) {
static void test_serialize_inject_text_long(void) {
struct control_msg msg;
msg.type = CONTROL_MSG_TYPE_INJECT_TEXT;
char text[CONTROL_MSG_TEXT_MAX_LENGTH + 1];
char text[CONTROL_MSG_INJECT_TEXT_MAX_LENGTH + 1];
memset(text, 'a', sizeof(text));
text[CONTROL_MSG_TEXT_MAX_LENGTH] = '\0';
text[CONTROL_MSG_INJECT_TEXT_MAX_LENGTH] = '\0';
msg.inject_text.text = text;
unsigned char buf[CONTROL_MSG_SERIALIZED_MAX_SIZE];
int size = control_msg_serialize(&msg, buf);
assert(size == 3 + CONTROL_MSG_TEXT_MAX_LENGTH);
assert(size == 3 + CONTROL_MSG_INJECT_TEXT_MAX_LENGTH);
unsigned char expected[3 + CONTROL_MSG_TEXT_MAX_LENGTH];
unsigned char expected[3 + CONTROL_MSG_INJECT_TEXT_MAX_LENGTH];
expected[0] = CONTROL_MSG_TYPE_INJECT_TEXT;
expected[1] = 0x01;
expected[2] = 0x2c; // text length (16 bits)
memset(&expected[3], 'a', CONTROL_MSG_TEXT_MAX_LENGTH);
memset(&expected[3], 'a', CONTROL_MSG_INJECT_TEXT_MAX_LENGTH);
assert(!memcmp(buf, expected, sizeof(expected)));
}

View File

@@ -1,6 +1,6 @@
project('scrcpy', 'c',
version: '1.12.1',
meson_version: '>= 0.37',
meson_version: '>= 0.48',
default_options: [
'c_std=c11',
'warning_level=2',

View File

@@ -3,7 +3,9 @@
prebuilt_server = get_option('prebuilt_server')
if prebuilt_server == ''
custom_target('scrcpy-server',
build_always: true, # gradle is responsible for tracking source changes
# gradle is responsible for tracking source changes
build_by_default: true,
build_always_stale: true,
output: 'scrcpy-server',
command: [find_program('./scripts/build-wrapper.sh'), meson.current_source_dir(), '@OUTPUT@', get_option('buildtype')],
console: true,

View File

@@ -13,8 +13,8 @@ public class ControlMessageReader {
static final int INJECT_SCROLL_EVENT_PAYLOAD_LENGTH = 20;
static final int SET_SCREEN_POWER_MODE_PAYLOAD_LENGTH = 1;
public static final int TEXT_MAX_LENGTH = 300;
public static final int CLIPBOARD_TEXT_MAX_LENGTH = 4093;
public static final int INJECT_TEXT_MAX_LENGTH = 300;
private static final int RAW_BUFFER_SIZE = 1024;
private final byte[] rawBuffer = new byte[RAW_BUFFER_SIZE];

View File

@@ -36,10 +36,7 @@ public final class Device {
*/
private final int layerStack;
/**
* The FLAG_PRESENTATION from the DisplayInfo
*/
private final boolean isPresentationDisplay;
private final boolean supportsInputEvents;
public Device(Options options) {
displayId = options.getDisplayId();
@@ -53,7 +50,6 @@ public final class Device {
screenInfo = ScreenInfo.computeScreenInfo(displayInfo, options.getCrop(), options.getMaxSize(), options.getLockedVideoOrientation());
layerStack = displayInfo.getLayerStack();
isPresentationDisplay = (displayInfoFlags & DisplayInfo.FLAG_PRESENTATION) != 0;
registerRotationWatcher(new IRotationWatcher.Stub() {
@Override
@@ -73,8 +69,10 @@ public final class Device {
Ln.w("Display doesn't have FLAG_SUPPORTS_PROTECTED_BUFFERS flag, mirroring can be restricted");
}
if (!supportsInputEvents()) {
Ln.w("Input events are not supported for displays with FLAG_PRESENTATION enabled for devices with API lower than 29");
// main display or any display on Android >= Q
supportsInputEvents = displayId == 0 || Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q;
if (!supportsInputEvents) {
Ln.w("Input events are not supported for secondary displays before Android 10");
}
}
@@ -116,10 +114,7 @@ public final class Device {
}
public boolean supportsInputEvents() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
return true;
}
return !isPresentationDisplay;
return supportsInputEvents;
}
public boolean injectInputEvent(InputEvent inputEvent, int mode) {

View File

@@ -8,7 +8,6 @@ public final class DisplayInfo {
private final int flags;
public static final int FLAG_SUPPORTS_PROTECTED_BUFFERS = 0x00000001;
public static final int FLAG_PRESENTATION = 0x00000008;
public DisplayInfo(int displayId, Size size, int rotation, int layerStack, int flags) {
this.displayId = displayId;

View File

@@ -136,12 +136,12 @@ public class ScreenEncoder implements Device.RotationListener {
}
private static MediaCodec createCodec() throws IOException {
return MediaCodec.createEncoderByType("video/avc");
return MediaCodec.createEncoderByType(MediaFormat.MIMETYPE_VIDEO_AVC);
}
private static MediaFormat createFormat(int bitRate, int maxFps, int iFrameInterval) {
MediaFormat format = new MediaFormat();
format.setString(MediaFormat.KEY_MIME, "video/avc");
format.setString(MediaFormat.KEY_MIME, MediaFormat.MIMETYPE_VIDEO_AVC);
format.setInteger(MediaFormat.KEY_BIT_RATE, bitRate);
// 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);

View File

@@ -53,8 +53,7 @@ public final class InputManager {
method.invoke(inputEvent, displayId);
return true;
} catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException e) {
// just a warning, it might happen on old devices
Ln.w("Cannot associate a display id to the input event");
Ln.e("Cannot associate a display id to the input event", e);
return false;
}
}

View File

@@ -66,7 +66,7 @@ public class ControlMessageReaderTest {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
DataOutputStream dos = new DataOutputStream(bos);
dos.writeByte(ControlMessage.TYPE_INJECT_TEXT);
byte[] text = new byte[ControlMessageReader.TEXT_MAX_LENGTH];
byte[] text = new byte[ControlMessageReader.INJECT_TEXT_MAX_LENGTH];
Arrays.fill(text, (byte) 'a');
dos.writeShort(text.length);
dos.write(text);