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

Add Socket for standalone server #4154

Open
wants to merge 2 commits into
base: master
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
168 changes: 145 additions & 23 deletions server/src/main/java/com/genymobile/scrcpy/DesktopConnection.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,16 @@
import android.net.LocalServerSocket;
import android.net.LocalSocket;
import android.net.LocalSocketAddress;
import android.os.ParcelFileDescriptor;

import java.io.Closeable;
import java.io.FileDescriptor;
import java.io.IOException;
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 {

Expand All @@ -18,33 +21,75 @@ 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;

private final ControlMessageReader reader = new ControlMessageReader();
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();
} else {
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));
Expand All @@ -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;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe this is not a good design, which adds unnecessary complexity and redundant branching judgments to the program. In order to implement Socket support, I'd be inclined to implement a Socket wrapper to provide a unified interface, and subsequently implement Socket and LocalSocket separately within it. however, your code contribution here is definitely a good enough PoC.


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) {
Expand All @@ -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;
}

Expand All @@ -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();
Expand All @@ -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 {
Expand All @@ -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() {
Expand Down
9 changes: 9 additions & 0 deletions server/src/main/java/com/genymobile/scrcpy/Options.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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;
Expand Down
3 changes: 2 additions & 1 deletion server/src/main/java/com/genymobile/scrcpy/Server.java
Original file line number Diff line number Diff line change
Expand Up @@ -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<AsyncProcessor> 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());
Expand Down