mirror of
https://github.com/moonlight-stream/moonlight-qt.git
synced 2026-10-22 16:17:20 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
860d317330 | ||
|
|
bab11a731e | ||
|
|
df9e7bead0 | ||
|
|
962dc847e7 | ||
|
|
566181e8d7 | ||
|
|
5f5f835b2e | ||
|
|
c0e697eb58 | ||
|
|
d3c2f50b20 | ||
|
|
f1ee1eeb63 | ||
|
|
bb18519e44 | ||
|
|
8e2ad133ea | ||
|
|
5e82e8413f | ||
|
|
917f6e5ee9 | ||
|
|
e1ca6e644e | ||
|
|
f1e89242cb | ||
|
|
91145cde0f | ||
|
|
8ae01fae3f | ||
|
|
4e60ccb2ff | ||
|
|
186052f21d | ||
|
|
0fb3bb0727 | ||
|
|
8f61a11452 | ||
|
|
4b1d3b0125 | ||
|
|
3f81d055af | ||
|
|
c61182bcb2 | ||
|
|
2fdfb3e998 |
No files matched your search
@@ -0,0 +1,8 @@
|
|||||||
|
# ProBot No Response (https://probot.github.io/apps/no-response/)
|
||||||
|
|
||||||
|
daysUntilClose: 7
|
||||||
|
responseRequiredLabel: 'need more info'
|
||||||
|
closeComment: >
|
||||||
|
This issue has been automatically closed because there was no response to a
|
||||||
|
request for more information from the issue opener. Please leave a comment or
|
||||||
|
open a new issue if you have additional information related to this issue.
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
# ProBot Stale (https://probot.github.io/apps/stale/)
|
||||||
|
|
||||||
|
daysUntilStale: 90
|
||||||
|
daysUntilClose: 7
|
||||||
|
exemptLabels:
|
||||||
|
- accepted
|
||||||
|
- bug
|
||||||
|
- enhancement
|
||||||
|
- meta
|
||||||
|
staleLabel: stale
|
||||||
|
markComment: >
|
||||||
|
This issue has been automatically marked as stale because it has not had
|
||||||
|
recent activity. It will be closed if no further activity occurs.
|
||||||
|
closeComment: false
|
||||||
@@ -22,6 +22,9 @@ matrix:
|
|||||||
- qt59svg
|
- qt59svg
|
||||||
- libesd0-dev
|
- libesd0-dev
|
||||||
- libgl-dev
|
- libgl-dev
|
||||||
|
- libegl1-mesa-dev
|
||||||
|
- libxkbcommon-dev
|
||||||
|
- wayland-protocols
|
||||||
- libopus-dev
|
- libopus-dev
|
||||||
- libavcodec-dev
|
- libavcodec-dev
|
||||||
- libavutil-dev
|
- libavutil-dev
|
||||||
|
|||||||
@@ -21,7 +21,10 @@ You can follow development on our [Discord server](https://discord.gg/6ERtzFY).
|
|||||||
- Gamepad support with SDL gamepad mappings
|
- Gamepad support with SDL gamepad mappings
|
||||||
|
|
||||||
## Building
|
## Building
|
||||||
1. Install the latest Qt SDK (and optionally, the Qt Creator IDE) from https://www.qt.io/download (select MSVC toolchain on Windows)
|
1. Install the latest Qt SDK (and optionally, the Qt Creator IDE) from https://www.qt.io/download
|
||||||
|
- On Windows, install Visual Studio 2017 and select MSVC 2017 toolchain during Qt installation
|
||||||
|
- On Mac, install the latest version of XCode
|
||||||
|
- On Linux, install your distro equivalents of: `openssl-devel qt5-devel SDL2-devel ffmpeg-devel qt5-qtquickcontrols2-devel libva-devel libvdpau-devel`
|
||||||
2. Run `git submodule update --init --recursive` from within `moonlight-qt/`
|
2. Run `git submodule update --init --recursive` from within `moonlight-qt/`
|
||||||
3. Open the project in Qt Creator or build from qmake on the command line
|
3. Open the project in Qt Creator or build from qmake on the command line
|
||||||
|
|
||||||
|
|||||||
+4
-2
@@ -17,13 +17,15 @@
|
|||||||
<key>CFBundleSignature</key>
|
<key>CFBundleSignature</key>
|
||||||
<string>????</string>
|
<string>????</string>
|
||||||
<key>LSMinimumSystemVersion</key>
|
<key>LSMinimumSystemVersion</key>
|
||||||
<string>10.10</string>
|
<string>10.11</string>
|
||||||
<key>NSPrincipalClass</key>
|
<key>NSPrincipalClass</key>
|
||||||
<string>NSApplication</string>
|
<string>NSApplication</string>
|
||||||
<key>NSSupportsAutomaticGraphicsSwitching</key>
|
<key>NSSupportsAutomaticGraphicsSwitching</key>
|
||||||
<true/>
|
<true/>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>0.1.0</string>
|
<string>0.2.0</string>
|
||||||
|
<key>CFBundleShortVersionString</key>
|
||||||
|
<string>0.2.0</string>
|
||||||
<key>CFBundleDisplayName</key>
|
<key>CFBundleDisplayName</key>
|
||||||
<string>Moonlight</string>
|
<string>Moonlight</string>
|
||||||
</dict>
|
</dict>
|
||||||
|
|||||||
+25
-4
@@ -44,13 +44,18 @@ macx {
|
|||||||
unix:!macx {
|
unix:!macx {
|
||||||
CONFIG += link_pkgconfig
|
CONFIG += link_pkgconfig
|
||||||
PKGCONFIG += openssl sdl2 opus
|
PKGCONFIG += openssl sdl2 opus
|
||||||
LIBS += -ldl
|
|
||||||
|
|
||||||
packagesExist(libavcodec) {
|
packagesExist(libavcodec) {
|
||||||
PKGCONFIG += libavcodec libavutil
|
PKGCONFIG += libavcodec libavutil
|
||||||
CONFIG += ffmpeg
|
CONFIG += ffmpeg
|
||||||
|
|
||||||
packagesExist(libva) {
|
packagesExist(libva) {
|
||||||
|
packagesExist(libva-x11) {
|
||||||
|
CONFIG += libva-x11
|
||||||
|
}
|
||||||
|
packagesExist(libva-wayland) {
|
||||||
|
CONFIG += libva-wayland
|
||||||
|
}
|
||||||
CONFIG += libva
|
CONFIG += libva
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -82,7 +87,8 @@ SOURCES += \
|
|||||||
streaming/audio.cpp \
|
streaming/audio.cpp \
|
||||||
gui/computermodel.cpp \
|
gui/computermodel.cpp \
|
||||||
gui/appmodel.cpp \
|
gui/appmodel.cpp \
|
||||||
streaming/streamutils.cpp
|
streaming/streamutils.cpp \
|
||||||
|
backend/autoupdatechecker.cpp
|
||||||
|
|
||||||
HEADERS += \
|
HEADERS += \
|
||||||
utils.h \
|
utils.h \
|
||||||
@@ -97,7 +103,8 @@ HEADERS += \
|
|||||||
gui/computermodel.h \
|
gui/computermodel.h \
|
||||||
gui/appmodel.h \
|
gui/appmodel.h \
|
||||||
streaming/video/decoder.h \
|
streaming/video/decoder.h \
|
||||||
streaming/streamutils.h
|
streaming/streamutils.h \
|
||||||
|
backend/autoupdatechecker.h
|
||||||
|
|
||||||
# Platform-specific renderers and decoders
|
# Platform-specific renderers and decoders
|
||||||
ffmpeg {
|
ffmpeg {
|
||||||
@@ -114,10 +121,23 @@ ffmpeg {
|
|||||||
libva {
|
libva {
|
||||||
message(VAAPI renderer selected)
|
message(VAAPI renderer selected)
|
||||||
|
|
||||||
|
PKGCONFIG += libva
|
||||||
DEFINES += HAVE_LIBVA
|
DEFINES += HAVE_LIBVA
|
||||||
SOURCES += streaming/video/ffmpeg-renderers/vaapi.cpp
|
SOURCES += streaming/video/ffmpeg-renderers/vaapi.cpp
|
||||||
HEADERS += streaming/video/ffmpeg-renderers/vaapi.h
|
HEADERS += streaming/video/ffmpeg-renderers/vaapi.h
|
||||||
}
|
}
|
||||||
|
libva-x11 {
|
||||||
|
message(VAAPI X11 support enabled)
|
||||||
|
|
||||||
|
PKGCONFIG += libva-x11
|
||||||
|
DEFINES += HAVE_LIBVA_X11
|
||||||
|
}
|
||||||
|
libva-wayland {
|
||||||
|
message(VAAPI Wayland support enabled)
|
||||||
|
|
||||||
|
PKGCONFIG += libva-wayland
|
||||||
|
DEFINES += HAVE_LIBVA_WAYLAND
|
||||||
|
}
|
||||||
libvdpau {
|
libvdpau {
|
||||||
message(VDPAU renderer selected)
|
message(VDPAU renderer selected)
|
||||||
|
|
||||||
@@ -202,4 +222,5 @@ macx {
|
|||||||
QMAKE_BUNDLE_DATA += APP_BUNDLE_RESOURCES
|
QMAKE_BUNDLE_DATA += APP_BUNDLE_RESOURCES
|
||||||
}
|
}
|
||||||
|
|
||||||
VERSION = 0.1.0
|
VERSION = 0.2.0
|
||||||
|
DEFINES += VERSION_STR=\\\"0.2.0\\\"
|
||||||
@@ -0,0 +1,142 @@
|
|||||||
|
#include "autoupdatechecker.h"
|
||||||
|
|
||||||
|
#include <QNetworkReply>
|
||||||
|
#include <QJsonDocument>
|
||||||
|
#include <QJsonArray>
|
||||||
|
#include <QJsonObject>
|
||||||
|
|
||||||
|
AutoUpdateChecker::AutoUpdateChecker(QObject *parent) :
|
||||||
|
QObject(parent),
|
||||||
|
m_Nam(this)
|
||||||
|
{
|
||||||
|
// Never communicate over HTTP
|
||||||
|
m_Nam.setStrictTransportSecurityEnabled(true);
|
||||||
|
|
||||||
|
connect(&m_Nam, SIGNAL(finished(QNetworkReply*)),
|
||||||
|
this, SLOT(handleUpdateCheckRequestFinished(QNetworkReply*)));
|
||||||
|
|
||||||
|
QString currentVersion(VERSION_STR);
|
||||||
|
qDebug() << "Current Moonlight version:" << currentVersion;
|
||||||
|
parseStringToVersionQuad(currentVersion, m_CurrentVersionQuad);
|
||||||
|
|
||||||
|
// Should at least have a 1.0-style version number
|
||||||
|
Q_ASSERT(m_CurrentVersionQuad.count() > 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AutoUpdateChecker::start()
|
||||||
|
{
|
||||||
|
#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
|
||||||
|
QUrl url("https://moonlight-stream.com/updates/qt.json");
|
||||||
|
m_Nam.get(QNetworkRequest(url));
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void AutoUpdateChecker::parseStringToVersionQuad(QString& string, QVector<int>& version)
|
||||||
|
{
|
||||||
|
QStringList list = string.split('.');
|
||||||
|
for (const QString& component : list) {
|
||||||
|
version.append(component.toInt());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AutoUpdateChecker::handleUpdateCheckRequestFinished(QNetworkReply* reply)
|
||||||
|
{
|
||||||
|
Q_ASSERT(reply->isFinished());
|
||||||
|
|
||||||
|
if (reply->error() == QNetworkReply::NoError) {
|
||||||
|
QTextStream stream(reply);
|
||||||
|
stream.setCodec("UTF-8");
|
||||||
|
|
||||||
|
// Read all data and queue the reply for deletion
|
||||||
|
QString jsonString = stream.readAll();
|
||||||
|
reply->deleteLater();
|
||||||
|
|
||||||
|
QJsonParseError error;
|
||||||
|
QJsonDocument jsonDoc = QJsonDocument::fromJson(jsonString.toUtf8(), &error);
|
||||||
|
if (jsonDoc.isNull()) {
|
||||||
|
qWarning() << "Update manifest malformed:" << error.errorString();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QJsonArray array = jsonDoc.array();
|
||||||
|
if (array.isEmpty()) {
|
||||||
|
qWarning() << "Update manifest doesn't contain an array";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (QJsonValueRef updateEntry : array) {
|
||||||
|
if (updateEntry.isObject()) {
|
||||||
|
QJsonObject updateObj = updateEntry.toObject();
|
||||||
|
if (!updateObj.contains("platform") ||
|
||||||
|
!updateObj.contains("arch") ||
|
||||||
|
!updateObj.contains("version") ||
|
||||||
|
!updateObj.contains("browser_url")) {
|
||||||
|
qWarning() << "Update manifest entry missing vital field";
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!updateObj["platform"].isString() ||
|
||||||
|
!updateObj["arch"].isString() ||
|
||||||
|
!updateObj["version"].isString() ||
|
||||||
|
!updateObj["browser_url"].isString()) {
|
||||||
|
qWarning() << "Update manifest entry has unexpected vital field type";
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (updateObj["arch"] == QSysInfo::buildCpuArchitecture() &&
|
||||||
|
updateObj["platform"] == QSysInfo::productType()) {
|
||||||
|
qDebug() << "Found update manifest match for current platform";
|
||||||
|
|
||||||
|
QString latestVersion = updateObj["version"].toString();
|
||||||
|
qDebug() << "Latest version of Moonlight for this platform is:" << latestVersion;
|
||||||
|
|
||||||
|
QVector<int> latestVersionQuad;
|
||||||
|
|
||||||
|
parseStringToVersionQuad(latestVersion, latestVersionQuad);
|
||||||
|
|
||||||
|
for (int i = 0;; i++) {
|
||||||
|
int latestVer = 0;
|
||||||
|
int currentVer = 0;
|
||||||
|
|
||||||
|
// Treat missing decimal places as 0
|
||||||
|
if (i < latestVersionQuad.count()) {
|
||||||
|
latestVer = latestVersionQuad[i];
|
||||||
|
}
|
||||||
|
if (i < m_CurrentVersionQuad.count()) {
|
||||||
|
currentVer = m_CurrentVersionQuad[i];
|
||||||
|
}
|
||||||
|
if (i >= latestVersionQuad.count() && i >= m_CurrentVersionQuad.count()) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentVer < latestVer) {
|
||||||
|
qDebug() << "Update available";
|
||||||
|
|
||||||
|
emit onUpdateAvailable(updateObj["browser_url"].toString());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else if (currentVer > latestVer) {
|
||||||
|
qDebug() << "Update manifest version lower than current version";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
qDebug() << "Update manifest version equal to current version";
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
qWarning() << "Update manifest contained unrecognized entry:" << updateEntry.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
qWarning() << "No entry in update manifest found for current platform: "
|
||||||
|
<< QSysInfo::buildCpuArchitecture() << QSysInfo::productType();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
qWarning() << "Update checking failed with error: " << reply->error();
|
||||||
|
reply->deleteLater();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
#include <QNetworkAccessManager>
|
||||||
|
|
||||||
|
class AutoUpdateChecker : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
explicit AutoUpdateChecker(QObject *parent = nullptr);
|
||||||
|
|
||||||
|
Q_INVOKABLE void start();
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void onUpdateAvailable(QString url);
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void handleUpdateCheckRequestFinished(QNetworkReply* reply);
|
||||||
|
|
||||||
|
private:
|
||||||
|
void parseStringToVersionQuad(QString& string, QVector<int>& version);
|
||||||
|
|
||||||
|
QVector<int> m_CurrentVersionQuad;
|
||||||
|
QNetworkAccessManager m_Nam;
|
||||||
|
};
|
||||||
@@ -33,6 +33,18 @@
|
|||||||
</screenshots>
|
</screenshots>
|
||||||
|
|
||||||
<releases>
|
<releases>
|
||||||
|
<release version="0.2.0" date="2018-08-12">
|
||||||
|
<description>
|
||||||
|
<p>Bugfixes:</p>
|
||||||
|
<ul>
|
||||||
|
<li>Fixed functionality of '=', 'Numpad Dot', and 'Numpad Enter' keys</li>
|
||||||
|
<li>Fixed uninitialized variable causing strange gamepad behavior</li>
|
||||||
|
<li>Added a warning for incompatible configs and decoder failures</li>
|
||||||
|
<li>Added a warning for Wayland performance issues</li>
|
||||||
|
<li>Stopped capturing the mouse by default for windowed streams</li>
|
||||||
|
</ul>
|
||||||
|
</description>
|
||||||
|
</release>
|
||||||
<release version="0.1.0" date="2018-08-05">
|
<release version="0.1.0" date="2018-08-05">
|
||||||
<description>
|
<description>
|
||||||
<p>New features:</p>
|
<p>New features:</p>
|
||||||
|
|||||||
@@ -28,14 +28,24 @@ GridView {
|
|||||||
// routine to run, but only if we start as invisible
|
// routine to run, but only if we start as invisible
|
||||||
visible: false
|
visible: false
|
||||||
|
|
||||||
|
function computerLost()
|
||||||
|
{
|
||||||
|
// Go back to the PC view on PC loss
|
||||||
|
stackView.pop()
|
||||||
|
}
|
||||||
|
|
||||||
onVisibleChanged: {
|
onVisibleChanged: {
|
||||||
if (visible) {
|
if (visible) {
|
||||||
// Start polling when this view is shown
|
// Start polling when this view is shown
|
||||||
ComputerManager.startPolling()
|
ComputerManager.startPolling()
|
||||||
|
|
||||||
|
appModel.computerLost.connect(computerLost)
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// Stop polling when this view is not on top
|
// Stop polling when this view is not on top
|
||||||
ComputerManager.stopPollingAsync()
|
ComputerManager.stopPollingAsync()
|
||||||
|
|
||||||
|
appModel.computerLost.disconnect(computerLost)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+16
-1
@@ -34,7 +34,10 @@ GridView {
|
|||||||
// Setup signals on CM
|
// Setup signals on CM
|
||||||
ComputerManager.computerAddCompleted.connect(addComplete)
|
ComputerManager.computerAddCompleted.connect(addComplete)
|
||||||
|
|
||||||
if (!prefs.hasAnyHardwareAcceleration()) {
|
if (prefs.isRunningWayland()) {
|
||||||
|
waylandDialog.open()
|
||||||
|
}
|
||||||
|
else if (!prefs.hasAnyHardwareAcceleration()) {
|
||||||
noHwDecoderDialog.open()
|
noHwDecoderDialog.open()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -224,6 +227,18 @@ GridView {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MessageDialog {
|
||||||
|
id: waylandDialog
|
||||||
|
modality:Qt.WindowModal
|
||||||
|
icon: StandardIcon.Warning
|
||||||
|
standardButtons: StandardButton.Ok | StandardButton.Help
|
||||||
|
text: "Moonlight does not support hardware acceleration on Wayland. Continuing on Wayland may result in poor streaming performance. " +
|
||||||
|
"Please switch to an X session for optimal performance."
|
||||||
|
onHelp: {
|
||||||
|
Qt.openUrlExternally("https://github.com/moonlight-stream/moonlight-docs/wiki/Fixing-Hardware-Decoding-Problems");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
MessageDialog {
|
MessageDialog {
|
||||||
id: pairDialog
|
id: pairDialog
|
||||||
// don't allow edits to the rest of the window while open
|
// don't allow edits to the rest of the window while open
|
||||||
|
|||||||
@@ -108,6 +108,14 @@ void AppModel::handleComputerStateChanged(NvComputer* computer)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If the computer has gone offline or we've been unpaired,
|
||||||
|
// signal the UI so we can go back to the PC view.
|
||||||
|
if (m_Computer->state == NvComputer::CS_OFFLINE ||
|
||||||
|
m_Computer->pairState == NvComputer::PS_NOT_PAIRED) {
|
||||||
|
emit computerLost();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// First, process additions/removals from the app list. This
|
// First, process additions/removals from the app list. This
|
||||||
// is required because the new game may now be running, so
|
// is required because the new game may now be running, so
|
||||||
// we can't check that first.
|
// we can't check that first.
|
||||||
|
|||||||
@@ -42,6 +42,9 @@ private slots:
|
|||||||
|
|
||||||
void handleBoxArtLoaded(NvComputer* computer, NvApp app, QUrl image);
|
void handleBoxArtLoaded(NvComputer* computer, NvApp app, QUrl image);
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void computerLost();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
NvComputer* m_Computer;
|
NvComputer* m_Computer;
|
||||||
BoxArtManager m_BoxArtManager;
|
BoxArtManager m_BoxArtManager;
|
||||||
|
|||||||
+53
-3
@@ -4,6 +4,8 @@ import QtQuick.Layouts 1.3
|
|||||||
|
|
||||||
import QtQuick.Controls.Material 2.1
|
import QtQuick.Controls.Material 2.1
|
||||||
|
|
||||||
|
import AutoUpdateChecker 1.0
|
||||||
|
|
||||||
ApplicationWindow {
|
ApplicationWindow {
|
||||||
id: window
|
id: window
|
||||||
visible: true
|
visible: true
|
||||||
@@ -66,14 +68,54 @@ ApplicationWindow {
|
|||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ToolButton {
|
||||||
|
property string browserUrl: ""
|
||||||
|
|
||||||
|
id: updateButton
|
||||||
|
icon.source: "qrc:/res/update.svg"
|
||||||
|
icon.color: "#0BF200"
|
||||||
|
|
||||||
|
ToolTip.delay: 1000
|
||||||
|
ToolTip.timeout: 3000
|
||||||
|
ToolTip.visible: hovered
|
||||||
|
ToolTip.text: "Update available for Moonlight"
|
||||||
|
|
||||||
|
// Invisible until we get a callback notifying us that
|
||||||
|
// an update is available
|
||||||
|
visible: false
|
||||||
|
|
||||||
|
onClicked: Qt.openUrlExternally(browserUrl);
|
||||||
|
|
||||||
|
Menu {
|
||||||
|
x: parent.width
|
||||||
|
transformOrigin: Menu.TopRight
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function updateAvailable(url)
|
||||||
|
{
|
||||||
|
updateButton.browserUrl = url
|
||||||
|
updateButton.visible = true
|
||||||
|
}
|
||||||
|
|
||||||
|
Component.onCompleted: {
|
||||||
|
AutoUpdateChecker.onUpdateAvailable.connect(updateAvailable)
|
||||||
|
AutoUpdateChecker.start()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ToolButton {
|
ToolButton {
|
||||||
icon.source: "qrc:/res/question_mark.svg"
|
icon.source: "qrc:/res/question_mark.svg"
|
||||||
|
|
||||||
|
ToolTip.delay: 1000
|
||||||
|
ToolTip.timeout: 3000
|
||||||
|
ToolTip.visible: hovered
|
||||||
|
ToolTip.text: "Help"
|
||||||
|
|
||||||
// TODO need to make sure browser is brought to foreground.
|
// TODO need to make sure browser is brought to foreground.
|
||||||
onClicked: Qt.openUrlExternally("https://github.com/moonlight-stream/moonlight-docs/wiki/Setup-Guide");
|
onClicked: Qt.openUrlExternally("https://github.com/moonlight-stream/moonlight-docs/wiki/Setup-Guide");
|
||||||
|
|
||||||
Menu {
|
Menu {
|
||||||
id: helpButton
|
|
||||||
x: parent.width
|
x: parent.width
|
||||||
transformOrigin: Menu.TopRight
|
transformOrigin: Menu.TopRight
|
||||||
}
|
}
|
||||||
@@ -83,11 +125,15 @@ ApplicationWindow {
|
|||||||
// TODO: Implement gamepad mapping then unhide this button
|
// TODO: Implement gamepad mapping then unhide this button
|
||||||
visible: false
|
visible: false
|
||||||
|
|
||||||
|
ToolTip.delay: 1000
|
||||||
|
ToolTip.timeout: 3000
|
||||||
|
ToolTip.visible: hovered
|
||||||
|
ToolTip.text: "Gamepad Mapper"
|
||||||
|
|
||||||
icon.source: "qrc:/res/ic_videogame_asset_white_48px.svg"
|
icon.source: "qrc:/res/ic_videogame_asset_white_48px.svg"
|
||||||
onClicked: navigateTo("qrc:/gui/GamepadMapper.qml", "Gamepad Mapping")
|
onClicked: navigateTo("qrc:/gui/GamepadMapper.qml", "Gamepad Mapping")
|
||||||
|
|
||||||
Menu {
|
Menu {
|
||||||
id: gamepadMappingMenu
|
|
||||||
x: parent.width
|
x: parent.width
|
||||||
transformOrigin: Menu.TopRight
|
transformOrigin: Menu.TopRight
|
||||||
}
|
}
|
||||||
@@ -97,8 +143,12 @@ ApplicationWindow {
|
|||||||
icon.source: "qrc:/res/settings.svg"
|
icon.source: "qrc:/res/settings.svg"
|
||||||
onClicked: navigateTo("qrc:/gui/SettingsView.qml", "Settings")
|
onClicked: navigateTo("qrc:/gui/SettingsView.qml", "Settings")
|
||||||
|
|
||||||
|
ToolTip.delay: 1000
|
||||||
|
ToolTip.timeout: 3000
|
||||||
|
ToolTip.visible: hovered
|
||||||
|
ToolTip.text: "Settings"
|
||||||
|
|
||||||
Menu {
|
Menu {
|
||||||
id: optionsMenu
|
|
||||||
x: parent.width
|
x: parent.width
|
||||||
transformOrigin: Menu.TopRight
|
transformOrigin: Menu.TopRight
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
|
|
||||||
#include "gui/computermodel.h"
|
#include "gui/computermodel.h"
|
||||||
#include "gui/appmodel.h"
|
#include "gui/appmodel.h"
|
||||||
|
#include "backend/autoupdatechecker.h"
|
||||||
#include "streaming/session.hpp"
|
#include "streaming/session.hpp"
|
||||||
#include "settings/streamingpreferences.h"
|
#include "settings/streamingpreferences.h"
|
||||||
|
|
||||||
@@ -131,7 +132,13 @@ int main(int argc, char *argv[])
|
|||||||
{
|
{
|
||||||
#ifdef USE_CUSTOM_LOGGER
|
#ifdef USE_CUSTOM_LOGGER
|
||||||
#ifdef LOG_TO_FILE
|
#ifdef LOG_TO_FILE
|
||||||
|
#ifdef Q_OS_DARWIN
|
||||||
|
// On macOS, $TMPDIR is some random folder under /var/folders/ that nobody can
|
||||||
|
// easily find, so use the system's global tmp directory instead.
|
||||||
|
QDir tempDir("/tmp");
|
||||||
|
#else
|
||||||
QDir tempDir(QDir::tempPath());
|
QDir tempDir(QDir::tempPath());
|
||||||
|
#endif
|
||||||
s_LoggerFile = new QFile(tempDir.filePath(QString("Moonlight-%1.log").arg(QDateTime::currentSecsSinceEpoch())));
|
s_LoggerFile = new QFile(tempDir.filePath(QString("Moonlight-%1.log").arg(QDateTime::currentSecsSinceEpoch())));
|
||||||
if (s_LoggerFile->open(QIODevice::WriteOnly)) {
|
if (s_LoggerFile->open(QIODevice::WriteOnly)) {
|
||||||
qInfo() << "Redirecting log output to " << s_LoggerFile->fileName();
|
qInfo() << "Redirecting log output to " << s_LoggerFile->fileName();
|
||||||
@@ -172,6 +179,11 @@ int main(int argc, char *argv[])
|
|||||||
[](QQmlEngine*, QJSEngine*) -> QObject* {
|
[](QQmlEngine*, QJSEngine*) -> QObject* {
|
||||||
return new ComputerManager();
|
return new ComputerManager();
|
||||||
});
|
});
|
||||||
|
qmlRegisterSingletonType<AutoUpdateChecker>("AutoUpdateChecker", 1, 0,
|
||||||
|
"AutoUpdateChecker",
|
||||||
|
[](QQmlEngine*, QJSEngine*) -> QObject* {
|
||||||
|
return new AutoUpdateChecker();
|
||||||
|
});
|
||||||
|
|
||||||
QQuickStyle::setStyle("Material");
|
QQuickStyle::setStyle("Material");
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +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">
|
||||||
|
<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>
|
||||||
|
After Width: | Height: | Size: 484 B |
@@ -13,5 +13,6 @@
|
|||||||
<file>res/arrow_left.svg</file>
|
<file>res/arrow_left.svg</file>
|
||||||
<file>res/question_mark.svg</file>
|
<file>res/question_mark.svg</file>
|
||||||
<file>res/moonlight.svg</file>
|
<file>res/moonlight.svg</file>
|
||||||
|
<file>res/update.svg</file>
|
||||||
</qresource>
|
</qresource>
|
||||||
</RCC>
|
</RCC>
|
||||||
@@ -67,6 +67,11 @@ bool StreamingPreferences::hasAnyHardwareAcceleration()
|
|||||||
1920, 1080, 60);
|
1920, 1080, 60);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool StreamingPreferences::isRunningWayland()
|
||||||
|
{
|
||||||
|
return qgetenv("XDG_SESSION_TYPE") == QByteArray("wayland");
|
||||||
|
}
|
||||||
|
|
||||||
int StreamingPreferences::getMaximumStreamingFrameRate()
|
int StreamingPreferences::getMaximumStreamingFrameRate()
|
||||||
{
|
{
|
||||||
// Never let the maximum drop below 60 FPS
|
// Never let the maximum drop below 60 FPS
|
||||||
|
|||||||
@@ -17,6 +17,8 @@ public:
|
|||||||
|
|
||||||
Q_INVOKABLE static bool hasAnyHardwareAcceleration();
|
Q_INVOKABLE static bool hasAnyHardwareAcceleration();
|
||||||
|
|
||||||
|
Q_INVOKABLE static bool isRunningWayland();
|
||||||
|
|
||||||
Q_INVOKABLE static int getMaximumStreamingFrameRate();
|
Q_INVOKABLE static int getMaximumStreamingFrameRate();
|
||||||
|
|
||||||
Q_INVOKABLE QRect getDisplayResolution(int displayIndex);
|
Q_INVOKABLE QRect getDisplayResolution(int displayIndex);
|
||||||
|
|||||||
+11
-3
@@ -41,6 +41,10 @@ SdlInputHandler::SdlInputHandler(bool multiController)
|
|||||||
// Player 1 is always present in non-MC mode
|
// Player 1 is always present in non-MC mode
|
||||||
m_GamepadMask = 0x1;
|
m_GamepadMask = 0x1;
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
|
// Otherwise, detect gamepads on the fly
|
||||||
|
m_GamepadMask = 0;
|
||||||
|
}
|
||||||
|
|
||||||
SDL_zero(m_GamepadState);
|
SDL_zero(m_GamepadState);
|
||||||
}
|
}
|
||||||
@@ -158,6 +162,7 @@ void SdlInputHandler::handleKeyEvent(SDL_KeyboardEvent* event)
|
|||||||
case SDL_SCANCODE_CLEAR:
|
case SDL_SCANCODE_CLEAR:
|
||||||
keyCode = 0x0C;
|
keyCode = 0x0C;
|
||||||
break;
|
break;
|
||||||
|
case SDL_SCANCODE_KP_ENTER: // FIXME: Is this correct?
|
||||||
case SDL_SCANCODE_RETURN:
|
case SDL_SCANCODE_RETURN:
|
||||||
keyCode = 0x0D;
|
keyCode = 0x0D;
|
||||||
break;
|
break;
|
||||||
@@ -235,7 +240,7 @@ void SdlInputHandler::handleKeyEvent(SDL_KeyboardEvent* event)
|
|||||||
case SDL_SCANCODE_KP_MINUS:
|
case SDL_SCANCODE_KP_MINUS:
|
||||||
keyCode = 0x6D;
|
keyCode = 0x6D;
|
||||||
break;
|
break;
|
||||||
case SDL_SCANCODE_KP_DECIMAL:
|
case SDL_SCANCODE_KP_PERIOD:
|
||||||
keyCode = 0x6E;
|
keyCode = 0x6E;
|
||||||
break;
|
break;
|
||||||
case SDL_SCANCODE_KP_DIVIDE:
|
case SDL_SCANCODE_KP_DIVIDE:
|
||||||
@@ -289,6 +294,9 @@ void SdlInputHandler::handleKeyEvent(SDL_KeyboardEvent* event)
|
|||||||
case SDL_SCANCODE_SEMICOLON:
|
case SDL_SCANCODE_SEMICOLON:
|
||||||
keyCode = 0xBA;
|
keyCode = 0xBA;
|
||||||
break;
|
break;
|
||||||
|
case SDL_SCANCODE_EQUALS:
|
||||||
|
keyCode = 0xBB;
|
||||||
|
break;
|
||||||
case SDL_SCANCODE_COMMA:
|
case SDL_SCANCODE_COMMA:
|
||||||
keyCode = 0xBC;
|
keyCode = 0xBC;
|
||||||
break;
|
break;
|
||||||
@@ -382,8 +390,8 @@ void SdlInputHandler::handleMouseMotionEvent(SDL_MouseMotionEvent* event)
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (event->xrel != 0 || event->yrel != 0) {
|
if (event->xrel != 0 || event->yrel != 0) {
|
||||||
LiSendMouseMoveEvent((unsigned short)event->xrel,
|
LiSendMouseMoveEvent((short)event->xrel,
|
||||||
(unsigned short)event->yrel);
|
(short)event->yrel);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -361,9 +361,9 @@ bool Session::validateLaunch()
|
|||||||
m_StreamConfig.width,
|
m_StreamConfig.width,
|
||||||
m_StreamConfig.height,
|
m_StreamConfig.height,
|
||||||
m_StreamConfig.fps) &&
|
m_StreamConfig.fps) &&
|
||||||
m_Preferences.videoDecoderSelection != StreamingPreferences::VDS_FORCE_SOFTWARE) {
|
m_Preferences.videoDecoderSelection == StreamingPreferences::VDS_AUTO) {
|
||||||
if (hevcForced) {
|
if (hevcForced) {
|
||||||
emitLaunchWarning("Using software decoding due to your selection to force HEVC. This may cause poor streaming performance.");
|
emitLaunchWarning("Using software decoding due to your selection to force HEVC without GPU support. This may cause poor streaming performance.");
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
emitLaunchWarning("This PC's GPU doesn't support HEVC decoding.");
|
emitLaunchWarning("This PC's GPU doesn't support HEVC decoding.");
|
||||||
@@ -375,6 +375,11 @@ bool Session::validateLaunch()
|
|||||||
if (m_Computer->maxLumaPixelsHEVC == 0) {
|
if (m_Computer->maxLumaPixelsHEVC == 0) {
|
||||||
emitLaunchWarning("Your host PC GPU doesn't support HEVC. "
|
emitLaunchWarning("Your host PC GPU doesn't support HEVC. "
|
||||||
"A GeForce GTX 900-series (Maxwell) or later GPU is required for HEVC streaming.");
|
"A GeForce GTX 900-series (Maxwell) or later GPU is required for HEVC streaming.");
|
||||||
|
|
||||||
|
// Moonlight-common-c will handle this case already, but we want
|
||||||
|
// to set this explicitly here so we can do our hardware acceleration
|
||||||
|
// check below.
|
||||||
|
m_StreamConfig.supportsHevc = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -426,7 +431,23 @@ bool Session::validateLaunch()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Always allow the launch to proceed for now
|
if (m_Preferences.videoDecoderSelection == StreamingPreferences::VDS_FORCE_HARDWARE &&
|
||||||
|
!isHardwareDecodeAvailable(m_Preferences.videoDecoderSelection,
|
||||||
|
m_StreamConfig.supportsHevc ? VIDEO_FORMAT_H265 : VIDEO_FORMAT_H264,
|
||||||
|
m_StreamConfig.width,
|
||||||
|
m_StreamConfig.height,
|
||||||
|
m_StreamConfig.fps)) {
|
||||||
|
if (m_Preferences.videoCodecConfig == StreamingPreferences::VCC_AUTO) {
|
||||||
|
emit displayLaunchError("Your selection to force hardware decoding cannot be satisfied due to missing hardware decoding support on this PC's GPU.");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
emit displayLaunchError("Your codec selection and force hardware decoding setting are not compatible. This PC's GPU lacks support for decoding your chosen codec.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fail the launch, because we won't manage to get a decoder for the actual stream
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -721,7 +742,9 @@ void Session::exec()
|
|||||||
// Capture the mouse by default on release builds only.
|
// Capture the mouse by default on release builds only.
|
||||||
// This prevents the mouse from becoming trapped inside
|
// This prevents the mouse from becoming trapped inside
|
||||||
// Moonlight when it's halted at a debug break.
|
// Moonlight when it's halted at a debug break.
|
||||||
SDL_SetRelativeMouseMode(SDL_TRUE);
|
if (m_Preferences.fullScreen) {
|
||||||
|
SDL_SetRelativeMouseMode(SDL_TRUE);
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Stop text input. SDL enables it by default
|
// Stop text input. SDL enables it by default
|
||||||
@@ -803,6 +826,7 @@ void Session::exec()
|
|||||||
SDL_AtomicUnlock(&m_DecoderLock);
|
SDL_AtomicUnlock(&m_DecoderLock);
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
"Failed to recreate decoder after reset");
|
"Failed to recreate decoder after reset");
|
||||||
|
emit displayLaunchError("Unable to initialize video decoder. Please check your streaming settings and try again.");
|
||||||
goto DispatchDeferredCleanup;
|
goto DispatchDeferredCleanup;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,14 +1,10 @@
|
|||||||
#include "vaapi.h"
|
#include "vaapi.h"
|
||||||
#include <streaming/streamutils.h>
|
#include <streaming/streamutils.h>
|
||||||
|
|
||||||
#include <dlfcn.h>
|
|
||||||
|
|
||||||
#include <SDL_syswm.h>
|
#include <SDL_syswm.h>
|
||||||
|
|
||||||
VAAPIRenderer::VAAPIRenderer()
|
VAAPIRenderer::VAAPIRenderer()
|
||||||
: m_HwContext(nullptr),
|
: m_HwContext(nullptr)
|
||||||
m_X11VaLibHandle(nullptr),
|
|
||||||
m_vaPutSurface(nullptr)
|
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -16,11 +12,17 @@ VAAPIRenderer::VAAPIRenderer()
|
|||||||
VAAPIRenderer::~VAAPIRenderer()
|
VAAPIRenderer::~VAAPIRenderer()
|
||||||
{
|
{
|
||||||
if (m_HwContext != nullptr) {
|
if (m_HwContext != nullptr) {
|
||||||
av_buffer_unref(&m_HwContext);
|
AVHWDeviceContext* deviceContext = (AVHWDeviceContext*)m_HwContext->data;
|
||||||
}
|
AVVAAPIDeviceContext* vaDeviceContext = (AVVAAPIDeviceContext*)deviceContext->hwctx;
|
||||||
|
|
||||||
if (m_X11VaLibHandle != nullptr) {
|
// Hold onto this VADisplay since we'll need it to uninitialize VAAPI
|
||||||
dlclose(m_X11VaLibHandle);
|
VADisplay display = vaDeviceContext->display;
|
||||||
|
|
||||||
|
av_buffer_unref(&m_HwContext);
|
||||||
|
|
||||||
|
if (display) {
|
||||||
|
vaTerminate(display);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -38,31 +40,53 @@ VAAPIRenderer::initialize(SDL_Window* window, int, int width, int height)
|
|||||||
SDL_VERSION(&info.version);
|
SDL_VERSION(&info.version);
|
||||||
|
|
||||||
if (!SDL_GetWindowWMInfo(window, &info)) {
|
if (!SDL_GetWindowWMInfo(window, &info)) {
|
||||||
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
"SDL_GetWindowWMInfo() failed: %s",
|
"SDL_GetWindowWMInfo() failed: %s",
|
||||||
SDL_GetError());
|
SDL_GetError());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
SDL_assert(info.subsystem == SDL_SYSWM_X11);
|
m_HwContext = av_hwdevice_ctx_alloc(AV_HWDEVICE_TYPE_VAAPI);
|
||||||
|
if (!m_HwContext) {
|
||||||
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
|
"Failed to allocate VAAPI context");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
AVHWDeviceContext* deviceContext = (AVHWDeviceContext*)m_HwContext->data;
|
||||||
|
AVVAAPIDeviceContext* vaDeviceContext = (AVVAAPIDeviceContext*)deviceContext->hwctx;
|
||||||
|
|
||||||
|
m_WindowSystem = info.subsystem;
|
||||||
if (info.subsystem == SDL_SYSWM_X11) {
|
if (info.subsystem == SDL_SYSWM_X11) {
|
||||||
|
#ifdef HAVE_LIBVA_X11
|
||||||
m_XWindow = info.info.x11.window;
|
m_XWindow = info.info.x11.window;
|
||||||
|
vaDeviceContext->display = vaGetDisplay(info.info.x11.display);
|
||||||
m_X11VaLibHandle = dlopen("libva-x11.so", RTLD_LAZY);
|
if (!vaDeviceContext->display) {
|
||||||
if (!m_X11VaLibHandle) {
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
"Unable to open X11 display for VAAPI");
|
||||||
"dlopen(libva.so) failed: %s",
|
|
||||||
dlerror());
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
#else
|
||||||
m_vaPutSurface = (vaPutSurface_t)dlsym(m_X11VaLibHandle, "vaPutSurface");
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
|
"Moonlight not compiled with VAAPI X11 support!");
|
||||||
|
return false;
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
else if (info.subsystem == SDL_SYSWM_WAYLAND) {
|
else if (info.subsystem == SDL_SYSWM_WAYLAND) {
|
||||||
|
#ifdef HAVE_LIBVA_WAYLAND
|
||||||
|
m_WaylandSurface = info.info.wl.surface;
|
||||||
|
m_WaylandDisplay = info.info.wl.display;
|
||||||
|
vaDeviceContext->display = vaGetDisplayWl(info.info.wl.display);
|
||||||
|
if (!vaDeviceContext->display) {
|
||||||
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
|
"Unable to open Wayland display for VAAPI");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
#else
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
"VAAPI backend does not currently support Wayland");
|
"Moonlight not compiled with VAAPI Wayland support!");
|
||||||
return false;
|
return false;
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
@@ -71,18 +95,31 @@ VAAPIRenderer::initialize(SDL_Window* window, int, int width, int height)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
err = av_hwdevice_ctx_create(&m_HwContext,
|
int major, minor;
|
||||||
AV_HWDEVICE_TYPE_VAAPI,
|
VAStatus status;
|
||||||
nullptr, nullptr, 0);
|
status = vaInitialize(vaDeviceContext->display, &major, &minor);
|
||||||
|
if (status != VA_STATUS_SUCCESS) {
|
||||||
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
|
"Failed to initialize VAAPI: %d",
|
||||||
|
status);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
|
"Initialized VAAPI %d.%d",
|
||||||
|
major, minor);
|
||||||
|
|
||||||
|
// This will populate the driver_quirks
|
||||||
|
err = av_hwdevice_ctx_init(m_HwContext);
|
||||||
if (err < 0) {
|
if (err < 0) {
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
"Failed to create VAAPI context: %d",
|
"Failed to initialize VAAPI context: %d",
|
||||||
err);
|
err);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// This quirk is set for the VDPAU wrapper which doesn't work with our VAAPI renderer
|
// This quirk is set for the VDPAU wrapper which doesn't work with our VAAPI renderer
|
||||||
if (((AVVAAPIDeviceContext*)((AVHWDeviceContext*)(m_HwContext->data))->hwctx)->driver_quirks & AV_VAAPI_DRIVER_QUIRK_SURFACE_ATTRIBUTES) {
|
if (vaDeviceContext->driver_quirks & AV_VAAPI_DRIVER_QUIRK_SURFACE_ATTRIBUTES) {
|
||||||
// Fail and let our VDPAU renderer pick this up
|
// Fail and let our VDPAU renderer pick this up
|
||||||
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
"Avoiding VDPAU wrapper for VAAPI decoding");
|
"Avoiding VDPAU wrapper for VAAPI decoding");
|
||||||
@@ -98,7 +135,8 @@ VAAPIRenderer::prepareDecoderContext(AVCodecContext* context)
|
|||||||
context->hw_device_ctx = av_buffer_ref(m_HwContext);
|
context->hw_device_ctx = av_buffer_ref(m_HwContext);
|
||||||
|
|
||||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
"Using VAAPI accelerated renderer");
|
"Using VAAPI accelerated renderer on %s",
|
||||||
|
m_WindowSystem == SDL_SYSWM_X11 ? "X11" : "Wayland");
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -120,14 +158,45 @@ VAAPIRenderer::renderFrame(AVFrame* frame)
|
|||||||
|
|
||||||
StreamUtils::scaleSourceToDestinationSurface(&src, &dst);
|
StreamUtils::scaleSourceToDestinationSurface(&src, &dst);
|
||||||
|
|
||||||
m_vaPutSurface(vaDeviceContext->display,
|
if (m_WindowSystem == SDL_SYSWM_X11) {
|
||||||
surface,
|
#ifdef HAVE_LIBVA_X11
|
||||||
m_XWindow,
|
vaPutSurface(vaDeviceContext->display,
|
||||||
0, 0,
|
surface,
|
||||||
m_VideoWidth, m_VideoHeight,
|
m_XWindow,
|
||||||
dst.x, dst.y,
|
0, 0,
|
||||||
dst.w, dst.h,
|
m_VideoWidth, m_VideoHeight,
|
||||||
NULL, 0, 0);
|
dst.x, dst.y,
|
||||||
|
dst.w, dst.h,
|
||||||
|
NULL, 0, 0);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
else if (m_WindowSystem == SDL_SYSWM_WAYLAND) {
|
||||||
|
#ifdef HAVE_LIBVA_WAYLAND
|
||||||
|
struct wl_buffer* buffer;
|
||||||
|
VAStatus status;
|
||||||
|
|
||||||
|
status = vaGetSurfaceBufferWl(vaDeviceContext->display,
|
||||||
|
surface,
|
||||||
|
VA_FRAME_PICTURE,
|
||||||
|
&buffer);
|
||||||
|
if (status == VA_STATUS_SUCCESS) {
|
||||||
|
wl_surface_attach(m_WaylandSurface, buffer, 0, 0);
|
||||||
|
wl_surface_damage(m_WaylandSurface, dst.x, dst.y, dst.w, dst.h);
|
||||||
|
|
||||||
|
wl_display_flush(m_WaylandDisplay);
|
||||||
|
wl_surface_commit(m_WaylandSurface);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
|
"vaGetSurfaceBufferWl failed(): %d",
|
||||||
|
status);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// We don't accept anything else in initialize().
|
||||||
|
SDL_assert(false);
|
||||||
|
}
|
||||||
|
|
||||||
av_frame_free(&frame);
|
av_frame_free(&frame);
|
||||||
}
|
}
|
||||||
@@ -2,31 +2,31 @@
|
|||||||
|
|
||||||
#include "renderer.h"
|
#include "renderer.h"
|
||||||
|
|
||||||
|
// Avoid X11 if SDL was built without it
|
||||||
|
#ifndef SDL_VIDEO_DRIVER_X11
|
||||||
|
#warning Unable to use libva-x11 without SDL support
|
||||||
|
#undef HAVE_LIBVA_X11
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Avoid Wayland if SDL was built without it
|
||||||
|
#ifndef SDL_VIDEO_DRIVER_WAYLAND
|
||||||
|
#warning Unable to use libva-wayland without SDL support
|
||||||
|
#undef HAVE_LIBVA_WAYLAND
|
||||||
|
#endif
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
#include <va/va.h>
|
#include <va/va.h>
|
||||||
|
#ifdef HAVE_LIBVA_X11
|
||||||
#include <va/va_x11.h>
|
#include <va/va_x11.h>
|
||||||
|
#endif
|
||||||
|
#ifdef HAVE_LIBVA_WAYLAND
|
||||||
|
#include <va/va_wayland.h>
|
||||||
|
#endif
|
||||||
#include <libavutil/hwcontext_vaapi.h>
|
#include <libavutil/hwcontext_vaapi.h>
|
||||||
}
|
}
|
||||||
|
|
||||||
class VAAPIRenderer : public IFFmpegRenderer
|
class VAAPIRenderer : public IFFmpegRenderer
|
||||||
{
|
{
|
||||||
typedef VAStatus (*vaPutSurface_t)(
|
|
||||||
VADisplay dpy,
|
|
||||||
VASurfaceID surface,
|
|
||||||
Drawable draw, /* X Drawable */
|
|
||||||
short srcx,
|
|
||||||
short srcy,
|
|
||||||
unsigned short srcw,
|
|
||||||
unsigned short srch,
|
|
||||||
short destx,
|
|
||||||
short desty,
|
|
||||||
unsigned short destw,
|
|
||||||
unsigned short desth,
|
|
||||||
VARectangle *cliprects,
|
|
||||||
unsigned int number_cliprects,
|
|
||||||
unsigned int flags
|
|
||||||
);
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
VAAPIRenderer();
|
VAAPIRenderer();
|
||||||
virtual ~VAAPIRenderer();
|
virtual ~VAAPIRenderer();
|
||||||
@@ -38,10 +38,18 @@ public:
|
|||||||
virtual void renderFrame(AVFrame* frame);
|
virtual void renderFrame(AVFrame* frame);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Window m_XWindow;
|
int m_WindowSystem;
|
||||||
AVBufferRef* m_HwContext;
|
AVBufferRef* m_HwContext;
|
||||||
void* m_X11VaLibHandle;
|
|
||||||
vaPutSurface_t m_vaPutSurface;
|
#ifdef HAVE_LIBVA_X11
|
||||||
|
Window m_XWindow;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef HAVE_LIBVA_WAYLAND
|
||||||
|
struct wl_surface* m_WaylandSurface;
|
||||||
|
struct wl_display* m_WaylandDisplay;
|
||||||
|
#endif
|
||||||
|
|
||||||
int m_VideoWidth;
|
int m_VideoWidth;
|
||||||
int m_VideoHeight;
|
int m_VideoHeight;
|
||||||
int m_DisplayWidth;
|
int m_DisplayWidth;
|
||||||
|
|||||||
@@ -97,9 +97,9 @@ bool VDPAURenderer::initialize(SDL_Window* window, int, int width, int height)
|
|||||||
SDL_VERSION(&info.version);
|
SDL_VERSION(&info.version);
|
||||||
|
|
||||||
if (!SDL_GetWindowWMInfo(window, &info)) {
|
if (!SDL_GetWindowWMInfo(window, &info)) {
|
||||||
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
"SDL_GetWindowWMInfo() failed: %s",
|
"SDL_GetWindowWMInfo() failed: %s",
|
||||||
SDL_GetError());
|
SDL_GetError());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -20,6 +20,8 @@
|
|||||||
// This is gross but it allows us to use sizeof()
|
// This is gross but it allows us to use sizeof()
|
||||||
#include "ffmpeg_videosamples.cpp"
|
#include "ffmpeg_videosamples.cpp"
|
||||||
|
|
||||||
|
#define FAILED_DECODES_RESET_THRESHOLD 20
|
||||||
|
|
||||||
bool FFmpegVideoDecoder::isHardwareAccelerated()
|
bool FFmpegVideoDecoder::isHardwareAccelerated()
|
||||||
{
|
{
|
||||||
return m_HwDecodeCfg != nullptr;
|
return m_HwDecodeCfg != nullptr;
|
||||||
@@ -50,7 +52,8 @@ FFmpegVideoDecoder::FFmpegVideoDecoder()
|
|||||||
: m_VideoDecoderCtx(nullptr),
|
: m_VideoDecoderCtx(nullptr),
|
||||||
m_DecodeBuffer(1024 * 1024, 0),
|
m_DecodeBuffer(1024 * 1024, 0),
|
||||||
m_HwDecodeCfg(nullptr),
|
m_HwDecodeCfg(nullptr),
|
||||||
m_Renderer(nullptr)
|
m_Renderer(nullptr),
|
||||||
|
m_ConsecutiveFailedDecodes(0)
|
||||||
{
|
{
|
||||||
av_init_packet(&m_Pkt);
|
av_init_packet(&m_Pkt);
|
||||||
SDL_AtomicSet(&m_QueuedFrames, 0);
|
SDL_AtomicSet(&m_QueuedFrames, 0);
|
||||||
@@ -317,7 +320,20 @@ int FFmpegVideoDecoder::submitDecodeUnit(PDECODE_UNIT du)
|
|||||||
char errorstring[512];
|
char errorstring[512];
|
||||||
av_strerror(err, errorstring, sizeof(errorstring));
|
av_strerror(err, errorstring, sizeof(errorstring));
|
||||||
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
"Decoding failed: %s", errorstring);
|
"avcodec_send_packet() failed: %s", errorstring);
|
||||||
|
|
||||||
|
// If we've failed a bunch of decodes in a row, the decoder/renderer is
|
||||||
|
// clearly unhealthy, so let's generate a synthetic reset event to trigger
|
||||||
|
// the event loop to destroy and recreate the decoder.
|
||||||
|
if (++m_ConsecutiveFailedDecodes == FAILED_DECODES_RESET_THRESHOLD) {
|
||||||
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
|
"Resetting decoder due to consistent failure");
|
||||||
|
|
||||||
|
SDL_Event event;
|
||||||
|
event.type = SDL_RENDER_DEVICE_RESET;
|
||||||
|
SDL_PushEvent(&event);
|
||||||
|
}
|
||||||
|
|
||||||
return DR_NEED_IDR;
|
return DR_NEED_IDR;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -332,12 +348,29 @@ int FFmpegVideoDecoder::submitDecodeUnit(PDECODE_UNIT du)
|
|||||||
|
|
||||||
err = avcodec_receive_frame(m_VideoDecoderCtx, frame);
|
err = avcodec_receive_frame(m_VideoDecoderCtx, frame);
|
||||||
if (err == 0) {
|
if (err == 0) {
|
||||||
|
// Reset failed decodes count if we reached this far
|
||||||
|
m_ConsecutiveFailedDecodes = 0;
|
||||||
|
|
||||||
// Queue the frame for rendering from the main thread
|
// Queue the frame for rendering from the main thread
|
||||||
SDL_AtomicIncRef(&m_QueuedFrames);
|
SDL_AtomicIncRef(&m_QueuedFrames);
|
||||||
queueFrame(frame);
|
queueFrame(frame);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
av_frame_free(&frame);
|
av_frame_free(&frame);
|
||||||
|
|
||||||
|
char errorstring[512];
|
||||||
|
av_strerror(err, errorstring, sizeof(errorstring));
|
||||||
|
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
|
"avcodec_receive_frame() failed: %s", errorstring);
|
||||||
|
|
||||||
|
if (++m_ConsecutiveFailedDecodes == FAILED_DECODES_RESET_THRESHOLD) {
|
||||||
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
|
"Resetting decoder due to consistent failure");
|
||||||
|
|
||||||
|
SDL_Event event;
|
||||||
|
event.type = SDL_RENDER_DEVICE_RESET;
|
||||||
|
SDL_PushEvent(&event);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return DR_OK;
|
return DR_OK;
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ private:
|
|||||||
const AVCodecHWConfig* m_HwDecodeCfg;
|
const AVCodecHWConfig* m_HwDecodeCfg;
|
||||||
IFFmpegRenderer* m_Renderer;
|
IFFmpegRenderer* m_Renderer;
|
||||||
SDL_atomic_t m_QueuedFrames;
|
SDL_atomic_t m_QueuedFrames;
|
||||||
|
int m_ConsecutiveFailedDecodes;
|
||||||
|
|
||||||
static const uint8_t k_H264TestFrame[];
|
static const uint8_t k_H264TestFrame[];
|
||||||
static const uint8_t k_HEVCTestFrame[];
|
static const uint8_t k_HEVCTestFrame[];
|
||||||
|
|||||||
Submodule moonlight-common-c/moonlight-common-c updated: fc124b60ce...feb46b978e.
@@ -1,3 +1,4 @@
|
|||||||
|
# This script requires create-dmg to be installed from https://github.com/sindresorhus/create-dmg
|
||||||
BUILD_CONFIG=$1
|
BUILD_CONFIG=$1
|
||||||
|
|
||||||
fail()
|
fail()
|
||||||
@@ -40,12 +41,12 @@ echo Copying frameworks dependencies
|
|||||||
mkdir $BUILD_FOLDER/app/Moonlight.app/Contents/Frameworks
|
mkdir $BUILD_FOLDER/app/Moonlight.app/Contents/Frameworks
|
||||||
cp -R $SOURCE_ROOT/libs/mac/Frameworks/ $BUILD_FOLDER/app/Moonlight.app/Contents/Frameworks/ || fail "Framework copy failed!"
|
cp -R $SOURCE_ROOT/libs/mac/Frameworks/ $BUILD_FOLDER/app/Moonlight.app/Contents/Frameworks/ || fail "Framework copy failed!"
|
||||||
|
|
||||||
echo Creating DMG
|
echo Creating app bundle
|
||||||
EXTRA_ARGS=
|
EXTRA_ARGS=
|
||||||
if [ "$BUILD_CONFIG" == "Debug" ]; then EXTRA_ARGS="$EXTRA_ARGS -use-debug-libs"; fi
|
if [ "$BUILD_CONFIG" == "Debug" ]; then EXTRA_ARGS="$EXTRA_ARGS -use-debug-libs"; fi
|
||||||
if [ "$SIGNING_KEY" != "" ]; then EXTRA_ARGS="$EXTRA_ARGS -codesign=$SIGNING_KEY"; fi
|
if [ "$SIGNING_KEY" != "" ]; then EXTRA_ARGS="$EXTRA_ARGS -codesign=$SIGNING_KEY"; fi
|
||||||
echo Extra deployment arguments: $EXTRA_ARGS
|
echo Extra deployment arguments: $EXTRA_ARGS
|
||||||
macdeployqt $BUILD_FOLDER/app/Moonlight.app -dmg $EXTRA_ARGS -qmldir=$SOURCE_ROOT/app/gui -appstore-compliant || fail "macdeployqt failed!"
|
macdeployqt $BUILD_FOLDER/app/Moonlight.app $EXTRA_ARGS -qmldir=$SOURCE_ROOT/app/gui -appstore-compliant || fail "macdeployqt failed!"
|
||||||
|
|
||||||
echo Deploying DMG
|
echo Creating DMG
|
||||||
mv $BUILD_FOLDER/app/Moonlight.dmg $INSTALLER_FOLDER/
|
create-dmg $BUILD_FOLDER/app/Moonlight.app $INSTALLER_FOLDER || fail "create-dmg failed!"
|
||||||
@@ -31,6 +31,7 @@ if /I "%ARCH%" NEQ "x86" (
|
|||||||
)
|
)
|
||||||
|
|
||||||
set VS_PATH=%ProgramFiles(x86)%\Microsoft Visual Studio\2017\Community
|
set VS_PATH=%ProgramFiles(x86)%\Microsoft Visual Studio\2017\Community
|
||||||
|
set VCREDIST_PATH=%VS_PATH%\VC\Redist\MSVC\14.14.26405
|
||||||
set SIGNTOOL_PARAMS=sign /tr http://timestamp.digicert.com /td sha256 /fd sha256 /sha1 1B3C676E831A94EC0327C3347EB32C68C26B3A67 /v
|
set SIGNTOOL_PARAMS=sign /tr http://timestamp.digicert.com /td sha256 /fd sha256 /sha1 1B3C676E831A94EC0327C3347EB32C68C26B3A67 /v
|
||||||
|
|
||||||
call "%VS_PATH%\VC\Auxiliary\Build\vcvarsall.bat" %ARCH%
|
call "%VS_PATH%\VC\Auxiliary\Build\vcvarsall.bat" %ARCH%
|
||||||
@@ -88,10 +89,18 @@ if "%SIGN%"=="1" (
|
|||||||
)
|
)
|
||||||
|
|
||||||
echo Building installer
|
echo Building installer
|
||||||
set VCREDIST_INSTALLER=%VS_PATH%\VC\Redist\MSVC\14.14.26405\vcredist_%ARCH%.exe
|
set VCREDIST_INSTALLER=%VCREDIST_PATH%\vcredist_%ARCH%.exe
|
||||||
msbuild %SOURCE_ROOT%\wix\Moonlight.sln /p:Configuration=%BUILD_CONFIG% /p:Platform=%ARCH%
|
msbuild %SOURCE_ROOT%\wix\Moonlight.sln /p:Configuration=%BUILD_CONFIG% /p:Platform=%ARCH%
|
||||||
if !ERRORLEVEL! NEQ 0 goto Error
|
if !ERRORLEVEL! NEQ 0 goto Error
|
||||||
|
|
||||||
|
echo Building portable package
|
||||||
|
rem This must be done after WiX harvesting and signing, since the VCRT dlls are MS signed
|
||||||
|
rem and should not be harvested for inclusion in the full installer
|
||||||
|
copy "%VCREDIST_PATH%\%ARCH%\Microsoft.VC141.CRT\*.dll" %DEPLOY_FOLDER%
|
||||||
|
if !ERRORLEVEL! NEQ 0 goto Error
|
||||||
|
7z a %INSTALLER_FOLDER%\MoonlightPortable.zip %DEPLOY_FOLDER%\*
|
||||||
|
if !ERRORLEVEL! NEQ 0 goto Error
|
||||||
|
|
||||||
if "%SIGN%"=="1" (
|
if "%SIGN%"=="1" (
|
||||||
echo Signing installer binary
|
echo Signing installer binary
|
||||||
"%WIX%\bin\insignia" -ib %INSTALLER_FOLDER%\MoonlightSetup.exe -o %BUILD_FOLDER%\engine.exe
|
"%WIX%\bin\insignia" -ib %INSTALLER_FOLDER%\MoonlightSetup.exe -o %BUILD_FOLDER%\engine.exe
|
||||||
@@ -108,6 +117,8 @@ if /i "%APPVEYOR%"=="true" (
|
|||||||
echo Pushing artifacts
|
echo Pushing artifacts
|
||||||
appveyor PushArtifact %INSTALLER_FOLDER%\MoonlightSetup.exe -FileName MoonlightSetup-%ARCH%-%BUILD_CONFIG%.exe
|
appveyor PushArtifact %INSTALLER_FOLDER%\MoonlightSetup.exe -FileName MoonlightSetup-%ARCH%-%BUILD_CONFIG%.exe
|
||||||
if !ERRORLEVEL! NEQ 0 goto Error
|
if !ERRORLEVEL! NEQ 0 goto Error
|
||||||
|
appveyor PushArtifact %INSTALLER_FOLDER%\MoonlightPortable.zip -FileName MoonlightPortable-%ARCH%-%BUILD_CONFIG%.zip
|
||||||
|
if !ERRORLEVEL! NEQ 0 goto Error
|
||||||
)
|
)
|
||||||
|
|
||||||
echo Build successful!
|
echo Build successful!
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
|
|
||||||
<Package InstallerVersion="500" Compressed="yes" InstallScope="perMachine" />
|
<Package InstallerVersion="500" Compressed="yes" InstallScope="perMachine" />
|
||||||
|
|
||||||
<MajorUpgrade DowngradeErrorMessage="A newer version of [ProductName] is already installed." />
|
<MajorUpgrade DowngradeErrorMessage="A newer version of [ProductName] is already installed." Schedule="afterInstallInitialize" />
|
||||||
<MediaTemplate CompressionLevel="high" />
|
<MediaTemplate CompressionLevel="high" />
|
||||||
|
|
||||||
<?if $(var.Platform) = x64 ?>
|
<?if $(var.Platform) = x64 ?>
|
||||||
|
|||||||
@@ -42,6 +42,9 @@
|
|||||||
</bal:Condition>
|
</bal:Condition>
|
||||||
<?endif ?>
|
<?endif ?>
|
||||||
|
|
||||||
|
<!-- HACK: Force replacement of lower versioned files too. This is only safe because we don't install any shared files -->
|
||||||
|
<Variable Name="REINSTALLMODE" bal:Overridable="yes" Value="dmus" />
|
||||||
|
|
||||||
<?if $(env.ARCH) ~= x64 ?>
|
<?if $(env.ARCH) ~= x64 ?>
|
||||||
<Variable Name="InstallFolder" Type="string" Value="[ProgramFiles64Folder]Moonlight Game Streaming" />
|
<Variable Name="InstallFolder" Type="string" Value="[ProgramFiles64Folder]Moonlight Game Streaming" />
|
||||||
<?elseif $(env.ARCH) ~= x86 ?>
|
<?elseif $(env.ARCH) ~= x86 ?>
|
||||||
@@ -71,6 +74,7 @@
|
|||||||
</ExePackage>
|
</ExePackage>
|
||||||
<MsiPackage Id="Moonlight" SourceFile="$(var.Moonlight.TargetPath)" Vital="yes">
|
<MsiPackage Id="Moonlight" SourceFile="$(var.Moonlight.TargetPath)" Vital="yes">
|
||||||
<MsiProperty Name="INSTALLFOLDER" Value="[InstallFolder]" />
|
<MsiProperty Name="INSTALLFOLDER" Value="[InstallFolder]" />
|
||||||
|
<MsiProperty Name="REINSTALLMODE" Value="[REINSTALLMODE]" />
|
||||||
</MsiPackage>
|
</MsiPackage>
|
||||||
</Chain>
|
</Chain>
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user