mirror of
https://github.com/moonlight-stream/moonlight-qt.git
synced 2026-10-28 06:50:06 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d696536486 | ||
|
|
b46a3cf759 | ||
|
|
8a5e3cdb76 | ||
|
|
4fb39226ef | ||
|
|
c40174034c | ||
|
|
5029c855f0 | ||
|
|
2eee839eaa | ||
|
|
f42c7fec1e | ||
|
|
0ab07303c9 | ||
|
|
ad47990a87 | ||
|
|
1968461ce9 | ||
|
|
8aa3b5d3da | ||
|
|
23e77eefb0 | ||
|
|
094443cf26 | ||
|
|
b97758ef46 | ||
|
|
d1fe752028 | ||
|
|
b26df787cd | ||
|
|
4c5b254730 | ||
|
|
9a153b7170 | ||
|
|
065a6caee5 |
No files matched your search
+1
-1
@@ -9,7 +9,7 @@
|
|||||||
url = https://github.com/gabomdq/SDL_GameControllerDB.git
|
url = https://github.com/gabomdq/SDL_GameControllerDB.git
|
||||||
[submodule "soundio/libsoundio"]
|
[submodule "soundio/libsoundio"]
|
||||||
path = soundio/libsoundio
|
path = soundio/libsoundio
|
||||||
url = https://github.com/cgutman/libsoundio.git
|
url = https://github.com/andrewrk/libsoundio.git
|
||||||
[submodule "h264bitstream/h264bitstream"]
|
[submodule "h264bitstream/h264bitstream"]
|
||||||
path = h264bitstream/h264bitstream
|
path = h264bitstream/h264bitstream
|
||||||
url = https://github.com/aizvorski/h264bitstream.git
|
url = https://github.com/aizvorski/h264bitstream.git
|
||||||
@@ -1,11 +1,9 @@
|
|||||||
# Moonlight PC
|
# Moonlight PC
|
||||||
|
|
||||||
[Moonlight PC](http://moonlight-stream.com) is an open source implementation of NVIDIA's GameStream, as used by the NVIDIA Shield, but built to run on Windows, Mac, and Linux. This client is the successor to [Moonlight Chrome](https://github.com/moonlight-stream/moonlight-chrome) for streaming on PC.
|
[Moonlight PC](https://moonlight-stream.org) is an open source implementation of NVIDIA's GameStream, as used by the NVIDIA Shield, but built to run on Windows, Mac, and Linux. This client is the successor to [Moonlight Chrome](https://github.com/moonlight-stream/moonlight-chrome) for streaming on PC.
|
||||||
|
|
||||||
Moonlight also has mobile versions for [Android](https://github.com/moonlight-stream/moonlight-android) and [iOS](https://github.com/moonlight-stream/moonlight-ios).
|
Moonlight also has mobile versions for [Android](https://github.com/moonlight-stream/moonlight-android) and [iOS](https://github.com/moonlight-stream/moonlight-ios).
|
||||||
|
|
||||||
See the [releases page](https://github.com/moonlight-stream/moonlight-qt/releases) for download links for Windows and Mac. For Linux, we're on [Flathub](https://flathub.org/apps/details/com.moonlight_stream.Moonlight).
|
|
||||||
|
|
||||||
You can follow development on our [Discord server](https://discord.gg/6ERtzFY).
|
You can follow development on our [Discord server](https://discord.gg/6ERtzFY).
|
||||||
|
|
||||||
[](https://ci.appveyor.com/project/cgutman/moonlight-qt/branch/master)
|
[](https://ci.appveyor.com/project/cgutman/moonlight-qt/branch/master)
|
||||||
@@ -20,6 +18,10 @@ You can follow development on our [Discord server](https://discord.gg/6ERtzFY).
|
|||||||
- Keyboard and mouse support
|
- Keyboard and mouse support
|
||||||
- Gamepad support with SDL gamepad mappings
|
- Gamepad support with SDL gamepad mappings
|
||||||
|
|
||||||
|
## Downloads
|
||||||
|
- [Windows and macOS](https://github.com/moonlight-stream/moonlight-qt/releases)
|
||||||
|
- [Linux](https://flathub.org/apps/details/com.moonlight_stream.Moonlight)
|
||||||
|
|
||||||
## Building
|
## Building
|
||||||
### General Build Requirements
|
### General Build Requirements
|
||||||
* Qt 5.9 SDK or later
|
* Qt 5.9 SDK or later
|
||||||
@@ -55,4 +57,4 @@ You can follow development on our [Discord server](https://discord.gg/6ERtzFY).
|
|||||||
2. Write code
|
2. Write code
|
||||||
3. Send Pull Requests
|
3. Send Pull Requests
|
||||||
|
|
||||||
Check out our [website](http://moonlight-stream.com) for project links and information.
|
Check out our [website](https://moonlight-stream.org) for project links and information.
|
||||||
+2
-2
@@ -23,9 +23,9 @@
|
|||||||
<key>NSSupportsAutomaticGraphicsSwitching</key>
|
<key>NSSupportsAutomaticGraphicsSwitching</key>
|
||||||
<true/>
|
<true/>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>0.6.4</string>
|
<string>0.7.0</string>
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
<string>0.6.4</string>
|
<string>0.7.0</string>
|
||||||
<key>CFBundleDisplayName</key>
|
<key>CFBundleDisplayName</key>
|
||||||
<string>Moonlight</string>
|
<string>Moonlight</string>
|
||||||
</dict>
|
</dict>
|
||||||
|
|||||||
+1
-1
Submodule app/SDL_GameControllerDB updated: 19944691bf...04ebce52ee.
+7
-3
@@ -54,7 +54,7 @@ macx {
|
|||||||
}
|
}
|
||||||
|
|
||||||
unix:!macx {
|
unix:!macx {
|
||||||
CONFIG += link_pkgconfig
|
CONFIG += link_pkgconfig soundio
|
||||||
PKGCONFIG += openssl sdl2 opus
|
PKGCONFIG += openssl sdl2 opus
|
||||||
|
|
||||||
# For libsoundio
|
# For libsoundio
|
||||||
@@ -100,6 +100,7 @@ macx {
|
|||||||
|
|
||||||
SOURCES += \
|
SOURCES += \
|
||||||
main.cpp \
|
main.cpp \
|
||||||
|
backend/computerseeker.cpp \
|
||||||
backend/identitymanager.cpp \
|
backend/identitymanager.cpp \
|
||||||
backend/nvcomputer.cpp \
|
backend/nvcomputer.cpp \
|
||||||
backend/nvhttp.cpp \
|
backend/nvhttp.cpp \
|
||||||
@@ -107,6 +108,7 @@ SOURCES += \
|
|||||||
backend/computermanager.cpp \
|
backend/computermanager.cpp \
|
||||||
backend/boxartmanager.cpp \
|
backend/boxartmanager.cpp \
|
||||||
cli/commandlineparser.cpp \
|
cli/commandlineparser.cpp \
|
||||||
|
cli/quitstream.cpp \
|
||||||
cli/startstream.cpp \
|
cli/startstream.cpp \
|
||||||
settings/streamingpreferences.cpp \
|
settings/streamingpreferences.cpp \
|
||||||
streaming/input.cpp \
|
streaming/input.cpp \
|
||||||
@@ -123,6 +125,7 @@ SOURCES += \
|
|||||||
|
|
||||||
HEADERS += \
|
HEADERS += \
|
||||||
utils.h \
|
utils.h \
|
||||||
|
backend/computerseeker.h \
|
||||||
backend/identitymanager.h \
|
backend/identitymanager.h \
|
||||||
backend/nvcomputer.h \
|
backend/nvcomputer.h \
|
||||||
backend/nvhttp.h \
|
backend/nvhttp.h \
|
||||||
@@ -130,6 +133,7 @@ HEADERS += \
|
|||||||
backend/computermanager.h \
|
backend/computermanager.h \
|
||||||
backend/boxartmanager.h \
|
backend/boxartmanager.h \
|
||||||
cli/commandlineparser.h \
|
cli/commandlineparser.h \
|
||||||
|
cli/quitstream.h \
|
||||||
cli/startstream.h \
|
cli/startstream.h \
|
||||||
settings/streamingpreferences.h \
|
settings/streamingpreferences.h \
|
||||||
streaming/input.h \
|
streaming/input.h \
|
||||||
@@ -323,5 +327,5 @@ macx {
|
|||||||
QMAKE_RPATHDIR += @executable_path/../Frameworks
|
QMAKE_RPATHDIR += @executable_path/../Frameworks
|
||||||
}
|
}
|
||||||
|
|
||||||
VERSION = 0.6.4
|
VERSION = 0.7.0
|
||||||
DEFINES += VERSION_STR=\\\"0.6.4\\\"
|
DEFINES += VERSION_STR=\\\"0.7.0\\\"
|
||||||
@@ -12,6 +12,9 @@ AutoUpdateChecker::AutoUpdateChecker(QObject *parent) :
|
|||||||
// Never communicate over HTTP
|
// Never communicate over HTTP
|
||||||
m_Nam.setStrictTransportSecurityEnabled(true);
|
m_Nam.setStrictTransportSecurityEnabled(true);
|
||||||
|
|
||||||
|
// Allow HTTP redirects
|
||||||
|
m_Nam.setRedirectPolicy(QNetworkRequest::NoLessSafeRedirectPolicy);
|
||||||
|
|
||||||
connect(&m_Nam, SIGNAL(finished(QNetworkReply*)),
|
connect(&m_Nam, SIGNAL(finished(QNetworkReply*)),
|
||||||
this, SLOT(handleUpdateCheckRequestFinished(QNetworkReply*)));
|
this, SLOT(handleUpdateCheckRequestFinished(QNetworkReply*)));
|
||||||
|
|
||||||
@@ -27,7 +30,7 @@ void AutoUpdateChecker::start()
|
|||||||
{
|
{
|
||||||
#if defined(Q_OS_WIN32) || defined(Q_OS_DARWIN) // Only run update checker on platforms without auto-update
|
#if defined(Q_OS_WIN32) || defined(Q_OS_DARWIN) // Only run update checker on platforms without auto-update
|
||||||
// We'll get a callback when this is finished
|
// We'll get a callback when this is finished
|
||||||
QUrl url("https://moonlight-stream.com/updates/qt.json");
|
QUrl url("https://moonlight-stream.org/updates/qt.json");
|
||||||
m_Nam.get(QNetworkRequest(url));
|
m_Nam.get(QNetworkRequest(url));
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,59 @@
|
|||||||
|
#include "computerseeker.h"
|
||||||
|
#include "computermanager.h"
|
||||||
|
#include <QTimer>
|
||||||
|
|
||||||
|
ComputerSeeker::ComputerSeeker(ComputerManager *manager, QString computerName, QObject *parent)
|
||||||
|
: QObject(parent), m_ComputerManager(manager), m_ComputerName(computerName),
|
||||||
|
m_TimeoutTimer(new QTimer(this))
|
||||||
|
{
|
||||||
|
m_TimeoutTimer->setSingleShot(true);
|
||||||
|
connect(m_TimeoutTimer, &QTimer::timeout,
|
||||||
|
this, &ComputerSeeker::onTimeout);
|
||||||
|
connect(m_ComputerManager, &ComputerManager::computerStateChanged,
|
||||||
|
this, &ComputerSeeker::onComputerUpdated);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ComputerSeeker::start(int timeout)
|
||||||
|
{
|
||||||
|
m_TimeoutTimer->start(timeout);
|
||||||
|
// Seek desired computer by both connecting to it directly (this may fail
|
||||||
|
// if m_ComputerName is UUID, or the name that doesn't resolve to an IP
|
||||||
|
// address) and by polling it using mDNS, hopefully one of these methods
|
||||||
|
// would find the host
|
||||||
|
m_ComputerManager->addNewHost(m_ComputerName, false);
|
||||||
|
m_ComputerManager->startPolling();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ComputerSeeker::onComputerUpdated(NvComputer *computer)
|
||||||
|
{
|
||||||
|
if (!m_TimeoutTimer->isActive()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (matchComputer(computer) && isOnline(computer)) {
|
||||||
|
m_ComputerManager->stopPollingAsync();
|
||||||
|
m_TimeoutTimer->stop();
|
||||||
|
emit computerFound(computer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ComputerSeeker::matchComputer(NvComputer *computer) const
|
||||||
|
{
|
||||||
|
QString value = m_ComputerName.toLower();
|
||||||
|
return computer->name.toLower() == value ||
|
||||||
|
computer->localAddress.toLower() == value ||
|
||||||
|
computer->remoteAddress.toLower() == value ||
|
||||||
|
computer->manualAddress.toLower() == value ||
|
||||||
|
computer->uuid.toLower() == value;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ComputerSeeker::isOnline(NvComputer *computer) const
|
||||||
|
{
|
||||||
|
return computer->state == NvComputer::CS_ONLINE;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ComputerSeeker::onTimeout()
|
||||||
|
{
|
||||||
|
m_TimeoutTimer->stop();
|
||||||
|
m_ComputerManager->stopPollingAsync();
|
||||||
|
emit errorTimeout();
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
|
||||||
|
class ComputerManager;
|
||||||
|
class NvComputer;
|
||||||
|
class QTimer;
|
||||||
|
|
||||||
|
class ComputerSeeker : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
explicit ComputerSeeker(ComputerManager *manager, QString computerName, QObject *parent = nullptr);
|
||||||
|
|
||||||
|
void start(int timeout);
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void computerFound(NvComputer *computer);
|
||||||
|
void errorTimeout();
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void onComputerUpdated(NvComputer *computer);
|
||||||
|
void onTimeout();
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool matchComputer(NvComputer *computer) const;
|
||||||
|
bool isOnline(NvComputer *computer) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
ComputerManager *m_ComputerManager;
|
||||||
|
QString m_ComputerName;
|
||||||
|
QTimer *m_TimeoutTimer;
|
||||||
|
};
|
||||||
@@ -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"
|
||||||
|
" quit Quit the currently running app\n"
|
||||||
" stream Start streaming an app\n"
|
" stream Start streaming an app\n"
|
||||||
"\n"
|
"\n"
|
||||||
"See 'moonlight <action> --help' for help of specific action."
|
"See 'moonlight <action> --help' for help of specific action."
|
||||||
@@ -179,6 +180,8 @@ GlobalCommandLineParser::ParseResult GlobalCommandLineParser::parse(const QStrin
|
|||||||
parser.handleHelpAndVersionOptions();
|
parser.handleHelpAndVersionOptions();
|
||||||
parser.handleUnknownOptions();
|
parser.handleUnknownOptions();
|
||||||
return NormalStartRequested;
|
return NormalStartRequested;
|
||||||
|
} else if (action == "quit") {
|
||||||
|
return QuitRequested;
|
||||||
} else if (action == "stream") {
|
} else if (action == "stream") {
|
||||||
return StreamRequested;
|
return StreamRequested;
|
||||||
} else {
|
} else {
|
||||||
@@ -186,6 +189,48 @@ GlobalCommandLineParser::ParseResult GlobalCommandLineParser::parse(const QStrin
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QuitCommandLineParser::QuitCommandLineParser()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
QuitCommandLineParser::~QuitCommandLineParser()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void QuitCommandLineParser::parse(const QStringList &args)
|
||||||
|
{
|
||||||
|
CommandLineParser parser;
|
||||||
|
parser.setupCommonOptions();
|
||||||
|
parser.setApplicationDescription(
|
||||||
|
"\n"
|
||||||
|
"Quit the currently running app on the given host."
|
||||||
|
);
|
||||||
|
parser.addPositionalArgument("quit", "quit running app");
|
||||||
|
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 QuitCommandLineParser::getHost() const
|
||||||
|
{
|
||||||
|
return m_Host;
|
||||||
|
}
|
||||||
|
|
||||||
StreamCommandLineParser::StreamCommandLineParser()
|
StreamCommandLineParser::StreamCommandLineParser()
|
||||||
{
|
{
|
||||||
m_WindowModeMap = {
|
m_WindowModeMap = {
|
||||||
@@ -238,6 +283,7 @@ void StreamCommandLineParser::parse(const QStringList &args, StreamingPreference
|
|||||||
parser.addChoiceOption("display-mode", "display mode", m_WindowModeMap.keys());
|
parser.addChoiceOption("display-mode", "display mode", m_WindowModeMap.keys());
|
||||||
parser.addChoiceOption("audio-config", "audio config", m_AudioConfigMap.keys());
|
parser.addChoiceOption("audio-config", "audio config", m_AudioConfigMap.keys());
|
||||||
parser.addToggleOption("multi-controller", "multiple controller support");
|
parser.addToggleOption("multi-controller", "multiple controller support");
|
||||||
|
parser.addToggleOption("quit-after", "quit app after session");
|
||||||
parser.addToggleOption("mouse-acceleration", "mouse acceleration");
|
parser.addToggleOption("mouse-acceleration", "mouse acceleration");
|
||||||
parser.addToggleOption("game-optimization", "game optimizations");
|
parser.addToggleOption("game-optimization", "game optimizations");
|
||||||
parser.addToggleOption("audio-on-host", "audio on host PC");
|
parser.addToggleOption("audio-on-host", "audio on host PC");
|
||||||
@@ -314,6 +360,9 @@ void StreamCommandLineParser::parse(const QStringList &args, StreamingPreference
|
|||||||
// Resolve --multi-controller and --no-multi-controller options
|
// Resolve --multi-controller and --no-multi-controller options
|
||||||
preferences->multiController = parser.getToggleOptionValue("multi-controller", preferences->multiController);
|
preferences->multiController = parser.getToggleOptionValue("multi-controller", preferences->multiController);
|
||||||
|
|
||||||
|
// Resolve --quit-after and --no-quit-after options
|
||||||
|
preferences->quitAppAfter = parser.getToggleOptionValue("quit-after", preferences->quitAppAfter);
|
||||||
|
|
||||||
// Resolve --mouse-acceleration and --no-mouse-acceleration options
|
// Resolve --mouse-acceleration and --no-mouse-acceleration options
|
||||||
preferences->mouseAcceleration = parser.getToggleOptionValue("mouse-acceleration", preferences->mouseAcceleration);
|
preferences->mouseAcceleration = parser.getToggleOptionValue("mouse-acceleration", preferences->mouseAcceleration);
|
||||||
|
|
||||||
@@ -359,4 +408,3 @@ QString StreamCommandLineParser::getAppName() const
|
|||||||
{
|
{
|
||||||
return m_AppName;
|
return m_AppName;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -11,6 +11,7 @@ public:
|
|||||||
enum ParseResult {
|
enum ParseResult {
|
||||||
NormalStartRequested,
|
NormalStartRequested,
|
||||||
StreamRequested,
|
StreamRequested,
|
||||||
|
QuitRequested,
|
||||||
};
|
};
|
||||||
|
|
||||||
GlobalCommandLineParser();
|
GlobalCommandLineParser();
|
||||||
@@ -20,6 +21,20 @@ public:
|
|||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class QuitCommandLineParser
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
QuitCommandLineParser();
|
||||||
|
virtual ~QuitCommandLineParser();
|
||||||
|
|
||||||
|
void parse(const QStringList &args);
|
||||||
|
|
||||||
|
QString getHost() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
QString m_Host;
|
||||||
|
};
|
||||||
|
|
||||||
class StreamCommandLineParser
|
class StreamCommandLineParser
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
|||||||
@@ -0,0 +1,171 @@
|
|||||||
|
#include "quitstream.h"
|
||||||
|
|
||||||
|
#include "backend/computermanager.h"
|
||||||
|
#include "backend/computerseeker.h"
|
||||||
|
#include "streaming/session.h"
|
||||||
|
|
||||||
|
#include <QCoreApplication>
|
||||||
|
#include <QTimer>
|
||||||
|
|
||||||
|
#define COMPUTER_SEEK_TIMEOUT 10000
|
||||||
|
|
||||||
|
namespace CliQuitStream
|
||||||
|
{
|
||||||
|
|
||||||
|
enum State {
|
||||||
|
StateInit,
|
||||||
|
StateSeekComputer,
|
||||||
|
StateQuitApp,
|
||||||
|
StateFailure,
|
||||||
|
};
|
||||||
|
|
||||||
|
class Event
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
enum Type {
|
||||||
|
AppQuitCompleted,
|
||||||
|
ComputerFound,
|
||||||
|
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 CliQuitStreamSegue becomes visible and the UI calls launcher's execute()
|
||||||
|
case Event::Executed:
|
||||||
|
if (m_State == StateInit) {
|
||||||
|
m_State = StateSeekComputer;
|
||||||
|
m_ComputerManager = event.computerManager;
|
||||||
|
q->connect(m_ComputerManager, &ComputerManager::quitAppCompleted,
|
||||||
|
q, &Launcher::onQuitAppCompleted);
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
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 = StateQuitApp;
|
||||||
|
emit q->quittingApp();
|
||||||
|
m_ComputerManager->quitRunningApp(event.computer);
|
||||||
|
} else {
|
||||||
|
m_State = StateFailure;
|
||||||
|
QString msg = QString("Computer %1 has not been paired. "
|
||||||
|
"Please open Moonlight to pair before streaming.")
|
||||||
|
.arg(event.computer->name);
|
||||||
|
emit q->failed(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
// Occurs when app quit completed (error message is set if failed)
|
||||||
|
case Event::AppQuitCompleted:
|
||||||
|
if (m_State == StateQuitApp) {
|
||||||
|
if (event.errorMessage.isEmpty()) {
|
||||||
|
QCoreApplication::exit(0);
|
||||||
|
} else {
|
||||||
|
m_State = StateFailure;
|
||||||
|
emit q->failed(QString("Quitting app failed, reason: %1").arg(event.errorMessage));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Launcher *q_ptr;
|
||||||
|
ComputerManager *m_ComputerManager;
|
||||||
|
QString m_ComputerName;
|
||||||
|
ComputerSeeker *m_ComputerSeeker;
|
||||||
|
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::onQuitAppCompleted(QVariant error)
|
||||||
|
{
|
||||||
|
Q_D(Launcher);
|
||||||
|
Event event(Event::AppQuitCompleted);
|
||||||
|
event.errorMessage = error.toString();
|
||||||
|
d->handleEvent(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
#include <QVariant>
|
||||||
|
|
||||||
|
class ComputerManager;
|
||||||
|
class NvComputer;
|
||||||
|
|
||||||
|
namespace CliQuitStream
|
||||||
|
{
|
||||||
|
|
||||||
|
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 quittingApp();
|
||||||
|
void failed(QString text);
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void onComputerFound(NvComputer *computer);
|
||||||
|
void onComputerSeekTimeout();
|
||||||
|
void onQuitAppCompleted(QVariant error);
|
||||||
|
|
||||||
|
private:
|
||||||
|
QScopedPointer<LauncherPrivate> m_DPtr;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
+74
-38
@@ -1,7 +1,9 @@
|
|||||||
#include "startstream.h"
|
#include "startstream.h"
|
||||||
#include "backend/computermanager.h"
|
#include "backend/computermanager.h"
|
||||||
|
#include "backend/computerseeker.h"
|
||||||
#include "streaming/session.h"
|
#include "streaming/session.h"
|
||||||
|
|
||||||
|
#include <QCoreApplication>
|
||||||
#include <QTimer>
|
#include <QTimer>
|
||||||
|
|
||||||
#define COMPUTER_SEEK_TIMEOUT 10000
|
#define COMPUTER_SEEK_TIMEOUT 10000
|
||||||
@@ -22,6 +24,9 @@ class Event
|
|||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
enum Type {
|
enum Type {
|
||||||
|
AppQuitCompleted,
|
||||||
|
AppQuitRequested,
|
||||||
|
ComputerFound,
|
||||||
ComputerUpdated,
|
ComputerUpdated,
|
||||||
Executed,
|
Executed,
|
||||||
Timedout,
|
Timedout,
|
||||||
@@ -33,6 +38,7 @@ public:
|
|||||||
Type type;
|
Type type;
|
||||||
ComputerManager *computerManager;
|
ComputerManager *computerManager;
|
||||||
NvComputer *computer;
|
NvComputer *computer;
|
||||||
|
QString errorMessage;
|
||||||
};
|
};
|
||||||
|
|
||||||
class LauncherPrivate
|
class LauncherPrivate
|
||||||
@@ -49,30 +55,34 @@ public:
|
|||||||
NvApp app;
|
NvApp app;
|
||||||
|
|
||||||
switch (event.type) {
|
switch (event.type) {
|
||||||
|
// Occurs when CliStartStreamSegue becomes visible and the UI calls launcher's execute()
|
||||||
case Event::Executed:
|
case Event::Executed:
|
||||||
if (m_State == StateInit) {
|
if (m_State == StateInit) {
|
||||||
m_State = StateSeekComputer;
|
m_State = StateSeekComputer;
|
||||||
m_TimeoutTimer->start(COMPUTER_SEEK_TIMEOUT);
|
|
||||||
m_ComputerManager = event.computerManager;
|
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::onTimeout);
|
||||||
|
m_ComputerSeeker->start(COMPUTER_SEEK_TIMEOUT);
|
||||||
|
|
||||||
q->connect(m_ComputerManager, &ComputerManager::computerStateChanged,
|
q->connect(m_ComputerManager, &ComputerManager::computerStateChanged,
|
||||||
q, &Launcher::onComputerUpdated);
|
q, &Launcher::onComputerUpdated);
|
||||||
// Seek desired computer by both connecting to it directly (this may fail
|
q->connect(m_ComputerManager, &ComputerManager::quitAppCompleted,
|
||||||
// if m_ComputerName is UUID, or the name that doesn't resolve to an IP
|
q, &Launcher::onQuitAppCompleted);
|
||||||
// address) and by polling it using mDNS, hopefully one of these methods
|
|
||||||
// would find the host
|
|
||||||
m_ComputerManager->addNewHost(m_ComputerName, false);
|
|
||||||
m_ComputerManager->startPolling();
|
|
||||||
emit q->searchingComputer();
|
emit q->searchingComputer();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case Event::ComputerUpdated:
|
// Occurs when searched computer is found
|
||||||
|
case Event::ComputerFound:
|
||||||
if (m_State == StateSeekComputer) {
|
if (m_State == StateSeekComputer) {
|
||||||
if (matchComputer(event.computer) && isOnline(event.computer)) {
|
if (event.computer->pairState == NvComputer::PS_PAIRED) {
|
||||||
if (isPaired(event.computer)) {
|
|
||||||
m_State = StateSeekApp;
|
m_State = StateSeekApp;
|
||||||
m_TimeoutTimer->start(APP_SEEK_TIMEOUT);
|
|
||||||
m_Computer = event.computer;
|
m_Computer = event.computer;
|
||||||
m_ComputerManager->stopPollingAsync();
|
m_TimeoutTimer->start(APP_SEEK_TIMEOUT);
|
||||||
emit q->searchingApp();
|
emit q->searchingApp();
|
||||||
} else {
|
} else {
|
||||||
m_State = StateFailure;
|
m_State = StateFailure;
|
||||||
@@ -82,25 +92,41 @@ public:
|
|||||||
emit q->failed(msg);
|
emit q->failed(msg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
break;
|
||||||
|
// Occurs when a computer is updated
|
||||||
|
case Event::ComputerUpdated:
|
||||||
if (m_State == StateSeekApp) {
|
if (m_State == StateSeekApp) {
|
||||||
int index = getAppIndex();
|
int index = getAppIndex();
|
||||||
if (-1 != index) {
|
if (-1 != index) {
|
||||||
app = m_Computer->appList[index];
|
app = m_Computer->appList[index];
|
||||||
|
m_TimeoutTimer->stop();
|
||||||
if (isNotStreaming() || isStreamingApp(app)) {
|
if (isNotStreaming() || isStreamingApp(app)) {
|
||||||
m_State = StateStartSession;
|
m_State = StateStartSession;
|
||||||
m_TimeoutTimer->stop();
|
|
||||||
session = new Session(m_Computer, app, m_Preferences);
|
session = new Session(m_Computer, app, m_Preferences);
|
||||||
emit q->sessionCreated(app.name, session);
|
emit q->sessionCreated(app.name, session);
|
||||||
} else {
|
} else {
|
||||||
m_State = StateFailure;
|
emit q->appQuitRequired(getCurrentAppName());
|
||||||
QString msg = QString("%1 is already running. Please quit %1 to stream %2.")
|
|
||||||
.arg(getCurrentAppName(), app.name);
|
|
||||||
emit q->failed(msg);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
// Occurs when there was another app running on computer and user accepted quit
|
||||||
|
// confirmation dialog
|
||||||
|
case Event::AppQuitRequested:
|
||||||
|
if (m_State == StateSeekApp) {
|
||||||
|
m_ComputerManager->quitRunningApp(m_Computer);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
// Occurs when the previous app quit has been completed, handles quitting errors if any
|
||||||
|
// happended. ComputerUpdated event's handler handles session start when previous app has
|
||||||
|
// quit.
|
||||||
|
case Event::AppQuitCompleted:
|
||||||
|
if (m_State == StateSeekApp && !event.errorMessage.isEmpty()) {
|
||||||
|
m_State = StateFailure;
|
||||||
|
emit q->failed(QString("Quitting app failed, reason: %1").arg(event.errorMessage));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
// Occurs when computer or app search timed out
|
||||||
case Event::Timedout:
|
case Event::Timedout:
|
||||||
if (m_State == StateSeekComputer) {
|
if (m_State == StateSeekComputer) {
|
||||||
m_State = StateFailure;
|
m_State = StateFailure;
|
||||||
@@ -114,26 +140,6 @@ public:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool matchComputer(NvComputer *computer) const
|
|
||||||
{
|
|
||||||
QString value = m_ComputerName.toLower();
|
|
||||||
return computer->name.toLower() == value ||
|
|
||||||
computer->localAddress.toLower() == value ||
|
|
||||||
computer->remoteAddress.toLower() == value ||
|
|
||||||
computer->manualAddress.toLower() == value ||
|
|
||||||
computer->uuid.toLower() == value;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool isOnline(NvComputer *computer) const
|
|
||||||
{
|
|
||||||
return computer->state == NvComputer::CS_ONLINE;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool isPaired(NvComputer *computer) const
|
|
||||||
{
|
|
||||||
return computer->pairState == NvComputer::PS_PAIRED;
|
|
||||||
}
|
|
||||||
|
|
||||||
int getAppIndex() const
|
int getAppIndex() const
|
||||||
{
|
{
|
||||||
for (int i = 0; i < m_Computer->appList.length(); i++) {
|
for (int i = 0; i < m_Computer->appList.length(); i++) {
|
||||||
@@ -169,6 +175,7 @@ public:
|
|||||||
QString m_AppName;
|
QString m_AppName;
|
||||||
StreamingPreferences *m_Preferences;
|
StreamingPreferences *m_Preferences;
|
||||||
ComputerManager *m_ComputerManager;
|
ComputerManager *m_ComputerManager;
|
||||||
|
ComputerSeeker *m_ComputerSeeker;
|
||||||
NvComputer *m_Computer;
|
NvComputer *m_Computer;
|
||||||
State m_State;
|
State m_State;
|
||||||
QTimer *m_TimeoutTimer;
|
QTimer *m_TimeoutTimer;
|
||||||
@@ -202,6 +209,27 @@ void Launcher::execute(ComputerManager *manager)
|
|||||||
d->handleEvent(event);
|
d->handleEvent(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Launcher::quitRunningApp()
|
||||||
|
{
|
||||||
|
Q_D(Launcher);
|
||||||
|
Event event(Event::AppQuitRequested);
|
||||||
|
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::onComputerUpdated(NvComputer *computer)
|
void Launcher::onComputerUpdated(NvComputer *computer)
|
||||||
{
|
{
|
||||||
Q_D(Launcher);
|
Q_D(Launcher);
|
||||||
@@ -217,4 +245,12 @@ void Launcher::onTimeout()
|
|||||||
d->handleEvent(event);
|
d->handleEvent(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Launcher::onQuitAppCompleted(QVariant error)
|
||||||
|
{
|
||||||
|
Q_D(Launcher);
|
||||||
|
Event event(Event::AppQuitCompleted);
|
||||||
|
event.errorMessage = error.toString();
|
||||||
|
d->handleEvent(event);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
|
#include <QVariant>
|
||||||
|
|
||||||
class ComputerManager;
|
class ComputerManager;
|
||||||
class NvComputer;
|
class NvComputer;
|
||||||
@@ -24,16 +25,21 @@ public:
|
|||||||
QObject *parent = nullptr);
|
QObject *parent = nullptr);
|
||||||
~Launcher();
|
~Launcher();
|
||||||
Q_INVOKABLE void execute(ComputerManager *manager);
|
Q_INVOKABLE void execute(ComputerManager *manager);
|
||||||
|
Q_INVOKABLE void quitRunningApp();
|
||||||
|
Q_INVOKABLE bool isExecuted() const;
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void searchingComputer();
|
void searchingComputer();
|
||||||
void searchingApp();
|
void searchingApp();
|
||||||
void sessionCreated(QString appName, Session *session);
|
void sessionCreated(QString appName, Session *session);
|
||||||
void failed(QString text);
|
void failed(QString text);
|
||||||
|
void appQuitRequired(QString appName);
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
|
void onComputerFound(NvComputer *computer);
|
||||||
void onComputerUpdated(NvComputer *computer);
|
void onComputerUpdated(NvComputer *computer);
|
||||||
void onTimeout();
|
void onTimeout();
|
||||||
|
void onQuitAppCompleted(QVariant error);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QScopedPointer<LauncherPrivate> m_DPtr;
|
QScopedPointer<LauncherPrivate> m_DPtr;
|
||||||
|
|||||||
@@ -19,20 +19,37 @@
|
|||||||
</ul>
|
</ul>
|
||||||
</description>
|
</description>
|
||||||
|
|
||||||
<url type="homepage">https://moonlight-stream.com</url>
|
<url type="homepage">https://moonlight-stream.org</url>
|
||||||
<url type="bugtracker">https://github.com/moonlight-stream/moonlight-qt/issues</url>
|
<url type="bugtracker">https://github.com/moonlight-stream/moonlight-qt/issues</url>
|
||||||
<url type="help">https://github.com/moonlight-stream/moonlight-docs/wiki/Setup-Guide</url>
|
<url type="help">https://github.com/moonlight-stream/moonlight-docs/wiki/Setup-Guide</url>
|
||||||
|
|
||||||
<screenshots>
|
<screenshots>
|
||||||
<screenshot type="default">
|
<screenshot type="default">
|
||||||
<image>https://moonlight-stream.com/appstream_resources/moonlight_screenshot_1.png</image>
|
<image>https://moonlight-stream.org/appstream_resources/moonlight_screenshot_1.png</image>
|
||||||
</screenshot>
|
</screenshot>
|
||||||
<screenshot>
|
<screenshot>
|
||||||
<image>https://moonlight-stream.com/appstream_resources/moonlight_screenshot_2.png</image>
|
<image>https://moonlight-stream.org/appstream_resources/moonlight_screenshot_2.png</image>
|
||||||
</screenshot>
|
</screenshot>
|
||||||
</screenshots>
|
</screenshots>
|
||||||
|
|
||||||
<releases>
|
<releases>
|
||||||
|
<release version="0.7.0" date="2018-12-05">
|
||||||
|
<description>
|
||||||
|
<p>New features:</p>
|
||||||
|
<ul>
|
||||||
|
<li>Added option to quit the app after stopping the stream</li>
|
||||||
|
<li>Added CLI option to quit the running app</li>
|
||||||
|
</ul>
|
||||||
|
<p>Bugfixes:</p>
|
||||||
|
<ul>
|
||||||
|
<li>Switched to libsoundio backend to address audio glitches and reduce latency</li>
|
||||||
|
<li>Fixed CLI version output</li>
|
||||||
|
<li>Fixed streaming error messages not showing up when launching from CLI</li>
|
||||||
|
<li>Updated included SDL gamepad mappings</li>
|
||||||
|
<li>Various minor UI tweaks</li>
|
||||||
|
</ul>
|
||||||
|
</description>
|
||||||
|
</release>
|
||||||
<release version="0.6.4" date="2018-11-20">
|
<release version="0.6.4" date="2018-11-20">
|
||||||
<description>
|
<description>
|
||||||
<p>New features:</p>
|
<p>New features:</p>
|
||||||
@@ -214,7 +231,7 @@
|
|||||||
</categories>
|
</categories>
|
||||||
|
|
||||||
<developer_name>Moonlight Game Streaming Team</developer_name>
|
<developer_name>Moonlight Game Streaming Team</developer_name>
|
||||||
<update_contact>cameron@moonlight-stream.com</update_contact>
|
<update_contact>cameron@moonlight-stream.org</update_contact>
|
||||||
|
|
||||||
<launchable type="desktop-id">com.moonlight_stream.Moonlight.desktop</launchable>
|
<launchable type="desktop-id">com.moonlight_stream.Moonlight.desktop</launchable>
|
||||||
|
|
||||||
|
|||||||
+1
-2
@@ -89,10 +89,9 @@ GridView {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Text {
|
Label {
|
||||||
id: appNameText
|
id: appNameText
|
||||||
text: model.name
|
text: model.name
|
||||||
color: "white"
|
|
||||||
width: parent.width
|
width: parent.width
|
||||||
height: 125
|
height: 125
|
||||||
anchors.top: appIcon.bottom
|
anchors.top: appIcon.bottom
|
||||||
|
|||||||
@@ -0,0 +1,70 @@
|
|||||||
|
import QtQuick 2.0
|
||||||
|
import QtQuick.Controls 2.2
|
||||||
|
import QtQuick.Dialogs 1.2
|
||||||
|
|
||||||
|
import ComputerManager 1.0
|
||||||
|
import Session 1.0
|
||||||
|
|
||||||
|
Item {
|
||||||
|
anchors.fill: parent
|
||||||
|
|
||||||
|
function onSearchingComputer() {
|
||||||
|
stageLabel.text = "Establishing connection to PC..."
|
||||||
|
}
|
||||||
|
|
||||||
|
function onQuittingApp() {
|
||||||
|
stageLabel.text = "Quitting app..."
|
||||||
|
}
|
||||||
|
|
||||||
|
function onFailure(message) {
|
||||||
|
errorDialog.text = message
|
||||||
|
errorDialog.open()
|
||||||
|
}
|
||||||
|
|
||||||
|
// The StackView will trigger a visibility change when
|
||||||
|
// we're pushed onto it, causing our onVisibleChanged
|
||||||
|
// routine to run, but only if we start as invisible
|
||||||
|
visible: false
|
||||||
|
|
||||||
|
onVisibleChanged: {
|
||||||
|
if (visible && !launcher.isExecuted()) {
|
||||||
|
toolBar.visible = false
|
||||||
|
launcher.searchingComputer.connect(onSearchingComputer)
|
||||||
|
launcher.quittingApp.connect(onQuittingApp)
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MessageDialog {
|
||||||
|
id: errorDialog
|
||||||
|
modality:Qt.WindowModal
|
||||||
|
icon: StandardIcon.Critical
|
||||||
|
standardButtons: StandardButton.Ok
|
||||||
|
|
||||||
|
onVisibleChanged: {
|
||||||
|
if (!visible) {
|
||||||
|
Qt.quit()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import QtQml 2.2
|
||||||
import QtQuick 2.0
|
import QtQuick 2.0
|
||||||
import QtQuick.Controls 2.2
|
import QtQuick.Controls 2.2
|
||||||
import QtQuick.Dialogs 1.2
|
import QtQuick.Dialogs 1.2
|
||||||
@@ -30,13 +31,19 @@ Item {
|
|||||||
errorDialog.open()
|
errorDialog.open()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function onAppQuitRequired(appName) {
|
||||||
|
quitAppDialog.appName = appName
|
||||||
|
quitAppDialog.open()
|
||||||
|
}
|
||||||
|
|
||||||
onVisibleChanged: {
|
onVisibleChanged: {
|
||||||
if (visible) {
|
if (visible && !launcher.isExecuted()) {
|
||||||
toolBar.visible = false
|
toolBar.visible = false
|
||||||
launcher.searchingComputer.connect(onSearchingComputer)
|
launcher.searchingComputer.connect(onSearchingComputer)
|
||||||
launcher.searchingApp.connect(onSearchingApp)
|
launcher.searchingApp.connect(onSearchingApp)
|
||||||
launcher.sessionCreated.connect(onSessionCreated)
|
launcher.sessionCreated.connect(onSessionCreated)
|
||||||
launcher.failed.connect(onLaunchFailed)
|
launcher.failed.connect(onLaunchFailed)
|
||||||
|
launcher.appQuitRequired.connect(onAppQuitRequired)
|
||||||
launcher.execute(ComputerManager)
|
launcher.execute(ComputerManager)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -56,7 +63,6 @@ Item {
|
|||||||
verticalAlignment: Text.AlignVCenter
|
verticalAlignment: Text.AlignVCenter
|
||||||
|
|
||||||
wrapMode: Text.Wrap
|
wrapMode: Text.Wrap
|
||||||
color: "white"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -75,4 +81,44 @@ Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MessageDialog {
|
||||||
|
id: quitAppDialog
|
||||||
|
modality:Qt.WindowModal
|
||||||
|
text:"Are you sure you want to quit " + appName +"? Any unsaved progress will be lost."
|
||||||
|
standardButtons: StandardButton.Yes | StandardButton.No
|
||||||
|
property string appName : ""
|
||||||
|
|
||||||
|
function quitApp() {
|
||||||
|
var component = Qt.createComponent("QuitSegue.qml")
|
||||||
|
var params = {"appName": appName}
|
||||||
|
stackView.push(component.createObject(stackView, params))
|
||||||
|
// Trigger the quit after pushing the quit segue on screen
|
||||||
|
launcher.quitRunningApp()
|
||||||
|
}
|
||||||
|
|
||||||
|
onYes: quitApp()
|
||||||
|
|
||||||
|
// For keyboard/gamepad navigation
|
||||||
|
onAccepted: quitApp()
|
||||||
|
|
||||||
|
// Exit process if app quit is rejected (reacts also to closing of the
|
||||||
|
// dialog from title bar's close button).
|
||||||
|
// Note: this depends on undocumented behavior of visibleChanged()
|
||||||
|
// signal being emitted before yes() or accepted() has been emitted.
|
||||||
|
onVisibleChanged: {
|
||||||
|
if (!visible) {
|
||||||
|
quitTimer.start()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Component.onCompleted: {
|
||||||
|
yes.connect(quitTimer.stop)
|
||||||
|
accepted.connect(quitTimer.stop)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Timer {
|
||||||
|
id: quitTimer
|
||||||
|
interval: 100
|
||||||
|
onTriggered: Qt.quit()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
+1
-2
@@ -124,10 +124,9 @@ GridView {
|
|||||||
visible: !model.addPc && model.statusUnknown
|
visible: !model.addPc && model.statusUnknown
|
||||||
}
|
}
|
||||||
|
|
||||||
Text {
|
Label {
|
||||||
id: pcNameText
|
id: pcNameText
|
||||||
text: model.name
|
text: model.name
|
||||||
color: "white"
|
|
||||||
|
|
||||||
width: parent.width
|
width: parent.width
|
||||||
anchors.top: pcIcon.bottom
|
anchors.top: pcIcon.bottom
|
||||||
|
|||||||
@@ -65,7 +65,6 @@ Item {
|
|||||||
verticalAlignment: Text.AlignVCenter
|
verticalAlignment: Text.AlignVCenter
|
||||||
|
|
||||||
wrapMode: Text.Wrap
|
wrapMode: Text.Wrap
|
||||||
color: "white"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+30
-22
@@ -73,7 +73,6 @@ Flickable {
|
|||||||
text: qsTr("Resolution and FPS")
|
text: qsTr("Resolution and FPS")
|
||||||
font.pointSize: 12
|
font.pointSize: 12
|
||||||
wrapMode: Text.Wrap
|
wrapMode: Text.Wrap
|
||||||
color: "white"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Label {
|
Label {
|
||||||
@@ -82,7 +81,6 @@ Flickable {
|
|||||||
text: qsTr("Setting values too high for your PC or network connection may cause lag, stuttering, or errors.")
|
text: qsTr("Setting values too high for your PC or network connection may cause lag, stuttering, or errors.")
|
||||||
font.pointSize: 9
|
font.pointSize: 9
|
||||||
wrapMode: Text.Wrap
|
wrapMode: Text.Wrap
|
||||||
color: "white"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Row {
|
Row {
|
||||||
@@ -297,7 +295,6 @@ Flickable {
|
|||||||
text: qsTr("Video bitrate: ")
|
text: qsTr("Video bitrate: ")
|
||||||
font.pointSize: 12
|
font.pointSize: 12
|
||||||
wrapMode: Text.Wrap
|
wrapMode: Text.Wrap
|
||||||
color: "white"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Label {
|
Label {
|
||||||
@@ -306,7 +303,6 @@ Flickable {
|
|||||||
text: qsTr("Lower bitrate to reduce lag and stuttering. Raise bitrate to increase image quality.")
|
text: qsTr("Lower bitrate to reduce lag and stuttering. Raise bitrate to increase image quality.")
|
||||||
font.pointSize: 9
|
font.pointSize: 9
|
||||||
wrapMode: Text.Wrap
|
wrapMode: Text.Wrap
|
||||||
color: "white"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Slider {
|
Slider {
|
||||||
@@ -334,7 +330,6 @@ Flickable {
|
|||||||
text: qsTr("Display mode")
|
text: qsTr("Display mode")
|
||||||
font.pointSize: 12
|
font.pointSize: 12
|
||||||
wrapMode: Text.Wrap
|
wrapMode: Text.Wrap
|
||||||
color: "white"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
AutoResizingComboBox {
|
AutoResizingComboBox {
|
||||||
@@ -383,7 +378,7 @@ Flickable {
|
|||||||
CheckBox {
|
CheckBox {
|
||||||
id: vsyncCheck
|
id: vsyncCheck
|
||||||
hoverEnabled: true
|
hoverEnabled: true
|
||||||
text: "<font color=\"white\">Enable V-Sync</font>"
|
text: "Enable V-Sync"
|
||||||
font.pointSize: 12
|
font.pointSize: 12
|
||||||
checked: prefs.enableVsync
|
checked: prefs.enableVsync
|
||||||
onCheckedChanged: {
|
onCheckedChanged: {
|
||||||
@@ -416,7 +411,6 @@ Flickable {
|
|||||||
text: qsTr("Audio configuration")
|
text: qsTr("Audio configuration")
|
||||||
font.pointSize: 12
|
font.pointSize: 12
|
||||||
wrapMode: Text.Wrap
|
wrapMode: Text.Wrap
|
||||||
color: "white"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
AutoResizingComboBox {
|
AutoResizingComboBox {
|
||||||
@@ -468,12 +462,12 @@ Flickable {
|
|||||||
spacing: 5
|
spacing: 5
|
||||||
|
|
||||||
CheckBox {
|
CheckBox {
|
||||||
id: startWindowedCheck
|
id: startMaximizedCheck
|
||||||
text: "<font color=\"white\">Start Moonlight in windowed mode</font>"
|
text: "Maximize Moonlight window on startup"
|
||||||
font.pointSize: 12
|
font.pointSize: 12
|
||||||
checked: prefs.startWindowed
|
checked: !prefs.startWindowed
|
||||||
onCheckedChanged: {
|
onCheckedChanged: {
|
||||||
prefs.startWindowed = checked
|
prefs.startWindowed = !checked
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -498,19 +492,25 @@ Flickable {
|
|||||||
spacing: 5
|
spacing: 5
|
||||||
|
|
||||||
CheckBox {
|
CheckBox {
|
||||||
id: multiControllerCheck
|
id: singleControllerCheck
|
||||||
text: "<font color=\"white\">Multiple controller support</font>"
|
text: "Force gamepad #1 always present"
|
||||||
font.pointSize: 12
|
font.pointSize: 12
|
||||||
checked: prefs.multiController
|
checked: !prefs.multiController
|
||||||
onCheckedChanged: {
|
onCheckedChanged: {
|
||||||
prefs.multiController = checked
|
prefs.multiController = !checked
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ToolTip.delay: 1000
|
||||||
|
ToolTip.timeout: 5000
|
||||||
|
ToolTip.visible: hovered
|
||||||
|
ToolTip.text: "Forces a single gamepad to always stay connected to the host, even if no gamepads are actually connected to this PC.\n" +
|
||||||
|
"Only enable this option when streaming a game that doesn't support gamepads being connected after startup."
|
||||||
}
|
}
|
||||||
|
|
||||||
CheckBox {
|
CheckBox {
|
||||||
id: rawInputCheck
|
id: rawInputCheck
|
||||||
hoverEnabled: true
|
hoverEnabled: true
|
||||||
text: "<font color=\"white\">Raw mouse input</font>"
|
text: "Raw mouse input"
|
||||||
font.pointSize: 12
|
font.pointSize: 12
|
||||||
checked: !prefs.mouseAcceleration
|
checked: !prefs.mouseAcceleration
|
||||||
onCheckedChanged: {
|
onCheckedChanged: {
|
||||||
@@ -538,7 +538,7 @@ Flickable {
|
|||||||
|
|
||||||
CheckBox {
|
CheckBox {
|
||||||
id: optimizeGameSettingsCheck
|
id: optimizeGameSettingsCheck
|
||||||
text: "<font color=\"white\">Optimize game settings</font>"
|
text: "Optimize game settings"
|
||||||
font.pointSize: 12
|
font.pointSize: 12
|
||||||
checked: prefs.gameOptimizations
|
checked: prefs.gameOptimizations
|
||||||
onCheckedChanged: {
|
onCheckedChanged: {
|
||||||
@@ -548,7 +548,7 @@ Flickable {
|
|||||||
|
|
||||||
CheckBox {
|
CheckBox {
|
||||||
id: audioPcCheck
|
id: audioPcCheck
|
||||||
text: "<font color=\"white\">Play audio on host PC</font>"
|
text: "Play audio on host PC"
|
||||||
font.pointSize: 12
|
font.pointSize: 12
|
||||||
checked: prefs.playAudioOnHost
|
checked: prefs.playAudioOnHost
|
||||||
onCheckedChanged: {
|
onCheckedChanged: {
|
||||||
@@ -575,7 +575,6 @@ Flickable {
|
|||||||
text: qsTr("Video decoder")
|
text: qsTr("Video decoder")
|
||||||
font.pointSize: 12
|
font.pointSize: 12
|
||||||
wrapMode: Text.Wrap
|
wrapMode: Text.Wrap
|
||||||
color: "white"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
AutoResizingComboBox {
|
AutoResizingComboBox {
|
||||||
@@ -622,7 +621,6 @@ Flickable {
|
|||||||
text: qsTr("Video codec")
|
text: qsTr("Video codec")
|
||||||
font.pointSize: 12
|
font.pointSize: 12
|
||||||
wrapMode: Text.Wrap
|
wrapMode: Text.Wrap
|
||||||
color: "white"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
AutoResizingComboBox {
|
AutoResizingComboBox {
|
||||||
@@ -672,7 +670,7 @@ Flickable {
|
|||||||
|
|
||||||
CheckBox {
|
CheckBox {
|
||||||
id: unlockUnsupportedFps
|
id: unlockUnsupportedFps
|
||||||
text: "<font color=\"white\">Unlock unsupported FPS options</font>"
|
text: "Unlock unsupported FPS options"
|
||||||
font.pointSize: 12
|
font.pointSize: 12
|
||||||
checked: prefs.unsupportedFps
|
checked: prefs.unsupportedFps
|
||||||
onCheckedChanged: {
|
onCheckedChanged: {
|
||||||
@@ -686,7 +684,7 @@ Flickable {
|
|||||||
|
|
||||||
CheckBox {
|
CheckBox {
|
||||||
id: enableMdns
|
id: enableMdns
|
||||||
text: "<font color=\"white\">Automatically find PCs on the local network (Recommended)</font>"
|
text: "Automatically find PCs on the local network (Recommended)"
|
||||||
font.pointSize: 12
|
font.pointSize: 12
|
||||||
checked: prefs.enableMdns
|
checked: prefs.enableMdns
|
||||||
onCheckedChanged: {
|
onCheckedChanged: {
|
||||||
@@ -703,6 +701,16 @@ Flickable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CheckBox {
|
||||||
|
id: quitAppAfter
|
||||||
|
text: "Quit app after quitting session"
|
||||||
|
font.pointSize: 12
|
||||||
|
checked: prefs.quitAppAfter
|
||||||
|
onCheckedChanged: {
|
||||||
|
prefs.quitAppAfter = checked
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+53
-21
@@ -3,6 +3,7 @@ import QtQuick.Controls 2.2
|
|||||||
import QtQuick.Dialogs 1.2
|
import QtQuick.Dialogs 1.2
|
||||||
import QtQuick.Window 2.2
|
import QtQuick.Window 2.2
|
||||||
|
|
||||||
|
import ComputerManager 1.0
|
||||||
import SdlGamepadKeyNavigation 1.0
|
import SdlGamepadKeyNavigation 1.0
|
||||||
import Session 1.0
|
import Session 1.0
|
||||||
|
|
||||||
@@ -59,6 +60,44 @@ Item {
|
|||||||
toast.visible = true
|
toast.visible = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function quitStarting()
|
||||||
|
{
|
||||||
|
// Avoid the push transition animation
|
||||||
|
var component = Qt.createComponent("QuitSegue.qml")
|
||||||
|
stackView.replace(stackView.currentItem, component.createObject(stackView, {"appName": appName}), StackView.Immediate)
|
||||||
|
|
||||||
|
// Show the Qt window again to show quit segue
|
||||||
|
window.visible = true
|
||||||
|
}
|
||||||
|
|
||||||
|
function sessionFinished()
|
||||||
|
{
|
||||||
|
if (quitAfter) {
|
||||||
|
if (errorDialog.text) {
|
||||||
|
// Quit when the error dialog is acknowledged
|
||||||
|
errorDialog.open()
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Quit immediately
|
||||||
|
Qt.quit()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Exit this view
|
||||||
|
stackView.pop()
|
||||||
|
|
||||||
|
// Show the Qt window again after streaming
|
||||||
|
window.visible = true
|
||||||
|
|
||||||
|
// Display any launch errors. We do this after
|
||||||
|
// the Qt UI is visible again to prevent losing
|
||||||
|
// focus on the dialog which would impact gamepad
|
||||||
|
// users.
|
||||||
|
if (errorDialog.text) {
|
||||||
|
errorDialog.open()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// It's important that we don't call enable() here
|
// It's important that we don't call enable() here
|
||||||
// or it may interfere with the Session instance
|
// or it may interfere with the Session instance
|
||||||
// getting notified of initial connected gamepads.
|
// getting notified of initial connected gamepads.
|
||||||
@@ -85,29 +124,13 @@ Item {
|
|||||||
session.connectionStarted.connect(connectionStarted)
|
session.connectionStarted.connect(connectionStarted)
|
||||||
session.displayLaunchError.connect(displayLaunchError)
|
session.displayLaunchError.connect(displayLaunchError)
|
||||||
session.displayLaunchWarning.connect(displayLaunchWarning)
|
session.displayLaunchWarning.connect(displayLaunchWarning)
|
||||||
|
session.quitStarting.connect(quitStarting)
|
||||||
|
session.sessionFinished.connect(sessionFinished)
|
||||||
|
|
||||||
// Run the streaming session to completion
|
// Run the streaming session to completion
|
||||||
session.exec(Screen.virtualX, Screen.virtualY)
|
session.exec(Screen.virtualX, Screen.virtualY)
|
||||||
|
|
||||||
if (quitAfter) {
|
|
||||||
Qt.quit()
|
|
||||||
} else {
|
|
||||||
// Show the Qt window again after streaming
|
|
||||||
window.visible = true
|
|
||||||
|
|
||||||
// Exit this view
|
|
||||||
stackView.pop()
|
|
||||||
|
|
||||||
// Display any launch errors. We do this after
|
|
||||||
// the Qt UI is visible again to prevent losing
|
|
||||||
// focus on the dialog which would impact gamepad
|
|
||||||
// users.
|
|
||||||
if (errorDialog.text) {
|
|
||||||
errorDialog.open()
|
|
||||||
}
|
}
|
||||||
}
|
else if (!quitAfter) {
|
||||||
}
|
|
||||||
else {
|
|
||||||
// Show the toolbar again when we become hidden
|
// Show the toolbar again when we become hidden
|
||||||
toolBar.visible = true
|
toolBar.visible = true
|
||||||
}
|
}
|
||||||
@@ -129,7 +152,6 @@ Item {
|
|||||||
verticalAlignment: Text.AlignVCenter
|
verticalAlignment: Text.AlignVCenter
|
||||||
|
|
||||||
wrapMode: Text.Wrap
|
wrapMode: Text.Wrap
|
||||||
color: "white"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -142,7 +164,6 @@ Item {
|
|||||||
verticalAlignment: Text.AlignVCenter
|
verticalAlignment: Text.AlignVCenter
|
||||||
|
|
||||||
wrapMode: Text.Wrap
|
wrapMode: Text.Wrap
|
||||||
color: "white"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
MessageDialog {
|
MessageDialog {
|
||||||
@@ -150,8 +171,19 @@ Item {
|
|||||||
modality:Qt.WindowModal
|
modality:Qt.WindowModal
|
||||||
icon: StandardIcon.Critical
|
icon: StandardIcon.Critical
|
||||||
standardButtons: StandardButton.Ok | StandardButton.Help
|
standardButtons: StandardButton.Ok | StandardButton.Help
|
||||||
|
|
||||||
|
onAccepted: {
|
||||||
|
if (quitAfter) {
|
||||||
|
Qt.quit()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
onHelp: {
|
onHelp: {
|
||||||
Qt.openUrlExternally("https://github.com/moonlight-stream/moonlight-docs/wiki/Troubleshooting");
|
Qt.openUrlExternally("https://github.com/moonlight-stream/moonlight-docs/wiki/Troubleshooting");
|
||||||
|
|
||||||
|
if (quitAfter) {
|
||||||
|
Qt.quit()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -21,6 +21,7 @@
|
|||||||
#include "antihookingprotection.h"
|
#include "antihookingprotection.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#include "cli/quitstream.h"
|
||||||
#include "cli/startstream.h"
|
#include "cli/startstream.h"
|
||||||
#include "cli/commandlineparser.h"
|
#include "cli/commandlineparser.h"
|
||||||
#include "path.h"
|
#include "path.h"
|
||||||
@@ -222,6 +223,9 @@ LONG WINAPI UnhandledExceptionHandler(struct _EXCEPTION_POINTERS *ExceptionInfo)
|
|||||||
|
|
||||||
int main(int argc, char *argv[])
|
int main(int argc, char *argv[])
|
||||||
{
|
{
|
||||||
|
// Set the app version for the QCommandLineParser's showVersion() command
|
||||||
|
QCoreApplication::setApplicationVersion(VERSION_STR);
|
||||||
|
|
||||||
// Set these here to allow us to use the default QSettings constructor.
|
// Set these here to allow us to use the default QSettings constructor.
|
||||||
// These also ensure that our cache directory is named correctly. As such,
|
// These also ensure that our cache directory is named correctly. As such,
|
||||||
// it is critical that these be called before Path::initialize().
|
// it is critical that these be called before Path::initialize().
|
||||||
@@ -342,6 +346,15 @@ int main(int argc, char *argv[])
|
|||||||
engine.rootContext()->setContextProperty("launcher", launcher);
|
engine.rootContext()->setContextProperty("launcher", launcher);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case GlobalCommandLineParser::QuitRequested:
|
||||||
|
{
|
||||||
|
initialView = "CliQuitStreamSegue.qml";
|
||||||
|
QuitCommandLineParser quitParser;
|
||||||
|
quitParser.parse(app.arguments());
|
||||||
|
auto launcher = new CliQuitStream::Launcher(quitParser.getHost(), &app);
|
||||||
|
engine.rootContext()->setContextProperty("launcher", launcher);
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
engine.rootContext()->setContextProperty("initialView", initialView);
|
engine.rootContext()->setContextProperty("initialView", initialView);
|
||||||
@@ -369,5 +382,9 @@ int main(int argc, char *argv[])
|
|||||||
|
|
||||||
int err = app.exec();
|
int err = app.exec();
|
||||||
|
|
||||||
|
// Give worker tasks time to properly exit. Fixes PendingQuitTask
|
||||||
|
// sometimes freezing and blocking process exit.
|
||||||
|
QThreadPool::globalInstance()->waitForDone(30000);
|
||||||
|
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
@@ -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/CliQuitStreamSegue.qml</file>
|
||||||
<file>gui/CliStartStreamSegue.qml</file>
|
<file>gui/CliStartStreamSegue.qml</file>
|
||||||
<file>gui/AutoResizingComboBox.qml</file>
|
<file>gui/AutoResizingComboBox.qml</file>
|
||||||
</qresource>
|
</qresource>
|
||||||
|
|||||||
+1
-1
@@ -1,3 +1,3 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="24" height="24" viewBox="0 0 24 24" fill="#0BF200">
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="24" height="24" viewBox="0 0 24 24" fill="#FF0000">
|
||||||
<path d="M21,10.12H14.22L16.96,7.3C14.23,4.6 9.81,4.5 7.08,7.2C4.35,9.91 4.35,14.28 7.08,17C9.81,19.7 14.23,19.7 16.96,17C18.32,15.65 19,14.08 19,12.1H21C21,14.08 20.12,16.65 18.36,18.39C14.85,21.87 9.15,21.87 5.64,18.39C2.14,14.92 2.11,9.28 5.62,5.81C9.13,2.34 14.76,2.34 18.27,5.81L21,3V10.12M12.5,8V12.25L16,14.33L15.28,15.54L11,13V8H12.5Z" />
|
<path d="M21,10.12H14.22L16.96,7.3C14.23,4.6 9.81,4.5 7.08,7.2C4.35,9.91 4.35,14.28 7.08,17C9.81,19.7 14.23,19.7 16.96,17C18.32,15.65 19,14.08 19,12.1H21C21,14.08 20.12,16.65 18.36,18.39C14.85,21.87 9.15,21.87 5.64,18.39C2.14,14.92 2.11,9.28 5.62,5.81C9.13,2.34 14.76,2.34 18.27,5.81L21,3V10.12M12.5,8V12.25L16,14.33L15.28,15.54L11,13V8H12.5Z" />
|
||||||
</svg>
|
</svg>
|
||||||
|
Before Width: | Height: | Size: 499 B After Width: | Height: | Size: 499 B |
@@ -20,6 +20,7 @@
|
|||||||
#define SER_WINDOWMODE "windowmode"
|
#define SER_WINDOWMODE "windowmode"
|
||||||
#define SER_UNSUPPORTEDFPS "unsupportedfps"
|
#define SER_UNSUPPORTEDFPS "unsupportedfps"
|
||||||
#define SER_MDNS "mdns"
|
#define SER_MDNS "mdns"
|
||||||
|
#define SER_QUITAPPAFTER "quitAppAfter"
|
||||||
#define SER_MOUSEACCELERATION "mouseacceleration"
|
#define SER_MOUSEACCELERATION "mouseacceleration"
|
||||||
#define SER_STARTWINDOWED "startwindowed"
|
#define SER_STARTWINDOWED "startwindowed"
|
||||||
|
|
||||||
@@ -43,6 +44,7 @@ void StreamingPreferences::reload()
|
|||||||
multiController = settings.value(SER_MULTICONT, true).toBool();
|
multiController = settings.value(SER_MULTICONT, true).toBool();
|
||||||
unsupportedFps = settings.value(SER_UNSUPPORTEDFPS, false).toBool();
|
unsupportedFps = settings.value(SER_UNSUPPORTEDFPS, false).toBool();
|
||||||
enableMdns = settings.value(SER_MDNS, true).toBool();
|
enableMdns = settings.value(SER_MDNS, true).toBool();
|
||||||
|
quitAppAfter = settings.value(SER_QUITAPPAFTER, false).toBool();
|
||||||
mouseAcceleration = settings.value(SER_MOUSEACCELERATION, false).toBool();
|
mouseAcceleration = settings.value(SER_MOUSEACCELERATION, false).toBool();
|
||||||
startWindowed = settings.value(SER_STARTWINDOWED, false).toBool();
|
startWindowed = settings.value(SER_STARTWINDOWED, false).toBool();
|
||||||
audioConfig = static_cast<AudioConfig>(settings.value(SER_AUDIOCFG,
|
audioConfig = static_cast<AudioConfig>(settings.value(SER_AUDIOCFG,
|
||||||
@@ -71,6 +73,7 @@ void StreamingPreferences::save()
|
|||||||
settings.setValue(SER_MULTICONT, multiController);
|
settings.setValue(SER_MULTICONT, multiController);
|
||||||
settings.setValue(SER_UNSUPPORTEDFPS, unsupportedFps);
|
settings.setValue(SER_UNSUPPORTEDFPS, unsupportedFps);
|
||||||
settings.setValue(SER_MDNS, enableMdns);
|
settings.setValue(SER_MDNS, enableMdns);
|
||||||
|
settings.setValue(SER_QUITAPPAFTER, quitAppAfter);
|
||||||
settings.setValue(SER_MOUSEACCELERATION, mouseAcceleration);
|
settings.setValue(SER_MOUSEACCELERATION, mouseAcceleration);
|
||||||
settings.setValue(SER_STARTWINDOWED, startWindowed);
|
settings.setValue(SER_STARTWINDOWED, startWindowed);
|
||||||
settings.setValue(SER_AUDIOCFG, static_cast<int>(audioConfig));
|
settings.setValue(SER_AUDIOCFG, static_cast<int>(audioConfig));
|
||||||
|
|||||||
@@ -73,6 +73,7 @@ public:
|
|||||||
Q_PROPERTY(bool multiController MEMBER multiController NOTIFY multiControllerChanged)
|
Q_PROPERTY(bool multiController MEMBER multiController NOTIFY multiControllerChanged)
|
||||||
Q_PROPERTY(bool unsupportedFps MEMBER unsupportedFps NOTIFY unsupportedFpsChanged)
|
Q_PROPERTY(bool unsupportedFps MEMBER unsupportedFps NOTIFY unsupportedFpsChanged)
|
||||||
Q_PROPERTY(bool enableMdns MEMBER enableMdns NOTIFY enableMdnsChanged)
|
Q_PROPERTY(bool enableMdns MEMBER enableMdns NOTIFY enableMdnsChanged)
|
||||||
|
Q_PROPERTY(bool quitAppAfter MEMBER quitAppAfter NOTIFY quitAppAfterChanged)
|
||||||
Q_PROPERTY(bool mouseAcceleration MEMBER mouseAcceleration NOTIFY mouseAccelerationChanged)
|
Q_PROPERTY(bool mouseAcceleration MEMBER mouseAcceleration NOTIFY mouseAccelerationChanged)
|
||||||
Q_PROPERTY(bool startWindowed MEMBER startWindowed NOTIFY startWindowedChanged)
|
Q_PROPERTY(bool startWindowed MEMBER startWindowed NOTIFY startWindowedChanged)
|
||||||
Q_PROPERTY(AudioConfig audioConfig MEMBER audioConfig NOTIFY audioConfigChanged)
|
Q_PROPERTY(AudioConfig audioConfig MEMBER audioConfig NOTIFY audioConfigChanged)
|
||||||
@@ -91,6 +92,7 @@ public:
|
|||||||
bool multiController;
|
bool multiController;
|
||||||
bool unsupportedFps;
|
bool unsupportedFps;
|
||||||
bool enableMdns;
|
bool enableMdns;
|
||||||
|
bool quitAppAfter;
|
||||||
bool mouseAcceleration;
|
bool mouseAcceleration;
|
||||||
bool startWindowed;
|
bool startWindowed;
|
||||||
AudioConfig audioConfig;
|
AudioConfig audioConfig;
|
||||||
@@ -107,6 +109,7 @@ signals:
|
|||||||
void multiControllerChanged();
|
void multiControllerChanged();
|
||||||
void unsupportedFpsChanged();
|
void unsupportedFpsChanged();
|
||||||
void enableMdnsChanged();
|
void enableMdnsChanged();
|
||||||
|
void quitAppAfterChanged();
|
||||||
void mouseAccelerationChanged();
|
void mouseAccelerationChanged();
|
||||||
void audioConfigChanged();
|
void audioConfigChanged();
|
||||||
void videoCodecConfigChanged();
|
void videoCodecConfigChanged();
|
||||||
|
|||||||
@@ -4,6 +4,13 @@
|
|||||||
|
|
||||||
#include <QtGlobal>
|
#include <QtGlobal>
|
||||||
|
|
||||||
|
// This determines the size of the buffers we'll
|
||||||
|
// get from CoreAudio. Since GFE sends us packets
|
||||||
|
// in 5 ms chunks, we'll give them to the OS in
|
||||||
|
// buffers of the same size. It is also the minimum
|
||||||
|
// size that we will write when called to fill a buffer.
|
||||||
|
const double SoundIoAudioRenderer::k_RawSampleLengthSec = 0.005;
|
||||||
|
|
||||||
SoundIoAudioRenderer::SoundIoAudioRenderer()
|
SoundIoAudioRenderer::SoundIoAudioRenderer()
|
||||||
: m_OpusChannelCount(0),
|
: m_OpusChannelCount(0),
|
||||||
m_SoundIo(nullptr),
|
m_SoundIo(nullptr),
|
||||||
@@ -152,17 +159,12 @@ bool SoundIoAudioRenderer::prepareForPlayback(const OPUS_MULTISTREAM_CONFIGURATI
|
|||||||
|
|
||||||
m_OutputStream->format = SoundIoFormatS16NE;
|
m_OutputStream->format = SoundIoFormatS16NE;
|
||||||
m_OutputStream->sample_rate = opusConfig->sampleRate;
|
m_OutputStream->sample_rate = opusConfig->sampleRate;
|
||||||
|
m_OutputStream->software_latency = k_RawSampleLengthSec;
|
||||||
m_OutputStream->name = "Moonlight";
|
m_OutputStream->name = "Moonlight";
|
||||||
m_OutputStream->userdata = this;
|
m_OutputStream->userdata = this;
|
||||||
m_OutputStream->error_callback = sioErrorCallback;
|
m_OutputStream->error_callback = sioErrorCallback;
|
||||||
m_OutputStream->write_callback = sioWriteCallback;
|
m_OutputStream->write_callback = sioWriteCallback;
|
||||||
|
|
||||||
// This determines the size of the buffers we'll
|
|
||||||
// get from CoreAudio. Since GFE sends us packets
|
|
||||||
// in 5 ms chunks, we'll give them to the OS in
|
|
||||||
// buffers of the same size.
|
|
||||||
m_OutputStream->software_latency = 0.005;
|
|
||||||
|
|
||||||
SoundIoChannelLayout bestLayout = m_Device->current_layout;
|
SoundIoChannelLayout bestLayout = m_Device->current_layout;
|
||||||
for (int i = 0; i < m_Device->layout_count; i++) {
|
for (int i = 0; i < m_Device->layout_count; i++) {
|
||||||
if (scoreChannelLayout(&bestLayout, opusConfig) <
|
if (scoreChannelLayout(&bestLayout, opusConfig) <
|
||||||
@@ -331,13 +333,20 @@ void SoundIoAudioRenderer::sioWriteCallback(SoundIoOutStream* stream, int frameC
|
|||||||
// Clamp framesLeft to frameCountMax
|
// Clamp framesLeft to frameCountMax
|
||||||
framesLeft = qMin(framesLeft, frameCountMax);
|
framesLeft = qMin(framesLeft, frameCountMax);
|
||||||
|
|
||||||
|
// Ensure we always write at least a buffer, even if it's silence, to avoid
|
||||||
|
// busy looping when no audio data is available while libsoundio tries to keep
|
||||||
|
// us from starving the output device.
|
||||||
|
frameCountMin = qMax(frameCountMin, (int)(stream->sample_rate * k_RawSampleLengthSec));
|
||||||
|
frameCountMin = qMin(frameCountMin, frameCountMax);
|
||||||
|
|
||||||
// Place an upper-bound on audio stream latency to
|
// Place an upper-bound on audio stream latency to
|
||||||
// avoid accumulating packets in queue-based backends
|
// avoid accumulating packets in queue-based backends
|
||||||
// like WASAPI. This bound was set by testing on several
|
// like WASAPI, ALSA, and PulseAudio.
|
||||||
// Windows machines. The highest latency was found on
|
//
|
||||||
// a XPS 9343 running Windows 7 in Steam Big Picture
|
// This bound was set by testing on several Windows machines.
|
||||||
// and the 5.1 audio test clip from Fraunhofer.
|
// The highest latency was found on a XPS 9343 running Windows 7
|
||||||
if (me->m_SoundIo->current_backend == SoundIoBackendWasapi) {
|
// in Steam Big Picture and the 5.1 audio test clip from Fraunhofer.
|
||||||
|
if (me->m_SoundIo->current_backend != SoundIoBackendCoreAudio && me->m_SoundIo->current_backend != SoundIoBackendJack) {
|
||||||
double latency;
|
double latency;
|
||||||
if (soundio_outstream_get_latency(stream, &latency) == SoundIoErrorNone) {
|
if (soundio_outstream_get_latency(stream, &latency) == SoundIoErrorNone) {
|
||||||
if (latency > 0.050) {
|
if (latency > 0.050) {
|
||||||
|
|||||||
@@ -33,4 +33,6 @@ private:
|
|||||||
struct SoundIoRingBuffer* m_RingBuffer;
|
struct SoundIoRingBuffer* m_RingBuffer;
|
||||||
struct SoundIoChannelLayout m_EffectiveLayout;
|
struct SoundIoChannelLayout m_EffectiveLayout;
|
||||||
bool m_Errored;
|
bool m_Errored;
|
||||||
|
|
||||||
|
static const double k_RawSampleLengthSec;
|
||||||
};
|
};
|
||||||
+66
-24
@@ -73,6 +73,7 @@ void Session::clStageFailed(int stage, long errorCode)
|
|||||||
|
|
||||||
void Session::clConnectionTerminated(long errorCode)
|
void Session::clConnectionTerminated(long errorCode)
|
||||||
{
|
{
|
||||||
|
s_ActiveSession->m_UnexpectedTermination = true;
|
||||||
emit s_ActiveSession->displayLaunchError("Connection terminated");
|
emit s_ActiveSession->displayLaunchError("Connection terminated");
|
||||||
|
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
@@ -287,12 +288,21 @@ Session::Session(NvComputer* computer, NvApp& app, StreamingPreferences *prefere
|
|||||||
m_DisplayOriginX(0),
|
m_DisplayOriginX(0),
|
||||||
m_DisplayOriginY(0),
|
m_DisplayOriginY(0),
|
||||||
m_PendingWindowedTransition(false),
|
m_PendingWindowedTransition(false),
|
||||||
|
m_UnexpectedTermination(true), // Failure prior to streaming is unexpected
|
||||||
m_OpusDecoder(nullptr),
|
m_OpusDecoder(nullptr),
|
||||||
m_AudioRenderer(nullptr),
|
m_AudioRenderer(nullptr),
|
||||||
m_AudioSampleCount(0)
|
m_AudioSampleCount(0)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Session::~Session()
|
||||||
|
{
|
||||||
|
// Acquire session semaphore to ensure all cleanup is done before the destructor returns
|
||||||
|
// and the object is deallocated.
|
||||||
|
s_ActiveSessionSemaphore.acquire();
|
||||||
|
s_ActiveSessionSemaphore.release();
|
||||||
|
}
|
||||||
|
|
||||||
void Session::initialize()
|
void Session::initialize()
|
||||||
{
|
{
|
||||||
qInfo() << "Server GPU:" << m_Computer->gpuModel;
|
qInfo() << "Server GPU:" << m_Computer->gpuModel;
|
||||||
@@ -315,6 +325,8 @@ void Session::initialize()
|
|||||||
m_StreamConfig.fps = m_Preferences->fps;
|
m_StreamConfig.fps = m_Preferences->fps;
|
||||||
m_StreamConfig.bitrate = m_Preferences->bitrateKbps;
|
m_StreamConfig.bitrate = m_Preferences->bitrateKbps;
|
||||||
m_StreamConfig.hevcBitratePercentageMultiplier = 75;
|
m_StreamConfig.hevcBitratePercentageMultiplier = 75;
|
||||||
|
m_StreamConfig.streamingRemotely = STREAM_CFG_AUTO;
|
||||||
|
m_StreamConfig.packetSize = 1392;
|
||||||
|
|
||||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
"Video bitrate: %d kbps",
|
"Video bitrate: %d kbps",
|
||||||
@@ -382,20 +394,6 @@ void Session::initialize()
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (m_Computer->activeAddress == m_Computer->remoteAddress) {
|
|
||||||
m_StreamConfig.streamingRemotely = 1;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
m_StreamConfig.streamingRemotely = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (m_Computer->activeAddress == m_Computer->localAddress) {
|
|
||||||
m_StreamConfig.packetSize = 1392;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
m_StreamConfig.packetSize = 1024;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (m_Preferences->windowMode)
|
switch (m_Preferences->windowMode)
|
||||||
{
|
{
|
||||||
case StreamingPreferences::WM_FULLSCREEN_DESKTOP:
|
case StreamingPreferences::WM_FULLSCREEN_DESKTOP:
|
||||||
@@ -563,15 +561,53 @@ bool Session::validateLaunch()
|
|||||||
|
|
||||||
class DeferredSessionCleanupTask : public QRunnable
|
class DeferredSessionCleanupTask : public QRunnable
|
||||||
{
|
{
|
||||||
void run() override
|
public:
|
||||||
{
|
DeferredSessionCleanupTask(Session* session) :
|
||||||
// Finish cleanup of the connection state
|
m_Session(session) {}
|
||||||
LiStopConnection();
|
|
||||||
|
|
||||||
|
private:
|
||||||
|
virtual ~DeferredSessionCleanupTask() override
|
||||||
|
{
|
||||||
// Allow another session to start now that we're cleaned up
|
// Allow another session to start now that we're cleaned up
|
||||||
Session::s_ActiveSession = nullptr;
|
Session::s_ActiveSession = nullptr;
|
||||||
Session::s_ActiveSessionSemaphore.release();
|
Session::s_ActiveSessionSemaphore.release();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void run() override
|
||||||
|
{
|
||||||
|
// Only quit the running app if our session terminated gracefully
|
||||||
|
bool shouldQuit =
|
||||||
|
!m_Session->m_UnexpectedTermination &&
|
||||||
|
m_Session->m_Preferences->quitAppAfter;
|
||||||
|
|
||||||
|
// Notify the UI
|
||||||
|
if (shouldQuit) {
|
||||||
|
emit m_Session->quitStarting();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
emit m_Session->sessionFinished();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finish cleanup of the connection state
|
||||||
|
LiStopConnection();
|
||||||
|
|
||||||
|
// Perform a best-effort app quit
|
||||||
|
if (shouldQuit) {
|
||||||
|
NvHTTP http(m_Session->m_Computer->activeAddress);
|
||||||
|
|
||||||
|
// Logging is already done inside NvHTTP
|
||||||
|
try {
|
||||||
|
http.quitApp();
|
||||||
|
} catch (const GfeHttpResponseException&) {
|
||||||
|
} catch (const QtNetworkReplyException&) {
|
||||||
|
}
|
||||||
|
|
||||||
|
// Session is finished now
|
||||||
|
emit m_Session->sessionFinished();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Session* m_Session;
|
||||||
};
|
};
|
||||||
|
|
||||||
void Session::getWindowDimensions(int& x, int& y,
|
void Session::getWindowDimensions(int& x, int& y,
|
||||||
@@ -745,6 +781,7 @@ void Session::exec(int displayOriginX, int displayOriginY)
|
|||||||
// Check for validation errors/warnings and emit
|
// Check for validation errors/warnings and emit
|
||||||
// signals for them, if appropriate
|
// signals for them, if appropriate
|
||||||
if (!validateLaunch()) {
|
if (!validateLaunch()) {
|
||||||
|
emit sessionFinished();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -787,11 +824,11 @@ void Session::exec(int displayOriginX, int displayOriginY)
|
|||||||
}
|
}
|
||||||
} catch (const GfeHttpResponseException& e) {
|
} catch (const GfeHttpResponseException& e) {
|
||||||
emit displayLaunchError(e.toQString());
|
emit displayLaunchError(e.toQString());
|
||||||
s_ActiveSessionSemaphore.release();
|
QThreadPool::globalInstance()->start(new DeferredSessionCleanupTask(this));
|
||||||
return;
|
return;
|
||||||
} catch (const QtNetworkReplyException& e) {
|
} catch (const QtNetworkReplyException& e) {
|
||||||
emit displayLaunchError(e.toQString());
|
emit displayLaunchError(e.toQString());
|
||||||
s_ActiveSessionSemaphore.release();
|
QThreadPool::globalInstance()->start(new DeferredSessionCleanupTask(this));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -801,7 +838,7 @@ void Session::exec(int displayOriginX, int displayOriginY)
|
|||||||
"SDL_InitSubSystem(SDL_INIT_VIDEO) failed: %s",
|
"SDL_InitSubSystem(SDL_INIT_VIDEO) failed: %s",
|
||||||
SDL_GetError());
|
SDL_GetError());
|
||||||
emit displayLaunchError(QString::fromLocal8Bit(SDL_GetError()));
|
emit displayLaunchError(QString::fromLocal8Bit(SDL_GetError()));
|
||||||
s_ActiveSessionSemaphore.release();
|
QThreadPool::globalInstance()->start(new DeferredSessionCleanupTask(this));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -829,7 +866,7 @@ void Session::exec(int displayOriginX, int displayOriginY)
|
|||||||
// We already displayed an error dialog in the stage failure
|
// We already displayed an error dialog in the stage failure
|
||||||
// listener.
|
// listener.
|
||||||
SDL_QuitSubSystem(SDL_INIT_VIDEO);
|
SDL_QuitSubSystem(SDL_INIT_VIDEO);
|
||||||
s_ActiveSessionSemaphore.release();
|
QThreadPool::globalInstance()->start(new DeferredSessionCleanupTask(this));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -851,7 +888,7 @@ void Session::exec(int displayOriginX, int displayOriginY)
|
|||||||
"SDL_CreateWindow() failed: %s",
|
"SDL_CreateWindow() failed: %s",
|
||||||
SDL_GetError());
|
SDL_GetError());
|
||||||
SDL_QuitSubSystem(SDL_INIT_VIDEO);
|
SDL_QuitSubSystem(SDL_INIT_VIDEO);
|
||||||
s_ActiveSessionSemaphore.release();
|
QThreadPool::globalInstance()->start(new DeferredSessionCleanupTask(this));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -931,6 +968,11 @@ void Session::exec(int displayOriginX, int displayOriginY)
|
|||||||
|
|
||||||
int currentDisplayIndex = SDL_GetWindowDisplayIndex(m_Window);
|
int currentDisplayIndex = SDL_GetWindowDisplayIndex(m_Window);
|
||||||
|
|
||||||
|
// Now that we're about to stream, any SDL_QUIT event is expected
|
||||||
|
// unless it comes from the connection termination callback where
|
||||||
|
// (m_UnexpectedTermination is set back to true).
|
||||||
|
m_UnexpectedTermination = false;
|
||||||
|
|
||||||
// Hijack this thread to be the SDL main thread. We have to do this
|
// Hijack this thread to be the SDL main thread. We have to do this
|
||||||
// because we want to suspend all Qt processing until the stream is over.
|
// because we want to suspend all Qt processing until the stream is over.
|
||||||
SDL_Event event;
|
SDL_Event event;
|
||||||
@@ -1164,6 +1206,6 @@ DispatchDeferredCleanup:
|
|||||||
// Cleanup can take a while, so dispatch it to a worker thread.
|
// Cleanup can take a while, so dispatch it to a worker thread.
|
||||||
// When it is complete, it will release our s_ActiveSessionSemaphore
|
// When it is complete, it will release our s_ActiveSessionSemaphore
|
||||||
// reference.
|
// reference.
|
||||||
QThreadPool::globalInstance()->start(new DeferredSessionCleanupTask());
|
QThreadPool::globalInstance()->start(new DeferredSessionCleanupTask(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -20,6 +20,8 @@ class Session : public QObject
|
|||||||
public:
|
public:
|
||||||
explicit Session(NvComputer* computer, NvApp& app, StreamingPreferences *preferences = nullptr);
|
explicit Session(NvComputer* computer, NvApp& app, StreamingPreferences *preferences = nullptr);
|
||||||
|
|
||||||
|
virtual ~Session();
|
||||||
|
|
||||||
Q_INVOKABLE void exec(int displayOriginX, int displayOriginY);
|
Q_INVOKABLE void exec(int displayOriginX, int displayOriginY);
|
||||||
|
|
||||||
static
|
static
|
||||||
@@ -41,6 +43,10 @@ signals:
|
|||||||
|
|
||||||
void displayLaunchWarning(QString text);
|
void displayLaunchWarning(QString text);
|
||||||
|
|
||||||
|
void quitStarting();
|
||||||
|
|
||||||
|
void sessionFinished();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void initialize();
|
void initialize();
|
||||||
|
|
||||||
@@ -114,6 +120,7 @@ private:
|
|||||||
int m_DisplayOriginX;
|
int m_DisplayOriginX;
|
||||||
int m_DisplayOriginY;
|
int m_DisplayOriginY;
|
||||||
bool m_PendingWindowedTransition;
|
bool m_PendingWindowedTransition;
|
||||||
|
bool m_UnexpectedTermination;
|
||||||
|
|
||||||
int m_ActiveVideoFormat;
|
int m_ActiveVideoFormat;
|
||||||
int m_ActiveVideoWidth;
|
int m_ActiveVideoWidth;
|
||||||
|
|||||||
Submodule moonlight-common-c/moonlight-common-c updated: 396b02a94d...80eba25bb9.
+1
-1
Submodule soundio/libsoundio updated: 5d6a7210fa...fb23636c95.
Reference in new issue
Block a user