Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
81943b12c0 | ||
|
|
18fa7c2832 | ||
|
|
dd02bf809e | ||
|
|
2f3a1a5d79 | ||
|
|
18654cb011 | ||
|
|
5954f80645 | ||
|
|
3e36357315 | ||
|
|
a06a454ade | ||
|
|
c0fab4b41f | ||
|
|
80b79f3636 | ||
|
|
b97ae25f93 | ||
|
|
4722ea6096 | ||
|
|
3e9781033a | ||
|
|
62b296cf85 | ||
|
|
dcbaa52683 | ||
|
|
1e29f61656 | ||
|
|
89319dc7aa | ||
|
|
5755afc4ea | ||
|
|
d00c434ccc | ||
|
|
4ada59ba48 | ||
|
|
14ccc38f4c | ||
|
|
a8be299bbd | ||
|
|
2e87f5ff85 | ||
|
|
829ebba622 | ||
|
|
81019fa2a8 | ||
|
|
1bc3675548 | ||
|
|
678ec979c9 | ||
|
|
d1a48ae641 | ||
|
|
eded86bc10 | ||
|
|
5a7a49d1d5 | ||
|
|
d3de8049bf | ||
|
|
d09dd90a73 | ||
|
|
78203eb9be | ||
|
|
31c7d4e36f | ||
|
|
d3949806f5 | ||
|
|
28ff15c99e | ||
|
|
bd2c74fe90 | ||
|
|
fb6995db94 | ||
|
|
8c2c4d3282 | ||
|
|
b81392b818 | ||
|
|
ad08440023 | ||
|
|
ec17623400 | ||
|
|
288eca7849 | ||
|
|
b5f202150e | ||
|
|
539bf0cb30 | ||
|
|
9385d62c89 | ||
|
|
897f916552 | ||
|
|
914cf6753e | ||
|
|
392208057e | ||
|
|
8d2cbf806d | ||
|
|
ab5025efbf | ||
|
|
912e51d863 | ||
|
|
d1c4ca5eae | ||
|
|
16bb4a148e | ||
|
|
d7071cb67f | ||
|
|
a13fb3d7c5 | ||
|
|
85558904a4 | ||
|
|
88ed72fdc1 | ||
|
|
586a93d7aa | ||
|
|
a446699c6c | ||
|
|
f1c459fd6c | ||
|
|
752f0a988b | ||
|
|
719b266691 | ||
|
|
dcba5762c7 | ||
|
|
0757717bea | ||
|
|
a9f762aa96 | ||
|
|
db45f46d9e | ||
|
|
f00653964e | ||
|
|
e0154f9314 | ||
|
|
4408a3a354 | ||
|
|
5abf5a157d | ||
|
|
d55d5f60a5 | ||
|
|
feacf2c359 | ||
|
|
72ea1601e9 | ||
|
|
02cfd8d978 | ||
|
|
728377cba3 |
+1
-1
Submodule app/SDL_GameControllerDB updated: 64dbb2ae89...7518c26389
-14
@@ -59,20 +59,6 @@ unix:!macx {
|
||||
CONFIG += link_pkgconfig
|
||||
PKGCONFIG += openssl sdl2 SDL2_ttf opus
|
||||
|
||||
# SLAudio is used on Steam Link
|
||||
!config_SL {
|
||||
CONFIG += soundio
|
||||
}
|
||||
|
||||
soundio {
|
||||
packagesExist(libpulse) {
|
||||
PKGCONFIG += libpulse
|
||||
}
|
||||
packagesExist(alsa) {
|
||||
PKGCONFIG += alsa
|
||||
}
|
||||
}
|
||||
|
||||
packagesExist(libavcodec) {
|
||||
PKGCONFIG += libavcodec libavutil
|
||||
CONFIG += ffmpeg
|
||||
|
||||
@@ -29,11 +29,8 @@ AutoUpdateChecker::AutoUpdateChecker(QObject *parent) :
|
||||
void AutoUpdateChecker::start()
|
||||
{
|
||||
#if defined(Q_OS_WIN32) || defined(Q_OS_DARWIN) || defined(STEAM_LINK) || defined(APP_IMAGE) // Only run update checker on platforms without auto-update
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) && !defined(QT_NO_BEARERMANAGEMENT)
|
||||
// HACK: Set network accessibility to work around QTBUG-80947.
|
||||
// Even though it was fixed in 5.14.2, it still breaks for users attempting to
|
||||
// directly connect their computers without a router using APIPA and in some cases
|
||||
// using OpenVPN with IPv6 enabled. https://github.com/moonlight-stream/moonlight-qt/issues/375
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) && QT_VERSION < QT_VERSION_CHECK(5, 15, 1) && !defined(QT_NO_BEARERMANAGEMENT)
|
||||
// HACK: Set network accessibility to work around QTBUG-80947 (introduced in Qt 5.14.0 and fixed in Qt 5.15.1)
|
||||
QT_WARNING_PUSH
|
||||
QT_WARNING_DISABLE_DEPRECATED
|
||||
m_Nam.setNetworkAccessible(QNetworkAccessManager::Accessible);
|
||||
|
||||
@@ -87,6 +87,16 @@ QUrl BoxArtManager::loadBoxArt(NvComputer* computer, NvApp& app)
|
||||
return QUrl("qrc:/res/no_app_image.png");
|
||||
}
|
||||
|
||||
void BoxArtManager::deleteBoxArt(NvComputer* computer)
|
||||
{
|
||||
QDir dir(Path::getBoxArtCacheDir());
|
||||
|
||||
// Delete everything in this computer's box art directory
|
||||
if (dir.cd(computer->uuid)) {
|
||||
dir.removeRecursively();
|
||||
}
|
||||
}
|
||||
|
||||
void BoxArtManager::handleBoxArtLoadComplete(NvComputer* computer, NvApp app, QUrl image)
|
||||
{
|
||||
if (!image.isEmpty()) {
|
||||
|
||||
@@ -18,6 +18,10 @@ public:
|
||||
QUrl
|
||||
loadBoxArt(NvComputer* computer, NvApp& app);
|
||||
|
||||
static
|
||||
void
|
||||
deleteBoxArt(NvComputer* computer);
|
||||
|
||||
signals:
|
||||
void
|
||||
boxArtLoadComplete(NvComputer* computer, NvApp app, QUrl image);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include "computermanager.h"
|
||||
#include "boxartmanager.h"
|
||||
#include "nvhttp.h"
|
||||
#include "settings/streamingpreferences.h"
|
||||
|
||||
@@ -67,12 +68,7 @@ private:
|
||||
}
|
||||
|
||||
QWriteLocker lock(&m_Computer->lock);
|
||||
if (m_Computer->appList != appList) {
|
||||
m_Computer->appList = appList;
|
||||
m_Computer->sortAppList();
|
||||
changed = true;
|
||||
}
|
||||
|
||||
changed = m_Computer->updateAppList(appList);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -399,6 +395,9 @@ public:
|
||||
// Delete the polling entry first. This will stop all polling threads too.
|
||||
delete pollingEntry;
|
||||
|
||||
// Delete cached box art
|
||||
BoxArtManager::deleteBoxArt(m_Computer);
|
||||
|
||||
// Finally, delete the computer itself. This must be done
|
||||
// last because the polling thread might be using it.
|
||||
delete m_Computer;
|
||||
@@ -429,6 +428,15 @@ void ComputerManager::renameHost(NvComputer* computer, QString name)
|
||||
handleComputerStateChanged(computer);
|
||||
}
|
||||
|
||||
void ComputerManager::clientSideAttributeUpdated(NvComputer* computer)
|
||||
{
|
||||
// Persist the change
|
||||
saveHosts();
|
||||
|
||||
// Notify the UI of the state change
|
||||
handleComputerStateChanged(computer);
|
||||
}
|
||||
|
||||
void ComputerManager::handleAboutToQuit()
|
||||
{
|
||||
QWriteLocker lock(&m_Lock);
|
||||
@@ -599,7 +607,7 @@ public:
|
||||
}
|
||||
|
||||
signals:
|
||||
void computerAddCompleted(QVariant success);
|
||||
void computerAddCompleted(QVariant success, QVariant detectedPortBlocking);
|
||||
|
||||
void computerStateChanged(NvComputer* computer);
|
||||
|
||||
@@ -629,7 +637,20 @@ private:
|
||||
return serverInfo;
|
||||
} catch (...) {
|
||||
if (!m_Mdns) {
|
||||
emit computerAddCompleted(false);
|
||||
StreamingPreferences prefs;
|
||||
int portTestResult;
|
||||
|
||||
if (prefs.detectNetworkBlocking) {
|
||||
// We failed to connect to the specified PC. Let's test to make sure this network
|
||||
// isn't blocking Moonlight, so we can tell the user about it.
|
||||
portTestResult = LiTestClientConnectivity("qt.conntest.moonlight-stream.org", 443,
|
||||
ML_PORT_FLAG_TCP_47984 | ML_PORT_FLAG_TCP_47989);
|
||||
}
|
||||
else {
|
||||
portTestResult = 0;
|
||||
}
|
||||
|
||||
emit computerAddCompleted(false, portTestResult != 0 && portTestResult != ML_TEST_RESULT_INCONCLUSIVE);
|
||||
}
|
||||
return QString();
|
||||
}
|
||||
@@ -707,6 +728,12 @@ private:
|
||||
newComputer->manualAddress = m_Address;
|
||||
}
|
||||
|
||||
QHostAddress hostAddress(m_Address);
|
||||
bool addressIsSiteLocalV4 =
|
||||
hostAddress.isInSubnet(QHostAddress("10.0.0.0"), 8) ||
|
||||
hostAddress.isInSubnet(QHostAddress("172.16.0.0"), 12) ||
|
||||
hostAddress.isInSubnet(QHostAddress("192.168.0.0"), 16);
|
||||
|
||||
{
|
||||
// Check if this PC already exists
|
||||
QWriteLocker lock(&m_ComputerManager->m_Lock);
|
||||
@@ -721,7 +748,7 @@ private:
|
||||
|
||||
// For non-mDNS clients, let them know it succeeded
|
||||
if (!m_Mdns) {
|
||||
emit computerAddCompleted(true);
|
||||
emit computerAddCompleted(true, false);
|
||||
}
|
||||
|
||||
// Tell our client if something changed
|
||||
@@ -740,9 +767,24 @@ private:
|
||||
// Drop the lock before notifying
|
||||
lock.unlock();
|
||||
|
||||
// If this wasn't added via mDNS but it is a RFC 1918 IPv4 address and not a VPN,
|
||||
// go ahead and do the STUN request now to populate an external address.
|
||||
if (!m_Mdns && addressIsSiteLocalV4 && !newComputer->isReachableOverVpn()) {
|
||||
quint32 addr;
|
||||
int err = LiFindExternalAddressIP4("stun.moonlight-stream.org", 3478, &addr);
|
||||
if (err == 0) {
|
||||
lock.relock();
|
||||
newComputer->remoteAddress = QHostAddress(qFromBigEndian(addr)).toString();
|
||||
lock.unlock();
|
||||
}
|
||||
else {
|
||||
qWarning() << "STUN failed to get WAN address:" << err;
|
||||
}
|
||||
}
|
||||
|
||||
// For non-mDNS clients, let them know it succeeded
|
||||
if (!m_Mdns) {
|
||||
emit computerAddCompleted(true);
|
||||
emit computerAddCompleted(true, false);
|
||||
}
|
||||
|
||||
// Tell our client about this new PC
|
||||
|
||||
@@ -181,12 +181,14 @@ public:
|
||||
|
||||
void renameHost(NvComputer* computer, QString name);
|
||||
|
||||
void clientSideAttributeUpdated(NvComputer* computer);
|
||||
|
||||
signals:
|
||||
void computerStateChanged(NvComputer* computer);
|
||||
|
||||
void pairingCompleted(NvComputer* computer, QString error);
|
||||
|
||||
void computerAddCompleted(QVariant success);
|
||||
void computerAddCompleted(QVariant success, QVariant detectedPortBlocking);
|
||||
|
||||
void quitAppCompleted(QVariant error);
|
||||
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
|
||||
#include <openssl/pem.h>
|
||||
#include <openssl/rsa.h>
|
||||
#include <openssl/bn.h>
|
||||
#include <openssl/x509.h>
|
||||
#include <openssl/rand.h>
|
||||
|
||||
@@ -32,20 +31,19 @@ void IdentityManager::createCredentials(QSettings& settings)
|
||||
X509* cert = X509_new();
|
||||
THROW_BAD_ALLOC_IF_NULL(cert);
|
||||
|
||||
EVP_PKEY* pk = EVP_PKEY_new();
|
||||
EVP_PKEY_CTX* ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, NULL);
|
||||
THROW_BAD_ALLOC_IF_NULL(ctx);
|
||||
|
||||
EVP_PKEY_keygen_init(ctx);
|
||||
EVP_PKEY_CTX_set_rsa_keygen_bits(ctx, 2048);
|
||||
|
||||
// pk must be initialized on input
|
||||
EVP_PKEY* pk = NULL;
|
||||
EVP_PKEY_keygen(ctx, &pk);
|
||||
|
||||
EVP_PKEY_CTX_free(ctx);
|
||||
THROW_BAD_ALLOC_IF_NULL(pk);
|
||||
|
||||
BIGNUM* bne = BN_new();
|
||||
THROW_BAD_ALLOC_IF_NULL(bne);
|
||||
|
||||
RSA* rsa = RSA_new();
|
||||
THROW_BAD_ALLOC_IF_NULL(rsa);
|
||||
|
||||
BN_set_word(bne, RSA_F4);
|
||||
RSA_generate_key_ex(rsa, 2048, bne, nullptr);
|
||||
|
||||
EVP_PKEY_assign_RSA(pk, rsa);
|
||||
|
||||
X509_set_version(cert, 2);
|
||||
ASN1_INTEGER_set(X509_get_serialNumber(cert), 0);
|
||||
#if OPENSSL_VERSION_NUMBER < 0x10100000L
|
||||
@@ -94,7 +92,6 @@ void IdentityManager::createCredentials(QSettings& settings)
|
||||
|
||||
X509_free(cert);
|
||||
EVP_PKEY_free(pk);
|
||||
BN_free(bne);
|
||||
BIO_free(biokey);
|
||||
BIO_free(biocert);
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#define SER_APPID "id"
|
||||
#define SER_APPHDR "hdr"
|
||||
#define SER_APPCOLLECTOR "appcollector"
|
||||
#define SER_HIDDEN "hidden"
|
||||
|
||||
NvApp::NvApp(QSettings& settings)
|
||||
{
|
||||
@@ -11,6 +12,7 @@ NvApp::NvApp(QSettings& settings)
|
||||
id = settings.value(SER_APPID).toInt();
|
||||
hdrSupported = settings.value(SER_APPHDR).toBool();
|
||||
isAppCollectorGame = settings.value(SER_APPCOLLECTOR).toBool();
|
||||
hidden = settings.value(SER_HIDDEN).toBool();
|
||||
}
|
||||
|
||||
void NvApp::serialize(QSettings& settings) const
|
||||
@@ -19,4 +21,5 @@ void NvApp::serialize(QSettings& settings) const
|
||||
settings.setValue(SER_APPID, id);
|
||||
settings.setValue(SER_APPHDR, hdrSupported);
|
||||
settings.setValue(SER_APPCOLLECTOR, isAppCollectorGame);
|
||||
settings.setValue(SER_HIDDEN, hidden);
|
||||
}
|
||||
|
||||
+11
-1
@@ -10,7 +10,16 @@ public:
|
||||
|
||||
bool operator==(const NvApp& other) const
|
||||
{
|
||||
return id == other.id;
|
||||
return id == other.id &&
|
||||
name == other.name &&
|
||||
hdrSupported == other.hdrSupported &&
|
||||
isAppCollectorGame == other.isAppCollectorGame &&
|
||||
hidden == other.hidden;
|
||||
}
|
||||
|
||||
bool operator!=(const NvApp& other) const
|
||||
{
|
||||
return !operator==(other);
|
||||
}
|
||||
|
||||
bool isInitialized()
|
||||
@@ -25,6 +34,7 @@ public:
|
||||
QString name;
|
||||
bool hdrSupported = false;
|
||||
bool isAppCollectorGame = false;
|
||||
bool hidden = false;
|
||||
};
|
||||
|
||||
Q_DECLARE_METATYPE(NvApp)
|
||||
|
||||
@@ -157,8 +157,9 @@ bool NvComputer::wake()
|
||||
}
|
||||
|
||||
const quint16 WOL_PORTS[] = {
|
||||
7, 9, // Standard WOL ports
|
||||
9, // Standard WOL port (privileged port)
|
||||
47998, 47999, 48000, 48002, 48010, // Ports opened by GFE
|
||||
47009, // Port opened by Moonlight Internet Hosting Tool for WoL (non-privileged port)
|
||||
};
|
||||
|
||||
// Create the WoL payload
|
||||
@@ -306,6 +307,25 @@ bool NvComputer::isReachableOverVpn()
|
||||
}
|
||||
}
|
||||
|
||||
bool NvComputer::updateAppList(QVector<NvApp> newAppList) {
|
||||
if (appList == newAppList) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Propagate client-side attributes to the new app list
|
||||
for (const NvApp& existingApp : appList) {
|
||||
for (NvApp& newApp : newAppList) {
|
||||
if (existingApp.id == newApp.id) {
|
||||
newApp.hidden = existingApp.hidden;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
appList = newAppList;
|
||||
sortAppList();
|
||||
return true;
|
||||
}
|
||||
|
||||
QVector<QString> NvComputer::uniqueAddresses() const
|
||||
{
|
||||
QVector<QString> uniqueAddressList;
|
||||
@@ -389,7 +409,12 @@ bool NvComputer::update(NvComputer& that)
|
||||
ASSIGN_IF_CHANGED(maxLumaPixelsHEVC);
|
||||
ASSIGN_IF_CHANGED(gpuModel);
|
||||
ASSIGN_IF_CHANGED_AND_NONNULL(serverCert);
|
||||
ASSIGN_IF_CHANGED_AND_NONEMPTY(appList);
|
||||
ASSIGN_IF_CHANGED_AND_NONEMPTY(displayModes);
|
||||
|
||||
if (!that.appList.isEmpty()) {
|
||||
// updateAppList() handles merging client-side attributes
|
||||
updateAppList(that.appList);
|
||||
}
|
||||
|
||||
return changed;
|
||||
}
|
||||
|
||||
@@ -16,6 +16,8 @@ class NvComputer
|
||||
private:
|
||||
void sortAppList();
|
||||
|
||||
bool updateAppList(QVector<NvApp> newAppList);
|
||||
|
||||
bool pendingQuit;
|
||||
|
||||
public:
|
||||
|
||||
@@ -163,7 +163,11 @@ NvHTTP::launchApp(int appId,
|
||||
"appid="+QString::number(appId)+
|
||||
"&mode="+QString::number(streamConfig->width)+"x"+
|
||||
QString::number(streamConfig->height)+"x"+
|
||||
QString::number(streamConfig->fps)+
|
||||
// Using an FPS value over 60 causes SOPS to default to 720p60,
|
||||
// so force it to 0 to ensure the correct resolution is set. We
|
||||
// used to use 60 here but that locked the frame rate to 60 FPS
|
||||
// on GFE 3.20.3.
|
||||
QString::number(streamConfig->fps > 60 ? 0 : streamConfig->fps)+
|
||||
"&additionalStates=1&sops="+QString::number(sops ? 1 : 0)+
|
||||
"&rikey="+QByteArray(streamConfig->remoteInputAesKey, sizeof(streamConfig->remoteInputAesKey)).toHex()+
|
||||
"&rikeyid="+QString::number(riKeyId)+
|
||||
@@ -440,11 +444,8 @@ NvHTTP::openConnection(QUrl baseUrl,
|
||||
// Add our client certificate
|
||||
request.setSslConfiguration(IdentityManager::get()->getSslConfig());
|
||||
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) && !defined(QT_NO_BEARERMANAGEMENT)
|
||||
// HACK: Set network accessibility to work around QTBUG-80947.
|
||||
// Even though it was fixed in 5.14.2, it still breaks for users attempting to
|
||||
// directly connect their computers without a router using APIPA and in some cases
|
||||
// using OpenVPN with IPv6 enabled. https://github.com/moonlight-stream/moonlight-qt/issues/375
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) && QT_VERSION < QT_VERSION_CHECK(5, 15, 1) && !defined(QT_NO_BEARERMANAGEMENT)
|
||||
// HACK: Set network accessibility to work around QTBUG-80947 (introduced in Qt 5.14.0 and fixed in Qt 5.15.1)
|
||||
QT_WARNING_PUSH
|
||||
QT_WARNING_DISABLE_DEPRECATED
|
||||
m_Nam.setNetworkAccessible(QNetworkAccessManager::Accessible);
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
#include "nvpairingmanager.h"
|
||||
#include "utils.h"
|
||||
|
||||
#include <stdexcept>
|
||||
|
||||
#include <openssl/bio.h>
|
||||
#include <openssl/aes.h>
|
||||
#include <openssl/rand.h>
|
||||
#include <openssl/pem.h>
|
||||
#include <openssl/x509.h>
|
||||
@@ -51,39 +52,63 @@ NvPairingManager::generateRandomBytes(int length)
|
||||
}
|
||||
|
||||
QByteArray
|
||||
NvPairingManager::encrypt(QByteArray plaintext, AES_KEY* key)
|
||||
NvPairingManager::encrypt(const QByteArray& plaintext, const QByteArray& key)
|
||||
{
|
||||
QByteArray ciphertext(plaintext.size(), 0);
|
||||
EVP_CIPHER_CTX* cipher;
|
||||
int ciphertextLen;
|
||||
|
||||
for (int i = 0; i < plaintext.size(); i += 16)
|
||||
{
|
||||
AES_encrypt(reinterpret_cast<unsigned char*>(&plaintext.data()[i]),
|
||||
reinterpret_cast<unsigned char*>(&ciphertext.data()[i]),
|
||||
key);
|
||||
}
|
||||
cipher = EVP_CIPHER_CTX_new();
|
||||
THROW_BAD_ALLOC_IF_NULL(cipher);
|
||||
|
||||
EVP_EncryptInit(cipher, EVP_aes_128_ecb(), reinterpret_cast<const unsigned char*>(key.data()), NULL);
|
||||
EVP_CIPHER_CTX_set_padding(cipher, 0);
|
||||
|
||||
EVP_EncryptUpdate(cipher,
|
||||
reinterpret_cast<unsigned char*>(ciphertext.data()),
|
||||
&ciphertextLen,
|
||||
reinterpret_cast<const unsigned char*>(plaintext.data()),
|
||||
plaintext.length());
|
||||
Q_ASSERT(ciphertextLen == ciphertext.length());
|
||||
|
||||
EVP_CIPHER_CTX_free(cipher);
|
||||
|
||||
return ciphertext;
|
||||
}
|
||||
|
||||
QByteArray
|
||||
NvPairingManager::decrypt(QByteArray ciphertext, AES_KEY* key)
|
||||
NvPairingManager::decrypt(const QByteArray& ciphertext, const QByteArray& key)
|
||||
{
|
||||
QByteArray plaintext(ciphertext.size(), 0);
|
||||
EVP_CIPHER_CTX* cipher;
|
||||
int plaintextLen;
|
||||
|
||||
for (int i = 0; i < plaintext.size(); i += 16)
|
||||
{
|
||||
AES_decrypt(reinterpret_cast<unsigned char*>(&ciphertext.data()[i]),
|
||||
reinterpret_cast<unsigned char*>(&plaintext.data()[i]),
|
||||
key);
|
||||
}
|
||||
cipher = EVP_CIPHER_CTX_new();
|
||||
THROW_BAD_ALLOC_IF_NULL(cipher);
|
||||
|
||||
EVP_DecryptInit(cipher, EVP_aes_128_ecb(), reinterpret_cast<const unsigned char*>(key.data()), NULL);
|
||||
EVP_CIPHER_CTX_set_padding(cipher, 0);
|
||||
|
||||
EVP_DecryptUpdate(cipher,
|
||||
reinterpret_cast<unsigned char*>(plaintext.data()),
|
||||
&plaintextLen,
|
||||
reinterpret_cast<const unsigned char*>(ciphertext.data()),
|
||||
ciphertext.length());
|
||||
Q_ASSERT(plaintextLen == plaintext.length());
|
||||
|
||||
EVP_CIPHER_CTX_free(cipher);
|
||||
|
||||
return plaintext;
|
||||
}
|
||||
|
||||
QByteArray
|
||||
NvPairingManager::getSignatureFromPemCert(QByteArray certificate)
|
||||
NvPairingManager::getSignatureFromPemCert(const QByteArray& certificate)
|
||||
{
|
||||
#if (OPENSSL_VERSION_NUMBER < 0x10100000L)
|
||||
BIO* bio = BIO_new_mem_buf(const_cast<char*>(certificate.data()), -1);
|
||||
#else
|
||||
BIO* bio = BIO_new_mem_buf(certificate.data(), -1);
|
||||
#endif
|
||||
THROW_BAD_ALLOC_IF_NULL(bio);
|
||||
|
||||
X509* cert = PEM_read_bio_X509(bio, nullptr, nullptr, nullptr);
|
||||
@@ -107,9 +132,13 @@ NvPairingManager::getSignatureFromPemCert(QByteArray certificate)
|
||||
}
|
||||
|
||||
bool
|
||||
NvPairingManager::verifySignature(QByteArray data, QByteArray signature, QByteArray serverCertificate)
|
||||
NvPairingManager::verifySignature(const QByteArray& data, const QByteArray& signature, const QByteArray& serverCertificate)
|
||||
{
|
||||
#if (OPENSSL_VERSION_NUMBER < 0x10100000L)
|
||||
BIO* bio = BIO_new_mem_buf(const_cast<char*>(serverCertificate.data()), -1);
|
||||
#else
|
||||
BIO* bio = BIO_new_mem_buf(serverCertificate.data(), -1);
|
||||
#endif
|
||||
THROW_BAD_ALLOC_IF_NULL(bio);
|
||||
|
||||
X509* cert = PEM_read_bio_X509(bio, nullptr, nullptr, nullptr);
|
||||
@@ -123,7 +152,7 @@ NvPairingManager::verifySignature(QByteArray data, QByteArray signature, QByteAr
|
||||
|
||||
EVP_DigestVerifyInit(mdctx, nullptr, EVP_sha256(), nullptr, pubKey);
|
||||
EVP_DigestVerifyUpdate(mdctx, data.data(), data.length());
|
||||
int result = EVP_DigestVerifyFinal(mdctx, reinterpret_cast<unsigned char*>(signature.data()), signature.length());
|
||||
int result = EVP_DigestVerifyFinal(mdctx, reinterpret_cast<unsigned char*>(const_cast<char*>(signature.data())), signature.length());
|
||||
|
||||
EVP_PKEY_free(pubKey);
|
||||
EVP_MD_CTX_destroy(mdctx);
|
||||
@@ -133,7 +162,7 @@ NvPairingManager::verifySignature(QByteArray data, QByteArray signature, QByteAr
|
||||
}
|
||||
|
||||
QByteArray
|
||||
NvPairingManager::signMessage(QByteArray message)
|
||||
NvPairingManager::signMessage(const QByteArray& message)
|
||||
{
|
||||
EVP_MD_CTX *ctx = EVP_MD_CTX_create();
|
||||
THROW_BAD_ALLOC_IF_NULL(ctx);
|
||||
@@ -143,7 +172,7 @@ NvPairingManager::signMessage(QByteArray message)
|
||||
|
||||
EVP_DigestInit_ex(ctx, md, NULL);
|
||||
EVP_DigestSignInit(ctx, NULL, md, NULL, m_PrivateKey);
|
||||
EVP_DigestSignUpdate(ctx, reinterpret_cast<unsigned char*>(message.data()), message.length());
|
||||
EVP_DigestSignUpdate(ctx, reinterpret_cast<unsigned char*>(const_cast<char*>(message.data())), message.length());
|
||||
|
||||
size_t signatureLength = 0;
|
||||
EVP_DigestSignFinal(ctx, NULL, &signatureLength);
|
||||
@@ -157,7 +186,7 @@ NvPairingManager::signMessage(QByteArray message)
|
||||
}
|
||||
|
||||
QByteArray
|
||||
NvPairingManager::saltPin(QByteArray salt, QString pin)
|
||||
NvPairingManager::saltPin(const QByteArray& salt, QString pin)
|
||||
{
|
||||
return QByteArray().append(salt).append(pin.toLatin1());
|
||||
}
|
||||
@@ -186,9 +215,8 @@ NvPairingManager::pair(QString appVersion, QString pin, QSslCertificate& serverC
|
||||
QByteArray salt = generateRandomBytes(16);
|
||||
QByteArray saltedPin = saltPin(salt, pin);
|
||||
|
||||
AES_KEY encKey, decKey;
|
||||
AES_set_decrypt_key(reinterpret_cast<const unsigned char*>(QCryptographicHash::hash(saltedPin, hashAlgo).data()), 128, &decKey);
|
||||
AES_set_encrypt_key(reinterpret_cast<const unsigned char*>(QCryptographicHash::hash(saltedPin, hashAlgo).data()), 128, &encKey);
|
||||
QByteArray aesKey = QCryptographicHash::hash(saltedPin, hashAlgo).data();
|
||||
aesKey.truncate(16);
|
||||
|
||||
QString getCert = m_Http.openConnectionToString(m_Http.m_BaseUrlHttp,
|
||||
"pair",
|
||||
@@ -223,7 +251,7 @@ NvPairingManager::pair(QString appVersion, QString pin, QSslCertificate& serverC
|
||||
m_Http.setServerCert(serverCert);
|
||||
|
||||
QByteArray randomChallenge = generateRandomBytes(16);
|
||||
QByteArray encryptedChallenge = encrypt(randomChallenge, &encKey);
|
||||
QByteArray encryptedChallenge = encrypt(randomChallenge, aesKey);
|
||||
QString challengeXml = m_Http.openConnectionToString(m_Http.m_BaseUrlHttp,
|
||||
"pair",
|
||||
"devicename=roth&updateState=1&clientchallenge=" +
|
||||
@@ -237,7 +265,7 @@ NvPairingManager::pair(QString appVersion, QString pin, QSslCertificate& serverC
|
||||
return PairState::FAILED;
|
||||
}
|
||||
|
||||
QByteArray challengeResponseData = decrypt(m_Http.getXmlStringFromHex(challengeXml, "challengeresponse"), &decKey);
|
||||
QByteArray challengeResponseData = decrypt(m_Http.getXmlStringFromHex(challengeXml, "challengeresponse"), aesKey);
|
||||
QByteArray clientSecretData = generateRandomBytes(16);
|
||||
QByteArray challengeResponse;
|
||||
QByteArray serverResponse(challengeResponseData.data(), hashLength);
|
||||
@@ -258,7 +286,7 @@ NvPairingManager::pair(QString appVersion, QString pin, QSslCertificate& serverC
|
||||
|
||||
QByteArray paddedHash = QCryptographicHash::hash(challengeResponse, hashAlgo);
|
||||
paddedHash.resize(32);
|
||||
QByteArray encryptedChallengeResponseHash = encrypt(paddedHash, &encKey);
|
||||
QByteArray encryptedChallengeResponseHash = encrypt(paddedHash, aesKey);
|
||||
QString respXml = m_Http.openConnectionToString(m_Http.m_BaseUrlHttp,
|
||||
"pair",
|
||||
"devicename=roth&updateState=1&serverchallengeresp=" +
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
#include "identitymanager.h"
|
||||
#include "nvhttp.h"
|
||||
|
||||
#include <openssl/aes.h>
|
||||
#include <openssl/x509.h>
|
||||
#include <openssl/evp.h>
|
||||
|
||||
@@ -30,22 +29,22 @@ private:
|
||||
generateRandomBytes(int length);
|
||||
|
||||
QByteArray
|
||||
saltPin(QByteArray salt, QString pin);
|
||||
saltPin(const QByteArray& salt, QString pin);
|
||||
|
||||
QByteArray
|
||||
encrypt(QByteArray plaintext, AES_KEY* key);
|
||||
encrypt(const QByteArray& plaintext, const QByteArray& key);
|
||||
|
||||
QByteArray
|
||||
decrypt(QByteArray ciphertext, AES_KEY* key);
|
||||
decrypt(const QByteArray& ciphertext, const QByteArray& key);
|
||||
|
||||
QByteArray
|
||||
getSignatureFromPemCert(QByteArray certificate);
|
||||
getSignatureFromPemCert(const QByteArray& certificate);
|
||||
|
||||
bool
|
||||
verifySignature(QByteArray data, QByteArray signature, QByteArray serverCertificate);
|
||||
verifySignature(const QByteArray& data, const QByteArray& signature, const QByteArray& serverCertificate);
|
||||
|
||||
QByteArray
|
||||
signMessage(QByteArray message);
|
||||
signMessage(const QByteArray& message);
|
||||
|
||||
NvHTTP m_Http;
|
||||
X509* m_Cert;
|
||||
|
||||
@@ -120,13 +120,21 @@ void SystemProperties::querySdlVideoInfo()
|
||||
}
|
||||
}
|
||||
|
||||
SDL_Window* testWindow = SDL_CreateWindow("", 0, 0, 1280, 720, SDL_WINDOW_HIDDEN);
|
||||
SDL_Window* testWindow = SDL_CreateWindow("", 0, 0, 1280, 720,
|
||||
SDL_WINDOW_HIDDEN | StreamUtils::getPlatformWindowFlags());
|
||||
if (!testWindow) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Failed to create window for hardware decode test: %s",
|
||||
SDL_GetError());
|
||||
SDL_QuitSubSystem(SDL_INIT_VIDEO);
|
||||
return;
|
||||
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Failed to create test window with platform flags: %s",
|
||||
SDL_GetError());
|
||||
|
||||
testWindow = SDL_CreateWindow("", 0, 0, 1280, 720, SDL_WINDOW_HIDDEN);
|
||||
if (!testWindow) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Failed to create window for hardware decode test: %s",
|
||||
SDL_GetError());
|
||||
SDL_QuitSubSystem(SDL_INIT_VIDEO);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Session::getDecoderInfo(testWindow, hasHardwareAcceleration, rendererAlwaysFullScreen, maximumResolution);
|
||||
|
||||
@@ -298,6 +298,7 @@ void StreamCommandLineParser::parse(const QStringList &args, StreamingPreference
|
||||
parser.addToggleOption("multi-controller", "multiple controller support");
|
||||
parser.addToggleOption("quit-after", "quit app after session");
|
||||
parser.addToggleOption("absolute-mouse", "remote desktop optimized mouse control");
|
||||
parser.addToggleOption("mouse-buttons-swap", "left and right mouse buttons swap");
|
||||
parser.addToggleOption("touchscreen-trackpad", "touchscreen in trackpad mode");
|
||||
parser.addToggleOption("game-optimization", "game optimizations");
|
||||
parser.addToggleOption("audio-on-host", "audio on host PC");
|
||||
@@ -389,6 +390,9 @@ void StreamCommandLineParser::parse(const QStringList &args, StreamingPreference
|
||||
// Resolve --absolute-mouse and --no-absolute-mouse options
|
||||
preferences->absoluteMouseMode = parser.getToggleOptionValue("absolute-mouse", preferences->absoluteMouseMode);
|
||||
|
||||
// Resolve --mouse-buttons-swap and --no-mouse-buttons-swap options
|
||||
preferences->swapMouseButtons = parser.getToggleOptionValue("mouse-buttons-swap", preferences->swapMouseButtons);
|
||||
|
||||
// Resolve --touchscreen-trackpad and --no-touchscreen-trackpad options
|
||||
preferences->absoluteTouchMode = !parser.getToggleOptionValue("touchscreen-trackpad", !preferences->absoluteTouchMode);
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<summary>Play games remotely from your NVIDIA GameStream-enabled PC</summary>
|
||||
|
||||
<description>
|
||||
<p>Moonlight can stream games and other applications from a PC with an NVIDIA GeForce GTX 600-series or higher GPU and GeForce Experience installed.</p>
|
||||
<p>Moonlight can stream games and other applications from a PC with an NVIDIA GeForce GTX/RTX or NVIDIA Quadro GPU and GeForce/Quadro Experience installed.</p>
|
||||
<p>Features include:</p>
|
||||
<ul>
|
||||
<li>Streaming at up to 4K resolution</li>
|
||||
@@ -35,6 +35,32 @@
|
||||
</screenshots>
|
||||
|
||||
<releases>
|
||||
<release version="2.2.0" date="2020-09-10">
|
||||
<description>
|
||||
<p>New features:</p>
|
||||
<ul>
|
||||
<li>Added the ability to hide unwanted apps and games</li>
|
||||
<li>Added a network test to determine if your Internet connection is blocking Moonlight</li>
|
||||
<li>Added the option to swap mouse buttons</li>
|
||||
<li>Added codec and resolution to the on-screen performance stats</li>
|
||||
<li>Added a warning if the connection is too unstable to receive any complete video frames</li>
|
||||
<li>Added a Discord link to the toolbar in settings</li>
|
||||
<li>Enabled the new EGL renderer by default on Linux</li>
|
||||
</ul>
|
||||
<p>Bugfixes:</p>
|
||||
<ul>
|
||||
<li>Fixed streaming over networks that block QoS-tagged packets</li>
|
||||
<li>Selecting a frame rate higher than 60 FPS no longer sets the host desktop to 720p</li>
|
||||
<li>Right-side modifier keys no longer press the corresponding left-side keys too</li>
|
||||
<li>Fixed mouse position never reaching the edge of the screen with certain desktop resolutions</li>
|
||||
<li>Improved synchronization between host and client mouse cursor in remote desktop mouse mode</li>
|
||||
<li>Cached box art is now deleted when the PC is removed from Moonlight</li>
|
||||
<li>Fixed several CLI options not properly overriding GUI options</li>
|
||||
<li>Updated included gamepad mappings</li>
|
||||
<li>Updated to Qt 5.15.1</li>
|
||||
</ul>
|
||||
</description>
|
||||
</release>
|
||||
<release version="2.1.0" date="2020-05-17">
|
||||
<description>
|
||||
<p>New features:</p>
|
||||
|
||||
+34
-3
@@ -9,6 +9,7 @@ CenteredGridView {
|
||||
property int computerIndex
|
||||
property AppModel appModel : createModel()
|
||||
property bool activated
|
||||
property bool showHiddenGames
|
||||
|
||||
id: appGrid
|
||||
focus: true
|
||||
@@ -48,7 +49,7 @@ CenteredGridView {
|
||||
function createModel()
|
||||
{
|
||||
var model = Qt.createQmlObject('import AppModel 1.0; AppModel {}', parent, '')
|
||||
model.initialize(ComputerManager, computerIndex)
|
||||
model.initialize(ComputerManager, computerIndex, showHiddenGames)
|
||||
return model
|
||||
}
|
||||
|
||||
@@ -58,6 +59,9 @@ CenteredGridView {
|
||||
width: 220; height: 287;
|
||||
grid: appGrid
|
||||
|
||||
// Dim the app if it's hidden
|
||||
opacity: model.hidden ? 0.4 : 1.0
|
||||
|
||||
Image {
|
||||
property bool isPlaceholder: false
|
||||
|
||||
@@ -165,8 +169,8 @@ CenteredGridView {
|
||||
|
||||
function launchOrResumeSelectedApp()
|
||||
{
|
||||
var runningIndex = appModel.getRunningAppIndex()
|
||||
if (runningIndex >= 0 && runningIndex !== index) {
|
||||
var runningId = appModel.getRunningAppId()
|
||||
if (runningId !== 0 && runningId !== model.appid) {
|
||||
quitAppDialog.appName = appModel.getRunningAppName()
|
||||
quitAppDialog.segueToStream = true
|
||||
quitAppDialog.nextAppName = model.name
|
||||
@@ -190,6 +194,25 @@ CenteredGridView {
|
||||
}
|
||||
}
|
||||
|
||||
onPressAndHold: {
|
||||
// popup() ensures the menu appears under the mouse cursor
|
||||
if (appContextMenu.popup) {
|
||||
appContextMenu.popup()
|
||||
}
|
||||
else {
|
||||
// Qt 5.9 doesn't have popup()
|
||||
appContextMenu.open()
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
acceptedButtons: Qt.RightButton;
|
||||
onClicked: {
|
||||
parent.onPressAndHold()
|
||||
}
|
||||
}
|
||||
|
||||
Keys.onReturnPressed: {
|
||||
// Open the app context menu if activated via the gamepad or keyboard
|
||||
// for running games. If the game isn't running, the above onClicked
|
||||
@@ -228,6 +251,14 @@ CenteredGridView {
|
||||
onTriggered: doQuitGame()
|
||||
visible: model.running
|
||||
}
|
||||
NavigableMenuItem {
|
||||
parentMenu: appContextMenu
|
||||
checkable: true
|
||||
checked: model.hidden
|
||||
text: "Hide Game"
|
||||
onTriggered: appModel.setAppHidden(model.index, !model.hidden)
|
||||
visible: !model.running || model.hidden
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ import QtQuick 2.0
|
||||
import QtQuick.Controls 2.2
|
||||
|
||||
import ComputerManager 1.0
|
||||
import SdlGamepadKeyNavigation 1.0
|
||||
|
||||
Item {
|
||||
function onSearchingComputer() {
|
||||
@@ -35,6 +36,11 @@ Item {
|
||||
StackView.onActivated: {
|
||||
if (!launcher.isExecuted()) {
|
||||
toolBar.visible = false
|
||||
|
||||
// Normally this is enabled by PcView, but we will won't
|
||||
// load PcView when streaming from the command-line.
|
||||
SdlGamepadKeyNavigation.enable()
|
||||
|
||||
launcher.searchingComputer.connect(onSearchingComputer)
|
||||
launcher.searchingApp.connect(onSearchingApp)
|
||||
launcher.sessionCreated.connect(onSessionCreated)
|
||||
|
||||
@@ -6,6 +6,8 @@ NavigableDialog {
|
||||
id: dialog
|
||||
|
||||
property alias text: dialogLabel.dialogText
|
||||
property alias showSpinner: dialogSpinner.visible
|
||||
property alias imageSrc: dialogImage.source
|
||||
|
||||
property string helpText
|
||||
property string helpUrl : "https://github.com/moonlight-stream/moonlight-docs/wiki/Troubleshooting"
|
||||
@@ -19,7 +21,13 @@ NavigableDialog {
|
||||
RowLayout {
|
||||
spacing: 10
|
||||
|
||||
BusyIndicator {
|
||||
id: dialogSpinner
|
||||
visible: false
|
||||
}
|
||||
|
||||
Image {
|
||||
id: dialogImage
|
||||
source: (standardButtons & Dialog.Yes) ?
|
||||
"qrc:/res/baseline-help_outline-24px.svg" :
|
||||
"qrc:/res/baseline-error_outline-24px.svg"
|
||||
@@ -28,6 +36,7 @@ NavigableDialog {
|
||||
width: 50
|
||||
height: 50
|
||||
}
|
||||
visible: !showSpinner
|
||||
}
|
||||
|
||||
Label {
|
||||
|
||||
+81
-10
@@ -26,6 +26,9 @@ CenteredGridView {
|
||||
currentIndex = -1
|
||||
}
|
||||
|
||||
// Note: Any initialization done here that is critical for streaming must
|
||||
// also be done in CliStartStreamSegue.qml, since this code does not run
|
||||
// for command-line initiated streams.
|
||||
StackView.onActivated: {
|
||||
// Setup signals on CM
|
||||
ComputerManager.computerAddCompleted.connect(addComplete)
|
||||
@@ -58,11 +61,18 @@ CenteredGridView {
|
||||
}
|
||||
}
|
||||
|
||||
function addComplete(success)
|
||||
function addComplete(success, detectedPortBlocking)
|
||||
{
|
||||
if (!success) {
|
||||
errorDialog.text = "Unable to connect to the specified PC."
|
||||
errorDialog.helpText = "Click the Help button for possible solutions."
|
||||
|
||||
if (detectedPortBlocking) {
|
||||
errorDialog.text += "\n\nThis PC's Internet connection is blocking Moonlight. Streaming over the Internet may not work while connected to this network."
|
||||
}
|
||||
else {
|
||||
errorDialog.helpText = "Click the Help button for possible solutions."
|
||||
}
|
||||
|
||||
errorDialog.open()
|
||||
}
|
||||
}
|
||||
@@ -72,6 +82,7 @@ CenteredGridView {
|
||||
var model = Qt.createQmlObject('import ComputerModel 1.0; ComputerModel {}', parent, '')
|
||||
model.initialize(ComputerManager)
|
||||
model.pairingCompleted.connect(pairingComplete)
|
||||
model.connectionTestCompleted.connect(testConnectionDialog.connectionTestComplete)
|
||||
return model
|
||||
}
|
||||
|
||||
@@ -153,13 +164,39 @@ CenteredGridView {
|
||||
id: pcContextMenu
|
||||
NavigableMenuItem {
|
||||
parentMenu: pcContextMenu
|
||||
text: "Delete PC"
|
||||
text: "View Apps"
|
||||
onTriggered: {
|
||||
deletePcDialog.pcIndex = index
|
||||
// get confirmation first, actual closing is called from the dialog
|
||||
deletePcDialog.open()
|
||||
var component = Qt.createComponent("AppView.qml")
|
||||
var appView = component.createObject(stackView, {"computerIndex": index, "objectName": model.name})
|
||||
stackView.push(appView)
|
||||
}
|
||||
visible: model.online && model.paired
|
||||
}
|
||||
NavigableMenuItem {
|
||||
parentMenu: pcContextMenu
|
||||
text: "View Hidden Apps"
|
||||
onTriggered: {
|
||||
var component = Qt.createComponent("AppView.qml")
|
||||
var appView = component.createObject(stackView, {"computerIndex": index, "objectName": model.name, "showHiddenGames": true})
|
||||
stackView.push(appView)
|
||||
}
|
||||
visible: model.online && model.paired
|
||||
}
|
||||
NavigableMenuItem {
|
||||
parentMenu: pcContextMenu
|
||||
text: "Wake PC"
|
||||
onTriggered: computerModel.wakeComputer(index)
|
||||
visible: !model.online && model.wakeable
|
||||
}
|
||||
NavigableMenuItem {
|
||||
parentMenu: pcContextMenu
|
||||
text: "Test Network"
|
||||
onTriggered: {
|
||||
computerModel.testConnectionForComputer(index)
|
||||
testConnectionDialog.open()
|
||||
}
|
||||
}
|
||||
|
||||
NavigableMenuItem {
|
||||
parentMenu: pcContextMenu
|
||||
text: "Rename PC"
|
||||
@@ -171,9 +208,12 @@ CenteredGridView {
|
||||
}
|
||||
NavigableMenuItem {
|
||||
parentMenu: pcContextMenu
|
||||
text: "Wake PC"
|
||||
onTriggered: computerModel.wakeComputer(index)
|
||||
visible: !model.online && model.wakeable
|
||||
text: "Delete PC"
|
||||
onTriggered: {
|
||||
deletePcDialog.pcIndex = index
|
||||
// get confirmation first, actual closing is called from the dialog
|
||||
deletePcDialog.open()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -187,7 +227,7 @@ CenteredGridView {
|
||||
}
|
||||
else {
|
||||
if (!model.busy) {
|
||||
var pin = ("0000" + Math.floor(Math.random() * 10000)).slice(-4)
|
||||
var pin = computerModel.generatePinString()
|
||||
|
||||
// Kick off pairing in the background
|
||||
computerModel.pairComputer(index, pin)
|
||||
@@ -275,6 +315,37 @@ CenteredGridView {
|
||||
onAccepted: deletePc()
|
||||
}
|
||||
|
||||
NavigableMessageDialog {
|
||||
id: testConnectionDialog
|
||||
closePolicy: Popup.CloseOnEscape
|
||||
standardButtons: Dialog.Ok
|
||||
|
||||
onAboutToShow: {
|
||||
testConnectionDialog.text = "Moonlight is testing your network connection to determine if NVIDIA GameStream is blocked.\n\nThis may take a few seconds…"
|
||||
showSpinner = true
|
||||
}
|
||||
|
||||
function connectionTestComplete(result, blockedPorts)
|
||||
{
|
||||
if (result === -1) {
|
||||
text = "The network test could not be performed because none of Moonlight's connection testing servers were reachable from this PC. Check your Internet connection or try again later."
|
||||
imageSrc = "qrc:/res/baseline-warning-24px.svg"
|
||||
}
|
||||
else if (result === 0) {
|
||||
text = "This network does not appear to be blocking Moonlight. If you still have trouble connecting, check your PC's firewall settings.\n\nIf you are trying to stream over the Internet, install the Moonlight Internet Hosting Tool on your gaming PC and run the included Internet Streaming Tester to check your gaming PC's Internet connection."
|
||||
imageSrc = "qrc:/res/baseline-check_circle_outline-24px.svg"
|
||||
}
|
||||
else {
|
||||
text = "Your PC's current network connection seems to be blocking Moonlight. Streaming over the Internet may not work while connected to this network.\n\nThe following network ports were blocked:\n"
|
||||
text += blockedPorts
|
||||
imageSrc = "qrc:/res/baseline-error_outline-24px.svg"
|
||||
}
|
||||
|
||||
// Stop showing the spinner and show the image instead
|
||||
showSpinner = false
|
||||
}
|
||||
}
|
||||
|
||||
NavigableDialog {
|
||||
id: renamePcDialog
|
||||
property string label: "Enter the new name for this PC:"
|
||||
|
||||
@@ -638,6 +638,23 @@ Flickable {
|
||||
ToolTip.visible: hovered
|
||||
ToolTip.text: "When enabled, holding the Start button will toggle mouse mode"
|
||||
}
|
||||
|
||||
CheckBox {
|
||||
id: swapMouseButtonsCheck
|
||||
hoverEnabled: true
|
||||
width: parent.width
|
||||
text: "Swap mouse buttons"
|
||||
font.pointSize: 12
|
||||
checked: StreamingPreferences.swapMouseButtons
|
||||
onCheckedChanged: {
|
||||
StreamingPreferences.swapMouseButtons = checked
|
||||
}
|
||||
|
||||
ToolTip.delay: 1000
|
||||
ToolTip.timeout: 3000
|
||||
ToolTip.visible: hovered
|
||||
ToolTip.text: "When checked, swap the left and right mouse buttons"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -655,7 +672,7 @@ Flickable {
|
||||
CheckBox {
|
||||
id: optimizeGameSettingsCheck
|
||||
width: parent.width
|
||||
text: "Optimize game settings"
|
||||
text: "Optimize game settings for streaming"
|
||||
font.pointSize: 12
|
||||
checked: StreamingPreferences.gameOptimizations
|
||||
onCheckedChanged: {
|
||||
@@ -673,6 +690,22 @@ Flickable {
|
||||
StreamingPreferences.playAudioOnHost = checked
|
||||
}
|
||||
}
|
||||
|
||||
CheckBox {
|
||||
id: quitAppAfter
|
||||
width: parent.width
|
||||
text: "Quit app on host PC after ending stream"
|
||||
font.pointSize: 12
|
||||
checked: StreamingPreferences.quitAppAfter
|
||||
onCheckedChanged: {
|
||||
StreamingPreferences.quitAppAfter = checked
|
||||
}
|
||||
|
||||
ToolTip.delay: 1000
|
||||
ToolTip.timeout: 5000
|
||||
ToolTip.visible: hovered
|
||||
ToolTip.text: "This will close the app or game you are streaming when you end your stream. You will lose any unsaved progress!"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -772,10 +805,10 @@ Flickable {
|
||||
text: "HEVC (H.265)"
|
||||
val: StreamingPreferences.VCC_FORCE_HEVC
|
||||
}
|
||||
ListElement {
|
||||
/*ListElement {
|
||||
text: "HEVC HDR (Experimental)"
|
||||
val: StreamingPreferences.VCC_FORCE_HEVC_HDR
|
||||
}
|
||||
}*/
|
||||
}
|
||||
// ::onActivated must be used, as it only listens for when the index is changed by a human
|
||||
onActivated : {
|
||||
@@ -828,13 +861,21 @@ Flickable {
|
||||
}
|
||||
|
||||
CheckBox {
|
||||
id: quitAppAfter
|
||||
id: detectNetworkBlocking
|
||||
width: parent.width
|
||||
text: "Quit app after quitting session"
|
||||
text: "Automatically detect blocked connections (Recommended)"
|
||||
font.pointSize: 12
|
||||
checked: StreamingPreferences.quitAppAfter
|
||||
checked: StreamingPreferences.detectNetworkBlocking
|
||||
onCheckedChanged: {
|
||||
StreamingPreferences.quitAppAfter = checked
|
||||
// This is called on init, so only do the work if we've
|
||||
// actually changed the value.
|
||||
if (StreamingPreferences.detectNetworkBlocking != checked) {
|
||||
StreamingPreferences.detectNetworkBlocking = checked
|
||||
|
||||
// We must save the updated preference to ensure
|
||||
// ComputerManager can observe the change internally.
|
||||
StreamingPreferences.save()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,8 +62,12 @@ Item {
|
||||
window.visible = true
|
||||
}
|
||||
|
||||
function sessionFinished()
|
||||
function sessionFinished(portTestResult)
|
||||
{
|
||||
if (portTestResult !== 0 && portTestResult !== -1 && streamSegueErrorDialog.text) {
|
||||
streamSegueErrorDialog.text += "\n\nThis PC's Internet connection is blocking Moonlight. Streaming over the Internet may not work while connected to this network."
|
||||
}
|
||||
|
||||
// Enable GUI gamepad usage now
|
||||
SdlGamepadKeyNavigation.enable()
|
||||
|
||||
|
||||
+133
-32
@@ -7,7 +7,7 @@ AppModel::AppModel(QObject *parent)
|
||||
this, &AppModel::handleBoxArtLoaded);
|
||||
}
|
||||
|
||||
void AppModel::initialize(ComputerManager* computerManager, int computerIndex)
|
||||
void AppModel::initialize(ComputerManager* computerManager, int computerIndex, bool showHiddenGames)
|
||||
{
|
||||
m_ComputerManager = computerManager;
|
||||
connect(m_ComputerManager, &ComputerManager::computerStateChanged,
|
||||
@@ -15,29 +15,23 @@ void AppModel::initialize(ComputerManager* computerManager, int computerIndex)
|
||||
|
||||
Q_ASSERT(computerIndex < m_ComputerManager->getComputers().count());
|
||||
m_Computer = m_ComputerManager->getComputers().at(computerIndex);
|
||||
m_Apps = m_Computer->appList;
|
||||
m_CurrentGameId = m_Computer->currentGameId;
|
||||
m_ShowHiddenGames = showHiddenGames;
|
||||
|
||||
updateAppList(m_Computer->appList);
|
||||
}
|
||||
|
||||
int AppModel::getRunningAppIndex()
|
||||
int AppModel::getRunningAppId()
|
||||
{
|
||||
if (m_CurrentGameId != 0) {
|
||||
for (int i = 0; i < m_Apps.count(); i++) {
|
||||
if (m_Apps[i].id == m_CurrentGameId) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
return m_CurrentGameId;
|
||||
}
|
||||
|
||||
QString AppModel::getRunningAppName()
|
||||
{
|
||||
if (m_CurrentGameId != 0) {
|
||||
for (int i = 0; i < m_Apps.count(); i++) {
|
||||
if (m_Apps[i].id == m_CurrentGameId) {
|
||||
return m_Apps[i].name;
|
||||
for (int i = 0; i < m_AllApps.count(); i++) {
|
||||
if (m_AllApps[i].id == m_CurrentGameId) {
|
||||
return m_AllApps[i].name;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -47,8 +41,8 @@ QString AppModel::getRunningAppName()
|
||||
|
||||
Session* AppModel::createSessionForApp(int appIndex)
|
||||
{
|
||||
Q_ASSERT(appIndex < m_Apps.count());
|
||||
NvApp app = m_Apps.at(appIndex);
|
||||
Q_ASSERT(appIndex < m_VisibleApps.count());
|
||||
NvApp app = m_VisibleApps.at(appIndex);
|
||||
|
||||
return new Session(m_Computer, app);
|
||||
}
|
||||
@@ -60,7 +54,7 @@ int AppModel::rowCount(const QModelIndex &parent) const
|
||||
if (parent.isValid())
|
||||
return 0;
|
||||
|
||||
return m_Apps.count();
|
||||
return m_VisibleApps.count();
|
||||
}
|
||||
|
||||
QVariant AppModel::data(const QModelIndex &index, int role) const
|
||||
@@ -68,8 +62,8 @@ QVariant AppModel::data(const QModelIndex &index, int role) const
|
||||
if (!index.isValid())
|
||||
return QVariant();
|
||||
|
||||
Q_ASSERT(index.row() < m_Apps.count());
|
||||
NvApp app = m_Apps.at(index.row());
|
||||
Q_ASSERT(index.row() < m_VisibleApps.count());
|
||||
NvApp app = m_VisibleApps.at(index.row());
|
||||
|
||||
switch (role)
|
||||
{
|
||||
@@ -80,6 +74,10 @@ QVariant AppModel::data(const QModelIndex &index, int role) const
|
||||
case BoxArtRole:
|
||||
// FIXME: const-correctness
|
||||
return const_cast<BoxArtManager&>(m_BoxArtManager).loadBoxArt(m_Computer, app);
|
||||
case HiddenRole:
|
||||
return app.hidden;
|
||||
case AppIdRole:
|
||||
return app.id;
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
@@ -92,6 +90,8 @@ QHash<int, QByteArray> AppModel::roleNames() const
|
||||
names[NameRole] = "name";
|
||||
names[RunningRole] = "running";
|
||||
names[BoxArtRole] = "boxart";
|
||||
names[HiddenRole] = "hidden";
|
||||
names[AppIdRole] = "appid";
|
||||
|
||||
return names;
|
||||
}
|
||||
@@ -101,6 +101,112 @@ void AppModel::quitRunningApp()
|
||||
m_ComputerManager->quitRunningApp(m_Computer);
|
||||
}
|
||||
|
||||
bool AppModel::isAppCurrentlyVisible(const NvApp& app)
|
||||
{
|
||||
for (const NvApp& visibleApp : m_VisibleApps) {
|
||||
if (app.id == visibleApp.id) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
QVector<NvApp> AppModel::getVisibleApps(const QVector<NvApp>& appList)
|
||||
{
|
||||
QVector<NvApp> visibleApps;
|
||||
|
||||
for (const NvApp& app : appList) {
|
||||
// Don't immediately hide games that were previously visible. This
|
||||
// allows users to easily uncheck the "Hide App" checkbox if they
|
||||
// check it by mistake.
|
||||
if (m_ShowHiddenGames || !app.hidden || isAppCurrentlyVisible(app)) {
|
||||
visibleApps.append(app);
|
||||
}
|
||||
}
|
||||
|
||||
return visibleApps;
|
||||
}
|
||||
|
||||
void AppModel::updateAppList(QVector<NvApp> newList)
|
||||
{
|
||||
m_AllApps = newList;
|
||||
|
||||
QVector<NvApp> newVisibleList = getVisibleApps(newList);
|
||||
|
||||
// Process removals and updates first
|
||||
for (int i = 0; i < m_VisibleApps.count(); i++) {
|
||||
const NvApp& existingApp = m_VisibleApps.at(i);
|
||||
|
||||
bool found = false;
|
||||
for (const NvApp& newApp : newVisibleList) {
|
||||
if (existingApp.id == newApp.id) {
|
||||
// If the data changed, update it in our list
|
||||
if (existingApp != newApp) {
|
||||
m_VisibleApps.replace(i, newApp);
|
||||
emit dataChanged(createIndex(i, 0), createIndex(i, 0));
|
||||
}
|
||||
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
beginRemoveRows(QModelIndex(), i, i);
|
||||
m_VisibleApps.removeAt(i);
|
||||
endRemoveRows();
|
||||
i--;
|
||||
}
|
||||
}
|
||||
|
||||
// Process additions now
|
||||
for (const NvApp& newApp : newVisibleList) {
|
||||
int insertionIndex = m_VisibleApps.size();
|
||||
bool found = false;
|
||||
|
||||
for (int i = 0; i < m_VisibleApps.count(); i++) {
|
||||
const NvApp& existingApp = m_VisibleApps.at(i);
|
||||
|
||||
if (existingApp.id == newApp.id) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
else if (existingApp.name.toLower() > newApp.name.toLower()) {
|
||||
insertionIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
beginInsertRows(QModelIndex(), insertionIndex, insertionIndex);
|
||||
m_VisibleApps.insert(insertionIndex, newApp);
|
||||
endInsertRows();
|
||||
}
|
||||
}
|
||||
|
||||
Q_ASSERT(newVisibleList == m_VisibleApps);
|
||||
}
|
||||
|
||||
void AppModel::setAppHidden(int appIndex, bool hidden)
|
||||
{
|
||||
Q_ASSERT(appIndex < m_VisibleApps.count());
|
||||
int appId = m_VisibleApps.at(appIndex).id;
|
||||
|
||||
{
|
||||
QWriteLocker lock(&m_Computer->lock);
|
||||
|
||||
for (NvApp& app : m_Computer->appList) {
|
||||
if (app.id == appId) {
|
||||
app.hidden = hidden;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
m_ComputerManager->clientSideAttributeUpdated(m_Computer);
|
||||
}
|
||||
|
||||
void AppModel::handleComputerStateChanged(NvComputer* computer)
|
||||
{
|
||||
// Ignore updates for computers that aren't ours
|
||||
@@ -119,20 +225,15 @@ void AppModel::handleComputerStateChanged(NvComputer* computer)
|
||||
// First, process additions/removals from the app list. This
|
||||
// is required because the new game may now be running, so
|
||||
// we can't check that first.
|
||||
if (computer->appList != m_Apps) {
|
||||
// Just reset the whole thing if the list changes
|
||||
beginResetModel();
|
||||
m_Apps = computer->appList;
|
||||
m_CurrentGameId = computer->currentGameId;
|
||||
endResetModel();
|
||||
return;
|
||||
if (computer->appList != m_AllApps) {
|
||||
updateAppList(computer->appList);
|
||||
}
|
||||
|
||||
// Finally, process changes to the active app
|
||||
if (computer->currentGameId != m_CurrentGameId) {
|
||||
// First, invalidate the running state of newly running game
|
||||
for (int i = 0; i < m_Apps.count(); i++) {
|
||||
if (m_Apps[i].id == computer->currentGameId) {
|
||||
for (int i = 0; i < m_VisibleApps.count(); i++) {
|
||||
if (m_VisibleApps[i].id == computer->currentGameId) {
|
||||
emit dataChanged(createIndex(i, 0),
|
||||
createIndex(i, 0),
|
||||
QVector<int>() << RunningRole);
|
||||
@@ -142,8 +243,8 @@ void AppModel::handleComputerStateChanged(NvComputer* computer)
|
||||
|
||||
// Next, invalidate the running state of the old game (if it exists)
|
||||
if (m_CurrentGameId != 0) {
|
||||
for (int i = 0; i < m_Apps.count(); i++) {
|
||||
if (m_Apps[i].id == m_CurrentGameId) {
|
||||
for (int i = 0; i < m_VisibleApps.count(); i++) {
|
||||
if (m_VisibleApps[i].id == m_CurrentGameId) {
|
||||
emit dataChanged(createIndex(i, 0),
|
||||
createIndex(i, 0),
|
||||
QVector<int>() << RunningRole);
|
||||
@@ -161,7 +262,7 @@ void AppModel::handleBoxArtLoaded(NvComputer* computer, NvApp app, QUrl /* image
|
||||
{
|
||||
Q_ASSERT(computer == m_Computer);
|
||||
|
||||
int index = m_Apps.indexOf(app);
|
||||
int index = m_VisibleApps.indexOf(app);
|
||||
|
||||
// Make sure we're not delivering a callback to an app that's already been removed
|
||||
if (index >= 0) {
|
||||
|
||||
+15
-4
@@ -14,23 +14,27 @@ class AppModel : public QAbstractListModel
|
||||
{
|
||||
NameRole = Qt::UserRole,
|
||||
RunningRole,
|
||||
BoxArtRole
|
||||
BoxArtRole,
|
||||
HiddenRole,
|
||||
AppIdRole,
|
||||
};
|
||||
|
||||
public:
|
||||
explicit AppModel(QObject *parent = nullptr);
|
||||
|
||||
// Must be called before any QAbstractListModel functions
|
||||
Q_INVOKABLE void initialize(ComputerManager* computerManager, int computerIndex);
|
||||
Q_INVOKABLE void initialize(ComputerManager* computerManager, int computerIndex, bool showHiddenGames);
|
||||
|
||||
Q_INVOKABLE Session* createSessionForApp(int appIndex);
|
||||
|
||||
Q_INVOKABLE int getRunningAppIndex();
|
||||
Q_INVOKABLE int getRunningAppId();
|
||||
|
||||
Q_INVOKABLE QString getRunningAppName();
|
||||
|
||||
Q_INVOKABLE void quitRunningApp();
|
||||
|
||||
Q_INVOKABLE void setAppHidden(int appIndex, bool hidden);
|
||||
|
||||
QVariant data(const QModelIndex &index, int role) const override;
|
||||
|
||||
int rowCount(const QModelIndex &parent) const override;
|
||||
@@ -46,9 +50,16 @@ signals:
|
||||
void computerLost();
|
||||
|
||||
private:
|
||||
void updateAppList(QVector<NvApp> newList);
|
||||
|
||||
QVector<NvApp> getVisibleApps(const QVector<NvApp>& appList);
|
||||
|
||||
bool isAppCurrentlyVisible(const NvApp& app);
|
||||
|
||||
NvComputer* m_Computer;
|
||||
BoxArtManager m_BoxArtManager;
|
||||
ComputerManager* m_ComputerManager;
|
||||
QVector<NvApp> m_Apps;
|
||||
QVector<NvApp> m_VisibleApps, m_AllApps;
|
||||
int m_CurrentGameId;
|
||||
bool m_ShowHiddenGames;
|
||||
};
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
#include <QThreadPool>
|
||||
|
||||
#include <random>
|
||||
|
||||
ComputerModel::ComputerModel(QObject* object)
|
||||
: QAbstractListModel(object) {}
|
||||
|
||||
@@ -135,6 +137,62 @@ void ComputerModel::renameComputer(int computerIndex, QString name)
|
||||
m_ComputerManager->renameHost(m_Computers[computerIndex], name);
|
||||
}
|
||||
|
||||
// TODO: Use QRandomGenerator when we drop Qt 5.9 support
|
||||
QString ComputerModel::generatePinString()
|
||||
{
|
||||
std::uniform_int_distribution<int> dist(0, 9999);
|
||||
std::random_device rd;
|
||||
std::mt19937 engine(rd());
|
||||
|
||||
return QString::asprintf("%04u", dist(engine));
|
||||
}
|
||||
|
||||
class DeferredTestConnectionTask : public QObject, public QRunnable
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
void run()
|
||||
{
|
||||
unsigned int portTestResult = LiTestClientConnectivity("qt.conntest.moonlight-stream.org", 443, ML_PORT_FLAG_ALL);
|
||||
if (portTestResult == ML_TEST_RESULT_INCONCLUSIVE) {
|
||||
emit connectionTestCompleted(-1, QString());
|
||||
}
|
||||
else {
|
||||
QString blockedPorts;
|
||||
|
||||
for (int i = 0; i < 32; i++) {
|
||||
if (portTestResult & (1 << i)) {
|
||||
if (!blockedPorts.isEmpty()) {
|
||||
blockedPorts += "\n";
|
||||
}
|
||||
|
||||
if (LiGetProtocolFromPortFlagIndex(i) == 17 /* IPPROTO_UDP */) {
|
||||
blockedPorts += "UDP ";
|
||||
}
|
||||
else {
|
||||
blockedPorts += "TCP ";
|
||||
}
|
||||
|
||||
blockedPorts += QString::number(LiGetPortFromPortFlagIndex(i));
|
||||
}
|
||||
}
|
||||
|
||||
emit connectionTestCompleted(portTestResult, blockedPorts);
|
||||
}
|
||||
}
|
||||
|
||||
signals:
|
||||
void connectionTestCompleted(int result, QString blockedPorts);
|
||||
};
|
||||
|
||||
void ComputerModel::testConnectionForComputer(int)
|
||||
{
|
||||
DeferredTestConnectionTask* testConnectionTask = new DeferredTestConnectionTask();
|
||||
QObject::connect(testConnectionTask, &DeferredTestConnectionTask::connectionTestCompleted,
|
||||
this, &ComputerModel::connectionTestCompleted);
|
||||
QThreadPool::globalInstance()->start(testConnectionTask);
|
||||
}
|
||||
|
||||
void ComputerModel::pairComputer(int computerIndex, QString pin)
|
||||
{
|
||||
Q_ASSERT(computerIndex < m_Computers.count());
|
||||
@@ -165,3 +223,4 @@ void ComputerModel::handleComputerStateChanged(NvComputer* computer)
|
||||
}
|
||||
}
|
||||
|
||||
#include "computermodel.moc"
|
||||
|
||||
@@ -31,8 +31,12 @@ public:
|
||||
|
||||
Q_INVOKABLE void deleteComputer(int computerIndex);
|
||||
|
||||
Q_INVOKABLE QString generatePinString();
|
||||
|
||||
Q_INVOKABLE void pairComputer(int computerIndex, QString pin);
|
||||
|
||||
Q_INVOKABLE void testConnectionForComputer(int computerIndex);
|
||||
|
||||
Q_INVOKABLE void wakeComputer(int computerIndex);
|
||||
|
||||
Q_INVOKABLE void renameComputer(int computerIndex, QString name);
|
||||
@@ -41,6 +45,7 @@ public:
|
||||
|
||||
signals:
|
||||
void pairingCompleted(QVariant error);
|
||||
void connectionTestCompleted(int result, QString blockedPorts);
|
||||
|
||||
private slots:
|
||||
void handleComputerStateChanged(NvComputer* computer);
|
||||
|
||||
@@ -230,6 +230,26 @@ ApplicationWindow {
|
||||
verticalAlignment: Qt.AlignVCenter
|
||||
}
|
||||
|
||||
NavigableToolButton {
|
||||
id: discordButton
|
||||
visible: SystemProperties.hasBrowser &&
|
||||
stackView.currentItem.objectName === "Settings"
|
||||
|
||||
iconSource: "qrc:/res/Discord-Logo-White.svg"
|
||||
|
||||
ToolTip.delay: 1000
|
||||
ToolTip.timeout: 3000
|
||||
ToolTip.visible: hovered
|
||||
ToolTip.text: "Join our community on Discord"
|
||||
|
||||
// TODO need to make sure browser is brought to foreground.
|
||||
onClicked: Qt.openUrlExternally("https://moonlight-stream.org/discord");
|
||||
|
||||
Keys.onDownPressed: {
|
||||
stackView.currentItem.forceActiveFocus(Qt.TabFocus)
|
||||
}
|
||||
}
|
||||
|
||||
NavigableToolButton {
|
||||
id: addPcButton
|
||||
visible: stackView.currentItem.objectName === "Computers"
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
<svg id="Layer_1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 245 240"><style>.st0{fill:#FFFFFF;}</style><path class="st0" d="M104.4 103.9c-5.7 0-10.2 5-10.2 11.1s4.6 11.1 10.2 11.1c5.7 0 10.2-5 10.2-11.1.1-6.1-4.5-11.1-10.2-11.1zM140.9 103.9c-5.7 0-10.2 5-10.2 11.1s4.6 11.1 10.2 11.1c5.7 0 10.2-5 10.2-11.1s-4.5-11.1-10.2-11.1z"/><path class="st0" d="M189.5 20h-134C44.2 20 35 29.2 35 40.6v135.2c0 11.4 9.2 20.6 20.5 20.6h113.4l-5.3-18.5 12.8 11.9 12.1 11.2 21.5 19V40.6c0-11.4-9.2-20.6-20.5-20.6zm-38.6 130.6s-3.6-4.3-6.6-8.1c13.1-3.7 18.1-11.9 18.1-11.9-4.1 2.7-8 4.6-11.5 5.9-5 2.1-9.8 3.5-14.5 4.3-9.6 1.8-18.4 1.3-25.9-.1-5.7-1.1-10.6-2.7-14.7-4.3-2.3-.9-4.8-2-7.3-3.4-.3-.2-.6-.3-.9-.5-.2-.1-.3-.2-.4-.3-1.8-1-2.8-1.7-2.8-1.7s4.8 8 17.5 11.8c-3 3.8-6.7 8.3-6.7 8.3-22.1-.7-30.5-15.2-30.5-15.2 0-32.2 14.4-58.3 14.4-58.3 14.4-10.8 28.1-10.5 28.1-10.5l1 1.2c-18 5.2-26.3 13.1-26.3 13.1s2.2-1.2 5.9-2.9c10.7-4.7 19.2-6 22.7-6.3.6-.1 1.1-.2 1.7-.2 6.1-.8 13-1 20.2-.2 9.5 1.1 19.7 3.9 30.1 9.6 0 0-7.9-7.5-24.9-12.7l1.4-1.6s13.7-.3 28.1 10.5c0 0 14.4 26.1 14.4 58.3 0 0-8.5 14.5-30.6 15.2z"/></svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 0 24 24" width="24" fill="#FFFFFF"><path d="M0 0h24v24H0V0zm0 0h24v24H0V0z" fill="none"/><path d="M16.59 7.58L10 14.17l-3.59-3.58L5 12l5 5 8-8zM12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8z"/></svg>
|
||||
|
After Width: | Height: | Size: 339 B |
@@ -15,6 +15,8 @@
|
||||
<file>res/update.svg</file>
|
||||
<file>res/baseline-help_outline-24px.svg</file>
|
||||
<file>res/baseline-error_outline-24px.svg</file>
|
||||
<file>res/baseline-check_circle_outline-24px.svg</file>
|
||||
<file>res/Discord-Logo-White.svg</file>
|
||||
</qresource>
|
||||
<qresource prefix="/data">
|
||||
<file alias="gamecontrollerdb.txt">SDL_GameControllerDB/gamecontrollerdb.txt</file>
|
||||
|
||||
@@ -28,6 +28,8 @@
|
||||
#define SER_GAMEPADMOUSE "gamepadmouse"
|
||||
#define SER_DEFAULTVER "defaultver"
|
||||
#define SER_PACKETSIZE "packetsize"
|
||||
#define SER_DETECTNETBLOCKING "detectnetblocking"
|
||||
#define SER_SWAPMOUSEBUTTONS "swapmousebuttons"
|
||||
|
||||
#define CURRENT_DEFAULT_VER 1
|
||||
|
||||
@@ -67,7 +69,9 @@ void StreamingPreferences::reload()
|
||||
connectionWarnings = settings.value(SER_CONNWARNINGS, true).toBool();
|
||||
richPresence = settings.value(SER_RICHPRESENCE, true).toBool();
|
||||
gamepadMouse = settings.value(SER_GAMEPADMOUSE, true).toBool();
|
||||
detectNetworkBlocking = settings.value(SER_DETECTNETBLOCKING, true).toBool();
|
||||
packetSize = settings.value(SER_PACKETSIZE, 0).toInt();
|
||||
swapMouseButtons = settings.value(SER_SWAPMOUSEBUTTONS, false).toBool();
|
||||
audioConfig = static_cast<AudioConfig>(settings.value(SER_AUDIOCFG,
|
||||
static_cast<int>(AudioConfig::AC_STEREO)).toInt());
|
||||
videoCodecConfig = static_cast<VideoCodecConfig>(settings.value(SER_VIDEOCFG,
|
||||
@@ -113,11 +117,13 @@ void StreamingPreferences::save()
|
||||
settings.setValue(SER_RICHPRESENCE, richPresence);
|
||||
settings.setValue(SER_GAMEPADMOUSE, gamepadMouse);
|
||||
settings.setValue(SER_PACKETSIZE, packetSize);
|
||||
settings.setValue(SER_DETECTNETBLOCKING, detectNetworkBlocking);
|
||||
settings.setValue(SER_AUDIOCFG, static_cast<int>(audioConfig));
|
||||
settings.setValue(SER_VIDEOCFG, static_cast<int>(videoCodecConfig));
|
||||
settings.setValue(SER_VIDEODEC, static_cast<int>(videoDecoderSelection));
|
||||
settings.setValue(SER_WINDOWMODE, static_cast<int>(windowMode));
|
||||
settings.setValue(SER_DEFAULTVER, CURRENT_DEFAULT_VER);
|
||||
settings.setValue(SER_SWAPMOUSEBUTTONS, swapMouseButtons);
|
||||
}
|
||||
|
||||
int StreamingPreferences::getDefaultBitrate(int width, int height, int fps)
|
||||
|
||||
@@ -68,11 +68,13 @@ public:
|
||||
Q_PROPERTY(bool connectionWarnings MEMBER connectionWarnings NOTIFY connectionWarningsChanged)
|
||||
Q_PROPERTY(bool richPresence MEMBER richPresence NOTIFY richPresenceChanged)
|
||||
Q_PROPERTY(bool gamepadMouse MEMBER gamepadMouse NOTIFY gamepadMouseChanged)
|
||||
Q_PROPERTY(bool detectNetworkBlocking MEMBER detectNetworkBlocking NOTIFY detectNetworkBlockingChanged);
|
||||
Q_PROPERTY(AudioConfig audioConfig MEMBER audioConfig NOTIFY audioConfigChanged)
|
||||
Q_PROPERTY(VideoCodecConfig videoCodecConfig MEMBER videoCodecConfig NOTIFY videoCodecConfigChanged)
|
||||
Q_PROPERTY(VideoDecoderSelection videoDecoderSelection MEMBER videoDecoderSelection NOTIFY videoDecoderSelectionChanged)
|
||||
Q_PROPERTY(WindowMode windowMode MEMBER windowMode NOTIFY windowModeChanged)
|
||||
Q_PROPERTY(WindowMode recommendedFullScreenMode MEMBER recommendedFullScreenMode CONSTANT)
|
||||
Q_PROPERTY(bool swapMouseButtons MEMBER swapMouseButtons NOTIFY mouseButtonsChanged)
|
||||
|
||||
// Directly accessible members for preferences
|
||||
int width;
|
||||
@@ -93,6 +95,8 @@ public:
|
||||
bool connectionWarnings;
|
||||
bool richPresence;
|
||||
bool gamepadMouse;
|
||||
bool detectNetworkBlocking;
|
||||
bool swapMouseButtons;
|
||||
int packetSize;
|
||||
AudioConfig audioConfig;
|
||||
VideoCodecConfig videoCodecConfig;
|
||||
@@ -121,5 +125,7 @@ signals:
|
||||
void connectionWarningsChanged();
|
||||
void richPresenceChanged();
|
||||
void gamepadMouseChanged();
|
||||
void detectNetworkBlockingChanged();
|
||||
void mouseButtonsChanged();
|
||||
};
|
||||
|
||||
|
||||
@@ -12,7 +12,11 @@
|
||||
SdlInputHandler::SdlInputHandler(StreamingPreferences& prefs, NvComputer*, int streamWidth, int streamHeight)
|
||||
: m_MultiController(prefs.multiController),
|
||||
m_GamepadMouse(prefs.gamepadMouse),
|
||||
m_SwapMouseButtons(prefs.swapMouseButtons),
|
||||
m_MouseMoveTimer(0),
|
||||
m_MousePositionLock(0),
|
||||
m_MouseWasInVideoRegion(false),
|
||||
m_PendingMouseButtonsAllUpOnVideoRegionLeave(false),
|
||||
m_FakeCaptureActive(false),
|
||||
m_LongPressTimer(0),
|
||||
m_StreamWidth(streamWidth),
|
||||
@@ -60,6 +64,23 @@ SdlInputHandler::SdlInputHandler(StreamingPreferences& prefs, NvComputer*, int s
|
||||
SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS4_RUMBLE, "1");
|
||||
#endif
|
||||
|
||||
m_OldIgnoreDevices = SDL_GetHint(SDL_HINT_GAMECONTROLLER_IGNORE_DEVICES);
|
||||
m_OldIgnoreDevicesExcept = SDL_GetHint(SDL_HINT_GAMECONTROLLER_IGNORE_DEVICES_EXCEPT);
|
||||
|
||||
QString streamIgnoreDevices = qgetenv("STREAM_GAMECONTROLLER_IGNORE_DEVICES");
|
||||
QString streamIgnoreDevicesExcept = qgetenv("STREAM_GAMECONTROLLER_IGNORE_DEVICES_EXCEPT");
|
||||
|
||||
if (!streamIgnoreDevices.isEmpty() && !streamIgnoreDevices.endsWith(',')) {
|
||||
streamIgnoreDevices += ',';
|
||||
}
|
||||
streamIgnoreDevices += m_OldIgnoreDevices;
|
||||
|
||||
// For SDL_HINT_GAMECONTROLLER_IGNORE_DEVICES, we use the union of SDL_GAMECONTROLLER_IGNORE_DEVICES
|
||||
// and STREAM_GAMECONTROLLER_IGNORE_DEVICES while streaming. STREAM_GAMECONTROLLER_IGNORE_DEVICES_EXCEPT
|
||||
// overrides SDL_GAMECONTROLLER_IGNORE_DEVICES_EXCEPT while streaming.
|
||||
SDL_SetHint(SDL_HINT_GAMECONTROLLER_IGNORE_DEVICES, streamIgnoreDevices.toUtf8());
|
||||
SDL_SetHint(SDL_HINT_GAMECONTROLLER_IGNORE_DEVICES_EXCEPT, streamIgnoreDevicesExcept.toUtf8());
|
||||
|
||||
// We must initialize joystick explicitly before gamecontroller in order
|
||||
// to ensure we receive gamecontroller attach events for gamepads where
|
||||
// SDL doesn't have a built-in mapping. By starting joystick first, we
|
||||
@@ -108,9 +129,11 @@ SdlInputHandler::SdlInputHandler(StreamingPreferences& prefs, NvComputer*, int s
|
||||
SDL_zero(m_LastTouchDownEvent);
|
||||
SDL_zero(m_LastTouchUpEvent);
|
||||
SDL_zero(m_TouchDownEvent);
|
||||
SDL_zero(m_MousePositionReport);
|
||||
|
||||
SDL_AtomicSet(&m_MouseDeltaX, 0);
|
||||
SDL_AtomicSet(&m_MouseDeltaY, 0);
|
||||
SDL_AtomicSet(&m_MousePositionUpdated, 0);
|
||||
|
||||
Uint32 pollingInterval = QString(qgetenv("MOUSE_POLLING_INTERVAL")).toUInt();
|
||||
if (pollingInterval == 0) {
|
||||
@@ -162,6 +185,10 @@ SdlInputHandler::~SdlInputHandler()
|
||||
// Return background event handling to off
|
||||
SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "0");
|
||||
|
||||
// Restore the ignored devices
|
||||
SDL_SetHint(SDL_HINT_GAMECONTROLLER_IGNORE_DEVICES, m_OldIgnoreDevices.toUtf8());
|
||||
SDL_SetHint(SDL_HINT_GAMECONTROLLER_IGNORE_DEVICES_EXCEPT, m_OldIgnoreDevicesExcept.toUtf8());
|
||||
|
||||
#ifdef STEAM_LINK
|
||||
// Hide SDL's cursor on Steam Link after quitting the stream.
|
||||
// FIXME: We should also do this for other situations where SDL
|
||||
@@ -258,6 +285,25 @@ void SdlInputHandler::setCaptureActive(bool active)
|
||||
SDL_ShowCursor(SDL_DISABLE);
|
||||
m_FakeCaptureActive = true;
|
||||
}
|
||||
|
||||
// Synchronize the client and host cursor when activating absolute capture
|
||||
if (m_AbsoluteMouseMode) {
|
||||
int mouseX, mouseY;
|
||||
int windowX, windowY;
|
||||
|
||||
// We have to use SDL_GetGlobalMouseState() because macOS may not reflect
|
||||
// the new position of the mouse when outside the window.
|
||||
SDL_GetGlobalMouseState(&mouseX, &mouseY);
|
||||
|
||||
// Convert global mouse state to window-relative
|
||||
SDL_GetWindowPosition(m_Window, &windowX, &windowY);
|
||||
mouseX -= windowX;
|
||||
mouseY -= windowY;
|
||||
|
||||
if (isMouseInVideoRegion(mouseX, mouseY)) {
|
||||
updateMousePositionReport(mouseX, mouseY);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (m_FakeCaptureActive) {
|
||||
|
||||
@@ -5,6 +5,9 @@
|
||||
|
||||
#include <SDL.h>
|
||||
|
||||
#define SDL_CODE_HIDE_CURSOR 1
|
||||
#define SDL_CODE_SHOW_CURSOR 2
|
||||
|
||||
struct GamepadState {
|
||||
SDL_GameController* controller;
|
||||
SDL_JoystickID jsId;
|
||||
@@ -77,6 +80,12 @@ public:
|
||||
|
||||
void setCaptureActive(bool active);
|
||||
|
||||
bool isMouseInVideoRegion(int mouseX, int mouseY, int windowWidth = -1, int windowHeight = -1);
|
||||
|
||||
void updateMousePositionReport(int mouseX, int mouseY);
|
||||
|
||||
void flushMousePositionUpdate();
|
||||
|
||||
static
|
||||
QString getUnmappedGamepads();
|
||||
|
||||
@@ -111,13 +120,26 @@ private:
|
||||
SDL_Window* m_Window;
|
||||
bool m_MultiController;
|
||||
bool m_GamepadMouse;
|
||||
bool m_SwapMouseButtons;
|
||||
SDL_TimerID m_MouseMoveTimer;
|
||||
SDL_atomic_t m_MouseDeltaX;
|
||||
SDL_atomic_t m_MouseDeltaY;
|
||||
|
||||
SDL_SpinLock m_MousePositionLock;
|
||||
struct {
|
||||
int x, y;
|
||||
int windowWidth, windowHeight;
|
||||
} m_MousePositionReport;
|
||||
SDL_atomic_t m_MousePositionUpdated;
|
||||
bool m_MouseWasInVideoRegion;
|
||||
bool m_PendingMouseButtonsAllUpOnVideoRegionLeave;
|
||||
|
||||
int m_GamepadMask;
|
||||
GamepadState m_GamepadState[MAX_GAMEPADS];
|
||||
QSet<short> m_KeysDown;
|
||||
bool m_FakeCaptureActive;
|
||||
QString m_OldIgnoreDevices;
|
||||
QString m_OldIgnoreDevicesExcept;
|
||||
|
||||
SDL_TouchFingerEvent m_LastTouchDownEvent;
|
||||
SDL_TouchFingerEvent m_LastTouchUpEvent;
|
||||
|
||||
+144
-21
@@ -13,7 +13,8 @@ void SdlInputHandler::handleMouseButtonEvent(SDL_MouseButtonEvent* event)
|
||||
return;
|
||||
}
|
||||
else if (!isCaptureActive()) {
|
||||
if (event->button == SDL_BUTTON_LEFT && event->state == SDL_RELEASED) {
|
||||
if (event->button == SDL_BUTTON_LEFT && event->state == SDL_RELEASED &&
|
||||
isMouseInVideoRegion(event->x, event->y)) {
|
||||
// Capture the mouse again if clicked when unbound.
|
||||
// We start capture on left button released instead of
|
||||
// pressed to avoid sending an errant mouse button released
|
||||
@@ -25,6 +26,10 @@ void SdlInputHandler::handleMouseButtonEvent(SDL_MouseButtonEvent* event)
|
||||
// Not capturing
|
||||
return;
|
||||
}
|
||||
else if (m_AbsoluteMouseMode && !isMouseInVideoRegion(event->x, event->y) && event->state == SDL_PRESSED) {
|
||||
// Ignore button presses outside the video region, but allow button releases
|
||||
return;
|
||||
}
|
||||
|
||||
switch (event->button)
|
||||
{
|
||||
@@ -50,12 +55,112 @@ void SdlInputHandler::handleMouseButtonEvent(SDL_MouseButtonEvent* event)
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_SwapMouseButtons) {
|
||||
if (button == BUTTON_RIGHT)
|
||||
button = BUTTON_LEFT;
|
||||
else if (button == BUTTON_LEFT)
|
||||
button = BUTTON_RIGHT;
|
||||
}
|
||||
|
||||
// Ensure any pending mouse position update has been sent before we send
|
||||
// our button event. This ensures that the cursor is in the correct location
|
||||
// when the button event is issued.
|
||||
//
|
||||
// On platforms like macOS, the mouse doesn't track when the window isn't
|
||||
// focused. When we gain focus via mouse click, we immediately get a mouse
|
||||
// move event and a mouse button event. If we don't flush here, the button
|
||||
// will probably arrive before the mouse timer issues the position update.
|
||||
flushMousePositionUpdate();
|
||||
|
||||
LiSendMouseButtonEvent(event->state == SDL_PRESSED ?
|
||||
BUTTON_ACTION_PRESS :
|
||||
BUTTON_ACTION_RELEASE,
|
||||
button);
|
||||
}
|
||||
|
||||
void SdlInputHandler::updateMousePositionReport(int mouseX, int mouseY)
|
||||
{
|
||||
int windowWidth, windowHeight;
|
||||
|
||||
// Call SDL_GetWindowSize() before entering the spinlock
|
||||
SDL_GetWindowSize(m_Window, &windowWidth, &windowHeight);
|
||||
|
||||
SDL_AtomicLock(&m_MousePositionLock);
|
||||
m_MousePositionReport.x = mouseX;
|
||||
m_MousePositionReport.y = mouseY;
|
||||
m_MousePositionReport.windowWidth = windowWidth;
|
||||
m_MousePositionReport.windowHeight = windowHeight;
|
||||
SDL_AtomicUnlock(&m_MousePositionLock);
|
||||
SDL_AtomicSet(&m_MousePositionUpdated, 1);
|
||||
}
|
||||
|
||||
void SdlInputHandler::flushMousePositionUpdate()
|
||||
{
|
||||
bool hasNewPosition = SDL_AtomicSet(&m_MousePositionUpdated, 0) != 0;
|
||||
if (hasNewPosition) {
|
||||
// If the lock is held now, the main thread is trying to update
|
||||
// the mouse position. We'll pick up the new position next time.
|
||||
if (SDL_AtomicTryLock(&m_MousePositionLock)) {
|
||||
SDL_Rect src, dst;
|
||||
bool mouseInVideoRegion;
|
||||
|
||||
src.x = src.y = 0;
|
||||
src.w = m_StreamWidth;
|
||||
src.h = m_StreamHeight;
|
||||
|
||||
dst.x = dst.y = 0;
|
||||
dst.w = m_MousePositionReport.windowWidth;
|
||||
dst.h = m_MousePositionReport.windowHeight;
|
||||
|
||||
// Use the stream and window sizes to determine the video region
|
||||
StreamUtils::scaleSourceToDestinationSurface(&src, &dst);
|
||||
|
||||
mouseInVideoRegion = isMouseInVideoRegion(m_MousePositionReport.x,
|
||||
m_MousePositionReport.y,
|
||||
m_MousePositionReport.windowWidth,
|
||||
m_MousePositionReport.windowHeight);
|
||||
|
||||
// Clamp motion to the video region
|
||||
short x = qMin(qMax(m_MousePositionReport.x - dst.x, 0), dst.w);
|
||||
short y = qMin(qMax(m_MousePositionReport.y - dst.y, 0), dst.h);
|
||||
|
||||
// Release the spinlock to unblock the main thread
|
||||
SDL_AtomicUnlock(&m_MousePositionLock);
|
||||
|
||||
// Send the mouse position update if one of the following is true:
|
||||
// a) it is in the video region now
|
||||
// b) it just left the video region (to ensure the mouse is clamped to the video boundary)
|
||||
// c) a mouse button is still down from before the cursor left the video region (to allow smooth dragging)
|
||||
Uint32 buttonState = SDL_GetMouseState(nullptr, nullptr);
|
||||
if (buttonState == 0) {
|
||||
m_PendingMouseButtonsAllUpOnVideoRegionLeave = false;
|
||||
}
|
||||
if (mouseInVideoRegion || m_MouseWasInVideoRegion || m_PendingMouseButtonsAllUpOnVideoRegionLeave) {
|
||||
LiSendMousePositionEvent(x, y, dst.w, dst.h);
|
||||
}
|
||||
|
||||
// Adjust the cursor visibility if applicable
|
||||
if (mouseInVideoRegion ^ m_MouseWasInVideoRegion) {
|
||||
// We must push an event for the main thread to process, because it's not safe
|
||||
// to directly can SDL_ShowCursor() on the arbitrary thread on which this timer
|
||||
// executes.
|
||||
SDL_Event event;
|
||||
event.type = SDL_USEREVENT;
|
||||
event.user.code = mouseInVideoRegion ? SDL_CODE_HIDE_CURSOR : SDL_CODE_SHOW_CURSOR;
|
||||
SDL_PushEvent(&event);
|
||||
|
||||
if (!mouseInVideoRegion && buttonState != 0) {
|
||||
// If we still have a button pressed on leave, wait for that to come up
|
||||
// before we stop sending mouse position events.
|
||||
m_PendingMouseButtonsAllUpOnVideoRegionLeave = true;
|
||||
}
|
||||
}
|
||||
|
||||
m_MouseWasInVideoRegion = mouseInVideoRegion;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SdlInputHandler::handleMouseMotionEvent(SDL_MouseMotionEvent* event)
|
||||
{
|
||||
if (!isCaptureActive()) {
|
||||
@@ -67,29 +172,12 @@ void SdlInputHandler::handleMouseMotionEvent(SDL_MouseMotionEvent* event)
|
||||
return;
|
||||
}
|
||||
|
||||
// Batch until the next mouse polling window or we'll get awful
|
||||
// input lag everything except GFE 3.14 and 3.15.
|
||||
if (m_AbsoluteMouseMode) {
|
||||
SDL_Rect src, dst;
|
||||
|
||||
src.x = src.y = 0;
|
||||
src.w = m_StreamWidth;
|
||||
src.h = m_StreamHeight;
|
||||
|
||||
dst.x = dst.y = 0;
|
||||
SDL_GetWindowSize(m_Window, &dst.w, &dst.h);
|
||||
|
||||
// Use the stream and window sizes to determine the video region
|
||||
StreamUtils::scaleSourceToDestinationSurface(&src, &dst);
|
||||
|
||||
// Clamp motion to the video region
|
||||
short x = qMin(qMax(event->x - dst.x, 0), dst.w);
|
||||
short y = qMin(qMax(event->y - dst.y, 0), dst.h);
|
||||
|
||||
// Send the mouse position update
|
||||
LiSendMousePositionEvent(x, y, dst.w, dst.h);
|
||||
updateMousePositionReport(event->x, event->y);
|
||||
}
|
||||
else {
|
||||
// Batch until the next mouse polling window or we'll get awful
|
||||
// input lag everything except GFE 3.14 and 3.15.
|
||||
SDL_AtomicAdd(&m_MouseDeltaX, event->xrel);
|
||||
SDL_AtomicAdd(&m_MouseDeltaY, event->yrel);
|
||||
}
|
||||
@@ -106,11 +194,43 @@ void SdlInputHandler::handleMouseWheelEvent(SDL_MouseWheelEvent* event)
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_AbsoluteMouseMode) {
|
||||
int mouseX, mouseY;
|
||||
SDL_GetMouseState(&mouseX, &mouseY);
|
||||
if (!isMouseInVideoRegion(mouseX, mouseY)) {
|
||||
// Ignore scroll events outside the video region
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (event->y != 0) {
|
||||
LiSendScrollEvent((signed char)event->y);
|
||||
}
|
||||
}
|
||||
|
||||
bool SdlInputHandler::isMouseInVideoRegion(int mouseX, int mouseY, int windowWidth, int windowHeight)
|
||||
{
|
||||
SDL_Rect src, dst;
|
||||
|
||||
if (windowWidth < 0 || windowHeight < 0) {
|
||||
SDL_GetWindowSize(m_Window, &windowWidth, &windowHeight);
|
||||
}
|
||||
|
||||
src.x = src.y = 0;
|
||||
src.w = m_StreamWidth;
|
||||
src.h = m_StreamHeight;
|
||||
|
||||
dst.x = dst.y = 0;
|
||||
dst.w = windowWidth;
|
||||
dst.h = windowHeight;
|
||||
|
||||
// Use the stream and window sizes to determine the video region
|
||||
StreamUtils::scaleSourceToDestinationSurface(&src, &dst);
|
||||
|
||||
return (mouseX >= dst.x && mouseX <= dst.x + dst.w) &&
|
||||
(mouseY >= dst.y && mouseY <= dst.y + dst.h);
|
||||
}
|
||||
|
||||
Uint32 SdlInputHandler::mouseMoveTimerCallback(Uint32 interval, void *param)
|
||||
{
|
||||
auto me = reinterpret_cast<SdlInputHandler*>(param);
|
||||
@@ -122,6 +242,9 @@ Uint32 SdlInputHandler::mouseMoveTimerCallback(Uint32 interval, void *param)
|
||||
LiSendMouseMoveEvent(deltaX, deltaY);
|
||||
}
|
||||
|
||||
// Send mouse position updates if applicable
|
||||
me->flushMousePositionUpdate();
|
||||
|
||||
#ifdef Q_OS_WIN32
|
||||
// See comment in SdlInputHandler::notifyMouseLeave()
|
||||
if (me->m_AbsoluteMouseMode && me->m_PendingMouseLeaveButtonUp != 0 && me->isCaptureActive()) {
|
||||
|
||||
+161
-73
@@ -33,6 +33,16 @@
|
||||
#include <QGuiApplication>
|
||||
#include <QCursor>
|
||||
|
||||
#define CONN_TEST_SERVER "qt.conntest.moonlight-stream.org"
|
||||
|
||||
// Running the connection process asynchronously seems to reliably
|
||||
// cause a crash in QSGRenderThread on Wayland and strange crashes
|
||||
// elsewhere. Until these are figured out, avoid the async connect
|
||||
// thread on Linux and BSDs.
|
||||
#if !defined(Q_OS_UNIX) || defined(Q_OS_DARWIN)
|
||||
#define USE_ASYNC_CONNECT_THREAD 1
|
||||
#endif
|
||||
|
||||
CONNECTION_LISTENER_CALLBACKS Session::k_ConnCallbacks = {
|
||||
Session::clStageStarting,
|
||||
nullptr,
|
||||
@@ -53,20 +63,30 @@ void Session::clStageStarting(int stage)
|
||||
// which happens to be the main thread, so it's cool to interact
|
||||
// with the GUI in these callbacks.
|
||||
emit s_ActiveSession->stageStarting(QString::fromLocal8Bit(LiGetStageName(stage)));
|
||||
|
||||
#ifndef USE_ASYNC_CONNECT_THREAD
|
||||
QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
|
||||
QCoreApplication::sendPostedEvents();
|
||||
#endif
|
||||
}
|
||||
|
||||
void Session::clStageFailed(int stage, int errorCode)
|
||||
{
|
||||
// We know this is called on the same thread as LiStartConnection()
|
||||
// which happens to be the main thread, so it's cool to interact
|
||||
// with the GUI in these callbacks.
|
||||
// Perform the port test now, while we're on the async connection thread and not blocking the UI.
|
||||
s_ActiveSession->m_PortTestResults = LiTestClientConnectivity(CONN_TEST_SERVER, 443, LiGetPortFlagsFromStage(stage));
|
||||
|
||||
emit s_ActiveSession->stageFailed(QString::fromLocal8Bit(LiGetStageName(stage)), errorCode);
|
||||
|
||||
#ifndef USE_ASYNC_CONNECT_THREAD
|
||||
QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
|
||||
QCoreApplication::sendPostedEvents();
|
||||
#endif
|
||||
}
|
||||
|
||||
void Session::clConnectionTerminated(int errorCode)
|
||||
{
|
||||
s_ActiveSession->m_PortTestResults = LiTestClientConnectivity(CONN_TEST_SERVER, 443, LiGetPortFlagsFromTerminationErrorCode(errorCode));
|
||||
|
||||
// Display the termination dialog if this was not intended
|
||||
switch (errorCode) {
|
||||
case ML_ERROR_GRACEFUL_TERMINATION:
|
||||
@@ -77,6 +97,11 @@ void Session::clConnectionTerminated(int errorCode)
|
||||
emit s_ActiveSession->displayLaunchError("No video received from host. Check the host PC's firewall and port forwarding rules.");
|
||||
break;
|
||||
|
||||
case ML_ERROR_NO_VIDEO_FRAME:
|
||||
s_ActiveSession->m_UnexpectedTermination = true;
|
||||
emit s_ActiveSession->displayLaunchError("Your network connection isn't performing well. Reduce your video bitrate setting or try a faster connection.");
|
||||
break;
|
||||
|
||||
default:
|
||||
s_ActiveSession->m_UnexpectedTermination = true;
|
||||
emit s_ActiveSession->displayLaunchError("Connection terminated");
|
||||
@@ -336,6 +361,8 @@ Session::Session(NvComputer* computer, NvApp& app, StreamingPreferences *prefere
|
||||
m_InputHandler(nullptr),
|
||||
m_InputHandlerLock(0),
|
||||
m_MouseEmulationRefCount(0),
|
||||
m_AsyncConnectionSuccess(false),
|
||||
m_PortTestResults(0),
|
||||
m_OpusDecoder(nullptr),
|
||||
m_AudioRenderer(nullptr),
|
||||
m_AudioSampleCount(0),
|
||||
@@ -363,13 +390,21 @@ bool Session::initialize()
|
||||
}
|
||||
|
||||
// Create a hidden window to use for decoder initialization tests
|
||||
SDL_Window* testWindow = SDL_CreateWindow("", 0, 0, 1280, 720, SDL_WINDOW_HIDDEN);
|
||||
SDL_Window* testWindow = SDL_CreateWindow("", 0, 0, 1280, 720,
|
||||
SDL_WINDOW_HIDDEN | StreamUtils::getPlatformWindowFlags());
|
||||
if (!testWindow) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Failed to create window for hardware decode test: %s",
|
||||
SDL_GetError());
|
||||
SDL_QuitSubSystem(SDL_INIT_VIDEO);
|
||||
return false;
|
||||
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Failed to create test window with platform flags: %s",
|
||||
SDL_GetError());
|
||||
|
||||
testWindow = SDL_CreateWindow("", 0, 0, 1280, 720, SDL_WINDOW_HIDDEN);
|
||||
if (!testWindow) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Failed to create window for hardware decode test: %s",
|
||||
SDL_GetError());
|
||||
SDL_QuitSubSystem(SDL_INIT_VIDEO);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
qInfo() << "Server GPU:" << m_Computer->gpuModel;
|
||||
@@ -520,6 +555,7 @@ void Session::emitLaunchWarning(QString text)
|
||||
// Pump the UI loop while we wait
|
||||
SDL_Delay(5);
|
||||
QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
|
||||
QCoreApplication::sendPostedEvents();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -706,7 +742,7 @@ private:
|
||||
emit m_Session->quitStarting();
|
||||
}
|
||||
else {
|
||||
emit m_Session->sessionFinished();
|
||||
emit m_Session->sessionFinished(m_Session->m_PortTestResults);
|
||||
}
|
||||
|
||||
// Finish cleanup of the connection state
|
||||
@@ -724,7 +760,7 @@ private:
|
||||
}
|
||||
|
||||
// Session is finished now
|
||||
emit m_Session->sessionFinished();
|
||||
emit m_Session->sessionFinished(m_Session->m_PortTestResults);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -913,39 +949,37 @@ void Session::notifyMouseEmulationMode(bool enabled)
|
||||
}
|
||||
}
|
||||
|
||||
void Session::exec(int displayOriginX, int displayOriginY)
|
||||
class AsyncConnectionStartThread : public QThread
|
||||
{
|
||||
m_DisplayOriginX = displayOriginX;
|
||||
m_DisplayOriginY = displayOriginY;
|
||||
public:
|
||||
AsyncConnectionStartThread(Session* session) :
|
||||
QThread(nullptr),
|
||||
m_Session(session) {}
|
||||
|
||||
// Complete initialization in this deferred context to avoid
|
||||
// calling expensive functions in the constructor (during the
|
||||
// process of loading the StreamSegue).
|
||||
if (!initialize()) {
|
||||
emit sessionFinished();
|
||||
return;
|
||||
void run() override
|
||||
{
|
||||
m_Session->m_AsyncConnectionSuccess = m_Session->startConnectionAsync();
|
||||
}
|
||||
|
||||
Session* m_Session;
|
||||
};
|
||||
|
||||
// Called in a non-main thread
|
||||
bool Session::startConnectionAsync()
|
||||
{
|
||||
// Wait 1.5 seconds before connecting to let the user
|
||||
// have time to read any messages present on the segue
|
||||
#ifdef USE_ASYNC_CONNECT_THREAD
|
||||
SDL_Delay(1500);
|
||||
#else
|
||||
uint32_t start = SDL_GetTicks();
|
||||
while (!SDL_TICKS_PASSED(SDL_GetTicks(), start + 1500)) {
|
||||
// Pump the UI loop while we wait
|
||||
// Pump the UI loop while we wait since we're not async
|
||||
SDL_Delay(5);
|
||||
QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
|
||||
QCoreApplication::sendPostedEvents();
|
||||
}
|
||||
|
||||
// Wait for any old session to finish cleanup
|
||||
s_ActiveSessionSemaphore.acquire();
|
||||
|
||||
// We're now active
|
||||
s_ActiveSession = this;
|
||||
|
||||
// Initialize the gamepad code with our preferences
|
||||
StreamingPreferences prefs;
|
||||
m_InputHandler = new SdlInputHandler(prefs, m_Computer,
|
||||
m_StreamConfig.width,
|
||||
m_StreamConfig.height);
|
||||
#endif
|
||||
|
||||
// The UI should have ensured the old game was already quit
|
||||
// if we decide to stream a different game.
|
||||
@@ -962,23 +996,11 @@ void Session::exec(int displayOriginX, int displayOriginY)
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Found host supported resolution: %dx%d",
|
||||
mode.width, mode.height);
|
||||
enableGameOptimizations = prefs.gameOptimizations;
|
||||
enableGameOptimizations = m_Preferences->gameOptimizations;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (prefs.fps > 60) {
|
||||
// Using SOPS with FPS values over 60 causes GFE to fall back
|
||||
// to 720p60. On previous GFE versions, we could avoid this by
|
||||
// forcing the FPS value to 60 when launching the stream, but
|
||||
// now on GFE 3.20.3 that seems to trigger some sort of
|
||||
// frame rate limiter that locks the game to 60 FPS.
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Disabling SOPS for %d FPS stream",
|
||||
prefs.fps);
|
||||
enableGameOptimizations = false;
|
||||
}
|
||||
|
||||
try {
|
||||
NvHTTP http(m_Computer->activeAddress, m_Computer->serverCert);
|
||||
if (m_Computer->currentGameId != 0) {
|
||||
@@ -987,23 +1009,15 @@ void Session::exec(int displayOriginX, int displayOriginY)
|
||||
else {
|
||||
http.launchApp(m_App.id, &m_StreamConfig,
|
||||
enableGameOptimizations,
|
||||
prefs.playAudioOnHost,
|
||||
m_Preferences->playAudioOnHost,
|
||||
m_InputHandler->getAttachedGamepadMask());
|
||||
}
|
||||
} catch (const GfeHttpResponseException& e) {
|
||||
delete m_InputHandler;
|
||||
m_InputHandler = nullptr;
|
||||
SDL_QuitSubSystem(SDL_INIT_VIDEO);
|
||||
emit displayLaunchError("GeForce Experience returned error: " + e.toQString());
|
||||
QThreadPool::globalInstance()->start(new DeferredSessionCleanupTask(this));
|
||||
return;
|
||||
return false;
|
||||
} catch (const QtNetworkReplyException& e) {
|
||||
delete m_InputHandler;
|
||||
m_InputHandler = nullptr;
|
||||
SDL_QuitSubSystem(SDL_INIT_VIDEO);
|
||||
emit displayLaunchError(e.toQString());
|
||||
QThreadPool::globalInstance()->start(new DeferredSessionCleanupTask(this));
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
QByteArray hostnameStr = m_Computer->activeAddress.toLatin1();
|
||||
@@ -1053,6 +1067,60 @@ void Session::exec(int displayOriginX, int displayOriginY)
|
||||
if (err != 0) {
|
||||
// We already displayed an error dialog in the stage failure
|
||||
// listener.
|
||||
return false;
|
||||
}
|
||||
|
||||
emit connectionStarted();
|
||||
return true;
|
||||
}
|
||||
|
||||
void Session::exec(int displayOriginX, int displayOriginY)
|
||||
{
|
||||
m_DisplayOriginX = displayOriginX;
|
||||
m_DisplayOriginY = displayOriginY;
|
||||
|
||||
// Complete initialization in this deferred context to avoid
|
||||
// calling expensive functions in the constructor (during the
|
||||
// process of loading the StreamSegue).
|
||||
//
|
||||
// NB: This initializes the SDL video subsystem, so it must be
|
||||
// called on the main thread.
|
||||
if (!initialize()) {
|
||||
emit sessionFinished(0);
|
||||
return;
|
||||
}
|
||||
|
||||
// Wait for any old session to finish cleanup
|
||||
s_ActiveSessionSemaphore.acquire();
|
||||
|
||||
// We're now active
|
||||
s_ActiveSession = this;
|
||||
|
||||
// Initialize the gamepad code with our preferences
|
||||
// NB: m_InputHandler must be initialize before starting the connection.
|
||||
m_InputHandler = new SdlInputHandler(*m_Preferences, m_Computer,
|
||||
m_StreamConfig.width,
|
||||
m_StreamConfig.height);
|
||||
|
||||
// Kick off the async connection thread while we sit here and pump the event loop
|
||||
AsyncConnectionStartThread asyncConnThread(this);
|
||||
#ifdef USE_ASYNC_CONNECT_THREAD
|
||||
asyncConnThread.start();
|
||||
while (!asyncConnThread.wait(10)) {
|
||||
QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
|
||||
QCoreApplication::sendPostedEvents();
|
||||
}
|
||||
#else
|
||||
asyncConnThread.run();
|
||||
#endif
|
||||
|
||||
// Pump the event loop one last time to ensure we pick up any events from
|
||||
// the thread that happened while it was in the final successful QThread::wait().
|
||||
QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
|
||||
QCoreApplication::sendPostedEvents();
|
||||
|
||||
// If the connection failed, clean up and abort the connection.
|
||||
if (!m_AsyncConnectionSuccess) {
|
||||
delete m_InputHandler;
|
||||
m_InputHandler = nullptr;
|
||||
SDL_QuitSubSystem(SDL_INIT_VIDEO);
|
||||
@@ -1060,10 +1128,6 @@ void Session::exec(int displayOriginX, int displayOriginY)
|
||||
return;
|
||||
}
|
||||
|
||||
// Pump the message loop to update the UI
|
||||
emit connectionStarted();
|
||||
QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
|
||||
|
||||
int x, y, width, height;
|
||||
getWindowDimensions(x, y, width, height);
|
||||
|
||||
@@ -1079,16 +1143,29 @@ void Session::exec(int displayOriginX, int displayOriginY)
|
||||
y,
|
||||
width,
|
||||
height,
|
||||
SDL_WINDOW_ALLOW_HIGHDPI);
|
||||
SDL_WINDOW_ALLOW_HIGHDPI | StreamUtils::getPlatformWindowFlags());
|
||||
if (!m_Window) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"SDL_CreateWindow() failed: %s",
|
||||
SDL_GetError());
|
||||
delete m_InputHandler;
|
||||
m_InputHandler = nullptr;
|
||||
SDL_QuitSubSystem(SDL_INIT_VIDEO);
|
||||
QThreadPool::globalInstance()->start(new DeferredSessionCleanupTask(this));
|
||||
return;
|
||||
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"SDL_CreateWindow() failed with platform flags: %s",
|
||||
SDL_GetError());
|
||||
|
||||
m_Window = SDL_CreateWindow("Moonlight",
|
||||
x,
|
||||
y,
|
||||
width,
|
||||
height,
|
||||
SDL_WINDOW_ALLOW_HIGHDPI);
|
||||
if (!m_Window) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"SDL_CreateWindow() failed: %s",
|
||||
SDL_GetError());
|
||||
|
||||
delete m_InputHandler;
|
||||
m_InputHandler = nullptr;
|
||||
SDL_QuitSubSystem(SDL_INIT_VIDEO);
|
||||
QThreadPool::globalInstance()->start(new DeferredSessionCleanupTask(this));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
m_InputHandler->setWindow(m_Window);
|
||||
@@ -1179,7 +1256,7 @@ void Session::exec(int displayOriginX, int displayOriginY)
|
||||
m_UnexpectedTermination = false;
|
||||
|
||||
// Start rich presence to indicate we're in game
|
||||
RichPresenceManager presence(prefs, m_App.name);
|
||||
RichPresenceManager presence(*m_Preferences, m_App.name);
|
||||
|
||||
// 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.
|
||||
@@ -1207,8 +1284,19 @@ void Session::exec(int displayOriginX, int displayOriginY)
|
||||
goto DispatchDeferredCleanup;
|
||||
|
||||
case SDL_USEREVENT:
|
||||
SDL_assert(event.user.code == SDL_CODE_FRAME_READY);
|
||||
m_VideoDecoder->renderFrameOnMainThread();
|
||||
switch (event.user.code) {
|
||||
case SDL_CODE_FRAME_READY:
|
||||
m_VideoDecoder->renderFrameOnMainThread();
|
||||
break;
|
||||
case SDL_CODE_HIDE_CURSOR:
|
||||
SDL_ShowCursor(SDL_DISABLE);
|
||||
break;
|
||||
case SDL_CODE_SHOW_CURSOR:
|
||||
SDL_ShowCursor(SDL_ENABLE);
|
||||
break;
|
||||
default:
|
||||
SDL_assert(false);
|
||||
}
|
||||
break;
|
||||
|
||||
case SDL_WINDOWEVENT:
|
||||
|
||||
@@ -16,6 +16,7 @@ class Session : public QObject
|
||||
|
||||
friend class SdlInputHandler;
|
||||
friend class DeferredSessionCleanupTask;
|
||||
friend class AsyncConnectionStartThread;
|
||||
|
||||
public:
|
||||
explicit Session(NvComputer* computer, NvApp& app, StreamingPreferences *preferences = nullptr);
|
||||
@@ -51,11 +52,13 @@ signals:
|
||||
|
||||
void quitStarting();
|
||||
|
||||
void sessionFinished();
|
||||
void sessionFinished(int portTestResult);
|
||||
|
||||
private:
|
||||
bool initialize();
|
||||
|
||||
bool startConnectionAsync();
|
||||
|
||||
bool validateLaunch(SDL_Window* testWindow);
|
||||
|
||||
void emitLaunchWarning(QString text);
|
||||
@@ -147,6 +150,9 @@ private:
|
||||
SDL_SpinLock m_InputHandlerLock;
|
||||
int m_MouseEmulationRefCount;
|
||||
|
||||
bool m_AsyncConnectionSuccess;
|
||||
int m_PortTestResults;
|
||||
|
||||
int m_ActiveVideoFormat;
|
||||
int m_ActiveVideoWidth;
|
||||
int m_ActiveVideoHeight;
|
||||
|
||||
@@ -6,6 +6,15 @@
|
||||
#include <ApplicationServices/ApplicationServices.h>
|
||||
#endif
|
||||
|
||||
Uint32 StreamUtils::getPlatformWindowFlags()
|
||||
{
|
||||
#ifdef Q_OS_DARWIN
|
||||
return SDL_WINDOW_METAL;
|
||||
#else
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
void StreamUtils::scaleSourceToDestinationSurface(SDL_Rect* src, SDL_Rect* dst)
|
||||
{
|
||||
int dstH = dst->w * src->h / src->w;
|
||||
|
||||
@@ -5,6 +5,9 @@
|
||||
class StreamUtils
|
||||
{
|
||||
public:
|
||||
static
|
||||
Uint32 getPlatformWindowFlags();
|
||||
|
||||
static
|
||||
void scaleSourceToDestinationSurface(SDL_Rect* src, SDL_Rect* dst);
|
||||
|
||||
|
||||
@@ -446,7 +446,7 @@ bool DXVA2Renderer::isDecoderBlacklisted()
|
||||
// https://www.intel.com/content/www/us/en/support/articles/000005654/graphics-drivers.html
|
||||
if (LOWORD(id.DriverVersion.LowPart) < 4836) {
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Intel driver version blacklisted for HEVC");
|
||||
"Detected buggy Intel GPU driver installed. Update your Intel GPU driver to enable HEVC!");
|
||||
result = (m_VideoFormat & VIDEO_FORMAT_MASK_H265) != 0;
|
||||
}
|
||||
else {
|
||||
|
||||
@@ -58,6 +58,8 @@ typedef EGLDisplay (EGLAPIENTRYP PFNEGLGETPLATFORMDISPLAYEXTPROC) (EGLenum platf
|
||||
SDL_LOG_CATEGORY_APPLICATION, \
|
||||
"EGLRenderer: " __VA_ARGS__)
|
||||
|
||||
SDL_Window* EGLRenderer::s_LastFailedWindow = nullptr;
|
||||
|
||||
EGLRenderer::EGLRenderer(IFFmpegRenderer *backendRenderer)
|
||||
:
|
||||
m_SwPixelFormat(AV_PIX_FMT_NONE),
|
||||
@@ -98,12 +100,13 @@ EGLRenderer::~EGLRenderer()
|
||||
SDL_assert(m_glDeleteVertexArraysOES != nullptr);
|
||||
m_glDeleteVertexArraysOES(1, &m_VAO);
|
||||
}
|
||||
if (m_DummyRenderer) {
|
||||
SDL_DestroyRenderer(m_DummyRenderer);
|
||||
}
|
||||
SDL_GL_DeleteContext(m_Context);
|
||||
}
|
||||
|
||||
if (m_DummyRenderer) {
|
||||
SDL_DestroyRenderer(m_DummyRenderer);
|
||||
}
|
||||
|
||||
// Reset the global properties back to what they were before
|
||||
SDL_SetHint(SDL_HINT_OPENGL_ES_DRIVER, "0");
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, m_OldContextProfileMask);
|
||||
@@ -275,6 +278,13 @@ bool EGLRenderer::initialize(PDECODER_PARAMETERS params)
|
||||
return false;
|
||||
}
|
||||
|
||||
// HACK: Work around bug where renderer will repeatedly fail with:
|
||||
// SDL_CreateRenderer() failed: Could not create GLES window surface
|
||||
if (m_Window == s_LastFailedWindow) {
|
||||
EGL_LOG(Error, "SDL_CreateRenderer() already failed on this window!");
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* Create a dummy renderer in order to craft an accelerated SDL Window.
|
||||
* Request opengl ES 3.0 context, otherwise it will SIGSEGV
|
||||
@@ -305,6 +315,7 @@ bool EGLRenderer::initialize(PDECODER_PARAMETERS params)
|
||||
|
||||
if (!(m_DummyRenderer = SDL_CreateRenderer(m_Window, renderIndex, SDL_RENDERER_ACCELERATED))) {
|
||||
EGL_LOG(Error, "SDL_CreateRenderer() failed: %s", SDL_GetError());
|
||||
s_LastFailedWindow = m_Window;
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -402,13 +413,8 @@ bool EGLRenderer::initialize(PDECODER_PARAMETERS params)
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
|
||||
if (params->enableVsync) {
|
||||
// Try to use adaptive VSYNC unless we're using frame pacing.
|
||||
// Frame pacing relies on us blocking in renderFrame() to
|
||||
// match the display refresh rate.
|
||||
if (params->enableFramePacing || SDL_GL_SetSwapInterval(-1)) {
|
||||
SDL_GL_SetSwapInterval(1);
|
||||
m_BlockingSwapBuffers = true;
|
||||
}
|
||||
SDL_GL_SetSwapInterval(1);
|
||||
m_BlockingSwapBuffers = true;
|
||||
} else {
|
||||
SDL_GL_SetSwapInterval(0);
|
||||
}
|
||||
|
||||
@@ -44,4 +44,8 @@ private:
|
||||
int m_OldContextMinorVersion;
|
||||
|
||||
SDL_Renderer *m_DummyRenderer;
|
||||
|
||||
// HACK: Work around bug where renderer will repeatedly fail with:
|
||||
// SDL_CreateRenderer() failed: Could not create GLES window surface
|
||||
static SDL_Window* s_LastFailedWindow;
|
||||
};
|
||||
|
||||
@@ -449,6 +449,12 @@ VAAPIRenderer::canExportEGL() {
|
||||
VASurfaceAttrib attrs[2];
|
||||
int attributeCount = 0;
|
||||
|
||||
if (qgetenv("VAAPI_FORCE_DIRECT") == "1") {
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Using direct rendering due to environment variable");
|
||||
return false;
|
||||
}
|
||||
|
||||
// FFmpeg handles setting these quirk flags for us
|
||||
if (!(vaDeviceContext->driver_quirks & AV_VAAPI_DRIVER_QUIRK_ATTRIB_MEMTYPE)) {
|
||||
attrs[attributeCount].type = VASurfaceAttribMemoryType;
|
||||
|
||||
@@ -282,7 +282,7 @@ public:
|
||||
CMSampleTimingInfo timingInfo = {
|
||||
.duration = kCMTimeInvalid,
|
||||
.decodeTimeStamp = kCMTimeInvalid,
|
||||
.presentationTimeStamp = CMTimeMake(mach_absolute_time(), 1000 * 1000 * 1000)
|
||||
.presentationTimeStamp = CMClockMakeHostTimeFromSystemUnits(mach_absolute_time())
|
||||
};
|
||||
|
||||
CMSampleBufferRef sampleBuffer;
|
||||
|
||||
@@ -194,20 +194,21 @@ void FFmpegVideoDecoder::reset()
|
||||
|
||||
bool FFmpegVideoDecoder::createFrontendRenderer(PDECODER_PARAMETERS params)
|
||||
{
|
||||
#ifdef HAVE_EGL
|
||||
if (m_BackendRenderer->canExportEGL()) {
|
||||
m_FrontendRenderer = new EGLRenderer(m_BackendRenderer);
|
||||
if (m_FrontendRenderer->initialize(params)) {
|
||||
return true;
|
||||
}
|
||||
delete m_FrontendRenderer;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (m_BackendRenderer->isDirectRenderingSupported()) {
|
||||
// The backend renderer can render to the display
|
||||
m_FrontendRenderer = m_BackendRenderer;
|
||||
}
|
||||
else {
|
||||
#ifdef HAVE_EGL
|
||||
if (m_BackendRenderer->canExportEGL()) {
|
||||
m_FrontendRenderer = new EGLRenderer(m_BackendRenderer);
|
||||
if (m_FrontendRenderer->initialize(params)) {
|
||||
return true;
|
||||
}
|
||||
delete m_FrontendRenderer;
|
||||
}
|
||||
#endif
|
||||
// The backend renderer cannot directly render to the display, so
|
||||
// we will create an SDL renderer to draw the frames.
|
||||
m_FrontendRenderer = new SdlRenderer();
|
||||
@@ -422,17 +423,45 @@ void FFmpegVideoDecoder::addVideoStats(VIDEO_STATS& src, VIDEO_STATS& dst)
|
||||
void FFmpegVideoDecoder::stringifyVideoStats(VIDEO_STATS& stats, char* output)
|
||||
{
|
||||
int offset = 0;
|
||||
const char* codecString;
|
||||
|
||||
// Start with an empty string
|
||||
output[offset] = 0;
|
||||
|
||||
switch (m_VideoFormat)
|
||||
{
|
||||
case VIDEO_FORMAT_H264:
|
||||
codecString = "H.264";
|
||||
break;
|
||||
|
||||
case VIDEO_FORMAT_H265:
|
||||
codecString = "HEVC";
|
||||
break;
|
||||
|
||||
case VIDEO_FORMAT_H265_MAIN10:
|
||||
codecString = "HEVC Main 10";
|
||||
break;
|
||||
|
||||
default:
|
||||
SDL_assert(false);
|
||||
codecString = "UNKNOWN";
|
||||
break;
|
||||
}
|
||||
|
||||
if (stats.receivedFps > 0) {
|
||||
if (m_VideoDecoderCtx != nullptr) {
|
||||
offset += sprintf(&output[offset],
|
||||
"Video stream: %dx%d %.2f FPS (Codec: %s)\n",
|
||||
m_VideoDecoderCtx->width,
|
||||
m_VideoDecoderCtx->height,
|
||||
stats.totalFps,
|
||||
codecString);
|
||||
}
|
||||
|
||||
offset += sprintf(&output[offset],
|
||||
"Estimated host PC frame rate: %.2f FPS\n"
|
||||
"Incoming frame rate from network: %.2f FPS\n"
|
||||
"Decoding frame rate: %.2f FPS\n"
|
||||
"Rendering frame rate: %.2f FPS\n",
|
||||
stats.totalFps,
|
||||
stats.receivedFps,
|
||||
stats.decodedFps,
|
||||
stats.renderedFps);
|
||||
@@ -855,6 +884,13 @@ int FFmpegVideoDecoder::submitDecodeUnit(PDECODE_UNIT du)
|
||||
m_Pkt.data = reinterpret_cast<uint8_t*>(m_DecodeBuffer.data());
|
||||
m_Pkt.size = offset;
|
||||
|
||||
if (du->frameType == FRAME_TYPE_IDR) {
|
||||
m_Pkt.flags = AV_PKT_FLAG_KEY;
|
||||
}
|
||||
else {
|
||||
m_Pkt.flags = 0;
|
||||
}
|
||||
|
||||
m_ActiveWndVideoStats.totalReassemblyTime += LiGetMillis() - du->receiveTimeMs;
|
||||
|
||||
Uint32 beforeDecode = SDL_GetTicks();
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
2.1.0
|
||||
2.2.0
|
||||
+6
-4
@@ -5,12 +5,12 @@ clone_depth: 1
|
||||
environment:
|
||||
matrix:
|
||||
- APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019
|
||||
QTDIR: C:\Qt\5.14\msvc2017_64
|
||||
QTDIR: C:\Qt\5.15\msvc2019_64
|
||||
ARCH: x64
|
||||
- APPVEYOR_BUILD_WORKER_IMAGE: macOS
|
||||
BUILD_TARGET: macos
|
||||
- APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019
|
||||
QTDIR: C:\Qt\5.14\msvc2017
|
||||
QTDIR: C:\Qt\5.15\msvc2019
|
||||
ARCH: x86
|
||||
- APPVEYOR_BUILD_WORKER_IMAGE: Ubuntu1604
|
||||
BUILD_TARGET: steamlink
|
||||
@@ -21,6 +21,8 @@ environment:
|
||||
install:
|
||||
- sh: '[ "$BUILD_TARGET" != macos ] || nvm use node'
|
||||
- sh: '[ "$BUILD_TARGET" != macos ] || npm install --global create-dmg'
|
||||
- sh: '[ "$BUILD_TARGET" != macos ] || brew install qt'
|
||||
- sh: '[ "$BUILD_TARGET" != macos ] || brew link --force qt'
|
||||
- sh: '[ "$BUILD_TARGET" != steamlink ] || sudo apt install -y libc6:i386 libstdc++6:i386'
|
||||
- sh: '[ "$BUILD_TARGET" != steamlink ] || git clone --depth=1 https://github.com/ValveSoftware/steamlink-sdk.git $HOME/steamlink-sdk'
|
||||
- sh: '[ "$BUILD_TARGET" != linux ] || sudo add-apt-repository ppa:beineri/opt-qt-5.14.2-xenial'
|
||||
@@ -28,9 +30,9 @@ install:
|
||||
- sh: '[ "$BUILD_TARGET" != linux ] || sudo apt install -y qt514base qt514quickcontrols2 qt514svg qt514wayland nasm libgbm-dev libdrm-dev libfreetype6-dev libasound2-dev libdbus-1-dev libegl1-mesa-dev libgl1-mesa-dev libgles2-mesa-dev libglu1-mesa-dev libibus-1.0-dev libpulse-dev libudev-dev libx11-dev libxcursor-dev libxext-dev libxi-dev libxinerama-dev libxkbcommon-dev libxrandr-dev libxss-dev libxt-dev libxv-dev libxxf86vm-dev wayland-protocols libopus-dev libvdpau-dev'
|
||||
- sh: 'if [[ "$BUILD_TARGET" = linux ]]; then export SDL2_VER=2.0.12 && wget https://www.libsdl.org/release/SDL2-$SDL2_VER.tar.gz && tar -xvf SDL2-$SDL2_VER.tar.gz && cd SDL2-$SDL2_VER && ./configure && make -j$(nproc) && sudo make install && cd ..; fi'
|
||||
- sh: 'if [[ "$BUILD_TARGET" = linux ]]; then export SDL2_TTF_VER=2.0.15 && wget https://www.libsdl.org/projects/SDL_ttf/release/SDL2_ttf-$SDL2_TTF_VER.tar.gz && tar -xvf SDL2_ttf-$SDL2_TTF_VER.tar.gz && cd SDL2_ttf-$SDL2_TTF_VER && ./configure && make -j$(nproc) && sudo make install && cd ..; fi'
|
||||
- sh: 'if [[ "$BUILD_TARGET" = linux ]]; then export LIBVA_VER=2.7.1 && wget https://github.com/intel/libva/releases/download/$LIBVA_VER/libva-$LIBVA_VER.tar.bz2 && tar -xvf libva-$LIBVA_VER.tar.bz2 && cd libva-$LIBVA_VER && ./configure && make -j$(nproc) && sudo make install && cd ..; fi'
|
||||
- sh: 'if [[ "$BUILD_TARGET" = linux ]]; then export LIBVA_VER=2.8.0 && wget https://github.com/intel/libva/releases/download/$LIBVA_VER/libva-$LIBVA_VER.tar.bz2 && tar -xvf libva-$LIBVA_VER.tar.bz2 && cd libva-$LIBVA_VER && ./configure && make -j$(nproc) && sudo make install && cd ..; fi'
|
||||
- sh: 'if [[ "$BUILD_TARGET" = linux ]]; then export NVHDR_VER=8.1.24.11 && wget https://github.com/FFmpeg/nv-codec-headers/releases/download/n$NVHDR_VER/nv-codec-headers-$NVHDR_VER.tar.gz && tar -xvf nv-codec-headers-$NVHDR_VER.tar.gz && cd nv-codec-headers-$NVHDR_VER && sudo make install && cd ..; fi'
|
||||
- sh: 'if [[ "$BUILD_TARGET" = linux ]]; then export FFMPEG_VER=4.2.2 && wget https://ffmpeg.org/releases/ffmpeg-$FFMPEG_VER.tar.bz2 && tar -xvf ffmpeg-$FFMPEG_VER.tar.bz2 && cd ffmpeg-$FFMPEG_VER && ./configure $FFMPEG_CONFIGURE_ARGS && make -j$(nproc) && sudo make install && sudo ldconfig && cd ..; fi'
|
||||
- sh: 'if [[ "$BUILD_TARGET" = linux ]]; then export FFMPEG_VER=4.3.1 && wget https://ffmpeg.org/releases/ffmpeg-$FFMPEG_VER.tar.bz2 && tar -xvf ffmpeg-$FFMPEG_VER.tar.bz2 && cd ffmpeg-$FFMPEG_VER && ./configure $FFMPEG_CONFIGURE_ARGS && make -j$(nproc) && sudo make install && sudo ldconfig && cd ..; fi'
|
||||
- sh: 'if [[ "$BUILD_TARGET" = linux ]]; then mkdir $HOME/bin && wget -O $HOME/bin/linuxdeployqt https://github.com/probonopd/linuxdeployqt/releases/download/continuous/linuxdeployqt-continuous-x86_64.AppImage && chmod a+x $HOME/bin/linuxdeployqt; fi'
|
||||
|
||||
before_build:
|
||||
|
||||
+3
-2
@@ -13,5 +13,6 @@ CONFIG(release, debug|release) {
|
||||
#CONFIG += sanitizer sanitize_address
|
||||
|
||||
# Enable ASan for Windows
|
||||
#QMAKE_CFLAGS += /fsanitize=address /MD
|
||||
#LIBS += clang_rt.asan_dynamic-i386.lib clang_rt.asan_dynamic_runtime_thunk-i386.lib
|
||||
#QMAKE_CFLAGS += -fsanitize=address
|
||||
#QMAKE_CXXFLAGS += -fsanitize=address
|
||||
#QMAKE_LFLAGS += -incremental:no -wholearchive:clang_rt.asan_dynamic-x86_64.lib -wholearchive:clang_rt.asan_dynamic_runtime_thunk-x86_64.lib
|
||||
|
||||
+5
-5
@@ -3,7 +3,7 @@
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>BuildMachineOSBuild</key>
|
||||
<string>19E287</string>
|
||||
<string>19G2021</string>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>English</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
@@ -27,17 +27,17 @@
|
||||
<key>DTCompiler</key>
|
||||
<string>com.apple.compilers.llvm.clang.1_0</string>
|
||||
<key>DTPlatformBuild</key>
|
||||
<string>11C505</string>
|
||||
<string>11E801a</string>
|
||||
<key>DTPlatformVersion</key>
|
||||
<string>GM</string>
|
||||
<key>DTSDKBuild</key>
|
||||
<string>19B90</string>
|
||||
<string>19G68</string>
|
||||
<key>DTSDKName</key>
|
||||
<string>macosx10.15</string>
|
||||
<key>DTXcode</key>
|
||||
<string>1130</string>
|
||||
<string>1170</string>
|
||||
<key>DTXcodeBuild</key>
|
||||
<string>11C505</string>
|
||||
<string>11E801a</string>
|
||||
<key>LSMinimumSystemVersion</key>
|
||||
<string>10.6</string>
|
||||
</dict>
|
||||
|
||||
+3
-3
@@ -10,7 +10,7 @@
|
||||
</data>
|
||||
<key>Resources/Info.plist</key>
|
||||
<data>
|
||||
xIjchI5RXIIqR9iBwzwdhWVeJmQ=
|
||||
k9vWJij3RA8s13h/WkHnqVv977U=
|
||||
</data>
|
||||
<key>Resources/LICENSE-bsd.txt</key>
|
||||
<data>
|
||||
@@ -57,11 +57,11 @@
|
||||
<dict>
|
||||
<key>hash</key>
|
||||
<data>
|
||||
xIjchI5RXIIqR9iBwzwdhWVeJmQ=
|
||||
k9vWJij3RA8s13h/WkHnqVv977U=
|
||||
</data>
|
||||
<key>hash2</key>
|
||||
<data>
|
||||
8Rqw3j5YT+AoPOKV4JE3RuVPPDb4jHtT4tpmG0phLYc=
|
||||
WME8EBqUAByTxhCypUOOYXVJiElcUNSkBXR1bxH3n5Q=
|
||||
</data>
|
||||
</dict>
|
||||
<key>Resources/LICENSE-bsd.txt</key>
|
||||
|
||||
BIN
Binary file not shown.
@@ -59,6 +59,7 @@
|
||||
#include "SDL_timer.h"
|
||||
#include "SDL_version.h"
|
||||
#include "SDL_video.h"
|
||||
#include "SDL_locale.h"
|
||||
|
||||
#include "begin_code.h"
|
||||
/* Set up for C function definitions, even when using C++ */
|
||||
|
||||
@@ -53,6 +53,8 @@ assert can have unique static variables associated with it.
|
||||
#define SDL_TriggerBreakpoint() __debugbreak()
|
||||
#elif ( (!defined(__NACL__)) && ((defined(__GNUC__) || defined(__clang__)) && (defined(__i386__) || defined(__x86_64__))) )
|
||||
#define SDL_TriggerBreakpoint() __asm__ __volatile__ ( "int $3\n\t" )
|
||||
#elif ( defined(__APPLE__) && defined(__arm64__) ) /* this might work on other ARM targets, but this is a known quantity... */
|
||||
#define SDL_TriggerBreakpoint() __asm__ __volatile__ ( "brk #22\n\t" )
|
||||
#elif defined(__386__) && defined(__WATCOMC__)
|
||||
#define SDL_TriggerBreakpoint() { _asm { int 0x03 } }
|
||||
#elif defined(HAVE_SIGNAL_H) && !defined(__WATCOMC__)
|
||||
|
||||
@@ -246,10 +246,33 @@ extern DECLSPEC size_t SDLCALL SDL_SIMDGetAlignment(void);
|
||||
* \return Pointer to newly-allocated block, NULL if out of memory.
|
||||
*
|
||||
* \sa SDL_SIMDAlignment
|
||||
* \sa SDL_SIMDRealloc
|
||||
* \sa SDL_SIMDFree
|
||||
*/
|
||||
extern DECLSPEC void * SDLCALL SDL_SIMDAlloc(const size_t len);
|
||||
|
||||
/**
|
||||
* \brief Reallocate memory obtained from SDL_SIMDAlloc
|
||||
*
|
||||
* It is not valid to use this function on a pointer from anything but
|
||||
* SDL_SIMDAlloc(). It can't be used on pointers from malloc, realloc,
|
||||
* SDL_malloc, memalign, new[], etc.
|
||||
*
|
||||
* \param mem The pointer obtained from SDL_SIMDAlloc. This function also
|
||||
* accepts NULL, at which point this function is the same as
|
||||
* calling SDL_realloc with a NULL pointer.
|
||||
* \param len The length, in bytes, of the block to allocated. The actual
|
||||
* allocated block might be larger due to padding, etc. Passing 0
|
||||
* will return a non-NULL pointer, assuming the system isn't out of
|
||||
* memory.
|
||||
* \return Pointer to newly-reallocated block, NULL if out of memory.
|
||||
*
|
||||
* \sa SDL_SIMDAlignment
|
||||
* \sa SDL_SIMDAlloc
|
||||
* \sa SDL_SIMDFree
|
||||
*/
|
||||
extern DECLSPEC void * SDLCALL SDL_SIMDRealloc(void *mem, size_t len);
|
||||
|
||||
/**
|
||||
* \brief Deallocate memory obtained from SDL_SIMDAlloc
|
||||
*
|
||||
@@ -260,6 +283,7 @@ extern DECLSPEC void * SDLCALL SDL_SIMDAlloc(const size_t len);
|
||||
* However, SDL_SIMDFree(NULL) is a legal no-op.
|
||||
*
|
||||
* \sa SDL_SIMDAlloc
|
||||
* \sa SDL_SIMDRealloc
|
||||
*/
|
||||
extern DECLSPEC void SDLCALL SDL_SIMDFree(void *ptr);
|
||||
|
||||
|
||||
@@ -85,6 +85,8 @@ typedef enum
|
||||
Called on Android in onResume()
|
||||
*/
|
||||
|
||||
SDL_LOCALECHANGED, /**< The user's locale preferences have changed. */
|
||||
|
||||
/* Display events */
|
||||
SDL_DISPLAYEVENT = 0x150, /**< Display state change */
|
||||
|
||||
|
||||
@@ -337,21 +337,12 @@ typedef struct _SDL_Haptic SDL_Haptic;
|
||||
#define SDL_HAPTIC_SPHERICAL 2
|
||||
|
||||
/**
|
||||
* \brief Uses first axis only.
|
||||
* For some device with only one axis (steering wheel,...),
|
||||
* SDL_HAPTIC_CARTESIAN does not work. SDL_HAPTIC_FIRST_AXIS can be used in
|
||||
* this case.
|
||||
* Using SDL_HAPTIC_FIRST_AXIS is equivalent to :
|
||||
* \code
|
||||
* SDL_HapticDirection direction;
|
||||
* direction.type = SDL_HAPTIC_CARTESIAN;
|
||||
* direction.dir[0] = 1;
|
||||
* direction.dir[1] = 0;
|
||||
* direction.dir[2] = 0;
|
||||
* \endcode
|
||||
* \brief Use this value to play an effect on the steering wheel axis. This
|
||||
* provides better compatibility across platforms and devices as SDL will guess
|
||||
* the correct axis.
|
||||
* \sa SDL_HapticDirection
|
||||
*/
|
||||
#define SDL_HAPTIC_FIRST_AXIS 3
|
||||
#define SDL_HAPTIC_STEERING_AXIS 3
|
||||
|
||||
/* @} *//* Direction encodings */
|
||||
|
||||
@@ -461,7 +452,7 @@ typedef struct _SDL_Haptic SDL_Haptic;
|
||||
* \sa SDL_HAPTIC_POLAR
|
||||
* \sa SDL_HAPTIC_CARTESIAN
|
||||
* \sa SDL_HAPTIC_SPHERICAL
|
||||
* \sa SDL_HAPTIC_FIRST_AXIS
|
||||
* \sa SDL_HAPTIC_STEERING_AXIS
|
||||
* \sa SDL_HapticEffect
|
||||
* \sa SDL_HapticNumAxes
|
||||
*/
|
||||
|
||||
@@ -369,7 +369,9 @@ extern "C" {
|
||||
#define SDL_HINT_MOUSE_TOUCH_EVENTS "SDL_MOUSE_TOUCH_EVENTS"
|
||||
|
||||
/**
|
||||
* \brief Minimize your SDL_Window if it loses key focus when in fullscreen mode. Defaults to true.
|
||||
* \brief Minimize your SDL_Window if it loses key focus when in fullscreen mode. Defaults to false.
|
||||
* \warning Before SDL 2.0.14, this defaulted to true! In 2.0.14, we're
|
||||
* seeing if "true" causes more problems than it solves in modern times.
|
||||
*
|
||||
*/
|
||||
#define SDL_HINT_VIDEO_MINIMIZE_ON_FOCUS_LOSS "SDL_VIDEO_MINIMIZE_ON_FOCUS_LOSS"
|
||||
@@ -694,6 +696,18 @@ extern "C" {
|
||||
*/
|
||||
#define SDL_HINT_JOYSTICK_RAWINPUT "SDL_JOYSTICK_RAWINPUT"
|
||||
|
||||
/**
|
||||
* \brief A variable controlling whether Linux joysticks adhere their HID-defined deadzones or return unfiltered values.
|
||||
* This is useful for Wine which implements its own deadzone handler if requested by games, also it enables xinput
|
||||
* games to receive unfiltered values as required from the API.
|
||||
*
|
||||
* This variable can be set to the following values:
|
||||
* "0" - Linux deadzones are not used by SDL
|
||||
* "1" - Linux deadzones are used by SDL (the default)
|
||||
*
|
||||
*/
|
||||
#define SDL_HINT_LINUX_JOYSTICK_DEADZONES "SDL_LINUX_JOYSTICK_DEADZONES"
|
||||
|
||||
/**
|
||||
* \brief If set to "0" then never set the top most bit on a SDL Window, even if the video mode expects it.
|
||||
* This is a debugging aid for developers and not expected to be used by end users. The default is "1"
|
||||
@@ -763,6 +777,19 @@ extern "C" {
|
||||
*/
|
||||
#define SDL_HINT_THREAD_STACK_SIZE "SDL_THREAD_STACK_SIZE"
|
||||
|
||||
/**
|
||||
* \brief A string specifying additional information to use with SDL_SetThreadPriority.
|
||||
*
|
||||
* By default SDL_SetThreadPriority will make appropriate system changes in order to
|
||||
* apply a thread priority. For example on systems using pthreads the scheduler policy
|
||||
* is changed automatically to a policy that works well with a given priority.
|
||||
* Code which has specific requirements can override SDL's default behavior with this hint.
|
||||
*
|
||||
* pthread hint values are "current", "other", "fifo" and "rr".
|
||||
* Currently no other platform hint values are defined but may be in the future.
|
||||
*/
|
||||
#define SDL_HINT_THREAD_PRIORITY_POLICY "SDL_THREAD_PRIORITY_POLICY"
|
||||
|
||||
/**
|
||||
* \brief If set to 1, then do not allow high-DPI windows. ("Retina" on Mac and iOS)
|
||||
*/
|
||||
@@ -1027,6 +1054,21 @@ extern "C" {
|
||||
*/
|
||||
#define SDL_HINT_EMSCRIPTEN_KEYBOARD_ELEMENT "SDL_EMSCRIPTEN_KEYBOARD_ELEMENT"
|
||||
|
||||
/**
|
||||
* \brief Disable giving back control to the browser automatically
|
||||
* when running with asyncify
|
||||
*
|
||||
* With -s ASYNCIFY, SDL2 calls emscripten_sleep during operations
|
||||
* such as refreshing the screen or polling events.
|
||||
*
|
||||
* This hint only applies to the emscripten platform
|
||||
*
|
||||
* The variable can be set to the following values:
|
||||
* "0" - Disable emscripten_sleep calls (if you give back browser control manually or use asyncify for other purposes)
|
||||
* "1" - Enable emscripten_sleep calls (the default)
|
||||
*/
|
||||
#define SDL_HINT_EMSCRIPTEN_ASYNCIFY "SDL_EMSCRIPTEN_ASYNCIFY"
|
||||
|
||||
/**
|
||||
* \brief Tell SDL not to catch the SIGINT or SIGTERM signals.
|
||||
*
|
||||
@@ -1292,7 +1334,7 @@ extern "C" {
|
||||
*/
|
||||
#define SDL_HINT_WAVE_FACT_CHUNK "SDL_WAVE_FACT_CHUNK"
|
||||
|
||||
/*
|
||||
/**
|
||||
* \brief Override for SDL_GetDisplayUsableBounds()
|
||||
*
|
||||
* If set, this hint will override the expected results for
|
||||
@@ -1306,6 +1348,63 @@ extern "C" {
|
||||
*/
|
||||
#define SDL_HINT_DISPLAY_USABLE_BOUNDS "SDL_DISPLAY_USABLE_BOUNDS"
|
||||
|
||||
/**
|
||||
* \brief Specify an application name for an audio device.
|
||||
*
|
||||
* Some audio backends (such as PulseAudio) allow you to describe your audio
|
||||
* stream. Among other things, this description might show up in a system
|
||||
* control panel that lets the user adjust the volume on specific audio
|
||||
* streams instead of using one giant master volume slider.
|
||||
*
|
||||
* This hints lets you transmit that information to the OS. The contents of
|
||||
* this hint are used while opening an audio device. You should use a string
|
||||
* that describes your program ("My Game 2: The Revenge")
|
||||
*
|
||||
* Setting this to "" or leaving it unset will have SDL use a reasonable
|
||||
* default: probably the application's name or "SDL Application" if SDL
|
||||
* doesn't have any better information.
|
||||
*
|
||||
* On targets where this is not supported, this hint does nothing.
|
||||
*/
|
||||
#define SDL_HINT_AUDIO_DEVICE_APP_NAME "SDL_AUDIO_DEVICE_APP_NAME"
|
||||
|
||||
/**
|
||||
* \brief Specify an application name for an audio device.
|
||||
*
|
||||
* Some audio backends (such as PulseAudio) allow you to describe your audio
|
||||
* stream. Among other things, this description might show up in a system
|
||||
* control panel that lets the user adjust the volume on specific audio
|
||||
* streams instead of using one giant master volume slider.
|
||||
*
|
||||
* This hints lets you transmit that information to the OS. The contents of
|
||||
* this hint are used while opening an audio device. You should use a string
|
||||
* that describes your what your program is playing ("audio stream" is
|
||||
* probably sufficient in many cases, but this could be useful for something
|
||||
* like "team chat" if you have a headset playing VoIP audio separately).
|
||||
*
|
||||
* Setting this to "" or leaving it unset will have SDL use a reasonable
|
||||
* default: "audio stream" or something similar.
|
||||
*
|
||||
* On targets where this is not supported, this hint does nothing.
|
||||
*/
|
||||
#define SDL_HINT_AUDIO_DEVICE_STREAM_NAME "SDL_AUDIO_DEVICE_STREAM_NAME"
|
||||
|
||||
|
||||
/**
|
||||
* \brief Override for SDL_GetPreferredLocales()
|
||||
*
|
||||
* If set, this will be favored over anything the OS might report for the
|
||||
* user's preferred locales. Changing this hint at runtime will not generate
|
||||
* a SDL_LOCALECHANGED event (but if you can change the hint, you can push
|
||||
* your own event, if you want).
|
||||
*
|
||||
* The format of this hint is a comma-separated list of language and locale,
|
||||
* combined with an underscore, as is a common format: "en_GB". Locale is
|
||||
* optional: "en". So you might have a list like this: "en_GB,jp,es_PT"
|
||||
*/
|
||||
#define SDL_HINT_PREFERRED_LOCALES "SDL_PREFERRED_LOCALES"
|
||||
|
||||
|
||||
/**
|
||||
* \brief An enumeration of hint priorities
|
||||
*/
|
||||
|
||||
@@ -336,13 +336,13 @@ typedef enum
|
||||
KMOD_NUM = 0x1000,
|
||||
KMOD_CAPS = 0x2000,
|
||||
KMOD_MODE = 0x4000,
|
||||
KMOD_RESERVED = 0x8000
|
||||
} SDL_Keymod;
|
||||
KMOD_RESERVED = 0x8000,
|
||||
|
||||
#define KMOD_CTRL (KMOD_LCTRL|KMOD_RCTRL)
|
||||
#define KMOD_SHIFT (KMOD_LSHIFT|KMOD_RSHIFT)
|
||||
#define KMOD_ALT (KMOD_LALT|KMOD_RALT)
|
||||
#define KMOD_GUI (KMOD_LGUI|KMOD_RGUI)
|
||||
KMOD_CTRL = KMOD_LCTRL | KMOD_RCTRL,
|
||||
KMOD_SHIFT = KMOD_LSHIFT | KMOD_RSHIFT,
|
||||
KMOD_ALT = KMOD_LALT | KMOD_RALT,
|
||||
KMOD_GUI = KMOD_LGUI | KMOD_RGUI,
|
||||
} SDL_Keymod;
|
||||
|
||||
#endif /* SDL_keycode_h_ */
|
||||
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2020 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
|
||||
/**
|
||||
* \file SDL_locale.h
|
||||
*
|
||||
* Include file for SDL locale services
|
||||
*/
|
||||
|
||||
#ifndef _SDL_locale_h
|
||||
#define _SDL_locale_h
|
||||
|
||||
#include "SDL_stdinc.h"
|
||||
#include "SDL_error.h"
|
||||
|
||||
#include "begin_code.h"
|
||||
/* Set up for C function definitions, even when using C++ */
|
||||
#ifdef __cplusplus
|
||||
/* *INDENT-OFF* */
|
||||
extern "C" {
|
||||
/* *INDENT-ON* */
|
||||
#endif
|
||||
|
||||
|
||||
typedef struct SDL_Locale
|
||||
{
|
||||
const char *language; /**< A language name, like "en" for English. */
|
||||
const char *country; /**< A country, like "US" for America. Can be NULL. */
|
||||
} SDL_Locale;
|
||||
|
||||
/**
|
||||
* \brief Report the user's preferred locale.
|
||||
*
|
||||
* This returns an array of SDL_Locale structs, the final item zeroed out.
|
||||
* When the caller is done with this array, it should call SDL_free() on
|
||||
* the returned value; all the memory involved is allocated in a single
|
||||
* block, so a single SDL_free() will suffice.
|
||||
*
|
||||
* Returned language strings are in the format xx, where 'xx' is an ISO-639
|
||||
* language specifier (such as "en" for English, "de" for German, etc).
|
||||
* Country strings are in the format YY, where "YY" is an ISO-3166 country
|
||||
* code (such as "US" for the United States, "CA" for Canada, etc). Country
|
||||
* might be NULL if there's no specific guidance on them (so you might get
|
||||
* { "en", "US" } for American English, but { "en", NULL } means "English
|
||||
* language, generically"). Language strings are never NULL, except to
|
||||
* terminate the array.
|
||||
*
|
||||
* Please note that not all of these strings are 2 characters; some are
|
||||
* three or more.
|
||||
*
|
||||
* The returned list of locales are in the order of the user's preference.
|
||||
* For example, a German citizen that is fluent in US English and knows
|
||||
* enough Japanese to navigate around Tokyo might have a list like:
|
||||
* { "de", "en_US", "jp", NULL }. Someone from England might prefer British
|
||||
* English (where "color" is spelled "colour", etc), but will settle for
|
||||
* anything like it: { "en_GB", "en", NULL }.
|
||||
*
|
||||
* This function returns NULL on error, including when the platform does not
|
||||
* supply this information at all.
|
||||
*
|
||||
* This might be a "slow" call that has to query the operating system. It's
|
||||
* best to ask for this once and save the results. However, this list can
|
||||
* change, usually because the user has changed a system preference outside
|
||||
* of your program; SDL will send an SDL_LOCALECHANGED event in this case,
|
||||
* if possible, and you can call this function again to get an updated copy
|
||||
* of preferred locales.
|
||||
*
|
||||
* \return array of locales, terminated with a locale with a NULL language
|
||||
* field. Will return NULL on error.
|
||||
*/
|
||||
extern DECLSPEC SDL_Locale * SDLCALL SDL_GetPreferredLocales(void);
|
||||
|
||||
/* Ends C function definitions when using C++ */
|
||||
#ifdef __cplusplus
|
||||
/* *INDENT-OFF* */
|
||||
}
|
||||
/* *INDENT-ON* */
|
||||
#endif
|
||||
#include "close_code.h"
|
||||
|
||||
#endif /* _SDL_locale_h */
|
||||
|
||||
/* vi: set ts=4 sw=4 expandtab: */
|
||||
@@ -188,18 +188,22 @@ typedef enum
|
||||
SDL_PIXELFORMAT_RGB332 =
|
||||
SDL_DEFINE_PIXELFORMAT(SDL_PIXELTYPE_PACKED8, SDL_PACKEDORDER_XRGB,
|
||||
SDL_PACKEDLAYOUT_332, 8, 1),
|
||||
SDL_PIXELFORMAT_RGB444 =
|
||||
SDL_PIXELFORMAT_XRGB4444 =
|
||||
SDL_DEFINE_PIXELFORMAT(SDL_PIXELTYPE_PACKED16, SDL_PACKEDORDER_XRGB,
|
||||
SDL_PACKEDLAYOUT_4444, 12, 2),
|
||||
SDL_PIXELFORMAT_BGR444 =
|
||||
SDL_PIXELFORMAT_RGB444 = SDL_PIXELFORMAT_XRGB4444,
|
||||
SDL_PIXELFORMAT_XBGR4444 =
|
||||
SDL_DEFINE_PIXELFORMAT(SDL_PIXELTYPE_PACKED16, SDL_PACKEDORDER_XBGR,
|
||||
SDL_PACKEDLAYOUT_4444, 12, 2),
|
||||
SDL_PIXELFORMAT_RGB555 =
|
||||
SDL_PIXELFORMAT_BGR444 = SDL_PIXELFORMAT_XBGR4444,
|
||||
SDL_PIXELFORMAT_XRGB1555 =
|
||||
SDL_DEFINE_PIXELFORMAT(SDL_PIXELTYPE_PACKED16, SDL_PACKEDORDER_XRGB,
|
||||
SDL_PACKEDLAYOUT_1555, 15, 2),
|
||||
SDL_PIXELFORMAT_BGR555 =
|
||||
SDL_PIXELFORMAT_RGB555 = SDL_PIXELFORMAT_XRGB1555,
|
||||
SDL_PIXELFORMAT_XBGR1555 =
|
||||
SDL_DEFINE_PIXELFORMAT(SDL_PIXELTYPE_PACKED16, SDL_PACKEDORDER_XBGR,
|
||||
SDL_PACKEDLAYOUT_1555, 15, 2),
|
||||
SDL_PIXELFORMAT_BGR555 = SDL_PIXELFORMAT_XBGR1555,
|
||||
SDL_PIXELFORMAT_ARGB4444 =
|
||||
SDL_DEFINE_PIXELFORMAT(SDL_PIXELTYPE_PACKED16, SDL_PACKEDORDER_ARGB,
|
||||
SDL_PACKEDLAYOUT_4444, 16, 2),
|
||||
@@ -236,15 +240,17 @@ typedef enum
|
||||
SDL_PIXELFORMAT_BGR24 =
|
||||
SDL_DEFINE_PIXELFORMAT(SDL_PIXELTYPE_ARRAYU8, SDL_ARRAYORDER_BGR, 0,
|
||||
24, 3),
|
||||
SDL_PIXELFORMAT_RGB888 =
|
||||
SDL_PIXELFORMAT_XRGB8888 =
|
||||
SDL_DEFINE_PIXELFORMAT(SDL_PIXELTYPE_PACKED32, SDL_PACKEDORDER_XRGB,
|
||||
SDL_PACKEDLAYOUT_8888, 24, 4),
|
||||
SDL_PIXELFORMAT_RGB888 = SDL_PIXELFORMAT_XRGB8888,
|
||||
SDL_PIXELFORMAT_RGBX8888 =
|
||||
SDL_DEFINE_PIXELFORMAT(SDL_PIXELTYPE_PACKED32, SDL_PACKEDORDER_RGBX,
|
||||
SDL_PACKEDLAYOUT_8888, 24, 4),
|
||||
SDL_PIXELFORMAT_BGR888 =
|
||||
SDL_PIXELFORMAT_XBGR8888 =
|
||||
SDL_DEFINE_PIXELFORMAT(SDL_PIXELTYPE_PACKED32, SDL_PACKEDORDER_XBGR,
|
||||
SDL_PACKEDLAYOUT_8888, 24, 4),
|
||||
SDL_PIXELFORMAT_BGR888 = SDL_PIXELFORMAT_XBGR8888,
|
||||
SDL_PIXELFORMAT_BGRX8888 =
|
||||
SDL_DEFINE_PIXELFORMAT(SDL_PIXELTYPE_PACKED32, SDL_PACKEDORDER_BGRX,
|
||||
SDL_PACKEDLAYOUT_8888, 24, 4),
|
||||
|
||||
@@ -96,15 +96,7 @@ typedef struct SDL_RWops
|
||||
#if defined(__ANDROID__)
|
||||
struct
|
||||
{
|
||||
void *fileNameRef;
|
||||
void *inputStreamRef;
|
||||
void *readableByteChannelRef;
|
||||
void *readMethod;
|
||||
void *assetFileDescriptorRef;
|
||||
long position;
|
||||
long size;
|
||||
long offset;
|
||||
int fd;
|
||||
void *asset;
|
||||
} androidio;
|
||||
#elif defined(__WIN32__)
|
||||
struct
|
||||
|
||||
@@ -577,6 +577,17 @@ extern DECLSPEC char *SDLCALL SDL_iconv_string(const char *tocode,
|
||||
/* force builds using Clang's static analysis tools to use literal C runtime
|
||||
here, since there are possibly tests that are ineffective otherwise. */
|
||||
#if defined(__clang_analyzer__) && !defined(SDL_DISABLE_ANALYZE_MACROS)
|
||||
|
||||
/* The analyzer knows about strlcpy even when the system doesn't provide it */
|
||||
#ifndef HAVE_STRLCPY
|
||||
size_t strlcpy(char* dst, const char* src, size_t size);
|
||||
#endif
|
||||
|
||||
/* The analyzer knows about strlcat even when the system doesn't provide it */
|
||||
#ifndef HAVE_STRLCAT
|
||||
size_t strlcat(char* dst, const char* src, size_t size);
|
||||
#endif
|
||||
|
||||
#define SDL_malloc malloc
|
||||
#define SDL_calloc calloc
|
||||
#define SDL_realloc realloc
|
||||
@@ -585,16 +596,23 @@ extern DECLSPEC char *SDLCALL SDL_iconv_string(const char *tocode,
|
||||
#define SDL_memcpy memcpy
|
||||
#define SDL_memmove memmove
|
||||
#define SDL_memcmp memcmp
|
||||
#define SDL_strlen strlen
|
||||
#define SDL_strlcpy strlcpy
|
||||
#define SDL_strlcat strlcat
|
||||
#define SDL_strlen strlen
|
||||
#define SDL_wcslen wcslen
|
||||
#define SDL_wcslcpy wcslcpy
|
||||
#define SDL_wcslcat wcslcat
|
||||
#define SDL_strdup strdup
|
||||
#define SDL_wcsdup wcsdup
|
||||
#define SDL_strchr strchr
|
||||
#define SDL_strrchr strrchr
|
||||
#define SDL_strstr strstr
|
||||
#define SDL_wcsstr wcsstr
|
||||
#define SDL_strtokr strtok_r
|
||||
#define SDL_strcmp strcmp
|
||||
#define SDL_wcscmp wcscmp
|
||||
#define SDL_strncmp strncmp
|
||||
#define SDL_wcsncmp wcsncmp
|
||||
#define SDL_strcasecmp strcasecmp
|
||||
#define SDL_strncasecmp strncasecmp
|
||||
#define SDL_sscanf sscanf
|
||||
|
||||
@@ -198,6 +198,14 @@ extern DECLSPEC int SDLCALL SDL_AndroidGetExternalStorageState(void);
|
||||
*/
|
||||
extern DECLSPEC const char * SDLCALL SDL_AndroidGetExternalStoragePath(void);
|
||||
|
||||
/**
|
||||
\brief Request permissions at runtime.
|
||||
|
||||
This blocks the calling thread until the permission is granted or
|
||||
denied. Returns SDL_TRUE if the permission was granted.
|
||||
*/
|
||||
extern DECLSPEC SDL_bool SDLCALL SDL_AndroidRequestPermission(const char *permission);
|
||||
|
||||
#endif /* __ANDROID__ */
|
||||
|
||||
/* Platform specific functions for WinRT */
|
||||
|
||||
@@ -54,6 +54,11 @@ typedef unsigned int SDL_TLSID;
|
||||
/**
|
||||
* The SDL thread priority.
|
||||
*
|
||||
* SDL will make system changes as necessary in order to apply the thread priority.
|
||||
* Code which attempts to control thread state related to priority should be aware
|
||||
* that calling SDL_SetThreadPriority may alter such state.
|
||||
* SDL_HINT_THREAD_PRIORITY_POLICY can be used to control aspects of this behavior.
|
||||
*
|
||||
* \note On many systems you require special privileges to set high or time critical priority.
|
||||
*/
|
||||
typedef enum {
|
||||
|
||||
@@ -204,10 +204,9 @@ extern DECLSPEC void SDLCALL SDL_Vulkan_UnloadLibrary(void);
|
||||
*
|
||||
* \sa SDL_Vulkan_CreateSurface()
|
||||
*/
|
||||
extern DECLSPEC SDL_bool SDLCALL SDL_Vulkan_GetInstanceExtensions(
|
||||
SDL_Window *window,
|
||||
unsigned int *pCount,
|
||||
const char **pNames);
|
||||
extern DECLSPEC SDL_bool SDLCALL SDL_Vulkan_GetInstanceExtensions(SDL_Window *window,
|
||||
unsigned int *pCount,
|
||||
const char **pNames);
|
||||
|
||||
/**
|
||||
* \brief Create a Vulkan rendering surface for a window.
|
||||
@@ -238,10 +237,9 @@ extern DECLSPEC SDL_bool SDLCALL SDL_Vulkan_GetInstanceExtensions(
|
||||
*
|
||||
* \sa SDL_Vulkan_GetInstanceExtensions()
|
||||
*/
|
||||
extern DECLSPEC SDL_bool SDLCALL SDL_Vulkan_CreateSurface(
|
||||
SDL_Window *window,
|
||||
VkInstance instance,
|
||||
VkSurfaceKHR* surface);
|
||||
extern DECLSPEC SDL_bool SDLCALL SDL_Vulkan_CreateSurface(SDL_Window *window,
|
||||
VkInstance instance,
|
||||
VkSurfaceKHR* surface);
|
||||
|
||||
/**
|
||||
* \brief Get the size of a window's underlying drawable in pixels (for use
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>BuildMachineOSBuild</key>
|
||||
<string>19E287</string>
|
||||
<string>19G2021</string>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>English</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
@@ -31,17 +31,17 @@
|
||||
<key>DTCompiler</key>
|
||||
<string>com.apple.compilers.llvm.clang.1_0</string>
|
||||
<key>DTPlatformBuild</key>
|
||||
<string>11C505</string>
|
||||
<string>11E801a</string>
|
||||
<key>DTPlatformVersion</key>
|
||||
<string>GM</string>
|
||||
<key>DTSDKBuild</key>
|
||||
<string>19B90</string>
|
||||
<string>19G68</string>
|
||||
<key>DTSDKName</key>
|
||||
<string>macosx10.15</string>
|
||||
<key>DTXcode</key>
|
||||
<string>1130</string>
|
||||
<string>1170</string>
|
||||
<key>DTXcodeBuild</key>
|
||||
<string>11C505</string>
|
||||
<string>11E801a</string>
|
||||
<key>LSMinimumSystemVersion</key>
|
||||
<string>10.6</string>
|
||||
</dict>
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -6,7 +6,7 @@
|
||||
<dict>
|
||||
<key>Resources/Info.plist</key>
|
||||
<data>
|
||||
kQWsUbAzKB6cDjSyOGRQOM++OWk=
|
||||
sh0oOLkdzcvMFVtkzp3NXS7VQh4=
|
||||
</data>
|
||||
<key>Resources/License.txt</key>
|
||||
<data>
|
||||
@@ -18,7 +18,7 @@
|
||||
</data>
|
||||
<key>Resources/default.metallib</key>
|
||||
<data>
|
||||
dF9gKJnv0WdQs57PRAzLo+EyGAA=
|
||||
awST523PmC36+lehlmfUr/pwZko=
|
||||
</data>
|
||||
</dict>
|
||||
<key>files2</key>
|
||||
@@ -27,31 +27,31 @@
|
||||
<dict>
|
||||
<key>cdhash</key>
|
||||
<data>
|
||||
p2lcvvUyS6Ahs1nLqMaQaVfaN6Y=
|
||||
pFEaabK4ZTzSRMngHXqK2k/a6tE=
|
||||
</data>
|
||||
<key>requirement</key>
|
||||
<string>cdhash H"51df5d325062884d3b15aea099188c86a0e1e261" or cdhash H"a7695cbef5324ba021b359cba8c6906957da37a6"</string>
|
||||
<string>cdhash H"41e662828193db1f141e133758d4455d73697c3d" or cdhash H"a4511a69b2b8653cd244c9e01d7a8ada4fdaead1"</string>
|
||||
</dict>
|
||||
<key>Headers/SDL.h</key>
|
||||
<dict>
|
||||
<key>hash</key>
|
||||
<data>
|
||||
y6Zw1zugKvy7YINfHUZioLU3h7c=
|
||||
o3KVp4++KuUyoa+pJDOXTKG80xY=
|
||||
</data>
|
||||
<key>hash2</key>
|
||||
<data>
|
||||
GRE94BY3eDOjtR9EDg6HtLEJAZjYaNV+dq4yfaEVj+U=
|
||||
CLsLV07G2/+c6GGLTVZh91FNYojAHOsuglk/oCKmlso=
|
||||
</data>
|
||||
</dict>
|
||||
<key>Headers/SDL_assert.h</key>
|
||||
<dict>
|
||||
<key>hash</key>
|
||||
<data>
|
||||
aeaMLHO4nr+PB14hBzqp6ZOQMqU=
|
||||
FwT1P3mJzGKC9r+NdzApvnBKWB0=
|
||||
</data>
|
||||
<key>hash2</key>
|
||||
<data>
|
||||
BvLXUx+Vfr2uygauOVYH8TxUkdOjRrpuC51187UhoCU=
|
||||
P97E6oE7Pm/2Rd/sQI2lfnboN3OaLWfA4fzXc4lHSW0=
|
||||
</data>
|
||||
</dict>
|
||||
<key>Headers/SDL_atomic.h</key>
|
||||
@@ -146,11 +146,11 @@
|
||||
<dict>
|
||||
<key>hash</key>
|
||||
<data>
|
||||
/DTS3Ju22TmH5Qrr4Sy4TfDdlcc=
|
||||
YjRdjce9KUBEQg0r9+V22aY6/tU=
|
||||
</data>
|
||||
<key>hash2</key>
|
||||
<data>
|
||||
pPVS8mDMD17ThwISdh+NEXGOqQkOOClFoU0ylYgDNSM=
|
||||
rOaeQwxwi97aliYs+g6rDQlSNO8BjTDD9tnx/2CcyjI=
|
||||
</data>
|
||||
</dict>
|
||||
<key>Headers/SDL_endian.h</key>
|
||||
@@ -179,11 +179,11 @@
|
||||
<dict>
|
||||
<key>hash</key>
|
||||
<data>
|
||||
K/UpbzGqq8vPXCNo//37Qod99bU=
|
||||
06e1jUIsAc9O803iXrh2jmdbXuM=
|
||||
</data>
|
||||
<key>hash2</key>
|
||||
<data>
|
||||
ndL6QYSkZleJN4Va0neG0C1cytQi6sN2YRBZNqg0KGU=
|
||||
6pGmPxZapBntgteeWW4BRZnhLf1R4CvlFsk60Xd3BPs=
|
||||
</data>
|
||||
</dict>
|
||||
<key>Headers/SDL_filesystem.h</key>
|
||||
@@ -223,22 +223,22 @@
|
||||
<dict>
|
||||
<key>hash</key>
|
||||
<data>
|
||||
A5VM8GOD6pgO+5umTAfGePUVpKE=
|
||||
M0Oo9dh3zLva6qpGJhBbgAxAIuY=
|
||||
</data>
|
||||
<key>hash2</key>
|
||||
<data>
|
||||
GNOzk3bli3Um2gMNnpq6O6rl97hq0dZfj5BIgDSfl9A=
|
||||
Nef5r8Bidx1FeTffn9+uzNZMlwxu1J37V4LOzOibxx4=
|
||||
</data>
|
||||
</dict>
|
||||
<key>Headers/SDL_hints.h</key>
|
||||
<dict>
|
||||
<key>hash</key>
|
||||
<data>
|
||||
2fcgomKzlDXR0nG2BTecGYNI2fw=
|
||||
/SzqZQDEmBA9aPLBuEQJLeHNGv4=
|
||||
</data>
|
||||
<key>hash2</key>
|
||||
<data>
|
||||
c8vMBfNzwpL6I0l2aghiLSNE3c/ZmOnQCIJCucACo7k=
|
||||
17+Nv9lXJBOkimhfhtzZJcBqVKWf8z5tK/KQrvvU9xE=
|
||||
</data>
|
||||
</dict>
|
||||
<key>Headers/SDL_joystick.h</key>
|
||||
@@ -267,11 +267,11 @@
|
||||
<dict>
|
||||
<key>hash</key>
|
||||
<data>
|
||||
gMeCtdre9A0i6YsiBcA9UYyFU2M=
|
||||
Fwnbxg/6DtHaQufmjH9Sx/zbF+A=
|
||||
</data>
|
||||
<key>hash2</key>
|
||||
<data>
|
||||
7q8a96GCxzmM/IeuC5Qe6HdURJ/tkw7ZE+w1EB05MMU=
|
||||
m69IH60aX73QRefpJOur+rWzKRZJH9RF7P4LNUxti+g=
|
||||
</data>
|
||||
</dict>
|
||||
<key>Headers/SDL_loadso.h</key>
|
||||
@@ -285,6 +285,17 @@
|
||||
bpcu/Ms5CE5EHVV1zXIfWghw8JM8qB2114xfsIUBPeE=
|
||||
</data>
|
||||
</dict>
|
||||
<key>Headers/SDL_locale.h</key>
|
||||
<dict>
|
||||
<key>hash</key>
|
||||
<data>
|
||||
JCF6SSXRcv6uAPICgwmTq+WsVZs=
|
||||
</data>
|
||||
<key>hash2</key>
|
||||
<data>
|
||||
SYFN/L2S2pKTNZQe4dGUKTPTFXbqXZlZ3Vyl4y5wZ8s=
|
||||
</data>
|
||||
</dict>
|
||||
<key>Headers/SDL_log.h</key>
|
||||
<dict>
|
||||
<key>hash</key>
|
||||
@@ -454,11 +465,11 @@
|
||||
<dict>
|
||||
<key>hash</key>
|
||||
<data>
|
||||
U6CRAlpSfGNDzBGXdbi4oftVkwU=
|
||||
dk4R8c81l+WVpQ9ueWDS2SGuXkA=
|
||||
</data>
|
||||
<key>hash2</key>
|
||||
<data>
|
||||
DgjII2f8X5emc/fKsW0hur9MGpTs8qpaTHe/NC7WdGE=
|
||||
AQ9Th5IdVLO2XfkpMUuZAqxtYQXaQiiiHQVjTJyRq2g=
|
||||
</data>
|
||||
</dict>
|
||||
<key>Headers/SDL_platform.h</key>
|
||||
@@ -531,11 +542,11 @@
|
||||
<dict>
|
||||
<key>hash</key>
|
||||
<data>
|
||||
DLm3FArKEgTtJ69RUmMmUvF0NsI=
|
||||
FWcGz9/xlaBkrR1vdvYvDibFPhg=
|
||||
</data>
|
||||
<key>hash2</key>
|
||||
<data>
|
||||
FIL6cT/Y+0AXFs3GF9EJ3m2pbkmMJxT/I+LuZP4/JrU=
|
||||
OWI95ONgZvdn/FBJfMHV1BQWKQjx5LBN7MYuXwAadgg=
|
||||
</data>
|
||||
</dict>
|
||||
<key>Headers/SDL_scancode.h</key>
|
||||
@@ -575,11 +586,11 @@
|
||||
<dict>
|
||||
<key>hash</key>
|
||||
<data>
|
||||
smZhl9uPKfaFlYEpDbqiYDUGseY=
|
||||
q9VYwFS+f9MaSiVwltDDrBpP8+o=
|
||||
</data>
|
||||
<key>hash2</key>
|
||||
<data>
|
||||
h9ZnfoqEmhF2HFFwjE00RNDngk+Lg0Fth+XPTKM/OdA=
|
||||
hfUwgCUrzO8oRZglsZlgHLqCVGo4tlorFxvRjh+d/A8=
|
||||
</data>
|
||||
</dict>
|
||||
<key>Headers/SDL_surface.h</key>
|
||||
@@ -597,11 +608,11 @@
|
||||
<dict>
|
||||
<key>hash</key>
|
||||
<data>
|
||||
3tHnp+2bdRcZ3HH7EKLtOcbMlWQ=
|
||||
iiUlVUMHJLVYtZYy8fesmrBw71U=
|
||||
</data>
|
||||
<key>hash2</key>
|
||||
<data>
|
||||
ZwR9c/Bto10ts3gyJZgBeZEDkcfYBxbXqZYho1rDulI=
|
||||
SmI4OODh0gdcy0WduqkDD+AGZI66vpEOgPEXc7JY3sM=
|
||||
</data>
|
||||
</dict>
|
||||
<key>Headers/SDL_syswm.h</key>
|
||||
@@ -619,11 +630,11 @@
|
||||
<dict>
|
||||
<key>hash</key>
|
||||
<data>
|
||||
3tGB/eQF+z1hhlWNiw4xuJ6c0NA=
|
||||
bN9PvUNyNE5PSLQzjdF4b38bzRY=
|
||||
</data>
|
||||
<key>hash2</key>
|
||||
<data>
|
||||
7WhLanB12mw0SlTjQFEZzDejcHmHtq/wBtc7oujsSSQ=
|
||||
MymOftu/SUBMl4mrBetXv06hTmexfmEwWFuvGoMdmT8=
|
||||
</data>
|
||||
</dict>
|
||||
<key>Headers/SDL_timer.h</key>
|
||||
@@ -685,11 +696,11 @@
|
||||
<dict>
|
||||
<key>hash</key>
|
||||
<data>
|
||||
0R0iBIIZHeiqfvyj+3+sbtepfwo=
|
||||
Hd6qVU8OT67v88A0rMVhZpceGQg=
|
||||
</data>
|
||||
<key>hash2</key>
|
||||
<data>
|
||||
ZUl2Z6rj4rI2+mitJQB2d+4hTIuTIzVf0fr7KM9hgKw=
|
||||
rD48rqfdCs8UEGgpQilcPwZo50sH0NXKds0uYKXnkc8=
|
||||
</data>
|
||||
</dict>
|
||||
<key>Headers/begin_code.h</key>
|
||||
@@ -718,11 +729,11 @@
|
||||
<dict>
|
||||
<key>hash</key>
|
||||
<data>
|
||||
kQWsUbAzKB6cDjSyOGRQOM++OWk=
|
||||
sh0oOLkdzcvMFVtkzp3NXS7VQh4=
|
||||
</data>
|
||||
<key>hash2</key>
|
||||
<data>
|
||||
z/sm5HUSHetKQYB64C+hQcCeWrT1zzAW5tX83K65RQ0=
|
||||
RWZqKrfBsPmghlDP7CT9Jf6NcUep+mHS5mvZnMIuieY=
|
||||
</data>
|
||||
</dict>
|
||||
<key>Resources/License.txt</key>
|
||||
@@ -751,11 +762,11 @@
|
||||
<dict>
|
||||
<key>hash</key>
|
||||
<data>
|
||||
dF9gKJnv0WdQs57PRAzLo+EyGAA=
|
||||
awST523PmC36+lehlmfUr/pwZko=
|
||||
</data>
|
||||
<key>hash2</key>
|
||||
<data>
|
||||
A5lFX3lFIAtu4Lxgjh7TXdIVDe0n6G1iPofA+3H+xMA=
|
||||
+Foetj5EZqEAqjfkJcaUPc5XEgo/kcNG4bP0vzWuVHA=
|
||||
</data>
|
||||
</dict>
|
||||
</dict>
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Submodule moonlight-common-c/moonlight-common-c updated: c8faf3539b...dbedf938d4
@@ -47,6 +47,7 @@ SOURCES += \
|
||||
$$COMMON_C_DIR/src/AudioStream.c \
|
||||
$$COMMON_C_DIR/src/ByteBuffer.c \
|
||||
$$COMMON_C_DIR/src/Connection.c \
|
||||
$$COMMON_C_DIR/src/ConnectionTester.c \
|
||||
$$COMMON_C_DIR/src/ControlStream.c \
|
||||
$$COMMON_C_DIR/src/FakeCallbacks.c \
|
||||
$$COMMON_C_DIR/src/InputStream.c \
|
||||
|
||||
+1
-1
@@ -11,7 +11,7 @@ win32:!winrt {
|
||||
SUBDIRS += AntiHooking
|
||||
app.depends += AntiHooking
|
||||
}
|
||||
!winrt {
|
||||
!winrt:win32|macx {
|
||||
SUBDIRS += soundio
|
||||
app.depends += soundio
|
||||
}
|
||||
|
||||
@@ -23,6 +23,18 @@
|
||||
<MajorUpgrade DowngradeErrorMessage="A newer version of [ProductName] is already installed." Schedule="afterInstallInitialize" />
|
||||
<MediaTemplate CompressionLevel="high" EmbedCab="yes" />
|
||||
|
||||
<Property Id="MPSSVC_START">
|
||||
<RegistrySearch Id="MpsSvcStart"
|
||||
Root="HKLM"
|
||||
Key="System\CurrentControlSet\Services\MpsSvc"
|
||||
Name="Start"
|
||||
Type="raw" />
|
||||
</Property>
|
||||
|
||||
<Condition Message="Setup cannot proceed because the Windows Firewall service has been improperly disabled or stopped. You must start the Windows Firewall service (MpsSvc) to continue. If you would like to disable Windows Firewall properly, use the Windows Firewall options in Control Panel.">
|
||||
<![CDATA[Installed OR MPSSVC_START="#2"]]>
|
||||
</Condition>
|
||||
|
||||
<?if $(var.Platform) = x64 ?>
|
||||
<?define PlatformProgramFilesFolder = "ProgramFiles64Folder" ?>
|
||||
<?else ?>
|
||||
|
||||
Reference in New Issue
Block a user