mirror of
https://github.com/moonlight-stream/moonlight-qt.git
synced 2026-06-17 22:23:31 +00:00
Add a 'list' option for the CLI
This commit addresses Issue #448 by adding a command line option that allows the listing of all the Apps reported by the remote host as a CSV.
This commit is contained in:
committed by
Cameron Gutman
parent
aca82f400a
commit
de88176995
@@ -143,6 +143,7 @@ SOURCES += \
|
|||||||
backend/boxartmanager.cpp \
|
backend/boxartmanager.cpp \
|
||||||
backend/richpresencemanager.cpp \
|
backend/richpresencemanager.cpp \
|
||||||
cli/commandlineparser.cpp \
|
cli/commandlineparser.cpp \
|
||||||
|
cli/listapps.cpp \
|
||||||
cli/quitstream.cpp \
|
cli/quitstream.cpp \
|
||||||
cli/startstream.cpp \
|
cli/startstream.cpp \
|
||||||
settings/compatfetcher.cpp \
|
settings/compatfetcher.cpp \
|
||||||
@@ -184,6 +185,7 @@ HEADERS += \
|
|||||||
backend/boxartmanager.h \
|
backend/boxartmanager.h \
|
||||||
backend/richpresencemanager.h \
|
backend/richpresencemanager.h \
|
||||||
cli/commandlineparser.h \
|
cli/commandlineparser.h \
|
||||||
|
cli/listapps.h \
|
||||||
cli/quitstream.h \
|
cli/quitstream.h \
|
||||||
cli/startstream.h \
|
cli/startstream.h \
|
||||||
settings/streamingpreferences.h \
|
settings/streamingpreferences.h \
|
||||||
|
|||||||
@@ -164,6 +164,7 @@ GlobalCommandLineParser::ParseResult GlobalCommandLineParser::parse(const QStrin
|
|||||||
"Starts Moonlight normally if no arguments are given.\n"
|
"Starts Moonlight normally if no arguments are given.\n"
|
||||||
"\n"
|
"\n"
|
||||||
"Available actions:\n"
|
"Available actions:\n"
|
||||||
|
" list List the available apps as CSV\n"
|
||||||
" quit Quit the currently running app\n"
|
" quit Quit the currently running app\n"
|
||||||
" stream Start streaming an app\n"
|
" stream Start streaming an app\n"
|
||||||
" pair Pair a new host\n"
|
" pair Pair a new host\n"
|
||||||
@@ -196,6 +197,8 @@ GlobalCommandLineParser::ParseResult GlobalCommandLineParser::parse(const QStrin
|
|||||||
return StreamRequested;
|
return StreamRequested;
|
||||||
} else if (action == "pair") {
|
} else if (action == "pair") {
|
||||||
return PairRequested;
|
return PairRequested;
|
||||||
|
} else if (action == "list") {
|
||||||
|
return ListRequested;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -523,3 +526,46 @@ QString StreamCommandLineParser::getAppName() const
|
|||||||
{
|
{
|
||||||
return m_AppName;
|
return m_AppName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ListCommandLineParser::ListCommandLineParser()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
ListCommandLineParser::~ListCommandLineParser()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void ListCommandLineParser::parse(const QStringList &args)
|
||||||
|
{
|
||||||
|
CommandLineParser parser;
|
||||||
|
parser.setupCommonOptions();
|
||||||
|
parser.setApplicationDescription(
|
||||||
|
"\n"
|
||||||
|
"List the available apps on the given host as CSV:\n"
|
||||||
|
"\tName, ID, HDR Support, App Collection Game, Hidden, Direct Launch, Path to Boxart"
|
||||||
|
);
|
||||||
|
parser.addPositionalArgument("list", "list available apps");
|
||||||
|
parser.addPositionalArgument("host", "Host computer name, UUID, or IP address", "<host>");
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
QString ListCommandLineParser::getHost() const
|
||||||
|
{
|
||||||
|
return m_Host;
|
||||||
|
}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ public:
|
|||||||
StreamRequested,
|
StreamRequested,
|
||||||
QuitRequested,
|
QuitRequested,
|
||||||
PairRequested,
|
PairRequested,
|
||||||
|
ListRequested,
|
||||||
};
|
};
|
||||||
|
|
||||||
GlobalCommandLineParser();
|
GlobalCommandLineParser();
|
||||||
@@ -72,3 +73,17 @@ private:
|
|||||||
QMap<QString, StreamingPreferences::VideoDecoderSelection> m_VideoDecoderMap;
|
QMap<QString, StreamingPreferences::VideoDecoderSelection> m_VideoDecoderMap;
|
||||||
QMap<QString, StreamingPreferences::CaptureSysKeysMode> m_CaptureSysKeysModeMap;
|
QMap<QString, StreamingPreferences::CaptureSysKeysMode> m_CaptureSysKeysModeMap;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class ListCommandLineParser
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
ListCommandLineParser();
|
||||||
|
virtual ~ListCommandLineParser();
|
||||||
|
|
||||||
|
void parse(const QStringList &args);
|
||||||
|
|
||||||
|
QString getHost() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
QString m_Host;
|
||||||
|
};
|
||||||
|
|||||||
@@ -0,0 +1,190 @@
|
|||||||
|
#include "listapps.h"
|
||||||
|
|
||||||
|
#include "backend/boxartmanager.h"
|
||||||
|
#include "backend/computermanager.h"
|
||||||
|
#include "backend/computerseeker.h"
|
||||||
|
#include "streaming/session.h"
|
||||||
|
|
||||||
|
#include <QCoreApplication>
|
||||||
|
#include <QTimer>
|
||||||
|
|
||||||
|
#define COMPUTER_SEEK_TIMEOUT 10000
|
||||||
|
#define APP_SEEK_TIMEOUT 10000
|
||||||
|
|
||||||
|
namespace CliListApps
|
||||||
|
{
|
||||||
|
|
||||||
|
enum State {
|
||||||
|
StateInit,
|
||||||
|
StateSeekComputer,
|
||||||
|
StateListApp,
|
||||||
|
StateSeekApp,
|
||||||
|
StateFailure,
|
||||||
|
};
|
||||||
|
|
||||||
|
class Event
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
enum Type {
|
||||||
|
ComputerFound,
|
||||||
|
ComputerUpdated,
|
||||||
|
ComputerSeekTimedout,
|
||||||
|
Executed,
|
||||||
|
};
|
||||||
|
|
||||||
|
Event(Type type)
|
||||||
|
: type(type), computerManager(nullptr), computer(nullptr) {}
|
||||||
|
|
||||||
|
Type type;
|
||||||
|
ComputerManager *computerManager;
|
||||||
|
NvComputer *computer;
|
||||||
|
QString errorMessage;
|
||||||
|
};
|
||||||
|
|
||||||
|
class LauncherPrivate
|
||||||
|
{
|
||||||
|
Q_DECLARE_PUBLIC(Launcher)
|
||||||
|
|
||||||
|
public:
|
||||||
|
LauncherPrivate(Launcher *q) : q_ptr(q) {}
|
||||||
|
|
||||||
|
void handleEvent(Event event)
|
||||||
|
{
|
||||||
|
Q_Q(Launcher);
|
||||||
|
NvApp app;
|
||||||
|
|
||||||
|
switch (event.type) {
|
||||||
|
// Occurs when CliListAppsSegue becomes visible and the UI calls launcher's execute()
|
||||||
|
case Event::Executed:
|
||||||
|
if (m_State == StateInit) {
|
||||||
|
m_State = StateSeekComputer;
|
||||||
|
m_ComputerManager = event.computerManager;
|
||||||
|
|
||||||
|
m_ComputerSeeker = new ComputerSeeker(m_ComputerManager, m_ComputerName, q);
|
||||||
|
q->connect(m_ComputerSeeker, &ComputerSeeker::computerFound,
|
||||||
|
q, &Launcher::onComputerFound);
|
||||||
|
q->connect(m_ComputerSeeker, &ComputerSeeker::errorTimeout,
|
||||||
|
q, &Launcher::onComputerSeekTimeout);
|
||||||
|
m_ComputerSeeker->start(COMPUTER_SEEK_TIMEOUT);
|
||||||
|
|
||||||
|
q->connect(m_ComputerManager, &ComputerManager::computerStateChanged,
|
||||||
|
q, &Launcher::onComputerUpdated);
|
||||||
|
|
||||||
|
m_BoxArtManager = new BoxArtManager(q);
|
||||||
|
|
||||||
|
emit q->searchingComputer();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
// Occurs when computer search timed out
|
||||||
|
case Event::ComputerSeekTimedout:
|
||||||
|
if (m_State == StateSeekComputer) {
|
||||||
|
m_State = StateFailure;
|
||||||
|
emit q->failed(QString("Failed to connect to %1").arg(m_ComputerName));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
// Occurs when searched computer is found
|
||||||
|
case Event::ComputerFound:
|
||||||
|
if (m_State == StateSeekComputer) {
|
||||||
|
if (event.computer->pairState == NvComputer::PS_PAIRED) {
|
||||||
|
m_State = StateSeekApp;
|
||||||
|
m_Computer = event.computer;
|
||||||
|
m_TimeoutTimer->start(APP_SEEK_TIMEOUT);
|
||||||
|
emit q->searchingApps();
|
||||||
|
} else {
|
||||||
|
m_State = StateFailure;
|
||||||
|
QString msg = QObject::tr("Computer %1 has not been paired. "
|
||||||
|
"Please open Moonlight to pair before retrieving games list.")
|
||||||
|
.arg(event.computer->name);
|
||||||
|
emit q->failed(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
// Occurs when a computer is updated
|
||||||
|
case Event::ComputerUpdated:
|
||||||
|
if (m_State == StateSeekApp) {
|
||||||
|
for (int i = 0; i < m_Computer->appList.length(); i++) {
|
||||||
|
printApp(m_Computer->appList[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
QCoreApplication::exit(0);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void printApp(NvApp app) const
|
||||||
|
{
|
||||||
|
fprintf(stdout, "%s,%d,%s,%s,%s,%s,%s\n", qPrintable(app.name),
|
||||||
|
app.id,
|
||||||
|
app.hdrSupported ? "true" : "false",
|
||||||
|
app.isAppCollectorGame ? "true" : "false",
|
||||||
|
app.hidden ? "true" : "false",
|
||||||
|
app.directLaunch ? "true" : "false",
|
||||||
|
qPrintable(m_BoxArtManager->loadBoxArt(m_Computer, app).toDisplayString()));
|
||||||
|
}
|
||||||
|
|
||||||
|
Launcher *q_ptr;
|
||||||
|
ComputerManager *m_ComputerManager;
|
||||||
|
QString m_ComputerName;
|
||||||
|
ComputerSeeker *m_ComputerSeeker;
|
||||||
|
BoxArtManager *m_BoxArtManager;
|
||||||
|
NvComputer *m_Computer;
|
||||||
|
State m_State;
|
||||||
|
QTimer *m_TimeoutTimer;
|
||||||
|
};
|
||||||
|
|
||||||
|
Launcher::Launcher(QString computer, QObject *parent)
|
||||||
|
: QObject(parent),
|
||||||
|
m_DPtr(new LauncherPrivate(this))
|
||||||
|
{
|
||||||
|
Q_D(Launcher);
|
||||||
|
d->m_ComputerName = computer;
|
||||||
|
d->m_State = StateInit;
|
||||||
|
d->m_TimeoutTimer = new QTimer(this);
|
||||||
|
d->m_TimeoutTimer->setSingleShot(true);
|
||||||
|
connect(d->m_TimeoutTimer, &QTimer::timeout,
|
||||||
|
this, &Launcher::onComputerSeekTimeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
Launcher::~Launcher()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void Launcher::execute(ComputerManager *manager)
|
||||||
|
{
|
||||||
|
Q_D(Launcher);
|
||||||
|
Event event(Event::Executed);
|
||||||
|
event.computerManager = manager;
|
||||||
|
d->handleEvent(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Launcher::isExecuted() const
|
||||||
|
{
|
||||||
|
Q_D(const Launcher);
|
||||||
|
return d->m_State != StateInit;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Launcher::onComputerFound(NvComputer *computer)
|
||||||
|
{
|
||||||
|
Q_D(Launcher);
|
||||||
|
Event event(Event::ComputerFound);
|
||||||
|
event.computer = computer;
|
||||||
|
d->handleEvent(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Launcher::onComputerSeekTimeout()
|
||||||
|
{
|
||||||
|
Q_D(Launcher);
|
||||||
|
Event event(Event::ComputerSeekTimedout);
|
||||||
|
d->handleEvent(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Launcher::onComputerUpdated(NvComputer *computer)
|
||||||
|
{
|
||||||
|
Q_D(Launcher);
|
||||||
|
Event event(Event::ComputerUpdated);
|
||||||
|
event.computer = computer;
|
||||||
|
d->handleEvent(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
#include <QVariant>
|
||||||
|
|
||||||
|
class ComputerManager;
|
||||||
|
class NvComputer;
|
||||||
|
|
||||||
|
namespace CliListApps
|
||||||
|
{
|
||||||
|
|
||||||
|
class LauncherPrivate;
|
||||||
|
|
||||||
|
class Launcher : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
Q_DECLARE_PRIVATE_D(m_DPtr, Launcher)
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit Launcher(QString computer, QObject *parent = nullptr);
|
||||||
|
~Launcher();
|
||||||
|
|
||||||
|
Q_INVOKABLE void execute(ComputerManager *manager);
|
||||||
|
Q_INVOKABLE bool isExecuted() const;
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void searchingComputer();
|
||||||
|
void searchingApps();
|
||||||
|
void failed(QString text);
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void onComputerFound(NvComputer *computer);
|
||||||
|
void onComputerUpdated(NvComputer *computer);
|
||||||
|
void onComputerSeekTimeout();
|
||||||
|
|
||||||
|
private:
|
||||||
|
QScopedPointer<LauncherPrivate> m_DPtr;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
import QtQuick 2.0
|
||||||
|
import QtQuick.Controls 2.2
|
||||||
|
|
||||||
|
import ComputerManager 1.0
|
||||||
|
import Session 1.0
|
||||||
|
|
||||||
|
Item {
|
||||||
|
function onSearchingComputer() {
|
||||||
|
stageLabel.text = qsTr("Establishing connection to PC...")
|
||||||
|
}
|
||||||
|
|
||||||
|
function onSearchingApps() {
|
||||||
|
stageLabel.text = qsTr("Searching for Apps...")
|
||||||
|
}
|
||||||
|
|
||||||
|
function onFailure(message) {
|
||||||
|
errorDialog.text = message
|
||||||
|
errorDialog.open()
|
||||||
|
}
|
||||||
|
|
||||||
|
StackView.onActivated: {
|
||||||
|
if (!launcher.isExecuted()) {
|
||||||
|
toolBar.visible = false
|
||||||
|
launcher.searchingComputer.connect(onSearchingComputer)
|
||||||
|
launcher.searchingComputer.connect(onSearchingApps)
|
||||||
|
launcher.failed.connect(onFailure)
|
||||||
|
launcher.execute(ComputerManager)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Row {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
spacing: 5
|
||||||
|
|
||||||
|
BusyIndicator {
|
||||||
|
id: stageSpinner
|
||||||
|
}
|
||||||
|
|
||||||
|
Label {
|
||||||
|
id: stageLabel
|
||||||
|
height: stageSpinner.height
|
||||||
|
text: stageText
|
||||||
|
font.pointSize: 20
|
||||||
|
verticalAlignment: Text.AlignVCenter
|
||||||
|
|
||||||
|
wrapMode: Text.Wrap
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ErrorMessageDialog {
|
||||||
|
id: errorDialog
|
||||||
|
|
||||||
|
onClosed: {
|
||||||
|
Qt.quit()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -28,6 +28,7 @@
|
|||||||
#include <openssl/ssl.h>
|
#include <openssl/ssl.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#include "cli/listapps.h"
|
||||||
#include "cli/quitstream.h"
|
#include "cli/quitstream.h"
|
||||||
#include "cli/startstream.h"
|
#include "cli/startstream.h"
|
||||||
#include "cli/pair.h"
|
#include "cli/pair.h"
|
||||||
@@ -620,6 +621,15 @@ int main(int argc, char *argv[])
|
|||||||
engine.rootContext()->setContextProperty("launcher", launcher);
|
engine.rootContext()->setContextProperty("launcher", launcher);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case GlobalCommandLineParser::ListRequested:
|
||||||
|
{
|
||||||
|
initialView = "qrc:/gui/CliListAppsSegue.qml";
|
||||||
|
ListCommandLineParser listParser;
|
||||||
|
listParser.parse(app.arguments());
|
||||||
|
auto launcher = new CliListApps::Launcher(listParser.getHost(), &app);
|
||||||
|
engine.rootContext()->setContextProperty("launcher", launcher);
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
engine.rootContext()->setContextProperty("initialView", initialView);
|
engine.rootContext()->setContextProperty("initialView", initialView);
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
<file>gui/NavigableToolButton.qml</file>
|
<file>gui/NavigableToolButton.qml</file>
|
||||||
<file>gui/NavigableItemDelegate.qml</file>
|
<file>gui/NavigableItemDelegate.qml</file>
|
||||||
<file>gui/NavigableMenuItem.qml</file>
|
<file>gui/NavigableMenuItem.qml</file>
|
||||||
|
<file>gui/CliListAppsSegue.qml</file>
|
||||||
<file>gui/CliQuitStreamSegue.qml</file>
|
<file>gui/CliQuitStreamSegue.qml</file>
|
||||||
<file>gui/CliStartStreamSegue.qml</file>
|
<file>gui/CliStartStreamSegue.qml</file>
|
||||||
<file>gui/AutoResizingComboBox.qml</file>
|
<file>gui/AutoResizingComboBox.qml</file>
|
||||||
|
|||||||
Reference in New Issue
Block a user