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

server: add root support #4947

Open
wants to merge 1 commit into
base: dev
Choose a base branch
from
Open
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
96 changes: 70 additions & 26 deletions server/src/main/java/com/genymobile/scrcpy/ScreenCapture.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,20 @@
import com.genymobile.scrcpy.wrappers.SurfaceControl;

import android.graphics.Rect;
import android.hardware.display.DisplayManager;
import android.hardware.display.VirtualDisplay;
import android.hardware.display.VirtualDisplayConfig;
import android.os.Build;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.system.ErrnoException;
import android.system.Os;
import android.view.Surface;

import java.io.IOException;
import java.util.concurrent.CountDownLatch;

public class ScreenCapture extends SurfaceCapture implements Device.RotationListener, Device.FoldListener {

private final Device device;
Expand All @@ -26,15 +35,8 @@ public void init() {
}

@Override
public void start(Surface surface) {
ScreenInfo screenInfo = device.getScreenInfo();
Rect contentRect = screenInfo.getContentRect();

// does not include the locked video orientation
Rect unlockedVideoRect = screenInfo.getUnlockedVideoSize().toRect();
int videoRotation = screenInfo.getVideoRotation();
int layerStack = device.getLayerStack();

@SuppressWarnings("deprecation")
public void start(Surface surface) throws IOException {
if (display != null) {
SurfaceControl.destroyDisplay(display);
display = null;
Expand All @@ -43,22 +45,59 @@ public void start(Surface surface) {
virtualDisplay.release();
virtualDisplay = null;
}
if (Os.getuid() == 0) {
Handler handler = new Handler(Looper.getMainLooper());
CountDownLatch latch = new CountDownLatch(1);
handler.post(() -> {
try {
Os.seteuid(0);
createDisplay(surface);
Os.seteuid(2000);
} catch (ErrnoException ignored) {
}
latch.countDown();
});
try {
latch.await();
} catch (InterruptedException e) {
throw new IOException(e);
}
} else {
createDisplay(surface);
}
}

private void createDisplay(Surface surface) {
ScreenInfo screenInfo = device.getScreenInfo();
Rect contentRect = screenInfo.getContentRect();

// does not include the locked video orientation
Rect unlockedVideoRect = screenInfo.getUnlockedVideoSize().toRect();
int videoRotation = screenInfo.getVideoRotation();
int layerStack = device.getLayerStack();

try {
display = createDisplay();
setDisplaySurface(display, surface, videoRotation, contentRect, unlockedVideoRect, layerStack);
Ln.d("Display: using SurfaceControl API");
} catch (Exception surfaceControlException) {
Rect videoRect = screenInfo.getVideoSize().toRect();
try {
virtualDisplay = ServiceManager.getDisplayManager()
.createVirtualDisplay("scrcpy", videoRect.width(), videoRect.height(), device.getDisplayId(), surface);
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.TIRAMISU) {
display = createDisplay();
setDisplaySurface(display, surface, videoRotation, contentRect, unlockedVideoRect, layerStack);
Ln.d("Display: using SurfaceControl API");
} else {
int flags = DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR;
if (Os.getuid() == 0) {
flags |= DisplayManager.VIRTUAL_DISPLAY_FLAG_SECURE;
}
VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder("scrcpy",
unlockedVideoRect.width(), unlockedVideoRect.height(), 1 /* densityDpi */)
.setFlags(flags)
.setSurface(surface);
//noinspection JavaReflectionMemberAccess
builder.getClass().getMethod("setDisplayIdToMirror", int.class).invoke(builder, device.getDisplayId());
virtualDisplay = ServiceManager.getDisplayManager().createVirtualDisplay(FakeContext.get(), builder.build());
Ln.d("Display: using DisplayManager API");
} catch (Exception displayManagerException) {
Ln.e("Could not create display using SurfaceControl", surfaceControlException);
Ln.e("Could not create display using DisplayManager", displayManagerException);
throw new AssertionError("Could not create display");
}
} catch (Exception e) {
Ln.e("Could not create display", e);
throw new AssertionError("Could not create display");
}
}

Expand All @@ -69,6 +108,9 @@ public void release() {
if (display != null) {
SurfaceControl.destroyDisplay(display);
}
if (virtualDisplay != null) {
virtualDisplay.release();
}
}

@Override
Expand All @@ -93,11 +135,13 @@ public void onRotationChanged(int rotation) {
}

private static IBinder createDisplay() throws Exception {
// Since Android 12 (preview), secure displays could not be created with shell permissions anymore.
// On Android 12 preview, SDK_INT is still R (not S), but CODENAME is "S".
boolean secure = Build.VERSION.SDK_INT < Build.VERSION_CODES.R || (Build.VERSION.SDK_INT == Build.VERSION_CODES.R && !"S".equals(
Build.VERSION.CODENAME));
return SurfaceControl.createDisplay("scrcpy", secure);
// Since Android 12, secure displays could not be created with shell permissions anymore.
// Since Android 14, this method is deprecated.
IBinder display = SurfaceControl.createDisplay("scrcpy", true);
if (display == null) {
display = SurfaceControl.createDisplay("scrcpy", false);
}
return display;
}

private static void setDisplaySurface(IBinder display, Surface surface, int orientation, Rect deviceRect, Rect displayRect, int layerStack) {
Expand Down
29 changes: 12 additions & 17 deletions server/src/main/java/com/genymobile/scrcpy/Server.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import android.os.BatteryManager;
import android.os.Build;
import android.os.Looper;
import android.system.Os;

import java.io.File;
import java.io.IOException;
Expand All @@ -20,29 +22,15 @@ public final class Server {

private static class Completion {
private int running;
private boolean fatalError;

Completion(int running) {
this.running = running;
}

synchronized void addCompleted(boolean fatalError) {
--running;
if (fatalError) {
this.fatalError = true;
}
if (running == 0 || this.fatalError) {
notify();
}
}

synchronized void await() {
try {
while (running > 0 && !fatalError) {
wait();
}
} catch (InterruptedException e) {
// ignore
if (running == 0 || fatalError) {
Looper.getMainLooper().quitSafely();
}
}
}
Expand Down Expand Up @@ -176,7 +164,7 @@ private static void scrcpy(Options options) throws IOException, ConfigurationExc
});
}

completion.await();
Looper.loop();
} finally {
if (initThread != null) {
initThread.interrupt();
Expand Down Expand Up @@ -223,11 +211,18 @@ public static void main(String... args) {
}
}

@SuppressWarnings("deprecation")
private static void internalMain(String... args) throws Exception {
Thread.setDefaultUncaughtExceptionHandler((t, e) -> {
Ln.e("Exception on thread " + t, e);
t.interrupt();
});

// Binder IPC uses the eUID of the main thread to determine the remote process's UID
if (Os.getuid() == 0) {
Os.seteuid(2000);
}

Options options = Options.parse(args);

Ln.disableSystemStreams();
Expand Down
12 changes: 7 additions & 5 deletions server/src/main/java/com/genymobile/scrcpy/Workarounds.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,9 @@ public final class Workarounds {
private static final Object ACTIVITY_THREAD;

static {
prepareMainLooper();

try {
prepareMainLooper();

// ActivityThread activityThread = new ActivityThread();
ACTIVITY_THREAD_CLASS = Class.forName("android.app.ActivityThread");
Constructor<?> activityThreadConstructor = ACTIVITY_THREAD_CLASS.getDeclaredConstructor();
Expand Down Expand Up @@ -106,8 +106,7 @@ public static void apply(boolean audio, boolean camera) {
}
}

@SuppressWarnings("deprecation")
private static void prepareMainLooper() {
private static void prepareMainLooper() throws ReflectiveOperationException {
// 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>
Expand All @@ -116,7 +115,10 @@ private static void prepareMainLooper() {
// "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();
Looper.prepare();
Field mainLooper = Looper.class.getDeclaredField("sMainLooper");
mainLooper.setAccessible(true);
mainLooper.set(null, Looper.myLooper());
}

private static void fillAppInfo() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,17 @@
import com.genymobile.scrcpy.Size;

import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.content.Context;
import android.hardware.display.VirtualDisplay;
import android.hardware.display.VirtualDisplayConfig;
import android.media.projection.MediaProjection;
import android.os.Build;
import android.view.Display;
import android.view.Surface;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.concurrent.Executor;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

Expand Down Expand Up @@ -112,16 +117,20 @@ public int[] getDisplayIds() {
}
}

@TargetApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
private Method getCreateVirtualDisplayMethod() throws NoSuchMethodException {
if (createVirtualDisplayMethod == null) {
createVirtualDisplayMethod = android.hardware.display.DisplayManager.class
.getMethod("createVirtualDisplay", String.class, int.class, int.class, int.class, Surface.class);
createVirtualDisplayMethod = manager.getClass()
.getMethod("createVirtualDisplay", Context.class,
MediaProjection.class, VirtualDisplayConfig.class,
VirtualDisplay.Callback.class, Executor.class);
}
return createVirtualDisplayMethod;
}

public VirtualDisplay createVirtualDisplay(String name, int width, int height, int displayIdToMirror, Surface surface) throws Exception {
@TargetApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
public VirtualDisplay createVirtualDisplay(Context context, VirtualDisplayConfig virtualDisplayConfig) throws Exception {
Method method = getCreateVirtualDisplayMethod();
return (VirtualDisplay) method.invoke(null, name, width, height, displayIdToMirror, surface);
return (VirtualDisplay) method.invoke(manager, context, null, virtualDisplayConfig, null, null);
}
}