2017-12-12 15:12:07 +01:00
|
|
|
package com.genymobile.scrcpy;
|
|
|
|
|
|
2020-05-02 01:54:48 +02:00
|
|
|
import android.os.BatteryManager;
|
2019-11-30 10:44:49 +01:00
|
|
|
import android.os.Build;
|
2018-08-09 19:12:27 +02:00
|
|
|
|
2017-12-12 15:12:07 +01:00
|
|
|
import java.io.IOException;
|
2023-03-03 18:49:05 +01:00
|
|
|
import java.util.ArrayList;
|
2020-04-26 15:22:08 +03:00
|
|
|
import java.util.List;
|
2017-12-12 15:12:07 +01:00
|
|
|
|
2018-02-28 14:57:18 +01:00
|
|
|
public final class Server {
|
2018-02-07 18:06:23 +01:00
|
|
|
|
2018-02-28 14:57:18 +01:00
|
|
|
private Server() {
|
2018-02-07 18:06:23 +01:00
|
|
|
// not instantiable
|
|
|
|
|
}
|
2017-12-12 15:12:07 +01:00
|
|
|
|
2021-11-17 10:21:42 +01:00
|
|
|
private static void initAndCleanUp(Options options) {
|
2020-05-01 23:49:37 +02:00
|
|
|
boolean mustDisableShowTouchesOnCleanUp = false;
|
2020-05-02 01:54:48 +02:00
|
|
|
int restoreStayOn = -1;
|
2022-01-27 08:10:04 +01:00
|
|
|
boolean restoreNormalPowerMode = options.getControl(); // only restore power mode if control is enabled
|
2020-05-02 01:54:48 +02:00
|
|
|
if (options.getShowTouches() || options.getStayAwake()) {
|
2021-11-16 22:10:34 +01:00
|
|
|
if (options.getShowTouches()) {
|
2021-11-16 22:40:53 +01:00
|
|
|
try {
|
2022-10-02 17:39:23 +02:00
|
|
|
String oldValue = Settings.getAndPutValue(Settings.TABLE_SYSTEM, "show_touches", "1");
|
2021-11-16 22:40:53 +01:00
|
|
|
// If "show touches" was disabled, it must be disabled back on clean up
|
|
|
|
|
mustDisableShowTouchesOnCleanUp = !"1".equals(oldValue);
|
|
|
|
|
} catch (SettingsException e) {
|
|
|
|
|
Ln.e("Could not change \"show_touches\"", e);
|
|
|
|
|
}
|
2021-11-16 22:10:34 +01:00
|
|
|
}
|
2020-05-02 01:54:48 +02:00
|
|
|
|
2021-11-16 22:10:34 +01:00
|
|
|
if (options.getStayAwake()) {
|
|
|
|
|
int stayOn = BatteryManager.BATTERY_PLUGGED_AC | BatteryManager.BATTERY_PLUGGED_USB | BatteryManager.BATTERY_PLUGGED_WIRELESS;
|
|
|
|
|
try {
|
2022-10-02 17:39:23 +02:00
|
|
|
String oldValue = Settings.getAndPutValue(Settings.TABLE_GLOBAL, "stay_on_while_plugged_in", String.valueOf(stayOn));
|
2021-11-16 22:40:53 +01:00
|
|
|
try {
|
|
|
|
|
restoreStayOn = Integer.parseInt(oldValue);
|
|
|
|
|
if (restoreStayOn == stayOn) {
|
|
|
|
|
// No need to restore
|
|
|
|
|
restoreStayOn = -1;
|
|
|
|
|
}
|
|
|
|
|
} catch (NumberFormatException e) {
|
|
|
|
|
restoreStayOn = 0;
|
2020-05-02 01:54:48 +02:00
|
|
|
}
|
2021-11-16 22:40:53 +01:00
|
|
|
} catch (SettingsException e) {
|
|
|
|
|
Ln.e("Could not change \"stay_on_while_plugged_in\"", e);
|
2020-05-02 01:54:48 +02:00
|
|
|
}
|
2020-05-01 23:49:37 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-02-13 17:17:01 +01:00
|
|
|
if (options.getCleanup()) {
|
|
|
|
|
try {
|
|
|
|
|
CleanUp.configure(options.getDisplayId(), restoreStayOn, mustDisableShowTouchesOnCleanUp, restoreNormalPowerMode,
|
|
|
|
|
options.getPowerOffScreenOnClose());
|
|
|
|
|
} catch (IOException e) {
|
|
|
|
|
Ln.e("Could not configure cleanup", e);
|
|
|
|
|
}
|
2021-11-17 10:21:42 +01:00
|
|
|
}
|
2021-11-17 10:16:11 +01:00
|
|
|
}
|
|
|
|
|
|
2023-02-19 19:36:46 +01:00
|
|
|
private static void scrcpy(Options options) throws IOException, ConfigurationException {
|
2021-11-17 10:16:11 +01:00
|
|
|
Ln.i("Device: " + Build.MANUFACTURER + " " + Build.MODEL + " (Android " + Build.VERSION.RELEASE + ")");
|
|
|
|
|
final Device device = new Device(options);
|
|
|
|
|
|
2021-11-17 10:29:41 +01:00
|
|
|
Thread initThread = startInitThread(options);
|
2020-05-02 00:39:40 +02:00
|
|
|
|
2023-02-11 09:52:31 +01:00
|
|
|
int scid = options.getScid();
|
2018-03-12 08:35:51 +01:00
|
|
|
boolean tunnelForward = options.isTunnelForward();
|
2021-12-06 21:50:24 +01:00
|
|
|
boolean control = options.getControl();
|
2023-02-03 16:50:42 +01:00
|
|
|
boolean audio = options.getAudio();
|
2022-01-26 10:36:18 +01:00
|
|
|
boolean sendDummyByte = options.getSendDummyByte();
|
2020-04-26 15:22:08 +03:00
|
|
|
|
2023-01-31 22:54:34 +01:00
|
|
|
Workarounds.prepareMainLooper();
|
2023-01-27 20:13:37 +08:00
|
|
|
|
|
|
|
|
// Workarounds must be applied for Meizu phones:
|
|
|
|
|
// - <https://github.com/Genymobile/scrcpy/issues/240>
|
|
|
|
|
// - <https://github.com/Genymobile/scrcpy/issues/365>
|
|
|
|
|
// - <https://github.com/Genymobile/scrcpy/issues/2656>
|
|
|
|
|
//
|
|
|
|
|
// But only apply when strictly necessary, since workarounds can cause other issues:
|
|
|
|
|
// - <https://github.com/Genymobile/scrcpy/issues/940>
|
|
|
|
|
// - <https://github.com/Genymobile/scrcpy/issues/994>
|
2023-03-14 22:51:31 +01:00
|
|
|
if (Build.BRAND.equalsIgnoreCase("meizu")) {
|
|
|
|
|
Workarounds.fillAppInfo();
|
|
|
|
|
}
|
2023-01-27 20:13:37 +08:00
|
|
|
|
|
|
|
|
// Before Android 11, audio is not supported.
|
|
|
|
|
// Since Android 12, we can properly set a context on the AudioRecord.
|
2023-03-14 22:51:31 +01:00
|
|
|
// Only on Android 11 we must fill the application context for the AudioRecord to work.
|
|
|
|
|
if (audio && Build.VERSION.SDK_INT == Build.VERSION_CODES.R) {
|
|
|
|
|
Workarounds.fillAppContext();
|
2023-01-31 22:54:34 +01:00
|
|
|
}
|
|
|
|
|
|
2023-03-03 18:49:05 +01:00
|
|
|
List<AsyncProcessor> asyncProcessors = new ArrayList<>();
|
2023-02-19 15:59:05 +01:00
|
|
|
|
2023-04-09 19:12:39 +02:00
|
|
|
DesktopConnection connection = DesktopConnection.open(scid, tunnelForward, audio, control, sendDummyByte);
|
|
|
|
|
try {
|
2022-01-26 10:24:41 +01:00
|
|
|
if (options.getSendDeviceMeta()) {
|
2023-03-11 09:21:49 +01:00
|
|
|
connection.sendDeviceMeta(Device.getDeviceName());
|
2022-01-26 10:24:41 +01:00
|
|
|
}
|
2018-01-23 14:37:54 +01:00
|
|
|
|
2021-12-06 21:50:24 +01:00
|
|
|
if (control) {
|
2023-03-03 18:49:05 +01:00
|
|
|
Controller controller = new Controller(device, connection, options.getClipboardAutosync(), options.getPowerOn());
|
|
|
|
|
device.setClipboardListener(text -> controller.getSender().pushClipboardText(text));
|
|
|
|
|
asyncProcessors.add(controller);
|
2019-06-04 21:31:46 +02:00
|
|
|
}
|
2018-01-23 14:37:54 +01:00
|
|
|
|
2023-01-27 20:13:37 +08:00
|
|
|
if (audio) {
|
2023-03-03 21:14:28 +01:00
|
|
|
AudioCodec audioCodec = options.getAudioCodec();
|
2023-03-10 22:47:38 +01:00
|
|
|
Streamer audioStreamer = new Streamer(connection.getAudioFd(), audioCodec, options.getSendCodecMeta(),
|
2023-02-18 19:05:43 +01:00
|
|
|
options.getSendFrameMeta());
|
2023-03-03 21:14:28 +01:00
|
|
|
AsyncProcessor audioRecorder;
|
|
|
|
|
if (audioCodec == AudioCodec.RAW) {
|
|
|
|
|
audioRecorder = new AudioRawRecorder(audioStreamer);
|
|
|
|
|
} else {
|
|
|
|
|
audioRecorder = new AudioEncoder(audioStreamer, options.getAudioBitRate(), options.getAudioCodecOptions(),
|
|
|
|
|
options.getAudioEncoder());
|
|
|
|
|
}
|
2023-03-03 18:49:05 +01:00
|
|
|
asyncProcessors.add(audioRecorder);
|
2023-01-27 20:13:37 +08:00
|
|
|
}
|
|
|
|
|
|
2023-03-10 22:47:38 +01:00
|
|
|
Streamer videoStreamer = new Streamer(connection.getVideoFd(), options.getVideoCodec(), options.getSendCodecMeta(),
|
2023-02-20 21:19:36 +01:00
|
|
|
options.getSendFrameMeta());
|
2023-02-21 21:46:34 +01:00
|
|
|
ScreenEncoder screenEncoder = new ScreenEncoder(device, videoStreamer, options.getVideoBitRate(), options.getMaxFps(),
|
2023-02-22 22:44:01 +01:00
|
|
|
options.getVideoCodecOptions(), options.getVideoEncoder(), options.getDownsizeOnError());
|
2023-03-03 18:49:05 +01:00
|
|
|
|
|
|
|
|
for (AsyncProcessor asyncProcessor : asyncProcessors) {
|
|
|
|
|
asyncProcessor.start();
|
|
|
|
|
}
|
|
|
|
|
|
2017-12-12 15:12:07 +01:00
|
|
|
try {
|
2017-12-14 11:38:44 +01:00
|
|
|
// synchronous
|
2023-02-06 13:46:19 +01:00
|
|
|
screenEncoder.streamScreen();
|
2017-12-12 15:12:07 +01:00
|
|
|
} catch (IOException e) {
|
2023-02-17 08:32:59 +01:00
|
|
|
// Broken pipe is expected on close, because the socket is closed by the client
|
|
|
|
|
if (!IO.isBrokenPipe(e)) {
|
|
|
|
|
Ln.e("Video encoding error", e);
|
|
|
|
|
}
|
2023-02-19 15:59:05 +01:00
|
|
|
}
|
|
|
|
|
} finally {
|
|
|
|
|
Ln.d("Screen streaming stopped");
|
|
|
|
|
initThread.interrupt();
|
2023-03-03 18:49:05 +01:00
|
|
|
for (AsyncProcessor asyncProcessor : asyncProcessors) {
|
|
|
|
|
asyncProcessor.stop();
|
2023-02-19 15:59:05 +01:00
|
|
|
}
|
2023-02-07 22:58:46 +01:00
|
|
|
|
2023-02-19 15:59:05 +01:00
|
|
|
try {
|
|
|
|
|
initThread.join();
|
2023-03-03 18:49:05 +01:00
|
|
|
for (AsyncProcessor asyncProcessor : asyncProcessors) {
|
|
|
|
|
asyncProcessor.join();
|
2023-02-07 22:58:46 +01:00
|
|
|
}
|
2023-02-19 15:59:05 +01:00
|
|
|
} catch (InterruptedException e) {
|
|
|
|
|
// ignore
|
2017-12-12 15:12:07 +01:00
|
|
|
}
|
2023-04-09 19:12:39 +02:00
|
|
|
|
|
|
|
|
connection.close();
|
2017-12-12 15:12:07 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-11-17 10:29:41 +01:00
|
|
|
private static Thread startInitThread(final Options options) {
|
2023-01-27 22:16:36 +01:00
|
|
|
Thread thread = new Thread(() -> initAndCleanUp(options));
|
2021-11-17 10:29:41 +01:00
|
|
|
thread.start();
|
|
|
|
|
return thread;
|
|
|
|
|
}
|
|
|
|
|
|
2018-01-31 19:49:49 +01:00
|
|
|
public static void main(String... args) throws Exception {
|
2023-01-27 22:16:36 +01:00
|
|
|
Thread.setDefaultUncaughtExceptionHandler((t, e) -> {
|
|
|
|
|
Ln.e("Exception on thread " + t, e);
|
2018-01-31 19:50:45 +01:00
|
|
|
});
|
|
|
|
|
|
2023-04-09 19:52:01 +02:00
|
|
|
Options options = Options.parse(args);
|
2020-05-24 21:08:22 +02:00
|
|
|
|
|
|
|
|
Ln.initLogLevel(options.getLogLevel());
|
|
|
|
|
|
2023-02-23 23:10:15 +01:00
|
|
|
if (options.getListEncoders() || options.getListDisplays()) {
|
2023-02-22 23:15:15 +01:00
|
|
|
if (options.getCleanup()) {
|
|
|
|
|
CleanUp.unlinkSelf();
|
|
|
|
|
}
|
|
|
|
|
|
2023-02-23 23:10:15 +01:00
|
|
|
if (options.getListEncoders()) {
|
|
|
|
|
Ln.i(LogUtils.buildVideoEncoderListMessage());
|
|
|
|
|
Ln.i(LogUtils.buildAudioEncoderListMessage());
|
|
|
|
|
}
|
|
|
|
|
if (options.getListDisplays()) {
|
|
|
|
|
Ln.i(LogUtils.buildDisplayListMessage());
|
|
|
|
|
}
|
|
|
|
|
// Just print the requested data, do not mirror
|
2023-02-22 23:15:15 +01:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2023-02-19 19:36:46 +01:00
|
|
|
try {
|
|
|
|
|
scrcpy(options);
|
|
|
|
|
} catch (ConfigurationException e) {
|
|
|
|
|
// Do not print stack trace, a user-friendly error-message has already been logged
|
|
|
|
|
}
|
2017-12-12 15:12:07 +01:00
|
|
|
}
|
|
|
|
|
}
|