Add shortcut to rotate screen
On Ctrl+r, disable auto-rotation (if enabled), set the screen rotation and re-enable auto-rotation (if it was enabled). Closes #11 <https://github.com/Genymobile/scrcpy/issues/11>
This commit is contained in:
parent
bdd05b4a16
commit
eb0f339271
13 changed files with 157 additions and 6 deletions
|
@ -425,6 +425,7 @@ Also see [issue #14].
|
|||
| Click on `POWER` | `Ctrl`+`p` | `Cmd`+`p`
|
||||
| Power on | _Right-click²_ | _Right-click²_
|
||||
| Turn device screen off (keep mirroring)| `Ctrl`+`o` | `Cmd`+`o`
|
||||
| Rotate device screen | `Ctrl`+`r` | `Cmd`+`r`
|
||||
| Expand notification panel | `Ctrl`+`n` | `Cmd`+`n`
|
||||
| Collapse notification panel | `Ctrl`+`Shift`+`n` | `Cmd`+`Shift`+`n`
|
||||
| Copy device clipboard to computer | `Ctrl`+`c` | `Cmd`+`c`
|
||||
|
|
|
@ -195,6 +195,10 @@ turn screen on
|
|||
.B Ctrl+o
|
||||
turn device screen off (keep mirroring)
|
||||
|
||||
.TP
|
||||
.B Ctrl+r
|
||||
rotate device screen
|
||||
|
||||
.TP
|
||||
.B Ctrl+n
|
||||
expand notification panel
|
||||
|
|
|
@ -78,6 +78,7 @@ control_msg_serialize(const struct control_msg *msg, unsigned char *buf) {
|
|||
case CONTROL_MSG_TYPE_EXPAND_NOTIFICATION_PANEL:
|
||||
case CONTROL_MSG_TYPE_COLLAPSE_NOTIFICATION_PANEL:
|
||||
case CONTROL_MSG_TYPE_GET_CLIPBOARD:
|
||||
case CONTROL_MSG_TYPE_ROTATE_DEVICE:
|
||||
// no additional data
|
||||
return 1;
|
||||
default:
|
||||
|
|
|
@ -28,6 +28,7 @@ enum control_msg_type {
|
|||
CONTROL_MSG_TYPE_GET_CLIPBOARD,
|
||||
CONTROL_MSG_TYPE_SET_CLIPBOARD,
|
||||
CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE,
|
||||
CONTROL_MSG_TYPE_ROTATE_DEVICE,
|
||||
};
|
||||
|
||||
enum screen_power_mode {
|
||||
|
|
|
@ -211,6 +211,16 @@ clipboard_paste(struct controller *controller) {
|
|||
}
|
||||
}
|
||||
|
||||
static void
|
||||
rotate_device(struct controller *controller) {
|
||||
struct control_msg msg;
|
||||
msg.type = CONTROL_MSG_TYPE_ROTATE_DEVICE;
|
||||
|
||||
if (!controller_push_msg(controller, &msg)) {
|
||||
LOGW("Could not request device rotation");
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
input_manager_process_text_input(struct input_manager *im,
|
||||
const SDL_TextInputEvent *event) {
|
||||
|
@ -388,6 +398,11 @@ input_manager_process_key(struct input_manager *im,
|
|||
}
|
||||
}
|
||||
return;
|
||||
case SDLK_r:
|
||||
if (control && cmd && !shift && !repeat && down) {
|
||||
rotate_device(controller);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
return;
|
||||
|
|
|
@ -175,6 +175,9 @@ static void usage(const char *arg0) {
|
|||
" " CTRL_OR_CMD "+o\n"
|
||||
" turn device screen off (keep mirroring)\n"
|
||||
"\n"
|
||||
" " CTRL_OR_CMD "+r\n"
|
||||
" rotate device screen\n"
|
||||
"\n"
|
||||
" " CTRL_OR_CMD "+n\n"
|
||||
" expand notification panel\n"
|
||||
"\n"
|
||||
|
|
|
@ -236,6 +236,21 @@ static void test_serialize_set_screen_power_mode(void) {
|
|||
assert(!memcmp(buf, expected, sizeof(expected)));
|
||||
}
|
||||
|
||||
static void test_serialize_rotate_device(void) {
|
||||
struct control_msg msg = {
|
||||
.type = CONTROL_MSG_TYPE_ROTATE_DEVICE,
|
||||
};
|
||||
|
||||
unsigned char buf[CONTROL_MSG_SERIALIZED_MAX_SIZE];
|
||||
int size = control_msg_serialize(&msg, buf);
|
||||
assert(size == 1);
|
||||
|
||||
const unsigned char expected[] = {
|
||||
CONTROL_MSG_TYPE_ROTATE_DEVICE,
|
||||
};
|
||||
assert(!memcmp(buf, expected, sizeof(expected)));
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
test_serialize_inject_keycode();
|
||||
test_serialize_inject_text();
|
||||
|
@ -248,5 +263,6 @@ int main(void) {
|
|||
test_serialize_get_clipboard();
|
||||
test_serialize_set_clipboard();
|
||||
test_serialize_set_screen_power_mode();
|
||||
test_serialize_rotate_device();
|
||||
return 0;
|
||||
}
|
||||
|
|
|
@ -15,6 +15,7 @@ public final class ControlMessage {
|
|||
public static final int TYPE_GET_CLIPBOARD = 7;
|
||||
public static final int TYPE_SET_CLIPBOARD = 8;
|
||||
public static final int TYPE_SET_SCREEN_POWER_MODE = 9;
|
||||
public static final int TYPE_ROTATE_DEVICE = 10;
|
||||
|
||||
private int type;
|
||||
private String text;
|
||||
|
|
|
@ -76,6 +76,7 @@ public class ControlMessageReader {
|
|||
case ControlMessage.TYPE_EXPAND_NOTIFICATION_PANEL:
|
||||
case ControlMessage.TYPE_COLLAPSE_NOTIFICATION_PANEL:
|
||||
case ControlMessage.TYPE_GET_CLIPBOARD:
|
||||
case ControlMessage.TYPE_ROTATE_DEVICE:
|
||||
msg = ControlMessage.createEmpty(type);
|
||||
break;
|
||||
default:
|
||||
|
|
|
@ -106,6 +106,9 @@ public class Controller {
|
|||
case ControlMessage.TYPE_SET_SCREEN_POWER_MODE:
|
||||
device.setScreenPowerMode(msg.getAction());
|
||||
break;
|
||||
case ControlMessage.TYPE_ROTATE_DEVICE:
|
||||
device.rotateDevice();
|
||||
break;
|
||||
default:
|
||||
// do nothing
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ package com.genymobile.scrcpy;
|
|||
|
||||
import com.genymobile.scrcpy.wrappers.ServiceManager;
|
||||
import com.genymobile.scrcpy.wrappers.SurfaceControl;
|
||||
import com.genymobile.scrcpy.wrappers.WindowManager;
|
||||
|
||||
import android.graphics.Rect;
|
||||
import android.os.Build;
|
||||
|
@ -170,6 +171,27 @@ public final class Device {
|
|||
Ln.i("Device screen turned " + (mode == Device.POWER_MODE_OFF ? "off" : "on"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable auto-rotation (if enabled), set the screen rotation and re-enable auto-rotation (if it was enabled).
|
||||
*/
|
||||
public void rotateDevice() {
|
||||
WindowManager wm = serviceManager.getWindowManager();
|
||||
|
||||
boolean accelerometerRotation = !wm.isRotationFrozen();
|
||||
|
||||
int currentRotation = wm.getRotation();
|
||||
int newRotation = (currentRotation & 1) ^ 1; // 0->1, 1->0, 2->1, 3->0
|
||||
String newRotationString = newRotation == 0 ? "portrait" : "landscape";
|
||||
|
||||
Ln.i("Device rotation requested: " + newRotationString);
|
||||
wm.freezeRotation(newRotation);
|
||||
|
||||
// restore auto-rotate if necessary
|
||||
if (accelerometerRotation) {
|
||||
wm.thawRotation();
|
||||
}
|
||||
}
|
||||
|
||||
static Rect flipRect(Rect crop) {
|
||||
return new Rect(crop.top, crop.left, crop.bottom, crop.right);
|
||||
}
|
||||
|
|
|
@ -1,28 +1,95 @@
|
|||
package com.genymobile.scrcpy.wrappers;
|
||||
|
||||
import com.genymobile.scrcpy.Ln;
|
||||
|
||||
import android.os.IInterface;
|
||||
import android.view.IRotationWatcher;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
public final class WindowManager {
|
||||
private final IInterface manager;
|
||||
private Method getRotationMethod;
|
||||
private Method freezeRotationMethod;
|
||||
private Method isRotationFrozenMethod;
|
||||
private Method thawRotationMethod;
|
||||
|
||||
public WindowManager(IInterface manager) {
|
||||
this.manager = manager;
|
||||
}
|
||||
|
||||
public int getRotation() {
|
||||
try {
|
||||
private Method getGetRotationMethod() throws NoSuchMethodException {
|
||||
if (getRotationMethod == null) {
|
||||
Class<?> cls = manager.getClass();
|
||||
try {
|
||||
// method changed since this commit:
|
||||
// https://android.googlesource.com/platform/frameworks/base/+/8ee7285128c3843401d4c4d0412cd66e86ba49e3%5E%21/#F2
|
||||
return (Integer) cls.getMethod("getDefaultDisplayRotation").invoke(manager);
|
||||
getRotationMethod = cls.getMethod("getDefaultDisplayRotation");
|
||||
} catch (NoSuchMethodException e) {
|
||||
// old version
|
||||
return (Integer) cls.getMethod("getRotation").invoke(manager);
|
||||
getRotationMethod = cls.getMethod("getRotation");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
return getRotationMethod;
|
||||
}
|
||||
|
||||
private Method getFreezeRotationMethod() throws NoSuchMethodException {
|
||||
if (freezeRotationMethod == null) {
|
||||
freezeRotationMethod = manager.getClass().getMethod("freezeRotation", int.class);
|
||||
}
|
||||
return freezeRotationMethod;
|
||||
}
|
||||
|
||||
private Method getIsRotationFrozenMethod() throws NoSuchMethodException {
|
||||
if (isRotationFrozenMethod == null) {
|
||||
isRotationFrozenMethod = manager.getClass().getMethod("isRotationFrozen");
|
||||
}
|
||||
return isRotationFrozenMethod;
|
||||
}
|
||||
|
||||
private Method getThawRotationMethod() throws NoSuchMethodException {
|
||||
if (thawRotationMethod == null) {
|
||||
thawRotationMethod = manager.getClass().getMethod("thawRotation");
|
||||
}
|
||||
return thawRotationMethod;
|
||||
}
|
||||
|
||||
public int getRotation() {
|
||||
try {
|
||||
Method method = getGetRotationMethod();
|
||||
return (int) method.invoke(manager);
|
||||
} catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException e) {
|
||||
Ln.e("Could not invoke method", e);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
public void freezeRotation(int rotation) {
|
||||
try {
|
||||
Method method = getFreezeRotationMethod();
|
||||
method.invoke(manager, rotation);
|
||||
} catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException e) {
|
||||
Ln.e("Could not invoke method", e);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isRotationFrozen() {
|
||||
try {
|
||||
Method method = getIsRotationFrozenMethod();
|
||||
return (boolean) method.invoke(manager);
|
||||
} catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException e) {
|
||||
Ln.e("Could not invoke method", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public void thawRotation() {
|
||||
try {
|
||||
Method method = getThawRotationMethod();
|
||||
method.invoke(manager);
|
||||
} catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException e) {
|
||||
Ln.e("Could not invoke method", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -240,6 +240,22 @@ public class ControlMessageReaderTest {
|
|||
Assert.assertEquals(Device.POWER_MODE_NORMAL, event.getAction());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParseRotateDevice() throws IOException {
|
||||
ControlMessageReader reader = new ControlMessageReader();
|
||||
|
||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||
DataOutputStream dos = new DataOutputStream(bos);
|
||||
dos.writeByte(ControlMessage.TYPE_ROTATE_DEVICE);
|
||||
|
||||
byte[] packet = bos.toByteArray();
|
||||
|
||||
reader.readFrom(new ByteArrayInputStream(packet));
|
||||
ControlMessage event = reader.next();
|
||||
|
||||
Assert.assertEquals(ControlMessage.TYPE_ROTATE_DEVICE, event.getType());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMultiEvents() throws IOException {
|
||||
ControlMessageReader reader = new ControlMessageReader();
|
||||
|
|
Loading…
Reference in a new issue