Compare commits

...
6 Commits
Author SHA1 Message Date
Romain Vimont a12b459746 Accept negative window position
It seems to work on some window managers.

Fixes #1242 <https://github.com/Genymobile/scrcpy/issues/1242>
2020-03-26 18:36:00 +01:00
Tzah Mazuz a0af402d96 Fix the printed versions (were opposite)
PR #1224 <https://github.com/Genymobile/scrcpy/pull/1224>

Signed-off-by: Romain Vimont <rom@rom1v.com>
2020-03-19 19:28:57 +01:00
Romain Vimont 600df37753 Mention AutoAdb in README 2020-03-19 19:16:08 +01:00
Romain Vimont 902b99174d Fix server debugger for Android >= 9
Add a compilation flag to select the debugger method to use:
 - old: Android < 9
 - new: Android >= 9

See <https://github.com/Genymobile/scrcpy/issues/1187#issuecomment-599075661>
2020-03-19 19:15:43 +01:00
Jaafar Biyadi cd69eb4a4f Handle NumPad events when NumLock is disabled
PR #1188 <https://github.com/Genymobile/scrcpy/pull/1188>
Fixes #1048 <https://github.com/Genymobile/scrcpy/issues/1048>

Signed-off-by: Romain Vimont <rom@rom1v.com>
2020-03-14 17:37:14 +01:00
Romain Vimont ae2d094362 Handle locked video orientation from ScreenInfo
Centralize video size management in ScreenInfo.

This allows to always send the correct initial video size to the client
if the video orientation is locked.
2020-03-08 10:38:31 +01:00
15 changed files with 152 additions and 71 deletions
+9
View File
@@ -282,6 +282,15 @@ meson x -Dserver_debugger=true
meson configure x -Dserver_debugger=true meson configure x -Dserver_debugger=true
``` ```
If your device runs Android 8 or below, set the `server_debugger_method` to
`old` in addition:
```bash
meson x -Dserver_debugger=true -Dserver_debugger_method=old
# or, if x is already configured
meson configure x -Dserver_debugger=true -Dserver_debugger_method=old
```
Then recompile. Then recompile.
When you start scrcpy, it will start a debugger on port 5005 on the device. When you start scrcpy, it will start a debugger on port 5005 on the device.
+10
View File
@@ -261,6 +261,16 @@ scrcpy -s 192.168.0.1:5555 # short version
You can start several instances of _scrcpy_ for several devices. You can start several instances of _scrcpy_ for several devices.
#### Autostart on device connection
You could use [AutoAdb]:
```bash
autoadb scrcpy -s '{}'
```
[AutoAdb]: https://github.com/rom1v/usbaudio
#### SSH tunnel #### SSH tunnel
To connect to a remote device, it is possible to connect a local `adb` client to To connect to a remote device, it is possible to connect a local `adb` client to
+3
View File
@@ -124,6 +124,9 @@ conf.set('WINDOWS_NOCONSOLE', get_option('windows_noconsole'))
# run a server debugger and wait for a client to be attached # run a server debugger and wait for a client to be attached
conf.set('SERVER_DEBUGGER', get_option('server_debugger')) conf.set('SERVER_DEBUGGER', get_option('server_debugger'))
# select the debugger method ('old' for Android < 9, 'new' for Android >= 9)
conf.set('SERVER_DEBUGGER_METHOD_NEW', get_option('server_debugger_method') == 'new')
configure_file(configuration: conf, output: 'config.h') configure_file(configuration: conf, output: 'config.h')
src_dir = include_directories('src') src_dir = include_directories('src')
+2 -2
View File
@@ -131,13 +131,13 @@ Set a custom window title.
.BI "\-\-window\-x " value .BI "\-\-window\-x " value
Set the initial window horizontal position. Set the initial window horizontal position.
Default is -1 (automatic).\n Default is "auto".\n
.TP .TP
.BI "\-\-window\-y " value .BI "\-\-window\-y " value
Set the initial window vertical position. Set the initial window vertical position.
Default is -1 (automatic).\n Default is "auto".\n
.TP .TP
.BI "\-\-window\-width " value .BI "\-\-window\-width " value
+12 -3
View File
@@ -1,5 +1,6 @@
#include "cli.h" #include "cli.h"
#include <assert.h>
#include <getopt.h> #include <getopt.h>
#include <stdint.h> #include <stdint.h>
#include <unistd.h> #include <unistd.h>
@@ -116,11 +117,11 @@ scrcpy_print_usage(const char *arg0) {
"\n" "\n"
" --window-x value\n" " --window-x value\n"
" Set the initial window horizontal position.\n" " Set the initial window horizontal position.\n"
" Default is -1 (automatic).\n" " Default is \"auto\".\n"
"\n" "\n"
" --window-y value\n" " --window-y value\n"
" Set the initial window vertical position.\n" " Set the initial window vertical position.\n"
" Default is -1 (automatic).\n" " Default is \"auto\".\n"
"\n" "\n"
" --window-width value\n" " --window-width value\n"
" Set the initial window width.\n" " Set the initial window width.\n"
@@ -302,8 +303,16 @@ parse_lock_video_orientation(const char *s, int8_t *lock_video_orientation) {
static bool static bool
parse_window_position(const char *s, int16_t *position) { parse_window_position(const char *s, int16_t *position) {
// special value for "auto"
static_assert(WINDOW_POSITION_UNDEFINED == -0x8000);
if (!strcmp(s, "auto")) {
*position = WINDOW_POSITION_UNDEFINED;
return true;
}
long value; long value;
bool ok = parse_integer_arg(s, &value, false, -1, 0x7FFF, bool ok = parse_integer_arg(s, &value, false, -0x7FFF, 0x7FFF,
"window position"); "window position");
if (!ok) { if (!ok) {
return false; return false;
+17
View File
@@ -94,6 +94,23 @@ convert_keycode(SDL_Keycode from, enum android_keycode *to, uint16_t mod,
MAP(SDLK_UP, AKEYCODE_DPAD_UP); MAP(SDLK_UP, AKEYCODE_DPAD_UP);
} }
if (!(mod & (KMOD_NUM | KMOD_SHIFT))) {
// Handle Numpad events when Num Lock is disabled
// If SHIFT is pressed, a text event will be sent instead
switch(from) {
MAP(SDLK_KP_0, AKEYCODE_INSERT);
MAP(SDLK_KP_1, AKEYCODE_MOVE_END);
MAP(SDLK_KP_2, AKEYCODE_DPAD_DOWN);
MAP(SDLK_KP_3, AKEYCODE_PAGE_DOWN);
MAP(SDLK_KP_4, AKEYCODE_DPAD_LEFT);
MAP(SDLK_KP_6, AKEYCODE_DPAD_RIGHT);
MAP(SDLK_KP_7, AKEYCODE_MOVE_HOME);
MAP(SDLK_KP_8, AKEYCODE_DPAD_UP);
MAP(SDLK_KP_9, AKEYCODE_PAGE_UP);
MAP(SDLK_KP_PERIOD, AKEYCODE_FORWARD_DEL);
}
}
if (prefer_text) { if (prefer_text) {
// do not forward alpha and space key events // do not forward alpha and space key events
return false; return false;
+4 -4
View File
@@ -21,8 +21,8 @@ struct scrcpy_options {
uint32_t bit_rate; uint32_t bit_rate;
uint16_t max_fps; uint16_t max_fps;
int8_t lock_video_orientation; int8_t lock_video_orientation;
int16_t window_x; int16_t window_x; // WINDOW_POSITION_UNDEFINED for "auto"
int16_t window_y; int16_t window_y; // WINDOW_POSITION_UNDEFINED for "auto"
uint16_t window_width; uint16_t window_width;
uint16_t window_height; uint16_t window_height;
bool show_touches; bool show_touches;
@@ -51,8 +51,8 @@ struct scrcpy_options {
.bit_rate = DEFAULT_BIT_RATE, \ .bit_rate = DEFAULT_BIT_RATE, \
.max_fps = 0, \ .max_fps = 0, \
.lock_video_orientation = DEFAULT_LOCK_VIDEO_ORIENTATION, \ .lock_video_orientation = DEFAULT_LOCK_VIDEO_ORIENTATION, \
.window_x = -1, \ .window_x = WINDOW_POSITION_UNDEFINED, \
.window_y = -1, \ .window_y = WINDOW_POSITION_UNDEFINED, \
.window_width = 0, \ .window_width = 0, \
.window_height = 0, \ .window_height = 0, \
.show_touches = false, \ .show_touches = false, \
+4 -2
View File
@@ -186,8 +186,10 @@ screen_init_rendering(struct screen *screen, const char *window_title,
window_flags |= SDL_WINDOW_BORDERLESS; window_flags |= SDL_WINDOW_BORDERLESS;
} }
int x = window_x != -1 ? window_x : (int) SDL_WINDOWPOS_UNDEFINED; int x = window_x != WINDOW_POSITION_UNDEFINED
int y = window_y != -1 ? window_y : (int) SDL_WINDOWPOS_UNDEFINED; ? window_x : (int) SDL_WINDOWPOS_UNDEFINED;
int y = window_y != WINDOW_POSITION_UNDEFINED
? window_y : (int) SDL_WINDOWPOS_UNDEFINED;
screen->window = SDL_CreateWindow(window_title, x, y, screen->window = SDL_CreateWindow(window_title, x, y,
window_size.width, window_size.height, window_size.width, window_size.height,
window_flags); window_flags);
+3
View File
@@ -8,6 +8,8 @@
#include "config.h" #include "config.h"
#include "common.h" #include "common.h"
#define WINDOW_POSITION_UNDEFINED (-0x8000)
struct video_buffer; struct video_buffer;
struct screen { struct screen {
@@ -53,6 +55,7 @@ void
screen_init(struct screen *screen); screen_init(struct screen *screen);
// initialize screen, create window, renderer and texture (window is hidden) // initialize screen, create window, renderer and texture (window is hidden)
// window_x and window_y accept WINDOW_POSITION_UNDEFINED
bool bool
screen_init_rendering(struct screen *screen, const char *window_title, 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,
+6
View File
@@ -244,7 +244,13 @@ execute_server(struct server *server, const struct server_params *params) {
"app_process", "app_process",
#ifdef SERVER_DEBUGGER #ifdef SERVER_DEBUGGER
# define SERVER_DEBUGGER_PORT "5005" # define SERVER_DEBUGGER_PORT "5005"
# ifdef SERVER_DEBUGGER_METHOD_NEW
/* Android 9 and above */
"-XjdwpProvider:internal -XjdwpOptions:transport=dt_socket,suspend=y,server=y,address="
# else
/* Android 8 and below */
"-agentlib:jdwp=transport=dt_socket,suspend=y,server=y,address=" "-agentlib:jdwp=transport=dt_socket,suspend=y,server=y,address="
# endif
SERVER_DEBUGGER_PORT, SERVER_DEBUGGER_PORT,
#endif #endif
"/", // unused "/", // unused
+1
View File
@@ -6,3 +6,4 @@ option('prebuilt_server', type: 'string', description: 'Path of the prebuilt ser
option('portable', type: 'boolean', value: false, description: 'Use scrcpy-server from the same directory as the scrcpy executable') 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('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') option('server_debugger', type: 'boolean', value: false, description: 'Run a server debugger and wait for a client to be attached')
option('server_debugger_method', type: 'combo', choices: ['old', 'new'], value: 'new', description: 'Select the debugger method (Android < 9: "old", Android >= 9: "new")')
@@ -22,14 +22,12 @@ public final class Device {
private final ServiceManager serviceManager = new ServiceManager(); private final ServiceManager serviceManager = new ServiceManager();
private final int lockedVideoOrientation;
private ScreenInfo screenInfo; private ScreenInfo screenInfo;
private RotationListener rotationListener; private RotationListener rotationListener;
public Device(Options options) { public Device(Options options) {
lockedVideoOrientation = options.getLockedVideoOrientation();
DisplayInfo displayInfo = serviceManager.getDisplayManager().getDisplayInfo(); DisplayInfo displayInfo = serviceManager.getDisplayManager().getDisplayInfo();
screenInfo = ScreenInfo.computeScreenInfo(displayInfo, options.getCrop(), options.getMaxSize()); screenInfo = ScreenInfo.computeScreenInfo(displayInfo, options.getCrop(), options.getMaxSize(), options.getLockedVideoOrientation());
registerRotationWatcher(new IRotationWatcher.Stub() { registerRotationWatcher(new IRotationWatcher.Stub() {
@Override @Override
public void onRotationChanged(int rotation) throws RemoteException { public void onRotationChanged(int rotation) throws RemoteException {
@@ -49,55 +47,28 @@ public final class Device {
return screenInfo; return screenInfo;
} }
/**
* Return the rotation to apply to the device rotation to get the requested locked video orientation
*
* @param deviceRotation the device rotation
* @return the rotation offset
*/
public int getVideoRotation(int deviceRotation) {
if (lockedVideoOrientation == -1) {
// no offset
return 0;
}
return (deviceRotation + 4 - lockedVideoOrientation) % 4;
}
/**
* Return the rotation to apply to the requested locked video orientation to get the device rotation
*
* @param deviceRotation the device rotation
* @return the (reverse) rotation offset
*/
private int getReverseVideoRotation(int deviceRotation) {
if (lockedVideoOrientation == -1) {
// no offset
return 0;
}
return (lockedVideoOrientation + 4 - deviceRotation) % 4;
}
public Point getPhysicalPoint(Position position) { public Point getPhysicalPoint(Position position) {
// it hides the field on purpose, to read it with a lock // it hides the field on purpose, to read it with a lock
@SuppressWarnings("checkstyle:HiddenField") @SuppressWarnings("checkstyle:HiddenField")
ScreenInfo screenInfo = getScreenInfo(); // read with synchronization ScreenInfo screenInfo = getScreenInfo(); // read with synchronization
Size videoSize = screenInfo.getVideoSize();
int deviceRotation = screenInfo.getDeviceRotation(); // ignore the locked video orientation, the events will apply in coordinates considered in the physical device orientation
int reverseVideoRotation = getReverseVideoRotation(deviceRotation); Size unlockedVideoSize = screenInfo.getUnlockedVideoSize();
int reverseVideoRotation = screenInfo.getReverseVideoRotation();
// reverse the video rotation to apply the events // reverse the video rotation to apply the events
Position devicePosition = position.rotate(reverseVideoRotation); Position devicePosition = position.rotate(reverseVideoRotation);
Size clientVideoSize = devicePosition.getScreenSize(); Size clientVideoSize = devicePosition.getScreenSize();
if (!videoSize.equals(clientVideoSize)) { if (!unlockedVideoSize.equals(clientVideoSize)) {
// The client sends a click relative to a video with wrong dimensions, // The client sends a click relative to a video with wrong dimensions,
// the device may have been rotated since the event was generated, so ignore the event // the device may have been rotated since the event was generated, so ignore the event
return null; return null;
} }
Rect contentRect = screenInfo.getContentRect(); Rect contentRect = screenInfo.getContentRect();
Point point = devicePosition.getPoint(); Point point = devicePosition.getPoint();
int convertedX = contentRect.left + point.getX() * contentRect.width() / videoSize.getWidth(); int convertedX = contentRect.left + point.getX() * contentRect.width() / unlockedVideoSize.getWidth();
int convertedY = contentRect.top + point.getY() * contentRect.height() / videoSize.getHeight(); int convertedY = contentRect.top + point.getY() * contentRect.height() / unlockedVideoSize.getHeight();
return new Point(convertedX, convertedY); return new Point(convertedX, convertedY);
} }
@@ -66,12 +66,15 @@ public class ScreenEncoder implements Device.RotationListener {
IBinder display = createDisplay(); IBinder display = createDisplay();
ScreenInfo screenInfo = device.getScreenInfo(); ScreenInfo screenInfo = device.getScreenInfo();
Rect contentRect = screenInfo.getContentRect(); Rect contentRect = screenInfo.getContentRect();
// include the locked video orientation
Rect videoRect = screenInfo.getVideoSize().toRect(); Rect videoRect = screenInfo.getVideoSize().toRect();
int videoRotation = device.getVideoRotation(screenInfo.getDeviceRotation()); // does not include the locked video orientation
setSize(format, videoRotation, videoRect.width(), videoRect.height()); Rect unlockedVideoRect = screenInfo.getUnlockedVideoSize().toRect();
int videoRotation = screenInfo.getVideoRotation();
setSize(format, videoRect.width(), videoRect.height());
configure(codec, format); configure(codec, format);
Surface surface = codec.createInputSurface(); Surface surface = codec.createInputSurface();
setDisplaySurface(display, surface, videoRotation, contentRect, videoRect); setDisplaySurface(display, surface, videoRotation, contentRect, unlockedVideoRect);
codec.start(); codec.start();
try { try {
alive = encode(codec, fd); alive = encode(codec, fd);
@@ -170,14 +173,9 @@ public class ScreenEncoder implements Device.RotationListener {
codec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE); codec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
} }
private static void setSize(MediaFormat format, int orientation, int width, int height) { private static void setSize(MediaFormat format, int width, int height) {
if (orientation % 2 == 0) { format.setInteger(MediaFormat.KEY_WIDTH, width);
format.setInteger(MediaFormat.KEY_WIDTH, width); format.setInteger(MediaFormat.KEY_HEIGHT, height);
format.setInteger(MediaFormat.KEY_HEIGHT, height);
return;
}
format.setInteger(MediaFormat.KEY_WIDTH, height);
format.setInteger(MediaFormat.KEY_HEIGHT, width);
} }
private static void setDisplaySurface(IBinder display, Surface surface, int orientation, Rect deviceRect, Rect displayRect) { private static void setDisplaySurface(IBinder display, Surface surface, int orientation, Rect deviceRect, Rect displayRect) {
@@ -9,27 +9,53 @@ public final class ScreenInfo {
private final Rect contentRect; // device size, possibly cropped private final Rect contentRect; // device size, possibly cropped
/** /**
* Video size, possibly smaller than the device size, already taking the device rotation and crop into account * Video size, possibly smaller than the device size, already taking the device rotation and crop into account.
* <p>
* However, it does not include the locked video orientation.
*/ */
private final Size videoSize; private final Size unlockedVideoSize;
/** /**
* Device rotation, related to the natural device orientation (0, 1, 2 or 3) * Device rotation, related to the natural device orientation (0, 1, 2 or 3)
*/ */
private final int deviceRotation; private final int deviceRotation;
public ScreenInfo(Rect contentRect, Size videoSize, int deviceRotation) { /**
* The locked video orientation (-1: disabled, 0: normal, 1: 90° CCW, 2: 180°, 3: 90° CW)
*/
private final int lockedVideoOrientation;
public ScreenInfo(Rect contentRect, Size unlockedVideoSize, int deviceRotation, int lockedVideoOrientation) {
this.contentRect = contentRect; this.contentRect = contentRect;
this.videoSize = videoSize; this.unlockedVideoSize = unlockedVideoSize;
this.deviceRotation = deviceRotation; this.deviceRotation = deviceRotation;
this.lockedVideoOrientation = lockedVideoOrientation;
} }
public Rect getContentRect() { public Rect getContentRect() {
return contentRect; return contentRect;
} }
/**
* Return the video size as if locked video orientation was not set.
*
* @return the unlocked video size
*/
public Size getUnlockedVideoSize() {
return unlockedVideoSize;
}
/**
* Return the actual video size if locked video orientation is set.
*
* @return the actual video size
*/
public Size getVideoSize() { public Size getVideoSize() {
return videoSize; if (getVideoRotation() % 2 == 0) {
return unlockedVideoSize;
}
return unlockedVideoSize.rotate();
} }
public int getDeviceRotation() { public int getDeviceRotation() {
@@ -43,18 +69,18 @@ public final class ScreenInfo {
// true if changed between portrait and landscape // true if changed between portrait and landscape
boolean orientationChanged = (deviceRotation + newDeviceRotation) % 2 != 0; boolean orientationChanged = (deviceRotation + newDeviceRotation) % 2 != 0;
Rect newContentRect; Rect newContentRect;
Size newVideoSize; Size newUnlockedVideoSize;
if (orientationChanged) { if (orientationChanged) {
newContentRect = flipRect(contentRect); newContentRect = flipRect(contentRect);
newVideoSize = videoSize.rotate(); newUnlockedVideoSize = unlockedVideoSize.rotate();
} else { } else {
newContentRect = contentRect; newContentRect = contentRect;
newVideoSize = videoSize; newUnlockedVideoSize = unlockedVideoSize;
} }
return new ScreenInfo(newContentRect, newVideoSize, newDeviceRotation); return new ScreenInfo(newContentRect, newUnlockedVideoSize, newDeviceRotation, lockedVideoOrientation);
} }
public static ScreenInfo computeScreenInfo(DisplayInfo displayInfo, Rect crop, int maxSize) { public static ScreenInfo computeScreenInfo(DisplayInfo displayInfo, Rect crop, int maxSize, int lockedVideoOrientation) {
int rotation = displayInfo.getRotation(); int rotation = displayInfo.getRotation();
Size deviceSize = displayInfo.getSize(); Size deviceSize = displayInfo.getSize();
Rect contentRect = new Rect(0, 0, deviceSize.getWidth(), deviceSize.getHeight()); Rect contentRect = new Rect(0, 0, deviceSize.getWidth(), deviceSize.getHeight());
@@ -71,7 +97,7 @@ public final class ScreenInfo {
} }
Size videoSize = computeVideoSize(contentRect.width(), contentRect.height(), maxSize); Size videoSize = computeVideoSize(contentRect.width(), contentRect.height(), maxSize);
return new ScreenInfo(contentRect, videoSize, rotation); return new ScreenInfo(contentRect, videoSize, rotation, lockedVideoOrientation);
} }
private static String formatCrop(Rect rect) { private static String formatCrop(Rect rect) {
@@ -109,4 +135,30 @@ public final class ScreenInfo {
private static Rect flipRect(Rect crop) { private static Rect flipRect(Rect crop) {
return new Rect(crop.top, crop.left, crop.bottom, crop.right); return new Rect(crop.top, crop.left, crop.bottom, crop.right);
} }
/**
* Return the rotation to apply to the device rotation to get the requested locked video orientation
*
* @return the rotation offset
*/
public int getVideoRotation() {
if (lockedVideoOrientation == -1) {
// no offset
return 0;
}
return (deviceRotation + 4 - lockedVideoOrientation) % 4;
}
/**
* Return the rotation to apply to the requested locked video orientation to get the device rotation
*
* @return the (reverse) rotation offset
*/
public int getReverseVideoRotation() {
if (lockedVideoOrientation == -1) {
// no offset
return 0;
}
return (lockedVideoOrientation + 4 - deviceRotation) % 4;
}
} }
@@ -77,7 +77,7 @@ public final class Server {
String clientVersion = args[0]; String clientVersion = args[0];
if (!clientVersion.equals(BuildConfig.VERSION_NAME)) { if (!clientVersion.equals(BuildConfig.VERSION_NAME)) {
throw new IllegalArgumentException( throw new IllegalArgumentException(
"The server version (" + clientVersion + ") does not match the client " + "(" + BuildConfig.VERSION_NAME + ")"); "The server version (" + BuildConfig.VERSION_NAME + ") does not match the client " + "(" + clientVersion + ")");
} }
if (args.length != 9) { if (args.length != 9) {