diff --git a/.github/workflows/ubuntu.yml b/.github/workflows/ubuntu.yml index 691377281..a71e9d748 100644 --- a/.github/workflows/ubuntu.yml +++ b/.github/workflows/ubuntu.yml @@ -18,7 +18,7 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - os: [ubuntu-18.04] + os: [ubuntu-22.04] qt-ver: [5.15.1] qt-arch-install: [gcc_64] gcc-arch: [x64] diff --git a/QtScrcpy/CMakeLists.txt b/QtScrcpy/CMakeLists.txt index 5ddf1434f..cc3568133 100755 --- a/QtScrcpy/CMakeLists.txt +++ b/QtScrcpy/CMakeLists.txt @@ -79,8 +79,8 @@ set(CMAKE_AUTOUIC ON) set(CMAKE_AUTOMOC ON) set(CMAKE_AUTORCC ON) -find_package(QT NAMES Qt6 Qt5 COMPONENTS Widgets Network REQUIRED) -find_package(Qt${QT_VERSION_MAJOR} COMPONENTS Widgets Network REQUIRED) +find_package(QT NAMES Qt6 Qt5 COMPONENTS Widgets Network Multimedia REQUIRED) +find_package(Qt${QT_VERSION_MAJOR} COMPONENTS Widgets Network Multimedia REQUIRED) if(CMAKE_SYSTEM_NAME STREQUAL "Linux") find_package(QT NAMES Qt6 Qt5 COMPONENTS X11Extras REQUIRED) find_package(Qt${QT_VERSION_MAJOR} COMPONENTS X11Extras REQUIRED) @@ -108,6 +108,13 @@ set(QC_UIBASE_SOURCES ) source_group(uibase FILES ${QC_UIBASE_SOURCES}) +# audio +set(QC_AUDIO_SOURCES + audio/audiooutput.h + audio/audiooutput.cpp +) +source_group(audio FILES ${QC_AUDIO_SOURCES}) + # ui set(QC_UI_SOURCES ui/toolform.h @@ -199,6 +206,7 @@ set(QC_PROJECT_SOURCES ${QC_MAIN_SOURCES} ${QC_GROUP_CONTROLLER} ${QC_PLANTFORM_SOURCES} + ${QC_AUDIO_SOURCES} ) if(CMAKE_SYSTEM_NAME STREQUAL "Darwin") @@ -242,6 +250,11 @@ set_target_properties(${PROJECT_NAME} PROPERTIES if(CMAKE_SYSTEM_NAME STREQUAL "Windows") get_target_property(QSC_BIN_OUTPUT_PATH ${PROJECT_NAME} RUNTIME_OUTPUT_DIRECTORY) set(QSC_DEPLOY_PATH ${QSC_BIN_OUTPUT_PATH}) + + add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_if_different "${CMAKE_CURRENT_SOURCE_DIR}/sndcpy/sndcpy.bat" "${QSC_BIN_OUTPUT_PATH}" + COMMAND ${CMAKE_COMMAND} -E copy_if_different "${CMAKE_CURRENT_SOURCE_DIR}/sndcpy/sndcpy.apk" "${QSC_BIN_OUTPUT_PATH}" + ) endif() # MacOS @@ -255,6 +268,8 @@ if(CMAKE_SYSTEM_NAME STREQUAL "Darwin") add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD # config file copy to Contents/MacOS/config COMMAND ${CMAKE_COMMAND} -E copy_if_different "${CMAKE_CURRENT_SOURCE_DIR}/../config/config.ini" "${MACOS_BUNDLE_PATH}/MacOS/config/config.ini" + COMMAND ${CMAKE_COMMAND} -E copy_if_different "${CMAKE_CURRENT_SOURCE_DIR}/sndcpy/sndcpy.sh" "${MACOS_BUNDLE_PATH}/MacOS" + COMMAND ${CMAKE_COMMAND} -E copy_if_different "${CMAKE_CURRENT_SOURCE_DIR}/sndcpy/sndcpy.apk" "${MACOS_BUNDLE_PATH}/MacOS" ) # Step 2. ues MACOSX_PACKAGE_LOCATION copy icns to Resources @@ -284,6 +299,11 @@ if(CMAKE_SYSTEM_NAME STREQUAL "Linux") get_target_property(QSC_BIN_OUTPUT_PATH ${PROJECT_NAME} RUNTIME_OUTPUT_DIRECTORY) set(QSC_DEPLOY_PATH ${QSC_BIN_OUTPUT_PATH}) + add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_if_different "${CMAKE_CURRENT_SOURCE_DIR}/sndcpy/sndcpy.sh" "${QSC_BIN_OUTPUT_PATH}" + COMMAND ${CMAKE_COMMAND} -E copy_if_different "${CMAKE_CURRENT_SOURCE_DIR}/sndcpy/sndcpy.apk" "${QSC_BIN_OUTPUT_PATH}" + ) + set(THREADS_PREFER_PTHREAD_FLAG ON) find_package(Threads REQUIRED) @@ -309,5 +329,6 @@ add_subdirectory(QtScrcpyCore) target_link_libraries(${PROJECT_NAME} PRIVATE Qt${QT_VERSION_MAJOR}::Widgets Qt${QT_VERSION_MAJOR}::Network + Qt${QT_VERSION_MAJOR}::Multimedia QtScrcpyCore ) diff --git a/QtScrcpy/QtScrcpyCore b/QtScrcpy/QtScrcpyCore index 7ac8571da..3004e6393 160000 --- a/QtScrcpy/QtScrcpyCore +++ b/QtScrcpy/QtScrcpyCore @@ -1 +1 @@ -Subproject commit 7ac8571daadf5af9b548a4c02eb7c6d504af6a97 +Subproject commit 3004e63935fe8a3e57b91e117a91c1a6aa68ae42 diff --git a/QtScrcpy/audio/audiooutput.cpp b/QtScrcpy/audio/audiooutput.cpp new file mode 100644 index 000000000..34e00cd0c --- /dev/null +++ b/QtScrcpy/audio/audiooutput.cpp @@ -0,0 +1,197 @@ +#include +#include +#include +#include +#include + +#include "audiooutput.h" + +AudioOutput::AudioOutput(QObject *parent) + : QObject(parent) +{ + connect(&m_sndcpy, &QProcess::readyReadStandardOutput, this, [this]() { + qInfo() << QString("AudioOutput::") << QString(m_sndcpy.readAllStandardOutput()); + }); + connect(&m_sndcpy, &QProcess::readyReadStandardError, this, [this]() { + qInfo() << QString("AudioOutput::") << QString(m_sndcpy.readAllStandardError()); + }); +} + +AudioOutput::~AudioOutput() +{ + if (QProcess::NotRunning != m_sndcpy.state()) { + m_sndcpy.kill(); + } + stop(); +} + +bool AudioOutput::start(const QString& serial, int port) +{ + if (m_running) { + stop(); + } + + QElapsedTimer timeConsumeCount; + timeConsumeCount.start(); + bool ret = runSndcpyProcess(serial, port); + qInfo() << "AudioOutput::run sndcpy cost:" << timeConsumeCount.elapsed() << "milliseconds"; + if (!ret) { + return ret; + } + + startAudioOutput(); + startRecvData(port); + + m_running = true; + return true; +} + +void AudioOutput::stop() +{ + if (!m_running) { + return; + } + m_running = false; + + stopRecvData(); + stopAudioOutput(); +} + +void AudioOutput::installonly(const QString &serial, int port) +{ + runSndcpyProcess(serial, port, false); +} + +bool AudioOutput::runSndcpyProcess(const QString &serial, int port, bool wait) +{ + if (QProcess::NotRunning != m_sndcpy.state()) { + m_sndcpy.kill(); + } + +#ifdef Q_OS_WIN32 + QStringList params; + params << serial; + params << QString("%1").arg(port); + m_sndcpy.start("sndcpy.bat", params); +#else + QStringList params; + params << "sndcpy.sh"; + params << serial; + params << QString("%1").arg(port); + m_sndcpy.start("bash", params); +#endif + + if (!wait) { + return true; + } + + if (!m_sndcpy.waitForStarted()) { + qWarning() << "AudioOutput::start sndcpy.bat failed"; + return false; + } + if (!m_sndcpy.waitForFinished()) { + qWarning() << "AudioOutput::sndcpy.bat crashed"; + return false; + } + + return true; +} + +void AudioOutput::startAudioOutput() +{ + if (m_audioOutput) { + return; + } + + QAudioFormat format; + format.setSampleRate(48000); + format.setChannelCount(2); + format.setSampleSize(16); + format.setCodec("audio/pcm"); + format.setByteOrder(QAudioFormat::LittleEndian); + format.setSampleType(QAudioFormat::SignedInt); + + QAudioDeviceInfo info(QAudioDeviceInfo::defaultOutputDevice()); + if (!info.isFormatSupported(format)) { + qWarning() << "AudioOutput::audio format not supported, cannot play audio."; + return; + } + + m_audioOutput = new QAudioOutput(format, this); + connect(m_audioOutput, &QAudioOutput::stateChanged, this, [](QAudio::State state) { + qInfo() << "AudioOutput::audio state changed:" << state; + }); + m_audioOutput->setBufferSize(48000*2*15/1000 * 20); + m_outputDevice = m_audioOutput->start(); +} + +void AudioOutput::stopAudioOutput() +{ + if (!m_audioOutput) { + return; + } + + m_audioOutput->stop(); + delete m_audioOutput; + m_audioOutput = nullptr; +} + +void AudioOutput::startRecvData(int port) +{ + if (m_workerThread.isRunning()) { + stopRecvData(); + } + + auto audioSocket = new QTcpSocket(); + audioSocket->moveToThread(&m_workerThread); + connect(&m_workerThread, &QThread::finished, audioSocket, &QObject::deleteLater); + + connect(this, &AudioOutput::connectTo, audioSocket, [audioSocket](int port) { + audioSocket->connectToHost(QHostAddress::LocalHost, port); + if (!audioSocket->waitForConnected(500)) { + qWarning("AudioOutput::audio socket connect failed"); + return; + } + qInfo("AudioOutput::audio socket connect success"); + }); + connect(audioSocket, &QIODevice::readyRead, audioSocket, [this, audioSocket]() { + qint64 recv = audioSocket->bytesAvailable(); + //qDebug() << "AudioOutput::recv data:" << recv; + + if (!m_outputDevice) { + return; + } + if (m_buffer.capacity() < recv) { + m_buffer.reserve(recv); + } + + qint64 count = audioSocket->read(m_buffer.data(), audioSocket->bytesAvailable()); + m_outputDevice->write(m_buffer.data(), count); + }); + connect(audioSocket, &QTcpSocket::stateChanged, audioSocket, [](QAbstractSocket::SocketState state) { + qInfo() << "AudioOutput::audio socket state changed:" << state; + + }); +#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) + connect(audioSocket, &QTcpSocket::errorOccurred, audioSocket, [](QAbstractSocket::SocketError error) { + qInfo() << "AudioOutput::audio socket error occurred:" << error; + }); +#else + connect(audioSocket, QOverload::of(&QAbstractSocket::error), audioSocket, [](QAbstractSocket::SocketError error) { + qInfo() << "AudioOutput::audio socket error occurred:" << error; + }); +#endif + + m_workerThread.start(); + emit connectTo(port); +} + +void AudioOutput::stopRecvData() +{ + if (!m_workerThread.isRunning()) { + return; + } + + m_workerThread.quit(); + m_workerThread.wait(); +} diff --git a/QtScrcpy/audio/audiooutput.h b/QtScrcpy/audio/audiooutput.h new file mode 100644 index 000000000..1690168b7 --- /dev/null +++ b/QtScrcpy/audio/audiooutput.h @@ -0,0 +1,41 @@ +#ifndef AUDIOOUTPUT_H +#define AUDIOOUTPUT_H + +#include +#include +#include +#include + +class QAudioOutput; +class QIODevice; +class AudioOutput : public QObject +{ + Q_OBJECT +public: + explicit AudioOutput(QObject *parent = nullptr); + ~AudioOutput(); + + bool start(const QString& serial, int port); + void stop(); + void installonly(const QString& serial, int port); + +private: + bool runSndcpyProcess(const QString& serial, int port, bool wait = true); + void startAudioOutput(); + void stopAudioOutput(); + void startRecvData(int port); + void stopRecvData(); + +signals: + void connectTo(int port); + +private: + QAudioOutput* m_audioOutput = nullptr; + QPointer m_outputDevice; + QThread m_workerThread; + QProcess m_sndcpy; + QVector m_buffer; + bool m_running = false; +}; + +#endif // AUDIOOUTPUT_H diff --git a/QtScrcpy/main.cpp b/QtScrcpy/main.cpp index b85d218b9..629e08bdb 100644 --- a/QtScrcpy/main.cpp +++ b/QtScrcpy/main.cpp @@ -22,21 +22,24 @@ int main(int argc, char *argv[]) { // set env #ifdef Q_OS_WIN32 - qputenv("QTSCRCPY_ADB_PATH", "D:/android/sdk/platform-tools/adb.exe"); + qputenv("QTSCRCPY_ADB_PATH", "../../../QtScrcpy/QtScrcpyCore/src/third_party/adb/win/adb.exe"); qputenv("QTSCRCPY_SERVER_PATH", "../../../QtScrcpy/QtScrcpyCore/src/third_party/scrcpy-server"); qputenv("QTSCRCPY_KEYMAP_PATH", "../../../keymap"); qputenv("QTSCRCPY_CONFIG_PATH", "../../../config"); #endif #ifdef Q_OS_OSX - qputenv("QTSCRCPY_KEYMAP_PATH", "../../../../../keymap"); + qputenv("QTSCRCPY_ADB_PATH", "../../../../../../QtScrcpy/QtScrcpyCore/src/third_party/adb/mac/adb"); + qputenv("QTSCRCPY_SERVER_PATH", "../../../../../../QtScrcpy/QtScrcpyCore/src/third_party/scrcpy-server"); + qputenv("QTSCRCPY_KEYMAP_PATH", "../../../../../../keymap"); + qputenv("QTSCRCPY_CONFIG_PATH", "../../../../../../config"); #endif #ifdef Q_OS_LINUX - qputenv("QTSCRCPY_ADB_PATH", "../../QtScrcpy/QtScrcpyCore/src/third_party/adb/linux/adb"); - qputenv("QTSCRCPY_SERVER_PATH", "../../QtScrcpy/QtScrcpyCore/src/third_party/scrcpy-server"); - qputenv("QTSCRCPY_CONFIG_PATH", "../../config"); - qputenv("QTSCRCPY_KEYMAP_PATH", "../../keymap"); + qputenv("QTSCRCPY_ADB_PATH", "../../../QtScrcpy/QtScrcpyCore/src/third_party/adb/linux/adb"); + qputenv("QTSCRCPY_SERVER_PATH", "../../../QtScrcpy/QtScrcpyCore/src/third_party/scrcpy-server"); + qputenv("QTSCRCPY_KEYMAP_PATH", "../../../keymap"); + qputenv("QTSCRCPY_CONFIG_PATH", "../../../config"); #endif g_msgType = covertLogLevel(Config::getInstance().getLogLevel()); diff --git a/QtScrcpy/res/i18n/en_US.qm b/QtScrcpy/res/i18n/en_US.qm index e64c864c3..ffb637224 100644 Binary files a/QtScrcpy/res/i18n/en_US.qm and b/QtScrcpy/res/i18n/en_US.qm differ diff --git a/QtScrcpy/res/i18n/en_US.ts b/QtScrcpy/res/i18n/en_US.ts index 986bd019e..c8787cf5f 100644 --- a/QtScrcpy/res/i18n/en_US.ts +++ b/QtScrcpy/res/i18n/en_US.ts @@ -3,6 +3,114 @@ Dialog + + show + show + + + quit + quit + + + original + original + + + no lock + no lock + + + Notice + Notice + + + Hidden here! + Hidden here! + + + select path + select path + + + + QObject + + This software is completely open source and free. Use it at your own risk. You can download it at the following address: + This software is completely open source and free. Use it at your own risk. You can download it at the following address: + + + + ToolForm + + Tool + Tool + + + full screen + full screen + + + expand notify + expand notify + + + 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 + + + open screen + open screen + + + group control + group control + + + + VideoForm + + file does not exist + file does not exist + + + + Widget Wireless Wireless @@ -166,109 +274,20 @@ refresh devices - show - show - show - - - quit - quit - quit - - - original - original - - - no lock - no lock - - - Notice - Notice - Notice - - - Hidden here! - Hidden here! - Hidden here! - - - - QObject - - This software is completely open source and free. Use it at your own risk. You can download it at the following address: - This software is completely open source and free. Use it at your own risk. You can download it at the following address: - - - - ToolForm - - Tool - Tool - - - full screen - full screen - - - expand notify - expand notify - - - touch switch - touch switch - - - close screen - close screen - - - power - power - - - volume up - volume up - - - volume down - volume down - - - app switch - app switch - - - menu - menu + install sndcpy + install sndcpy - home - home + start audio + start audio - return - return - - - screen shot - screen shot + stop audio + stop audio - open screen - open screen - - - group control - group control - - - - VideoForm - - file does not exist - file does not exist + auto update + autp update diff --git a/QtScrcpy/res/i18n/zh_CN.qm b/QtScrcpy/res/i18n/zh_CN.qm index b98cc9c44..a505d6d00 100644 Binary files a/QtScrcpy/res/i18n/zh_CN.qm and b/QtScrcpy/res/i18n/zh_CN.qm differ diff --git a/QtScrcpy/res/i18n/zh_CN.ts b/QtScrcpy/res/i18n/zh_CN.ts index a356f7efb..093c35d75 100644 --- a/QtScrcpy/res/i18n/zh_CN.ts +++ b/QtScrcpy/res/i18n/zh_CN.ts @@ -3,6 +3,114 @@ Dialog + + show + 显示 + + + quit + 退出 + + + original + 原始 + + + no lock + 不锁定 + + + Notice + 提示 + + + Hidden here! + 安卓录屏程序隐藏在这! + + + select path + 选择路径 + + + + QObject + + This software is completely open source and free. Use it at your own risk. You can download it at the following address: + 本软件完全开源免费,作者不对使用该软件产生的一切后果负责。你可以在以下地址下载: + + + + ToolForm + + Tool + 工具 + + + full screen + 全屏 + + + expand notify + 下拉通知 + + + touch switch + 触摸显示开关 + + + close screen + 关闭屏幕 + + + power + 电源 + + + volume up + 音量加 + + + volume down + 音量减 + + + app switch + 切换应用 + + + menu + 菜单 + + + home + 主界面 + + + return + 返回 + + + screen shot + 截图 + + + open screen + 打开屏幕 + + + group control + 群控 + + + + VideoForm + + file does not exist + 文件不存在 + + + + Widget Wireless 无线 @@ -166,109 +274,20 @@ 刷新设备列表 - show - 显示 - 显示 - - - quit - 退出 - 退出 - - - original - 原始 - - - no lock - 不锁定 - - - Notice - 提示 - 提示 - - - Hidden here! - 安卓录屏程序隐藏在这! - 安卓录屏程序隐藏在这! - - - - QObject - - This software is completely open source and free. Use it at your own risk. You can download it at the following address: - 本软件完全开源免费,作者不对使用该软件产生的一切后果负责。你可以在以下地址下载: - - - - ToolForm - - Tool - 工具 - - - full screen - 全屏 - - - expand notify - 下拉通知 - - - touch switch - 触摸显示开关 - - - close screen - 关闭屏幕 - - - power - 电源 - - - volume up - 音量加 - - - volume down - 音量减 - - - app switch - 切换应用 - - - menu - 菜单 + install sndcpy + 安装sndcpy - home - 主界面 + start audio + 开始音频 - return - 返回 - - - screen shot - 截图 + stop audio + 停止音频 - open screen - 打开屏幕 - - - group control - 群控 - - - - VideoForm - - file does not exist - 文件不存在 + auto update + 自动刷新 diff --git a/QtScrcpy/sndcpy/sndcpy.apk b/QtScrcpy/sndcpy/sndcpy.apk new file mode 100644 index 000000000..93e8cc2c2 Binary files /dev/null and b/QtScrcpy/sndcpy/sndcpy.apk differ diff --git a/QtScrcpy/sndcpy/sndcpy.bat b/QtScrcpy/sndcpy/sndcpy.bat new file mode 100644 index 000000000..f8a41a7f4 --- /dev/null +++ b/QtScrcpy/sndcpy/sndcpy.bat @@ -0,0 +1,53 @@ +@echo off + +echo Begin Runing... +set SNDCPY_PORT=28200 +set SNDCPY_APK=sndcpy.apk +set ADB=adb.exe + +if not "%1"=="" ( + set serial=-s %1 +) +if not "%2"=="" ( + set SNDCPY_PORT=%2 +) + +echo Waiting for device %1... +%ADB% %serial% wait-for-device || goto :error +echo Find device %1 + +for /f "delims=" %%i in ('%ADB% %serial% shell pm path com.rom1v.sndcpy') do set sndcpy_installed=%%i +if "%sndcpy_installed%"=="" ( + echo Install %SNDCPY_APK%... + %ADB% %serial% uninstall com.rom1v.sndcpy || goto :error + %ADB% %serial% install -t -r -g %SNDCPY_APK% || goto :error + echo Install %SNDCPY_APK% success +) + +echo Request PROJECT_MEDIA permission... +%ADB% %serial% shell appops set com.rom1v.sndcpy PROJECT_MEDIA allow + +echo Forward port %SNDCPY_PORT%... +%ADB% %serial% forward tcp:%SNDCPY_PORT% localabstract:sndcpy || goto :error + +echo Start %SNDCPY_APK%... +%ADB% %serial% shell am start com.rom1v.sndcpy/.MainActivity || goto :error + +:check_start +echo Waiting %SNDCPY_APK% start... +::timeout /T 1 /NOBREAK > nul +%ADB% %serial% shell sleep 0.1 +for /f "delims=" %%i in ("%ADB% shell 'ps | grep com.rom1v.sndcpy'") do set sndcpy_started=%%i +if "%sndcpy_started%"=="" ( + goto :check_start +) +echo %SNDCPY_APK% started... + +echo Ready playing... +::vlc.exe -Idummy --demux rawaud --network-caching=0 --play-and-exit tcp://localhost:%SNDCPY_PORT% +::ffplay.exe -nodisp -autoexit -probesize 32 -sync ext -f s16le -ar 48k -ac 2 tcp://localhost:%SNDCPY_PORT% +goto :EOF + +:error +echo Failed with error #%errorlevel%. +exit /b %errorlevel% diff --git a/QtScrcpy/sndcpy/sndcpy.sh b/QtScrcpy/sndcpy/sndcpy.sh new file mode 100755 index 000000000..c9dfb9e36 --- /dev/null +++ b/QtScrcpy/sndcpy/sndcpy.sh @@ -0,0 +1,46 @@ +#!/bin/bash + +echo Begin Runing... +SNDCPY_PORT=28200 +SNDCPY_APK=sndcpy.apk +ADB=./adb + +serial= +if [[ $# -ge 2 ]] +then + serial="-s $1" + SNDCPY_PORT=$2 +fi + +echo "Waiting for device $1..." +$ADB $serial wait-for-device +echo "Find device $1" + +sndcpy_installed=$($ADB $serial shell pm path com.rom1v.sndcpy) +if [[ $sndcpy_installed == "" ]]; then + echo Install $SNDCPY_APK... + $ADB $serial uninstall com.rom1v.sndcpy + $ADB $serial install -t -r -g $SNDCPY_APK + echo Install $SNDCPY_APK success +fi + +echo Request PROJECT_MEDIA permission... +$ADB $serial shell appops set com.rom1v.sndcpy PROJECT_MEDIA allow + +echo Forward port $SNDCPY_PORT... +$ADB $serial forward tcp:$SNDCPY_PORT localabstract:sndcpy + +echo Start $SNDCPY_APK... +$ADB $serial shell am start com.rom1v.sndcpy/.MainActivity + +while ((1)) +do + echo Waiting $SNDCPY_APK start... + sleep 0.1 + sndcpy_started=$($ADB shell 'ps | grep com.rom1v.sndcpy') + if [[ $sndcpy_started != "" ]]; then + break + fi +done + +echo Ready playing... \ No newline at end of file diff --git a/QtScrcpy/ui/dialog.cpp b/QtScrcpy/ui/dialog.cpp index 1683cf6d1..6b0e84a85 100644 --- a/QtScrcpy/ui/dialog.cpp +++ b/QtScrcpy/ui/dialog.cpp @@ -25,11 +25,21 @@ const QString &getKeyMapPath() return s_keyMapPath; } -Dialog::Dialog(QWidget *parent) : QDialog(parent), ui(new Ui::Dialog) +Dialog::Dialog(QWidget *parent) : QWidget(parent), ui(new Ui::Widget) { ui->setupUi(this); initUI(); + updateBootConfig(true); + + on_useSingleModeCheck_clicked(); + on_updateDevice_clicked(); + + connect(&m_autoUpdatetimer, &QTimer::timeout, this, &Dialog::on_updateDevice_clicked); + if (ui->autoUpdatecheckBox->isChecked()) { + m_autoUpdatetimer.start(5000); + } + connect(&m_adb, &qsc::AdbProcess::adbProcessResult, this, [this](qsc::AdbProcess::ADB_EXEC_RESULT processResult) { QString log = ""; bool newLine = true; @@ -101,7 +111,7 @@ Dialog::Dialog(QWidget *parent) : QDialog(parent), ui(new Ui::Dialog) m_menu->addAction(m_quit); m_hideIcon->setContextMenu(m_menu); m_hideIcon->show(); - connect(m_showWindow, &QAction::triggered, this, &Dialog::slotShow); + connect(m_showWindow, &QAction::triggered, this, &Dialog::show); connect(m_quit, &QAction::triggered, this, [this]() { m_hideIcon->hide(); qApp->quit(); @@ -123,7 +133,7 @@ Dialog::~Dialog() void Dialog::initUI() { setAttribute(Qt::WA_DeleteOnClose); - setWindowFlags(windowFlags() | Qt::WindowMinimizeButtonHint | Qt::WindowCloseButtonHint | Qt::CustomizeWindowHint); + //setWindowFlags(windowFlags() | Qt::WindowMinimizeButtonHint | Qt::WindowCloseButtonHint | Qt::CustomizeWindowHint); setWindowTitle(Config::getInstance().getTitle()); @@ -145,12 +155,6 @@ void Dialog::initUI() ui->lockOrientationBox->addItem("180"); ui->lockOrientationBox->addItem("270"); ui->lockOrientationBox->setCurrentIndex(0); - - updateBootConfig(true); - - on_useSingleModeCheck_clicked(); - - on_updateDevice_clicked(); } void Dialog::updateBootConfig(bool toView) @@ -181,6 +185,7 @@ void Dialog::updateBootConfig(bool toView) ui->closeScreenCheck->setChecked(config.autoOffScreen); ui->stayAwakeCheck->setChecked(config.keepAlive); ui->useSingleModeCheck->setChecked(config.simpleMode); + ui->autoUpdatecheckBox->setChecked(config.autoUpdateDevice); } else { UserBootConfig config; @@ -198,6 +203,7 @@ void Dialog::updateBootConfig(bool toView) config.framelessWindow = ui->framelessCheck->isChecked(); config.keepAlive = ui->stayAwakeCheck->isChecked(); config.simpleMode = ui->useSingleModeCheck->isChecked(); + config.autoUpdateDevice = ui->autoUpdatecheckBox->isChecked(); Config::getInstance().setUserBootConfig(config); } } @@ -238,17 +244,13 @@ QString Dialog::getGameScript(const QString &fileName) return ret; } -void Dialog::slotShow() -{ - this->show(); - m_hideIcon->hide(); -} - void Dialog::slotActivated(QSystemTrayIcon::ActivationReason reason) { switch (reason) { case QSystemTrayIcon::Trigger: +#ifdef Q_OS_WIN32 this->show(); +#endif break; default: break; @@ -464,8 +466,11 @@ void Dialog::onDeviceConnected(bool success, const QString &serial, const QStrin void Dialog::onDeviceDisconnected(QString serial) { GroupController::instance().removeDevice(serial); - - auto data = qsc::IDeviceManage::getInstance().getDevice(serial)->getUserData(); + auto device = qsc::IDeviceManage::getInstance().getDevice(serial); + if (!device) { + return; + } + auto data = device->getUserData(); if (data) { VideoForm* vf = static_cast(data); qsc::IDeviceManage::getInstance().getDevice(serial)->deRegisterDeviceObserver(vf); @@ -688,3 +693,36 @@ const QString &Dialog::getServerPath() } return serverPath; } + +void Dialog::on_startAudioBtn_clicked() +{ + if (ui->serialBox->count() == 0) { + qWarning() << "No device is connected!"; + return; + } + + m_audioOutput.start(ui->serialBox->currentText(), 28200); +} + +void Dialog::on_stopAudioBtn_clicked() +{ + m_audioOutput.stop(); +} + +void Dialog::on_installSndcpyBtn_clicked() +{ + if (ui->serialBox->count() == 0) { + qWarning() << "No device is connected!"; + return; + } + m_audioOutput.installonly(ui->serialBox->currentText(), 28200); +} + +void Dialog::on_autoUpdatecheckBox_toggled(bool checked) +{ + if (checked) { + m_autoUpdatetimer.start(5000); + } else { + m_autoUpdatetimer.stop(); + } +} diff --git a/QtScrcpy/ui/dialog.h b/QtScrcpy/ui/dialog.h index 4fa05bd04..860c87e43 100644 --- a/QtScrcpy/ui/dialog.h +++ b/QtScrcpy/ui/dialog.h @@ -1,24 +1,26 @@ #ifndef DIALOG_H #define DIALOG_H -#include +#include #include #include #include #include #include +#include #include "adbprocess.h" #include "../QtScrcpyCore/include/QtScrcpyCore.h" +#include "audio/audiooutput.h" namespace Ui { - class Dialog; + class Widget; } class QYUVOpenGLWidget; -class Dialog : public QDialog +class Dialog : public QWidget { Q_OBJECT @@ -57,6 +59,14 @@ private slots: void on_useSingleModeCheck_clicked(); void on_serialBox_currentIndexChanged(const QString &arg1); + void on_startAudioBtn_clicked(); + + void on_stopAudioBtn_clicked(); + + void on_installSndcpyBtn_clicked(); + + void on_autoUpdatecheckBox_toggled(bool checked); + private: bool checkAdbRun(); void initUI(); @@ -64,7 +74,6 @@ private slots: void execAdbCmd(); void delayMs(int ms); QString getGameScript(const QString &fileName); - void slotShow(); void slotActivated(QSystemTrayIcon::ActivationReason reason); int findDeviceFromeSerialBox(bool wifi); quint32 getBitRate(); @@ -74,12 +83,14 @@ private slots: void closeEvent(QCloseEvent *event); private: - Ui::Dialog *ui; + Ui::Widget *ui; qsc::AdbProcess m_adb; QSystemTrayIcon *m_hideIcon; QMenu *m_menu; QAction *m_showWindow; QAction *m_quit; + AudioOutput m_audioOutput; + QTimer m_autoUpdatetimer; }; #endif // DIALOG_H diff --git a/QtScrcpy/ui/dialog.ui b/QtScrcpy/ui/dialog.ui index 97f4f0cc2..569e76956 100644 --- a/QtScrcpy/ui/dialog.ui +++ b/QtScrcpy/ui/dialog.ui @@ -1,13 +1,13 @@ - Dialog - + Widget + 0 0 1293 - 419 + 454 @@ -100,17 +100,34 @@ - - - - 0 - 0 - - - - Double click to connect: + + + 0 - + + + + + 0 + 0 + + + + Double click to connect: + + + + + + + auto update + + + true + + + + @@ -941,6 +958,45 @@ + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + install sndcpy + + + + + + + start audio + + + + + + + stop audio + + + + + + diff --git a/QtScrcpy/util/config.cpp b/QtScrcpy/util/config.cpp index d1716cfa5..8212b76bb 100644 --- a/QtScrcpy/util/config.cpp +++ b/QtScrcpy/util/config.cpp @@ -87,6 +87,9 @@ #define COMMON_SIMPLE_MODE_KEY "SimpleMode" #define COMMON_SIMPLE_MODE_DEF false +#define COMMON_AUTO_UPDATE_DEVICE_KEY "AutoUpdateDevice" +#define COMMON_AUTO_UPDATE_DEVICE_DEF true + // device config #define SERIAL_WINDOW_RECT_KEY_X "WindowRectX" #define SERIAL_WINDOW_RECT_KEY_Y "WindowRectY" @@ -145,6 +148,7 @@ void Config::setUserBootConfig(const UserBootConfig &config) m_userData->setValue(COMMON_AUTO_OFF_SCREEN_KEY, config.autoOffScreen); m_userData->setValue(COMMON_KEEP_ALIVE_KEY, config.keepAlive); m_userData->setValue(COMMON_SIMPLE_MODE_KEY, config.simpleMode); + m_userData->setValue(COMMON_AUTO_UPDATE_DEVICE_KEY, config.autoUpdateDevice); m_userData->endGroup(); m_userData->sync(); } @@ -167,6 +171,7 @@ UserBootConfig Config::getUserBootConfig() config.autoOffScreen = m_userData->value(COMMON_AUTO_OFF_SCREEN_KEY, COMMON_AUTO_OFF_SCREEN_DEF).toBool(); config.keepAlive = m_userData->value(COMMON_KEEP_ALIVE_KEY, COMMON_KEEP_ALIVE_DEF).toBool(); config.simpleMode = m_userData->value(COMMON_SIMPLE_MODE_KEY, COMMON_SIMPLE_MODE_DEF).toBool(); + config.autoUpdateDevice = m_userData->value(COMMON_AUTO_UPDATE_DEVICE_KEY, COMMON_AUTO_UPDATE_DEVICE_DEF).toBool(); m_userData->endGroup(); return config; } diff --git a/QtScrcpy/util/config.h b/QtScrcpy/util/config.h index 0eeea83cf..137e3755d 100644 --- a/QtScrcpy/util/config.h +++ b/QtScrcpy/util/config.h @@ -21,6 +21,7 @@ struct UserBootConfig bool framelessWindow = false; bool keepAlive = false; bool simpleMode = false; + bool autoUpdateDevice = true; }; class QSettings; diff --git a/README.md b/README.md index 3d226c62a..d819158c3 100644 --- a/README.md +++ b/README.md @@ -25,11 +25,11 @@ It focuses on: [lowlatency]: https://github.com/Genymobile/scrcpy/pull/646 -![win](screenshot/win.png) +![win](screenshot/win-en.png) -![mac](screenshot/mac.jpg) +![mac](screenshot/mac-en.png) -![linux](screenshot/ubuntu.png) +![linux](screenshot/linux-en.png) ## Customized key mapping 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/KeyMapDes.md) are the rules. @@ -122,12 +122,7 @@ you can [build it by yourself](##Build)(just ubuntu test) ## Run -### Simple Mode Connect to your Android device on your computer, then run the program and click `USB connect` or `WiFi connect` -### Not Simple Mode -Connect to your Android device on your computer, then run the program and click the button below to connect to the Android device. - -![run](screenshot/run.png) ### 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 @@ -139,7 +134,6 @@ Connect to your Android device on your computer, then run the program and click 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. @@ -187,6 +181,7 @@ Note: it is not necessary to keep you Android device connected via USB after you - `Ctrl`+`v` _pastes_ the computer clipboard as a sequence of text events (but breaks non-ASCII characters). - Group control +- Sync device speaker sound to the computer (based on [sndcpy](https://github.com/rom1v/sndcpy), Android 10+ only) ## Shortcuts diff --git a/README_zh.md b/README_zh.md index d82059462..0698199bd 100644 --- a/README_zh.md +++ b/README_zh.md @@ -24,12 +24,11 @@ QtScrcpy可以通过USB(或通过TCP/IP)连接Android设备,并进行显示和 [低延迟]: https://github.com/Genymobile/scrcpy/pull/646 +![win](screenshot/win-zh.png) -![win](screenshot/win.png) +![mac](screenshot/mac-zh.png) -![mac](screenshot/mac.jpg) - -![linux](screenshot/ubuntu.png) +![linux](screenshot/linux-zh.png) ## 自定义按键映射 可以根据需要,自己编写脚本将PC键盘按键映射为手机的触摸点击,编写规则在[这里](docs/KeyMapDes_zh.md)。 @@ -126,12 +125,7 @@ Mac OS平台,你可以直接使用我编译好的可执行程序: 目前只在ubuntu上测试过 ## 运行 -### 精简模式 在你的电脑上接入Android设备,然后运行程序,点击`一键USB连接`或者`一键WIFI连接` -### 非精简模式 -在你的电脑上接入Android设备,然后运行程序,按顺序点击如下按钮即可连接到Android设备 - -![运行](screenshot/run.png) ### 无线连接步骤(保证手机和电脑在同一个局域网): 1. 安卓手机端在开发者选项中打开usb调试 @@ -186,6 +180,7 @@ Mac OS平台,你可以直接使用我编译好的可执行程序: - `Ctrl` + `Shift` + `v`将计算机剪贴板复制到设备剪贴板; - `Ctrl` +`v` 将计算机剪贴板作为一系列文本事件发送到设备(不支持非ASCII字符)。 - 群控 +- 同步设备扬声器声音到电脑(基于[sndcpy](https://github.com/rom1v/sndcpy),仅支持安卓10+) ## 快捷键 diff --git a/screenshot/linux-en.png b/screenshot/linux-en.png new file mode 100644 index 000000000..2fde86532 Binary files /dev/null and b/screenshot/linux-en.png differ diff --git a/screenshot/linux-zh.png b/screenshot/linux-zh.png new file mode 100644 index 000000000..c2769ab46 Binary files /dev/null and b/screenshot/linux-zh.png differ diff --git a/screenshot/mac-en.png b/screenshot/mac-en.png new file mode 100644 index 000000000..3793967a9 Binary files /dev/null and b/screenshot/mac-en.png differ diff --git a/screenshot/mac-zh.png b/screenshot/mac-zh.png new file mode 100644 index 000000000..c7b0068a6 Binary files /dev/null and b/screenshot/mac-zh.png differ diff --git a/screenshot/mac.jpg b/screenshot/mac.jpg deleted file mode 100644 index f8f096f90..000000000 Binary files a/screenshot/mac.jpg and /dev/null differ diff --git a/screenshot/run.png b/screenshot/run.png deleted file mode 100644 index 93460b517..000000000 Binary files a/screenshot/run.png and /dev/null differ diff --git a/screenshot/ubuntu.png b/screenshot/ubuntu.png deleted file mode 100644 index 5ed1ffcee..000000000 Binary files a/screenshot/ubuntu.png and /dev/null differ diff --git a/screenshot/win-en.png b/screenshot/win-en.png new file mode 100644 index 000000000..7a5246e33 Binary files /dev/null and b/screenshot/win-en.png differ diff --git a/screenshot/win-zh.png b/screenshot/win-zh.png new file mode 100644 index 000000000..29f3e4889 Binary files /dev/null and b/screenshot/win-zh.png differ diff --git a/screenshot/win.png b/screenshot/win.png deleted file mode 100644 index e2399fb1f..000000000 Binary files a/screenshot/win.png and /dev/null differ