diff --git a/.gitignore b/.gitignore index 705f29c..8627497 100644 --- a/.gitignore +++ b/.gitignore @@ -9,5 +9,6 @@ /server/gradlew /server/gradlew.bat /server/local.properties +/build/ build-* *.DS_Store diff --git a/QtScrcpy/device/controller/controller.cpp b/QtScrcpy/device/controller/controller.cpp index 5da8336..ee78bb4 100644 --- a/QtScrcpy/device/controller/controller.cpp +++ b/QtScrcpy/device/controller/controller.cpp @@ -12,13 +12,7 @@ Controller::Controller(QString gameScript, QObject* parent) : QObject(parent) m_receiver = new Receiver(this); Q_ASSERT(m_receiver); - if (!gameScript.isEmpty()) { - InputConvertGame* convertgame = new InputConvertGame(this); - convertgame->loadKeyMap(gameScript); - m_inputConvert = convertgame; - } else { - m_inputConvert = new InputConvertNormal(this); - } + updateScript(gameScript); Q_ASSERT(m_inputConvert); connect(m_inputConvert, &InputConvertBase::grabCursor, this, &Controller::grabCursor); } @@ -51,6 +45,18 @@ void Controller::test(QRect rc) postControlMsg(controlMsg); } +void Controller::updateScript(QString gameScript) +{ + if (!gameScript.isEmpty()) { + InputConvertGame* convertgame = new InputConvertGame(this); + convertgame->loadKeyMap(gameScript); + m_inputConvert = convertgame; + } else { + m_inputConvert = new InputConvertNormal(this); + } + +} + void Controller::postTurnOn() { ControlMsg* controlMsg = new ControlMsg(ControlMsg::CMT_BACK_OR_SCREEN_ON); @@ -161,6 +167,12 @@ void Controller::setScreenPowerMode(ControlMsg::ScreenPowerMode mode) postControlMsg(controlMsg); } +void Controller::screenShot() +{ + // TODO: + qDebug() << "screen shot"; +} + void Controller::mouseEvent(const QMouseEvent *from, const QSize &frameSize, const QSize &showSize) { if (m_inputConvert) { diff --git a/QtScrcpy/device/controller/controller.h b/QtScrcpy/device/controller/controller.h index 6380b1e..14550ba 100644 --- a/QtScrcpy/device/controller/controller.h +++ b/QtScrcpy/device/controller/controller.h @@ -20,6 +20,8 @@ public: void postControlMsg(ControlMsg* controlMsg); void test(QRect rc); + void updateScript(QString gameScript = ""); + // turn the screen on if it was off, press BACK otherwise void postTurnOn(); void postGoHome(); @@ -36,6 +38,7 @@ public: void clipboardPaste(); void postTextInput(QString& text); void setScreenPowerMode(ControlMsg::ScreenPowerMode mode); + void screenShot(); // for input convert void mouseEvent(const QMouseEvent* from, const QSize& frameSize, const QSize& showSize); diff --git a/QtScrcpy/device/controller/inputconvert/inputconvertgame.cpp b/QtScrcpy/device/controller/inputconvert/inputconvertgame.cpp index e4a03b5..285ada2 100644 --- a/QtScrcpy/device/controller/inputconvert/inputconvertgame.cpp +++ b/QtScrcpy/device/controller/inputconvert/inputconvertgame.cpp @@ -1,9 +1,38 @@ -#include +#include #include #include #include "inputconvertgame.h" +#ifdef _WIN32 + +#define WIN32_LEAN_AND_MEAN +#include +#include +// restrict mouse into a window +static void restrictMouse(const int left, const int right, + const int top, const int bottom) +{ + RECT mainWinRect; // RECT is defined in + mainWinRect.left = static_cast(left); + mainWinRect.right = static_cast(right); + mainWinRect.top = static_cast(top); + mainWinRect.bottom = static_cast(bottom); + ClipCursor(&mainWinRect); // Windows API +} +static void freeMouse() +{ + ClipCursor(nullptr); +} + +#else // linux and macos +static void restrictMouse(const int left, const int right, + const int top, const int bottom) +{} +static void freeMouse() +{} +#endif + #define CURSOR_POS_CHECK 50 InputConvertGame::InputConvertGame(Controller* controller) @@ -19,17 +48,25 @@ InputConvertGame::~InputConvertGame() void InputConvertGame::mouseEvent(const QMouseEvent *from, const QSize &frameSize, const QSize &showSize) { + // 处理开关按键 + if (m_keyMap.isSwitchOnKeyboard() == false && + from->type() == QEvent::MouseButtonPress && + m_keyMap.getSwitchKey() == from->button()) + { + if (!switchGameMap()) { + m_needSwitchGameAgain = false; + } + return; + } + if (m_gameMap) { updateSize(frameSize, showSize); - - if (m_keyMap.enableMouseMoveMap()) { - // mouse move + // mouse move + if (m_keyMap.isValidMouseMoveMap()) { if (processMouseMove(from)) { return; } } - - // mouse click if (processMouseClick(from)) { return; @@ -51,7 +88,7 @@ void InputConvertGame::wheelEvent(const QWheelEvent *from, const QSize &frameSiz void InputConvertGame::keyEvent(const QKeyEvent *from, const QSize& frameSize, const QSize& showSize) { // 处理开关按键 - if (m_keyMap.getSwitchKey() == from->key()) { + if (m_keyMap.isSwitchOnKeyboard() && m_keyMap.getSwitchKey() == from->key()) { if (QEvent::KeyPress == from->type()) { if (!switchGameMap()) { m_needSwitchGameAgain = false; @@ -60,15 +97,14 @@ void InputConvertGame::keyEvent(const QKeyEvent *from, const QSize& frameSize, c return; } - KeyMap::KeyMapNode& node = m_keyMap.getKeyMapNode(from->key()); + const KeyMap::KeyMapNode& node = m_keyMap.getKeyMapNodeKey(from->key()); // 处理特殊按键:可以在按键映射和普通映射间切换的按键 if (m_needSwitchGameAgain && KeyMap::KMT_CLICK == node.type && node.click.switchMap) { updateSize(frameSize, showSize); - // Qt::Key_Tab Qt::Key_M - processKeyClick(node.click.keyNode.pos, false, - node.click.switchMap, from); + // Qt::Key_Tab Qt::Key_M for PUBG mobile + processKeyClick(node.click.keyNode.pos, false, node.click.switchMap, from); return; } @@ -78,21 +114,23 @@ void InputConvertGame::keyEvent(const QKeyEvent *from, const QSize& frameSize, c return; } + switch (node.type) { // 处理方向盘 - if (KeyMap::KMT_STEER_WHEEL == node.type) { + case KeyMap::KMT_STEER_WHEEL: processSteerWheel(node, from); return; - } - // 处理普通按键 - switch (node.type) { case KeyMap::KMT_CLICK: - processKeyClick(node.click.keyNode.pos, false, - node.click.switchMap, from); + processKeyClick(node.click.keyNode.pos, false, node.click.switchMap, from); return; case KeyMap::KMT_CLICK_TWICE: processKeyClick(node.clickTwice.keyNode.pos, true, false, from); return; + case KeyMap::KMT_DRAG: + processKeyDrag(node.drag.startPos, node.drag.endPos, from); + return; + default: + break; } } else { InputConvertNormal::keyEvent(from, frameSize, showSize); @@ -102,8 +140,15 @@ void InputConvertGame::keyEvent(const QKeyEvent *from, const QSize& frameSize, c void InputConvertGame::loadKeyMap(const QString &json) { m_keyMap.loadKeyMap(json); - if (m_keyMap.enableMouseMoveMap()) { - m_mouseMoveLastConverPos = m_keyMap.getMouseMoveMap().startPos; + if (m_keyMap.isValidMouseMoveMap()) { + m_ctrlMouseMove.valid = true; + m_ctrlMouseMove.touching = false; + m_ctrlMouseMove.startPosRel = m_keyMap.getMouseMoveMap().startPos; + m_ctrlMouseMove.startPosPixel = calcFrameAbsolutePos(m_ctrlMouseMove.startPosRel); + } + if(m_keyMap.isValidSteerWheelMap()){ + m_ctrlSteerWheel.valid = true; + m_ctrlMouseMove.touching = false; } } @@ -111,6 +156,9 @@ void InputConvertGame::updateSize(const QSize &frameSize, const QSize &showSize) { m_frameSize = frameSize; m_showSize = showSize; + if(m_ctrlMouseMove.valid){ + m_ctrlMouseMove.startPosPixel = calcScreenAbsolutePos(m_ctrlMouseMove.startPosRel); + } } void InputConvertGame::sendTouchDownEvent(int id, QPointF pos) @@ -149,22 +197,35 @@ QPointF InputConvertGame::calcFrameAbsolutePos(QPointF relativePos) return absolutePos; } +QPointF InputConvertGame::calcScreenAbsolutePos(QPointF relativePos) +{ + QPointF absolutePos; + absolutePos.setX(m_showSize.width() * relativePos.x()); + absolutePos.setY(m_showSize.height() * relativePos.y()); + return absolutePos; +} + int InputConvertGame::attachTouchID(int key) { + //QMetaEnum map = QMetaEnum::fromType(); for (int i = 0; i < MULTI_TOUCH_MAX_NUM; i++) { if (0 == multiTouchID[i]) { multiTouchID[i] = key; + //qDebug() << "attach "<(); for (int i = 0; i < MULTI_TOUCH_MAX_NUM; i++) { if (key == multiTouchID[i]) { multiTouchID[i] = 0; + //qDebug() << "detach "<type()) { - if (1 == keysNum) { - node.steerWheel.firstPressKey = from->key(); - int id = attachTouchID(node.steerWheel.firstPressKey); - if (-1 == id) { - return; - } +// -------- steer wheel event -------- + +void InputConvertGame::processSteerWheel(const KeyMap::KeyMapNode &node, const QKeyEvent *from) +{ + int key = from->key(); + bool flag = from->type() == QEvent::KeyPress; + // identify keys + if(key == node.steerWheel.up.key){ + m_ctrlSteerWheel.pressedUp = flag; + }else if(key == node.steerWheel.right.key){ + m_ctrlSteerWheel.pressedRight = flag; + }else if(key == node.steerWheel.down.key){ + m_ctrlSteerWheel.pressedDown = flag; + }else{ // left + m_ctrlSteerWheel.pressedLeft = flag; + } + QPointF offset(0.0, 0.0); + int nPressed = 0; + if(m_ctrlSteerWheel.pressedUp){ + ++nPressed; + offset.ry() -= node.steerWheel.up.offset; + } + if(m_ctrlSteerWheel.pressedRight){ + ++nPressed; + offset.rx() += node.steerWheel.right.offset; + } + if(m_ctrlSteerWheel.pressedDown){ + ++nPressed; + offset.ry() += node.steerWheel.down.offset; + } + if(m_ctrlSteerWheel.pressedLeft){ + ++nPressed; + offset.rx() -= node.steerWheel.left.offset; + } + // action + //qDebug()<key())<<"-"<type()<<"-"<key(); + id = attachTouchID(m_ctrlSteerWheel.touchKey); sendTouchDownEvent(id, node.steerWheel.centerPos); - needMove = true; - } else if (2 == keysNum) { - needMove = true; - } - } else if (QEvent::KeyRelease == from->type()){ - if (0 == keysNum) { - int id = getTouchID(node.steerWheel.firstPressKey); - sendTouchUpEvent(id, node.steerWheel.centerPos); - detachTouchID(node.steerWheel.firstPressKey); - node.steerWheel.firstPressKey = 0; - } else if (1 == keysNum) { - needMove = true; + }else{ + id = getTouchID(m_ctrlSteerWheel.touchKey); } + sendTouchMoveEvent(id, node.steerWheel.centerPos + offset); } - if (needMove) { - steerWheelMove(node, keysNum, keyPress1, keyPress2); - } + m_ctrlSteerWheel.lastOffset = offset; + return; } -int InputConvertGame::updateSteerWheelKeysPress(KeyMap::KeyMapNode &node, const QKeyEvent *from, int& keyPress1, int& keyPress2) -{ - bool keyPress = false; - if (QEvent::KeyPress == from->type()) { - keyPress = true; - } else if (QEvent::KeyRelease == from->type()) { - keyPress = false; - } - if (from->key() == node.steerWheel.upKey) { - node.steerWheel.upKeyPressed = keyPress; - } else if (from->key() == node.steerWheel.rightKey) { - node.steerWheel.rightKeyPressed = keyPress; - } else if (from->key() == node.steerWheel.downKey) { - node.steerWheel.downKeyPressed = keyPress; - } else if (from->key() == node.steerWheel.leftKey) { - node.steerWheel.leftKeyPressed = keyPress; - } +// -------- key event -------- - int count = 0; - keyPress1 = Qt::Key_unknown; - keyPress2 = Qt::Key_unknown; - - // 上右下左的顺序统计按键数量,并记录前两个按键码 - if (node.steerWheel.upKeyPressed) { - count++; - if (Qt::Key_unknown == keyPress1) { - keyPress1 = node.steerWheel.upKey; - } else if (Qt::Key_unknown == keyPress2) { - keyPress2 = node.steerWheel.upKey; - } - } - if (node.steerWheel.rightKeyPressed) { - count++; - if (Qt::Key_unknown == keyPress1) { - keyPress1 = node.steerWheel.rightKey; - } else if (Qt::Key_unknown == keyPress2) { - keyPress2 = node.steerWheel.rightKey; - } - } - if (node.steerWheel.downKeyPressed) { - count++; - if (Qt::Key_unknown == keyPress1) { - keyPress1 = node.steerWheel.downKey; - } else if (Qt::Key_unknown == keyPress2) { - keyPress2 = node.steerWheel.downKey; - } - } - if (node.steerWheel.leftKeyPressed) { - count++; - if (Qt::Key_unknown == keyPress1) { - keyPress1 = node.steerWheel.leftKey; - } else if (Qt::Key_unknown == keyPress2) { - keyPress2 = node.steerWheel.leftKey; - } - } - return count; -} - -void InputConvertGame::steerWheelMove(KeyMap::KeyMapNode &node, int keysNum, int keyPress1, int keyPress2) -{ - if (1 != keysNum && 2 != keysNum) { - return; - } - QPointF movePos = node.steerWheel.centerPos; - switch (keysNum) { - case 2: - if (keyPress2 == node.steerWheel.upKey) { - movePos.setY(movePos.y() - node.steerWheel.upOffset); - } else if (keyPress2 == node.steerWheel.rightKey) { - movePos.setX(movePos.x() + node.steerWheel.rightOffset); - } else if (keyPress2 == node.steerWheel.downKey) { - movePos.setY(movePos.y() + node.steerWheel.downOffset); - } else if (keyPress2 == node.steerWheel.leftKey) { - movePos.setX(movePos.x() - node.steerWheel.leftOffset); - } - case 1: - if (keyPress1 == node.steerWheel.upKey) { - movePos.setY(movePos.y() - node.steerWheel.upOffset); - } else if (keyPress1 == node.steerWheel.rightKey) { - movePos.setX(movePos.x() + node.steerWheel.rightOffset); - } else if (keyPress1 == node.steerWheel.downKey) { - movePos.setY(movePos.y() + node.steerWheel.downOffset); - } else if (keyPress1 == node.steerWheel.leftKey) { - movePos.setX(movePos.x() - node.steerWheel.leftOffset); - } - break; - } - sendTouchMoveEvent(getTouchID(node.steerWheel.firstPressKey), movePos); -} - -void InputConvertGame::processKeyClick(QPointF clickPos, bool clickTwice, bool switchMap, const QKeyEvent *from) +void InputConvertGame::processKeyClick( + const QPointF& clickPos, bool clickTwice, bool switchMap, const QKeyEvent *from) { if (switchMap && QEvent::KeyRelease == from->type()) { m_needSwitchGameAgain = !m_needSwitchGameAgain; @@ -327,9 +323,22 @@ void InputConvertGame::processKeyClick(QPointF clickPos, bool clickTwice, bool s } } +void InputConvertGame::processKeyDrag(const QPointF& startPos, QPointF endPos, const QKeyEvent* from) +{ + if(QEvent::KeyPress == from->type()){ + int id = attachTouchID(from->key()); + sendTouchDownEvent(id, startPos); + sendTouchMoveEvent(id, endPos); + sendTouchUpEvent(id, endPos); + detachTouchID(from->key()); + } +} + +// -------- mouse event -------- + bool InputConvertGame::processMouseClick(const QMouseEvent *from) { - KeyMap::KeyMapNode& node = m_keyMap.getKeyMapNode(from->button()); + const KeyMap::KeyMapNode& node = m_keyMap.getKeyMapNodeMouse(from->button()); if (KeyMap::KMT_INVALID == node.type) { return false; } @@ -350,55 +359,62 @@ bool InputConvertGame::processMouseMove(const QMouseEvent *from) { if (QEvent::MouseMove != from->type()) { return false; - } - - if (checkCursorPos(from)) { - m_mouseMoveLastPos = QPointF(0.0f, 0.0f); - return true; } + if(m_ctrlMouseMove.touching){ + QPointF mousePos = from->localPos(); + mousePos.rx() /= m_showSize.width(); + mousePos.ry() /= m_showSize.height(); + QPointF offset = mousePos - m_ctrlMouseMove.startPosRel; + //qDebug()<localPos()<<" - "<localPos() - m_mouseMoveLastPos; - distance /= m_keyMap.getMouseMoveMap().speedRatio; - - mouseMoveStartTouch(from); - startMouseMoveTimer(); - - m_mouseMoveLastConverPos.setX(m_mouseMoveLastConverPos.x() + distance.x() / m_showSize.width()); - m_mouseMoveLastConverPos.setY(m_mouseMoveLastConverPos.y() + distance.y() / m_showSize.height()); - - if (m_mouseMoveLastConverPos.x() < 0.1 - || m_mouseMoveLastConverPos.x() > 0.8 - || m_mouseMoveLastConverPos.y() < 0.1 - || m_mouseMoveLastConverPos.y() > 0.8) { + if(mousePos.x()<0.05 || mousePos.x()>0.95 || mousePos.y()<0.05 || mousePos.y()>0.95) + { + //qDebug()<<"reset"; mouseMoveStopTouch(); mouseMoveStartTouch(from); } - - sendTouchMoveEvent(getTouchID(Qt::ExtraButton24), m_mouseMoveLastConverPos); + offset /= m_keyMap.getMouseMoveMap().speedRatio; + m_ctrlMouseMove.lastPosRel = m_ctrlMouseMove.startPosRel + offset; + mouseMoveMovingTouch(m_ctrlMouseMove.lastPosRel); + }else{ + m_ctrlMouseMove.touching = true; + mouseMoveStartTouch(from); + int left = from->globalX() - from->x(); + int top = from->globalY() - from->y(); + restrictMouse(left, left + m_showSize.width(), top, top+m_showSize.height()); } - m_mouseMoveLastPos = from->localPos(); return true; } -void InputConvertGame::moveCursorToStart(const QMouseEvent *from) +void InputConvertGame::moveCursorTo(const QMouseEvent *from, const QPoint &localPosPixel) { - QPointF mouseMoveStartPos = m_keyMap.getMouseMoveMap().startPos; - QPoint localPos = QPoint(m_showSize.width()*mouseMoveStartPos.x(), m_showSize.height()*mouseMoveStartPos.y()); - QPoint posOffset = from->localPos().toPoint() - localPos; + QPoint posOffset = from->pos() - localPosPixel; QPoint globalPos = from->globalPos(); - globalPos -= posOffset; + //qDebug()<<"move cursor to "<localPos().toPoint() - pos; - QPoint globalPos = from->globalPos(); + moveCursorTo(from, m_ctrlMouseMove.startPosPixel.toPoint()); + int id = attachTouchID(m_ctrlMouseMove.touchKey); + sendTouchDownEvent(id, m_ctrlMouseMove.startPosRel); + m_ctrlMouseMove.lastPosRel = m_ctrlMouseMove.startPosRel; + m_ctrlMouseMove.touching = true; +} - globalPos -= posOffset; - QCursor::setPos(globalPos); +void InputConvertGame::mouseMoveMovingTouch(const QPointF& target) +{ + sendTouchMoveEvent(getTouchID(m_ctrlMouseMove.touchKey), target); +} + +void InputConvertGame::mouseMoveStopTouch() +{ + int id = getTouchID(m_ctrlMouseMove.touchKey); + sendTouchUpEvent(id, m_ctrlMouseMove.lastPosRel); + detachTouchID(m_ctrlMouseMove.touchKey); + m_ctrlMouseMove.touching = false; } void InputConvertGame::startMouseMoveTimer() @@ -415,66 +431,26 @@ void InputConvertGame::stopMouseMoveTimer() } } -void InputConvertGame::mouseMoveStartTouch(const QMouseEvent* from) -{ - Q_UNUSED(from); - if (!m_mouseMovePress) { - QPointF mouseMoveStartPos = m_keyMap.getMouseMoveMap().startPos; - //moveCursorToStart(from); - int id = attachTouchID(Qt::ExtraButton24); - sendTouchDownEvent(id, mouseMoveStartPos); - m_mouseMoveLastConverPos = mouseMoveStartPos; - m_mouseMovePress = true; - } -} - -void InputConvertGame::mouseMoveStopTouch() -{ - if (m_mouseMovePress) { - sendTouchUpEvent(getTouchID(Qt::ExtraButton24), m_mouseMoveLastConverPos); - detachTouchID(Qt::ExtraButton24); - m_mouseMovePress = false; - } -} - bool InputConvertGame::switchGameMap() { - m_gameMap = !m_gameMap; + m_gameMap = !m_gameMap; emit grabCursor(m_gameMap); if (m_gameMap) { - QGuiApplication::setOverrideCursor(QCursor(Qt::BlankCursor)); + #ifdef QT_NO_DEBUG + QGuiApplication::setOverrideCursor(QCursor(Qt::BlankCursor)); + #else + QGuiApplication::setOverrideCursor(QCursor(Qt::CrossCursor)); + #endif + //restrictMouse(); // called at the first run of processMouseMove() } else { - mouseMoveStopTouch(); + if(m_ctrlMouseMove.touching) + mouseMoveStopTouch(); QGuiApplication::restoreOverrideCursor(); + freeMouse(); } return m_gameMap; } -bool InputConvertGame::checkCursorPos(const QMouseEvent *from) -{ - bool moveCursor = false; - QPoint pos = from->pos(); - if (pos.x() < CURSOR_POS_CHECK) { - pos.setX(m_showSize.width() - CURSOR_POS_CHECK); - moveCursor = true; - } else if (pos.x() > m_showSize.width() - CURSOR_POS_CHECK) { - pos.setX(CURSOR_POS_CHECK); - moveCursor = true; - } else if (pos.y() < CURSOR_POS_CHECK) { - pos.setY(m_showSize.height() - CURSOR_POS_CHECK); - moveCursor = true; - } else if (pos.y() > m_showSize.height() - CURSOR_POS_CHECK) { - pos.setY(CURSOR_POS_CHECK); - moveCursor = true; - } - - if (moveCursor) { - moveCursorTo(from, pos); - } - - return moveCursor; -} - void InputConvertGame::timerEvent(QTimerEvent *event) { if (m_mouseMoveTimer == event->timerId()) { diff --git a/QtScrcpy/device/controller/inputconvert/inputconvertgame.h b/QtScrcpy/device/controller/inputconvert/inputconvertgame.h index 0869d05..acf585b 100644 --- a/QtScrcpy/device/controller/inputconvert/inputconvertgame.h +++ b/QtScrcpy/device/controller/inputconvert/inputconvertgame.h @@ -27,6 +27,7 @@ protected: void sendTouchUpEvent(int id, QPointF pos); void sendTouchEvent(int id, QPointF pos, AndroidMotioneventAction action); QPointF calcFrameAbsolutePos(QPointF relativePos); + QPointF calcScreenAbsolutePos(QPointF relativePos); // multi touch id int attachTouchID(int key); @@ -34,48 +35,60 @@ protected: int getTouchID(int key); // steer wheel - void processSteerWheel(KeyMap::KeyMapNode &node, const QKeyEvent* from); - int updateSteerWheelKeysPress(KeyMap::KeyMapNode &node, const QKeyEvent* from, int& keyPress1, int& keyPress2); - void steerWheelMove(KeyMap::KeyMapNode &node, int keysNum, int keyPress1, int keyPress2); + void processSteerWheel(const KeyMap::KeyMapNode &node, const QKeyEvent* from); // click - void processKeyClick(QPointF clickPos, bool clickTwice, bool switchMap, const QKeyEvent* from); + void processKeyClick(const QPointF& clickPos, bool clickTwice, bool switchMap, const QKeyEvent* from); + + // drag + void processKeyDrag(const QPointF& startPos, QPointF endPos, const QKeyEvent* from); // mouse bool processMouseClick(const QMouseEvent* from); bool processMouseMove(const QMouseEvent* from); - void moveCursorToStart(const QMouseEvent* from); - void moveCursorTo(const QMouseEvent* from, const QPoint& pos); - void startMouseMoveTimer(); - void stopMouseMoveTimer(); + void moveCursorTo(const QMouseEvent* from, const QPoint& localPosPixel); void mouseMoveStartTouch(const QMouseEvent* from); + void mouseMoveMovingTouch(const QPointF& target); void mouseMoveStopTouch(); + void startMouseMoveTimer(); + void stopMouseMoveTimer(); + bool switchGameMap(); - bool checkCursorPos(const QMouseEvent* from); protected: void timerEvent(QTimerEvent *event); -private: - enum SteerWheelDirection { - SWD_UP = 0, - SWD_RIGHT, - SWD_DOWN, - SWD_LEFT, - }; - private: QSize m_frameSize; QSize m_showSize; bool m_gameMap = false; - int multiTouchID[MULTI_TOUCH_MAX_NUM] = { 0 }; + int multiTouchID[MULTI_TOUCH_MAX_NUM] = { 0 }; + + // steer wheel + struct{ + bool valid = false; + bool touching = false; + int touchKey = Qt::Key_unknown; // the first key pressed + int nKeyPressed = 0; + bool pressedUp = false, pressedDown = false; + bool pressedLeft = false, pressedRight = false; + QPointF centerPos; + QPointF lastOffset; + } m_ctrlSteerWheel; + + // mouse move + struct{ + bool valid = false; + bool touching = false; + const int touchKey = Qt::ExtraButton24; + QPointF startPosRel; // in [0, 1) + QPointF startPosPixel; // in [0, size) + QPointF lastPosRel; + //QPointF lastPosPixel; + } m_ctrlMouseMove; - // mouse move - QPointF m_mouseMoveLastConverPos; - QPointF m_mouseMoveLastPos = {0.0f, 0.0f}; - bool m_mouseMovePress = false; int m_mouseMoveTimer = 0; bool m_needSwitchGameAgain = false; diff --git a/QtScrcpy/device/controller/inputconvert/keymap/keymap.cpp b/QtScrcpy/device/controller/inputconvert/keymap/keymap.cpp index 4297485..ce1dd84 100644 --- a/QtScrcpy/device/controller/inputconvert/keymap/keymap.cpp +++ b/QtScrcpy/device/controller/inputconvert/keymap/keymap.cpp @@ -41,10 +41,6 @@ void KeyMap::loadKeyMap(const QString &json) QJsonDocument jsonDoc; QJsonObject rootObj; - QMetaEnum metaEnumKey = QMetaEnum::fromType(); - QMetaEnum metaEnumMouseButtons = QMetaEnum::fromType(); - QMetaEnum metaEnumKeyMapType = QMetaEnum::fromType(); - jsonDoc = QJsonDocument::fromJson(json.toUtf8(), &jsonError); if(jsonError.error != QJsonParseError::NoError) @@ -56,12 +52,13 @@ void KeyMap::loadKeyMap(const QString &json) // switchKey rootObj = jsonDoc.object(); if (rootObj.contains("switchKey") && rootObj.value("switchKey").isString()) { - Qt::Key key = (Qt::Key)metaEnumKey.keyToValue(rootObj.value("switchKey").toString().toStdString().c_str()); - if (-1 == key) { + QPair p = getItemKey(rootObj, "switchKey"); + if(p.first == AT_INVALID){ errorString = QString("json error: switchKey invalid"); goto parseError; } - m_switchKey = key; + m_switchType = p.first; + m_switchKey = p.second; } else { errorString = QString("json error: no find switchKey"); goto parseError; @@ -106,153 +103,124 @@ void KeyMap::loadKeyMap(const QString &json) goto parseError; } - KeyMap::KeyMapType type = (KeyMap::KeyMapType)metaEnumKeyMapType.keyToValue(node.value("type").toString().toStdString().c_str()); + KeyMap::KeyMapType type = getItemType(node, "type"); switch (type) { case KeyMap::KMT_CLICK: { // safe check - if (!node.contains("key") || !node.value("key").isString() - || !node.contains("pos") || !node.value("pos").isObject() - || !node.value("pos").toObject().contains("x") || !node.value("pos").toObject().value("x").isDouble() - || !node.value("pos").toObject().contains("y") || !node.value("pos").toObject().value("y").isDouble() - || !node.contains("switchMap") || !node.value("switchMap").isBool() - ) { + if (!checkForClick(node)) { qWarning() << "json error: keyMapNodes node format error"; break; } - - Qt::Key key = (Qt::Key)metaEnumKey.keyToValue(node.value("key").toString().toStdString().c_str()); - Qt::MouseButtons btn = (Qt::MouseButtons)metaEnumMouseButtons.keyToValue(node.value("key").toString().toStdString().c_str()); - if (-1 == key && -1 == btn) { + QPair key = getItemKey(node, "key"); + if(key.first == AT_INVALID){ qWarning() << "json error: keyMapNodes node invalid key: " << node.value("key").toString(); break; } - KeyMapNode keyMapNode; keyMapNode.type = type; - if (key != -1) { - keyMapNode.click.keyNode.key = key; - } else { - keyMapNode.click.keyNode.key = btn; - } - keyMapNode.click.keyNode.pos = QPointF(node.value("pos").toObject().value("x").toDouble(), - node.value("pos").toObject().value("y").toDouble()); - keyMapNode.click.switchMap = node.value("switchMap").toBool(); + keyMapNode.click.keyNode.type = key.first; + keyMapNode.click.keyNode.key = key.second; + keyMapNode.click.keyNode.pos = getItemPos(node, "pos"); + keyMapNode.click.switchMap = getItemSwitchMap(node, "switchMap"); m_keyMapNodes.push_back(keyMapNode); } break; case KeyMap::KMT_CLICK_TWICE: { // safe check - if (!node.contains("key") || !node.value("key").isString() - || !node.contains("pos") || !node.value("pos").isObject() - || !node.value("pos").toObject().contains("x") || !node.value("pos").toObject().value("x").isDouble() - || !node.value("pos").toObject().contains("y") || !node.value("pos").toObject().value("y").isDouble() - ) { + if (!checkForClickDouble(node)) { qWarning() << "json error: keyMapNodes node format error"; break; } - Qt::Key key = (Qt::Key)metaEnumKey.keyToValue(node.value("key").toString().toStdString().c_str()); - Qt::MouseButtons btn = (Qt::MouseButtons)metaEnumMouseButtons.keyToValue(node.value("key").toString().toStdString().c_str()); - if (-1 == key && -1 == btn) { + QPair key = getItemKey(node, "key"); + if(key.first == AT_INVALID){ qWarning() << "json error: keyMapNodes node invalid key: " << node.value("key").toString(); break; } - KeyMapNode keyMapNode; keyMapNode.type = type; - if (key != -1) { - keyMapNode.clickTwice.keyNode.key = key; - } else { - keyMapNode.clickTwice.keyNode.key = btn; - } - keyMapNode.clickTwice.keyNode.pos = QPointF(node.value("pos").toObject().value("x").toDouble(), - node.value("pos").toObject().value("y").toDouble()); + keyMapNode.click.keyNode.type = key.first; + keyMapNode.click.keyNode.key = key.second; + keyMapNode.click.keyNode.pos = getItemPos(node, "pos"); + keyMapNode.click.switchMap = getItemSwitchMap(node, "switchMap"); m_keyMapNodes.push_back(keyMapNode); } break; case KeyMap::KMT_STEER_WHEEL: { // safe check - if (!node.contains("leftKey") || !node.value("leftKey").isString() - || !node.contains("rightKey") || !node.value("rightKey").isString() - || !node.contains("upKey") || !node.value("upKey").isString() - || !node.contains("downKey") || !node.value("downKey").isString() - || !node.contains("leftOffset") || !node.value("leftOffset").isDouble() - || !node.contains("rightOffset") || !node.value("rightOffset").isDouble() - || !node.contains("upOffset") || !node.value("upOffset").isDouble() - || !node.contains("downOffset") || !node.value("downOffset").isDouble() - || !node.contains("centerPos") || !node.value("centerPos").isObject() - || !node.value("centerPos").toObject().contains("x") || !node.value("centerPos").toObject().value("x").isDouble() - || !node.value("centerPos").toObject().contains("y") || !node.value("centerPos").toObject().value("y").isDouble() - ) { + if(!checkForSteerWhell(node)){ qWarning() << "json error: keyMapNodes node format error"; break; } - - Qt::Key leftKey = (Qt::Key)metaEnumKey.keyToValue(node.value("leftKey").toString().toStdString().c_str()); - Qt::MouseButtons leftBtn = (Qt::MouseButtons)metaEnumMouseButtons.keyToValue(node.value("leftKey").toString().toStdString().c_str()); - Qt::Key rightKey = (Qt::Key)metaEnumKey.keyToValue(node.value("rightKey").toString().toStdString().c_str()); - Qt::MouseButtons rightBtn = (Qt::MouseButtons)metaEnumMouseButtons.keyToValue(node.value("rightKey").toString().toStdString().c_str()); - Qt::Key upKey = (Qt::Key)metaEnumKey.keyToValue(node.value("upKey").toString().toStdString().c_str()); - Qt::MouseButtons upBtn = (Qt::MouseButtons)metaEnumMouseButtons.keyToValue(node.value("upKey").toString().toStdString().c_str()); - Qt::Key downKey = (Qt::Key)metaEnumKey.keyToValue(node.value("downKey").toString().toStdString().c_str()); - Qt::MouseButtons downBtn = (Qt::MouseButtons)metaEnumMouseButtons.keyToValue(node.value("downKey").toString().toStdString().c_str()); - - if ((-1 == leftKey && -1 == leftBtn) - || (-1 == rightKey && -1 == rightBtn) - || (-1 == upKey && -1 == upBtn) - || (-1 == downKey && -1 == downBtn) - ) { - qWarning() << "json error: keyMapNodes node invalid key: " << node.value("key").toString(); + QPair leftKey = getItemKey(node, "leftKey"); + QPair rightKey = getItemKey(node, "rightKey"); + QPair upKey = getItemKey(node, "upKey"); + QPair downKey = getItemKey(node, "downKey"); + if(leftKey.first == AT_INVALID || rightKey.first == AT_INVALID + || upKey.first == AT_INVALID || downKey.first == AT_INVALID) + { + if(leftKey.first == AT_INVALID) + qWarning() << "json error: keyMapNodes node invalid key: " << node.value("leftKey").toString(); + if(rightKey.first == AT_INVALID) + qWarning() << "json error: keyMapNodes node invalid key: " << node.value("rightKey").toString(); + if(upKey.first == AT_INVALID) + qWarning() << "json error: keyMapNodes node invalid key: " << node.value("upKey").toString(); + if(downKey.first == AT_INVALID) + qWarning() << "json error: keyMapNodes node invalid key: " << node.value("downKey").toString(); break; } KeyMapNode keyMapNode; keyMapNode.type = type; - keyMapNode.steerWheel.leftKeyPressed = false; - keyMapNode.steerWheel.rightKeyPressed = false; - keyMapNode.steerWheel.upKeyPressed = false; - keyMapNode.steerWheel.downKeyPressed = false; - keyMapNode.steerWheel.pressKeysNum = 0; - keyMapNode.steerWheel.firstPressKey = 0; - if (leftKey != -1) { - keyMapNode.steerWheel.leftKey = leftKey; - } else { - keyMapNode.steerWheel.leftKey = leftBtn; - } - if (rightKey != -1) { - keyMapNode.steerWheel.rightKey = rightKey; - } else { - keyMapNode.steerWheel.rightKey = rightBtn; - } - if (upKey != -1) { - keyMapNode.steerWheel.upKey = upKey; - } else { - keyMapNode.steerWheel.upKey = upBtn; - } - if (downKey != -1) { - keyMapNode.steerWheel.downKey = downKey; - } else { - keyMapNode.steerWheel.downKey = downBtn; - } - keyMapNode.steerWheel.leftOffset = node.value("leftOffset").toDouble(); - keyMapNode.steerWheel.rightOffset = node.value("rightOffset").toDouble(); - keyMapNode.steerWheel.upOffset = node.value("upOffset").toDouble(); - keyMapNode.steerWheel.downOffset = node.value("downOffset").toDouble(); - keyMapNode.steerWheel.centerPos = QPointF(node.value("centerPos").toObject().value("x").toDouble(), - node.value("centerPos").toObject().value("y").toDouble()); + keyMapNode.steerWheel.left = { leftKey.first, leftKey.second, + getItemNumber(node, "leftOffset") }; + keyMapNode.steerWheel.right = { rightKey.first, rightKey.second, + getItemNumber(node, "rightOffset") }; + keyMapNode.steerWheel.up = { upKey.first, upKey.second, + getItemNumber(node, "upOffset") }; + keyMapNode.steerWheel.down = { downKey.first, downKey.second, + getItemNumber(node, "downOffset") }; + + keyMapNode.steerWheel.centerPos = getItemPos(node, "centerPos"); + m_idxSteerWheel = m_keyMapNodes.size(); m_keyMapNodes.push_back(keyMapNode); } break; + case KeyMap::KMT_DRAG: + { + // safe check + if(!checkForDrag(node)){ + qWarning() << "json error: keyMapNodes node format error"; + break; + } + + QPair key = getItemKey(node, "key"); + if(key.first == AT_INVALID){ + qWarning() << "json error: keyMapNodes node invalid key: " << node.value("key").toString(); + break; + } + KeyMapNode keyMapNode; + keyMapNode.type = type; + keyMapNode.drag.type = key.first; + keyMapNode.drag.key = key.second; + keyMapNode.drag.startPos = getItemPos(node, "startPos"); + keyMapNode.drag.endPos = getItemPos(node, "endPos"); + m_keyMapNodes.push_back(keyMapNode); + break; + } default: qWarning() << "json error: keyMapNodes invalid node type:" << node.value("type").toString(); break; } } } + // this must be called after m_keyMapNodes is stable + makeReverseMap(); + qWarning() << "Script updated."; parseError: if (!errorString.isEmpty()) { @@ -261,35 +229,27 @@ parseError: return; } -KeyMap::KeyMapNode& KeyMap::getKeyMapNode(int key) +const KeyMap::KeyMapNode& KeyMap::getKeyMapNode(int key) { - for (auto& itemNode : m_keyMapNodes) { - switch (itemNode.type) { - case KMT_CLICK: - if (itemNode.click.keyNode.key == key) { - return itemNode; - } - break; - case KMT_CLICK_TWICE: - if (itemNode.clickTwice.keyNode.key == key) { - return itemNode; - } - break; - case KMT_STEER_WHEEL: - if (itemNode.steerWheel.leftKey == key - || itemNode.steerWheel.rightKey == key - || itemNode.steerWheel.upKey == key - || itemNode.steerWheel.downKey == key - ) { - return itemNode; - } - break; - default: - break; - } - } + auto p = rmapKey.value(key, &m_invalidNode); + if(p == &m_invalidNode) + return *rmapMouse.value(key, &m_invalidNode); + return *p; +} - return m_invalidNode; +const KeyMap::KeyMapNode& KeyMap::getKeyMapNodeKey(int key) +{ + return *rmapKey.value(key, &m_invalidNode); +} + +const KeyMap::KeyMapNode& KeyMap::getKeyMapNodeMouse(int key) +{ + return *rmapMouse.value(key, &m_invalidNode); +} + +bool KeyMap::isSwitchOnKeyboard() +{ + return m_switchType == AT_KEY; } int KeyMap::getSwitchKey() @@ -297,12 +257,159 @@ int KeyMap::getSwitchKey() return m_switchKey; } -KeyMap::MouseMoveMap KeyMap::getMouseMoveMap() +const KeyMap::MouseMoveMap& KeyMap::getMouseMoveMap() { return m_mouseMoveMap; } -bool KeyMap::enableMouseMoveMap() +const KeyMap::KeyMapNode& KeyMap::getSteerWheelMap() +{ + return m_keyMapNodes[m_idxSteerWheel]; +} + +bool KeyMap::isValidMouseMoveMap() { return !m_mouseMoveMap.startPos.isNull(); } + +bool KeyMap::isValidSteerWheelMap() +{ + return m_idxSteerWheel != -1; +} + +void KeyMap::makeReverseMap() +{ + rmapKey.clear(); + rmapMouse.clear(); + for(int i = 0 ;i < m_keyMapNodes.size(); ++i) { + auto& node = m_keyMapNodes[i]; + switch (node.type) { + case KMT_CLICK: + { + QMultiHash& m = node.click.keyNode.type == AT_KEY ? rmapKey : rmapMouse; + m.insert(node.click.keyNode.key, &node); + } + break; + case KMT_CLICK_TWICE: + { + QMultiHash& m = node.clickTwice.keyNode.type == AT_KEY ? rmapKey : rmapMouse; + m.insert(node.clickTwice.keyNode.key, &node); + } + break; + case KMT_STEER_WHEEL: + { + QMultiHash& ml = node.steerWheel.left.type == AT_KEY ? rmapKey : rmapMouse; + ml.insert(node.steerWheel.left.key, &node); + QMultiHash& mr = node.steerWheel.right.type == AT_KEY ? rmapKey : rmapMouse; + mr.insert(node.steerWheel.right.key, &node); + QMultiHash& mu = node.steerWheel.up.type == AT_KEY ? rmapKey : rmapMouse; + mu.insert(node.steerWheel.up.key, &node); + QMultiHash& md = node.steerWheel.down.type == AT_KEY ? rmapKey : rmapMouse; + md.insert(node.steerWheel.down.key, &node); + } + break; + case KMT_DRAG: + { + QMultiHash& m = node.drag.type == AT_KEY ? rmapKey : rmapMouse; + m.insert(node.drag.key, &node); + } + break; + default: + break; + } + } +} + +// ---- check and get of json item ---- + +bool KeyMap::checkItemKey(const QJsonObject& node, const QString& name) +{ + return node.contains(name) && node.value(name).isString(); +} + +bool KeyMap::checkItemPos(const QJsonObject& node, const QString& name) +{ + if(node.contains(name) && node.value(name).isObject()){ + QJsonObject pos = node.value(name).toObject(); + return pos.contains("x") && pos.value("x").isDouble() + && pos.contains("y") && pos.value("y").isDouble(); + } + return false; +} + +bool KeyMap::checkItemDouble(const QJsonObject& node, const QString& name) +{ + return node.contains(name) && node.value(name).isDouble(); +} + +bool KeyMap::checkItemSwitchMap(const QJsonObject& node, const QString& name) +{ + return !node.contains(name) || node.value(name).isBool(); +} + +KeyMap::KeyMapType KeyMap::getItemType(const QJsonObject& node, const QString& name) +{ + QString value = node.value(name).toString(); + return static_cast(m_metaEnumKeyMapType.keyToValue(value.toStdString().c_str())); +} + +QPair KeyMap::getItemKey(const QJsonObject& node, const QString& name) +{ + QString value = node.value(name).toString(); + int key = m_metaEnumKey.keyToValue(value.toStdString().c_str()); + int btn = m_metaEnumMouseButtons.keyToValue(value.toStdString().c_str()); + if(key == -1 && btn == -1){ + return {AT_INVALID, -1}; + }else if(key != -1){ + return {AT_KEY, key}; + }else{ + return {AT_MOUSE, btn}; + } +} + +QPointF KeyMap::getItemPos(const QJsonObject& node, const QString& name) +{ + QJsonObject pos = node.value(name).toObject(); + return QPointF(pos.value("x").toDouble(), pos.value("y").toDouble()); +} + +double KeyMap::getItemNumber(const QJsonObject& node, const QString& name) +{ + return node.value(name).toDouble(); +} + +bool KeyMap::getItemSwitchMap(const QJsonObject& node, const QString& name) +{ + return node.value(name).toBool(false); +} + + +// ---- check for key-map node ---- + +bool KeyMap::checkForClick(const QJsonObject& node) +{ + return checkItemKey(node, "key") && checkItemPos(node, "pos") + && checkItemSwitchMap(node, "switchMap"); +} + +bool KeyMap::checkForClickDouble(const QJsonObject& node) +{ + return checkForClick(node); +} + +bool KeyMap::checkForSteerWhell(const QJsonObject& node) +{ + return checkItemKey(node, "leftKey") && checkItemKey(node, "rightKey") + && checkItemKey(node, "upKey") && checkItemKey(node, "downKey") + && checkItemDouble(node, "leftOffset") && checkItemDouble(node, "rightOffset") + && checkItemDouble(node, "upOffset") && checkItemDouble(node, "downOffset") + && checkItemPos(node, "centerPos"); +} + +bool KeyMap::checkForDrag(const QJsonObject& node) +{ + return checkItemKey(node, "key") + && checkItemPos(node, "startPos") && checkItemPos(node, "endPos") + && checkItemSwitchMap(node, "switchMap"); +} + diff --git a/QtScrcpy/device/controller/inputconvert/keymap/keymap.h b/QtScrcpy/device/controller/inputconvert/keymap/keymap.h index 1b4883b..bfcd83f 100644 --- a/QtScrcpy/device/controller/inputconvert/keymap/keymap.h +++ b/QtScrcpy/device/controller/inputconvert/keymap/keymap.h @@ -4,7 +4,11 @@ #include #include #include +#include +#include +#include +class QJsonObject; class KeyMap : public QObject { @@ -15,10 +19,19 @@ public: KMT_CLICK = 0, KMT_CLICK_TWICE, KMT_STEER_WHEEL, + KMT_DRAG, }; Q_ENUM(KeyMapType) + enum ActionType { + AT_INVALID = -1, + AT_KEY = 0, + AT_MOUSE = 1, + }; + Q_ENUM(ActionType) + struct KeyNode { + ActionType type = AT_INVALID; int key = Qt::Key_unknown; QPointF pos = QPointF(0, 0); }; @@ -34,39 +47,27 @@ public: KeyNode keyNode; } clickTwice; struct { - // 方向盘矩形中心位置 - QPointF centerPos = {0.0f, 0.0f}; - - // 方向盘矩形四个方向偏移量 - float leftOffset = 0.0f; - float rightOffset = 0.0f; - float upOffset = 0.0f; - float downOffset = 0.0f; - - // 方向盘矩形四个方向按键 - int leftKey = Qt::Key_unknown; - int rightKey = Qt::Key_unknown; - int upKey = Qt::Key_unknown; - int downKey = Qt::Key_unknown; - - // 辅助变量 - // 方向键的按下状态 - bool leftKeyPressed = false; - bool rightKeyPressed = false; - bool upKeyPressed = false; - bool downKeyPressed = false; - // 按下方向键的数量 - int pressKeysNum = 0; - // 第一次按下的键 - int firstPressKey = 0; + QPointF centerPos = {0.0, 0.0}; + struct DirInfo{ + ActionType type = AT_KEY; // keyboard/mouse + int key = Qt::Key_unknown; // key/button + double offset = 0.0; + }; + DirInfo left, right, up, down; } steerWheel; + struct { + ActionType type = AT_KEY; + int key = Qt::Key_unknown; + QPointF startPos = QPointF(0, 0); + QPointF endPos = QPointF(0, 0); + } drag; }; KeyMapNode() {} ~KeyMapNode() {} }; struct MouseMoveMap { - QPointF startPos = {0.0f, 0.0f}; + QPointF startPos = {0.0, 0.0}; int speedRatio = 1; }; @@ -74,19 +75,58 @@ public: virtual ~KeyMap(); void loadKeyMap(const QString &json); - KeyMap::KeyMapNode& getKeyMapNode(int key); + const KeyMap::KeyMapNode& getKeyMapNode(int key); + const KeyMap::KeyMapNode& getKeyMapNodeKey(int key); + const KeyMap::KeyMapNode& getKeyMapNodeMouse(int key); + bool isSwitchOnKeyboard(); int getSwitchKey(); - MouseMoveMap getMouseMoveMap(); - bool enableMouseMoveMap(); + + bool isValidMouseMoveMap(); + bool isValidSteerWheelMap(); + const MouseMoveMap& getMouseMoveMap(); + const KeyMapNode& getSteerWheelMap(); static const QString& getKeyMapPath(); +private: + // set up the reverse map from key/event event to keyMapNode + void makeReverseMap(); + + // parse json of the mapping script + bool checkItemKey(const QJsonObject& node, const QString& name="key"); + bool checkItemPos(const QJsonObject& node, const QString& name="pos"); + bool checkItemDouble(const QJsonObject& node, const QString& name); + bool checkItemSwitchMap(const QJsonObject& node, const QString& name="switchMap"); + + KeyMapType getItemType(const QJsonObject& node, const QString& name="type"); + QPair getItemKey(const QJsonObject& node, const QString& name="key"); + QPointF getItemPos(const QJsonObject& node, const QString& name="pos"); + double getItemNumber(const QJsonObject& node, const QString& name); + bool getItemSwitchMap(const QJsonObject& node, const QString& name="switchMap"); + +private: + bool checkForClick(const QJsonObject& node); + bool checkForClickDouble(const QJsonObject& node); + bool checkForSteerWhell(const QJsonObject& node); + bool checkForDrag(const QJsonObject& node); + private: QVector m_keyMapNodes; KeyMapNode m_invalidNode; + ActionType m_switchType = AT_KEY; int m_switchKey = Qt::Key_QuoteLeft; MouseMoveMap m_mouseMoveMap; static QString s_keyMapPath; + + int m_idxSteerWheel = -1; + + // mapping of key/mouse event name to index + QMetaEnum m_metaEnumKey = QMetaEnum::fromType(); + QMetaEnum m_metaEnumMouseButtons = QMetaEnum::fromType(); + QMetaEnum m_metaEnumKeyMapType = QMetaEnum::fromType(); + // reverse map of key/mouse event + QMultiHash rmapKey; + QMultiHash rmapMouse; }; #endif // KEYMAP_H diff --git a/QtScrcpy/device/device.cpp b/QtScrcpy/device/device.cpp index 341afe4..1127001 100644 --- a/QtScrcpy/device/device.cpp +++ b/QtScrcpy/device/device.cpp @@ -90,6 +90,13 @@ Server *Device::getServer() return m_server; } +void Device::updateScript(QString script) +{ + if(m_controller){ + m_controller->updateScript(script); + } +} + void Device::initSignals() { if (m_controller && m_videoForm) { diff --git a/QtScrcpy/device/device.h b/QtScrcpy/device/device.h index 7e6dd0b..b7573ee 100644 --- a/QtScrcpy/device/device.h +++ b/QtScrcpy/device/device.h @@ -35,6 +35,8 @@ public: Controller *getController(); Server *getServer(); + void updateScript(QString script); + signals: void deviceDisconnect(QString serial); diff --git a/QtScrcpy/device/ui/toolform.cpp b/QtScrcpy/device/ui/toolform.cpp index b8190bd..b8924dd 100644 --- a/QtScrcpy/device/ui/toolform.cpp +++ b/QtScrcpy/device/ui/toolform.cpp @@ -40,6 +40,7 @@ void ToolForm::initStyle() IconHelper::Instance()->SetIcon(ui->closeScreenBtn, QChar(0xf070), 15); IconHelper::Instance()->SetIcon(ui->powerBtn, QChar(0xf011), 15); IconHelper::Instance()->SetIcon(ui->expandNotifyBtn, QChar(0xf103), 15); + IconHelper::Instance()->SetIcon(ui->screenShotBtn, QChar(0xf05b), 15); } void ToolForm::mousePressEvent(QMouseEvent *event) @@ -115,6 +116,13 @@ void ToolForm::on_powerBtn_clicked() } } +void ToolForm::on_screenShotBtn_clicked() +{ + if (m_videoForm && m_videoForm->getController()) { + m_videoForm->getController()->screenShot(); + } +} + void ToolForm::on_volumeUpBtn_clicked() { if (m_videoForm && m_videoForm->getController()) { diff --git a/QtScrcpy/device/ui/toolform.h b/QtScrcpy/device/ui/toolform.h index 78baa82..01e18bf 100644 --- a/QtScrcpy/device/ui/toolform.h +++ b/QtScrcpy/device/ui/toolform.h @@ -40,6 +40,8 @@ private slots: void on_powerBtn_clicked(); + void on_screenShotBtn_clicked(); + void on_volumeUpBtn_clicked(); void on_volumeDownBtn_clicked(); diff --git a/QtScrcpy/device/ui/toolform.ui b/QtScrcpy/device/ui/toolform.ui index 865db5f..4676beb 100644 --- a/QtScrcpy/device/ui/toolform.ui +++ b/QtScrcpy/device/ui/toolform.ui @@ -7,7 +7,7 @@ 0 0 63 - 497 + 537 @@ -133,6 +133,16 @@ + + + + screen shot + + + + + + diff --git a/QtScrcpy/device/ui/videoform.cpp b/QtScrcpy/device/ui/videoform.cpp index 45e132e..e07d4b9 100644 --- a/QtScrcpy/device/ui/videoform.cpp +++ b/QtScrcpy/device/ui/videoform.cpp @@ -145,7 +145,8 @@ void VideoForm::updateShowSize(const QSize &newSize) showSize.setHeight(qMin(newSize.height(), screenRect.height() - 200)); showSize.setWidth(showSize.height() * m_widthHeightRatio); } else { - showSize.setWidth(qMin(newSize.width(), screenRect.width()/2)); + //showSize.setWidth(qMin(newSize.width(), screenRect.width()/2)); + showSize.setWidth(qMin(newSize.width(), screenRect.width() - 200)); showSize.setHeight(showSize.width() * m_widthHeightRatio); } diff --git a/QtScrcpy/devicemanage/devicemanage.cpp b/QtScrcpy/devicemanage/devicemanage.cpp index 210e7a9..646becc 100644 --- a/QtScrcpy/devicemanage/devicemanage.cpp +++ b/QtScrcpy/devicemanage/devicemanage.cpp @@ -47,6 +47,17 @@ bool DeviceManage::connectDevice(Device::DeviceParams params) return true; } +void DeviceManage::updateScript(QString script) +{ + QMapIterator> i(m_devices); + while (i.hasNext()) { + i.next(); + if (i.value()) { + i.value()->updateScript(script); + } + } +} + bool DeviceManage::disconnectDevice(const QString &serial) { bool ret = false; diff --git a/QtScrcpy/devicemanage/devicemanage.h b/QtScrcpy/devicemanage/devicemanage.h index ddcbd1f..09b9c7e 100644 --- a/QtScrcpy/devicemanage/devicemanage.h +++ b/QtScrcpy/devicemanage/devicemanage.h @@ -14,6 +14,8 @@ public: virtual ~DeviceManage(); bool connectDevice(Device::DeviceParams params); + void updateScript(QString script); + bool disconnectDevice(const QString &serial); void disconnectAllDevice(); diff --git a/QtScrcpy/dialog.cpp b/QtScrcpy/dialog.cpp index 5469ab7..fd8500a 100644 --- a/QtScrcpy/dialog.cpp +++ b/QtScrcpy/dialog.cpp @@ -81,11 +81,12 @@ void Dialog::initUI() ui->bitRateBox->setCurrentIndex(2); ui->maxSizeBox->addItem("600"); - ui->maxSizeBox->addItem("800"); + ui->maxSizeBox->addItem("720"); ui->maxSizeBox->addItem("1000"); - ui->maxSizeBox->addItem("1200"); + ui->maxSizeBox->addItem("1280"); + ui->maxSizeBox->addItem("1920"); ui->maxSizeBox->addItem("native"); - ui->maxSizeBox->setCurrentIndex(2); + ui->maxSizeBox->setCurrentIndex(3); ui->formatBox->addItem("mp4"); ui->formatBox->addItem("mkv"); @@ -316,7 +317,7 @@ void Dialog::on_stopAllServerBtn_clicked() m_deviceManage.disconnectAllDevice(); } -void Dialog::on_updateGameScriptBtn_clicked() +void Dialog::on_refreshGameScriptBtn_clicked() { ui->gameBox->clear(); QDir dir(KeyMap::getKeyMapPath()); @@ -334,9 +335,14 @@ void Dialog::on_updateGameScriptBtn_clicked() } } +void Dialog::on_applyScriptBtn_clicked() +{ + m_deviceManage.updateScript(getGameScript(ui->gameBox->currentText())); +} + void Dialog::on_gameCheck_clicked(bool checked) { if (checked) { - on_updateGameScriptBtn_clicked(); + on_refreshGameScriptBtn_clicked(); } } diff --git a/QtScrcpy/dialog.h b/QtScrcpy/dialog.h index 23a471b..7f58fa8 100644 --- a/QtScrcpy/dialog.h +++ b/QtScrcpy/dialog.h @@ -49,7 +49,8 @@ private slots: void on_stopAllServerBtn_clicked(); - void on_updateGameScriptBtn_clicked(); + void on_refreshGameScriptBtn_clicked(); + void on_applyScriptBtn_clicked(); void on_gameCheck_clicked(bool checked); private: diff --git a/QtScrcpy/dialog.ui b/QtScrcpy/dialog.ui index 1b77309..c4161aa 100644 --- a/QtScrcpy/dialog.ui +++ b/QtScrcpy/dialog.ui @@ -6,14 +6,14 @@ 0 0 - 513 - 637 + 600 + 700 600 - 16777215 + 2160 @@ -26,20 +26,20 @@ USB line - - + + + + + - get device ip - - - false + device serial: - - + + - stop server + get device IP false @@ -49,33 +49,13 @@ - update device + refresh devices false - - - - start adbd - - - false - - - - - - - device serial: - - - - - - @@ -93,6 +73,26 @@ + + + + stop server + + + false + + + + + + + start adbd + + + false + + + @@ -101,8 +101,37 @@ Wireless - - + + + + + + 0 + 0 + + + + wireless connect + + + false + + + + + + + + + + 128 + + + 192.168.0.1 + + + + @@ -121,38 +150,27 @@ - + + + 6 + 5555 - - - - - - - 192.168.0.1 - - - - - - - wireless connect - - - false - - - - + + + + 0 + 0 + + wireless disconnect @@ -169,7 +187,7 @@ 0 - 200 + 240 @@ -243,49 +261,13 @@ - - + + - not display - - - false + record format: - - - - - - - - - - - - - - always top - - - false - - - - - - - use reverse - - - true - - - - - - @@ -296,35 +278,85 @@ - - + + + + + - max size: + refresh script - - - - close screen + + + + + 0 + 0 + - - - - - + + reverse connection + + true - - - - + + + + auto enable - + + + + + 0 + 0 + + + + always on top + + + false + + + + + + + + 0 + 0 + + + + background record + + + false + + + + + + + + 0 + 0 + + + + screen-off + + + + select path @@ -334,40 +366,44 @@ - - - - record format: - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - + - - - - update script + + + + true - - + + + + + + + + + + + + - custom map + apply + + + + + + + + + + + + + + max size: @@ -378,6 +414,16 @@ + bitRateBox + formatBox + recordPathEdt + selectRecordPathBtn + gameBox + refreshGameScriptBtn + serialBox + startServerBtn + stopServerBtn + stopAllServerBtn updateDevice getIPBtn startAdbdBtn @@ -387,13 +433,8 @@ wirelessDisConnectBtn adbCommandEdt adbCommandBtn - formatBox - recordPathEdt - selectRecordPathBtn - serialBox - startServerBtn - stopServerBtn - outEdit + stopAdbBtn + clearOut diff --git a/QtScrcpy/res/i18n/QtScrcpy_en.qm b/QtScrcpy/res/i18n/QtScrcpy_en.qm index 89b24d1..f8fd86b 100644 Binary files a/QtScrcpy/res/i18n/QtScrcpy_en.qm and b/QtScrcpy/res/i18n/QtScrcpy_en.qm differ diff --git a/QtScrcpy/res/i18n/QtScrcpy_en.ts b/QtScrcpy/res/i18n/QtScrcpy_en.ts index 1552df7..a5e14e4 100644 --- a/QtScrcpy/res/i18n/QtScrcpy_en.ts +++ b/QtScrcpy/res/i18n/QtScrcpy_en.ts @@ -16,22 +16,22 @@ file transfer failed - + install apk install apk - + file transfer file transfer - + wait current %1 to complete wait current %1 to complete - + %1 complete, save in %2 %1 complete, save in %2 @@ -41,7 +41,7 @@ %1 complete\n save in %2 - + %1 failed %1 failed @@ -54,99 +54,105 @@ Wireless - + wireless connect wireless connect - + wireless disconnect wireless disconnect - + Start Config Start Config - + record save path: record save path: - - + + select path select path - + record format: record format: - Game for Peace - Game for Peace - - - - not display - not display - - - + stop all server stop all server - + adb command: adb command: - + terminate terminate - + execute execute - + clear clear - - always top - always top + + reverse connection + reverse connection - - use reverse - use reverse + + auto enable + auto enable - + + background record + background record + + + + screen-off + screen-off + + + + apply + apply + + + max size: - max size + max size: - - close screen - close screen + + always on top + always on top - - update script - update script + + refresh script + refresh script - - custom map - custom map + + get device IP + get device IP @@ -154,17 +160,17 @@ USB line - + stop server stop server - + start server start server - + device serial: device serial: @@ -173,28 +179,19 @@ Config - + bit rate: bit rate: - video size: - video size: - - - + start adbd start adbd - - - get device ip - get device ip - - update device - update device + refresh devices + refresh devices @@ -262,6 +259,11 @@ return return + + + screen shot + screen shot + VideoForm @@ -278,7 +280,7 @@ file transfer failed - + file does not exist file does not exist diff --git a/QtScrcpy/res/i18n/QtScrcpy_zh.qm b/QtScrcpy/res/i18n/QtScrcpy_zh.qm index 0fa54e3..819342a 100644 Binary files a/QtScrcpy/res/i18n/QtScrcpy_zh.qm and b/QtScrcpy/res/i18n/QtScrcpy_zh.qm differ diff --git a/QtScrcpy/res/i18n/QtScrcpy_zh.ts b/QtScrcpy/res/i18n/QtScrcpy_zh.ts index f790a38..3fbf5f1 100644 --- a/QtScrcpy/res/i18n/QtScrcpy_zh.ts +++ b/QtScrcpy/res/i18n/QtScrcpy_zh.ts @@ -16,22 +16,22 @@ 文件传输失败 - + install apk 安装apk - + file transfer 文件传输 - + wait current %1 to complete 等待当前%1完成 - + %1 complete, save in %2 %1完成,保存在%2 @@ -41,7 +41,7 @@ %1完成\n 保存在 %2 - + %1 failed %1 失败 @@ -54,99 +54,105 @@ 无线 - + wireless connect - 无线连接 + wireless connect - + wireless disconnect - 无线断开 + wireless disconnect - + Start Config 启动配置 - + record save path: 录像保存路径: - - + + select path 选择路径 - + record format: 录制格式: - Game for Peace - 和平精英 - - - - not display - 仅后台录制 - - - + stop all server 停止所有服务 - + adb command: - adb命令行: + adb命令: - + terminate 终止 - + execute 执行 - + clear 清理 - - always top - 窗口置顶 + + reverse connection + 反向连接 - - use reverse - 使用reverse + + auto enable + 自动启用脚本 - - max size: - 最大尺寸 + + background record + 后台录制 - - close screen + + screen-off 自动息屏 - - update script - 更新脚本 + + apply + 应用脚本 - - custom map - 自定义映射 + + max size: + 最大尺寸: + + + + always on top + 窗口置顶 + + + + refresh script + 刷新脚本 + + + + get device IP + 获取设备IP @@ -154,17 +160,17 @@ USB线 - + stop server 停止服务 - + start server 启动服务 - + device serial: 设备序列号: @@ -173,27 +179,18 @@ 配置 - + bit rate: 比特率: - video size: - 视频尺寸: - - - + start adbd 启动adbd - - - get device ip - 获取设备IP - - update device + refresh devices 刷新设备列表 @@ -262,6 +259,11 @@ return 返回 + + + screen shot + 截图 + VideoForm @@ -278,7 +280,7 @@ 文件传输失败 - + file does not exist 文件不存在 diff --git a/QtScrcpy/res/image/videoform/phone-h.png b/QtScrcpy/res/image/videoform/phone-h.png index 0734a9b..7c55b49 100644 Binary files a/QtScrcpy/res/image/videoform/phone-h.png and b/QtScrcpy/res/image/videoform/phone-h.png differ diff --git a/QtScrcpy/res/image/videoform/phone-v.png b/QtScrcpy/res/image/videoform/phone-v.png index 09724cb..7fae500 100644 Binary files a/QtScrcpy/res/image/videoform/phone-v.png and b/QtScrcpy/res/image/videoform/phone-v.png differ diff --git a/README.md b/README.md index 859ac24..9a00f29 100644 --- a/README.md +++ b/README.md @@ -2,15 +2,15 @@ ![build state](https://img.shields.io/badge/build-passing-brightgreen.svg) ![license](https://img.shields.io/badge/license-Apache2.0-blue.svg) -![release](https://img.shields.io/badge/release-v1.0.1-brightgreen.svg) +![release](https://img.shields.io/badge/release-v1.0.3-brightgreen.svg) [中文介绍](README_zh.md) -QtScrcpy can connect to Android devices via USB (or via TCP/IP) for display and control. No root privileges are required. +QtScrcpy connects to Android devices via USB (or via TCP/IP) for display and control. It does NOT require the root privileges. -A single application can support up to 16 Android devices to connect at the same time. +A single instance supports up to 16 Android device connections at the same time. -Supports three major desktop platforms, GNU/Linux, Windows and MacOS. +It supports three major platforms: GNU/Linux, Windows and MacOS. It focuses on: @@ -29,59 +29,61 @@ It focuses on: ![linux](screenshot/ubuntu.png) -## Custom keymap(only windows enable) -You can write your own script to map the PC keyboard keys to the touch and click of the mobile phone according to your needs. [Here](docs/按键映射说明.md) are the rules. +## Customized key mapping (Windows only) +You can write your own script to map keyboard and mouse actions to touches and clicks of the mobile phone according to your needs. [Here](docs/按键映射说明.md) are the rules. -By default, it has its own mapping script for key and mouse mapping of "Game for peace" mobile games. When enabled, you can use key and mouse to play "Game for peace" mobile games like PC games. You can also write mapping files of other games according to [writing rules](docs/按键映射说明.md). The default key mapping is as follows: +A script for "PUBG mobile" mapping is provided by default. Once enabled, you can play the game with your keyboard and mouse as the PC version. You can also write your own mapping files for other games according to [writing rules](docs/按键映射说明.md). The default key mapping is as follows: ![game](screenshot/game.jpg) -[Here is a video demonstration of playing "Game for peace"](http://mp.weixin.qq.com/mp/video?__biz=MzU1NTg5MjYyNw==&mid=100000015&sn=3e301fdc5a364bd16d6207fa674bc8b3&vid=wxv_968792362971430913&idx=1&vidsn=eec329cc13c3e24c187dc9b4d5eb8760&fromid=1&scene=20&xtrack=1&clicktime=1567346543&sessionid=1567346375&subscene=92&ascene=0&fasttmpl_type=0&fasttmpl_fullversion=4730859-zh_CN-zip&fasttmpl_flag=0&realreporttime=1567346543910#wechat_redirect) +[Here is a video demonstration of playing "PUBG mobile"](http://mp.weixin.qq.com/mp/video?__biz=MzU1NTg5MjYyNw==&mid=100000015&sn=3e301fdc5a364bd16d6207fa674bc8b3&vid=wxv_968792362971430913&idx=1&vidsn=eec329cc13c3e24c187dc9b4d5eb8760&fromid=1&scene=20&xtrack=1&clicktime=1567346543&sessionid=1567346375&subscene=92&ascene=0&fasttmpl_type=0&fasttmpl_fullversion=4730859-zh_CN-zip&fasttmpl_flag=0&realreporttime=1567346543910#wechat_redirect) -The operation method of custom key mapping is as follows: -- Write a custom script and put it in the keymap directory -- Check the custom mapping option and select the custom mapping script before starting the service -- Enter the game scene after connecting the mobile phone -- Press the ~ key (left side of the number key 1) to switch to the game mapping mode to experience (what key to press depends on the switchkey defined by your key script) -- Press the ~ key again to switch to normal control mode -- To start the WASD control car, remember to set it to single rocker mode in vehicle settings. +Here is the instruction of adding new customized mapping files. + +- Write a customized script and put it in the `keymap` directory +- Click `refresh script` to check whether it can be found +- Connect your phone, start service and click `apply` +- Start the game and press `~` key (left side of the number key 1) to switch to the mapping mode (It can be changed in the script as `switchkey`) +- Press the ~ key again to switch back to normal mode +- (For PUBG and similar games) If you want to drive cars with WASD, you need to check the `single rocker mode` in the game setting. ## Thanks -QtScrcpy is based on [Genymobile's](https://github.com/Genymobile) [scrcpy](https://github.com/Genymobile/scrcpy) project and is very grateful to him. +QtScrcpy is based on [Genymobile's](https://github.com/Genymobile) [scrcpy](https://github.com/Genymobile/scrcpy) project. Thanks The difference between QtScrcpy and the original scrcpy is as follows: keys|scrcpy|QtScrcpy --|:--:|:--: ui|sdl|qt -video decode|ffmpeg|ffmpeg +video encode|ffmpeg|ffmpeg video render|sdl|opengl -base tool|c++|Qt +cross-platform|self implemented|provided by Qt language|C|C++ -style|sync|asyn +style|sync|async +control|single touch|single/multi touch build|meson+gradle|Qt Creator -- It's very easy to customize your interface with Qt +- It's very easy to customize your GUI with Qt - Asynchronous programming of Qt-based signal slot mechanism improves performance -- Easy for novices to learn -- Add multi touch support +- Easy to learn +- Add support for multi-touch ## Learn -If you are interested in it and want to learn how it works and feel that you can't get started, you can choose to purchase my recorded video lessons. -It details the development architecture and development process of the entire software, and takes you to develop QtScrcpy from scratch.: +If you are interested in it and want to learn how it works but do not know how to get started, you can choose to purchase my recorded video lessons. +It details the development architecture and the development process of the entire software, and help you develop QtScrcpy from scratch. -course introduction:[https://blog.csdn.net/rankun1/article/details/87970523](https://blog.csdn.net/rankun1/article/details/87970523) +Course introduction:[https://blog.csdn.net/rankun1/article/details/87970523](https://blog.csdn.net/rankun1/article/details/87970523) -Or you can join my QtScrcpy qq group and exchange ideas with like-minded friends.: +You can join my QQ group for QtScrcpy and exchange ideas with like-minded friends.: QQ Group number:901736468 ## Requirements -The Android part requires at least API 21 (Android 5.0). +Android API >= 21 (Android 5.0). Make sure you enabled [adb debugging][enable-adb] on your device(s). @@ -117,47 +119,50 @@ Connect to your Android device on your computer, then run the program and click ![run](screenshot/run.png) -### Wireless connection steps (ensure that the mobile phone and computer are in the same LAN): -1. Enable USB debugging in developer options on Android mobile terminal -2. Connect Android phone to computer via USB -3. Click update device, and you will see that the device number is updated. -4. Click get ip +### Wireless connection steps (ensure that the mobile phone and PC are in the same LAN): +1. Enable USB debugging in developer options on the Android device +2. Connect the Android device to computer via USB +3. Click update device, and you will see that the device number is updated +4. Click get device IP 5. Click start adbd 6. Click wireless connect -7. Click update device again, and another device with IP address beginning is found. Select this device. +7. Click update device again, and another device with IP address will be found. Select this device. 8. Click start service +​ + +Note: it is not necessary to keep you Android device connected via USB after you start adbd. + ## Interface button introduction: -- Startup configuration: function parameter settings before starting the service +- Start config: function parameter settings before starting the service You can set the bit rate, resolution, recording format, and video save path of the local recorded video. - - Recording only in the background: Starting the service is not realistic, just recording the Android device screen - - Window Top: Android device video window top display + - Background record: the Android device screen is not displayed after starting the service. It is recorded in background. + - Always on top: the video window for Android device will be kept on the top - Close screen: automatically turn off the Android device screen to save power after starting the service - - Use reverse: service startup mode, service startup failure error more than one device can remove this check to try to connect + - Reverse connection: service startup mode. You can uncheck it if you experience connection failure with message `more than one device` - -- Refresh device list: Refresh the currently connected device -- Start the service: connect to the Android device +- Refresh devices: Refresh the currently connected device +- Start service: connect to the Android device - Stop service: disconnect from Android device - Stop all services: disconnect all connected Android devices -- Get device ip: Get the IP address of the Android device and update it to the "Wireless" area for easy wireless connection. -- Start adbd: Start the adbd service of the Android device, you must start it before the wireless connection. -- Wireless connection: Connect to Android devices wirelessly +- Get device IP: Get the IP address of the Android device and update it to the "Wireless" area for the ease of wireless connection setting. +- Start adbd: Start the adbd service of the Android device. You must start it before the wireless connection. +- Wireless connect: Connect to Android devices wirelessly - Wireless disconnect: Disconnect wirelessly connected Android devices -- adb command line: convenient to execute custom adb commands (currently does not support blocking commands, such as shell) +- adb command: execute customized adb commands (blocking commands are not supported now, such as shell) ## The main function - Display Android device screens in real time -Real-time mouse and keyboard control Android device +- Real-time mouse and keyboard control of Android devices - Screen recording -- Wireless connections -- Supports up to 16 device connections (can be added if PC performance allows, you need to compile it yourself) -- full-screen display -- Window topping +- Wireless connection +- Supports up to 16 device connections (the number can be higher if your PC performance allows. You need to compile it by yourself) +- Full-screen display +- Display on the top - Install apk: drag and drop apk to the video window to install - Transfer files: Drag files to the video window to send files to Android devices - Background recording: record only, no display interface @@ -181,23 +186,24 @@ Real-time mouse and keyboard control Android device [DEVELOP](docs/DEVELOP.md) ## Why develop QtScrcpy? -There are several reasons for this, and the proportions are arranged from large to small: -1. In the process of learning Qt, you need a project to combat -2. It has audio and video related skills and is very interested in audio and video. -3. It has Android development skills, it’s a bit rusty for a long time, you need to consolidate it. -4. Found scrcpy, decided to re-entamrate with the new technology stack (C++ + Qt + Opengl + ffmpeg) +There are several reasons listed as below according to importance (high to low). +1. In the process of learning Qt, I need a real project to try +2. I have some background skill about audio and video and I am interested at them +3. I have some Android development skills. But I have used it for a long time. I want to consolidate it. +4. I found scrcpy and decided to re-make it with the new technology stack (C++ + Qt + Opengl + ffmpeg) -## Build -Try to provide all the dependencies and make it easy to compile. +## How to build +All the dependencies are provided and it is easy to compile. ### PC client -1. Set up the Qt development environment on the target platform (Qt >= 5.9.7, vs >= 2015 (not support mingw)) +1. Set up the Qt development environment on the target platform (Qt >= 5.9.7, vs >= 2015 (mingw not supported)) 2. Clone the project 3. Open the project root directory all.pro with QtCreator 4. Compile and run -### Android (If you do not need to modify the requirements, you can use the built-in scrcpy-server directly) +### Android (If you do not have special requirements, you can directly use the built-in scrcpy-server.jar) + 1. Set up an Android development environment on the target platform 2. Open server project in project root with Android Studio 3. The first time you open it, if you do not have the corresponding version of gradle, you will be prompted to find gradle, whether to upgrade gradle and create it. Select Cancel. After canceling, you will be prompted to select the location of the existing gradle. You can also cancel it (it will download automatically). @@ -208,13 +214,13 @@ Try to provide all the dependencies and make it easy to compile. Since it is based on scrcpy, respect its Licence Copyright (C) 2018 Genymobile - + Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at - + http://www.apache.org/licenses/LICENSE-2.0 - + Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. diff --git a/README_zh.md b/README_zh.md index 871e1c1..2216598 100644 --- a/README_zh.md +++ b/README_zh.md @@ -2,9 +2,9 @@ ![build state](https://img.shields.io/badge/build-passing-brightgreen.svg) ![license](https://img.shields.io/badge/license-Apache2.0-blue.svg) -![release](https://img.shields.io/badge/release-v1.0.1-brightgreen.svg) +![release](https://img.shields.io/badge/release-v1.0.3-brightgreen.svg) -[English introduction (from Google Translate)](README.md) +[English introduction](README.md) QtScrcpy可以通过USB(或通过TCP/IP)连接Android设备,并进行显示和控制。不需要root权限。 @@ -41,15 +41,15 @@ QtScrcpy可以通过USB(或通过TCP/IP)连接Android设备,并进行显示和 自定义按键映射操作方法如下: - 编写自定义脚本放入keymap目录 -- 启动服务之前记得勾选自定义映射选项,并选择自定义映射脚本 -- 连接手机以后进入游戏场景 -- 按~键(数字键1左边)切换为游戏映射模式即可体验(具体按什么键要看你按键脚本定义的switchKey) +- 点击刷新脚本,确保脚本可以被检测到 +- 连接手机并启动服务之后,点击应用脚本 +- 进入游戏场景,按~键(数字键1左边)切换为游戏映射模式即可体验(具体按什么键要看你按键脚本定义的switchKey) - 再次按~键切换为正常控制模式 - 要想wasd控制开车记得在载具设置中设置为单摇杆模式 ## 感谢 -基于[Genymobile](https://github.com/Genymobile)的[scrcpy](https://github.com/Genymobile/scrcpy)项目进行复刻,重构,非常感谢他。QtScrcpy和原版scrcpy区别如下: +基于[Genymobile](https://github.com/Genymobile)的[scrcpy](https://github.com/Genymobile/scrcpy)项目进行复刻,重构,非常感谢。QtScrcpy和原版scrcpy区别如下: 关键点|scrcpy|QtScrcpy --|:--:|:--: @@ -94,7 +94,7 @@ Android部分至少需要API 21(Android 5.0)。 ### Windows -windows平台,你可以直接使用我编译好的可执行程序: +Windows平台,你可以直接使用我编译好的可执行程序: - [国内下载][gitee-download] - [国外下载][github-download] @@ -128,13 +128,13 @@ Mac OS平台,你可以直接使用我编译好的可执行程序: 1. 安卓手机端在开发者选项中打开usb调试 2. 通过usb连接安卓手机到电脑 3. 点击刷新设备,会看到有设备号更新出来 -4. 点击获取设备ip +4. 点击获取设备IP 5. 点击启动adbd 6. 无线连接 -7. 再次点击刷新设备,发现多出了一个ip地址开头的设备,选择这个设备 +7. 再次点击刷新设备,发现多出了一个IP地址开头的设备,选择这个设备 8. 启动服务 -备注:启动adbd以后不用再usb线了,以后连接断开都不再需要,除非安卓adbd停了需要重新启动 +备注:启动adbd以后不用再连着usb线了,以后连接断开都不再需要,除非安卓adbd停了需要重新启动 ## 界面按钮介绍: @@ -147,7 +147,6 @@ Mac OS平台,你可以直接使用我编译好的可执行程序: - 自动息屏:启动服务以后,自动关闭Android设备屏幕节省电量 - 使用reverse:服务启动模式,出现服务启动失败报错more than one device可以去掉这个勾选尝试连接 - - 刷新设备列表:刷新当前连接的设备 - 启动服务:连接到Android设备 - 停止服务:断开与Android设备的连接 @@ -214,13 +213,13 @@ Mac OS平台,你可以直接使用我编译好的可执行程序: 由于是复刻的scrcpy,尊重它的Licence Copyright (C) 2018 Genymobile - + Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at - + http://www.apache.org/licenses/LICENSE-2.0 - + Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. diff --git a/docs/按键映射说明.md b/docs/按键映射说明.md index 0e1231f..35dd042 100644 --- a/docs/按键映射说明.md +++ b/docs/按键映射说明.md @@ -10,7 +10,7 @@ - 按键映射中的坐标位置都是用相对位置表示的,屏幕的宽高都用1表示,例如屏幕的像素为1920x1080,那么坐标(0.5,0.5)则表示的是 以屏幕左上角为原点,像素坐标(1920,1080)*(0.5,0.5)=(960,540)的位置。 -- 按键映射中的按键码是用Qt的枚举表示的,详细说明可以[参考Qt文档](https://doc-snapshots.qt.io/4.8/qt.html)(搜索 The key names used by Qt. 可以快速定位)。 +- 按键映射中的按键码是用Qt的枚举表示的,详细说明可以[参考Qt文档]( https://doc.qt.io/qt-5/qt.html )(搜索 The key names used by Qt. 可以快速定位)。 ### 映射类型说明