diff --git a/server/src/main/java/com/genymobile/scrcpy/DesktopConnection.java b/server/src/main/java/com/genymobile/scrcpy/DesktopConnection.java index c3408fffe8..547fc54eba 100644 --- a/server/src/main/java/com/genymobile/scrcpy/DesktopConnection.java +++ b/server/src/main/java/com/genymobile/scrcpy/DesktopConnection.java @@ -3,6 +3,7 @@ import android.net.LocalServerSocket; import android.net.LocalSocket; import android.net.LocalSocketAddress; +import android.os.ParcelFileDescriptor; import java.io.Closeable; import java.io.FileDescriptor; @@ -10,6 +11,8 @@ import java.io.InputStream; import java.io.OutputStream; import java.nio.charset.StandardCharsets; +import java.net.ServerSocket; +import java.net.Socket; public final class DesktopConnection implements Closeable { @@ -18,12 +21,15 @@ public final class DesktopConnection implements Closeable { private static final String SOCKET_NAME_PREFIX = "scrcpy"; private final LocalSocket videoSocket; + private final Socket videoExtSocket; private final FileDescriptor videoFd; private final LocalSocket audioSocket; + private final Socket audioExtSocket; private final FileDescriptor audioFd; private final LocalSocket controlSocket; + private final Socket controlExtSocket; private final InputStream controlInputStream; private final OutputStream controlOutputStream; @@ -31,9 +37,14 @@ public final class DesktopConnection implements Closeable { private final DeviceMessageWriter writer = new DeviceMessageWriter(); private DesktopConnection(LocalSocket videoSocket, LocalSocket audioSocket, LocalSocket controlSocket) throws IOException { + this.videoExtSocket = null; + this.audioExtSocket = null; + this.controlExtSocket = null; + this.videoSocket = videoSocket; this.controlSocket = controlSocket; this.audioSocket = audioSocket; + if (controlSocket != null) { controlInputStream = controlSocket.getInputStream(); controlOutputStream = controlSocket.getOutputStream(); @@ -41,10 +52,44 @@ private DesktopConnection(LocalSocket videoSocket, LocalSocket audioSocket, Loca controlInputStream = null; controlOutputStream = null; } + videoFd = videoSocket != null ? videoSocket.getFileDescriptor() : null; + audioFd = audioSocket != null ? audioSocket.getFileDescriptor() : null; } + private DesktopConnection(Socket videoExtSocket, Socket audioExtSocket, Socket controlExtSocket) throws IOException { + this.videoSocket = null; + this.controlSocket = null; + this.audioSocket = null; + + this.videoExtSocket = videoExtSocket; + this.audioExtSocket = audioExtSocket; + this.controlExtSocket = controlExtSocket; + + if (controlExtSocket != null) { + controlInputStream = controlExtSocket.getInputStream(); + controlOutputStream = controlExtSocket.getOutputStream(); + } else { + controlInputStream = null; + controlOutputStream = null; + } + + if (videoExtSocket != null) { + ParcelFileDescriptor pfd = ParcelFileDescriptor.fromSocket(videoExtSocket); + videoFd = pfd.getFileDescriptor(); + } else { + videoFd = null; + } + + if (audioExtSocket != null) { + ParcelFileDescriptor pfd = ParcelFileDescriptor.fromSocket(audioExtSocket); + audioFd = pfd.getFileDescriptor(); + } else { + audioFd = null; + } + } + private static LocalSocket connect(String abstractName) throws IOException { LocalSocket localSocket = new LocalSocket(); localSocket.connect(new LocalSocketAddress(abstractName)); @@ -60,40 +105,76 @@ private static String getSocketName(int scid) { return SOCKET_NAME_PREFIX + String.format("_%08x", scid); } - public static DesktopConnection open(int scid, boolean tunnelForward, boolean video, boolean audio, boolean control, boolean sendDummyByte) + public static DesktopConnection open(int scid, boolean tunnelForward, boolean video, boolean audio, boolean control, boolean sendDummyByte, int serverPort) throws IOException { String socketName = getSocketName(scid); LocalSocket videoSocket = null; LocalSocket audioSocket = null; LocalSocket controlSocket = null; + + Socket videoExtSocket = null; + Socket audioExtSocket = null; + Socket controlExtSocket = null; + try { if (tunnelForward) { - try (LocalServerSocket localServerSocket = new LocalServerSocket(socketName)) { - if (video) { - videoSocket = localServerSocket.accept(); - if (sendDummyByte) { - // send one byte so the client may read() to detect a connection error - videoSocket.getOutputStream().write(0); - sendDummyByte = false; + if (serverPort == -1) { + try (LocalServerSocket localServerSocket = new LocalServerSocket(socketName)) { + if (video) { + videoSocket = localServerSocket.accept(); + if (sendDummyByte) { + // send one byte so the client may read() to detect a connection error + videoSocket.getOutputStream().write(0); + sendDummyByte = false; + } } - } - if (audio) { - audioSocket = localServerSocket.accept(); - if (sendDummyByte) { - // send one byte so the client may read() to detect a connection error - audioSocket.getOutputStream().write(0); - sendDummyByte = false; + if (audio) { + audioSocket = localServerSocket.accept(); + if (sendDummyByte) { + // send one byte so the client may read() to detect a connection error + audioSocket.getOutputStream().write(0); + sendDummyByte = false; + } + } + if (control) { + controlSocket = localServerSocket.accept(); + if (sendDummyByte) { + // send one byte so the client may read() to detect a connection error + controlSocket.getOutputStream().write(0); + sendDummyByte = false; + } } } - if (control) { - controlSocket = localServerSocket.accept(); - if (sendDummyByte) { - // send one byte so the client may read() to detect a connection error - controlSocket.getOutputStream().write(0); - sendDummyByte = false; + } else { + try (ServerSocket serverSocket = new ServerSocket(serverPort)) { + if (video) { + videoExtSocket = serverSocket.accept(); + if (sendDummyByte) { + // send one byte so the client may read() to detect a connection error + videoExtSocket.getOutputStream().write(0); + sendDummyByte = false; + } + } + if (audio) { + audioExtSocket = serverSocket.accept(); + if (sendDummyByte) { + // send one byte so the client may read() to detect a connection error + audioExtSocket.getOutputStream().write(0); + sendDummyByte = false; + } + } + if (control) { + controlExtSocket = serverSocket.accept(); + if (sendDummyByte) { + // send one byte so the client may read() to detect a connection error + controlExtSocket.getOutputStream().write(0); + sendDummyByte = false; + } } } + + return new DesktopConnection(videoExtSocket, audioExtSocket, controlExtSocket); } } else { if (video) { @@ -116,6 +197,15 @@ public static DesktopConnection open(int scid, boolean tunnelForward, boolean vi if (controlSocket != null) { controlSocket.close(); } + if (videoExtSocket != null) { + videoExtSocket.close(); + } + if (audioExtSocket != null) { + audioExtSocket.close(); + } + if (controlExtSocket != null) { + controlExtSocket.close(); + } throw e; } @@ -132,6 +222,16 @@ private LocalSocket getFirstSocket() { return controlSocket; } + private Socket getFirstExtSocket() { + if (videoExtSocket != null) { + return videoExtSocket; + } + if (audioExtSocket != null) { + return audioExtSocket; + } + return controlExtSocket; + } + public void close() throws IOException { if (videoSocket != null) { videoSocket.shutdownInput(); @@ -148,6 +248,21 @@ public void close() throws IOException { controlSocket.shutdownOutput(); controlSocket.close(); } + if (videoExtSocket != null) { + videoExtSocket.shutdownInput(); + videoExtSocket.shutdownOutput(); + videoExtSocket.close(); + } + if (audioExtSocket != null) { + audioExtSocket.shutdownInput(); + audioExtSocket.shutdownOutput(); + audioExtSocket.close(); + } + if (controlExtSocket != null) { + controlExtSocket.shutdownInput(); + controlExtSocket.shutdownOutput(); + controlExtSocket.close(); + } } public void sendDeviceMeta(String deviceName) throws IOException { @@ -158,8 +273,15 @@ public void sendDeviceMeta(String deviceName) throws IOException { System.arraycopy(deviceNameBytes, 0, buffer, 0, len); // byte[] are always 0-initialized in java, no need to set '\0' explicitly - FileDescriptor fd = getFirstSocket().getFileDescriptor(); - IO.writeFully(fd, buffer, 0, buffer.length); + LocalSocket firstSocket = getFirstSocket(); + if (firstSocket == null) { + ParcelFileDescriptor pfd = ParcelFileDescriptor.fromSocket(getFirstExtSocket()); + FileDescriptor fd = pfd.getFileDescriptor(); + IO.writeFully(fd, buffer, 0, buffer.length); + } else { + FileDescriptor fd = getFirstSocket().getFileDescriptor(); + IO.writeFully(fd, buffer, 0, buffer.length); + } } public FileDescriptor getVideoFd() { diff --git a/server/src/main/java/com/genymobile/scrcpy/Options.java b/server/src/main/java/com/genymobile/scrcpy/Options.java index aab6fce86c..a686de80c1 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Options.java +++ b/server/src/main/java/com/genymobile/scrcpy/Options.java @@ -45,6 +45,8 @@ public class Options { private boolean sendDummyByte = true; // write a byte on start to detect connection issues private boolean sendCodecMeta = true; // write the codec metadata before the stream + private int serverPort = -1; + public Ln.Level getLogLevel() { return logLevel; } @@ -177,6 +179,10 @@ public boolean getSendCodecMeta() { return sendCodecMeta; } + public int getServerPort() { + return serverPort; + } + @SuppressWarnings("MethodLength") public static Options parse(String... args) { if (args.length < 1) { @@ -327,6 +333,9 @@ public static Options parse(String... args) { options.sendCodecMeta = false; } break; + case "serverPort": + options.serverPort = Integer.parseInt(value); + break; default: Ln.w("Unknown server option: " + key); break; diff --git a/server/src/main/java/com/genymobile/scrcpy/Server.java b/server/src/main/java/com/genymobile/scrcpy/Server.java index 2e6e1d4a99..db81dce14b 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Server.java +++ b/server/src/main/java/com/genymobile/scrcpy/Server.java @@ -98,12 +98,13 @@ private static void scrcpy(Options options) throws IOException, ConfigurationExc boolean video = options.getVideo(); boolean audio = options.getAudio(); boolean sendDummyByte = options.getSendDummyByte(); + int serverPort = options.getServerPort(); Workarounds.apply(audio); List asyncProcessors = new ArrayList<>(); - DesktopConnection connection = DesktopConnection.open(scid, tunnelForward, video, audio, control, sendDummyByte); + DesktopConnection connection = DesktopConnection.open(scid, tunnelForward, video, audio, control, sendDummyByte, serverPort); try { if (options.getSendDeviceMeta()) { connection.sendDeviceMeta(Device.getDeviceName());