diff --git a/QtScrcpy/dialog.cpp b/QtScrcpy/dialog.cpp index 5388a1d..c7603df 100644 --- a/QtScrcpy/dialog.cpp +++ b/QtScrcpy/dialog.cpp @@ -105,7 +105,8 @@ void Dialog::on_startServerBtn_clicked() quint32 bitRate = ui->bitRateBox->currentText().trimmed().toUInt(); // this is ok that "native" toUshort is 0 quint16 videoSize = ui->videoSizeBox->currentText().trimmed().toUShort(); - m_videoForm = new VideoForm(ui->serialBox->currentText().trimmed(), videoSize, bitRate, absFilePath); + m_videoForm = new VideoForm(ui->serialBox->currentText().trimmed(), videoSize, bitRate, + absFilePath, ui->closeScreenCheck->isChecked()); if (ui->alwaysTopCheck->isChecked()) { m_videoForm->staysOnTop(); } @@ -238,3 +239,15 @@ void Dialog::on_alwaysTopCheck_stateChanged(int arg1) m_videoForm->staysOnTop(false); } } + +void Dialog::on_closeScreenCheck_stateChanged(int arg1) +{ + if (!m_videoForm) { + return; + } + if (ui->closeScreenCheck->isChecked()) { + m_videoForm->setScreenPowerMode(ControlMsg::SPM_OFF); + } else { + m_videoForm->setScreenPowerMode(ControlMsg::SPM_NORMAL); + } +} diff --git a/QtScrcpy/dialog.h b/QtScrcpy/dialog.h index 1d81f8d..43e1f63 100644 --- a/QtScrcpy/dialog.h +++ b/QtScrcpy/dialog.h @@ -43,6 +43,8 @@ private slots: void on_alwaysTopCheck_stateChanged(int arg1); + void on_closeScreenCheck_stateChanged(int arg1); + private: bool checkAdbRun(); void initUI(); diff --git a/QtScrcpy/dialog.ui b/QtScrcpy/dialog.ui index bf6b04b..18ceff5 100644 --- a/QtScrcpy/dialog.ui +++ b/QtScrcpy/dialog.ui @@ -230,6 +230,13 @@ + + + + close screen + + + diff --git a/QtScrcpy/inputcontrol/controlmsg.cpp b/QtScrcpy/inputcontrol/controlmsg.cpp index 9226ec4..45147ff 100644 --- a/QtScrcpy/inputcontrol/controlmsg.cpp +++ b/QtScrcpy/inputcontrol/controlmsg.cpp @@ -12,21 +12,21 @@ ControlMsg::ControlMsg(ControlMsgType controlMsgType) ControlMsg::~ControlMsg() { if (CMT_SET_CLIPBOARD == m_data.type - && Q_NULLPTR != m_data.setClipboardMsg.text) { - delete m_data.setClipboardMsg.text; - m_data.setClipboardMsg.text = Q_NULLPTR; + && Q_NULLPTR != m_data.setClipboard.text) { + delete m_data.setClipboard.text; + m_data.setClipboard.text = Q_NULLPTR; } else if (CMT_INJECT_TEXT == m_data.type - && Q_NULLPTR != m_data.injectTextMsg.text){ - delete m_data.injectTextMsg.text; - m_data.injectTextMsg.text = Q_NULLPTR; + && Q_NULLPTR != m_data.injectText.text){ + delete m_data.injectText.text; + m_data.injectText.text = Q_NULLPTR; } } void ControlMsg::setInjectKeycodeMsgData(AndroidKeyeventAction action, AndroidKeycode keycode, AndroidMetastate metastate) { - m_data.injectKeycodeMsg.action = action; - m_data.injectKeycodeMsg.keycode = keycode; - m_data.injectKeycodeMsg.metastate = metastate; + m_data.injectKeycode.action = action; + m_data.injectKeycode.keycode = keycode; + m_data.injectKeycode.metastate = metastate; } void ControlMsg::setInjectTextMsgData(QString& text) @@ -37,30 +37,30 @@ void ControlMsg::setInjectTextMsgData(QString& text) text = text.left(CONTROL_MSG_TEXT_MAX_LENGTH); } QByteArray tmp = text.toUtf8(); - m_data.injectTextMsg.text = new char[tmp.length() + 1]; - memcpy(m_data.injectTextMsg.text, tmp.data(), tmp.length()); - m_data.injectTextMsg.text[tmp.length()] = '\0'; + m_data.injectText.text = new char[tmp.length() + 1]; + memcpy(m_data.injectText.text, tmp.data(), tmp.length()); + m_data.injectText.text[tmp.length()] = '\0'; } void ControlMsg::setInjectMouseMsgData(AndroidMotioneventAction action, AndroidMotioneventButtons buttons, QRect position) { - m_data.injectMouseMsg.action = action; - m_data.injectMouseMsg.buttons = buttons; - m_data.injectMouseMsg.position = position; + m_data.injectMouse.action = action; + m_data.injectMouse.buttons = buttons; + m_data.injectMouse.position = position; } void ControlMsg::setInjectTouchMsgData(quint32 id, AndroidMotioneventAction action, QRect position) { - m_data.injectTouchMsg.action = action; - m_data.injectTouchMsg.id = id; - m_data.injectTouchMsg.position = position; + m_data.injectTouch.action = action; + m_data.injectTouch.id = id; + m_data.injectTouch.position = position; } void ControlMsg::setInjectScrollMsgData(QRect position, qint32 hScroll, qint32 vScroll) { - m_data.injectScrollMsg.position = position; - m_data.injectScrollMsg.hScroll = hScroll; - m_data.injectScrollMsg.vScroll = vScroll; + m_data.injectScroll.position = position; + m_data.injectScroll.hScroll = hScroll; + m_data.injectScroll.vScroll = vScroll; } void ControlMsg::setSetClipboardMsgData(QString &text) @@ -73,9 +73,14 @@ void ControlMsg::setSetClipboardMsgData(QString &text) } QByteArray tmp = text.toUtf8(); - m_data.setClipboardMsg.text = new char[tmp.length() + 1]; - memcpy(m_data.setClipboardMsg.text, tmp.data(), tmp.length()); - m_data.setClipboardMsg.text[tmp.length()] = '\0'; + m_data.setClipboard.text = new char[tmp.length() + 1]; + memcpy(m_data.setClipboard.text, tmp.data(), tmp.length()); + m_data.setClipboard.text[tmp.length()] = '\0'; +} + +void ControlMsg::setSetScreenPowerModeData(ControlMsg::ScreenPowerMode mode) +{ + m_data.setScreenPowerMode.mode = mode; } void ControlMsg::writePosition(QBuffer &buffer, const QRect& value) @@ -95,32 +100,35 @@ QByteArray ControlMsg::serializeData() switch (m_data.type) { case CMT_INJECT_KEYCODE: - buffer.putChar(m_data.injectKeycodeMsg.action); - BufferUtil::write32(buffer, m_data.injectKeycodeMsg.keycode); - BufferUtil::write32(buffer, m_data.injectKeycodeMsg.metastate); + buffer.putChar(m_data.injectKeycode.action); + BufferUtil::write32(buffer, m_data.injectKeycode.keycode); + BufferUtil::write32(buffer, m_data.injectKeycode.metastate); break; case CMT_INJECT_TEXT: - BufferUtil::write16(buffer, strlen(m_data.injectTextMsg.text)); - buffer.write(m_data.injectTextMsg.text, strlen(m_data.injectTextMsg.text)); + BufferUtil::write16(buffer, strlen(m_data.injectText.text)); + buffer.write(m_data.injectText.text, strlen(m_data.injectText.text)); break; case CMT_INJECT_MOUSE: - buffer.putChar(m_data.injectMouseMsg.action); - BufferUtil::write32(buffer, m_data.injectMouseMsg.buttons); - writePosition(buffer, m_data.injectMouseMsg.position); + buffer.putChar(m_data.injectMouse.action); + BufferUtil::write32(buffer, m_data.injectMouse.buttons); + writePosition(buffer, m_data.injectMouse.position); break; case CMT_INJECT_TOUCH: - buffer.putChar(m_data.injectTouchMsg.id); - buffer.putChar(m_data.injectTouchMsg.action); - writePosition(buffer, m_data.injectTouchMsg.position); + buffer.putChar(m_data.injectTouch.id); + buffer.putChar(m_data.injectTouch.action); + writePosition(buffer, m_data.injectTouch.position); break; case CMT_INJECT_SCROLL: - writePosition(buffer, m_data.injectScrollMsg.position); - BufferUtil::write32(buffer, m_data.injectScrollMsg.hScroll); - BufferUtil::write32(buffer, m_data.injectScrollMsg.vScroll); + writePosition(buffer, m_data.injectScroll.position); + BufferUtil::write32(buffer, m_data.injectScroll.hScroll); + BufferUtil::write32(buffer, m_data.injectScroll.vScroll); break; case CMT_SET_CLIPBOARD: - BufferUtil::write16(buffer, strlen(m_data.setClipboardMsg.text)); - buffer.write(m_data.setClipboardMsg.text, strlen(m_data.setClipboardMsg.text)); + BufferUtil::write16(buffer, strlen(m_data.setClipboard.text)); + buffer.write(m_data.setClipboard.text, strlen(m_data.setClipboard.text)); + break; + case CMT_SET_SCREEN_POWER_MODE: + buffer.putChar(m_data.setScreenPowerMode.mode); break; case CMT_BACK_OR_SCREEN_ON: case CMT_EXPAND_NOTIFICATION_PANEL: diff --git a/QtScrcpy/inputcontrol/controlmsg.h b/QtScrcpy/inputcontrol/controlmsg.h index d137475..e226428 100644 --- a/QtScrcpy/inputcontrol/controlmsg.h +++ b/QtScrcpy/inputcontrol/controlmsg.h @@ -26,9 +26,16 @@ public: CMT_COLLAPSE_NOTIFICATION_PANEL, CMT_GET_CLIPBOARD, CMT_SET_CLIPBOARD, + CMT_SET_SCREEN_POWER_MODE, CMT_INJECT_TOUCH, - }; + }; + + enum ScreenPowerMode { + // see + SPM_OFF = 0, + SPM_NORMAL = 2, + }; ControlMsg(ControlMsgType controlMsgType); virtual ~ControlMsg(); @@ -42,6 +49,7 @@ public: void setInjectTouchMsgData(quint32 id, AndroidMotioneventAction action, QRect position); void setInjectScrollMsgData(QRect position, qint32 hScroll, qint32 vScroll); void setSetClipboardMsgData(QString& text); + void setSetScreenPowerModeData(ControlMsg::ScreenPowerMode mode); QByteArray serializeData(); @@ -56,28 +64,31 @@ private: AndroidKeyeventAction action; AndroidKeycode keycode; AndroidMetastate metastate; - } injectKeycodeMsg; + } injectKeycode; struct { char* text = Q_NULLPTR; - } injectTextMsg; + } injectText; struct { AndroidMotioneventAction action; AndroidMotioneventButtons buttons; QRect position; - } injectMouseMsg; + } injectMouse; struct { quint32 id; AndroidMotioneventAction action; QRect position; - } injectTouchMsg; + } injectTouch; struct { QRect position; qint32 hScroll; qint32 vScroll; - } injectScrollMsg; + } injectScroll; struct { char *text = Q_NULLPTR; - } setClipboardMsg; + } setClipboard; + struct { + ScreenPowerMode mode; + } setScreenPowerMode; }; ControlMsgData(){} diff --git a/QtScrcpy/inputcontrol/receiver.cpp b/QtScrcpy/inputcontrol/receiver.cpp index d59a443..af5dead 100644 --- a/QtScrcpy/inputcontrol/receiver.cpp +++ b/QtScrcpy/inputcontrol/receiver.cpp @@ -41,6 +41,7 @@ void Receiver::processMsg(DeviceMsg *deviceMsg) switch (deviceMsg->type()) { case DeviceMsg::DMT_GET_CLIPBOARD: { + qInfo("Device clipboard copied"); QClipboard *board = QApplication::clipboard(); QString text; deviceMsg->getClipboardMsgData(text); diff --git a/QtScrcpy/server/server.cpp b/QtScrcpy/server/server.cpp index f042ba1..04d68ad 100644 --- a/QtScrcpy/server/server.cpp +++ b/QtScrcpy/server/server.cpp @@ -65,7 +65,7 @@ bool Server::pushServer() if (m_workProcess.isRuning()) { m_workProcess.kill(); } - m_workProcess.push(m_serial, getServerPath(), DEVICE_SERVER_PATH); + m_workProcess.push(m_params.serial, getServerPath(), DEVICE_SERVER_PATH); return true; } @@ -74,7 +74,7 @@ bool Server::enableTunnelReverse() if (m_workProcess.isRuning()) { m_workProcess.kill(); } - m_workProcess.reverse(m_serial, SOCKET_NAME, m_localPort); + m_workProcess.reverse(m_params.serial, SOCKET_NAME, m_params.localPort); return true; } @@ -89,7 +89,7 @@ bool Server::disableTunnelReverse() sender()->deleteLater(); } }); - adb->reverseRemove(m_serial, SOCKET_NAME); + adb->reverseRemove(m_params.serial, SOCKET_NAME); return true; } @@ -98,7 +98,7 @@ bool Server::enableTunnelForward() if (m_workProcess.isRuning()) { m_workProcess.kill(); } - m_workProcess.forward(m_serial, m_localPort, SOCKET_NAME); + m_workProcess.forward(m_params.serial, m_params.localPort, SOCKET_NAME); return true; } bool Server::disableTunnelForward() @@ -112,7 +112,7 @@ bool Server::disableTunnelForward() sender()->deleteLater(); } }); - adb->forwardRemove(m_serial, m_localPort); + adb->forwardRemove(m_params.serial, m_params.localPort); return true; } @@ -127,31 +127,27 @@ bool Server::execute() args << "app_process"; args << "/"; // unused; args << "com.genymobile.scrcpy.Server"; - args << QString::number(m_maxSize); - args << QString::number(m_bitRate); + args << QString::number(m_params.maxSize); + args << QString::number(m_params.bitRate); args << (m_tunnelForward ? "true" : "false"); - if (m_crop.isEmpty()) { + if (m_params.crop.isEmpty()) { args << "-"; } else { - args << m_crop; + args << m_params.crop; } - args << (m_sendFrameMeta ? "true" : "false"); + args << (m_params.sendFrameMeta ? "true" : "false"); + args << (m_params.control ? "true" : "false"); + // adb -s P7C0218510000537 shell CLASSPATH=/data/local/tmp/scrcpy-server.jar app_process / com.genymobile.scrcpy.Server 0 8000000 false // mark: crop input format: "width:height:x:y" or - for no crop, for example: "100:200:0:0" // 这条adb命令是阻塞运行的,m_serverProcess进程不会退出了 - m_serverProcess.execute(m_serial, args); + m_serverProcess.execute(m_params.serial, args); return true; } -bool Server::start(const QString& serial, quint16 localPort, quint16 maxSize, quint32 bitRate, const QString& crop, bool sendFrameMeta) +bool Server::start(Server::ServerParams params) { - m_serial = serial; - m_localPort = localPort; - m_maxSize = maxSize; - m_bitRate = bitRate; - m_crop = crop; - m_sendFrameMeta = sendFrameMeta; - + m_params = params; m_serverStartStep = SSS_PUSH; return startServerByStep(); } @@ -179,7 +175,7 @@ bool Server::connectTo() // video socket m_videoSocket = new VideoSocket(); - m_videoSocket->connectToHost(QHostAddress::LocalHost, m_localPort); + m_videoSocket->connectToHost(QHostAddress::LocalHost, m_params.localPort); if (!m_videoSocket->waitForConnected(1000)) { stop(); qWarning("video socket connect to server failed"); @@ -206,7 +202,7 @@ bool Server::connectTo() // control socket m_controlSocket = new QTcpSocket(); - m_controlSocket->connectToHost(QHostAddress::LocalHost, m_localPort); + m_controlSocket->connectToHost(QHostAddress::LocalHost, m_params.localPort); if (!m_controlSocket->waitForConnected(1000)) { stop(); qWarning("control socket connect to server failed"); @@ -294,8 +290,8 @@ bool Server::startServerByStep() // client can listen before starting the server app, so there is no need to // try to connect until the server socket is listening on the device. m_serverSocket.setMaxPendingConnections(2); - if (!m_serverSocket.listen(QHostAddress::LocalHost, m_localPort)) { - qCritical(QString("Could not listen on port %1").arg(m_localPort).toStdString().c_str()); + if (!m_serverSocket.listen(QHostAddress::LocalHost, m_params.localPort)) { + qCritical(QString("Could not listen on port %1").arg(m_params.localPort).toStdString().c_str()); m_serverStartStep = SSS_NULL; if (m_tunnelForward) { disableTunnelForward(); diff --git a/QtScrcpy/server/server.h b/QtScrcpy/server/server.h index bccad71..00d7a87 100644 --- a/QtScrcpy/server/server.h +++ b/QtScrcpy/server/server.h @@ -22,10 +22,20 @@ class Server : public QObject SSS_RUNNING, }; public: + struct ServerParams { + QString serial = ""; + quint16 localPort = 27183; + quint16 maxSize = 0; + quint32 bitRate = 8000000; + QString crop = "-"; + bool sendFrameMeta = false; + bool control = true; + }; + explicit Server(QObject *parent = nullptr); virtual ~Server(); - bool start(const QString& serial, quint16 localPort, quint16 maxSize, quint32 bitRate, const QString& crop, bool sendFrameMeta = false); + bool start(Server::ServerParams params); bool connectTo(); VideoSocket* getVideoSocket(); @@ -61,21 +71,16 @@ private: private: QString m_serverPath = ""; AdbProcess m_workProcess; - QString m_serial = ""; AdbProcess m_serverProcess; TcpServer m_serverSocket; // only used if !tunnel_forward QPointer m_videoSocket = Q_NULLPTR; QPointer m_controlSocket = Q_NULLPTR; - quint16 m_localPort = 0; bool m_tunnelEnabled = false; bool m_tunnelForward = false; // use "adb forward" instead of "adb reverse" - bool m_sendFrameMeta = false; - quint16 m_maxSize = 0; - quint32 m_bitRate = 0; - QString m_crop = ""; quint32 m_acceptTimeoutTimer = 0; QString m_deviceName = ""; QSize m_deviceSize = QSize(); + ServerParams m_params; SERVER_START_STEP m_serverStartStep = SSS_NULL; }; diff --git a/QtScrcpy/stream/stream.cpp b/QtScrcpy/stream/stream.cpp index 46110c3..42938e5 100644 --- a/QtScrcpy/stream/stream.cpp +++ b/QtScrcpy/stream/stream.cpp @@ -145,7 +145,7 @@ static qint32 readPacketWithMeta(void *opaque, uint8_t *buf, int bufSize) { quint8 header[HEADER_SIZE]; qint32 r = stream->recvData(header, HEADER_SIZE); if (r == -1) { - return AVERROR(errno); + return errno ? AVERROR(errno) : AVERROR_EOF; } if (r == 0) { return AVERROR_EOF; @@ -173,7 +173,7 @@ static qint32 readPacketWithMeta(void *opaque, uint8_t *buf, int bufSize) { qint32 r = stream->recvData(buf, bufSize); if (r == -1) { - return AVERROR(errno); + return errno ? AVERROR(errno) : AVERROR_EOF; } if (r == 0) { return AVERROR_EOF; diff --git a/QtScrcpy/videoform.cpp b/QtScrcpy/videoform.cpp index 4546d9d..55cc744 100644 --- a/QtScrcpy/videoform.cpp +++ b/QtScrcpy/videoform.cpp @@ -23,7 +23,7 @@ #include "controlmsg.h" #include "mousetap/mousetap.h" -VideoForm::VideoForm(const QString& serial, quint16 maxSize, quint32 bitRate, const QString& fileName, QWidget *parent) : +VideoForm::VideoForm(const QString& serial, quint16 maxSize, quint32 bitRate, const QString& fileName, bool closeScreen, QWidget *parent) : QWidget(parent), ui(new Ui::videoForm), m_serial(serial), @@ -33,6 +33,7 @@ VideoForm::VideoForm(const QString& serial, quint16 maxSize, quint32 bitRate, co ui->setupUi(this); initUI(); + m_closeScreen = closeScreen; m_server = new Server(); m_vb = new VideoBuffer(); m_vb->init(); @@ -55,7 +56,15 @@ VideoForm::VideoForm(const QString& serial, quint16 maxSize, quint32 bitRate, co // only one devices, serial can be null // mark: crop input format: "width:height:x:y" or - for no crop, for example: "100:200:0:0" // sendFrameMeta for recorder mp4 - m_server->start(m_serial, 27183, m_maxSize, m_bitRate, "-", sendFrameMeta); + Server::ServerParams params; + params.serial = m_serial; + params.localPort = 27183; + params.maxSize = m_maxSize; + params.bitRate = m_bitRate; + params.crop = "-"; + params.sendFrameMeta = sendFrameMeta; + params.control = true; + m_server->start(params); }); updateShowSize(size()); @@ -177,6 +186,10 @@ void VideoForm::initSignals() // init controller m_inputConvert.setControlSocket(m_server->getControlSocket()); + + if (m_closeScreen) { + setScreenPowerMode(ControlMsg::SPM_OFF); + } } }); @@ -404,6 +417,16 @@ void VideoForm::postTextInput(QString& text) m_inputConvert.sendControlMsg(controlMsg); } +void VideoForm::setScreenPowerMode(ControlMsg::ScreenPowerMode mode) +{ + ControlMsg* controlMsg = new ControlMsg(ControlMsg::CMT_SET_SCREEN_POWER_MODE); + if (!controlMsg) { + return; + } + controlMsg->setSetScreenPowerModeData(mode); + m_inputConvert.sendControlMsg(controlMsg); +} + void VideoForm::staysOnTop(bool top) { bool needShow = false; diff --git a/QtScrcpy/videoform.h b/QtScrcpy/videoform.h index 1b229c7..edb8deb 100644 --- a/QtScrcpy/videoform.h +++ b/QtScrcpy/videoform.h @@ -24,7 +24,7 @@ class VideoForm : public QWidget Q_OBJECT public: - explicit VideoForm(const QString& serial, quint16 maxSize = 720, quint32 bitRate = 8000000, const QString& fileName = "", QWidget *parent = 0); + explicit VideoForm(const QString& serial, quint16 maxSize = 720, quint32 bitRate = 8000000, const QString& fileName = "", bool closeScreen = false, QWidget *parent = 0); ~VideoForm(); void switchFullScreen(); @@ -43,6 +43,7 @@ public: void setDeviceClipboard(); void clipboardPaste(); void postTextInput(QString& text); + void setScreenPowerMode(ControlMsg::ScreenPowerMode mode); void staysOnTop(bool top = true); @@ -89,6 +90,7 @@ private: Recorder* m_recorder = Q_NULLPTR; QTime m_startTimeCount; QPointer m_loadingWidget; + bool m_closeScreen = false; }; #endif // VIDEOFORM_H diff --git a/TODO.txt b/TODO.txt index dcbd103..85b82f3 100644 --- a/TODO.txt +++ b/TODO.txt @@ -5,11 +5,17 @@ Mac 루serverҪΪapkΪһ뷨ݲʵ֣ -scrcpy 7764a836f1ee02a4540cfc4118c20729018daaac +ͬscrcpy b91ecf52256da73f5c8dca04fb82c13ec826cbd7 b35733edb6df2a00b6af9b1c98627d344c377963 ¼ϵ ֻ¼ƲڣعĿǰvideoform +֡Ϊ̬ãǾ̬ https://github.com/Genymobile/scrcpy/commit/ebccb9f6cc111e8acfbe10d656cac5c1f1b744a0 +ճԱ +ճIJ +ʻ +̴߳ӡ֡ https://github.com/Genymobile/scrcpy/commit/e2a272bf99ecf48fcb050177113f903b3fb323c4 + mark: diff --git a/server/src/main/java/com/genymobile/scrcpy/ControlMessage.java b/server/src/main/java/com/genymobile/scrcpy/ControlMessage.java index 73bb4b5..0a8fe7c 100644 --- a/server/src/main/java/com/genymobile/scrcpy/ControlMessage.java +++ b/server/src/main/java/com/genymobile/scrcpy/ControlMessage.java @@ -14,14 +14,15 @@ public final class ControlMessage { public static final int TYPE_COLLAPSE_NOTIFICATION_PANEL = 6; public static final int TYPE_GET_CLIPBOARD = 7; public static final int TYPE_SET_CLIPBOARD = 8; + public static final int TYPE_SET_SCREEN_POWER_MODE = 9; - public static final int TYPE_INJECT_TOUCH = 9; + public static final int TYPE_INJECT_TOUCH = 10; private int type; private String text; private int metaState; // KeyEvent.META_* - private int action; // KeyEvent.ACTION_* or MotionEvent.ACTION_* + private int action; // KeyEvent.ACTION_* or MotionEvent.ACTION_* or POWER_MODE_* private int keycode; // KeyEvent.KEYCODE_* private int buttons; // MotionEvent.BUTTON_* private int id; @@ -82,6 +83,16 @@ public final class ControlMessage { return event; } + /** + * @param mode one of the {@code Device.SCREEN_POWER_MODE_*} constants + */ + public static ControlMessage createSetScreenPowerMode(int mode) { + ControlMessage event = new ControlMessage(); + event.type = TYPE_SET_SCREEN_POWER_MODE; + event.action = mode; + return event; + } + public static ControlMessage createEmpty(int type) { ControlMessage event = new ControlMessage(); event.type = type; diff --git a/server/src/main/java/com/genymobile/scrcpy/ControlMessageReader.java b/server/src/main/java/com/genymobile/scrcpy/ControlMessageReader.java index c06b599..2d1d96f 100644 --- a/server/src/main/java/com/genymobile/scrcpy/ControlMessageReader.java +++ b/server/src/main/java/com/genymobile/scrcpy/ControlMessageReader.java @@ -12,6 +12,7 @@ public class ControlMessageReader { private static final int INJECT_MOUSE_PAYLOAD_LENGTH = 13; private static final int INJECT_SCROLL_PAYLOAD_LENGTH = 16; private static final int INJECT_TOUCH_PAYLOAD_LENGTH = 10; + private static final int SET_SCREEN_POWER_MODE_PAYLOAD_LENGTH = 1; public static final int TEXT_MAX_LENGTH = 300; public static final int CLIPBOARD_TEXT_MAX_LENGTH = 4093; @@ -50,43 +51,46 @@ public class ControlMessageReader { } int savedPosition = buffer.position(); int type = buffer.get(); - ControlMessage controlEvent; + ControlMessage msg; switch (type) { case ControlMessage.TYPE_INJECT_KEYCODE: - controlEvent = parseInjectKeycode(); + msg = parseInjectKeycode(); break; case ControlMessage.TYPE_INJECT_TEXT: - controlEvent = parseInjectText(); + msg = parseInjectText(); break; case ControlMessage.TYPE_INJECT_MOUSE: - controlEvent = parseInjectMouse(); + msg = parseInjectMouse(); break; case ControlMessage.TYPE_INJECT_TOUCH: - controlEvent = parseInjectMouseTouch(); + msg = parseInjectMouseTouch(); break; case ControlMessage.TYPE_INJECT_SCROLL: - controlEvent = parseInjectScroll(); + msg = parseInjectScroll(); break; case ControlMessage.TYPE_SET_CLIPBOARD: - controlEvent = parseSetClipboard(); + msg = parseSetClipboard(); + break; + case ControlMessage.TYPE_SET_SCREEN_POWER_MODE: + msg = parseSetScreenPowerMode(); break; case ControlMessage.TYPE_BACK_OR_SCREEN_ON: case ControlMessage.TYPE_EXPAND_NOTIFICATION_PANEL: case ControlMessage.TYPE_COLLAPSE_NOTIFICATION_PANEL: case ControlMessage.TYPE_GET_CLIPBOARD: - controlEvent = ControlMessage.createEmpty(type); + msg = ControlMessage.createEmpty(type); break; default: Ln.w("Unknown event type: " + type); - controlEvent = null; + msg = null; break; } - if (controlEvent == null) { + if (msg == null) { // failure, reset savedPosition buffer.position(savedPosition); } - return controlEvent; + return msg; } private ControlMessage parseInjectKeycode() { @@ -157,6 +161,14 @@ public class ControlMessageReader { return ControlMessage.createSetClipboard(text); } + private ControlMessage parseSetScreenPowerMode() { + if (buffer.remaining() < SET_SCREEN_POWER_MODE_PAYLOAD_LENGTH) { + return null; + } + int mode = buffer.get(); + return ControlMessage.createSetScreenPowerMode(mode); + } + private static Position readPosition(ByteBuffer buffer) { int x = toUnsigned(buffer.getShort()); int y = toUnsigned(buffer.getShort()); diff --git a/server/src/main/java/com/genymobile/scrcpy/Controller.java b/server/src/main/java/com/genymobile/scrcpy/Controller.java index 5aeb252..490ce05 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Controller.java +++ b/server/src/main/java/com/genymobile/scrcpy/Controller.java @@ -102,9 +102,21 @@ public class Controller { return sender; } + @SuppressWarnings("checkstyle:MagicNumber") public void control() throws IOException { - // on start, turn screen on - turnScreenOn(); + // on start, power on the device + if (!device.isScreenOn()) { + injectKeycode(KeyEvent.KEYCODE_POWER); + + // dirty hack + // After POWER is injected, the device is powered on asynchronously. + // To turn the device screen off while mirroring, the client will send a message that + // would be handled before the device is actually powered on, so its effect would + // be "canceled" once the device is turned back on. + // Adding this delay prevents to handle the message before the device is actually + // powered on. + SystemClock.sleep(500); + } while (true) { handleEvent(); @@ -145,6 +157,9 @@ public class Controller { case ControlMessage.TYPE_SET_CLIPBOARD: device.setClipboardText(msg.getText()); break; + case ControlMessage.TYPE_SET_SCREEN_POWER_MODE: + device.setScreenPowerMode(msg.getAction()); + break; default: // do nothing } @@ -309,10 +324,6 @@ public class Controller { return device.injectInputEvent(event, InputManager.INJECT_INPUT_EVENT_MODE_ASYNC); } - private boolean turnScreenOn() { - return device.isScreenOn() || injectKeycode(KeyEvent.KEYCODE_POWER); - } - private boolean pressBackOrTurnScreenOn() { int keycode = device.isScreenOn() ? KeyEvent.KEYCODE_BACK : KeyEvent.KEYCODE_POWER; return injectKeycode(keycode); diff --git a/server/src/main/java/com/genymobile/scrcpy/Device.java b/server/src/main/java/com/genymobile/scrcpy/Device.java index 93b0302..0420587 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Device.java +++ b/server/src/main/java/com/genymobile/scrcpy/Device.java @@ -1,16 +1,21 @@ package com.genymobile.scrcpy; import com.genymobile.scrcpy.wrappers.ServiceManager; +import com.genymobile.scrcpy.wrappers.SurfaceControl; import android.graphics.Point; import android.graphics.Rect; import android.os.Build; +import android.os.IBinder; import android.os.RemoteException; import android.view.IRotationWatcher; import android.view.InputEvent; public final class Device { + public static final int POWER_MODE_OFF = SurfaceControl.POWER_MODE_OFF; + public static final int POWER_MODE_NORMAL = SurfaceControl.POWER_MODE_NORMAL; + public interface RotationListener { void onRotationChanged(int rotation); } @@ -190,6 +195,16 @@ public final class Device { public void setClipboardText(String text) { serviceManager.getClipboardManager().setText(text); + Ln.i("Device clipboard set"); + } + + /** + * @param mode one of the {@code SCREEN_POWER_MODE_*} constants + */ + public void setScreenPowerMode(int mode) { + IBinder d = SurfaceControl.getBuiltInDisplay(0); + SurfaceControl.setDisplayPowerMode(d, mode); + Ln.i("Device screen turned " + (mode == Device.POWER_MODE_OFF ? "off" : "on")); } static Rect flipRect(Rect crop) { diff --git a/server/src/main/java/com/genymobile/scrcpy/Ln.java b/server/src/main/java/com/genymobile/scrcpy/Ln.java index cd466b3..d991419 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Ln.java +++ b/server/src/main/java/com/genymobile/scrcpy/Ln.java @@ -8,7 +8,8 @@ import android.util.Log; */ public final class Ln { - private static final String TAG = "scrcpy"; + private static final String TAG = "qtscrcpy"; + private static final String PREFIX = "[server] "; enum Level { DEBUG, @@ -30,28 +31,28 @@ public final class Ln { public static void d(String message) { if (isEnabled(Level.DEBUG)) { Log.d(TAG, message); - System.out.println("DEBUG: " + message); + System.out.println(PREFIX + "DEBUG: " + message); } } public static void i(String message) { if (isEnabled(Level.INFO)) { Log.i(TAG, message); - System.out.println("INFO: " + message); + System.out.println(PREFIX + "INFO: " + message); } } public static void w(String message) { if (isEnabled(Level.WARN)) { Log.w(TAG, message); - System.out.println("WARN: " + message); + System.out.println(PREFIX + "WARN: " + message); } } public static void e(String message, Throwable throwable) { if (isEnabled(Level.ERROR)) { Log.e(TAG, message, throwable); - System.out.println("ERROR: " + message); + System.out.println(PREFIX + "ERROR: " + message); if (throwable != null) { throwable.printStackTrace(); } diff --git a/server/src/main/java/com/genymobile/scrcpy/Options.java b/server/src/main/java/com/genymobile/scrcpy/Options.java index 0d02451..697cc96 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Options.java +++ b/server/src/main/java/com/genymobile/scrcpy/Options.java @@ -8,6 +8,7 @@ public class Options { private boolean tunnelForward; private Rect crop; private boolean sendFrameMeta; + private boolean control; public int getMaxSize() { return maxSize; @@ -48,4 +49,12 @@ public class Options { public void setSendFrameMeta(boolean sendFrameMeta) { this.sendFrameMeta = sendFrameMeta; } + + public boolean getControl() { + return control; + } + + public void setControl(boolean control) { + this.control = control; + } } diff --git a/server/src/main/java/com/genymobile/scrcpy/Server.java b/server/src/main/java/com/genymobile/scrcpy/Server.java index 95dbdb3..1c76afd 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Server.java +++ b/server/src/main/java/com/genymobile/scrcpy/Server.java @@ -20,11 +20,13 @@ public final class Server { try (DesktopConnection connection = DesktopConnection.open(device, tunnelForward)) { ScreenEncoder screenEncoder = new ScreenEncoder(options.getSendFrameMeta(), options.getBitRate()); - Controller controller = new Controller(device, connection); + if (options.getControl()) { + Controller controller = new Controller(device, connection); - // asynchronous - startController(controller); - startDeviceMessageSender(controller.getSender()); + // asynchronous + startController(controller); + startDeviceMessageSender(controller.getSender()); + } try { // synchronous @@ -66,7 +68,7 @@ public final class Server { @SuppressWarnings("checkstyle:MagicNumber") private static Options createOptions(String... args) { - if (args.length != 5) { + if (args.length != 6) { throw new IllegalArgumentException("Expecting 5 parameters"); } @@ -88,6 +90,9 @@ public final class Server { boolean sendFrameMeta = Boolean.parseBoolean(args[4]); options.setSendFrameMeta(sendFrameMeta); + boolean control = Boolean.parseBoolean(args[5]); + options.setControl(control); + return options; } diff --git a/server/src/main/java/com/genymobile/scrcpy/wrappers/SurfaceControl.java b/server/src/main/java/com/genymobile/scrcpy/wrappers/SurfaceControl.java index 8573386..5b5586f 100644 --- a/server/src/main/java/com/genymobile/scrcpy/wrappers/SurfaceControl.java +++ b/server/src/main/java/com/genymobile/scrcpy/wrappers/SurfaceControl.java @@ -2,6 +2,7 @@ package com.genymobile.scrcpy.wrappers; import android.annotation.SuppressLint; import android.graphics.Rect; +import android.os.Build; import android.os.IBinder; import android.view.Surface; @@ -10,6 +11,10 @@ public final class SurfaceControl { private static final Class CLASS; + // see + public static final int POWER_MODE_OFF = 0; + public static final int POWER_MODE_NORMAL = 2; + static { try { CLASS = Class.forName("android.view.SurfaceControl"); @@ -71,6 +76,27 @@ public final class SurfaceControl { } } + public static IBinder getBuiltInDisplay(int builtInDisplayId) { + try { + // the method signature has changed in Android Q + // + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { + return (IBinder) CLASS.getMethod("getBuiltInDisplay", int.class).invoke(null, builtInDisplayId); + } + return (IBinder) CLASS.getMethod("getPhysicalDisplayToken", long.class).invoke(null, builtInDisplayId); + } catch (Exception e) { + throw new AssertionError(e); + } + } + + public static void setDisplayPowerMode(IBinder displayToken, int mode) { + try { + CLASS.getMethod("setDisplayPowerMode", IBinder.class, int.class).invoke(null, displayToken, mode); + } catch (Exception e) { + throw new AssertionError(e); + } + } + public static void destroyDisplay(IBinder displayToken) { try { CLASS.getMethod("destroyDisplay", IBinder.class).invoke(null, displayToken); diff --git a/third_party/scrcpy-server.jar b/third_party/scrcpy-server.jar index aaad381..7abbf27 100644 Binary files a/third_party/scrcpy-server.jar and b/third_party/scrcpy-server.jar differ