Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Configure clean up actions dynamically #4649

Merged
merged 1 commit into from
Feb 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
218 changes: 86 additions & 132 deletions server/src/main/java/com/genymobile/scrcpy/CleanUp.java
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
package com.genymobile.scrcpy;

import android.os.Parcel;
import android.os.Parcelable;
import android.util.Base64;

import java.io.File;
import java.io.IOException;
import java.io.OutputStream;

/**
* Handle the cleanup of scrcpy, even if the main process is killed.
Expand All @@ -14,127 +11,59 @@
*/
public final class CleanUp {

// A simple struct to be passed from the main process to the cleanup process
public static class Config implements Parcelable {

public static final Creator<Config> CREATOR = new Creator<Config>() {
@Override
public Config createFromParcel(Parcel in) {
return new Config(in);
}

@Override
public Config[] newArray(int size) {
return new Config[size];
}
};

private static final int FLAG_DISABLE_SHOW_TOUCHES = 1;
private static final int FLAG_RESTORE_NORMAL_POWER_MODE = 2;
private static final int FLAG_POWER_OFF_SCREEN = 4;

private int displayId;

// Restore the value (between 0 and 7), -1 to not restore
// <https://developer.android.com/reference/android/provider/Settings.Global#STAY_ON_WHILE_PLUGGED_IN>
private int restoreStayOn = -1;

private boolean disableShowTouches;
private boolean restoreNormalPowerMode;
private boolean powerOffScreen;

public Config() {
// Default constructor, the fields are initialized by CleanUp.configure()
}

protected Config(Parcel in) {
displayId = in.readInt();
restoreStayOn = in.readInt();
byte options = in.readByte();
disableShowTouches = (options & FLAG_DISABLE_SHOW_TOUCHES) != 0;
restoreNormalPowerMode = (options & FLAG_RESTORE_NORMAL_POWER_MODE) != 0;
powerOffScreen = (options & FLAG_POWER_OFF_SCREEN) != 0;
}

@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(displayId);
dest.writeInt(restoreStayOn);
byte options = 0;
if (disableShowTouches) {
options |= FLAG_DISABLE_SHOW_TOUCHES;
}
if (restoreNormalPowerMode) {
options |= FLAG_RESTORE_NORMAL_POWER_MODE;
}
if (powerOffScreen) {
options |= FLAG_POWER_OFF_SCREEN;
}
dest.writeByte(options);
}
private static final int MSG_TYPE_MASK = 0b11;
private static final int MSG_TYPE_RESTORE_STAY_ON = 0;
private static final int MSG_TYPE_DISABLE_SHOW_TOUCHES = 1;
private static final int MSG_TYPE_RESTORE_NORMAL_POWER_MODE = 2;
private static final int MSG_TYPE_POWER_OFF_SCREEN = 3;

private boolean hasWork() {
return disableShowTouches || restoreStayOn != -1 || restoreNormalPowerMode || powerOffScreen;
}
private static final int MSG_PARAM_SHIFT = 2;

@Override
public int describeContents() {
return 0;
}
private final OutputStream out;

byte[] serialize() {
Parcel parcel = Parcel.obtain();
writeToParcel(parcel, 0);
byte[] bytes = parcel.marshall();
parcel.recycle();
return bytes;
}
public CleanUp(OutputStream out) {
this.out = out;
}

static Config deserialize(byte[] bytes) {
Parcel parcel = Parcel.obtain();
parcel.unmarshall(bytes, 0, bytes.length);
parcel.setDataPosition(0);
return CREATOR.createFromParcel(parcel);
}
public static CleanUp configure(int displayId) throws IOException {
String[] cmd = {"app_process", "/", CleanUp.class.getName(), String.valueOf(displayId)};

static Config fromBase64(String base64) {
byte[] bytes = Base64.decode(base64, Base64.NO_WRAP);
return deserialize(bytes);
}
ProcessBuilder builder = new ProcessBuilder(cmd);
builder.environment().put("CLASSPATH", Server.SERVER_PATH);
Process process = builder.start();
return new CleanUp(process.getOutputStream());
}

String toBase64() {
byte[] bytes = serialize();
return Base64.encodeToString(bytes, Base64.NO_WRAP);
private boolean sendMessage(int type, int param) {
assert (type & ~MSG_TYPE_MASK) == 0;
int msg = type | param << MSG_PARAM_SHIFT;
try {
out.write(msg);
out.flush();
return true;
} catch (IOException e) {
Ln.w("Could not configure cleanup (type=" + type + ", param=" + param + ")", e);
return false;
}
}

private CleanUp() {
// not instantiable
public boolean setRestoreStayOn(int restoreValue) {
// Restore the value (between 0 and 7), -1 to not restore
// <https://developer.android.com/reference/android/provider/Settings.Global#STAY_ON_WHILE_PLUGGED_IN>
assert restoreValue >= -1 && restoreValue <= 7;
return sendMessage(MSG_TYPE_RESTORE_STAY_ON, restoreValue & 0b1111);
}

public static void configure(int displayId, int restoreStayOn, boolean disableShowTouches, boolean restoreNormalPowerMode, boolean powerOffScreen)
throws IOException {
Config config = new Config();
config.displayId = displayId;
config.disableShowTouches = disableShowTouches;
config.restoreStayOn = restoreStayOn;
config.restoreNormalPowerMode = restoreNormalPowerMode;
config.powerOffScreen = powerOffScreen;

if (config.hasWork()) {
startProcess(config);
} else {
// There is no additional clean up to do when scrcpy dies
unlinkSelf();
}
public boolean setDisableShowTouches(boolean disableOnExit) {
return sendMessage(MSG_TYPE_DISABLE_SHOW_TOUCHES, disableOnExit ? 1 : 0);
}

private static void startProcess(Config config) throws IOException {
String[] cmd = {"app_process", "/", CleanUp.class.getName(), config.toBase64()};
public boolean setRestoreNormalPowerMode(boolean restoreOnExit) {
return sendMessage(MSG_TYPE_RESTORE_NORMAL_POWER_MODE, restoreOnExit ? 1 : 0);
}

ProcessBuilder builder = new ProcessBuilder(cmd);
builder.environment().put("CLASSPATH", Server.SERVER_PATH);
builder.start();
public boolean setPowerOffScreen(boolean powerOffScreenOnExit) {
return sendMessage(MSG_TYPE_POWER_OFF_SCREEN, powerOffScreenOnExit ? 1 : 0);
}

public static void unlinkSelf() {
Expand All @@ -148,41 +77,66 @@ public static void unlinkSelf() {
public static void main(String... args) {
unlinkSelf();

int displayId = Integer.parseInt(args[0]);

int restoreStayOn = -1;
boolean disableShowTouches = false;
boolean restoreNormalPowerMode = false;
boolean powerOffScreen = false;

try {
// Wait for the server to die
System.in.read();
int msg;
while ((msg = System.in.read()) != -1) {
int type = msg & MSG_TYPE_MASK;
int param = msg >> MSG_PARAM_SHIFT;
switch (type) {
case MSG_TYPE_RESTORE_STAY_ON:
restoreStayOn = param > 7 ? -1 : param;
break;
case MSG_TYPE_DISABLE_SHOW_TOUCHES:
disableShowTouches = param != 0;
break;
case MSG_TYPE_RESTORE_NORMAL_POWER_MODE:
restoreNormalPowerMode = param != 0;
break;
case MSG_TYPE_POWER_OFF_SCREEN:
powerOffScreen = param != 0;
break;
default:
Ln.w("Unexpected msg type: " + type);
break;
}
}
} catch (IOException e) {
// Expected when the server is dead
}

Ln.i("Cleaning up");

Config config = Config.fromBase64(args[0]);

if (config.disableShowTouches || config.restoreStayOn != -1) {
if (config.disableShowTouches) {
Ln.i("Disabling \"show touches\"");
try {
Settings.putValue(Settings.TABLE_SYSTEM, "show_touches", "0");
} catch (SettingsException e) {
Ln.e("Could not restore \"show_touches\"", e);
}
if (disableShowTouches) {
Ln.i("Disabling \"show touches\"");
try {
Settings.putValue(Settings.TABLE_SYSTEM, "show_touches", "0");
} catch (SettingsException e) {
Ln.e("Could not restore \"show_touches\"", e);
}
if (config.restoreStayOn != -1) {
Ln.i("Restoring \"stay awake\"");
try {
Settings.putValue(Settings.TABLE_GLOBAL, "stay_on_while_plugged_in", String.valueOf(config.restoreStayOn));
} catch (SettingsException e) {
Ln.e("Could not restore \"stay_on_while_plugged_in\"", e);
}
}

if (restoreStayOn != -1) {
Ln.i("Restoring \"stay awake\"");
try {
Settings.putValue(Settings.TABLE_GLOBAL, "stay_on_while_plugged_in", String.valueOf(restoreStayOn));
} catch (SettingsException e) {
Ln.e("Could not restore \"stay_on_while_plugged_in\"", e);
}
}

if (Device.isScreenOn()) {
if (config.powerOffScreen) {
if (powerOffScreen) {
Ln.i("Power off screen");
Device.powerOffScreen(config.displayId);
} else if (config.restoreNormalPowerMode) {
Device.powerOffScreen(displayId);
} else if (restoreNormalPowerMode) {
Ln.i("Restoring normal power mode");
Device.setScreenPowerMode(Device.POWER_MODE_NORMAL);
}
Expand Down
8 changes: 7 additions & 1 deletion server/src/main/java/com/genymobile/scrcpy/Controller.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ public class Controller implements AsyncProcessor {

private final Device device;
private final DesktopConnection connection;
private final CleanUp cleanUp;
private final DeviceMessageSender sender;
private final boolean clipboardAutosync;
private final boolean powerOn;
Expand All @@ -41,9 +42,10 @@ public class Controller implements AsyncProcessor {

private boolean keepPowerModeOff;

public Controller(Device device, DesktopConnection connection, boolean clipboardAutosync, boolean powerOn) {
public Controller(Device device, DesktopConnection connection, CleanUp cleanUp, boolean clipboardAutosync, boolean powerOn) {
this.device = device;
this.connection = connection;
this.cleanUp = cleanUp;
this.clipboardAutosync = clipboardAutosync;
this.powerOn = powerOn;
initPointers();
Expand Down Expand Up @@ -170,6 +172,10 @@ private void handleEvent() throws IOException {
if (setPowerModeOk) {
keepPowerModeOff = mode == Device.POWER_MODE_OFF;
Ln.i("Device screen turned " + (mode == Device.POWER_MODE_OFF ? "off" : "on"));
if (cleanUp != null) {
boolean mustRestoreOnExit = mode != Device.POWER_MODE_NORMAL;
cleanUp.setRestoreNormalPowerMode(mustRestoreOnExit);
}
}
}
break;
Expand Down
Loading