diff --git a/app/app.pro b/app/app.pro index 0d4c4bc7..ba94f3c8 100644 --- a/app/app.pro +++ b/app/app.pro @@ -132,6 +132,7 @@ macx { SOURCES += \ backend/nvaddress.cpp \ backend/nvapp.cpp \ + cli/pair.cpp \ main.cpp \ backend/computerseeker.cpp \ backend/identitymanager.cpp \ @@ -170,6 +171,7 @@ SOURCES += \ HEADERS += \ backend/nvaddress.h \ backend/nvapp.h \ + cli/pair.h \ settings/compatfetcher.h \ settings/mappingfetcher.h \ utils.h \ diff --git a/app/cli/commandlineparser.cpp b/app/cli/commandlineparser.cpp index ac47c7ca..977a163d 100644 --- a/app/cli/commandlineparser.cpp +++ b/app/cli/commandlineparser.cpp @@ -166,6 +166,7 @@ GlobalCommandLineParser::ParseResult GlobalCommandLineParser::parse(const QStrin "Available actions:\n" " quit Quit the currently running app\n" " stream Start streaming an app\n" + " pair Pair a new host\n" "\n" "See 'moonlight --help' for help of specific action." ); @@ -193,6 +194,8 @@ GlobalCommandLineParser::ParseResult GlobalCommandLineParser::parse(const QStrin return QuitRequested; } else if (action == "stream") { return StreamRequested; + } else if (action == "pair") { + return PairRequested; } } @@ -242,6 +245,58 @@ QString QuitCommandLineParser::getHost() const return m_Host; } +PairCommandLineParser::PairCommandLineParser() +{ +} + +PairCommandLineParser::~PairCommandLineParser() +{ +} + +void PairCommandLineParser::parse(const QStringList &args) +{ + CommandLineParser parser; + parser.setupCommonOptions(); + parser.setApplicationDescription( + "\n" + "Pair with the specified host." + ); + parser.addPositionalArgument("pair", "pair host"); + parser.addPositionalArgument("host", "Host computer name, UUID, or IP address", ""); + parser.addValueOption("pin", "4 digit pairing PIN"); + + if (!parser.parse(args)) { + parser.showError(parser.errorText()); + } + + parser.handleUnknownOptions(); + + // This method will not return and terminates the process if --version or + // --help is specified + parser.handleHelpAndVersionOptions(); + + // Verify that host has been provided + auto posArgs = parser.positionalArguments(); + if (posArgs.length() < 2) { + parser.showError("Host not provided"); + } + m_Host = parser.positionalArguments().at(1); + m_PredefinedPin = parser.value("pin"); + if (!m_PredefinedPin.isEmpty() && m_PredefinedPin.length() != 4) { + parser.showError("PIN must be 4 digits"); + } +} + +QString PairCommandLineParser::getHost() const +{ + return m_Host; +} + +QString PairCommandLineParser::getPredefinedPin() const +{ + return m_PredefinedPin; +} + StreamCommandLineParser::StreamCommandLineParser() { m_WindowModeMap = { diff --git a/app/cli/commandlineparser.h b/app/cli/commandlineparser.h index 5844204b..cb1d19ee 100644 --- a/app/cli/commandlineparser.h +++ b/app/cli/commandlineparser.h @@ -12,6 +12,7 @@ public: NormalStartRequested, StreamRequested, QuitRequested, + PairRequested, }; GlobalCommandLineParser(); @@ -35,6 +36,22 @@ private: QString m_Host; }; +class PairCommandLineParser +{ +public: + PairCommandLineParser(); + virtual ~PairCommandLineParser(); + + void parse(const QStringList &args); + + QString getHost() const; + QString getPredefinedPin() const; + +private: + QString m_Host; + QString m_PredefinedPin; +}; + class StreamCommandLineParser { public: diff --git a/app/gui/CliPair.qml b/app/gui/CliPair.qml new file mode 100644 index 00000000..083c4dbd --- /dev/null +++ b/app/gui/CliPair.qml @@ -0,0 +1,91 @@ +import QtQuick 2.0 +import QtQuick.Controls 2.2 + +import ComputerManager 1.0 +import SdlGamepadKeyNavigation 1.0 + +Item { + function onSearchingComputer() { + stageLabel.text = qsTr("Establishing connection to PC...") + } + + function onPairing(pcName, pin) { + stageLabel.text = qsTr("Pairing... Please enter '%1' on %2.").arg(pin).arg(pcName) + } + + function onFailed(message) { + stageIndicator.visible = false + errorDialog.text = message + errorDialog.open() + } + + function onSuccess(appName) { + stageIndicator.visible = false + pairCompleteDialog.open() + } + + // Allow user to back out of pairing + Keys.onEscapePressed: { + Qt.quit() + } + Keys.onBackPressed: { + Qt.quit() + } + Keys.onCancelPressed: { + Qt.quit() + } + + StackView.onActivated: { + if (!launcher.isExecuted()) { + toolBar.visible = false + + // Normally this is enabled by PcView, but we will won't + // load PcView when streaming from the command-line. + SdlGamepadKeyNavigation.enable() + + launcher.searchingComputer.connect(onSearchingComputer) + launcher.pairing.connect(onPairing) + launcher.failed.connect(onFailed) + launcher.success.connect(onSuccess) + launcher.execute(ComputerManager) + } + } + + Row { + anchors.centerIn: parent + spacing: 5 + id: stageIndicator + + BusyIndicator { + id: stageSpinner + } + + Label { + id: stageLabel + height: stageSpinner.height + font.pointSize: 20 + verticalAlignment: Text.AlignVCenter + + wrapMode: Text.Wrap + } + } + + ErrorMessageDialog { + id: errorDialog + + onClosed: { + Qt.quit(); + } + } + + NavigableMessageDialog { + id: pairCompleteDialog + closePolicy: Popup.CloseOnEscape + + text:qsTr("Pairing completed successfully") + standardButtons: Dialog.Ok + onClosed: { + Qt.quit() + } + } +} diff --git a/app/main.cpp b/app/main.cpp index c749a587..dd778389 100644 --- a/app/main.cpp +++ b/app/main.cpp @@ -30,6 +30,7 @@ #include "cli/quitstream.h" #include "cli/startstream.h" +#include "cli/pair.h" #include "cli/commandlineparser.h" #include "path.h" #include "utils.h" @@ -610,6 +611,15 @@ int main(int argc, char *argv[]) engine.rootContext()->setContextProperty("launcher", launcher); break; } + case GlobalCommandLineParser::PairRequested: + { + initialView = "qrc:/gui/CliPair.qml"; + PairCommandLineParser pairParser; + pairParser.parse(app.arguments()); + auto launcher = new CliPair::Launcher(pairParser.getHost(), pairParser.getPredefinedPin(), &app); + engine.rootContext()->setContextProperty("launcher", launcher); + break; + } } engine.rootContext()->setContextProperty("initialView", initialView); diff --git a/app/qml.qrc b/app/qml.qrc index ba1b26b8..5c2c369f 100644 --- a/app/qml.qrc +++ b/app/qml.qrc @@ -18,5 +18,6 @@ gui/NavigableMessageDialog.qml gui/NavigableDialog.qml gui/CenteredGridView.qml + gui/CliPair.qml