diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index c5d3e60..84fe4fa 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -46,15 +46,18 @@ jobs: - uses: actions/checkout@v1 with: fetch-depth: 1 + # 编译 - name: Build MacOS run: | export ENV_QT_CLANG=$(pwd)/${{env.Qt5_Dir}} ci/mac/build_for_mac.sh release + # 发布 - name: Publish if: startsWith(github.event.ref, 'refs/tags/') run: | export ENV_QT_CLANG=$(pwd)/${{env.Qt5_Dir}} ci/mac/publish_for_mac.sh ../build + ci/mac/package_for_mac.sh # tag 打包 - name: Package if: startsWith(github.event.ref, 'refs/tags/') @@ -65,7 +68,7 @@ jobs: [string]$tag = ${env:ref}.Substring(${env:ref}.LastIndexOf('/') + 1) [string]$name = 'QtScrcpy-mac-x64-' + ${tag} # 打包zip - Compress-Archive -Path ci\build\QtScrcpy.app ci\build\${name}.zip + Compress-Archive -Path ci\build\QtScrcpy.dmg ci\build\${name}.zip # 查询Release - name: Query Release if: startsWith(github.event.ref, 'refs/tags/') @@ -73,12 +76,20 @@ jobs: env: githubFullName: ${{ github.event.repository.full_name }} ref: ${{ github.event.ref }} + github_token: ${{ secrets.GITHUB_TOKEN }} run: | [string]$tag = ${env:ref}.Substring(${env:ref}.LastIndexOf('/') + 1) [string]$url = 'https://api.github.com/repos/' + ${env:githubFullName} + '/releases/tags/' + ${tag} + + $token = ${env:github_token} + $authInfo = ("{0}" -f $token) + $authInfo = [System.Text.Encoding]::UTF8.GetBytes($authInfo) + $authInfo = [System.Convert]::ToBase64String($authInfo) + $headers = @{Authorization=("barry-ran {0}" -f $authInfo)} + $response={} try { - $response = Invoke-RestMethod -Uri $url -Method Get + $response = Invoke-RestMethod -Uri $url -Headers $headers -Method Get } catch { Write-Host "StatusCode:" $_.Exception.Response.StatusCode.value__ Write-Host "StatusDescription:" $_.Exception.Response.StatusDescription @@ -102,12 +113,14 @@ jobs: run: | [string]$tag = ${env:ref}.Substring(${env:ref}.LastIndexOf('/') + 1) [string]$url = 'https://api.github.com/repos/' + ${env:githubFullName} + '/releases/tags/' + ${tag} + # github token防止api rate limite,否则一个小时只能60个api请求 $token = ${env:github_token} $authInfo = ("{0}" -f $token) $authInfo = [System.Text.Encoding]::UTF8.GetBytes($authInfo) $authInfo = [System.Convert]::ToBase64String($authInfo) $headers = @{Authorization=("barry-ran {0}" -f $authInfo)} + $response = Invoke-RestMethod -Uri $url -ContentType 'text/json' -Headers $headers -Method Get [string]$latestUpUrl = $response.upload_url Write-Host 'latestUpUrl:'$latestUpUrl diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index 2b40ad2..6b58178 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -103,12 +103,20 @@ jobs: env: githubFullName: ${{ github.event.repository.full_name }} ref: ${{ github.event.ref }} + github_token: ${{ secrets.GITHUB_TOKEN }} run: | [string]$tag = ${env:ref}.Substring(${env:ref}.LastIndexOf('/') + 1) [string]$url = 'https://api.github.com/repos/' + ${env:githubFullName} + '/releases/tags/' + ${tag} + + $token = ${env:github_token} + $authInfo = ("{0}" -f $token) + $authInfo = [System.Text.Encoding]::UTF8.GetBytes($authInfo) + $authInfo = [System.Convert]::ToBase64String($authInfo) + $headers = @{Authorization=("barry-ran {0}" -f $authInfo)} + $response={} try { - $response = Invoke-RestMethod -Uri $url -Method Get + $response = Invoke-RestMethod -Uri $url -Headers $headers -Method Get } catch { Write-Host "StatusCode:" $_.Exception.Response.StatusCode.value__ Write-Host "StatusDescription:" $_.Exception.Response.StatusDescription diff --git a/QtScrcpy/device/controller/controller.cpp b/QtScrcpy/device/controller/controller.cpp index 0ec988e..8418109 100644 --- a/QtScrcpy/device/controller/controller.cpp +++ b/QtScrcpy/device/controller/controller.cpp @@ -62,7 +62,7 @@ void Controller::updateScript(QString gameScript) connect(m_inputConvert, &InputConvertBase::grabCursor, this, &Controller::grabCursor); } -void Controller::postTurnOn() +void Controller::onPostBackOrScreenOn() { ControlMsg* controlMsg = new ControlMsg(ControlMsg::CMT_BACK_OR_SCREEN_ON); if (!controlMsg) { @@ -71,42 +71,42 @@ void Controller::postTurnOn() postControlMsg(controlMsg); } -void Controller::postGoHome() +void Controller::onPostGoHome() { postKeyCodeClick(AKEYCODE_HOME); } -void Controller::postGoMenu() +void Controller::onPostGoMenu() { postKeyCodeClick(AKEYCODE_MENU); } -void Controller::postGoBack() +void Controller::onPostGoBack() { postKeyCodeClick(AKEYCODE_BACK); } -void Controller::postAppSwitch() +void Controller::onPostAppSwitch() { postKeyCodeClick(AKEYCODE_APP_SWITCH); } -void Controller::postPower() +void Controller::onPostPower() { postKeyCodeClick(AKEYCODE_POWER); } -void Controller::postVolumeUp() +void Controller::onPostVolumeUp() { postKeyCodeClick(AKEYCODE_VOLUME_UP); } -void Controller::postVolumeDown() +void Controller::onPostVolumeDown() { postKeyCodeClick(AKEYCODE_VOLUME_DOWN); } -void Controller::expandNotificationPanel() +void Controller::onExpandNotificationPanel() { ControlMsg* controlMsg = new ControlMsg(ControlMsg::CMT_EXPAND_NOTIFICATION_PANEL); if (!controlMsg) { @@ -115,7 +115,7 @@ void Controller::expandNotificationPanel() postControlMsg(controlMsg); } -void Controller::collapseNotificationPanel() +void Controller::onCollapseNotificationPanel() { ControlMsg* controlMsg = new ControlMsg(ControlMsg::CMT_COLLAPSE_NOTIFICATION_PANEL); if (!controlMsg) { @@ -124,7 +124,7 @@ void Controller::collapseNotificationPanel() postControlMsg(controlMsg); } -void Controller::requestDeviceClipboard() +void Controller::onRequestDeviceClipboard() { ControlMsg* controlMsg = new ControlMsg(ControlMsg::CMT_GET_CLIPBOARD); if (!controlMsg) { @@ -133,7 +133,7 @@ void Controller::requestDeviceClipboard() postControlMsg(controlMsg); } -void Controller::setDeviceClipboard() +void Controller::onSetDeviceClipboard() { QClipboard *board = QApplication::clipboard(); QString text = board->text(); @@ -145,14 +145,14 @@ void Controller::setDeviceClipboard() postControlMsg(controlMsg); } -void Controller::clipboardPaste() +void Controller::onClipboardPaste() { QClipboard *board = QApplication::clipboard(); QString text = board->text(); - postTextInput(text); + onPostTextInput(text); } -void Controller::postTextInput(QString& text) +void Controller::onPostTextInput(QString& text) { ControlMsg* controlMsg = new ControlMsg(ControlMsg::CMT_INJECT_TEXT); if (!controlMsg) { @@ -162,7 +162,7 @@ void Controller::postTextInput(QString& text) postControlMsg(controlMsg); } -void Controller::setScreenPowerMode(ControlMsg::ScreenPowerMode mode) +void Controller::onSetScreenPowerMode(ControlMsg::ScreenPowerMode mode) { ControlMsg* controlMsg = new ControlMsg(ControlMsg::CMT_SET_SCREEN_POWER_MODE); if (!controlMsg) { @@ -172,21 +172,21 @@ void Controller::setScreenPowerMode(ControlMsg::ScreenPowerMode mode) postControlMsg(controlMsg); } -void Controller::mouseEvent(const QMouseEvent *from, const QSize &frameSize, const QSize &showSize) +void Controller::onMouseEvent(const QMouseEvent *from, const QSize &frameSize, const QSize &showSize) { if (m_inputConvert) { m_inputConvert->mouseEvent(from, frameSize, showSize); } } -void Controller::wheelEvent(const QWheelEvent *from, const QSize &frameSize, const QSize &showSize) +void Controller::onWheelEvent(const QWheelEvent *from, const QSize &frameSize, const QSize &showSize) { if (m_inputConvert) { m_inputConvert->wheelEvent(from, frameSize, showSize); } } -void Controller::keyEvent(const QKeyEvent *from, const QSize &frameSize, const QSize &showSize) +void Controller::onKeyEvent(const QKeyEvent *from, const QSize &frameSize, const QSize &showSize) { if (m_inputConvert) { m_inputConvert->keyEvent(from, frameSize, showSize); diff --git a/QtScrcpy/device/controller/controller.h b/QtScrcpy/device/controller/controller.h index d6b1730..cf11610 100644 --- a/QtScrcpy/device/controller/controller.h +++ b/QtScrcpy/device/controller/controller.h @@ -22,27 +22,29 @@ public: void updateScript(QString gameScript = ""); - // turn the screen on if it was off, press BACK otherwise - void postTurnOn(); - void postGoHome(); - void postGoMenu(); - void postGoBack(); - void postAppSwitch(); - void postPower(); - void postVolumeUp(); - void postVolumeDown(); - void expandNotificationPanel(); - void collapseNotificationPanel(); - void requestDeviceClipboard(); - void setDeviceClipboard(); - void clipboardPaste(); - void postTextInput(QString& text); - void setScreenPowerMode(ControlMsg::ScreenPowerMode mode); +public slots: + void onPostGoBack(); + void onPostGoHome(); + void onPostGoMenu(); + void onPostAppSwitch(); + void onPostPower(); + void onPostVolumeUp(); + void onPostVolumeDown(); + void onExpandNotificationPanel(); + void onCollapseNotificationPanel(); + void onSetScreenPowerMode(ControlMsg::ScreenPowerMode mode); // for input convert - void mouseEvent(const QMouseEvent* from, const QSize& frameSize, const QSize& showSize); - void wheelEvent(const QWheelEvent* from, const QSize& frameSize, const QSize& showSize); - void keyEvent(const QKeyEvent* from, const QSize& frameSize, const QSize& showSize); + void onMouseEvent(const QMouseEvent* from, const QSize& frameSize, const QSize& showSize); + void onWheelEvent(const QWheelEvent* from, const QSize& frameSize, const QSize& showSize); + void onKeyEvent(const QKeyEvent* from, const QSize& frameSize, const QSize& showSize); + + // turn the screen on if it was off, press BACK otherwise + void onPostBackOrScreenOn(); + void onRequestDeviceClipboard(); + void onSetDeviceClipboard(); + void onClipboardPaste(); + void onPostTextInput(QString& text); signals: void grabCursor(bool grab); diff --git a/QtScrcpy/device/controller/inputconvert/inputconvertnormal.cpp b/QtScrcpy/device/controller/inputconvert/inputconvertnormal.cpp index 12d1d04..73c4633 100644 --- a/QtScrcpy/device/controller/inputconvert/inputconvertnormal.cpp +++ b/QtScrcpy/device/controller/inputconvert/inputconvertnormal.cpp @@ -1,3 +1,5 @@ +#include + #include "inputconvertnormal.h" InputConvertNormal::InputConvertNormal(Controller* controller) @@ -63,10 +65,10 @@ void InputConvertNormal::wheelEvent(const QWheelEvent *from, const QSize& frameS qint32 vScroll = 0; switch (from->orientation()) { case Qt::Horizontal: - hScroll = from->delta(); + hScroll = from->delta() / abs(from->delta()) * 2; break; case Qt::Vertical: - vScroll = from->delta(); + vScroll = from->delta() / abs(from->delta()) * 2; break; } diff --git a/QtScrcpy/device/device.cpp b/QtScrcpy/device/device.cpp index 9b4f0e5..a0bda6b 100644 --- a/QtScrcpy/device/device.cpp +++ b/QtScrcpy/device/device.cpp @@ -13,6 +13,7 @@ #include "controller.h" #include "config.h" #include "avframeconvert.h" +#include "mousetap/mousetap.h" extern "C" { #include "libavutil/imgutils.h" @@ -34,15 +35,8 @@ Device::Device(DeviceParams params, QObject *parent) m_decoder = new Decoder(m_vb, this); m_fileHandler = new FileHandler(this); m_controller = new Controller(params.gameScript, this); - m_videoForm = new VideoForm(Config::getInstance().getSkin()); - m_videoForm->setSerial(m_params.serial); - if (m_controller) { - m_videoForm->setController(m_controller); - } - if (m_fileHandler) { - m_videoForm->setFileHandler(m_fileHandler); - } - m_videoForm->show(); + m_videoForm = new VideoForm(Config::getInstance().getFramelessWindow(), Config::getInstance().getSkin()); + m_videoForm->setDevice(this); } m_stream = new Stream(this); @@ -76,6 +70,7 @@ Device::~Device() delete m_vb; } if (m_videoForm) { + m_videoForm->close(); delete m_videoForm; } emit deviceDisconnect(m_params.serial); @@ -86,16 +81,25 @@ VideoForm *Device::getVideoForm() return m_videoForm; } -Controller *Device::getController() -{ - return m_controller; -} - Server *Device::getServer() { return m_server; } +const QString &Device::getSerial() +{ + return m_params.serial; +} + +const QSize Device::frameSize() +{ + QSize size; + if (!m_videoForm) { + return size; + } + return m_videoForm->frameSize(); +} + void Device::updateScript(QString script) { if(m_controller){ @@ -115,35 +119,86 @@ void Device::onScreenshot() m_vb->unLock(); } +void Device::onShowTouch(bool show) +{ + AdbProcess* adb = new AdbProcess(); + if (!adb) { + return; + } + connect(adb, &AdbProcess::adbProcessResult, this, [this](AdbProcess::ADB_EXEC_RESULT processResult){ + if (AdbProcess::AER_SUCCESS_START != processResult) { + sender()->deleteLater(); + } + }); + adb->setShowTouchesEnabled(getSerial(), show); + + qInfo() << getSerial() << " show touch " << (show ? "enable" : "disable"); +} + void Device::initSignals() { - if (m_controller && m_videoForm) { - connect(m_controller, &Controller::grabCursor, m_videoForm, &VideoForm::onGrabCursor); - connect(m_videoForm, &VideoForm::screenshot, this, &Device::onScreenshot); + connect(this, &Device::screenshot, this, &Device::onScreenshot); + connect(this, &Device::showTouch, this, &Device::onShowTouch); + connect(this, &Device::setControlState, this, &Device::onSetControlState); + connect(this, &Device::grabCursor, this, &Device::onGrabCursor); + + if (m_controller) { + connect(m_controller, &Controller::grabCursor, this, &Device::grabCursor); + } + if (m_controller) { + connect(this, &Device::postGoBack, m_controller, &Controller::onPostGoBack); + connect(this, &Device::postGoHome, m_controller, &Controller::onPostGoHome); + connect(this, &Device::postGoMenu, m_controller, &Controller::onPostGoMenu); + connect(this, &Device::postAppSwitch, m_controller, &Controller::onPostAppSwitch); + connect(this, &Device::postPower, m_controller, &Controller::onPostPower); + connect(this, &Device::postVolumeUp, m_controller, &Controller::onPostVolumeUp); + connect(this, &Device::postVolumeDown, m_controller, &Controller::onPostVolumeDown); + connect(this, &Device::setScreenPowerMode, m_controller, &Controller::onSetScreenPowerMode); + connect(this, &Device::expandNotificationPanel, m_controller, &Controller::onExpandNotificationPanel); + connect(this, &Device::collapseNotificationPanel, m_controller, &Controller::onCollapseNotificationPanel); + connect(this, &Device::mouseEvent, m_controller, &Controller::onMouseEvent); + connect(this, &Device::wheelEvent, m_controller, &Controller::onWheelEvent); + connect(this, &Device::keyEvent, m_controller, &Controller::onKeyEvent); + + connect(this, &Device::postBackOrScreenOn, m_controller, &Controller::onPostBackOrScreenOn); + connect(this, &Device::requestDeviceClipboard, m_controller, &Controller::onRequestDeviceClipboard); + connect(this, &Device::setDeviceClipboard, m_controller, &Controller::onSetDeviceClipboard); + connect(this, &Device::clipboardPaste, m_controller, &Controller::onClipboardPaste); + connect(this, &Device::postTextInput, m_controller, &Controller::onPostTextInput); } if (m_videoForm) { connect(m_videoForm, &VideoForm::destroyed, this, [this](QObject *obj){ Q_UNUSED(obj) deleteLater(); }); + + connect(this, &Device::switchFullScreen, m_videoForm, &VideoForm::onSwitchFullScreen); } if (m_fileHandler) { + connect(this, &Device::pushFileRequest, m_fileHandler, &FileHandler::onPushFileRequest); + connect(this, &Device::installApkRequest, m_fileHandler, &FileHandler::onInstallApkRequest); connect(m_fileHandler, &FileHandler::fileHandlerResult, this, [this](FileHandler::FILE_HANDLER_RESULT processResult, bool isApk){ - QString tips = ""; + QString tipsType = ""; if (isApk) { - tips = tr("install apk"); + tipsType = tr("install apk"); } else { - tips = tr("file transfer"); + tipsType = tr("file transfer"); } + QString tips; if (FileHandler::FAR_IS_RUNNING == processResult && m_videoForm) { - QMessageBox::warning(m_videoForm, "QtScrcpy", tr("wait current %1 to complete").arg(tips), QMessageBox::Ok); + tips = tr("wait current %1 to complete").arg(tipsType); } if (FileHandler::FAR_SUCCESS_EXEC == processResult && m_videoForm) { - QMessageBox::information(m_videoForm, "QtScrcpy", tr("%1 complete, save in %2").arg(tips).arg(Config::getInstance().getPushFilePath()), QMessageBox::Ok); + tips = tr("%1 complete, save in %2").arg(tipsType).arg(Config::getInstance().getPushFilePath()); } if (FileHandler::FAR_ERROR_EXEC == processResult && m_videoForm) { - QMessageBox::information(m_videoForm, "QtScrcpy", tr("%1 failed").arg(tips), QMessageBox::Ok); + tips = tr("%1 failed").arg(tipsType); } + qInfo() << tips; + if (m_controlState == GCS_CLIENT) { + return; + } + QMessageBox::information(m_videoForm, "QtScrcpy", tips, QMessageBox::Ok); }); } @@ -164,6 +219,16 @@ void Device::initSignals() if (m_videoForm) { m_videoForm->setWindowTitle(deviceName); m_videoForm->updateShowSize(size); + + QRect rc = Config::getInstance().getRect(getSerial()); + if (rc.isValid()) { + m_videoForm->move(rc.topLeft()); + m_videoForm->resize(rc.size()); + // TODO: setGeometry magneticwidget bug + //m_videoForm->setGeometry(rc); + } + // videoForm delay show + m_videoForm->show(); } // init recorder @@ -182,7 +247,7 @@ void Device::initSignals() // 显示界面时才自动息屏(m_params.display) if (m_params.closeScreen && m_params.display && m_controller) { - m_controller->setScreenPowerMode(ControlMsg::SPM_OFF); + emit m_controller->onSetScreenPowerMode(ControlMsg::SPM_OFF); } } }); @@ -235,6 +300,34 @@ void Device::startServer() }); } +void Device::onSetControlState(Device* device, Device::GroupControlState state) +{ + Q_UNUSED(device) + if (m_controlState == state) { + return; + } + GroupControlState oldState = m_controlState; + m_controlState = state; + emit controlStateChange(this, oldState, m_controlState); +} + +void Device::onGrabCursor(bool grab) +{ + if (!m_videoForm) { + return; + } + if (m_controlState == GCS_CLIENT) { + return; + } + QRect rc = m_videoForm->getGrabCursorRect(); + MouseTap::getInstance()->enableMouseEventTap(rc, grab); +} + +Device::GroupControlState Device::controlState() +{ + return m_controlState; +} + bool Device::saveFrame(const AVFrame* frame) { if (!frame) { @@ -276,10 +369,10 @@ bool Device::saveFrame(const AVFrame* frame) } QDateTime dateTime = QDateTime::currentDateTime(); QString fileName = dateTime.toString("_yyyyMMdd_hhmmss_zzz"); - fileName = Config::getInstance().getTitle() + fileName + ".jpg"; + fileName = Config::getInstance().getTitle() + fileName + ".png"; QDir dir(fileDir); absFilePath = dir.absoluteFilePath(fileName); - ret = rgbImage.save(absFilePath); + ret = rgbImage.save(absFilePath, "PNG", 100); if (!ret) { return false; } diff --git a/QtScrcpy/device/device.h b/QtScrcpy/device/device.h index cfccc54..041ad8e 100644 --- a/QtScrcpy/device/device.h +++ b/QtScrcpy/device/device.h @@ -4,6 +4,11 @@ #include #include +#include "controlmsg.h" + +class QMouseEvent; +class QWheelEvent; +class QKeyEvent; class Recorder; class Server; class VideoBuffer; @@ -30,20 +35,64 @@ public: QString gameScript = ""; // 游戏映射脚本 bool renderExpiredFrames = false; // 是否渲染延迟视频帧 }; + enum GroupControlState { + GCS_FREE = 0, + GCS_HOST, + GCS_CLIENT, + }; explicit Device(DeviceParams params, QObject *parent = nullptr); virtual ~Device(); VideoForm *getVideoForm(); - Controller *getController(); Server *getServer(); + const QString &getSerial(); + const QSize frameSize(); void updateScript(QString script); + Device::GroupControlState controlState(); signals: void deviceDisconnect(QString serial); + // tool bar + void switchFullScreen(); + void postGoBack(); + void postGoHome(); + void postGoMenu(); + void postAppSwitch(); + void postPower(); + void postVolumeUp(); + void postVolumeDown(); + void setScreenPowerMode(ControlMsg::ScreenPowerMode mode); + void expandNotificationPanel(); + void collapseNotificationPanel(); + void postBackOrScreenOn(); + void postTextInput(QString& text); + void requestDeviceClipboard(); + void setDeviceClipboard(); + void clipboardPaste(); + void pushFileRequest(const QString& serial, const QString& file, const QString& devicePath = ""); + void installApkRequest(const QString& serial, const QString& apkFile); + + // key map + void mouseEvent(const QMouseEvent* from, const QSize& frameSize, const QSize& showSize); + void wheelEvent(const QWheelEvent* from, const QSize& frameSize, const QSize& showSize); + void keyEvent(const QKeyEvent* from, const QSize& frameSize, const QSize& showSize); + + // self connect signal and slots + void screenshot(); + void showTouch(bool show); + void setControlState(Device* device, Device::GroupControlState state); + void grabCursor(bool grab); + + // for notify + void controlStateChange(Device* device, Device::GroupControlState oldState, Device::GroupControlState newState); + public slots: void onScreenshot(); + void onShowTouch(bool show); + void onSetControlState(Device* device, Device::GroupControlState state); + void onGrabCursor(bool grab); private: void initSignals(); @@ -65,6 +114,8 @@ private: QTime m_startTimeCount; DeviceParams m_params; + + GroupControlState m_controlState = GCS_FREE; }; #endif // DEVICE_H diff --git a/QtScrcpy/device/filehandler/filehandler.cpp b/QtScrcpy/device/filehandler/filehandler.cpp index c92425d..4730f6c 100644 --- a/QtScrcpy/device/filehandler/filehandler.cpp +++ b/QtScrcpy/device/filehandler/filehandler.cpp @@ -24,7 +24,7 @@ FileHandler::~FileHandler() } -void FileHandler::pushFileRequest(const QString &serial, const QString &file, const QString& devicePath) +void FileHandler::onPushFileRequest(const QString &serial, const QString &file, const QString& devicePath) { if (m_adb.isRuning()) { emit fileHandlerResult(FAR_IS_RUNNING, false); @@ -35,7 +35,7 @@ void FileHandler::pushFileRequest(const QString &serial, const QString &file, co m_adb.push(serial, file, devicePath); } -void FileHandler::installApkRequest(const QString &serial, const QString &apkFile) +void FileHandler::onInstallApkRequest(const QString &serial, const QString &apkFile) { if (m_adb.isRuning()) { emit fileHandlerResult(FAR_IS_RUNNING, true); diff --git a/QtScrcpy/device/filehandler/filehandler.h b/QtScrcpy/device/filehandler/filehandler.h index 3a2023c..8c48db1 100644 --- a/QtScrcpy/device/filehandler/filehandler.h +++ b/QtScrcpy/device/filehandler/filehandler.h @@ -17,10 +17,12 @@ public: FileHandler(QObject *parent = nullptr); virtual ~FileHandler(); - void pushFileRequest(const QString& serial, const QString& file, const QString& devicePath = ""); - void installApkRequest(const QString& serial, const QString& apkFile); const QString &getDevicePath(); +public slots: + void onPushFileRequest(const QString& serial, const QString& file, const QString& devicePath = ""); + void onInstallApkRequest(const QString& serial, const QString& apkFile); + signals: void fileHandlerResult(FILE_HANDLER_RESULT processResult, bool isApk = false); diff --git a/QtScrcpy/device/ui/toolform.cpp b/QtScrcpy/device/ui/toolform.cpp index ea09914..e640bd4 100644 --- a/QtScrcpy/device/ui/toolform.cpp +++ b/QtScrcpy/device/ui/toolform.cpp @@ -6,9 +6,7 @@ #include "toolform.h" #include "ui_toolform.h" #include "iconhelper.h" -#include "videoform.h" -#include "controller.h" -#include "adbprocess.h" +#include "device.h" ToolForm::ToolForm(QWidget* adsorbWidget, AdsorbPositions adsorbPos) : MagneticWidget(adsorbWidget, adsorbPos) @@ -18,8 +16,6 @@ ToolForm::ToolForm(QWidget* adsorbWidget, AdsorbPositions adsorbPos) setWindowFlags(windowFlags() | Qt::FramelessWindowHint); //setWindowFlags(windowFlags() & ~Qt::WindowMinMaxButtonsHint); - m_videoForm = dynamic_cast(adsorbWidget); - initStyle(); } @@ -28,6 +24,15 @@ ToolForm::~ToolForm() delete ui; } +void ToolForm::setDevice(Device *device) +{ + if (!device) { + return; + } + m_device = device; + connect(m_device, &Device::controlStateChange, this, &ToolForm::onControlStateChange); +} + void ToolForm::initStyle() { IconHelper::Instance()->SetIcon(ui->fullScreenBtn, QChar(0xf0b2), 15); @@ -43,6 +48,25 @@ void ToolForm::initStyle() IconHelper::Instance()->SetIcon(ui->expandNotifyBtn, QChar(0xf103), 15); IconHelper::Instance()->SetIcon(ui->screenShotBtn, QChar(0xf0c4), 15); IconHelper::Instance()->SetIcon(ui->touchBtn, QChar(0xf111), 15); + IconHelper::Instance()->SetIcon(ui->groupControlBtn, QChar(0xf0c0), 15); +} + +void ToolForm::updateGroupControl() +{ + if (!m_device) { + return; + } + switch (m_device->controlState()) { + case Device::GroupControlState::GCS_FREE: + ui->groupControlBtn->setStyleSheet("color: #DCDCDC"); + break; + case Device::GroupControlState::GCS_HOST: + ui->groupControlBtn->setStyleSheet("color: red"); + break; + case Device::GroupControlState::GCS_CLIENT: + ui->groupControlBtn->setStyleSheet("color: green"); + break; + } } void ToolForm::mousePressEvent(QMouseEvent *event) @@ -80,97 +104,121 @@ void ToolForm::hideEvent(QHideEvent *event) void ToolForm::on_fullScreenBtn_clicked() { - if (m_videoForm) { - m_videoForm->switchFullScreen(); + if (!m_device) { + return; } + + emit m_device->switchFullScreen(); } void ToolForm::on_returnBtn_clicked() { - if (m_videoForm && m_videoForm->getController()) { - m_videoForm->getController()->postGoBack(); + if (!m_device) { + return; } + emit m_device->postGoBack(); } void ToolForm::on_homeBtn_clicked() { - if (m_videoForm && m_videoForm->getController()) { - m_videoForm->getController()->postGoHome(); + if (!m_device) { + return; } + emit m_device->postGoHome(); } void ToolForm::on_menuBtn_clicked() { - if (m_videoForm && m_videoForm->getController()) { - m_videoForm->getController()->postGoMenu(); + if (!m_device) { + return; } + emit m_device->postGoMenu(); } void ToolForm::on_appSwitchBtn_clicked() { - if (m_videoForm && m_videoForm->getController()) { - m_videoForm->getController()->postAppSwitch(); + if (!m_device) { + return; } + emit m_device->postAppSwitch(); } void ToolForm::on_powerBtn_clicked() { - if (m_videoForm && m_videoForm->getController()) { - m_videoForm->getController()->postPower(); + if (!m_device) { + return; } + emit m_device->postPower(); } void ToolForm::on_screenShotBtn_clicked() { - emit screenshot(); + if (!m_device) { + return; + } + emit m_device->screenshot(); } void ToolForm::on_volumeUpBtn_clicked() { - if (m_videoForm && m_videoForm->getController()) { - m_videoForm->getController()->postVolumeUp(); + if (!m_device) { + return; } + emit m_device->postVolumeUp(); } void ToolForm::on_volumeDownBtn_clicked() { - if (m_videoForm && m_videoForm->getController()) { - m_videoForm->getController()->postVolumeDown(); + if (!m_device) { + return; } + emit m_device->postVolumeDown(); } void ToolForm::on_closeScreenBtn_clicked() { - if (m_videoForm && m_videoForm->getController()) { - m_videoForm->getController()->setScreenPowerMode(ControlMsg::SPM_OFF); + if (!m_device) { + return; } + emit m_device->setScreenPowerMode(ControlMsg::SPM_OFF); } void ToolForm::on_expandNotifyBtn_clicked() { - if (m_videoForm && m_videoForm->getController()) { - m_videoForm->getController()->expandNotificationPanel(); + if (!m_device) { + return; } + emit m_device->expandNotificationPanel(); } void ToolForm::on_touchBtn_clicked() { - if (!m_videoForm) { + if (!m_device) { return; } m_showTouch = !m_showTouch; + emit m_device->showTouch(m_showTouch); +} - AdbProcess* adb = new AdbProcess(); - if (!adb) { +void ToolForm::on_groupControlBtn_clicked() +{ + if (!m_device) { return; } - connect(adb, &AdbProcess::adbProcessResult, this, [this](AdbProcess::ADB_EXEC_RESULT processResult){ - if (AdbProcess::AER_SUCCESS_START != processResult) { - sender()->deleteLater(); - } - }); - adb->setShowTouchesEnabled(m_videoForm->getSerial(), m_showTouch); - - qInfo() << "show touch " << (m_showTouch ? "enable" : "disable"); + Device::GroupControlState state = m_device->controlState(); + if (state == Device::GroupControlState::GCS_FREE) { + emit m_device->setControlState(m_device, Device::GroupControlState::GCS_HOST); + } + if (state == Device::GroupControlState::GCS_HOST) { + emit m_device->setControlState(m_device, Device::GroupControlState::GCS_FREE); + } +} + +void ToolForm::onControlStateChange(Device *device, Device::GroupControlState oldState, Device::GroupControlState newState) +{ + Q_UNUSED(device) + Q_UNUSED(oldState) + Q_UNUSED(newState) + updateGroupControl(); } diff --git a/QtScrcpy/device/ui/toolform.h b/QtScrcpy/device/ui/toolform.h index 4b63a35..037e55c 100644 --- a/QtScrcpy/device/ui/toolform.h +++ b/QtScrcpy/device/ui/toolform.h @@ -5,12 +5,13 @@ #include #include "magneticwidget.h" +#include "device.h" namespace Ui { class ToolForm; } -class VideoForm; +class Device; class ToolForm : public MagneticWidget { Q_OBJECT @@ -19,6 +20,8 @@ public: explicit ToolForm(QWidget* adsorbWidget, AdsorbPositions adsorbPos); ~ToolForm(); + void setDevice(Device *device); + protected: void mousePressEvent(QMouseEvent *event); void mouseReleaseEvent(QMouseEvent *event); @@ -27,41 +30,31 @@ protected: void showEvent(QShowEvent *event); void hideEvent(QHideEvent *event); -signals: - void screenshot(); - private slots: void on_fullScreenBtn_clicked(); - void on_returnBtn_clicked(); - void on_homeBtn_clicked(); - void on_menuBtn_clicked(); - void on_appSwitchBtn_clicked(); - void on_powerBtn_clicked(); - void on_screenShotBtn_clicked(); - void on_volumeUpBtn_clicked(); - void on_volumeDownBtn_clicked(); - void on_closeScreenBtn_clicked(); - void on_expandNotifyBtn_clicked(); - void on_touchBtn_clicked(); + void on_groupControlBtn_clicked(); + + void onControlStateChange(Device* device, Device::GroupControlState oldState, Device::GroupControlState newState); private: void initStyle(); + void updateGroupControl(); private: Ui::ToolForm *ui; QPoint m_dragPosition; - QPointer m_videoForm; + QPointer m_device; bool m_showTouch = false; }; diff --git a/QtScrcpy/device/ui/toolform.ui b/QtScrcpy/device/ui/toolform.ui index e2bd9fb..77a2a0e 100644 --- a/QtScrcpy/device/ui/toolform.ui +++ b/QtScrcpy/device/ui/toolform.ui @@ -20,6 +20,13 @@ 30 + + + + + + + diff --git a/QtScrcpy/device/ui/videoform.cpp b/QtScrcpy/device/ui/videoform.cpp index 82a51e2..8fea7db 100644 --- a/QtScrcpy/device/ui/videoform.cpp +++ b/QtScrcpy/device/ui/videoform.cpp @@ -8,33 +8,39 @@ #include #include #include +#include +#include +#include #include "videoform.h" #include "qyuvopenglwidget.h" -#include "mousetap/mousetap.h" #include "ui_videoform.h" #include "iconhelper.h" #include "toolform.h" +#include "device.h" #include "controller.h" -#include "filehandler.h" #include "config.h" extern "C" { #include "libavutil/frame.h" } -VideoForm::VideoForm(bool skin, QWidget *parent) +VideoForm::VideoForm(bool framelessWindow, bool skin, QWidget *parent) : QWidget(parent) , ui(new Ui::videoForm) , m_skin(skin) { ui->setupUi(this); initUI(); + installShortcut(); updateShowSize(size()); bool vertical = size().height() > size().width(); if (m_skin) { updateStyleSheet(vertical); } + if (framelessWindow) { + setWindowFlags(windowFlags() | Qt::FramelessWindowHint); + } } VideoForm::~VideoForm() @@ -70,13 +76,47 @@ void VideoForm::initUI() ui->keepRadioWidget->setMouseTracking(true); } -void VideoForm::onGrabCursor(bool grab) +QRect VideoForm::getGrabCursorRect() { -#if defined(Q_OS_WIN32) || defined(Q_OS_OSX) - MouseTap::getInstance()->enableMouseEventTap(m_videoWidget, grab); + QRect rc; +#if defined(Q_OS_WIN32) + rc = QRect(m_videoWidget->mapToGlobal(m_videoWidget->pos()) + , m_videoWidget->size()); + // high dpi support + rc.setTopLeft(rc.topLeft() * m_videoWidget->devicePixelRatio()); + rc.setBottomRight(rc.bottomRight() * m_videoWidget->devicePixelRatio()); +#elif defined(Q_OS_OSX) + rc = m_videoWidget->geometry(); + rc.setTopLeft(m_videoWidget->mapToGlobal(rc.topLeft())); + rc.setBottomRight(m_videoWidget->mapToGlobal(rc.bottomRight())); + rc.setX(rc.x() + 100); + rc.setY(rc.y() + 30); + rc.setWidth(rc.width() - 180); + rc.setHeight(rc.height() - 60); #else - Q_UNUSED(grab) + #endif + return rc; +} + +const QSize &VideoForm::frameSize() +{ + return m_frameSize; +} + +void VideoForm::resizeSquare() +{ + QRect screenRect = getScreenRect(); + if (screenRect.isEmpty()) { + qWarning() << "getScreenRect is empty"; + return; + } + resize(screenRect.height(), screenRect.height()); +} + +void VideoForm::removeBlackRect() +{ + resize(ui->keepRadioWidget->goodSize()); } void VideoForm::updateRender(const AVFrame *frame) @@ -98,7 +138,7 @@ void VideoForm::showToolForm(bool show) { if (!m_toolForm) { m_toolForm = new ToolForm(this, ToolForm::AP_OUTSIDE_RIGHT); - connect(m_toolForm, &ToolForm::screenshot, this, &VideoForm::screenshot); + m_toolForm->setDevice(m_device); } m_toolForm->move(pos().x() + geometry().width(), pos().y() + 30); m_toolForm->setVisible(show); @@ -106,16 +146,178 @@ void VideoForm::showToolForm(bool show) void VideoForm::moveCenter() { - QDesktopWidget* desktop = QApplication::desktop(); - if (!desktop) { - qWarning() << "QApplication::desktop() is nullptr"; + QRect screenRect = getScreenRect(); + if (screenRect.isEmpty()) { + qWarning() << "getScreenRect is empty"; return; } - QRect screenRect = desktop->availableGeometry(); // 窗口居中 move(screenRect.center() - QRect(0, 0, size().width(), size().height()).center()); } +void VideoForm::installShortcut() +{ + QShortcut *shortcut = nullptr; + + // switchFullScreen + shortcut = new QShortcut(QKeySequence("Ctrl+f"), this); + connect(shortcut, &QShortcut::activated, this, [this](){ + if (!m_device) { + return; + } + emit m_device->switchFullScreen(); + }); + + // resizeSquare + shortcut = new QShortcut(QKeySequence("Ctrl+g"), this); + connect(shortcut, &QShortcut::activated, this, [this](){ + resizeSquare(); + }); + + // removeBlackRect + shortcut = new QShortcut(QKeySequence("Ctrl+x"), this); + connect(shortcut, &QShortcut::activated, this, [this](){ + removeBlackRect(); + }); + + + // postGoHome + shortcut = new QShortcut(QKeySequence("Ctrl+h"), this); + connect(shortcut, &QShortcut::activated, this, [this](){ + if (!m_device) { + return; + } + emit m_device->postGoHome(); + }); + + // postGoBack + shortcut = new QShortcut(QKeySequence("Ctrl+b"), this); + connect(shortcut, &QShortcut::activated, this, [this](){ + if (!m_device) { + return; + } + emit m_device->postGoBack(); + }); + + // postAppSwitch + shortcut = new QShortcut(QKeySequence("Ctrl+s"), this); + connect(shortcut, &QShortcut::activated, this, [this](){ + if (!m_device) { + return; + } + emit m_device->postAppSwitch(); + }); + + // postGoMenu + shortcut = new QShortcut(QKeySequence("Ctrl+m"), this); + connect(shortcut, &QShortcut::activated, this, [this](){ + if (!m_device) { + return; + } + emit m_device->postGoMenu(); + }); + + // postVolumeUp + shortcut = new QShortcut(QKeySequence("Ctrl+up"), this); + connect(shortcut, &QShortcut::activated, this, [this](){ + if (!m_device) { + return; + } + emit m_device->postVolumeUp(); + }); + + // postVolumeDown + shortcut = new QShortcut(QKeySequence("Ctrl+down"), this); + connect(shortcut, &QShortcut::activated, this, [this](){ + if (!m_device) { + return; + } + emit m_device->postVolumeDown(); + }); + + // postPower + shortcut = new QShortcut(QKeySequence("Ctrl+p"), this); + connect(shortcut, &QShortcut::activated, this, [this](){ + if (!m_device) { + return; + } + emit m_device->postPower(); + }); + + // setScreenPowerMode(ControlMsg::SPM_OFF) + shortcut = new QShortcut(QKeySequence("Ctrl+o"), this); + connect(shortcut, &QShortcut::activated, this, [this](){ + if (!m_device) { + return; + } + emit m_device->setScreenPowerMode(ControlMsg::SPM_OFF); + }); + + // expandNotificationPanel + shortcut = new QShortcut(QKeySequence("Ctrl+n"), this); + connect(shortcut, &QShortcut::activated, this, [this](){ + if (!m_device) { + return; + } + emit m_device->expandNotificationPanel(); + }); + + // collapseNotificationPanel + shortcut = new QShortcut(QKeySequence("Ctrl+Shift+n"), this); + connect(shortcut, &QShortcut::activated, this, [this](){ + if (!m_device) { + return; + } + emit m_device->collapseNotificationPanel(); + }); + + // requestDeviceClipboard + shortcut = new QShortcut(QKeySequence("Ctrl+c"), this); + connect(shortcut, &QShortcut::activated, this, [this](){ + if (!m_device) { + return; + } + emit m_device->requestDeviceClipboard(); + }); + + // clipboardPaste + shortcut = new QShortcut(QKeySequence("Ctrl+v"), this); + connect(shortcut, &QShortcut::activated, this, [this](){ + if (!m_device) { + return; + } + emit m_device->clipboardPaste(); + }); + + // setDeviceClipboard + shortcut = new QShortcut(QKeySequence("Ctrl+Shift+v"), this); + connect(shortcut, &QShortcut::activated, this, [this](){ + if (!m_device) { + return; + } + emit m_device->setDeviceClipboard(); + }); +} + +QRect VideoForm::getScreenRect() +{ + QRect screenRect; + QWidget *win = window(); + if (!win) { + return screenRect; + } + QWindow *winHandle = win->windowHandle(); + if (!winHandle) { + return screenRect; + } + QScreen *screen = winHandle->screen(); + if (!screen) { + return screenRect; + } + screenRect = screen->availableGeometry(); + return screenRect; +} + void VideoForm::updateStyleSheet(bool vertical) { if (vertical) { @@ -157,12 +359,11 @@ void VideoForm::updateShowSize(const QSize &newSize) bool vertical = m_widthHeightRatio < 1.0f ? true : false; QSize showSize = newSize; - QDesktopWidget* desktop = QApplication::desktop(); - if (!desktop) { - qWarning() << "QApplication::desktop() is nullptr"; + QRect screenRect = getScreenRect(); + if (screenRect.isEmpty()) { + qWarning() << "getScreenRect is empty"; return; } - QRect screenRect = desktop->availableGeometry(); if (vertical) { showSize.setHeight(qMin(newSize.height(), screenRect.height() - 200)); showSize.setWidth(showSize.height() * m_widthHeightRatio); @@ -171,8 +372,8 @@ void VideoForm::updateShowSize(const QSize &newSize) showSize.setHeight(showSize.width() / m_widthHeightRatio); } - if (isFullScreen()) { - switchFullScreen(); + if (isFullScreen() && m_device) { + emit m_device->switchFullScreen(); } if (m_skin) { QMargins m = getMargins(vertical); @@ -190,7 +391,7 @@ void VideoForm::updateShowSize(const QSize &newSize) } } -void VideoForm::switchFullScreen() +void VideoForm::onSwitchFullScreen() { if (isFullScreen()) { // 横屏全屏铺满全屏,恢复时,恢复保持宽高比 @@ -253,39 +454,25 @@ void VideoForm::staysOnTop(bool top) } } -Controller *VideoForm::getController() +void VideoForm::setDevice(Device *device) { - return m_controller; -} - -void VideoForm::setFileHandler(FileHandler *fileHandler) -{ - m_fileHandler = fileHandler; -} - -void VideoForm::setSerial(const QString &serial) -{ - m_serial = serial; -} - -const QString &VideoForm::getSerial() -{ - return m_serial; -} - -void VideoForm::setController(Controller *controller) -{ - m_controller = controller; + m_device = device; } void VideoForm::mousePressEvent(QMouseEvent *event) { + if (event->button() == Qt::MiddleButton) { + if (m_device) { + emit m_device->postGoHome(); + } + } + if (m_videoWidget->geometry().contains(event->pos())) { - if (!m_controller) { + if (!m_device) { return; } event->setLocalPos(m_videoWidget->mapFrom(this, event->localPos().toPoint())); - m_controller->mouseEvent(event, m_videoWidget->frameSize(), m_videoWidget->size()); + emit m_device->mouseEvent(event, m_videoWidget->frameSize(), m_videoWidget->size()); } else { if (event->button() == Qt::LeftButton) { m_dragPosition = event->globalPos() - frameGeometry().topLeft(); @@ -297,7 +484,7 @@ void VideoForm::mousePressEvent(QMouseEvent *event) void VideoForm::mouseReleaseEvent(QMouseEvent *event) { if (m_dragPosition.isNull()) { - if (!m_controller) { + if (!m_device) { return; } event->setLocalPos(m_videoWidget->mapFrom(this, event->localPos().toPoint())); @@ -316,7 +503,7 @@ void VideoForm::mouseReleaseEvent(QMouseEvent *event) local.setY(m_videoWidget->height()); } event->setLocalPos(local); - m_controller->mouseEvent(event, m_videoWidget->frameSize(), m_videoWidget->size()); + emit m_device->mouseEvent(event, m_videoWidget->frameSize(), m_videoWidget->size()); } else { m_dragPosition = QPoint(0, 0); } @@ -325,11 +512,11 @@ void VideoForm::mouseReleaseEvent(QMouseEvent *event) void VideoForm::mouseMoveEvent(QMouseEvent *event) { if (m_videoWidget->geometry().contains(event->pos())) { - if (!m_controller) { + if (!m_device) { return; } event->setLocalPos(m_videoWidget->mapFrom(this, event->localPos().toPoint())); - m_controller->mouseEvent(event, m_videoWidget->frameSize(), m_videoWidget->size()); + emit m_device->mouseEvent(event, m_videoWidget->frameSize(), m_videoWidget->size()); } else if (!m_dragPosition.isNull()){ if (event->buttons() & Qt::LeftButton) { move(event->globalPos() - m_dragPosition); @@ -338,10 +525,22 @@ void VideoForm::mouseMoveEvent(QMouseEvent *event) } } +void VideoForm::mouseDoubleClickEvent(QMouseEvent *event) +{ + if (event->button() == Qt::LeftButton + && !m_videoWidget->geometry().contains(event->pos())) { + removeBlackRect(); + } + + if (event->button() == Qt::RightButton && m_device) { + emit m_device->postBackOrScreenOn(); + } +} + void VideoForm::wheelEvent(QWheelEvent *event) { if (m_videoWidget->geometry().contains(event->pos())) { - if (!m_controller) { + if (!m_device) { return; } QPointF pos = m_videoWidget->mapFrom(this, event->pos()); @@ -352,41 +551,30 @@ void VideoForm::wheelEvent(QWheelEvent *event) */ QWheelEvent wheelEvent(pos, event->globalPosF(), event->delta(), event->buttons(), event->modifiers(), event->orientation()); - m_controller->wheelEvent(&wheelEvent, m_videoWidget->frameSize(), m_videoWidget->size()); + emit m_device->wheelEvent(&wheelEvent, m_videoWidget->frameSize(), m_videoWidget->size()); } } void VideoForm::keyPressEvent(QKeyEvent *event) { + if (!m_device) { + return; + } if (Qt::Key_Escape == event->key() && !event->isAutoRepeat() && isFullScreen()) { - switchFullScreen(); - } - if (!m_controller) { - return; - } - if (event->key() == Qt::Key_C && (event->modifiers() & Qt::ControlModifier)) { - m_controller->requestDeviceClipboard(); - } - if (event->key() == Qt::Key_V && (event->modifiers() & Qt::ControlModifier)) { - if (event->modifiers() & Qt::ShiftModifier) { - m_controller->setDeviceClipboard(); - } else { - m_controller->clipboardPaste(); - } - return; + emit m_device->switchFullScreen(); } - m_controller->keyEvent(event, m_videoWidget->frameSize(), m_videoWidget->size()); + emit m_device->keyEvent(event, m_videoWidget->frameSize(), m_videoWidget->size()); } void VideoForm::keyReleaseEvent(QKeyEvent *event) { - if (!m_controller) { + if (!m_device) { return; } - m_controller->keyEvent(event, m_videoWidget->frameSize(), m_videoWidget->size()); + emit m_device->keyEvent(event, m_videoWidget->frameSize(), m_videoWidget->size()); } void VideoForm::paintEvent(QPaintEvent *paint) @@ -432,6 +620,15 @@ void VideoForm::resizeEvent(QResizeEvent *event) } } +void VideoForm::closeEvent(QCloseEvent *event) +{ + Q_UNUSED(event) + if (!m_device) { + return; + } + Config::getInstance().setRect(m_device->getSerial(), geometry()); +} + void VideoForm::dragEnterEvent(QDragEnterEvent *event) { event->acceptProposedAction(); @@ -449,7 +646,7 @@ void VideoForm::dragLeaveEvent(QDragLeaveEvent *event) void VideoForm::dropEvent(QDropEvent *event) { - if (!m_fileHandler) { + if (!m_device) { return; } const QMimeData* qm = event->mimeData(); @@ -462,8 +659,8 @@ void VideoForm::dropEvent(QDropEvent *event) } if (fileInfo.isFile() && fileInfo.suffix() == "apk") { - m_fileHandler->installApkRequest(m_serial, file); + emit m_device->installApkRequest(m_device->getSerial(), file); return; } - m_fileHandler->pushFileRequest(m_serial, file, Config::getInstance().getPushFilePath() + fileInfo.fileName()); + emit m_device->pushFileRequest(m_device->getSerial(), file, Config::getInstance().getPushFilePath() + fileInfo.fileName()); } diff --git a/QtScrcpy/device/ui/videoform.h b/QtScrcpy/device/ui/videoform.h index aaf598a..c3b4fc6 100644 --- a/QtScrcpy/device/ui/videoform.h +++ b/QtScrcpy/device/ui/videoform.h @@ -10,31 +10,27 @@ class videoForm; struct AVFrame; class ToolForm; -class Controller; +class Device; class FileHandler; class QYUVOpenGLWidget; class VideoForm : public QWidget { Q_OBJECT public: - explicit VideoForm(bool skin = true, QWidget *parent = 0); + explicit VideoForm(bool framelessWindow = false, bool skin = true, QWidget *parent = 0); ~VideoForm(); - void switchFullScreen(); void staysOnTop(bool top = true); void updateShowSize(const QSize &newSize); void updateRender(const AVFrame *frame); - void setController(Controller *controller); - Controller* getController(); - void setFileHandler(FileHandler *fileHandler); - void setSerial(const QString &serial); - const QString& getSerial(); - -signals: - void screenshot(); + void setDevice(Device *device); + QRect getGrabCursorRect(); + const QSize &frameSize(); + void resizeSquare(); + void removeBlackRect(); public slots: - void onGrabCursor(bool grab); + void onSwitchFullScreen(); private: void updateStyleSheet(bool vertical); @@ -43,11 +39,14 @@ private: void showToolForm(bool show = true); void moveCenter(); + void installShortcut(); + QRect getScreenRect(); protected: void mousePressEvent(QMouseEvent *event); void mouseReleaseEvent(QMouseEvent *event); void mouseMoveEvent(QMouseEvent *event); + void mouseDoubleClickEvent(QMouseEvent *event); void wheelEvent(QWheelEvent *event); void keyPressEvent(QKeyEvent *event); void keyReleaseEvent(QKeyEvent *event); @@ -55,6 +54,7 @@ protected: void paintEvent(QPaintEvent *); void showEvent(QShowEvent *event); void resizeEvent(QResizeEvent *event); + void closeEvent(QCloseEvent *event); void dragEnterEvent(QDragEnterEvent *event); void dragMoveEvent(QDragMoveEvent *event); @@ -76,9 +76,7 @@ private: QPoint m_fullScreenBeforePos; //outside member - QString m_serial = ""; - QPointer m_controller; - QPointer m_fileHandler; + QPointer m_device; }; #endif // VIDEOFORM_H diff --git a/QtScrcpy/devicemanage/devicemanage.cpp b/QtScrcpy/devicemanage/devicemanage.cpp index 8379911..75f5465 100644 --- a/QtScrcpy/devicemanage/devicemanage.cpp +++ b/QtScrcpy/devicemanage/devicemanage.cpp @@ -1,4 +1,7 @@ #include +#include +#include +#include #include "devicemanage.h" #include "server.h" @@ -44,6 +47,7 @@ bool DeviceManage::connectDevice(Device::DeviceParams params) */ Device *device = new Device(params); connect(device, &Device::deviceDisconnect, this, &DeviceManage::onDeviceDisconnect); + connect(device, &Device::controlStateChange, this, &DeviceManage::onControlStateChange); m_devices[params.serial] = device; return true; } @@ -102,13 +106,166 @@ void DeviceManage::disconnectAllDevice() } } +void DeviceManage::setGroupControlSignals(Device *host, Device *client, bool install) +{ + if (!host || !client) { + return; + } + if (install) { + connect(host, &Device::postGoBack, client, &Device::postGoBack); + connect(host, &Device::postGoHome, client, &Device::postGoHome); + connect(host, &Device::postGoMenu, client, &Device::postGoMenu); + connect(host, &Device::postAppSwitch, client, &Device::postAppSwitch); + connect(host, &Device::postPower, client, &Device::postPower); + connect(host, &Device::postVolumeUp, client, &Device::postVolumeUp); + connect(host, &Device::postVolumeDown, client, &Device::postVolumeDown); + connect(host, &Device::setScreenPowerMode, client, &Device::setScreenPowerMode); + connect(host, &Device::expandNotificationPanel, client, &Device::expandNotificationPanel); + connect(host, &Device::collapseNotificationPanel, client, &Device::collapseNotificationPanel); + connect(host, &Device::postBackOrScreenOn, client, &Device::postBackOrScreenOn); + connect(host, &Device::postTextInput, client, &Device::postTextInput); + connect(host, &Device::setDeviceClipboard, client, &Device::setDeviceClipboard); + connect(host, &Device::clipboardPaste, client, &Device::clipboardPaste); + connect(host, &Device::pushFileRequest, client, &Device::pushFileRequest); + connect(host, &Device::installApkRequest, client, &Device::installApkRequest); + connect(host, &Device::screenshot, client, &Device::screenshot); + connect(host, &Device::showTouch, client, &Device::showTouch); + // dont connect requestDeviceClipboard + //connect(host, &Device::requestDeviceClipboard, client, &Device::requestDeviceClipboard); + } else { + disconnect(host, &Device::postGoBack, client, &Device::postGoBack); + disconnect(host, &Device::postGoHome, client, &Device::postGoHome); + disconnect(host, &Device::postGoMenu, client, &Device::postGoMenu); + disconnect(host, &Device::postAppSwitch, client, &Device::postAppSwitch); + disconnect(host, &Device::postPower, client, &Device::postPower); + disconnect(host, &Device::postVolumeUp, client, &Device::postVolumeUp); + disconnect(host, &Device::postVolumeDown, client, &Device::postVolumeDown); + disconnect(host, &Device::setScreenPowerMode, client, &Device::setScreenPowerMode); + disconnect(host, &Device::expandNotificationPanel, client, &Device::expandNotificationPanel); + disconnect(host, &Device::collapseNotificationPanel, client, &Device::collapseNotificationPanel); + disconnect(host, &Device::postBackOrScreenOn, client, &Device::postBackOrScreenOn); + disconnect(host, &Device::postTextInput, client, &Device::postTextInput); + disconnect(host, &Device::setDeviceClipboard, client, &Device::setDeviceClipboard); + disconnect(host, &Device::clipboardPaste, client, &Device::clipboardPaste); + disconnect(host, &Device::pushFileRequest, client, &Device::pushFileRequest); + disconnect(host, &Device::installApkRequest, client, &Device::installApkRequest); + disconnect(host, &Device::screenshot, client, &Device::screenshot); + disconnect(host, &Device::showTouch, client, &Device::showTouch); + } +} + +void DeviceManage::setGroupControlHost(Device *host, bool install) +{ + QMapIterator> i(m_devices); + while (i.hasNext()) { + i.next(); + if (!i.value()) { + continue; + } + if (i.value() == host) { + continue; + } + if (install) { + if (host) { + setGroupControlSignals(host, i.value(), true); + } + emit i.value()->setControlState(i.value(), Device::GroupControlState::GCS_CLIENT); + } else { + if (host) { + setGroupControlSignals(host, i.value(), false); + } + emit i.value()->setControlState(i.value(), Device::GroupControlState::GCS_FREE); + } + } +} + void DeviceManage::onDeviceDisconnect(QString serial) { if (!serial.isEmpty() && m_devices.contains(serial)) { + if (m_devices[serial]->controlState() == Device::GroupControlState::GCS_HOST) { + setGroupControlHost(nullptr, false); + } m_devices.remove(serial); } } +void DeviceManage::onControlStateChange(Device *device, Device::GroupControlState oldState, Device::GroupControlState newState) +{ + if (!device) { + return; + } + // free to host + if (oldState == Device::GroupControlState::GCS_FREE + && newState == Device::GroupControlState::GCS_HOST) { + // install direct control signals + setGroupControlHost(device, true); + // install convert control signals(frameSize need convert) + connect(device, &Device::mouseEvent, this, &DeviceManage::onMouseEvent, Qt::UniqueConnection); + connect(device, &Device::wheelEvent, this, &DeviceManage::onWheelEvent, Qt::UniqueConnection); + connect(device, &Device::keyEvent, this, &DeviceManage::onKeyEvent, Qt::UniqueConnection); + return; + } + // host to free + if (oldState == Device::GroupControlState::GCS_HOST + && newState == Device::GroupControlState::GCS_FREE) { + // uninstall direct control signals + setGroupControlHost(device, false); + // uninstall convert control signals(frameSize need convert) + disconnect(device, &Device::mouseEvent, this, &DeviceManage::onMouseEvent); + disconnect(device, &Device::wheelEvent, this, &DeviceManage::onWheelEvent); + disconnect(device, &Device::keyEvent, this, &DeviceManage::onKeyEvent); + return; + } +} + +void DeviceManage::onMouseEvent(const QMouseEvent *from, const QSize &frameSize, const QSize &showSize) +{ + QMapIterator> i(m_devices); + while (i.hasNext()) { + i.next(); + if (!i.value()) { + continue; + } + if (i.value() == sender()) { + continue; + } + // neend convert frameSize to its frameSize + emit i.value()->mouseEvent(from, i.value()->frameSize(), showSize); + } +} + +void DeviceManage::onWheelEvent(const QWheelEvent *from, const QSize &frameSize, const QSize &showSize) +{ + QMapIterator> i(m_devices); + while (i.hasNext()) { + i.next(); + if (!i.value()) { + continue; + } + if (i.value() == sender()) { + continue; + } + // neend convert frameSize to its frameSize + emit i.value()->wheelEvent(from, i.value()->frameSize(), showSize); + } +} + +void DeviceManage::onKeyEvent(const QKeyEvent *from, const QSize &frameSize, const QSize &showSize) +{ + QMapIterator> i(m_devices); + while (i.hasNext()) { + i.next(); + if (!i.value()) { + continue; + } + if (i.value() == sender()) { + continue; + } + // neend convert frameSize to its frameSize + emit i.value()->keyEvent(from, i.value()->frameSize(), showSize); + } +} + quint16 DeviceManage::getFreePort() { quint16 port = m_localPortStart; diff --git a/QtScrcpy/devicemanage/devicemanage.h b/QtScrcpy/devicemanage/devicemanage.h index bfe1f7c..fb86089 100644 --- a/QtScrcpy/devicemanage/devicemanage.h +++ b/QtScrcpy/devicemanage/devicemanage.h @@ -20,8 +20,18 @@ public: bool disconnectDevice(const QString &serial); void disconnectAllDevice(); +protected: + void setGroupControlSignals(Device *host, Device *client, bool install); + void setGroupControlHost(Device *host, bool install); + protected slots: void onDeviceDisconnect(QString serial); + void onControlStateChange(Device* device, Device::GroupControlState oldState, Device::GroupControlState newState); + + // neend convert frameSize to its frameSize + void onMouseEvent(const QMouseEvent* from, const QSize& frameSize, const QSize& showSize); + void onWheelEvent(const QWheelEvent* from, const QSize& frameSize, const QSize& showSize); + void onKeyEvent(const QKeyEvent* from, const QSize& frameSize, const QSize& showSize); private: quint16 getFreePort(); diff --git a/QtScrcpy/dialog.cpp b/QtScrcpy/dialog.cpp index 938b6d5..3aa54bb 100644 --- a/QtScrcpy/dialog.cpp +++ b/QtScrcpy/dialog.cpp @@ -98,6 +98,7 @@ void Dialog::initUI() ui->formatBox->setCurrentIndex(Config::getInstance().getRecordFormatIndex()); ui->recordPathEdt->setText(Config::getInstance().getRecordPath()); + ui->framelessCheck->setChecked(Config::getInstance().getFramelessWindow()); #ifdef Q_OS_OSX // mac need more width @@ -384,3 +385,9 @@ void Dialog::on_formatBox_activated(int index) { Config::getInstance().setRecordFormatIndex(index); } + +void Dialog::on_framelessCheck_stateChanged(int arg1) +{ + Q_UNUSED(arg1) + Config::getInstance().setFramelessWindow(ui->framelessCheck->isChecked()); +} diff --git a/QtScrcpy/dialog.h b/QtScrcpy/dialog.h index a55361f..2b871a3 100644 --- a/QtScrcpy/dialog.h +++ b/QtScrcpy/dialog.h @@ -62,6 +62,8 @@ private slots: void on_formatBox_activated(int index); + void on_framelessCheck_stateChanged(int arg1); + private: bool checkAdbRun(); void initUI(); diff --git a/QtScrcpy/dialog.ui b/QtScrcpy/dialog.ui index f22349b..9060e15 100644 --- a/QtScrcpy/dialog.ui +++ b/QtScrcpy/dialog.ui @@ -215,6 +215,22 @@ + + + + + 0 + 0 + + + + always on top + + + false + + + @@ -228,22 +244,6 @@ - - - - - 0 - 0 - - - - background record - - - false - - - @@ -260,8 +260,8 @@ - - + + 0 @@ -269,13 +269,20 @@ - always on top + background record - + false + + + + frameless + + + diff --git a/QtScrcpy/res/i18n/QtScrcpy_en.qm b/QtScrcpy/res/i18n/QtScrcpy_en.qm index d6717b1..d68f816 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 f158eee..9592331 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 @@ -49,17 +49,17 @@ Dialog - + Wireless Wireless - + wireless connect wireless connect - + wireless disconnect wireless disconnect @@ -75,7 +75,7 @@ - + select path select path @@ -90,27 +90,32 @@ record screen - + + frameless + frameless + + + stop all server stop all server - + adb command: adb command: - + terminate terminate - + execute execute - + clear clear @@ -124,12 +129,12 @@ auto enable - + background record background record - + screen-off screen-off @@ -144,7 +149,7 @@ max size: - + always on top always on top @@ -154,27 +159,27 @@ refresh script - + get device IP get device IP - + USB line USB line - + stop server stop server - + start server start server - + device serial: device serial: @@ -188,12 +193,12 @@ bit rate: - + start adbd start adbd - + refresh devices refresh devices @@ -206,7 +211,7 @@ QObject - + This software is completely open source and free, you can download it at the following address: This software is completely open source and free, you can download it at the following address: @@ -219,12 +224,12 @@ Tool - + full screen full screen - + expand notify expand notify @@ -237,52 +242,52 @@ turn on - + touch switch touch switch - + close screen close screen - + power power - + volume up volume up - + volume down volume down - + app switch app switch - + menu menu - + home home - + return return - + screen shot screen shot @@ -302,7 +307,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 3552ab7..7bcbdad 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 5001248..f3e0056 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 失败 @@ -49,17 +49,17 @@ Dialog - + Wireless 无线 - + wireless connect 无线连接 - + wireless disconnect 无线断开 @@ -75,7 +75,7 @@ - + select path 选择路径 @@ -90,27 +90,32 @@ 录制屏幕 - + + frameless + 无边框 + + + stop all server 停止所有服务 - + adb command: adb命令: - + terminate 终止 - + execute 执行 - + clear 清理 @@ -124,12 +129,12 @@ 自动启用脚本 - + background record 后台录制 - + screen-off 自动息屏 @@ -144,7 +149,7 @@ 最大尺寸: - + always on top 窗口置顶 @@ -154,27 +159,27 @@ 刷新脚本 - + get device IP 获取设备IP - + USB line USB线 - + stop server 停止服务 - + start server 启动服务 - + device serial: 设备序列号: @@ -188,12 +193,12 @@ 比特率: - + start adbd 启动adbd - + refresh devices 刷新设备列表 @@ -206,7 +211,7 @@ QObject - + This software is completely open source and free, you can download it at the following address: 本软件完全开源免费,你可以在下面的地址下载: @@ -219,12 +224,12 @@ 工具 - + full screen 全屏 - + expand notify 下拉通知 @@ -237,52 +242,52 @@ 解锁 - + touch switch 触摸显示开关 - + close screen 关闭屏幕 - + power 电源 - + volume up 音量加 - + volume down 音量减 - + app switch 切换应用 - + menu 菜单 - + home 主界面 - + return 返回 - + screen shot 截图 @@ -302,7 +307,7 @@ 文件传输失败 - + file does not exist 文件不存在 diff --git a/QtScrcpy/util/config.cpp b/QtScrcpy/util/config.cpp index 630faea..ea39ea7 100644 --- a/QtScrcpy/util/config.cpp +++ b/QtScrcpy/util/config.cpp @@ -44,6 +44,15 @@ #define COMMON_RECORD_FORMAT_INDEX_KEY "RecordFormatIndex" #define COMMON_RECORD_FORMAT_INDEX_DEF 0 +#define SERIAL_WINDOW_RECT_KEY_X "WindowRectX" +#define SERIAL_WINDOW_RECT_KEY_Y "WindowRectY" +#define SERIAL_WINDOW_RECT_KEY_W "WindowRectW" +#define SERIAL_WINDOW_RECT_KEY_H "WindowRectH" +#define SERIAL_WINDOW_RECT_KEY_DEF -1 + +#define COMMON_FRAMELESS_WINDOW_KEY "FramelessWindow" +#define COMMON_FRAMELESS_WINDOW_DEF false + // 最大尺寸 录制格式 QString Config::s_configPath = ""; @@ -140,6 +149,45 @@ void Config::setRecordFormatIndex(int recordFormatIndex) m_userData->endGroup(); } +void Config::setRect(const QString &serial, const QRect &rc) +{ + m_userData->beginGroup(serial); + m_userData->setValue(SERIAL_WINDOW_RECT_KEY_X, rc.left()); + m_userData->setValue(SERIAL_WINDOW_RECT_KEY_Y, rc.top()); + m_userData->setValue(SERIAL_WINDOW_RECT_KEY_W, rc.width()); + m_userData->setValue(SERIAL_WINDOW_RECT_KEY_H, rc.height()); + m_userData->endGroup(); + m_userData->sync(); +} + +QRect Config::getRect(const QString &serial) +{ + QRect rc; + m_userData->beginGroup(serial); + rc.setX(m_userData->value(SERIAL_WINDOW_RECT_KEY_X, SERIAL_WINDOW_RECT_KEY_DEF).toInt()); + rc.setY(m_userData->value(SERIAL_WINDOW_RECT_KEY_Y, SERIAL_WINDOW_RECT_KEY_DEF).toInt()); + rc.setWidth(m_userData->value(SERIAL_WINDOW_RECT_KEY_W, SERIAL_WINDOW_RECT_KEY_DEF).toInt()); + rc.setHeight(m_userData->value(SERIAL_WINDOW_RECT_KEY_H, SERIAL_WINDOW_RECT_KEY_DEF).toInt()); + m_userData->endGroup(); + return rc; +} + +void Config::setFramelessWindow(bool frameless) +{ + m_userData->beginGroup(GROUP_COMMON); + m_userData->setValue(COMMON_FRAMELESS_WINDOW_KEY, frameless); + m_userData->endGroup(); +} + +bool Config::getFramelessWindow() +{ + bool framelessWindow = false; + m_userData->beginGroup(GROUP_COMMON); + framelessWindow = m_userData->value(COMMON_FRAMELESS_WINDOW_KEY, COMMON_FRAMELESS_WINDOW_DEF).toBool(); + m_userData->endGroup(); + return framelessWindow; +} + QString Config::getServerVersion() { QString server; diff --git a/QtScrcpy/util/config.h b/QtScrcpy/util/config.h index 9e91841..28cfec2 100644 --- a/QtScrcpy/util/config.h +++ b/QtScrcpy/util/config.h @@ -3,6 +3,7 @@ #include #include +#include class QSettings; class Config : public QObject @@ -29,6 +30,10 @@ public: void setMaxSizeIndex(int maxSizeIndex); int getRecordFormatIndex(); void setRecordFormatIndex(int recordFormatIndex); + void setRect(const QString &serial, const QRect &rc); + QRect getRect(const QString &serial); + bool getFramelessWindow(); + void setFramelessWindow(bool frameless); private: explicit Config(QObject *parent = nullptr); diff --git a/QtScrcpy/util/mousetap/cocoamousetap.h b/QtScrcpy/util/mousetap/cocoamousetap.h index 419887b..e1757f8 100644 --- a/QtScrcpy/util/mousetap/cocoamousetap.h +++ b/QtScrcpy/util/mousetap/cocoamousetap.h @@ -15,7 +15,7 @@ public: void initMouseEventTap() override; void quitMouseEventTap() override; - void enableMouseEventTap(QWidget* widget, bool enabled) override; + void enableMouseEventTap(QRect rc, bool enabled) override; protected: void run() override; diff --git a/QtScrcpy/util/mousetap/cocoamousetap.mm b/QtScrcpy/util/mousetap/cocoamousetap.mm index c03827c..67c009f 100644 --- a/QtScrcpy/util/mousetap/cocoamousetap.mm +++ b/QtScrcpy/util/mousetap/cocoamousetap.mm @@ -1,6 +1,5 @@ #import #include -#include #include "cocoamousetap.h" @@ -20,77 +19,55 @@ typedef struct MouseEventTapData{ CFMachPortRef tap = Q_NULLPTR; CFRunLoopRef runloop = Q_NULLPTR; CFRunLoopSourceRef runloopSource = Q_NULLPTR; - QWidget* widget = Q_NULLPTR; + QRect rc; } MouseEventTapData; static CGEventRef Cocoa_MouseTapCallback(CGEventTapProxy proxy, CGEventType type, CGEventRef event, void *refcon) { Q_UNUSED(proxy); MouseEventTapData *tapdata = (MouseEventTapData*)refcon; - - NSView *nsview; - NSWindow *nswindow; - NSRect windowRect; - NSRect newWindowRect; - CGPoint eventLocation; - switch (type) { case kCGEventTapDisabledByTimeout: { CGEventTapEnable(tapdata->tap, true); - return NULL; + return nullptr; } case kCGEventTapDisabledByUserInput: { - return NULL; + return nullptr; } default: break; } - if (!tapdata->widget) { + if (tapdata->rc.isEmpty()) { return event; } - // get nswindow from qt widget - nsview = (NSView *)tapdata->widget->window()->winId(); - if (!nsview) { - return event; - } - nswindow = [nsview window]; - eventLocation = CGEventGetUnflippedLocation(event); - windowRect = [nswindow contentRectForFrameRect:[nswindow frame]]; - - windowRect.origin.x += 100; - windowRect.origin.y += 30; - windowRect.size.width -= 180; - windowRect.size.height -= 60; - - newWindowRect = NSMakeRect(windowRect.origin.x, windowRect.origin.y, - windowRect.size.width - 10, windowRect.size.height - 10); - //qDebug() << newWindowRect.origin.x << newWindowRect.origin.y << newWindowRect.size.width << newWindowRect.size.height; - - if (!NSMouseInRect(NSPointFromCGPoint(eventLocation), newWindowRect, NO)) { - - /* This is in CGs global screenspace coordinate system, which has a - * flipped Y. - */ - CGPoint newLocation = CGEventGetLocation(event); - - if (eventLocation.x < NSMinX(windowRect)) { - newLocation.x = NSMinX(windowRect); - } else if (eventLocation.x >= NSMaxX(windowRect)) { - newLocation.x = NSMaxX(windowRect) - 1.0; + NSRect limitWindowRect = NSMakeRect(tapdata->rc.left(), tapdata->rc.top(), + tapdata->rc.width(), tapdata->rc.height()); + // check rect samll than limit rect + NSRect checkWindowRect = NSMakeRect(limitWindowRect.origin.x + 10, limitWindowRect.origin.y + 10, + limitWindowRect.size.width - 10, limitWindowRect.size.height - 10); + /* This is in CGs global screenspace coordinate system, which has a + * flipped Y. + */ + CGPoint eventLocation = CGEventGetLocation(event); + if (!NSMouseInRect(NSPointFromCGPoint(eventLocation), checkWindowRect, NO)) { + if (eventLocation.x <= NSMinX(limitWindowRect)) { + eventLocation.x = NSMinX(limitWindowRect) + 1.0; + } else if (eventLocation.x >= NSMaxX(limitWindowRect)) { + eventLocation.x = NSMaxX(limitWindowRect) - 1.0; } - if (eventLocation.y <= NSMinY(windowRect)) { - newLocation.y -= (NSMinY(windowRect) - eventLocation.y + 1); - } else if (eventLocation.y > NSMaxY(windowRect)) { - newLocation.y += (eventLocation.y - NSMaxY(windowRect)); + if (eventLocation.y <= NSMinY(limitWindowRect)) { + eventLocation.y = NSMinY(limitWindowRect) + 1.0; + } else if (eventLocation.y >= NSMaxY(limitWindowRect)) { + eventLocation.y = NSMaxY(limitWindowRect) - 1.0; } - CGWarpMouseCursorPosition(newLocation); + CGWarpMouseCursorPosition(eventLocation); CGAssociateMouseAndMouseCursorPosition(YES); if ((CGEventMaskBit(type) & movementEventsMask) == 0) { @@ -99,7 +76,7 @@ static CGEventRef Cocoa_MouseTapCallback(CGEventTapProxy proxy, CGEventType type * movement events, since they mean that our warp cursor above * behaves strangely. */ - CGEventSetLocation(event, newLocation); + CGEventSetLocation(event, eventLocation); } } @@ -171,11 +148,11 @@ void CocoaMouseTap::quitMouseEventTap() } } -void CocoaMouseTap::enableMouseEventTap(QWidget* widget, bool enabled) +void CocoaMouseTap::enableMouseEventTap(QRect rc, bool enabled) { if (m_tapData && m_tapData->tap) { - enabled ? m_tapData->widget = widget : m_tapData->widget = Q_NULLPTR; + enabled ? m_tapData->rc = rc : m_tapData->rc = QRect(); CGEventTapEnable(m_tapData->tap, enabled); } } diff --git a/QtScrcpy/util/mousetap/mousetap.h b/QtScrcpy/util/mousetap/mousetap.h index eab1389..8cad8f9 100644 --- a/QtScrcpy/util/mousetap/mousetap.h +++ b/QtScrcpy/util/mousetap/mousetap.h @@ -1,12 +1,15 @@ #ifndef MOUSETAP_H #define MOUSETAP_H +#include + class QWidget; class MouseTap { public: static MouseTap* getInstance(); virtual void initMouseEventTap() = 0; virtual void quitMouseEventTap() = 0; - virtual void enableMouseEventTap(QWidget* widget, bool enabled) = 0; + // rc base global screenspace coordinate system, which has a flipped Y. + virtual void enableMouseEventTap(QRect rc, bool enabled) = 0; private: static MouseTap *s_instance; diff --git a/QtScrcpy/util/mousetap/winmousetap.cpp b/QtScrcpy/util/mousetap/winmousetap.cpp index 7303998..ecbf369 100644 --- a/QtScrcpy/util/mousetap/winmousetap.cpp +++ b/QtScrcpy/util/mousetap/winmousetap.cpp @@ -24,17 +24,12 @@ void WinMouseTap::quitMouseEventTap() } -void WinMouseTap::enableMouseEventTap(QWidget *widget, bool enabled) +void WinMouseTap::enableMouseEventTap(QRect rc, bool enabled) { - if (!widget) { + if (enabled && rc.isEmpty()) { return; } if(enabled) { - QRect rc(widget->parentWidget()->mapToGlobal(widget->pos()) - , widget->size()); - // high dpi support - rc.setTopLeft(rc.topLeft() * widget->devicePixelRatio()); - rc.setBottomRight(rc.bottomRight() * widget->devicePixelRatio()); RECT mainRect; mainRect.left = (LONG)rc.left(); mainRect.right = (LONG)rc.right(); diff --git a/QtScrcpy/util/mousetap/winmousetap.h b/QtScrcpy/util/mousetap/winmousetap.h index 9b23f68..a5d4de1 100644 --- a/QtScrcpy/util/mousetap/winmousetap.h +++ b/QtScrcpy/util/mousetap/winmousetap.h @@ -11,7 +11,7 @@ public: void initMouseEventTap() override; void quitMouseEventTap() override; - void enableMouseEventTap(QWidget* widget, bool enabled) override; + void enableMouseEventTap(QRect rc, bool enabled) override; }; #endif // WINMOUSETAP_H diff --git a/README.md b/README.md index 0f6b2cd..4003f8e 100644 --- a/README.md +++ b/README.md @@ -160,6 +160,7 @@ Note: it is not necessary to keep you Android device connected via USB after you - Display Android device screens in real time - Real-time mouse and keyboard control of Android devices - Screen recording +- Screenshot to png - 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 @@ -176,6 +177,33 @@ Note: it is not necessary to keep you Android device connected via USB after you - `Ctrl`+`Shift`+`v` copies the computer clipboard to the device clipboard; - `Ctrl`+`v` _pastes_ the computer clipboard as a sequence of text events (but breaks non-ASCII characters). +- Group control + +## Shortcuts + + | Action | Shortcut (Windows) | Shortcut (macOS) + | -------------------------------------- |:----------------------------- |:----------------------------- + | Switch fullscreen mode | `Ctrl`+`f` | `Cmd`+`f` + | Resize window to 1:1 (pixel-perfect) | `Ctrl`+`g` | `Cmd`+`g` + | Resize window to remove black borders | `Ctrl`+`x` \| _Double-click¹_ | `Cmd`+`x` \| _Double-click¹_ + | Click on `HOME` | `Ctrl`+`h` \| _Middle-click_ | `Ctrl`+`h` \| _Middle-click_ + | Click on `BACK` | `Ctrl`+`b` \| _Right-click²_ | `Cmd`+`b` \| _Right-click²_ + | Click on `APP_SWITCH` | `Ctrl`+`s` | `Cmd`+`s` + | Click on `MENU` | `Ctrl`+`m` | `Ctrl`+`m` + | Click on `VOLUME_UP` | `Ctrl`+`↑` _(up)_ | `Cmd`+`↑` _(up)_ + | Click on `VOLUME_DOWN` | `Ctrl`+`↓` _(down)_ | `Cmd`+`↓` _(down)_ + | Click on `POWER` | `Ctrl`+`p` | `Cmd`+`p` + | Power on | _Right-click²_ | _Right-click²_ + | Turn device screen off (keep mirroring)| `Ctrl`+`o` | `Cmd`+`o` + | Expand notification panel | `Ctrl`+`n` | `Cmd`+`n` + | Collapse notification panel | `Ctrl`+`Shift`+`n` | `Cmd`+`Shift`+`n` + | Copy device clipboard to computer | `Ctrl`+`c` | `Cmd`+`c` + | Paste computer clipboard to device | `Ctrl`+`v` | `Cmd`+`v` + | Copy computer clipboard to device | `Ctrl`+`Shift`+`v` | `Cmd`+`Shift`+`v` + +_¹Double-click on black borders to remove them._ + +_²Right-click turns the screen on if it was off, presses BACK otherwise._ ## TODO [TODO](docs/TODO.md) @@ -198,7 +226,7 @@ There are several reasons listed as below according to importance (high to low). 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 (mingw not supported)) +1. Set up the Qt development environment on the target platform (Qt >= 5.12.0, vs >= 2017 (mingw not supported)) 2. Clone the project 3. Open the project root directory all.pro with QtCreator 4. Compile and run diff --git a/README_zh.md b/README_zh.md index 4afff4c..f1c6dcc 100644 --- a/README_zh.md +++ b/README_zh.md @@ -163,6 +163,7 @@ Mac OS平台,你可以直接使用我编译好的可执行程序: - 实时显示Android设备屏幕 - 实时键鼠控制Android设备 - 屏幕录制 +- 截图为png - 无线连接 - 最多支持16台设备连接(PC性能允许的情况下可以增加,需要自己编译) - 全屏显示 @@ -176,6 +177,33 @@ Mac OS平台,你可以直接使用我编译好的可执行程序: - `Ctrl` + `c`将设备剪贴板复制到计算机剪贴板; - `Ctrl` + `Shift` + `v`将计算机剪贴板复制到设备剪贴板; - `Ctrl` +`v` 将计算机剪贴板作为一系列文本事件发送到设备(不支持非ASCII字符)。 +- 群控 + +## 快捷键 + + | 功能 | 快捷键(Windows) | 快捷键 (macOS) + | -------------------------------------- |:----------------------------- |:----------------------------- + | 切换全屏 | `Ctrl`+`f` | `Cmd`+`f` + | 调整窗口大小为 1:1 | `Ctrl`+`g` | `Cmd`+`g` + | 调整窗口大小去除黑边 | `Ctrl`+`x` \| _左键双击_ | `Cmd`+`x` \| _左键双击_ + | 点击 `主页` | `Ctrl`+`h` \| _点击鼠标中键_ | `Ctrl`+`h` \| _点击鼠标中键_ + | 点击 `BACK` | `Ctrl`+`b` \| _右键双击_ | `Cmd`+`b` \| _右键双击_ + | 点击 `APP_SWITCH` | `Ctrl`+`s` | `Cmd`+`s` + | 点击 `MENU` | `Ctrl`+`m` | `Ctrl`+`m` + | 点击 `VOLUME_UP` | `Ctrl`+`↑` _(上)_ | `Cmd`+`↑` _(上)_ + | 点击 `VOLUME_DOWN` | `Ctrl`+`↓` _(下)_ | `Cmd`+`↓` _(下)_ + | 点击 `POWER` | `Ctrl`+`p` | `Cmd`+`p` + | 打开电源 | _右键双击_ | _右键双击_ + | 关闭屏幕 (保持投屏) | `Ctrl`+`o` | `Cmd`+`o` + | 打开下拉菜单 | `Ctrl`+`n` | `Cmd`+`n` + | 关闭下拉菜单 | `Ctrl`+`Shift`+`n` | `Cmd`+`Shift`+`n` + | 复制设备剪切板到电脑 | `Ctrl`+`c` | `Cmd`+`c` + | 粘贴电脑剪切板到设备 | `Ctrl`+`v` | `Cmd`+`v` + | 复制电脑剪切板到设备 | `Ctrl`+`Shift`+`v` | `Cmd`+`Shift`+`v` + +鼠标左键双击黑色区域可以去除黑色区域 + +如果电源关闭,鼠标右键双击打开电源;如果电源开启,鼠标右键双击相当于返回 ## TODO [后期计划](docs/TODO.md) @@ -198,7 +226,7 @@ Mac OS平台,你可以直接使用我编译好的可执行程序: 尽量提供了所有依赖资源,方便傻瓜式编译。 ### PC端 -1. 目标平台上搭建Qt开发环境(Qt >= 5.9.7, vs >= 2015 (**不支持mingw**)) +1. 目标平台上搭建Qt开发环境(Qt >= 5.12.0, vs >= 2017 (**不支持mingw**)) 2. 克隆该项目 3. 使用QtCreator打开项目根目录all.pro 4. 编译,运行即可 diff --git a/ci/mac/package/dmg-background.jpg b/ci/mac/package/dmg-background.jpg new file mode 100644 index 0000000..84c5074 Binary files /dev/null and b/ci/mac/package/dmg-background.jpg differ diff --git a/ci/mac/package/package.py b/ci/mac/package/package.py new file mode 100644 index 0000000..a3bca07 --- /dev/null +++ b/ci/mac/package/package.py @@ -0,0 +1,55 @@ +import dmgbuild +import os +import json +import sys + +current_file_path = os.path.dirname(os.path.realpath(__file__)) +dmg_settings_path = '%s/dmg-settings.json' % current_file_path +dmg_background_img = '%s/dmg-background.jpg' % current_file_path +app_path = '%s/../../build/QtScrcpy.app' % current_file_path +dmg_path = '%s/../../build/QtScrcpy.dmg' % current_file_path +app_name = 'QtScrcpy' + +def console_print(msg): + print(msg) + sys.stdout.flush() + +def generate_dmg_info(): + with open(dmg_settings_path, 'w') as file: + info = { + 'title': app_name, + 'icon-size': 120, + 'background': dmg_background_img, + 'format': 'UDZO', + 'compression-level': 9, + 'window': { + 'position': {'x': 400, 'y': 200}, + 'size': {'width': 780, 'height': 480} + }, + 'contents': [ + { + 'x': 223, + 'y': 227, + 'type': 'file', + 'path': app_path + }, + { + 'x': 550, + 'y': 227, + 'type': 'link', + 'path': '/Applications' + } + ] + } + json.dump(info, file) + +if __name__ == '__main__': + console_print('generate dmg info') + generate_dmg_info() + console_print('build dmg: %s' % dmg_path) + dmgbuild.build_dmg(dmg_path, app_name, dmg_settings_path) + if not os.path.exists(dmg_path): + console_print('fail to create %s' % dmg_path) + sys.exit(1) + + sys.exit(0) \ No newline at end of file diff --git a/ci/mac/package/requirements.txt b/ci/mac/package/requirements.txt new file mode 100644 index 0000000..aa42ddf --- /dev/null +++ b/ci/mac/package/requirements.txt @@ -0,0 +1 @@ +dmgbuild==1.3.3 \ No newline at end of file diff --git a/ci/mac/package_for_mac.sh b/ci/mac/package_for_mac.sh new file mode 100755 index 0000000..60f01fa --- /dev/null +++ b/ci/mac/package_for_mac.sh @@ -0,0 +1,37 @@ +# 获取绝对路径,保证其他目录执行此脚本依然正确 +{ +cd $(dirname "$0") +script_path=$(pwd) +cd - +} &> /dev/null # disable output +# 设置当前目录,cd的目录影响接下来执行程序的工作目录 +old_cd=$(pwd) +cd $(dirname "$0") + +echo +echo +echo --------------------------------------------------------------- +echo pip install requirements +echo --------------------------------------------------------------- + +pip install -r $script_path/package/requirements.txt +if [ $? -ne 0 ] ;then + echo "pip install requirements failed" + exit 1 +fi + +echo +echo +echo --------------------------------------------------------------- +echo create package +echo --------------------------------------------------------------- + +python $script_path/package/package.py +if [ $? -ne 0 ] ;then + echo "create package failed" + exit 1 +fi + +# 恢复当前目录 +cd $old_cd +exit 0 diff --git a/docs/FAQ.md b/docs/FAQ.md index 728270f..1ebe090 100644 --- a/docs/FAQ.md +++ b/docs/FAQ.md @@ -3,16 +3,26 @@ 如果在此文档没有解决你的问题,描述你的问题,截图软件控制台中打印的日志,一起发到QQ群里提问。 +## 支持声音(软件不做支持) +[关于转发安卓声音到PC的讨论](https://github.com/Genymobile/scrcpy/issues/14#issuecomment-543204526) + ## 无法输入中文 -安装搜狗输入法/QQ输入法就可以支持输入中文了 +手机端安装搜狗输入法/QQ输入法就可以支持输入中文了 ## 可以看到画面,但无法控制 有些手机(小米等手机)需要额外打开控制权限,检查是否USB调试里打开了允许模拟点击 ![image](image/USB调试(安全设置).jpg) -## 错误信息Could not open video stream +## 手机通过数据线连接电脑,刷新设备列表以后,没有任何设备出现 +随便下载一个手机助手,尝试连接成功以后,再用QtScrcpy刷新设备列表连接 + +## 错误信息:AdbProcess::error:adb server version (40) doesnt match this client (41) +任务管理找到adb进程并杀死,重新操作即可 + +## 错误信息:Could not open video stream 导致这个错误的原因有很多,最简单的解决方法是在分辨率设置中,选择一个较低的分辨率 -## 声音 -[关于转发安卓声音到PC的讨论](https://github.com/Genymobile/scrcpy/issues/14#issuecomment-543204526) +## 错误信息:QOpenGLShaderProgram::attributeLocation(vertexIn): shader program is not linked +config.ini里修改下解码方式,改成1或者2试试 + diff --git a/docs/TODO.md b/docs/TODO.md index cfec2f0..47feaf3 100644 --- a/docs/TODO.md +++ b/docs/TODO.md @@ -7,7 +7,6 @@ ## 中优先级 - 脚本 -- 群控 - 软解 - opengles 3.0 兼容性参考[这里](https://github.com/libretro/glsl-shaders/blob/master/nnedi3/shaders/yuv-to-rgb-2x.glsl)